adaptivetesting 1.1.2__tar.gz → 1.1.4__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 (96) hide show
  1. adaptivetesting-1.1.4/CHANGELOG.md +10 -0
  2. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/PKG-INFO +1 -1
  3. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/estimators/__functions/__bayes.py +12 -9
  4. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/estimators/__functions/__estimators.py +49 -21
  5. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/estimators/__test_information.py +8 -18
  6. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/pyproject.toml +1 -1
  7. adaptivetesting-1.1.2/CHANGELOG.md +0 -4
  8. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.github/CONTRIBUTING.md +0 -0
  9. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.github/workflows/publish.yml +0 -0
  10. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.github/workflows/python-test.yml +0 -0
  11. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.gitignore +0 -0
  12. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.gitlab-ci.yml +0 -0
  13. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.python-version +0 -0
  14. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.readthedocs.yml +0 -0
  15. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.vscode/extensions.json +0 -0
  16. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/.vscode/settings.json +0 -0
  17. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/LICENSE +0 -0
  18. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/README.md +0 -0
  19. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/__init__.py +0 -0
  20. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/data/__csv_context.py +0 -0
  21. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/data/__init__.py +0 -0
  22. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/data/__pickle_context.py +0 -0
  23. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/implementations/__default_implementation.py +0 -0
  24. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/implementations/__init__.py +0 -0
  25. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/implementations/__pre_test.py +0 -0
  26. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/implementations/__semi_implementation.py +0 -0
  27. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/implementations/__test_assembler.py +0 -0
  28. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/__gen_response_pattern.py +0 -0
  29. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/__init__.py +0 -0
  30. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/estimators/__bayes_modal_estimation.py +0 -0
  31. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/estimators/__expect_a_posteriori.py +0 -0
  32. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/estimators/__init__.py +0 -0
  33. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/estimators/__ml_estimation.py +0 -0
  34. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/estimators/__prior.py +0 -0
  35. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/item_selection/__init__.py +0 -0
  36. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/item_selection/__maximum_information_criterion.py +0 -0
  37. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/math/item_selection/__urrys_rule.py +0 -0
  38. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/models/__adaptive_test.py +0 -0
  39. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/models/__algorithm_exception.py +0 -0
  40. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/models/__init__.py +0 -0
  41. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/models/__item_pool.py +0 -0
  42. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/models/__item_selection_exception.py +0 -0
  43. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/models/__misc.py +0 -0
  44. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/models/__test_item.py +0 -0
  45. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/models/__test_result.py +0 -0
  46. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/services/__estimator_interface.py +0 -0
  47. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/services/__init__.py +0 -0
  48. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/services/__item_selection_protocol.py +0 -0
  49. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/services/__test_results_interface.py +0 -0
  50. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/simulation/__init__.py +0 -0
  51. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/simulation/__simulation.py +0 -0
  52. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/__init__.py +0 -0
  53. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_adaptive_test.py +0 -0
  54. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_bayes_modal.py +0 -0
  55. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_eap.py +0 -0
  56. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_generate_response_pattern.py +0 -0
  57. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_information_test.py +0 -0
  58. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_item_selection.py +0 -0
  59. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_load_test_items.py +0 -0
  60. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_mle.py +0 -0
  61. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_pretest.py +0 -0
  62. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_simulation.py +0 -0
  63. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_standard_error.py +0 -0
  64. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/adaptivetesting/tests/test_test_assembler.py +0 -0
  65. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/Makefile +0 -0
  66. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/data.md +0 -0
  67. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/implementations.md +0 -0
  68. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/index.md +0 -0
  69. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/make.bat +0 -0
  70. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/math.md +0 -0
  71. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/models.md +0 -0
  72. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/readme.md +0 -0
  73. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/requirements.txt +0 -0
  74. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/services.md +0 -0
  75. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/simulation.md +0 -0
  76. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/_static/logo.svg +0 -0
  77. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/_static/main.css +0 -0
  78. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/conf.py +0 -0
  79. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/data.rst +0 -0
  80. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/implementations.rst +0 -0
  81. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/index.rst +0 -0
  82. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/math.rst +0 -0
  83. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/models.rst +0 -0
  84. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/services.rst +0 -0
  85. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/simulation.rst +0 -0
  86. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/source/tests.rst +0 -0
  87. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/docs/tests.md +0 -0
  88. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/examples/.gitignore +0 -0
  89. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/examples/simulation.py +0 -0
  90. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/examples/testing.py +0 -0
  91. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/images/default.svg +0 -0
  92. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/images/semi-adaptive.svg +0 -0
  93. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/mypy.ini +0 -0
  94. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/requirements.txt +0 -0
  95. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/setup.cfg +0 -0
  96. {adaptivetesting-1.1.2 → adaptivetesting-1.1.4}/uv.lock +0 -0
@@ -0,0 +1,10 @@
1
+ # Version 1.1.4
2
+ - Bug Fix #43 (optimization interval in `maximize_posterior` function)
3
+
4
+ # Version 1.1.3
5
+ - Bug Fix #34 (numerical stability issue concerning the ability estimation and standard error calculation)
6
+
7
+ # Version 1.1.2
8
+
9
+ - The `ItemPool` class now correctly honors the `id` field of items. (#30, #31)
10
+ - Examples and the readme file have been updated accordingly.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adaptivetesting
3
- Version: 1.1.2
3
+ Version: 1.1.4
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
5
  Project-URL: Homepage, https://github.com/condecon/adaptivetesting
6
6
  Project-URL: Documentation, https://github.com/condecon/adaptivetesting/tree/main/docs
@@ -11,22 +11,25 @@ def maximize_posterior(
11
11
  c: np.ndarray,
12
12
  d: np.ndarray,
13
13
  response_pattern: np.ndarray,
14
- prior: Prior
14
+ prior: Prior,
15
+ optimization_interval: tuple[float, float] = (-10, 10)
15
16
  ) -> float:
16
17
  """_summary_
17
18
 
18
19
  Args:
19
- a (np.ndarray): _description_
20
-
21
- b (np.ndarray): _description_
20
+ a (np.ndarray): item parameter a
21
+
22
+ b (np.ndarray): item parameter b
22
23
 
23
- c (np.ndarray): _description_
24
+ c (np.ndarray): item parameter c
24
25
 
25
- d (np.ndarray): _description_
26
+ d (np.ndarray): item parameter d
26
27
 
27
- response_pattern (np.ndarray): _description_
28
+ response_pattern (np.ndarray): response pattern (simulated or user generated)
28
29
 
29
- prior (Prior): _description_
30
+ prior (Prior): prior distribution
31
+
32
+ optimization_interval (Tuple[float, float]): interval used for the optimization function
30
33
 
31
34
  Returns:
32
35
  float: Bayes Modal estimator for the given parameters
@@ -35,7 +38,7 @@ def maximize_posterior(
35
38
  return likelihood(mu, a, b, c, d, response_pattern) * prior.pdf(mu)
36
39
 
37
40
  result: OptimizeResult = minimize_scalar(lambda mu: posterior(mu),
38
- bounds=(-10, 10),
41
+ bounds=optimization_interval,
39
42
  method="bounded") # type: ignore
40
43
 
41
44
  if not result.success:
@@ -12,22 +12,50 @@ def probability_y1(mu: np.ndarray,
12
12
 
13
13
  Args:
14
14
  mu (np.ndarray): latent ability level
15
-
16
15
  a (np.ndarray): item discrimination parameter
17
-
18
16
  b (np.ndarray): item difficulty parameter
19
-
20
17
  c (np.ndarray): pseudo guessing parameter
21
-
22
18
  d (np.ndarray): inattention parameter
23
19
 
24
20
  Returns:
25
21
  np.ndarray: probability of getting the item correct
26
22
  """
23
+ # Compute the exponent safely
24
+ z = a * (mu - b)
25
+
26
+ # Use log-sum-exp trick for numerical stability
27
+ # For large positive z: exp(z)/(1+exp(z)) ≈ 1
28
+ # For large negative z: exp(z)/(1+exp(z)) ≈ exp(z)
29
+
30
+ # Clip z to prevent overflow in exp()
31
+ z_clipped = np.clip(z, -500, 500) # exp(-500) and exp(500) are safe
32
+
33
+ # Compute the logistic function safely
34
+ # Use different formulas based on the sign of z for stability
35
+ result = np.empty_like(z_clipped)
36
+
37
+ # For large positive values, use alternative formula to avoid overflow
38
+ mask_positive = z_clipped > 20 # exp(20) is large but manageable
39
+ mask_negative = z_clipped < -20 # exp(-20) is very small
40
+ mask_medium = ~(mask_positive | mask_negative)
41
+
42
+ # Large positive z: 1/(1+exp(-z)) is more stable
43
+ result[mask_positive] = 1.0 / (1.0 + np.exp(-z_clipped[mask_positive]))
44
+
45
+ # Large negative z: exp(z)/(1+exp(z)) is stable
46
+ exp_z_neg = np.exp(z_clipped[mask_negative])
47
+ result[mask_negative] = exp_z_neg / (1.0 + exp_z_neg)
48
+
49
+ # Medium values: use standard formula
50
+ exp_z_med = np.exp(z_clipped[mask_medium])
51
+ result[mask_medium] = exp_z_med / (1.0 + exp_z_med)
52
+
53
+ # Apply the 4PL transformation
54
+ value = c + (d - c) * result
55
+
56
+ # Ensure probabilities are within valid range [c, d]
57
+ value = np.clip(value, c, d)
27
58
 
28
- value = c + (d - c) * (np.exp(a * (mu - b))) / \
29
- (1 + np.exp(a * (mu - b)))
30
-
31
59
  return np.squeeze(value)
32
60
 
33
61
 
@@ -40,13 +68,13 @@ def probability_y0(mu: np.ndarray,
40
68
 
41
69
  Args:
42
70
  mu (np.ndarray): latent ability level
43
-
71
+
44
72
  a (np.ndarray): item discrimination parameter
45
-
73
+
46
74
  b (np.ndarray): item difficulty parameter
47
-
75
+
48
76
  c (np.ndarray): pseudo guessing parameter
49
-
77
+
50
78
  d (np.ndarray): inattention parameter
51
79
 
52
80
  Returns:
@@ -68,13 +96,13 @@ def likelihood(mu: np.ndarray,
68
96
 
69
97
  Args:
70
98
  mu (np.ndarray): ability level
71
-
99
+
72
100
  a (np.ndarray): item discrimination parameter
73
-
101
+
74
102
  b (np.ndarray): item difficulty parameter
75
-
103
+
76
104
  c (np.ndarray): pseudo guessing parameter
77
-
105
+
78
106
  d (np.ndarray): inattention parameter
79
107
 
80
108
  Returns:
@@ -88,7 +116,7 @@ def likelihood(mu: np.ndarray,
88
116
 
89
117
  terms = (probability_y1(mu, a, b, c, d)**response_pattern) * \
90
118
  (probability_y0(mu, a, b, c, d) ** (1 - response_pattern))
91
-
119
+
92
120
  return -np.prod(terms)
93
121
 
94
122
 
@@ -100,14 +128,14 @@ def maximize_likelihood_function(a: np.ndarray,
100
128
  border: tuple[float, float] = (-10, 10)) -> float:
101
129
  """Find the ability value that maximizes the likelihood function.
102
130
  This function uses the minimize_scalar function from scipy and the "bounded" method.
103
-
131
+
104
132
  Args:
105
133
  a (np.ndarray): item discrimination parameter
106
-
134
+
107
135
  b (np.ndarray): item difficulty parameter
108
-
136
+
109
137
  c (np.ndarray): pseudo guessing parameter
110
-
138
+
111
139
  d (np.ndarray): inattention parameter
112
140
 
113
141
  response_pattern (np.ndarray): response pattern of the item
@@ -129,7 +157,7 @@ def maximize_likelihood_function(a: np.ndarray,
129
157
  "Response pattern is invalid. It consists of only one type of response.")
130
158
  raise AlgorithmException(
131
159
  "Response pattern is invalid. It consists of only one type of response.")
132
-
160
+
133
161
  result: OptimizeResult = minimize_scalar(likelihood, args=(a, b, c, d, response_pattern),
134
162
  bounds=border, method='bounded') # type: ignore
135
163
 
@@ -13,27 +13,17 @@ def item_information_function(
13
13
  c: np.ndarray,
14
14
  d: np.ndarray
15
15
  ) -> np.ndarray:
16
- """Calculate the information of a test item given the currently
17
- estimated ability `mu`.
18
-
19
- Args:
20
- mu (np.ndarray): currently estimated ability
21
- a (np.ndarray): _description_
22
- b (np.ndarray): _description_
23
- c (np.ndarray): _description_
24
- d (np.ndarray): _description_
25
-
26
- Returns:
27
- np.ndarray: _description_
28
- """
29
16
  p_y1 = probability_y1(mu, a, b, c, d)
30
17
 
18
+ # Clip probabilities
19
+ p_y1_clipped = np.clip(p_y1, 1e-10, 1 - 1e-10)
20
+
31
21
  def p_y1_grad(x: np.ndarray) -> np.ndarray:
32
- # Central finite difference for gradient
33
- h = 1e-5
22
+ # Use a more robust finite difference scheme
23
+ h = np.maximum(1e-8, 1e-8 * np.abs(x)) # Adaptive step size
34
24
  return (probability_y1(x + h, a, b, c, d) - probability_y1(x - h, a, b, c, d)) / (2 * h)
35
-
36
- product = (p_y1_grad(mu) ** 2) / (p_y1 * (1 - p_y1))
25
+
26
+ product = (p_y1_grad(mu) ** 2) / (p_y1_clipped * (1 - p_y1_clipped))
37
27
  information = np.sum(product)
38
28
  return information
39
29
 
@@ -59,7 +49,7 @@ def prior_information_function(prior: Prior,
59
49
  (score_values ** 2) * prior.pdf(x_vals),
60
50
  x_vals
61
51
  )
62
-
52
+
63
53
  return information
64
54
 
65
55
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name="adaptivetesting"
3
- version="1.1.2"
3
+ version="1.1.4"
4
4
  description="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
5
  readme = "README.md"
6
6
  authors = [
@@ -1,4 +0,0 @@
1
- # Version 1.1.2
2
-
3
- - The `ItemPool` class now correctly honors the `id` field of items. (#30, #31)
4
- - Examples and the readme file have been updated accordingly.
File without changes
File without changes