Source code for sdpb_python.lmi

"""Linear matrix inequalities: ``maximize f + b.y  s.t.  M_0 + sum_n y_n M_n >= 0``."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Sequence

from . import numbers as _num
from . import _mpi
from .errors import wrap_cpp_error
from .options import SolverOptions, effective_precision, resolve
from .solution import Solution


def _ext():
    from . import _sdpb

    return _sdpb


[docs] @dataclass class LMI: """Block-diagonal linear matrix inequality: maximize ``f + b . y`` over ``y`` in ``R^N`` such that ``M_0 + sum_n y_n M_n`` is positive semidefinite, block by block. ``blocks[j]`` is the sequence ``(M_0, M_1, ..., M_N)`` of symmetric matrices for block ``j`` (nested lists, numpy arrays or ``mpmath.matrix``). To minimise, negate ``b`` and ``f``. Internally this is a PMP whose polynomials are constants, so every block has a single sample point. """ b: Sequence[Any] blocks: Sequence[Sequence[Any]] f: Any = 0 def __post_init__(self): self.b = list(self.b) self.blocks = [list(block) for block in self.blocks] if not self.blocks: raise ValueError("LMI needs at least one block") n = len(self.b) + 1 for j, block in enumerate(self.blocks): if len(block) != n: raise ValueError(f"blocks[{j}] has {len(block)} matrices, expected N+1 = {n}") def _to_spec(self, bits: int) -> dict: blocks = [] for j, block in enumerate(self.blocks): mats = [_num.matrix_strs(m, bits) for m in block] d = len(mats[0]) for n, m in enumerate(mats): if len(m) != d or any(len(row) != d for row in m): raise ValueError(f"blocks[{j}][{n}] must be {d}x{d}") for r in range(d): for s in range(r + 1, d): if m[r][s] != m[s][r]: raise ValueError(f"blocks[{j}][{n}] is not symmetric at ({r},{s})") blocks.append(mats) return {"f": _num.to_str(self.f, bits), "b": _num.strs(self.b, bits), "blocks": blocks}
[docs] def solver(self, options: SolverOptions | None = None, **overrides: Any): """A :class:`~sdpb_python.handle.Solver` holding this problem's state.""" from .handle import Solver opts = resolve(options, overrides) return Solver("lmi", self._to_spec(effective_precision(opts.precision)), opts)
[docs] def solve(self, options: SolverOptions | None = None, **overrides: Any) -> Solution: opts = resolve(options, overrides) _mpi.check_single_process(_ext().mpi_size()) try: d = _ext().solve_lmi(self._to_spec(effective_precision(opts.precision)), opts._to_dict()) except Exception as exc: raise wrap_cpp_error(exc) from None return Solution._from_dict(d)