diff --git a/docs/examples/io_operations.ipynb b/docs/examples/io_operations.ipynb new file mode 100644 index 0000000..b908632 --- /dev/null +++ b/docs/examples/io_operations.ipynb @@ -0,0 +1,282 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Input/Output Operations\n", + "\n", + "This notebook demonstrates how to perform input/output operations with SMS++ networks using pySMSpp.\n", + "In particular, it shows how to:\n", + "\n", + "1. Load an existing SMS++ network from a NetCDF file\n", + "2. Create a new SMS++ network programmatically\n", + "3. Save a network to a NetCDF file\n", + "4. Reload a saved network and verify its contents\n", + "\n", + "SMS++ stores models in [NetCDF4](https://www.unidata.ucar.edu/software/netcdf/) files (`.nc` or `.nc4`),\n", + "a self-describing, machine-independent data format designed for array-oriented scientific data." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, let's import the necessary modules." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import tempfile\n", + "\n", + "import numpy as np\n", + "\n", + "from pysmspp import Block, SMSFileType, SMSNetwork, Variable" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Loading a Network from a File\n", + "\n", + "An existing SMS++ network stored in a NetCDF file can be loaded by passing the file path to the\n", + "`SMSNetwork` constructor. pySMSpp reads the file and reconstructs the full block hierarchy in memory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Load a sample network from a NetCDF file\n", + "network_path = \"../../test/test_data/microgrid_ALLbutStore_1N.nc4\"\n", + "\n", + "net = SMSNetwork(network_path)\n", + "print(\"Network loaded successfully!\")\n", + "net" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "After loading, you can inspect the network structure with `print_tree()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "net.print_tree(show_attributes=True)" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "## Creating a Network Programmatically\n", + "\n", + "A new SMS++ network can be built from scratch using the `SMSNetwork` and `Block` classes.\n", + "In this example, we create a simple unit commitment network with a single thermal generator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "# Create an empty network with block-file format\n", + "sn = SMSNetwork(file_type=SMSFileType.eBlockFile)\n", + "\n", + "# Add a UCBlock with a 24-hour time horizon and constant demand of 50 kW\n", + "sn.add(\n", + " \"UCBlock\",\n", + " \"Block_0\",\n", + " id=\"0\",\n", + " TimeHorizon=24,\n", + " NumberUnits=1,\n", + " NumberElectricalGenerators=1,\n", + " NumberNodes=1,\n", + " ActivePowerDemand=Variable(\n", + " \"ActivePowerDemand\",\n", + " \"float\",\n", + " (\"NumberNodes\", \"TimeHorizon\"),\n", + " np.full((1, 24), 50.0),\n", + " ),\n", + ")\n", + "\n", + "# Add a thermal generator to the UCBlock\n", + "thermal_unit = Block().from_kwargs(\n", + " block_type=\"ThermalUnitBlock\",\n", + " MinPower=Variable(\"MinPower\", \"float\", (), 0.0),\n", + " MaxPower=Variable(\"MaxPower\", \"float\", (), 100.0),\n", + " LinearTerm=Variable(\"LinearTerm\", \"float\", (), 0.3),\n", + " InitUpDownTime=Variable(\"InitUpDownTime\", \"int\", (), 1),\n", + ")\n", + "sn.blocks[\"Block_0\"].add(\"ThermalUnitBlock\", \"UnitBlock_0\", block=thermal_unit)\n", + "\n", + "print(\"Network created successfully!\")\n", + "sn.print_tree(show_dimensions=True)" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "## Saving a Network to a File\n", + "\n", + "Any `SMSNetwork` (or `Block`) can be serialized to a NetCDF file using the `to_netcdf()` method.\n", + "By default, `to_netcdf()` raises an error if the target file already exists; pass `force=True` to\n", + "overwrite it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "# Save the newly created network to a temporary file\n", + "with tempfile.TemporaryDirectory() as tmpdir:\n", + " output_path = os.path.join(tmpdir, \"my_network.nc4\")\n", + "\n", + " sn.to_netcdf(output_path)\n", + " print(f\"Network saved to: {output_path}\")\n", + " print(f\"File size: {os.path.getsize(output_path)} bytes\")\n", + "\n", + " # --- Reload and verify ---\n", + " reloaded = SMSNetwork(output_path)\n", + " print(\"\\nReloaded network:\")\n", + " reloaded.print_tree(show_dimensions=True)" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "The `force=True` flag can be used to overwrite an existing file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "with tempfile.TemporaryDirectory() as tmpdir:\n", + " output_path = os.path.join(tmpdir, \"my_network.nc4\")\n", + "\n", + " # First save\n", + " sn.to_netcdf(output_path)\n", + "\n", + " # Overwrite with force=True\n", + " sn.to_netcdf(output_path, force=True)\n", + " print(\"File overwritten successfully with force=True.\")" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "## Round-Trip: Save and Reload an Existing Network\n", + "\n", + "A common workflow is to load a network, modify it, and save the updated version.\n", + "The example below loads the sample network, saves it under a new name, and verifies\n", + "that the reloaded copy has the same structure." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "with tempfile.TemporaryDirectory() as tmpdir:\n", + " resaved_path = os.path.join(tmpdir, \"resaved_network.nc4\")\n", + "\n", + " # Load the original network\n", + " original = SMSNetwork(network_path)\n", + "\n", + " # Save it to a new file\n", + " original.to_netcdf(resaved_path)\n", + "\n", + " # Reload the saved file\n", + " reloaded = SMSNetwork(resaved_path)\n", + "\n", + " # Compare top-level block names\n", + " original_blocks = list(original.blocks.keys())\n", + " reloaded_blocks = list(reloaded.blocks.keys())\n", + "\n", + " print(\"Original blocks :\", original_blocks)\n", + " print(\"Reloaded blocks :\", reloaded_blocks)\n", + " print(\"Blocks match :\", original_blocks == reloaded_blocks)" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This notebook demonstrated:\n", + "\n", + "1. **Loading** an SMS++ network from a NetCDF file with `SMSNetwork(fp)`\n", + "2. **Creating** a network programmatically using `SMSNetwork`, `Block`, and `Variable`\n", + "3. **Saving** a network to a NetCDF file with `to_netcdf(fp)` (and `force=True` to overwrite)\n", + "4. **Reloading** a saved network and verifying its contents\n", + "\n", + "These operations form the foundation of any pySMSpp workflow: you can build models in Python,\n", + "persist them to disk, and exchange them with the SMS++ solver or other tools." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/ucblock_2thermal_line.ipynb b/docs/examples/ucblock_2thermal_line.ipynb index 644f99a..2a44ac1 100644 --- a/docs/examples/ucblock_2thermal_line.ipynb +++ b/docs/examples/ucblock_2thermal_line.ipynb @@ -272,11 +272,13 @@ ") # path to the template solver config file \"uc_solverconfig\"\n", "temporary_smspp_file = \"./2buses_2thermal.nc\" # path to temporary SMS++ file\n", "output_file = \"./2buses_2thermal.txt\" # path to the output file (optional)\n", + "fp_solution = \"./fp_solution_2buses_2thermal.nc4\" # path to the file where the full problem solution will be saved (optional). When provided, the result.solution object will be populated with the SMS++ solution object\n", "\n", "result = sn.optimize(\n", " configfile,\n", " temporary_smspp_file,\n", " output_file,\n", + " fp_solution=fp_solution,\n", ")" ] }, @@ -306,6 +308,15 @@ "source": [ "result.log" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result.solution" + ] } ], "metadata": { diff --git a/docs/examples/ucblock_thermal_1bus.ipynb b/docs/examples/ucblock_thermal_1bus.ipynb index 217e7a5..50e40d5 100644 --- a/docs/examples/ucblock_thermal_1bus.ipynb +++ b/docs/examples/ucblock_thermal_1bus.ipynb @@ -141,11 +141,13 @@ ") # path to the template solver config file \"uc_solverconfig\"\n", "temporary_smspp_file = \"./smspp_temp_file.nc\" # path to temporary SMS++ file\n", "output_file = \"./smspp_output.txt\" # path to the output file (optional)\n", + "fp_solution = \"./fp_solution.nc4\" # path to the file where the full problem solution will be saved (optional). When provided, the result.solution object will be populated with the SMS++ solution object\n", "\n", "result = sn.optimize(\n", " configfile,\n", " temporary_smspp_file,\n", " output_file,\n", + " fp_solution=fp_solution,\n", ")\n", "\n", "print(\"Optimization finished.\")" @@ -172,6 +174,24 @@ "print(\"Status:\", result.status)\n", "print(\"Objective value:\", result.objective_value)" ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "View the solution object saved in `fp_solution` and automatically read in the `result` object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "result.solution" + ] } ], "metadata": { diff --git a/docs/release_notes.md b/docs/release_notes.md index 398e640..ef661cf 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -4,6 +4,7 @@ ### New Features and Major Changes +* [Introduce example on input/output operations and introduce solution object into the example #80](https://github.com/SPSUnipi/pySMSpp/pull/80) * [Revise SMSPPSolverTool.is_available to support shell option and move shell option to constructor of SMSPPSolverTool #78](https://github.com/SPSUnipi/pySMSpp/pull/78) * [Enable shell option in SMSNetwork.optimize #77](https://github.com/SPSUnipi/pySMSpp/pull/77) * [Enable shell option in subprocess of tools and generalize options: add explicit solverconfig option and kwargs to generalize options #73](https://github.com/SPSUnipi/pySMSpp/pull/73) diff --git a/mkdocs.yml b/mkdocs.yml index c11b27b..6873306 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -52,6 +52,7 @@ nav: - Unit Commitment (2 thermal, line): examples/ucblock_2thermal_line.ipynb - Navigating SMS Blocks: examples/navigating_sms_blocks.ipynb - Plotting Variables and Blocks: examples/plotting_variables_and_blocks.ipynb + - Input/Output Operations: examples/io_operations.ipynb - User Guide: - API Reference: api_reference.md - Release Notes: release_notes.md