pepflow 0.1.4__tar.gz → 0.1.5__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 (45) hide show
  1. {pepflow-0.1.4 → pepflow-0.1.5}/PKG-INFO +24 -5
  2. {pepflow-0.1.4 → pepflow-0.1.5}/README.md +22 -3
  3. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow/__init__.py +6 -1
  4. pepflow-0.1.5/pepflow/constraint.py +95 -0
  5. pepflow-0.1.5/pepflow/constraint_test.py +71 -0
  6. pepflow-0.1.5/pepflow/e2e_test.py +113 -0
  7. pepflow-0.1.5/pepflow/expression_manager.py +417 -0
  8. pepflow-0.1.5/pepflow/expression_manager_test.py +150 -0
  9. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow/function.py +294 -52
  10. pepflow-0.1.5/pepflow/function_test.py +275 -0
  11. pepflow-0.1.5/pepflow/interactive_constraint.py +354 -0
  12. pepflow-0.1.5/pepflow/parameter.py +187 -0
  13. pepflow-0.1.5/pepflow/parameter_test.py +128 -0
  14. pepflow-0.1.5/pepflow/pep.py +420 -0
  15. pepflow-0.1.5/pepflow/pep_context.py +252 -0
  16. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow/pep_context_test.py +25 -0
  17. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow/pep_test.py +8 -0
  18. pepflow-0.1.5/pepflow/point.py +366 -0
  19. pepflow-0.1.5/pepflow/point_test.py +176 -0
  20. pepflow-0.1.5/pepflow/scalar.py +511 -0
  21. pepflow-0.1.5/pepflow/scalar_test.py +222 -0
  22. pepflow-0.1.5/pepflow/solver.py +290 -0
  23. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow/solver_test.py +50 -2
  24. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow/utils.py +39 -7
  25. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow.egg-info/PKG-INFO +24 -5
  26. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow.egg-info/SOURCES.txt +4 -0
  27. {pepflow-0.1.4 → pepflow-0.1.5}/pyproject.toml +11 -2
  28. pepflow-0.1.4/pepflow/constraint.py +0 -38
  29. pepflow-0.1.4/pepflow/e2e_test.py +0 -34
  30. pepflow-0.1.4/pepflow/expression_manager.py +0 -132
  31. pepflow-0.1.4/pepflow/function_test.py +0 -209
  32. pepflow-0.1.4/pepflow/interactive_constraint.py +0 -264
  33. pepflow-0.1.4/pepflow/pep.py +0 -173
  34. pepflow-0.1.4/pepflow/pep_context.py +0 -136
  35. pepflow-0.1.4/pepflow/point.py +0 -260
  36. pepflow-0.1.4/pepflow/point_test.py +0 -324
  37. pepflow-0.1.4/pepflow/scalar.py +0 -298
  38. pepflow-0.1.4/pepflow/scalar_test.py +0 -250
  39. pepflow-0.1.4/pepflow/solver.py +0 -123
  40. {pepflow-0.1.4 → pepflow-0.1.5}/LICENSE +0 -0
  41. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow/constants.py +0 -0
  42. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow.egg-info/dependency_links.txt +0 -0
  43. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow.egg-info/requires.txt +0 -0
  44. {pepflow-0.1.4 → pepflow-0.1.5}/pepflow.egg-info/top_level.txt +0 -0
  45. {pepflow-0.1.4 → pepflow-0.1.5}/setup.cfg +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pepflow
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: PEPFlow: A framework for Performance Estimation Problem (PEP) Workflow
5
- Requires-Python: >=3.9
5
+ Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: attrs>=25.3.0
@@ -37,6 +37,7 @@ uv sync; source .venv/bin/activate;
37
37
 
38
38
  In windows, use `.venv\Scripts\activate` instead.
39
39
 
40
+ ### Lint & Testing
40
41
  We use `ruff` to do the format and lint and `isort` to do the import ordering.
41
42
 
42
43
  ```bash
@@ -45,12 +46,30 @@ ruff check .;
45
46
  isort .;
46
47
  ```
47
48
 
48
- ### Testing
49
-
50
49
  We use `pytest` framework to do the test. To run all unit tests, run the following command:
51
50
 
52
- ```python
51
+ ```bash
53
52
  pytest -s -vv pepflow
54
53
  ```
55
54
 
55
+ We have a convenient script to above
56
+ ```bash
57
+ scripts/check.sh [format|lint|typecheck|test]
58
+ ```
59
+ See the script for the options.
60
+
61
+ ### Build doc website
62
+
63
+ Install the required library (one-time) and `pandoc` in order to build ipynb.
64
+ ```bash
65
+ pip install -r docs/requirements.txt
66
+ ```
67
+
68
+ To build the website, run
69
+ ```bash
70
+ scripts/build_doc.sh [--serve-only]
71
+ ```
72
+ The argument `--serve-only` is optional for hosting the website locally.
73
+
74
+
56
75
 
@@ -15,6 +15,7 @@ uv sync; source .venv/bin/activate;
15
15
 
16
16
  In windows, use `.venv\Scripts\activate` instead.
17
17
 
18
+ ### Lint & Testing
18
19
  We use `ruff` to do the format and lint and `isort` to do the import ordering.
19
20
 
20
21
  ```bash
@@ -23,12 +24,30 @@ ruff check .;
23
24
  isort .;
24
25
  ```
25
26
 
26
- ### Testing
27
-
28
27
  We use `pytest` framework to do the test. To run all unit tests, run the following command:
29
28
 
30
- ```python
29
+ ```bash
31
30
  pytest -s -vv pepflow
32
31
  ```
33
32
 
33
+ We have a convenient script to above
34
+ ```bash
35
+ scripts/check.sh [format|lint|typecheck|test]
36
+ ```
37
+ See the script for the options.
38
+
39
+ ### Build doc website
40
+
41
+ Install the required library (one-time) and `pandoc` in order to build ipynb.
42
+ ```bash
43
+ pip install -r docs/requirements.txt
44
+ ```
45
+
46
+ To build the website, run
47
+ ```bash
48
+ scripts/build_doc.sh [--serve-only]
49
+ ```
50
+ The argument `--serve-only` is optional for hosting the website locally.
51
+
52
+
34
53
 
@@ -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 CVXSolver as CVXSolver
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
@@ -0,0 +1,95 @@
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
+ """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
+ """
51
+
52
+ scalar: Scalar | float
53
+ comparator: utils.Comparator
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
@@ -0,0 +1,113 @@
1
+ import math
2
+
3
+ from pepflow import function
4
+ from pepflow import parameter as pm
5
+ from pepflow import pep
6
+ from pepflow import pep_context as pc
7
+
8
+
9
+ def test_gd_e2e():
10
+ ctx = pc.PEPContext("gd").set_as_current()
11
+ pep_builder = pep.PEPBuilder()
12
+ eta = 1
13
+ N = 9
14
+
15
+ f = pep_builder.declare_func(function.SmoothConvexFunction, "f", L=1)
16
+ x = pep_builder.set_init_point("x_0")
17
+ x_star = f.add_stationary_point("x_star")
18
+ pep_builder.set_initial_constraint(
19
+ ((x - x_star) ** 2).le(1, name="initial_condition")
20
+ )
21
+
22
+ # We first build the algorithm with the largest number of iterations.
23
+ for i in range(N):
24
+ x = x - eta * f.gradient(x)
25
+ x.add_tag(f"x_{i + 1}")
26
+
27
+ # To achieve the sweep, we can just update the performance_metric.
28
+ for i in range(1, N + 1):
29
+ p = ctx.get_by_tag(f"x_{i}")
30
+ pep_builder.set_performance_metric(
31
+ f.function_value(p) - f.function_value(x_star)
32
+ )
33
+ result = pep_builder.solve_primal()
34
+ expected_opt_value = 1 / (4 * i + 2)
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
+ )