Tutorial

This tutorial provides a step-by-step introduction to PlexosDB, guiding you through the essential concepts and operations for working with PLEXOS energy market simulation models.

Prerequisites

Before starting this tutorial, ensure you have:

  • Python 3.7 or higher installed

  • PlexosDB installed (see Installation)

  • Basic understanding of energy market modeling concepts

Learning Objectives

By the end of this tutorial, you will be able to:

  • Create and initialize a PlexosDB database

  • Import data from PLEXOS XML files

  • Add objects and properties to your model

  • Query the database for information

  • Work with scenarios and model configurations

Creating Your First Database

Let’s start by creating a new PlexosDB database:

from plexosdb import PlexosDB

# Create a new in-memory database
db = PlexosDB()

# Initialize with the built-in default schema
ok = db.create_schema()
assert ok

# Option A: initialize schema + minimal defaults for object workflows
db_seeded = PlexosDB()
db_seeded.create_schema(seed_defaults=True)

# Option B: initialize using your own SQL schema text
custom_schema = """
CREATE TABLE IF NOT EXISTS my_table (
    id INTEGER PRIMARY KEY,
    name TEXT
);
"""
db_custom = PlexosDB()
db_custom.create_schema(schema=custom_schema)

# Option C: initialize and seed from a versioned master template
db_versioned = PlexosDB()
db_versioned.create_schema(version=10)

This creates an in-memory database with the PLEXOS schema and minimal lookup data so object APIs work immediately.

If you call db.create_schema() without seed_defaults=True, only the table structure is created. For add_object(...) and related workflows, use seed_defaults=True, import from XML, or provide a custom seeded schema.

create_schema options: seed_defaults and schema

create_schema supports two useful parameters:

  • seed_defaults=True: seeds minimal classes/collections/System object so methods like add_object work immediately on a fresh database. Mutually exclusive with version; use one or the other.

  • schema="...": execute custom SQL schema content directly from a string.

  • version=...: loads the matching versioned master template (e.g. version=11, "11.0", or "v11.0R4"). Cannot be combined with seed_defaults=True.

from plexosdb import PlexosDB
from plexosdb.enums import ClassEnum

# Option 1: minimal seed — no master template, add_object works immediately
db = PlexosDB()
db.create_schema(seed_defaults=True)
db.add_object(ClassEnum.Generator, "Generator1")

# Option 2: full master template — use version, not seed_defaults
db2 = PlexosDB()
db2.create_schema(version="11.0")

Custom schema string example (copy/paste):

from plexosdb import PlexosDB
from plexosdb.enums import ClassEnum

schema = """
CREATE TABLE IF NOT EXISTS t_config (
    element TEXT PRIMARY KEY,
    value   TEXT
);

CREATE TABLE IF NOT EXISTS t_class (
    class_id INTEGER PRIMARY KEY,
    name TEXT,
    description TEXT,
    is_enabled INTEGER
);

CREATE TABLE IF NOT EXISTS t_category (
    category_id INTEGER PRIMARY KEY,
    class_id INTEGER,
    name TEXT,
    rank INTEGER
);

CREATE TABLE IF NOT EXISTS t_object (
    object_id INTEGER PRIMARY KEY,
    name TEXT,
    class_id INTEGER,
    category_id INTEGER,
    GUID TEXT,
    description TEXT
);

INSERT INTO t_class(class_id, name, description, is_enabled)
VALUES (1, 'System', 'System class', 1),
       (2, 'Generator', 'Generator class', 1);

INSERT INTO t_category(category_id, class_id, name, rank)
VALUES (1, 1, '-', 1),
       (2, 2, '-', 1);

INSERT INTO t_object(object_id, name, class_id, category_id, GUID)
VALUES (1, 'System', 1, 1, '00000000-0000-0000-0000-000000000001');
"""

db = PlexosDB()
db.create_schema(schema=schema)

# The schema string can be as small or as complete as your workflow requires.

If you want to preload a PLEXOS master template while creating the schema, pass the major version (supported: 9, 10, 11, 12):

db_base = PlexosDB()
db = db_base.create_schema(version=10)

You can also pass string or tuple formats such as "v10.0R2" or (11, 0, 4). create_schema(...) returns a boolean status; it does not return a new database instance.

Working with Objects

Objects represent entities in your energy model such as generators, nodes, and regions.

Adding Objects

from plexosdb.enums import ClassEnum

# Add a generator to the database
db.add_object(ClassEnum.Generator, "Generator1")

# Add a node with additional details
db.add_object(ClassEnum.Node, "Node1", description="Main transmission node")

# Verify objects were added
generators = db.list_objects_by_class(ClassEnum.Generator)
print(f"Generators: {generators}")

Adding Properties

Properties define characteristics of objects like capacity, cost, or operational parameters:

# Add capacity property to the generator
db.add_property(
    ClassEnum.Generator,
    "Generator1",
    "Max Capacity",
    500.0  # MW
)

# Add multiple properties
db.add_property(ClassEnum.Generator, "Generator1", "Min Stable Level", 100.0)
db.add_property(ClassEnum.Generator, "Generator1", "Heat Rate", 9500.0)

Querying Properties

Retrieve property information for specific objects:

# Get all properties for Generator1
properties = db.get_object_properties(ClassEnum.Generator, "Generator1")

for prop in properties:
    print(f"{prop['property']}: {prop['value']} {prop['unit'] or ''}")

Working with XML Files

PlexosDB can import existing PLEXOS XML files and export databases back to XML format.

Importing from XML

# Create database from existing XML file
db = PlexosDB.from_xml("/path/to/your/plexos_model.xml")

# List all generators in the imported model
generators = db.list_objects_by_class(ClassEnum.Generator)
print(f"Found {len(generators)} generators in the model")

Exporting to XML

# Export your database to XML format
db.to_xml("/path/to/output_model.xml")

Working with Scenarios

Scenarios allow you to model different operational conditions or future projections:

# Add a scenario
scenario_id = db.add_scenario("High Demand")

# Add property with scenario context
db.add_property(
    ClassEnum.Generator,
    "Generator1",
    "Max Capacity",
    750.0,  # Increased capacity for high demand scenario
    scenario="High Demand"
)

# List all scenarios
scenarios = db.list_scenarios()
print(f"Available scenarios: {scenarios}")

Working with Model Configurations

PlexosDB can be used to build and manage model configuration workflows, not just object data. In practice, this usually means:

  • Defining model/scenario sets for different study cases

  • Storing scenario-specific property overrides

  • Verifying run-level config flags imported from XML

  • Configuring report outputs for post-processing

1. Build a model and attach scenarios

from plexosdb.enums import ClassEnum, CollectionEnum

# Add a model and scenarios
db.add_object(ClassEnum.Model, "BaseModel")
db.add_object(ClassEnum.Scenario, "Reference")
db.add_object(ClassEnum.Scenario, "High Demand")

# Link scenarios to the model
db.add_membership(
    parent_class_enum=ClassEnum.Model,
    child_class_enum=ClassEnum.Scenario,
    parent_object_name="BaseModel",
    child_object_name="Reference",
    collection_enum=CollectionEnum.Scenarios,
)
db.add_membership(
    parent_class_enum=ClassEnum.Model,
    child_class_enum=ClassEnum.Scenario,
    parent_object_name="BaseModel",
    child_object_name="High Demand",
    collection_enum=CollectionEnum.Scenarios,
)

print(db.list_scenarios_by_model("BaseModel"))
# ['Reference', 'High Demand']

2. Add scenario-specific operating assumptions

# Base-case capacity
db.add_property(
    ClassEnum.Generator,
    "Generator1",
    "Max Capacity",
    500.0,
)

# Scenario override for high demand planning case
db.add_property(
    ClassEnum.Generator,
    "Generator1",
    "Max Capacity",
    650.0,
    scenario="High Demand",
)

# Retrieve properties and filter for the scenario value
all_capacity_props = db.get_object_properties(
    ClassEnum.Generator,
    "Generator1",
    property_names="Max Capacity",
)
high_demand_props = [p for p in all_capacity_props if p.get("scenario_name") == "High Demand"]
print(high_demand_props)

3. Modify simulation configuration classes (Production, Performance, etc.)

Yes, this is supported in PlexosDB through class objects plus attributes. The common workflow is:

  1. Create (or reference) a configuration object in the target class.

  2. Discover valid attribute names for that class.

  3. Set attribute values with add_attribute.

  4. Read them back with get_attribute.

from plexosdb.enums import ClassEnum

# Example: create configuration objects
db.add_object(ClassEnum.Production, "Production_Config")
db.add_object(ClassEnum.Performance, "Performance_Config")

# Inspect which attributes are valid for each class
production_attrs = db.list_attributes(ClassEnum.Production)
performance_attrs = db.list_attributes(ClassEnum.Performance)
print("Production attributes:", production_attrs)
print("Performance attributes:", performance_attrs)

# Set attribute values (use exact names from list_attributes)
db.add_attribute(
    ClassEnum.Production,
    "Production_Config",
    attribute_name="Unit Commitment Optimality",
    attribute_value=2,
)
db.add_attribute(
    ClassEnum.Performance,
    "Performance_Config",
    attribute_name="MIP Relative Gap",
    attribute_value=0.01,
)

# Read back values
uc_opt_row = db.get_attribute(
    ClassEnum.Production,
    object_name="Production_Config",
    attribute_name="Unit Commitment Optimality",
)
mip_gap_row = db.get_attribute(
    ClassEnum.Performance,
    object_name="Performance_Config",
    attribute_name="MIP Relative Gap",
)
uc_opt = uc_opt_row[0]
mip_gap = mip_gap_row[0]
print("UC optimality:", uc_opt)
print("MIP gap:", mip_gap)

You can apply the same pattern to ClassEnum.PASA, ClassEnum.MTSchedule, ClassEnum.STSchedule, ClassEnum.Transmission, and ClassEnum.Diagnostic.

For a full Production attributes catalog (defaults, validation, and descriptions), see Production Attributes Reference.

For a full Performance attributes catalog (defaults, validation, and descriptions), see Performance Attributes Reference.

For a full Transmission attributes catalog (defaults, validation, and descriptions), see Transmission Attributes Reference.

For a full PASA attributes catalog (defaults, validation, and descriptions), see PASA Attributes Reference.

For a full Diagnostic attributes catalog (defaults, validation, and descriptions), see Diagnostic Attributes Reference.

For a full Horizon attributes catalog (defaults, validation, and descriptions), see Horizon Attributes Reference.

For a full Model attributes catalog (defaults, validation, and descriptions), see Model Attributes Reference.

4. Inspect run configuration flags

When you import from XML, PlexosDB preserves model metadata in t_config and enables Dynamic by default (for time series files *.csv associations).

db = PlexosDB.from_xml("/path/to/your/plexos_model.xml")

config_rows = db.query(
    "SELECT element, value FROM t_config WHERE element IN (?, ?)",
    ("Version", "Dynamic"),
)
config = {element: value for element, value in config_rows}

print(f"PLEXOS version: {config.get('Version')}")
print(f"Dynamic enabled: {config.get('Dynamic')}")

Note: get_config and set_config are currently placeholders in the public API, so use query(...) for now when inspecting t_config values directly.

5. Configure report outputs

# Add a report object and configure output fields
db.add_object(ClassEnum.Report, "Generator Output Report")
db.add_report(
    object_name="Generator Output Report",
    property="Generation",
    collection=CollectionEnum.Generators,
    parent_class=ClassEnum.System,
    child_class=ClassEnum.Generator,
    phase_id=4,  # Long-term phase
    report_period=True,
    report_summary=True,
)

For more report-focused patterns, see How to Add Report Configurations.

Working with Collections and Memberships

Collections define relationships between objects in your model:

from plexosdb.enums import CollectionEnum

# Add a region
db.add_object(ClassEnum.Region, "RegionA")

# Create membership: Generator1 belongs to RegionA
db.add_membership(
    parent_class_enum=ClassEnum.Region,
    child_class_enum=ClassEnum.Generator,
    parent_object_name="RegionA",
    child_object_name="Generator1",
    collection_enum=CollectionEnum.Generators
)

# Query memberships
memberships = db.list_object_memberships(ClassEnum.Generator, "Generator1")
for member in memberships:
    print(f"Generator1 belongs to {member['parent_name']} ({member['parent_class_name']})")

You can also work with purchaser objects and the corresponding purchasers collection:

from plexosdb.enums import ClassEnum, CollectionEnum, get_default_collection

# Add a purchaser object
db.add_object(ClassEnum.Purchaser, "RetailerA")

# Confirm default class-to-collection mapping
assert get_default_collection(ClassEnum.Purchaser) == CollectionEnum.Purchasers

# Confirm the System -> Purchaser collection exists in the schema
db.check_collection_exists(
    CollectionEnum.Purchasers,
    parent_class=ClassEnum.System,
    child_class=ClassEnum.Purchaser,
)

Bulk Operations

For large models, PlexosDB provides efficient bulk operations:

# Add multiple objects at once
generator_names = ["Gen1", "Gen2", "Gen3", "Gen4", "Gen5"]
db.add_objects(*generator_names, class_enum=ClassEnum.Generator)

# Bulk property addition using records
property_records = [
    {"name": "Gen1", "Max Capacity": 100.0, "Heat Rate": 9000.0},
    {"name": "Gen2", "Max Capacity": 150.0, "Heat Rate": 9200.0},
    {"name": "Gen3", "Max Capacity": 200.0, "Heat Rate": 8800.0},
]

db.add_properties_from_records(
    property_records,
    object_class=ClassEnum.Generator,
    collection=CollectionEnum.Generators,
    scenario="Base Case"
)

Next Steps

Now that you have completed the tutorial, you can:

  1. Explore the How-to Guides for specific tasks

  2. Consult the API Reference for detailed method documentation

  3. Review examples in the source code test files