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/__init__.py
CHANGED
@@ -21,6 +21,7 @@
|
|
21
21
|
from .constants import PSD_CONSTRAINT as PSD_CONSTRAINT
|
22
22
|
from .constraint import Constraint as Constraint
|
23
23
|
from .expression_manager import ExpressionManager as ExpressionManager
|
24
|
+
from .expression_manager import represent_matrix_by_basis as represent_matrix_by_basis
|
24
25
|
|
25
26
|
# interactive_constraint
|
26
27
|
from .interactive_constraint import launch as launch
|
@@ -28,6 +29,7 @@ from .interactive_constraint import launch as launch
|
|
28
29
|
# pep
|
29
30
|
from .pep import PEPBuilder as PEPBuilder
|
30
31
|
from .pep import PEPResult as PEPResult
|
32
|
+
from .pep import DualPEPResult as DualPEPResult
|
31
33
|
from .pep_context import PEPContext as PEPContext
|
32
34
|
from .pep_context import get_current_context as get_current_context
|
33
35
|
from .pep_context import set_current_context as set_current_context
|
@@ -35,6 +37,7 @@ from .pep_context import set_current_context as set_current_context
|
|
35
37
|
# Function, Point, Scalar
|
36
38
|
from .function import Function as Function
|
37
39
|
from .function import SmoothConvexFunction as SmoothConvexFunction
|
40
|
+
from .function import ConvexFunction as ConvexFunction
|
38
41
|
from .function import Triplet as Triplet
|
39
42
|
from .point import EvaluatedPoint as EvaluatedPoint
|
40
43
|
from .point import Point as Point
|
@@ -42,8 +45,10 @@ from .scalar import EvaluatedScalar as EvaluatedScalar
|
|
42
45
|
from .scalar import Scalar as Scalar
|
43
46
|
|
44
47
|
# Solver
|
45
|
-
from .solver import
|
48
|
+
from .solver import CVXPrimalSolver as CVXPrimalSolver
|
49
|
+
from .solver import CVXDualSolver as CVXDualSolver
|
46
50
|
from .solver import DualVariableManager as DualVariableManager
|
51
|
+
from .solver import PrimalVariableManager as PrimalVariableManager
|
47
52
|
|
48
53
|
# Others
|
49
54
|
from .utils import SOP as SOP
|
pepflow/constraint.py
CHANGED
@@ -31,8 +31,65 @@ from pepflow import utils
|
|
31
31
|
|
32
32
|
@attrs.frozen
|
33
33
|
class Constraint:
|
34
|
-
"""
|
34
|
+
"""A :class:`Constraint` object that represents inequalities and
|
35
|
+
equalities of :class:`Scalar` objects.
|
36
|
+
|
37
|
+
Denote an arbitrary :class:`Scalar` object as `x`. Constraints represent:
|
38
|
+
`x <= 0`, `x >= 0`, and `x = 0`.
|
39
|
+
|
40
|
+
Attributes:
|
41
|
+
scalar (:class:`Scalar`): The :class:`Scalar` object involved in
|
42
|
+
the inequality or equality.
|
43
|
+
comparator (:class:`Comparator`): :class:`Comparator` is an enumeration
|
44
|
+
that can be either `GT`, `LT`, or `EQ`. They represent `>=`, `<=`,
|
45
|
+
and `=` respectively.
|
46
|
+
name (str): The unique name of the :class:`Comparator` object.
|
47
|
+
associated_dual_var_constraints (list[tuple[utils.Comparator, float]]):
|
48
|
+
A list of all the constraints imposed on the associated dual
|
49
|
+
variable of this :class:`Constraint` object.
|
50
|
+
"""
|
35
51
|
|
36
52
|
scalar: Scalar | float
|
37
53
|
comparator: utils.Comparator
|
38
54
|
name: str
|
55
|
+
|
56
|
+
# Used to represent the constraint on primal variable in dual PEP.
|
57
|
+
associated_dual_var_constraints: list[tuple[utils.Comparator, float]] = attrs.field(
|
58
|
+
factory=list
|
59
|
+
)
|
60
|
+
|
61
|
+
def dual_lt(self, val: float) -> None:
|
62
|
+
"""
|
63
|
+
Denote the associated dual variable of this constraint as `lambd`.
|
64
|
+
This generates a relation of the form `lambd <= val`.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
val (float): The other object in the relation.
|
68
|
+
"""
|
69
|
+
if not utils.is_numerical(val):
|
70
|
+
raise ValueError(f"The input {val=} must be a numerical value")
|
71
|
+
self.associated_dual_var_constraints.append((utils.Comparator.LT, val))
|
72
|
+
|
73
|
+
def dual_gt(self, val: float) -> None:
|
74
|
+
"""
|
75
|
+
Denote the associated dual variable of this constraint as `lambd`.
|
76
|
+
This generates a relation of the form `lambd >= val`.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
val (float): The other object in the relation.
|
80
|
+
"""
|
81
|
+
if not utils.is_numerical(val):
|
82
|
+
raise ValueError(f"The input {val=} must be a numerical value")
|
83
|
+
self.associated_dual_var_constraints.append((utils.Comparator.GT, val))
|
84
|
+
|
85
|
+
def dual_eq(self, val: float) -> None:
|
86
|
+
"""
|
87
|
+
Denote the associated dual variable of this constraint as `lambd`.
|
88
|
+
This generates a relation of the form `lambd = val`.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
val (float): The other object in the relation.
|
92
|
+
"""
|
93
|
+
if not utils.is_numerical(val):
|
94
|
+
raise ValueError(f"The input {val=} must be a numerical value")
|
95
|
+
self.associated_dual_var_constraint.append((utils.Comparator.EQ, val))
|
@@ -0,0 +1,71 @@
|
|
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
|
+
|
21
|
+
from typing import Iterator
|
22
|
+
|
23
|
+
import numpy as np
|
24
|
+
import pytest
|
25
|
+
|
26
|
+
from pepflow import expression_manager as exm
|
27
|
+
from pepflow import pep as pep
|
28
|
+
from pepflow import pep_context as pc
|
29
|
+
from pepflow import scalar, utils
|
30
|
+
|
31
|
+
|
32
|
+
@pytest.fixture
|
33
|
+
def pep_context() -> Iterator[pc.PEPContext]:
|
34
|
+
"""Prepare the pep context and reset the context to None at the end."""
|
35
|
+
ctx = pc.PEPContext("test").set_as_current()
|
36
|
+
yield ctx
|
37
|
+
pc.set_current_context(None)
|
38
|
+
|
39
|
+
|
40
|
+
def test_constraint(pep_context: pc.PEPContext):
|
41
|
+
s1 = scalar.Scalar(is_basis=True, tags=["s1"])
|
42
|
+
s2 = scalar.Scalar(is_basis=True, tags=["s2"])
|
43
|
+
s3 = 2 * s1 + s2 / 4 + 5
|
44
|
+
|
45
|
+
c1 = s3.le(5, name="c1")
|
46
|
+
c2 = s3.lt(5, name="c2")
|
47
|
+
c3 = s3.ge(5, name="c3")
|
48
|
+
c4 = s3.gt(5, name="c4")
|
49
|
+
c5 = s3.eq(5, name="c5")
|
50
|
+
|
51
|
+
pm = exm.ExpressionManager(pep_context)
|
52
|
+
|
53
|
+
np.testing.assert_allclose(pm.eval_scalar(c1.scalar).vector, np.array([2, 0.25]))
|
54
|
+
np.testing.assert_allclose(pm.eval_scalar(c1.scalar).constant, 0)
|
55
|
+
assert c1.comparator == utils.Comparator.LT
|
56
|
+
|
57
|
+
np.testing.assert_allclose(pm.eval_scalar(c2.scalar).vector, np.array([2, 0.25]))
|
58
|
+
np.testing.assert_allclose(pm.eval_scalar(c2.scalar).constant, 0)
|
59
|
+
assert c2.comparator == utils.Comparator.LT
|
60
|
+
|
61
|
+
np.testing.assert_allclose(pm.eval_scalar(c3.scalar).vector, np.array([2, 0.25]))
|
62
|
+
np.testing.assert_allclose(pm.eval_scalar(c3.scalar).constant, 0)
|
63
|
+
assert c3.comparator == utils.Comparator.GT
|
64
|
+
|
65
|
+
np.testing.assert_allclose(pm.eval_scalar(c4.scalar).vector, np.array([2, 0.25]))
|
66
|
+
np.testing.assert_allclose(pm.eval_scalar(c4.scalar).constant, 0)
|
67
|
+
assert c4.comparator == utils.Comparator.GT
|
68
|
+
|
69
|
+
np.testing.assert_allclose(pm.eval_scalar(c5.scalar).vector, np.array([2, 0.25]))
|
70
|
+
np.testing.assert_allclose(pm.eval_scalar(c5.scalar).constant, 0)
|
71
|
+
assert c5.comparator == utils.Comparator.EQ
|
pepflow/e2e_test.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import math
|
2
2
|
|
3
|
-
from pepflow import function
|
3
|
+
from pepflow import function
|
4
|
+
from pepflow import parameter as pm
|
5
|
+
from pepflow import pep
|
4
6
|
from pepflow import pep_context as pc
|
5
7
|
|
6
8
|
|
@@ -10,8 +12,7 @@ def test_gd_e2e():
|
|
10
12
|
eta = 1
|
11
13
|
N = 9
|
12
14
|
|
13
|
-
f = pep_builder.declare_func(function.SmoothConvexFunction, L=1)
|
14
|
-
f.add_tag("f")
|
15
|
+
f = pep_builder.declare_func(function.SmoothConvexFunction, "f", L=1)
|
15
16
|
x = pep_builder.set_init_point("x_0")
|
16
17
|
x_star = f.add_stationary_point("x_star")
|
17
18
|
pep_builder.set_initial_constraint(
|
@@ -29,6 +30,84 @@ def test_gd_e2e():
|
|
29
30
|
pep_builder.set_performance_metric(
|
30
31
|
f.function_value(p) - f.function_value(x_star)
|
31
32
|
)
|
32
|
-
result = pep_builder.
|
33
|
+
result = pep_builder.solve_primal()
|
33
34
|
expected_opt_value = 1 / (4 * i + 2)
|
34
35
|
assert math.isclose(result.primal_opt_value, expected_opt_value, rel_tol=1e-3)
|
36
|
+
|
37
|
+
dual_result = pep_builder.solve_dual()
|
38
|
+
assert math.isclose(
|
39
|
+
dual_result.dual_opt_value, expected_opt_value, rel_tol=1e-3
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
def test_gd_diff_stepsize_e2e():
|
44
|
+
pc.PEPContext("gd").set_as_current()
|
45
|
+
pep_builder = pep.PEPBuilder()
|
46
|
+
eta = 1 / pm.Parameter(name="L")
|
47
|
+
N = 4
|
48
|
+
|
49
|
+
f = pep_builder.declare_func(
|
50
|
+
function.SmoothConvexFunction, "f", L=pm.Parameter(name="L")
|
51
|
+
)
|
52
|
+
x = pep_builder.set_init_point("x_0")
|
53
|
+
x_star = f.add_stationary_point("x_star")
|
54
|
+
pep_builder.set_initial_constraint(
|
55
|
+
((x - x_star) ** 2).le(1, name="initial_condition")
|
56
|
+
)
|
57
|
+
|
58
|
+
# We first build the algorithm with the largest number of iterations.
|
59
|
+
for i in range(N):
|
60
|
+
x = x - eta * f.gradient(x)
|
61
|
+
x.add_tag(f"x_{i + 1}")
|
62
|
+
pep_builder.set_performance_metric(f(x) - f(x_star))
|
63
|
+
|
64
|
+
for l_val in [1, 4, 0.25]:
|
65
|
+
result = pep_builder.solve_primal(resolve_parameters={"L": l_val})
|
66
|
+
expected_opt_value = l_val / (4 * N + 2)
|
67
|
+
assert math.isclose(result.primal_opt_value, expected_opt_value, rel_tol=1e-3)
|
68
|
+
|
69
|
+
dual_result = pep_builder.solve_dual(resolve_parameters={"L": l_val})
|
70
|
+
assert math.isclose(
|
71
|
+
dual_result.dual_opt_value, expected_opt_value, rel_tol=1e-3
|
72
|
+
)
|
73
|
+
|
74
|
+
|
75
|
+
def test_pgm_e2e():
|
76
|
+
ctx = pc.PEPContext("pgm").set_as_current()
|
77
|
+
pep_builder = pep.PEPBuilder()
|
78
|
+
eta = 1
|
79
|
+
N = 1
|
80
|
+
|
81
|
+
f = pep_builder.declare_func(function.SmoothConvexFunction, "f", L=1)
|
82
|
+
g = pep_builder.declare_func(function.ConvexFunction, "g")
|
83
|
+
|
84
|
+
h = f + g
|
85
|
+
|
86
|
+
x = pep_builder.set_init_point("x_0")
|
87
|
+
x_star = h.add_stationary_point("x_star")
|
88
|
+
pep_builder.set_initial_constraint(
|
89
|
+
((x - x_star) ** 2).le(1, name="initial_condition")
|
90
|
+
)
|
91
|
+
|
92
|
+
# We first build the algorithm with the largest number of iterations.
|
93
|
+
for i in range(N):
|
94
|
+
y = x - eta * f.gradient(x)
|
95
|
+
y.add_tag(f"y_{i + 1}")
|
96
|
+
x = g.proximal_step(y, eta)
|
97
|
+
x.add_tag(f"x_{i + 1}")
|
98
|
+
|
99
|
+
# To achieve the sweep, we can just update the performance_metric.
|
100
|
+
for i in range(1, N + 1):
|
101
|
+
p = ctx.get_by_tag(f"x_{i}")
|
102
|
+
pep_builder.set_performance_metric(
|
103
|
+
h.function_value(p) - h.function_value(x_star)
|
104
|
+
)
|
105
|
+
|
106
|
+
result = pep_builder.solve_primal()
|
107
|
+
expected_opt_value = 1 / (4 * i)
|
108
|
+
assert math.isclose(result.primal_opt_value, expected_opt_value, rel_tol=1e-3)
|
109
|
+
|
110
|
+
dual_result = pep_builder.solve_dual()
|
111
|
+
assert math.isclose(
|
112
|
+
dual_result.dual_opt_value, expected_opt_value, rel_tol=1e-3
|
113
|
+
)
|