FASTSim

FASTSim Logo

homepage tests wheels python documentation github

Description

This is the python/rust flavor of NREL's FASTSimTM, which is based on the original Excel implementation. Effort will be made to keep the core methodology between this software and the Excel flavor in line with one another.

All classes and methods are self-documented.

Installation

Python

Set up and activate a python environment (compatible with Python 3.8 - 3.10; we recommend Python 3.10) with the following steps.

Anaconda

  1. Create: conda create -n fastsim python=3.10
  2. Activate: conda activate fastsim

venv

There is some variation based on your Operating System:

  • PowerShell (windows):

    1. Create: python -m venv fastsim-venv -- name is user decision
    2. Activate: fastsim-venv/Scripts/Activate.ps1
  • Bash (i.e. unix/linux/mac):

    1. Create: python -m venv fastsim-venv -- name is user decision
    2. Activate: source fastsim-venv/bin/activate
  • Command Prompt (windows):

    1. Create: python -m venv fastsim-venv -- name is user decision
    2. Activate: fastsim-venv/Scripts/activate.bat

FASTSim

Via PyPI

In an active Python environment created above, run pip install fastsim.

Building from Scratch

Developers might want to install the code in place so that FASTSim files can be editable (the -e flag for pip provides this behavior). This option can be handy since FASTSim will be installed in place from the installation location and any updates will be propagated each time FASTSim is freshly imported. To do this, you'll need to have the Rust toolchain installed.

  • Option 1: run sh build_and_test.sh in root folder.
  • Option 2:
    1. Run pip install -e ".[dev]"
      Optional testing steps:
    2. Run cd rust/ && cargo test
    3. Run pytest -v python/fastsim/tests/

Usage

To see and run examples, navigate to ./python/fastsim/demos and run the various *demo.py files to see fastsim use cases. There are other examples in fastsim/tests.

Adding FASTSim as a Dependency in Rust

Via GitHub

Add this line:

fastsim-core = { git = "https://github.com/NREL/fastsim/", branch = "fastsim-2" }

to your Cargo.toml file, modifying the branch key as appropriate.

Via Cargo

FASTSim is available as a Rust crate, which can be added to your dependencies via the following command:

cargo add fastsim-core

List of Abbreviations

cur = current time step
prev = previous time step
cyc = drive cycle
secs = seconds
mps = meters per second
mph = miles per hour
kw = kilowatts, unit of power
kwh = kilowatt-hour, unit of energy
kg = kilograms, unit of mass
max = maximum
min = minimum
avg = average
fs = fuel storage (eg. gasoline/diesel tank, pressurized hydrogen tank)
fc = fuel converter (eg. internal combustion engine, fuel cell)
mc = electric motor/generator and controller
ess = energy storage system (eg. high voltage traction battery)
chg = charging of a component
dis = discharging of a component
lim = limit of a component
regen = associated with regenerative braking
des = desired value
ach = achieved value
in = component input
out = component output

Known Issues

Rust versions of classes have limited Language Server Protocol integration, and we are actively working on fixing this.

Release Notes

2.1.2 -- SerdeAPI revamp with many new functions, various new vehicles, calibration demo, better error propagation, demo testing 2.1.1 -- license changed to Apache 2.0, default cycle grade and road type to zero if not provided, defaults to regenerative braking parameters, optional documentation fields now generated in Rust 2.1.0 -- release and installation improvements, RustVehicle init cleanup, calibration improvements 2.0.11 - 2.0.22 -- PyPI fixes. Also, Rust version is now >100x faster than Python version. 2.0.10 -- logging fixes, proc macro reorganization, some CAVs performance fixes
2.0.9 -- support for mac ARM/RISC architecture
2.0.8 -- performance improvements
2.0.6 -- dist_v2_m fixes and preliminary CAV functionality
2.0.5 -- added to_rust method for cycle
2.0.4 -- exposed veh.set_veh_mass
2.0.3 -- exposed veh.__post_init__
2.0.2 -- provisioned for non-default vehdb path
2.0.1 -- bug fix
2.0.0 -- All second-by-second calculations are now implemented in both rust and python. Rust provides a ~30x speedup
1.3.1 -- fastsim.simdrive.copy_sim_drive function can deepcopy jit to non-jit (and back) for pickling
1.2.6 -- time dilation bug fix for zero speed
1.2.4 -- bug fix changing == to =
1.2.3 -- veh_file can be passed as standalone argument. fcEffType can be anything if fcEffMap is provided, but typing is otherwise enforced.
1.2.2 -- added checks for some conflicting vehicle parameters. Vehicle parameters fcEffType and vehPtType must now be str type.
1.2.1 -- improved time dilation and added test for it
1.1.7 -- get_numba_veh() and get_numba_cyc() can now be called from already jitted objects
1.1.6 -- another bug fix for numba compatibility with corresponding unit test
1.1.5 -- bug fix for numba compatibility of fcPeakEffOverride and mcPeakEffOverride
1.1.4 -- nan bug fix for fcPeakEffOverride and mcPeakEffOverride
1.1.3 -- provisioned for optional load time motor and engine peak overrides
1.1.2 -- made vehicle loading more more robust
1.1.1 -- made vehicle loading more robust
1.1.0 -- separated jitclasses into own module, made vehicle engine and motor efficiency setting more robust
1.0.4 -- bug fix with custom engine curve
1.0.3 -- bug fixes, faster testing
1.0.2 -- forced type np.float64 on vehicle mass attributes
1.0.1 -- Added vehYear attribute to vehicle and other minor changes. 1.0.0 -- Implemented unittest package. Fixed energy audit calculations to be based on achieved speed. Updated this file. Improved documentation. Vehicle can be instantiated as dict. 0.1.5 -- Updated to be compatible with ADOPT 0.1.4 -- Bug fix: mcEffMap is now robust to having zero as first element 0.1.3 -- Bug fix: fastsim.vehicle.Vehicle method set_init_calcs no longer overrides fcEffMap. 0.1.2 -- Fixes os-dependency of xlwings by not running stuff that needs xlwings. Improvements in functional test. Refinment utomated typying of jitclass objects. 0.1.1 -- Now includes label fuel economy and/or battery kW-hr/mi values that match excel and test for benchmarking against Excel values and CPU time.

Contributors

Chad Baker -- Chad.Baker@nrel.gov
Aaron Brooker -- Aaron.Brooker@nrel.gov
Kyle Carow -- Kyle.Carow@nrel.gov
Robin Steuteville -- Robin.Steuteville@nrel.gov
Jeffrey Gonder -- Jeff.Gonder@nrel.gov
Jacob Holden -- Jacob.Holden@nrel.gov
Jinghu Hu -- Jinghu.Hu@nrel.gov
Jason Lustbader -- Jason.Lustbader@nrel.gov
Sean Lopp -- sean@rstudio.com
Matthew Moniot -- Matthew.Moniot@nrel.gov
Grant Payne -- Grant.Payne@nrel.gov
Laurie Ramroth -- lramroth@ford.com
Eric Wood -- Eric.Wood@nrel.gov

About this book

This is the overall FASTSim documentation. We're working toward making this a fully integrated document that includes both the Python API and Rust core documentation for the fastsim-2 branch and eventually also for the fastsim-3 branch.

Documentation

Python

Rust

Table of Contents

fastsim

Package containing modules for running FASTSim. For example usage, see

package_root

def package_root() -> Path

Returns the package root directory.

fastsim.vehicle_base

Boiler plate stuff needed for vehicle.py

fastsim.calibration

get_error_val

def get_error_val(model: npt.NDArray[np.float64],
                  test: npt.NDArray[np.float64],
                  time_steps: npt.NDArray[np.float64]) -> float

Returns time-averaged error for model and test signal.

Arguments:

  • model npt.NDArray[np.float64] - array of values for signal from model
  • test npt.NDArray[np.float64] - array of values for signal from test data
  • time_steps npt.NDArray[np.float64] - array (or scalar for constant) of values for model time steps [s]

Returns:

  • float - integral of absolute value of difference between model and test per time

ModelObjectives Objects

@dataclass
class ModelObjectives(object)

Class for calculating eco-driving objectives

get_errors

def get_errors(
    sim_drives: Dict[str, fsr.RustSimDrive | fsr.SimDriveHot],
    return_mods: bool = False,
    plot: bool = False,
    plot_save_dir: Optional[str] = None,
    plot_perc_err: bool = False,
    show: bool = False,
    fontsize: float = 12,
    plotly: bool = False
) -> Union[
        Dict[str, Dict[str, float]],
        # or if return_mods is True
        Tuple[Dict[str, fsim.simdrive.SimDrive], Dict[str, Dict[str, float]]]]

Calculate model errors w.r.t. test data for each element in dfs/models for each objective.

Arguments:

  • sim_drives Dict[str, fsr.RustSimDrive | fsr.SimDriveHot] - dictionary with user-defined keys and SimDrive or SimDriveHot instances
  • return_mods bool, optional - if true, also returns dict of solved models. Defaults to False.
  • plot bool, optional - if true, plots objectives using matplotlib.pyplot. Defaults to False.
  • plot_save_dir Optional[str], optional - directory in which to save plots. If None, plots are not saved. Defaults to None.
  • plot_perc_err bool, optional - whether to include % error axes in plots. Defaults to False.
  • show bool, optional - whether to show matplotlib.pyplot plots. Defaults to False.
  • fontsize float, optional - plot font size. Defaults to 12.
  • plotly bool, optional - whether to generate plotly plots, which can be opened manually in a browser window. Defaults to False.

Returns:

Objectives and optionally solved models

update_params

def update_params(xs: List[Any])

Updates model parameters based on x, which must match length of self.params

get_parser

def get_parser(
        def_description: str = "Program for calibrating fastsim models.",
        def_p: int = 4,
        def_n_max_gen: int = 500,
        def_pop_size: int = 12,
        def_save_path: Optional[str] = "pymoo_res") -> argparse.ArgumentParser

Generate parser for optimization hyper params and misc. other params

Arguments:

  • def_p int, optional - default number of processes. Defaults to 4.
  • def_n_max_gen int, optional - max allowed generations. Defaults to 500.
  • def_pop_size int, optional - default population size. Defaults to 12.
  • def_save_path str, optional - default save path. Defaults to pymoo_res.

Returns:

  • argparse.ArgumentParser - description

fastsim.simdrivelabel

Module containing classes and methods for calculating label fuel economy.

get_label_fe

def get_label_fe(veh: vehicle.Vehicle,
                 full_detail: bool = False,
                 verbose: bool = False,
                 chg_eff: float = None,
                 use_rust=False)

Generates label fuel economy (FE) values for a provided vehicle.

Arguments:


veh : vehicle.Vehicle() full_detail : boolean, default False If True, sim_drive objects for each cycle are also returned. verbose : boolean, default false If true, print out key results chg_eff : float between 0 and 1 Override for chg_eff -- currently not functional

  • use_rust - bool, if True, use rust version of classes, else Python

    Returns label fuel economy values as a dict and (optionally) simdrive.SimDriveClassic objects.

fastsim.resample

resample

def resample(df: pd.DataFrame,
             dt_new: Optional[float] = 1.0,
             time_col: Optional[str] = "Time[s]",
             rate_vars: Optional[Tuple[str]] = [],
             hold_vars: Optional[Tuple[str]] = []) -> pd.DataFrame

Resamples dataframe df.

Arguments:

  • df: dataframe to resample
  • dt_new: new time step size, default 1.0 s
  • time_col: column for time in s
  • rate_vars: list of variables that represent rates that need to be time averaged
  • hold_vars: vars that need zero-order hold from previous nearest time step (e.g. quantized variables like current gear)

fastsim.cycle

Module containing classes and methods for cycle data.

CycleCache Objects

class CycleCache()

interp_grade

def interp_grade(dist: float)

Interpolate the single-point grade at the given distance. Assumes that the grade at i applies from sample point (i-1, i]

interp_elevation

def interp_elevation(dist: float)

Interpolate the elevation at the given distance

Cycle Objects

@dataclass
class Cycle(object)

Object for containing time, speed, road grade, and road charging vectors for drive cycle. Instantiate with the from_file or from_dict method.

from_file

@classmethod
def from_file(cls, filename: str) -> Self

Load cycle from filename (str). Can be absolute or relative path. If relative, looks in working dir first and then in fastsim/resources/cycles.

File must contain columns for: -- cycSecs or time_s -- cycMps or mps -- cycGrade or grade (optional) -- cycRoadType or road_type (optional)

from_dict

@classmethod
def from_dict(cls, cyc_dict: dict) -> Self

Load cycle from dict, which must contain keys for: -- cycSecs or time_s -- cycMps or mps -- cycGrade or grade (optional) -- cycRoadType or road_type (optional)

get_numba_cyc

def get_numba_cyc()

Deprecated.

build_cache

def build_cache() -> CycleCache

Calculates a dataclass containing expensive-to-calculate items. The data created can persist between calls and optionally be passed into methods that can use it which will result in a performance enhancement. RETURN: CycleCache

dt_s_at_i

def dt_s_at_i(i: int) -> float

Calculate the time-step duration for time-step i. Returns: the time-step duration in seconds

delta_elev_m

@property
def delta_elev_m() -> np.ndarray

Cumulative elevation change w.r.t. to initial

__len__

def __len__() -> int

return cycle length

to_dict

def to_dict() -> Dict[str, np.ndarray]

Returns cycle as dict rather than class instance.

reset_orphaned

def reset_orphaned()

Dummy method for flexibility between Rust/Python version interfaces

copy

def copy() -> Self

Return a copy of this Cycle instance.

average_grade_over_range

def average_grade_over_range(distance_start_m,
                             delta_distance_m,
                             cache: Optional[CycleCache] = None)

Returns the average grade over the given range of distances

  • distance_start_m: non-negative-number, the distance at start of evaluation area (m)
  • delta_distance_m: non-negative-number, the distance traveled from distance_start_m (m) RETURN: number, the average grade (rise over run) over the given distance range Note: grade is assumed to be constant from just after the previous sample point until the current sample point. That is, grade[i] applies over the range of distances, d, from (d[i - 1], d[i]]

calc_distance_to_next_stop_from

def calc_distance_to_next_stop_from(distance_m: float,
                                    cache: Optional[CycleCache] = None
                                    ) -> float

Calculate the distance to next stop from distance_m

  • distance_m: non-negative-number, the current distance from start (m) RETURN: returns the distance to the next stop from distance_m NOTE: distance may be negative if we're beyond the last stop

modify_by_const_jerk_trajectory

def modify_by_const_jerk_trajectory(idx, n, jerk_m__s3, accel0_m__s2)

Modifies the cycle using the given constant-jerk trajectory parameters

  • idx: non-negative integer, the point in the cycle to initiate modification (note: THIS point is modified since trajectory should be calculated from idx-1)
  • jerk_m__s3: number, the "Jerk" associated with the trajectory (m/s3)
  • accel0_m__s2: number, the initial acceleration (m/s2) NOTE:
  • modifies cyc in place to hit any critical rendezvous_points by a trajectory adjustment
  • CAUTION: NOT ROBUST AGAINST VARIABLE DURATION TIME-STEPS RETURN: Number, final modified speed (m/s)

modify_with_braking_trajectory

def modify_with_braking_trajectory(brake_accel_m__s2: float,
                                   idx: int,
                                   dts_m: Optional[float] = None) -> tuple

Add a braking trajectory that would cover the same distance as the given constant brake deceleration

  • brake_accel_m__s2: negative number, the braking acceleration (m/s2)
  • idx: non-negative integer, the index where to initiate the stop trajectory, start of the step (i in FASTSim)
  • dts_m: None | float: if given, this is the desired distance-to-stop in meters. If not given, it is calculated based on braking deceleration. RETURN: (non-negative-number, positive-integer)
  • the final speed of the modified trajectory (m/s)
  • the number of time-steps required to complete the braking maneuver NOTE:
  • modifies the cycle in place for the braking trajectory

LegacyCycle Objects

class LegacyCycle(object)

Implementation of Cycle with legacy keys.

__init__

def __init__(cycle: Cycle)

Given cycle, returns legacy cycle.

cyc_equal

def cyc_equal(a: Cycle, b: Cycle) -> bool

Return True if a and b are equal

to_microtrips

def to_microtrips(cycle, stop_speed_m__s=1e-6, keep_name=False)

Split a cycle into an array of microtrips with one microtrip being a start to subsequent stop plus any idle (stopped time).

Arguments:


  • cycle - drive cycle converted to dictionary by cycle.to_dict()
  • stop_speed_m__s - speed at which vehicle is considered stopped for trip separation
  • keep_name - (optional) bool, if True and cycle contains "name", adds that name to all microtrips

make_cycle

def make_cycle(ts, vs, gs=None, rs=None) -> dict

(Array Num) (Array Num) (Array Num)? -> Dict Create a cycle from times, speeds, and grades. If grades is not specified, it is set to zero.

Arguments:


  • ts - array of times [s]
  • vs - array of vehicle speeds [mps]
  • gs - array of grades
  • rs - array of road types (charging or not)

equals

def equals(c1, c2) -> bool

Dict Dict -> Bool Returns true if the two cycles are equal, false otherwise

Arguments:


  • c1 - cycle as dictionary from to_dict()
  • c2 - cycle as dictionary from to_dict()

concat

def concat(cycles, name=None)

Concatenates cycles together one after another into a single dictionary (Array Dict) String -> Dict

Arguments:


  • cycles - (Array Dict)
  • name - (optional) string or None, if a string, adds the "name" key to the output

resample

def resample(cycle: Dict[str, Any],
             new_dt: Optional[float] = None,
             start_time: Optional[float] = None,
             end_time: Optional[float] = None,
             hold_keys: Optional[Set[str]] = None,
             hold_keys_next: Optional[Set[str]] = None,
             rate_keys: Optional[Set[str]] = None)

Cycle new_dt=?Real start_time=?Real end_time=?Real -> Cycle Resample a cycle with a new delta time from start time to end time.

  • cycle: Dict with keys 'time_s': numpy.array Real giving the elapsed time
  • new_dt: Real, optional the new delta time of the sampling. Defaults to the difference between the first two times of the cycle passed in
  • start_time: Real, optional the start time of the sample. Defaults to 0.0 seconds
  • end_time: Real, optional the end time of the cycle. Defaults to the last time of the passed in cycle.
  • hold_keys: None or (Set String), if specified, yields values that should be interpolated step-wise, holding their value until an explicit change (i.e., NOT interpolated)
  • hold_keys_next: None or (Set String), similar to hold_keys but yields values that should be interpolated step-wise, taking the NEXT value as the value (vs hold_keys which uses the previous)
  • rate_keys: None or (Set String), if specified, yields values that maintain the interpolated value of the given rate. So, for example, if a speed, will set the speed such that the distance traveled is consistent. Note: using rate keys for mps may result in non-zero starting and ending speeds Resamples all non-time metrics by the new sample time.

clip_by_times

def clip_by_times(cycle, t_end, t_start=0)

Cycle Number Number -> Cycle INPUT:

  • cycle: Dict, a legitimate driving cycle
  • t_start: Number, time to start
  • t_end: Number, time to end RETURNS: Dict, the cycle with fields snipped to times >= t_start and <= t_end Clip the cycle to the given times and return

accelerations

def accelerations(cycle)

Cycle -> Real Return the acceleration of the given cycle INPUTS:

  • cycle: Dict, a legitimate driving cycle OUTPUTS: Real, the maximum acceleration

peak_acceleration

def peak_acceleration(cycle)

Cycle -> Real Return the maximum acceleration of the given cycle INPUTS:

  • cycle: Dict, a legitimate driving cycle OUTPUTS: Real, the maximum acceleration

peak_deceleration

def peak_deceleration(cycle)

Cycle -> Real Return the minimum acceleration (maximum deceleration) of the given cycle INPUTS:

  • cycle: Dict, a legitimate driving cycle OUTPUTS: Real, the maximum acceleration

calc_constant_jerk_trajectory

def calc_constant_jerk_trajectory(n: int, D0: float, v0: float, Dr: float,
                                  vr: float, dt: float) -> tuple

Num Num Num Num Num Int -> (Tuple 'jerk_m__s3': Num, 'accel_m__s2': Num) INPUTS:

  • n: Int, number of time-steps away from rendezvous
  • D0: Num, distance of simulated vehicle (m/s)
  • v0: Num, speed of simulated vehicle (m/s)
  • Dr: Num, distance of rendezvous point (m)
  • vr: Num, speed of rendezvous point (m/s)
  • dt: Num, step duration (s) RETURNS: (Tuple 'jerk_m__s3': Num, 'accel_m__s2': Num) Returns the constant jerk and acceleration for initial time step.

accel_for_constant_jerk

def accel_for_constant_jerk(n, a0, k, dt)

Calculate the acceleration n timesteps away INPUTS:

  • n: Int, number of times steps away to calculate
  • a0: Num, initial acceleration (m/s2)
  • k: Num, constant jerk (m/s3)
  • dt: Num, time-step duration in seconds NOTE:
  • this is the constant acceleration over the time-step from sample n to sample n+1 RETURN: Num, the acceleration n timesteps away (m/s2)

speed_for_constant_jerk

def speed_for_constant_jerk(n, v0, a0, k, dt)

Int Num Num Num Num -> Num Calculate speed (m/s) n timesteps away INPUTS:

  • n: Int, numer of timesteps away to calculate
  • v0: Num, initial speed (m/s)
  • a0: Num, initial acceleration (m/s2)
  • k: Num, constant jerk
  • dt: Num, duration of a timestep (s) NOTE:
  • this is the speed at sample n
  • if n == 0, speed is v0
  • if n == 1, speed is v0 + a0*dt, etc. RETURN: Num, the speed n timesteps away (m/s)

dist_for_constant_jerk

def dist_for_constant_jerk(n, d0, v0, a0, k, dt)

Calculate distance (m) after n timesteps INPUTS:

  • n: Int, numer of timesteps away to calculate
  • d0: Num, initial distance (m)
  • v0: Num, initial speed (m/s)
  • a0: Num, initial acceleration (m/s2)
  • k: Num, constant jerk
  • dt: Num, duration of a timestep (s) NOTE:
  • this is the distance traveled from start (i.e., n=0) measured at sample point n RETURN: Num, the distance at n timesteps away (m)

detect_passing

def detect_passing(cyc: Cycle,
                   cyc0: Cycle,
                   i: int,
                   dist_tol_m: float = 0.1) -> PassingInfo

Reports back information of the first point where cyc passes cyc0, starting at step i until the next stop of cyc.

  • cyc: fastsim.Cycle, the proposed cycle of the vehicle under simulation
  • cyc0: fastsim.Cycle, the reference/lead vehicle/shadow cycle to compare with
  • i: int, the time-step index to consider
  • dist_tol_m: float, the distance tolerance away from lead vehicle to be seen as "deviated" from the reference/shadow trace (m) RETURNS: PassingInfo

average_step_speeds

def average_step_speeds(cyc: Cycle) -> np.ndarray

Calculate the average speed per each step in m/s

average_step_speed_at

def average_step_speed_at(cyc: Cycle, i: int) -> float

Calculate the average step speed at step i in m/s (i.e., from sample point i-1 to i)

trapz_step_distances

def trapz_step_distances(cyc: Cycle) -> np.ndarray

Sum of the distance traveled over each step using trapezoidal integration

trapz_step_start_distance

def trapz_step_start_distance(cyc: Cycle, i: int) -> float

The distance traveled from start at the beginning of step i (i.e., distance traveled up to sample point i-1) Distance is in meters.

trapz_distance_for_step

def trapz_distance_for_step(cyc: Cycle, i: int) -> float

The distance traveled during step i in meters (i.e., from sample point i-1 to i)

trapz_distance_over_range

def trapz_distance_over_range(cyc: Cycle, i_start: int, i_end: int) -> float

Calculate the distance from step i_start to the start of step i_end (i.e., distance from sample point i_start-1 to i_end-1)

extend_cycle

def extend_cycle(cyc: Cycle,
                 absolute_time_s: float = 0.0,
                 time_fraction: float = 0.0,
                 use_rust: bool = False) -> Cycle
  • cyc: fastsim.cycle.Cycle
  • absolute_time_s: float, the seconds to extend
  • time_fraction: float, the fraction of the original cycle time to add on
  • use_rust: bool, if True, return a RustCycle instance, else a normal Python Cycle RETURNS: fastsim.cycle.Cycle (or fastsimrust.RustCycle), the new cycle with stopped time appended NOTE: additional time is rounded to the nearest second

create_dist_and_target_speeds_by_microtrip

def create_dist_and_target_speeds_by_microtrip(
        cyc: Cycle,
        blend_factor: float = 0.0,
        min_target_speed_mps: float = 8.0) -> list

Create distance and target speeds by microtrip This helper function splits a cycle up into microtrips and returns a list of 2-tuples of: (distance from start in meters, target speed in meters/second)

  • cyc: the cycle to operate on
  • blend_factor: float, from 0 to 1 if 0, use average speed of the microtrip if 1, use average speed while moving (i.e., no stopped time) else something in between
  • min_target_speed_mps: float, the minimum target speed allowed (m/s) RETURN: list of 2-tuple of (float, float) representing the distance of start of each microtrip and target speed for that microtrip NOTE: target speed per microtrip is not allowed to be below min_target_speed_mps

copy_cycle

def copy_cycle(
    cyc: Cycle,
    return_type: str = None,
    deep: bool = True
) -> Dict[str, np.ndarray] | Cycle | LegacyCycle | RustCycle

Returns copy of Cycle.

Arguments:

  • cyc - instantianed Cycle or CycleJit return_type:
  • default - infer from type of cyc
  • 'dict' - dict
  • 'python' - Cycle
  • 'legacy' - LegacyCycle
  • 'rust' - RustCycle
  • deep - if True, uses deepcopy on everything

fastsim.utils.utilities

Various optional utilities that may support some applications of FASTSim.

R_air

J/(kg*K)

get_rho_air

def get_rho_air(temperature_degC, elevation_m=180)

Returns air density [kg/m**3] for given elevation and temperature. Source: https://www.grc.nasa.gov/WWW/K-12/rocket/atmosmet.html

Arguments:


temperature_degC : ambient temperature [°C] elevation_m : elevation above sea level [m]. Default 180 m is for Chicago, IL

l__100km_to_mpg

def l__100km_to_mpg(l__100km)

Given fuel economy in L/100km, returns mpg.

mpg_to_l__100km

def mpg_to_l__100km(mpg)

Given fuel economy in mpg, returns L/100km.

rollav

def rollav(x, y, width=10)

Returns x-weighted backward-looking rolling average of y. Good for resampling data that needs to preserve cumulative information.

Arguments:


x : x data y : y data (len(y) == len(x) must be True)

  • width - rolling average width

camel_to_snake

def camel_to_snake(name)

Given camelCase, returns snake_case.

set_log_level

def set_log_level(level: str | int) -> int

Sets logging level for both Python and Rust FASTSim. The default logging level is WARNING (30). https://docs.python.org/3/library/logging.html#logging-levels

Parameters

level: str | int Logging level to set. str level name or int logging level

=========== ================
Level       Numeric value
=========== ================
CRITICAL    50
ERROR       40
WARNING     30
INFO        20
DEBUG       10
NOTSET      0

Returns

int Previous log level

disable_logging

def disable_logging() -> int

Disable FASTSim logs from being shown by setting log level to CRITICAL+1 (51).

Returns

int Previous log level

enable_logging

def enable_logging(level: Optional[int | str] = None)

Re-enable FASTSim logging, optionally to a specified log level, otherwise to the default WARNING (30) level.

Parameters

level: str | int, optional Logging level to set. str level name or int logging level. See utils.set_log_level() docstring for more details on logging levels.

suppress_logging

@contextmanager
def suppress_logging()

Disable, then re-enable FASTSim logging using a context manager. The log level is returned to its previous value. Logging is re-enabled even if the nested code throws an error.

Example:

with fastsim.utils.suppress_logging():
    ...  # do stuff with logging suppressed

get_containers_with_path

def get_containers_with_path(struct: Any, path: str | list) -> list

Get all attributes containers from nested struct using path to attribute.

Parameters

struct: Any Outermost struct where first name in path is an attribute path: str | list Dot-separated path, e.g. "sd.veh.drag_coef" or ["sd", "veh", "drag_coef"]

Returns

List[Any] Ordered list of containers, from outermost to innermost

get_attr_with_path

def get_attr_with_path(struct: Any, path: str | list) -> Any

Get attribute from nested struct using path to attribute.

Parameters

struct: Any Outermost struct where first name in path is an attribute path: str | list Dot-separated path, e.g. "sd.veh.drag_coef" or ["sd", "veh", "drag_coef"]

Returns

Any Requested attribute

set_attr_with_path

def set_attr_with_path(struct: Any, path: str | list, value: Any) -> Any

Set attribute on nested struct using path to attribute.

Parameters

struct: Any Outermost struct where first name in path is an attribute path: str | list Dot-separated path, e.g. "sd.veh.drag_coef" or ["sd", "veh", "drag_coef"] value: Any

Returns

Any struct with nested value set

set_attrs_with_path

def set_attrs_with_path(struct: Any, paths_and_values: Dict[str, Any]) -> Any

Set multiple attributes on nested struct using path: value pairs.

Parameters

struct: Any Outermost struct where first name in path is an attribute paths_and_values: Dict[str | list, Any] Mapping of dot-separated path (e.g. sd.veh.drag_coef or ["sd", "veh", "drag_coef"]) to values (e.g. 0.32)

Returns

Any struct with nested values set

calculate_tire_radius

def calculate_tire_radius(tire_code: str, units: str = "m")

Calculate tire radius from ISO tire code, with variable units

Unit options: "m", "cm", "mm", "ft", "in". Default is "m".

Examples:

fastsim.utils.calculate_tire_radius("P205/60R16") 0.3262 fastsim.utils.calculate_tire_radius("225/70Rx19.5G", units="in") 15.950787401574804

show_plots

def show_plots() -> bool

Returns true if plots should be displayed

do_tests

def do_tests() -> bool

Returns true if plots should be displayed

copy_demo_files

def copy_demo_files(path_for_copies: Path = Path("demos"))

Copies demo files from demos folder into specified local directory

Arguments

  • - path_for_copies: path to copy files into (relative or absolute in)

Warning

Running this function will overwrite existing files with the same name in the specified directory, so make sure any files with changes you'd like to keep are renamed.

fastsim.utils

fastsim.utils.vehicle_import_preproc

Module for pre-processing data from fueleconomy.gov and EPA vehicle testing that is used for "vehicle import" functionality. Vehicle import allows FASTSim to import vehicles by specifying make, model, and year. See fastsim.demos.vehicle_import_demo for usage.

In order to run this pre-processing script, the data from the sources below should be placed in the "input_dir" (see the run function).

fueleconomy.gov data: https://www.fueleconomy.gov/feg/download.shtml

  • vehicles.csv
  • emissions.csv

EPA Test data: https://www.epa.gov/compliance-and-fuel-economy-data/data-cars-used-testing-fuel-economy

  • the data for emissions by year; e.g., 20tstcar-2021-03-02.xlsx
  • note: there are multiple formats in use

process_csv

def process_csv(path: Path, fn)

write_csvs_for_each_year

def write_csvs_for_each_year(output_data_dir, basename, rows_by_year, header)

sort_fueleconomygov_data_by_year

def sort_fueleconomygov_data_by_year(input_data_dir: Path,
                                     output_data_dir: Path)

Opens up the vehicles.csv and emissions.csv and breaks them up to be by year and saves them again.

xlsx_to_csv

def xlsx_to_csv(xlsx_path, csv_path)

process_epa_test_data

def process_epa_test_data(input_dir, output_dir)

create_zip_archives_by_year

def create_zip_archives_by_year(files_dir, zip_dir)

Takes files in the files_dir that start with \d\d\d\d-*.csv and adds them to a \d\d\d\d.zip in the zip_dir

fastsim.simdrive

Module containing classes and methods for simulating vehicle drive cycle.

SimDriveParams Objects

class SimDriveParams(object)

Class containing attributes used for configuring sim_drive. Usually the defaults are ok, and there will be no need to use this.

See comments in code for descriptions of various parameters that affect simulation behavior. If, for example, you want to suppress warning messages, use the following pastable code EXAMPLE:

import logging logging.getLogger("fastsim").setLevel(logging.DEBUG)

from_dict

@classmethod
def from_dict(cls, sdp_dict)

Create from a dictionary

__init__

def __init__()

Default values that affect simulation behavior. Can be modified after instantiation.

to_rust

def to_rust()

Change to the Rust version

reset_orphaned

def reset_orphaned()

Dummy method for flexibility between Rust/Python version interfaces

copy_sim_params

def copy_sim_params(sdp: SimDriveParams, return_type: str = None)

Returns copy of SimDriveParams.

Arguments:

  • sdp - instantianed SimDriveParams or RustSimDriveParams return_type:
  • default - infer from type of sdp
  • 'dict' - dict
  • 'python' - SimDriveParams
  • 'rust' - RustSimDriveParams
  • deep - if True, uses deepcopy on everything

sim_params_equal

def sim_params_equal(a: SimDriveParams, b: SimDriveParams) -> bool

Returns True if objects are structurally equal (i.e., equal by value), else false.

Arguments:

  • a - instantiated SimDriveParams object
  • b - instantiated SimDriveParams object

SimDrive Objects

class SimDrive(object)

Class containing methods for running FASTSim vehicle fuel economy simulations. This class is not compiled and will run slower for large batch runs.

Arguments:


  • cyc - cycle.Cycle instance
  • veh - vehicle.Vehicle instance

__init__

def __init__(cyc: cycle.Cycle, veh: vehicle.Vehicle)

Initalizes arrays, given vehicle.Vehicle() and cycle.Cycle() as arguments. sim_params is needed only if non-default behavior is desired.

gap_to_lead_vehicle_m

@property
def gap_to_lead_vehicle_m()

Provides the gap-with lead vehicle from start to finish

sim_drive

def sim_drive(init_soc: Optional[float] = None,
              aux_in_kw_override: Optional[np.ndarray] = None)

Initialize and run sim_drive_walk as appropriate for vehicle attribute vehPtType. Arguments

init_soc: initial SOC for electrified vehicles.
aux_in_kw: aux_in_kw override. Array of same length as cyc.time_s.
Default of None causes veh.aux_kw to be used.

init_for_step

def init_for_step(init_soc: float,
                  aux_in_kw_override: Optional[np.ndarray] = None)

This is a specialty method which should be called prior to using sim_drive_step in a loop.

Arguments

init_soc: initial battery state-of-charge (SOC) for electrified vehicles aux_in_kw: aux_in_kw override. Array of same length as cyc.time_s.
Default of None causes veh.aux_kw to be used.

sim_drive_walk

def sim_drive_walk(init_soc: float,
                   aux_in_kw_override: Optional[np.ndarray] = None)

Receives second-by-second cycle information, vehicle properties, and an initial state of charge and runs sim_drive_step to perform a backward facing powertrain simulation. Method 'sim_drive' runs this iteratively to achieve correct SOC initial and final conditions, as needed.

Arguments

init_soc: initial battery state-of-charge (SOC) for electrified vehicles aux_in_kw: aux_in_kw override. Array of same length as cyc.time_s.
Default of None causes veh.aux_kw to be used.

activate_eco_cruise

def activate_eco_cruise(by_microtrip: bool = False,
                        extend_fraction: float = 0.1,
                        blend_factor: float = 0.0,
                        min_target_speed_m_per_s: float = 8.0)

Sets the intelligent driver model parameters for an eco-cruise driving trajectory. This is a convenience method instead of setting the sim_params.idm* parameters yourself.

  • by_microtrip: bool, if True, target speed is set by microtrip, else by cycle
  • extend_fraction: float, the fraction of time to extend the cycle to allow for catch-up of the following vehicle
  • blend_factor: float, a value between 0 and 1; only used of by_microtrip is True, blends between microtrip average speed and microtrip average speed when moving. Must be between 0 and 1 inclusive

sim_drive_step

def sim_drive_step()

Step through 1 time step. TODO: create self.set_speed_for_target_gap(self.i): TODO: consider implementing for battery SOC dependence

solve_step

def solve_step(i)

Perform all the calculations to solve 1 time step.

set_misc_calcs

def set_misc_calcs(i)

Sets misc. calculations at time step 'i'

Arguments:


  • i - index of time step

set_comp_lims

def set_comp_lims(i)

Sets component limits for time step 'i' Arguments

i: index of time step init_soc: initial SOC for electrified vehicles

set_power_calcs

def set_power_calcs(i)

Calculate power requirements to meet cycle and determine if cycle can be met.
Arguments

i: index of time step

set_ach_speed

def set_ach_speed(i)

Calculate actual speed achieved if vehicle hardware cannot achieve trace speed. Arguments

i: index of time step

set_hybrid_cont_calcs

def set_hybrid_cont_calcs(i)

Hybrid control calculations. Arguments

i: index of time step

set_fc_forced_state

def set_fc_forced_state(i)

Calculate control variables related to engine on/off state Arguments

i: index of time step

set_hybrid_cont_decisions

def set_hybrid_cont_decisions(i)

Hybrid control decisions. Arguments

i: index of time step

set_fc_power

def set_fc_power(i)

Sets fcKwOutAch and fcKwInAch. Arguments

i: index of time step

set_post_scalars

def set_post_scalars()

Sets scalar variables that can be calculated after a cycle is run. This includes mpgge, various energy metrics, and others

to_rust

def to_rust()

Create a rust version of SimDrive

copy_sim_drive

def copy_sim_drive(sd: SimDrive,
                   return_type: str = None,
                   deep: bool = True) -> SimDrive

Returns copy of SimDriveClassic or SimDriveJit as SimDriveClassic.

Arguments:


  • sd - instantiated SimDriveClassic or SimDriveJit return_type:
  • default - infer from type of sd
  • 'python' - Cycle
  • 'legacy' - LegacyCycle
  • 'rust' - RustCycle
  • deep - if True, uses deepcopy on everything

sim_drive_equal

def sim_drive_equal(a: SimDrive, b: SimDrive) -> bool

run_simdrive_for_accel_test

def run_simdrive_for_accel_test(sd: SimDrive)

Initialize and run sim_drive_walk as appropriate for vehicle attribute vehPtType.

SimDrivePost Objects

class SimDrivePost(object)

Class for post-processing of SimDrive instance. Requires already-run SimDrive instance.

__init__

def __init__(sim_drive: SimDrive)

Arguments:


  • sim_drive - solved sim_drive object

get_diagnostics

def get_diagnostics()

This method is to be run after runing sim_drive if diagnostic variables are needed. Diagnostic variables are returned in a dict. Diagnostic variables include:

  • final integrated value of all positive powers
  • final integrated value of all negative powers
  • total distance traveled
  • miles per gallon gasoline equivalent (mpgge)

set_battery_wear

def set_battery_wear()

Battery wear calcs

SimDriveJit

def SimDriveJit(cyc_jit, veh_jit)

deprecated

estimate_soc_corrected_fuel_kJ

def estimate_soc_corrected_fuel_kJ(sd: SimDrive) -> float
  • sd: SimDriveClassic, the simdrive instance after simulation RETURN: number, the kJ of fuel corrected for SOC imbalance

fastsim.demos

fastsim.demos.fusion_thermal_demo

fastsim.demos.stop_start_demo

fastsim.demos.2017_Ford_F150_thermal_val

lhv_fuel_btu_per_lbm

from "2012FordFusionV6Overview V5.pdf"

fastsim.demos.test_demos

fastsim.demos.cav_demo

fastsim.demos.vehicle_import_demo

Vehicle Import Demonstration This module demonstrates the vehicle import API

other_inputs

None -> calculate from EPA data

fastsim.demos.time_dilation_demo

fastsim.demos.cav_sweep

ABSOLUTE_EXTENDED_TIME_S

180.0

make_debug_plot

def make_debug_plot(sd: fastsim.simdrive.SimDrive,
                    save_file: Optional[str] = None,
                    do_show: bool = False)

load_cycle

def load_cycle(cyc_name: str, use_rust: bool = False) -> fastsim.cycle.Cycle

Load the given cycle and return

main

def main(cycle_name=None,
         powertrain=None,
         do_show=None,
         use_rust=False,
         verbose=True,
         save_dir=None,
         maneuver=None)

fastsim.demos.fusion_thermal_cal

lhv_fuel_btu_per_lbm

from "2012FordFusionV6Overview V5.pdf"

fastsim.demos.wltc_calibration

WILLANS_FACTOR

gCO2/MJ

E10_HEAT_VALUE

kWh/L

fastsim.demos.accel_demo

create_accel_cyc

def create_accel_cyc(length_in_seconds=300, spd_mph=89.48, grade=0.0, hz=10)

Create a synthetic Drive Cycle for acceleration targeting. Defaults to a 15 second acceleration cycle. Should be adjusted based on target acceleration time and initial vehicle acceleration time, so that time isn't wasted on cycles that are needlessly long.

spd_mph @ 89.48 FASTSim XL version mph default speed for acceleration cycles grade @ 0 and hz @ 10 also matches XL version settings

main

def main()

Arguments:


fastsim.demos.timing_demo

fastsim.demos.calibration_demo

Script for demonstrating how to calibrate a vehicle model. See FASTSim Calibration/Validation documentation for more info on how to use this.

lhv_fuel_btu_per_lbm

from "2012FordFusionV6Overview V5.pdf"

load_data

def load_data() -> Dict[str, pd.DataFrame]

Loads dyno test data from csv files 61811011, 61811012, 61811013, and 61811014 downloaded from https://www.anl.gov/taps/d3-2018-toyota-camry-xle

Returns:

Dict[str, pd.DataFrame]: dictionary of dataframes

get_cal_and_val_objs

def get_cal_and_val_objs(dfs: Dict[str, pd.DataFrame] = load_data()) -> Tuple[
        fsim.cal.ModelObjectives, fsim.cal.ModelObjectives, List[Tuple[
            float, float]]]

Returns objects to be used by PyMOO optimizer

Arguments:

  • dfs Dict[str, pd.DataFrame] - output of load_data

Returns:

Tuple[fsim.cal.ModelObjectives, fsim.cal.ModelObjectives, List[Tuple[float, float]]]: description

fastsim.demos.fusion_thermal_cal_post

save_path

seems to be best

fastsim.demos.demo

v2

should not have derived params

data_path

path to drive cycles

veh

load vehicle model

veh

load vehicle model

veh

load vehicle using name

fastsim.demos.demo_eu_vehicle_wltp

fastsim.demos.demo_abc_drag_coef_conv

fastsim.parameters

Global constants representing unit conversions that shourd never change, physical properties that should rarely change, and vehicle model parameters that can be modified by advanced users.

PhysicalProperties Objects

class PhysicalProperties(object)

Container class for physical constants that could change under certain special circumstances (e.g. high altitude or extreme weather)

copy_physical_properties

def copy_physical_properties(p: PhysicalProperties,
                             return_type: str = None,
                             deep: bool = True)

Returns copy of PhysicalProperties.

Arguments:

  • p - instantianed PhysicalProperties or RustPhysicalProperties return_type:
  • default - infer from type of p
  • 'dict' - dict
  • 'python' - PhysicalProperties
  • 'legacy' - LegacyPhysicalProperties -- NOT IMPLEMENTED YET; is it needed?
  • 'rust' - RustPhysicalProperties
  • deep - if True, uses deepcopy on everything

physical_properties_equal

def physical_properties_equal(a: PhysicalProperties,
                              b: PhysicalProperties) -> bool

Return True if the physical properties are equal by value

fc_perc_out_array

hardcoded ***

chg_eff

charger efficiency for PEVs, this should probably not be hard coded long term

fastsim.vehicle

Module containing classes and methods for for loading vehicle data.

clean_data

def clean_data(raw_data)

Cleans up data formatting. Argument:

raw_data: cell of vehicle dataframe

Output: clean_data: cleaned up data

Vehicle Objects

@dataclass
class Vehicle(object)

Class for loading and contaning vehicle attributes See from_vehdb, from_file, and from_dict methods for usage instructions.

from_vehdb

@classmethod
def from_vehdb(cls,
               vnum: int,
               veh_file: str = None,
               to_rust: bool = False) -> Self

Load vehicle vnum from default vehdb or veh_file.

Arguments:

  • vnum - vehicle number
  • veh_file - path to vehicle database file
  • to_rust - if True, convert to rust-compatible vehicle

from_file

@classmethod
def from_file(cls,
              filename: str,
              vnum: int = None,
              to_rust: bool = False) -> Self

Loads vehicle from file filename (str). Looks in working dir and then fastsim/resources/vehdb, which also contains numerous examples of vehicle csv files. vnum is needed for multi-vehicle files.

Arguments:

  • filename - path to vehicle database file
  • vnum - vehicle number
  • to_rust - if True, convert to rust-compatible vehicle

from_df

@classmethod
def from_df(cls,
            vehdf: pd.DataFrame,
            vnum: int,
            veh_file: Path,
            to_rust: bool = False) -> Self

Given vehdf, generates dict to feed to from_dict.

Arguments:

  • vehdf - pandas dataframe of vehicle attributes
  • vnum - vehicle number
  • veh_file - path to vehicle database file
  • to_rust - if True, convert to rust-compatible vehicle

from_dict

@classmethod
def from_dict(cls, veh_dict: dict, to_rust: bool = False) -> Self

Load vehicle from dict with snake_case key names.

Arguments:

  • veh_dict - dict of vehicle attributes
  • to_rust - if True, convert to rust-compatible vehicle

__post_init__

def __post_init__(converted_to_rust: bool = False)

Sets derived parameters.

Arguments:


  • fc_peak_eff_override - float (0, 1) or -1, if provided and not -1, overrides engine peak efficiency with proportional scaling. Default of -1 has no effect.
  • mc_peak_eff_override - float (0, 1) or -1, if provided and not -1, overrides motor peak efficiency with proportional scaling. Default of -1 has no effect.

set_derived

def set_derived()

Sets derived parameters.

Arguments:


  • fc_peak_eff_override - float (0, 1) or -1, if provided and not -1, overrides engine peak efficiency with proportional scaling. Default of -1 has no effect.
  • mc_peak_eff_override - float (0, 1) or -1, if provided and not -1, overrides motor peak efficiency with proportional scaling. Default of -1 has no effect.

set_veh_mass

def set_veh_mass()

Calculate total vehicle mass. Sum up component masses if positive real number is not specified for self.veh_override_kg

veh_type_selection

@property
def veh_type_selection() -> str

Copying veh_pt_type to additional key to be consistent with Excel version but not used in Python version

get_mcPeakEff

def get_mcPeakEff() -> float

Return np.max(self.mc_eff_array)

set_mcPeakEff

def set_mcPeakEff(new_peak)

Set motor peak efficiency EVERYWHERE.

Arguments:


  • new_peak - float, new peak motor efficiency in decimal form

get_fcPeakEff

def get_fcPeakEff() -> float

Return np.max(self.fc_eff_array)

set_fcPeakEff

def set_fcPeakEff(new_peak)

Set fc peak efficiency EVERWHERE.

Arguments:


  • new_peak - float, new peak fc efficiency in decimal form

get_numba_veh

def get_numba_veh()

Deprecated.

to_rust

def to_rust() -> RustVehicle

Return a Rust version of the vehicle

reset_orphaned

def reset_orphaned()

Dummy method for flexibility between Rust/Python version interfaces

LegacyVehicle Objects

class LegacyVehicle(object)

Implementation of Vehicle with legacy keys.

__init__

def __init__(vehicle: Vehicle)

Given cycle, returns legacy cycle.

to_native_type

def to_native_type(value)

Attempts to map from numpy and other types to python native for better yaml (de-)serialization

copy_vehicle

def copy_vehicle(
    veh: Vehicle,
    return_type: str = None,
    deep: bool = True
) -> Dict[str, np.ndarray] | Vehicle | LegacyVehicle | RustVehicle

Returns copy of Vehicle.

Arguments:

  • veh - instantiated Vehicle or RustVehicle return_type:
  • 'dict' - dict
  • 'vehicle' - Vehicle
  • 'legacy' - LegacyVehicle
  • 'rust' - RustVehicle

veh_equal

def veh_equal(veh1: Vehicle, veh2: Vehicle, full_out: bool = False) -> bool

Given veh1 and veh2, which can be Vehicle and/or RustVehicle instances, return True if equal.

Arguments:


fastsim.auxiliaries

Auxiliary functions that require fastsim and provide faster access FASTSim vehicle properties.

R_air

J/(kg*K)

abc_to_drag_coeffs

def abc_to_drag_coeffs(veh: Vehicle,
                       a_lbf: float,
                       b_lbf__mph: float,
                       c_lbf__mph2: float,
                       custom_rho: bool = False,
                       custom_rho_temp_degC: float = 20.,
                       custom_rho_elevation_m: float = 180.,
                       simdrive_optimize: bool = True,
                       show_plots: bool = False,
                       use_rust=True) -> Tuple[float, float]

For a given vehicle and target A, B, and C coefficients; calculate and return drag and rolling resistance coefficients.

Arguments:


  • veh - vehicle.Vehicle with all parameters correct except for drag and rolling resistance coefficients a_lbf, b_lbf__mph, c_lbf__mph2: coastdown coefficients for road load [lbf] vs speed [mph]
  • custom_rho - if True, use fastsim.utilities.get_rho_air() to calculate the current ambient density
  • custom_rho_temp_degC - ambient temperature [degree C] for get_rho_air(); will only be used when custom_rho is True
  • custom_rho_elevation_m - location elevation [degree C] for get_rho_air(); will only be used when custom_rho is True; default value is elevation of Chicago, IL
  • simdrive_optimize - if True, use SimDrive to optimize the drag and rolling resistance; otherwise, directly use target A, B, C to calculate the results
  • show_plots - if True, plots are shown
  • use_rust - if True, use rust implementation of drag coefficient calculation.

drag_coeffs_to_abc

def drag_coeffs_to_abc(veh,
                       custom_rho: bool = False,
                       custom_rho_temp_degC: float = 20.,
                       custom_rho_elevation_m: float = 180.,
                       fit_with_curve: bool = False,
                       show_plots: bool = False) -> Tuple[float, float, float]

For a given vehicle mass, frontal area, dragCoef, and wheelRrCoef, calculate and return ABCs.

Arguments:


  • veh - vehicle.Vehicle with correct drag and rolling resistance
  • custom_rho - if True, use fastsim.utilities.get_rho_air() to calculate the current ambient density
  • custom_rho_temp_degC - ambient temperature [degree C] for get_rho_air(); will only be used when custom_rho is True
  • custom_rho_elevation_m - location elevation [degree C] for get_rho_air(); will only be used when custom_rho is True; default value is elevation of Chicago, IL
  • fit_with_curve - if True, use scipy.curve_fit to get A, B, Cs; otherwise, directly calculate A, B, Cs from given drag and rolling resistance
  • show_plots - if True, plots are shown

Returns:

a_lbf, b_lbf__mph, c_lbf__mph2: coastdown coefficients for road load [lbf] vs speed [mph]

fastsim.tests.test_rust

Tests using the Rust versions of SimDrive, Cycle, and Vehicle

TestRust Objects

class TestRust(unittest.TestCase)

test_discrepancies

def test_discrepancies(veh_type="ALL", use_dict=True, cyc_name="udds")

Function for testing for Rust/Python discrepancies, both in the vehicle database CSV as well as the individual model files. Uses test_vehicle_for_discrepancies as backend.

Arguments:

  • veh_type - type of vehicle to test for discrepancies can be "CONV", "HEV", "PHEV", "BEV", or "ALL"
  • use_dict - if True, use small cyc_dict to speed up test if false, default to UDDS
  • cyc_name - name of cycle from database to use if use_dict == False

test_vehicle_for_discrepancies

def test_vehicle_for_discrepancies(vnum=1,
                                   veh_filename=None,
                                   cyc_dict=None,
                                   cyc_name="udds")

Test for finding discrepancies between Rust and Python for single vehicle.

Arguments:

  • vnum - vehicle database number, optional, default option without any arguments
  • veh_filename - vehicle filename from vehdb folder, optional
  • cyc_dict - cycle dictionary for custom cycle, optional
  • cyc_name - cycle name from cycle database, optional

test_fueling_prediction_for_multiple_vehicle

def test_fueling_prediction_for_multiple_vehicle()

This test assures that Rust and Python agree on at least one example of all permutations of veh_pt_type and fc_eff_type.

fastsim.tests.test_following

Tests that check the drive cycle modification functionality.

TestFollowing Objects

class TestFollowing(unittest.TestCase)

test_that_we_have_a_gap_between_us_and_the_lead_vehicle

def test_that_we_have_a_gap_between_us_and_the_lead_vehicle()

A positive gap should exist between us and the lead vehicle

test_that_the_gap_changes_over_the_cycle

def test_that_the_gap_changes_over_the_cycle()

Ensure that our gap calculation is doing something

test_that_following_works_over_parameter_sweep

def test_that_following_works_over_parameter_sweep()

We're going to sweep through all of the parameters and see how it goes

test_that_we_can_use_the_idm

def test_that_we_can_use_the_idm()

Tests use of the IDM model for following

test_sweeping_idm_parameters

def test_sweeping_idm_parameters()

Tests use of the IDM model for following

test_distance_based_grade_on_following

def test_distance_based_grade_on_following()

Tests use of the IDM model for following

fastsim.tests.test_vs_excel

Module for comparing python results with Excel by running all the vehicles in both Excel (uses archived results if Excel version not available) and Python FASTSim for both UDDS and HWFET cycles.

run

def run(vehicles=np.arange(1, 27), verbose=True, use_rust=False)

Runs python fastsim through 26 vehicles and returns list of dictionaries containing scenario descriptions.

Arguments:


verbose : Boolean if True, print progress

  • use_rust - Boolean, if True, use Rust versions of classes

run_excel

def run_excel(
        vehicles=np.arange(1, 28), prev_res_path=PREV_RES_PATH,
        rerun_excel=False)

Runs excel fastsim through 26 vehicles and returns list of dictionaries containing scenario descriptions.

Arguments:


prev_res_path : path (str) to prevous results in pickle (*.p) file rerun_excel : (Boolean) if True, re-runs Excel FASTSim, which must be open

compare

def compare(res_python, res_excel, err_tol=0.001, verbose=True)

Finds common vehicle names in both excel and python (hypothetically all of them, but there may be discrepancies) and then compares fuel economy results. Arguments: results from run_python and run_excel Returns dict of comparsion results.

Arguments:


res_python : output of run_python res_excel : output of run_excel err_tol : (float) error tolerance, default=1e-3 verbose : Boolean if True, print progress

main

def main(err_tol=0.001,
         prev_res_path=PREV_RES_PATH,
         rerun_excel=False,
         verbose=False)

Function for running both python and excel and then comparing

Arguments:


err_tol : (float) error tolerance, default=1e-3 prev_res_path : path (str) to prevous results in pickle (*.p) file rerun_excel : (Boolean) if True, re-runs Excel FASTSim, which must be open verbose : Boolean if True, print progress

TestExcel Objects

class TestExcel(unittest.TestCase)

test_vs_excel

def test_vs_excel()

Compares results against archived Excel results.

fastsim.tests

Package containing tests for FASTSim.

run_functional_tests

def run_functional_tests()

Runs all functional tests.

fastsim.tests.test_eco_cruise

Test the eco-cruise feature in FASTSim

fastsim.tests.test_coasting

Tests that check the drive cycle modification functionality.

make_coasting_plot

def make_coasting_plot(
        cyc0: fastsim.cycle.Cycle,
        cyc: fastsim.cycle.Cycle,
        use_mph: bool = False,
        title: Optional[str] = None,
        save_file: Optional[str] = None,
        do_show: bool = False,
        verbose: bool = False,
        gap_offset_m: float = 0.0,
        coast_brake_start_speed_m_per_s: Optional[float] = None)
  • cyc0: Cycle, the reference cycle (the "shadow trace" or "lead vehicle")
  • cyc: Cycle, the actual cycle driven
  • use_mph: Bool, if True, plot in miles per hour, else m/s
  • title: None or string, if string, set the title
  • save_file: (Or None string), if specified, save the file to disk
  • do_show: Bool, whether to show the file or not
  • verbose: Bool, if True, prints out
  • gap_offset_m: number, an offset to apply to the gap metrics (m)
  • coast_brake_start_speed_m_per_s: None | number, if supplied, plots the coast-start speed (m/s) RETURN: None
  • saves creates the given file and shows it

make_dvdd_plot

def make_dvdd_plot(cyc: fastsim.cycle.Cycle,
                   coast_to_break_speed_m__s: Union[float, None] = None,
                   use_mph: bool = False,
                   save_file: Union[None, str] = None,
                   do_show: bool = False,
                   curve_fit: bool = True,
                   additional_xs: Union[None, List[float]] = None,
                   additional_ys: Union[None, List[float]] = None)

Create a change in speed (dv) by change in distance (dd) plot

TestCoasting Objects

class TestCoasting(unittest.TestCase)

test_cycle_reported_distance_traveled_m

def test_cycle_reported_distance_traveled_m()

test_cycle_modifications_with_constant_jerk

def test_cycle_modifications_with_constant_jerk()

test_that_cycle_modifications_work_as_expected

def test_that_cycle_modifications_work_as_expected()

test_that_we_can_coast

def test_that_we_can_coast()

Test the standard interface to Eco-Approach for 'free coasting'

test_eco_approach_modeling

def test_eco_approach_modeling()

Test a simplified model of eco-approach

test_consistency_of_constant_jerk_trajectory

def test_consistency_of_constant_jerk_trajectory()

Confirm that acceleration, speed, and distances are as expected for constant jerk trajectory

test_that_final_speed_of_cycle_modification_matches_trajectory_calcs

def test_that_final_speed_of_cycle_modification_matches_trajectory_calcs()

test_that_cycle_distance_reported_is_correct

def test_that_cycle_distance_reported_is_correct()

Test the reported distances via cycDistMeters

test_brake_trajectory

def test_brake_trajectory()

test_logic_to_enter_eco_approach_automatically

def test_logic_to_enter_eco_approach_automatically()

Test that we can auto-enter eco-approach

test_that_coasting_works_going_uphill

def test_that_coasting_works_going_uphill()

Test coasting logic while hill climbing

test_that_coasting_logic_works_going_uphill

def test_that_coasting_logic_works_going_uphill()

When going uphill, we want to ensure we can still hit our coasting target

test_that_coasting_logic_works_going_downhill

def test_that_coasting_logic_works_going_downhill()

When going downhill, ensure we can still hit our coasting target

test_that_coasting_works_with_multiple_stops_and_grades

def test_that_coasting_works_with_multiple_stops_and_grades()

Ensure coasting hits distance target with multiple stops and both uphill/downhill

fastsim.tests.test_copy

Test various copy utilities

TestCopy Objects

class TestCopy(unittest.TestCase)

test_copy_cycle

def test_copy_cycle()

Test that cycle_copy works as expected

test_copy_physical_properties

def test_copy_physical_properties()

Test that copy_physical_properties works as expected

test_copy_vehicle

def test_copy_vehicle()

Test that vehicle_copy works as expected

test_copy_sim_params

def test_copy_sim_params()

Test that copy_sim_params works as expected

test_copy_sim_drive

def test_copy_sim_drive()

Test that copy_sim_drive works as expected

fastsim.tests.test_simdrive

Test suite for simdrive instantiation and usage.

TestSimDriveClassic Objects

class TestSimDriveClassic(unittest.TestCase)

Tests for fastsim.simdrive.SimDriveClassic methods

test_sim_drive_step

def test_sim_drive_step()

Verify that sim_drive_step produces an expected result.

test_sim_drive_walk

def test_sim_drive_walk()

Verify that sim_drive_walk produces an expected result.

fastsim.tests.test_logging

fastsim.tests.test_auxiliaries

fastsim.tests.test_cycle

Test suite for cycle instantiation and manipulation.

calc_distance_traveled_m

def calc_distance_traveled_m(cyc, up_to=None)

Calculate the distance traveled in meters

  • cyc: a cycle dictionary
  • up_to: None or a positive number indicating a time in seconds. Will calculate the distance up-to that given time RETURN: Number, the distance traveled in meters

dicts_are_equal

def dicts_are_equal(d1, d2, d1_name=None, d2_name=None)

Checks if dictionaries are equal

  • d1: dict
  • d2: dict
  • d1_name: None or string, the name used for dict 1 in messaging
  • d2_name: None or string, the name used for dict 1 in messaging RETURN: (boolean, (Array string)), Returns (True, []) if the dictionaries are equal; otherwise, returns (False, [... list of issues here])

TestCycle Objects

class TestCycle(unittest.TestCase)

test_monotonicity

def test_monotonicity()

checks that time is monotonically increasing

test_load_dict

def test_load_dict()

checks that conversion from dict works

test_that_udds_has_18_microtrips

def test_that_udds_has_18_microtrips()

Check that the number of microtrips equals expected

test_roundtrip_of_microtrip_and_concat

def test_roundtrip_of_microtrip_and_concat()

A cycle split into microtrips and concatenated back together should equal the original

test_roundtrip_of_microtrip_and_concat_using_keep_name_arg

def test_roundtrip_of_microtrip_and_concat_using_keep_name_arg()

A cycle split into microtrips and concatenated back together should equal the original

test_set_from_dict_for_a_microtrip

def test_set_from_dict_for_a_microtrip()

Test splitting into microtrips and setting is as expected

test_duration_of_concatenated_cycles_is_the_sum_of_the_components

def test_duration_of_concatenated_cycles_is_the_sum_of_the_components()

Test that two cycles concatenated have the same duration as the sum of the constituents

test_cycle_equality

def test_cycle_equality()

Test structural equality of driving cycles

test_that_cycle_resampling_works_as_expected

def test_that_cycle_resampling_works_as_expected()

Test resampling the values of a cycle

test_resampling_and_concatenating_cycles

def test_resampling_and_concatenating_cycles()

Test that concatenating cycles at different sampling rates works as expected

test_resampling_with_hold_keys

def test_resampling_with_hold_keys()

Test that 'hold_keys' works with resampling

test_that_resampling_preserves_total_distance_traveled_using_rate_keys

def test_that_resampling_preserves_total_distance_traveled_using_rate_keys()

Distance traveled before and after resampling should be the same when rate_keys are used

test_clip_by_times

def test_clip_by_times()

Test that clipping by times works as expected

test_get_accelerations

def test_get_accelerations()

Test getting and processing accelerations

test_that_copy_creates_idential_structures

def test_that_copy_creates_idential_structures()

Checks that copy methods produce identical cycles

test_make_cycle

def test_make_cycle()

Check that make_cycle works as expected

test_key_conversion

def test_key_conversion()

check that legacy keys can still be generated

test_get_grade_by_distance

def test_get_grade_by_distance()

check that we can lookup grade by distance

test_dt_s_vs_dt_s_at_i

def test_dt_s_vs_dt_s_at_i()

Test that dt_s_at_i is a true replacement for dt_s[i]

test_trapz_step_start_distance

def test_trapz_step_start_distance()

Test the implementation of trapz_step_start_distance

test_that_cycle_cache_interp_grade_substitutes_for_average_grade_over_range

def test_that_cycle_cache_interp_grade_substitutes_for_average_grade_over_range(
)

Ensure that CycleCache.interp_grade actually predicts the same values as Cycle.average_grade_over_range(d, 0.0, cache=None|CycleCache) with and without using CycleCache

test_that_trapz_step_start_distance_equals_cache_trapz_distances

def test_that_trapz_step_start_distance_equals_cache_trapz_distances()

Test that cycle.trapz_step_start_distance(self.cyc0, i) == self._cyc0_cache.trapz_distances_m[i-1]

test_average_grade_over_range_with_and_without_cache

def test_average_grade_over_range_with_and_without_cache()

Ensure that CycleCache usage only speeds things up; doesn't change values...

fastsim.tests.test_utils

fastsim.tests.test_soc_correction

Tests an HEV correction methodology versus other techniques

TestSocCorrection Objects

class TestSocCorrection(unittest.TestCase)

test_that_soc_correction_method_works

def test_that_soc_correction_method_works()

Test using an SOC equivalency method versus other techniques

fastsim.tests.test_cav_sweep

Test fastsim/demos/cav_sweep.py for regressions

fastsim.tests.test_vehicle

Test suite for cycle instantiation and manipulation.

TestVehicle Objects

class TestVehicle(unittest.TestCase)

test_equal

def test_equal()

Verify that a copied Vehicle and original are equal.

test_properties

def test_properties()

Verify that some of the property variables are working as expected.

test_fc_efficiency_override

def test_fc_efficiency_override()

Verify that we can scale FC

test_set_derived_init

def test_set_derived_init()

Verify that we can set derived parameters or not on init.

fastsim.tests.test_simdrivelabel

fastsim.tests.test_simdrive_sweep

Test script that saves results from 26 vehicles currently in master branch of FASTSim as of 17 December 2019 for 3 standard cycles. From command line, pass True (default if left blank) or False argument to use JIT compilation or not, respectively.

main

def main(err_tol=1e-4, verbose=True, use_rust=False)

Runs test test for 26 vehicles and 3 cycles. Test compares cumulative positive and negative energy values to a benchmark from earlier.

Arguments:


err_tol : error tolerance default of 1e-4 was selected to prevent minor errors from showing. As of 31 December 2020, a recent python update caused errors that are smaller than this and therefore ok to neglect.

  • verbose - if True, prints progress
  • use_rust - Boolean, if True, use Rust version of classes, else python version

Returns:


df_err : pandas datafram, fractional errors df : pandas dataframe, new values df0 : pandas dataframe, original benchmark values

  • col_for_max_error - string or None, the column name of the column having max absolute error
  • max_abs_err - number or None, the maximum absolute error if it exists

TestSimDriveSweep Objects

class TestSimDriveSweep(unittest.TestCase)

test_sweep

def test_sweep()

Compares results against benchmark.

fastsim.inspect_utils

Utilities to assist with object introspection.

isprop

def isprop(attr) -> bool

Checks if instance attribute is a property.

isfunc

def isfunc(attr) -> bool

Checks if instance attribute is method.

get_attrs

def get_attrs(instance)

Given an instantiated object, returns attributes that are not: -- callable
-- special (i.e. start with __)
-- properties

Rust Documentation

Calibration and Validation of Vehicle Models

FASTSim powertrain models can have varying levels of calibration and resolution based on available calibration and validation data. In the simplest (US) cases, the only available validation data for a powertrain model is the EPA "window sticker" energy consumption rates. However, there are also situations in which detailed dynamometer or on-road data is available for a particular vehicle, enabling much more detailed model calibration. This documentation is meant to summarize these various calibration levels and the tools available to help with more detailed calibration.

Calibration/Validation Levels

LevelCalibrationValidation
0Vehicle is parameterized without any fitting to performance data. This is called parameterization, not calibration.Could be none or could be validated against aggregate energy consumption data like EPA window sticker values.
1Vehicle parameters are adjusted so that model results reasonably match test data for aggregate, cycle-level data (e.g. fuel usage, net SOC change).Model results reasonably match at least some aggregate, cycle-level test data not used in any calibration process.
2Vehicle parameters are adjusted so that model results reasonably match test data for time-resolved test data (e.g. instantaneous fuel usage, instantaneous cumulative fuel usage, instantaneous SOC).Model results reasonably match at least some time-resolved test data not used in any calibration process.
3Some amount of component-level thermal modeling is included and vehicle parameters are adjusted so that model results reasonably match test data for time-resolved test data (e.g. instantaneous fuel usage, instantaneous cumulative fuel usage, instantaneous SOC).Model results reasonably match time-resolved test data not used in any calibration process that covers various temperatures and/vehcile transient thermal states.

Examples of calibration levels 0, 2, and 3 from the FASTSim Validation Report:

image

image

image

Calibration Level 0 (Parameterization) Guidelines

As noted in the table above, parameterization of a new FASTSim powertrain model is performed when little or no ground truth performance data is available for a specific vehicle. One example of this is if EPA window-sticker fuel economy is the only available performance data. In this situation, it is recommended to parameterize a FASTSim powertrain model using the most reliable vehicle parameters from available information (e.g., specification websites). This helps to avoid overfitting and relies on the robustness of the FASTSim approach to capture the most important powertrain dynamics and simulate energy consumption.

  • Create a new vehicle file, either from a template or an existing vehicle model (ideally of the same powertrain type)
  • Enter vehicle parameters from various specification sources (note: it is recommended to document the source of specifications that are used to determine each parameter)
    • veh_pt_type and fc_eff_type are important high level descriptors that define the powertrain technology
      • veh_pt_type: Vehicle powertrain type
        • Parameter values:
          • Conv: conventional (ICE, gasoline or diesel) vehicle
          • HEV: hybrid electric vehicle
          • PHEV: plug-in hybrid electric vehicle
          • BEV: battery electric vehicle
      • fc_eff_type: Fuel converter efficiency type
        • This parameter is used to retrieve the default fc_eff_map for a particular engine type if a custom map is not provided
        • Unnecessary and not used for vehicles without a fuel converter (e.g. BEVs)
        • Parameter values:
          • SI: spark ignition
          • Atkinson: Atkinson cycle (typical for hybrids)
          • Diesel: diesel (compression ignition)
          • H2FC: hydrogen fuel cell (use with veh_pt_type set to HEV)
          • HD_Diesel: heavy-duty diesel
    • veh_override_kg is the simplest way to specify total vehicle mass
      • If not provided, the various component mass parameters will be used to calculate total vehicle mass
      • If veh_override_kg is provided, component mass parameters are unnecessary and not used
    • drag_coef and wheel_rr_coef can be calculated from dynamometer road load equation coefficients (ABCs) for vehicles tested by the US EPA using fastsim.auxiliaries.abc_to_drag_coeffs. Test data, including the road load coefficients from coast-down testing, for cars tested by the US EPA is available here.
      • drag_coef is sometimes provided on specification websites and reasonable values informed by engineering judgement for wheel_rr_coef can be used , but when possible the ABCs and fastsim.auxiliaries.abc_to_drag_coeffs method should be used instead
    • wheel_radius_m is often not explicitly available for a vehicle, but a tire code can be supplied to fastsim.utils.calculate_tire_radius to calculate a radius
    • Note: For hybrids, 'total system power' is often provided (e.g., combined ICE and electric motor powers). This should not be used for either fc_max_kw or mc_max_kw, peak engine-only power should be used for fc_max_kw and peak electric motor-only power for mc_max_kw.

Calibration Level 2 Guidelines

  • Copy calibration_demo.py to your project directory and modify as needed.
  • By default, this script selects the model that minimizes the euclidean error across all objectives, which may not be the way that you want to select your final design. By looking at the plots that get generated in save_path, you can use both the time series and parallel coordinates plots to down select an appropriate design.
  • Because PyMOO is a multi-objective optimizer that finds a multi-dimensional Pareto surface, it will not necessarily return a single best result -- rather, it will produce a pareto-optimal set of results, and you must down select. Often, the design with minimal euclidean error will be the best design, but it's good to pick a handful of designs from the pareto set and check how they behave in the time-resolved plots that can be optionally generated by the optimization script.
  • Run python calibration_demo.py --help to see details about how to run calibration and validation. Greater population size typically results in faster convergence at the expense of increased run time for each generation. There's no benefit in having a number of processes larger than the population size. xtol and ftol (see CLI help) can be used to adjust when the minimization is considered converged. If the optimization is terminating when n_max_gen is hit, then that means it has not converged, and you may want to increase n_max_gen.
  • Usually, start out with an existing vehicle model that is reasonably close to the new vehicle, and make sure to provide as many explicit parameters as possible. In some cases, a reasonable engineering judgment is appropriate.
  • Resample data to 1 Hz. This is a good idea because higher frequency data will cause fastsim to run more slowly. This can be done with fastsim.resample.resample. Be sure to specify rate_vars (e.g. fuel power flow rate [W]), which will be time averaged over the previous time step in the new frequency.
  • Identify test data signals and corresponding fastsim signals that need to match. These pairs of signals will be used to construct minimization objectives. See where obj_names is defined in calibration_demo.py for an example.
  • See where cycs[key] gets assigned to see an example of constructing a Cycle from a dataframe.
  • Partition out calibration/validation data by specifying a tuple of regex patterns that correspond to cycle names. See where cal_cyc_patterns is defined for an example. Typically, it's good to reserve about 25-33% of your data for validation.
  • To set parameters and corresponding ranges that the optimizer is allowed to use in getting the model to match test data, see where params_and_bounds is defined below.

How to Update This Markdown Book

mdBook Documentation

Setup

If not already done, install mdbook

Serving locally

Run the following in the repository root directory:

  1. If any python files were modified,
    1. Install pipx
    2. Install pydoc-markdown
    3. run pydoc-markdown -I python/ --render-toc > docs/src/python-doc.md. Do not modify this file manually.
  2. Run mdbook serve --open docs/

Publishing

  1. Update book.toml or files in docs/src/
  2. Make sure the docs look good locally by running the steps in Serving Locally
  3. Commit files and push to main branch

After that, a GitHub action will build the book and publish it.