# Path: catsim/cat.py
# def dodd(theta: float, items: numpy.ndarray, correct: bool) -> float:
# def bias(
#     actual: Union[List[float], numpy.ndarray],
#     predicted: Union[List[float], numpy.ndarray],
# ) -> float:
# def mse(
#     actual: Union[List[float], numpy.ndarray],
#     predicted: Union[List[float], numpy.ndarray],
# ) -> float:
# def rmse(
#     actual: Union[List[float], numpy.ndarray],
#     predicted: Union[List[float], numpy.ndarray],
# ) -> float:
# def overlap_rate(usages: numpy.ndarray, test_size: int) -> float:
# def generate_item_bank(n: int, itemtype: str = "4PL", corr: float = 0) -> numpy.ndarray:
# def random_response_vector(size: int) -> list:
# 
# Path: catsim/irt.py
# def icc(theta: float, a: float, b: float, c: float = 0, d: float = 1) -> float:
# def _split_params(items: numpy.ndarray):
# def detect_model(items: numpy.ndarray) -> int:
# def icc_hpc(theta: float, items: numpy.ndarray) -> numpy.ndarray:
# def inf_hpc(theta: float, items: numpy.ndarray):
# def inf(theta: float, a: float, b: float, c: float = 0, d: float = 1) -> float:
# def test_info(theta: float, items: numpy.ndarray) -> float:
# def var(theta: float, items: numpy.ndarray) -> float:
# def see(theta: float, items: numpy.ndarray) -> float:
# def reliability(theta: float, items: numpy.ndarray):
# def max_info(a: float = 1, b: float = 0, c: float = 0, d: float = 1) -> float:
# def max_info_hpc(items: numpy.ndarray):
# def log_likelihood(
#     est_theta: float, response_vector: List[bool], administered_items: numpy.ndarray
# ) -> float:
# def negative_log_likelihood(est_theta: float, *args) -> float:
# def normalize_item_bank(items: numpy.ndarray) -> numpy.ndarray:
# def validate_item_bank(items: numpy.ndarray, raise_err: bool = False):
# 
# Path: catsim/simulation.py
# class Estimator(Simulable, metaclass=ABCMeta):
#     """Base class for ability estimators"""
# 
#     def __init__(self, verbose: bool = False):
#         super().__init__()
#         self._calls = 0
#         self._evaluations = 0
#         self._verbose = verbose
# 
#     @abstractmethod
#     def estimate(self, index: int) -> float:
#         """Returns the theta value that minimizes the negative log-likelihood function, given the current state of the
#          test for the given examinee.
# 
#         :param index: index of the current examinee in the simulator
#         :returns: the current :math:`\\hat\\theta`
#         """
#         pass
# 
#     @property
#     def calls(self):
#         """How many times the estimator has been called to maximize/minimize the log-likelihood function
# 
#         :returns: number of times the estimator has been called to maximize/minimize the log-likelihood function"""
#         return self._calls
# 
#     @property
#     def evaluations(self):
#         """Total number of times the estimator has evaluated the log-likelihood function during its existence
# 
#         :returns: number of function evaluations"""
#         return self._evaluations
# 
#     @property
#     def avg_evaluations(self):
#         """Average number of function evaluations for all tests the estimator has been used
# 
#         :returns: average number of function evaluations"""
#         return self._evaluations / self._calls


from abc import ABC, abstractmethod
from typing import List
from scipy.optimize import minimize_scalar
from catsim import cat, irt
from catsim.simulation import Estimator
import numpy
        :type lower_bound: float
        :param response_vector: the responses given to the answered items
        :type response_vector: List[bool]
        :param item_params: the parameter matrix of the answered items
        :type item_params: numpy.ndarray
        :return: the estimated ability
        :rtype: float
        """
        c = b + (a - b) / NumericalSearchEstimator.golden_ratio
        d = a + (b - a) / NumericalSearchEstimator.golden_ratio

        left_side_ll = irt.log_likelihood(c, response_vector, item_params)
        right_side_ll = irt.log_likelihood(d, response_vector, item_params)

        while abs(b - a) > self._epsilon:
            self._evaluations += 1

            if left_side_ll >= right_side_ll:
                b = d
                d = c
                c = b + (a - b) / NumericalSearchEstimator.golden_ratio

                right_side_ll = left_side_ll
                left_side_ll = irt.log_likelihood(c, response_vector, item_params)
            else:
                a = c
                c = d
                d = a + (b - a) / NumericalSearchEstimator.golden_ratio

                left_side_ll = right_side_ll

# Path: catsim/cat.py
# def dodd(theta: float, items: numpy.ndarray, correct: bool) -> float:
# def bias(
#     actual: Union[List[float], numpy.ndarray],
#     predicted: Union[List[float], numpy.ndarray],
# ) -> float:
# def mse(
#     actual: Union[List[float], numpy.ndarray],
#     predicted: Union[List[float], numpy.ndarray],
# ) -> float:
# def rmse(
#     actual: Union[List[float], numpy.ndarray],
#     predicted: Union[List[float], numpy.ndarray],
# ) -> float:
# def overlap_rate(usages: numpy.ndarray, test_size: int) -> float:
# def generate_item_bank(n: int, itemtype: str = "4PL", corr: float = 0) -> numpy.ndarray:
# def random_response_vector(size: int) -> list:
# 
# Path: catsim/irt.py
# def icc(theta: float, a: float, b: float, c: float = 0, d: float = 1) -> float:
# def _split_params(items: numpy.ndarray):
# def detect_model(items: numpy.ndarray) -> int:
# def icc_hpc(theta: float, items: numpy.ndarray) -> numpy.ndarray:
# def inf_hpc(theta: float, items: numpy.ndarray):
# def inf(theta: float, a: float, b: float, c: float = 0, d: float = 1) -> float:
# def test_info(theta: float, items: numpy.ndarray) -> float:
# def var(theta: float, items: numpy.ndarray) -> float:
# def see(theta: float, items: numpy.ndarray) -> float:
# def reliability(theta: float, items: numpy.ndarray):
# def max_info(a: float = 1, b: float = 0, c: float = 0, d: float = 1) -> float:
# def max_info_hpc(items: numpy.ndarray):
# def log_likelihood(
#     est_theta: float, response_vector: List[bool], administered_items: numpy.ndarray
# ) -> float:
# def negative_log_likelihood(est_theta: float, *args) -> float:
# def normalize_item_bank(items: numpy.ndarray) -> numpy.ndarray:
# def validate_item_bank(items: numpy.ndarray, raise_err: bool = False):
# 
# Path: catsim/simulation.py
# class Estimator(Simulable, metaclass=ABCMeta):
#     """Base class for ability estimators"""
# 
#     def __init__(self, verbose: bool = False):
#         super().__init__()
#         self._calls = 0
#         self._evaluations = 0
#         self._verbose = verbose
# 
#     @abstractmethod
#     def estimate(self, index: int) -> float:
#         """Returns the theta value that minimizes the negative log-likelihood function, given the current state of the
#          test for the given examinee.
# 
#         :param index: index of the current examinee in the simulator
#         :returns: the current :math:`\\hat\\theta`
#         """
#         pass
# 
#     @property
#     def calls(self):
#         """How many times the estimator has been called to maximize/minimize the log-likelihood function
# 
#         :returns: number of times the estimator has been called to maximize/minimize the log-likelihood function"""
#         return self._calls
# 
#     @property
#     def evaluations(self):
#         """Total number of times the estimator has evaluated the log-likelihood function during its existence
# 
#         :returns: number of function evaluations"""
#         return self._evaluations
# 
#     @property
#     def avg_evaluations(self):
#         """Average number of function evaluations for all tests the estimator has been used
# 
#         :returns: average number of function evaluations"""
#         return self._evaluations / self._calls


# Path: catsim/estimation.py
from abc import ABC, abstractmethod
from typing import List
from scipy.optimize import minimize_scalar
from catsim import cat, irt
from catsim.simulation import Estimator
import numpy

        :type lower_bound: float
        :param response_vector: the responses given to the answered items
        :type response_vector: List[bool]
        :param item_params: the parameter matrix of the answered items
        :type item_params: numpy.ndarray
        :return: the estimated ability
        :rtype: float
        """
        c = b + (a - b) / NumericalSearchEstimator.golden_ratio
        d = a + (b - a) / NumericalSearchEstimator.golden_ratio

        left_side_ll = irt.log_likelihood(c, response_vector, item_params)
        right_side_ll = irt.log_likelihood(d, response_vector, item_params)

        while abs(b - a) > self._epsilon:
            self._evaluations += 1

            if left_side_ll >= right_side_ll:
                b = d
                d = c
                c = b + (a - b) / NumericalSearchEstimator.golden_ratio

                right_side_ll = left_side_ll
                left_side_ll = irt.log_likelihood(c, response_vector, item_params)
            else:
                a = c
                c = d
                d = a + (b - a) / NumericalSearchEstimator.golden_ratio

                left_side_ll = right_side_ll

                right_side_ll = irt.log_likelihood(d, response_vector, item_params)