use crate::core::math::Scalar;
#[cfg(test)]
#[path = "model/integer_domain_api_prototype.rs"]
mod integer_domain_api_prototype;
pub trait ProblemConstraints {
type Constraints: ConstraintDescription;
fn constraint_set(
&self,
) -> <Self::Constraints as ConstraintDescription>::Borrowed<'_>;
}
mod sealed {
pub trait Set {}
}
pub trait ConstraintDescription: sealed::Set {
type Borrowed<'a>
where
Self: 'a;
fn borrowed(&self) -> Self::Borrowed<'_>;
}
pub trait BorrowConstraint {
type Borrowed<'a>
where
Self: 'a;
fn borrowed(&self) -> Self::Borrowed<'_>;
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Constraints<
B = NoBounds,
L = NoLinearConstraints,
N = NoNonlinearConstraints,
> {
pub(crate) bounds: B,
pub(crate) linear: L,
pub(crate) nonlinear: N,
}
impl<B, L, N> sealed::Set for Constraints<B, L, N> {}
impl<B: BorrowConstraint, L: BorrowConstraint, N: BorrowConstraint>
ConstraintDescription for Constraints<B, L, N>
{
type Borrowed<'a>
= Constraints<B::Borrowed<'a>, L::Borrowed<'a>, N::Borrowed<'a>>
where
Self: 'a;
fn borrowed(&self) -> Self::Borrowed<'_> {
Constraints {
bounds: self.bounds.borrowed(),
linear: self.linear.borrowed(),
nonlinear: self.nonlinear.borrowed(),
}
}
}
pub type NoConstraints = Constraints;
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NoBounds;
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NoLinearBlock;
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NoNonlinearBlock;
pub type NoLinearConstraints = LinearBlocks<NoLinearBlock, NoLinearBlock>;
pub type NoNonlinearConstraints =
NonlinearBlocks<NoNonlinearBlock, NoNonlinearBlock>;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BoxBounds<V> {
lower: Option<V>,
upper: Option<V>,
}
impl<V> BoxBounds<V> {
pub fn new(lower: Option<V>, upper: Option<V>) -> Self {
Self { lower, upper }
}
pub fn lower(&self) -> Option<&V> {
self.lower.as_ref()
}
pub fn upper(&self) -> Option<&V> {
self.upper.as_ref()
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LinearBlock<M, R> {
pub(crate) matrix: Option<M>,
pub(crate) rhs: Option<R>,
}
impl<M, R> LinearBlock<M, R> {
pub fn new(matrix: M, rhs: R) -> Self {
Self {
matrix: Some(matrix),
rhs: Some(rhs),
}
}
pub fn optional(data: Option<(M, R)>) -> Self {
match data {
Some((m, r)) => Self::new(m, r),
None => Self {
matrix: None,
rhs: None,
},
}
}
pub fn matrix(&self) -> Option<&M> {
self.matrix.as_ref()
}
pub fn rhs(&self) -> Option<&R> {
self.rhs.as_ref()
}
}
#[cfg(feature = "serde")]
impl<'de, M, R> serde::Deserialize<'de> for LinearBlock<M, R>
where
M: serde::Deserialize<'de>,
R: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
#[serde(rename = "LinearBlock")]
struct StoredLinearBlock<M, R> {
matrix: Option<M>,
rhs: Option<R>,
}
let stored = StoredLinearBlock::<M, R>::deserialize(deserializer)?;
if stored.matrix.is_some() != stored.rhs.is_some() {
return Err(serde::de::Error::custom(
"linear block matrix and rhs must both be present or both be absent",
));
}
Ok(Self {
matrix: stored.matrix,
rhs: stored.rhs,
})
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LinearBlocks<E = NoLinearBlock, I = NoLinearBlock> {
pub(crate) equalities: E,
pub(crate) inequalities: I,
}
impl<E, I> LinearBlocks<E, I> {
pub fn equalities(&self) -> &E {
&self.equalities
}
pub fn inequalities(&self) -> &I {
&self.inequalities
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NonlinearBlock {
pub(crate) count: usize,
}
impl NonlinearBlock {
pub fn count(&self) -> usize {
self.count
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NonlinearBlocks<E = NoNonlinearBlock, I = NoNonlinearBlock> {
pub(crate) equalities: E,
pub(crate) inequalities: I,
}
impl<E, I> NonlinearBlocks<E, I> {
pub fn equalities(&self) -> &E {
&self.equalities
}
pub fn inequalities(&self) -> &I {
&self.inequalities
}
}
impl NoConstraints {
pub const EMPTY: Self = Self {
bounds: NoBounds,
linear: LinearBlocks {
equalities: NoLinearBlock,
inequalities: NoLinearBlock,
},
nonlinear: NonlinearBlocks {
equalities: NoNonlinearBlock,
inequalities: NoNonlinearBlock,
},
};
pub const fn new() -> Self {
Self::EMPTY
}
}
impl Default for NoConstraints {
fn default() -> Self {
Self::new()
}
}
impl<B, L, N> Constraints<B, L, N> {
pub fn bounds(&self) -> &B {
&self.bounds
}
pub fn linear(&self) -> &L {
&self.linear
}
pub fn nonlinear(&self) -> &N {
&self.nonlinear
}
pub fn with_bounds<V>(
self,
bounds: BoxBounds<V>,
) -> Constraints<BoxBounds<V>, L, N> {
Constraints {
bounds,
linear: self.linear,
nonlinear: self.nonlinear,
}
}
}
impl<B, E, I, N> Constraints<B, LinearBlocks<E, I>, N> {
pub fn with_linear_equalities<M, R>(
self,
block: LinearBlock<M, R>,
) -> Constraints<B, LinearBlocks<LinearBlock<M, R>, I>, N> {
Constraints {
bounds: self.bounds,
linear: LinearBlocks {
equalities: block,
inequalities: self.linear.inequalities,
},
nonlinear: self.nonlinear,
}
}
pub fn with_linear_inequalities<M, R>(
self,
block: LinearBlock<M, R>,
) -> Constraints<B, LinearBlocks<E, LinearBlock<M, R>>, N> {
Constraints {
bounds: self.bounds,
linear: LinearBlocks {
equalities: self.linear.equalities,
inequalities: block,
},
nonlinear: self.nonlinear,
}
}
}
impl<B, L, E, I> Constraints<B, L, NonlinearBlocks<E, I>> {
pub fn with_nonlinear_equalities(
self,
count: usize,
) -> Constraints<B, L, NonlinearBlocks<NonlinearBlock, I>> {
Constraints {
bounds: self.bounds,
linear: self.linear,
nonlinear: NonlinearBlocks {
equalities: NonlinearBlock { count },
inequalities: self.nonlinear.inequalities,
},
}
}
pub fn with_nonlinear_inequalities(
self,
count: usize,
) -> Constraints<B, L, NonlinearBlocks<E, NonlinearBlock>> {
Constraints {
bounds: self.bounds,
linear: self.linear,
nonlinear: NonlinearBlocks {
equalities: self.nonlinear.equalities,
inequalities: NonlinearBlock { count },
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConstraintBlock {
Bounds,
LinearEqualities,
LinearInequalities,
NonlinearEqualities,
NonlinearInequalities,
Jacobian,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BoundSide {
Lower,
Upper,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConstraintAxis {
Length,
Rows,
Columns,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConstraintError {
NaNBound {
side: BoundSide,
coordinate: usize,
},
InvalidInfinity {
side: BoundSide,
coordinate: usize,
},
InvertedBounds {
coordinate: usize,
},
DimensionMismatch {
block: ConstraintBlock,
axis: ConstraintAxis,
expected: usize,
actual: usize,
},
MissingBound {
side: BoundSide,
},
NonFiniteInterval {
coordinate: usize,
},
NonFiniteLinearData {
block: ConstraintBlock,
},
LayoutChanged,
CountOverflow,
}
impl std::fmt::Display for ConstraintError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid constraints: {self:?}")
}
}
impl std::error::Error for ConstraintError {}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConstraintSolveError<E> {
InvalidConstraints(ConstraintError),
Evaluation(E),
}
impl<E> From<E> for ConstraintSolveError<E> {
fn from(error: E) -> Self {
Self::Evaluation(error)
}
}
impl<E: std::fmt::Display> std::fmt::Display for ConstraintSolveError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidConstraints(e) => e.fmt(f),
Self::Evaluation(e) => write!(f, "problem evaluation failed: {e}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error
for ConstraintSolveError<E>
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidConstraints(e) => Some(e),
Self::Evaluation(e) => Some(e),
}
}
}
pub trait NonlinearInequalityValues: ProblemConstraints {
type Param;
type Values;
type Error;
fn nonlinear_inequalities(
&self,
x: &Self::Param,
) -> Result<Self::Values, ConstraintSolveError<Self::Error>>;
}
pub trait NonlinearEqualityValues: ProblemConstraints {
type Param;
type Values;
type Error;
fn nonlinear_equalities(
&self,
x: &Self::Param,
) -> Result<Self::Values, ConstraintSolveError<Self::Error>>;
}
pub(crate) fn dimension(
block: ConstraintBlock,
axis: ConstraintAxis,
expected: usize,
actual: usize,
) -> Result<(), ConstraintError> {
if expected == actual {
Ok(())
} else {
Err(ConstraintError::DimensionMismatch {
block,
axis,
expected,
actual,
})
}
}
pub(crate) fn validate_bound<F: Scalar>(
value: F,
side: BoundSide,
coordinate: usize,
) -> Result<(), ConstraintError> {
if value.is_nan() {
return Err(ConstraintError::NaNBound { side, coordinate });
}
if (side == BoundSide::Lower && value == F::infinity())
|| (side == BoundSide::Upper && value == F::neg_infinity())
{
return Err(ConstraintError::InvalidInfinity { side, coordinate });
}
Ok(())
}
macro_rules! borrow_copy {
($($ty:ty),* $(,)?) => {$ (
impl BorrowConstraint for $ty {
type Borrowed<'a> = Self where Self: 'a;
fn borrowed(&self) -> Self { *self }
}
)*};
}
borrow_copy!(NoBounds, NoLinearBlock, NoNonlinearBlock, NonlinearBlock);
impl<V> BorrowConstraint for BoxBounds<V> {
type Borrowed<'a>
= BoxBounds<&'a V>
where
Self: 'a;
fn borrowed(&self) -> Self::Borrowed<'_> {
BoxBounds::new(self.lower(), self.upper())
}
}
impl<M, R> BorrowConstraint for LinearBlock<M, R> {
type Borrowed<'a>
= LinearBlock<&'a M, &'a R>
where
Self: 'a;
fn borrowed(&self) -> Self::Borrowed<'_> {
LinearBlock::optional(self.matrix.as_ref().zip(self.rhs.as_ref()))
}
}
impl<E: BorrowConstraint, I: BorrowConstraint> BorrowConstraint
for LinearBlocks<E, I>
{
type Borrowed<'a>
= LinearBlocks<E::Borrowed<'a>, I::Borrowed<'a>>
where
Self: 'a;
fn borrowed(&self) -> Self::Borrowed<'_> {
LinearBlocks {
equalities: self.equalities.borrowed(),
inequalities: self.inequalities.borrowed(),
}
}
}
impl<E: BorrowConstraint, I: BorrowConstraint> BorrowConstraint
for NonlinearBlocks<E, I>
{
type Borrowed<'a>
= NonlinearBlocks<E::Borrowed<'a>, I::Borrowed<'a>>
where
Self: 'a;
fn borrowed(&self) -> Self::Borrowed<'_> {
NonlinearBlocks {
equalities: self.equalities.borrowed(),
inequalities: self.inequalities.borrowed(),
}
}
}