solve without enemies, initial impl. still requires factoring out

This commit is contained in:
Joshua Burman 2023-11-13 16:37:35 -05:00
parent eb8138eaf9
commit 8667bec8d5
6 changed files with 85 additions and 8 deletions

View File

@ -1,8 +1,11 @@
from typing import List
from lib.irt.test_response_function import TestResponseFunction from lib.irt.test_response_function import TestResponseFunction
from lib.irt.test_information_function import TestInformationFunction from lib.irt.test_information_function import TestInformationFunction
from models.targets.tif_target import TifTarget from models.targets.tif_target import TifTarget
from models.targets.tcc_target import TccTarget from models.targets.tcc_target import TccTarget
from models.item import Item
def generate_tif_results(items, solver_run): def generate_tif_results(items, solver_run):
targets = [] targets = []

View File

@ -14,6 +14,16 @@ class Bundle(BaseModel):
items: List[Item] items: List[Item]
type: str type: str
def find_items(self, requested_items_ids: [int]) -> [Item]:
found_items = []
for item in self.items:
if item.id in requested_items_ids:
found_items.append(item)
return found_items
def tif(self, irt_model: IRTModel, theta: float) -> float: def tif(self, irt_model: IRTModel, theta: float) -> float:
return TestInformationFunction(irt_model).calculate(self.items, theta=theta) return TestInformationFunction(irt_model).calculate(self.items, theta=theta)

View File

@ -10,7 +10,7 @@ class Item(BaseModel):
id: int id: int
position: Optional[int] = None position: Optional[int] = None
passage_id: Optional[int] = None passage_id: Optional[int] = None
enemies: Optional[int] = None enemies: List[int] = []
workflow_state: Optional[str] = None workflow_state: Optional[str] = None
attributes: List[Attribute] = None attributes: List[Attribute] = None
b_param: float = 0.00 b_param: float = 0.00

View File

@ -3,10 +3,12 @@ from typing import TYPE_CHECKING
from pydantic import BaseModel from pydantic import BaseModel
from typing import Any, List from typing import Any, List
from pulp import LpProblem, LpVariable, lpSum from pulp import LpProblem, LpVariable, LpStatus, lpSum
import logging import logging
from helpers import service_helper, irt_helper
from models.solution import Solution from models.solution import Solution
from models.item import Item from models.item import Item
from models.bundle import Bundle from models.bundle import Bundle
@ -38,8 +40,71 @@ class Problem(BaseModel):
upBound=1, upBound=1,
cat='Binary') cat='Binary')
def solve(self) -> LpProblem: def solve(self, solver_run: SolverRun) -> LpProblem:
logging.info('solving problem...')
# if we allow enemies, go through the normal solving process
if solver_run.allow_enemies:
logging.info('enemes allowed, so just solving')
self.problem.solve() self.problem.solve()
# otherwise begin the process of filtering enemies
else:
self.problem.solve()
# however, if the solve was infeasible, kick it back
# to the normal process
if LpStatus[self.problem.status] == 'Infeasible':
return self.problem
# otherwise continue
else:
# get items from solution
solved_items, _ = service_helper.solution_items(self.problem.variables(), solver_run)
sacred_ids = []
enemy_ids = []
# get all enemies
for item in solved_items:
# if it has enemies, check if it exists as part of the solved items
for enemy_id in item.enemies:
# if it does, it's a true enemy
if enemy_id in (item.id for item in solved_items):
enemy_ids.append(enemy_id)
# remove enemy from solved items,
# lest it has this sacred item added to enemies
solved_items = [i for i in solved_items if i.id != enemy_id]
# the item is cleansed, now it's sacred
sacred_ids.append(item.id)
if enemy_ids:
logging.info('enemies found, adding constraints...')
# remove old enemy/sacred constraints
self.problem.constrants.pop('Exclude_enemy_items')
self.problem.constrants.pop('Include_sacred_items')
# add constraint to not allow enemy items
self.problem += lpSum([
len(bundle.find_items(enemy_ids)) * self.solver_bundles_var[bundle.id]
for bundle in self.bundles
] + [
(item.id in enemy_ids) * self.solver_items_var[item.id]
for item in self.items
]) == 0, 'Exclude enemy items'
# add constraint to use sacred items
self.problem += lpSum([
len(bundle.find_items(sacred_ids)) * self.solver_bundles_var[bundle.id]
for bundle in self.bundles
] + [
(item.id in sacred_ids) * self.solver_items_var[item.id]
for item in self.items
]) == len(sacred_ids), 'Include sacred items'
# recursively solve until no enemies exist or infeasible
logging.info('recursively solving...')
self.solve(solver_run)
return self.problem return self.problem
def generate(self, solution: Solution, solver_run: SolverRun) -> None: def generate(self, solution: Solution, solver_run: SolverRun) -> None:
@ -63,5 +128,3 @@ class Problem(BaseModel):
error.msg, error.msg,
error.args[0]) error.args[0])

View File

@ -33,6 +33,7 @@ class SolverRun(BaseModel):
total_forms: int = 1 total_forms: int = 1
theta_cut_score: float = 0.00 theta_cut_score: float = 0.00
drift_style: Literal['constant', 'variable'] = 'constant' drift_style: Literal['constant', 'variable'] = 'constant'
allow_enemies: bool = False
advanced_options: Optional[AdvancedOptions] advanced_options: Optional[AdvancedOptions]
engine: str engine: str

View File

@ -1,6 +1,6 @@
import json, random, io, logging import json, random, io, logging
from pulp import LpProblem, LpVariable, LpMinimize, LpStatus, lpSum from pulp import LpProblem, LpMinimize, LpStatus
from lib.application_configs import ApplicationConfigs from lib.application_configs import ApplicationConfigs
from helpers import aws_helper, tar_helper, csv_helper, service_helper from helpers import aws_helper, tar_helper, csv_helper, service_helper
@ -82,7 +82,7 @@ class FormGenerationService(Base):
# create problem # create problem
problem_handler = Problem(items = self.solver_run.unbundled_items(), bundles = self.solver_run.bundles, problem = LpProblem('ata-form-generate', LpMinimize)) problem_handler = Problem(items = self.solver_run.unbundled_items(), bundles = self.solver_run.bundles, problem = LpProblem('ata-form-generate', LpMinimize))
problem_handler.generate(solution, self.solver_run) problem_handler.generate(solution, self.solver_run)
problem = problem_handler.solve() problem = problem_handler.solve(self.solver_run)
if LpStatus[problem.status] == 'Infeasible': if LpStatus[problem.status] == 'Infeasible':
logging.info( logging.info(