diff --git a/Makefile b/Makefile index fd36957..40ab61e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ venv: curl -LsSf https://astral.sh/uv/install.sh | sh - uv venv + uv venv --python '3.12' install: venv ## Install dependencies and setup environment uv pip install --upgrade pip diff --git a/experiments/__init__.py b/experiments/__init__.py new file mode 100644 index 0000000..7c67f96 --- /dev/null +++ b/experiments/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2025 Stanford University Convex Optimization Group +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/experiments/alter1.py b/experiments/alter1.py new file mode 100644 index 0000000..1a98e2f --- /dev/null +++ b/experiments/alter1.py @@ -0,0 +1,23 @@ +import cvxpy as cp + +from cvx.ball.utils.circle import Center, Circle + + +def min_circle_cvx(points, **kwargs): + # Use con_1 if no constraint construction is defined + # cvxpy variable for the radius + r = cp.Variable(name="Radius") + # cvxpy variable for the midpoint + x = cp.Variable(points.shape[1], name="Midpoint") + objective = cp.Minimize(r) + constraints = [cp.SOC(r, point - x) for point in points] + # * #np.ones(points.shape[0]), + # points - cp.outer(np.ones(points.shape[0]), x), + # axis=1, + # ) + # ] + + problem = cp.Problem(objective=objective, constraints=constraints) + problem.solve(**kwargs) + + return Circle(radius=float(r.value), center=Center(x.value)) diff --git a/experiments/alter2.py b/experiments/alter2.py new file mode 100644 index 0000000..8e6ee90 --- /dev/null +++ b/experiments/alter2.py @@ -0,0 +1,23 @@ +import cvxpy as cp + +from cvx.ball.utils.circle import Center, Circle + + +def min_circle_cvx(points, **kwargs): + # Use con_1 if no constraint construction is defined + # cvxpy variable for the radius + r = cp.Variable(name="Radius") + # cvxpy variable for the midpoint + x = cp.Variable(points.shape[1], name="Midpoint") + objective = cp.Minimize(r) + constraints = [cp.norm2(x - point) <= r for point in points] + # * #np.ones(points.shape[0]), + # points - cp.outer(np.ones(points.shape[0]), x), + # axis=1, + # ) + # ] + + problem = cp.Problem(objective=objective, constraints=constraints) + problem.solve(**kwargs) + + return Circle(radius=float(r.value), center=Center(x.value)) diff --git a/experiments/asymptotic.py b/experiments/asymptotic.py new file mode 100644 index 0000000..8956323 --- /dev/null +++ b/experiments/asymptotic.py @@ -0,0 +1,82 @@ +import time +from typing import List, Tuple + +import numpy as np +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +from cvx.ball.solver import min_circle_cvx + + +def cvx(n: int) -> float: + points = np.random.rand(n, 5) + return min_circle_cvx(points, solver="CLARABEL") + + +def measure_execution_time(func, n: int, num_trials: int = 3) -> float: + """Run multiple trials and return average execution time""" + times = [] + for _ in range(num_trials): + start = time.time() + func(n) + times.append(time.time() - start) + return np.mean(times) + + +def run_analysis() -> Tuple[List[int], List[float]]: + # Test for different values of n (powers of 2) + sequence = np.array([2**n for n in range(4, 20)]) + execution_times = [] + + for n in sequence: + avg_time = measure_execution_time(cvx, int(n)) + execution_times.append(avg_time) + print(f"n={n}: {avg_time:.4f} seconds") + + return sequence, execution_times + + +def plot_results(sizes: List[int], times: List[float]) -> None: + # Create figure + fig = make_subplots(specs=[[{"secondary_y": True}]]) + + # Add actual execution times + fig.add_trace( + go.Scatter( + x=sizes, y=times, name="Actual Time", mode="lines+markers", line=dict(color="blue"), marker=dict(size=8) + ) + ) + + # Add theoretical O(n) complexity line + normalized_n = np.array(sizes) / sizes[0] + fig.add_trace( + go.Scatter(x=sizes, y=normalized_n * times[0], name="O(n)", line=dict(color="red", dash="dash"), mode="lines") + ) + + # Update layout with log scales + fig.update_layout( + title="Algorithm Performance Analysis", + xaxis=dict( + title="Input Size (n)", + type="log", + dtick="D1", # Show ticks for each power of 10 + ), + yaxis=dict(title="Execution Time (seconds)", type="log", dtick="D1"), + hovermode="x unified", + showlegend=True, + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.99), + plot_bgcolor="white", + ) + + # Add grid lines + fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor="LightGray") + fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor="LightGray") + + # Show the plot + fig.show() + + +if __name__ == "__main__": + # Run the analysis + sizes, times = run_analysis() + plot_results(sizes, times) diff --git a/experiments/speed.py b/experiments/speed.py new file mode 100644 index 0000000..0c7cca3 --- /dev/null +++ b/experiments/speed.py @@ -0,0 +1,32 @@ +import statistics +import timeit as tt + +import numpy as np + +from cvx.ball.solver import min_circle_cvx +from experiments.alter1 import min_circle_cvx as alter1 +from experiments.alter2 import min_circle_cvx as alter2 + +if __name__ == "__main__": + points = np.random.randn(10000, 5) + + def cvx(): + min_circle_cvx(points, solver="CLARABEL") + + def alter_a(): + alter1(points, solver="CLARABEL") + + def alter_b(): + alter2(points, solver="CLARABEL") + + times_clarabel = tt.repeat(cvx, number=1, repeat=5) + print(times_clarabel) + print(statistics.mean(times_clarabel)) + + times_alter1 = tt.repeat(alter_a, number=1, repeat=5) + print(times_alter1) + print(statistics.mean(times_alter1)) + + times_alter2 = tt.repeat(alter_b, number=1, repeat=5) + print(times_alter2) + print(statistics.mean(times_alter2)) diff --git a/pyproject.toml b/pyproject.toml index 99c2178..cd22044 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/cvxball"] +packages = ["src/cvx"] [tool.deptry.per_rule_ignores] DEP002 = ["clarabel"]