@@ -172,3 +172,158 @@ function spec_description(item_bank::CdfMirtItemBank, level)
172172 end
173173 end
174174end
175+
176+ """
177+ ```julia
178+ struct $(FUNCTIONNAME) <: AbstractItemBank
179+ $(FUNCTIONNAME) (distribution, difficulties, discriminations) -> $(FUNCTIONNAME)
180+ DomainType(::SlopeInterceptMirtItemBank) = VectorContinuousDomain()
181+ ResponseType(::SlopeInterceptMirtItemBank) = BooleanResponse()
182+ ```
183+
184+ This item bank corresponds to the slope-intercept version of MIRT in the
185+ literature. Its items feature multidimensional discriminations and its learners
186+ multidimensional abilities, but item difficulties are single-dimensional.
187+ """
188+ struct SlopeInterceptMirtItemBank{DistT <: ContinuousUnivariateDistribution } <: AbstractItemBank
189+ distribution:: DistT
190+ intercepts:: Vector{Float64}
191+ slopes:: Matrix{Float64}
192+
193+ function SlopeInterceptMirtItemBank (
194+ distribution:: DistT ,
195+ intercepts:: Vector{Float64} ,
196+ slopes:: Matrix{Float64}
197+ ) where {DistT <: ContinuousUnivariateDistribution }
198+ if size (slopes, 2 ) != length (intercepts)
199+ error (
200+ " Number of items in first (only) dimension of difficulties " *
201+ " should match number of item in 2nd dimension of discriminations"
202+ )
203+ end
204+ new {typeof(distribution)} (distribution, intercepts, slopes)
205+ end
206+ end
207+
208+ function SlopeInterceptMirtItemBank (item_bank:: CdfMirtItemBank )
209+ SlopeInterceptMirtItemBank (
210+ item_bank. distribution,
211+ [diff * sum (disc) for (diff, disc) in zip (item_bank. difficulties, eachcol (item_bank. discriminations))],
212+ item_bank. discriminations
213+ )
214+ end
215+
216+ function CdfMirtItemBank (item_bank:: SlopeInterceptMirtItemBank )
217+ CdfMirtItemBank (
218+ item_bank. distribution,
219+ [intercept / sum (slope) for (intercept, slope) in zip (item_bank. intercepts, eachcol (item_bank. slopes))],
220+ item_bank. slopes
221+ )
222+ end
223+
224+ DomainType (:: SlopeInterceptMirtItemBank ) = VectorContinuousDomain ()
225+ ResponseType (:: SlopeInterceptMirtItemBank ) = BooleanResponse ()
226+
227+ function Base. length (item_bank:: SlopeInterceptMirtItemBank )
228+ length (item_bank. intercepts)
229+ end
230+
231+ function subset (item_bank:: SlopeInterceptMirtItemBank , idxs)
232+ SlopeInterceptMirtItemBank (
233+ item_bank. distribution,
234+ item_bank. intercepts[idxs],
235+ item_bank. slopes[:, idxs]
236+ )
237+ end
238+
239+ function domdims (item_bank:: SlopeInterceptMirtItemBank )
240+ size (item_bank. slopes, 1 )
241+ end
242+
243+ function _mirt_norm_abil_si (θ, intercept, slope)
244+ dot (θ, slope) .- intercept
245+ end
246+
247+ function norm_abil (ir:: ItemResponse{<:SlopeInterceptMirtItemBank} , θ)
248+ _mirt_norm_abil_si (θ, ir. item_bank. intercepts[ir. index],
249+ @view ir. item_bank. slopes[:, ir. index])
250+ end
251+
252+ function resp_vec (ir:: ItemResponse{<:SlopeInterceptMirtItemBank} , θ)
253+ resp1 = resp (ir, θ)
254+ SVector (1.0 - resp1, resp1)
255+ end
256+
257+ #=
258+ function item_domain(
259+ ir::ItemResponse{<:SlopeInterceptMirtItemBank}; reference_point, mass = default_mass, left_mass = mass, right_mass = mass)
260+ ndims = domdims(ir.item_bank)
261+ z_lo = quantile(ir.item_bank.distribution, left_mass)
262+ z_hi = quantile(ir.item_bank.distribution, 1.0 - right_mass)
263+ lo = fill(Inf, ndims)
264+ hi = fill(-Inf, ndims)
265+ difficulty = ir.item_bank.difficulties[ir.index]
266+ discrimination = @view ir.item_bank.discriminations[:, ir.index]
267+ diff_disc = sum(difficulty .* discrimination)
268+ function nearest_point(z)
269+ c = z + diff_disc
270+ t = (c - dot(reference_point, discrimination)) / sum(discrimination .^ 2)
271+ return reference_point .+ t .* discrimination
272+ end
273+ function update_bounds!(point)
274+ for i in 1:ndims
275+ if point[i] < lo[i]
276+ lo[i] = point[i]
277+ end
278+ if point[i] > hi[i]
279+ hi[i] = point[i]
280+ end
281+ end
282+ end
283+ update_bounds!(nearest_point(z_lo))
284+ update_bounds!(nearest_point(z_hi))
285+ return (lo, hi)
286+ end
287+ =#
288+
289+ function resp (ir:: ItemResponse{<:SlopeInterceptMirtItemBank} , outcome:: Bool , θ)
290+ if outcome
291+ resp (ir, θ)
292+ else
293+ cresp (ir, θ)
294+ end
295+ end
296+
297+ function resp (ir:: ItemResponse{<:SlopeInterceptMirtItemBank} , θ)
298+ cdf (ir. item_bank. distribution, norm_abil (ir, θ))
299+ end
300+
301+ function cresp (ir:: ItemResponse{<:SlopeInterceptMirtItemBank} , θ)
302+ ccdf (ir. item_bank. distribution, norm_abil (ir, θ))
303+ end
304+
305+ function item_params (item_bank:: SlopeInterceptMirtItemBank , idx)
306+ (; intercept = item_bank. intercepts[idx],
307+ slop = @view item_bank. slopes[:, idx])
308+ end
309+
310+ function spec_description (item_bank:: SlopeInterceptMirtItemBank , level)
311+ dim = length (item_bank. slopes)
312+ if item_bank. distribution == normal_scaled_logistic
313+ if level == :long
314+ return " Two parameter $(dim) -dimensional slope-intercept multidimensional item bank with normal scaled logistic distribution"
315+ elseif level == :short
316+ return " 2PL MIRT $(dim) d"
317+ else
318+ return " 2pl_mirt_$(dim) d"
319+ end
320+ else
321+ if level == :long
322+ return " Two parameter $(dim) -dimensional slope-intercept multidimensional item bank with unknown transfer function"
323+ elseif level == :short
324+ return " 2P MIRT $(dim) d"
325+ else
326+ return " 2p_mirt_$(dim) d"
327+ end
328+ end
329+ end
0 commit comments