Coverage for src / min_circle / cvx.py: 100%
30 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-26 19:42 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-26 19:42 +0000
1"""CVXPY implementation for finding the minimum enclosing circle.
3This module provides functions to find the minimum enclosing circle
4using convex optimization with the CVXPY library.
5"""
7import statistics
8import timeit
9from typing import Any
11import cvxpy as cp
12import numpy as np
14from .utils.circle import Circle
17def min_circle_cvx(points: np.ndarray, **kwargs: Any) -> Circle:
18 """Find the minimum enclosing circle using convex optimization.
20 Uses CVXPY to formulate and solve the minimum enclosing circle problem
21 as a second-order cone program (SOCP).
23 Args:
24 points: Array of 2D points with shape (n, 2)
25 **kwargs: Additional keyword arguments to pass to the solver
27 Returns:
28 Circle object containing the center and radius of the minimum enclosing circle
30 Examples:
31 >>> import numpy as np
32 >>> points = np.array([[0.0, 0.0], [2.0, 0.0]])
33 >>> circle = min_circle_cvx(points, solver="CLARABEL")
34 >>> round(circle.radius, 6)
35 1.0
36 >>> np.round(circle.center, 6)
37 array([1., 0.])
38 """
39 # CVXPY variable for the radius
40 r = cp.Variable(name="Radius")
41 # CVXPY variable for the midpoint
42 x = cp.Variable(points.shape[1], name="Midpoint")
44 # Minimize the radius
45 objective = cp.Minimize(r)
47 # Second-order cone constraints ensuring all points are within the circle
48 constraints = [
49 cp.SOC(
50 r * np.ones(points.shape[0]),
51 points - cp.outer(np.ones(points.shape[0]), x),
52 axis=1,
53 )
54 ]
56 # Create and solve the problem
57 problem = cp.Problem(objective=objective, constraints=constraints)
58 problem.solve(**kwargs)
60 return Circle(radius=float(r.value), center=x.value)
63def min_circle_cvx_3() -> cp.Problem:
64 """Create a CVXPY problem for finding the minimum circle for 3 points.
66 Creates a parameterized problem that can be reused with different point sets.
68 Returns:
69 CVXPY Problem object ready to be solved
70 """
71 # CVXPY variable for the radius
72 r = cp.Variable(name="Radius")
73 # CVXPY variable for the midpoint
74 x = cp.Variable((1, 2), name="Midpoint")
75 # Parameter for the 3 points
76 p = cp.Parameter((3, 2), "points")
78 # Minimize the radius
79 objective = cp.Minimize(r)
81 # Second-order cone constraints for 3 points
82 constraints = [
83 cp.SOC(
84 cp.hstack([r, r, r]),
85 p - cp.vstack([x, x, x]),
86 axis=1,
87 )
88 ]
90 problem = cp.Problem(objective=objective, constraints=constraints)
91 return problem
94def min_circle_cvx_2() -> cp.Problem:
95 """Create a CVXPY problem for finding the minimum circle for 2 points.
97 Creates a parameterized problem that can be reused with different point sets.
99 Returns:
100 CVXPY Problem object ready to be solved
101 """
102 # CVXPY variable for the radius
103 r = cp.Variable(name="Radius")
104 # CVXPY variable for the midpoint
105 x = cp.Variable((1, 2), name="Midpoint")
106 # Parameter for the 2 points
107 p = cp.Parameter((2, 2), "points")
109 # Minimize the radius
110 objective = cp.Minimize(r)
112 # Second-order cone constraints for 2 points
113 constraints = [
114 cp.SOC(
115 cp.hstack([r, r]),
116 p - cp.vstack([x, x]),
117 axis=1,
118 )
119 ]
121 problem = cp.Problem(objective=objective, constraints=constraints)
122 return problem
125if __name__ == "__main__": # pragma: no cover
126 # Example with 3 points
127 p = np.array([[0, 0], [0.0, 2.0], [4.0, 4.0]])
128 problem = min_circle_cvx_3()
129 problem.param_dict["points"].value = p
130 problem.solve(solver="CLARABEL")
131 print(problem.var_dict["Radius"].value)
133 # Performance testing with 2 points
134 p = np.random.randn(2, 2)
135 problem = min_circle_cvx_2()
137 def f() -> None:
138 """Benchmark function for parameterized problem."""
139 problem.param_dict["points"].value = p
140 problem.solve(solver="CLARABEL")
142 results = timeit.repeat(f, number=1, repeat=10)
143 print(f"Parameterized problem mean time: {statistics.mean(results):.6f} seconds")
145 def g() -> Circle:
146 """Benchmark function for direct problem formulation."""
147 return min_circle_cvx(p, solver="CLARABEL")
149 results = timeit.repeat(g, number=1, repeat=10)
150 print(f"Direct problem mean time: {statistics.mean(results):.6f} seconds")