pepflow 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl
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.
- pepflow/__init__.py +6 -1
- pepflow/constraint.py +58 -1
- pepflow/constraint_test.py +71 -0
- pepflow/e2e_test.py +83 -4
- pepflow/expression_manager.py +329 -44
- pepflow/expression_manager_test.py +150 -0
- pepflow/function.py +294 -52
- pepflow/function_test.py +180 -114
- pepflow/interactive_constraint.py +165 -75
- pepflow/parameter.py +187 -0
- pepflow/parameter_test.py +128 -0
- pepflow/pep.py +263 -16
- pepflow/pep_context.py +122 -6
- pepflow/pep_context_test.py +25 -0
- pepflow/pep_test.py +8 -0
- pepflow/point.py +155 -49
- pepflow/point_test.py +40 -188
- pepflow/scalar.py +260 -47
- pepflow/scalar_test.py +102 -130
- pepflow/solver.py +170 -3
- pepflow/solver_test.py +50 -2
- pepflow/utils.py +39 -7
- {pepflow-0.1.4.dist-info → pepflow-0.1.5.dist-info}/METADATA +24 -5
- pepflow-0.1.5.dist-info/RECORD +28 -0
- pepflow-0.1.4.dist-info/RECORD +0 -24
- {pepflow-0.1.4.dist-info → pepflow-0.1.5.dist-info}/WHEEL +0 -0
- {pepflow-0.1.4.dist-info → pepflow-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {pepflow-0.1.4.dist-info → pepflow-0.1.5.dist-info}/top_level.txt +0 -0
pepflow/function.py
CHANGED
@@ -19,7 +19,9 @@
|
|
19
19
|
|
20
20
|
from __future__ import annotations
|
21
21
|
|
22
|
+
import numbers
|
22
23
|
import uuid
|
24
|
+
from typing import TYPE_CHECKING
|
23
25
|
|
24
26
|
import attrs
|
25
27
|
|
@@ -28,9 +30,25 @@ from pepflow import point as pt
|
|
28
30
|
from pepflow import scalar as sc
|
29
31
|
from pepflow import utils
|
30
32
|
|
33
|
+
if TYPE_CHECKING:
|
34
|
+
from pepflow.constraint import Constraint
|
35
|
+
|
31
36
|
|
32
37
|
@attrs.frozen
|
33
38
|
class Triplet:
|
39
|
+
"""
|
40
|
+
A data class that represents, for some given function :math:`f`,
|
41
|
+
the tuple :math:`\\{x, f(x), \\nabla f(x)\\}`. We can also consider
|
42
|
+
a subgradient :math:`\\widetilde{\\nabla} f(x)` instead of the gradient.
|
43
|
+
|
44
|
+
Attributes:
|
45
|
+
point (:class:`Point`): The point :math:`x`.
|
46
|
+
function_value (:class:`Scalar`): The function value :math:`f(x)`.
|
47
|
+
gradient (:class:`Point`): The gradient :math:`\\nabla f(x)` or
|
48
|
+
a subgradient :math:`\\widetilde{\\nabla} f(x)`.
|
49
|
+
name (str): The unique name of the :class:`Triplet` object.
|
50
|
+
"""
|
51
|
+
|
34
52
|
point: pt.Point
|
35
53
|
function_value: sc.Scalar
|
36
54
|
gradient: pt.Point
|
@@ -48,7 +66,7 @@ class AddedFunc:
|
|
48
66
|
|
49
67
|
@attrs.frozen
|
50
68
|
class ScaledFunc:
|
51
|
-
"""Represents
|
69
|
+
"""Represents scalar * base_func."""
|
52
70
|
|
53
71
|
scale: float
|
54
72
|
base_func: Function
|
@@ -56,8 +74,26 @@ class ScaledFunc:
|
|
56
74
|
|
57
75
|
@attrs.mutable
|
58
76
|
class Function:
|
77
|
+
"""A :class:`Function` object represents a function.
|
78
|
+
|
79
|
+
:class:`Function` objects can be constructed as linear combinations
|
80
|
+
of other :class:`Function` objects. Let `a` and `b` be some numeric
|
81
|
+
data type. Let `f` and `g` be :class:`Function` objects. Then, we
|
82
|
+
can form a new :class:`Function` object: `a*f+b*g`.
|
83
|
+
|
84
|
+
A :class:`Function` object should never be explicitly constructed. Only
|
85
|
+
children of :class:`Function` such as :class:`ConvexFunction` or
|
86
|
+
:class:`SmoothConvexFunction` should be constructed. See their respective
|
87
|
+
documentation to see how.
|
88
|
+
|
89
|
+
Attributes:
|
90
|
+
is_basis (bool): `True` if this function is not formed through a linear
|
91
|
+
combination of other functions. `False` otherwise.
|
92
|
+
tags (list[str]): A list that contains tags that can be used to
|
93
|
+
identify the :class:`Function` object. Tags should be unique.
|
94
|
+
"""
|
95
|
+
|
59
96
|
is_basis: bool
|
60
|
-
reuse_gradient: bool
|
61
97
|
|
62
98
|
composition: AddedFunc | ScaledFunc | None = None
|
63
99
|
|
@@ -75,11 +111,21 @@ class Function:
|
|
75
111
|
|
76
112
|
@property
|
77
113
|
def tag(self):
|
114
|
+
"""Returns the most recently added tag.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
str: The most recently added tag of this :class:`Function` object.
|
118
|
+
"""
|
78
119
|
if len(self.tags) == 0:
|
79
120
|
raise ValueError("Function should have a name.")
|
80
121
|
return self.tags[-1]
|
81
122
|
|
82
123
|
def add_tag(self, tag: str) -> None:
|
124
|
+
"""Add a new tag for this :class:`Function` object.
|
125
|
+
|
126
|
+
Args:
|
127
|
+
tag (str): The new tag to be added to the `tags` list.
|
128
|
+
"""
|
83
129
|
self.tags.append(tag)
|
84
130
|
|
85
131
|
def __repr__(self):
|
@@ -89,7 +135,7 @@ class Function:
|
|
89
135
|
|
90
136
|
def _repr_latex_(self):
|
91
137
|
s = repr(self)
|
92
|
-
return rf"
|
138
|
+
return rf"$\displaystyle {s}$"
|
93
139
|
|
94
140
|
def get_interpolation_constraints(self):
|
95
141
|
raise NotImplementedError(
|
@@ -157,12 +203,32 @@ class Function:
|
|
157
203
|
return triplet
|
158
204
|
|
159
205
|
def add_stationary_point(self, name: str) -> pt.Point:
|
206
|
+
"""
|
207
|
+
Return a stationary point for this :class:`Function` object.
|
208
|
+
A :class:`Function` object can only have one stationary point.
|
209
|
+
|
210
|
+
Args:
|
211
|
+
name (str): The tag for the :class:`Point` object which
|
212
|
+
will serve as the stationary point.
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
:class:`Point`: The stationary point for this :class:`Function`
|
216
|
+
object.
|
217
|
+
"""
|
160
218
|
# assert we can only add one stationary point?
|
219
|
+
pep_context = pc.get_current_context()
|
220
|
+
if pep_context is None:
|
221
|
+
raise RuntimeError("Did you forget to create a context?")
|
222
|
+
if len(pep_context.stationary_triplets[self]) > 0:
|
223
|
+
raise ValueError(
|
224
|
+
"You are trying to add a stationary point to a function that already has a stationary point."
|
225
|
+
)
|
161
226
|
point = pt.Point(is_basis=True)
|
162
227
|
point.add_tag(name)
|
163
|
-
desired_grad =
|
228
|
+
desired_grad = pt.Point.zero() # zero point
|
164
229
|
desired_grad.add_tag(f"gradient_{self.tag}({name})")
|
165
|
-
self.add_point_with_grad_restriction(point, desired_grad)
|
230
|
+
triplet = self.add_point_with_grad_restriction(point, desired_grad)
|
231
|
+
pep_context.add_stationary_triplet(self, triplet)
|
166
232
|
return point
|
167
233
|
|
168
234
|
# The following the old gradient(opt) = 0 constraint style.
|
@@ -183,43 +249,26 @@ class Function:
|
|
183
249
|
if pep_context is None:
|
184
250
|
raise RuntimeError("Did you forget to create a context?")
|
185
251
|
|
252
|
+
if not isinstance(point, pt.Point):
|
253
|
+
raise ValueError("The Function can only take point as input.")
|
254
|
+
|
186
255
|
if self.is_basis:
|
187
|
-
generate_new_basis = True
|
188
|
-
instances_of_point = 0
|
189
256
|
for triplet in pep_context.triplets[self]:
|
190
257
|
if triplet.point.uid == point.uid:
|
191
|
-
|
192
|
-
generate_new_basis = False
|
193
|
-
previous_triplet = triplet
|
258
|
+
return triplet
|
194
259
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
gradient.add_tag(f"gradient_{self.tag}({point.tag})")
|
260
|
+
function_value = sc.Scalar(is_basis=True)
|
261
|
+
function_value.add_tag(f"{self.tag}({point.tag})")
|
262
|
+
gradient = pt.Point(is_basis=True)
|
263
|
+
gradient.add_tag(f"gradient_{self.tag}({point.tag})")
|
200
264
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
elif not generate_new_basis and self.reuse_gradient:
|
209
|
-
function_value = previous_triplet.function_value
|
210
|
-
gradient = previous_triplet.gradient
|
211
|
-
elif not generate_new_basis and not self.reuse_gradient:
|
212
|
-
function_value = previous_triplet.function_value
|
213
|
-
gradient = pt.Point(is_basis=True)
|
214
|
-
gradient.add_tag(f"gradient_{self.tag}({point.tag})")
|
215
|
-
|
216
|
-
new_triplet = Triplet(
|
217
|
-
point,
|
218
|
-
previous_triplet.function_value,
|
219
|
-
gradient,
|
220
|
-
name=f"{point.tag}_{function_value.tag}_{gradient.tag}_{instances_of_point}",
|
221
|
-
)
|
222
|
-
self.add_triplet_to_func(new_triplet)
|
265
|
+
new_triplet = Triplet(
|
266
|
+
point,
|
267
|
+
function_value,
|
268
|
+
gradient,
|
269
|
+
name=f"{point.tag}_{function_value.tag}_{gradient.tag}",
|
270
|
+
)
|
271
|
+
self.add_triplet_to_func(new_triplet)
|
223
272
|
else:
|
224
273
|
if isinstance(self.composition, AddedFunc):
|
225
274
|
left_triplet = self.composition.left_func.generate_triplet(point)
|
@@ -240,14 +289,50 @@ class Function:
|
|
240
289
|
return Triplet(point, function_value, gradient, name=None)
|
241
290
|
|
242
291
|
def gradient(self, point: pt.Point) -> pt.Point:
|
292
|
+
"""
|
293
|
+
Returns a :class:`Point` object that is the gradient of the
|
294
|
+
:class:`Function` at the given :class:`Point`.
|
295
|
+
|
296
|
+
Args:
|
297
|
+
point (:class:`Point`): Any :class:`Point`.
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
:class:`Point`: The gradient of the :class:`Function` at the
|
301
|
+
given :class:`Point`.
|
302
|
+
"""
|
243
303
|
triplet = self.generate_triplet(point)
|
244
304
|
return triplet.gradient
|
245
305
|
|
246
306
|
def subgradient(self, point: pt.Point) -> pt.Point:
|
307
|
+
"""
|
308
|
+
Returns a :class:`Point` object that is the subgradient of the
|
309
|
+
:class:`Function` at the given :class:`Point`.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
point (:class:`Point`): Any :class:`Point`.
|
313
|
+
|
314
|
+
Returns:
|
315
|
+
:class:`Point`: The subgradient of the :class:`Function` at the
|
316
|
+
given :class:`Point`.
|
317
|
+
|
318
|
+
Note:
|
319
|
+
The method `gradient` is exactly the same.
|
320
|
+
"""
|
247
321
|
triplet = self.generate_triplet(point)
|
248
322
|
return triplet.gradient
|
249
323
|
|
250
324
|
def function_value(self, point: pt.Point) -> sc.Scalar:
|
325
|
+
"""
|
326
|
+
Returns a :class:`Scalar` object that is the function value of the
|
327
|
+
:class:`Function` at the given :class:`Point`.
|
328
|
+
|
329
|
+
Args:
|
330
|
+
point (:class:`Point`): Any :class:`Point`.
|
331
|
+
|
332
|
+
Returns:
|
333
|
+
:class:`Point`: The function value of the :class:`Function` at the
|
334
|
+
given :class:`Point`.
|
335
|
+
"""
|
251
336
|
triplet = self.generate_triplet(point)
|
252
337
|
return triplet.function_value
|
253
338
|
|
@@ -255,46 +340,46 @@ class Function:
|
|
255
340
|
return self.function_value(point)
|
256
341
|
|
257
342
|
def __add__(self, other):
|
258
|
-
|
343
|
+
if not isinstance(other, Function):
|
344
|
+
return NotImplemented
|
259
345
|
return Function(
|
260
346
|
is_basis=False,
|
261
|
-
reuse_gradient=self.reuse_gradient and other.reuse_gradient,
|
262
347
|
composition=AddedFunc(self, other),
|
263
348
|
tags=[f"{self.tag}+{other.tag}"],
|
264
349
|
)
|
265
350
|
|
266
351
|
def __sub__(self, other):
|
267
|
-
|
352
|
+
if not isinstance(other, Function):
|
353
|
+
return NotImplemented
|
268
354
|
tag_other = other.tag
|
269
355
|
if isinstance(other.composition, AddedFunc):
|
270
356
|
tag_other = f"({other.tag})"
|
271
357
|
return Function(
|
272
358
|
is_basis=False,
|
273
|
-
reuse_gradient=self.reuse_gradient and other.reuse_gradient,
|
274
359
|
composition=AddedFunc(self, -other),
|
275
360
|
tags=[f"{self.tag}-{tag_other}"],
|
276
361
|
)
|
277
362
|
|
278
363
|
def __mul__(self, other):
|
279
|
-
|
364
|
+
if not utils.is_numerical(other):
|
365
|
+
return NotImplemented
|
280
366
|
tag_self = self.tag
|
281
367
|
if isinstance(self.composition, AddedFunc):
|
282
368
|
tag_self = f"({self.tag})"
|
283
369
|
return Function(
|
284
370
|
is_basis=False,
|
285
|
-
reuse_gradient=self.reuse_gradient,
|
286
371
|
composition=ScaledFunc(scale=other, base_func=self),
|
287
372
|
tags=[f"{other:.4g}*{tag_self}"],
|
288
373
|
)
|
289
374
|
|
290
375
|
def __rmul__(self, other):
|
291
|
-
|
376
|
+
if not utils.is_numerical(other):
|
377
|
+
return NotImplemented
|
292
378
|
tag_self = self.tag
|
293
379
|
if isinstance(self.composition, AddedFunc):
|
294
380
|
tag_self = f"({self.tag})"
|
295
381
|
return Function(
|
296
382
|
is_basis=False,
|
297
|
-
reuse_gradient=self.reuse_gradient,
|
298
383
|
composition=ScaledFunc(scale=other, base_func=self),
|
299
384
|
tags=[f"{other:.4g}*{tag_self}"],
|
300
385
|
)
|
@@ -305,19 +390,18 @@ class Function:
|
|
305
390
|
tag_self = f"({self.tag})"
|
306
391
|
return Function(
|
307
392
|
is_basis=False,
|
308
|
-
reuse_gradient=self.reuse_gradient,
|
309
393
|
composition=ScaledFunc(scale=-1, base_func=self),
|
310
394
|
tags=[f"-{tag_self}"],
|
311
395
|
)
|
312
396
|
|
313
397
|
def __truediv__(self, other):
|
314
|
-
|
398
|
+
if not utils.is_numerical(other):
|
399
|
+
return NotImplemented
|
315
400
|
tag_self = self.tag
|
316
401
|
if isinstance(self.composition, AddedFunc):
|
317
402
|
tag_self = f"({self.tag})"
|
318
403
|
return Function(
|
319
404
|
is_basis=False,
|
320
|
-
reuse_gradient=self.reuse_gradient,
|
321
405
|
composition=ScaledFunc(scale=1 / other, base_func=self),
|
322
406
|
tags=[f"1/{other:.4g}*{tag_self}"],
|
323
407
|
)
|
@@ -331,10 +415,159 @@ class Function:
|
|
331
415
|
return self.uid == other.uid
|
332
416
|
|
333
417
|
|
418
|
+
class ConvexFunction(Function):
|
419
|
+
"""
|
420
|
+
The :class:`ConvexFunction` class is a child of :class:`Function.`
|
421
|
+
The :class:`ConvexFunction` class represents a closed, convex, and
|
422
|
+
proper (CCP) function, i.e., a convex function whose epigraph is a
|
423
|
+
non-empty closed set.
|
424
|
+
|
425
|
+
A CCP function typically has no parameters. We can instantiate a
|
426
|
+
:class:`ConvexFunction` object as follows:
|
427
|
+
|
428
|
+
Example:
|
429
|
+
>>> import pepflow as pf
|
430
|
+
>>> ctx = pf.PEPContext("example").set_as_current()
|
431
|
+
>>> pep_builder = pf.PEPBuilder()
|
432
|
+
>>> g = pep_builder.declare_func(pf.ConvexFunction, "g")
|
433
|
+
"""
|
434
|
+
|
435
|
+
def __init__(
|
436
|
+
self,
|
437
|
+
is_basis=True,
|
438
|
+
composition=None,
|
439
|
+
):
|
440
|
+
super().__init__(
|
441
|
+
is_basis=is_basis,
|
442
|
+
composition=composition,
|
443
|
+
)
|
444
|
+
|
445
|
+
def convex_interpolability_constraints(
|
446
|
+
self, triplet_i: Triplet, triplet_j: Triplet
|
447
|
+
) -> Constraint:
|
448
|
+
point_i = triplet_i.point
|
449
|
+
function_value_i = triplet_i.function_value
|
450
|
+
|
451
|
+
point_j = triplet_j.point
|
452
|
+
function_value_j = triplet_j.function_value
|
453
|
+
grad_j = triplet_j.gradient
|
454
|
+
|
455
|
+
func_diff = function_value_j - function_value_i
|
456
|
+
cross_term = grad_j * (point_i - point_j)
|
457
|
+
|
458
|
+
return (func_diff + cross_term).le(
|
459
|
+
0, name=f"{self.tag}:{point_i.tag},{point_j.tag}"
|
460
|
+
)
|
461
|
+
|
462
|
+
def get_interpolation_constraints(
|
463
|
+
self, pep_context: pc.PEPContext | None = None
|
464
|
+
) -> list[Constraint]:
|
465
|
+
interpolation_constraints = []
|
466
|
+
if pep_context is None:
|
467
|
+
pep_context = pc.get_current_context()
|
468
|
+
if pep_context is None:
|
469
|
+
raise RuntimeError("Did you forget to create a context?")
|
470
|
+
for i in pep_context.triplets[self]:
|
471
|
+
for j in pep_context.triplets[self]:
|
472
|
+
if i == j:
|
473
|
+
continue
|
474
|
+
interpolation_constraints.append(
|
475
|
+
self.convex_interpolability_constraints(i, j)
|
476
|
+
)
|
477
|
+
return interpolation_constraints
|
478
|
+
|
479
|
+
def interpolate_ineq(
|
480
|
+
self, p1_tag: str, p2_tag: str, pep_context: pc.PEPContext | None = None
|
481
|
+
) -> sc.Scalar:
|
482
|
+
"""Generate the interpolation inequality :class:`Scalar` by tags.
|
483
|
+
The interpolation inequality between two points :math:`p_1, p_2` for a
|
484
|
+
CCP function :math:`f` is
|
485
|
+
|
486
|
+
.. math:: f(p_2) - f(p_1) + \\langle \\nabla f(p_2), p_1 - p_2 \\rangle.
|
487
|
+
|
488
|
+
Args:
|
489
|
+
p1_tag (str): A tag of the :class:`Point` :math:`p_1`.
|
490
|
+
p2_tag (str): A tag of the :class:`Point` :math:`p_2`.
|
491
|
+
"""
|
492
|
+
if pep_context is None:
|
493
|
+
pep_context = pc.get_current_context()
|
494
|
+
if pep_context is None:
|
495
|
+
raise RuntimeError("Did you forget to specify a context?")
|
496
|
+
# TODO: we definitely need a more robust tag system
|
497
|
+
x1 = pep_context.get_by_tag(p1_tag)
|
498
|
+
x2 = pep_context.get_by_tag(p2_tag)
|
499
|
+
f1 = pep_context.get_by_tag(f"{self.tag}({p1_tag})")
|
500
|
+
f2 = pep_context.get_by_tag(f"{self.tag}({p2_tag})")
|
501
|
+
g2 = pep_context.get_by_tag(f"gradient_{self.tag}({p2_tag})")
|
502
|
+
return f2 - f1 + g2 * (x1 - x2)
|
503
|
+
|
504
|
+
def proximal_step(self, x_0: pt.Point, stepsize: numbers.Number) -> pt.Point:
|
505
|
+
""" Define the proximal operator as
|
506
|
+
|
507
|
+
.. math:: \\text{prox}_{\\gamma f}(x_0) := \\arg\\min_x \\left\\{ \\gamma f(x) + \\frac{1}{2} \\|x - x_0\\|^2 \\right\\}.
|
508
|
+
|
509
|
+
This function performs a proximal step with respect to some
|
510
|
+
:class:`Function` :math:`f` on the :class:`Point` :math:`x_0`
|
511
|
+
with stepsize :math:`\\gamma`:
|
512
|
+
|
513
|
+
.. math::
|
514
|
+
:nowrap:
|
515
|
+
|
516
|
+
\\begin{eqnarray}
|
517
|
+
x := \\text{prox}_{\\gamma f}(x_0) & := & \\arg\\min_x \\left\\{ \\gamma f(x) + \\frac{1}{2} \\|x - x_0\\|^2 \\right\\}, \\\\
|
518
|
+
& \\Updownarrow & \\\\
|
519
|
+
0 & = & \\gamma \\partial f(x) + x - x_0,\\\\
|
520
|
+
& \\Updownarrow & \\\\
|
521
|
+
x & = & x_0 - \\gamma \\widetilde{\\nabla} f(x) \\text{ where } \\widetilde{\\nabla} f(x)\\in\\partial f(x).
|
522
|
+
\\end{eqnarray}
|
523
|
+
|
524
|
+
Args:
|
525
|
+
x_0 (:class:`Point`): The initial point.
|
526
|
+
stepsize (int | float): The stepsize.
|
527
|
+
"""
|
528
|
+
gradient = pt.Point(is_basis=True)
|
529
|
+
gradient.add_tag(
|
530
|
+
f"gradient_{self.tag}(prox_{{{stepsize}*{self.tag}}}({x_0.tag}))"
|
531
|
+
)
|
532
|
+
function_value = sc.Scalar(is_basis=True)
|
533
|
+
function_value.add_tag(f"{self.tag}(prox_{{{stepsize}*{self.tag}}}({x_0.tag}))")
|
534
|
+
x = x_0 - stepsize * gradient
|
535
|
+
x.add_tag(f"prox_{{{stepsize}*{self.tag}}}({x_0.tag})")
|
536
|
+
new_triplet = Triplet(
|
537
|
+
x,
|
538
|
+
function_value,
|
539
|
+
gradient,
|
540
|
+
name=f"{x.tag}_{function_value.tag}_{gradient.tag}",
|
541
|
+
)
|
542
|
+
self.add_triplet_to_func(new_triplet)
|
543
|
+
return x
|
544
|
+
|
545
|
+
|
334
546
|
class SmoothConvexFunction(Function):
|
335
|
-
|
547
|
+
"""
|
548
|
+
The :class:`SmoothConvexFunction` class is a child of :class:`Function.`
|
549
|
+
The :class:`SmoothConvexFunction` class represents a smooth,
|
550
|
+
convex function.
|
551
|
+
|
552
|
+
A smooth, convex function has a smoothness parameter :math:`L`.
|
553
|
+
We can instantiate a :class:`SmoothConvexFunction` object as follows:
|
554
|
+
|
555
|
+
Example:
|
556
|
+
>>> import pepflow as pf
|
557
|
+
>>> ctx = pf.PEPContext("example").set_as_current()
|
558
|
+
>>> pep_builder = pf.PEPBuilder()
|
559
|
+
>>> f = pep_builder.declare_func(pf.SmoothConvexFunction, "f", L=1)
|
560
|
+
"""
|
561
|
+
|
562
|
+
def __init__(
|
563
|
+
self,
|
564
|
+
L,
|
565
|
+
is_basis=True,
|
566
|
+
composition=None,
|
567
|
+
):
|
336
568
|
super().__init__(
|
337
|
-
is_basis=is_basis,
|
569
|
+
is_basis=is_basis,
|
570
|
+
composition=composition,
|
338
571
|
)
|
339
572
|
self.L = L
|
340
573
|
|
@@ -372,8 +605,17 @@ class SmoothConvexFunction(Function):
|
|
372
605
|
|
373
606
|
def interpolate_ineq(
|
374
607
|
self, p1_tag: str, p2_tag: str, pep_context: pc.PEPContext | None = None
|
375
|
-
) ->
|
376
|
-
"""Generate the interpolation inequality
|
608
|
+
) -> sc.Scalar:
|
609
|
+
"""Generate the interpolation inequality :class:`Scalar` by tags.
|
610
|
+
The interpolation inequality between two points :math:`p_1, p_2` for a
|
611
|
+
smooth, convex function :math:`f` is
|
612
|
+
|
613
|
+
.. math:: f(p_2) - f(p_1) + \\langle \\nabla f(p_2), p_1 - p_2 \\rangle + \\tfrac{1}{2} \\lVert \\nabla f(p_1) - \\nabla f(p_2) \\rVert^2.
|
614
|
+
|
615
|
+
Args:
|
616
|
+
p1_tag (str): A tag of the :class:`Point` :math:`p_1`.
|
617
|
+
p2_tag (str): A tag of the :class:`Point` :math:`p_2`.
|
618
|
+
"""
|
377
619
|
if pep_context is None:
|
378
620
|
pep_context = pc.get_current_context()
|
379
621
|
if pep_context is None:
|