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.
@@ -0,0 +1,3 @@
1
+ module CatEngine
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,9 @@
1
+ require "cat_engine/engine"
2
+
3
+ namespace :cat_engine do
4
+ desc "compile the CAT Engine"
5
+ task :compile_cat_engine do
6
+ CatEngine::Engine.compile
7
+ end
8
+ end
9
+
@@ -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
+ }