Source code for mpt4py.solvers.lcp
import numpy as np
from typing import Union
from dataclasses import dataclass, field
from mpt4py.base import Vector, Matrix
from .lcp_solver import lcp_solve, LcpSolution
from .plcp_solver import plcp_solve, PlcpSolution
[docs]
@dataclass
class LinearComplementarityProblem:
r"""
Encapsulates data and solutions for LCP and pLCP.
.. math::
w - Mz = q + Q\theta, w,z \geq 0, w^\top z = 0
"""
# LCP
M: Matrix = field(default_factory=lambda: np.zeros((0, 0)))
q: Vector = field(default_factory=lambda: np.zeros((0,)))
Q: Matrix = field(default_factory=lambda: np.zeros((0, 0)))
# parameter set: Ath*th <= bth
Ath: Matrix = field(default_factory=lambda: np.zeros((0, 0)))
bth: Vector = field(default_factory=lambda: np.zeros((0,)))
n: int = field(init=False) # problem dimension
d: int = field(init=False) # number of parameters
def __post_init__(self):
"""
Check if the dimensions and values are valid
If M is non-empty, then the problem is considered to be an LCP
otherwise it is considered to be LP/QP
"""
# make sure the inputs are matrices and vectors
for attr in ['M', 'Q', 'Ath']:
if np.ndim(getattr(self, attr)) != 2:
raise ValueError("Opt: " + attr + " must be a matrix")
for attr in ['q', 'bth']:
if np.ndim(getattr(self, attr)) != 1:
raise ValueError("Opt: " + attr + " must be a vector")
# validate LCP inputs
if self.M.size:
self.n = self.M.shape[0]
self.d = max(self.Q.shape[1], self.Ath.shape[1])
if self.M.shape[0] != self.M.shape[1]:
raise ValueError("Opt: M must be square. The size of provided M is {}".format(self.M.shape))
if self.q.shape != (self.n,):
raise ValueError("Opt: q must be of size ({},)".format(self.n))
if self.Q.size:
if self.Q.shape[0] != self.n or self.Q.shape[1] != self.d:
raise ValueError("Opt: Q must be of size ({},{})".format(self.n, self.d))
else:
self.Q = np.zeros((self.n, 0))
if self.Ath.size:
if self.Ath.shape[1] != self.d:
raise ValueError("Opt: Ath must have {} columns)".format(self.d))
else:
self.Ath = np.zeros((0, self.d))
if self.bth.size != self.Ath.shape[0]:
raise ValueError("Opt: bth must be of size ({},))".format(self.Ath.shape[0]))
self.problem_type = 'LCP'
@property
def is_parametric(self) -> bool:
return self.d > 0
def __str__(self):
lines = [
"Linear Complementarity Problem",
f"Num variables: {self.n}",
]
if self.is_parametric:
lines[0] = "Parametric " + lines[0]
lines.append(f"Num parameters: {self.d}")
max_line_length = max(len(line) for line in lines)
dashed_line = "-" * max_line_length
return f"\n{dashed_line}\n" + "\n".join(lines) + f"\n{dashed_line}"
[docs]
def solve(self, **kwargs) -> Union[LcpSolution, PlcpSolution]:
"""
Solve the LCP / pLCP
"""
if not self.is_parametric:
return lcp_solve(self.M, self.q, **kwargs)
else:
raise plcp_solve(self.M, self.Q, self.q, self.Ath, self.bth, **kwargs)
[docs]
def solve_for_param(self, theta: Vector, **kwargs) -> LcpSolution:
"""
Solve the LCP / pLCP for a specific parameter value
"""
if not self.is_parametric:
raise ValueError("Cannot solve for a specific parameter value for non-parametric LCP.")
else:
assert theta.shape == (self.d,), f"theta must be of size ({self.d},)"
return lcp_solve(self.M, self.Q @ theta + self.q, **kwargs)
LCP = LinearComplementarityProblem