use super::*;
use crate::algebra::*;
use itertools::izip;
use std::iter::zip;
pub struct GenPowerConeData<T> {
grad: Vec<T>,
z: Vec<T>,
pub μ: T,
pub p: Vec<T>,
pub q: Vec<T>,
pub r: Vec<T>,
pub d1: Vec<T>,
d2: T,
ψ: T,
work: Vec<T>,
work_pb: Vec<T>,
}
impl<T> GenPowerConeData<T>
where
T: FloatT,
{
pub fn new(α: &[T], dim2: usize) -> Self {
let dim1 = α.len();
let dim = dim1 + dim2;
assert!(α.iter().all(|r| *r > T::zero())); assert!((T::one() - α.sum()).abs() < (T::epsilon() * α.len().as_T() * (0.5).as_T()));
Self {
grad: vec![T::zero(); dim],
z: vec![T::zero(); dim],
μ: T::one(),
p: vec![T::zero(); dim],
q: vec![T::zero(); dim1],
r: vec![T::zero(); dim2],
d1: vec![T::zero(); dim1],
d2: T::zero(),
ψ: T::one() / (α.sumsq()),
work: vec![T::zero(); dim],
work_pb: vec![T::zero(); dim],
}
}
}
pub struct GenPowerCone<T> {
pub α: Vec<T>, dim2: usize, pub data: Box<GenPowerConeData<T>>, }
impl<T> GenPowerCone<T>
where
T: FloatT,
{
pub fn new(α: Vec<T>, dim2: usize) -> Self {
let data = Box::new(GenPowerConeData::<T>::new(&α, dim2));
Self { α, dim2, data }
}
pub fn dim1(&self) -> usize {
self.α.len()
}
pub fn dim2(&self) -> usize {
self.dim2
}
pub fn dim(&self) -> usize {
self.dim1() + self.dim2()
}
}
impl<T> Cone<T> for GenPowerCone<T>
where
T: FloatT,
{
fn degree(&self) -> usize {
self.dim1() + 1
}
fn numel(&self) -> usize {
self.dim()
}
fn is_symmetric(&self) -> bool {
false
}
fn is_sparse_expandable(&self) -> bool {
true
}
fn allows_primal_dual_scaling(&self) -> bool {
false
}
fn rectify_equilibration(&self, δ: &mut [T], e: &[T]) -> bool {
δ.copy_from(e).recip().scale(e.mean());
true }
fn margins(&mut self, _z: &mut [T], _pd: PrimalOrDualCone) -> (T, T) {
unreachable!();
}
fn scaled_unit_shift(&self, _z: &mut [T], _α: T, _pd: PrimalOrDualCone) {
unreachable!();
}
fn unit_initialization(&self, z: &mut [T], s: &mut [T]) {
let α = &self.α;
let dim1 = self.dim1();
s[..dim1].scalarop_from(|αi| T::sqrt(T::one() + αi), α);
s[dim1..].set(T::zero());
z.copy_from(s);
}
fn set_identity_scaling(&mut self) {
unreachable!();
}
fn update_scaling(
&mut self,
_s: &[T],
z: &[T],
μ: T,
_scaling_strategy: ScalingStrategy,
) -> bool {
self.update_dual_grad_H(z);
self.data.μ = μ;
self.data.z.copy_from(z);
true
}
fn Hs_is_diagonal(&self) -> bool {
true
}
fn get_Hs(&self, Hsblock: &mut [T]) {
let dim1 = self.dim1();
let data = &self.data;
Hsblock[..dim1].scalarop_from(|d1| data.μ * d1, &data.d1);
Hsblock[dim1..].set(data.μ * data.d2);
}
fn mul_Hs(&mut self, y: &mut [T], x: &[T], _work: &mut [T]) {
let dim1 = self.dim1();
let data = &self.data;
let coef_p = data.p.dot(x);
let coef_q = data.q.dot(&x[..dim1]);
let coef_r = data.r.dot(&x[dim1..]);
for (y, &x, &d1, &q) in izip!(&mut y[..dim1], &x[..dim1], &data.d1, &data.q) {
*y = d1 * x - coef_q * q;
}
for (y, &x, &r) in izip!(&mut y[dim1..], &x[dim1..], &data.r) {
*y = data.d2 * x - coef_r * r;
}
y.axpby(coef_p, &data.p, T::one());
y.scale(data.μ);
}
fn affine_ds(&self, ds: &mut [T], s: &[T]) {
ds.copy_from(s);
}
fn combined_ds_shift(
&mut self, shift: &mut [T], _step_z: &mut [T], _step_s: &mut [T], σμ: T
) {
shift.scalarop_from(|g| g * σμ, &self.data.grad);
}
fn Δs_from_Δz_offset(&mut self, out: &mut [T], ds: &[T], _work: &mut [T], _z: &[T]) {
out.copy_from(ds);
}
fn step_length(
&mut self,
dz: &[T],
ds: &[T],
z: &[T],
s: &[T],
settings: &CoreSettings<T>,
αmax: T,
) -> (T, T) {
let step = settings.linesearch_backtrack_step;
let αmin = settings.min_terminate_step_length;
let mut work = std::mem::take(&mut self.data.work);
let is_prim_feasible_fcn = |s: &[T]| -> bool { self.is_primal_feasible(s) };
let is_dual_feasible_fcn = |s: &[T]| -> bool { self.is_dual_feasible(s) };
let αz = backtrack_search(dz, z, αmax, αmin, step, is_dual_feasible_fcn, &mut work);
let αs = backtrack_search(ds, s, αmax, αmin, step, is_prim_feasible_fcn, &mut work);
self.data.work = work;
(αz, αs)
}
fn compute_barrier(&mut self, z: &[T], s: &[T], dz: &[T], ds: &[T], α: T) -> T {
let mut barrier = T::zero();
let mut work = std::mem::take(&mut self.data.work);
work.waxpby(T::one(), s, α, ds);
barrier += self.barrier_primal(&work);
work.waxpby(T::one(), z, α, dz);
barrier += self.barrier_dual(&work);
self.data.work = work;
barrier
}
}
impl<T> NonsymmetricCone<T> for GenPowerCone<T>
where
T: FloatT,
{
fn is_primal_feasible(&self, s: &[T]) -> bool
where
T: FloatT,
{
let α = &self.α;
let two: T = (2f64).as_T();
let dim1 = self.dim1();
if s[..dim1].iter().all(|&x| x > T::zero()) {
let res = zip(α, &s[..dim1]).fold(T::zero(), |res, (&αi, &si)| -> T {
res + two * αi * si.logsafe()
});
let res = T::exp(res) - s[dim1..].sumsq();
if res > T::zero() {
return true;
}
}
false
}
fn is_dual_feasible(&self, z: &[T]) -> bool
where
T: FloatT,
{
let α = &self.α;
let two: T = (2.).as_T();
let dim1 = self.dim1();
if z[..dim1].iter().all(|&x| x > T::zero()) {
let res = zip(α, &z[..dim1]).fold(T::zero(), |res, (&αi, &zi)| -> T {
res + two * αi * (zi / αi).logsafe()
});
let res = T::exp(res) - z[dim1..].sumsq();
if res > T::zero() {
return true;
}
}
false
}
fn barrier_primal(&mut self, s: &[T]) -> T
where
T: FloatT,
{
let mut g = std::mem::take(&mut self.data.work_pb);
self.gradient_primal(&mut g, s);
g.negate();
let out = -self.barrier_dual(&g) - self.degree().as_T();
self.data.work_pb = g;
out
}
fn barrier_dual(&mut self, z: &[T]) -> T
where
T: FloatT,
{
let α = &self.α;
let dim1 = self.dim1();
let two: T = (2.).as_T();
let mut res = T::zero();
for (&zi, &αi) in zip(&z[..dim1], α) {
res += two * αi * (zi / αi).logsafe();
}
res = T::exp(res) - z[dim1..].sumsq();
let mut barrier: T = -res.logsafe();
for (&zi, &αi) in zip(&z[..dim1], α) {
barrier -= (zi).logsafe() * (T::one() - αi);
}
barrier
}
fn higher_correction(&mut self, _η: &mut [T], _ds: &[T], _v: &[T]) {
unimplemented!()
}
fn update_dual_grad_H(&mut self, z: &[T]) {
let α = &self.α;
let dim1 = self.dim1();
let data = &mut self.data;
let two: T = (2.).as_T();
let phi = zip(α, z).fold(T::one(), |phi, (&αi, &zi)| phi * (zi / αi).powf(two * αi));
let norm2w = z[dim1..].sumsq();
let ζ = phi - norm2w;
assert!(ζ > T::zero());
let grad = &mut data.grad;
let τ = &mut data.q;
for (τ, grad, &α, &z) in izip!(τ.iter_mut(), &mut grad[..dim1], α, &z[..dim1]) {
*τ = two * α / z;
*grad = -(*τ) * phi / ζ - (T::one() - α) / z;
}
grad[dim1..].scalarop_from(|z| (two / ζ) * z, &z[dim1..]);
let p0 = T::sqrt(phi * (phi + norm2w) / two);
let p1 = -two * phi / p0;
let q0 = T::sqrt(ζ * phi / two);
let r1 = two * T::sqrt(ζ / (phi + norm2w));
for (d1, &τ, &α, &z) in izip!(&mut data.d1, τ.iter(), α, &z[..dim1]) {
*d1 = (τ) * phi / (ζ * z) + (T::one() - α) / (z * z);
}
data.d2 = two / ζ;
data.p[..dim1].scalarop_from(|τi| (p0 / ζ) * τi, τ);
data.p[dim1..].scalarop_from(|zi| (p1 / ζ) * zi, &z[dim1..]);
data.q.scale(q0 / ζ);
data.r.scalarop_from(|zi| (r1 / ζ) * zi, &z[dim1..]);
}
}
impl<T> NonsymmetricNDCone<T> for GenPowerCone<T>
where
T: FloatT,
{
fn gradient_primal(&self, g: &mut [T], s: &[T])
where
T: FloatT,
{
let dim1 = self.dim1();
let two: T = (2.).as_T();
let data = &self.data;
let phi =
zip(&s[..dim1], &self.α).fold(T::one(), |phi, (&si, &αi)| phi * si.powf(two * αi));
let (p, r) = s.split_at(dim1);
let (gp, gr) = g.split_at_mut(dim1);
let norm_r = r.norm();
if norm_r > T::epsilon() {
let g1 = _newton_raphson_genpowcone(norm_r, p, phi, &self.α, data.ψ);
gr.scalarop_from(|r| (g1 / norm_r) * r, &data.r);
for (gp, &α, &p) in izip!(gp.iter_mut(), &self.α, p) {
*gp = -(T::one() + α + α * g1 * norm_r) / p;
}
} else {
gr.set(T::zero());
for (gp, &α, &p) in izip!(gp.iter_mut(), &self.α, p) {
*gp = -(T::one() + α) / p;
}
}
}
}
fn _newton_raphson_genpowcone<T>(norm_r: T, p: &[T], phi: T, α: &[T], ψ: T) -> T
where
T: FloatT,
{
let two: T = (2.).as_T();
let x0 = -norm_r.recip()
+ (ψ * norm_r + ((phi / norm_r / norm_r + ψ * ψ - T::one()) * phi).sqrt())
/ (phi - norm_r * norm_r);
let f0 = {
|x: T| -> T {
let finit = -(two * x / norm_r + x * x).logsafe();
zip(α, p).fold(finit, |f, (&αi, &pi)| {
f + two * αi * ((x * norm_r + (T::one() + αi) / αi).logsafe() - pi.logsafe())
})
}
};
let f1 = {
|x: T| -> T {
let finit = -(two * x + two / norm_r) / (x * x + two * x / norm_r);
α.iter().fold(finit, |f, &αi| {
f + two * (αi) * norm_r / (norm_r * x + (T::one() + αi) / αi)
})
}
};
newton_raphson_onesided(x0, f0, f1)
}