# Local Dev Sandbox for the solver # Useful for testing some concepts and functionality # and offers a much faster feedback loop than the usual end-to-end process in Local Dev # # How to use: # 1. run `compose exec meazure-solver bash` # 2. run `python` # 3. import this file in the python repl by `from services.solver_sandbox import SolverSandbox` # 4. run any of the methds below e.g. `SolverSandbox.yas_elastic()` import logging from pulp import LpProblem, LpVariable, LpInteger, LpMinimize, LpMaximize, LpAffineExpression, LpConstraint, LpStatus, lpSum from services.loft_service import LoftService class SolverSandbox: def loft_service(): body = {'Records': [{'eventVersion': '2.1', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-1', 'eventTime': '2022-03-09T14:40:04.115Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': {'principalId': 'AIDAJDPLRKLG7UEXAMPLE'}, 'requestParameters': {'sourceIPAddress': '127.0.0.1'}, 'responseElements': {'x-amz-request-id': '4629a38d', 'x-amz-id-2': 'eftixk72aD6Ap51TnqcoF8eFidJG9Z/2'}, 's3': {'s3SchemaVersion': '1.0', 'configurationId': 'testConfigRule', 'bucket': {'name': 'measure-local-solver-ingest', 'ownerIdentity': {'principalId': 'A3NL1KOZZKExample'}, 'arn': 'arn:aws:s3:::measure-local-solver-ingest'}, 'object': {'key': 'baf511b0-81e4-013a-6e98-0242ac120010_solver_run.tar.gz', 'size': 509, 'eTag': '"4c0911a335c6feca5493d63b58654e3a"', 'versionId': None, 'sequencer': '0055AED6DCD90281E5'}}}]} LoftService(body).process() def yosh_loop(): Items = [1,2,3,4,5] tif = { 1: 0.2, 2: 0.5, 3: 0.3, 4: 0.8, 5: 0.1 } iif = { 1: 0.09, 2: 0.2, 3: 0.113, 4: 0.3, 5: 0.1 } drift = 0.0 drift_limit = 0.2 iif_target = 0.5 tif_target = 0.9 item_vars = LpVariable.dicts("Item", Items, cat="Binary") while drift <= drift_limit: prob = LpProblem("tif_tcc_test", LpMinimize) prob += lpSum([(tif[i] + iif[i]) * item_vars[i] for i in Items]), "TifTccSum" prob += lpSum([item_vars[i] for i in Items]) == 3, "TotalItems" prob += lpSum([tif[i] * item_vars[i] for i in Items]) >= tif_target - (tif_target * drift), 'TifMin' prob += lpSum([tif[i] * item_vars[i] for i in Items]) <= tif_target + (tif_target * drift), 'TifMax' prob += lpSum([iif[i] * item_vars[i] for i in Items]) >= iif_target - (iif_target * drift), 'TccMin' prob += lpSum([iif[i] * item_vars[i] for i in Items]) <= iif_target + (iif_target * drift), 'TccMax' prob.solve() print(prob) if LpStatus[prob.status] == "Infeasible": print('attempt infeasible') for v in prob.variables(): print(v.name, "=", v.varValue) drift += 0.02 else: print(f"solution found with drift of {drift}!") for v in prob.variables(): print(v.name, "=", v.varValue) break def yas_elastic(tif_targets = [50.0], tcc_targets = [50.0]): Bundles = [1,2,3,4,5] Items = [1,2,3,4,5] # For TIF target tif = { 1: 10, 2: 20, 3: 40, 4: 60, 5: 80 } iif = { 1: 10, 2: 20, 3: 30, 4: 50, 5: 70 } # --- # For TCC target trf = { 1: 100, 2: 200, 3: 400, 4: 600, 5: 800 } irf = { 1: 100, 2: 200, 3: 300, 4: 500, 5: 700 } # --- total_forms = 2 items = LpVariable.dicts('Item', Items, cat='Binary') bundles = LpVariable.dicts('Bundle', Bundles, cat='Binary') for form in range(total_forms): drift = 0 max_drift = 10 # 10% elasticity while drift <= max_drift: drift_percent = drift / 100 problem = LpProblem('TIF_TCC', LpMinimize) # objective function problem += lpSum( [ (iif[i] + irf[i]) * items[i] for i in Items ] + [ (tif[b] + trf[b]) * bundles[b] for b in Bundles ] ), 'TIF_TCC_Sum' # Constraint 1 problem += lpSum( [ items[i] for i in Items ] + [ bundles[b] for b in Bundles ] ) == 3, 'TotalItems' for tif_target in tif_targets: print(f"Calculating TIF target of {tif_target} with drift of {drift} for Form {form + 1}") # Our own "Elastic Constraints" problem += lpSum( [ tif[b] * bundles[b] for b in Bundles ] + [ iif[i] * items[i] for i in Items ] ) >= tif_target - (tif_target * drift_percent), 'TifIifMin' problem += lpSum( [ tif[b] * bundles[b] for b in Bundles ] + [ iif[i] * items[i] for i in Items ] ) <= tif_target + (tif_target * drift_percent), 'TifIifMax' for tcc_target in tcc_targets: print(f"Calculating TCC target of {tcc_target} with drift of {drift} for Form {form + 1}") # Our own "Elastic Constraints" problem += lpSum( [ trf[b] * bundles[b] for b in Bundles ] + [ irf[i] * items[i] for i in Items ] ) >= tcc_target - (tcc_target * drift_percent), 'TrfIrffMin' problem += lpSum( [ trf[b] * bundles[b] for b in Bundles ] + [ irf[i] * items[i] for i in Items ] ) <= tcc_target + (tcc_target * drift_percent), 'TrfIrfMax' problem.solve() if LpStatus[problem.status] == 'Infeasible': print(f"attempt infeasible for drift of {drift}") for v in problem.variables(): print(v.name, "=", v.varValue) print(problem.constraints) print(problem.objective) drift += 1 # breakpoint() if drift == 10: else: print(f"solution found with drift of {drift}!") for v in problem.variables(): print(v.name, "=", v.varValue) print(problem.constraints) print(problem.objective) break # Implementation of the Whiskas Cat problem, with elastic constraints # https://www.coin-or.org/PuLP/CaseStudies/a_blending_problem.html # https://stackoverflow.com/questions/27278691/how-can-an-elastic-subproblem-in-pulp-be-used-as-a-constraint?noredirect=1&lq=1 def whiskas(): # Creates a list of the Ingredients Ingredients = ['CHICKEN', 'BEEF', 'MUTTON', 'RICE', 'WHEAT', 'GEL'] # A dictionary of the costs of each of the Ingredients is created costs = {'CHICKEN': 0.013, 'BEEF': 0.008, 'MUTTON': 0.010, 'RICE': 0.002, 'WHEAT': 0.005, 'GEL': 0.001} # A dictionary of the protein percent in each of the Ingredients is created proteinPercent = {'CHICKEN': 0.100, 'BEEF': 0.200, 'MUTTON': 0.150, 'RICE': 0.000, 'WHEAT': 0.040, 'GEL': 0.000} # A dictionary of the fat percent in each of the Ingredients is created fatPercent = {'CHICKEN': 0.080, 'BEEF': 0.100, 'MUTTON': 0.110, 'RICE': 0.010, 'WHEAT': 0.010, 'GEL': 0.000} # A dictionary of the fibre percent in each of the Ingredients is created fibrePercent = {'CHICKEN': 0.001, 'BEEF': 0.005, 'MUTTON': 0.003, 'RICE': 0.100, 'WHEAT': 0.150, 'GEL': 0.000} # A dictionary of the salt percent in each of the Ingredients is created saltPercent = {'CHICKEN': 0.002, 'BEEF': 0.005, 'MUTTON': 0.007, 'RICE': 0.002, 'WHEAT': 0.008, 'GEL': 0.000} logging.info('Running Test...') # create problem problem = LpProblem("The Whiskas Problem", LpMinimize) # A dictionary called 'ingredient_vars' is created to contain the referenced Variables ingredient_vars = LpVariable.dicts("Ingr", Ingredients, 0) # set objective problem += lpSum([costs[i]*ingredient_vars[i] for i in Ingredients]), "Total Cost of Ingredients per can" # The five constraints are added to 'prob' problem += lpSum([ingredient_vars[i] for i in Ingredients]) == 100, "PercentagesSum" problem += lpSum([proteinPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 8.0, "ProteinRequirement" problem += lpSum([fatPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 6.0, "FatRequirement" problem += lpSum([fibrePercent[i] * ingredient_vars[i] for i in Ingredients]) <= 2.0, "FibreRequirement" problem += lpSum([saltPercent[i] * ingredient_vars[i] for i in Ingredients]) <= 0.4, "SaltRequirement" # ELASTICIZE # c6_LHS_A = LpAffineExpression([ingredient_vars]) c6_LHS = LpAffineExpression([(ingredient_vars['GEL'],1), (ingredient_vars['BEEF'],1)]) c6= LpConstraint(e=c6_LHS, sense=-1, name='GelBeefTotal', rhs=30) c6_elastic = c6.makeElasticSubProblem(penalty = 100, proportionFreeBound = .10) problem.extend(c6_elastic) print(problem) # solve problem problem.solve() # The status of the solution is printed to the screen print("Status:", LpStatus[problem.status]) # Each of the variables is printed with it's resolved optimum value for v in problem.variables(): print(v.name, "=", v.varValue) # The optimised objective function value is printed to the screen print("Total Cost of Ingredients per can = ", problem.objective.value())