From f4b5bdb58228453b71c83a39188ee06903322df0 Mon Sep 17 00:00:00 2001 From: James Tomlinson Date: Fri, 22 Sep 2023 16:12:51 +0100 Subject: [PATCH] fix: Several tests. --- ipm-ocl/src/lib.rs | 6 ++-- ipm-simd/src/lib.rs | 6 ++-- ipm-simd/src/path_following_direct.rs | 2 -- src/model.rs | 52 ++++++++++++++++----------- src/schema/nodes/delay.rs | 1 - src/solvers/clp/mod.rs | 6 +++- src/solvers/ipm_ocl/mod.rs | 2 +- src/solvers/mod.rs | 1 + src/test_utils.rs | 35 ++++++++++++------ 9 files changed, 69 insertions(+), 42 deletions(-) diff --git a/ipm-ocl/src/lib.rs b/ipm-ocl/src/lib.rs index ce2fbc5b..d81843cf 100644 --- a/ipm-ocl/src/lib.rs +++ b/ipm-ocl/src/lib.rs @@ -13,9 +13,9 @@ pub struct Tolerances { impl Default for Tolerances { fn default() -> Self { Self { - primal_feasibility: 1e-6, - dual_feasibility: 1e-6, - optimality: 1e-6, + primal_feasibility: 1e-8, + dual_feasibility: 1e-8, + optimality: 1e-8, } } } diff --git a/ipm-simd/src/lib.rs b/ipm-simd/src/lib.rs index 90ad6e33..de4760d7 100644 --- a/ipm-simd/src/lib.rs +++ b/ipm-simd/src/lib.rs @@ -164,9 +164,9 @@ where { fn default() -> Self { Self { - primal_feasibility: Simd::::splat(1e-6.into()), - dual_feasibility: Simd::::splat(1e-6.into()), - optimality: Simd::::splat(1e-6.into()), + primal_feasibility: Simd::::splat(1e-8.into()), + dual_feasibility: Simd::::splat(1e-8.into()), + optimality: Simd::::splat(1e-8.into()), } } } diff --git a/ipm-simd/src/path_following_direct.rs b/ipm-simd/src/path_following_direct.rs index 63ebc69c..73387646 100644 --- a/ipm-simd/src/path_following_direct.rs +++ b/ipm-simd/src/path_following_direct.rs @@ -2,8 +2,6 @@ use super::{dual_feasibility, primal_feasibility, Matrix}; use crate::common::{compute_dx_dz_dw, dot_product, normal_eqn_rhs, vector_norm, vector_set, vector_update}; use crate::Tolerances; use ipm_common::SparseNormalCholeskyIndices; -use nalgebra_sparse::na::SimdBool; -use std::fmt::Debug; use std::iter::Sum; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub}; use std::simd::{ diff --git a/src/model.rs b/src/model.rs index e7c417e3..61a4740d 100644 --- a/src/model.rs +++ b/src/model.rs @@ -652,6 +652,11 @@ impl Model { fn required_features(&self) -> HashSet { let mut features = HashSet::new(); + // Aggregated node feature required if there are any aggregated nodes + if self.aggregated_nodes.len() > 0 { + features.insert(SolverFeatures::AggregatedNode); + } + // Aggregated node factors required if any aggregated node has factors defined. if self.aggregated_nodes.iter().any(|n| n.get_factors().is_some()) { features.insert(SolverFeatures::AggregatedNodeFactors); @@ -1479,7 +1484,7 @@ mod tests { use crate::solvers::{ClIpmF64Solver, SimdIpmF64Solver}; use crate::solvers::{ClpSolver, ClpSolverSettings}; use crate::test_utils::{default_timestepper, run_all_solvers, simple_model, simple_storage_model}; - use float_cmp::approx_eq; + use float_cmp::{approx_eq, assert_approx_eq}; use ndarray::{Array, Array2}; use std::ops::Deref; @@ -1577,7 +1582,8 @@ mod tests { #[test] fn test_step() { - let model = simple_model(2); + const NUM_SCENARIOS: usize = 2; + let model = simple_model(NUM_SCENARIOS); let timestepper = default_timestepper(); @@ -1585,31 +1591,35 @@ mod tests { let timesteps = timestepper.timesteps(); let mut ts_iter = timesteps.iter(); - let ts = ts_iter.next().unwrap(); let (scenario_indices, mut current_state, mut p_internal, _r_internal) = model.setup(×teps).unwrap(); let mut solvers = model.setup_solver::(&ClpSolverSettings::default()).unwrap(); assert_eq!(current_state.len(), scenario_indices.len()); - model - .step( - ts, - &scenario_indices, - &mut solvers, - &mut current_state, - &mut p_internal, - &mut timings, - ) - .unwrap(); - let output_node = model.get_node_by_name("output", None).unwrap(); - let state0 = current_state.get(0).unwrap(); - let output_inflow = state0 - .get_network_state() - .get_node_in_flow(&output_node.index()) - .unwrap(); - assert!(approx_eq!(f64, output_inflow, 10.0)); + for i in 0..2 { + let ts = ts_iter.next().unwrap(); + model + .step( + ts, + &scenario_indices, + &mut solvers, + &mut current_state, + &mut p_internal, + &mut timings, + ) + .unwrap(); + + for j in 0..NUM_SCENARIOS { + let state_j = current_state.get(j).unwrap(); + let output_inflow = state_j + .get_network_state() + .get_node_in_flow(&output_node.index()) + .unwrap(); + assert_approx_eq!(f64, output_inflow, (1.0 + i as f64 + j as f64).min(12.0)); + } + } } #[test] @@ -1620,7 +1630,7 @@ mod tests { // Set-up assertion for "input" node let idx = model.get_node_by_name("input", None).unwrap().index(); - let expected = Array::from_shape_fn((366, 10), |(i, j)| (i as f64 + j as f64).min(12.0)); + let expected = Array::from_shape_fn((366, 10), |(i, j)| (1.0 + i as f64 + j as f64).min(12.0)); let recorder = AssertionRecorder::new("input-flow", Metric::NodeOutFlow(idx), expected.clone(), None, None); model.add_recorder(Box::new(recorder)).unwrap(); diff --git a/src/schema/nodes/delay.rs b/src/schema/nodes/delay.rs index c0662faa..f711bf4b 100644 --- a/src/schema/nodes/delay.rs +++ b/src/schema/nodes/delay.rs @@ -122,7 +122,6 @@ mod tests { use crate::metric::Metric; use crate::recorders::AssertionRecorder; use crate::schema::model::PywrModel; - use crate::solvers::{ClpSolver, ClpSolverSettings}; use crate::test_utils::run_all_solvers; use crate::timestep::Timestepper; use ndarray::{concatenate, Array2, Axis}; diff --git a/src/solvers/clp/mod.rs b/src/solvers/clp/mod.rs index 4ff8c29f..e8ef56b4 100644 --- a/src/solvers/clp/mod.rs +++ b/src/solvers/clp/mod.rs @@ -230,7 +230,11 @@ impl Solver for ClpSolver { type Settings = ClpSolverSettings; fn features() -> &'static [SolverFeatures] { - &[SolverFeatures::AggregatedNodeFactors, SolverFeatures::VirtualStorage] + &[ + SolverFeatures::AggregatedNode, + SolverFeatures::AggregatedNodeFactors, + SolverFeatures::VirtualStorage, + ] } fn setup(model: &Model, settings: &Self::Settings) -> Result, PywrError> { diff --git a/src/solvers/ipm_ocl/mod.rs b/src/solvers/ipm_ocl/mod.rs index 49dcc162..348607e1 100644 --- a/src/solvers/ipm_ocl/mod.rs +++ b/src/solvers/ipm_ocl/mod.rs @@ -706,7 +706,7 @@ impl MultiStateSolver for ClIpmF64Solver { let mut queues = Vec::new(); let num_chunks = settings.num_chunks(); - let chunk_size = NonZeroUsize::new(num_scenarios / num_chunks).unwrap(); + let chunk_size = NonZeroUsize::new(num_scenarios / num_chunks).unwrap_or(NonZeroUsize::MIN); for chunk_scenarios in (0..num_scenarios).collect::>().chunks(chunk_size.get()) { // Create a queue per chunk. diff --git a/src/solvers/mod.rs b/src/solvers/mod.rs index fb3ce5d3..11f55dd9 100644 --- a/src/solvers/mod.rs +++ b/src/solvers/mod.rs @@ -65,6 +65,7 @@ impl AddAssign for SolverTimings { /// to solve a given model. #[derive(PartialEq, Eq, Hash)] pub enum SolverFeatures { + AggregatedNode, AggregatedNodeFactors, VirtualStorage, } diff --git a/src/test_utils.rs b/src/test_utils.rs index fa31c4dd..5f325c85 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -37,7 +37,7 @@ pub fn simple_model(num_scenarios: usize) -> Model { model.connect_nodes(input_node, link_node).unwrap(); model.connect_nodes(link_node, output_node).unwrap(); - let inflow = Array::from_shape_fn((366, num_scenarios), |(i, j)| i as f64 + j as f64); + let inflow = Array::from_shape_fn((366, num_scenarios), |(i, j)| 1.0 + i as f64 + j as f64); let inflow = Array2Parameter::new("inflow", inflow, scenario_idx); let inflow = model.add_parameter(Box::new(inflow)).unwrap(); @@ -148,25 +148,40 @@ pub fn run_and_assert_parameter( } /// Run a model using each of the in-built solvers. +/// +/// The model will only be run if the solver has the required solver features (and +/// is also enabled as a Cargo feature). pub fn run_all_solvers(model: &Model, timestepper: &Timestepper) { model .run::(timestepper, &Default::default()) .expect("Failed to solve with CLP"); #[cfg(feature = "highs")] - model - .run::(timestepper, &Default::default()) - .expect("Failed to solve with Highs"); + { + if model.check_solver_features::() { + model + .run::(timestepper, &Default::default()) + .expect("Failed to solve with Highs"); + } + } #[cfg(feature = "ipm-simd")] - model - .run_multi_scenario::>(timestepper, &Default::default()) - .expect("Failed to solve with SIMD IPM"); + { + if model.check_multi_scenario_solver_features::>() { + model + .run_multi_scenario::>(timestepper, &Default::default()) + .expect("Failed to solve with SIMD IPM"); + } + } #[cfg(feature = "ipm-ocl")] - model - .run_multi_scenario::(timestepper, &Default::default()) - .expect("Failed to solve with OpenCl IPM"); + { + if model.check_multi_scenario_solver_features::() { + model + .run_multi_scenario::(timestepper, &Default::default()) + .expect("Failed to solve with OpenCl IPM"); + } + } } /// Make a simple system with random inputs.