Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 33 additions & 44 deletions fastsim-core/fastsim-proc-macros/src/serde_api/serde_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,7 @@ pub(crate) fn serde_attrs_for_si_fields(field: &mut syn::Field) -> Option<()> {

// Determine the canonical (serialization) unit and serialize_with path.
//
// With no field-level override: use the first entry from quantity_config,
// except Ratio which preserves a legacy bare-name canonical serialization key.
// With no field-level override: use the first entry from quantity_config.
// With `#[si_unit(name)]`: use the specified unit, constructing a serialize_with
// path of the form `fastsim_core::utils::serde_helpers::{quantity_snake}_as_{unit}`
// unless the unit is the SI base unit (first entry, no global serialize_with),
Expand Down Expand Up @@ -408,10 +407,6 @@ pub(crate) fn serde_attrs_for_si_fields(field: &mut syn::Field) -> Option<()> {
(override_name.clone(), serialize_with)
}
}
} else if quantity == "Ratio" {
// Legacy escape hatch: keep bare field names canonical for Ratio fields
// unless an explicit override is provided.
(String::new(), None)
} else {
let canonical = unit_impls
.first()
Expand Down Expand Up @@ -511,8 +506,12 @@ fn quantity_config(quantity: &str) -> Option<(Vec<(TokenStream2, String)>, Optio
None,
)),
"Energy" => Some((
extract_units!(uom::si::energy::joule, uom::si::energy::kilowatt_hour),
None,
extract_units!(
uom::si::energy::kilojoule,
uom::si::energy::kilowatt_hour,
uom::si::energy::joule
),
Some("fastsim_core::utils::serde_helpers::energy_as_kilojoules"),
)),
// "EnergyDensity" => Some((
// vec![(
Expand Down Expand Up @@ -558,33 +557,23 @@ fn quantity_config(quantity: &str) -> Option<(Vec<(TokenStream2, String)>, Optio
extract_units!(uom::si::moment_of_inertia::kilogram_square_meter),
None,
)),
// First entry = canonical serialization unit.
// To serialize Power in kilowatts, move kilowatt to first and set serialize_with:
// "Power" => Some((
// extract_units!(
// uom::si::power::kilowatt,
// uom::si::power::watt,
// uom::si::power::horsepower
// ),
// Some("fastsim_core::utils::serde_helpers::power_as_kilowatts"),
// )),
"Power" => Some((
extract_units!(
uom::si::power::watt,
uom::si::power::kilowatt,
uom::si::power::watt,
uom::si::power::horsepower
),
None,
Some("fastsim_core::utils::serde_helpers::power_as_kilowatts"),
)),
"PowerRate" => Some((extract_units!(uom::si::power_rate::watt_per_second), None)),
"Pressure" => Some((
extract_units!(
uom::si::pressure::pascal,
uom::si::pressure::kilopascal,
uom::si::pressure::pascal,
uom::si::pressure::bar,
uom::si::pressure::pound_force_per_square_inch
),
None,
Some("fastsim_core::utils::serde_helpers::pressure_as_kilopascals"),
)),
"Ratio" => Some((
extract_units!(uom::si::ratio::ratio, uom::si::ratio::percent),
Expand All @@ -595,44 +584,47 @@ fn quantity_config(quantity: &str) -> Option<(Vec<(TokenStream2, String)>, Optio
// as custom uom units (not present upstream). extract_custom_units! takes
// ("path", "name") string literals because the proc-macro binary cannot link
// against downstream crates to call Unit::plural().
let mut units = extract_units!(
uom::si::available_energy::joule_per_kilogram,
uom::si::available_energy::kilojoule_per_kilogram
);
units.extend(extract_custom_units!(
(
"fastsim_core::si::watt_hour_per_kilogram",
"watt_hours_per_kilogram"
),
let mut units = extract_custom_units!(
(
"fastsim_core::si::kilowatt_hour_per_kilogram",
"kilowatt_hours_per_kilogram"
),
(
"fastsim_core::si::watt_hour_per_kilogram",
"watt_hours_per_kilogram"
)
);
units.extend(extract_units!(
uom::si::available_energy::kilojoule_per_kilogram,
uom::si::available_energy::joule_per_kilogram
));
Some((units, None))
Some((
units,
Some("fastsim_core::utils::serde_helpers::specific_energy_as_kilowatt_hours_per_kilogram"),
))
}
"SpecificPower" => Some((
extract_units!(
uom::si::specific_power::watt_per_kilogram,
uom::si::specific_power::kilowatt_per_kilogram
uom::si::specific_power::kilowatt_per_kilogram,
uom::si::specific_power::watt_per_kilogram
),
None,
Some("fastsim_core::utils::serde_helpers::specific_power_as_kilowatts_per_kilogram"),
)),
"Temperature" => Some((
extract_units!(
uom::si::thermodynamic_temperature::kelvin,
uom::si::thermodynamic_temperature::degree_celsius,
uom::si::thermodynamic_temperature::kelvin,
uom::si::thermodynamic_temperature::degree_fahrenheit
),
None,
Some("fastsim_core::utils::serde_helpers::temperature_as_degrees_celsius"),
)),
"TemperatureInterval" => Some((
extract_units!(
uom::si::temperature_interval::kelvin,
uom::si::temperature_interval::degree_celsius,
uom::si::temperature_interval::kelvin,
uom::si::temperature_interval::degree_fahrenheit
),
None,
Some("fastsim_core::utils::serde_helpers::temperature_interval_as_degrees_celsius"),
)),
"ThermalConductance" => Some((
extract_units!(uom::si::thermal_conductance::watt_per_kelvin),
Expand Down Expand Up @@ -784,11 +776,8 @@ pub fn generate_helper_struct(
// Field-unit is canonical by default, with bare-field aliasing for the
// SI base unit. A field-level `#[si_unit(unitless)]` override switches
// canonical naming to bare field for the base unit.
// Legacy escape hatch: unannotated Ratio fields also serialize bare.
let ratio_escape_hatch = si_field.quantity == "Ratio";
let use_bare_as_canonical = (si_field.unitless_canonical
|| ratio_escape_hatch)
&& idx == base_unit_idx;
let use_bare_as_canonical =
si_field.unitless_canonical && idx == base_unit_idx;
let (canonical_name, unit_alias) = if use_bare_as_canonical {
(bare_name.clone(), serde_key.clone())
} else {
Expand Down
2 changes: 2 additions & 0 deletions fastsim-core/src/drive_cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub struct Cycle {
pub dist: Vec<si::Length>,
/// road grade (expressed as a decimal, not percent)
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[si_unit(percent)]
pub grade: Vec<si::Ratio>,
// TODO: consider trapezoidal integration scheme
// TODO: @mokeefe, please check out how elevation is handled
Expand Down Expand Up @@ -1604,6 +1605,7 @@ pub struct CycleElement {
// `dist` is not included here because it is derived in `Init::init`
/// road grade
#[serde(alias = "cycGrade")]
#[si_unit(percent)]
pub grade: Option<si::Ratio>,
// `elev` is not included here because it is derived in `Init::init`
/// road charging/discharing capacity
Expand Down
32 changes: 32 additions & 0 deletions fastsim-core/src/utils/serde_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,36 @@
#[allow(unused_imports)]
use crate::si;

crate::impl_si_serialize_as!(energy_as_kilojoules, si::Energy, uom::si::energy::kilojoule);
crate::impl_si_serialize_as!(
energy_as_kilowatt_hours,
si::Energy,
crate::si::kilowatt_hour
);
crate::impl_si_serialize_as!(power_as_kilowatts, si::Power, uom::si::power::kilowatt);
crate::impl_si_serialize_as!(
pressure_as_kilopascals,
si::Pressure,
uom::si::pressure::kilopascal
);
crate::impl_si_serialize_as!(ratio_as_percent, si::Ratio, uom::si::ratio::percent);
crate::impl_si_serialize_as!(
specific_energy_as_kilowatt_hours_per_kilogram,
si::SpecificEnergy,
crate::si::kilowatt_hour_per_kilogram
);
crate::impl_si_serialize_as!(
specific_power_as_kilowatts_per_kilogram,
si::SpecificPower,
crate::si::kilowatt_per_kilogram
);
crate::impl_si_serialize_as!(
temperature_as_degrees_celsius,
si::Temperature,
uom::si::thermodynamic_temperature::degree_celsius
);
crate::impl_si_serialize_as!(
temperature_interval_as_degrees_celsius,
si::TemperatureInterval,
uom::si::temperature_interval::degree_celsius
);
3 changes: 3 additions & 0 deletions fastsim-core/src/vehicle/chassis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ impl Init for DriveTypes {}
/// Struct for simulating vehicle
pub struct Chassis {
/// Aerodynamic drag coefficient
#[si_unit(unitless)]
pub drag_coef: si::Ratio,
/// Projected frontal area for drag calculations
pub frontal_area: si::Area,
/// Wheel rolling resistance coefficient for the vehicle (i.e. all wheels included)
#[si_unit(unitless)]
pub wheel_rr_coef: si::Ratio,
/// Wheel inertia per wheel
pub wheel_inertia: si::MomentOfInertia,
Expand All @@ -45,6 +47,7 @@ pub struct Chassis {
/// Vehicle center of mass height
pub cg_height: si::Length,
/// Wheel coefficient of friction
#[si_unit(unitless)]
pub wheel_fric_coef: si::Ratio,

/// Drive wheel configuration
Expand Down
1 change: 1 addition & 0 deletions fastsim-core/src/vehicle/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ pub struct ConventionalVehicle {
/// powertrain mass
pub(crate) mass: Option<si::Mass>,
/// Alternator efficiency used to calculate aux mechanical power demand on engine
#[si_unit(percent)]
pub alt_eff: si::Ratio,
}

Expand Down
5 changes: 5 additions & 0 deletions fastsim-core/src/vehicle/hev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ pub struct RGWDBState {
/// SOC is below min buffer so FC is charging RES
pub charging_for_low_soc: TrackedState<bool>,
/// buffer at which FC is forced on
#[si_unit(percent)]
pub soc_fc_on_buffer: TrackedState<si::Ratio>,
}
impl SerdeAPI for RGWDBState {}
Expand Down Expand Up @@ -865,17 +866,20 @@ pub struct RESGreedyWithDynamicBuffers {
/// vehicle at this speed that triggers ramp down in RES discharge.
pub speed_soc_disch_buffer: Option<si::Velocity>,
/// Coefficient for modifying amount of accel buffer
#[si_unit(percent)]
pub speed_soc_disch_buffer_coeff: Option<si::Ratio>,
/// RES energy delta from minimum SOC corresponding to kinetic energy of
/// vehicle at this speed that triggers FC to be forced on.
pub speed_soc_fc_on_buffer: Option<si::Velocity>,
/// Coefficient for modifying amount of [Self::speed_soc_fc_on_buffer]
#[si_unit(percent)]
pub speed_soc_fc_on_buffer_coeff: Option<si::Ratio>,
/// RES energy delta from maximum SOC corresponding to kinetic energy of
/// vehicle at current speed minus kinetic energy of vehicle at this speed
/// triggers ramp down in RES discharge
pub speed_soc_regen_buffer: Option<si::Velocity>,
/// Coefficient for modifying amount of regen buffer
#[si_unit(percent)]
pub speed_soc_regen_buffer_coeff: Option<si::Ratio>,
/// Minimum time engine must remain on if it was on during the previous
/// simulation time step.
Expand Down Expand Up @@ -1268,6 +1272,7 @@ pub struct HEVStartStopControl {
pub fc_min_time_on: Option<si::Time>,
/// The range of usable SOC of the storage system below which the
/// [FuelConverter] is forced on.
#[si_unit(percent)]
pub soc_fc_forced_on: Option<si::Ratio>,
/// Force engine, if on, to run at this fraction of power at which peak
/// efficiency occurs or the required power, whichever is greater. If SOC is
Expand Down
1 change: 1 addition & 0 deletions fastsim-core/src/vehicle/hvac/hvac_sys_for_lumped_cabin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ pub struct HVACSystemForLumpedCabinState {
/// portion of total HVAC cooling/heating (negative/positive) cumulative energy due to derivative gain
pub energy_d: TrackedState<si::Energy>,
/// coefficient of performance (i.e. efficiency) of vapor compression cycle
#[si_unit(unitless)]
pub cop: TrackedState<Option<si::Ratio>>,
/// Aux power demand from [Vehicle::hvac] system
pub pwr_aux_for_hvac: TrackedState<si::Power>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,7 @@ pub struct HVACSystemForLumpedCabinAndRESState {
/// to [ReversibleEnergyStorage::thrml] due to derivative gain
pub energy_d_res: TrackedState<si::Energy>,
/// coefficient of performance (i.e. efficiency) of vapor compression cycle
#[si_unit(unitless)]
pub cop: TrackedState<Option<si::Ratio>>,
/// Reference temperature used to calculate coefficient of performance (i.e.
/// efficiency) of vapor compression cycle
Expand Down
3 changes: 3 additions & 0 deletions fastsim-core/src/vehicle/powertrain/electric_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -877,15 +877,18 @@ pub struct ElectricMachineState {
/// time step index
pub i: TrackedState<usize>,
/// Component efficiency based on current power demand.
#[si_unit(percent)]
pub eff: TrackedState<si::Ratio>,
// Component limits
/// Maximum possible positive traction power.
pub pwr_mech_fwd_out_max: TrackedState<si::Power>,
/// efficiency in forward direction at max possible input power from `FuelConverter` and `ReversibleEnergyStorage`
#[si_unit(percent)]
pub eff_fwd_at_max_input: TrackedState<si::Ratio>,
/// Maximum possible regeneration power going to ReversibleEnergyStorage.
pub pwr_mech_regen_max: TrackedState<si::Power>,
/// efficiency in backward direction at max possible input power from `FuelConverter` and `ReversibleEnergyStorage`
#[si_unit(percent)]
pub eff_at_max_regen: TrackedState<si::Ratio>,

// Current values
Expand Down
2 changes: 2 additions & 0 deletions fastsim-core/src/vehicle/powertrain/fuel_converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ pub struct FuelConverterState {
/// max propulsion power fc can produce at current time
pub pwr_prop_max: TrackedState<si::Power>,
/// efficiency evaluated at current demand
#[si_unit(percent)]
pub eff: TrackedState<si::Ratio>,
/// instantaneous power going to drivetrain, not including aux
pub pwr_prop: TrackedState<si::Power>,
Expand Down Expand Up @@ -1152,6 +1153,7 @@ pub struct FuelConverterThermalState {
/// Cumulative heat transfer energy to ambient
pub energy_thrml_to_amb: TrackedState<si::Energy>,
/// Efficency coefficient, used to modify [FuelConverter] effciency based on temperature
#[si_unit(percent)]
pub eff_coeff: TrackedState<si::Ratio>,
/// Thermal power flowing from fuel converter to cabin
pub pwr_thrml_fc_to_cab: TrackedState<si::Power>,
Expand Down
1 change: 1 addition & 0 deletions fastsim-core/src/vehicle/powertrain/fuel_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct FuelStorage {
#[serde(default)]
pub fuel_type: Option<FuelType>,
/// energy capacity
#[si_unit(kilowatt_hours)]
pub energy_capacity: si::Energy,
/// Fuel and tank specific energy
pub(crate) specific_energy: Option<si::SpecificEnergy>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@ pub struct ReversibleEnergyStorage {
pub pwr_out_max: si::Power,

/// Total energy capacity of battery of full discharge SOC of 0.0 and 1.0
#[si_unit(kilowatt_hours)]
pub energy_capacity: si::Energy,

/// interpolator for calculating [Self] efficiency
pub eff_interp: RESEfficiency,

/// Hard limit on minimum SOC, e.g. 0.05
#[si_unit(percent)]
pub min_soc: si::Ratio,
/// Hard limit on maximum SOC, e.g. 0.95
#[si_unit(percent)]
pub max_soc: si::Ratio,
/// struct for tracking current state
#[serde(default)]
Expand Down Expand Up @@ -889,17 +892,22 @@ pub struct ReversibleEnergyStorageState {
pub i: TrackedState<usize>,

/// state of charge (SOC)
#[si_unit(percent)]
pub soc: TrackedState<si::Ratio>,
/// SOC at which [ReversibleEnergyStorage] regen power begins linearly
/// derating as it approaches maximum SOC
#[si_unit(percent)]
pub soc_regen_buffer: TrackedState<si::Ratio>,
/// SOC at which [ReversibleEnergyStorage] discharge power begins linearly
/// derating as it approaches minimum SOC
#[si_unit(percent)]
pub soc_disch_buffer: TrackedState<si::Ratio>,
/// Chemical <-> Electrical conversion efficiency based on current power demand
#[si_unit(percent)]
pub eff: TrackedState<si::Ratio>,
/// State of Health (SOH)
pub soh: TrackedState<f64>,
#[si_unit(percent)]
pub soh: TrackedState<si::Ratio>,

// TODO: add `pwr_out_neg_electrical` and `pwr_out_pos_electrical` and corresponding energies
// powers to separately pin negative- and positive-power operation
Expand Down
1 change: 1 addition & 0 deletions fastsim-core/src/vehicle/powertrain/transmission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ pub struct TransmissionState {
pub pwr_out_regen_max: TrackedState<si::Power>,

/// efficiency at current time step
#[si_unit(percent)]
pub eff: TrackedState<si::Ratio>,

/// Power at output side of transmission. Positive indicates forward power
Expand Down
1 change: 1 addition & 0 deletions fastsim-core/src/vehicle/vehicle_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,7 @@ pub struct VehicleState {
/// cumulative distance traveled, integral of [Self::speed_ach]
pub dist: TrackedState<si::Length>,
/// current grade
#[si_unit(percent)]
pub grade_curr: TrackedState<si::Ratio>,
/// current grade
// will be overridden during simulation anyway
Expand Down
Loading
Loading