trellis_runner/result.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
//! This module defines the default output type for a trellis calculation, in addition to the error
//! wrapper.
use crate::{State, UserState};
use num_traits::float::FloatCore;
use std::fmt;
/// The output of a calculation
///
/// The calculation output is user defined in the finalise step of the [`Calculation`] trait, but
/// this is presented as a good verbose option in situations where the caller wants granular
/// information about the calculation and its progress. It returns the entire original problem,
/// solver and state object.
pub struct Output<R, S>
where
S: UserState,
{
/// The original calculation carried out by `trellis`
pub result: R,
/// Solver state after the last iterationn
pub state: State<S>,
}
impl<R, S> std::fmt::Display for Output<R, S>
where
R: fmt::Display,
S: UserState,
<S as UserState>::Float: FloatCore,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(cause) = self.state.termination_cause() {
use crate::Cause::*;
match cause {
Converged => {
writeln!(
f,
"Solver converged after {} iterations",
self.state.current_iteration()
)?;
if let Some(duration) = self.state.duration() {
writeln!(f, "Duration {:?}", duration)?;
}
writeln!(f, "{}", self.result)?;
}
ControlC => {
writeln!(
f,
"Solver converged by ctrl-c intervention after {} iterations",
self.state.current_iteration()
)?;
}
Parent => {
writeln!(
f,
"Solver converged by parent intervention after {} iterations",
self.state.current_iteration()
)?;
}
ExceededMaxIterations => {
writeln!(
f,
"Solver exceeded maximum iterations ({})",
self.state.current_iteration()
)?;
}
}
} else {
writeln!(f, "Solver is still in progress.")?;
}
Ok(())
}
}
impl<R, S> Output<R, S>
where
S: UserState,
{
pub(crate) fn new(result: R, state: State<S>) -> Self {
Self { result, state }
}
}
#[derive(thiserror::Error, Debug)]
/// An error wrapper for trellis calculations
///
/// The error wraps the underlying error type [`ErrorCause`], which contains information about the
/// reason the calculation failed. In addition it can optionally return the an output from the
/// calculation. This is useful in situations where a failure occured due to running out of
/// iterations, or termination from the parent thread, but the state of the calculation at that
/// point may still contain meaningful information. Maybe the calculation ran out of iterations
/// because it was unable to reach the required tolerance, but is still at convergence?
pub struct TrellisError<O, E> {
#[source]
/// The underlying error cause.
pub cause: ErrorCause<E>,
/// An optional result which can be extracted by the caller
pub result: Option<O>,
}
impl<O, E> From<E> for TrellisError<O, E> {
fn from(cause: E) -> Self {
Self {
cause: ErrorCause::User(cause),
result: None,
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum ErrorCause<E> {
#[error("error in user defined calculation: {0}")]
User(#[from] E),
#[error("exceeded maximum number of iterations")]
MaxIterExceeded,
#[error("calculation cancelled due to ctrl-c")]
ControlC,
#[error("calculation cancelled due to cancelled token")]
CancellationToken,
}