From 82b6cd25ed676d4b8d1956f4616f7f089c2d8dac Mon Sep 17 00:00:00 2001 From: Joshua Burman Date: Fri, 17 Nov 2023 19:02:42 -0500 Subject: [PATCH] sneaky update: add multi-objective functions --- app/models/item.py | 3 +- app/models/objective_function.py | 56 +++++++++++++++++++------ app/models/problem.py | 10 +++-- app/services/form_generation_service.py | 5 ++- 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/app/models/item.py b/app/models/item.py index 2c1ca66..7f786aa 100644 --- a/app/models/item.py +++ b/app/models/item.py @@ -1,6 +1,5 @@ -import logging from pydantic import BaseModel, validator -from typing import List, Optional, Tuple +from typing import List, Optional from models.attribute import Attribute diff --git a/app/models/objective_function.py b/app/models/objective_function.py index 3a43680..35f9a6c 100644 --- a/app/models/objective_function.py +++ b/app/models/objective_function.py @@ -1,13 +1,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING, Any, Literal, Union -from pydantic import BaseModel -from typing import Dict, List, AnyStr -from pulp import lpSum +from pydantic import BaseModel, validator +from typing import Dict, List +from pulp import lpSum, LpMinimize, LpMaximize from models.targets.tif_target import TifTarget from models.targets.tcc_target import TccTarget from models.problem import Problem +if TYPE_CHECKING: + from models.solver_run import SolverRun + class ObjectiveFunction(BaseModel): # minimizing tif/tcc target value is only option currently # as we add more we can build this out to be more dynamic @@ -15,17 +19,45 @@ class ObjectiveFunction(BaseModel): tif_targets: List[TifTarget] tcc_targets: List[TccTarget] target_variance_percentage: int = 10 - objective: AnyStr = "minimize" + objective: Literal[1,-1] = 1 + functions: List[Literal['tcc', 'tif']] = ['tcc', 'tif'] weight: Dict = {'tif': 1, 'tcc': 1} - def for_problem(self, problem_handler: Problem) -> None: - problem_handler.problem += lpSum([ - bundle.count * problem_handler.solver_bundles_var[bundle.id] - for bundle in problem_handler.bundles - ] + [ - problem_handler.solver_items_var[item.id] - for item in problem_handler.items - ]) + @validator("objective", pre=True) + def set_objective(cls, v) -> List[int]: + if v == 'minimize': + return 1 + elif v == 'maximize': + return -1 + else: + return None + + def for_problem(self, problem_handler: Problem, solver_run: SolverRun) -> List[lpSum]: + functions = [] + + for function in self.functions: + if function == 'tcc': + functions.append(lpSum([ + bundle.trf(solver_run.irt_model, solver_run.theta_cut_score) + * problem_handler.solver_bundles_var[bundle.id] + for bundle in problem_handler.bundles + ] + [ + item.irf(solver_run.irt_model, solver_run.theta_cut_score) * + problem_handler.solver_items_var[item.id] + for item in problem_handler.items + ])) + elif function == 'tif': + problem_handler.problem += lpSum([ + bundle.tif(solver_run.irt_model, solver_run.theta_cut_score) + * problem_handler.solver_bundles_var[bundle.id] + for bundle in problem_handler.bundles + ] + [ + item.iif(solver_run.irt_model, solver_run.theta_cut_score) * + problem_handler.solver_items_var[item.id] + for item in problem_handler.items + ]) + + return functions def increment_targets_drift(self, limit: float or bool, diff --git a/app/models/problem.py b/app/models/problem.py index f08ed73..1179af4 100644 --- a/app/models/problem.py +++ b/app/models/problem.py @@ -43,7 +43,12 @@ class Problem(BaseModel): def solve(self, solver_run: SolverRun, enemy_ids: List[int] = []) -> LpProblem: logging.info('solving problem...') - self.problem.solve() + + # creating problem multi-objective functions + objective_functions = solver_run.objective_function.for_problem(self, solver_run) + self.problem.sequentialSolve(objective_functions) + + return self.problem # NOTICE: Legacy enemies implementation # leaving this in, just in case the current impl fails to function @@ -106,9 +111,6 @@ class Problem(BaseModel): def generate(self, solution: Solution, solver_run: SolverRun) -> None: try: - # creating problem objective function - solver_run.objective_function.for_problem(self) - logging.info('Creating Constraints...') # generic constraints for constraint in solver_run.constraints: diff --git a/app/services/form_generation_service.py b/app/services/form_generation_service.py index 2f14f2f..49d67fc 100644 --- a/app/services/form_generation_service.py +++ b/app/services/form_generation_service.py @@ -1,6 +1,6 @@ import json, random, io, logging -from pulp import LpProblem, LpMinimize, LpStatus +from pulp import LpProblem, LpMinimize, LpMaximize, LpStatus from lib.application_configs import ApplicationConfigs from helpers import aws_helper, tar_helper, csv_helper, service_helper @@ -81,7 +81,8 @@ class FormGenerationService(Base): drift_percent) # create problem - problem_handler = Problem(items = self.solver_run.unbundled_items(), bundles = self.solver_run.bundles, problem = LpProblem('ata-form-generate', LpMinimize)) + problem = LpProblem('ata-form-generate', self.solver_run.objective_function.objective) + problem_handler = Problem(items = self.solver_run.unbundled_items(), bundles = self.solver_run.bundles, problem = problem) problem_handler.generate(solution, self.solver_run) problem = problem_handler.solve(self.solver_run)