Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The package aims to support:
Alternatively, using conda:

```bash
conda create -n pypsa2smspp python=3.10
conda create -n pypsa2smspp python=3.12 pip
conda activate pypsa2smspp
```

Expand Down
4 changes: 3 additions & 1 deletion docs/examples/capacity_expansion_2bus.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"1. Create a simple PyPSA model\n",
"2. Convert the PyPSA model to SMS++ using pypsa2smspp and optimize the model using default conversion to SMS++ UCBlock component\n",
"3. Verify the equivalence of objective function\n",
"4. Analyze the PyPSA model created with SMS++ and pypsa2smspp and verify dispatch"
"4. Analyze the PyPSA model created with SMS++ and pypsa2smspp and verify dispatch\n",
"\n",
"This notebook will use the UCBlock solver from [SMS++ tools](https://gitlab.com/smspp/tools) to optimize the model. To do so, pypsa2smspp will convert the PyPSA model to an [UCBlock](https://gitlab.com/smspp/ucblock) and optimize it using the default settings."
]
},
{
Expand Down
282 changes: 282 additions & 0 deletions docs/examples/capacity_expansion_investmentblock.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Capacity expansion with InvestmentBlock\n",
"\n",
"This notebook describes an example on how to use InvestmentBlock solver from [SMS++ tools](https://gitlab.com/smspp/tools) that allows using Benders Decomposition for capacity expansion, where investment decisions are located in the master problem and dispatch ones in the lower problem. To do so, the PyPSA model is converted to an [InvestmentBlock](https://gitlab.com/smspp/investmentblock) and then solved using the appropriate solver.\n",
"\n",
"This notebook relies on two libraries:\n",
"- pySMSpp: it aims to provide an abstract python interface for SMS++ models\n",
"- pypsa2smspp: it aims to provide a converter from PyPSA to SMS++ models exploiting pySMSpp\n",
"\n",
"In this notebook, the following steps are performed:\n",
"1. Create a simple PyPSA model\n",
"2. Define the configuration to use InvestmentBlock\n",
"3. Convert the PyPSA model to SMS++ using pypsa2smspp and optimize the model\n",
"4. Verify the equivalence of objective function\n",
"5. Analyze the PyPSA model created with SMS++ and pypsa2smspp and verify optimal investment decisions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Creation of the PyPSA model\n",
"\n",
"We create a simple PyPSA model arbitrary number of buses, few generators, storages and loads.\n",
"\n",
"Main assumptions are:\n",
"- The network is purely radial in the form bus1 -> bus2 -> bus3 -> ... busN\n",
"- A load is added to each bus of the network\n",
"- A generator is added to the first node"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pypsa\n",
"import pandas as pd\n",
"import pypsa2smspp"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### The following code creates the desired pypsa model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"n = pypsa.Network()\n",
"n.set_snapshots(pd.date_range(\"2024-01-01T00:00\", \"2024-01-01T23:00\", freq=\"h\"))\n",
"\n",
"# Add carriers\n",
"n.add(\"Carrier\", \"AC\")\n",
"\n",
"# Add buses\n",
"n_buses = 2\n",
"for b in range(n_buses):\n",
" n.add(\"Bus\", f\"bus{b}\", carrier=\"AC\")\n",
"\n",
"# Add lines in a radial topology using bidirectional links\n",
"n_lines = n_buses - 1\n",
"for l in range(n_lines):\n",
" n.add(\n",
" \"Link\",\n",
" f\"line{l}\",\n",
" bus0=f\"bus{l}\",\n",
" bus1=f\"bus{l+1}\",\n",
" length=1,\n",
" capital_cost=1000,\n",
" p_min_pu=-1,\n",
" p_nom_extendable=True,\n",
" )\n",
"\n",
"# Add a load to each bus\n",
"n_loads = n_buses\n",
"for l in range(n_loads):\n",
" n.add(\"Load\", f\"load{l}\", bus=f\"bus{l}\", p_set=pd.Series(100, index=n.snapshots))\n",
"\n",
"# Add a generator to the first bus\n",
"n.add(\n",
" \"Generator\",\n",
" \"gen0\",\n",
" bus=\"bus0\",\n",
" p_nom_extendable=True,\n",
" capital_cost=1000,\n",
" marginal_cost=1,\n",
")\n",
"\n",
"# Add a slack unit to each node: needed when using investmentblock\n",
"for b in range(n_buses):\n",
" n.add(\n",
" \"Generator\",\n",
" f\"slack{b}\",\n",
" bus=f\"bus{b}\",\n",
" carrier=\"slack\",\n",
" marginal_cost=1000,\n",
" p_nom=1e6,\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"n.optimize(solver_name=\"highs\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Define the configuration to use InvestmentBlock"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create the transformation object and run the transformation\n",
"# The `capacity_expansion_ucblock` option is set to `False` to enable capacity expansion using InvestmentBlockSolver instead of UnitCommitmentBlockSolver\n",
"tran = pypsa2smspp.Transformation(\n",
" capacity_expansion_ucblock=False,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Convert the PyPSA model to SMS++ and optimize it"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"n_smspp = tran.run(n, verbose=False) # create the model and optimizes it in one line\n",
"\n",
"# If you wish to create the model and optimize it in separate steps, you can do so as follows:\n",
"# tran.create_model(n) # create the model\n",
"# tran.optimize(solver_name=\"highs\") # optimize the model\n",
"# n_smspp = tran.retrieve_solution(n) # retrieve the solution and store it in a new Network object"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Objective value"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"n_smspp.objective"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Verify the equivalence of SMS++ and PyPSA results"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pypsa_obj = n.objective\n",
"smspp_obj = n_smspp.objective\n",
"print(\"SMS++ obj : %.6f\" % smspp_obj)\n",
"print(\"PyPSA obj : %.6f\" % pypsa_obj)\n",
"print(\"Error SMS++ - PyPSA [%%]: %.5f\" % (100*(smspp_obj - pypsa_obj)/pypsa_obj))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Analyze the PyPSA model created with SMS++ and pypsa2smspp"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Retrieve PyPSA statistics"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"n_stat = n_smspp.statistics.optimal_capacity()\n",
"n_stat"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Retrieve SMS++ statistics"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"n_parsed_stat = n_smspp.statistics.optimal_capacity()\n",
"n_parsed_stat"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Calculate difference between the two"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"error_stat = n_stat - n_parsed_stat\n",
"\n",
"merged_stat = pd.concat([n_stat, n_parsed_stat, error_stat], axis=1)\n",
"merged_stat.columns = [\"pypsa\", \"smspp\", \"difference\"]\n",
"merged_stat"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pypsa2smspp",
"language": "python",
"name": "python3"
},
"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.13.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
27 changes: 22 additions & 5 deletions docs/examples/stochastic_demand_intermittent.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
"id": "45653590",
"metadata": {},
"source": [
"# Stochastic Unit Commitment example: PyPSA → SMS++\n",
"# Stochastic Optimization with pypsa2smspp\n",
"\n",
"This notebook builds a small PyPSA network directly in Python, converts it into a stochastic network, solves the two-stage stochastic problem with PyPSA, and then solves the same instance through the `pypsa2smspp` transformation pipeline.\n",
"This notebook showcases an example on how to solve a PyPSA `StochasticNetwork` with SMS++.\n",
"\n",
"The example assumes that `pypsa2smspp`, `pySMSpp`, SMS++, PyPSA, and the selected solver are already installed and correctly configured.\n"
"To this goal, the following steps are performed:\n",
"1. Create a small PyPSA network\n",
"2. Optimizes it using PyPSA's built-in optimization capabilities to establish a baseline for comparison\n",
"3. Convert it to SMS++ same instance through the `pypsa2smspp` transformation pipeline\n",
"4. Optimizes the SMS++ model using the Two Stage Stochastic Block (ttsb_solver) solver from SMS++ tools\n",
"5. Compare the results obtained from both optimizations\n",
"\n",
"This notebook will use the Two Stage Stochastic Block (ttsb_solver) solver from [SMS++ tools](https://gitlab.com/smspp/tools) to optimize the model. To do so, pypsa2smspp will convert the PyPSA model to an [TwoStageStochastic Block](https://gitlab.com/smspp/twostagestochasticblock) and optimize it using the default settings.\n",
"\n",
"Note: it requires smspp-project version 0.6.0 or higher.\n"
]
},
{
Expand Down Expand Up @@ -177,7 +186,15 @@
")\n",
"\n",
"# Add high-cost slack units to guarantee feasibility in all scenarios.\n",
"n = add_slack_unit(n)\n",
"for b in range(len(n.buses)):\n",
" n.add(\n",
" \"Generator\",\n",
" f\"slack_{b}\",\n",
" bus=f\"IT0 {b}\",\n",
" carrier=\"slack\",\n",
" marginal_cost=1000,\n",
" p_nom=1e6,\n",
" )\n",
"\n",
"n"
]
Expand Down Expand Up @@ -382,7 +399,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.12"
"version": "3.13.13"
}
},
"nbformat": 4,
Expand Down
21 changes: 10 additions & 11 deletions docs/getting-started/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@

## What is pypsa2smspp?

pypsa2smspp is a Python package to enable advanced mathematical decomposition in the energy modeling framework [PyPSA](https://github.com/PyPSA/pypsa). This package interfaces [PyPSA](https://github.com/PyPSA/pypsa) using [Structured Modeling System for mathematical models (SMS++)](https://smspp.gitlab.io/smspp-project/) and its python interface [pySMSpp](https://github.com/SPSUnipi/pySMSpp).
pypsa2smspp is a Python package that connects [PyPSA](https://github.com/PyPSA/pypsa) energy-system models with [SMS++](https://smspp.gitlab.io/smspp-project/), using the [pySMSpp](https://github.com/SPSUnipi/pySMSpp) Python interface.

The package is under active development and the current documentation aims to facilitate the development of the package. Currently, there is not yet a stable release of the package on PyPI or Conda, so the installation can be performed by cloning the repository and installing the package locally.
PyPSA remains the user-facing modelling environment: networks, components, time series, and optimized results are represented as PyPSA objects. SMS++ provides the block-structured mathematical model and the solvers that can exploit that structure. pypsa2smspp sits between them: it converts a PyPSA network into an SMS++ block hierarchy, runs the optimization through pySMSpp, and maps the solution back onto the original PyPSA network.

## What is SMS++?
The package is under active development. The current documentation is intended both for users who want to run conversions and for developers who want to understand and extend the transformation pipeline. There is not yet a stable release on PyPI or Conda, so the package is currently installed by cloning the repository and installing it locally.

SMS++ is a software tool for advanced optimization of mathematical models by adopting advanced decomposition tools.
It is a collection of C++ classes for modeling complex, block-structured mathematical models and solving them via sophisticated, structure-exploiting algorithms such as decomposition methods and specialized Interior-Point approaches.
## Why pypsa2smspp?

SMS++ preserves the block-structure of the model and allows the user to define the model in terms of blocks, which can be solved by different solvers. Each block may describe a specific physical system to model (e.g. a generator of a power system) or a specific mathematical structure (e.g. to allow Lagrangian relaxation). Each block may be characterized by a set of variables, constraints, and objectives, and may be solved by highly specialized solvers and decomposition techniques. As each block is solved by a specialized solver, the overall solution process is highly efficient and can exploit the structure of the model.
PyPSA is a flexible framework for defining and optimizing energy-system models in Python. SMS++ is designed for advanced optimization of block-structured mathematical models, including decomposition methods and specialized solvers.

SMS++ supports a hierarchical model structure, where blocks may contain other blocks. This allows the user to define complex models in a modular way, where each block can be solved by a specialized solver. The nested structure combined with the specialized solvers allows the user to exploit the structure of the model and aims to break down computational resources to solve large-scale models.
pypsa2smspp combines these strengths. Users can continue to build networks in PyPSA while experimenting with SMS++ formulations such as `UCBlock`, `InvestmentBlock`, `DesignNetworkBlock`, and `TwoStageStochasticBlock`. This is especially useful for capacity expansion, unit-commitment-style formulations, network design, and stochastic problems where the mathematical structure matters.

For more information about SMS++, please refer to the [SMS++ website](https://smspp.gitlab.io/smspp-project/).
## What is SMS++?

## Why pypsa2smspp?
SMS++ is a C++ framework for modelling and solving complex block-structured optimization problems. A model is represented as a hierarchy of blocks; each block can contain variables, constraints, objectives, data, and nested sub-blocks.

As mentioned above SMS++ is a powerful tool for solving complex mathematical models. On the other hand, PyPSA is a powerful tool for modeling and optimizing energy systems. By interfacing PyPSA with SMS++, we can leverage the power of SMS++ to solve complex energy system models defined in PyPSA. This allows us to solve larger and more complex models than what is possible with the default solvers available in PyPSA, and to exploit the structure of the model to achieve faster solution times.
This block structure is important because it lets solvers exploit the mathematical organization of the problem. For example, different units, networks, investment variables, and scenario-dependent data can be represented as separate but connected blocks. SMS++ can then use decomposition-oriented algorithms or specialized solvers that are aware of this structure.

By leveraging on [pySMSpp](https://pysmspp.readthedocs.io/en/latest/), that is the Python interface to SMS++, pypsa2smspp allows to convert PyPSA models into SMS++ objects in Python, and to write, read and optimize SMS++ models. This allows us to leverage the power of SMS++ while still being able to define the model in Python, which is a widely used programming language in the energy modeling community.
For more information, see the [SMS++ website](https://smspp.gitlab.io/smspp-project/).
Loading
Loading