def solve(self, factors, observed, config, all_bits): rvs = list(sorted(factors.keys())) # https://cdn.rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/cp/creating_model.html model = CpoModel() # model.context.cplex_parameters.threads = cpu_count() rv2var = {rv: model.binary_var(str(rv)) for rv in rvs} for rv, val in observed.items(): model.add(rv2var[rv] == bool(val)) for rv, factor in factors.items(): ftype = factor.factor_type if ftype == 'INV': inp = factor.input_rvs[0] model.add(rv2var[inp] == 1 - rv2var[rv]) elif ftype == 'SAME': inp = factor.input_rvs[0] model.add(rv2var[inp] == rv2var[rv]) elif ftype == 'AND': inp1, inp2 = factor.input_rvs[:2] # Linearization of out = inp1 * inp2 for binary variables model.add(rv2var[rv] <= rv2var[inp1]) model.add(rv2var[rv] <= rv2var[inp2]) model.add(rv2var[rv] >= rv2var[inp1] + rv2var[inp2] - 1) # https://cdn.rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/cp/docplex.cp.parameters.py.html#docplex.cp.parameters.CpoParameters params = CpoParameters(Workers=cpu_count()) model.print_information() sol = model.solve( agent='local', params=params, execfile= '/Applications/CPLEX_Studio1210/cpoptimizer/bin/x86-64_osx/cpoptimizer' ) solution = {rv: sol.get_value(rv2var[rv]) for rv in rvs} return solution
def fixed_resource_allocation_model(env: OnlineFlexibleResourceAllocationEnv, state: EnvState): """ Generate the fixed resource allocation model and then solve it Args: env: Online Flexible Resource Allocation Env state: Environment state Returns: Cplex model """ tasks = env._unallocated_tasks if state.auction_task: tasks.append(state.auction_task) fixed_tasks = convert_fixed_task(tasks) servers = list(state.server_tasks.keys()) model = CpoModel('FixedEnv') server_task_allocation = { (server, task): model.binary_var(name=f'{server.name} server - {task.name} task') for server in servers for task in fixed_tasks } for task in fixed_tasks: model.add( sum(server_task_allocation[(server, task)] for server in servers) <= 1) for server in servers: for time in range(env._total_time_steps): time_tasks = [ task for task in fixed_tasks if task.auction_time <= time <= task.deadline ] model.add( sum( min( task.required_storage, task.fixed_loading_speed * (time + 1 - task.auction_time)) * server_task_allocation[(server, task)] for task in time_tasks) <= server.storage_cap) model.add( sum(task.fixed_compute_speed * server_task_allocation[(server, task)] for task in time_tasks) <= server.computational_cap) model.add( sum((task.fixed_loading_speed + task.fixed_sending_speed) * server_task_allocation[(server, task)] for task in time_tasks) <= server.bandwidth_cap) model.maximize( sum(server_task_allocation[(server, task)] for server in servers for task in fixed_tasks)) model_solution = model.solve(log_output=None, TimeLimit=150) total_tasks_completed = sum( model_solution.get_value(server_task_allocation[(server, task)]) for server in servers for task in fixed_tasks) return total_tasks_completed
def elastic_optimal_solver(tasks: List[ElasticTask], servers: List[Server], time_limit: Optional[int]): """ Elastic Optimal algorithm solver using cplex :param tasks: List of tasks :param servers: List of servers :param time_limit: Time limit for cplex :return: the results of the algorithm """ assert time_limit is None or 0 < time_limit, f'Time limit: {time_limit}' model = CpoModel('Elastic Optimal') # The resource speed variables and the allocation variables loading_speeds, compute_speeds, sending_speeds, task_allocation = {}, {}, {}, {} # Loop over each task to allocate the variables and add the deadline constraints max_bandwidth = max(server.bandwidth_capacity for server in servers) max_computation = max(server.computation_capacity for server in servers) runnable_tasks = [ task for task in tasks if any( server.can_run_empty(task) for server in servers) ] for task in runnable_tasks: # Check if the task can be run on any server even if empty loading_speeds[task] = model.integer_var( min=1, max=max_bandwidth - 1, name=f'{task.name} loading speed') compute_speeds[task] = model.integer_var( min=1, max=max_computation, name=f'{task.name} compute speed') sending_speeds[task] = model.integer_var( min=1, max=max_bandwidth - 1, name=f'{task.name} sending speed') model.add((task.required_storage / loading_speeds[task]) + (task.required_computation / compute_speeds[task]) + (task.required_results_data / sending_speeds[task]) <= task.deadline) # The task allocation variables and add the allocation constraint for server in servers: task_allocation[(task, server)] = model.binary_var( name=f'{task.name} Task - {server.name} Server') model.add( sum(task_allocation[(task, server)] for server in servers) <= 1) # For each server, add the resource constraint for server in servers: model.add( sum(task.required_storage * task_allocation[(task, server)] for task in runnable_tasks) <= server.available_storage) model.add( sum(compute_speeds[task] * task_allocation[(task, server)] for task in runnable_tasks) <= server.available_computation) model.add( sum((loading_speeds[task] + sending_speeds[task]) * task_allocation[(task, server)] for task in runnable_tasks) <= server.available_bandwidth) # The optimisation statement model.maximize( sum(task.value * task_allocation[(task, server)] for task in runnable_tasks for server in servers)) # Solve the cplex model with time limit try: model_solution: CpoSolveResult = model.solve(log_output=None, TimeLimit=time_limit) except CpoSolverException as e: print(f'Solver Exception: ', e) return None # Check that it is solved if model_solution.get_solve_status() != SOLVE_STATUS_FEASIBLE and \ model_solution.get_solve_status() != SOLVE_STATUS_OPTIMAL: print(f'Optimal solver failed', file=sys.stderr) print_model_solution(model_solution) print_model(tasks, servers) return None # Generate the allocation of the tasks and servers try: for task in runnable_tasks: for server in servers: if model_solution.get_value(task_allocation[(task, server)]): server_task_allocation( server, task, model_solution.get_value(loading_speeds[task]), model_solution.get_value(compute_speeds[task]), model_solution.get_value(sending_speeds[task])) break if abs(model_solution.get_objective_values()[0] - sum(t.value for t in tasks if t.running_server)) > 0.1: print( 'Elastic optimal different objective values - ' f'cplex: {model_solution.get_objective_values()[0]} and ' f'running task values: {sum(task.value for task in tasks if task.running_server)}', file=sys.stderr) return model_solution except (AssertionError, KeyError) as e: print('Error: ', e, file=sys.stderr) print_model_solution(model_solution)
def optimal_task_price(new_task: ElasticTask, server: Server, time_limit: int, debug_results: bool = False): """ Calculates the task price :param new_task: The new task :param server: The server :param time_limit: Time limit for the cplex :param debug_results: debug the results :return: task price and task speeds """ assert 0 < time_limit, f'Time limit: {time_limit}' assert new_task.required_storage <= server.storage_capacity model = CpoModel(f'{new_task.name} Task Price') # Add the new task to the list of server allocated tasks tasks = server.allocated_tasks + [new_task] # Create all of the resource speeds variables loading_speeds = {task: model.integer_var(min=1, max=task.loading_ub()) for task in tasks} compute_speeds = {task: model.integer_var(min=1, max=task.compute_ub()) for task in tasks} sending_speeds = {task: model.integer_var(min=1, max=task.sending_ub()) for task in tasks} # Create all of the allocation variables however only on the currently allocated tasks allocation = {task: model.binary_var(name=f'{task.name} Task allocated') for task in server.allocated_tasks} # Add the deadline constraint for task in tasks: model.add((task.required_storage / loading_speeds[task]) + (task.required_computation / compute_speeds[task]) + (task.required_results_data / sending_speeds[task]) <= task.deadline) # Add the server resource constraints model.add(sum(task.required_storage * allocated for task, allocated in allocation.items()) + new_task.required_storage <= server.storage_capacity) model.add(sum(compute_speeds[task] * allocated for task, allocated in allocation.items()) + compute_speeds[new_task] <= server.computation_capacity) model.add(sum((loading_speeds[task] + sending_speeds[task]) * allocated for task, allocated in allocation.items()) + (loading_speeds[new_task] + sending_speeds[new_task]) <= server.bandwidth_capacity) # The optimisation function model.maximize(sum(task.price * allocated for task, allocated in allocation.items())) # Solve the model with a time limit model_solution = model.solve(log_output=None, TimeLimit=time_limit) # If the model solution failed then return an infinite price if model_solution.get_solve_status() != SOLVE_STATUS_FEASIBLE and \ model_solution.get_solve_status() != SOLVE_STATUS_OPTIMAL: print(f'Cplex model failed - status: {model_solution.get_solve_status()} ' f'for new {str(new_task)} and {str(server)}') return math.inf, {} # Get the max server profit that the model finds and calculate the task price through a vcg similar function new_server_revenue = model_solution.get_objective_values()[0] task_price = max(server.revenue - new_server_revenue + server.price_change, server.initial_price) # Get the resource speeds and task allocations speeds = { task: (model_solution.get_value(loading_speeds[task]), model_solution.get_value(compute_speeds[task]), model_solution.get_value(sending_speeds[task]), model_solution.get_value(allocation[task]) if task in allocation else True) for task in tasks } debug(f'Sever: {server.name} - Prior revenue: {server.revenue}, new revenue: {new_server_revenue}, ' f'price change: {server.price_change} therefore task price: {task_price}', debug_results) return task_price, speeds
def CP_model_subsequence_restricted(params): ## NUM_COLORS, NUM_ITEMS, BIN_SIZE, DISCREPANCY, ITEM_SIZES, COLORS, NUM_BINS, Timelimit, SEED = params print("*** Running CP Subseq Restricted with instance NUM_COLORS{} NUM_ITEMS{} BIN_SIZE {} DISCREPANCY {} SEED {}".format( NUM_COLORS, NUM_ITEMS, BIN_SIZE, DISCREPANCY, SEED)) mdl = CpoModel() ITEMS = [mdl.interval_var(start=[0, sum(ITEM_SIZES)], size=ITEM_SIZES[item], optional=False, name="item_"+str(item)) for item in range(NUM_ITEMS)] ITEMS_TO_BINS = [[mdl.interval_var(start=(0, BIN_SIZE-min(ITEM_SIZES)), end=(min(ITEM_SIZES), BIN_SIZE), size=ITEM_SIZES[item], optional=True, name="item{}InBin{}".format(item, bin)) for bin in range(NUM_BINS)] for item in range(NUM_ITEMS)] ITEMS_S = mdl.sequence_var(ITEMS, types=COLORS, name="itemSequence") BIN_S = [mdl.sequence_var([ITEMS_TO_BINS[item][bin] for item in range(NUM_ITEMS)], types=[COLORS[item] for item in range(NUM_ITEMS)], name="bin{}Sequence".format(bin)) for bin in range(NUM_BINS)] # COLORS_S = [[mdl.sequence_var(ITEMS[item][bin] for item in range(NUM_ITEMS) if COLORS[item] == color)] for color in range(NUM_COLORS)] BINS_USED = [mdl.binary_var() for bin in range(NUM_BINS)] for item in range(NUM_ITEMS): # for bin in range(NUM_BINS): # mdl.add_kpi( # mdl.presence_of(ITEMS_TO_BINS[item][bin]), # "item_"+str(item)+" "+str(bin) # ) mdl.add( mdl.sum( [mdl.presence_of( ITEMS_TO_BINS[item][bin]) for bin in range(NUM_BINS)] ) == 1 ) # for item in range(NUM_ITEMS): # mdl.add( # mdl.alternative(ITEMS[item], [ITEMS_TO_BINS[item][bin] for bin in range(NUM_BINS)]) # ) for bin in range(NUM_BINS): mdl.add(mdl.no_overlap(BIN_S[bin])) for item in range(NUM_ITEMS-1): mdl.add( mdl.end_before_start(ITEMS[item], ITEMS[item+1]) ) mdl.add(mdl.no_overlap(ITEMS_S)) for bin in range(NUM_BINS): mdl.add( mdl.same_common_subsequence( ITEMS_S, BIN_S[bin], ) ) for item in range(NUM_ITEMS): for bin in range(NUM_BINS): # if (item, bin) in [(17,5), (20, 5), (29, 5)]: # mdl.add_kpi(mdl.type_of_next(BIN_S[bin], ITEMS_TO_BINS[item][bin], lastValue=-1, absentValue=-1), name="typeofnext{}_{}".format(item, bin)) # # mdl.add_kpi(mdl.type_of_prev(BIN_S[bin], ITEMS_TO_BINS[item][bin], firstValue=-1, absentValue=-1), name="typeofprev{}_{}".format(item, bin)) mdl.add( COLORS[item] != mdl.type_of_next(BIN_S[bin], ITEMS_TO_BINS[item][bin], lastValue=-1, absentValue=-1) ) mdl.add( COLORS[item] != mdl.type_of_prev(BIN_S[bin], ITEMS_TO_BINS[item][bin], firstValue=-1, absentValue=-1) ) for bin in range(NUM_BINS): mdl.add( BINS_USED[bin] == mdl.any([ mdl.presence_of(ITEMS_TO_BINS[item][bin]) for item in range(NUM_ITEMS) ]) ) for bin in range(NUM_BINS-1): mdl.add( BINS_USED[bin] >= BINS_USED[bin+1] ) mdl.add( mdl.minimize( mdl.sum( BINS_USED ) ) ) # mdl.add( # mdl.sum( # BINS_USED # ) < 48 # ) try: msol = mdl.solve(TimeLimit = Timelimit) mdl._myInstance = (NUM_COLORS, NUM_ITEMS, BIN_SIZE, DISCREPANCY, SEED) if msol: ITEMS_TO_BINS_S = [] for bin in range(NUM_BINS): b_ = [] for item in range(NUM_ITEMS): s = msol[ITEMS_TO_BINS[item][bin]] if s: b_.append(s) ITEMS_TO_BINS_S.append(b_) # return mdl seq = msol.get_var_solution(BIN_S[0]) print(ITEM_SIZES) print("CL: ",COLORS) Xs = [] for item in range(NUM_ITEMS): for bin in range(NUM_BINS): s = msol[ITEMS_TO_BINS[item][bin]] if s: # print(s) Xs.append(bin) print("Xs: ", Xs) Xs = np.array(Xs) if solution_checker(Xs, params, "restricted_cp_subsequence"): write_to_global_cp(msol, mdl, 'restricted_subsequence') except Exception as err: print(err) write_to_global_failed(params, 'restricted_cp_subsequence', is_bad_solution=False)
for j in hours_in_day: time = util.Time(i, j, len(time_seg_idx)) time_seg_idx.append(time) # ----------------------------------------------------------------------------- # Optimization variables # ----------------------------------------------------------------------------- # the main loop of making the variables. mdl = CpoModel() all_visits = [] # Set admittance variables. for pat in patient_list: name = 'z_' + str(pat.id) pat.is_admitted_var = mdl.binary_var(name=name) # follow indexing system of X_{k,i,j}^{l}: {doc,patient, visit session}, {day} for doc in doctor_list: for pat in patient_list: if (doc.skill in pat.treatment_activities): # then visit! # NB! [0:pat.nb_visit_days] slices the visiting days available # in the scheuling horizon to exactly the number of days patient # should be visited, and forces the selection of the first # nb_visit_days. So the patient wont stay in the center for more # days than necessary. But maby enforcing the paitent to come # exactly in the first nb_visit_days is too limiting? It depends # on the top-level assignment sch days = policy_data.calendar_visit_days[0:pat.nb_visit_days]
def non_elastic_optimal_solver(tasks: List[NonElasticTask], servers: List[Server], time_limit: Optional[int]): """ Finds the optimal solution :param tasks: A list of tasks :param servers: A list of servers :param time_limit: The time limit to solve with :return: The results """ assert time_limit is None or 0 < time_limit, f'Time limit: {time_limit}' model = CpoModel('vcg') # As no resource speeds then only assign binary variables for the allocation allocations = { (task, server): model.binary_var(name=f'{task.name} task {server.name} server') for task in tasks for server in servers } # Allocation constraint for task in tasks: model.add(sum(allocations[(task, server)] for server in servers) <= 1) # Server resource speeds constraints for server in servers: model.add( sum(task.required_storage * allocations[(task, server)] for task in tasks) <= server.available_storage) model.add( sum(task.compute_speed * allocations[(task, server)] for task in tasks) <= server.available_computation) model.add( sum((task.loading_speed + task.sending_speed) * allocations[(task, server)] for task in tasks) <= server.available_bandwidth) # Optimisation problem model.maximize( sum(task.value * allocations[(task, server)] for task in tasks for server in servers)) # Solve the cplex model with time limit model_solution = model.solve(log_output=None, TimeLimit=time_limit) # Check that the model is solved if model_solution.get_solve_status() != SOLVE_STATUS_FEASIBLE and \ model_solution.get_solve_status() != SOLVE_STATUS_OPTIMAL: print('Non-elastic optimal failure', file=sys.stderr) print_model_solution(model_solution) return None # Allocate all of the tasks to the servers try: for task in tasks: for server in servers: if model_solution.get_value(allocations[(task, server)]): server_task_allocation(server, task, task.loading_speed, task.compute_speed, task.sending_speed) break if abs(model_solution.get_objective_values()[0] - sum(t.value for t in tasks if t.running_server)) > 0.1: print( 'Non-elastic optimal different objective values - ' f'cplex: {model_solution.get_objective_values()[0]} and ' f'running task values: {sum(task.value for task in tasks if task.running_server)}', file=sys.stderr) except (KeyError, AssertionError) as e: print('Assertion error in non-elastic optimal algorithm: ', e, file=sys.stderr) print_model_solution(model_solution) return None return model_solution