use crate::core::constraint::evaluation::BoxProblem;
use crate::core::inner::InitialState;
use crate::core::math::{
ClampInPlace, NegInPlace, Scalar, ScaledAdd, VectorLen,
};
use crate::core::problem::{CostFunction, Gradient, Problem};
use crate::core::solver::Solver;
use crate::core::state::{FirstOrderState, State};
use crate::core::termination::Termination;
use crate::line_search::{Constant, LineSearch, LineSearchOutcome};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProjectedGradientDescent<S, F: Scalar = f64> {
scalar: core::marker::PhantomData<F>,
line_search: S,
}
impl<F: Scalar> ProjectedGradientDescent<Constant<F>, F> {
pub fn new(alpha: F) -> Self {
Self {
line_search: Constant(alpha),
scalar: core::marker::PhantomData,
}
}
}
impl<S, F: Scalar> ProjectedGradientDescent<S, F> {
pub fn with_line_search(line_search: S) -> Self {
Self {
line_search,
scalar: core::marker::PhantomData,
}
}
}
impl<S, V, F: Scalar> InitialState<V> for ProjectedGradientDescent<S, F>
where
V: Clone,
{
type State = FirstOrderState<V, F>;
fn seed(&self, x: &V) -> Self::State {
FirstOrderState::new(x.clone())
}
}
impl<P, V, F, S> Solver<P, FirstOrderState<V, F>>
for ProjectedGradientDescent<S, F>
where
F: Scalar,
P: CostFunction<Param = V, Output = F>
+ Gradient<Gradient = V>
+ BoxProblem<Param = V>,
V: ScaledAdd<F> + NegInPlace + ClampInPlace + Clone + VectorLen,
S: LineSearch<P, V, F, Error = P::Error>,
V: crate::VectorLen + std::ops::Index<usize, Output = F>,
{
type Error = crate::ConstraintSolveError<P::Error>;
fn init(
&mut self,
problem: &mut Problem<P>,
mut state: FirstOrderState<V, F>,
) -> Result<FirstOrderState<V, F>, Self::Error> {
crate::core::constraint::evaluation::validate_box_problem(
problem.inner(),
Some(state.param().vec_len()),
false,
)
.map_err(crate::core::constraint::evaluation::invalid)?;
state.reset();
self.line_search.reset();
state
.seed_param_mut()
.clamp_in_place(problem.inner().lower(), problem.inner().upper());
let (cost, grad) = problem.cost_and_gradient(state.param())?;
state
.set_evaluation(cost, grad)
.expect("gradient dimension differs from parameter");
Ok(state)
}
fn next_iter(
&mut self,
problem: &mut Problem<P>,
mut state: FirstOrderState<V, F>,
) -> Result<crate::SolverStep<FirstOrderState<V, F>>, Self::Error> {
let (prev_cost, grad) = state
.take_evaluation()
.expect("solver requires initialized progress");
let mut direction = grad.clone();
direction.neg_in_place();
let alpha = match self.line_search.next_with_outcome(
problem,
state.param(),
prev_cost,
&grad,
&direction,
)? {
LineSearchOutcome::Step(alpha) => alpha,
LineSearchOutcome::Failed => {
state
.set_evaluation(prev_cost, grad)
.expect("gradient dimension differs from parameter");
return Ok(crate::SolverStep::from((
state,
Some(Termination::numerical_failure(
"Projected gradient-descent line search failed.",
)),
)));
}
};
state.seed_param_mut().scaled_add(alpha, &direction);
state
.seed_param_mut()
.clamp_in_place(problem.inner().lower(), problem.inner().upper());
let (cost, grad) = problem.cost_and_gradient(state.param())?;
state
.set_evaluation(cost, grad)
.expect("gradient dimension differs from parameter");
Ok(crate::SolverStep::from((state, None)))
}
}