import ecole import pyscipopt class DefaultInformationFunction(): def before_reset(self, model): pass def extract(self, model, done): m = model.as_pyscipopt() stage = m.getStage() sense = 1 if m.getObjectiveSense() == "minimize" else -1 primal_bound = sense * m.infinity() dual_bound = sense * -m.infinity() nlpiters = 0 nnodes = 0 solvingtime = 0 status = m.getStatus() if stage >= pyscipopt.scip.PY_SCIP_STAGE.PROBLEM: primal_bound = m.getObjlimit() nnodes = m.getNNodes() solvingtime = m.getSolvingTime() if stage >= pyscipopt.scip.PY_SCIP_STAGE.TRANSFORMED: primal_bound = m.getPrimalbound() dual_bound = m.getDualbound() if stage >= pyscipopt.scip.PY_SCIP_STAGE.PRESOLVING: nlpiters = m.getNLPIterations() return {'primal_bound': primal_bound, 'dual_bound': dual_bound, 'nlpiters': nlpiters, 'nnodes': nnodes, 'solvingtime': solvingtime, 'status': status} class BranchingDynamics(ecole.dynamics.BranchingDynamics): def __init__(self, time_limit): super().__init__(pseudo_candidates=True) self.time_limit = time_limit def reset_dynamics(self, model): pyscipopt_model = model.as_pyscipopt() # disable SCIP heuristics pyscipopt_model.setHeuristics(pyscipopt.scip.PY_SCIP_PARAMSETTING.OFF) # disable restarts model.set_params({ 'estimation/restarts/restartpolicy': 'n', }) # process the root node done, action_set = super().reset_dynamics(model) # set time limit after reset reset_time = pyscipopt_model.getSolvingTime() pyscipopt_model.setParam("limits/time", self.time_limit + reset_time) return done, action_set class ObjectiveLimitEnvironment(ecole.environment.Environment): def reset(self, instance, objective_limit=None, *dynamics_args, **dynamics_kwargs): """We add one optional parameter not supported by Ecole yet: the instance's objective limit.""" self.can_transition = True try: if isinstance(instance, ecole.core.scip.Model): self.model = instance.copy_orig() else: self.model = ecole.core.scip.Model.from_file(instance) self.model.set_params(self.scip_params) # >>> changes specific to this environment if objective_limit is not None: self.model.as_pyscipopt().setObjlimit(objective_limit) # <<< self.dynamics.set_dynamics_random_state(self.model, self.rng) # Reset data extraction functions self.reward_function.before_reset(self.model) self.observation_function.before_reset(self.model) self.information_function.before_reset(self.model) # Place the environment in its initial state done, action_set = self.dynamics.reset_dynamics( self.model, *dynamics_args, **dynamics_kwargs ) self.can_transition = not done # Extract additional data to be returned by reset reward_offset = self.reward_function.extract(self.model, done) if not done: observation = self.observation_function.extract(self.model, done) else: observation = None information = self.information_function.extract(self.model, done) return observation, action_set, reward_offset, done, information except Exception as e: self.can_transition = False raise e class ConfiguringDynamics(ecole.dynamics.ConfiguringDynamics): def __init__(self, time_limit): super().__init__() self.time_limit = time_limit def reset_dynamics(self, model): pyscipopt_model = model.as_pyscipopt() # process the root node done, action_set = super().reset_dynamics(model) # set time limit after reset reset_time = pyscipopt_model.getSolvingTime() pyscipopt_model.setParam("limits/time", self.time_limit + reset_time) return done, action_set def step_dynamics(self, model, action): forbidden_params = [ "limits/time", "timing/clocktype", "timing/enabled", "timing/reading", "timing/rareclockcheck", "timing/statistictiming", "limits/memory"] for param in forbidden_params: if param in action: raise ValueError(f"Setting the SCIP parameter '{param}' is forbidden.") done, action_set = super().step_dynamics(model, action) return done, action_set class Branching(ObjectiveLimitEnvironment): __Dynamics__ = BranchingDynamics __DefaultInformationFunction__ = DefaultInformationFunction class Configuring(ObjectiveLimitEnvironment): __Dynamics__ = ConfiguringDynamics __DefaultInformationFunction__ = DefaultInformationFunction # def reset_dynamics(self, model): # pyscipopt_model = model.as_pyscipopt() # # Set time limit before reset # reset_time = pyscipopt_model.getSolvingTime() # pyscipopt_model.setParam("limits/time", self.time_limit + reset_time) # # Disable SCIP heuristics # pyscipopt_model.setHeuristics(pyscipopt.scip.PY_SCIP_PARAMSETTING.OFF) # # Disable restarts # model.set_params({ # 'estimation/restarts/restartpolicy': 'n', # }) # # Process the root node # done, action_set = super().reset_dynamics(model) # return done, action_set