from pulp import lpSum from random import randint, sample import logging from models.bundle import Bundle from lib.errors.item_generation_error import ItemGenerationError def build_constraints(solver_run, problem, items, bundles): try: total_form_items = solver_run.total_form_items constraints = solver_run.constraints for constraint in constraints: attribute = constraint.reference_attribute min = constraint.minimum max = constraint.maximum if attribute.type == 'metadata': con = dict(zip([item.id for item in solver_run.items], [item.attribute_exists(attribute) for item in solver_run.items])) problem += lpSum([con[item.id] * items[item.id] for item in solver_run.items]) >= round(total_form_items * (min / 100)), f'{attribute.id} - {attribute.value} - min' problem += lpSum([con[item.id] * items[item.id] for item in solver_run.items]) <= round(total_form_items * (max / 100)), f'{attribute.id} - {attribute.value} - max' elif attribute.type == 'bundle': # TODO: account for many different bundle types, since the id condition in L33 could yield duplicates if solver_run.bundles != None: # total_bundles = randint(constraint.minimum, constraint.maximum) # selected_bundles = sample(solver_run.bundles, total_bundles, solver_run.bundles) total_bundle_items = 0 selected_bundles = get_random_bundles(solver_run.total_form_items, solver_run.bundles, int(constraint.minimum), int(constraint.maximum)) for bundle in selected_bundles: con = dict(zip([item.id for item in solver_run.items], [(getattr(item, bundle.type, False) == bundle.id) for item in solver_run.items])) problem += lpSum([con[item.id] * items[item.id] for item in solver_run.items]) == bundle.count, f'Bundle constraint for {bundle.type} ({bundle.id})' total_bundle_items += bundle.count # make sure all other items added to the form # are not a part of any bundle # currently only supports single bundle constraints, will need refactoring for multiple bundle constraints con = dict(zip([item.id for item in solver_run.items], [(getattr(item, attribute.id, None) == None) for item in solver_run.items])) problem += lpSum([con[item.id] * items[item.id] for item in solver_run.items]) == solver_run.total_form_items - total_bundle_items, f'Remaining items are not of a bundle type' return problem except ValueError as error: logging.error(error) raise ItemGenerationError("Bundle min and/or max larger than bundle amount provided", error.args[0]) def get_random_bundles(total_form_items: int, bundles: list[Bundle], min: int , max: int, found_bundles = False) -> list[Bundle]: selected_bundles = None total_bundle_items = 0 total_bundles = randint(min, max) while found_bundles == False: selected_bundles = sample(bundles, total_bundles) total_bundle_items = sum(bundle.count for bundle in selected_bundles) if total_bundle_items <= total_form_items: found_bundles = True if found_bundles == True: return selected_bundles else: return get_random_bundles(total_form_items, total_bundles - 1, bundles)