#![doc = include_str!("../../js/mathjax.tag")]
use itertools::Either;
use super::matrix::NDArray;
use crate::utils::*;
mod psd;
mod ranged;
mod linear;
pub use psd::*;
pub use ranged::*;
pub use linear::*;
#[derive(Clone,Copy)]
pub enum AsymmetricConeType {
Primal,
Dual
}
#[derive(Clone,Copy)]
pub enum QuadraticCone { Normal, Rotated }
#[derive(Clone,Copy)]
pub struct SVecPSDCone();
#[derive(Clone,Copy)]
pub struct GeometricMeanCone(pub AsymmetricConeType);
#[derive(Clone,Copy)]
pub struct ExponentialCone(pub AsymmetricConeType);
#[derive(Clone)]
pub struct PowerCone(pub Vec<f64>,pub AsymmetricConeType);
#[derive(Clone,Copy)]
pub struct LinearCone(LinearDomainType);
pub trait VectorDomainTrait {
fn check_conesize(&self, d : usize) -> Result<(),String>;
fn to_conic_domain_type(&self) -> VectorDomainType;
}
pub trait DomainTrait<const N : usize> { }
pub trait IntoDomain {
type Result : 'static;
fn try_into_domain(self) -> Result<Self::Result,String>;
}
pub trait IntoShapedDomain<const N : usize> {
type Result : DomainTrait<N>+'static;
fn try_into_domain(self,shape : [usize;N]) -> Result<Self::Result,String>;
}
pub trait IntoVectorDomain<const N : usize,D> where D : VectorDomainTrait {
fn into_conic(self) -> VectorDomain<N,D>;
}
impl VectorDomainTrait for QuadraticCone {
fn check_conesize(&self, d : usize) -> Result<(),String> {
match self {
QuadraticCone::Normal => if d >= 1 { Ok(()) } else { Err("Invalid dimension for quadratic cone".to_string()) },
QuadraticCone::Rotated => if d >= 2 { Ok(()) } else { Err("Invalid dimension for rotated quadratic cone".to_string()) },
}
}
fn to_conic_domain_type(&self) -> VectorDomainType {
match self {
QuadraticCone::Normal => VectorDomainType::QuadraticCone,
QuadraticCone::Rotated => VectorDomainType::RotatedQuadraticCone,
}
}
}
impl VectorDomainTrait for SVecPSDCone {
fn check_conesize(&self, d : usize) -> Result<(),String> {
if d < 1 {
return Err(format!("Size of SVecPSDCone must be at least 1, got: {}", d));
}
let n = ((((1 + d*8) as f64).sqrt()-1.0)/2.0) as usize;
if n * (n+1)/2 != d {
return Err(format!("Size of SVecPSDCone must correspond to the lower triangular part of a square matrix, but got: {}", d));
}
Ok(())
}
fn to_conic_domain_type(&self) -> VectorDomainType {
VectorDomainType::SVecPSDCone
}
}
impl VectorDomainTrait for GeometricMeanCone {
fn check_conesize(&self, d : usize) -> Result<(),String> { if d >= 1 { Ok(()) } else { Err("Invalid dimension for geometric mean code".to_string()) } }
fn to_conic_domain_type(&self) -> VectorDomainType {
match self.0 {
AsymmetricConeType::Primal => VectorDomainType::GeometricMeanCone,
AsymmetricConeType::Dual => VectorDomainType::DualGeometricMeanCone
}
}
}
impl VectorDomainTrait for ExponentialCone {
fn check_conesize(&self, d : usize) -> Result<(),String> { if d == 3 { Ok(()) } else { Err("Invalid dimension for exponential code".to_string()) } }
fn to_conic_domain_type(&self) -> VectorDomainType {
match self.0 {
AsymmetricConeType::Primal => VectorDomainType::ExponentialCone,
AsymmetricConeType::Dual => VectorDomainType::DualExponentialCone
}
}
}
impl VectorDomainTrait for PowerCone {
fn check_conesize(&self, d : usize) -> Result<(),String> {
if d >= self.0.len() { Ok(()) } else { Err("Invalid dimension for power cone".to_string()) }
}
fn to_conic_domain_type(&self) -> VectorDomainType {
match self.1 {
AsymmetricConeType::Primal => VectorDomainType::PrimalPowerCone(self.0.clone()),
AsymmetricConeType::Dual => VectorDomainType::DualPowerCone(self.0.clone())
}
}
}
impl VectorDomainTrait for LinearCone {
fn check_conesize(&self, _d : usize) -> Result<(),String> { Ok(()) }
fn to_conic_domain_type(&self) -> VectorDomainType {
match self.0 {
LinearDomainType::Zero => VectorDomainType::Zero,
LinearDomainType::Free => VectorDomainType::Free,
LinearDomainType::NonNegative => VectorDomainType::NonNegative,
LinearDomainType::NonPositive => VectorDomainType::NonPositive
}
}
}
#[derive(Clone)]
pub enum VectorDomainType {
QuadraticCone,
RotatedQuadraticCone,
SVecPSDCone,
GeometricMeanCone,
DualGeometricMeanCone,
ExponentialCone,
DualExponentialCone,
PrimalPowerCone(Vec<f64>),
DualPowerCone(Vec<f64>),
NonNegative,
NonPositive,
Zero,
Free
}
impl<const N : usize> DomainTrait<N> for LinearDomain<N> {}
impl<const N : usize,D> DomainTrait<N> for VectorDomain<N,D> where D : VectorDomainTrait {}
impl<const N : usize> DomainTrait<N> for PSDDomain<N> {}
impl<const N : usize> DomainTrait<N> for LinearRangeDomain<N> {}
pub struct ScalableVectorDomain<D> where D : VectorDomainTrait {
domain_type : D,
cone_dim : Option<usize>,
is_integer : bool
}
pub struct VectorProtoDomain<const N : usize,D> where D : VectorDomainTrait {
shape : [usize;N],
domain_type : D,
offset : Vec<f64>,
cone_dim : usize,
is_integer : bool,
}
pub struct VectorDomain<const N : usize, D> {
domain_type : D,
offset : Vec<f64>,
shape : [usize; N],
conedim : usize,
is_integer : bool
}
impl<D> ScalableVectorDomain<D> where D : VectorDomainTrait {
pub fn with_shape<const N : usize>(self, shape : &[usize;N]) -> VectorProtoDomain<N,D> {
let cone_dim = if let Some(cd) = self.cone_dim { cd } else { N.max(1) - 1 };
VectorProtoDomain{
shape : *shape,
cone_dim,
offset : vec![0.0; shape.iter().product()],
domain_type : self.domain_type,
is_integer : self.is_integer
}
}
pub fn with_conedim(self,cone_dim : usize) -> Self { ScalableVectorDomain{ cone_dim : Some(cone_dim), ..self } }
pub fn integer(self) -> Self { ScalableVectorDomain{is_integer : true, ..self} }
pub fn continuous(self) -> Self { ScalableVectorDomain{is_integer : false, ..self} }
}
impl<D> IntoDomain for ScalableVectorDomain<D> where D : VectorDomainTrait+'static {
type Result = VectorDomain<0,D>;
fn try_into_domain(self) -> Result<Self::Result,String> {
Err(format!("Domain size or shape cannot be determined"))
}
}
impl<const N : usize,D> IntoShapedDomain<N> for ScalableVectorDomain<D> where D : VectorDomainTrait+'static {
type Result = VectorDomain<N,D>;
fn try_into_domain(self,shape : [usize;N]) -> Result<Self::Result,String> {
let cd =
if shape.len() == 0 {
1
}
else if let Some(d) = self.cone_dim {
*shape.get(d).ok_or_else(|| ("Invalid cone dimension index for this shape".to_string()))?
}
else {
shape[N-1]
};
self.domain_type.check_conesize(cd)?;
Ok(VectorDomain{
domain_type : self.domain_type,
offset: vec![0.0; shape.iter().product()],
shape,
conedim : N-1,
is_integer : self.is_integer
})
}
}
impl<const N : usize,D> VectorProtoDomain<N,D> where D : VectorDomainTrait {
pub fn with_shape<const M : usize>(self, shape : &[usize;M]) -> VectorProtoDomain<M,D> {
VectorProtoDomain{
shape : *shape,
domain_type : self.domain_type,
offset : self.offset,
cone_dim : self.cone_dim,
is_integer : self.is_integer
}
}
pub fn with_conedim(self,cone_dim : usize) -> Self { VectorProtoDomain{ cone_dim, ..self }}
pub fn with_offset(self,offset : Vec<f64>) -> Self { VectorProtoDomain{ offset, ..self }}
pub fn integer(self) -> Self { VectorProtoDomain{ is_integer : true, ..self } }
pub fn continuous(self) -> Self { VectorProtoDomain{ is_integer : false, ..self } }
}
impl<const N : usize,D> IntoDomain for VectorProtoDomain<N,D> where D : VectorDomainTrait+'static {
type Result = VectorDomain<N,D>;
fn try_into_domain(self) -> Result<Self::Result,String> {
if self.offset.len() != self.shape.iter().product::<usize>() {
return Err(format!("Domain offset length does not match shape"));
}
if self.cone_dim >= N {
return Err(format!("Domain has invalid cone dimension, expected 0..{}, got {}",N-1,self.cone_dim));
}
let cd = self.shape[self.cone_dim];
self.domain_type.check_conesize(cd)?;
Ok(VectorDomain{
domain_type : self.domain_type,
offset: self.offset,
shape : self.shape,
conedim : self.cone_dim,
is_integer : self.is_integer
})
}
}
impl<const N : usize,D> IntoShapedDomain<N> for VectorProtoDomain<N,D> where D : VectorDomainTrait+'static {
type Result = VectorDomain<N,D>;
fn try_into_domain(self,shape : [usize;N]) -> Result<Self::Result,String> {
let dom = IntoDomain::try_into_domain(self)?;
if dom.shape != shape {
Err(format!("Domain shape did not match the expected shape: {:?} vs {:?}",dom.shape,shape))
}
else {
Ok(dom)
}
}
}
impl<const N :usize,D> VectorDomain<N,D> where D : VectorDomainTrait+'static {
pub fn dissolve(self) -> (D,Vec<f64>,[usize;N],usize,bool) { (self.domain_type,self.offset,self.shape,self.conedim,self.is_integer) }
pub fn get(&self) -> (&D,&[f64],&[usize;N],usize,bool) { (&self.domain_type,self.offset.as_slice(),&self.shape,self.conedim,self.is_integer) }
}
pub trait OffsetTrait {
type Result;
fn greater_than(self) -> Self::Result;
fn less_than(self) -> Self::Result;
fn equal_to(self) -> Self::Result;
}
pub fn zeros<const N : usize>(shape : &[usize; N]) -> LinearProtoDomain<N> { zero().with_shape(shape) }
pub fn greater_than<T : OffsetTrait>(v : T) -> T::Result { v.greater_than() }
pub fn less_than<T : OffsetTrait>(v : T) -> T::Result { v.less_than() }
pub fn equal_to<T : OffsetTrait>(v : T) -> T::Result { v.equal_to() }
pub fn in_quadratic_cone() -> ScalableVectorDomain<QuadraticCone> {
ScalableVectorDomain{ domain_type: QuadraticCone::Normal, is_integer : false, cone_dim : None}
}
pub fn in_rotated_quadratic_cone() -> ScalableVectorDomain<QuadraticCone> {
ScalableVectorDomain{ domain_type: QuadraticCone::Rotated, is_integer : false, cone_dim : None}
}
pub fn in_svecpsd_cone() -> ScalableVectorDomain<SVecPSDCone> {
ScalableVectorDomain{ domain_type: SVecPSDCone(), is_integer : false, cone_dim : None}
}
pub fn in_geometric_mean_cone() -> ScalableVectorDomain<GeometricMeanCone> {
ScalableVectorDomain{ domain_type: GeometricMeanCone(AsymmetricConeType::Primal), is_integer : false, cone_dim : None}
}
pub fn in_dual_geometric_mean_cone() -> ScalableVectorDomain<GeometricMeanCone> {
ScalableVectorDomain{ domain_type: GeometricMeanCone(AsymmetricConeType::Dual), is_integer : false, cone_dim : None}
}
pub fn in_exponential_cone() -> ScalableVectorDomain<ExponentialCone> {
ScalableVectorDomain{ domain_type: ExponentialCone(AsymmetricConeType::Primal), is_integer : false, cone_dim : None}
}
pub fn in_dual_exponential_cone() -> ScalableVectorDomain<ExponentialCone> {
ScalableVectorDomain{ domain_type: ExponentialCone(AsymmetricConeType::Dual), is_integer : false, cone_dim : None}
}
pub fn in_power_cone(alpha : &[f64]) -> ScalableVectorDomain<PowerCone> {
let s : f64 = alpha.iter().sum();
ScalableVectorDomain{
domain_type : PowerCone(alpha.iter().map(|a| a/s).collect(),AsymmetricConeType::Primal),
is_integer : false,
cone_dim : None} }
pub fn in_dual_power_cone(alpha : &[f64]) -> ScalableVectorDomain<PowerCone> {
let s : f64 = alpha.iter().sum();
ScalableVectorDomain{
domain_type : PowerCone(alpha.iter().map(|a| a/s).collect(),AsymmetricConeType::Dual),
is_integer : false,
cone_dim : None }
}
fn in_cones<const N : usize,D>(shape : &[usize; N], cone_dim : usize,domain_type : D) -> VectorProtoDomain<N,D> where D : VectorDomainTrait {
if cone_dim >= shape.len() {
panic!("Invalid cone dimension");
}
VectorProtoDomain{domain_type,
offset : vec![0.0; shape.iter().product()],
shape:*shape,
cone_dim,
is_integer : false}
}
pub fn in_quadratic_cones<const N : usize>(shape : &[usize; N], conedim : usize) -> VectorProtoDomain<N,QuadraticCone> {
in_cones(shape,conedim,QuadraticCone::Normal)
}
pub fn in_rotated_quadratic_cones<const N : usize>(shape : &[usize; N], conedim : usize) -> VectorProtoDomain<N,QuadraticCone> {
in_cones(shape,conedim,QuadraticCone::Rotated)
}
pub fn in_svecpsd_cones<const N : usize>(shape : &[usize; N], conedim : usize) -> VectorProtoDomain<N,SVecPSDCone> {
let dim = shape[conedim];
let n = ((-1.0 + (1.0+8.0*dim as f64).sqrt())/2.0).floor() as usize;
if n * (n+1)/2 != dim { panic!("Invalid dimension {} for svecpsd cone", dim) }
in_cones(shape,conedim,SVecPSDCone())
}
pub fn in_geometric_mean_cones<const N : usize>(shape : &[usize; N], conedim : usize) -> VectorProtoDomain<N,GeometricMeanCone> {
in_cones(shape,conedim,GeometricMeanCone(AsymmetricConeType::Primal))
}
pub fn in_dual_geometric_mean_cones<const N : usize>(shape : &[usize; N], conedim : usize) -> VectorProtoDomain<N,GeometricMeanCone> {
in_cones(shape,conedim,GeometricMeanCone(AsymmetricConeType::Dual))
}
pub fn in_exponential_cones<const N : usize>(shape : &[usize; N], conedim : usize) -> VectorProtoDomain<N,ExponentialCone> {
if let Some(&d) = shape.get(conedim) { if d != 3 { panic!("Invalid shape or exponential cone") } }
in_cones(shape,conedim,ExponentialCone(AsymmetricConeType::Primal))
}
pub fn in_dual_exponential_cones<const N : usize>(shape : &[usize; N], conedim : usize) -> VectorProtoDomain<N,ExponentialCone> {
if let Some(&d) = shape.get(conedim) { if d != 3 { panic!("Invalid shape or exponential cone") } }
in_cones(shape,conedim,ExponentialCone(AsymmetricConeType::Dual))
}
pub fn in_power_cones<const N : usize>(shape : &[usize;N], cone_dim : usize, alpha : &[f64]) -> VectorProtoDomain<N,PowerCone> {
let alphasum : f64 = alpha.iter().sum();
VectorProtoDomain{
domain_type:PowerCone(alpha.iter().map(|&a| a / alphasum ).collect(),AsymmetricConeType::Primal),
shape : *shape,
offset:vec![0.0; shape.iter().product()],
cone_dim,
is_integer : false}
}
pub fn in_dual_power_cones<const N : usize>(shape : &[usize;N], cone_dim : usize, alpha : &[f64]) -> VectorProtoDomain<N,PowerCone> {
let alphasum : f64 = alpha.iter().sum();
VectorProtoDomain{
domain_type:PowerCone(alpha.iter().map(|&a| a / alphasum ).collect(),AsymmetricConeType::Dual),
shape : *shape,
offset:vec![0.0; shape.iter().product()],
cone_dim,
is_integer : false}
}
pub fn in_range<T>(lower : T, upper : T) -> T::Result where T : IntoProtoRangeBound {
lower.make(upper)
}