-
Notifications
You must be signed in to change notification settings - Fork 518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding (parameterized) linear programming dual transformation! #3402
base: main
Are you sure you want to change the base?
Conversation
… with where the mappings are stored
…ing transformed model match
…that assume that the data is numeric. Creating my own csr and csc classes that are almost complete
…ssions in the data
…rized_standard_form itself.
…use the correct converstion to a CSC matrix depending on the circumstances, testing the conversion to nonnegative vars
…ut parameterized standard repn doesn't
…t a little if we assume ordered, so this isn't done or tested
…d form with attributes rather than methods, implementing eliminate_zeros
…ding tests for API error messages
) | ||
|
||
def apply_to(self, model, **options): | ||
raise MouseTrap( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think MouseTrap
is the right error here. NotImplementedError
would make more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MouseTrap
inherits from (and is a special case of) NotImplementedError
def get_primal_constraint(self, model, dual_var): | ||
"""Return the primal constraint corresponding to 'dual_var' | ||
Returns | ||
------- | ||
Constraint | ||
Parameters | ||
---------- | ||
model: ConcreteModel | ||
A dual model returned from the 'core.lp_dual' transformation | ||
dual_var: Var | ||
A dual variable on 'model' | ||
""" | ||
primal_constraint = model.private_data().primal_constraint | ||
if dual_var in primal_constraint: | ||
return primal_constraint[dual_var] | ||
else: | ||
raise ValueError( | ||
"It does not appear that Var '%s' is a dual variable on model '%s'" | ||
% (dual_var.name, model.name) | ||
) | ||
|
||
def get_dual_constraint(self, model, primal_var): | ||
"""Return the dual constraint corresponding to 'primal_var' | ||
Returns | ||
------- | ||
Constraint | ||
Parameters | ||
---------- | ||
model: ConcreteModel | ||
A primal model passed as an argument to the 'core.lp_dual' transformation | ||
primal_var: Var | ||
A primal variable on 'model' | ||
""" | ||
dual_constraint = model.private_data().dual_constraint | ||
if primal_var in dual_constraint: | ||
return dual_constraint[primal_var] | ||
else: | ||
raise ValueError( | ||
"It does not appear that Var '%s' is a primal variable on model '%s'" | ||
% (primal_var.name, model.name) | ||
) | ||
|
||
def get_primal_var(self, model, dual_constraint): | ||
"""Return the primal variable corresponding to 'dual_constraint' | ||
Returns | ||
------- | ||
Var | ||
Parameters | ||
---------- | ||
model: ConcreteModel | ||
A dual model returned from the 'core.lp_dual' transformation | ||
dual_constraint: Constraint | ||
A constraint on 'model' | ||
""" | ||
primal_var = model.private_data().primal_var | ||
if dual_constraint in primal_var: | ||
return primal_var[dual_constraint] | ||
else: | ||
raise ValueError( | ||
"It does not appear that Constraint '%s' is a dual constraint on " | ||
"model '%s'" % (dual_constraint.name, model.name) | ||
) | ||
|
||
def get_dual_var(self, model, primal_constraint): | ||
"""Return the dual variable corresponding to 'primal_constraint' | ||
Returns | ||
------- | ||
Var | ||
Parameters | ||
---------- | ||
model: ConcreteModel | ||
A primal model passed as an argument to the 'core.lp_dual' transformation | ||
primal_constraint: Constraint | ||
A constraint on 'model' | ||
""" | ||
dual_var = model.private_data().dual_var | ||
if primal_constraint in dual_var: | ||
return dual_var[primal_constraint] | ||
else: | ||
raise ValueError( | ||
"It does not appear that Constraint '%s' is a primal constraint on " | ||
"model '%s'" % (primal_constraint.name, model.name) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code in all of these is basically exactly the same minus the thing that you are getting. You could abstract this by doing something like:
def _get_corresponding_entity(self, model, entity, entity_type, mapping):
"""Return the corresponding entity based on the provided mapping.
Parameters
----------
model: ConcreteModel
The model containing the mappings.
entity: Var or Constraint
The entity for which we want to find the corresponding entity.
entity_type: str
A string indicating whether the entity is 'dual' or 'primal'.
mapping: dict
The mapping to look up the corresponding entity.
Returns
-------
Var or Constraint
The corresponding entity.
Raises
------
ValueError
If the entity is not found in the mapping.
"""
if entity in mapping:
return mapping[entity]
else:
raise ValueError(
"It does not appear that %s '%s' is a %s entity on model '%s'"
% (entity_type.capitalize(), entity.name, 'dual' if entity_type == 'primal' else 'primal', model.name)
)
def get_primal_constraint(self, model, dual_var):
"""Return the primal constraint corresponding to 'dual_var'."""
primal_constraint = model.private_data().primal_constraint
return self._get_corresponding_entity(model, dual_var, 'primal', primal_constraint)
# we leave off the last entry (the number of nonzeros) because we are | ||
# going to do the cute scipy thing and it will get 'added' at the end | ||
# when we shift right (by which I mean it will conveniently already be | ||
# there) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand what this comment is attached to - the lines above or below?
num_non_zeros = 0 | ||
col_end = 0 | ||
for i in range(ncols): | ||
jj = col_end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a math/matrix perspective, I get this; from a SWE perspective, I don't. Not asking for a change - just making a comment about jj
in software.
def var_component_set(x): | ||
""" | ||
For domain validation in ConfigDicts: Takes singletone or iterable argument 'x' | ||
of Vars and converts it to a ComponentSet of Vars. | ||
""" | ||
if hasattr(x, 'ctype') and x.ctype is Var: | ||
if not x.is_indexed(): | ||
return ComponentSet([x]) | ||
ans = ComponentSet() | ||
for j in x.index_set(): | ||
ans.add(x[j]) | ||
return ans | ||
elif hasattr(x, '__iter__'): | ||
ans = ComponentSet() | ||
for i in x: | ||
ans.update(var_component_set(i)) | ||
return ans | ||
else: | ||
raise ValueError("Expected Var or iterable of Vars.\n\tReceived %s" % type(x)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this make more sense in pyomo/common/config.py
? There are other domain validators in there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly, but we require that pyomo.common
NOT import from any other part of Pyomo, and this validator needs to know about Var
. This just came up in #3384 as well: we should decide on a good place for "Pyomo-aware" domain validators.
Fixes the complaints of several reasonable people
Summary/Motivation:
This PR adds a transformation in
core
to take the dual of a linear program. For the purposes of supporting bilevel programming, it also allows for this dual to be "parameterized" by certain variables--that is, the user can specify a list of Vars that are treated as data for the purposes of taking the dual (e.g., if they are the outer variables of a bilevel program with an LP inner problem).Changes proposed in this PR:
core.lp_dual
transformation.Legal Acknowledgement
By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution: