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
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_2.png
area/NO2
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_4.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_5.png
area/tev
area/NO2
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_7.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_8.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_9.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_10.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_11.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_12.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_13.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_14.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_15.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_16.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_17.png
../../../_images/ltm-api_guides_live-ltm_live-ltm_13_18.png
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