adaptivetesting 1.2b1__tar.gz → 1.2.2__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.
Files changed (91) hide show
  1. adaptivetesting-1.2.2/PKG-INFO +122 -0
  2. adaptivetesting-1.2.2/README.md +92 -0
  3. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/__init__.py +11 -1
  4. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/data/__read_prev_items_exp_cont.py +25 -4
  5. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/implementations/__test_assembler.py +12 -2
  6. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/__gen_response_pattern.py +26 -2
  7. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/content_balancing/__constraint.py +1 -1
  8. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/content_balancing/__functions.py +5 -4
  9. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/content_balancing/__weighted_penalty_model.py +59 -4
  10. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/estimators/__bayes_modal_estimation.py +2 -0
  11. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/estimators/__expect_a_posteriori.py +2 -0
  12. adaptivetesting-1.2.2/adaptivetesting/math/estimators/__functions/__bayes.py +74 -0
  13. adaptivetesting-1.2.2/adaptivetesting/math/estimators/__functions/__estimators.py +182 -0
  14. adaptivetesting-1.2.2/adaptivetesting/math/estimators/__functions/__poly/__gpcm.py +70 -0
  15. adaptivetesting-1.2.2/adaptivetesting/math/estimators/__functions/__poly/__grm.py +77 -0
  16. adaptivetesting-1.2.2/adaptivetesting/math/estimators/__functions/__poly/__init__.py +4 -0
  17. adaptivetesting-1.2.2/adaptivetesting/math/estimators/__functions/__poly/__poly_math.py +193 -0
  18. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/estimators/__ml_estimation.py +4 -0
  19. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/estimators/__test_information.py +39 -3
  20. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/exposure_control/__mpi_exposure_control.py +1 -1
  21. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/exposure_control/__randomesque.py +1 -1
  22. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/item_selection/__maximum_information_criterion.py +2 -1
  23. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/models/__adaptive_test.py +2 -1
  24. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/models/__test_item.py +5 -5
  25. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/services/__estimator_interface.py +1 -0
  26. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/simulation/__simulation.py +36 -19
  27. adaptivetesting-1.2.2/adaptivetesting/tests/__init__.py +0 -0
  28. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_generate_response_pattern.py +12 -0
  29. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/utils/__funcs.py +1 -1
  30. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/utils/__plots.py +23 -17
  31. adaptivetesting-1.2.2/adaptivetesting.egg-info/PKG-INFO +122 -0
  32. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting.egg-info/SOURCES.txt +7 -0
  33. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting.egg-info/requires.txt +1 -1
  34. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/pyproject.toml +16 -3
  35. adaptivetesting-1.2b1/PKG-INFO +0 -374
  36. adaptivetesting-1.2b1/README.md +0 -353
  37. adaptivetesting-1.2b1/adaptivetesting.egg-info/PKG-INFO +0 -374
  38. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/LICENSE +0 -0
  39. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/data/__csv_context.py +0 -0
  40. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/data/__init__.py +0 -0
  41. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/data/__pickle_context.py +0 -0
  42. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/implementations/__default_implementation.py +0 -0
  43. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/implementations/__init__.py +0 -0
  44. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/implementations/__pre_test.py +0 -0
  45. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/implementations/__semi_implementation.py +0 -0
  46. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/__init__.py +0 -0
  47. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/content_balancing/__content_balancing.py +0 -0
  48. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/content_balancing/__init__.py +0 -0
  49. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/content_balancing/__maximum_priority_index.py +0 -0
  50. {adaptivetesting-1.2b1/adaptivetesting/tests → adaptivetesting-1.2.2/adaptivetesting/math/estimators/__functions}/__init__.py +0 -0
  51. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/estimators/__init__.py +0 -0
  52. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/estimators/__prior.py +0 -0
  53. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/exposure_control/__exposure_control.py +0 -0
  54. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/exposure_control/__init__.py +0 -0
  55. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/item_selection/__init__.py +0 -0
  56. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/math/item_selection/__urrys_rule.py +0 -0
  57. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/models/__algorithm_exception.py +0 -0
  58. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/models/__init__.py +0 -0
  59. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/models/__item_pool.py +0 -0
  60. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/models/__item_selection_exception.py +0 -0
  61. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/models/__misc.py +0 -0
  62. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/models/__test_result.py +0 -0
  63. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/services/__init__.py +0 -0
  64. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/services/__item_selection_protocol.py +0 -0
  65. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/services/__test_results_interface.py +0 -0
  66. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/simulation/__init__.py +0 -0
  67. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_adaptive_test.py +0 -0
  68. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_bayes_modal.py +0 -0
  69. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_content_balacing.py +0 -0
  70. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_descriptives.py +0 -0
  71. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_eap.py +0 -0
  72. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_exposure_control_mpi.py +0 -0
  73. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_full_tests.py +0 -0
  74. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_information_test.py +0 -0
  75. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_item_selection.py +0 -0
  76. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_load_test_items.py +0 -0
  77. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_mle.py +0 -0
  78. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_plots.py +0 -0
  79. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_poly_models.py +0 -0
  80. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_pretest.py +0 -0
  81. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_priors.py +0 -0
  82. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_simulation.py +0 -0
  83. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_standard_error.py +0 -0
  84. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_test_assembler.py +0 -0
  85. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/tests/test_weighted_penalty_model.py +0 -0
  86. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/utils/__descriptives.py +0 -0
  87. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting/utils/__init__.py +0 -0
  88. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting.egg-info/dependency_links.txt +0 -0
  89. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/adaptivetesting.egg-info/top_level.txt +0 -0
  90. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/setup.cfg +0 -0
  91. {adaptivetesting-1.2b1 → adaptivetesting-1.2.2}/setup.py +0 -0
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: adaptivetesting
3
+ Version: 1.2.2
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>
6
+ Project-URL: Homepage, https://github.com/condecon/adaptivetesting
7
+ Project-URL: Documentation, https://github.com/condecon/adaptivetesting/wiki
8
+ Project-URL: Repository, https://github.com/condecon/adaptivetesting.git
9
+ Project-URL: Issues, https://github.com/condecon/adaptivetesting/issues
10
+ Keywords: statistics,psychology,item-response-theory,computerized-adaptive-testing
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Python: >=3.13
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: numpy>=2.3.0
24
+ Requires-Dist: pandas>=2.3.0
25
+ Requires-Dist: scipy>=1.15.0
26
+ Requires-Dist: tqdm>=4.66.0
27
+ Requires-Dist: matplotlib>=3.10.0
28
+ Requires-Dist: numdifftools>=0.9.42
29
+ Dynamic: license-file
30
+
31
+ # adaptivetesting
32
+
33
+ <img src="/docs/_static/logo.svg" style="width: 100%">
34
+ </img>
35
+
36
+ **An open-source Python package for simplified, customizable Computerized Adaptive Testing (CAT) using Bayesian methods.**
37
+
38
+
39
+ ## Key Features
40
+
41
+ - **Bayesian Methods**: Built-in support for Bayesian ability estimation with customizable priors
42
+ - **Flexible Architecture**: Object-oriented design with abstract classes for easy extension
43
+ - **Item Response Theory**: Full support for 1PL, 2PL, 3PL, and 4PL models, GRM, GPCM
44
+ - **Multiple Estimators**:
45
+ - Maximum Likelihood Estimation (MLE)
46
+ - Bayesian Modal Estimation (BM)
47
+ - Expected A Posteriori (EAP)
48
+ - **Item Selection Strategies**: Maximum information criterion
49
+ - **Content Balancing**: Maximum Priority Index, Weighted Penalty Model
50
+ - **Exposure Control**: Randomesque Item Selection, Maximum Priority Index
51
+ - **Simulation Framework**: Comprehensive tools for CAT simulation and evaluation
52
+ - **Real-world Application**: Direct transition from simulation to production testing
53
+ - **Stopping Criteria**: Support for standard error and test length criteria
54
+ - **Data Management**: Built-in support for CSV and pickle data formats
55
+
56
+ ## Installation
57
+
58
+ Install from PyPI using pip:
59
+
60
+ ```bash
61
+ pip install adaptivetesting
62
+ ```
63
+
64
+ For the latest development version:
65
+
66
+ ```bash
67
+ pip install git+https://github.com/condecon/adaptivetesting
68
+ ```
69
+
70
+ ## Documentation
71
+ You can find our documentation in the [GitHub wiki](https://github.com/condecon/adaptivetesting/wiki).
72
+
73
+ ## Contributing
74
+
75
+ We welcome contributions! Please see our [GitHub repository](https://github.com/condecon/adaptivetesting) for:
76
+
77
+ - Issue tracking
78
+ - Feature requests
79
+ - Pull request guidelines
80
+ - Development setup
81
+
82
+ ## Research and Applications
83
+
84
+ This package is designed for researchers and practitioners in:
85
+
86
+ - Educational assessment
87
+ - Psychological testing
88
+ - Cognitive ability measurement
89
+ - Adaptive learning systems
90
+ - Psychometric research
91
+
92
+ The package facilitates the transition from research simulation to real-world testing applications without requiring major code modifications.
93
+
94
+ ## Citation
95
+ If you use this package for your academic work, please provide the following reference:
96
+ Engicht, J., Bee, R.M. & Koch, T. Customizable Bayesian adaptive testing with Python – The adaptivetesting package. Behav Res 58, 250 (2026). https://doi.org/10.3758/s13428-026-03079-w
97
+
98
+ ```
99
+ @Article{Engicht2026,
100
+ author="Engicht, Jonas
101
+ and Bee, R. Maximilian
102
+ and Koch, Tobias",
103
+ title="Customizable Bayesian adaptive testing with Python -- The adaptivetesting package",
104
+ journal="Behavior Research Methods",
105
+ year="2026",
106
+ month="Jul",
107
+ day="24",
108
+ volume="58",
109
+ number="9",
110
+ pages="250",
111
+ abstract="This paper introduces an open-source Python package for simplified, customizable computerized adaptive testing (CAT) using Bayesian methods for ability estimation. It addresses the lack of sophisticated packages for CAT in the Python programming language. Moreover, it bridges the gap between the construction and simulation of adaptive tests and their practical application by providing a dedicated API for integration with experiment software. Thereby, it eliminates the need for major code rewrites when transitioning from simulated to real-world adaptive testing. By leveraging Python's object-oriented programming approach, such as abstract classes, protocols, and inheritance, the package allows for easy extension and customization of its functionality. For example, Bayesian estimators can be modified to incorporate custom priors. This paper outlines the relevance and practical use of the adaptivetesting package through a walkthrough example. The package is fully documented, and its source code is published on GitHub. It is also available on the Python Package Index (PyPi) and conda-forge thus it can easily be installed using Python's package manager pip or conda. Leveraging R's reticulate package, adaptivetesting can also be accessed from within RStudio.",
112
+ issn="1554-3528",
113
+ doi="10.3758/s13428-026-03079-w",
114
+ url="https://doi.org/10.3758/s13428-026-03079-w"
115
+ }
116
+
117
+
118
+ ```
119
+
120
+ ## License
121
+
122
+ This project is licensed under the terms specified in the [LICENSE](LICENSE) file.
@@ -0,0 +1,92 @@
1
+ # adaptivetesting
2
+
3
+ <img src="/docs/_static/logo.svg" style="width: 100%">
4
+ </img>
5
+
6
+ **An open-source Python package for simplified, customizable Computerized Adaptive Testing (CAT) using Bayesian methods.**
7
+
8
+
9
+ ## Key Features
10
+
11
+ - **Bayesian Methods**: Built-in support for Bayesian ability estimation with customizable priors
12
+ - **Flexible Architecture**: Object-oriented design with abstract classes for easy extension
13
+ - **Item Response Theory**: Full support for 1PL, 2PL, 3PL, and 4PL models, GRM, GPCM
14
+ - **Multiple Estimators**:
15
+ - Maximum Likelihood Estimation (MLE)
16
+ - Bayesian Modal Estimation (BM)
17
+ - Expected A Posteriori (EAP)
18
+ - **Item Selection Strategies**: Maximum information criterion
19
+ - **Content Balancing**: Maximum Priority Index, Weighted Penalty Model
20
+ - **Exposure Control**: Randomesque Item Selection, Maximum Priority Index
21
+ - **Simulation Framework**: Comprehensive tools for CAT simulation and evaluation
22
+ - **Real-world Application**: Direct transition from simulation to production testing
23
+ - **Stopping Criteria**: Support for standard error and test length criteria
24
+ - **Data Management**: Built-in support for CSV and pickle data formats
25
+
26
+ ## Installation
27
+
28
+ Install from PyPI using pip:
29
+
30
+ ```bash
31
+ pip install adaptivetesting
32
+ ```
33
+
34
+ For the latest development version:
35
+
36
+ ```bash
37
+ pip install git+https://github.com/condecon/adaptivetesting
38
+ ```
39
+
40
+ ## Documentation
41
+ You can find our documentation in the [GitHub wiki](https://github.com/condecon/adaptivetesting/wiki).
42
+
43
+ ## Contributing
44
+
45
+ We welcome contributions! Please see our [GitHub repository](https://github.com/condecon/adaptivetesting) for:
46
+
47
+ - Issue tracking
48
+ - Feature requests
49
+ - Pull request guidelines
50
+ - Development setup
51
+
52
+ ## Research and Applications
53
+
54
+ This package is designed for researchers and practitioners in:
55
+
56
+ - Educational assessment
57
+ - Psychological testing
58
+ - Cognitive ability measurement
59
+ - Adaptive learning systems
60
+ - Psychometric research
61
+
62
+ The package facilitates the transition from research simulation to real-world testing applications without requiring major code modifications.
63
+
64
+ ## Citation
65
+ If you use this package for your academic work, please provide the following reference:
66
+ Engicht, J., Bee, R.M. & Koch, T. Customizable Bayesian adaptive testing with Python – The adaptivetesting package. Behav Res 58, 250 (2026). https://doi.org/10.3758/s13428-026-03079-w
67
+
68
+ ```
69
+ @Article{Engicht2026,
70
+ author="Engicht, Jonas
71
+ and Bee, R. Maximilian
72
+ and Koch, Tobias",
73
+ title="Customizable Bayesian adaptive testing with Python -- The adaptivetesting package",
74
+ journal="Behavior Research Methods",
75
+ year="2026",
76
+ month="Jul",
77
+ day="24",
78
+ volume="58",
79
+ number="9",
80
+ pages="250",
81
+ abstract="This paper introduces an open-source Python package for simplified, customizable computerized adaptive testing (CAT) using Bayesian methods for ability estimation. It addresses the lack of sophisticated packages for CAT in the Python programming language. Moreover, it bridges the gap between the construction and simulation of adaptive tests and their practical application by providing a dedicated API for integration with experiment software. Thereby, it eliminates the need for major code rewrites when transitioning from simulated to real-world adaptive testing. By leveraging Python's object-oriented programming approach, such as abstract classes, protocols, and inheritance, the package allows for easy extension and customization of its functionality. For example, Bayesian estimators can be modified to incorporate custom priors. This paper outlines the relevance and practical use of the adaptivetesting package through a walkthrough example. The package is fully documented, and its source code is published on GitHub. It is also available on the Python Package Index (PyPi) and conda-forge thus it can easily be installed using Python's package manager pip or conda. Leveraging R's reticulate package, adaptivetesting can also be accessed from within RStudio.",
82
+ issn="1554-3528",
83
+ doi="10.3758/s13428-026-03079-w",
84
+ url="https://doi.org/10.3758/s13428-026-03079-w"
85
+ }
86
+
87
+
88
+ ```
89
+
90
+ ## License
91
+
92
+ This project is licensed under the terms specified in the [LICENSE](LICENSE) file.
@@ -42,4 +42,14 @@ from .services.__item_selection_protocol import ItemSelectionStrategy
42
42
 
43
43
  from .simulation.__simulation import Simulation, SimulationPool, setup_simulation_and_start
44
44
 
45
- from .utils.__descriptives import bias, average_absolute_deviation, rmse
45
+ from .utils.__descriptives import bias, average_absolute_deviation, rmse
46
+ from .utils.__funcs import load_final_test_results, load_test_results_single_participant
47
+ from .utils.__plots import (
48
+ plot_exposure_rate,
49
+ plot_final_ability_estimates,
50
+ plot_icc,
51
+ plot_iif,
52
+ plot_exposure_rate,
53
+ plot_test_information,
54
+ plot_theta_estimation_trace
55
+ )
@@ -9,8 +9,16 @@ from .__pickle_context import PickleContext
9
9
  def read_prev_items(format: Literal["CSV", "PICKLE"],
10
10
  test_id: str,
11
11
  participant_ids: list[str]) -> list[TestItem]:
12
- """Read previously administered items
13
- to perform exposure control
12
+ """Read previously administered items.
13
+ This function is internally used to perform exposure control.
14
+
15
+ Args:
16
+ format (Literal["CSV", "PICKLE"]): The format of the results: CSV or PICKLE.
17
+ test_id (str): The test ID / simulation ID.
18
+ participant_ids (list[str]): The participant IDs.
19
+
20
+ Returns:
21
+ list[TestItem]: A list of TestItem objects.
14
22
  """
15
23
  all_shown_test_items = []
16
24
 
@@ -23,9 +31,21 @@ def read_prev_items(format: Literal["CSV", "PICKLE"],
23
31
  return all_shown_test_items
24
32
 
25
33
 
26
- @deprecated("This function should be replaced after merging"
27
- " with revision branch")
34
+ @deprecated("This function will be removed in future releases.")
28
35
  def read_single_participant(test_id: str, participant_id: str, format: Literal["CSV", "PICKLE"]) -> list[TestResult]:
36
+ """
37
+ Reads the test results of a particular participant.
38
+ Args:
39
+ test_id (str): The test ID / simulation ID.
40
+ participant_id (str): The participant ID.
41
+ format (Literal["CSV", "PICKLE"]): The format of the results: CSV or PICKLE.
42
+
43
+ Returns:
44
+ list[TestResult]: A list of TestResult objects.
45
+
46
+ Raises:
47
+ ValueError: If the format is not supported.
48
+ """
29
49
  context: CSVContext | PickleContext
30
50
  if format == "CSV":
31
51
  context = CSVContext(test_id, participant_id)
@@ -33,3 +53,4 @@ def read_single_participant(test_id: str, participant_id: str, format: Literal["
33
53
  if format == "PICKLE":
34
54
  context = PickleContext(test_id, participant_id)
35
55
  return context.load()
56
+ raise ValueError(f"Unknown format: {format}")
@@ -133,6 +133,15 @@ class TestAssembler(AdaptiveTest):
133
133
  content_balancing (CONTENT_BALANCING, optional): Selected content balancing strategy. Defaults to None.
134
134
  If a content balancing strategy is specified, the item selection strategy will be ignored.
135
135
 
136
+ content_balancing_args (ContentBalancingArgs, optional): Arguments for the content balancing strategy.
137
+ Defaults to None.
138
+
139
+ exposure_control (EXPOSURE_CONTROL, optional): Exposure control strategy. Defaults to None.
140
+ If an exposure control strategy is specified, the item selection strategy will be ignored.
141
+
142
+ exposure_control_args (ExposureControlArgs, optional): Arguments for the exposure control strategy.
143
+ Defaults to None.
144
+
136
145
  pretest (bool, optional): Whether to run a pretest phase before the main test. Defaults to False.
137
146
 
138
147
  pretest_seed (int | None, optional): Random seed for pretest item selection. Defaults to None.
@@ -166,6 +175,7 @@ class TestAssembler(AdaptiveTest):
166
175
  initial_ability_level,
167
176
  simulation,
168
177
  debug,
178
+ model_type=model_type,
169
179
  **kwargs)
170
180
 
171
181
  def estimate_ability_level(self):
@@ -345,7 +355,7 @@ class TestAssembler(AdaptiveTest):
345
355
  The result of the superclass's run_test_once() method.
346
356
  """
347
357
  # check if to run pretest
348
- if self.__pretest is True:
358
+ if self.__pretest:
349
359
  pretest = PreTest(
350
360
  self.item_pool.test_items,
351
361
  self.__pretest_seed
@@ -353,7 +363,7 @@ class TestAssembler(AdaptiveTest):
353
363
  # get selected items
354
364
  random_items = pretest.select_random_item_quantile()
355
365
  for item in random_items:
356
- if self.simulation is True:
366
+ if self.simulation:
357
367
  response = self.item_pool.get_item_response(item)
358
368
  else:
359
369
  # not simulation
@@ -13,7 +13,7 @@ def generate_response_pattern(ability: float,
13
13
  model: Literal["GRM", "GPCM"] | None = None,
14
14
  seed: int | None = None) -> list[int]:
15
15
  """Generates a response pattern for a given ability level
16
- and item difficulties. Also, a seed can be set.
16
+ and item difficulties. A seed may be set for reproducibility.
17
17
 
18
18
  Args:
19
19
  ability (float): participants ability
@@ -48,7 +48,19 @@ def generate_response_pattern(ability: float,
48
48
 
49
49
  def gen_pattern_dichotomous(ability: float,
50
50
  items: list[TestItem],
51
- rng: np.random.RandomState):
51
+ rng: np.random.RandomState) -> list[int]:
52
+ """
53
+ Generates a response pattern for a given ability level for an IRT
54
+ model with dichotomous test items (e.g., 4PL, 3PL).
55
+
56
+ Args:
57
+ ability (float): participants ability
58
+ items (list[TestItem]): test items
59
+ rng (np.random.RandomState): random state (numpy object)
60
+
61
+ Returns:
62
+ list[int]: generated response pattern
63
+ """
52
64
  responses: list[int] = []
53
65
 
54
66
  for item in items:
@@ -82,6 +94,18 @@ def gen_pattern_poly(
82
94
  model: Literal["GRM", "GPCM"],
83
95
  rng: np.random.RandomState
84
96
  ) -> list[int]:
97
+ """
98
+ Generates a response pattern for a given ability level for polytomous items.
99
+
100
+ Args:
101
+ ability (float): participants ability
102
+ items (list[TestItem]): test items
103
+ model (literal, optional): model type (GRM, or GPCM)
104
+ rng (np.random.RandomState, optional): random state (numpy object)
105
+
106
+ Returns:
107
+ list[int]: response pattern
108
+ """
85
109
  responses: list[int] = []
86
110
  # loop through all items
87
111
  for item in items:
@@ -3,7 +3,7 @@ from dataclasses import dataclass
3
3
 
4
4
  @dataclass
5
5
  class Constraint:
6
- """Constraint for Exposure Constrol and Content Balancing
6
+ """Constraint for Exposure Control and Content Balancing
7
7
  """
8
8
  name: str
9
9
  """Constraint name. This string has to be the same
@@ -21,6 +21,7 @@ def compute_priority_index(item: TestItem,
21
21
  required_items (int): number of items required to be shown per constraint
22
22
  shown_items (int): number of items already shown per constraint
23
23
  current_ability (float): currently estimated ability level
24
+ model (Literal['GRM', 'GPCM'] | None): model type. Required for polytomous items.
24
25
 
25
26
  Returns:
26
27
  float: priority index of an item
@@ -103,7 +104,7 @@ def compute_expected_difference(proportion: float,
103
104
  constraint_target (float): constraint target
104
105
 
105
106
  Returns:
106
- float: _description_
107
+ float: expected difference
107
108
  """
108
109
  expected_difference = proportion - constraint_target
109
110
  return expected_difference
@@ -154,9 +155,9 @@ def compute_total_content_penalty_value_for_item(item: TestItem,
154
155
 
155
156
  Args:
156
157
  item (TestItem): given test item
157
- shown_items:
158
- available_items:
159
- constraints (list[Constraint]): YYYYYY
158
+ shown_items: shown item
159
+ available_items: available items in the test
160
+ constraints (list[Constraint]): constraints assigned to the test and its items
160
161
 
161
162
  Returns:
162
163
  float: total content penalty value
@@ -21,7 +21,7 @@ ITEM_GROUP = Literal["green", "orange", "yellow", "red", None]
21
21
  class WeightedPenaltyModel(ContentBalancing):
22
22
  """This content balancing method follows Shin et al. (2009)
23
23
  and allows to apply constraints to the item selection.
24
- The users can define a custom weight for the item information and the constriant.
24
+ The users can define a custom weight for the item information and the constraint.
25
25
 
26
26
  References
27
27
  ------------
@@ -98,7 +98,7 @@ class WeightedPenaltyModel(ContentBalancing):
98
98
  max_content_penalty = max(content_penalties)
99
99
  min_content_penalty = min(content_penalties)
100
100
 
101
- self.calcualte_weighted_penalty_for_all_items(
101
+ self.calculate_weighted_penalty_for_all_items(
102
102
  item_information_list=item_information_list,
103
103
  max_item=max_item,
104
104
  content_penalties=content_penalties,
@@ -114,7 +114,16 @@ class WeightedPenaltyModel(ContentBalancing):
114
114
  # order items
115
115
  self.order_candidate_items()
116
116
 
117
- def calculate_information(self, model: Literal['GRM', 'GPCM'] | None = None) -> list[float]:
117
+ def calculate_information(self,
118
+ model: Literal['GRM', 'GPCM'] | None = None) -> list[float]:
119
+ """
120
+ Calculates the item information for every item
121
+ Args:
122
+ model: model type. Required for polytomous models.
123
+
124
+ Returns:
125
+ list of item information
126
+ """
118
127
  information_list = [
119
128
  float(item_information_function(
120
129
  ability=self.ability,
@@ -126,6 +135,7 @@ class WeightedPenaltyModel(ContentBalancing):
126
135
  return information_list
127
136
 
128
137
  def calculate_content_penalties(self) -> list[float]:
138
+ """Calculate content penalty values for every item"""
129
139
  content_penalties = [
130
140
  compute_total_content_penalty_value_for_item(
131
141
  item=item,
@@ -137,12 +147,13 @@ class WeightedPenaltyModel(ContentBalancing):
137
147
  ]
138
148
  return content_penalties
139
149
 
140
- def calcualte_weighted_penalty_for_all_items(self,
150
+ def calculate_weighted_penalty_for_all_items(self,
141
151
  item_information_list: list[float],
142
152
  max_item: float,
143
153
  content_penalties: list[float],
144
154
  max_content_penalty: float,
145
155
  min_content_penalty: float):
156
+ """Calculate the weighted penalty value for every item"""
146
157
  for i, item in enumerate(self.items):
147
158
  weighted_penalty_value = self.calculate_weighted_penalty_value(
148
159
  item_information=item_information_list[i],
@@ -157,6 +168,8 @@ class WeightedPenaltyModel(ContentBalancing):
157
168
  self.eligible_items.append((item, weighted_penalty_value, None))
158
169
 
159
170
  def get_constraint_group_assignments(self) -> list[tuple[Constraint, CONSTRAINT_GROUP]]:
171
+ """Assign every constraint to a constraint group depending on the number
172
+ of items shown from each specific constraint."""
160
173
  group_assignment: list[tuple[Constraint, CONSTRAINT_GROUP]] = []
161
174
  for constraint in self.constraints:
162
175
  # calculate proportion of the constraint
@@ -183,6 +196,15 @@ class WeightedPenaltyModel(ContentBalancing):
183
196
  @staticmethod
184
197
  def assign_color_group_per_proportion(constraint: Constraint,
185
198
  prop: float):
199
+ """
200
+ Implements constraint assignment logic.
201
+ Args:
202
+ constraint (Constraint): constraint
203
+ prop (float): proportion
204
+
205
+ Returns:
206
+ tuple[Constraint, str]: constraint and assigned group
207
+ """
186
208
  if constraint.lower is not None and constraint.upper is not None:
187
209
  if prop <= constraint.lower:
188
210
  return (constraint, "A")
@@ -195,6 +217,12 @@ class WeightedPenaltyModel(ContentBalancing):
195
217
 
196
218
  def form_list_of_candidate_items(self,
197
219
  group_assignment: list[tuple[Constraint, CONSTRAINT_GROUP]]) -> None:
220
+ """
221
+ Form a list of candidate items according to the constraint group assignment.
222
+ Args:
223
+ group_assignment (list[tuple[Constraint, CONSTRAINT_GROUP]): list of constraints and assigned groups
224
+
225
+ """
198
226
  for i, item_entry in enumerate(self.eligible_items):
199
227
  item, weighted_penalty_value, _ = item_entry
200
228
  # find associated constraint
@@ -214,6 +242,17 @@ class WeightedPenaltyModel(ContentBalancing):
214
242
  associated_constraints: list[tuple[Constraint, Literal['A', 'B', 'C']]],
215
243
  weighted_penalty_value: float
216
244
  ):
245
+ """
246
+ Assign items according to the constraint group assignment to an item group (color group)
247
+ Args:
248
+ item (TestItem): item
249
+ associated_constraints (list[tuple[Constraint, Literal['A', 'B', 'C']]]):
250
+ constraints relevant for this item
251
+ weighted_penalty_value (float): calculated weighted penalty value of this item
252
+
253
+ Returns:
254
+ tuple[TestItem, float, str]: item, weighted penalty value and assigned color
255
+ """
217
256
  group_set = set([group for _, group in associated_constraints])
218
257
 
219
258
  # if all associated constraints A or B -> green group
@@ -232,6 +271,8 @@ class WeightedPenaltyModel(ContentBalancing):
232
271
  return (item, weighted_penalty_value, None)
233
272
 
234
273
  def order_candidate_items(self):
274
+ """Order candidate items according to the color group assignment.
275
+ """
235
276
  # between group ordering: green, orange, yellow, red
236
277
  # within group ordering: ascending order of weighted penalty value
237
278
  self.eligible_items = sorted(
@@ -251,6 +292,20 @@ class WeightedPenaltyModel(ContentBalancing):
251
292
  constraint_weight: float,
252
293
  information_weight: float
253
294
  ) -> float:
295
+ """
296
+ Calculate the weighted penalty value of an item.
297
+ Args:
298
+ content_penalty: content penalty value
299
+ minimum_total_content_penalty: minimum total content penalty value
300
+ maximum_total_content_penalty: maximum total content penalty value
301
+ item_information: item information
302
+ max_information: maximum item information
303
+ constraint_weight: constraint weight
304
+ information_weight: information weight
305
+
306
+ Returns:
307
+ weighted penalty value
308
+ """
254
309
  # reference content penalty
255
310
  total_content_penalty_value = content_penalty
256
311
 
@@ -31,6 +31,8 @@ class BayesModal(IEstimator):
31
31
  prior (Prior): prior distribution
32
32
 
33
33
  optimization_interval (Tuple[float, float]): interval used for the optimization function
34
+
35
+ model (Literal["GRM", "GPCM"], optional): model type (required for polytomous models)
34
36
  """
35
37
  super().__init__(response_pattern, items, optimization_interval)
36
38
 
@@ -31,6 +31,8 @@ class ExpectedAPosteriori(BayesModal):
31
31
  prior (Prior): prior distribution
32
32
 
33
33
  optimization_interval (Tuple[float, float]): interval used for the optimization function
34
+
35
+ model (Literal["GRM", "GPCM"], optional): model type (required for polytomous models)
34
36
  """
35
37
  super().__init__(response_pattern, items, prior, optimization_interval)
36
38
 
@@ -0,0 +1,74 @@
1
+ import numpy as np
2
+ from scipy.optimize import minimize_scalar, OptimizeResult # type: ignore
3
+ from ..__prior import Prior
4
+ from ....models.__algorithm_exception import AlgorithmException
5
+ from .__estimators import log_likelihood
6
+ from .__estimators import probability_y0, probability_y1
7
+
8
+
9
+ def maximize_posterior(
10
+ a: np.ndarray,
11
+ b: np.ndarray,
12
+ c: np.ndarray,
13
+ d: np.ndarray,
14
+ response_pattern: np.ndarray,
15
+ prior: Prior,
16
+ optimization_interval: tuple[float, float] = (-10, 10)
17
+ ) -> float:
18
+ """Get the maximum of the posterior distribution
19
+
20
+ Args:
21
+ a (np.ndarray): item parameter a
22
+ b (np.ndarray): item parameter b
23
+ c (np.ndarray): item parameter c
24
+ d (np.ndarray): item parameter d
25
+ response_pattern (np.ndarray): response pattern (simulated or user generated)
26
+ prior (Prior): prior distribution
27
+ optimization_interval (Tuple[float, float]): interval used for the optimization function
28
+
29
+ Returns:
30
+ float: Bayes Modal estimator for the given parameters
31
+ """
32
+ def log_posterior(mu):
33
+ log_likelihood_res = log_likelihood(mu, a, b, c, d, response_pattern)
34
+
35
+ if hasattr(prior, "logpdf"):
36
+ log_prior = prior.logpdf(mu)
37
+ else:
38
+ log_prior = np.log(np.clip(prior.pdf(mu), 1e-300, None))
39
+
40
+ log_post = log_likelihood_res + log_prior
41
+
42
+ if not np.isfinite(log_post):
43
+ return -1e300
44
+ else:
45
+ return float(log_post.ravel()[0])
46
+
47
+ result: OptimizeResult = minimize_scalar(lambda mu: -log_posterior(mu),
48
+ bounds=optimization_interval,
49
+ method="bounded") # type: ignore
50
+
51
+ if not result.success:
52
+ raise AlgorithmException(f"Optimization failed: {result.message}")
53
+
54
+ else:
55
+ return float(result.x)
56
+
57
+
58
+ def likelihood(mu: np.ndarray,
59
+ a: np.ndarray,
60
+ b: np.ndarray,
61
+ c: np.ndarray,
62
+ d: np.ndarray,
63
+ response_pattern: np.ndarray
64
+ ):
65
+
66
+ p1 = probability_y1(mu, a, b, c, d)
67
+ p0 = probability_y0(mu, a, b, c, d)
68
+
69
+ log_likelihood = np.sum(
70
+ (response_pattern * np.log(p1 + 1e-300)) + (
71
+ (1 - response_pattern) * np.log(p0 + 1e-300)
72
+ ))
73
+
74
+ return -np.exp(log_likelihood)