Reading a PLEXOS Solution

Use the DuckDB-backed PlexosSolution API when you need to read result tables from a PLEXOS solution ZIP file.

Converting a solution

Create the solution with from_zip() and convert it with to_duckdb():

from pathlib import Path

from plexosdb.db_solution import PlexosSolution

solution_zip = Path("/path/to/Model Solution.zip")

sol = PlexosSolution.from_zip(solution_zip)
sol.to_duckdb("solution.duckdb", if_exists="reuse")

For temporary analysis, omit the database path. The final connection is in-memory:

sol = PlexosSolution.from_zip(solution_zip)
result = sol.to_duckdb()

assert result.is_in_memory

For large solutions, prefer a file-backed DuckDB database so you can reuse it without converting the ZIP again.

Limiting result tables during conversion

If you only need a subset of result tables, pass a table_name_pattern to to_duckdb():

sol.to_duckdb(
    "solution.duckdb",
    if_exists="replace",
    table_name_pattern="ST__Interval__Generators__Generation",
)

This pattern is useful when a solution ZIP contains many result tables but your analysis only needs one family of outputs.

Listing available result tables

Use list_result_tables() to find the result tables generated by plexos2duckdb:

from plexosdb.enums import ClassEnum

for table in sol.list_result_tables(class_enum=ClassEnum.Generator):
    print(table.name)

To select one table, use the existing PLEXOS enums and the result-table property name:

from plexosdb.enums import PeriodEnum, PhaseEnum

generation = sol.result_table(
    phase=PhaseEnum.ST,
    period=PeriodEnum.INTERVAL,
    class_enum=ClassEnum.Generator,
    property_name="Generation",
)

print(generation.name)

Reading filtered result rows

Use get_result() to build a lazy DuckDB relation. Convert to pandas only when you need a DataFrame:

relation = sol.get_result(
    generation,
    object_names="Coal_Gen",
    start="2017-01-01",
    end="2017-01-08",
    columns=["name", "timestamp", "Generation", "unit"],
)

df = relation.df()

You can also pass a result table name directly. String table names use the report schema:

one_day = sol.get_result(
    "ST__Interval__Generators__Generation",
    object_names=["Coal_Gen"],
    start="2017-01-01",
    end="2017-01-02",
)

rows = one_day.fetchall()

Querying with DuckDB SQL

Use query() for eager row results:

rows = sol.query(
    'SELECT COUNT(*) FROM report."ST__Interval__Generators__Generation"'
)

Use query_dicts() when column names are useful:

rows = sol.query_dicts(
    "SELECT name FROM processed.objects WHERE class = ? ORDER BY name",
    (ClassEnum.Generator.value,),
)

Use sql() for a lazy DuckDB relation that you can keep composing:

summary = sol.sql(
    '''
    SELECT name, SUM(Generation) AS generation_mwh
    FROM report."ST__Interval__Generators__Generation"
    WHERE sample_name = 'Mean'
    GROUP BY name
    ORDER BY name
    '''
)

summary_df = summary.df()

Closing the connection

Close the DuckDB connection when you are done:

sol.close()

You can also use a context manager after conversion:

sol = PlexosSolution.from_zip(solution_zip)
sol.to_duckdb("solution.duckdb", if_exists="reuse")

with sol:
    print(sol.list_tables(schema="report")[:5])

Troubleshooting

RuntimeError: No active connection

Call to_duckdb() before reading tables or results.

KeyError: No result table matched ...

Run list_result_tables() with fewer filters to see which phase, period, collection, and property combinations exist.

Conversion creates more tables than needed

Use table_name_pattern during to_duckdb() or reuse a file-backed database with if_exists="reuse".