Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b82927e
derating appers to be working correctly,
May 8, 2025
75bf176
partway through making train planner equivalent based on historical data
May 9, 2025
c58af12
more progress
May 13, 2025
ed5747d
made some edits to manual train planner. Not complete yet
SWRIganderson May 23, 2025
3cd55be
working through making manual train planner function. Added manual s…
BenchData May 24, 2025
c598e8c
cleaned up something from debugging.
BenchData May 24, 2025
fb639c5
copying over things from regular train planner to build slts list. L…
BenchData May 24, 2025
e23cef8
created consist plan file for manual planner demo. Added more stuff …
BenchData May 29, 2025
5c73986
Manual train planner demo runs. Not sure if the results are right, b…
BenchData May 30, 2025
a3060a9
borrowed pymoo_api.py from fastsim
Jun 4, 2025
81e3aaf
added some dev tools as dev deps
Jun 4, 2025
d56cbbe
got started but not very far
Jun 4, 2025
9e7a26e
fixed a whole ton of ruff and mypy errors
Jun 5, 2025
30d1e65
more cleanup and deprecation warning fixes
Jun 5, 2025
f741445
first commit of new pymoo_api.py
Jun 25, 2025
2e57a78
unittest -> pytest
Jun 25, 2025
10cc539
updates from `Consolidate-plotting-functions`
Jun 25, 2025
d8e9321
formatting
Jun 25, 2025
668f89f
updated lock files
Jun 25, 2025
800f8be
auto fix
Jun 25, 2025
e5600bc
stub file updates that need scrutiny
Jun 26, 2025
cb01baa
some progress but still needs work
Jul 2, 2025
47d278a
added a bunch of error context
Jul 10, 2025
f8433f5
fixed a bunch of `??` typos
Jul 10, 2025
2c7768d
Merge branch 'Consolidate-plotting-functions' of https://github.com/N…
FDsteven Jul 13, 2025
7ba2630
Merge branch 'main' of github.com:NREL/altrios into feature/manual-tr…
Jul 17, 2025
ed4fb53
replaced `assert` with `raise`
Jul 17, 2025
c7b89dc
Fixed the manual_train_planner_demo.py
FDsteven Jul 17, 2025
9b66760
Merge branch 'feature/manual-train-planner' of https://github.com/NRE…
FDsteven Jul 17, 2025
67bbfe3
Merge branch 'feature/manual-train-planner' into cal-and-val-update
Jul 18, 2025
a106b42
added more aliases for better backwards compatibility
Jul 21, 2025
0ebf74c
better pixi task syntax
Jul 21, 2025
190860c
fixed `assert` that should have been `raise` in several places
Jul 21, 2025
f90692f
added error context
Aug 14, 2025
488354e
manual formatting changes to address warnings
Aug 14, 2025
c54b8c7
added speed_limit_tolerance
Aug 14, 2025
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
16 changes: 9 additions & 7 deletions altrios-core/src/consist/locomotive/powertrain/fuel_converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ pub struct FuelConverter {
pub(crate) pwr_for_peak_eff: si::Power,
/// idle fuel power to overcome internal friction (not including aux load)
pub pwr_idle_fuel: si::Power,
/// Interpolator for derating dynamic engine peak power based on altitude
/// and temperature. When interpolating, this returns fraction of normal
/// Interpolator for derating dynamic engine peak power based on altitude [m]
/// and temperature [*C]. When interpolating, this returns fraction of normal
/// peak power, e.g. a value of 1 means no derating and a value of 0 means
/// the engine is completely disabled.
#[api(skip_get, skip_set)]
Expand Down Expand Up @@ -190,10 +190,12 @@ impl FuelConverter {

let pwr_max_derated = match (&mut self.elev_and_temp_derate, elev_and_temp) {
(Some(elev_and_temp_derate), Some(elev_and_temp)) => {
elev_and_temp_derate.interpolate(&[
let derate_factor = elev_and_temp_derate.interpolate(&[
elev_and_temp.0.get::<si::meter>(),
elev_and_temp.1.get::<si::degree_celsius>(),
])? * self.pwr_out_max
])?;
ensure!(derate_factor <= 1.0 && derate_factor >= 0.0, format_dbg!());
derate_factor * self.pwr_out_max
}
(None, Some(_)) => bail!(
"{}\nExpected (self.elev_and_temp_derate, elev_and_temp) to both be Some or None",
Expand All @@ -209,7 +211,6 @@ impl FuelConverter {
self.pwr_out_max_init = self.pwr_out_max_init.max(self.pwr_out_max / 10.);
self.state.pwr_out_max = (self.state.pwr_mech_out
+ (self.pwr_out_max / self.pwr_ramp_lag) * dt)
.min(self.pwr_out_max)
.min(pwr_max_derated)
.max(self.pwr_out_max_init);
Ok(())
Expand Down Expand Up @@ -319,8 +320,9 @@ impl FuelConverter {
fn set_default_elev_and_temp_derate(&mut self) {
self.elev_and_temp_derate = Some(
Interp2D::new(
array![0.0, 3_000.0, 6_000.0],
array![0.0, 35.0, 45.0, 50.0],
array![0.0, 1_000.0, 2_000.0], // elevation in meters
array![0.0, 35.0, 45.0, 50.0], // temperature in degrees Celsius
// Fraction of static peak power
array![
[1.0, 1.0, 0.95, 0.8],
[0.95, 0.95, 0.9025, 0.76],
Expand Down
201 changes: 201 additions & 0 deletions python/altrios/demos/manual_train_planner_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# %%
from altrios import sim_manager_manual
from altrios import utilities, defaults
import altrios as alt
from altrios.train_planner import planner_config, manual_train_planner
import numpy as np
import polars as pl
import matplotlib.pyplot as plt
import time
import seaborn as sns
from pathlib import Path

sns.set_theme()

t0_total = time.perf_counter()

SHOW_PLOTS = alt.utils.show_plots()
# %

plot_dir = Path() / "plots"
# make the dir if it doesn't exist
plot_dir.mkdir(exist_ok=True)
#%%

consist_plan = pl.read_csv(alt.resources_root() / "demo_data/consist plan for manual planner demo.csv")

location_map = alt.import_locations(
alt.resources_root() / "networks/default_locations.csv"
)
network = alt.Network.from_file(
alt.resources_root() / "networks/Taconite-NoBalloon.yaml"
)

loco_map = {'Diesel_Large' : 'Diesel_Large',
'BEL' : 'BEL'}

rail_vehicles = [
alt.RailVehicle.from_file(vehicle_file, skip_init=False)
for vehicle_file in Path(alt.resources_root() / "rolling_stock/").glob("*.yaml")
]

t0_main = time.perf_counter()
#not passing in a trainplanner config here because we do not need to specify train length or any other parameters like
#sim_manager_demo.py. This example is just replaying trains that have already been planned.
(
train_consist_plan,
loco_pool,
refuel_facilities,
grid_emissions_factors,
nodal_energy_prices,
speed_limit_train_sims,
timed_paths,
train_consist_plan_untrimmed,
) = sim_manager_manual.main(
network=network,
rail_vehicles=rail_vehicles, #double check this to see if it is actually need in sim_manager_manual.py
location_map=location_map,
consist_plan= consist_plan,
loco_map=loco_map,
debug=True,
)


# train_consist_plan, loco_pool, refuelers, speed_limit_train_sims, est_time_nets = (
# manual_train_planner(consist_plan, loco_map)
# )

#%%
t1_main = time.perf_counter()
print(f"Elapsed time to run `sim_manager.main()`: {t1_main-t0_main:.3g} s")

# %%
t0_train_sims = time.perf_counter()
speed_limit_train_sims.set_save_interval(100)
(sims, refuel_sessions) = alt.run_speed_limit_train_sims(
speed_limit_train_sims=speed_limit_train_sims,
network=network,
train_consist_plan_py=train_consist_plan,
loco_pool_py=loco_pool,
refuel_facilities_py=refuel_facilities,
timed_paths=timed_paths,
)
t1_train_sims = time.perf_counter()
print(f"Elapsed time to run train sims: {t1_train_sims-t0_train_sims:.3g} s")
t_train_time = sum([sim.state.time_seconds for sim in sims.tolist()])
print(f"Total train-seconds simulated: {t_train_time} s")

# %%
t0_summary_sims = time.perf_counter()
speed_limit_train_sims.set_save_interval(None)
(summary_sims, summary_refuel_sessions) = alt.run_speed_limit_train_sims(
speed_limit_train_sims=speed_limit_train_sims,
network=network,
train_consist_plan_py=train_consist_plan,
loco_pool_py=loco_pool,
refuel_facilities_py=refuel_facilities,
timed_paths=timed_paths,
)
t1_summary_sims = time.perf_counter()
print(
f"Elapsed time to build and run summary sims: {t1_summary_sims-t0_summary_sims:.3g} s"
)

# %%
t0_tolist = time.perf_counter()
sims_list = sims.tolist()
t1_tolist = time.perf_counter()
print(f"Elapsed time to run `tolist()`: {t1_tolist-t0_tolist:.3g} s")

sim0 = sims_list[0]
# sim0 = alt.SpeedLimitTrainSim.from_bincode(
# sim0.to_bincode()) # to support linting


# %%

t0_main = time.perf_counter()
e_total_fuel_mj = summary_sims.get_energy_fuel_joules(annualize=False) / 1e9
t1_main = time.perf_counter()

print(f"Elapsed time to get total fuel energy: {t1_main-t0_main:.3g} s")
print(f"Total fuel energy used: {e_total_fuel_mj:.3g} GJ")

v_total_fuel_gal = (
summary_sims.get_energy_fuel_joules(annualize=False)
/ 1e3
/ defaults.LHV_DIESEL_KJ_PER_KG
/ defaults.RHO_DIESEL_KG_PER_M3
* utilities.LITER_PER_M3
* utilities.GALLONS_PER_LITER
)

print(f"Total fuel used: {v_total_fuel_gal:.3g} gallons")
print(f"Total elapsed time: {time.perf_counter() - t0_total} s")


# %%

if SHOW_PLOTS:
for idx, sim in enumerate(sims_list[:10]):
sim: alt.SpeedLimitTrainSim
fig, ax = plt.subplots(3, 1, sharex=True)

loco0 = sim.loco_con.loco_vec.tolist()[0]
# loco0 = alt.Locomotive.from_bincode(
# loco0.to_bincode()) # to support linting
plt.suptitle(f"sim #: {idx}")

if loco0.fc is not None:
ax[0].plot(
np.array(sim.history.time_seconds) / 3_600,
np.array(loco0.fc.history.pwr_fuel_watts) / 1e6,
# label='fuel'
)
# ax[0].plot(
# np.array(sim.history.time_seconds) / 3_600,
# np.array(loco0.history.pwr_out_watts) / 1e6,
# label='conv. loco. tractive'
# )
# ax[0].plot(
# np.array(sim.history.time_seconds) / 3_600,
# np.array(loco1.history.pwr_out_watts) / 1e6
# label='BEL tractive'
# )
ax[0].set_ylabel("Single Loco.\nFuel Power [MW]")
# ax[0].legend()

# TODO: Figure out robust way to ensure one bel in demo consist
if len(sim.loco_con.loco_vec.tolist()) > 1:
loco1 = sim.loco_con.loco_vec.tolist()[1]
if loco1.res is not None:
ax[1].plot(
np.array(sim.history.time_seconds) / 3_600, loco1.res.history.soc
)
ax[1].set_ylabel("SOC")

ax[-1].plot(
np.array(sim.history.time_seconds) / 3_600,
sim.history.speed_meters_per_second,
label="actual",
)
ax[-1].plot(
np.array(sim.history.time_seconds) / 3_600,
sim.history.speed_limit_meters_per_second,
label="limit",
)
# ax[-1].plot(
# sim.history.time_seconds,
# sim.history.speed_target_meters_per_second,
# label='target',
# linestyle="-."
# )
ax[-1].legend()
ax[-1].set_xlabel("Time [hr]")
ax[-1].set_ylabel("Speed [m/s]")

plt.tight_layout()
plt.savefig(plot_dir / f"sim num {idx}.png")
plt.savefig(plot_dir / f"sim num {idx}.svg")

# %%
56 changes: 33 additions & 23 deletions python/altrios/demos/sim_manager_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,40 @@
t0_import = time.perf_counter()
t0_total = time.perf_counter()

rail_vehicles=[alt.RailVehicle.from_file(vehicle_file, skip_init=False)
for vehicle_file in Path(alt.resources_root() / "rolling_stock/").glob('*.yaml')]
rail_vehicles = [
alt.RailVehicle.from_file(vehicle_file, skip_init=False)
for vehicle_file in Path(alt.resources_root() / "rolling_stock/").glob("*.yaml")
]

location_map = alt.import_locations(alt.resources_root() / "networks/default_locations.csv")
network = alt.Network.from_file(alt.resources_root() / "networks/Taconite-NoBalloon.yaml")
location_map = alt.import_locations(
alt.resources_root() / "networks/default_locations.csv"
)
network = alt.Network.from_file(
alt.resources_root() / "networks/Taconite-NoBalloon.yaml"
)

t1_import = time.perf_counter()
print(
f"Elapsed time to import rail vehicles, locations, and network: {t1_import - t0_import:.3g} s"
)

train_planner_config = planner_config.TrainPlannerConfig(
cars_per_locomotive={"Default": 50},
target_cars_per_train={"Default": 90},
require_diesel=True)
cars_per_locomotive={"Default": 50},
target_cars_per_train={"Default": 90},
require_diesel=True,
)

t0_main = time.perf_counter()

(
train_consist_plan,
loco_pool,
refuel_facilities,
grid_emissions_factors,
nodal_energy_prices,
speed_limit_train_sims,
train_consist_plan,
loco_pool,
refuel_facilities,
grid_emissions_factors,
nodal_energy_prices,
speed_limit_train_sims,
timed_paths,
train_consist_plan_untrimmed
train_consist_plan_untrimmed,
) = sim_manager.main(
network=network,
rail_vehicles=rail_vehicles,
Expand All @@ -72,13 +79,11 @@
train_consist_plan_py=train_consist_plan,
loco_pool_py=loco_pool,
refuel_facilities_py=refuel_facilities,
timed_paths=timed_paths
timed_paths=timed_paths,
)
t1_train_sims = time.perf_counter()
print(f"Elapsed time to run train sims: {t1_train_sims-t0_train_sims:.3g} s")
t_train_time = sum([
sim.state.time_seconds for sim in sims.tolist()
])
t_train_time = sum([sim.state.time_seconds for sim in sims.tolist()])
print(f"Total train-seconds simulated: {t_train_time} s")

# %%
Expand Down Expand Up @@ -117,8 +122,14 @@
print(f"Elapsed time to get total fuel energy: {t1_main-t0_main:.3g} s")
print(f"Total fuel energy used: {e_total_fuel_mj:.3g} GJ")

v_total_fuel_gal = summary_sims.get_energy_fuel_joules(annualize=False) / 1e3 / defaults.LHV_DIESEL_KJ_PER_KG / \
defaults.RHO_DIESEL_KG_PER_M3 * utilities.LITER_PER_M3 * utilities.GALLONS_PER_LITER
v_total_fuel_gal = (
summary_sims.get_energy_fuel_joules(annualize=False)
/ 1e3
/ defaults.LHV_DIESEL_KJ_PER_KG
/ defaults.RHO_DIESEL_KG_PER_M3
* utilities.LITER_PER_M3
* utilities.GALLONS_PER_LITER
)

print(f"Total fuel used: {v_total_fuel_gal:.3g} gallons")
print(f"Total elapsed time: {time.perf_counter() - t0_total} s")
Expand Down Expand Up @@ -160,10 +171,9 @@
loco1 = sim.loco_con.loco_vec.tolist()[1]
if loco1.res is not None:
ax[1].plot(
np.array(sim.history.time_seconds) / 3_600,
loco1.res.history.soc
np.array(sim.history.time_seconds) / 3_600, loco1.res.history.soc
)
ax[1].set_ylabel('SOC')
ax[1].set_ylabel("SOC")

ax[-1].plot(
np.array(sim.history.time_seconds) / 3_600,
Expand Down
Loading
Loading