From 220e05d1b582cc020f7263a5c2b12f577c23762a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 12:12:44 +0000 Subject: [PATCH 1/5] Initial plan From 46d0e84fd38dc1d8693cfcb7434313d337805e2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 12:17:22 +0000 Subject: [PATCH 2/5] fix factory defaults for inequality-only Clarabel problems Agent-Logs-Url: https://github.com/Jebel-Quant/fast_minimum_variance/sessions/352af6df-018d-427b-b143-2a96c6df1dd0 Co-authored-by: tschm <2046079+tschm@users.noreply.github.com> --- src/fast_minimum_variance/__init__.py | 2 +- tests/test_factory.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/fast_minimum_variance/__init__.py b/src/fast_minimum_variance/__init__.py index 1282c30..b38fcec 100644 --- a/src/fast_minimum_variance/__init__.py +++ b/src/fast_minimum_variance/__init__.py @@ -57,7 +57,7 @@ def Problem( # noqa: N802 n = X.shape[1] A = A if A is not None else np.ones((n, 0)) # noqa: N806 - b = b if b is not None else np.ones(1) + b = b if b is not None else (np.ones(A.shape[1]) if A.shape[1] else np.zeros(0)) C = C if C is not None else -np.eye(n) # noqa: N806 d = d if d is not None else np.zeros(n) diff --git a/tests/test_factory.py b/tests/test_factory.py index c00bc53..3679f7b 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -86,6 +86,29 @@ def test_partial_constraints_fill_defaults(self, X): # noqa: N803 np.testing.assert_array_equal(p.d, np.zeros(5)) +# --------------------------------------------------------------------------- +# Regression: inequality-only Clarabel setup (Boyd experiment style) +# --------------------------------------------------------------------------- + + +class TestInequalityOnlyClarabel: + """Inequality-only problems use an empty equality block consistently.""" + + def test_boyd_experiment_orthogonal_factors(self): + """For orthonormal X, Clarabel solves min x^T(X^T X)x with x >= 0 at x = 0.""" + rng = np.random.default_rng(0) + x_mat, _ = np.linalg.qr(rng.standard_normal((5000, 50))) + gram = x_mat.T @ x_mat + + p = Problem(x_mat, C=-np.eye(50), d=np.zeros(50)) + np.testing.assert_array_equal(p.b, np.zeros(0)) + + x, _ = p.solve_clarabel(project=False) + assert x.shape == (50,) + assert np.all(x >= -1e-8) + assert x @ gram @ x <= 1e-4 + + # --------------------------------------------------------------------------- # Public API surface # --------------------------------------------------------------------------- From 34e4bb46335819db382ca74a4ae019f73a2ad4dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 12:21:59 +0000 Subject: [PATCH 3/5] add Boyd clarabel regression and fix inequality-only factory defaults Agent-Logs-Url: https://github.com/Jebel-Quant/fast_minimum_variance/sessions/352af6df-018d-427b-b143-2a96c6df1dd0 Co-authored-by: tschm <2046079+tschm@users.noreply.github.com> --- src/fast_minimum_variance/__init__.py | 3 ++- tests/test_factory.py | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/fast_minimum_variance/__init__.py b/src/fast_minimum_variance/__init__.py index b38fcec..98d9bec 100644 --- a/src/fast_minimum_variance/__init__.py +++ b/src/fast_minimum_variance/__init__.py @@ -57,7 +57,8 @@ def Problem( # noqa: N802 n = X.shape[1] A = A if A is not None else np.ones((n, 0)) # noqa: N806 - b = b if b is not None else (np.ones(A.shape[1]) if A.shape[1] else np.zeros(0)) + default_equality_rhs = np.ones(A.shape[1]) if A.shape[1] else np.zeros(0) + b = b if b is not None else default_equality_rhs C = C if C is not None else -np.eye(n) # noqa: N806 d = d if d is not None else np.zeros(n) diff --git a/tests/test_factory.py b/tests/test_factory.py index 3679f7b..32692cd 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -7,6 +7,9 @@ from fast_minimum_variance.minvar_problem import _MinVarProblem from fast_minimum_variance.problem import _Problem +NONNEGATIVITY_TOLERANCE = 1e-8 +BOYD_OBJECTIVE_TOLERANCE = 1e-4 + @pytest.fixture(scope="module") def X(): # noqa: N802 @@ -95,18 +98,22 @@ class TestInequalityOnlyClarabel: """Inequality-only problems use an empty equality block consistently.""" def test_boyd_experiment_orthogonal_factors(self): - """For orthonormal X, Clarabel solves min x^T(X^T X)x with x >= 0 at x = 0.""" + """For orthonormal X, Clarabel solves min_x x^T (X^T X) x subject to x >= 0.""" rng = np.random.default_rng(0) - x_mat, _ = np.linalg.qr(rng.standard_normal((5000, 50))) + x_mat, r_mat = np.linalg.qr(rng.standard_normal((5000, 50))) + assert r_mat.shape == (50, 50) gram = x_mat.T @ x_mat + np.testing.assert_allclose(gram, np.eye(50), atol=1e-10) p = Problem(x_mat, C=-np.eye(50), d=np.zeros(50)) + assert p.A.shape == (50, 0) np.testing.assert_array_equal(p.b, np.zeros(0)) - x, _ = p.solve_clarabel(project=False) + x, iters = p.solve_clarabel(project=False) assert x.shape == (50,) - assert np.all(x >= -1e-8) - assert x @ gram @ x <= 1e-4 + assert iters >= 1 + assert np.all(x >= -NONNEGATIVITY_TOLERANCE) + assert x @ gram @ x <= BOYD_OBJECTIVE_TOLERANCE # --------------------------------------------------------------------------- From a0577cc8ad719cec046c338e30b01de30fea6050 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 15:54:57 +0000 Subject: [PATCH 4/5] refocus Boyd Clarabel experiment on MinVarProblem Agent-Logs-Url: https://github.com/Jebel-Quant/fast_minimum_variance/sessions/b4a7a035-f393-4211-998c-390b75abf466 Co-authored-by: tschm <2046079+tschm@users.noreply.github.com> --- src/fast_minimum_variance/__init__.py | 3 +-- tests/test_clarabel.py | 18 ++++++++++++++++ tests/test_factory.py | 30 --------------------------- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/fast_minimum_variance/__init__.py b/src/fast_minimum_variance/__init__.py index 98d9bec..1282c30 100644 --- a/src/fast_minimum_variance/__init__.py +++ b/src/fast_minimum_variance/__init__.py @@ -57,8 +57,7 @@ def Problem( # noqa: N802 n = X.shape[1] A = A if A is not None else np.ones((n, 0)) # noqa: N806 - default_equality_rhs = np.ones(A.shape[1]) if A.shape[1] else np.zeros(0) - b = b if b is not None else default_equality_rhs + b = b if b is not None else np.ones(1) C = C if C is not None else -np.eye(n) # noqa: N806 d = d if d is not None else np.zeros(n) diff --git a/tests/test_clarabel.py b/tests/test_clarabel.py index 33f6c5a..e776958 100644 --- a/tests/test_clarabel.py +++ b/tests/test_clarabel.py @@ -87,6 +87,24 @@ def test_project_false(self, mvp): assert w.shape == (mvp.n,) assert iters >= 1 + def test_boyd_experiment_orthogonal_factors(self): + """Orthogonal-factor MinVarProblem matches the simplex-constrained optimum.""" + rng = np.random.default_rng(0) + x_mat, _ = np.linalg.qr(rng.standard_normal((5000, 50))) + gram = x_mat.T @ x_mat + np.testing.assert_allclose(gram, np.eye(50), atol=1e-10) + + p = MinVarProblem(x_mat) + w, iters = p.solve_clarabel(project=False) + + expected = np.full(50, 1.0 / 50.0) + assert w.shape == (50,) + assert iters >= 1 + assert np.all(w >= -1e-8) + assert w.sum() == pytest.approx(1.0, abs=1e-6) + np.testing.assert_allclose(w, expected, atol=1e-5) + assert w @ gram @ w == pytest.approx(1.0 / 50.0, abs=1e-5) + # --------------------------------------------------------------------------- # Problem diff --git a/tests/test_factory.py b/tests/test_factory.py index 32692cd..c00bc53 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -7,9 +7,6 @@ from fast_minimum_variance.minvar_problem import _MinVarProblem from fast_minimum_variance.problem import _Problem -NONNEGATIVITY_TOLERANCE = 1e-8 -BOYD_OBJECTIVE_TOLERANCE = 1e-4 - @pytest.fixture(scope="module") def X(): # noqa: N802 @@ -89,33 +86,6 @@ def test_partial_constraints_fill_defaults(self, X): # noqa: N803 np.testing.assert_array_equal(p.d, np.zeros(5)) -# --------------------------------------------------------------------------- -# Regression: inequality-only Clarabel setup (Boyd experiment style) -# --------------------------------------------------------------------------- - - -class TestInequalityOnlyClarabel: - """Inequality-only problems use an empty equality block consistently.""" - - def test_boyd_experiment_orthogonal_factors(self): - """For orthonormal X, Clarabel solves min_x x^T (X^T X) x subject to x >= 0.""" - rng = np.random.default_rng(0) - x_mat, r_mat = np.linalg.qr(rng.standard_normal((5000, 50))) - assert r_mat.shape == (50, 50) - gram = x_mat.T @ x_mat - np.testing.assert_allclose(gram, np.eye(50), atol=1e-10) - - p = Problem(x_mat, C=-np.eye(50), d=np.zeros(50)) - assert p.A.shape == (50, 0) - np.testing.assert_array_equal(p.b, np.zeros(0)) - - x, iters = p.solve_clarabel(project=False) - assert x.shape == (50,) - assert iters >= 1 - assert np.all(x >= -NONNEGATIVITY_TOLERANCE) - assert x @ gram @ x <= BOYD_OBJECTIVE_TOLERANCE - - # --------------------------------------------------------------------------- # Public API surface # --------------------------------------------------------------------------- From cdcb6dd9c379cd3d7880ccf5b8b27a0cb75b4ab0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 15:56:22 +0000 Subject: [PATCH 5/5] move Boyd clarabel coverage to MinVarProblem test path Agent-Logs-Url: https://github.com/Jebel-Quant/fast_minimum_variance/sessions/b4a7a035-f393-4211-998c-390b75abf466 Co-authored-by: tschm <2046079+tschm@users.noreply.github.com> --- tests/test_clarabel.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_clarabel.py b/tests/test_clarabel.py index e776958..6e3d1ac 100644 --- a/tests/test_clarabel.py +++ b/tests/test_clarabel.py @@ -6,6 +6,8 @@ from fast_minimum_variance.minvar_problem import _MinVarProblem as MinVarProblem from fast_minimum_variance.problem import _Problem as Problem +NONNEGATIVE_TOLERANCE = 1e-8 + def _make_returns(T, N, seed=42): # noqa: N803 return np.random.default_rng(seed).standard_normal((T, N)) @@ -100,7 +102,7 @@ def test_boyd_experiment_orthogonal_factors(self): expected = np.full(50, 1.0 / 50.0) assert w.shape == (50,) assert iters >= 1 - assert np.all(w >= -1e-8) + assert np.all(w >= -NONNEGATIVE_TOLERANCE), "Clarabel returned weights below non-negativity tolerance" assert w.sum() == pytest.approx(1.0, abs=1e-6) np.testing.assert_allclose(w, expected, atol=1e-5) assert w @ gram @ w == pytest.approx(1.0 / 50.0, abs=1e-5)