adaptivetesting 1.1.5__tar.gz → 1.2b1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/PKG-INFO +14 -15
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/README.md +2 -5
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/__init__.py +5 -2
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/data/__csv_context.py +23 -10
- adaptivetesting-1.2b1/adaptivetesting/data/__read_prev_items_exp_cont.py +35 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/implementations/__pre_test.py +10 -3
- adaptivetesting-1.2b1/adaptivetesting/implementations/__test_assembler.py +418 -0
- adaptivetesting-1.2b1/adaptivetesting/math/__gen_response_pattern.py +117 -0
- adaptivetesting-1.2b1/adaptivetesting/math/content_balancing/__constraint.py +23 -0
- adaptivetesting-1.2b1/adaptivetesting/math/content_balancing/__content_balancing.py +35 -0
- adaptivetesting-1.2b1/adaptivetesting/math/content_balancing/__functions.py +265 -0
- adaptivetesting-1.2b1/adaptivetesting/math/content_balancing/__init__.py +1 -0
- adaptivetesting-1.2b1/adaptivetesting/math/content_balancing/__maximum_priority_index.py +82 -0
- adaptivetesting-1.2b1/adaptivetesting/math/content_balancing/__weighted_penalty_model.py +282 -0
- adaptivetesting-1.2b1/adaptivetesting/math/estimators/__bayes_modal_estimation.py +122 -0
- adaptivetesting-1.2b1/adaptivetesting/math/estimators/__expect_a_posteriori.py +164 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/math/estimators/__init__.py +1 -1
- adaptivetesting-1.2b1/adaptivetesting/math/estimators/__ml_estimation.py +109 -0
- adaptivetesting-1.2b1/adaptivetesting/math/estimators/__prior.py +183 -0
- adaptivetesting-1.2b1/adaptivetesting/math/estimators/__test_information.py +181 -0
- adaptivetesting-1.2b1/adaptivetesting/math/exposure_control/__exposure_control.py +32 -0
- adaptivetesting-1.2b1/adaptivetesting/math/exposure_control/__init__.py +2 -0
- adaptivetesting-1.2b1/adaptivetesting/math/exposure_control/__mpi_exposure_control.py +81 -0
- adaptivetesting-1.2b1/adaptivetesting/math/exposure_control/__randomesque.py +114 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/math/item_selection/__maximum_information_criterion.py +7 -13
- adaptivetesting-1.2b1/adaptivetesting/math/item_selection/__urrys_rule.py +38 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/models/__adaptive_test.py +2 -33
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/models/__item_pool.py +87 -69
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/models/__item_selection_exception.py +1 -0
- adaptivetesting-1.2b1/adaptivetesting/models/__test_item.py +81 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/services/__estimator_interface.py +16 -10
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/services/__item_selection_protocol.py +0 -2
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/services/__test_results_interface.py +0 -2
- adaptivetesting-1.2b1/adaptivetesting/simulation/__init__.py +5 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/simulation/__simulation.py +6 -12
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_adaptive_test.py +0 -4
- adaptivetesting-1.2b1/adaptivetesting/tests/test_bayes_modal.py +194 -0
- adaptivetesting-1.2b1/adaptivetesting/tests/test_content_balacing.py +131 -0
- adaptivetesting-1.2b1/adaptivetesting/tests/test_eap.py +159 -0
- adaptivetesting-1.2b1/adaptivetesting/tests/test_exposure_control_mpi.py +87 -0
- adaptivetesting-1.2b1/adaptivetesting/tests/test_full_tests.py +224 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_generate_response_pattern.py +33 -2
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_item_selection.py +12 -2
- adaptivetesting-1.2b1/adaptivetesting/tests/test_load_test_items.py +325 -0
- adaptivetesting-1.2b1/adaptivetesting/tests/test_poly_models.py +103 -0
- adaptivetesting-1.2b1/adaptivetesting/tests/test_priors.py +56 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_simulation.py +63 -1
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_test_assembler.py +8 -8
- adaptivetesting-1.2b1/adaptivetesting/tests/test_weighted_penalty_model.py +530 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/utils/__plots.py +13 -11
- adaptivetesting-1.2b1/adaptivetesting.egg-info/PKG-INFO +374 -0
- adaptivetesting-1.2b1/adaptivetesting.egg-info/SOURCES.txt +80 -0
- adaptivetesting-1.2b1/adaptivetesting.egg-info/dependency_links.txt +1 -0
- adaptivetesting-1.2b1/adaptivetesting.egg-info/requires.txt +6 -0
- adaptivetesting-1.2b1/adaptivetesting.egg-info/top_level.txt +1 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/pyproject.toml +22 -12
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/setup.cfg +7 -2
- adaptivetesting-1.2b1/setup.py +21 -0
- adaptivetesting-1.1.5/.github/CONTRIBUTING.md +0 -11
- adaptivetesting-1.1.5/.github/workflows/publish.yml +0 -39
- adaptivetesting-1.1.5/.github/workflows/python-test.yml +0 -60
- adaptivetesting-1.1.5/.gitignore +0 -170
- adaptivetesting-1.1.5/.gitlab-ci.yml +0 -13
- adaptivetesting-1.1.5/.python-version +0 -1
- adaptivetesting-1.1.5/.readthedocs.yml +0 -22
- adaptivetesting-1.1.5/.vscode/extensions.json +0 -8
- adaptivetesting-1.1.5/.vscode/settings.json +0 -14
- adaptivetesting-1.1.5/CHANGELOG.md +0 -13
- adaptivetesting-1.1.5/adaptivetesting/implementations/__test_assembler.py +0 -231
- adaptivetesting-1.1.5/adaptivetesting/math/__gen_response_pattern.py +0 -50
- adaptivetesting-1.1.5/adaptivetesting/math/estimators/__bayes_modal_estimation.py +0 -121
- adaptivetesting-1.1.5/adaptivetesting/math/estimators/__expect_a_posteriori.py +0 -90
- adaptivetesting-1.1.5/adaptivetesting/math/estimators/__functions/__bayes.py +0 -56
- adaptivetesting-1.1.5/adaptivetesting/math/estimators/__functions/__estimators.py +0 -167
- adaptivetesting-1.1.5/adaptivetesting/math/estimators/__ml_estimation.py +0 -65
- adaptivetesting-1.1.5/adaptivetesting/math/estimators/__prior.py +0 -94
- adaptivetesting-1.1.5/adaptivetesting/math/estimators/__test_information.py +0 -91
- adaptivetesting-1.1.5/adaptivetesting/math/item_selection/__urrys_rule.py +0 -33
- adaptivetesting-1.1.5/adaptivetesting/models/__test_item.py +0 -33
- adaptivetesting-1.1.5/adaptivetesting/simulation/__init__.py +0 -1
- adaptivetesting-1.1.5/adaptivetesting/tests/test_bayes_modal.py +0 -89
- adaptivetesting-1.1.5/adaptivetesting/tests/test_eap.py +0 -27
- adaptivetesting-1.1.5/adaptivetesting/tests/test_load_test_items.py +0 -176
- adaptivetesting-1.1.5/docs/Makefile +0 -20
- adaptivetesting-1.1.5/docs/data.md +0 -69
- adaptivetesting-1.1.5/docs/implementations.md +0 -117
- adaptivetesting-1.1.5/docs/index.md +0 -32
- adaptivetesting-1.1.5/docs/make.bat +0 -35
- adaptivetesting-1.1.5/docs/math.md +0 -254
- adaptivetesting-1.1.5/docs/models.md +0 -248
- adaptivetesting-1.1.5/docs/readme.md +0 -32
- adaptivetesting-1.1.5/docs/requirements.txt +0 -0
- adaptivetesting-1.1.5/docs/services.md +0 -42
- adaptivetesting-1.1.5/docs/simulation.md +0 -53
- adaptivetesting-1.1.5/docs/source/_static/SPEC-0.svg +0 -1
- adaptivetesting-1.1.5/docs/source/_static/logo.svg +0 -67
- adaptivetesting-1.1.5/docs/source/_static/main.css +0 -24
- adaptivetesting-1.1.5/docs/source/conf.py +0 -49
- adaptivetesting-1.1.5/docs/source/data.rst +0 -9
- adaptivetesting-1.1.5/docs/source/implementations.rst +0 -8
- adaptivetesting-1.1.5/docs/source/index.rst +0 -37
- adaptivetesting-1.1.5/docs/source/math.rst +0 -21
- adaptivetesting-1.1.5/docs/source/models.rst +0 -9
- adaptivetesting-1.1.5/docs/source/services.rst +0 -8
- adaptivetesting-1.1.5/docs/source/simulation.rst +0 -9
- adaptivetesting-1.1.5/docs/source/tests.rst +0 -5
- adaptivetesting-1.1.5/docs/tests.md +0 -4
- adaptivetesting-1.1.5/examples/.gitignore +0 -3
- adaptivetesting-1.1.5/examples/simulation.py +0 -134
- adaptivetesting-1.1.5/examples/testing.py +0 -108
- adaptivetesting-1.1.5/images/default.svg +0 -208
- adaptivetesting-1.1.5/images/semi-adaptive.svg +0 -254
- adaptivetesting-1.1.5/mypy.ini +0 -8
- adaptivetesting-1.1.5/requirements.txt +0 -0
- adaptivetesting-1.1.5/uv.lock +0 -4274
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/LICENSE +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/data/__init__.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/data/__pickle_context.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/implementations/__default_implementation.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/implementations/__init__.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/implementations/__semi_implementation.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/math/__init__.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/math/item_selection/__init__.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/models/__algorithm_exception.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/models/__init__.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/models/__misc.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/models/__test_result.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/services/__init__.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/__init__.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_descriptives.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_information_test.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_mle.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_plots.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_pretest.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/tests/test_standard_error.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/utils/__descriptives.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/utils/__funcs.py +0 -0
- {adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/utils/__init__.py +0 -0
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adaptivetesting
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2b1
|
|
4
4
|
Summary: adaptivetesting is a Python package that can be used to simulate and evaluate custom CAT scenarios as well as implement them in real-world testing scenarios from a single codebase
|
|
5
|
+
Author-email: Jonas Engicht <dev@condecon.de>
|
|
5
6
|
Project-URL: Homepage, https://github.com/condecon/adaptivetesting
|
|
6
|
-
Project-URL: Documentation, https://github.com/condecon/adaptivetesting/
|
|
7
|
+
Project-URL: Documentation, https://github.com/condecon/adaptivetesting/wiki
|
|
7
8
|
Project-URL: Repository, https://github.com/condecon/adaptivetesting.git
|
|
8
9
|
Project-URL: Issues, https://github.com/condecon/adaptivetesting/issues
|
|
9
|
-
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Keywords: computerized-adaptive-testing,item-response-theory,psychology,statistics
|
|
10
|
+
Keywords: statistics,psychology,item-response-theory,computerized-adaptive-testing
|
|
12
11
|
Requires-Python: >=3.12
|
|
13
|
-
Requires-Dist: matplotlib>=3.9.0
|
|
14
|
-
Requires-Dist: numpy>=2.0.0
|
|
15
|
-
Requires-Dist: pandas>=2.2.0
|
|
16
|
-
Requires-Dist: scipy>=1.12.0
|
|
17
|
-
Requires-Dist: tqdm>=4.66.0
|
|
18
12
|
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy>=2.1.0
|
|
15
|
+
Requires-Dist: pandas>=2.3.0
|
|
16
|
+
Requires-Dist: scipy>=1.15.0
|
|
17
|
+
Requires-Dist: tqdm>=4.66.0
|
|
18
|
+
Requires-Dist: matplotlib>=3.10.0
|
|
19
|
+
Requires-Dist: numdifftools>=0.9.42
|
|
20
|
+
Dynamic: license-file
|
|
19
21
|
|
|
20
22
|
# adaptivetesting
|
|
21
|
-
[](https://scientific-python.org/specs/spec-0000/)
|
|
22
|
-
|
|
23
|
-
|
|
24
23
|
|
|
25
|
-
<img src="/docs/
|
|
24
|
+
<img src="/docs/_static/logo.svg" style="width: 100%">
|
|
26
25
|
</img>
|
|
27
26
|
|
|
28
27
|
**An open-source Python package for simplified, customizable Computerized Adaptive Testing (CAT) using Bayesian methods.**
|
|
@@ -37,7 +36,7 @@ Description-Content-Type: text/markdown
|
|
|
37
36
|
- Maximum Likelihood Estimation (MLE)
|
|
38
37
|
- Bayesian Modal Estimation (BM)
|
|
39
38
|
- Expected A Posteriori (EAP)
|
|
40
|
-
- **Item Selection Strategies**:
|
|
39
|
+
- **Item Selection Strategies**: Maximum information criterion and Urry's rule
|
|
41
40
|
- **Simulation Framework**: Comprehensive tools for CAT simulation and evaluation
|
|
42
41
|
- **Real-world Application**: Direct transition from simulation to production testing
|
|
43
42
|
- **Stopping Criteria**: Support for standard error and test length criteria
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# adaptivetesting
|
|
2
|
-
[](https://scientific-python.org/specs/spec-0000/)
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<img src="/docs/source/_static/logo.svg" style="width: 100%">
|
|
3
|
+
<img src="/docs/_static/logo.svg" style="width: 100%">
|
|
7
4
|
</img>
|
|
8
5
|
|
|
9
6
|
**An open-source Python package for simplified, customizable Computerized Adaptive Testing (CAT) using Bayesian methods.**
|
|
@@ -18,7 +15,7 @@
|
|
|
18
15
|
- Maximum Likelihood Estimation (MLE)
|
|
19
16
|
- Bayesian Modal Estimation (BM)
|
|
20
17
|
- Expected A Posteriori (EAP)
|
|
21
|
-
- **Item Selection Strategies**:
|
|
18
|
+
- **Item Selection Strategies**: Maximum information criterion and Urry's rule
|
|
22
19
|
- **Simulation Framework**: Comprehensive tools for CAT simulation and evaluation
|
|
23
20
|
- **Real-world Application**: Direct transition from simulation to production testing
|
|
24
21
|
- **Stopping Criteria**: Support for standard error and test length criteria
|
|
@@ -12,18 +12,21 @@ from .data.__pickle_context import PickleContext
|
|
|
12
12
|
from .implementations.__default_implementation import DefaultImplementation
|
|
13
13
|
from .implementations.__pre_test import PreTest
|
|
14
14
|
from .implementations.__semi_implementation import SemiAdaptiveImplementation
|
|
15
|
-
from .implementations.__test_assembler import TestAssembler
|
|
15
|
+
from .implementations.__test_assembler import TestAssembler, ContentBalancingArgs, ExposureControlArgs
|
|
16
16
|
|
|
17
17
|
from .math.__gen_response_pattern import generate_response_pattern
|
|
18
18
|
from .math.estimators.__ml_estimation import MLEstimator
|
|
19
19
|
from .math.estimators.__bayes_modal_estimation import BayesModal
|
|
20
20
|
from .math.estimators.__expect_a_posteriori import ExpectedAPosteriori
|
|
21
|
-
from .math.estimators.__prior import Prior, NormalPrior, CustomPrior, CustomPriorException
|
|
21
|
+
from .math.estimators.__prior import Prior, NormalPrior, CustomPrior, CustomPriorException, SkewNormalPrior, EmpiricalPrior
|
|
22
22
|
from .math.estimators.__functions.__estimators import probability_y0, probability_y1, maximize_likelihood_function, likelihood
|
|
23
23
|
from .math.estimators.__functions.__bayes import maximize_posterior
|
|
24
24
|
from .math.estimators.__test_information import test_information_function, item_information_function, prior_information_function
|
|
25
25
|
from .math.item_selection.__maximum_information_criterion import maximum_information_criterion
|
|
26
26
|
from .math.item_selection.__urrys_rule import urrys_rule
|
|
27
|
+
from .math.content_balancing.__weighted_penalty_model import WeightedPenaltyModel
|
|
28
|
+
from .math.content_balancing.__constraint import *
|
|
29
|
+
from .math.content_balancing.__functions import *
|
|
27
30
|
|
|
28
31
|
from .models.__adaptive_test import AdaptiveTest
|
|
29
32
|
from .models.__algorithm_exception import AlgorithmException
|
|
@@ -3,7 +3,8 @@ import csv
|
|
|
3
3
|
import pathlib
|
|
4
4
|
from ..models.__test_result import TestResult
|
|
5
5
|
from ..services.__test_results_interface import ITestResults
|
|
6
|
-
|
|
6
|
+
import json
|
|
7
|
+
import ast
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class CSVContext(ITestResults):
|
|
@@ -47,22 +48,34 @@ class CSVContext(ITestResults):
|
|
|
47
48
|
file.close()
|
|
48
49
|
|
|
49
50
|
def load(self) -> List[TestResult]:
|
|
50
|
-
"""Loads
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
"""Loads results from the database.
|
|
52
|
+
The implementation of this method is required
|
|
53
|
+
by the interface. However, it does not have
|
|
54
|
+
any implemented functionality and will throw an error
|
|
55
|
+
if used.
|
|
56
|
+
|
|
57
|
+
Returns: List[TestResult]
|
|
57
58
|
"""
|
|
58
59
|
foldername = f"data/{self.simulation_id}"
|
|
59
60
|
|
|
60
|
-
fieldnames = list(fields(TestResult))
|
|
61
61
|
test_results: list[TestResult] = []
|
|
62
62
|
with open(f"{foldername}/{self.participant_id}.csv", "r", encoding="utf-8") as file:
|
|
63
|
-
reader = csv.DictReader(file
|
|
63
|
+
reader = csv.DictReader(file)
|
|
64
|
+
next(reader, None) # skip header row
|
|
64
65
|
for row in reader:
|
|
65
66
|
test_result = TestResult.from_dict(row)
|
|
67
|
+
# showed_item is read as a string representation of a Python
|
|
68
|
+
# literal (e.g. a dict). Use ast.literal_eval to safely parse
|
|
69
|
+
# it back into a dict. Fallback to a JSON-style conversion
|
|
70
|
+
# if literal_eval fails.
|
|
71
|
+
try:
|
|
72
|
+
test_result.showed_item = ast.literal_eval(str(test_result.showed_item))
|
|
73
|
+
except Exception:
|
|
74
|
+
json_string = str(test_result.showed_item).replace("'", '"').replace("None", "null")
|
|
75
|
+
try:
|
|
76
|
+
test_result.showed_item = json.loads(json_string)
|
|
77
|
+
except Exception:
|
|
78
|
+
test_result.showed_item = {}
|
|
66
79
|
test_results.append(test_result)
|
|
67
80
|
file.close()
|
|
68
81
|
return test_results
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
from warnings import deprecated
|
|
3
|
+
from ..models.__test_item import TestItem
|
|
4
|
+
from ..models.__test_result import TestResult
|
|
5
|
+
from .__csv_context import CSVContext
|
|
6
|
+
from .__pickle_context import PickleContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def read_prev_items(format: Literal["CSV", "PICKLE"],
|
|
10
|
+
test_id: str,
|
|
11
|
+
participant_ids: list[str]) -> list[TestItem]:
|
|
12
|
+
"""Read previously administered items
|
|
13
|
+
to perform exposure control
|
|
14
|
+
"""
|
|
15
|
+
all_shown_test_items = []
|
|
16
|
+
|
|
17
|
+
for part_id in participant_ids:
|
|
18
|
+
test_results = read_single_participant(test_id, part_id, format)
|
|
19
|
+
items = [TestItem.from_dict(res.showed_item) for res in test_results]
|
|
20
|
+
|
|
21
|
+
all_shown_test_items += items
|
|
22
|
+
|
|
23
|
+
return all_shown_test_items
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@deprecated("This function should be replaced after merging"
|
|
27
|
+
" with revision branch")
|
|
28
|
+
def read_single_participant(test_id: str, participant_id: str, format: Literal["CSV", "PICKLE"]) -> list[TestResult]:
|
|
29
|
+
context: CSVContext | PickleContext
|
|
30
|
+
if format == "CSV":
|
|
31
|
+
context = CSVContext(test_id, participant_id)
|
|
32
|
+
return context.load()
|
|
33
|
+
if format == "PICKLE":
|
|
34
|
+
context = PickleContext(test_id, participant_id)
|
|
35
|
+
return context.load()
|
{adaptivetesting-1.1.5 → adaptivetesting-1.2b1}/adaptivetesting/implementations/__pre_test.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List
|
|
1
|
+
from typing import List, cast
|
|
2
2
|
import numpy as np
|
|
3
3
|
from ..models.__test_item import TestItem
|
|
4
4
|
import random
|
|
@@ -10,6 +10,7 @@ class PreTest:
|
|
|
10
10
|
The pretest class can be used to draw items randomly from
|
|
11
11
|
difficulty quantiles
|
|
12
12
|
of the item pool.
|
|
13
|
+
This pretest can only be used with dichotomous IRT models and items.
|
|
13
14
|
|
|
14
15
|
Args:
|
|
15
16
|
items: Item pool
|
|
@@ -18,15 +19,21 @@ class PreTest:
|
|
|
18
19
|
If not, the item selection will be drawn randomly, and you will not be able
|
|
19
20
|
to reproduce the results.
|
|
20
21
|
|
|
22
|
+
Raises:
|
|
23
|
+
ValueError: Raised if the items are specified for polytomous IRT models.
|
|
24
|
+
|
|
21
25
|
"""
|
|
22
26
|
self.items = items
|
|
23
27
|
self.seed = seed
|
|
24
28
|
|
|
29
|
+
if any([isinstance(item.b, list) for item in items]):
|
|
30
|
+
raise ValueError("The pretest can only be used with dichotomous IRT models and items.")
|
|
31
|
+
|
|
25
32
|
def calculate_quantiles(self) -> np.ndarray:
|
|
26
33
|
"""Calculates quantiles 0.25, 0.5, 0.75
|
|
27
34
|
"""
|
|
28
35
|
# get difficulties
|
|
29
|
-
difficulties: List[float] = [item.b for item in self.items]
|
|
36
|
+
difficulties: List[float] = [cast(float, item.b) for item in self.items]
|
|
30
37
|
|
|
31
38
|
quantiles = np.array([])
|
|
32
39
|
# calculate quantiles
|
|
@@ -50,7 +57,7 @@ class PreTest:
|
|
|
50
57
|
"""
|
|
51
58
|
# select only items with difficulty in interval
|
|
52
59
|
items_in_interval: List[TestItem] = [item for item in list(self.items)
|
|
53
|
-
if lower < item.b <= upper]
|
|
60
|
+
if lower < cast(float, item.b) <= upper]
|
|
54
61
|
# draw one item randomly
|
|
55
62
|
if self.seed is not None:
|
|
56
63
|
random.seed(self.seed)
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
from ..models.__item_selection_exception import ItemSelectionException
|
|
2
|
+
from ..math.content_balancing.__constraint import Constraint
|
|
3
|
+
from ..models.__adaptive_test import AdaptiveTest
|
|
4
|
+
from ..models.__test_item import TestItem
|
|
5
|
+
from ..services.__estimator_interface import IEstimator
|
|
6
|
+
from typing import Any, Type, TypedDict, Callable, Literal
|
|
7
|
+
from ..math.item_selection.__maximum_information_criterion import maximum_information_criterion
|
|
8
|
+
from ..models.__algorithm_exception import AlgorithmException
|
|
9
|
+
from ..implementations.__pre_test import PreTest
|
|
10
|
+
from ..models.__test_result import TestResult
|
|
11
|
+
from ..models.__misc import ResultOutputFormat
|
|
12
|
+
from ..services.__item_selection_protocol import ItemSelectionStrategy
|
|
13
|
+
from ..math.content_balancing.__content_balancing import CONTENT_BALANCING
|
|
14
|
+
from ..math.content_balancing.__weighted_penalty_model import WeightedPenaltyModel
|
|
15
|
+
from ..math.content_balancing.__maximum_priority_index import MaximumPriorityIndex
|
|
16
|
+
from ..math.estimators.__prior import Prior
|
|
17
|
+
from ..math.exposure_control.__exposure_control import EXPOSURE_CONTROL
|
|
18
|
+
from ..math.exposure_control.__randomesque import Randomesque
|
|
19
|
+
from ..math.exposure_control.__mpi_exposure_control import MaximumPriorityIndexExposureControl
|
|
20
|
+
import inspect
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EstimatorArgs(TypedDict):
|
|
24
|
+
prior: Prior | None
|
|
25
|
+
optimization_interval: tuple[float, float]
|
|
26
|
+
model: Literal["GRM", "GPCM"] | None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ContentBalancingArgs(TypedDict):
|
|
30
|
+
constraints: list[Constraint] | None
|
|
31
|
+
"""constraints that are applied to the item selection"""
|
|
32
|
+
constraint_weight: float | Callable[[AdaptiveTest], float] | None
|
|
33
|
+
"""weight of the constraints
|
|
34
|
+
This can also be a function taking adaptive test as an input argument.
|
|
35
|
+
This allows the user to specify custom weight values depending
|
|
36
|
+
on the specific states and progress of the test."""
|
|
37
|
+
information_weight: float | Callable[[AdaptiveTest], float] | None
|
|
38
|
+
"""weight of the item information
|
|
39
|
+
This can also be a function taking adaptive test as an input argument.
|
|
40
|
+
This allows the user to specify custom weight values depending
|
|
41
|
+
on the specific states and progress of the test."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ExposureControlArgs(TypedDict):
|
|
45
|
+
constraints: list[Constraint] | None
|
|
46
|
+
"""Constraints applied for Maximum Priority Index exposure control."""
|
|
47
|
+
participant_ids: list[str] | None
|
|
48
|
+
"""Participant ids to read the previously administered test in order to
|
|
49
|
+
perform exposure control (MPI only).
|
|
50
|
+
"""
|
|
51
|
+
output_format: ResultOutputFormat | None
|
|
52
|
+
"""Format in which the previous tests have been saved (MPI only)."""
|
|
53
|
+
n_items: int | None
|
|
54
|
+
"""number of items to select for Randomesque items selection"""
|
|
55
|
+
seed: int | None
|
|
56
|
+
"""random seed for the final item selection during Randomesque item selection"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestAssembler(AdaptiveTest):
|
|
60
|
+
"""
|
|
61
|
+
TestAssembler is a subclass of AdaptiveTest designed to assemble and administer adaptive tests,
|
|
62
|
+
optionally including a pretest phase. It supports customizable ability estimation and item selection strategies.
|
|
63
|
+
|
|
64
|
+
Methods:
|
|
65
|
+
estimate_ability_level():
|
|
66
|
+
Estimates the current ability level using the specified estimator and handles exceptions
|
|
67
|
+
for specific response patterns (all correct or all incorrect).
|
|
68
|
+
|
|
69
|
+
get_next_item() -> TestItem:
|
|
70
|
+
Selects the next item to administer using the specified item selection strategy.
|
|
71
|
+
|
|
72
|
+
run_test_once():
|
|
73
|
+
Runs a single iteration of the test, including an optional pretest phase. Handles item
|
|
74
|
+
administration, response collection, ability estimation, and result recording.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
__ability_estimator: The estimator class for ability estimation.
|
|
78
|
+
|
|
79
|
+
__estimator_args: Arguments for the ability estimator.
|
|
80
|
+
|
|
81
|
+
__item_selector: The item selection strategy.
|
|
82
|
+
|
|
83
|
+
__item_selector_args: Arguments for the item selector.
|
|
84
|
+
|
|
85
|
+
__pretest: Whether to run a pretest phase.
|
|
86
|
+
|
|
87
|
+
__pretest_seed: Random seed for pretest item selection.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(self,
|
|
91
|
+
item_pool,
|
|
92
|
+
simulation_id,
|
|
93
|
+
participant_id,
|
|
94
|
+
ability_estimator: Type[IEstimator],
|
|
95
|
+
estimator_args: EstimatorArgs = {
|
|
96
|
+
"prior": None,
|
|
97
|
+
"optimization_interval": (-10, 10),
|
|
98
|
+
"model": None
|
|
99
|
+
},
|
|
100
|
+
item_selector: ItemSelectionStrategy = maximum_information_criterion, # type: ignore
|
|
101
|
+
item_selector_args: dict[str, Any] = {},
|
|
102
|
+
model_type: Literal["GRM", "GPCM"] | None = None,
|
|
103
|
+
content_balancing: None | CONTENT_BALANCING = None,
|
|
104
|
+
content_balancing_args: ContentBalancingArgs | None = None,
|
|
105
|
+
exposure_control: None | EXPOSURE_CONTROL = None,
|
|
106
|
+
exposure_control_args: None | ExposureControlArgs = None,
|
|
107
|
+
pretest: bool = False,
|
|
108
|
+
pretest_seed: int | None = None,
|
|
109
|
+
true_ability_level=None,
|
|
110
|
+
initial_ability_level=0,
|
|
111
|
+
simulation=True,
|
|
112
|
+
debug=False,
|
|
113
|
+
**kwargs):
|
|
114
|
+
"""
|
|
115
|
+
Args:
|
|
116
|
+
|
|
117
|
+
item_pool: The pool of test items available for selection.
|
|
118
|
+
|
|
119
|
+
simulation_id: Identifier for the simulation run.
|
|
120
|
+
|
|
121
|
+
participant_id: Identifier for the participant.
|
|
122
|
+
|
|
123
|
+
ability_estimator (Type[IEstimator]): The estimator class used for ability estimation.
|
|
124
|
+
|
|
125
|
+
estimator_args (EstimatorArgs, optional):
|
|
126
|
+
Arguments for the ability estimator. Defaults to {"prior": None, "optimization_interval": (-10, 10)}.
|
|
127
|
+
|
|
128
|
+
item_selector (ItemSelectionStrategy, optional):
|
|
129
|
+
Function or strategy for selecting the next item. Defaults to maximum_information_criterion.
|
|
130
|
+
|
|
131
|
+
item_selector_args (dict[str, Any], optional): Arguments for the item selector. Defaults to {}.
|
|
132
|
+
|
|
133
|
+
content_balancing (CONTENT_BALANCING, optional): Selected content balancing strategy. Defaults to None.
|
|
134
|
+
If a content balancing strategy is specified, the item selection strategy will be ignored.
|
|
135
|
+
|
|
136
|
+
pretest (bool, optional): Whether to run a pretest phase before the main test. Defaults to False.
|
|
137
|
+
|
|
138
|
+
pretest_seed (int | None, optional): Random seed for pretest item selection. Defaults to None.
|
|
139
|
+
|
|
140
|
+
true_ability_level (optional): The true ability level of the participant (for simulation).
|
|
141
|
+
|
|
142
|
+
initial_ability_level (optional): The initial ability estimate. Defaults to 0.
|
|
143
|
+
|
|
144
|
+
simulation (bool, optional): Whether the test is run in simulation mode. Defaults to True.
|
|
145
|
+
|
|
146
|
+
debug (bool, optional): Whether to enable debug output. Defaults to False.
|
|
147
|
+
|
|
148
|
+
**kwargs: Additional keyword arguments passed to the AdaptiveTest superclass.
|
|
149
|
+
"""
|
|
150
|
+
self.__ability_estimator = ability_estimator
|
|
151
|
+
self.__estimator_args = estimator_args
|
|
152
|
+
self.__item_selector = item_selector
|
|
153
|
+
self.__item_selector_args = item_selector_args
|
|
154
|
+
self.content_balancing = content_balancing
|
|
155
|
+
self.content_balancing_args = content_balancing_args
|
|
156
|
+
self.exposure_control = exposure_control
|
|
157
|
+
self.exposure_control_args = exposure_control_args
|
|
158
|
+
self.__pretest = pretest
|
|
159
|
+
self.__pretest_seed = pretest_seed
|
|
160
|
+
self.__estimator_args["model"] = model_type
|
|
161
|
+
|
|
162
|
+
super().__init__(item_pool,
|
|
163
|
+
simulation_id,
|
|
164
|
+
participant_id,
|
|
165
|
+
true_ability_level,
|
|
166
|
+
initial_ability_level,
|
|
167
|
+
simulation,
|
|
168
|
+
debug,
|
|
169
|
+
**kwargs)
|
|
170
|
+
|
|
171
|
+
def estimate_ability_level(self):
|
|
172
|
+
"""
|
|
173
|
+
Estimates the ability level of a test-taker based on their response pattern and answered items.
|
|
174
|
+
This method uses the configured ability estimator to calculate the ability estimation and its standard error.
|
|
175
|
+
If an AlgorithmException occurs during estimation,
|
|
176
|
+
and all responses are identical (all correct or all incorrect),
|
|
177
|
+
it assigns a default estimation value (-10 for all incorrect, 10 for all correct)
|
|
178
|
+
and recalculates the standard error.
|
|
179
|
+
Otherwise, it raises an AlgorithmException with additional context.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
tuple[float, float]: A tuple containing the estimated ability level (float) and its standard error (float).
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
AlgorithmException: If estimation fails for reasons other than all responses being identical.
|
|
186
|
+
"""
|
|
187
|
+
# filter estimator args
|
|
188
|
+
sig = inspect.signature(self.__ability_estimator)
|
|
189
|
+
allowed = set(sig.parameters.keys())
|
|
190
|
+
filtered_estimator_args = {k: v for k, v in self.__estimator_args.items() if k in allowed}
|
|
191
|
+
|
|
192
|
+
# setup estimator
|
|
193
|
+
estimator = self.__ability_estimator(
|
|
194
|
+
self.response_pattern,
|
|
195
|
+
self.answered_items,
|
|
196
|
+
**filtered_estimator_args # type: ignore
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
estimation = estimator.get_estimation()
|
|
201
|
+
standard_error = estimator.get_standard_error(estimation)
|
|
202
|
+
except AlgorithmException as exception:
|
|
203
|
+
# check if all responses are the same
|
|
204
|
+
if len(set(self.response_pattern)) == 1:
|
|
205
|
+
if self.response_pattern[0] == 0:
|
|
206
|
+
estimation = -10
|
|
207
|
+
elif self.response_pattern[0] == 1:
|
|
208
|
+
estimation = 10
|
|
209
|
+
standard_error = estimator.get_standard_error(estimation)
|
|
210
|
+
|
|
211
|
+
else:
|
|
212
|
+
raise AlgorithmException(f"""Something
|
|
213
|
+
when wrong when running {type(estimator)}""") from exception
|
|
214
|
+
|
|
215
|
+
return estimation, standard_error
|
|
216
|
+
|
|
217
|
+
def get_next_item(self) -> TestItem:
|
|
218
|
+
"""
|
|
219
|
+
Selects and returns the next test item based on the current ability level and item selector strategy.
|
|
220
|
+
If a content balancing strategy is specified, the item selection strategy will be ignored.
|
|
221
|
+
Instead, the item selected by the content balancing strategy will be returned.
|
|
222
|
+
This also applies to exposure control.
|
|
223
|
+
However, content balancing and exposure control cannot be specified at the same time
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
TestItem: The next item to be administered in the test, as determined by the item selector.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
Any exceptions raised by the item selector function.
|
|
230
|
+
"""
|
|
231
|
+
item: TestItem | None
|
|
232
|
+
if self.content_balancing is None and self.exposure_control_args is None:
|
|
233
|
+
# content balancing an exposure control are not specified
|
|
234
|
+
# filter item selection args
|
|
235
|
+
sig = inspect.signature(self.__item_selector)
|
|
236
|
+
allowed = set(sig.parameters.keys())
|
|
237
|
+
filtered_item_selector_args = {k: v for k, v in self.__item_selector_args.items() if k in allowed}
|
|
238
|
+
|
|
239
|
+
item = self.__item_selector(
|
|
240
|
+
self.item_pool.test_items,
|
|
241
|
+
self.ability_level,
|
|
242
|
+
**filtered_item_selector_args
|
|
243
|
+
)
|
|
244
|
+
return item
|
|
245
|
+
elif self.content_balancing and self.exposure_control:
|
|
246
|
+
raise ValueError("Content balancing and exposure cannot be specified at the same time!")
|
|
247
|
+
# content balancing only
|
|
248
|
+
elif self.content_balancing and self.exposure_control is None:
|
|
249
|
+
if self.content_balancing_args is not None:
|
|
250
|
+
# which strategy has been selected
|
|
251
|
+
if self.content_balancing == "WeightedPenaltyModel":
|
|
252
|
+
# parse content balancing args
|
|
253
|
+
|
|
254
|
+
# setup wep
|
|
255
|
+
adaptive_test = self
|
|
256
|
+
wep = WeightedPenaltyModel(
|
|
257
|
+
adaptive_test,
|
|
258
|
+
constraints=self.check_args_are_not_none(
|
|
259
|
+
"constraints",
|
|
260
|
+
self.content_balancing_args["constraints"]),
|
|
261
|
+
constraint_weight=self.check_args_are_not_none(
|
|
262
|
+
"constraint_weights",
|
|
263
|
+
self.content_balancing_args["constraint_weight"]),
|
|
264
|
+
information_weight=self.check_args_are_not_none(
|
|
265
|
+
"information_weight",
|
|
266
|
+
self.content_balancing_args["information_weight"]))
|
|
267
|
+
|
|
268
|
+
item = wep.select_item()
|
|
269
|
+
if item is None:
|
|
270
|
+
raise ItemSelectionException(
|
|
271
|
+
f"""Something went wrong when selecting an item using {self.content_balancing}""")
|
|
272
|
+
else:
|
|
273
|
+
return item
|
|
274
|
+
elif self.content_balancing == "MaximumPriorityIndex":
|
|
275
|
+
adaptive_test = self
|
|
276
|
+
mpi = MaximumPriorityIndex(
|
|
277
|
+
adaptive_test,
|
|
278
|
+
constraints=self.check_args_are_not_none(
|
|
279
|
+
"constraints",
|
|
280
|
+
self.content_balancing_args["constraints"]))
|
|
281
|
+
|
|
282
|
+
item = mpi.select_item()
|
|
283
|
+
|
|
284
|
+
if item is None:
|
|
285
|
+
raise ItemSelectionException(
|
|
286
|
+
f"""Something went wrong when selecting an item using {self.content_balancing}""")
|
|
287
|
+
else:
|
|
288
|
+
return item
|
|
289
|
+
else:
|
|
290
|
+
raise ValueError("content_balancing_args cannot be None when using content balancing.")
|
|
291
|
+
# exposure control only
|
|
292
|
+
elif self.content_balancing is None and self.exposure_control:
|
|
293
|
+
if self.exposure_control_args is None:
|
|
294
|
+
raise ValueError("exposure_control_args cannot be None when using exposure control.")
|
|
295
|
+
# which strategy has been selected
|
|
296
|
+
if self.exposure_control == "Randomesque":
|
|
297
|
+
# Randomesque
|
|
298
|
+
adaptive_test = self
|
|
299
|
+
dict_keys = list(self.exposure_control_args.keys())
|
|
300
|
+
if "seed" not in dict_keys or "n_items" not in dict_keys:
|
|
301
|
+
raise ValueError("exposure_control_args are not correctly specified")
|
|
302
|
+
|
|
303
|
+
if self.exposure_control_args["n_items"] is not None:
|
|
304
|
+
randomesque = Randomesque(
|
|
305
|
+
adaptive_test=adaptive_test,
|
|
306
|
+
n_items=self.exposure_control_args["n_items"],
|
|
307
|
+
seed=self.exposure_control_args["seed"]
|
|
308
|
+
)
|
|
309
|
+
return randomesque.select_item()
|
|
310
|
+
else:
|
|
311
|
+
raise ValueError("n_items cannot be None.")
|
|
312
|
+
|
|
313
|
+
if self.exposure_control == "MaximumPriorityIndex":
|
|
314
|
+
if (self.exposure_control_args["participant_ids"]
|
|
315
|
+
is None or self.exposure_control_args["output_format"] is None):
|
|
316
|
+
raise ValueError("exposure_control_args are not correctly specified")
|
|
317
|
+
mpi = MaximumPriorityIndexExposureControl(
|
|
318
|
+
self,
|
|
319
|
+
constraints=self.exposure_control_args["constraints"],
|
|
320
|
+
participant_ids=self.exposure_control_args["participant_ids"],
|
|
321
|
+
format=self.exposure_control_args["output_format"]
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
selected_item = mpi.select_item()
|
|
325
|
+
if selected_item is None:
|
|
326
|
+
raise ItemSelectionException("Fatal! Not appropriated item was "
|
|
327
|
+
"selected using MPI for exposure control")
|
|
328
|
+
else:
|
|
329
|
+
return selected_item
|
|
330
|
+
raise ValueError(f"Something went wrong when selecting an item using {self.content_balancing}.")
|
|
331
|
+
|
|
332
|
+
def run_test_once(self):
|
|
333
|
+
"""
|
|
334
|
+
Executes a single run of the test, including optional pretest logic.
|
|
335
|
+
If pretesting is enabled, this method:
|
|
336
|
+
- Selects a random quantile of items from the item pool using a PreTest instance.
|
|
337
|
+
- For each selected item:
|
|
338
|
+
- Obtains a response (either simulated or real).
|
|
339
|
+
- Appends the response and item to the respective lists.
|
|
340
|
+
- Removes the item from the item pool.
|
|
341
|
+
- Estimates the ability level and standard error after pretest responses.
|
|
342
|
+
- Records test results for each pretest item, with the final item including the first ability estimation.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
The result of the superclass's run_test_once() method.
|
|
346
|
+
"""
|
|
347
|
+
# check if to run pretest
|
|
348
|
+
if self.__pretest is True:
|
|
349
|
+
pretest = PreTest(
|
|
350
|
+
self.item_pool.test_items,
|
|
351
|
+
self.__pretest_seed
|
|
352
|
+
)
|
|
353
|
+
# get selected items
|
|
354
|
+
random_items = pretest.select_random_item_quantile()
|
|
355
|
+
for item in random_items:
|
|
356
|
+
if self.simulation is True:
|
|
357
|
+
response = self.item_pool.get_item_response(item)
|
|
358
|
+
else:
|
|
359
|
+
# not simulation
|
|
360
|
+
response = self.get_response(item)
|
|
361
|
+
|
|
362
|
+
if self.DEBUG:
|
|
363
|
+
print(f"Response: {response}")
|
|
364
|
+
# add response to response pattern
|
|
365
|
+
self.response_pattern.append(response)
|
|
366
|
+
# add item to answered items list
|
|
367
|
+
self.answered_items.append(item)
|
|
368
|
+
|
|
369
|
+
# remove items
|
|
370
|
+
self.item_pool.delete_item(item)
|
|
371
|
+
# estimate ability level
|
|
372
|
+
estimation, sd_error = self.estimate_ability_level()
|
|
373
|
+
self.ability_level = estimation
|
|
374
|
+
self.standard_error = sd_error
|
|
375
|
+
# create test results for all n-1 random items
|
|
376
|
+
for i in range(0, len(random_items) - 1):
|
|
377
|
+
result = TestResult(
|
|
378
|
+
ability_estimation=float("nan"),
|
|
379
|
+
standard_error=float("nan"),
|
|
380
|
+
showed_item=random_items[i].as_dict(),
|
|
381
|
+
response=self.response_pattern[i],
|
|
382
|
+
test_id=self.simulation_id,
|
|
383
|
+
true_ability_level=self.true_ability_level if self.true_ability_level is not None else float("NaN"),
|
|
384
|
+
)
|
|
385
|
+
# append to memory
|
|
386
|
+
self.test_results.append(result)
|
|
387
|
+
|
|
388
|
+
# create test result for first ability estimation
|
|
389
|
+
intermediate_result = TestResult(
|
|
390
|
+
ability_estimation=self.ability_level,
|
|
391
|
+
standard_error=self.standard_error,
|
|
392
|
+
showed_item=random_items[-1].as_dict(),
|
|
393
|
+
response=self.response_pattern[-1],
|
|
394
|
+
test_id=self.simulation_id,
|
|
395
|
+
true_ability_level=self.true_ability_level if self.true_ability_level is not None else float("NaN"),
|
|
396
|
+
)
|
|
397
|
+
self.test_results.append(intermediate_result)
|
|
398
|
+
|
|
399
|
+
return super().run_test_once()
|
|
400
|
+
|
|
401
|
+
def check_args_are_not_none(self, key: str, x: Any | None) -> Any:
|
|
402
|
+
"""This functions checks if an object is none.
|
|
403
|
+
If not a ValueError is raised.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
key (str): parameter name where the object is typically assigned
|
|
407
|
+
x (Any | None): object
|
|
408
|
+
|
|
409
|
+
Raises:
|
|
410
|
+
ValueError: raised if the object is None.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Any: object
|
|
414
|
+
"""
|
|
415
|
+
if x is not None:
|
|
416
|
+
return x
|
|
417
|
+
else:
|
|
418
|
+
raise ValueError(f"{key} cannot be None.")
|