pyomo icon indicating copy to clipboard operation
pyomo copied to clipboard

PyROS causes Python to crash when solving a model with uncertainty set constrained to be a single value.

Open makansij opened this issue 3 years ago • 2 comments

In running my PyROS models, I occasionally get an error that there was no feasible solution found. Even for problems to which I know there is a feasible solution.

Of course, when I fix the parameters to be equal to the feasible solution that is known to exist, and thus remove them from the set of uncertain parameters - it finds the optimal/feasible solution with ease.

However, when these parameters are made to be uncertain again, by creating an uncertainty set such that it includes only the nominal value for the uncertain parameters, then it causes PyROS/Python to crash.

Compare the two examples of runnable code:

## Feasible solution clearly exists, because when we make the uncertain parameters to be certain, it finds the solution


# === Required import ===
import pyomo.kernel as pmo
import numpy as np
import pyomo.environ as pe
import pyomo.contrib.pyros as pyros

# === Instantiate the PyROS solver object ===
pyros_solver = pe.SolverFactory("pyros")

def d_init(m, ix):
    if ix == 19:
        return 1
    return 0

def t_init(m, i):
    t = 0.5*np.ones((1,m.n))
    return t[0,i]

# === Construct the Pyomo model object ===
m = pe.ConcreteModel()
n = m.n = 3
Q = m.Q = 20
N = m.N = 2

# === Define continuous variables ===
m.del_component('L')
m.add_component('L', pe.Var(range(n), range(n*Q*N), within=pe.Reals))

# === Define parameters ===
m.t = pe.Param(range(n), initialize=t_init, mutable=True)
m.d = pe.Param(range(Q), initialize=d_init, mutable=True)

# === Specify the objective function ===
m.del_component('objective')
m.add_component('objective', pe.Objective(expr=0)) 

S=2
M=1
W=5
# === Specify the constraints ===
m.del_component('c1')
m.add_component('c1', pe.Constraint(expr= 0 <= S*m.t[0] + W*m.d[0] + m.d[1]*M*m.L[0,1]*m.t[1])) 


results = ipopt_solver.solve(m)

Now look at the example below, where the uncertain parameters are introduced, yet fixed to a single value. This causes Python/PyROS to crash on my machine:

### In the example below, the parameters are made to be uncertain, but fixed to a particular value,...
### ...and this causes PyROS/Python to crash

# === Required import ===
import pyomo.kernel as pmo
import numpy as np
import pyomo.environ as pe
import pyomo.contrib.pyros as pyros

# === Instantiate the PyROS solver object ===
pyros_solver = pe.SolverFactory("pyros")

def d_init(m, ix):
    if ix == 19:
        return 1
    return 0

def t_init(m, i):
    t = 0.5*np.ones((1,m.n))
    return t[0,i]

# === Construct the Pyomo model object ===
m = pe.ConcreteModel()
n = m.n = 3
Q = m.Q = 20
N = m.N = 2

# === Define continuous variables ===
m.del_component('L')
m.add_component('L', pe.Var(range(n), range(n*Q*N), within=pe.Reals))

# === Define parameters ===
m.t = pe.Param(range(n), initialize=t_init, mutable=True)
m.d = pe.Param(range(Q), initialize=d_init, mutable=True)

# === Specify the objective function ===
m.del_component('objective')
m.add_component('objective', pe.Objective(expr=0)) 

S=2
M=1
W=5
# === Specify the constraints ===
m.del_component('c1')
m.add_component('c1', pe.Constraint(expr= 0 <= S*m.t[0] + W*m.d[0] + m.d[1]*M*m.L[0,1]*m.t[1])) 

# === Specify which parameters are uncertain ===
uncertain_parameters = [m.t, m.d] # We can pass IndexedParams this way to PyROS, or as an expanded list per index
# === Define the pertinent data ===

bounds_t = [(0.5, 0.5)]*pe.value(m.n)
bounds_d = [(0,0)]*pe.value(m.Q)
bounds_d[19] = (1,1)
bounds = bounds_t + bounds_d

# === Construct the desirable uncertainty set ===
box_uncertainty_set = pyros.BoxSet(bounds=bounds)

# === Designate local and global NLP solvers ===
ipopt_solver = pe.SolverFactory('appsi_ipopt')

local_solver = ipopt_solver
global_solver = ipopt_solver

# === Designate which variables correspond to first- and second-stage degrees of freedom ===
first_stage_variables =[m.L]
second_stage_variables = []
# The remaining variables are implicitly designated to be state variables

# === Call PyROS to solve the robust optimization problem ===
results_1 = pyros_solver.solve(model = m,
                                 first_stage_variables = first_stage_variables,
                                 second_stage_variables = second_stage_variables,
                                 uncertain_params = uncertain_parameters,
                                 uncertainty_set = box_uncertainty_set,
                                 local_solver = local_solver,
                                 global_solver = global_solver,
                                 tee=True,
                                 options = {
                                    "objective_focus": pyros.ObjectiveType.worst_case,
                                    "solve_master_globally": True,
                                    "load_solution":False
                                  })

# === Query results ===
time = results_1.time
iterations = results_1.iterations
termination_condition = results_1.pyros_termination_condition
objective = results_1.final_objective_value
# === Print some results ===
single_stage_final_objective = round(objective,-1)
print("Final objective value: %s" % single_stage_final_objective)

print("PyROS termination condition: %s" % termination_condition)

I'm running:

  • MacOS Monterey 12.4
  • Python 3.7

makansij avatar Jul 19 '22 18:07 makansij

Are you able to share the traceback printed to your console when you attempt to run the second script?

shermanjasonaf avatar Jul 25 '22 19:07 shermanjasonaf

@makansij I've run the second script independently. It appears that

  1. your model variables m.L are uninitialized when passed to PyROS (in which case, their values are set to None),
  2. only m.L[0,1] is included in your model constraint expression(s), and that variable effectively does not participate in the constraint (in either your determinstic problem or the first PyROS master problem) since it only shows up in the term m.d[1] * m.L[0, 1] * m.t[1]---which is identically 0 since m.d[1] is set to 0.

In fact, if I attempt to solve your deterministic model with SolverFactory("ipopt") independently, then I encounter an exception with the traceback:

ipopt_solver.solve(m, tee=True, load_solutions=False)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/opt/base/solvers.py", line 570, in solve
    self._presolve(*args, **kwds)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/opt/solver/shellcmd.py", line 209, in _presolve
    OptSolver._presolve(self, *args, **kwds)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/opt/base/solvers.py", line 667, in _presolve
    self._convert_problem(args,
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/opt/base/solvers.py", line 718, in _convert_problem
    return convert_problem(args,
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/opt/base/convert.py", line 101, in convert_problem
    problem_files, symbol_map = converter.apply(*tmp, **tmpkw)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/solvers/plugins/converter/model.py", line 181, in apply
    instance.write(
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/base/block.py", line 1808, in write
    (filename, smap) = problem_writer(self,
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/repn/plugins/ampl/ampl_.py", line 405, in __call__
    symbol_map = self._print_model_NL(
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/repn/plugins/ampl/ampl_.py", line 1280, in _print_model_NL
    raise ValueError("No variables appear in the Pyomo model constraints or"
ValueError: No variables appear in the Pyomo model constraints or objective. This is not supported by the NL file interface

However, this traceback does not show up when PyROS attempts to solve the first master problem---possibly due to the fact that PyROS automatically performs an epigraph reformulation of the model objective function in the master problem. in fact, IPOPT reports an optimal solution, but the value of m.L remain unchanged (set to None), which may be problematic during the separation phase. When I run your second script, I encounter the traceback:

Traceback (most recent call last):
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/contrib/pyros/separation_problem_methods.py", line 290, in solve_separation_problem
    model_data.master_nominal_scenario_value = value(model_data.master_nominal_scenario.find_component(nom_constraint))
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/expr/numvalue.py", line 141, in value
    tmp = obj(exception=True)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/base/constraint.py", line 151, in __call__
    return value(self.body, exception=exception)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/expr/numvalue.py", line 141, in value
    tmp = obj(exception=True)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/expr/numeric_expr.py", line 210, in __call__
    return evaluate_expression(self, exception)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/expr/visitor.py", line 893, in evaluate_expression
    return visitor.dfs_postorder_stack(exp)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/expr/visitor.py", line 575, in dfs_postorder_stack
    flag, value = self.visiting_potential_leaf(_sub)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/expr/visitor.py", line 803, in visiting_potential_leaf
    return True, value(node, exception=self.exception)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/core/expr/numvalue.py", line 143, in value
    raise ValueError(
ValueError: No value for uninitialized NumericValue object scenarios[0,0].L[0,1]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/jasherma/Documents/vim_example/pyros_test_examples/makansij_github_issues/makansij_singleton_box_example.py", line 70, in <module>
    results_1 = pyros_solver.solve(model = m,
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/contrib/pyros/pyros.py", line 434, in solve
    pyros_soln, final_iter_separation_solns = ROSolver_iterative_solve(model_data, config)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/contrib/pyros/pyros_algorithm_methods.py", line 255, in ROSolver_iterative_solve
    separation_problem_methods.solve_separation_problem(model_data=separation_data, config=config)
  File "/home/jasherma/Documents/cmu/phd-project/pyomo_repo/pyomo/pyomo/contrib/pyros/separation_problem_methods.py", line 292, in solve_separation_problem
    raise ValueError("Unable to access nominal scenario value for the constraint " + str(nom_constraint))
ValueError: Unable to access nominal scenario value for the constraint c1

Was this what you encountered?

shermanjasonaf avatar Jul 25 '22 21:07 shermanjasonaf