Source code for ellalgo.oracles.lmi0_oracle
from typing import Optional, Tuple
import numpy as np
from ellalgo.oracles.ldlt_mgr import LDLTMgr
Cut = Tuple[np.ndarray, float]
[docs]
class LMI0Oracle:
"""Oracle for Linear Matrix Inequality (LMI) constraint: F(x) ⪰ 0
Solves the feasibility problem:
Find x ∈ ℝⁿ such that ∑_{k=1}^n F_k x_k ≽ 0
Where:
- F_k ∈ 𝕊^m (symmetric matrices) are given in mat_f
- x = [x_1, ..., x_n]^T is the decision vector
- ≽ denotes positive semidefinite (PSD) constraint
The oracle uses LDLT factorization to verify PSD property
and generates cutting planes for infeasible solutions.
"""
def __init__(self, mat_f):
"""Initialize LMI oracle with coefficient matrices
Args:
mat_f (List[np.ndarray]): List of symmetric coefficient matrices [F₁, F₂, ..., Fₙ]
Each F_k must be square matrix of same dimension
mat_f[0] determines the matrix size m×m
"""
self.mat_f = mat_f # Store coefficient matrices
# Initialize LDLT factorization manager with matrix dimension from F₁
self.ldlt_mgr = LDLTMgr(len(mat_f[0]))
[docs]
def assess_feas(self, x: np.ndarray) -> Optional[Cut]:
"""Assess feasibility of solution x against LMI constraint
Implementation Strategy:
1. Construct matrix F(x) = ∑ x_k F_k through element-wise computation
2. Attempt LDLT factorization:
- Success: F(x) is PSD (feasible) → return None
- Failure: F(x) not PSD → compute cutting plane (g, σ)
Args:
x (np.ndarray): Candidate solution vector [x₁, ..., xₙ]^T ∈ ℝⁿ
Returns:
Optional[Cut]:
- None if x is feasible (F(x) ≽ 0)
- Tuple (g, σ) representing cutting plane gᵀ(y - x) ≥ σ otherwise
"""
def get_elem(i, j):
"""Construct element (i,j) of F(x) = ∑ x_k F_k
Enables on-demand element computation without full matrix construction.
This sparse approach saves memory for large-scale problems.
"""
n = len(x)
return sum(self.mat_f[k][i, j] * x[k] for k in range(n))
# Attempt LDLT factorization (fails if matrix not PSD)
if not self.ldlt_mgr.factor(get_elem):
# Compute infeasibility certificate
ep = self.ldlt_mgr.witness() # Witness vector v such that vᵀF(x)v < 0
# Calculate subgradient components: g_k = -vᵀF_k v
g = np.array([-self.ldlt_mgr.sym_quad(Fk) for Fk in self.mat_f])
return g, ep
return None