Summary
When a square Hessian with floating-point-level asymmetry (on the order of machine epsilon, ~1e-18) is passed to HiGHS 1.14.0 via passHessian from the C API / highspy, HiGHS first emits ERROR: Square Hessian contains N non-symmetries and then the Python process crashes with native heap corruption (malloc: Incorrect checksum for freed object, free(): invalid size, double free or corruption, munmap_chunk(): invalid pointer, SIGABRT / SIGSEGV). The crash is not catchable from Python since the entire interpreter is terminated.
From #2821 / #2857: passHessian now returns kError on any asymmetry instead of using (Q+Q^T)/2. The check at highs/model/HighsHessianUtils.cpp#L422 uses strict != equality with no tolerance, so two values that differ by 1e-18 are counted as asymmetric.
The practical impact is that CVXPY's HiGHS backend fails to solve certain QPs whose Hessian has dense off-diagonal entries. In our case, the Hessian originates from cp.quad_form(w, Sigma) where Sigma is the result of a PSD projection (V @ diag(λ) @ V.T) of a covariance matrix. Although Sigma is mathematically symmetric, the matrix that ultimately reaches HiGHS is not bit-exactly symmetric after floating-point reconstruction and CVXPY's canonicalization, and the new strict equality check flags all such asymmetries as errors.
Environment
- HiGHS: 1.14.0 (via
highspy 1.14.0, PyPI wheels)
- CVXPY: 1.7.5
- Python: 3.12
- NumPy 1.26.4, SciPy 1.15.3
- Reproduced on both macOS arm64 (local) and Linux x86_64 (containers). Reproduced sequentially, so not a threading issue.
Reproduction
import numpy as np
import cvxpy as cp
import scipy.sparse as sp
rng = np.random.default_rng(0)
N_PATHS = 150
N_NODES = 50
# ---- Sparse "incidence" matrix mapping paths to nodes ----
rows, cols, vals = [], [], []
for p in range(N_PATHS):
src, snk = rng.choice(N_NODES, size=2, replace=False)
rows += [p, p]
cols += [src, snk]
vals += [1.0, -1.0]
incidence = sp.csr_matrix((vals, (rows, cols)), shape=(N_PATHS, N_NODES))
# ---- Dense PSD covariance on nodes, built by eigenvalue clamping ----
# (mirrors a typical "project onto PSD cone" step used after estimating a
# covariance matrix from noisy / partially-missing data)
A = rng.standard_normal((N_NODES, N_NODES))
Sigma_raw = A @ A.T + rng.standard_normal((N_NODES, N_NODES)) * 0.01
Sigma_sym = 0.5 * (Sigma_raw + Sigma_raw.T)
w_eig, V_eig = np.linalg.eigh(Sigma_sym)
w_eig = np.maximum(w_eig, 0.0)
Sigma = V_eig @ np.diag(w_eig) @ V_eig.T
# Sigma is mathematically symmetric and PSD, but not bit-exact symmetric:
print("max |Sigma - Sigma.T|:", np.max(np.abs(Sigma - Sigma.T)))
print("min eigenvalue:", float(np.linalg.eigvalsh(Sigma).min()))
# ---- Build the CVXPY problem ----
w_path = cp.Variable(N_PATHS, nonneg=True)
# Linear transformation to "nodal" space via the sparse incidence matrix.
w_nodal = w_path @ incidence # shape: (N_NODES,)
# Some realistic constraints so the problem is bounded and not trivial.
path_coef = rng.standard_normal(N_PATHS)
path_upper = 50.0
constraints = [
w_path <= path_upper,
cp.sum(w_path) <= 500.0,
]
cov_penalty = 0.1
diag_penalty = 0.01
diag_weights = rng.uniform(0.1, 2.0, size=N_NODES)
obj_terms = [
cp.sum(cp.multiply(path_coef, w_path)),
-diag_penalty * cp.sum_squares(cp.multiply(diag_weights, w_nodal)),
-cov_penalty * cp.quad_form(w_nodal, Sigma),
]
prob = cp.Problem(cp.Maximize(cp.sum(obj_terms)), constraints)
prob.solve(solver=cp.HIGHS, verbose=True)
results in
Running HiGHS 1.14.0 (git hash: 7df0786): Copyright (c) 2026 under MIT licence terms
ERROR: Square Hessian contains 743 non-symmetries
QP has 401 rows; 250 cols; 1150 matrix nonzeros; 1325 Hessian nonzeros
Coefficient ranges:
Matrix [1e-01, 2e+00]
Cost [3e-03, 3e+00]
Hessian [3e-03, 1e+01]
Bound [0e+00, 0e+00]
RHS [5e+01, 5e+02]
Iteration Objective NullspaceDim
python(76500,0x1f2066140) malloc: Incorrect checksum for freed object 0x132c4f600: probably modified after being freed.
Corrupt value: 0xc004115f9637ddc9
python(76500,0x1f2066140) malloc: *** set a breakpoint in malloc_error_break to debug
[1] 76500 abort python
Summary
When a square Hessian with floating-point-level asymmetry (on the order of machine epsilon, ~1e-18) is passed to HiGHS 1.14.0 via
passHessianfrom the C API /highspy, HiGHS first emitsERROR: Square Hessian contains N non-symmetriesand then the Python process crashes with native heap corruption (malloc: Incorrect checksum for freed object,free(): invalid size,double free or corruption,munmap_chunk(): invalid pointer, SIGABRT / SIGSEGV). The crash is not catchable from Python since the entire interpreter is terminated.From #2821 / #2857:
passHessiannow returnskErroron any asymmetry instead of using(Q+Q^T)/2. The check athighs/model/HighsHessianUtils.cpp#L422uses strict!=equality with no tolerance, so two values that differ by1e-18are counted as asymmetric.The practical impact is that CVXPY's HiGHS backend fails to solve certain QPs whose Hessian has dense off-diagonal entries. In our case, the Hessian originates from
cp.quad_form(w, Sigma)where Sigma is the result of a PSD projection (V @ diag(λ) @ V.T) of a covariance matrix. Although Sigma is mathematically symmetric, the matrix that ultimately reaches HiGHS is not bit-exactly symmetric after floating-point reconstruction and CVXPY's canonicalization, and the new strict equality check flags all such asymmetries as errors.Environment
highspy 1.14.0, PyPI wheels)Reproduction
results in