From c9b937d4f0c0c0740f70c427a0bf22f06ff5a52c Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 22 Nov 2024 20:30:01 -0500 Subject: [PATCH 1/8] Add chi-squared confidence level attr to `EllipsoidalSet` --- .../pyros/tests/test_uncertainty_sets.py | 82 +++++++++++++++++ pyomo/contrib/pyros/uncertainty_sets.py | 90 ++++++++++++++++--- 2 files changed, 159 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py index 13195b3270e..e28e557fabd 100644 --- a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py +++ b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py @@ -20,6 +20,7 @@ attempt_import, numpy as np, numpy_available, + scipy as sp, scipy_available, ) from pyomo.environ import SolverFactory @@ -1863,6 +1864,11 @@ def test_normal_construction_and_update(self): np.testing.assert_allclose( scale, eset.scale, err_msg="EllipsoidalSet scale not as expected" ) + np.testing.assert_allclose( + sp.stats.chi2.cdf(scale ** 0.5, df=2), + eset.chi_sq_conf_lvl, + err_msg="EllipsoidalSet chi-squared confidence level not as expected", + ) # check attributes update new_center = [-1, -3] @@ -1886,6 +1892,40 @@ def test_normal_construction_and_update(self): np.testing.assert_allclose( new_scale, eset.scale, err_msg="EllipsoidalSet scale update not as expected" ) + np.testing.assert_allclose( + sp.stats.chi2.cdf(new_scale ** 0.5, df=2), + eset.chi_sq_conf_lvl, + err_msg="EllipsoidalSet chi-square confidence level update not as expected" + ) + + def test_normal_construction_and_update_chi_sq_conf_lvl(self): + """ + Test EllipsoidalSet constructor and setter + work normally when arguments are appropriate. + """ + init_conf_lvl = 0.95 + eset = EllipsoidalSet( + center=[0, 0, 0], + shape_matrix=np.eye(3), + scale=None, + chi_sq_conf_lvl=init_conf_lvl, + ) + + self.assertEqual(eset.chi_sq_conf_lvl, init_conf_lvl) + 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", + ) + + new_conf_lvl = 0.99 + eset.chi_sq_conf_lvl = new_conf_lvl + self.assertEqual(eset.chi_sq_conf_lvl, new_conf_lvl) + np.testing.assert_allclose( + sp.stats.chi2.isf(q=1 - new_conf_lvl, df=eset.dim), + eset.scale, + err_msg="EllipsoidalSet scale not as expected", + ) def test_error_on_ellipsoidal_dim_change(self): """ @@ -1926,6 +1966,48 @@ def test_error_on_neg_scale(self): with self.assertRaisesRegex(ValueError, exc_str): eset.scale = neg_scale + def test_error_invalid_chi_sq_conf_lvl(self): + """ + Test error when attempting to initialize with chi-squared + confidence level outside range. + """ + center = [0, 0] + shape_matrix = [[1, 0], [0, 2]] + invalid_conf_lvl = 1.001 + + exc_str = r"Ensure the confidence level is a value in \[0, 1\)." + + # error on construction + with self.assertRaisesRegex(ValueError, exc_str): + EllipsoidalSet( + center=center, + shape_matrix=shape_matrix, + scale=None, + chi_sq_conf_lvl=invalid_conf_lvl, + ) + + # error on updating valid ellipsoid + eset = EllipsoidalSet(center, shape_matrix, scale=None, chi_sq_conf_lvl=0.95) + with self.assertRaisesRegex(ValueError, exc_str): + eset.chi_sq_conf_lvl = invalid_conf_lvl + + # negative confidence level + eset = EllipsoidalSet(center, shape_matrix, scale=None, chi_sq_conf_lvl=0.95) + with self.assertRaisesRegex(ValueError, exc_str): + eset.chi_sq_conf_lvl = -0.1 + + def test_error_scale_chi_sq_conf_lvl_construction(self): + """ + Test exception raised if neither or both of + `scale` and `chi_sq_conf_lvl` are None. + """ + exc_str = r"Exactly one of `scale` and `chi_sq_conf_lvl` should be None" + with self.assertRaisesRegex(ValueError, exc_str): + EllipsoidalSet([0], [[1]], scale=None, chi_sq_conf_lvl=None) + + with self.assertRaisesRegex(ValueError, exc_str): + EllipsoidalSet([0], [[1]], scale=1, chi_sq_conf_lvl=0.95) + def test_error_on_shape_matrix_with_wrong_size(self): """ Test error in event EllipsoidalSet shape matrix diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index ed03aa7553a..3da37648528 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -2374,31 +2374,37 @@ class EllipsoidalSet(UncertaintySet): center : (N,) array-like Center of the ellipsoid. shape_matrix : (N, N) array-like - A positive definite matrix characterizing the shape - and orientation of the ellipsoid. - scale : numeric type, optional + A symmetric positive definite matrix characterizing + the shape and orientation of the ellipsoid. + scale : numeric type or None, optional Square of the factor by which to scale the semi-axes of the ellipsoid (i.e. the eigenvectors of the shape matrix). The default is `1`. + chi_sq_conf_lvl : numeric type or None, optional + (Fractional) chi-squared confidence level of the multivariate + normal distribution with mean `center` and covariance + matrix `shape_matrix`. + Exactly one of `scale` and `chi_sq_conf_lvl` should be + None; otherwise, an exception is raised. Examples -------- - 3D origin-centered unit hypersphere: + A 3D origin-centered unit ball: >>> from pyomo.contrib.pyros import EllipsoidalSet >>> import numpy as np - >>> hypersphere = EllipsoidalSet( + >>> ball = EllipsoidalSet( ... center=[0, 0, 0], ... shape_matrix=np.eye(3), ... scale=1, ... ) - >>> hypersphere.center + >>> ball.center array([0, 0, 0]) - >>> hypersphere.shape_matrix + >>> ball.shape_matrix array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) - >>> hypersphere.scale + >>> ball.scale 1 A 2D ellipsoid with custom rotation and scaling: @@ -2416,13 +2422,42 @@ class EllipsoidalSet(UncertaintySet): >>> rotated_ellipsoid.scale 0.5 + A 4D 95% confidence ellipsoid: + + >>> conf_ellipsoid = EllipsoidalSet( + ... center=np.zeros(4), + ... shape_matrix=np.diag(range(1, 5)), + ... scale=None, + ... chi_sq_conf_lvl=0.95, + ... ) + >>> conf_ellipsoid.center + array([0, 0, 0, 0]) + >>> conf_ellipsoid.shape_matrix + array([[1, 0, 0, 0]], + [0, 2, 0, 0]], + [0, 0, 3, 0]], + [0, 0, 0. 4]]) + >>> conf_ellipsoid.scale + ...9.4877... + >>> conf_ellipsoid.chi_sq_conf_lvl + 0.95 + """ - def __init__(self, center, shape_matrix, scale=1): + def __init__(self, center, shape_matrix, scale=1, chi_sq_conf_lvl=None): """Initialize self (see class docstring).""" self.center = center self.shape_matrix = shape_matrix - self.scale = scale + + if scale is not None and chi_sq_conf_lvl is None: + self.scale = scale + elif scale is None and chi_sq_conf_lvl is not None: + self.chi_sq_conf_lvl = chi_sq_conf_lvl + else: + raise ValueError( + "Exactly one of `scale` and `chi_sq_conf_lvl` should be " + f"None (got {scale=}, {chi_sq_conf_lvl=})" + ) @property def type(self): @@ -2456,7 +2491,7 @@ def center(self, val): if val_arr.size != self.dim: raise ValueError( "Attempting to set attribute 'center' of " - f"AxisAlignedEllipsoidalSet of dimension {self.dim} " + f"{type(self).__name__} of dimension {self.dim} " f"to value of dimension {val_arr.size}" ) @@ -2535,7 +2570,7 @@ def shape_matrix(self, val): if hasattr(self, "_center"): if not all(size == self.dim for size in shape_mat_arr.shape): raise ValueError( - f"EllipsoidalSet attribute 'shape_matrix' " + f"{type(self).__name__} attribute 'shape_matrix' " f"must be a square matrix of size " f"{self.dim} to match set dimension " f"(provided matrix with shape {shape_mat_arr.shape})" @@ -2558,12 +2593,41 @@ def scale(self, val): validate_arg_type("scale", val, valid_num_types, "a valid numeric type", False) if val < 0: raise ValueError( - "EllipsoidalSet attribute " + f"{type(self).__name__} attribute " f"'scale' must be a non-negative real " f"(provided value {val})" ) self._scale = val + self._chi_sq_conf_lvl = sp.stats.chi2.cdf(x=val ** 0.5, df=self.dim) + + @property + def chi_sq_conf_lvl(self): + """ + numeric type : (Fractional) chi-squared confidence level of the + multivariate normal distribution with mean ``self.origin`` + and covariance ``self.shape_matrix`` for ellipsoidal region + with square magnification factor ``self.scale``. + """ + return self._chi_sq_conf_lvl + + @chi_sq_conf_lvl.setter + def chi_sq_conf_lvl(self, val): + validate_arg_type( + "chi_sq_conf_lvl", + val, + valid_num_types, + "a valid numeric type", + False, + ) + self._chi_sq_conf_lvl = val + self._scale = sp.stats.chi2.isf(q=1 - val, df=self.dim) + if np.isnan(self._scale) or np.isinf(self._scale): + raise ValueError( + f"Squared scaling factor calculation for confidence level {val} " + f"and set dimension {self.dim} returned {self._scale}. " + "Ensure the confidence level is a value in [0, 1)." + ) @property def dim(self): From 290868c509b95399a9a76f217da333deca51d1c5 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 22 Nov 2024 20:30:45 -0500 Subject: [PATCH 2/8] Apply black --- pyomo/contrib/pyros/tests/test_uncertainty_sets.py | 6 +++--- pyomo/contrib/pyros/uncertainty_sets.py | 8 ++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py index e28e557fabd..fb0f927b4ac 100644 --- a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py +++ b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py @@ -1865,7 +1865,7 @@ def test_normal_construction_and_update(self): scale, eset.scale, err_msg="EllipsoidalSet scale not as expected" ) np.testing.assert_allclose( - sp.stats.chi2.cdf(scale ** 0.5, df=2), + sp.stats.chi2.cdf(scale**0.5, df=2), eset.chi_sq_conf_lvl, err_msg="EllipsoidalSet chi-squared confidence level not as expected", ) @@ -1893,9 +1893,9 @@ def test_normal_construction_and_update(self): new_scale, eset.scale, err_msg="EllipsoidalSet scale update not as expected" ) np.testing.assert_allclose( - sp.stats.chi2.cdf(new_scale ** 0.5, df=2), + sp.stats.chi2.cdf(new_scale**0.5, df=2), eset.chi_sq_conf_lvl, - err_msg="EllipsoidalSet chi-square confidence level update not as expected" + err_msg="EllipsoidalSet chi-square confidence level update not as expected", ) def test_normal_construction_and_update_chi_sq_conf_lvl(self): diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 3da37648528..74c6dbe62eb 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -2599,7 +2599,7 @@ def scale(self, val): ) self._scale = val - self._chi_sq_conf_lvl = sp.stats.chi2.cdf(x=val ** 0.5, df=self.dim) + self._chi_sq_conf_lvl = sp.stats.chi2.cdf(x=val**0.5, df=self.dim) @property def chi_sq_conf_lvl(self): @@ -2614,11 +2614,7 @@ def chi_sq_conf_lvl(self): @chi_sq_conf_lvl.setter def chi_sq_conf_lvl(self, val): validate_arg_type( - "chi_sq_conf_lvl", - val, - valid_num_types, - "a valid numeric type", - False, + "chi_sq_conf_lvl", val, valid_num_types, "a valid numeric type", False ) self._chi_sq_conf_lvl = val self._scale = sp.stats.chi2.isf(q=1 - val, df=self.dim) From b960fc0e8bcea8002665bc361e20cc59c1da80c3 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 22 Nov 2024 21:45:55 -0500 Subject: [PATCH 3/8] Fix `EllipsoidalSet.scale` setter --- pyomo/contrib/pyros/tests/test_uncertainty_sets.py | 8 ++++++-- pyomo/contrib/pyros/uncertainty_sets.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py index fb0f927b4ac..286cd620d85 100644 --- a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py +++ b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py @@ -1865,7 +1865,9 @@ def test_normal_construction_and_update(self): scale, eset.scale, err_msg="EllipsoidalSet scale not as expected" ) np.testing.assert_allclose( - sp.stats.chi2.cdf(scale**0.5, df=2), + # evaluate chisquare CDF for 2 degrees of freedom + # using simplified formula + 1 - np.exp(-scale / 2), eset.chi_sq_conf_lvl, err_msg="EllipsoidalSet chi-squared confidence level not as expected", ) @@ -1893,7 +1895,9 @@ def test_normal_construction_and_update(self): new_scale, eset.scale, err_msg="EllipsoidalSet scale update not as expected" ) np.testing.assert_allclose( - sp.stats.chi2.cdf(new_scale**0.5, df=2), + # evaluate chisquare CDF for 2 degrees of freedom + # using simplified formula + 1 - np.exp(-new_scale / 2), eset.chi_sq_conf_lvl, err_msg="EllipsoidalSet chi-square confidence level update not as expected", ) diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 74c6dbe62eb..ef2f6dfceaf 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -2599,7 +2599,7 @@ def scale(self, val): ) self._scale = val - self._chi_sq_conf_lvl = sp.stats.chi2.cdf(x=val**0.5, df=self.dim) + self._chi_sq_conf_lvl = sp.stats.chi2.cdf(x=val, df=self.dim) @property def chi_sq_conf_lvl(self): From b9576c00a0fcf086502b2cfd7f60246f8a9b8732 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 25 Nov 2024 02:20:00 -0500 Subject: [PATCH 4/8] Update version number, changelog --- doc/OnlineDocs/explanation/solvers/pyros.rst | 4 ++-- pyomo/contrib/pyros/CHANGELOG.txt | 7 +++++++ pyomo/contrib/pyros/pyros.py | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/OnlineDocs/explanation/solvers/pyros.rst b/doc/OnlineDocs/explanation/solvers/pyros.rst index ad3c99c1c11..24b6cf31319 100644 --- a/doc/OnlineDocs/explanation/solvers/pyros.rst +++ b/doc/OnlineDocs/explanation/solvers/pyros.rst @@ -935,8 +935,8 @@ Observe that the log contains the following information: :linenos: ============================================================================== - PyROS: The Pyomo Robust Optimization Solver, v1.3.0. - Pyomo version: 6.8.1 + PyROS: The Pyomo Robust Optimization Solver, v1.3.1. + Pyomo version: 6.8.3 Commit hash: unknown Invoked at UTC 2024-11-01T00:00:00.000000 diff --git a/pyomo/contrib/pyros/CHANGELOG.txt b/pyomo/contrib/pyros/CHANGELOG.txt index afae4b3db71..0036311fc91 100644 --- a/pyomo/contrib/pyros/CHANGELOG.txt +++ b/pyomo/contrib/pyros/CHANGELOG.txt @@ -2,6 +2,13 @@ PyROS CHANGELOG =============== +------------------------------------------------------------------------------- +PyROS 1.3.1 25 Nov 2024 +------------------------------------------------------------------------------- +- Add new EllipsoidalSet attribute for specifying a + confidence level in lieu of a (squared) scale factor + + ------------------------------------------------------------------------------- PyROS 1.3.0 12 Aug 2024 ------------------------------------------------------------------------------- diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 2ffef5054aa..2b13999c9c9 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -33,7 +33,7 @@ ) -__version__ = "1.3.0" +__version__ = "1.3.1" default_pyros_solver_logger = setup_pyros_logger() From 6b36ebb497c32eab7ff4d3255d876f443c31de74 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 25 Nov 2024 12:31:04 -0500 Subject: [PATCH 5/8] Rename `chi_sq_conf_lvl` -> `gaussian_conf_lvl` --- .../pyros/tests/test_uncertainty_sets.py | 42 +++++++++---------- pyomo/contrib/pyros/uncertainty_sets.py | 40 +++++++++--------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py index 286cd620d85..e0e5bb7d137 100644 --- a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py +++ b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py @@ -1868,8 +1868,8 @@ def test_normal_construction_and_update(self): # evaluate chisquare CDF for 2 degrees of freedom # using simplified formula 1 - np.exp(-scale / 2), - eset.chi_sq_conf_lvl, - err_msg="EllipsoidalSet chi-squared confidence level not as expected", + eset.gaussian_conf_lvl, + err_msg="EllipsoidalSet Gaussian confidence level not as expected", ) # check attributes update @@ -1898,11 +1898,11 @@ def test_normal_construction_and_update(self): # evaluate chisquare CDF for 2 degrees of freedom # using simplified formula 1 - np.exp(-new_scale / 2), - eset.chi_sq_conf_lvl, - err_msg="EllipsoidalSet chi-square confidence level update not as expected", + eset.gaussian_conf_lvl, + err_msg="EllipsoidalSet Gaussian confidence level update not as expected", ) - def test_normal_construction_and_update_chi_sq_conf_lvl(self): + def test_normal_construction_and_update_gaussian_conf_lvl(self): """ Test EllipsoidalSet constructor and setter work normally when arguments are appropriate. @@ -1912,10 +1912,10 @@ def test_normal_construction_and_update_chi_sq_conf_lvl(self): center=[0, 0, 0], shape_matrix=np.eye(3), scale=None, - chi_sq_conf_lvl=init_conf_lvl, + gaussian_conf_lvl=init_conf_lvl, ) - self.assertEqual(eset.chi_sq_conf_lvl, init_conf_lvl) + self.assertEqual(eset.gaussian_conf_lvl, init_conf_lvl) np.testing.assert_allclose( sp.stats.chi2.isf(q=1 - init_conf_lvl, df=eset.dim), eset.scale, @@ -1923,8 +1923,8 @@ def test_normal_construction_and_update_chi_sq_conf_lvl(self): ) new_conf_lvl = 0.99 - eset.chi_sq_conf_lvl = new_conf_lvl - self.assertEqual(eset.chi_sq_conf_lvl, new_conf_lvl) + eset.gaussian_conf_lvl = new_conf_lvl + self.assertEqual(eset.gaussian_conf_lvl, new_conf_lvl) np.testing.assert_allclose( sp.stats.chi2.isf(q=1 - new_conf_lvl, df=eset.dim), eset.scale, @@ -1970,9 +1970,9 @@ def test_error_on_neg_scale(self): with self.assertRaisesRegex(ValueError, exc_str): eset.scale = neg_scale - def test_error_invalid_chi_sq_conf_lvl(self): + def test_error_invalid_gaussian_conf_lvl(self): """ - Test error when attempting to initialize with chi-squared + Test error when attempting to initialize with Gaussian confidence level outside range. """ center = [0, 0] @@ -1987,30 +1987,30 @@ def test_error_invalid_chi_sq_conf_lvl(self): center=center, shape_matrix=shape_matrix, scale=None, - chi_sq_conf_lvl=invalid_conf_lvl, + gaussian_conf_lvl=invalid_conf_lvl, ) # error on updating valid ellipsoid - eset = EllipsoidalSet(center, shape_matrix, scale=None, chi_sq_conf_lvl=0.95) + eset = EllipsoidalSet(center, shape_matrix, scale=None, gaussian_conf_lvl=0.95) with self.assertRaisesRegex(ValueError, exc_str): - eset.chi_sq_conf_lvl = invalid_conf_lvl + eset.gaussian_conf_lvl = invalid_conf_lvl # negative confidence level - eset = EllipsoidalSet(center, shape_matrix, scale=None, chi_sq_conf_lvl=0.95) + eset = EllipsoidalSet(center, shape_matrix, scale=None, gaussian_conf_lvl=0.95) with self.assertRaisesRegex(ValueError, exc_str): - eset.chi_sq_conf_lvl = -0.1 + eset.gaussian_conf_lvl = -0.1 - def test_error_scale_chi_sq_conf_lvl_construction(self): + def test_error_scale_gaussian_conf_lvl_construction(self): """ Test exception raised if neither or both of - `scale` and `chi_sq_conf_lvl` are None. + `scale` and `gaussian_conf_lvl` are None. """ - exc_str = r"Exactly one of `scale` and `chi_sq_conf_lvl` should be None" + exc_str = r"Exactly one of `scale` and `gaussian_conf_lvl` should be None" with self.assertRaisesRegex(ValueError, exc_str): - EllipsoidalSet([0], [[1]], scale=None, chi_sq_conf_lvl=None) + EllipsoidalSet([0], [[1]], scale=None, gaussian_conf_lvl=None) with self.assertRaisesRegex(ValueError, exc_str): - EllipsoidalSet([0], [[1]], scale=1, chi_sq_conf_lvl=0.95) + EllipsoidalSet([0], [[1]], scale=1, gaussian_conf_lvl=0.95) def test_error_on_shape_matrix_with_wrong_size(self): """ diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index ef2f6dfceaf..3fc90b79cdf 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -2380,11 +2380,11 @@ class EllipsoidalSet(UncertaintySet): Square of the factor by which to scale the semi-axes of the ellipsoid (i.e. the eigenvectors of the shape matrix). The default is `1`. - chi_sq_conf_lvl : numeric type or None, optional - (Fractional) chi-squared confidence level of the multivariate + gaussian_conf_lvl : numeric type or None, optional + (Fractional) confidence level of the multivariate normal distribution with mean `center` and covariance matrix `shape_matrix`. - Exactly one of `scale` and `chi_sq_conf_lvl` should be + Exactly one of `scale` and `gaussian_conf_lvl` should be None; otherwise, an exception is raised. Examples @@ -2428,7 +2428,7 @@ class EllipsoidalSet(UncertaintySet): ... center=np.zeros(4), ... shape_matrix=np.diag(range(1, 5)), ... scale=None, - ... chi_sq_conf_lvl=0.95, + ... gaussian_conf_lvl=0.95, ... ) >>> conf_ellipsoid.center array([0, 0, 0, 0]) @@ -2439,24 +2439,24 @@ class EllipsoidalSet(UncertaintySet): [0, 0, 0. 4]]) >>> conf_ellipsoid.scale ...9.4877... - >>> conf_ellipsoid.chi_sq_conf_lvl + >>> conf_ellipsoid.gaussian_conf_lvl 0.95 """ - def __init__(self, center, shape_matrix, scale=1, chi_sq_conf_lvl=None): + def __init__(self, center, shape_matrix, scale=1, gaussian_conf_lvl=None): """Initialize self (see class docstring).""" self.center = center self.shape_matrix = shape_matrix - if scale is not None and chi_sq_conf_lvl is None: + if scale is not None and gaussian_conf_lvl is None: self.scale = scale - elif scale is None and chi_sq_conf_lvl is not None: - self.chi_sq_conf_lvl = chi_sq_conf_lvl + 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 `chi_sq_conf_lvl` should be " - f"None (got {scale=}, {chi_sq_conf_lvl=})" + "Exactly one of `scale` and `gaussian_conf_lvl` should be " + f"None (got {scale=}, {gaussian_conf_lvl=})" ) @property @@ -2599,24 +2599,24 @@ def scale(self, val): ) self._scale = val - self._chi_sq_conf_lvl = sp.stats.chi2.cdf(x=val, df=self.dim) + self._gaussian_conf_lvl = sp.stats.chi2.cdf(x=val, df=self.dim) @property - def chi_sq_conf_lvl(self): + def gaussian_conf_lvl(self): """ - numeric type : (Fractional) chi-squared confidence level of the - multivariate normal distribution with mean ``self.origin`` + numeric type : (Fractional) confidence level of the + multivariate Gaussian distribution with mean ``self.origin`` and covariance ``self.shape_matrix`` for ellipsoidal region with square magnification factor ``self.scale``. """ - return self._chi_sq_conf_lvl + return self._gaussian_conf_lvl - @chi_sq_conf_lvl.setter - def chi_sq_conf_lvl(self, val): + @gaussian_conf_lvl.setter + def gaussian_conf_lvl(self, val): validate_arg_type( - "chi_sq_conf_lvl", val, valid_num_types, "a valid numeric type", False + "gaussian_conf_lvl", val, valid_num_types, "a valid numeric type", False ) - self._chi_sq_conf_lvl = val + self._gaussian_conf_lvl = val self._scale = sp.stats.chi2.isf(q=1 - val, df=self.dim) if np.isnan(self._scale) or np.isinf(self._scale): raise ValueError( From 3d97337113f93509fe22611234e55c180a386927 Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 26 Nov 2024 12:33:16 -0500 Subject: [PATCH 6/8] Update attrs in `gaussian_conf_lvl` setter only after validation --- pyomo/contrib/pyros/uncertainty_sets.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 3fc90b79cdf..f1c67c9df57 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -2616,15 +2616,18 @@ def gaussian_conf_lvl(self, val): validate_arg_type( "gaussian_conf_lvl", val, valid_num_types, "a valid numeric type", False ) - self._gaussian_conf_lvl = val - self._scale = sp.stats.chi2.isf(q=1 - val, df=self.dim) - if np.isnan(self._scale) or np.isinf(self._scale): + + scale_val = sp.stats.chi2.isf(q=1 - val, df=self.dim) + if np.isnan(scale_val) or np.isinf(scale_val): raise ValueError( f"Squared scaling factor calculation for confidence level {val} " - f"and set dimension {self.dim} returned {self._scale}. " + f"and set dimension {self.dim} returned {scale_val}. " "Ensure the confidence level is a value in [0, 1)." ) + self._gaussian_conf_lvl = val + self._scale = scale_val + @property def dim(self): """ From 0b11c545c7db0305c94cda2b22943a2d6c78bdba Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 27 Nov 2024 12:16:32 -0500 Subject: [PATCH 7/8] Correct pyomo version number in logging example --- doc/OnlineDocs/explanation/solvers/pyros.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/explanation/solvers/pyros.rst b/doc/OnlineDocs/explanation/solvers/pyros.rst index 24b6cf31319..367b6f2b102 100644 --- a/doc/OnlineDocs/explanation/solvers/pyros.rst +++ b/doc/OnlineDocs/explanation/solvers/pyros.rst @@ -936,7 +936,7 @@ Observe that the log contains the following information: ============================================================================== PyROS: The Pyomo Robust Optimization Solver, v1.3.1. - Pyomo version: 6.8.3 + Pyomo version: 6.9.0 Commit hash: unknown Invoked at UTC 2024-11-01T00:00:00.000000 From ec2478e56424ce740d98eda747f77c28e93bf86b Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 3 Dec 2024 13:00:33 -0500 Subject: [PATCH 8/8] Simplify `EllipsoidalSet` constructor docstring --- pyomo/contrib/pyros/uncertainty_sets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index f1c67c9df57..a4b6ba6aa1a 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -2376,11 +2376,11 @@ class EllipsoidalSet(UncertaintySet): shape_matrix : (N, N) array-like A symmetric positive definite matrix characterizing the shape and orientation of the ellipsoid. - scale : numeric type or None, optional + scale : numeric type, optional Square of the factor by which to scale the semi-axes of the ellipsoid (i.e. the eigenvectors of the shape matrix). The default is `1`. - gaussian_conf_lvl : numeric type or None, optional + gaussian_conf_lvl : numeric type, optional (Fractional) confidence level of the multivariate normal distribution with mean `center` and covariance matrix `shape_matrix`.