Live LTMΒΆ
This notebook creates a model by using only the Python interface, with no file backing.
[1]:
import os
import numpy as np
from pyltmapi import LtmSession, LtmPlot
import logging
logging.basicConfig(level=logging.INFO)
from pathlib import Path
ltm_core_path = os.environ.get("LTM_CORE_PATH", str(Path("~").expanduser().joinpath("ltm/release/bin/")))
license_file = os.environ.get("LTM_CORE_LICENSE_FILE", str(Path("~").expanduser().joinpath("ltm/ltm-license.dat")))
[2]:
from IPython.display import HTML, display, display_markdown
ltmapi_version = LtmSession.version()
display(f"pyltm version {ltmapi_version}")
'pyltm version PyLTM version: 0.25.2'
[3]:
def usercallback(program_info: dict, userdata: any):
print(userdata)
print(program_info)
return True
def generate_plots(ltm):
# Water values and price series
for area in ltm.model.areas():
print(area)
if area.have_water_value_results():
# Water values
LtmPlot.make_water_value_plot(area.water_value_results(), area.name)
# Market results
LtmPlot.make_market_results_plot(area.market_result_price(), area.name)
# Detailed hydro results from
for area in ltm.model.areas():
print(area)
# area reservoirs
rsvs = area.reservoirs()
for rsv in rsvs:
LtmPlot.make_generic_plot(rsv.reservoir(), f"Reservoir '{rsv.name}'")
for rsv in rsvs:
LtmPlot.make_generic_plot(rsv.discharge(), f"Discharge '{rsv.name}'")
LtmPlot.make_generic_plot(rsv.inflow(), f"Inflow '{rsv.name}'")
LtmPlot.make_generic_plot(rsv.production(), f"Production '{rsv.name}'")
LtmPlot.make_generic_plot(rsv.bypass(), f"Bypass '{rsv.name}'")
LtmPlot.make_generic_plot(rsv.spill(), f"Spill '{rsv.name}'")
[4]:
from datetime import datetime, timedelta
from pyltm import Txy
[5]:
from pyltm import Area
def add_area(session, name):
area = Area()
area.name = name
return session.model.add(area)
[6]:
from pyltm import DCLine, util
def add_dclines(session, name):
dcline = DCLine()
dc = session.model.add(dcline)
dcline.name = name
dcline.forward_capacity = util.make_constant_txy(200)
dcline.backward_capacity = util.make_constant_txy(200)
dcline.loss_percentage = 2.0
dcline.forward_cost = 5
dcline.backward_cost = 5
return dc
[7]:
from pyltm import Load, Txy
from pyltm import util
def add_load(session, name, capacity: Txy = Txy()):
load = Load()
if len(capacity.timestamps) == 0:
load.capacity = util.make_txy(
[
("2024-W01", [20]),
("2024-W05", [25]),
("2024-W10", [30]),
("2024-W15", [20]),
]
)
else:
load.capacity = capacity
load.name = name
return session.model.add(load)
[8]:
from pyltm import Inflow
def add_default_inflow_series(session):
small = session.model.add(Inflow())
small.name = "small"
small.series = util.make_constant_txy(0.001)
constant = session.model.add(Inflow())
constant.name = "constant"
constant.series = util.make_constant_txy(1.0)
[9]:
from pyltm import Reservoir, Plant, Xy
def create_simple_topology(session):
upper1: Reservoir = Reservoir()
upper1.name = "upper1"
upper1.degree_of_regulation = 3
upper1.regulated_inflow = util.make_txy(
[
("1900-01-01T00:00:00Z", [1.0]),
]
)
upper1.average_regulated_inflow = 10
upper1.max_discharge = 50
upper1.reference_curve = util.make_txylin(
[
("W15", [50]),
("W20", [350]),
("W40", [450]),
]
)
upper1.initial_volume = 5
upper1.tailrace_elevation = 150.0
upper1.gross_head = 800.0
upper1.volume_curve = util.make_volume_curve(
[
(1100, 0),
(1130, 500),
]
)
session.model.add(upper1)
lower1 = Reservoir()
lower1.name = "lower1"
lower1.degree_of_regulation = 0.5
lower1.regulated_inflow_name = "constant"
lower1.max_discharge = 200
lower1.reference_curve = util.make_txylin(
[
("W01", [1500]),
]
)
lower1.initial_volume = 0
session.model.add(lower1)
plant1 = Plant()
plant1.name = "plant1"
plant1.average_energy_equivalent = 1.6
plant1.discharge_energy_equivalent = util.make_txy([("W01", [1.6])])
plant1.pq_curves = util.make_pq_curve(
[
(
"W01",
[
(0, 0),
(50, 200),
],
)
]
)
plant1.unregulated_inflow_name = "small"
plant1.average_unregulated_inflow = 0.0
session.model.add(plant1)
session.connect(upper1, plant1)
session.connect(plant1, lower1)
return upper1
[10]:
from pyltm import MarketStep
def add_market_step(session, name):
ms = MarketStep()
ms.name = name
ms.price = util.make_txy(
[
("2024-W01", [260]),
("2024-W20", [200]),
]
)
ms.capacity = util.make_txy(
[
("2023-W01", [150]),
]
)
return ms
[11]:
import pyltm
from pyltm import Wind
def add_wind(session, name, area):
wind = Wind()
wind.name = name
wind.capacity = pyltm.util.make_txy(
[
("2024-W01", [1, 2, 3, 4, 5, 6, 7, 8]),
("2024-W20", [3, 3, 3, 3, 3, 3, 3, 3]),
("2024-W40", [0, 0, 0, 0, 0, 0, 0, 0]),
("2024-W41", [30, 30, 30, 30, 30, 30, 30, 30]),
("2024-W43", [0, 0, 0, 0, 0, 0, 0, 0]),
]
)
session.model.add(wind)
session.connect(wind, area)
pass
[12]:
def create_basic_model(
name: str,
output_path: str,
sim_period: Txy,
hist_period: Txy,
):
session = LtmSession(name, ltm_core_path=ltm_core_path, overwrite_session=True)
sim = sim_period
hist = hist_period
# Explicitly set license file
gs = session.model.global_settings
gs.name = name
gs.output_path = output_path
gs.ltm_license_file_path = license_file
gs.delete_output_dir = False
gs.generate_output_dir = True
gs.simulation_period = sim
gs.historical_period = hist
gs.timesteps_per_week = 168
gs.default_spill_cost = 0.01
gs.default_load_penalty = 900.0
return session
def build_model():
session = create_basic_model(
"basic_model",
"testout_basic_model",
sim_period=util.make_txy_period("2024-W01", "2027-W01"),
hist_period=util.make_txy_period(
"2000-01-01T00:00:00Z", "2008-01-01T00:00:00Z"
),
)
add_default_inflow_series(session)
# areas
tev = add_area(session, "tev")
no2 = add_area(session, "NO2")
# DCLines
dc1 = add_dclines(session, "DC_TEV_NO2")
session.connect(tev, dc1)
session.connect(dc1, no2)
# Loads
load_tev = add_load(session, "load_tev")
load_no2 = add_load(session, "load_no2")
session.connect(load_tev, tev)
session.connect(load_no2, tev)
# Market step
ms1 = add_market_step(session, "market1")
session.connect(ms1, no2)
# Wind
add_wind(session, "windy", no2)
# NO2 Topology
upper1 = create_simple_topology(session)
session.connect(upper1, no2)
return session
def run_model(session: LtmSession):
# with build_model() as session:
with session:
try:
# Write model to disk, and automatically generate an output directory.
session._apimodule.dump_model("live_dump")
session.write_model()
# return
# Execute/run LTM/EMPS on the model
last_rc, results = session.execute_model()
# If last return code is not 0, then there was an error.
if last_rc != 0:
err = results[0]["log_file_contents"]
display_markdown(err)
else:
# Make plots from the results
generate_plots(session)
except Exception as e:
print(e)
raise (e)
[13]:
def go():
session = build_model()
run_model(session=session)
go()
INFO:LtmApiModel:(basic_model) Added object 'inflow/'
INFO:LtmApiModel:(basic_model) Added object 'inflow/'
INFO:LtmApiModel:(basic_model) Added object 'area/tev'
INFO:LtmApiModel:(basic_model) Added object 'area/NO2'
INFO:LtmApiModel:(basic_model) Added object 'dcline/'
INFO:LtmApiModel:(basic_model) Added object 'load/load_tev'
INFO:LtmApiModel:(basic_model) Added object 'load/load_no2'
INFO:LtmApiModel:(basic_model) Added object 'wind/windy'
INFO:LtmApiModel:(basic_model) Added object 'reservoir/upper1'
INFO:LtmApiModel:(basic_model) Added object 'reservoir/lower1'
INFO:LtmApiModel:(basic_model) Added object 'plant/plant1'
INFO:LtmApiModel:(basic_model) Not deleting output dir (testout_basic_model), as delete_output_dir: false, and has_generated_output_dir: false
INFO:LtmApiModel:(basic_model) LtmApiModel::maybe_generate_output_dir: output_path: /builds/energy/ltm/pyltmapi/docs/ltm-api/guides/live-ltm/testout_basic_model/2026-07-12-103744.405-EMPS-parallell
INFO:LtmApiModel:(basic_model) Using license file '/builds/energy/ltm/pyltmapi.tmp/CI_LTM_LICENSE_FILE'
INFO:Validator:(basic_model) Model validation succeeded
INFO:LtmApiModel:(basic_model) Writing model to path /builds/energy/ltm/pyltmapi/docs/ltm-api/guides/live-ltm/testout_basic_model/2026-07-12-103744.405-EMPS-parallell
INFO:InflowHandler:(basic_model) Writing inflow series
INFO:Validator:(basic_model) Model validation succeeded
INFO:LtmApiModel:(basic_model) Model executed successfully
area/tev
area/NO2
area/tev
area/NO2
INFO:LtmApiModel:(basic_model) Not deleting output dir (/builds/energy/ltm/pyltmapi/docs/ltm-api/guides/live-ltm/testout_basic_model/2026-07-12-103744.405-EMPS-parallell), as delete_output_dir: false, and has_generated_output_dir: true
INFO:LtmApiModel:(basic_model) Not deleting output dir (/builds/energy/ltm/pyltmapi/docs/ltm-api/guides/live-ltm/testout_basic_model/2026-07-12-103744.405-EMPS-parallell), as delete_output_dir: false, and has_generated_output_dir: true
[14]:
def open_and_modify_dump():
loaded_session = LtmSession(
"loaded_session", ltm_core_path=ltm_core_path, overwrite_session=True
)
with loaded_session:
loaded_session.load(filename="live_dump/model.json")
# Change unregulated inflow to 100 m3/sec
plant: Plant = loaded_session.model.get("plant", "plant1")
plant.average_unregulated_inflow = 100
# Remove wind
w: Wind = loaded_session.model.get("wind", "windy")
loaded_session.model.remove(w)
loaded_session.dump_model("live_dump_2")
pass
open_and_modify_dump()
INFO:LtmApiModel:(loaded_session) Loading model from file: live_dump/model.json
INFO:LtmApiModel:(loaded_session) LtmApiModel::maybe_generate_output_dir: output_path: /builds/energy/ltm/pyltmapi/docs/ltm-api/guides/live-ltm/testout_basic_model/2026-07-12-103800.568-EMPS-parallell
INFO:LtmApiModel:(loaded_session) Using license file '/builds/energy/ltm/pyltmapi.tmp/CI_LTM_LICENSE_FILE'
INFO:LtmApiModel:(loaded_session) Removing object 'wind/windy'
INFO:LtmApiModel:(loaded_session) Not deleting output dir (/builds/energy/ltm/pyltmapi/docs/ltm-api/guides/live-ltm/testout_basic_model/2026-07-12-103800.568-EMPS-parallell), as delete_output_dir: false, and has_generated_output_dir: true