squaredcuber's picture
download
raw
5.26 kB
from __future__ import annotations
import numpy as np
from loss_aware_dro_repro.appendix_conic import (
absolute_regression_socp,
solve_appendix_socp,
solve_absolute_regression,
solve_empirical_w1_portfolio,
solve_squared_regression,
)
from loss_aware_dro_repro.empirical_ot import empirical_wasserstein_value_gradient
from loss_aware_dro_repro.gelbrich import lower_triangular_central_difference, relative_gradient_error
from loss_aware_dro_repro.residuals import conic_residual_maximum
def _data():
rng = np.random.default_rng(731)
portfolio = rng.normal(size=(10, 3))
X = rng.normal(size=(10, 1))
y = 1.4 * X[:, 0] + rng.normal(size=10)
return portfolio, X, y
def test_empirical_w1_portfolio_matches_closed_form_and_gradient():
portfolio, _, _ = _data()
epsilon = 0.2
L = np.array([[1.1, 0.0, 0.0], [0.1, 0.9, 0.0], [-0.05, 0.08, 1.2]])
result = solve_empirical_w1_portfolio(portfolio, epsilon, L)
decision = result["decision"]
expected = -portfolio.mean(axis=0) @ decision + epsilon * np.linalg.norm(np.linalg.solve(L, decision))
numerical = lower_triangular_central_difference(lambda factor: solve_empirical_w1_portfolio(portfolio, epsilon, factor)["objective"], L)
assert abs(result["objective"] - expected) < 1e-8
assert abs(decision.sum() - 1.0) < 1e-9
assert decision.min() > -1e-9
assert conic_residual_maximum(result["residuals"]) < 1e-8
assert relative_gradient_error(result["value_gradient"], numerical) < 1e-4
def test_absolute_regression_matches_appendix_objective_and_gradient():
_, X, y = _data()
epsilon = 0.2
L = np.array([[1.1, 0.0], [0.15, 0.9]])
result = solve_absolute_regression(X, y, epsilon, L)
weight = result["decision"]
coefficient = np.concatenate((-weight, [1.0]))
expected = np.mean(np.abs(y - X @ weight)) + epsilon * np.linalg.norm(np.linalg.solve(L, coefficient))
numerical = lower_triangular_central_difference(lambda factor: solve_absolute_regression(X, y, epsilon, factor)["objective"], L)
assert abs(result["objective"] - expected) < 1e-8
assert conic_residual_maximum(result["residuals"]) < 1e-8
assert relative_gradient_error(result["value_gradient"], numerical) < 1e-4
def test_appendix_complementarity_excludes_zero_cone_equalities():
_, X, y = _data()
program = absolute_regression_socp(
X,
y,
0.2,
np.array([[1.1, 0.0], [0.15, 0.9]]),
)
result = solve_appendix_socp(program)
expected = abs(float(result["dual"] @ result["slack"]))
assert result["residuals"]["complementarity"] == expected
def test_squared_regression_post_solve_square_and_gradient():
_, X, y = _data()
epsilon = 0.2
L = np.array([[1.1, 0.0], [0.15, 0.9]])
result = solve_squared_regression(X, y, epsilon, L)
weight = result["decision"]
coefficient = np.concatenate((-weight, [1.0]))
expected_root = np.sqrt(np.mean((y - X @ weight) ** 2)) + epsilon * np.linalg.norm(np.linalg.solve(L, coefficient))
numerical = lower_triangular_central_difference(lambda factor: solve_squared_regression(X, y, epsilon, factor)["objective"], L)
assert result["post_solve_square"] is True
assert abs(result["root_objective"] - expected_root) < 1e-8
assert abs(result["objective"] - expected_root**2) < 1e-8
assert conic_residual_maximum(result["residuals"]) < 1e-8
assert relative_gradient_error(result["value_gradient"], numerical) < 1e-4
def test_empirical_wasserstein_envelope_gradients_and_marginals():
rng = np.random.default_rng(19)
first = rng.normal(size=(8, 2))
second = rng.normal(size=(8, 2))
L = np.array([[1.1, 0.0], [0.2, 0.9]])
for order in (1, 2):
value, analytic, metadata = empirical_wasserstein_value_gradient(first, second, L, order)
numerical = lower_triangular_central_difference(
lambda factor: empirical_wasserstein_value_gradient(first, second, factor, order)[0], L
)
assert value > 0
assert metadata["marginal_row_residual"] < 1e-12
assert metadata["marginal_column_residual"] < 1e-12
assert metadata["dual_feasibility_violation"] < 1e-12
assert metadata["complementary_slackness"] < 1e-12
assert metadata["duality_gap"] < 1e-12
assert relative_gradient_error(analytic, numerical) < 1e-6
def test_empirical_w2_exact_match_has_zero_conservative_subgradient():
points = np.array([[0.0, 1.0], [2.0, -1.0]])
value, gradient, metadata = empirical_wasserstein_value_gradient(points, points, np.eye(2), 2)
assert value == 0.0
assert np.array_equal(gradient, np.zeros((2, 2)))
assert metadata["transport_power_value"] == 0.0
def test_empirical_wasserstein_retains_tiny_distinct_shift():
first = np.array([[0.0]])
second = np.array([[1e-12]])
for order in (1, 2):
value, gradient, metadata = empirical_wasserstein_value_gradient(first, second, np.eye(1), order)
np.testing.assert_allclose(value, 1e-12, rtol=1e-12, atol=0.0)
assert gradient[0, 0] != 0.0
assert metadata["transport_power_value"] > 0.0

Xet Storage Details

Size:
5.26 kB
·
Xet hash:
ca067f1b626bd2d92108e1e29cd962453a04e77f92abeb4ae43158580d82783c

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.