Skip to content

Add support for templatized models, and special handling in gurobi_direct_v2#3362

Merged
blnicho merged 65 commits intoPyomo:mainfrom
jsiirola:templatized-writer
Oct 30, 2024
Merged

Add support for templatized models, and special handling in gurobi_direct_v2#3362
blnicho merged 65 commits intoPyomo:mainfrom
jsiirola:templatized-writer

Conversation

@jsiirola
Copy link
Copy Markdown
Member

Fixes # .

Summary/Motivation:

This adds support for generating models with "templatized" objectives and constraints. In most cases, this does not save any net time (time saved in model construction is moved to the writer). This also introduces an extension to the contrib.solver.gurobi_direct that recognizes and takes advantage of the expression templates. For a large model, this reduces the Pyomo overhead by roughly a third.

Changes proposed in this PR:

  • Add the option to templatize Constraints and Objectives
  • Add a new walker for generating a LinearRepn for template expressions
  • Rework parts of the expression compilers to make deriving / overriding behavior easier
  • Update contrib/solver/gurobi_direct.py to work with templated models

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:

  1. I agree my contributions are submitted under the BSD license.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

jsiirola added 30 commits March 8, 2024 15:09
@mrmundt mrmundt self-requested a review October 8, 2024 18:56
mrmundt
mrmundt previously requested changes Oct 8, 2024
Comment on lines +177 to +188
def args(self):
# Note that it is faster to just generate the expression from
# scratch than it is to clone it and replace the IndexTemplate objects
self.set_value(self.parent_component().rule(self.parent_block(), self.index()))
return self._args_

def template_expr(self):
return self._args_

def set_value(self, expr):
self.__class__ = ObjectiveData
return self.set_value(expr)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repeated code irks me but I don't have an immediate suggestion for how to get around it besides a MixIn class.

Copy link
Copy Markdown
Member

@blnicho blnicho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why you didn't add template support for named Expression components as part of this PR?

):
super().__init__(subexpression_cache, var_map, var_order, sorter, var_recorder)
if wrt is None:
raise ValueError("ParameterizedLinearRepn: wrt not specified")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"with respect to"

Copy link
Copy Markdown
Contributor

@emma58 emma58 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few questions below, but mostly this looks good. Please know that my tardiness in reviewing is not correlated with how excited I am about this PR--it makes me very very happy! :)

Comment on lines +885 to +891
try:
if self._component()[self._index] is self:
return self._index
except:
pass
if self._index is NOTSET:
return self._index
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity, but why this change? Wouldn't the try-except be slower?

Comment on lines +549 to +550
# FIXME: This is a hack to get certain complex cases to print without error
_ToStringVisitor._leaf_node_types.add(TemplateSumExpression)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How hard is this to fix? What are the complex cases?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly don't remember (it was several months ago). I do remember fighting with it for a night and landing on this as a "reasonable" solution.

Comment on lines -1247 to +1254
raise NonConstantExpressionError()
raise NonConstantExpressionError(
f"{node} ({type(node).__name__}) is not fixed"
)
if not node.is_constant():
raise FixedExpressionError()
raise FixedExpressionError(
f"{node} ({type(node).__name__}) is not constant"
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these error types right? They seem flipped?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused, too: that is why I added the text description. I thought about changing it, but it has been that way since PR #467, so I was hesitant to change a "public" API (even one that is not used). I believe that the original meaning was that if you are evaluating an expression that is supposed to be constant and run across a fixed variable, then you get a FixedExpressionError. Otherwise, if you run across a free variable, then you get a generic NonConstantExpressionError.

IMHO, the long-term solution is to remove that walker.

Comment on lines +710 to +714
deprecation_warning(
"var_map, var_order, and sorter are deprecated arguments to "
"LinearRepnVisitor(). Please pass the VarRecorder object directly.",
version='6.7.4.dev0',
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this change!

):
super().__init__(subexpression_cache, var_map, var_order, sorter, var_recorder)
if wrt is None:
raise ValueError("ParameterizedLinearRepn: wrt not specified")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in: "Parameterized with respect to..." (In past Emma's defense. :))

Comment on lines +251 to +255
# Making these methods class attributes so that others can change the hooks
_get_visitor = LinearRepnVisitor
_to_vector = None
_csc_matrix = None
_csr_matrix = None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheeee, thank you! (I forgot how many amazing things there are in this PR! :))

@blnicho blnicho requested a review from mrmundt October 29, 2024 19:20
@blnicho blnicho dismissed mrmundt’s stale review October 30, 2024 03:36

Requested changes have been made

@blnicho blnicho merged commit a5e6f23 into Pyomo:main Oct 30, 2024
@jsiirola jsiirola deleted the templatized-writer branch November 15, 2024 03:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants