def solve_inner_dro(self, stage2_objvals): """ Solve inner maximization over Wasserstein ball given first stage decisions. """ dro_inner_model = Model() dro_inner_model.params.Method = 0 # method set to primal simplex q = dro_inner_model.addVars(stage2_objvals.keys(), self.samples.keys(), lb=0) lhs = LinExpr() for sample_name, sample in self.samples.items(): curr_tot_prob = quicksum(q[s, sample_name] for s in stage2_objvals.keys()) dro_inner_model.addLConstr(curr_tot_prob == 1) for scenario_name in stage2_objvals.keys(): scenario = self.scenarios[scenario_name] sampleDev = sc.get_scenario_distance(scenario, sample, self.lr_instance) lhs.addTerms(sampleDev * self.probs[sample_name], q[scenario_name, sample_name]) dro_inner_model.addLConstr(lhs, "<", self.wass_rad) objExpr = quicksum(q[scenario_name, sample_name] * stage2_objvals[scenario_name] for scenario_name in stage2_objvals.keys() for sample_name in self.samples.keys()) dro_inner_model.setObjective(objExpr, GRB.MAXIMIZE) dro_inner_model.setParam("OutputFlag", 0) return dro_inner_model, q
def construct_master(self, master_scenarios): """ Construct master model. Included in the model are first stage decisions and constraints, and second stage decisions and constraints for each scenario in master_scenarios Parameters ---------- master_scenarios : list of str List of scenario names to be initially included in the master model. """ lr_instance = self.tsdro.lr_instance self.master, self.stage1_vars = lr_instance.construct_stage1() if self.method == "RO": self.wass_mult = 0 else: self.wass_mult = self.master.addVar(name="wass_multiplier", lb=0) self.epi_vars = self.master.addVars(self.tsdro.samples.keys(), lb=0, name="epi_vars") objexpr_stage1 = lr_instance.get_objective_stage1(self.stage1_vars) objexpr_stage2 = quicksum(self.tsdro.probs[sample_name] * self.epi_vars[sample_name] for sample_name in self.tsdro.samples.keys()) self.objexpr_master = (objexpr_stage1 + self.tsdro.wass_rad * self.wass_mult + objexpr_stage2) self.master.setObjective(self.objexpr_master, GRB.MINIMIZE) for scenario_name in master_scenarios: scenario = self.tsdro.scenarios[scenario_name] curr_vars, _ = lr_instance.add_stage2(self.master, self.stage1_vars, scenario, scenario_name) for sample_name, sample in self.tsdro.samples.items(): objexpr_stage2 = lr_instance.get_objective_stage2( curr_vars, scenario) if scenario_name == sample_name or self.method == "RO": scenario_distance = 0 else: scenario_distance = sc.get_scenario_distance( scenario, sample, lr_instance) rhs = objexpr_stage2 - self.wass_mult * scenario_distance self.master.addLConstr( self.epi_vars[sample_name], ">", rhs, name=("epi_constr_" + str(scenario_name) + "_" + str(sample_name))) self.stage2_vars[scenario_name] = curr_vars return
def getDisjunctionLHS(self, kAdaptVars, scenarioName, sampleName): scenario = self.tsdro.scenarios[scenarioName] sample = self.tsdro.samples[sampleName] beta = kAdaptVars.beta wassMult = kAdaptVars.wassMult scenario_distance = sc.get_scenario_distance(sample, scenario, self.tsdro.lrInstance) lhs = LinExpr() lhs.addTerms(scenario_distance * self.tsdro.probs[sampleName], wassMult) lhs.addTerms(1, beta[sampleName]) return lhs
def addConvexSubproblem(self, model, v): lrInstance = self.tsdro.lrInstance samples = self.tsdro.samples scenarios = self.tsdro.scenarios initialScenarioNames = self.tsdro.initialScenarioNames wassMult = model.addVar(lb=0, name="wass_multiplier") beta = model.addVars(samples.keys(), lb=-np.inf, ub=np.inf, name="beta") for scenarioName in initialScenarioNames: scenario = scenarios[scenarioName] for sampleName, sample in samples.items(): scenario_distance = sc.get_scenario_distance(sample, scenario, lrInstance) lhs = LinExpr() lhs.addTerms(scenario_distance * self.tsdro.probs[sampleName], wassMult) lhs.addTerms(1, beta[sampleName]) rhs = self.tsdro.probs[sampleName] * v[scenarios] constr_name = ("dro_" + "sc" + str(scenarioName) + "_sa" + str(sampleName) + "_k" + str(k)) model.addLConstr(lhs, ">", rhs, name= constr_name) return model, KAdaptVars(wassMult, beta, None, None)
def enumeration_separation(self, stage1_vars, sample_name, limit=1, enumerate_all=True): """ Enumerate remaining scenarios and update master if violated constraint if found. Parameters ---------- stage1_vars : location_routing.Stage1Vars dataclass sample_name : str limit : int Maximum number of scenarios that can be added to the master enumerate_all : bool If True, enumerate all scenarios and only add the scenario with the greatest violation. """ sample = self.tsdro.samples[sample_name] scenarios = self.tsdro.scenarios lr_instance = self.tsdro.lr_instance curr_remaining = self.tsdro.remaining_scenario_names[sample_name] curr_remaining_copy = curr_remaining.copy() found_hyp = False count = 0 max_violation_scenario_name = None max_violation = -np.inf for scenario_name in curr_remaining: scenario = scenarios[scenario_name] if scenario_name not in self.stage2_objvals: self.stage2_objvals[scenario_name] = self.get_stage2_costs( stage1_vars, [scenario_name]) if scenario_name == sample_name or self.method == "RO": scenario_distance = 0 rhs_val = self.stage2_objvals[scenario_name] else: scenario_distance = sc.get_scenario_distance( scenario, sample, lr_instance) rhs_val = (self.stage2_objvals[scenario_name] - self.wass_mult.X * scenario_distance) if self.epi_vars[sample_name].X < rhs_val + 1e-7: found_hyp = True if not enumerate_all: count += 1 if scenario_name not in self.master_scenarios: tmp = lr_instance.add_stage2(self.master, self.stage1_vars, scenario, scenario_name) self.stage2_vars[scenario_name] = tmp[0] self.master_scenarios.append(scenario_name) objexpr_stage2 = lr_instance.get_objective_stage2( self.stage2_vars[scenario_name], scenario) rhs = objexpr_stage2 - self.wass_mult * scenario_distance self.master.addLConstr(self.epi_vars[sample_name], ">", rhs) curr_remaining_copy.remove(scenario_name) else: if rhs_val - self.epi_vars[sample_name].X > max_violation: max_violation = rhs_val - self.epi_vars[sample_name].X max_violation_scenario_name = scenario_name if count == limit: break if enumerate_all and found_hyp: scenario = scenarios[max_violation_scenario_name] if scenario_name == sample_name or self.method == "RO": scenario_distance = 0 else: scenario_distance = sc.get_scenario_distance( scenario, sample, lr_instance) if scenario_name not in self.master_scenarios: tmp = lr_instance.add_stage2(self.master, self.stage1_vars, scenario, max_violation_scenario_name) self.stage2_vars[max_violation_scenario_name] = tmp[0] self.master_scenarios.append(max_violation_scenario_name) objexpr_stage2 = lr_instance.get_objective_stage2( self.stage2_vars[max_violation_scenario_name], scenario) rhs = objexpr_stage2 - self.wass_mult * scenario_distance self.master.addLConstr(self.epi_vars[sample_name], ">", rhs) curr_remaining_copy.remove(max_violation_scenario_name) self.tsdro.remaining_scenario_names[sample_name] = curr_remaining_copy if count > 0: print(sample_name, "added", count, "scenarios.") return found_hyp