pepflow 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {pepflow-0.1.0 → pepflow-0.1.2}/PKG-INFO +7 -2
  2. pepflow-0.1.2/pepflow/__init__.py +50 -0
  3. pepflow-0.1.2/pepflow/constants.py +20 -0
  4. pepflow-0.1.2/pepflow/constraint.py +38 -0
  5. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow/expression_manager.py +19 -0
  6. pepflow-0.1.2/pepflow/function.py +349 -0
  7. pepflow-0.1.2/pepflow/function_test.py +134 -0
  8. pepflow-0.1.2/pepflow/interactive_constraint.py +264 -0
  9. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow/pep.py +63 -7
  10. pepflow-0.1.2/pepflow/pep_context.py +127 -0
  11. pepflow-0.1.2/pepflow/pep_context_test.py +102 -0
  12. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow/pep_test.py +19 -0
  13. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow/point.py +31 -1
  14. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow/point_test.py +67 -30
  15. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow/scalar.py +32 -2
  16. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow/solver.py +28 -3
  17. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow/solver_test.py +19 -0
  18. pepflow-0.1.2/pepflow/utils.py +52 -0
  19. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow.egg-info/PKG-INFO +7 -2
  20. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow.egg-info/SOURCES.txt +4 -0
  21. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow.egg-info/requires.txt +5 -0
  22. pepflow-0.1.2/pyproject.toml +30 -0
  23. pepflow-0.1.0/pepflow/__init__.py +0 -0
  24. pepflow-0.1.0/pepflow/constraint.py +0 -19
  25. pepflow-0.1.0/pepflow/function.py +0 -183
  26. pepflow-0.1.0/pepflow/pep_context.py +0 -30
  27. pepflow-0.1.0/pepflow/utils.py +0 -51
  28. pepflow-0.1.0/pyproject.toml +0 -16
  29. {pepflow-0.1.0 → pepflow-0.1.2}/LICENSE +0 -0
  30. {pepflow-0.1.0 → pepflow-0.1.2}/README.md +0 -0
  31. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow.egg-info/dependency_links.txt +0 -0
  32. {pepflow-0.1.0 → pepflow-0.1.2}/pepflow.egg-info/top_level.txt +0 -0
  33. {pepflow-0.1.0 → pepflow-0.1.2}/setup.cfg +0 -0
@@ -1,15 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pepflow
3
- Version: 0.1.0
4
- Summary: Add your description here
3
+ Version: 0.1.2
4
+ Summary: PEPFlow: A framework for Performance Estimation Problem (PEP) Workflow
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: attrs>=25.3.0
9
9
  Requires-Dist: cvxpy>=1.7.1
10
+ Requires-Dist: dash>=3.2.0
11
+ Requires-Dist: dash-bootstrap-components>=2.0.3
10
12
  Requires-Dist: isort>=6.0.1
11
13
  Requires-Dist: matplotlib>=3.9.4
14
+ Requires-Dist: natsort>=8.4.0
12
15
  Requires-Dist: numpy>=2.0.2
16
+ Requires-Dist: pandas>=2.3.1
17
+ Requires-Dist: plotly>=6.3.0
13
18
  Requires-Dist: pytest>=8.4.1
14
19
  Requires-Dist: ruff>=0.12.4
15
20
  Requires-Dist: sympy>=1.14.0
@@ -0,0 +1,50 @@
1
+ # Copyright: 2025 The PEPFlow Developers
2
+ #
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ # isort: skip_file
21
+ from .constants import PSD_CONSTRAINT as PSD_CONSTRAINT
22
+ from .constraint import Constraint as Constraint
23
+ from .expression_manager import ExpressionManager as ExpressionManager
24
+
25
+ # interactive_constraint
26
+ from .interactive_constraint import launch as launch
27
+
28
+ # pep
29
+ from .pep import PEPBuilder as PEPBuilder
30
+ from .pep import PEPResult as PEPResult
31
+ from .pep_context import PEPContext as PEPContext
32
+ from .pep_context import get_current_context as get_current_context
33
+ from .pep_context import set_current_context as set_current_context
34
+
35
+ # Function, Point, Scalar
36
+ from .function import Function as Function
37
+ from .function import SmoothConvexFunction as SmoothConvexFunction
38
+ from .function import Triplet as Triplet
39
+ from .point import EvaluatedPoint as EvaluatedPoint
40
+ from .point import Point as Point
41
+ from .scalar import EvaluatedScalar as EvaluatedScalar
42
+ from .scalar import Scalar as Scalar
43
+
44
+ # Solver
45
+ from .solver import CVXSolver as CVXSolver
46
+ from .solver import DualVariableManager as DualVariableManager
47
+
48
+ # Others
49
+ from .utils import SOP as SOP
50
+ from .utils import SOP_self as SOP_self
@@ -0,0 +1,20 @@
1
+ # Copyright: 2025 The PEPFlow Developers
2
+ #
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ PSD_CONSTRAINT = "PSD of Grammian Matrix"
@@ -0,0 +1,38 @@
1
+ # Copyright: 2025 The PEPFlow Developers
2
+ #
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import TYPE_CHECKING
23
+
24
+ import attrs
25
+
26
+ if TYPE_CHECKING:
27
+ from pepflow.point import Scalar
28
+
29
+ from pepflow import utils
30
+
31
+
32
+ @attrs.frozen
33
+ class Constraint:
34
+ """It represents `expression relation 0`."""
35
+
36
+ scalar: Scalar | float
37
+ comparator: utils.Comparator
38
+ name: str
@@ -1,3 +1,22 @@
1
+ # Copyright: 2025 The PEPFlow Developers
2
+ #
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
1
20
  import functools
2
21
 
3
22
  import numpy as np
@@ -0,0 +1,349 @@
1
+ # Copyright: 2025 The PEPFlow Developers
2
+ #
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ from __future__ import annotations
21
+
22
+ import uuid
23
+
24
+ import attrs
25
+
26
+ from pepflow import pep_context as pc
27
+ from pepflow import point as pt
28
+ from pepflow import scalar as sc
29
+ from pepflow import utils
30
+
31
+
32
+ @attrs.frozen
33
+ class Triplet:
34
+ point: pt.Point
35
+ function_value: sc.Scalar
36
+ gradient: pt.Point
37
+ name: str | None
38
+ uid: uuid.UUID = attrs.field(factory=uuid.uuid4, init=False)
39
+
40
+
41
+ @attrs.frozen
42
+ class AddedFunc:
43
+ """Represents left_func + right_func."""
44
+
45
+ left_func: Function
46
+ right_func: Function
47
+
48
+
49
+ @attrs.frozen
50
+ class ScaledFunc:
51
+ """Represents scale * base_func."""
52
+
53
+ scale: float
54
+ base_func: Function
55
+
56
+
57
+ @attrs.mutable
58
+ class Function:
59
+ is_basis: bool
60
+ reuse_gradient: bool
61
+
62
+ composition: AddedFunc | ScaledFunc | None = None
63
+
64
+ # Human tagged value for the function
65
+ tags: list[str] = attrs.field(factory=list)
66
+
67
+ # Generate an automatic id
68
+ uid: uuid.UUID = attrs.field(factory=uuid.uuid4, init=False)
69
+
70
+ def __attrs_post_init__(self):
71
+ if self.is_basis:
72
+ assert self.composition is None
73
+ else:
74
+ assert self.composition is not None
75
+
76
+ @property
77
+ def tag(self):
78
+ if len(self.tags) == 0:
79
+ raise ValueError("Function should have a name.")
80
+ return self.tags[-1]
81
+
82
+ def add_tag(self, tag: str) -> None:
83
+ self.tags.append(tag)
84
+
85
+ def __repr__(self):
86
+ if self.tags:
87
+ return self.tag
88
+ return super().__repr__()
89
+
90
+ def get_interpolation_constraints(self):
91
+ raise NotImplementedError(
92
+ "This method should be implemented in the children class."
93
+ )
94
+
95
+ def add_triplet_to_func(self, triplet: Triplet) -> None:
96
+ pep_context = pc.get_current_context()
97
+ if pep_context is None:
98
+ raise RuntimeError("Did you forget to create a context?")
99
+ pep_context.triplets[self].append(triplet)
100
+
101
+ def add_point_with_grad_restriction(
102
+ self, point: pt.Point, desired_grad: pt.Point
103
+ ) -> Triplet:
104
+ # todo find a better tagging approach.
105
+ if self.is_basis:
106
+ function_value = sc.Scalar(is_basis=True)
107
+ function_value.add_tag(f"{self.tag}({point.tag})")
108
+ triplet = Triplet(
109
+ point,
110
+ function_value,
111
+ desired_grad,
112
+ name=f"{point.tag}_{function_value.tag}_{desired_grad.tag}",
113
+ )
114
+ self.add_triplet_to_func(triplet)
115
+ else:
116
+ if isinstance(self.composition, AddedFunc):
117
+ left_triplet = self.composition.left_func.generate_triplet(point)
118
+ next_desired_grad = desired_grad - left_triplet.gradient
119
+ next_desired_grad.add_tag(
120
+ f"gradient_{self.composition.right_func.tag}({point.tag})"
121
+ )
122
+ right_triplet = (
123
+ self.composition.right_func.add_point_with_grad_restriction(
124
+ point, next_desired_grad
125
+ )
126
+ )
127
+ triplet = Triplet(
128
+ point,
129
+ left_triplet.function_value + right_triplet.function_value,
130
+ desired_grad,
131
+ name=f"{point.tag}_{self.tag}_{desired_grad.tag}",
132
+ )
133
+ elif isinstance(self.composition, ScaledFunc):
134
+ next_desired_grad = desired_grad / self.composition.scale
135
+ next_desired_grad.add_tag(
136
+ f"gradient_{self.composition.base_func.tag}({point.tag})"
137
+ )
138
+ base_triplet = (
139
+ self.composition.base_func.add_point_with_grad_restriction(
140
+ point, next_desired_grad
141
+ )
142
+ )
143
+ triplet = Triplet(
144
+ point,
145
+ base_triplet.function_value * self.composition.scale,
146
+ desired_grad,
147
+ name=f"{point.tag}_{self.tag}_{desired_grad.tag}",
148
+ )
149
+ else:
150
+ raise ValueError(
151
+ f"Unknown composition of functions: {self.composition}"
152
+ )
153
+ return triplet
154
+
155
+ def add_stationary_point(self, name: str) -> pt.Point:
156
+ # assert we can only add one stationary point?
157
+ point = pt.Point(is_basis=True)
158
+ point.add_tag(name)
159
+ desired_grad = 0 * point
160
+ desired_grad.add_tag(f"gradient_{self.tag}({name})")
161
+ self.add_point_with_grad_restriction(point, desired_grad)
162
+ return point
163
+
164
+ # The following the old gradient(opt) = 0 constraint style.
165
+ # It is mathamatically correct but hard for solver so we abandon it.
166
+ #
167
+ # triplet = self.generate_triplet(point)
168
+ # pep_context = pc.get_current_context()
169
+ # pep_context.add_opt_condition(
170
+ # self,
171
+ # ((triplet.gradient) ** 2).eq(
172
+ # 0, name=f"{self.tags[0]}({point.tags[0]}) optimality condition"
173
+ # ),
174
+ # )
175
+ # return point
176
+
177
+ def generate_triplet(self, point: pt.Point) -> Triplet:
178
+ pep_context = pc.get_current_context()
179
+ if pep_context is None:
180
+ raise RuntimeError("Did you forget to create a context?")
181
+
182
+ if self.is_basis:
183
+ generate_new_basis = True
184
+ instances_of_point = 0
185
+ for triplet in pep_context.triplets[self]:
186
+ if triplet.point.uid == point.uid:
187
+ instances_of_point += 1
188
+ generate_new_basis = False
189
+ previous_triplet = triplet
190
+
191
+ if generate_new_basis:
192
+ function_value = sc.Scalar(is_basis=True)
193
+ function_value.add_tag(f"{self.tag}({point.tag})")
194
+ gradient = pt.Point(is_basis=True)
195
+ gradient.add_tag(f"gradient_{self.tag}({point.tag})")
196
+
197
+ new_triplet = Triplet(
198
+ point,
199
+ function_value,
200
+ gradient,
201
+ name=f"{point.tag}_{function_value.tag}_{gradient.tag}",
202
+ )
203
+ self.add_triplet_to_func(new_triplet)
204
+ elif not generate_new_basis and self.reuse_gradient:
205
+ function_value = previous_triplet.function_value
206
+ gradient = previous_triplet.gradient
207
+ elif not generate_new_basis and not self.reuse_gradient:
208
+ function_value = previous_triplet.function_value
209
+ gradient = pt.Point(is_basis=True)
210
+ gradient.add_tag(f"gradient_{self.tag}({point.tag})")
211
+
212
+ new_triplet = Triplet(
213
+ point,
214
+ previous_triplet.function_value,
215
+ gradient,
216
+ name=f"{point.tag}_{function_value.tag}_{gradient.tag}_{instances_of_point}",
217
+ )
218
+ self.add_triplet_to_func(new_triplet)
219
+ else:
220
+ if isinstance(self.composition, AddedFunc):
221
+ left_triplet = self.composition.left_func.generate_triplet(point)
222
+ right_triplet = self.composition.right_func.generate_triplet(point)
223
+ function_value = (
224
+ left_triplet.function_value + right_triplet.function_value
225
+ )
226
+ gradient = left_triplet.gradient + right_triplet.gradient
227
+ elif isinstance(self.composition, ScaledFunc):
228
+ base_triplet = self.composition.base_func.generate_triplet(point)
229
+ function_value = self.composition.scale * base_triplet.function_value
230
+ gradient = self.composition.scale * base_triplet.gradient
231
+ else:
232
+ raise ValueError(
233
+ f"Unknown composition of functions: {self.composition}"
234
+ )
235
+
236
+ return Triplet(point, function_value, gradient, name=None)
237
+
238
+ def gradient(self, point: pt.Point) -> pt.Point:
239
+ triplet = self.generate_triplet(point)
240
+ return triplet.gradient
241
+
242
+ def subgradient(self, point: pt.Point) -> pt.Point:
243
+ triplet = self.generate_triplet(point)
244
+ return triplet.gradient
245
+
246
+ def function_value(self, point: pt.Point) -> sc.Scalar:
247
+ triplet = self.generate_triplet(point)
248
+ return triplet.function_value
249
+
250
+ def __add__(self, other):
251
+ assert isinstance(other, Function)
252
+ return Function(
253
+ is_basis=False,
254
+ reuse_gradient=self.reuse_gradient and other.reuse_gradient,
255
+ composition=AddedFunc(self, other),
256
+ tags=[f"{self.tag}+{other.tag}"],
257
+ )
258
+
259
+ def __sub__(self, other):
260
+ assert isinstance(other, Function)
261
+ return Function(
262
+ is_basis=False,
263
+ reuse_gradient=self.reuse_gradient and other.reuse_gradient,
264
+ composition=AddedFunc(self, -other),
265
+ tags=[f"{self.tag}-{other.tag}"],
266
+ )
267
+
268
+ def __mul__(self, other):
269
+ assert utils.is_numerical(other)
270
+ return Function(
271
+ is_basis=False,
272
+ reuse_gradient=self.reuse_gradient,
273
+ composition=ScaledFunc(scale=other, base_func=self),
274
+ tags=[f"{other:.4g}*{self.tag}"],
275
+ )
276
+
277
+ def __rmul__(self, other):
278
+ assert utils.is_numerical(other)
279
+ return Function(
280
+ is_basis=False,
281
+ reuse_gradient=self.reuse_gradient,
282
+ composition=ScaledFunc(scale=other, base_func=self),
283
+ tags=[f"{other:.4g}*{self.tag}"],
284
+ )
285
+
286
+ def __neg__(self):
287
+ return Function(
288
+ is_basis=False,
289
+ reuse_gradient=self.reuse_gradient,
290
+ composition=ScaledFunc(scale=-1, base_func=self),
291
+ tags=[f"-{self.tag}"],
292
+ )
293
+
294
+ def __truediv__(self, other):
295
+ assert utils.is_numerical(other)
296
+ return Function(
297
+ is_basis=False,
298
+ reuse_gradient=self.reuse_gradient,
299
+ composition=ScaledFunc(scale=1 / other, base_func=self),
300
+ tags=[f"1/{other:.4g}*{self.tag}"],
301
+ )
302
+
303
+ def __hash__(self):
304
+ return hash(self.uid)
305
+
306
+ def __eq__(self, other):
307
+ if not isinstance(other, Function):
308
+ return NotImplemented
309
+ return self.uid == other.uid
310
+
311
+
312
+ class SmoothConvexFunction(Function):
313
+ def __init__(self, L, is_basis=True, composition=None, reuse_gradient=True):
314
+ super().__init__(
315
+ is_basis=is_basis, composition=composition, reuse_gradient=reuse_gradient
316
+ )
317
+ self.L = L
318
+
319
+ def smooth_convex_interpolability_constraints(self, triplet_i, triplet_j):
320
+ point_i = triplet_i.point
321
+ function_value_i = triplet_i.function_value
322
+ grad_i = triplet_i.gradient
323
+
324
+ point_j = triplet_j.point
325
+ function_value_j = triplet_j.function_value
326
+ grad_j = triplet_j.gradient
327
+
328
+ func_diff = function_value_j - function_value_i
329
+ cross_term = grad_j * (point_i - point_j)
330
+ quad_term = 1 / (2 * self.L) * (grad_i - grad_j) ** 2
331
+
332
+ return (func_diff + cross_term + quad_term).le(
333
+ 0, name=f"{self.tag}:{point_i.tag},{point_j.tag}"
334
+ )
335
+
336
+ def get_interpolation_constraints(self, pep_context: pc.PEPContext | None = None):
337
+ interpolation_constraints = []
338
+ if pep_context is None:
339
+ pep_context = pc.get_current_context()
340
+ if pep_context is None:
341
+ raise RuntimeError("Did you forget to create a context?")
342
+ for i in pep_context.triplets[self]:
343
+ for j in pep_context.triplets[self]:
344
+ if i == j:
345
+ continue
346
+ interpolation_constraints.append(
347
+ self.smooth_convex_interpolability_constraints(i, j)
348
+ )
349
+ return interpolation_constraints
@@ -0,0 +1,134 @@
1
+ # Copyright: 2025 The PEPFlow Developers
2
+ #
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ import numpy as np
21
+
22
+ from pepflow import expression_manager as exm
23
+ from pepflow import function as fc
24
+ from pepflow import pep as pep
25
+
26
+
27
+ def test_function_repr():
28
+ pep_builder = pep.PEPBuilder()
29
+ with pep_builder.make_context("test"):
30
+ f = fc.Function(is_basis=True, reuse_gradient=False)
31
+ print(f) # it should be fine without tag
32
+ f.add_tag("f")
33
+ assert str(f) == "f"
34
+
35
+
36
+ def test_stationary_point():
37
+ pep_builder = pep.PEPBuilder()
38
+ with pep_builder.make_context("test") as ctx:
39
+ f = fc.Function(is_basis=True, reuse_gradient=False, tags=["f"])
40
+ f.add_stationary_point("x_star")
41
+
42
+ assert len(ctx.triplets) == 1
43
+ assert len(ctx.triplets[f]) == 1
44
+
45
+ f_triplet = ctx.triplets[f][0]
46
+ assert f_triplet.name == "x_star_f(x_star)_gradient_f(x_star)"
47
+ assert f_triplet.gradient.tag == "gradient_f(x_star)"
48
+ assert f_triplet.function_value.tag == "f(x_star)"
49
+
50
+ em = exm.ExpressionManager(ctx)
51
+ np.testing.assert_allclose(
52
+ em.eval_point(f_triplet.gradient).vector, np.array([0])
53
+ )
54
+ np.testing.assert_allclose(em.eval_point(f_triplet.point).vector, np.array([1]))
55
+
56
+
57
+ def test_stationary_point_scaled():
58
+ pep_builder = pep.PEPBuilder()
59
+ with pep_builder.make_context("test") as ctx:
60
+ f = fc.Function(is_basis=True, reuse_gradient=False, tags=["f"])
61
+ g = 5 * f
62
+ g.add_stationary_point("x_star")
63
+
64
+ assert len(ctx.triplets) == 1
65
+ assert len(ctx.triplets[f]) == 1
66
+
67
+ f_triplet = ctx.triplets[f][0]
68
+ assert f_triplet.name == "x_star_f(x_star)_gradient_f(x_star)"
69
+ assert f_triplet.gradient.tag == "gradient_f(x_star)"
70
+ assert f_triplet.function_value.tag == "f(x_star)"
71
+
72
+ em = exm.ExpressionManager(ctx)
73
+ np.testing.assert_allclose(
74
+ em.eval_point(f_triplet.gradient).vector, np.array([0])
75
+ )
76
+ np.testing.assert_allclose(em.eval_point(f_triplet.point).vector, np.array([1]))
77
+
78
+
79
+ def test_stationary_point_additive():
80
+ pep_builder = pep.PEPBuilder()
81
+ with pep_builder.make_context("test") as ctx:
82
+ f = fc.Function(is_basis=True, reuse_gradient=False)
83
+ f.add_tag("f")
84
+ g = fc.Function(is_basis=True, reuse_gradient=False)
85
+ g.add_tag("g")
86
+ h = f + g
87
+ h.add_tag("h")
88
+
89
+ h.add_stationary_point("x_star")
90
+ assert len(ctx.triplets) == 2
91
+ assert len(ctx.triplets[f]) == 1
92
+ assert len(ctx.triplets[g]) == 1
93
+
94
+ f_triplet = ctx.triplets[f][0]
95
+ g_triplet = ctx.triplets[g][0]
96
+ assert f_triplet.name == "x_star_f(x_star)_gradient_f(x_star)"
97
+ assert g_triplet.name == "x_star_g(x_star)_gradient_g(x_star)"
98
+
99
+ em = exm.ExpressionManager(ctx)
100
+ np.testing.assert_allclose(
101
+ em.eval_point(f_triplet.gradient).vector, np.array([0, 1])
102
+ )
103
+ np.testing.assert_allclose(
104
+ em.eval_point(g_triplet.gradient).vector, np.array([0, -1])
105
+ )
106
+
107
+
108
+ def test_stationary_point_linear_combination():
109
+ pep_builder = pep.PEPBuilder()
110
+ with pep_builder.make_context("test") as ctx:
111
+ f = fc.Function(is_basis=True, reuse_gradient=False)
112
+ f.add_tag("f")
113
+ g = fc.Function(is_basis=True, reuse_gradient=False)
114
+ g.add_tag("g")
115
+ h = 3 * f + 2 * g
116
+ h.add_tag("h")
117
+
118
+ h.add_stationary_point("x_star")
119
+ assert len(ctx.triplets) == 2
120
+ assert len(ctx.triplets[f]) == 1
121
+ assert len(ctx.triplets[g]) == 1
122
+
123
+ f_triplet = ctx.triplets[f][0]
124
+ g_triplet = ctx.triplets[g][0]
125
+ assert f_triplet.name == "x_star_f(x_star)_gradient_f(x_star)"
126
+ assert g_triplet.name == "x_star_g(x_star)_gradient_g(x_star)"
127
+
128
+ em = exm.ExpressionManager(ctx)
129
+ np.testing.assert_allclose(
130
+ em.eval_point(f_triplet.gradient).vector, np.array([0, 1])
131
+ )
132
+ np.testing.assert_allclose(
133
+ em.eval_point(g_triplet.gradient).vector, np.array([0, -1.5])
134
+ )