diff --git a/src/next_item_rules/NextItemRules.jl b/src/next_item_rules/NextItemRules.jl index b83d119..e028c4e 100644 --- a/src/next_item_rules/NextItemRules.jl +++ b/src/next_item_rules/NextItemRules.jl @@ -46,167 +46,32 @@ export AbilityCovarianceStateCriteria, StateCriteria, ItemCriteria export InformationMatrixCriteria export ScalarizedStateCriteron, ScalarizedItemCriteron -""" -$(TYPEDEF) - -Abstract base type for all item selection rules. All descendants of this type -are expected to implement the interface -`(rule::NextItemRule)(responses::TrackedResponses, items::AbstractItemBank)::Int` - - $(FUNCTIONNAME)(bits...; ability_estimator=nothing, parallel=true) - -Implicit constructor for $(FUNCTIONNAME). Uses any given `NextItemRule` or -delegates to `ItemStrategyNextItemRule`. -""" -abstract type NextItemRule <: CatConfigBase end - -function NextItemRule(bits...; - ability_estimator = nothing, - ability_tracker = nothing, - parallel = true) - @returnsome find1_instance(NextItemRule, bits) - @returnsome ItemStrategyNextItemRule(bits..., - ability_estimator = ability_estimator, - ability_tracker = ability_tracker, - parallel = parallel) -end - -include("./random.jl") -include("./information.jl") -include("./information_special.jl") -include("./objective_function.jl") -include("./expectation.jl") - -const default_prior = IntegralCoeffs.Prior(Cauchy(5, 2)) - -function choose_item_1ply(objective::ItemCriterionT, - responses::TrackedResponseT, - items::AbstractItemBank)::Tuple{ - Int, - Float64 -} where {ItemCriterionT <: ItemCriterion, TrackedResponseT <: TrackedResponses} - #pre_next_item(expectation_tracker, items) - objective_state = init_thread(objective, responses) - min_obj_idx::Int = -1 - min_obj_val::Float64 = Inf - for item_idx in eachindex(items) - # TODO: Add these back in - #@init irf_states_storage = zeros(Int, length(responses) + 1) - if (findfirst(idx -> idx == item_idx, responses.responses.indices) !== nothing) - continue - end - - obj_val = objective(objective_state, responses, item_idx) - - if obj_val <= min_obj_val - min_obj_val = obj_val - min_obj_idx = item_idx - end - end - return (min_obj_idx, min_obj_val) -end - -function init_thread(::ItemCriterion, ::TrackedResponses) - nothing -end - -""" -$(TYPEDEF) -""" -abstract type NextItemStrategy <: CatConfigBase end - -function NextItemStrategy(; parallel = true) - ExhaustiveSearch(parallel) -end - -function NextItemStrategy(bits...; parallel = true) - @returnsome find1_instance(NextItemStrategy, bits) - @returnsome find1_type(NextItemStrategy, bits) typ->typ(; parallel = parallel) - @returnsome NextItemStrategy(; parallel = parallel) -end - -""" -$(TYPEDEF) -$(TYPEDFIELDS) - -""" -@with_kw struct ExhaustiveSearch <: NextItemStrategy - parallel::Bool = false -end - -""" -$(TYPEDEF) -$(TYPEDFIELDS) - -`ItemStrategyNextItemRule` which together with a `NextItemStrategy` acts as an -adapter by which an `ItemCriterion` can serve as a `NextItemRule`. - - $(FUNCTIONNAME)(bits...; ability_estimator=nothing, parallel=true) - -Implicit constructor for $(FUNCTIONNAME). Will default to -`ExhaustiveSearch` when no `NextItemStrategy` is given. -""" -struct ItemStrategyNextItemRule{ - NextItemStrategyT <: NextItemStrategy, - ItemCriterionT <: ItemCriterion -} <: NextItemRule - strategy::NextItemStrategyT - criterion::ItemCriterionT -end - -function ItemStrategyNextItemRule(bits...; - parallel = true, - ability_estimator = nothing, - ability_tracker = nothing) - strategy = NextItemStrategy(bits...; parallel = parallel) - criterion = ItemCriterion(bits...; - ability_estimator = ability_estimator, - ability_tracker = ability_tracker) - if strategy !== nothing && criterion !== nothing - return ItemStrategyNextItemRule(strategy, criterion) - end -end - -function (rule::ItemStrategyNextItemRule{ExhaustiveSearch, ItemCriterionT})(responses, - items) where {ItemCriterionT <: ItemCriterion} - #, rule.strategy.parallel - choose_item_1ply(rule.criterion, responses, items)[1] -end - -function (item_criterion::ItemCriterion)(::Nothing, tracked_responses, item_idx) - item_criterion(tracked_responses, item_idx) -end - -function (item_criterion::ItemCriterion)(tracked_responses, item_idx) - criterion_state = init_thread(item_criterion, tracked_responses) - if criterion_state === nothing - error("Tried to run an state-requiring item criterion $(typeof(item_criterion)), but init_thread(...) returned nothing") - end - item_criterion(criterion_state, tracked_responses, item_idx) -end - -function compute_criteria( - criterion::ItemCriterionT, - responses::TrackedResponseT, - items::AbstractItemBank -) where {ItemCriterionT <: ItemCriterion, TrackedResponseT <: TrackedResponses} - objective_state = init_thread(criterion, responses) - return [criterion(objective_state, responses, item_idx) - for item_idx in eachindex(items)] -end - -function compute_criteria( - rule::ItemStrategyNextItemRule{StrategyT, ItemCriterionT}, - responses, - items -) where {StrategyT, ItemCriterionT <: ItemCriterion} - compute_criteria(rule.criterion, responses, items) -end - -include("./mirt.jl") -include("./aliases.jl") -include("./preallocate.jl") - -include("./ka.jl") +# Prelude +include("./prelude/abstract.jl") +include("./prelude/next_item_rule.jl") +include("./prelude/strategy.jl") +include("./prelude/criteria.jl") +include("./prelude/preallocate.jl") + +# Selection strategies +include("./strategies/random.jl") +include("./strategies/exhaustive.jl") + +# Combinators +include("./combinators/expectation.jl") +include("./combinators/scalarizers.jl") + +# Criteria +include("./criteria/item/information_special.jl") +include("./criteria/item/information_support.jl") +include("./criteria/item/information.jl") +include("./criteria/item/urry.jl") +include("./criteria/state/ability_variance.jl") + +# Porcelain +include("./porcelain/aliases.jl") + +# Experimental +include("./experimental/ka.jl") end diff --git a/src/next_item_rules/expectation.jl b/src/next_item_rules/combinators/expectation.jl similarity index 100% rename from src/next_item_rules/expectation.jl rename to src/next_item_rules/combinators/expectation.jl diff --git a/src/next_item_rules/combinators/scalarizers.jl b/src/next_item_rules/combinators/scalarizers.jl new file mode 100644 index 0000000..7bdf723 --- /dev/null +++ b/src/next_item_rules/combinators/scalarizers.jl @@ -0,0 +1,57 @@ +struct DeterminantScalarizer <: MatrixScalarizer end +(::DeterminantScalarizer)(mat) = det(mat) + +struct TraceScalarizer <: MatrixScalarizer end +(::TraceScalarizer)(mat) = tr(mat) + +struct ScalarizedItemCriteron{ + ItemCriteriaT <: ItemCriteria, + MatrixScalarizerT <: MatrixScalarizer +} <: ItemCriterion + criteria::ItemCriteriaT + scalarizer::MatrixScalarizerT +end + +function (ssc::ScalarizedItemCriteron)(tracked_responses, item_idx) + res = ssc.criteria( + init_thread(ssc.criteria, tracked_responses), tracked_responses, item_idx) |> + ssc.scalarizer + if !should_minimize(ssc.criteria) + res = -res + end + res +end + +struct ScalarizedStateCriteron{ + StateCriteriaT <: StateCriteria, + MatrixScalarizerT <: MatrixScalarizer +} <: StateCriterion + criteria::StateCriteriaT + scalarizer::MatrixScalarizerT +end + +function (ssc::ScalarizedStateCriteron)(tracked_responses) + res = ssc.criteria(tracked_responses) |> ssc.scalarizer + if !should_minimize(ssc.criteria) + res = -res + end + res +end + +struct WeightedStateCriteria{InnerT <: StateCriteria} <: StateCriteria + weights::Vector{Float64} + criteria::InnerT +end + +function (wsc::WeightedStateCriteria)(tracked_responses, item_idx) + wsc.weights' * wsc.criteria(tracked_responses, item_idx) * wsc.weights +end + +struct WeightedItemCriteria{InnerT <: ItemCriteria} <: ItemCriteria + weights::Vector{Float64} + criteria::InnerT +end + +function (wsc::WeightedItemCriteria)(tracked_responses, item_idx) + wsc.weights' * wsc.criteria(tracked_responses, item_idx) * wsc.weights +end diff --git a/src/next_item_rules/criteria/item/information.jl b/src/next_item_rules/criteria/item/information.jl new file mode 100644 index 0000000..b20484c --- /dev/null +++ b/src/next_item_rules/criteria/item/information.jl @@ -0,0 +1,49 @@ +# TODO: Should have Variants for point ability versus distribution ability +struct InformationItemCriterion{AbilityEstimatorT <: PointAbilityEstimator, F} <: + ItemCriterion + ability_estimator::AbilityEstimatorT + expected_item_information::F +end + +function InformationItemCriterion(ability_estimator) + InformationItemCriterion(ability_estimator, expected_item_information) +end + +function (item_criterion::InformationItemCriterion)(tracked_responses::TrackedResponses, + item_idx) + ability = maybe_tracked_ability_estimate(tracked_responses, + item_criterion.ability_estimator) + ir = ItemResponse(tracked_responses.item_bank, item_idx) + return -item_criterion.expected_item_information(ir, ability) +end + +struct InformationMatrixCriteria{AbilityEstimatorT <: AbilityEstimator, F} <: ItemCriteria + ability_estimator::AbilityEstimatorT + expected_item_information::F +end + +function InformationMatrixCriteria(ability_estimator) + InformationMatrixCriteria(ability_estimator, expected_item_information) +end + +function init_thread(item_criterion::InformationMatrixCriteria, + responses::TrackedResponses) + # TODO: No need to do this one per thread. It just need to be done once per + # θ update. + # TODO: Update this to use track!(...) mechanism + ability = maybe_tracked_ability_estimate(responses, item_criterion.ability_estimator) + responses_information(responses.item_bank, responses.responses, ability) +end + +function (item_criterion::InformationMatrixCriteria)(acc_info::Matrix{Float64}, + tracked_responses::TrackedResponses, + item_idx) + # TODO: Add in information from the prior + ability = maybe_tracked_ability_estimate( + tracked_responses, item_criterion.ability_estimator) + return acc_info .+ + item_criterion.expected_item_information( + ItemResponse(tracked_responses.item_bank, item_idx), ability) +end + +should_minimize(::InformationMatrixCriteria) = false diff --git a/src/next_item_rules/information_special.jl b/src/next_item_rules/criteria/item/information_special.jl similarity index 100% rename from src/next_item_rules/information_special.jl rename to src/next_item_rules/criteria/item/information_special.jl diff --git a/src/next_item_rules/information.jl b/src/next_item_rules/criteria/item/information_support.jl similarity index 100% rename from src/next_item_rules/information.jl rename to src/next_item_rules/criteria/item/information_support.jl diff --git a/src/next_item_rules/criteria/item/urry.jl b/src/next_item_rules/criteria/item/urry.jl new file mode 100644 index 0000000..dbf2de3 --- /dev/null +++ b/src/next_item_rules/criteria/item/urry.jl @@ -0,0 +1,22 @@ +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +This item criterion just picks the item with the raw difficulty closest to the +current ability estimate. +""" +struct UrryItemCriterion{AbilityEstimatorT <: PointAbilityEstimator} <: ItemCriterion + ability_estimator::AbilityEstimatorT +end + +# TODO: Slow + poor error handling +function raw_difficulty(item_bank, item_idx) + item_params(item_bank, item_idx).difficulty +end + +function (item_criterion::UrryItemCriterion)(tracked_responses::TrackedResponses, item_idx) + ability = maybe_tracked_ability_estimate(tracked_responses, + item_criterion.ability_estimator) + diff = raw_difficulty(tracked_responses.item_bank, item_idx) + abs(ability - diff) +end diff --git a/src/next_item_rules/objective_function.jl b/src/next_item_rules/criteria/state/ability_variance.jl similarity index 54% rename from src/next_item_rules/objective_function.jl rename to src/next_item_rules/criteria/state/ability_variance.jl index 0c901ba..b685e1b 100644 --- a/src/next_item_rules/objective_function.jl +++ b/src/next_item_rules/criteria/state/ability_variance.jl @@ -1,28 +1,3 @@ -""" -$(TYPEDEF) -""" -abstract type ItemCriterion <: CatConfigBase end - -function ItemCriterion(bits...; ability_estimator = nothing, ability_tracker = nothing) - @returnsome find1_instance(ItemCriterion, bits) - @returnsome find1_type(ItemCriterion, bits) typ->typ( - ability_estimator = ability_estimator, - ability_tracker = ability_tracker) - @returnsome ExpectationBasedItemCriterion(bits...; - ability_estimator = ability_estimator, - ability_tracker = ability_tracker) -end - -""" -$(TYPEDEF) -""" -abstract type StateCriterion <: CatConfigBase end - -function StateCriterion(bits...; ability_estimator = nothing, ability_tracker = nothing) - @returnsome find1_instance(StateCriterion, bits) - @returnsome find1_type(StateCriterion, bits) typ->typ() -end - """ $(TYPEDEF) $(TYPEDFIELDS) @@ -102,44 +77,37 @@ function (criterion::AbilityVarianceStateCriterion)(::Vector, denom) end -""" -$(TYPEDEF) -$(TYPEDFIELDS) - -This item criterion just picks the item with the raw difficulty closest to the -current ability estimate. -""" -struct UrryItemCriterion{AbilityEstimatorT <: PointAbilityEstimator} <: ItemCriterion - ability_estimator::AbilityEstimatorT -end - -# TODO: Slow + poor error handling -function raw_difficulty(item_bank, item_idx) - item_params(item_bank, item_idx).difficulty -end - -function (item_criterion::UrryItemCriterion)(tracked_responses::TrackedResponses, item_idx) - ability = maybe_tracked_ability_estimate(tracked_responses, - item_criterion.ability_estimator) - diff = raw_difficulty(tracked_responses.item_bank, item_idx) - abs(ability - diff) +struct AbilityCovarianceStateCriteria{ + DistEstT <: DistributionAbilityEstimator, + IntegratorT <: AbilityIntegrator +} <: StateCriteria + dist_est::DistEstT + integrator::IntegratorT + skip_zero::Bool end -# TODO: Should have Variants for point ability versus distribution ability -struct InformationItemCriterion{AbilityEstimatorT <: PointAbilityEstimator, F} <: - ItemCriterion - ability_estimator::AbilityEstimatorT - expected_item_information::F +function AbilityCovarianceStateCriteria(bits...) + skip_zero = false + @requiresome (dist_est, integrator) = _get_dist_est_and_integrator(bits...) + return AbilityCovarianceStateCriteria(dist_est, integrator, skip_zero) end -function InformationItemCriterion(ability_estimator) - InformationItemCriterion(ability_estimator, expected_item_information) -end +# XXX: Should be at type level +should_minimize(::AbilityCovarianceStateCriteria) = true -function (item_criterion::InformationItemCriterion)(tracked_responses::TrackedResponses, - item_idx) - ability = maybe_tracked_ability_estimate(tracked_responses, - item_criterion.ability_estimator) - ir = ItemResponse(tracked_responses.item_bank, item_idx) - return -item_criterion.expected_item_information(ir, ability) +function (criteria::AbilityCovarianceStateCriteria)( + tracked_responses::TrackedResponses, + denom = normdenom(criteria.integrator, + criteria.dist_est, + tracked_responses) +) + if denom == 0.0 && criteria.skip_zero + return Inf + end + covariance_matrix( + criteria.integrator, + criteria.dist_est, + tracked_responses, + denom + ) end diff --git a/src/next_item_rules/ka.jl b/src/next_item_rules/experimental/ka.jl similarity index 100% rename from src/next_item_rules/ka.jl rename to src/next_item_rules/experimental/ka.jl diff --git a/src/next_item_rules/mirt.jl b/src/next_item_rules/mirt.jl deleted file mode 100644 index 8fdc805..0000000 --- a/src/next_item_rules/mirt.jl +++ /dev/null @@ -1,128 +0,0 @@ -abstract type MatrixScalarizer end - -struct DeterminantScalarizer <: MatrixScalarizer end -(::DeterminantScalarizer)(mat) = det(mat) - -struct TraceScalarizer <: MatrixScalarizer end -(::TraceScalarizer)(mat) = tr(mat) - -abstract type StateCriteria end -abstract type ItemCriteria end - -struct AbilityCovarianceStateCriteria{ - DistEstT <: DistributionAbilityEstimator, - IntegratorT <: AbilityIntegrator -} <: StateCriteria - dist_est::DistEstT - integrator::IntegratorT - skip_zero::Bool -end - -function AbilityCovarianceStateCriteria(bits...) - skip_zero = false - @requiresome (dist_est, integrator) = _get_dist_est_and_integrator(bits...) - return AbilityCovarianceStateCriteria(dist_est, integrator, skip_zero) -end - -# XXX: Should be at type level -should_minimize(::AbilityCovarianceStateCriteria) = true - -function (criteria::AbilityCovarianceStateCriteria)( - tracked_responses::TrackedResponses, - denom = normdenom(criteria.integrator, - criteria.dist_est, - tracked_responses) -) - if denom == 0.0 && criteria.skip_zero - return Inf - end - covariance_matrix( - criteria.integrator, - criteria.dist_est, - tracked_responses, - denom - ) -end - -struct ScalarizedStateCriteron{ - StateCriteriaT <: StateCriteria, - MatrixScalarizerT <: MatrixScalarizer -} <: StateCriterion - criteria::StateCriteriaT - scalarizer::MatrixScalarizerT -end - -function (ssc::ScalarizedStateCriteron)(tracked_responses) - res = ssc.criteria(tracked_responses) |> ssc.scalarizer - if !should_minimize(ssc.criteria) - res = -res - end - res -end - -struct InformationMatrixCriteria{AbilityEstimatorT <: AbilityEstimator, F} <: ItemCriteria - ability_estimator::AbilityEstimatorT - expected_item_information::F -end - -function InformationMatrixCriteria(ability_estimator) - InformationMatrixCriteria(ability_estimator, expected_item_information) -end - -function init_thread(item_criterion::InformationMatrixCriteria, - responses::TrackedResponses) - # TODO: No need to do this one per thread. It just need to be done once per - # θ update. - # TODO: Update this to use track!(...) mechanism - ability = maybe_tracked_ability_estimate(responses, item_criterion.ability_estimator) - responses_information(responses.item_bank, responses.responses, ability) -end - -function (item_criterion::InformationMatrixCriteria)(acc_info::Matrix{Float64}, - tracked_responses::TrackedResponses, - item_idx) - # TODO: Add in information from the prior - ability = maybe_tracked_ability_estimate( - tracked_responses, item_criterion.ability_estimator) - return acc_info .+ - item_criterion.expected_item_information( - ItemResponse(tracked_responses.item_bank, item_idx), ability) -end - -should_minimize(::InformationMatrixCriteria) = false - -struct ScalarizedItemCriteron{ - ItemCriteriaT <: ItemCriteria, - MatrixScalarizerT <: MatrixScalarizer -} <: ItemCriterion - criteria::ItemCriteriaT - scalarizer::MatrixScalarizerT -end - -function (ssc::ScalarizedItemCriteron)(tracked_responses, item_idx) - res = ssc.criteria( - init_thread(ssc.criteria, tracked_responses), tracked_responses, item_idx) |> - ssc.scalarizer - if !should_minimize(ssc.criteria) - res = -res - end - res -end - -struct WeightedStateCriteria{InnerT <: StateCriteria} <: StateCriteria - weights::Vector{Float64} - criteria::InnerT -end - -function (wsc::WeightedStateCriteria)(tracked_responses, item_idx) - wsc.weights' * wsc.criteria(tracked_responses, item_idx) * wsc.weights -end - -struct WeightedItemCriteria{InnerT <: ItemCriteria} <: ItemCriteria - weights::Vector{Float64} - criteria::InnerT -end - -function (wsc::WeightedItemCriteria)(tracked_responses, item_idx) - wsc.weights' * wsc.criteria(tracked_responses, item_idx) * wsc.weights -end diff --git a/src/next_item_rules/aliases.jl b/src/next_item_rules/porcelain/aliases.jl similarity index 100% rename from src/next_item_rules/aliases.jl rename to src/next_item_rules/porcelain/aliases.jl diff --git a/src/next_item_rules/porcelain/porcelain.jl b/src/next_item_rules/porcelain/porcelain.jl new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/next_item_rules/porcelain/porcelain.jl @@ -0,0 +1 @@ + diff --git a/src/next_item_rules/prelude/abstract.jl b/src/next_item_rules/prelude/abstract.jl new file mode 100644 index 0000000..e00dba4 --- /dev/null +++ b/src/next_item_rules/prelude/abstract.jl @@ -0,0 +1,33 @@ + +""" +$(TYPEDEF) + +Abstract base type for all item selection rules. All descendants of this type +are expected to implement the interface +`(rule::NextItemRule)(responses::TrackedResponses, items::AbstractItemBank)::Int` + + $(FUNCTIONNAME)(bits...; ability_estimator=nothing, parallel=true) + +Implicit constructor for $(FUNCTIONNAME). Uses any given `NextItemRule` or +delegates to `ItemStrategyNextItemRule`. +""" +abstract type NextItemRule <: CatConfigBase end + +""" +$(TYPEDEF) +""" +abstract type NextItemStrategy <: CatConfigBase end + +""" +$(TYPEDEF) +""" +abstract type ItemCriterion <: CatConfigBase end + +""" +$(TYPEDEF) +""" +abstract type StateCriterion <: CatConfigBase end + +abstract type MatrixScalarizer end +abstract type StateCriteria end +abstract type ItemCriteria end diff --git a/src/next_item_rules/prelude/criteria.jl b/src/next_item_rules/prelude/criteria.jl new file mode 100644 index 0000000..4c9986c --- /dev/null +++ b/src/next_item_rules/prelude/criteria.jl @@ -0,0 +1,50 @@ +#= Single dimensional =# + +function ItemCriterion(bits...; ability_estimator = nothing, ability_tracker = nothing) + @returnsome find1_instance(ItemCriterion, bits) + @returnsome find1_type(ItemCriterion, bits) typ->typ( + ability_estimator = ability_estimator, + ability_tracker = ability_tracker) + @returnsome ExpectationBasedItemCriterion(bits...; + ability_estimator = ability_estimator, + ability_tracker = ability_tracker) +end + +function init_thread(::ItemCriterion, ::TrackedResponses) + nothing +end + +function StateCriterion(bits...; ability_estimator = nothing, ability_tracker = nothing) + @returnsome find1_instance(StateCriterion, bits) + @returnsome find1_type(StateCriterion, bits) typ->typ() +end + +function (item_criterion::ItemCriterion)(::Nothing, tracked_responses, item_idx) + item_criterion(tracked_responses, item_idx) +end + +function (item_criterion::ItemCriterion)(tracked_responses, item_idx) + criterion_state = init_thread(item_criterion, tracked_responses) + if criterion_state === nothing + error("Tried to run an state-requiring item criterion $(typeof(item_criterion)), but init_thread(...) returned nothing") + end + item_criterion(criterion_state, tracked_responses, item_idx) +end + +function compute_criteria( + criterion::ItemCriterionT, + responses::TrackedResponseT, + items::AbstractItemBank +) where {ItemCriterionT <: ItemCriterion, TrackedResponseT <: TrackedResponses} + objective_state = init_thread(criterion, responses) + return [criterion(objective_state, responses, item_idx) + for item_idx in eachindex(items)] +end + +function compute_criteria( + rule::ItemStrategyNextItemRule{StrategyT, ItemCriterionT}, + responses, + items +) where {StrategyT, ItemCriterionT <: ItemCriterion} + compute_criteria(rule.criterion, responses, items) +end diff --git a/src/next_item_rules/prelude/next_item_rule.jl b/src/next_item_rules/prelude/next_item_rule.jl new file mode 100644 index 0000000..244efdd --- /dev/null +++ b/src/next_item_rules/prelude/next_item_rule.jl @@ -0,0 +1,11 @@ + +function NextItemRule(bits...; + ability_estimator = nothing, + ability_tracker = nothing, + parallel = true) + @returnsome find1_instance(NextItemRule, bits) + @returnsome ItemStrategyNextItemRule(bits..., + ability_estimator = ability_estimator, + ability_tracker = ability_tracker, + parallel = parallel) +end diff --git a/src/next_item_rules/preallocate.jl b/src/next_item_rules/prelude/preallocate.jl similarity index 100% rename from src/next_item_rules/preallocate.jl rename to src/next_item_rules/prelude/preallocate.jl diff --git a/src/next_item_rules/prelude/strategy.jl b/src/next_item_rules/prelude/strategy.jl new file mode 100644 index 0000000..995831f --- /dev/null +++ b/src/next_item_rules/prelude/strategy.jl @@ -0,0 +1,42 @@ +function NextItemStrategy(; parallel = true) + ExhaustiveSearch(parallel) +end + +function NextItemStrategy(bits...; parallel = true) + @returnsome find1_instance(NextItemStrategy, bits) + @returnsome find1_type(NextItemStrategy, bits) typ->typ(; parallel = parallel) + @returnsome NextItemStrategy(; parallel = parallel) +end + +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +`ItemStrategyNextItemRule` which together with a `NextItemStrategy` acts as an +adapter by which an `ItemCriterion` can serve as a `NextItemRule`. + + $(FUNCTIONNAME)(bits...; ability_estimator=nothing, parallel=true) + +Implicit constructor for $(FUNCTIONNAME). Will default to +`ExhaustiveSearch` when no `NextItemStrategy` is given. +""" +struct ItemStrategyNextItemRule{ + NextItemStrategyT <: NextItemStrategy, + ItemCriterionT <: ItemCriterion +} <: NextItemRule + strategy::NextItemStrategyT + criterion::ItemCriterionT +end + +function ItemStrategyNextItemRule(bits...; + parallel = true, + ability_estimator = nothing, + ability_tracker = nothing) + strategy = NextItemStrategy(bits...; parallel = parallel) + criterion = ItemCriterion(bits...; + ability_estimator = ability_estimator, + ability_tracker = ability_tracker) + if strategy !== nothing && criterion !== nothing + return ItemStrategyNextItemRule(strategy, criterion) + end +end diff --git a/src/next_item_rules/strategies/exhaustive.jl b/src/next_item_rules/strategies/exhaustive.jl new file mode 100644 index 0000000..7f20895 --- /dev/null +++ b/src/next_item_rules/strategies/exhaustive.jl @@ -0,0 +1,41 @@ +function exhaustive_search(objective::ItemCriterionT, + responses::TrackedResponseT, + items::AbstractItemBank)::Tuple{ + Int, + Float64 +} where {ItemCriterionT <: ItemCriterion, TrackedResponseT <: TrackedResponses} + #pre_next_item(expectation_tracker, items) + objective_state = init_thread(objective, responses) + min_obj_idx::Int = -1 + min_obj_val::Float64 = Inf + for item_idx in eachindex(items) + # TODO: Add these back in + #@init irf_states_storage = zeros(Int, length(responses) + 1) + if (findfirst(idx -> idx == item_idx, responses.responses.indices) !== nothing) + continue + end + + obj_val = objective(objective_state, responses, item_idx) + + if obj_val <= min_obj_val + min_obj_val = obj_val + min_obj_idx = item_idx + end + end + return (min_obj_idx, min_obj_val) +end + +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +""" +@with_kw struct ExhaustiveSearch <: NextItemStrategy + parallel::Bool = false +end + +function (rule::ItemStrategyNextItemRule{ExhaustiveSearch, ItemCriterionT})(responses, + items) where {ItemCriterionT <: ItemCriterion} + #, rule.strategy.parallel + exhaustive_search(rule.criterion, responses, items)[1] +end diff --git a/src/next_item_rules/random.jl b/src/next_item_rules/strategies/random.jl similarity index 100% rename from src/next_item_rules/random.jl rename to src/next_item_rules/strategies/random.jl