Skip to content

Commit 2c86ec2

Browse files
authored
Merge pull request #131 from PerformanceEstimation/feature/constraints_deactivation
Feature/constraints deactivation
2 parents 06a3f2f + 0566307 commit 2c86ec2

11 files changed

Lines changed: 217 additions & 94 deletions

File tree

PEPit/constraint.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Constraint(object):
88
99
Attributes:
1010
name (str): A name set through the set_name method. None is no name is given.
11+
activated (bool): A boolean flag used to activate/deactivate the Constraint in the PEP.
1112
expression (Expression): The :class:`Expression` that is compared to 0.
1213
equality_or_inequality (str): "equality" or "inequality". Encodes the type of constraint.
1314
_value (float): numerical value of `self.expression` obtained after solving the PEP via SDP solver.
@@ -56,13 +57,16 @@ def __init__(self,
5657
AssertionError: if provided `equality_or_inequality` argument is neither "equality" nor "inequality".
5758
5859
"""
59-
# Initialize name of the constraint
60-
self.name = None
60+
# Initialize the activated attribute to True
61+
self.activated = True
6162

6263
# Update the counter
6364
self.counter = Constraint.counter
6465
Constraint.counter += 1
6566

67+
# Initialize name of the constraint
68+
self.name = "Constraint {}".format(self.counter)
69+
6670
# Store the underlying expression
6771
self.expression = expression
6872

@@ -92,6 +96,20 @@ def get_name(self):
9296
"""
9397
return self.name
9498

99+
def activate(self):
100+
"""
101+
Activate the use of the Constraint.
102+
103+
"""
104+
self.activated = True
105+
106+
def deactivate(self):
107+
"""
108+
Deactivate the use of the Constraint.
109+
110+
"""
111+
self.activated = False
112+
95113
def eval(self):
96114
"""
97115
Compute, store and return the value of the underlying :class:`Expression` of this :class:`Constraint`.

PEPit/pep.py

Lines changed: 115 additions & 68 deletions
Large diffs are not rendered by default.

PEPit/psd_matrix.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class PSDMatrix(object):
99
1010
Attributes:
1111
name (str): A name set through the set_name method. None is no name is given.
12+
activated (bool): A boolean flag used to activate/deactivate the LMI constraint in the PEP.
1213
matrix_of_expressions (Iterable of Iterable of Expression): a square matrix of :class:`Expression` objects.
1314
shape (tuple of ints): the shape of the underlying matrix of :class:`Expression` objects.
1415
_value (2D ndarray of floats): numerical values of :class:`Expression` objects
@@ -61,6 +62,9 @@ def __init__(self,
6162
TypeError: if provided matrix does not contain only Expressions and / or scalar values.
6263
6364
"""
65+
# Initialize the activated attribute to True
66+
self.activated = True
67+
6468
# Initialize name of the psd matrix
6569
self.name = name
6670

@@ -95,6 +99,20 @@ def get_name(self):
9599
"""
96100
return self.name
97101

102+
def activate(self):
103+
"""
104+
Activate the use of the LMI constraint.
105+
106+
"""
107+
self.activated = True
108+
109+
def deactivate(self):
110+
"""
111+
Deactivate the use of the LMI constraint.
112+
113+
"""
114+
self.activated = False
115+
98116
@staticmethod
99117
def _store(matrix_of_expressions):
100118
"""

PEPit/wrapper.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,12 @@ def send_constraint_to_solver(self, constraint):
8989
"""
9090
raise NotImplementedError("This method must be overwritten in children classes")
9191

92-
def send_lmi_constraint_to_solver(self, psd_counter, psd_matrix):
92+
def send_lmi_constraint_to_solver(self, psd_matrix):
9393
"""
9494
Transfer a PEPit :class:`PSDMatrix` (LMI constraint) to the solver
9595
and add it the tracking lists.
9696
9797
Args:
98-
psd_counter (int): a counter useful for the verbose mode.
9998
psd_matrix (PSDMatrix): a matrix of expressions that is constrained to be PSD.
10099
101100
"""

PEPit/wrappers/cvxpy_wrapper.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,18 +135,17 @@ def send_constraint_to_solver(self, constraint):
135135
# Raise an exception otherwise
136136
raise ValueError('The attribute \'equality_or_inequality\' of a constraint object'
137137
' must either be \'equality\' or \'inequality\'.'
138-
'Got {}'.format(constraint.equality_or_inequality))
138+
'{} got {}'.format(constraint.get_name(), constraint.equality_or_inequality))
139139

140140
# Add the corresponding CVXPY constraint to the list of constraints to be sent to CVXPY
141141
self._list_of_solver_constraints.append(cvxpy_constraint)
142142

143-
def send_lmi_constraint_to_solver(self, psd_counter, psd_matrix):
143+
def send_lmi_constraint_to_solver(self, psd_matrix):
144144
"""
145145
Transform a PEPit :class:`PSDMatrix` into a CVXPY symmetric PSD matrix
146146
and add the 2 formats of the constraints into the tracking lists.
147147
148148
Args:
149-
psd_counter (int): a counter useful for the verbose mode.
150149
psd_matrix (PSDMatrix): a matrix of expressions that is constrained to be PSD.
151150
152151
"""
@@ -170,10 +169,6 @@ def send_lmi_constraint_to_solver(self, psd_counter, psd_matrix):
170169
for j in range(psd_matrix.shape[1]):
171170
cvxpy_constraints_list.append(M[i, j] == self._expression_to_solver(psd_matrix[i, j]))
172171

173-
# Print a message if verbose mode activated
174-
if self.verbose > 0:
175-
print('\t\t Size of PSD matrix {}: {}x{}'.format(psd_counter + 1, *psd_matrix.shape))
176-
177172
# Add the corresponding CVXPY constraints to the list of constraints to be sent to CVXPY
178173
self._list_of_solver_constraints += cvxpy_constraints_list
179174

PEPit/wrappers/mosek_wrapper.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,13 @@ def send_constraint_to_solver(self, constraint, track=True):
159159
# Raise an exception otherwise
160160
raise ValueError('The attribute \'equality_or_inequality\' of a constraint object'
161161
' must either be \'equality\' or \'inequality\'.'
162-
'Got {}'.format(constraint.equality_or_inequality))
162+
'{} got {}'.format(constraint.get_name(), constraint.equality_or_inequality))
163163

164-
def send_lmi_constraint_to_solver(self, psd_counter, psd_matrix):
164+
def send_lmi_constraint_to_solver(self, psd_matrix):
165165
"""
166166
Transfer a PEPit :class:`PSDMatrix` (LMI constraint) to MOSEK and add it the tracking lists.
167167
168168
Args:
169-
psd_counter (int): a counter useful for the verbose mode.
170169
psd_matrix (PSDMatrix): a matrix of expressions that is constrained to be PSD.
171170
172171
"""
@@ -204,10 +203,6 @@ def send_lmi_constraint_to_solver(self, psd_counter, psd_matrix):
204203
self.task.putaijlist(np.full(a_i.shape, nb_cons), a_i, a_val)
205204
self.task.putconbound(nb_cons, mosek.boundkey.fx, -alpha_val, -alpha_val)
206205

207-
# Print a message if verbose mode activated
208-
if self.verbose > 0:
209-
print('\t\t Size of PSD matrix {}: {}x{}'.format(psd_counter + 1, *psd_matrix.shape))
210-
211206
def _recover_dual_values(self):
212207
"""
213208
Recover all dual variables from solver.

docs/source/whatsnew/0.4.1.rst

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
What's new in PEPit 0.4.1
22
=========================
33

4-
- Improvement: The :class:`SmoothQuadraticLojasiewiczFunctionExpensive` has been improved with slightly cheaper formulations.
4+
New features:
5+
-------------
56

6-
- Added uselessly complexified tests to verify numerically that the formulations are in line.
7+
- New: one can disable some software verification tools (licensing, installation, etc) to allow for fast processing time (via boolean option safe_mode).
78

8-
- Fix: An integer overflow (due to previously specified dtype=int8) in :class:`MosekWrapper` has been fixed.
9+
- Constraints (scalar and LMI) can now be deactivated using the `deactivate` method and activated back using the `activated` method. Note the PEP can be solved several times with different activated constraints. The method `solve` only creates the interpolation constraints once for all.
910

10-
- New: one can disable some software verification tools (licensing, installation, etc) to allow for fast processing time (via boolean option safe_mode).
11+
Fixes:
12+
------
13+
14+
- The :class:`SmoothQuadraticLojasiewiczFunctionExpensive` has been improved with slightly cheaper formulations.
15+
16+
- An integer overflow (due to previously specified dtype=int8) in :class:`MosekWrapper` has been fixed.
17+
18+
- The method `add_constraints_from_two_lists_of_points` of the class :class:`Function` has been fixed. So far, it was assumed that the two lists were identical.

tests/test_constraints.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def test_counter(self):
7474

7575
def test_name(self):
7676

77-
self.assertIsNone(self.initial_condition.get_name())
77+
self.assertEqual(self.initial_condition.get_name(), "Constraint 0")
7878
self.assertIsNone(self.performance_metric.get_name())
7979

8080
self.initial_condition.set_name("init")
@@ -93,6 +93,13 @@ def test_equality_inequality(self):
9393
self.assertIsInstance(self.problem.list_of_constraints[i].equality_or_inequality, str)
9494
self.assertIn(self.problem.list_of_constraints[i].equality_or_inequality, {'equality', 'inequality'})
9595

96+
def test_activated(self):
97+
self.assertIs(self.initial_condition.activated, True)
98+
self.initial_condition.deactivate()
99+
self.assertIs(self.initial_condition.activated, False)
100+
self.initial_condition.activate()
101+
self.assertIs(self.initial_condition.activated, True)
102+
96103
def test_eval(self):
97104

98105
for i in range(len(self.func.list_of_class_constraints)):

tests/test_pep.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,40 @@ def test_lmi_constraints_in_real_problem(self):
160160
def test_consistency(self):
161161

162162
# Solve twice the same problem in a row and verify the two lists of constraints have same length.
163+
self.assertEqual(len(self.problem._list_of_prepared_constraints), 0)
164+
self.assertEqual(len(self.problem._list_of_constraints_sent_to_wrapper), 0)
165+
166+
# Run solve
163167
_ = self.problem.solve(verbose=self.verbose)
164-
l1 = self.problem._list_of_constraints_sent_to_wrapper
168+
lp1 = self.problem._list_of_prepared_constraints
169+
ls1 = self.problem._list_of_constraints_sent_to_wrapper
170+
171+
# Rerun solve
165172
_ = self.problem.solve(verbose=self.verbose)
166-
l2 = self.problem._list_of_constraints_sent_to_wrapper
173+
lp2 = self.problem._list_of_prepared_constraints
174+
ls2 = self.problem._list_of_constraints_sent_to_wrapper
167175

168-
self.assertEqual(len(l1), len(l2))
176+
# Deactivate one constraint, then rerun solve
177+
self.problem.list_of_constraints[0].deactivate()
178+
_ = self.problem.solve(verbose=self.verbose)
179+
lp3 = self.problem._list_of_prepared_constraints
180+
ls3 = self.problem._list_of_constraints_sent_to_wrapper
181+
182+
# Reactivate the constraint, then rerun solve
183+
self.problem.list_of_constraints[0].activate()
184+
_ = self.problem.solve(verbose=self.verbose)
185+
lp4 = self.problem._list_of_prepared_constraints
186+
ls4 = self.problem._list_of_constraints_sent_to_wrapper
187+
188+
# Compare lengths
189+
self.assertEqual(len(lp1), len(lp2))
190+
self.assertEqual(len(lp1), len(lp3))
191+
self.assertEqual(len(lp1), len(lp4))
192+
193+
self.assertEqual(len(lp1), len(ls1))
194+
self.assertEqual(len(lp2), len(ls2))
195+
self.assertEqual(len(lp3), len(ls3)+1)
196+
self.assertEqual(len(lp4), len(ls4))
169197

170198
def test_dimension_reduction(self):
171199

tests/test_psd_matrix.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ def test_getitem(self):
103103
self.assertIs(self.psd1[0, 0], self.expr1)
104104
self.assertIs(self.psd2[0, 1], self.expr3)
105105

106+
def test_activated(self):
107+
self.assertIs(self.psd1.activated, True)
108+
self.psd1.deactivate()
109+
self.assertIs(self.psd1.activated, False)
110+
self.psd1.activate()
111+
self.assertIs(self.psd1.activated, True)
112+
106113
def test_eval(self):
107114

108115
# The PEP has not been solved yet, so no value is accessible.

0 commit comments

Comments
 (0)