1010# of Health and Human Services, which is making the software available to the
1111# public for any commercial or non-commercial purpose under the following
1212# open-source BSD license.
13- #
13+ #
1414# Redistribution and use in source and binary forms, with or without
1515# modification, are permitted provided that the following conditions are met:
16- #
16+ #
1717# (1) Redistributions of source code must retain this copyright
1818# notice, this list of conditions and the following disclaimer.
19- #
19+ #
2020# (2) Redistributions in binary form must reproduce this copyright
2121# notice, this list of conditions and the following disclaimer in the
2222# documentation and/or other materials provided with the distribution.
23- #
23+ #
2424# (3) Neither the names of the National Institutes of Health Clinical
2525# Center, the National Institutes of Health, the U.S. Department of
2626# Health and Human Services, nor the names of any of the software
2727# developers may be used to endorse or promote products derived from
2828# this software without specific prior written permission.
29- #
29+ #
3030# (4) Please acknowledge NIHCC as the source of this software by including
3131# the phrase "Courtesy of the U.S. National Institutes of Health Clinical
3232# Center"or "Source: U.S. National Institutes of Health Clinical Center."
33- #
33+ #
3434# THIS SOFTWARE IS PROVIDED BY THE U.S. GOVERNMENT AND CONTRIBUTORS "AS
3535# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
3636# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
3737# PARTICULAR PURPOSE ARE DISCLAIMED.
38- #
38+ #
3939# You are under no obligation whatsoever to provide any bug fixes,
4040# patches, or upgrades to the features, functionality or performance of
4141# the source code ("Enhancements") to anyone; however, if you choose to
5454import numpy as np
5555
5656from libfabulouscatpy .cat .itemselection import ItemSelector
57+ from libfabulouscatpy .cat .session import CatSessionTracker
5758from libfabulouscatpy .irt .scoring import BayesianScoring
5859
5960
@@ -67,14 +68,14 @@ def __init__(self, scoring, deterministic=True, hybrid=False, **kwargs):
6768 self .hybrid = hybrid
6869 self .deterministic = deterministic
6970
70- def criterion (self , scoring : BayesianScoring , items : list [dict ], scale = None ) -> dict [str : Any ]:
71-
71+ def criterion (
72+ self , scoring : BayesianScoring , items : list [dict ], scale = None
73+ ) -> dict [str :Any ]:
7274 """
7375 Parameters: session: instance of CatSession
7476 Returns: item dictionary entry or None
7577 """
7678
77-
7879 unresponded = [i for i in items if "scales" in i .keys ()]
7980 in_scale = [i for i in unresponded if scale in i ["scales" ].keys ()]
8081
@@ -105,12 +106,58 @@ def criterion(self, scoring: BayesianScoring, items: list[dict], scale=None) ->
105106 )[
106107 :, unresponded_ndx , :
107108 ] #
108-
109+
109110 p_itemized = np .exp (lp_itemized )
110111 pi_density = scoring .scores [scale ].density
111112
112- criterion = np .sum (p_itemized * (lp_itemized - lp_point )* pi_density [:, np .newaxis , np .newaxis ], axis = 0 )
113+ criterion = np .trapz (
114+ p_itemized
115+ * (lp_itemized - lp_point )
116+ * pi_density [:, np .newaxis , np .newaxis ],
117+ x = scoring .interpolation_pts [scale ],
118+ axis = 0 ,
119+ )
113120 criterion = np .sum (criterion , axis = - 1 )
114- criterion = dict (zip ([x [' item' ] for x in items ], criterion ))
121+ criterion = dict (zip ([x [" item" ] for x in items ], criterion ))
115122 return criterion
116-
123+
124+ def _next_scored_item (
125+ self , tracker : CatSessionTracker , scale = None
126+ ) -> dict [str : dict [str :Any ]]:
127+
128+ scale = self .next_scale (tracker )
129+ un_items = self .un_items (tracker , scale )
130+
131+ if un_items is None :
132+ # Not sure if this can happen under normal testing, but included as
133+ # a safety feature.
134+ return None
135+
136+ trait = tracker .scores [scale ]
137+ trait = 0.0 if trait is None else trait
138+ error = tracker .errors [scale ]
139+ error = 100.0 if error is None else error
140+
141+ criterion = self .criterion (scoring = self .scoring , items = un_items , scale = scale )
142+ valid_items = [x ["item" ] for x in un_items ]
143+ items = []
144+ Delta = []
145+ for k , v in criterion .items ():
146+ if k in valid_items :
147+ items += [k ]
148+ Delta += [v ]
149+ if len (items ) == 0 :
150+ return {}
151+ Delta -= np .max (Delta )
152+ probs = np .exp (Delta / self .temperature )
153+ probs /= np .sum (probs )
154+
155+ if self .deterministic or (self .hybrid and ((self .scoring .n_scored [scale ] > 3 ))):
156+ ndx = np .argmax (probs )
157+ else :
158+ ndx = np .random .choice (np .arange (len (criterion .keys ())), p = probs )
159+ result = list (criterion .keys ())[ndx ]
160+ for i in un_items :
161+ if i ["item" ] == result :
162+ return i
163+ return {}
0 commit comments