diff --git a/Cargo.lock b/Cargo.lock index d5378adb..76ce49c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1043,9 +1043,9 @@ dependencies = [ [[package]] name = "ninterp" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724327ef8105185c34fc8a6f52e6e3c517a75440533f326d9fb6baf2aabb99b6" +checksum = "e60a408704882574368061cb86dba333d64027a93d8634dfe39c85da167df2a5" dependencies = [ "itertools 0.13.0", "ndarray 0.16.1", diff --git a/fastsim-core/Cargo.toml b/fastsim-core/Cargo.toml index c757e980..bcb18e46 100644 --- a/fastsim-core/Cargo.toml +++ b/fastsim-core/Cargo.toml @@ -38,7 +38,7 @@ toml = { version = "0.8.12", optional = true } derive_more = { version = "1.0.0", features = ["from_str", "from", "is_variant", "try_into"] } ureq = { version = "2.9.1", optional = true } url = { version = "2.5.0", optional = true } -ninterp = { version = "0.1.0", features = ["serde"] } +ninterp = { version = "0.2.0", features = ["serde"] } [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/fastsim-core/src/drive_cycle.rs b/fastsim-core/src/drive_cycle.rs index a82161b4..c2018aad 100644 --- a/fastsim-core/src/drive_cycle.rs +++ b/fastsim-core/src/drive_cycle.rs @@ -100,19 +100,19 @@ impl Init for Cycle { ) .collect(); // println!("{:?}", self.dist); - self.grade_interp = Some(Interpolator::Interp1D(Interp1D::new( + self.grade_interp = Some(Interpolator::new_1d( self.dist.iter().map(|x| x.get::()).collect(), self.grade.iter().map(|y| y.get::()).collect(), Strategy::Linear, Extrapolate::Error, - )?)); + )?); - self.elev_interp = Some(Interpolator::Interp1D(Interp1D::new( + self.elev_interp = Some(Interpolator::new_1d( self.dist.iter().map(|x| x.get::()).collect(), self.elev.iter().map(|y| y.get::()).collect(), Strategy::Linear, Extrapolate::Error, - )?)); + )?); Ok(()) } diff --git a/fastsim-core/src/gas_properties.rs b/fastsim-core/src/gas_properties.rs index 8a75be56..fb8e9c67 100644 --- a/fastsim-core/src/gas_properties.rs +++ b/fastsim-core/src/gas_properties.rs @@ -273,13 +273,13 @@ mod air_static_props { .iter() .map(|x| *x * uc::KELVIN + *uc::CELSIUS_TO_KELVIN) .collect(); - pub static ref TEMP_FROM_ENTHALPY: Interp1D = Interp1D::new( + pub static ref TEMP_FROM_ENTHALPY: Interpolator = Interpolator::new_1d( ENTHALPY_VALUES.iter().map(|x| x.get::()).collect::>(), TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, Extrapolate::Error ).unwrap(); - pub static ref TEMP_FROM_ENERGY: Interp1D = Interp1D::new( + pub static ref TEMP_FROM_ENERGY: Interpolator = Interpolator::new_1d( ENERGY_VALUES.iter().map(|x| x.get::()).collect::>(), TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, @@ -316,7 +316,7 @@ mod air_static_props { .iter() .map(|x| *x * uc::WATT_PER_METER_KELVIN) .collect(); - pub static ref THERMAL_CONDUCTIVITY_INTERP: Interp1D = Interp1D::new( + pub static ref THERMAL_CONDUCTIVITY_INTERP: Interpolator = Interpolator::new_1d( TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), THERMAL_CONDUCTIVITY_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, @@ -353,7 +353,7 @@ mod air_static_props { .iter() .map(|x| *x * uc::J_PER_KG_K) .collect(); - pub static ref C_P_INTERP: Interp1D = Interp1D::new( + pub static ref C_P_INTERP: Interpolator = Interpolator::new_1d( TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), C_P_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, @@ -389,7 +389,7 @@ mod air_static_props { .iter() .map(|x| *x * uc::J_PER_KG) .collect(); - pub static ref ENTHALPY_INTERP: Interp1D = Interp1D::new( + pub static ref ENTHALPY_INTERP: Interpolator = Interpolator::new_1d( TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), ENTHALPY_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, @@ -425,7 +425,7 @@ mod air_static_props { .iter() .map(|x| *x * uc::J_PER_KG) .collect(); - pub static ref ENERGY_INTERP: Interp1D = Interp1D::new( + pub static ref ENERGY_INTERP: Interpolator = Interpolator::new_1d( TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), ENERGY_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, @@ -461,7 +461,7 @@ mod air_static_props { .iter() .map(|x| *x * uc::PASCAL_SECOND) .collect(); - pub static ref DYN_VISC_INTERP: Interp1D = Interp1D::new( + pub static ref DYN_VISC_INTERP: Interpolator = Interpolator::new_1d( TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), DYN_VISCOSITY_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, @@ -473,7 +473,7 @@ mod air_static_props { .zip(THERMAL_CONDUCTIVITY_VALUES.iter()) .map(|((mu, c_p), k)| -> si::Ratio {*mu * *c_p / *k}) .collect::>(); - pub static ref PRANDTL_INTERP: Interp1D = Interp1D::new( + pub static ref PRANDTL_INTERP: Interpolator = Interpolator::new_1d( TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), PRANDTL_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, @@ -551,7 +551,7 @@ mod octane_static_props { .iter() .map(|x| *x * uc::KELVIN + *uc::CELSIUS_TO_KELVIN) .collect(); - pub static ref TEMP_FROM_ENERGY: Interp1D = Interp1D::new( + pub static ref TEMP_FROM_ENERGY: Interpolator = Interpolator::new_1d( ENERGY_VALUES.iter().map(|x| x.get::()).collect::>(), TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, @@ -587,7 +587,7 @@ mod octane_static_props { .iter() .map(|x| *x * uc::J_PER_KG) .collect(); - pub static ref ENERGY_INTERP: Interp1D = Interp1D::new( + pub static ref ENERGY_INTERP: Interpolator = Interpolator::new_1d( TEMPERATURE_VALUES.iter().map(|x| x.get::()).collect::>(), ENERGY_VALUES.iter().map(|x| x.get::()).collect::>(), Strategy::Linear, diff --git a/fastsim-core/src/traits.rs b/fastsim-core/src/traits.rs index 656b13f3..27b6c89f 100644 --- a/fastsim-core/src/traits.rs +++ b/fastsim-core/src/traits.rs @@ -72,11 +72,11 @@ impl Min for Interpolator { fn min(&self) -> anyhow::Result { match self { Interpolator::Interp0D(value) => Ok(*value), - Interpolator::Interp1D(interp) => interp.f_x().min(), - Interpolator::Interp2D(interp) => interp.f_xy().min(), - Interpolator::Interp3D(interp) => interp.f_xyz().min(), - Interpolator::InterpND(interp) => Ok(interp - .values() + Interpolator::Interp1D(..) => self.f_x()?.min(), + Interpolator::Interp2D(..) => self.f_xy()?.min(), + Interpolator::Interp3D(..) => self.f_xyz()?.min(), + Interpolator::InterpND(..) => Ok(self + .values()? .iter() .fold(f64::INFINITY, |acc, x| acc.min(*x))), } @@ -138,11 +138,11 @@ impl Max for Interpolator { fn max(&self) -> anyhow::Result { match self { Interpolator::Interp0D(value) => Ok(*value), - Interpolator::Interp1D(interp) => interp.f_x().max(), - Interpolator::Interp2D(interp) => interp.f_xy().max(), - Interpolator::Interp3D(interp) => interp.f_xyz().max(), - Interpolator::InterpND(interp) => Ok(interp - .values() + Interpolator::Interp1D(..) => self.f_x()?.max(), + Interpolator::Interp2D(..) => self.f_xy()?.max(), + Interpolator::Interp3D(..) => self.f_xyz()?.max(), + Interpolator::InterpND(..) => Ok(self + .values()? .iter() .fold(f64::NEG_INFINITY, |acc, x| acc.max(*x))), } diff --git a/fastsim-core/src/utils/interp.rs b/fastsim-core/src/utils/interp.rs index 9e474069..3f3a7dd4 100644 --- a/fastsim-core/src/utils/interp.rs +++ b/fastsim-core/src/utils/interp.rs @@ -16,16 +16,16 @@ impl InterpolatorMethods for Interpolator { *value = min; Ok(()) } - Interpolator::Interp1D(interp) => { + Interpolator::Interp1D(..) => { todo!() } - Interpolator::Interp2D(interp) => { + Interpolator::Interp2D(..) => { todo!() } - Interpolator::Interp3D(interp) => { + Interpolator::Interp3D(..) => { todo!() } - Interpolator::InterpND(interp) => { + Interpolator::InterpND(..) => { todo!() } } @@ -38,19 +38,17 @@ impl InterpolatorMethods for Interpolator { *value = max; Ok(()) } - Interpolator::Interp1D(interp) => { - Ok(interp.set_f_x(interp.f_x().iter().map(|x| x * max / old_max).collect())?) + Interpolator::Interp1D(..) => { + Ok(self.set_f_x(self.f_x()?.iter().map(|x| x * max / old_max).collect())?) } - Interpolator::Interp2D(interp) => Ok(interp.set_f_xy( - interp - .f_xy() + Interpolator::Interp2D(..) => Ok(self.set_f_xy( + self.f_xy()? .iter() .map(|v| v.iter().map(|x| x * max / old_max).collect()) .collect(), )?), - Interpolator::Interp3D(interp) => Ok(interp.set_f_xyz( - interp - .f_xyz() + Interpolator::Interp3D(..) => Ok(self.set_f_xyz( + self.f_xyz()? .iter() .map(|v0| { v0.iter() @@ -59,8 +57,8 @@ impl InterpolatorMethods for Interpolator { }) .collect(), )?), - Interpolator::InterpND(interp) => { - Ok(interp.set_values(interp.values().map(|x| x * max / old_max))?) + Interpolator::InterpND(..) => { + Ok(self.set_values(self.values()?.map(|x| x * max / old_max))?) } } } @@ -70,17 +68,15 @@ impl InterpolatorMethods for Interpolator { let old_range = old_max - self.min()?; ensure!(old_range != 0., "Cannot modify range when min == max"); match self { - Interpolator::Interp0D(_value) => unreachable!("The above `ensure` should trigger"), - Interpolator::Interp1D(interp) => Ok(interp.set_f_x( - interp - .f_x() + Interpolator::Interp0D(..) => unreachable!("The above `ensure` should trigger"), + Interpolator::Interp1D(..) => Ok(self.set_f_x( + self.f_x()? .iter() .map(|x| old_max + (x - old_max) * range / old_range) .collect(), )?), - Interpolator::Interp2D(interp) => Ok(interp.set_f_xy( - interp - .f_xy() + Interpolator::Interp2D(..) => Ok(self.set_f_xy( + self.f_xy()? .iter() .map(|v| { v.iter() @@ -89,9 +85,8 @@ impl InterpolatorMethods for Interpolator { }) .collect(), )?), - Interpolator::Interp3D(interp) => Ok(interp.set_f_xyz( - interp - .f_xyz() + Interpolator::Interp3D(..) => Ok(self.set_f_xyz( + self.f_xyz()? .iter() .map(|v0| { v0.iter() @@ -104,9 +99,8 @@ impl InterpolatorMethods for Interpolator { }) .collect(), )?), - Interpolator::InterpND(interp) => Ok(interp.set_values( - interp - .values() + Interpolator::InterpND(..) => Ok(self.set_values( + self.values()? .map(|x| old_max + (x - old_max) * range / old_range), )?), } diff --git a/fastsim-core/src/vehicle/powertrain/electric_machine.rs b/fastsim-core/src/vehicle/powertrain/electric_machine.rs index 123d50da..44a77b3e 100755 --- a/fastsim-core/src/vehicle/powertrain/electric_machine.rs +++ b/fastsim-core/src/vehicle/powertrain/electric_machine.rs @@ -321,7 +321,7 @@ impl Init for ElectricMachine { if self.eff_interp_at_max_input.is_none() { // sets eff_interp_bwd to eff_interp_fwd, but changes the x-value. // TODO: what should the default strategy be for eff_interp_bwd? - let eff_interp_at_max_input = Interp1D::new( + let eff_interp_at_max_input = Interpolator::new_1d( self.eff_interp_achieved .x()? .iter() @@ -335,7 +335,7 @@ impl Init for ElectricMachine { self.eff_interp_achieved.strategy()?.to_owned(), self.eff_interp_achieved.extrapolate()?.to_owned(), )?; - self.eff_interp_at_max_input = Some(Interpolator::Interp1D(eff_interp_at_max_input)); + self.eff_interp_at_max_input = Some(eff_interp_at_max_input); } else { println!( "`eff_interp_at_max_input` is being overwritten by {}", @@ -445,9 +445,8 @@ impl ElectricMachine { let old_max_bwd = self.get_eff_max_bwd()?; let f_x_fwd = self.eff_interp_achieved.f_x()?.to_owned(); match &mut self.eff_interp_achieved { - Interpolator::Interp1D(interp1d) => { - interp1d - .set_f_x(f_x_fwd.iter().map(|x| x * eff_max / old_max_fwd).collect())?; + interp @ Interpolator::Interp1D(..) => { + interp.set_f_x(f_x_fwd.iter().map(|x| x * eff_max / old_max_fwd).collect())?; } _ => bail!("{}\n", "Only `Interpolator::Interp1D` is allowed."), } @@ -460,9 +459,9 @@ impl ElectricMachine { .f_x()? .to_owned(); match &mut self.eff_interp_at_max_input { - Some(Interpolator::Interp1D(interp1d)) => { + Some(interp @ Interpolator::Interp1D(..)) => { // let old_interp = interp1d; - interp1d.set_f_x( + interp.set_f_x( f_x_bwd .iter() .map(|x| x * eff_max / old_max_bwd) @@ -556,8 +555,8 @@ impl ElectricMachine { } let f_x_fwd = self.eff_interp_achieved.f_x()?.to_owned(); match &mut self.eff_interp_achieved { - Interpolator::Interp1D(interp1d) => { - interp1d.set_f_x( + interp @ Interpolator::Interp1D(..) => { + interp.set_f_x( f_x_fwd .iter() .map(|x| eff_max_fwd + (x - eff_max_fwd) * eff_range / old_range) @@ -570,8 +569,8 @@ impl ElectricMachine { let x_neg = self.get_eff_min_fwd()?; let f_x_fwd = self.eff_interp_achieved.f_x()?.to_owned(); match &mut self.eff_interp_achieved { - Interpolator::Interp1D(interp1d) => { - interp1d.set_f_x(f_x_fwd.iter().map(|x| x - x_neg).collect())?; + interp @ Interpolator::Interp1D(..) => { + interp.set_f_x(f_x_fwd.iter().map(|x| x - x_neg).collect())?; } _ => bail!("{}\n", "Only `Interpolator::Interp1D` is allowed."), } diff --git a/fastsim-core/src/vehicle/powertrain/fuel_converter.rs b/fastsim-core/src/vehicle/powertrain/fuel_converter.rs index ee36ee84..239dcf4d 100755 --- a/fastsim-core/src/vehicle/powertrain/fuel_converter.rs +++ b/fastsim-core/src/vehicle/powertrain/fuel_converter.rs @@ -535,8 +535,8 @@ pub struct FuelConverterThermal { } /// Dummy interpolator that will be overridden in [FuelConverterThermal::init] -fn tstat_interp_default() -> Interp1D { - Interp1D::new( +fn tstat_interp_default() -> Interpolator { + Interpolator::new_1d( vec![85.0, 90.0], vec![0.0, 1.0], Strategy::Linear, @@ -690,7 +690,7 @@ impl Init for FuelConverterThermal { .tstat_te_sto .or(Some(85. * uc::KELVIN + *uc::CELSIUS_TO_KELVIN)); self.tstat_te_delta = self.tstat_te_delta.or(Some(5. * uc::KELVIN)); - self.tstat_interp = Interp1D::new( + self.tstat_interp = Interpolator::new_1d( vec![ self.tstat_te_sto.unwrap().get::(), self.tstat_te_sto.unwrap().get::() diff --git a/fastsim-core/src/vehicle/powertrain/reversible_energy_storage.rs b/fastsim-core/src/vehicle/powertrain/reversible_energy_storage.rs index eff5433d..dd70456a 100644 --- a/fastsim-core/src/vehicle/powertrain/reversible_energy_storage.rs +++ b/fastsim-core/src/vehicle/powertrain/reversible_energy_storage.rs @@ -203,28 +203,23 @@ impl ReversibleEnergyStorage { ) ); } - state.eff = match &self.eff_interp { - Interpolator::Interp0D(eff) => *eff * uc::R, - Interpolator::Interp1D(interp1d) => { - interp1d.interpolate(&[state.pwr_out_electrical.get::()])? * uc::R - } - Interpolator::Interp2D(interp2d) => { - interp2d.interpolate(&[ - state.pwr_out_electrical.get::(), - state.soc.get::(), - ])? * uc::R - } - Interpolator::Interp3D(interp3d) => { - interp3d.interpolate(&[ - state.pwr_out_electrical.get::(), - state.soc.get::(), - te_res - .with_context(|| format_dbg!("Expected thermal model to be configured"))? - .get::(), - ])? * uc::R - } + let interp_pt: &[f64] = match &self.eff_interp { + Interpolator::Interp0D(..) => &[], + Interpolator::Interp1D(..) => &[state.pwr_out_electrical.get::()], + Interpolator::Interp2D(..) => &[ + state.pwr_out_electrical.get::(), + state.soc.get::(), + ], + Interpolator::Interp3D(..) => &[ + state.pwr_out_electrical.get::(), + state.soc.get::(), + te_res + .with_context(|| format_dbg!("Expected thermal model to be configured"))? + .get::(), + ], _ => bail!("Invalid interpolator. See docs for `ReversibleEnergyStorage::eff_interp`"), }; + state.eff = self.eff_interp.interpolate(interp_pt)? * uc::R; ensure!( state.eff >= 0.0 * uc::R && state.eff <= 1.0 * uc::R, format!( diff --git a/fastsim-core/src/vehicle/vehicle_model/fastsim2_interface.rs b/fastsim-core/src/vehicle/vehicle_model/fastsim2_interface.rs index d8fa82fc..3e488e37 100644 --- a/fastsim-core/src/vehicle/vehicle_model/fastsim2_interface.rs +++ b/fastsim-core/src/vehicle/vehicle_model/fastsim2_interface.rs @@ -65,12 +65,12 @@ impl TryFrom<&fastsim_2::vehicle::RustVehicle> for PowertrainType { // assumes 1 s time step pwr_out_max_init: f2veh.fc_max_kw * uc::KW / f2veh.fc_sec_to_peak_pwr, pwr_ramp_lag: f2veh.fc_sec_to_peak_pwr * uc::S, - eff_interp_from_pwr_out: Interpolator::Interp1D(Interp1D::new( + eff_interp_from_pwr_out: Interpolator::new_1d( f2veh.fc_perc_out_array.to_vec(), f2veh.fc_eff_array.to_vec(), Strategy::LeftNearest, Extrapolate::Error, - )?), + )?, pwr_for_peak_eff: uc::KW * f64::NAN, // this gets updated in `init` // this means that aux power must include idle fuel pwr_idle_fuel: si::Power::ZERO, @@ -143,12 +143,12 @@ impl TryFrom<&fastsim_2::vehicle::RustVehicle> for PowertrainType { // assumes 1 s time step pwr_out_max_init: f2veh.fc_max_kw * uc::KW / f2veh.fc_sec_to_peak_pwr, pwr_ramp_lag: f2veh.fc_sec_to_peak_pwr * uc::S, - eff_interp_from_pwr_out: Interpolator::Interp1D(Interp1D::new( + eff_interp_from_pwr_out: Interpolator::new_1d( f2veh.fc_perc_out_array.to_vec(), f2veh.fc_eff_array.to_vec(), Strategy::LeftNearest, Extrapolate::Error, - )?), + )?, pwr_for_peak_eff: uc::KW * f64::NAN, // this gets updated in `init` // this means that aux power must include idle fuel pwr_idle_fuel: si::Power::ZERO, @@ -175,20 +175,18 @@ impl TryFrom<&fastsim_2::vehicle::RustVehicle> for PowertrainType { }, em: ElectricMachine { state: Default::default(), - eff_interp_achieved: (Interpolator::Interp1D( - Interp1D::new( - f2veh.mc_perc_out_array.to_vec(), - { - let mut mc_full_eff_vec = f2veh.mc_full_eff_array.to_vec(); - ensure!(mc_full_eff_vec.len() > 1); - mc_full_eff_vec[0] = mc_full_eff_vec[1]; - mc_full_eff_vec - }, - Strategy::LeftNearest, - Extrapolate::Error, - ) - .unwrap(), - )), + eff_interp_achieved: Interpolator::new_1d( + f2veh.mc_perc_out_array.to_vec(), + { + let mut mc_full_eff_vec = f2veh.mc_full_eff_array.to_vec(); + ensure!(mc_full_eff_vec.len() > 1); + mc_full_eff_vec[0] = mc_full_eff_vec[1]; + mc_full_eff_vec + }, + Strategy::LeftNearest, + Extrapolate::Error, + ) + .unwrap(), eff_interp_at_max_input: None, // pwr_in_frac_interp: Default::default(), pwr_out_max: f2veh.mc_max_kw * uc::KW, @@ -225,13 +223,13 @@ impl TryFrom<&fastsim_2::vehicle::RustVehicle> for PowertrainType { }, em: ElectricMachine { state: Default::default(), - eff_interp_achieved: (Interpolator::Interp1D(Interp1D::new( + eff_interp_achieved: Interpolator::new_1d( f2veh.mc_pwr_out_perc.to_vec(), f2veh.mc_eff_array.to_vec(), Strategy::LeftNearest, Extrapolate::Error, - )?)), - eff_interp_at_max_input: Some(Interpolator::Interp1D(Interp1D::new( + )?, + eff_interp_at_max_input: Some(Interpolator::new_1d( // before adding the interpolator, pwr_in_frac_interp was set as Default::default(), can this // be transferred over as done here, or does a new defualt need to be defined? f2veh @@ -244,7 +242,7 @@ impl TryFrom<&fastsim_2::vehicle::RustVehicle> for PowertrainType { f2veh.mc_eff_array.to_vec(), Strategy::LeftNearest, Extrapolate::Error, - )?)), + )?), pwr_out_max: f2veh.mc_max_kw * uc::KW, specific_pwr: None, mass: None, @@ -348,8 +346,8 @@ impl Vehicle { fc_eff_map: self .fc() .map(|fc| match &fc.eff_interp_from_pwr_out { - Interpolator::Interp1D(_interp1d) => { - Ok(fc.eff_interp_from_pwr_out.f_x()?.to_vec().into()) + interp @ Interpolator::Interp1D(..) => { + Ok(interp.f_x()?.to_vec().into()) } _ => bail!( "{}\nOnly 1-D interpolators can be converted to FASTSim 2", @@ -382,8 +380,8 @@ impl Vehicle { fc_pwr_out_perc: self .fc() .map(|fc| match &fc.eff_interp_from_pwr_out { - Interpolator::Interp1D(_interp) => { - Ok(fc.eff_interp_from_pwr_out.x()?.to_vec().into()) + interp @ Interpolator::Interp1D(..) => { + Ok(interp.x()?.to_vec().into()) } _ => bail!( "{}\nOnly 1-D interpolators can be converted to FASTSim 2",