Skip to content
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

Add Gaussian confidence level attribute to PyROS EllipsoidalSet #3434

Merged
merged 13 commits into from
Dec 18, 2024

Conversation

shermanjasonaf
Copy link
Contributor

@shermanjasonaf shermanjasonaf commented Nov 23, 2024

Summary/Motivation:

Motivated by recent user feedback, this PR adds a new PyROS EllipsoidalSet attribute and constructor argument gaussian_conf_lvl that specifies, as an alternative to the the squared semi-axis scaling factor attribute and constructor argument scale, the level of the (ellipsoidal) confidence region of the multivariate Gaussian distribution with mean center and covariance shape_matrix. Mathematically, the PyROS ellipsoidal set with center $q^0 \in \mathbb{R}^n$, positive definite shape matrix $P \in \mathbb{S}_{++}^n$, and squared semi-axis scale factor $s \geq 0$ is defined by

$$ \{ q \in \mathbb{R}^n \mid (q - q^0)^\top P^{-1} (q - q^0) \leq s \}. $$

For confidence level $p \in [0, 1)$, the $100p$% multivariate Gaussian confidence region is obtained by setting $s = \chi_{n,p}^2$, where $\chi_{n,\cdot}^2$ is the (invertible) lower-tail quantile function of the chi-squared distribution with $n$ degrees of freedom. Thus, we ensure that setting gaussian_conf_lvl triggers, through the invertible mapping between the squared scaling factor $s$ and confidence level $p$, an update of scale, and vice-versa.

Changes proposed in this PR:

  • Add new settable attribute and constructor argument gaussian_conf_lvl to PyROS EllipsoidalSet.

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.

@shermanjasonaf shermanjasonaf changed the title Add chi-squared confidence level attribute to PyROS EllipsoidalSet Add Gaussian confidence level attribute to PyROS EllipsoidalSet Nov 25, 2024
Copy link
Member

@jsiirola jsiirola left a comment

Choose a reason for hiding this comment

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

Two ignorable minor comments, but otherwise looks good.

pyomo/contrib/pyros/uncertainty_sets.py Outdated Show resolved Hide resolved
Comment on lines +2452 to +2460
if scale is not None and gaussian_conf_lvl is None:
self.scale = scale
elif scale is None and gaussian_conf_lvl is not None:
self.gaussian_conf_lvl = gaussian_conf_lvl
else:
raise ValueError(
"Exactly one of `scale` and `gaussian_conf_lvl` should be "
f"None (got {scale=}, {gaussian_conf_lvl=})"
)
Copy link
Member

Choose a reason for hiding this comment

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

You don't need to change this, but would it be more clear to have this be:

if (scale is None) ^ (gaussian_conf_lvl is None):
    self.scale = scale
    self.gaussian_conf_lvl = gaussian_conf_lvl
else:
    raise ValueError(
        "Exactly one of `scale` and `gaussian_conf_lvl` should be "
        f"None (got {scale=}, {gaussian_conf_lvl=})"
    )

And then have the setters just "do nothing" if the incoming value is None?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree this would clarify the code, but are we sure we want each of those setters to leave the state unchanged if None is passed (without at least noting this behavior in the property docstrings)? Currently, each of the other EllipsoidalSet attribute setters (and more broadly, attribute setters for other concrete UncertaintySet subclasses) raises an exception if None is passed and not an acceptable value/type/shape for the attribute. I am wondering whether this new behavior for scale and gaussian_conf_lvl might not be clear to users attempting to set either attribute to None post construction.

np.testing.assert_allclose(
sp.stats.chi2.isf(q=1 - init_conf_lvl, df=eset.dim),
eset.scale,
err_msg="EllipsoidalSet scale not as expected",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: I won't have this hold up the PR, but it might be worth making this message into its own class-level var so you don't have to potentially change the error message in a bunch of places if you decide to modify the language or the error message someday.

Copy link
Contributor

@mrmundt mrmundt left a comment

Choose a reason for hiding this comment

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

I am fine with this PR as-is, but I did have one suggestion for better maintainability within the tests.

@mrmundt mrmundt merged commit 83767d3 into Pyomo:main Dec 18, 2024
32 checks passed
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.

4 participants