cat_engine 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/cat_engine.gemspec +30 -0
- data/lib/cat_engine.rb +11 -0
- data/lib/cat_engine/bank.rb +57 -0
- data/lib/cat_engine/config.rb +12 -0
- data/lib/cat_engine/engine.rb +34 -0
- data/lib/cat_engine/flat_engine.rb +40 -0
- data/lib/cat_engine/question.rb +83 -0
- data/lib/cat_engine/rails.rb +7 -0
- data/lib/cat_engine/response.rb +50 -0
- data/lib/cat_engine/version.rb +3 -0
- data/lib/tasks/cat_engine_tasks.rake +9 -0
- data/vendor/cat_engine/CATEngine3.cs +2123 -0
- data/vendor/cat_engine/IEngine.cs +65 -0
- data/vendor/cat_engine/ShellWrapper.cs +44 -0
- data/vendor/cat_engine/Util.cs +825 -0
- metadata +169 -0
@@ -0,0 +1,2123 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Collections.Generic;
|
3
|
+
using System.Text;
|
4
|
+
using System.Collections;
|
5
|
+
using System.IO;
|
6
|
+
using System.Linq;
|
7
|
+
using System.Xml;
|
8
|
+
|
9
|
+
|
10
|
+
namespace CORE.Engines
|
11
|
+
{
|
12
|
+
|
13
|
+
/// <summary>
|
14
|
+
/// Engine for computer adaptive testing.
|
15
|
+
/// </summary>
|
16
|
+
[Serializable]
|
17
|
+
public class CATEngine3 : IEngine
|
18
|
+
{
|
19
|
+
|
20
|
+
/// <summary>
|
21
|
+
/// Enumerator for the item response theory model type.
|
22
|
+
/// </summary>
|
23
|
+
private enum IRTModelType
|
24
|
+
{
|
25
|
+
/// <summary>GRM: 0</summary>
|
26
|
+
GradedResponseModel,
|
27
|
+
/// <summary>GPCM: 1</summary>
|
28
|
+
GeneralizedPartialCreditModel,
|
29
|
+
/// <summary>WR1PL: 2</summary>
|
30
|
+
WideRange1PL,
|
31
|
+
/// <summary>WR2PL: 3</summary>
|
32
|
+
WideRange2PL
|
33
|
+
};
|
34
|
+
|
35
|
+
/// <summary>
|
36
|
+
/// Enumerator for the Selection Method type. These are types of methods for calculating
|
37
|
+
/// the next item to select.
|
38
|
+
/// </summary>
|
39
|
+
private enum SelectionMethodType
|
40
|
+
{
|
41
|
+
/// <summary>MLWI: 0</summary>
|
42
|
+
MaximumLikelihoodWeightedInformation,
|
43
|
+
/// <summary>MPWI: 1</summary>
|
44
|
+
MaximumPosteriorWeightedInformation,
|
45
|
+
/// <summary>MI: 2</summary>
|
46
|
+
MaximumInformation,
|
47
|
+
/// <summary>ST: 3</summary>
|
48
|
+
Stradaptive,
|
49
|
+
/// <summary>MIN: 4</summary>
|
50
|
+
MaximumInformationWithNoise,
|
51
|
+
/// <summary>STMI: 5</summary>
|
52
|
+
StradaptiveWithMaximumInformation,
|
53
|
+
/// <summary>PR: 6</summary>
|
54
|
+
MaximumInformationProgressiveRestricted
|
55
|
+
};
|
56
|
+
|
57
|
+
|
58
|
+
/// <summary>
|
59
|
+
/// Constructor
|
60
|
+
/// </summary>
|
61
|
+
public CATEngine3()
|
62
|
+
{
|
63
|
+
Init();
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
/// <summary>
|
68
|
+
/// Constructor
|
69
|
+
/// </summary>
|
70
|
+
/// <param name="NumCategoriesByItem">The number of categories by item.</param>
|
71
|
+
/// <param name="DiscriminationValues">The discrimination values.</param>
|
72
|
+
/// <param name="CategoryBoundaryValues">The category boundary values.</param>
|
73
|
+
public CATEngine3(int[] NumCategoriesByItem, double[] DiscriminationValues, double[][] CategoryBoundaryValues)
|
74
|
+
{
|
75
|
+
_NumCategoriesByItem = NumCategoriesByItem;
|
76
|
+
_DiscriminationValues = DiscriminationValues;
|
77
|
+
_CategoryBoundaryValues = CategoryBoundaryValues;
|
78
|
+
|
79
|
+
Init();
|
80
|
+
}
|
81
|
+
|
82
|
+
/// <summary>
|
83
|
+
/// Initializations.
|
84
|
+
/// </summary>
|
85
|
+
private void Init()
|
86
|
+
{
|
87
|
+
_MaxCategories = 0;
|
88
|
+
_MinTheta = 0;
|
89
|
+
_MaxTheta = 0;
|
90
|
+
_ThetaIncrement = 0;
|
91
|
+
|
92
|
+
_Theta = 0;
|
93
|
+
_StdError = 9.900;
|
94
|
+
_NumQuadraturePoints = 0;
|
95
|
+
_Recalculate = true;
|
96
|
+
_CriteriaMet = false;
|
97
|
+
_finished = false;
|
98
|
+
_IsCompleted = false;
|
99
|
+
_IsResume = false;
|
100
|
+
|
101
|
+
_ItemsAsked = new ArrayList();
|
102
|
+
_ItemsSkipped = new ArrayList();
|
103
|
+
_Responses = new ArrayList();
|
104
|
+
_ThetaHistory = new ArrayList();
|
105
|
+
_StdErrorHistory = new ArrayList();
|
106
|
+
_LikelihoodHistory = new ArrayList();
|
107
|
+
|
108
|
+
_Initialized = false;
|
109
|
+
_RandomNumber = new Random(unchecked((int)DateTime.Now.Ticks));
|
110
|
+
|
111
|
+
_position = 0;
|
112
|
+
_store = new ArrayList();
|
113
|
+
_ItemIDs = new Hashtable();
|
114
|
+
_ResponseIDs = new Hashtable();
|
115
|
+
|
116
|
+
_paramPROC = "dbo.loadWideRangeCATParameters";
|
117
|
+
_message = "";
|
118
|
+
|
119
|
+
}
|
120
|
+
|
121
|
+
|
122
|
+
#region Inheirited methods
|
123
|
+
|
124
|
+
|
125
|
+
/// <summary>
|
126
|
+
/// Gets all the form items.
|
127
|
+
/// </summary>
|
128
|
+
/// <returns>XML document with all the form items.</returns>
|
129
|
+
public XmlDocument getForm()
|
130
|
+
{
|
131
|
+
XmlDocument doc = new XmlDocument();
|
132
|
+
doc.LoadXml(_form);
|
133
|
+
|
134
|
+
XmlDocumentFragment docFrag = doc.CreateDocumentFragment();
|
135
|
+
for (int i = 0; i < _store.Count; i++) {
|
136
|
+
docFrag.InnerXml = (String)_store[i];
|
137
|
+
XmlNode deep = docFrag.CloneNode(true);
|
138
|
+
doc.GetElementsByTagName("Items")[0].AppendChild(doc.ImportNode(deep, true));
|
139
|
+
}
|
140
|
+
return doc;
|
141
|
+
}
|
142
|
+
|
143
|
+
|
144
|
+
/// <summary>
|
145
|
+
/// Loads the items into the document store from an XML document.
|
146
|
+
/// </summary>
|
147
|
+
/// <param name="doc">The XML document.</param>
|
148
|
+
/// <param name="FormParams">The form parameters.</param>
|
149
|
+
/// <param name="WithHeader">If set to <c>true</c> treat transition or header items as actual items.</param>
|
150
|
+
/// <returns><c>true</c> if items successfully loaded, otherwise <c>false</c>.</returns>
|
151
|
+
/// <remarks>The form parameters are the CAT parameters in this case. The XML document requires
|
152
|
+
/// Property nodes within the Calibration node with the following Name and Value attributes with
|
153
|
+
/// appropriate data types:
|
154
|
+
/// <br/><br/>
|
155
|
+
/// <code>IRTModel</code> Item response theory model to use: integer data type
|
156
|
+
/// <code>SelectionMethod</code> Item selection method: integer data type
|
157
|
+
/// <code>MinNumItems</code> Minimum number of items to administer: integer data type
|
158
|
+
/// <code>MaxNumItems</code> Maximum number of items to administer: integer data type
|
159
|
+
/// <code>MaxStdErr</code> Maximum standard error: double data type
|
160
|
+
/// <code>SelectionGroupSize</code> Group size from which the next item is selected randomly: integer data type
|
161
|
+
/// <code>LogisticScaling</code> Logistic scaling constant: double data type
|
162
|
+
/// <code>DistMean</code> Prior distribution mean: double data type
|
163
|
+
/// <code>DistStdDev</code> Prior distribution standard deviation: double data type
|
164
|
+
/// <code>MinNumStradaptive</code> Minimum number of iterations for Stradaptive method: integer data type
|
165
|
+
/// <code>NoiseRange</code> Noise range around a point: double data type
|
166
|
+
/// <code>DefaultBin</code> Default bin if none are pre-selected: integer data type
|
167
|
+
/// <code>MaxStepChange</code> Maximum step change for maximum likelihood theta estimates: integer data type
|
168
|
+
/// <code>MaxNumIterations</code> Maximum number of iterations for maximum likelihood theta estimates: integer data type
|
169
|
+
/// <code>Convergence</code> Convergence criterion for Wide Range CAT: double data type
|
170
|
+
/// <code>MinTheta</code> Minimum theta value: double data type
|
171
|
+
/// <code>MaxTheta</code> Maximum theta value: double data type
|
172
|
+
/// <code>ThetaIncrement</code> Increment of theta to use for numerical integration: double data type
|
173
|
+
/// <code>FirstItem</code> Specific item to use first: string data type (stores a GUID)
|
174
|
+
/// <br/><br/>
|
175
|
+
/// The XML document will also have per-item properties:<br/><br/>
|
176
|
+
/// <code>A_GRM</code>Discrimination value for the Graded Response Model: double data type
|
177
|
+
/// <code>A_PCM</code>Discrimination value for the Partial Credit Model: double data type
|
178
|
+
/// <code>Slope</code>Discrimination value for the Wide Range 1PL or 2PL model: double data type
|
179
|
+
/// <code>Difficulty</code>Item difficulty for the Wide Range 1PL or 2PL model: double data type
|
180
|
+
/// <code>Bin</code>Item bin assignment for the Wide Range 1PL or 2PL model: integer data type
|
181
|
+
/// <br/><br/>
|
182
|
+
/// The Wide Range CAT methods also require an <code>InitialBin</code> integer value that is determined
|
183
|
+
/// on a per-person basis. This is in the Form XML document rather than the Parameter XML document.
|
184
|
+
/// <br/><br/>
|
185
|
+
/// Required parameters that are missing will throw an ApplicationException, missing optional parameters will not.
|
186
|
+
/// Some parameters are only required if other parameters have certain values and will only throw an ApplicationException
|
187
|
+
/// if those conditions are met.
|
188
|
+
///</remarks>
|
189
|
+
public bool loadItems(XmlDocument doc, XmlDocument FormParams, bool WithHeader)
|
190
|
+
{
|
191
|
+
bool AreItemsLoaded = false;
|
192
|
+
StringWriter sw;
|
193
|
+
XmlTextWriter xw;
|
194
|
+
string FormItemOID;
|
195
|
+
string ItemResponseOID;
|
196
|
+
XmlNodeList items;
|
197
|
+
XmlNodeList responses;
|
198
|
+
XmlNodeList CATitems;
|
199
|
+
XmlNode CATitem;
|
200
|
+
ArrayList arItemsAvailable = new ArrayList();
|
201
|
+
ArrayList arNumCatByItem = new ArrayList();
|
202
|
+
ArrayList arDisc = new ArrayList();
|
203
|
+
List<double> liDiff = new List<double>();
|
204
|
+
List<int> liBin = new List<int>();
|
205
|
+
ArrayList arCBV = new ArrayList();
|
206
|
+
ArrayList arItemCBV;
|
207
|
+
Hashtable ItemResponseIDs;
|
208
|
+
ArrayList arNumCat = new ArrayList();
|
209
|
+
int j;
|
210
|
+
int counter = -1; // Count of items that have parameters
|
211
|
+
int lowestBin = Int32.MaxValue;
|
212
|
+
|
213
|
+
if (_store.Count == 0) {
|
214
|
+
//Load items
|
215
|
+
((XmlElement)doc.GetElementsByTagName("Form")[0]).SetAttribute("Engine", "CATEngine3");
|
216
|
+
items = doc.GetElementsByTagName("Item");
|
217
|
+
|
218
|
+
|
219
|
+
//Get form CAT properties
|
220
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='IRTModel']") != null)) {
|
221
|
+
_IRTModel = Convert.ToInt32(FormParams.SelectSingleNode("/Calibration/Property[@Name='IRTModel']").Attributes["Value"].Value);
|
222
|
+
}
|
223
|
+
else {
|
224
|
+
throw new ApplicationException("missing IRTModel value. ");
|
225
|
+
}
|
226
|
+
|
227
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='SelectionMethod']") != null)) {
|
228
|
+
_SelectionMethod = Convert.ToInt32(FormParams.SelectSingleNode("/Calibration/Property[@Name='SelectionMethod']").Attributes["Value"].Value);
|
229
|
+
}
|
230
|
+
else {
|
231
|
+
throw new ApplicationException("missing SelectionMethod value. ");
|
232
|
+
}
|
233
|
+
|
234
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='MinNumItems']") != null)) {
|
235
|
+
_MinNumItems = Convert.ToInt32(FormParams.SelectSingleNode("/Calibration/Property[@Name='MinNumItems']").Attributes["Value"].Value);
|
236
|
+
}
|
237
|
+
else {
|
238
|
+
throw new ApplicationException("missing MinNumItems value. ");
|
239
|
+
}
|
240
|
+
|
241
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxNumItems']") != null)) {
|
242
|
+
_MaxNumItems = Convert.ToInt32(FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxNumItems']").Attributes["Value"].Value);
|
243
|
+
}
|
244
|
+
else {
|
245
|
+
throw new ApplicationException("missing MaxNumItems value. ");
|
246
|
+
}
|
247
|
+
|
248
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxStdErr']") != null)) {
|
249
|
+
_MaxStdErr = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxStdErr']").Attributes["Value"].Value);
|
250
|
+
}
|
251
|
+
else {
|
252
|
+
throw new ApplicationException("missing MaxStdErr value. ");
|
253
|
+
}
|
254
|
+
|
255
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='SelectionGroupSize']") != null)) {
|
256
|
+
_SelectionGroupSize = Convert.ToInt32(FormParams.SelectSingleNode("/Calibration/Property[@Name='SelectionGroupSize']").Attributes["Value"].Value);
|
257
|
+
}
|
258
|
+
else {
|
259
|
+
throw new ApplicationException("missing SelectionGroupSize value. ");
|
260
|
+
}
|
261
|
+
|
262
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='LogisticScaling']") != null)) {
|
263
|
+
_LogisticScaling = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='LogisticScaling']").Attributes["Value"].Value);
|
264
|
+
}
|
265
|
+
else {
|
266
|
+
throw new ApplicationException("missing LogisticScaling value. ");
|
267
|
+
}
|
268
|
+
|
269
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='DistMean']") != null)) {
|
270
|
+
_PriorDistributionMean = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='DistMean']").Attributes["Value"].Value);
|
271
|
+
}
|
272
|
+
else {
|
273
|
+
if ((_IRTModel == (int)IRTModelType.GradedResponseModel) || (_IRTModel == (int)IRTModelType.GeneralizedPartialCreditModel)) {
|
274
|
+
throw new ApplicationException("missing DistMean value. ");
|
275
|
+
}
|
276
|
+
else {
|
277
|
+
_PriorDistributionMean = -1.0;
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='DistStdDev']") != null)) {
|
282
|
+
_PriorDistributionStdDev = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='DistStdDev']").Attributes["Value"].Value);
|
283
|
+
}
|
284
|
+
else {
|
285
|
+
if ((_IRTModel == (int)IRTModelType.GradedResponseModel) || (_IRTModel == (int)IRTModelType.GeneralizedPartialCreditModel)) {
|
286
|
+
throw new ApplicationException("missing DistStdDev value. ");
|
287
|
+
}
|
288
|
+
else {
|
289
|
+
_PriorDistributionStdDev = -1.0;
|
290
|
+
}
|
291
|
+
}
|
292
|
+
|
293
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='MinNumStradaptive']") != null)) {
|
294
|
+
_MinNumStradaptive = Convert.ToInt32(FormParams.SelectSingleNode("/Calibration/Property[@Name='MinNumStradaptive']").Attributes["Value"].Value);
|
295
|
+
}
|
296
|
+
else {
|
297
|
+
if (_SelectionMethod == (int)SelectionMethodType.StradaptiveWithMaximumInformation) {
|
298
|
+
throw new ApplicationException("missing MinNumStradaptive value. ");
|
299
|
+
}
|
300
|
+
else {
|
301
|
+
_MinNumStradaptive = -1;
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
305
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='NoiseRange']") != null)) {
|
306
|
+
_NoiseRange = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='NoiseRange']").Attributes["Value"].Value);
|
307
|
+
}
|
308
|
+
else {
|
309
|
+
if (((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) && (_SelectionMethod == (int)SelectionMethodType.MaximumInformationWithNoise)) {
|
310
|
+
throw new ApplicationException("missing NoiseRange value. ");
|
311
|
+
}
|
312
|
+
else {
|
313
|
+
_NoiseRange = 0.0;
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
317
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='DefaultBin']") != null)) {
|
318
|
+
_DefaultBin = Convert.ToInt32(FormParams.SelectSingleNode("/Calibration/Property[@Name='DefaultBin']").Attributes["Value"].Value);
|
319
|
+
}
|
320
|
+
else {
|
321
|
+
if ((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) {
|
322
|
+
throw new ApplicationException("missing DefaultBin value. ");
|
323
|
+
}
|
324
|
+
else {
|
325
|
+
_DefaultBin = -1;
|
326
|
+
}
|
327
|
+
}
|
328
|
+
|
329
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxStepChange']") != null)) {
|
330
|
+
_MaxStepChange = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxStepChange']").Attributes["Value"].Value);
|
331
|
+
}
|
332
|
+
else {
|
333
|
+
if ((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) {
|
334
|
+
throw new ApplicationException("missing MaxStepChange value. ");
|
335
|
+
}
|
336
|
+
else {
|
337
|
+
_MaxStepChange = 0.0;
|
338
|
+
}
|
339
|
+
}
|
340
|
+
|
341
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxNumIterations']") != null)) {
|
342
|
+
_MaxNumIterations = Convert.ToInt32(FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxNumIterations']").Attributes["Value"].Value);
|
343
|
+
}
|
344
|
+
else {
|
345
|
+
if ((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) {
|
346
|
+
throw new ApplicationException("missing MaxNumIterations value. ");
|
347
|
+
}
|
348
|
+
else {
|
349
|
+
_MaxNumIterations = 0;
|
350
|
+
}
|
351
|
+
}
|
352
|
+
|
353
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='Convergence']") != null)) {
|
354
|
+
_Convergence = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='Convergence']").Attributes["Value"].Value);
|
355
|
+
}
|
356
|
+
else {
|
357
|
+
if ((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) {
|
358
|
+
throw new ApplicationException("missing Convergence value. ");
|
359
|
+
}
|
360
|
+
else {
|
361
|
+
_Convergence = 0.0;
|
362
|
+
}
|
363
|
+
}
|
364
|
+
|
365
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='MinTheta']") != null)) {
|
366
|
+
MinTheta = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='MinTheta']").Attributes["Value"].Value);
|
367
|
+
}
|
368
|
+
else {
|
369
|
+
throw new ApplicationException("missing MinTheta value. ");
|
370
|
+
}
|
371
|
+
|
372
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxTheta']") != null)) {
|
373
|
+
MaxTheta = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='MaxTheta']").Attributes["Value"].Value);
|
374
|
+
}
|
375
|
+
else {
|
376
|
+
throw new ApplicationException("missing MaxTheta value. ");
|
377
|
+
}
|
378
|
+
|
379
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='ThetaIncrement']") != null)) {
|
380
|
+
ThetaIncrement = Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Property[@Name='ThetaIncrement']").Attributes["Value"].Value);
|
381
|
+
}
|
382
|
+
else {
|
383
|
+
throw new ApplicationException("missing ThetaIncrement value. ");
|
384
|
+
}
|
385
|
+
|
386
|
+
if ((FormParams.SelectSingleNode("/Calibration/Property[@Name='FirstItem']") != null)) {
|
387
|
+
_FirstItem = (FormParams.SelectSingleNode("/Calibration/Property[@Name='FirstItem']").Attributes["Value"].Value).ToUpper().Trim();
|
388
|
+
}
|
389
|
+
else {
|
390
|
+
_FirstItem = String.Empty;
|
391
|
+
}
|
392
|
+
|
393
|
+
if ((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) {
|
394
|
+
if ((doc.GetElementsByTagName("Form")[0].Attributes["ScreenItem"]) != null) {
|
395
|
+
if (!(int.TryParse((doc.GetElementsByTagName("Form")[0].Attributes["ScreenItem"]).Value, out _InitialBin))) {
|
396
|
+
_InitialBin = _DefaultBin;
|
397
|
+
}
|
398
|
+
}
|
399
|
+
else {
|
400
|
+
_InitialBin = _DefaultBin;
|
401
|
+
}
|
402
|
+
|
403
|
+
}
|
404
|
+
|
405
|
+
|
406
|
+
//Get item CAT parameters
|
407
|
+
CATitems = FormParams.GetElementsByTagName("Item");
|
408
|
+
|
409
|
+
//Item CAT parameters should have same number of items as the main Item doc
|
410
|
+
//if (items.Count != CATitems.Count) {
|
411
|
+
// _message += "Error (CATEngine.loadItems): CAT items don't match Form items\n";
|
412
|
+
//return false;
|
413
|
+
//}
|
414
|
+
|
415
|
+
_position = 0;
|
416
|
+
|
417
|
+
for (int i = 0; i < items.Count; i++) {
|
418
|
+
|
419
|
+
sw = new StringWriter();
|
420
|
+
xw = new XmlTextWriter(sw);
|
421
|
+
FormItemOID = items[i].Attributes["FormItemOID"].Value.ToUpper();
|
422
|
+
|
423
|
+
XmlNode CATitem2 = FormParams.SelectSingleNode("/Calibration/Item[@FormItemOID='" + FormItemOID + "']");
|
424
|
+
|
425
|
+
// Don't load items without parameters
|
426
|
+
if (CATitem2 == null) {
|
427
|
+
continue;
|
428
|
+
}
|
429
|
+
else {
|
430
|
+
counter++;
|
431
|
+
}
|
432
|
+
|
433
|
+
_ItemIDs.Add(FormItemOID, counter);
|
434
|
+
|
435
|
+
if (String.IsNullOrEmpty(items[i].Attributes["ID"].Value) && !WithHeader) {
|
436
|
+
continue;
|
437
|
+
}
|
438
|
+
|
439
|
+
//Check if item already has a response
|
440
|
+
if (!String.IsNullOrEmpty(((XmlElement)items[i]).GetAttribute("Response"))) {
|
441
|
+
_position++;
|
442
|
+
_IsResume = true;
|
443
|
+
arItemsAvailable.Add(0);
|
444
|
+
}
|
445
|
+
else {
|
446
|
+
arItemsAvailable.Add(1);
|
447
|
+
}
|
448
|
+
|
449
|
+
((XmlElement)items[i]).SetAttribute("Position", (i + 1).ToString());
|
450
|
+
items[i].WriteTo(xw);
|
451
|
+
|
452
|
+
//Item
|
453
|
+
_store.Add(sw.ToString());
|
454
|
+
//Responses
|
455
|
+
ItemResponseIDs = new Hashtable();
|
456
|
+
|
457
|
+
//responses = doc.SelectNodes("/Form/Items/Item[@FormItemOID='" + FormItemOID + "']/Elements/Element[@ElementType='ResponseSet']/Mappings/Map");
|
458
|
+
responses = doc.SelectNodes("/Form/Item[@FormItemOID='" + FormItemOID + "']/descendant::Map");
|
459
|
+
|
460
|
+
bool adjustPatientResponse = false;
|
461
|
+
int lowestValue = Int32.MaxValue;
|
462
|
+
int k = 0;
|
463
|
+
foreach (XmlNode n in responses) {
|
464
|
+
k++;
|
465
|
+
ItemResponseOID = n.Attributes["ItemResponseOID"].Value.ToUpper();
|
466
|
+
|
467
|
+
int description;
|
468
|
+
|
469
|
+
//Expected types: int = response "score"; Guid.Empty = skipped response (don't add to hashtable)
|
470
|
+
//Assumes consecutive description values
|
471
|
+
if (int.TryParse(n.Attributes["Position"].Value, out description)) {
|
472
|
+
//description--; // Assume "Description" is base-1 list -- !!! CAN'T DO THIS: Description CAN be zero or start at any other number
|
473
|
+
|
474
|
+
//Note: Collapsed categories will have different ItemResponseOID but same description
|
475
|
+
ItemResponseIDs.Add(ItemResponseOID, description);
|
476
|
+
|
477
|
+
if (description < lowestValue) {
|
478
|
+
lowestValue = description;
|
479
|
+
}
|
480
|
+
}
|
481
|
+
else {
|
482
|
+
description = -1; // skipped or invalid
|
483
|
+
}
|
484
|
+
|
485
|
+
//Check if item already has patient response
|
486
|
+
if (((XmlElement)items[i]).GetAttribute("ItemResponseOID").ToUpper() == ItemResponseOID) {
|
487
|
+
//Skipped item has empty GUID for ResponseOID
|
488
|
+
if (Guid.Empty.ToString() == items[i].Attributes["Response"].Value) {
|
489
|
+
_ItemsSkipped.Add(counter);
|
490
|
+
_NumSkippedItems++;
|
491
|
+
}
|
492
|
+
|
493
|
+
_ItemsAsked.Add(counter);
|
494
|
+
|
495
|
+
//Note: k is the array index, not the collapsed value
|
496
|
+
//_Responses.Add(k);
|
497
|
+
//Note: Calculations later need the category value to properly account for collapsed categories
|
498
|
+
_Responses.Add(description);
|
499
|
+
|
500
|
+
adjustPatientResponse = true;
|
501
|
+
}
|
502
|
+
}
|
503
|
+
|
504
|
+
//If the lowest description value is not zero then adjust descriptions to start at zero
|
505
|
+
//Note: The CAT parameter file uses StepOrder for this (corresponds to "description" here) and is automatically forced to start at zero
|
506
|
+
if (lowestValue != 0) {
|
507
|
+
//Patient response
|
508
|
+
if (adjustPatientResponse) {
|
509
|
+
_Responses[_Responses.Count - 1] = (int)_Responses[_Responses.Count - 1] - lowestValue;
|
510
|
+
}
|
511
|
+
|
512
|
+
//All item responses
|
513
|
+
Hashtable ht = new Hashtable();
|
514
|
+
foreach (string OID in ItemResponseIDs.Keys) {
|
515
|
+
ht[OID] = (int)ItemResponseIDs[OID] - lowestValue;
|
516
|
+
}
|
517
|
+
ItemResponseIDs = ht;
|
518
|
+
}
|
519
|
+
|
520
|
+
//Note: Array location of _ResponseIDs = array location of corresponding _ItemIDs
|
521
|
+
_ResponseIDs.Add(counter, ItemResponseIDs); //_ResponseIDs.Add(i,ItemResponseIDs);
|
522
|
+
|
523
|
+
//CAT parameters
|
524
|
+
CATitem = FormParams.SelectSingleNode("/Calibration/Item[@FormItemOID='" + FormItemOID + "']");
|
525
|
+
|
526
|
+
//Item Discrimination value (slope or A)
|
527
|
+
if (CATitem.Attributes["A_GRM"] != null) {
|
528
|
+
arDisc.Add(Convert.ToDouble(CATitem.Attributes["A_GRM"].Value));
|
529
|
+
}
|
530
|
+
else if (CATitem.Attributes["A_PCM"] != null) {
|
531
|
+
arDisc.Add(Convert.ToDouble(CATitem.Attributes["A_PCM"].Value));
|
532
|
+
}
|
533
|
+
else if (CATitem.Attributes["Slope"] != null) {
|
534
|
+
arDisc.Add(Convert.ToDouble(CATitem.Attributes["Slope"].Value));
|
535
|
+
}
|
536
|
+
else {
|
537
|
+
//Rasch or 1PL
|
538
|
+
arDisc.Add(1);
|
539
|
+
}
|
540
|
+
|
541
|
+
//Item Difficulty value (B)
|
542
|
+
if ((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) {
|
543
|
+
if (CATitem.Attributes["Difficulty"] == null) {
|
544
|
+
throw new ApplicationException("missing item Difficulty value. ");
|
545
|
+
}
|
546
|
+
else {
|
547
|
+
liDiff.Add(Convert.ToDouble(CATitem.Attributes["Difficulty"].Value));
|
548
|
+
}
|
549
|
+
}
|
550
|
+
|
551
|
+
//Item Bin assignment (BIN)
|
552
|
+
if ((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) {
|
553
|
+
if (CATitem.Attributes["Bin"] == null) {
|
554
|
+
throw new ApplicationException("missing item Bin value. ");
|
555
|
+
}
|
556
|
+
else {
|
557
|
+
liBin.Add(Convert.ToInt32(CATitem.Attributes["Bin"].Value));
|
558
|
+
if (liBin[liBin.Count - 1] < lowestBin) {
|
559
|
+
lowestBin = liBin[liBin.Count - 1];
|
560
|
+
}
|
561
|
+
}
|
562
|
+
}
|
563
|
+
|
564
|
+
//Category boundary values
|
565
|
+
arItemCBV = new ArrayList();
|
566
|
+
ItemResponseIDs = new Hashtable();
|
567
|
+
|
568
|
+
//Position loading of Threshold based on Value
|
569
|
+
//Assumes that the category boundary values are always in the correct order in the XML document
|
570
|
+
j = 1;
|
571
|
+
foreach (XmlNode n in CATitem.SelectNodes("Map")) {
|
572
|
+
//Note: j = number of categories, or one less than the number of category boundary values
|
573
|
+
int desc;
|
574
|
+
//Check for non-integer step order (might happen with skip or invalid responses)
|
575
|
+
if (int.TryParse(n.Attributes["StepOrder"].Value, out desc)) {
|
576
|
+
//This takes into account collapsed categories
|
577
|
+
arItemCBV.Add(Convert.ToDouble(n.Attributes["Value"].Value));
|
578
|
+
j++;
|
579
|
+
}
|
580
|
+
}
|
581
|
+
|
582
|
+
//Position loading of Threshold based on ordinal position
|
583
|
+
//Does not assume that the category boundary values are always in the correct order in the XML document
|
584
|
+
//for (j=1; j<=CATitem.SelectNodes("Map").Count; j++) {
|
585
|
+
// arItemCBV.Add(Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Item[@FormItemOID='" + FormItemOID + "']/Map[@StepOrder='" + j + "']").Attributes["Value"].Value));
|
586
|
+
// arItemCBV.Add(Convert.ToDouble(FormParams.SelectSingleNode("/Calibration/Item[@FormItemOID='" + FormItemOID + "']/Map[@Property='CB" + j + "']").Attributes["Value"].Value));
|
587
|
+
//}
|
588
|
+
|
589
|
+
arCBV.Add((double[])arItemCBV.ToArray(typeof(double)));
|
590
|
+
//Number of categories is one more than the number of category boundary values: ending value from FOR loop
|
591
|
+
arNumCat.Add(j);
|
592
|
+
if (j > _MaxCategories) {
|
593
|
+
_MaxCategories = j;
|
594
|
+
}
|
595
|
+
|
596
|
+
}
|
597
|
+
|
598
|
+
//If the lowest bin number is not zero then adjust bins to start at zero
|
599
|
+
if (lowestBin != 0) {
|
600
|
+
//Initial Bin
|
601
|
+
_InitialBin = _InitialBin - lowestBin;
|
602
|
+
|
603
|
+
//All bins
|
604
|
+
for (int k=0; k < liBin.Count; k++) {
|
605
|
+
liBin[k] = liBin[k] - lowestBin;
|
606
|
+
}
|
607
|
+
}
|
608
|
+
|
609
|
+
|
610
|
+
_DiscriminationValues = (double[])arDisc.ToArray(typeof(double));
|
611
|
+
_Difficulty = liDiff.ToArray();
|
612
|
+
_Bin = liBin.ToArray();
|
613
|
+
_CategoryBoundaryValues = (double[][])arCBV.ToArray(typeof(double[]));
|
614
|
+
_NumCategoriesByItem = (int[])arNumCat.ToArray(typeof(int));
|
615
|
+
_ItemsAvailable = (int[])arItemsAvailable.ToArray(typeof(int));
|
616
|
+
|
617
|
+
_NumTotalItems = _ItemIDs.Count;
|
618
|
+
|
619
|
+
_BinAverages = Util.CategoryAverages(liBin, liDiff);
|
620
|
+
_NumBins = _BinAverages.Count;
|
621
|
+
|
622
|
+
if ((_IRTModel == (int)IRTModelType.WideRange1PL) || (_IRTModel == (int)IRTModelType.WideRange2PL)) {
|
623
|
+
liDiff.Sort(0, liDiff.Count - 1, null);
|
624
|
+
_MinDifficulty = liDiff[0];
|
625
|
+
_MaxDifficulty = liDiff[liDiff.Count - 1];
|
626
|
+
}
|
627
|
+
|
628
|
+
//Check if all items have been used
|
629
|
+
if (_position == _store.Count) {
|
630
|
+
_CriteriaMet = true;
|
631
|
+
_IsCompleted = true;
|
632
|
+
}
|
633
|
+
|
634
|
+
//Store Document Shell
|
635
|
+
XmlNodeList _collection = doc.GetElementsByTagName("Item");
|
636
|
+
for(int i = _collection.Count - 1; i >= 0; i--) {
|
637
|
+
XmlNode node = _collection[i];
|
638
|
+
node.ParentNode.RemoveChild(node);
|
639
|
+
}
|
640
|
+
|
641
|
+
StringWriter sw2 = new StringWriter();
|
642
|
+
XmlTextWriter xw2 = new XmlTextWriter(sw2);
|
643
|
+
doc.WriteTo(xw2);
|
644
|
+
_form = sw2.ToString();
|
645
|
+
|
646
|
+
|
647
|
+
AreItemsLoaded = true;
|
648
|
+
}
|
649
|
+
|
650
|
+
return AreItemsLoaded;
|
651
|
+
}
|
652
|
+
|
653
|
+
|
654
|
+
/// <summary>
|
655
|
+
/// Gets the current item.
|
656
|
+
/// </summary>
|
657
|
+
/// <returns>XML document containing the current item.</returns>
|
658
|
+
public XmlDocument getCurrentItem()
|
659
|
+
{
|
660
|
+
|
661
|
+
XmlDocument doc = new XmlDocument();
|
662
|
+
|
663
|
+
int ItemIndex;
|
664
|
+
XmlDocumentFragment docFrag;
|
665
|
+
XmlNode deep;
|
666
|
+
XmlNode form;
|
667
|
+
|
668
|
+
//check if no items available or if done
|
669
|
+
int NumItemsAvailable = (int)Util.ArraySum(_ItemsAvailable);
|
670
|
+
|
671
|
+
if ((_finished) || (NumItemsAvailable == 0) || (_CriteriaMet == true)) {
|
672
|
+
|
673
|
+
return null;
|
674
|
+
}
|
675
|
+
|
676
|
+
if (!_Initialized) {
|
677
|
+
|
678
|
+
//Prepare probability matrix
|
679
|
+
switch (_IRTModel) {
|
680
|
+
case (int)IRTModelType.GradedResponseModel:
|
681
|
+
PrepareProbabilityInfoGRM();
|
682
|
+
break;
|
683
|
+
case (int)IRTModelType.GeneralizedPartialCreditModel:
|
684
|
+
PrepareProbabilityInfoGPCM();
|
685
|
+
break;
|
686
|
+
case (int)IRTModelType.WideRange1PL:
|
687
|
+
case (int)IRTModelType.WideRange2PL:
|
688
|
+
_CurrentBin = _InitialBin;
|
689
|
+
_Theta = _BinAverages[_CurrentBin];
|
690
|
+
break;
|
691
|
+
default:
|
692
|
+
break;
|
693
|
+
}
|
694
|
+
|
695
|
+
_Initialized = true;
|
696
|
+
}
|
697
|
+
|
698
|
+
if (_NumTotalItems == NumItemsAvailable) {
|
699
|
+
//First item:
|
700
|
+
//if pre-selected first item,
|
701
|
+
if (!(String.IsNullOrEmpty(_FirstItem))) {
|
702
|
+
//Check if the _FirstItem OID exists in the Item list
|
703
|
+
if (_ItemIDs.ContainsKey(_FirstItem)) {
|
704
|
+
|
705
|
+
ItemIndex = (int)_ItemIDs[_FirstItem];
|
706
|
+
|
707
|
+
//get item from the document store
|
708
|
+
doc.LoadXml(_form);
|
709
|
+
docFrag = doc.CreateDocumentFragment();
|
710
|
+
docFrag.InnerXml = (String)_store[ItemIndex];
|
711
|
+
deep = docFrag.CloneNode(true);
|
712
|
+
doc.GetElementsByTagName("Form")[0].AppendChild(doc.ImportNode(deep, true));
|
713
|
+
|
714
|
+
|
715
|
+
return doc;
|
716
|
+
}
|
717
|
+
}
|
718
|
+
}
|
719
|
+
|
720
|
+
|
721
|
+
int i;
|
722
|
+
int rnd;
|
723
|
+
double[] CatInfo = new double[_NumTotalItems];
|
724
|
+
|
725
|
+
//Execute item selection method
|
726
|
+
switch (_IRTModel) {
|
727
|
+
case (int)IRTModelType.GradedResponseModel:
|
728
|
+
case (int)IRTModelType.GeneralizedPartialCreditModel:
|
729
|
+
CatInfo = CalcLWInfo();
|
730
|
+
/*
|
731
|
+
switch ( _SelectionMethod ) {
|
732
|
+
case (int)SelectionMethodType.MaximumLikelihoodWeightedInformation:
|
733
|
+
CatInfo = CalcLWInfo();
|
734
|
+
break;
|
735
|
+
case (int)SelectionMethodType.MaximumPosteriorWeightedInformation:
|
736
|
+
//The only difference between Posterior and Likelihood methods is initialization, handled in the InitializeLikelihood function
|
737
|
+
CatInfo = CalcLWInfo();
|
738
|
+
break;
|
739
|
+
//case (int)SelectionMethodType.MaximumFisherInformation:
|
740
|
+
// break;
|
741
|
+
//case (int)SelectionMethodType.MaximumExpectedInformation:
|
742
|
+
// break;
|
743
|
+
//case (int)SelectionMethodType.MaximumExpectedPosteriorWeightedInformation:
|
744
|
+
// break;
|
745
|
+
//case (int)SelectionMethodType.MinimumExpectedPosteriorVariance:
|
746
|
+
// break;
|
747
|
+
//case (int)SelectionMethodType.RandomSelection:
|
748
|
+
// break;
|
749
|
+
//case (int)SelectionMethodType.KnownOrExternalTheta:
|
750
|
+
// break;
|
751
|
+
default:
|
752
|
+
CatInfo = CalcLWInfo();
|
753
|
+
break;
|
754
|
+
}
|
755
|
+
*/
|
756
|
+
break;
|
757
|
+
|
758
|
+
case (int)IRTModelType.WideRange1PL:
|
759
|
+
case (int)IRTModelType.WideRange2PL:
|
760
|
+
CatInfo = WideRangeItemValues(_Theta, _CurrentBin);
|
761
|
+
|
762
|
+
break;
|
763
|
+
|
764
|
+
default:
|
765
|
+
break;
|
766
|
+
|
767
|
+
}
|
768
|
+
|
769
|
+
//Eliminate used items
|
770
|
+
CatInfo = Util.MatrixMultiply(CatInfo, _ItemsAvailable);
|
771
|
+
|
772
|
+
//Sort CatInfo in ascending order
|
773
|
+
//Allows non-unique CatInfo double values; SortedList doesn't allow this
|
774
|
+
List<KeyValuePair<double,int>> ItemList = new List<KeyValuePair<double, int>>();
|
775
|
+
|
776
|
+
for (i = 0; i < _NumTotalItems; i++) {
|
777
|
+
//Don't add zeroes: these are eliminated items
|
778
|
+
if (CatInfo[i] != 0) {
|
779
|
+
ItemList.Add(new KeyValuePair<double,int>(CatInfo[i], i));
|
780
|
+
}
|
781
|
+
}
|
782
|
+
ItemList.Sort(new Util.KVPDoubleIntComparer());
|
783
|
+
|
784
|
+
//Use C# Random instead of Util.RandomNumber to maintain compatibility with the R version of the code
|
785
|
+
rnd = _RandomNumber.Next(_SelectionGroupSize - 1);
|
786
|
+
//rnd = Util.RandomNumber.Next(_SelectionGroupSize - 1);
|
787
|
+
|
788
|
+
//Get index of highest item (randomly selected from the top _SelectionGroupSize number of items)
|
789
|
+
ItemIndex = ItemList[ItemList.Count - 1 - rnd].Value;
|
790
|
+
|
791
|
+
|
792
|
+
/*
|
793
|
+
//Sort CatInfo (automatically ascending order)
|
794
|
+
SortedList Items = new SortedList();
|
795
|
+
|
796
|
+
for (i = 0; i < _NumTotalItems; i++) {
|
797
|
+
//Don't add zeroes: these are eliminated items
|
798
|
+
if (CatInfo[i] != 0) {
|
799
|
+
Items.Add(CatInfo[i], i);
|
800
|
+
}
|
801
|
+
}
|
802
|
+
Items.TrimToSize();
|
803
|
+
|
804
|
+
rnd = _RandomNumber.Next(_SelectionGroupSize - 1);
|
805
|
+
|
806
|
+
//Get index of highest item (randomly selected from the top _SelectionGroupSize number of items)
|
807
|
+
ItemIndex = (int)Items[Items.GetKey(Items.Count - 1 - rnd)];
|
808
|
+
*/
|
809
|
+
|
810
|
+
//Get item from the document store
|
811
|
+
doc.LoadXml(_form);
|
812
|
+
docFrag = doc.CreateDocumentFragment();
|
813
|
+
docFrag.InnerXml = (String)_store[ItemIndex];
|
814
|
+
((XmlElement)doc.GetElementsByTagName("Form")[0]).SetAttribute("Theta", ((decimal)_Theta).ToString());
|
815
|
+
((XmlElement)doc.GetElementsByTagName("Form")[0]).SetAttribute("StdError", ((decimal)_StdError).ToString());
|
816
|
+
doc.GetElementsByTagName("Form")[0].AppendChild(doc.ImportNode(docFrag, true));
|
817
|
+
return doc;
|
818
|
+
}
|
819
|
+
|
820
|
+
|
821
|
+
/// <summary>
|
822
|
+
/// Updates the document store with the changed node.
|
823
|
+
/// </summary>
|
824
|
+
/// <param name="node">The node.</param>
|
825
|
+
public void updateNode(XmlNode node)
|
826
|
+
{
|
827
|
+
|
828
|
+
string FormItemOID;
|
829
|
+
string ItemResponseOID;
|
830
|
+
int ItemIndex;
|
831
|
+
int ResponseIndex;
|
832
|
+
XmlAttribute attrib;
|
833
|
+
int NumAnsweredItems;
|
834
|
+
bool skipped = false;
|
835
|
+
double[] QAProbability;
|
836
|
+
|
837
|
+
//Get the index of the item and response
|
838
|
+
FormItemOID = node.Attributes["FormItemOID"].Value.ToUpper();
|
839
|
+
ItemResponseOID = node.Attributes["ItemResponseOID"].Value.ToUpper();
|
840
|
+
|
841
|
+
ItemIndex = (int)_ItemIDs[FormItemOID];
|
842
|
+
|
843
|
+
//Skipped response
|
844
|
+
if (((XmlElement)node).GetAttribute("ItemResponseOID").ToUpper() == Guid.Empty.ToString().ToUpper()) {
|
845
|
+
skipped = true;
|
846
|
+
_NumSkippedItems++;
|
847
|
+
_ItemsSkipped.Add(ItemIndex);
|
848
|
+
ResponseIndex = -1;
|
849
|
+
}
|
850
|
+
else {
|
851
|
+
//Note: this takes into account a collapsed response category
|
852
|
+
ResponseIndex = (int)((Hashtable)_ResponseIDs[ItemIndex])[ItemResponseOID];
|
853
|
+
}
|
854
|
+
|
855
|
+
_ItemsAsked.Add(ItemIndex);
|
856
|
+
//Note: this is the collapsed value if category is collapsed; -1 if skipped or invalid
|
857
|
+
_Responses.Add(ResponseIndex);
|
858
|
+
_ItemsAvailable[ItemIndex] = 0;
|
859
|
+
NumAnsweredItems = _ItemsAsked.Count - _NumSkippedItems;
|
860
|
+
|
861
|
+
//If this is a skip response, don't do any calculations.
|
862
|
+
if (!skipped) {
|
863
|
+
// if ((_IRTModel == (int)IRTModelType.GradedResponseModel) || (_IRTModel == (int)IRTModelType.GeneralizedPartialCreditModel)) {
|
864
|
+
// QAProbability = new double[_Probability.GetLength(0)];
|
865
|
+
// }
|
866
|
+
|
867
|
+
if (!_Initialized) {
|
868
|
+
switch (_IRTModel) {
|
869
|
+
case (int)IRTModelType.GradedResponseModel:
|
870
|
+
PrepareProbabilityInfoGRM();
|
871
|
+
break;
|
872
|
+
case (int)IRTModelType.GeneralizedPartialCreditModel:
|
873
|
+
PrepareProbabilityInfoGPCM();
|
874
|
+
break;
|
875
|
+
case (int)IRTModelType.WideRange1PL:
|
876
|
+
case (int)IRTModelType.WideRange2PL:
|
877
|
+
_NumCorrectItems = 0;
|
878
|
+
for (int i = 0; i < _Responses.Count-2; i++ ) {
|
879
|
+
_NumCorrectItems = _NumCorrectItems + ResponseIndex;
|
880
|
+
}
|
881
|
+
break;
|
882
|
+
default: //Graded Response Model
|
883
|
+
//PrepareProbabilityInfoGRM();
|
884
|
+
break;
|
885
|
+
}
|
886
|
+
_Initialized = true;
|
887
|
+
}
|
888
|
+
|
889
|
+
switch (_IRTModel) {
|
890
|
+
case (int)IRTModelType.GradedResponseModel:
|
891
|
+
case (int)IRTModelType.GeneralizedPartialCreditModel:
|
892
|
+
//get probability slice
|
893
|
+
QAProbability = Util.MatrixSlice1D(_Probability, Util.DimensionType.Row, ItemIndex, Util.DimensionType.Column, ResponseIndex, Util.DimensionType.Depth);
|
894
|
+
//new likelihood
|
895
|
+
_Likelihood = Util.MatrixMultiply(_Likelihood, QAProbability);
|
896
|
+
|
897
|
+
//estimate new theta
|
898
|
+
CalcThetaEstimate();
|
899
|
+
break;
|
900
|
+
|
901
|
+
case (int)IRTModelType.WideRange1PL:
|
902
|
+
case (int)IRTModelType.WideRange2PL:
|
903
|
+
//Note: ResponseIndex is the same as the item score: 0 (incorrect) or 1 (correct)
|
904
|
+
_NumCorrectItems = _NumCorrectItems + ResponseIndex;
|
905
|
+
|
906
|
+
if ((ResponseIndex == 1) && (_CurrentBin < (_NumBins - 1))) {
|
907
|
+
_CurrentBin++;
|
908
|
+
}
|
909
|
+
else if ((ResponseIndex == 0) && (_CurrentBin > 0)) {
|
910
|
+
_CurrentBin--;
|
911
|
+
}
|
912
|
+
|
913
|
+
if ((_NumCorrectItems < _ItemsAsked.Count) && (_NumCorrectItems > 0)) {
|
914
|
+
CalcMaximumLikelihoodEstimate();
|
915
|
+
|
916
|
+
if (Math.Abs(_MaximumLikelihoodTheta - _Theta) > _MaxStepChange) {
|
917
|
+
int sign = (_MaximumLikelihoodTheta - _Theta < 0) ? -1 : 1;
|
918
|
+
_Theta = _Theta + sign * _MaxStepChange;
|
919
|
+
}
|
920
|
+
else {
|
921
|
+
_Theta = _MaximumLikelihoodTheta;
|
922
|
+
_StdError = _MaximumLikelihoodError;
|
923
|
+
}
|
924
|
+
}
|
925
|
+
else {
|
926
|
+
_Theta = _BinAverages[_CurrentBin];
|
927
|
+
}
|
928
|
+
|
929
|
+
break;
|
930
|
+
}
|
931
|
+
|
932
|
+
//store histories
|
933
|
+
_ThetaHistory.Add(_Theta);
|
934
|
+
_StdErrorHistory.Add(_StdError);
|
935
|
+
_LikelihoodHistory.Add(_LikelihoodEstimate);
|
936
|
+
}
|
937
|
+
|
938
|
+
//If skip response selected then only check if MaxNumItems has been reached
|
939
|
+
//Need to subtract global skip item counter from ItemsAsked counter AND MaxNumItems count and then compare
|
940
|
+
//Check if stopping criterion is met or asked sufficient questions
|
941
|
+
//if ((_ItemsAsked.Count >= _MaxNumItems) || ((_StdError <= _MaxStdErr) && (_ItemsAsked.Count >= _MinNumItems))) {
|
942
|
+
if ((NumAnsweredItems >= _MaxNumItems) || ((_StdError <= _MaxStdErr) && (NumAnsweredItems >= _MinNumItems)) || (_ItemsAsked.Count >= _NumTotalItems)) {
|
943
|
+
_CriteriaMet = true;
|
944
|
+
_finished = true;
|
945
|
+
}
|
946
|
+
|
947
|
+
//No new theta or standard error for skipped items
|
948
|
+
if (!skipped) {
|
949
|
+
//add theta attribute
|
950
|
+
attrib = node.OwnerDocument.CreateAttribute("Theta");
|
951
|
+
attrib.Value = _Theta.ToString();
|
952
|
+
node.Attributes.SetNamedItem(attrib);
|
953
|
+
|
954
|
+
//add standard error attribute
|
955
|
+
attrib = node.OwnerDocument.CreateAttribute("StdError");
|
956
|
+
attrib.Value = _StdError.ToString();
|
957
|
+
node.Attributes.SetNamedItem(attrib);
|
958
|
+
}
|
959
|
+
|
960
|
+
//update store
|
961
|
+
StringWriter sw = new StringWriter();
|
962
|
+
XmlTextWriter xw = new XmlTextWriter(sw);
|
963
|
+
node.WriteTo(xw);
|
964
|
+
_store[ItemIndex] = sw.ToString();
|
965
|
+
}
|
966
|
+
|
967
|
+
|
968
|
+
/// <summary>
|
969
|
+
/// Gets the previous item.
|
970
|
+
/// </summary>
|
971
|
+
/// <returns>XML document with the previous item</returns>
|
972
|
+
/// <remarks>Not valid for CAT, empty document returned.</remarks>
|
973
|
+
public XmlDocument getPreviousItem()
|
974
|
+
{
|
975
|
+
//_Recalculate = true;
|
976
|
+
return new XmlDocument();
|
977
|
+
}
|
978
|
+
|
979
|
+
|
980
|
+
/// <summary>
|
981
|
+
/// Gets the next item.
|
982
|
+
/// </summary>
|
983
|
+
/// <returns>XML document containing the next item.</returns>
|
984
|
+
public XmlDocument getNextItem()
|
985
|
+
{
|
986
|
+
_position++;
|
987
|
+
return getCurrentItem();
|
988
|
+
}
|
989
|
+
|
990
|
+
|
991
|
+
/// <summary>
|
992
|
+
/// Increments the position.
|
993
|
+
/// </summary>
|
994
|
+
/// <remarks>Not used by CAT, position incremented at getNextItem</remarks>
|
995
|
+
public void incrementPosition()
|
996
|
+
{
|
997
|
+
}
|
998
|
+
|
999
|
+
|
1000
|
+
/// <summary>
|
1001
|
+
/// Decrements the position.
|
1002
|
+
/// </summary>
|
1003
|
+
/// <remarks>Not valid for CAT, does nothing.</remarks>
|
1004
|
+
public void decrementPosition()
|
1005
|
+
{
|
1006
|
+
//_Recalculate = true;
|
1007
|
+
}
|
1008
|
+
|
1009
|
+
|
1010
|
+
/// <summary>
|
1011
|
+
/// Rolls back the position.
|
1012
|
+
/// </summary>
|
1013
|
+
/// <remarks>Not used by CAT, does nothing.</remarks>
|
1014
|
+
public void rollbackPosition()
|
1015
|
+
{
|
1016
|
+
//_Recalculate = true;
|
1017
|
+
}
|
1018
|
+
|
1019
|
+
|
1020
|
+
#endregion
|
1021
|
+
|
1022
|
+
#region Inherited properties
|
1023
|
+
|
1024
|
+
/// <summary>
|
1025
|
+
/// Has the user now finished this form? <c>true</c> if yes, <c>false</c> if not.
|
1026
|
+
/// </summary>
|
1027
|
+
private bool _finished;
|
1028
|
+
/// <summary>
|
1029
|
+
/// Gets a value indicating if the user has now finished this form.
|
1030
|
+
/// </summary>
|
1031
|
+
/// <value><c>true</c> if finished; otherwise, <c>false</c>.</value>
|
1032
|
+
public bool finished
|
1033
|
+
{
|
1034
|
+
get { return _finished; }
|
1035
|
+
set { _finished = value; }
|
1036
|
+
}
|
1037
|
+
|
1038
|
+
/// <summary>
|
1039
|
+
/// Has the user completed this form in the past? <c>true</c> if yes, <c>false</c> if not.
|
1040
|
+
/// </summary>
|
1041
|
+
private bool _IsCompleted;
|
1042
|
+
/// <summary>
|
1043
|
+
/// Gets a value indicating if this form has was completed in the past.
|
1044
|
+
/// </summary>
|
1045
|
+
/// <value>
|
1046
|
+
/// <c>true</c> if this form has been completed; otherwise, <c>false</c>.
|
1047
|
+
/// </value>
|
1048
|
+
public bool IsCompleted
|
1049
|
+
{
|
1050
|
+
get { return _IsCompleted; }
|
1051
|
+
}
|
1052
|
+
|
1053
|
+
/// <summary>
|
1054
|
+
/// Is the user resuming a previously started form? <c>true</c> if yes, <c>false</c> if not.
|
1055
|
+
/// </summary>
|
1056
|
+
private bool _IsResume;
|
1057
|
+
/// <summary>
|
1058
|
+
/// Gets a value indicating whether the user is resuming a previously started form.
|
1059
|
+
/// </summary>
|
1060
|
+
/// <value><c>true</c> if resuming; otherwise, <c>false</c>.</value>
|
1061
|
+
public bool IsResume
|
1062
|
+
{
|
1063
|
+
get { return _IsResume; }
|
1064
|
+
}
|
1065
|
+
|
1066
|
+
/// <summary>
|
1067
|
+
/// Integer containing the current position number on the form.
|
1068
|
+
/// </summary>
|
1069
|
+
private int _currentPosition;
|
1070
|
+
/// <summary>
|
1071
|
+
/// Gets or sets the current position.
|
1072
|
+
/// </summary>
|
1073
|
+
/// <value>The current position.</value>
|
1074
|
+
public int currentPosition
|
1075
|
+
{
|
1076
|
+
get { return _position; }
|
1077
|
+
set
|
1078
|
+
{
|
1079
|
+
_position = value;
|
1080
|
+
_currentPosition = value;
|
1081
|
+
}
|
1082
|
+
}
|
1083
|
+
|
1084
|
+
/// <summary>
|
1085
|
+
/// Integer containing the previous position number on the form.
|
1086
|
+
/// </summary>
|
1087
|
+
private int _previousPosition;
|
1088
|
+
/// <summary>
|
1089
|
+
/// Gets or sets the previous position.
|
1090
|
+
/// </summary>
|
1091
|
+
/// <value>The previous position.</value>
|
1092
|
+
public int previousPosition
|
1093
|
+
{
|
1094
|
+
get { return _position - 1; }
|
1095
|
+
set { _previousPosition = value; }
|
1096
|
+
}
|
1097
|
+
|
1098
|
+
/// <summary>
|
1099
|
+
/// Gets the total number of items.
|
1100
|
+
/// </summary>
|
1101
|
+
/// <value>The total number of items.</value>
|
1102
|
+
public int TotalItems
|
1103
|
+
{
|
1104
|
+
get { return _NumTotalItems; }
|
1105
|
+
}
|
1106
|
+
|
1107
|
+
/// <summary>
|
1108
|
+
/// String containing a message to send back to the caller,
|
1109
|
+
/// </summary>
|
1110
|
+
private string _message;
|
1111
|
+
/// <summary>
|
1112
|
+
/// Gets or sets a message to send back to the caller.
|
1113
|
+
/// </summary>
|
1114
|
+
/// <value>The message.</value>
|
1115
|
+
public string message
|
1116
|
+
{
|
1117
|
+
get { return _message; }
|
1118
|
+
set { _message = value; }
|
1119
|
+
}
|
1120
|
+
|
1121
|
+
/// <summary>
|
1122
|
+
/// String containing the stored procedure name used to get the form and item parameters.
|
1123
|
+
/// </summary>
|
1124
|
+
private string _paramPROC;
|
1125
|
+
/// <summary>
|
1126
|
+
/// Gets the stored procedure name used to get the form and item parameters.
|
1127
|
+
/// </summary>
|
1128
|
+
/// <value>The stored procedure name.</value>
|
1129
|
+
public string paramPROC
|
1130
|
+
{
|
1131
|
+
get { return _paramPROC; }
|
1132
|
+
}
|
1133
|
+
|
1134
|
+
#endregion
|
1135
|
+
|
1136
|
+
#region New private (formerly public) properties
|
1137
|
+
|
1138
|
+
|
1139
|
+
//theta.history, theta.cat
|
1140
|
+
/// <summary>
|
1141
|
+
/// Arraylist containing the theta history.
|
1142
|
+
/// </summary>
|
1143
|
+
private ArrayList _ThetaHistory;
|
1144
|
+
/// <summary>
|
1145
|
+
/// Gets the theta history.
|
1146
|
+
/// </summary>
|
1147
|
+
/// <value>The Theta history.</value>
|
1148
|
+
public double[] ThetaHistory
|
1149
|
+
{
|
1150
|
+
get { return (double[])_ThetaHistory.ToArray(typeof(double)); }
|
1151
|
+
}
|
1152
|
+
|
1153
|
+
//sem.history, sem.cat
|
1154
|
+
/// <summary>
|
1155
|
+
/// Arraylist containing the standard error history.
|
1156
|
+
/// </summary>
|
1157
|
+
private ArrayList _StdErrorHistory;
|
1158
|
+
/// <summary>
|
1159
|
+
/// Gets the standard error history.
|
1160
|
+
/// </summary>
|
1161
|
+
/// <value>The standard error history.</value>
|
1162
|
+
public double[] StdErrorHistory
|
1163
|
+
{
|
1164
|
+
get { return (double[])_StdErrorHistory.ToArray(typeof(double)); }
|
1165
|
+
}
|
1166
|
+
|
1167
|
+
//likelihood.matrix
|
1168
|
+
/// <summary>
|
1169
|
+
/// Arraylist containing the likelihood history.
|
1170
|
+
/// </summary>
|
1171
|
+
private ArrayList _LikelihoodHistory;
|
1172
|
+
/// <summary>
|
1173
|
+
/// Gets the likelihood history.
|
1174
|
+
/// </summary>
|
1175
|
+
/// <value>The likelihood history.</value>
|
1176
|
+
public double[][] LikelihoodHistory
|
1177
|
+
{
|
1178
|
+
get { return (double[][])_LikelihoodHistory.ToArray(typeof(double[])); }
|
1179
|
+
}
|
1180
|
+
|
1181
|
+
|
1182
|
+
#endregion
|
1183
|
+
|
1184
|
+
#region Private properties
|
1185
|
+
|
1186
|
+
/// <summary>
|
1187
|
+
/// String containing the FormItemOID of the first item to be selected (pre-selected rather than calculated).
|
1188
|
+
/// </summary>
|
1189
|
+
private string _FirstItem;
|
1190
|
+
|
1191
|
+
//minTheta
|
1192
|
+
/// <summary>
|
1193
|
+
/// Double containing the minimum theta value.
|
1194
|
+
/// </summary>
|
1195
|
+
private double _MinTheta;
|
1196
|
+
/// <summary>
|
1197
|
+
/// Gets or sets the minimum theta value.
|
1198
|
+
/// </summary>
|
1199
|
+
/// <value>The minimum theta.</value>
|
1200
|
+
private double MinTheta
|
1201
|
+
{
|
1202
|
+
get { return _MinTheta; }
|
1203
|
+
set
|
1204
|
+
{
|
1205
|
+
_MinTheta = value;
|
1206
|
+
InitializeQuadrature();
|
1207
|
+
_PriorDistribution = Util.SetNormalDistribution(_QuadraturePoints, _PriorDistributionMean, _PriorDistributionStdDev);
|
1208
|
+
InitializeLikelihood();
|
1209
|
+
}
|
1210
|
+
}
|
1211
|
+
|
1212
|
+
//maxTheta
|
1213
|
+
/// <summary>
|
1214
|
+
/// Double containing the maximum theta value.
|
1215
|
+
/// </summary>
|
1216
|
+
private double _MaxTheta;
|
1217
|
+
/// <summary>
|
1218
|
+
/// Gets or sets the maximum theta value.
|
1219
|
+
/// </summary>
|
1220
|
+
/// <value>The maximum theta value.</value>
|
1221
|
+
private double MaxTheta
|
1222
|
+
{
|
1223
|
+
get { return _MaxTheta; }
|
1224
|
+
set
|
1225
|
+
{
|
1226
|
+
_MaxTheta = value;
|
1227
|
+
InitializeQuadrature();
|
1228
|
+
_PriorDistribution = Util.SetNormalDistribution(_QuadraturePoints, _PriorDistributionMean, _PriorDistributionStdDev);
|
1229
|
+
InitializeLikelihood();
|
1230
|
+
}
|
1231
|
+
}
|
1232
|
+
|
1233
|
+
//inc
|
1234
|
+
/// <summary>
|
1235
|
+
/// Double containing the theta value increment.
|
1236
|
+
/// </summary>
|
1237
|
+
private double _ThetaIncrement;
|
1238
|
+
/// <summary>
|
1239
|
+
/// Gets or sets the theta value increment.
|
1240
|
+
/// </summary>
|
1241
|
+
/// <value>The theta value increment.</value>
|
1242
|
+
private double ThetaIncrement
|
1243
|
+
{
|
1244
|
+
get { return _ThetaIncrement; }
|
1245
|
+
set
|
1246
|
+
{
|
1247
|
+
_ThetaIncrement = value;
|
1248
|
+
InitializeQuadrature();
|
1249
|
+
_PriorDistribution = Util.SetNormalDistribution(_QuadraturePoints, _PriorDistributionMean, _PriorDistributionStdDev);
|
1250
|
+
InitializeLikelihood();
|
1251
|
+
}
|
1252
|
+
}
|
1253
|
+
|
1254
|
+
/// <summary>
|
1255
|
+
/// Integer containing the item currently presented. The position starts at zero and increments by one
|
1256
|
+
/// for each item as it is presented.
|
1257
|
+
/// </summary>
|
1258
|
+
private int _position;
|
1259
|
+
|
1260
|
+
/// <summary>
|
1261
|
+
/// Arraylist containing each item as an XML node.
|
1262
|
+
/// </summary>
|
1263
|
+
private ArrayList _store;
|
1264
|
+
|
1265
|
+
/// <summary>
|
1266
|
+
/// String containing a shell of the the assessment form in XML format. Does not contain any items.
|
1267
|
+
/// </summary>
|
1268
|
+
private string _form;
|
1269
|
+
|
1270
|
+
/// <summary>
|
1271
|
+
/// Integer containing the number of items available to administer. Does not incude items that have already been presented.
|
1272
|
+
/// Stores 1 if item is available, 0 if not.
|
1273
|
+
/// </summary>
|
1274
|
+
private int[] _ItemsAvailable;
|
1275
|
+
|
1276
|
+
/// <summary>
|
1277
|
+
/// Hashtable containing the FormItemOIDs and their order in the form.<br/>
|
1278
|
+
/// Key: FormItemID (GUID)<br/>
|
1279
|
+
/// Value: array index of item (int)
|
1280
|
+
/// </summary>
|
1281
|
+
private Hashtable _ItemIDs;
|
1282
|
+
|
1283
|
+
/// <summary>
|
1284
|
+
/// Hashtable containing the ResponseItemOIDs and their order in the form and in each item.<br/>
|
1285
|
+
/// Key: array index of item (int)<br/>
|
1286
|
+
/// Value: Hashtable of ItemResponseIDs for this item<br/>
|
1287
|
+
/// -----<br/>
|
1288
|
+
/// ItemResponseIDs hashtable:<br/>
|
1289
|
+
/// Key: ItemResponseID (GUID)<br/>
|
1290
|
+
/// Value: array index of response (int)
|
1291
|
+
/// </summary>
|
1292
|
+
private Hashtable _ResponseIDs;
|
1293
|
+
|
1294
|
+
/// <summary>
|
1295
|
+
/// Arraylist containing the array indexes of items presented to the user
|
1296
|
+
/// </summary>
|
1297
|
+
private ArrayList _ItemsAsked;
|
1298
|
+
|
1299
|
+
/// <summary>
|
1300
|
+
/// Arraylist containing the array indexes of items skipped by the user
|
1301
|
+
/// </summary>
|
1302
|
+
private ArrayList _ItemsSkipped;
|
1303
|
+
|
1304
|
+
/// <summary>
|
1305
|
+
/// Arraylist containing the array indexes of responses from the user
|
1306
|
+
/// </summary>
|
1307
|
+
private ArrayList _Responses;
|
1308
|
+
|
1309
|
+
//maxCat
|
1310
|
+
/// <summary>
|
1311
|
+
/// Integer containing the maximum number of response categories.
|
1312
|
+
/// </summary>
|
1313
|
+
private int _MaxCategories;
|
1314
|
+
|
1315
|
+
//ni
|
1316
|
+
/// <summary>
|
1317
|
+
/// Integer containing the total number of items.
|
1318
|
+
/// </summary>
|
1319
|
+
private int _NumTotalItems;
|
1320
|
+
|
1321
|
+
/// <summary>
|
1322
|
+
/// Integer containing the total number of skipped items.
|
1323
|
+
/// </summary>
|
1324
|
+
private int _NumSkippedItems;
|
1325
|
+
|
1326
|
+
/// <summary>
|
1327
|
+
/// Integer containing the total number of correctly answered items.
|
1328
|
+
/// </summary>
|
1329
|
+
private int _NumCorrectItems;
|
1330
|
+
|
1331
|
+
//maxNI
|
1332
|
+
/// <summary>
|
1333
|
+
/// Integer containing the maximum number of items to administer.
|
1334
|
+
/// </summary>
|
1335
|
+
private int _MaxNumItems;
|
1336
|
+
|
1337
|
+
//minNI
|
1338
|
+
/// <summary>
|
1339
|
+
/// Integer containing the minimum number of items to administer.
|
1340
|
+
/// </summary>
|
1341
|
+
private int _MinNumItems;
|
1342
|
+
|
1343
|
+
//maxSE
|
1344
|
+
/// <summary>
|
1345
|
+
/// Double containing the maximum allowable standard error before terminating.
|
1346
|
+
/// </summary>
|
1347
|
+
private double _MaxStdErr;
|
1348
|
+
|
1349
|
+
//topN
|
1350
|
+
/// <summary>
|
1351
|
+
/// Integer containing the selection group size from which the next item is selected randomly.
|
1352
|
+
/// </summary>
|
1353
|
+
private int _SelectionGroupSize;
|
1354
|
+
|
1355
|
+
//selection.method, method
|
1356
|
+
/// <summary>
|
1357
|
+
/// Integer containing the CAT item selection method to use.
|
1358
|
+
/// </summary>
|
1359
|
+
private int _SelectionMethod;
|
1360
|
+
|
1361
|
+
/// <summary>
|
1362
|
+
/// Integer containing the item response theory model to use.
|
1363
|
+
/// </summary>
|
1364
|
+
private int _IRTModel;
|
1365
|
+
|
1366
|
+
/// <summary>
|
1367
|
+
/// Integer containing the minimum numer of Stradaptive items for Wide Range CAT
|
1368
|
+
/// </summary>
|
1369
|
+
private int _MinNumStradaptive;
|
1370
|
+
|
1371
|
+
//noiseML
|
1372
|
+
/// <summary>
|
1373
|
+
/// Double containing the range of noise in maximum likelihood for Wide Range CAT. The total noise is plus and minus this value (i.e., 2 * _NoiseRange).
|
1374
|
+
/// </summary>
|
1375
|
+
private double _NoiseRange;
|
1376
|
+
|
1377
|
+
/// <summary>
|
1378
|
+
/// Integer containing the default bin to use when none is specified.
|
1379
|
+
/// </summary>
|
1380
|
+
private int _DefaultBin;
|
1381
|
+
|
1382
|
+
//maxIter
|
1383
|
+
/// <summary>
|
1384
|
+
/// Integer containing the maximum number of iterations to use for Wide Range CAT.
|
1385
|
+
/// </summary>
|
1386
|
+
private int _MaxNumIterations;
|
1387
|
+
|
1388
|
+
//maxStep
|
1389
|
+
/// <summary>
|
1390
|
+
/// Double containing the maximum step change for Wide Range CAT
|
1391
|
+
/// </summary>
|
1392
|
+
private double _MaxStepChange;
|
1393
|
+
|
1394
|
+
//crit
|
1395
|
+
/// <summary>
|
1396
|
+
/// Double containing the convergence criterion for Wide Range CAT
|
1397
|
+
/// </summary>
|
1398
|
+
private double _Convergence;
|
1399
|
+
|
1400
|
+
//D
|
1401
|
+
/// <summary>
|
1402
|
+
/// Double containing the logistic scaling constant.
|
1403
|
+
/// </summary>
|
1404
|
+
private double _LogisticScaling;
|
1405
|
+
|
1406
|
+
//start.theta, theta.current
|
1407
|
+
/// <summary>
|
1408
|
+
/// Double containing the current theta value.
|
1409
|
+
/// </summary>
|
1410
|
+
private double _Theta;
|
1411
|
+
|
1412
|
+
//bin.current
|
1413
|
+
/// <summary>
|
1414
|
+
/// Integer containing the current bin assignment.
|
1415
|
+
/// </summary>
|
1416
|
+
private int _CurrentBin;
|
1417
|
+
|
1418
|
+
//NCAT
|
1419
|
+
/// <summary>
|
1420
|
+
/// Integer array containing the number of response categories for each item.
|
1421
|
+
/// </summary>
|
1422
|
+
private int[] _NumCategoriesByItem;
|
1423
|
+
|
1424
|
+
//DISC
|
1425
|
+
/// <summary>
|
1426
|
+
/// Double array containing the discrimination values for each item.
|
1427
|
+
/// </summary>
|
1428
|
+
private double[] _DiscriminationValues;
|
1429
|
+
|
1430
|
+
//B
|
1431
|
+
/// <summary>
|
1432
|
+
/// Double array containing the difficulty values for each item.
|
1433
|
+
/// </summary>
|
1434
|
+
private double[] _Difficulty;
|
1435
|
+
|
1436
|
+
//minB
|
1437
|
+
/// <summary>
|
1438
|
+
/// Double containing the minimum difficulty
|
1439
|
+
/// </summary>
|
1440
|
+
private double _MinDifficulty;
|
1441
|
+
|
1442
|
+
//maxB
|
1443
|
+
/// <summary>
|
1444
|
+
/// Double containing the maximum difficulty
|
1445
|
+
/// </summary>
|
1446
|
+
private double _MaxDifficulty;
|
1447
|
+
|
1448
|
+
//mean.theta.bin
|
1449
|
+
/// <summary>
|
1450
|
+
/// SortedList containing the averages of each bin
|
1451
|
+
/// </summary>
|
1452
|
+
private SortedList<int, double> _BinAverages;
|
1453
|
+
|
1454
|
+
//BIN
|
1455
|
+
/// <summary>
|
1456
|
+
/// Integer array containing the bin assignments for each item.
|
1457
|
+
/// </summary>
|
1458
|
+
private int[] _Bin;
|
1459
|
+
|
1460
|
+
//nBin
|
1461
|
+
/// <summary>
|
1462
|
+
/// Integer containing the number of bins to use for Wide Range CAT.
|
1463
|
+
/// </summary>
|
1464
|
+
private int _NumBins;
|
1465
|
+
|
1466
|
+
//nBin
|
1467
|
+
/// <summary>
|
1468
|
+
/// Integer containing the initial bin to use for this person for Wide Range CAT.
|
1469
|
+
/// </summary>
|
1470
|
+
private int _InitialBin;
|
1471
|
+
|
1472
|
+
//CB
|
1473
|
+
/// <summary>
|
1474
|
+
/// Two dimensional jagged double array containing the category boundary values for each item.
|
1475
|
+
/// </summary>
|
1476
|
+
private double[][] _CategoryBoundaryValues;
|
1477
|
+
|
1478
|
+
//theta
|
1479
|
+
/// <summary>
|
1480
|
+
/// Double array containing the quadrature points for numerical integration
|
1481
|
+
/// </summary>
|
1482
|
+
private double[] _QuadraturePoints;
|
1483
|
+
|
1484
|
+
//nq
|
1485
|
+
/// <summary>
|
1486
|
+
/// Integer containing the number of quadrature points
|
1487
|
+
/// </summary>
|
1488
|
+
private int _NumQuadraturePoints;
|
1489
|
+
|
1490
|
+
//LH
|
1491
|
+
/// <summary>
|
1492
|
+
/// Double array containing the likelihood values for each item.
|
1493
|
+
/// </summary>
|
1494
|
+
private double[] _Likelihood;
|
1495
|
+
|
1496
|
+
//prior
|
1497
|
+
/// <summary>
|
1498
|
+
/// Double array containing the prior distribution of likelihood estimates.
|
1499
|
+
/// </summary>
|
1500
|
+
private double[] _PriorDistribution;
|
1501
|
+
|
1502
|
+
//prior.mean
|
1503
|
+
/// <summary>
|
1504
|
+
/// Double containing the prior distribution mean of likelihood estimates.
|
1505
|
+
/// </summary>
|
1506
|
+
private double _PriorDistributionMean;
|
1507
|
+
|
1508
|
+
//prior.sd
|
1509
|
+
/// <summary>
|
1510
|
+
/// Double containing the prior distribution standard deviation of likelihood estimates.
|
1511
|
+
/// </summary>
|
1512
|
+
private double _PriorDistributionStdDev;
|
1513
|
+
|
1514
|
+
//pp
|
1515
|
+
/// <summary>
|
1516
|
+
/// Three dimensional double array containing the probability values for each item.
|
1517
|
+
/// </summary>
|
1518
|
+
private double[, ,] _Probability;
|
1519
|
+
|
1520
|
+
//matrix.info
|
1521
|
+
/// <summary>
|
1522
|
+
/// Two dimensional double array containing the item information over each of the quadrature points.
|
1523
|
+
/// </summary>
|
1524
|
+
private double[,] _ItemInfoOverQuadrature;
|
1525
|
+
|
1526
|
+
//SEM
|
1527
|
+
/// <summary>
|
1528
|
+
/// Double containing the standard error estimate.
|
1529
|
+
/// </summary>
|
1530
|
+
private double _StdError;
|
1531
|
+
|
1532
|
+
//critMet
|
1533
|
+
/// <summary>
|
1534
|
+
/// Has the stopping criteria been met? <c>true</c> if yes, <c>false</c> if not.
|
1535
|
+
/// </summary>
|
1536
|
+
private bool _CriteriaMet;
|
1537
|
+
|
1538
|
+
/// <summary>
|
1539
|
+
/// Should a full re-calculation from the beginning be performed? <c>true</c> if yes, <c>false</c> if not.
|
1540
|
+
/// </summary>
|
1541
|
+
private bool _Recalculate;
|
1542
|
+
|
1543
|
+
/// <summary>
|
1544
|
+
/// Has the probability matrix been initialized? <c>true</c> if yes, <c>false</c> if not.
|
1545
|
+
/// </summary>
|
1546
|
+
private bool _Initialized;
|
1547
|
+
|
1548
|
+
/// <summary>
|
1549
|
+
/// Random number generator.
|
1550
|
+
/// </summary>
|
1551
|
+
private Random _RandomNumber;
|
1552
|
+
|
1553
|
+
/// <summary>
|
1554
|
+
/// Double array containing the latest likelihood estimates.
|
1555
|
+
/// </summary>
|
1556
|
+
private double[] _LikelihoodEstimate;
|
1557
|
+
|
1558
|
+
/// <summary>
|
1559
|
+
/// Double array containing the Maximum Likelihood theta estimate
|
1560
|
+
/// </summary>
|
1561
|
+
private double _MaximumLikelihoodTheta;
|
1562
|
+
|
1563
|
+
/// <summary>
|
1564
|
+
/// Double array containing the Maximum Likelihood error estimate
|
1565
|
+
/// </summary>
|
1566
|
+
private double _MaximumLikelihoodError;
|
1567
|
+
|
1568
|
+
#endregion
|
1569
|
+
|
1570
|
+
#region Private methods
|
1571
|
+
|
1572
|
+
|
1573
|
+
/// <summary>
|
1574
|
+
/// Initializes the quadrature point array.
|
1575
|
+
/// </summary>
|
1576
|
+
/// <remarks>If MinTheta is greater than MaxTheta, null value is returned.
|
1577
|
+
/// If ThetaIncrement is zero, null value is returned.
|
1578
|
+
/// This also calculates the number of quadrature points.
|
1579
|
+
/// </remarks>
|
1580
|
+
private void InitializeQuadrature()
|
1581
|
+
{
|
1582
|
+
int i;
|
1583
|
+
|
1584
|
+
if (_MinTheta >= _MaxTheta) {
|
1585
|
+
_NumQuadraturePoints = 0;
|
1586
|
+
return;
|
1587
|
+
}
|
1588
|
+
else if (_ThetaIncrement == 0) {
|
1589
|
+
_NumQuadraturePoints = 0;
|
1590
|
+
return;
|
1591
|
+
}
|
1592
|
+
|
1593
|
+
//Calculate quadrature points
|
1594
|
+
ArrayList Points = new ArrayList();
|
1595
|
+
double pnt = _MinTheta;
|
1596
|
+
|
1597
|
+
//Assume 20 decimal places
|
1598
|
+
double Pow10;
|
1599
|
+
i = 1;
|
1600
|
+
double OnePercent = _ThetaIncrement * 0.01;
|
1601
|
+
do {
|
1602
|
+
i--;
|
1603
|
+
Pow10 = Math.Pow(10, i);
|
1604
|
+
} while ((i > -20) && (Pow10 > OnePercent));
|
1605
|
+
int RoundingPlace = -1 * i;
|
1606
|
+
|
1607
|
+
|
1608
|
+
do {
|
1609
|
+
Points.Add(pnt);
|
1610
|
+
pnt = Math.Round((pnt + _ThetaIncrement), RoundingPlace);
|
1611
|
+
} while (pnt <= _MaxTheta);
|
1612
|
+
|
1613
|
+
_QuadraturePoints = (double[])Points.ToArray(typeof(double));
|
1614
|
+
|
1615
|
+
//Calculate number of quadrature points
|
1616
|
+
_NumQuadraturePoints = Points.Count;
|
1617
|
+
}
|
1618
|
+
|
1619
|
+
/// <summary>
|
1620
|
+
/// Initializes the likelihood array.
|
1621
|
+
/// </summary>
|
1622
|
+
private void InitializeLikelihood()
|
1623
|
+
{
|
1624
|
+
if (_NumQuadraturePoints == 0) {
|
1625
|
+
return;
|
1626
|
+
}
|
1627
|
+
_Likelihood = new double[_NumQuadraturePoints];
|
1628
|
+
for (int i = 0; i < _NumQuadraturePoints; i++) {
|
1629
|
+
//Initialize according to likelihood method or posterior method (posterior is the default)
|
1630
|
+
switch (_SelectionMethod) {
|
1631
|
+
case (int)SelectionMethodType.MaximumLikelihoodWeightedInformation:
|
1632
|
+
_Likelihood[i] = 1;
|
1633
|
+
break;
|
1634
|
+
case (int)SelectionMethodType.MaximumPosteriorWeightedInformation:
|
1635
|
+
_Likelihood[i] = _PriorDistribution[i];
|
1636
|
+
break;
|
1637
|
+
default: //MPWI
|
1638
|
+
_Likelihood[i] = _PriorDistribution[i];
|
1639
|
+
break;
|
1640
|
+
}
|
1641
|
+
}
|
1642
|
+
|
1643
|
+
}
|
1644
|
+
|
1645
|
+
/// <summary>
|
1646
|
+
/// Generate matrix for category probabilities and matrix for information for items
|
1647
|
+
/// over quadrature points (Graded Response Model).
|
1648
|
+
/// </summary>
|
1649
|
+
private void PrepareProbabilityInfoGRM()
|
1650
|
+
{
|
1651
|
+
int qpnt;
|
1652
|
+
int item;
|
1653
|
+
int cat;
|
1654
|
+
//pp<-array(0,c(nq,ni,maxCat)) //an array for probability values (nq x ni x maxCat)
|
1655
|
+
_Probability = new double[_NumQuadraturePoints, _NumTotalItems, _MaxCategories];
|
1656
|
+
//matrix.info<-matrix(0,nq,ni) //a matrix for item information over theta (nq x ni)
|
1657
|
+
_ItemInfoOverQuadrature = new double[_NumQuadraturePoints, _NumTotalItems];
|
1658
|
+
//ps
|
1659
|
+
double[,] CumulativeCategory;
|
1660
|
+
|
1661
|
+
//BEWARE Base zero/one errors
|
1662
|
+
//for (i in 1:ni) {
|
1663
|
+
for (item = 0; item < _NumTotalItems; item++) {
|
1664
|
+
//ps<-matrix(0,nq,NCAT[i]+1) //cumulative category functions
|
1665
|
+
CumulativeCategory = new double[_NumQuadraturePoints, _NumCategoriesByItem[item] + 1];
|
1666
|
+
for (qpnt = 0; qpnt < CumulativeCategory.GetLength(0); qpnt++) {
|
1667
|
+
//ps[,1]<-1
|
1668
|
+
CumulativeCategory[qpnt, 0] = 1;
|
1669
|
+
//ps[,NCAT[i]+1]<-0
|
1670
|
+
//CumulativeCategory[qpnt,_NumCategoriesByItem[item]] = 0; //already zero by initialization
|
1671
|
+
}
|
1672
|
+
//for (k in 1:(NCAT[i]-1)) {
|
1673
|
+
for (cat = 0; cat < _NumCategoriesByItem[item] - 1; cat++) {
|
1674
|
+
for (qpnt = 0; qpnt < CumulativeCategory.GetLength(0); qpnt++) {
|
1675
|
+
//ps[,k+1]<-1/(1+exp(-D*DISC[i]*(theta-CB[i,k])))
|
1676
|
+
CumulativeCategory[qpnt, cat + 1] = 1 / (1 + Math.Exp((-1) * _LogisticScaling * _DiscriminationValues[item] * (_QuadraturePoints[qpnt] - _CategoryBoundaryValues[item][cat])));
|
1677
|
+
}
|
1678
|
+
}
|
1679
|
+
for (qpnt = 0; qpnt < _Probability.GetLength(0); qpnt++) {
|
1680
|
+
//pp[,i,1]<-1-ps[,1]
|
1681
|
+
_Probability[qpnt, item, 0] = 1 - CumulativeCategory[qpnt, 0];
|
1682
|
+
//pp[,i,NCAT[i]]<-ps[,NCAT[i]]
|
1683
|
+
_Probability[qpnt, item, _NumCategoriesByItem[item] - 1] = CumulativeCategory[qpnt, _NumCategoriesByItem[item] - 1];
|
1684
|
+
}
|
1685
|
+
|
1686
|
+
//for (k in 1:NCAT[i]) {
|
1687
|
+
for (cat = 0; cat < _NumCategoriesByItem[item]; cat++) {
|
1688
|
+
for (qpnt = 0; qpnt < _Probability.GetLength(0); qpnt++) {
|
1689
|
+
//pp[,i,k]=ps[,k]-ps[,k+1]
|
1690
|
+
_Probability[qpnt, item, cat] = CumulativeCategory[qpnt, cat] - CumulativeCategory[qpnt, cat + 1];
|
1691
|
+
//matrix.info[,i]<- matrix.info[,i] + ( DISC[i] * ( ps[,k] * (1-ps[,k] ) - ps[,k+1] * (1-ps[,k+1] ) ) )^2 / pp[,i,k]
|
1692
|
+
_ItemInfoOverQuadrature[qpnt, item] = _ItemInfoOverQuadrature[qpnt, item] + Math.Pow(_DiscriminationValues[item] * (CumulativeCategory[qpnt, cat] * (1 - CumulativeCategory[qpnt, cat]) - CumulativeCategory[qpnt, cat + 1] * (1 - CumulativeCategory[qpnt, cat + 1])), 2) / _Probability[qpnt, item, cat];
|
1693
|
+
}
|
1694
|
+
}
|
1695
|
+
//For debugging:
|
1696
|
+
//Util.WriteNumericArrayToFile("CumulativeCategory" + item.ToString() + ".txt", false, " ", CumulativeCategory);
|
1697
|
+
}
|
1698
|
+
|
1699
|
+
//For debugging:
|
1700
|
+
//Util.WriteNumericArrayToFile("ItemInfoOverQuadrature.txt", false, " ", _ItemInfoOverQuadrature);
|
1701
|
+
/*
|
1702
|
+
//For debugging:
|
1703
|
+
double[,] ProbItemSlice;
|
1704
|
+
for (int z=0; z<_Probability.GetLength(1); z++) {
|
1705
|
+
ProbItemSlice = new double[_Probability.GetLength(0),_Probability.GetLength(2)];
|
1706
|
+
for (int i=0; i<_Probability.GetLength(0); i++) {
|
1707
|
+
for (int j=0; j<_Probability.GetLength(2); j++) {
|
1708
|
+
ProbItemSlice[i,j] = _Probability[i,z,j];
|
1709
|
+
}
|
1710
|
+
}
|
1711
|
+
Util.WriteNumericArrayToFile("ProbItemSlice" + z.ToString() + ".txt", false, " ", ProbItemSlice);
|
1712
|
+
}
|
1713
|
+
*/
|
1714
|
+
}
|
1715
|
+
|
1716
|
+
|
1717
|
+
/// <summary>
|
1718
|
+
/// Generate matrix for category probabilities and matrix for information for items
|
1719
|
+
/// over quadrature points (Generalized Partial Credit Model).
|
1720
|
+
/// </summary>
|
1721
|
+
private void PrepareProbabilityInfoGPCM()
|
1722
|
+
{
|
1723
|
+
int qpnt;
|
1724
|
+
int item;
|
1725
|
+
int cat;
|
1726
|
+
|
1727
|
+
_Probability = new double[_NumQuadraturePoints, _NumTotalItems, _MaxCategories]; //an array for probability values
|
1728
|
+
_ItemInfoOverQuadrature = new double[_NumQuadraturePoints, _NumTotalItems]; //a matrix for item information over theta
|
1729
|
+
|
1730
|
+
double[,] NumeratorProbability; // numerators for probabilities
|
1731
|
+
double[] DenominatorProbability; // denominators for probabilities
|
1732
|
+
double sdsum; // sum of category parameters
|
1733
|
+
double[] AX; // A component for information
|
1734
|
+
double[] BX; // B component for information
|
1735
|
+
|
1736
|
+
//BEWARE Base zero/one errors
|
1737
|
+
for (item = 0; item < _NumTotalItems; item++) {
|
1738
|
+
|
1739
|
+
NumeratorProbability = new double[_NumQuadraturePoints, _NumCategoriesByItem[item]];
|
1740
|
+
DenominatorProbability = new double[_NumQuadraturePoints];
|
1741
|
+
sdsum = 0.0; //initialize for the item
|
1742
|
+
AX = new double[_NumQuadraturePoints]; //initialize for the item
|
1743
|
+
BX = new double[_NumQuadraturePoints]; //initialize for the item
|
1744
|
+
|
1745
|
+
for (cat = 0; cat < _NumCategoriesByItem[item]; cat++) {
|
1746
|
+
if (cat > 0) {
|
1747
|
+
sdsum += _CategoryBoundaryValues[item][cat - 1];
|
1748
|
+
}
|
1749
|
+
for (qpnt = 0; qpnt < _NumQuadraturePoints; qpnt++) {
|
1750
|
+
NumeratorProbability[qpnt, cat] = Math.Exp(_LogisticScaling * _DiscriminationValues[item] * (cat * _QuadraturePoints[qpnt] - sdsum));
|
1751
|
+
DenominatorProbability[qpnt] += NumeratorProbability[qpnt, cat];
|
1752
|
+
}
|
1753
|
+
}
|
1754
|
+
|
1755
|
+
for (qpnt = 0; qpnt < _Probability.GetLength(0); qpnt++) {
|
1756
|
+
for (cat = 0; cat < _NumCategoriesByItem[item]; cat++) {
|
1757
|
+
_Probability[qpnt, item, cat] = NumeratorProbability[qpnt, cat] / DenominatorProbability[qpnt];
|
1758
|
+
AX[qpnt] += Math.Pow(cat, 2) * _Probability[qpnt, item, cat];
|
1759
|
+
BX[qpnt] += cat * _Probability[qpnt, item, cat];
|
1760
|
+
}
|
1761
|
+
_ItemInfoOverQuadrature[qpnt, item] = Math.Pow(_LogisticScaling, 2) * Math.Pow(_DiscriminationValues[item], 2) * (AX[qpnt] - Math.Pow(BX[qpnt], 2));
|
1762
|
+
}
|
1763
|
+
}
|
1764
|
+
}
|
1765
|
+
|
1766
|
+
|
1767
|
+
/// <summary>
|
1768
|
+
/// Calculates the theta estimate, the standard error, and the new likelihood
|
1769
|
+
/// given questions and their responses.
|
1770
|
+
/// </summary>
|
1771
|
+
//CalcEAP
|
1772
|
+
private void CalcThetaEstimate()
|
1773
|
+
{
|
1774
|
+
double[] QAProbability = new double[_QuadraturePoints.Length];
|
1775
|
+
double[] tmp = new double[_QuadraturePoints.Length];
|
1776
|
+
|
1777
|
+
if (_Recalculate) {
|
1778
|
+
//initialize likelihood estimate with prior information
|
1779
|
+
_LikelihoodEstimate = _PriorDistribution;
|
1780
|
+
|
1781
|
+
//for each item given so far
|
1782
|
+
for (int i = 0; i < _ItemsAsked.Count - 1; i++) {
|
1783
|
+
//if skip response then don't calculate probability or update likelihood estimate
|
1784
|
+
if (!_ItemsSkipped.Contains((int)_ItemsAsked[i])) {
|
1785
|
+
QAProbability = Util.MatrixSlice1D(_Probability, Util.DimensionType.Row, (int)_ItemsAsked[i], Util.DimensionType.Column, (int)_Responses[i], Util.DimensionType.Depth);
|
1786
|
+
_LikelihoodEstimate = Util.MatrixMultiply(_LikelihoodEstimate, QAProbability);
|
1787
|
+
}
|
1788
|
+
}
|
1789
|
+
}
|
1790
|
+
|
1791
|
+
//Since this whole method should be skipped in updateNode for a skip response, don't need to skip again here, but can add a condition check anyway to be safe.
|
1792
|
+
//For skip response we do not want to update probability, likelihood estimate, theta, or standard error
|
1793
|
+
if (!_ItemsSkipped.Contains((int)_ItemsAsked[_ItemsAsked.Count - 1])) {
|
1794
|
+
//new likelihood estimate
|
1795
|
+
QAProbability = Util.MatrixSlice1D(_Probability, Util.DimensionType.Row, (int)_ItemsAsked[_ItemsAsked.Count - 1], Util.DimensionType.Column, (int)_Responses[_Responses.Count - 1], Util.DimensionType.Depth);
|
1796
|
+
_LikelihoodEstimate = Util.MatrixMultiply(_LikelihoodEstimate, QAProbability);
|
1797
|
+
|
1798
|
+
//EAP<-sum(LH*theta)/sum(LH)
|
1799
|
+
_Theta = Util.ArraySum(Util.MatrixMultiply(_LikelihoodEstimate, _QuadraturePoints)) / Util.ArraySum(_LikelihoodEstimate);
|
1800
|
+
|
1801
|
+
//SEM<-sqrt(sum(LH*(theta-EAP)^2)/sum(LH))
|
1802
|
+
tmp.Initialize();
|
1803
|
+
for (int i = 0; i < _QuadraturePoints.Length; i++) {
|
1804
|
+
tmp[i] = Math.Pow((_QuadraturePoints[i] - _Theta), 2);
|
1805
|
+
}
|
1806
|
+
_StdError = Math.Sqrt(Util.ArraySum(Util.MatrixMultiply(_LikelihoodEstimate, tmp)) / Util.ArraySum(_LikelihoodEstimate));
|
1807
|
+
}
|
1808
|
+
|
1809
|
+
_Recalculate = false;
|
1810
|
+
|
1811
|
+
}
|
1812
|
+
|
1813
|
+
/// <summary>
|
1814
|
+
/// Calculates the likelihood weighted information for all available items
|
1815
|
+
/// given likelihood function over quadrature points. This is the primary algorithm
|
1816
|
+
/// for the Maximum Likelihood Weighted Information and the Maximum Posterior
|
1817
|
+
/// Weighted Information CAT method.
|
1818
|
+
/// </summary>
|
1819
|
+
/// <returns>The likelihood weighted information for all available items.</returns>
|
1820
|
+
//calc.LW.info
|
1821
|
+
private double[] CalcLWInfo()
|
1822
|
+
{
|
1823
|
+
double[] info = new double[_Likelihood.Length];
|
1824
|
+
double[,] ar2d = new double[_ItemInfoOverQuadrature.GetLength(0), _ItemInfoOverQuadrature.GetLength(1)];
|
1825
|
+
|
1826
|
+
//info<-apply(matrix.info*lk,2,sum)
|
1827
|
+
ar2d = Util.MatrixMultiply(_ItemInfoOverQuadrature, _Likelihood);
|
1828
|
+
info = Util.MatrixReduceBySum(ar2d, Util.DimensionType.Column);
|
1829
|
+
|
1830
|
+
return info;
|
1831
|
+
}
|
1832
|
+
|
1833
|
+
|
1834
|
+
/// <summary>
|
1835
|
+
/// Get the item values for Wide Range IRT method.
|
1836
|
+
/// </summary>
|
1837
|
+
/// <param name="theta">The current theta value</param>
|
1838
|
+
/// <param name="bin">The current bin assignment</param>
|
1839
|
+
/// <returns>Double array of item values</returns>
|
1840
|
+
private double[] WideRangeItemValues(double theta, int bin)
|
1841
|
+
{
|
1842
|
+
double[] ItemValues = new double[_NumTotalItems];
|
1843
|
+
int[] ItemIndexes = new int[_NumTotalItems];
|
1844
|
+
List<int> Items = new List<int>();
|
1845
|
+
double Probability;
|
1846
|
+
SortedList<int, double> Diffs = new SortedList<int, double>();
|
1847
|
+
double MinDiff;
|
1848
|
+
List<int> BinRange = new List<int>();
|
1849
|
+
|
1850
|
+
//method: #1=Maximum Information (MI) #2=Stradaptive #3=MI within +- noise #4=Stradaptive/MI
|
1851
|
+
|
1852
|
+
//if (method %in% c(1,3) || (method==4 && ni.given>nStradaptive)) {
|
1853
|
+
if ((_SelectionMethod == (int)SelectionMethodType.MaximumInformation || _SelectionMethod == (int)SelectionMethodType.MaximumInformationWithNoise || _SelectionMethod == (int)SelectionMethodType.MaximumInformationProgressiveRestricted)
|
1854
|
+
|| (_SelectionMethod == (int)SelectionMethodType.StradaptiveWithMaximumInformation && _ItemsAsked.Count > _MinNumStradaptive)) {
|
1855
|
+
|
1856
|
+
//bin<-locate(th,mean.theta.bin) # find bin of this theta: closest average value
|
1857
|
+
// locate<-function(val,vec) { which(abs(vec-val)==min(abs(vec-val))) }
|
1858
|
+
foreach (int i in _BinAverages.Keys) {
|
1859
|
+
Diffs[i] = Math.Abs(_BinAverages[i] - theta);
|
1860
|
+
}
|
1861
|
+
MinDiff = Diffs.Values.AsQueryable().Min();
|
1862
|
+
BinRange.Add(Diffs.Keys[Diffs.IndexOfValue(MinDiff)]);
|
1863
|
+
|
1864
|
+
/*
|
1865
|
+
bin<-expand(bin,1,nbin) # expand the bin range by 1
|
1866
|
+
items<-which(BIN %in% bin) # find index locations of items within the bin range
|
1867
|
+
while (sum(items.available[items])==0) { # if no items available within the bin range
|
1868
|
+
bin<-expand(bin,1,nbin) # expand the bin range by 1
|
1869
|
+
items<-which(BIN %in% bin) # find index locations of items within the expanded bin range
|
1870
|
+
} # repeat (if no items available within the bin range)
|
1871
|
+
*/
|
1872
|
+
ItemIndexes = ItemsIndexesInBinRange(BinRange, true);
|
1873
|
+
|
1874
|
+
//if (method==3) th<-th+runif(1)*2*noiseML-noiseML
|
1875
|
+
if (_SelectionMethod == (int)SelectionMethodType.MaximumInformationWithNoise) {
|
1876
|
+
theta = theta + Util.RandomNumber.NextDouble() * 2.0 * _NoiseRange - _NoiseRange;
|
1877
|
+
}
|
1878
|
+
|
1879
|
+
//for (i in items) {
|
1880
|
+
for (int i = 0; i < ItemIndexes.Length; i++) {
|
1881
|
+
//if (items.available[i]==TRUE) {
|
1882
|
+
if ((_ItemsAvailable[i] == 1) && (ItemIndexes[i] == 1)) {
|
1883
|
+
//P<-1/(1+exp(-D*A[i]*(th-B[i])))
|
1884
|
+
Probability = 1 / (1 + Math.Exp((-1) * _LogisticScaling * _DiscriminationValues[i] * (theta - _Difficulty[i])));
|
1885
|
+
//Q<-1-P
|
1886
|
+
//index[i]<-D^2*A[i]^2*P*Q
|
1887
|
+
ItemValues[i] = Math.Pow(_LogisticScaling, 2.0) * Math.Pow(_DiscriminationValues[i], 2.0) * Probability * (1.0 - Probability);
|
1888
|
+
}
|
1889
|
+
}
|
1890
|
+
|
1891
|
+
if (_SelectionMethod == (int)SelectionMethodType.MaximumInformationProgressiveRestricted) {
|
1892
|
+
double MaxItemValue = ItemValues.AsQueryable().Max();
|
1893
|
+
double Weight = (double)(_ItemsAsked.Count + 1) / _MaxNumItems;
|
1894
|
+
|
1895
|
+
for (int i = 0; i < ItemIndexes.Length; i++) {
|
1896
|
+
if ((_ItemsAvailable[i] == 1) && (ItemIndexes[i] == 1)) {
|
1897
|
+
ItemValues[i] = Util.RandomNumber.NextDouble() * MaxItemValue * (1 - Weight) + Weight * ItemValues[i];
|
1898
|
+
}
|
1899
|
+
}
|
1900
|
+
|
1901
|
+
/*
|
1902
|
+
if (method==5) { #added for PR
|
1903
|
+
rc<-numeric(ni) #random component
|
1904
|
+
wt<-ni.given/maxNI #weight
|
1905
|
+
for (i in items) {
|
1906
|
+
if (items.available[i]==TRUE) {
|
1907
|
+
rc[i]<-get.rn()*max(index,na.rm=T) #uniform from (0,H) where H=max information value
|
1908
|
+
index[i]<-rc[i]*(1-wt)+wt*index[i] #PR weighting
|
1909
|
+
}
|
1910
|
+
}
|
1911
|
+
}
|
1912
|
+
*/
|
1913
|
+
}
|
1914
|
+
}
|
1915
|
+
|
1916
|
+
//} else if (method %in% c(2,4)) {
|
1917
|
+
else if ((_SelectionMethod == (int)SelectionMethodType.Stradaptive) || (_SelectionMethod == (int)SelectionMethodType.StradaptiveWithMaximumInformation)) {
|
1918
|
+
BinRange.Add(bin);
|
1919
|
+
|
1920
|
+
//if (is.na(bin)) {
|
1921
|
+
if (BinRange.Count() == 0) {
|
1922
|
+
//items<-1:ni
|
1923
|
+
for (int i = 0; i < ItemIndexes.Count(); i++) {
|
1924
|
+
ItemIndexes[i] = 1;
|
1925
|
+
}
|
1926
|
+
}
|
1927
|
+
//} else {
|
1928
|
+
else {
|
1929
|
+
/*
|
1930
|
+
items<-which(BIN %in% bin)
|
1931
|
+
while (sum(items.available[items])==0) {
|
1932
|
+
bin<-expand(bin,1,nbin)
|
1933
|
+
items<-which(BIN %in% bin)
|
1934
|
+
}
|
1935
|
+
*/
|
1936
|
+
ItemIndexes = ItemsIndexesInBinRange(BinRange, false);
|
1937
|
+
}
|
1938
|
+
/*
|
1939
|
+
for (i in items) {
|
1940
|
+
if (items.available[i]==TRUE) index[i]<-runif(1)
|
1941
|
+
}
|
1942
|
+
*/
|
1943
|
+
for (int i = 0; i < ItemValues.Count(); i++) {
|
1944
|
+
if ((_ItemsAvailable[i] == 1) && (ItemIndexes[i] == 1)) {
|
1945
|
+
ItemValues[i] = Util.RandomNumber.NextDouble();
|
1946
|
+
}
|
1947
|
+
}
|
1948
|
+
}
|
1949
|
+
|
1950
|
+
//return(index);
|
1951
|
+
return ItemValues;
|
1952
|
+
}
|
1953
|
+
|
1954
|
+
|
1955
|
+
/// <summary>
|
1956
|
+
/// Calculates the standard error for the Wide Range IRT method.
|
1957
|
+
/// </summary>
|
1958
|
+
/// <returns>nothing</returns>
|
1959
|
+
private void CalcStandardError()
|
1960
|
+
{
|
1961
|
+
int ItemIndex;
|
1962
|
+
double Probability;
|
1963
|
+
double info = 0.0;
|
1964
|
+
for (int i = 0; i < _ItemsAsked.Count; i++) {
|
1965
|
+
ItemIndex = (int)_ItemsAsked[i];
|
1966
|
+
Probability = 1 / (1 + Math.Exp((-1) * _LogisticScaling * _DiscriminationValues[ItemIndex] * (_Theta - _Difficulty[ItemIndex])));
|
1967
|
+
info = info + Math.Pow(_LogisticScaling, 2) * Math.Pow(_DiscriminationValues[ItemIndex], 2) * Probability * (1 - Probability);
|
1968
|
+
}
|
1969
|
+
_StdError = 1 / Math.Sqrt(info);
|
1970
|
+
|
1971
|
+
/*
|
1972
|
+
calcSE<-function(examinee,ngiven,th) {
|
1973
|
+
info<-0;
|
1974
|
+
for (i in 1:ngiven) {
|
1975
|
+
item<-items.used[examinee,i]
|
1976
|
+
P<-1/(1+exp(-D*A[item]*(th-B[item])))
|
1977
|
+
Q<-1-P
|
1978
|
+
info<-info+D^2*A[item]^2*P*Q
|
1979
|
+
}
|
1980
|
+
SEM<-1/sqrt(info)
|
1981
|
+
return(SEM)
|
1982
|
+
}
|
1983
|
+
*/
|
1984
|
+
}
|
1985
|
+
|
1986
|
+
|
1987
|
+
/// <summary>
|
1988
|
+
/// Calculates the maximum likelihood estimate for the Wide Range IRT method.
|
1989
|
+
/// </summary>
|
1990
|
+
/// <returns>nothing</returns>
|
1991
|
+
private void CalcMaximumLikelihoodEstimate()
|
1992
|
+
{
|
1993
|
+
double ThetaEst = _Theta;
|
1994
|
+
int iteration = 0;
|
1995
|
+
bool converged = false;
|
1996
|
+
double info;
|
1997
|
+
double change;
|
1998
|
+
double SumScore;
|
1999
|
+
double SumProbability;
|
2000
|
+
double Probability;
|
2001
|
+
int ItemIndex;;
|
2002
|
+
int ResponseIndex;
|
2003
|
+
|
2004
|
+
do {
|
2005
|
+
SumScore = 0.0;
|
2006
|
+
SumProbability = 0.0;
|
2007
|
+
info = 0.0;
|
2008
|
+
for (int i = 0; i < _ItemsAsked.Count; i++) {
|
2009
|
+
ItemIndex = (int)_ItemsAsked[i];
|
2010
|
+
ResponseIndex = (int)_Responses[i];
|
2011
|
+
Probability = 1 / ( 1 + Math.Exp((-1) * _LogisticScaling * _DiscriminationValues[ItemIndex] * (ThetaEst - _Difficulty[ItemIndex])) );
|
2012
|
+
info = info + Math.Pow(_LogisticScaling, 2) * Math.Pow(_DiscriminationValues[ItemIndex], 2) * Probability * (1 - Probability);
|
2013
|
+
SumScore = SumScore + _DiscriminationValues[ItemIndex] * ResponseIndex;
|
2014
|
+
SumProbability = SumProbability + _DiscriminationValues[ItemIndex] * Probability;
|
2015
|
+
}
|
2016
|
+
|
2017
|
+
change = (SumScore - SumProbability) / ((-1) * info);
|
2018
|
+
if (Math.Abs(change) <= _Convergence) {
|
2019
|
+
converged = true;
|
2020
|
+
}
|
2021
|
+
if (Math.Abs(change) > 0.5) {
|
2022
|
+
change = (change >= 0) ? 0.5 : -0.5;
|
2023
|
+
}
|
2024
|
+
ThetaEst -= change;
|
2025
|
+
|
2026
|
+
iteration++;
|
2027
|
+
} while ((iteration <= _MaxNumIterations) && (!converged));
|
2028
|
+
|
2029
|
+
_MaximumLikelihoodTheta = ThetaEst;
|
2030
|
+
_MaximumLikelihoodError = 1 / Math.Sqrt(info);
|
2031
|
+
/*
|
2032
|
+
F means FALSE (a logical/Boolean literal)
|
2033
|
+
P means Probability of a correct response
|
2034
|
+
Q means Probability of an incorrect response
|
2035
|
+
U means a realized (observed) response
|
2036
|
+
|
2037
|
+
calcMLE<-function(examinee,ngiven) {
|
2038
|
+
post.theta<-theta.current
|
2039
|
+
nIter<-0
|
2040
|
+
converged<-F
|
2041
|
+
while (nIter<=maxIter && !converged) {
|
2042
|
+
pre.theta<-post.theta
|
2043
|
+
sum.score<-0
|
2044
|
+
sum.p<-0
|
2045
|
+
info<-0
|
2046
|
+
for (i in 1:ngiven) {
|
2047
|
+
item<-items.used[examinee,i]
|
2048
|
+
P<-1/(1+exp(-D*A[item]*(pre.theta-B[item])))
|
2049
|
+
Q<-1-P
|
2050
|
+
info<-info+D^2*A[item]^2*P*Q
|
2051
|
+
U<-resp[examinee,item]
|
2052
|
+
sum.score<-sum.score+A[item]*U
|
2053
|
+
sum.p<-sum.p+A[item]*P
|
2054
|
+
}
|
2055
|
+
change<-(sum.score-sum.p)/-info
|
2056
|
+
if (abs(change)<=crit) converged=T
|
2057
|
+
if (abs(change)>0.5) change<-sign(change)*0.5
|
2058
|
+
post.theta<-pre.theta-change
|
2059
|
+
nIter<-nIter+1
|
2060
|
+
}
|
2061
|
+
return(list(theta=post.theta,SE=1/sqrt(info)))
|
2062
|
+
}
|
2063
|
+
|
2064
|
+
*/
|
2065
|
+
|
2066
|
+
}
|
2067
|
+
|
2068
|
+
|
2069
|
+
|
2070
|
+
private int[] ItemsIndexesInBinRange(List<int> binRange, bool expandRangeFirst)
|
2071
|
+
{
|
2072
|
+
int BinRangeStart;
|
2073
|
+
int BinRangeEnd;
|
2074
|
+
bool NoneFound = true;
|
2075
|
+
int[] BinItemsAvailable = new int[_Bin.Count()];
|
2076
|
+
int ItemNum;
|
2077
|
+
int BinNum;
|
2078
|
+
int iteration = 0;
|
2079
|
+
|
2080
|
+
/*
|
2081
|
+
bin<-expand(bin,1,nbin) # expand the bin range by 1
|
2082
|
+
items<-which(BIN %in% bin) # find index locations of items within the bin range
|
2083
|
+
while (sum(items.available[items])==0) { # if no items available within the bin range
|
2084
|
+
bin<-expand(bin,1,nbin) # expand the bin range by 1
|
2085
|
+
items<-which(BIN %in% bin) # find index locations of items within the expanded bin range
|
2086
|
+
} # repeat (if no items available within the bin range)
|
2087
|
+
*/
|
2088
|
+
|
2089
|
+
do {
|
2090
|
+
iteration++;
|
2091
|
+
if ((expandRangeFirst && (iteration == 1)) || (iteration > 1)) {
|
2092
|
+
BinRangeStart = (_BinAverages.IndexOfKey(binRange[0]) == 0) ? 0 : _BinAverages.IndexOfKey(binRange[0]) - 1;
|
2093
|
+
BinRangeEnd = (_BinAverages.IndexOfKey(binRange[binRange.Count - 1]) == _BinAverages.Count - 1) ? _BinAverages.Count - 1 : _BinAverages.IndexOfKey(binRange[binRange.Count - 1]) + 1;
|
2094
|
+
if (!(binRange.Contains(_BinAverages.Keys[BinRangeStart]))) {
|
2095
|
+
binRange.Add(_BinAverages.Keys[BinRangeStart]);
|
2096
|
+
}
|
2097
|
+
if (!(binRange.Contains(_BinAverages.Keys[BinRangeEnd]))) {
|
2098
|
+
binRange.Add(_BinAverages.Keys[BinRangeEnd]);
|
2099
|
+
}
|
2100
|
+
}
|
2101
|
+
binRange.Sort();
|
2102
|
+
|
2103
|
+
ItemNum = 0;
|
2104
|
+
while (ItemNum < _Bin.Count()) {
|
2105
|
+
BinNum = 0;
|
2106
|
+
while (BinNum < binRange.Count()) {
|
2107
|
+
if (_Bin[ItemNum] == binRange[BinNum] && _ItemsAvailable[ItemNum] == 1) {
|
2108
|
+
BinItemsAvailable[ItemNum] = 1;
|
2109
|
+
NoneFound = false;
|
2110
|
+
}
|
2111
|
+
BinNum++;
|
2112
|
+
}
|
2113
|
+
ItemNum++;
|
2114
|
+
}
|
2115
|
+
} while (NoneFound && !((_BinAverages.IndexOfKey(binRange[0]) == 0) && (_BinAverages.IndexOfKey(binRange[binRange.Count - 1]) == _BinAverages.Count - 1)));
|
2116
|
+
|
2117
|
+
return BinItemsAvailable;
|
2118
|
+
}
|
2119
|
+
|
2120
|
+
#endregion
|
2121
|
+
|
2122
|
+
}
|
2123
|
+
}
|