Repository: flechtenberg/pulpo (language: python)

Issue:
Elementary flow calculation and constraints



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index f7d9a12..4c4c5ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,3 +94,4 @@ pulpo_install_methanol_case.ipynb
 
 
 
+/notebooks/energy_transition_showcase_figures.ipynb
diff --git a/LICENSE b/LICENSE
index f685224..81d4fed 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 BSD 3-Clause License
 
-Copyright (c) 2023, Fabian Lechtenberg
+Copyright (c) 2024, Fabian Lechtenberg
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/README.md b/README.md
index a3246c9..cc46e5b 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,6 @@
 
 ## 📖 Table of Contents
 - [📍 Overview](#-overview)
-- [📂 Repository Structure](#-repository-structure)
 - [⚙️ Modules](#modules)
 - [🚀 Getting Started](#-getting-started)
     - [🔧 Installation](#-installation)
@@ -35,34 +34,7 @@
 
 ## 📍 Overview
 
-The project, "pulpo," is a life cycle assessment (LCA) optimization tool that aims to import, optimize, solve, retrieve, save, and summarize LCA results. It provides functionalities for data import from the Ecoinvent database, conversion of inputs into a dictionary for the optimization model, definition and solving of the optimization model using the pyomo package, and saving and summarizing of the model results. The tool's value lies in streamlining LCA processes, enabling optimization and analysis of environmental impacts, and facilitating the decision-making process for sustainable solutions.
-
-
----
-
-
-## 📂 Repository Structure
-
-```sh
-└── pulpo/
-    ├── .gitattributes
-    ├── .gitconfig
-    ├── .gitignore
-    ├── desktop.ini
-    ├── LICENSE
-    ├── notebooks/
-    │   └── hydrogen_showcase.ipynb
-    ├── pulpo/
-    │   ├── __init__.py
-    │   ├── pulpo.py
-    │   └── utils/
-    ├── README.md
-    ├── setup.cfg
-    └── tests/
-        ├── __init__.py
-        └── test_functions.py
-```
-
+Pulpo is a comprehensive Life Cycle Optimization (LCO) tool designed to streamline the optimization of environmental impacts across the entire lifecycle of products. It facilitates the import of data from the LCI databases accessed via brightway, converts inputs into optimization-ready formats, defines and solves optimization models using the Pyomo package, and saves and summarizes results. Pulpo empowers users to efficiently optimize and analyze environmental impacts, supporting sustainable decision-making through lifecycle-based strategies.
 
 ---
 
@@ -70,14 +42,14 @@ The project, "pulpo," is a life cycle assessment (LCA) optimization tool that ai
 
 <details closed><summary>Root</summary>
 
-| File                                                                                                         | Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
+| File                                                                                                         | Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
 | ---                                                                                                          | ---                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
-| [.gitconfig](https://github.com/flechtenberg/pulpo/blob/main/.gitconfig)                                     | This code fragment configures a git filter to clean Jupyter Notebook files in the.gitconfig file. It uses the Jupyter nbconvert command to remove the output cells and smudge to display the file's contents.                                                                                                                                                                                                                                                                                                                                              |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
-| [pulpo.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\pulpo.py)                                   | The code implements a PulpoOptimizer class that provides functionalities for data import, optimization, solving, retrieval, saving and summarizing results related to life cycle assessments. It uses modules like optimizer, bw_parser, converter, and saver for different operations.                                                                                                                                                                                                                                                                    |
-| [bw_parser.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\utils\bw_parser.py)                     | The code in `bw_parser.py` provides functions for importing, saving, and retrieving life cycle inventory (LCI) data from the Ecoinvent database using the Brightway2 library. It includes functions for importing data, checking if data needs to be reloaded, performing LCA calculations, saving LCI data to files, and reading LCI data from files. Additionally, it provides functions for retrieving activities and environmental flows from the database based on specified criteria. The `main` function demonstrates the usage of these functions. |
-| [converter.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\utils\converter.py)                     | The code in pulpo\utils\converter.py combines various inputs into a dictionary for an optimization model. It converts sparse matrices to dictionaries, modifies the technosphere matrix, creates sets, specifies demand, limits, and supply, assigns weights, and assembles the final data dictionary for the model. This function serves as a crucial step in preparing the inputs for the optimization process.                                                                                                                                          |
-| [optimizer.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\utils\optimizer.py)                     | The code defines an optimization model using the pyomo package. It includes sets, parameters, variables, constraints, and an objective function. The model is created and solved using different solvers.                                                                                                                                                                                                                                                                                                                                                  |
-| [saver.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\utils\saver.py)                             | The code provides two main functionalities:1. save_results: Saves the results of a Pyomo model to an Excel file, including raw results, metadata, and constraints.2. summarize_results: Prints a summary of the model results, including demand, impacts, choices, and constraints.                                                                                                                                                                                                                                                                        |
+| [.gitconfig](https://github.com/flechtenberg/pulpo/blob/main/.gitconfig)                                     | This code fragment configures a git filter to clean Jupyter Notebook files in the.gitconfig file. It uses the Jupyter nbconvert command to remove the output cells and smudge to display the file's contents.                                                                                                                                                                                                                                                                                                                                             |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
+| [pulpo.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\pulpo.py)                                   | The code implements a PulpoOptimizer class that provides functionalities for data import, optimization, solving, retrieval, saving and summarizing results related to life cycle assessments. It uses modules like optimizer, bw_parser, converter, and saver for different operations.                                                                                                                                                                                                                                                                   |
+| [bw_parser.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\utils\bw_parser.py)                     | The code in `bw_parser.py` provides functions for importing, saving, and retrieving life cycle inventory (LCI) data from the Ecoinvent database using the Brightway2 library. It includes functions for importing data, checking if data needs to be reloaded, performing LCA calculations, saving LCI data to files, and reading LCI data from files. Additionally, it provides functions for retrieving activities and environmental flows from the database based on specified criteria. |
+| [converter.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\utils\converter.py)                     | The code in pulpo\utils\converter.py combines various inputs into a dictionary for an optimization model. It converts sparse matrices to dictionaries, modifies the technosphere matrix, creates sets, specifies demand, limits, and supply, assigns weights, and assembles the final data dictionary for the model. This function serves as a crucial step in preparing the inputs for the optimization process.                                                                                                                                         |
+| [optimizer.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\utils\optimizer.py)                     | The code defines an optimization model using the pyomo package. It includes sets, parameters, variables, constraints, and an objective function. The model is created and solved using different solvers.                                                                                                                                                                                                                                                                                                                                                 |
+| [saver.py](https://github.com/flechtenberg/pulpo/blob/main/pulpo\utils\saver.py)                             | The code provides two main functionalities:1. save_results: Saves the results of a Pyomo model to an Excel file, including raw results, metadata, and constraints.2. summarize_results: Prints a summary of the model results, including demand, impacts, choices, and constraints.                                                                                                                                                                                                                                                                       |
 
 </details>
 
@@ -95,7 +67,10 @@ pip install pulpo-dev
 ### 🤖 Running pulpo
 See [pypi](https://pypi.org/project/pulpo-dev/) for a description of how to use PULPO via pip.
 
-Find example notebooks for a [hydrogen case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/hydrogen_showcase.ipynb) and an [electricity case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/electricity_showcase.ipynb) here.
+Find example notebooks for a [hydrogen case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/hydrogen_showcase.ipynb), an [electricity case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/electricity_showcase.ipynb), and a [plastic case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/plastic_showcase.ipynb) here.
+
+There is also a "workshop" repository ([here](https://github.com/flechtenberg/pulpo_workshop)), which has been created for the Brightcon 2024 conference. It contains several notebooks that guide you through the PULPO package and its functionalities, as well as an exercise.
+
 
 ### 🧪 Tests
 
@@ -105,6 +80,9 @@ Calling from the package folder:
 python -m unittest discover -s tests
 ```
 
+### Updates 
+> - [ ] `ℹ️ The package is currently under development.`
+
 ---
 
 
@@ -143,7 +121,7 @@ The project maintainers will review your changes and provide feedback or merge t
 ## 📄 License
 
 This project is licensed under the `ℹ️  BSD 3-Clause` License. See the [LICENSE](LICENSE) file for additional info.
-Copyright (c) 2023, Fabian Lechtenberg. All rights reserved.
+Copyright (c) 2024, Fabian Lechtenberg. All rights reserved.
 
 ---
 
diff --git a/README_pypi.md b/README_pypi.md
index 601a5dc..f0ee26f 100644
--- a/README_pypi.md
+++ b/README_pypi.md
@@ -18,9 +18,7 @@
 
 ## 📍 Overview
 
-The project, "pulpo," is a life cycle assessment (LCA) optimization tool that aims to import, optimize, solve, retrieve, save, and summarize LCA results. It provides functionalities for data import from the Ecoinvent database, conversion of inputs into a dictionary for the optimization model, definition and solving of the optimization model using the pyomo package, and saving and summarizing of the model results. The tool's value lies in streamlining LCA processes, enabling optimization and analysis of environmental impacts, and facilitating the decision-making process for sustainable solutions.
-
-
+Pulpo is a comprehensive Life Cycle Optimization (LCO) tool designed to streamline the optimization of environmental impacts across the entire lifecycle of products. It facilitates the import of data from the LCI databases accessed via brightway, converts inputs into optimization-ready formats, defines and solves optimization models using the Pyomo package, and saves and summarizes results. Pulpo empowers users to efficiently optimize and analyze environmental impacts, supporting sustainable decision-making through lifecycle-based strategies.
 
 ---
 
@@ -46,7 +44,7 @@ tests.setup()
 ```
 This function mimics the development test functions and if all tests have passed you are good to go.
 
-To learn the PULPO workflow for more complex case studies, find example notebooks for a [hydrogen case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/hydrogen_showcase.ipynb) and an [electricity case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/electricity_showcase.ipynb) here.
+To learn the PULPO workflow for more complex case studies, find example notebooks for a [hydrogen case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/hydrogen_showcase.ipynb), an [electricity case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/electricity_showcase.ipynb), and a [plastic case](https://github.com/flechtenberg/pulpo/blob/master/notebooks/plastic_showcase.ipynb) here.
 
 You can also follow these notebooks locally with the shipped package by calling:
 ```sh
@@ -54,6 +52,7 @@ from pulpo import pulpo
 
 pulpo.electricity_showcase()
 pulpo.hydrogen_showcase()
+pulpo.plastic_showcase()
 ```
 
 It should be noted that to run these showcase it is necessary to install the ecoinvent cutoff38 system model via brightway2/activity-browser.
diff --git a/notebooks/electricity_showcase.ipynb b/notebooks/electricity_showcase.ipynb
index dbbfe9c..a537bef 100644
--- a/notebooks/electricity_showcase.ipynb
+++ b/notebooks/electricity_showcase.ipynb
@@ -51,15 +51,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "606b78ed",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:01.153082Z",
-     "iopub.status.busy": "2023-10-10T18:15:01.153082Z",
-     "iopub.status.idle": "2023-10-10T18:15:02.965434Z",
-     "shell.execute_reply": "2023-10-10T18:15:02.964401Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:01.153082Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "import os\n",
@@ -91,15 +83,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "ead64289",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:07.705965Z",
-     "iopub.status.busy": "2023-10-10T18:15:07.704968Z",
-     "iopub.status.idle": "2023-10-10T18:15:07.725909Z",
-     "shell.execute_reply": "2023-10-10T18:15:07.724901Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:07.705965Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "project = \"pulpo\"\n",
@@ -126,15 +110,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "ae4b5787",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:09.429787Z",
-     "iopub.status.busy": "2023-10-10T18:15:09.428790Z",
-     "iopub.status.idle": "2023-10-10T18:15:09.435766Z",
-     "shell.execute_reply": "2023-10-10T18:15:09.434770Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:09.429787Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "pulpo_worker = pulpo.PulpoOptimizer(project, database, methods, directory)"
@@ -152,15 +128,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "80697078",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:10.611821Z",
-     "iopub.status.busy": "2023-10-10T18:15:10.610825Z",
-     "iopub.status.idle": "2023-10-10T18:15:14.445695Z",
-     "shell.execute_reply": "2023-10-10T18:15:14.444696Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:10.611821Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "pulpo_worker.get_lci_data()"
@@ -228,15 +196,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "167a2bfb",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:16.554554Z",
-     "iopub.status.busy": "2023-10-10T18:15:16.553556Z",
-     "iopub.status.idle": "2023-10-10T18:15:16.562526Z",
-     "shell.execute_reply": "2023-10-10T18:15:16.561529Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:16.554554Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "keys = [\"('cutoff38', '962727b9a36bcaa186f222b29b57f6a3')\", \"('cutoff38', '473d4bb488e8f903b58203f3e5161636')\"]"
@@ -246,15 +206,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "b46fa327",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:16.769269Z",
-     "iopub.status.busy": "2023-10-10T18:15:16.768274Z",
-     "iopub.status.idle": "2023-10-10T18:15:18.341459Z",
-     "shell.execute_reply": "2023-10-10T18:15:18.340462Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:16.769269Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "pulpo_worker.retrieve_activities(keys=keys)"
@@ -280,15 +232,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "4d445dfc",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:18.343452Z",
-     "iopub.status.busy": "2023-10-10T18:15:18.342456Z",
-     "iopub.status.idle": "2023-10-10T18:15:18.357406Z",
-     "shell.execute_reply": "2023-10-10T18:15:18.356409Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:18.343452Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "activities = [\"market for electricity, high voltage\"]\n",
@@ -309,13 +253,6 @@
    "execution_count": null,
    "id": "0cb7d84f",
    "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:18.358403Z",
-     "iopub.status.busy": "2023-10-10T18:15:18.358403Z",
-     "iopub.status.idle": "2023-10-10T18:15:20.237540Z",
-     "shell.execute_reply": "2023-10-10T18:15:20.236542Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:18.358403Z"
-    },
     "scrolled": true
    },
    "outputs": [],
@@ -328,13 +265,6 @@
    "execution_count": null,
    "id": "b211d17a",
    "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:20.244515Z",
-     "iopub.status.busy": "2023-10-10T18:15:20.240529Z",
-     "iopub.status.idle": "2023-10-10T18:15:24.221327Z",
-     "shell.execute_reply": "2023-10-10T18:15:24.220330Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:20.243519Z"
-    },
     "scrolled": true
    },
    "outputs": [],
@@ -354,15 +284,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "332b2f95",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:24.222324Z",
-     "iopub.status.busy": "2023-10-10T18:15:24.222324Z",
-     "iopub.status.idle": "2023-10-10T18:15:25.805359Z",
-     "shell.execute_reply": "2023-10-10T18:15:25.804358Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:24.222324Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "electricity_market = pulpo_worker.retrieve_activities(activities=activities, reference_products=reference_products, locations=locations)"
@@ -372,15 +294,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "1959b64f",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:25.807353Z",
-     "iopub.status.busy": "2023-10-10T18:15:25.806357Z",
-     "iopub.status.idle": "2023-10-10T18:15:25.820128Z",
-     "shell.execute_reply": "2023-10-10T18:15:25.820128Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:25.806357Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "electricity_market"
@@ -398,15 +312,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "223e1cf1",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:25.822126Z",
-     "iopub.status.busy": "2023-10-10T18:15:25.821156Z",
-     "iopub.status.idle": "2023-10-10T18:15:25.836106Z",
-     "shell.execute_reply": "2023-10-10T18:15:25.835106Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:25.822126Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "demand = {electricity_market[0]: 1.28819e+11}"
@@ -432,15 +338,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "d704a9a5",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:25.837102Z",
-     "iopub.status.busy": "2023-10-10T18:15:25.837102Z",
-     "iopub.status.idle": "2023-10-10T18:15:27.327241Z",
-     "shell.execute_reply": "2023-10-10T18:15:27.326240Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:25.837102Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "activities = [\"electricity production, lignite\", \n",
@@ -457,15 +355,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "9d8fac69",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:27.328238Z",
-     "iopub.status.busy": "2023-10-10T18:15:27.328238Z",
-     "iopub.status.idle": "2023-10-10T18:15:27.357193Z",
-     "shell.execute_reply": "2023-10-10T18:15:27.356191Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:27.328238Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "electricity_activities"
@@ -493,15 +383,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "bfae6af4",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:27.360182Z",
-     "iopub.status.busy": "2023-10-10T18:15:27.359185Z",
-     "iopub.status.idle": "2023-10-10T18:15:27.373256Z",
-     "shell.execute_reply": "2023-10-10T18:15:27.372259Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:27.360182Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "choices  = {'electricity': {electricity_activities[0]: 1e16,\n",
@@ -533,13 +415,6 @@
    "execution_count": null,
    "id": "be46ea2d",
    "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:27.374252Z",
-     "iopub.status.busy": "2023-10-10T18:15:27.373256Z",
-     "iopub.status.idle": "2023-10-10T18:15:35.737432Z",
-     "shell.execute_reply": "2023-10-10T18:15:35.736455Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:27.374252Z"
-    },
     "scrolled": true
    },
    "outputs": [],
@@ -570,13 +445,6 @@
    "execution_count": null,
    "id": "eb13ed22",
    "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:35.738427Z",
-     "iopub.status.busy": "2023-10-10T18:15:35.738427Z",
-     "iopub.status.idle": "2023-10-10T18:15:59.973249Z",
-     "shell.execute_reply": "2023-10-10T18:15:59.972248Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:35.738427Z"
-    },
     "scrolled": true
    },
    "outputs": [],
@@ -606,15 +474,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "cf427cea",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:15:59.974245Z",
-     "iopub.status.busy": "2023-10-10T18:15:59.974245Z",
-     "iopub.status.idle": "2023-10-10T18:16:01.078467Z",
-     "shell.execute_reply": "2023-10-10T18:16:01.078467Z",
-     "shell.execute_reply.started": "2023-10-10T18:15:59.974245Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "pulpo_worker.save_results(choices=choices, demand=demand, name='electricity_showcase_results.xlsx')"
@@ -640,15 +500,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "f6141497",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:16:01.080465Z",
-     "iopub.status.busy": "2023-10-10T18:16:01.079499Z",
-     "iopub.status.idle": "2023-10-10T18:16:01.127449Z",
-     "shell.execute_reply": "2023-10-10T18:16:01.126496Z",
-     "shell.execute_reply.started": "2023-10-10T18:16:01.080465Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
@@ -710,15 +562,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "04b68998",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:16:01.128446Z",
-     "iopub.status.busy": "2023-10-10T18:16:01.128446Z",
-     "iopub.status.idle": "2023-10-10T18:16:02.434337Z",
-     "shell.execute_reply": "2023-10-10T18:16:02.433211Z",
-     "shell.execute_reply.started": "2023-10-10T18:16:01.128446Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "activities = [\"market for nuclear fuel element, for pressure water reactor, UO2 4.0% & MOX\"]\n",
@@ -732,15 +576,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "aebdc35c",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:16:02.435335Z",
-     "iopub.status.busy": "2023-10-10T18:16:02.435335Z",
-     "iopub.status.idle": "2023-10-10T18:16:02.449289Z",
-     "shell.execute_reply": "2023-10-10T18:16:02.448292Z",
-     "shell.execute_reply.started": "2023-10-10T18:16:02.435335Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "nuclear_fuel"
@@ -750,15 +586,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "a89c6ee5",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:16:02.450285Z",
-     "iopub.status.busy": "2023-10-10T18:16:02.449289Z",
-     "iopub.status.idle": "2023-10-10T18:16:02.465235Z",
-     "shell.execute_reply": "2023-10-10T18:16:02.464239Z",
-     "shell.execute_reply.started": "2023-10-10T18:16:02.449289Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "upper_limit = {nuclear_fuel[0]: 100000}"
@@ -777,13 +605,6 @@
    "execution_count": null,
    "id": "87267aa9",
    "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:16:02.466232Z",
-     "iopub.status.busy": "2023-10-10T18:16:02.465235Z",
-     "iopub.status.idle": "2023-10-10T18:16:28.047272Z",
-     "shell.execute_reply": "2023-10-10T18:16:28.046240Z",
-     "shell.execute_reply.started": "2023-10-10T18:16:02.466232Z"
-    },
     "scrolled": true
    },
    "outputs": [],
@@ -796,15 +617,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "a03f1671",
-   "metadata": {
-    "execution": {
-     "iopub.execute_input": "2023-10-10T18:16:28.048238Z",
-     "iopub.status.busy": "2023-10-10T18:16:28.048238Z",
-     "iopub.status.idle": "2023-10-10T18:16:28.092092Z",
-     "shell.execute_reply": "2023-10-10T18:16:28.092092Z",
-     "shell.execute_reply.started": "2023-10-10T18:16:28.048238Z"
-    }
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "pulpo_worker.summarize_results(choices=choices, demand=demand, constraints=upper_limit)"
@@ -815,7 +628,7 @@
    "id": "5f409b26",
    "metadata": {},
    "source": [
-    "As can be seen from the summary above, part of the final electricity demand is supplied by the wind turbine processes, because the nuclear process is constrained by the nuclear fuel process. It is also evident that the impact is higher than the previous one, as the GWP minimizing process (nuclear) can no longer supply the full demand."
+    "As can be seen from the summary above, part of the final electricity demand is supplied by the wind turbine processes, because the nuclear electricity process is constrained by the nuclear fuel process. It is also evident that the impact is higher than the previous one, as the GWP minimizing process (nuclear) can no longer supply the full demand."
    ]
   },
   {
@@ -996,7 +809,7 @@
    "metadata": {},
    "source": [
     "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
-    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Lower Level Decisions</h2>\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Lower Level Decisions (Regional Choice)</h2>\n",
     "</div>"
    ]
   },
@@ -1412,7 +1225,7 @@
    "id": "5e8924a0-6f11-4891-9f12-c06eb2c699b2",
    "metadata": {},
    "source": [
-    "The result of this optimization is a minimum GWP of 1.764977e+10 kg CO2eq instead of 1.599836e+10 kg CO2eq from the initial calculations uising the full LCI. This is a difference of **8%**!"
+    "The result of this optimization is a minimum GWP of 1.764977e+10 kg CO2eq instead of 1.599836e+10 kg CO2eq from the initial calculations using the full LCI. This is a difference of **8%**!"
    ]
   },
   {
@@ -1450,7 +1263,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.16"
+   "version": "3.9.19"
   }
  },
  "nbformat": 4,
diff --git a/notebooks/energy_transition_showcase.ipynb b/notebooks/energy_transition_showcase.ipynb
new file mode 100644
index 0000000..d1c451a
--- /dev/null
+++ b/notebooks/energy_transition_showcase.ipynb
@@ -0,0 +1,250 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "bd87768d-b2ea-4593-8a70-c89ed8227bb8",
+   "metadata": {},
+   "source": [
+    "## Energy Transition Case Study\n",
+    "\n",
+    "This notebook presents the base case optimization of the German electricity market as presented in the manuscript presenting the PULPO framework.\n",
+    "\n",
+    "Updated by Fabian Lechtenberg, 15.04.2024.\n",
+    "\n",
+    "See [this notebook](./electricity_showcase.ipynb) for additional results and functionalities of this case study, and [this notebook](./plastic_showcase.ipynb) for a more thorough demonstration of the functionalities on a plastics recycling case study."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "bbc904da-04a9-4151-baba-f1537711e796",
+   "metadata": {},
+   "source": [
+    "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Demonstration 🚀</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ea903462-b798-40ae-ba75-52820ac325ff",
+   "metadata": {},
+   "source": [
+    "### Base Problem"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "614b70a0-858d-46c6-bc11-afc44cc7b0e6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Import section\n",
+    "import os\n",
+    "import sys\n",
+    "sys.path.append('../')\n",
+    "\n",
+    "import pandas as pd\n",
+    "from pulpo import pulpo"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6bb959c5-ef79-45c3-8f29-a876322eabb4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Your brightway2 project, database and targeted impact category\n",
+    "project = \"pulpo\"\n",
+    "database = \"cutoff38\"\n",
+    "methods = {\"('IPCC 2013', 'climate change', 'GWP 100a')\": 1,\n",
+    "           \"('ReCiPe Endpoint (E,A)', 'resources', 'total')\": 0,\n",
+    "           \"('ReCiPe Endpoint (E,A)', 'human health', 'total')\": 0,\n",
+    "           \"('ReCiPe Endpoint (E,A)', 'ecosystem quality', 'total')\": 0,\n",
+    "           \"('ReCiPe Midpoint (E) V1.13', 'ionising radiation', 'IRP_HE')\": 0}\n",
+    "\n",
+    "# Substitute with your working directory of choice\n",
+    "notebook_dir = os.path.dirname(os.getcwd())\n",
+    "directory = os.path.join(notebook_dir, 'data')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e19799b6-7601-4614-8320-76f84e00aba8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Create a PULPO object\n",
+    "pulpo_worker = pulpo.PulpoOptimizer(project, database, methods, directory)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "763ba723-2074-48ba-bc38-7922b86134e9",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Retrieve LCI data\n",
+    "pulpo_worker.get_lci_data()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ac586be8-813c-40b3-8fd2-83642b43c378",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Retrieve the electricity market\n",
+    "activities = [\"market for electricity, high voltage\"]\n",
+    "reference_products = [\"electricity, high voltage\"]\n",
+    "locations = [\"DE\"]\n",
+    "electricity_market = pulpo_worker.retrieve_activities(activities=activities,\n",
+    "                                                      reference_products=reference_products,\n",
+    "                                                      locations=locations)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ff3da007-1c3e-434c-ab21-f6fb00a19d41",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Specify the functional unit as demand dictionary\n",
+    "demand = {electricity_market[0]: 1.28819e+11}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b0d92d90-b230-43b5-99c9-77b9477bb601",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Retrieve the choices\n",
+    "activities = [\"electricity production, lignite\", \n",
+    "             \"electricity production, hard coal\",\n",
+    "             \"electricity production, nuclear, pressure water reactor\",\n",
+    "             \"electricity production, wind, 1-3MW turbine, onshore\"]\n",
+    "reference_products = [\"electricity, high voltage\"]\n",
+    "locations = [\"DE\"]\n",
+    "\n",
+    "electricity_activities = pulpo_worker.retrieve_activities(activities=activities,\n",
+    "                                                          reference_products=reference_products,\n",
+    "                                                          locations=locations)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f14ab5a4-5ecd-4d93-91e4-958bec6326c0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Specify the choices dictionary\n",
+    "choices  = {'electricity': {electricity_activities[0]: 1e16,\n",
+    "                            electricity_activities[1]: 1e16,\n",
+    "                            electricity_activities[2]: 1e16,\n",
+    "                            electricity_activities[3]: 1e16}}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d9baca84-3346-4496-9690-c39a30d5081a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Instantiate and solve the problem (here with HiGHS)\n",
+    "instance = pulpo_worker.instantiate(choices=choices, demand=demand)\n",
+    "results = pulpo_worker.solve()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "71143edd-c671-4ee0-a79c-3ebe192fc450",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Summarize the results\n",
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ea3df02a-fbe5-4937-842d-990b403bd109",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pulpo_worker.save_results(choices=choices, demand=demand, name='energy_transition_showcase.xlsx')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2bb668e7-30ca-4b55-9ea0-50e1415afee6",
+   "metadata": {},
+   "source": [
+    "### No optimization"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d70de808-ca7b-4db1-9eed-1c9a0913c073",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Instantiate and solve the problem (here with HiGHS)\n",
+    "instance = pulpo_worker.instantiate(choices={}, demand=demand)\n",
+    "results = pulpo_worker.solve()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f7e055e4-f7a6-4cdf-863f-2b104f0e2ed1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c756d642-6362-417b-80d4-11eff0d8880e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pulpo_worker.save_results(choices=choices, demand=demand, name='energy_transition_showcase_noopt.xlsx')"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "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.9.19"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/helper.py b/notebooks/helper.py
new file mode 100644
index 0000000..658cf32
--- /dev/null
+++ b/notebooks/helper.py
@@ -0,0 +1,57 @@
+import brightway2 as bw
+
+def create_or_update_activity(db, activity_code, activity_details, exchanges, biosphere):
+    """
+    Creates or updates an activity with specified technosphere and biosphere exchanges.
+
+    Parameters:
+    - db: The Brightway2 database object.
+    - activity_code: Unique code for the activity.
+    - activity_details: Dictionary containing the name, unit, and location of the activity.
+    - exchanges: List of dictionaries detailing the exchanges to be added or updated.
+    - biosphere: The Brightway2 biosphere database object for biosphere exchanges.
+    """
+    try:
+        # Try to get the activity if it already exists
+        activity = db.get(activity_code)
+        print(f"{activity_details['name']} already exists, updating exchanges.")
+    except:
+        # If not, create a new activity
+        activity = db.new_activity(code=activity_code, **activity_details)
+        activity.save()
+        print(f"Created {activity_details['name']}.")
+
+    # Add or update exchanges
+    for exch in exchanges:
+        # Determine if the exchange is technosphere or biosphere
+        if exch['type'] == 'biosphere':
+            search_result = biosphere.search(exch['name'])[0]
+        else:  # 'technosphere'
+            search_result = db.search(exch['name'])[0]
+
+        # Add or update the exchange
+        activity.new_exchange(input=search_result.key, amount=exch['amount'], type=exch['type']).save()
+    
+    # Save changes to the activity
+    activity.save()
+    print(f"Updated {activity_details['name']} with new exchanges.")
+
+def copy_activity(original_activity):
+    # Construct the new name for the copied activity
+    new_activity_name = original_activity['name'] + " copy"
+
+    # Get the database of the original activity
+    db = bw.Database(original_activity['database'])
+
+    # Check if the activity with the new name already exists
+    existing_activities = {a['name']: a for a in db}
+    if new_activity_name in existing_activities:
+        print(f"Activity '{new_activity_name}' already exists.")
+        return existing_activities[new_activity_name]
+    else:
+        # Create a new activity as a copy of the original activity
+        copied_activity = original_activity.copy()
+        copied_activity['name'] = new_activity_name
+        copied_activity.save()
+        print(f"Created a copy of '{original_activity['name']}' named '{new_activity_name}'.")
+        return copied_activity
diff --git a/notebooks/hydrogen_showcase.ipynb b/notebooks/hydrogen_showcase.ipynb
index e88c4f6..c37a4d3 100644
--- a/notebooks/hydrogen_showcase.ipynb
+++ b/notebooks/hydrogen_showcase.ipynb
@@ -455,9 +455,7 @@
    "cell_type": "code",
    "execution_count": null,
    "id": "f6141497",
-   "metadata": {
-    "scrolled": false
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
@@ -885,7 +883,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.16"
+   "version": "3.9.19"
   }
  },
  "nbformat": 4,
diff --git a/notebooks/pictures/PP_pyrolysis_inventory.png b/notebooks/pictures/PP_pyrolysis_inventory.png
new file mode 100644
index 0000000..8602f07
Binary files /dev/null and b/notebooks/pictures/PP_pyrolysis_inventory.png differ
diff --git a/notebooks/pictures/PP_pyrolysis_system_boundaries.png b/notebooks/pictures/PP_pyrolysis_system_boundaries.png
new file mode 100644
index 0000000..f95721c
Binary files /dev/null and b/notebooks/pictures/PP_pyrolysis_system_boundaries.png differ
diff --git a/notebooks/pictures/PP_regional_choice.png b/notebooks/pictures/PP_regional_choice.png
new file mode 100644
index 0000000..189d07e
Binary files /dev/null and b/notebooks/pictures/PP_regional_choice.png differ
diff --git a/notebooks/pictures/PP_ripple_effect.png b/notebooks/pictures/PP_ripple_effect.png
new file mode 100644
index 0000000..12a809f
Binary files /dev/null and b/notebooks/pictures/PP_ripple_effect.png differ
diff --git a/notebooks/plastic_showcase.ipynb b/notebooks/plastic_showcase.ipynb
new file mode 100644
index 0000000..fc9bf15
--- /dev/null
+++ b/notebooks/plastic_showcase.ipynb
@@ -0,0 +1,2094 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "512986c4-dc9a-4758-bbfb-130278bd8c03",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-30T11:06:45.551434Z",
+     "iopub.status.busy": "2024-03-30T11:06:45.551434Z",
+     "iopub.status.idle": "2024-03-30T11:06:45.570370Z",
+     "shell.execute_reply": "2024-03-30T11:06:45.568377Z",
+     "shell.execute_reply.started": "2024-03-30T11:06:45.551434Z"
+    }
+   },
+   "source": [
+    "## Plastic Waste Pyrolysis Case Study 🚮♻️\n",
+    "\n",
+    "This notebook explores the capabilities of PULPO in the context of (simplified) plastic waste pyrolysis. This case study focuses on the conversion of plastic waste into valuable products through pyrolysis technology.\n",
+    "\n",
+    "Updated by Fabian Lechtenberg, 30.03.2024\n",
+    "\n",
+    "The key functionalities demonstrated include:\n",
+    "\n",
+    "- Technology Choice 🛠️\n",
+    "- Foreground vs. Background 🎭🌐\n",
+    "- Regional Choice 🌍\n",
+    "- Alternative Indicators 🔄\n",
+    "- Technosphere Capacity Constraint ⛓️\n",
+    "- Biosphere Capacity Constraint 🌱⛔\n",
+    "- Indicator Constraints 📏\n",
+    "- Supply vs. Demand 🔄💼"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "93c8779d-6182-45c6-9cf9-608df6f468ec",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-31T07:48:33.597797Z",
+     "iopub.status.busy": "2024-03-31T07:48:33.597797Z",
+     "iopub.status.idle": "2024-03-31T07:48:33.611750Z",
+     "shell.execute_reply": "2024-03-31T07:48:33.610753Z",
+     "shell.execute_reply.started": "2024-03-31T07:48:33.597797Z"
+    }
+   },
+   "source": [
+    "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">🚨 Disclaimer: Implementation of Inventories</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0bf61e02-6982-480c-89fd-7618259565c5",
+   "metadata": {},
+   "source": [
+    "In typical LCA applications, especially for attributional / comparative studies, inventories are pre-established or pre-build by the practitioner within LCA databases. The manual steps shown here to create or adjust inventories for the plastic waste pyrolysis case study are illustrative, aimed at demonstrating process customization for this specific context."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "a56ba1a4-e5ab-4d56-8de6-eb449c917b28",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:20.212772Z",
+     "iopub.status.busy": "2024-06-30T13:57:20.211775Z",
+     "iopub.status.idle": "2024-06-30T13:57:23.315910Z",
+     "shell.execute_reply": "2024-06-30T13:57:23.314914Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:20.212772Z"
+    }
+   },
+   "source": [
+    "import brightway2 as bw"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0000ea69-0377-4252-8aad-5abe6a57c12a",
+   "metadata": {},
+   "source": [
+    "**Setup Brightway2 Project**\n",
+    "\n",
+    "Initializes the Brightway2 project named \"PULPO Final\". It checks if the Ecoinvent database \"cutoff38\" is available.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "64738a38-ec25-4922-857b-238c23ea8c7c",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:23.317904Z",
+     "iopub.status.busy": "2024-06-30T13:57:23.316907Z",
+     "iopub.status.idle": "2024-06-30T13:57:23.379698Z",
+     "shell.execute_reply": "2024-06-30T13:57:23.378700Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:23.317904Z"
+    }
+   },
+   "source": [
+    "# Setup Brightway2 project\n",
+    "bw.projects.set_current(\"pulpo\")\n",
+    "if \"cutoff38\" not in bw.databases:\n",
+    "    print(\"Ecoinvent database not found. Please import it before running this script.\")\n",
+    "else:\n",
+    "    db = bw.Database(\"cutoff38\")\n",
+    "    biosphere = bw.Database(\"biosphere3\")"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "79f350b9-d397-411b-a3fd-c6c1e6c4fb88",
+   "metadata": {},
+   "source": [
+    "**System Inventory Overview**\n",
+    "\n",
+    "The diagram illustrates the inventory flows for the polypropylene (PP) pyrolysis process. Inputs such as electricity, deionized water, and chromium steel are quantified on the left, feeding into the central PP pyrolysis activity. The \"PP waste collected\" is considered a burden-free input, serving as a placeholder for later implementing technical constraints. Outputs of the process include pyrolysis oil and emissions of CO2 and H2O. 🔄🚮🛢️➡️🌎"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6c6611a6-59e9-4cb8-b3f6-89ff1397aa64",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-30T17:30:48.187968Z",
+     "iopub.status.busy": "2024-03-30T17:30:48.186973Z",
+     "iopub.status.idle": "2024-03-30T17:30:48.195942Z",
+     "shell.execute_reply": "2024-03-30T17:30:48.194945Z",
+     "shell.execute_reply.started": "2024-03-30T17:30:48.187968Z"
+    }
+   },
+   "source": [
+    "<div style=\"text-align: center;\">\n",
+    "    <img src=\"pictures/PP_pyrolysis_inventory.png\" alt=\"Image Alt Text\" style=\"width: 50%;\" />\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "76aaf86e-9c72-486b-96d6-e5755886a499",
+   "metadata": {},
+   "source": [
+    "**Create or Check for Waste Polypropylene Process**\n",
+    "\n",
+    "Retrieve or create a new activity for the collection of PP waste."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "8a9d4e28-3b6a-4ebb-ab00-53fd4d4b9214",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:23.383684Z",
+     "iopub.status.busy": "2024-06-30T13:57:23.383684Z",
+     "iopub.status.idle": "2024-06-30T13:57:23.413663Z",
+     "shell.execute_reply": "2024-06-30T13:57:23.410670Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:23.383684Z"
+    }
+   },
+   "source": [
+    "# Check if the empty PP waste process already exists\n",
+    "try:\n",
+    "    pp_waste_process = db.get(\"pp_waste\")\n",
+    "    print(\"PP waste process already exists, skipping the addition.\")\n",
+    "except:\n",
+    "    pp_waste_process = db.new_activity(\n",
+    "        code=\"pp_waste\",\n",
+    "        name=\"PP Waste collected\",\n",
+    "        unit=\"kilogram\",\n",
+    "        location=\"Europe without Switzerland\",\n",
+    "    )\n",
+    "    pp_waste_process[\"reference product\"] = \"PP waste\"\n",
+    "    pp_waste_process.save()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "82a9e059-cc56-4eb5-93b1-560cb7b26101",
+   "metadata": {},
+   "source": [
+    "**Create or Check for PP Pyrolysis Process**\n",
+    "\n",
+    "Retrieve or create a new activity for the PP pyrolysis process, including setting up various technosphere and biosphere exchanges."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "263125d3-69d4-4250-9895-74d0603aab22",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:23.415653Z",
+     "iopub.status.busy": "2024-06-30T13:57:23.414657Z",
+     "iopub.status.idle": "2024-06-30T13:57:23.442563Z",
+     "shell.execute_reply": "2024-06-30T13:57:23.441568Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:23.415653Z"
+    }
+   },
+   "source": [
+    "# Check if the PP pyrolysis process already exists\n",
+    "try:\n",
+    "    pp_pyrolysis_process = db.get(\"pp_pyrolysis\")\n",
+    "    print(\"PP pyrolysis process already exists, skipping the addition.\")\n",
+    "except:\n",
+    "    pp_pyrolysis_process = db.new_activity(\n",
+    "        code=\"pp_pyrolysis\",\n",
+    "        name=\"PP pyrolysis process\",\n",
+    "        unit=\"kilogram\",\n",
+    "        location=\"Europe without Switzerland\",\n",
+    "    )\n",
+    "    pp_pyrolysis_process[\"reference product\"] = \"pyrolysis oil\"\n",
+    "\n",
+    "    # Add technosphere exchanges\n",
+    "    # PP waste\n",
+    "    pp = db.search(\"PP Waste collected\")[0]\n",
+    "    pp_pyrolysis_process.new_exchange(input=pp.key, amount=(26.6)/23.9, type='technosphere').save()\n",
+    "    # Add electricity input\n",
+    "    electricities = [act for act in db if \"market group for electricity, high voltage\" in act['name'] and act['location'] == \"Europe without Switzerland\"]\n",
+    "    electricity = electricities[0] if electricities else None\n",
+    "    if electricity:\n",
+    "        pp_pyrolysis_process.new_exchange(input=electricity.key, amount=(7.881+7.368e-2)/23.9, type='technosphere').save()\n",
+    "    # Add water input\n",
+    "    water = db.search(\"market for water, deionised\")[0]\n",
+    "    pp_pyrolysis_process.new_exchange(input=water.key, amount=(1e-4)/23.9, type='technosphere').save()  \n",
+    "    # Add equipment input\n",
+    "    steel = db.search(\"market for steel, chromium steel 18/8\")[0]\n",
+    "    pp_pyrolysis_process.new_exchange(input=steel.key, amount=(1.075e-4)/23.9, type='technosphere').save()\n",
+    "    # Add biosphere exchanges\n",
+    "    co2 = biosphere.search(\"carbon dioxide\")[0]\n",
+    "    pp_pyrolysis_process.new_exchange(input=co2.key, amount=(5.278)/23.9, type='biosphere').save()\n",
+    "    h2o = biosphere.search(\"water, in air\")[0]\n",
+    "    pp_pyrolysis_process.new_exchange(input=h2o.key, amount=(3.129)/23.9, type='biosphere').save()\n",
+    "\n",
+    "    # Save the activity\n",
+    "    pp_pyrolysis_process.save()\n",
+    "    print(\"PP pyrolysis process and its exchanges have been successfully added.\")"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3a23a090-54e0-4093-922d-3bf57bd891d7",
+   "metadata": {},
+   "source": [
+    "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Demonstration 🚀</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a34e0fdc-4c51-49a8-a711-f425fd87e0b8",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-30T11:12:35.355473Z",
+     "iopub.status.busy": "2024-03-30T11:12:35.355473Z",
+     "iopub.status.idle": "2024-03-30T11:12:35.376455Z",
+     "shell.execute_reply": "2024-03-30T11:12:35.375459Z",
+     "shell.execute_reply.started": "2024-03-30T11:12:35.355473Z"
+    }
+   },
+   "source": [
+    "With the inventories now defined, we're stepping into the practical demonstration of our plastic waste pyrolysis case study.\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a50bdeff-8d89-493c-afe2-414babf31166",
+   "metadata": {},
+   "source": [
+    "In these code snippets, we're setting up the environment and preparing for an analysis with the Pulpo framework:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "33cda03a-3488-4447-8609-f3c3c3bc8cec",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:23.446551Z",
+     "iopub.status.busy": "2024-06-30T13:57:23.445554Z",
+     "iopub.status.idle": "2024-06-30T13:57:23.869228Z",
+     "shell.execute_reply": "2024-06-30T13:57:23.868232Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:23.446551Z"
+    }
+   },
+   "source": [
+    "import os\n",
+    "import sys\n",
+    "sys.path.append('../') ### not necessary if pulpo is pip installed!\n",
+    "from pulpo import pulpo\n",
+    "\n",
+    "import pandas as pd\n",
+    "pd.set_option('display.max_colwidth', None)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a1910169-9386-4067-ab86-407610e2b4eb",
+   "metadata": {},
+   "source": [
+    "This segment of the code is focused on defining key parameters and paths for the analysis:\n",
+    "\n",
+    "- **Project and Database**: Specifies the Brightway2 project (`\"pulpo\"`) and database (`\"cutoff38\"`) to be used for the life cycle assessment.\n",
+    "- **Methods**: Sets the impact assessment method to IPCC 2013 GWP 100a, a widely recognized standard for evaluating global warming potential.\n",
+    "- **Directory Paths**: Configures the working directory (`directory`) relative to the notebook's location for storing and accessing data files.\n",
+    "- **GAMS Path**: Establishes the file path to the GAMS executable (`GAMS_PATH`), integrating the GAMS optimization software with our Python environment for performing complex optimization tasks.\n",
+    "\n",
+    "<span style=\"color:red\">**Note:**</span> Instead of GAMS, HiGHS, an open-source linear and nonlinear solver, can also be used for optimization tasks."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "d1da19fa-555d-4e0b-bffc-b3e6dd931e23",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:23.870225Z",
+     "iopub.status.busy": "2024-06-30T13:57:23.870225Z",
+     "iopub.status.idle": "2024-06-30T13:57:23.887168Z",
+     "shell.execute_reply": "2024-06-30T13:57:23.884185Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:23.870225Z"
+    }
+   },
+   "source": [
+    "project = \"pulpo\"\n",
+    "database = \"cutoff38\"\n",
+    "methods = \"('IPCC 2013', 'climate change', 'GWP 100a')\"\n",
+    "\n",
+    "# Substitute with your working directory of choice\n",
+    "notebook_dir = os.path.dirname(os.getcwd())\n",
+    "directory = os.path.join(notebook_dir, 'data')\n",
+    "\n",
+    "# Substitute with your GAMS path\n",
+    "GAMS_PATH = \"C:/GAMS/37/gams.exe\""
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ff9723e8-002a-4893-8738-604afd7c5e65",
+   "metadata": {},
+   "source": [
+    "Initialize a Pulpo object by passing the key parameters and paths to the PulpoOptimizer class:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "cafc2ad9-575e-41a4-ba70-6af9660adb02",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:23.890161Z",
+     "iopub.status.busy": "2024-06-30T13:57:23.889162Z",
+     "iopub.status.idle": "2024-06-30T13:57:23.901122Z",
+     "shell.execute_reply": "2024-06-30T13:57:23.900125Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:23.890161Z"
+    }
+   },
+   "source": [
+    "pulpo_worker = pulpo.PulpoOptimizer(project, database, methods, directory)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d1df751a-dfff-48d7-899b-4d7e8f0ca594",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-31T07:47:46.947508Z",
+     "iopub.status.busy": "2024-03-31T07:47:46.947508Z",
+     "iopub.status.idle": "2024-03-31T07:47:46.957474Z",
+     "shell.execute_reply": "2024-03-31T07:47:46.956477Z",
+     "shell.execute_reply.started": "2024-03-31T07:47:46.947508Z"
+    }
+   },
+   "source": [
+    "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Technology choice 🛠️ and fore- vs. background optimization 🎭🌐</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3bc58aef-6c20-4219-ad0d-ceb0afd17f79",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-30T18:00:09.433988Z",
+     "iopub.status.busy": "2024-03-30T18:00:09.432991Z",
+     "iopub.status.idle": "2024-03-30T18:00:09.453921Z",
+     "shell.execute_reply": "2024-03-30T18:00:09.452925Z",
+     "shell.execute_reply.started": "2024-03-30T18:00:09.433988Z"
+    }
+   },
+   "source": [
+    "In the forthcoming segment, we delve into a comparative demonstration focused on alternative technologies:\n",
+    "\n",
+    "- **Pyrolysis Oil** 🚮⛽\n",
+    "- **Fossil Diesel** 🦕⛽\n",
+    "- **Biodiesel** 🌱⛽\n",
+    "\n",
+    "We'll also examine two contrasting optimization strategies:\n",
+    "\n",
+    "1. **Decoupled (Offline) Optimization** Assessing the market in a vacuum, disconnected from wider technosphere / economy dynamics.\n",
+    "2. **Integrated (Simultaneous) Optimization** Conducting a holistic technosphere optimization, considering the ripple effects and feedback from market changes."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c2fb4314-7eee-4646-9286-c03c9a32aba7",
+   "metadata": {},
+   "source": [
+    "**Retrieving Necessary Activities for Assessment**\n",
+    "\n",
+    "Below, we fetch the essential activities for our analysis. The system under review is illustrated in the figure that follows. 📉👇"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "aa732440-bd14-4356-aa8a-8064c195bd28",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-30T18:04:21.157485Z",
+     "iopub.status.busy": "2024-03-30T18:04:21.157485Z",
+     "iopub.status.idle": "2024-03-30T18:04:21.175468Z",
+     "shell.execute_reply": "2024-03-30T18:04:21.175468Z",
+     "shell.execute_reply.started": "2024-03-30T18:04:21.157485Z"
+    }
+   },
+   "source": [
+    "<div style=\"text-align: center;\">\n",
+    "    <img src=\"pictures/PP_pyrolysis_system_boundaries.png\" alt=\"Image Alt Text\" style=\"width: 50%;\" />\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f9f7d950-ee6d-4bc7-a1b6-940ec1389c5d",
+   "metadata": {},
+   "source": [
+    "**Diesel Market**\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "129a190a-0397-4aaa-baa0-9154a050bc14",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:23.903115Z",
+     "iopub.status.busy": "2024-06-30T13:57:23.902119Z",
+     "iopub.status.idle": "2024-06-30T13:57:23.917069Z",
+     "shell.execute_reply": "2024-06-30T13:57:23.916072Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:23.902119Z"
+    }
+   },
+   "source": [
+    "activities = [\"market for diesel\"]\n",
+    "reference_products = [\"diesel\"]\n",
+    "locations = [\"Europe without Switzerland\"]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "a349fae8-ef67-4135-b7a9-a5ac9db84bc2",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:23.919062Z",
+     "iopub.status.busy": "2024-06-30T13:57:23.919062Z",
+     "iopub.status.idle": "2024-06-30T13:57:27.053714Z",
+     "shell.execute_reply": "2024-06-30T13:57:27.053714Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:23.919062Z"
+    }
+   },
+   "source": [
+    "diesel_market = pulpo_worker.retrieve_activities(activities=activities, reference_products=reference_products, locations=locations)[0]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "857c4d88-bcc6-41f3-b91e-2db2eb16724d",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:27.055744Z",
+     "iopub.status.busy": "2024-06-30T13:57:27.054709Z",
+     "iopub.status.idle": "2024-06-30T13:57:28.037360Z",
+     "shell.execute_reply": "2024-06-30T13:57:28.036362Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:27.055744Z"
+    }
+   },
+   "source": [
+    "from helper import copy_activity\n",
+    "diesel_market_foreground = copy_activity(diesel_market)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3effbf0d-6778-4bdb-83e8-9342f6721b3b",
+   "metadata": {},
+   "source": [
+    "**Diesel Production**"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "645d9806-e6eb-46e3-93af-eacf325757cb",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:28.038355Z",
+     "iopub.status.busy": "2024-06-30T13:57:28.038355Z",
+     "iopub.status.idle": "2024-06-30T13:57:28.052308Z",
+     "shell.execute_reply": "2024-06-30T13:57:28.052308Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:28.038355Z"
+    }
+   },
+   "source": [
+    "activities = [\"diesel production, petroleum refinery operation\"]\n",
+    "reference_products = [\"diesel\"]\n",
+    "locations = [\"Europe without Switzerland\"]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "9b369b84-b663-4cf4-8bd7-321e6e02de88",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:28.054303Z",
+     "iopub.status.busy": "2024-06-30T13:57:28.054303Z",
+     "iopub.status.idle": "2024-06-30T13:57:29.038745Z",
+     "shell.execute_reply": "2024-06-30T13:57:29.036752Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:28.054303Z"
+    }
+   },
+   "source": [
+    "diesel_fossil = pulpo_worker.retrieve_activities(activities=activities, reference_products=reference_products, locations=locations)[0]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "e58777d6-8be6-49e0-9db0-4a7314ccf84d",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:29.039741Z",
+     "iopub.status.busy": "2024-06-30T13:57:29.039741Z",
+     "iopub.status.idle": "2024-06-30T13:57:30.099482Z",
+     "shell.execute_reply": "2024-06-30T13:57:30.099482Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:29.039741Z"
+    }
+   },
+   "source": [
+    "diesel_fossil_foreground = copy_activity(diesel_fossil)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "fd68811f-cd0d-4b91-9fc2-1275c667e489",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:30.101475Z",
+     "iopub.status.busy": "2024-06-30T13:57:30.100479Z",
+     "iopub.status.idle": "2024-06-30T13:57:30.115429Z",
+     "shell.execute_reply": "2024-06-30T13:57:30.115429Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:30.100479Z"
+    }
+   },
+   "source": [
+    "activities = [\"soybean oil refinery operation\"]\n",
+    "reference_products = [\"soybean oil, refined\"]\n",
+    "locations = [\"RoW\"]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e7b1e593-30a9-4996-b1ba-a79fcc573439",
+   "metadata": {},
+   "source": [
+    "**Biodiesel Production**"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "e613439b-9a42-4b32-8e23-c92113c7bacc",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:30.117422Z",
+     "iopub.status.busy": "2024-06-30T13:57:30.116426Z",
+     "iopub.status.idle": "2024-06-30T13:57:31.197550Z",
+     "shell.execute_reply": "2024-06-30T13:57:31.197550Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:30.117422Z"
+    }
+   },
+   "source": [
+    "diesel_bio = pulpo_worker.retrieve_activities(activities=activities, reference_products=reference_products, locations=locations)[0]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "208dbb7f-00cc-4b3d-bb2e-a493ab95dde2",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:31.198547Z",
+     "iopub.status.busy": "2024-06-30T13:57:31.198547Z",
+     "iopub.status.idle": "2024-06-30T13:57:32.185718Z",
+     "shell.execute_reply": "2024-06-30T13:57:32.185718Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:31.198547Z"
+    }
+   },
+   "source": [
+    "diesel_bio_foreground = copy_activity(diesel_bio)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "2a058f29-e599-4d8e-aa7b-2349e512727c",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:32.186714Z",
+     "iopub.status.busy": "2024-06-30T13:57:32.186714Z",
+     "iopub.status.idle": "2024-06-30T13:57:32.202663Z",
+     "shell.execute_reply": "2024-06-30T13:57:32.201666Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:32.186714Z"
+    }
+   },
+   "source": [
+    "activities = [\"PP pyrolysis process\"]\n",
+    "reference_products = [\"pyrolysis oil\"]\n",
+    "locations = [\"Europe without Switzerland\"]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f40b25a8-c16c-40b8-bea9-a91a634fb567",
+   "metadata": {},
+   "source": [
+    "**Pyrolysis Oil Production**"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "5d02aff8-9914-4f2f-bf00-29f1304df2d7",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:32.203658Z",
+     "iopub.status.busy": "2024-06-30T13:57:32.202663Z",
+     "iopub.status.idle": "2024-06-30T13:57:33.459295Z",
+     "shell.execute_reply": "2024-06-30T13:57:33.459295Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:32.203658Z"
+    }
+   },
+   "source": [
+    "diesel_pp = pulpo_worker.retrieve_activities(activities=activities, reference_products=reference_products, locations=locations)[0]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "546e2288-0207-443e-95e0-8bebff83a506",
+   "metadata": {},
+   "source": [
+    "Once all inventories have been fetched, load the LCI data to the Pulpo worker:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "0e80cbc6-62f2-427a-a900-1e39b1dfe310",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:33.460292Z",
+     "iopub.status.busy": "2024-06-30T13:57:33.460292Z",
+     "iopub.status.idle": "2024-06-30T13:57:34.857163Z",
+     "shell.execute_reply": "2024-06-30T13:57:34.857163Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:33.460292Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.get_lci_data()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6b5da7e6-c017-40ab-9e6b-4a5370a61c8d",
+   "metadata": {},
+   "source": [
+    "**No optimization**"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ddf19cdc-ef3b-44b7-8aa8-81f312f5892e",
+   "metadata": {},
+   "source": [
+    "In this segment, we're setting up a comparative LCA for three different types of diesel without optimization.\n",
+    "\n",
+    "For each fuel type, we are evaluating the environmental impact associated with the production of 1 kilogram. The assessment is conducted using a suite of impact assessment methods:\n",
+    "\n",
+    "- **Global Warming Potential (GWP)**: Assessed over 100 years as per IPCC 2013.\n",
+    "- **Land Use Change GWP100**: Evaluates climate impact specifically from land use and land use change.\n",
+    "- **Freshwater Eutrophication**: Measures the potential impact of nutrient enrichment in freshwater ecosystems.\n",
+    "- **Ionising Radiation Human Health**: Estimates the exposure efficiency to ionizing radiation and its effect on human health."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "15c3c94d-fef1-43ce-ae22-48927fa2bd18",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:57:34.858161Z",
+     "iopub.status.busy": "2024-06-30T13:57:34.858161Z",
+     "iopub.status.idle": "2024-06-30T13:59:18.379992Z",
+     "shell.execute_reply": "2024-06-30T13:59:18.379667Z",
+     "shell.execute_reply.started": "2024-06-30T13:57:34.858161Z"
+    }
+   },
+   "source": [
+    "functional_units = [\n",
+    "    {diesel_bio: 1},   # 1 kg of biodiesel\n",
+    "    {diesel_pp: 1},     # 1 kg of diesel from PP\n",
+    "    {diesel_fossil: 1} # 1 kg of fossil diesel\n",
+    "]\n",
+    "\n",
+    "methods_list = [\n",
+    "    (\"IPCC 2013\", \"climate change\", \"GWP 100a\"),\n",
+    "    (\"EF v3.0\", \"climate change: land use and land use change\", \"global warming potential (GWP100)\"),\n",
+    "    (\"EF v3.0\", \"eutrophication: freshwater\", \"fraction of nutrients reaching freshwater end compartment (P)\"),\n",
+    "    (\"EF v3.0\", \"ionising radiation: human health\", \"human exposure efficiency relative to u235\"),\n",
+    "]\n",
+    "\n",
+    "my_calculation_setup = {'inv': functional_units, 'ia': methods_list}\n",
+    "bw.calculation_setups['diesel_comparison'] = my_calculation_setup\n",
+    "mlca = bw.MultiLCA('diesel_comparison')"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "0fe58313-6136-453e-aacd-3c4295ed05ad",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:59:18.380211Z",
+     "iopub.status.busy": "2024-06-30T13:59:18.380211Z",
+     "iopub.status.idle": "2024-06-30T13:59:18.660529Z",
+     "shell.execute_reply": "2024-06-30T13:59:18.659819Z",
+     "shell.execute_reply.started": "2024-06-30T13:59:18.380211Z"
+    }
+   },
+   "source": [
+    "import pandas as pd\n",
+    "from IPython.display import display\n",
+    "\n",
+    "# Define the names of the activities and methods based on your setup\n",
+    "activity_names = ['Biodiesel', 'Diesel from PP', 'Fossil Diesel']\n",
+    "\n",
+    "# Convert the mlca.results array to a DataFrame\n",
+    "results_df = pd.DataFrame(\n",
+    "    mlca.results, \n",
+    "    index=activity_names, \n",
+    "    columns=methods_list\n",
+    ")\n",
+    "\n",
+    "# Define a custom function to apply colors based on ranking within each column\n",
+    "def highlight_by_column_rank(s):\n",
+    "    is_max = s == s.max()\n",
+    "    is_min = s == s.min()\n",
+    "    # Assign colors: red for max, green for min, orange for others\n",
+    "    return ['background-color: red' if v else 'background-color: green' if m else 'background-color: orange' for v, m in zip(is_max, is_min)]\n",
+    "\n",
+    "# Apply the styling for each column\n",
+    "styled_df = results_df.style.format(\"{:.2e}\")\n",
+    "styled_df = styled_df.apply(highlight_by_column_rank, axis=0)\n",
+    "\n",
+    "# Display the styled DataFrame\n",
+    "display(styled_df)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f7b9dc7e-5eba-4429-9afe-fb2376b2a93b",
+   "metadata": {},
+   "source": [
+    "The table showcases the comparative LCA results across different diesel options, color-coded to indicate the lowest (green), middle (orange), and highest (red) environmental impacts in each category. Biodiesel shows a higher GWP 100a, while Diesel from PP demonstrates potential as the least impactful in the GWP category. As we move to optimize for GWP, the preliminary data suggests that pyrolysis diesel might minimize the impact. However, the extent of this benefit, especially when considering technical constraints and the distinction between decoupled and integrated optimization results, remains to be explored in the following sections."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8320b759-0228-4016-9ec6-d84d63fc53fe",
+   "metadata": {},
+   "source": [
+    "**Foreground Assessment** 🎭"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4bc49008-2601-49b8-a861-e8663b88bd2f",
+   "metadata": {},
+   "source": [
+    "Specify the demand of the assessment as 1 kg of diesel from the *foreground* diesel market:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "013db433-bf2c-44d1-a69c-1757f5df3c1c",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:59:18.661657Z",
+     "iopub.status.busy": "2024-06-30T13:59:18.660529Z",
+     "iopub.status.idle": "2024-06-30T13:59:18.664090Z",
+     "shell.execute_reply": "2024-06-30T13:59:18.663948Z",
+     "shell.execute_reply.started": "2024-06-30T13:59:18.661657Z"
+    }
+   },
+   "source": [
+    "demand = {diesel_market_foreground: 1}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9392b436-a02c-43ba-8f49-62be3d99fb12",
+   "metadata": {},
+   "source": [
+    "Specify the choices of the assessment as the three diesel production alternatives:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "fb7585bc-6084-4e69-85f4-6118bdb448c6",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:59:18.664090Z",
+     "iopub.status.busy": "2024-06-30T13:59:18.664090Z",
+     "iopub.status.idle": "2024-06-30T13:59:18.679715Z",
+     "shell.execute_reply": "2024-06-30T13:59:18.679715Z",
+     "shell.execute_reply.started": "2024-06-30T13:59:18.664090Z"
+    }
+   },
+   "source": [
+    "choices  = {'diesel': {diesel_bio_foreground: 10,\n",
+    "                       diesel_fossil_foreground: 10,\n",
+    "                       diesel_pp: 10}}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2fdc9222-b711-4640-868b-64a6a8265035",
+   "metadata": {},
+   "source": [
+    "Instantiate and solve the problem:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "b75c116a-7645-42f3-9233-7eea0e8ef3a9",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:59:18.679715Z",
+     "iopub.status.busy": "2024-06-30T13:59:18.679715Z",
+     "iopub.status.idle": "2024-06-30T13:59:40.802055Z",
+     "shell.execute_reply": "2024-06-30T13:59:40.802055Z",
+     "shell.execute_reply.started": "2024-06-30T13:59:18.679715Z"
+    }
+   },
+   "source": [
+    "instance = pulpo_worker.instantiate(choices=choices, demand=demand)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "994b077f-3bb8-4b51-a2ab-17840cbffca0",
+   "metadata": {},
+   "source": [
+    "Summarize the results:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "4de2467c-f0b7-4d10-8b19-f0f97c145b49",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:59:40.802055Z",
+     "iopub.status.busy": "2024-06-30T13:59:40.802055Z",
+     "iopub.status.idle": "2024-06-30T13:59:40.852199Z",
+     "shell.execute_reply": "2024-06-30T13:59:40.852199Z",
+     "shell.execute_reply.started": "2024-06-30T13:59:40.802055Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a3e8379b-c0d5-442c-a1c2-db3435935f8f",
+   "metadata": {},
+   "source": [
+    "**Background Assessment** 🌐"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7acdbce1-26bc-4819-a5ca-5c3cf12a2819",
+   "metadata": {},
+   "source": [
+    "Specify the demand of the assessment as 1 kg of diesel from the *original* diesel market which is connected to all other processes of the LCI database:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "ef7be8cd-5776-4e7d-ace2-bdf82b2a44bb",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:59:40.853192Z",
+     "iopub.status.busy": "2024-06-30T13:59:40.853192Z",
+     "iopub.status.idle": "2024-06-30T13:59:40.867901Z",
+     "shell.execute_reply": "2024-06-30T13:59:40.867901Z",
+     "shell.execute_reply.started": "2024-06-30T13:59:40.853192Z"
+    }
+   },
+   "source": [
+    "demand = {diesel_market: 1}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "53779561-671c-4e70-b303-f844d24e83bc",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:59:40.868901Z",
+     "iopub.status.busy": "2024-06-30T13:59:40.868901Z",
+     "iopub.status.idle": "2024-06-30T13:59:40.881157Z",
+     "shell.execute_reply": "2024-06-30T13:59:40.881157Z",
+     "shell.execute_reply.started": "2024-06-30T13:59:40.868901Z"
+    }
+   },
+   "source": [
+    "choices  = {'diesel': {diesel_bio: 10,\n",
+    "                       diesel_fossil: 10,\n",
+    "                       diesel_pp: 10}}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "632b04a6-1a9f-4499-881c-e3bbc4140183",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T13:59:40.881157Z",
+     "iopub.status.busy": "2024-06-30T13:59:40.881157Z",
+     "iopub.status.idle": "2024-06-30T14:00:04.662000Z",
+     "shell.execute_reply": "2024-06-30T14:00:04.661242Z",
+     "shell.execute_reply.started": "2024-06-30T13:59:40.881157Z"
+    }
+   },
+   "source": [
+    "instance = pulpo_worker.instantiate(choices=choices, demand=demand)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "b99cb105-6d3f-4f19-8331-7e256e7c342e",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:04.662000Z",
+     "iopub.status.busy": "2024-06-30T14:00:04.662000Z",
+     "iopub.status.idle": "2024-06-30T14:00:04.711285Z",
+     "shell.execute_reply": "2024-06-30T14:00:04.711285Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:04.662000Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e0e5e675-8283-4ed0-8b10-0a2bddca748f",
+   "metadata": {},
+   "source": [
+    "The optimization analysis reaffirms our expectations: pyrolysis oil emerges as the preferable choice, with its corresponding alpha (α) parameter set to \"1\", indicating full selection in the optimized mix.\n",
+    "\n",
+    "When contrasting the \"foreground\" (market-specific) with the \"background\" (economy-wide) optimization scenarios, we observe a marginal disparity: the foreground scenario yields a GWP of **0.373366 kg CO<sub>2</sub>e**, while the background scenario presents a marginally lower GWP of **0.373292 kg CO<sub>2</sub>e**. This small difference is attributable to the ripple effects of the diesel market's lower impacts cascading upstream, thereby slightly reducing the overall environmental burden.\n",
+    "\n",
+    "Notably, the scaling vector for PP pyrolysis, s<sub>diesel_PP</sub>, which represents the level of utilization of pyrolysis oil, remains consistent at \"1\" in the foreground scenario—signifying exclusive use within the market. In contrast, the background scenario, which integrates the technology economy-wide, reflects a subtle uptick in utilization to s<sub>diesel_PP</sub> = 1.000713. This represents a 0.07% increment, hinting at a small but noteworthy expansion in demand when the technology is adopted across the broader economy. \n",
+    "\n",
+    "The following illustration shows two example paths that are affected by the change in the market composition, which are omitted in the foreground scenario 📉👇 "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c307d5d5-727f-4e16-9556-63099bbc8b07",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-31T13:54:22.768890Z",
+     "iopub.status.busy": "2024-03-31T13:54:22.768890Z",
+     "iopub.status.idle": "2024-03-31T13:54:22.777859Z",
+     "shell.execute_reply": "2024-03-31T13:54:22.776863Z",
+     "shell.execute_reply.started": "2024-03-31T13:54:22.768890Z"
+    }
+   },
+   "source": [
+    "<div style=\"text-align: center;\">\n",
+    "    <img src=\"pictures/PP_ripple_effect.png\" alt=\"Image Alt Text\" style=\"width: 50%;\" />\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3f18cdaa-f2e6-4f46-b5b5-97b18f715ce8",
+   "metadata": {},
+   "source": [
+    "The more direct route originates from the diesel market and proceeds to the freight train transport activity. This activity subsequently feeds into the transport market, which in turn serves as an input back to the diesel market, thus establishing a feedback loop. Such a loop allows any changes in the environmental impact of the diesel market to feed back to the diesel market, influencing its own impact."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4a5ca882-432f-4100-8738-624284d59573",
+   "metadata": {},
+   "source": [
+    "A secondary feedback loop is illustrated as follows: it starts from the \"market for transport, freight train\" and moves to the market for lignite. This, in sequence, contributes to the production of electricity from lignite, which is part of the German electricity mix. The German mix then plays a role in the European electricity mix, which, in turn, is a key input for the plastic pyrolysis process. This interconnected loop exemplifies how changes within one segment can propagate through the entire energy supply chain, ultimately affecting the pyrolysis process and the diesel market itself."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ceb07d33-4895-4b6c-b48c-f7694f61a9f0",
+   "metadata": {},
+   "source": [
+    "See [this showcase](electricity_showcase.ipynb) for an example with a much larger ripple effect! "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f9fc48c0-725f-4533-8730-081a77f29341",
+   "metadata": {},
+   "source": [
+    "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Regional choice 🌍</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "add6e5c7-6355-4ac4-92d1-72cb0fbee021",
+   "metadata": {},
+   "source": [
+    "Our previous assessment concentrated on evaluating **technology choices**. Now, we shift our focus to **regional selections**, simultaneously considering various European sourcing options for electricity. By specifying the PP pyrolysis process to accept electricity from the broader EUR electricity market group, we allow for the possibility of drawing power from any eligible national electricity markets within Europe.\n",
+    "\n",
+    "From a mathematical standpoint, the distinction between technological and regional choices is subtle, as both essentially involve selecting from explicitly modeled **processes** within the Life Cycle Inventory (LCI) database. Whether differentiating by technology or by geographic origin, the underlying computational approach remains consistent."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "680abe18-0b81-4752-bc5f-271888a05ac9",
+   "metadata": {},
+   "source": [
+    "In our expanded analysis, we introduce \"electricity\" as a new choice alongside \"diesel.\" Now, the electricity used in the PP pyrolysis process can come from any national market within Europe, not just the European mix. This addition opens up 40 different alternatives, complicating the decision-making process as each choice has implications for multiple impact categories and can directly or indirectly affect the other technologies. Determining the optimal choice in this intricate network of possibilities presents a more complex challenge  📉👇 "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "82cf253c-6bad-457f-9ada-de8e6611131c",
+   "metadata": {},
+   "source": [
+    "<div style=\"text-align: center;\">\n",
+    "    <img src=\"pictures/PP_regional_choice.png\" alt=\"Image Alt Text\" style=\"width: 50%;\" />\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "cf3ab0a2-d544-4280-b9c7-57ccd7610454",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:04.711285Z",
+     "iopub.status.busy": "2024-06-30T14:00:04.711285Z",
+     "iopub.status.idle": "2024-06-30T14:00:04.725614Z",
+     "shell.execute_reply": "2024-06-30T14:00:04.725614Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:04.711285Z"
+    }
+   },
+   "source": [
+    "country_codes = [\n",
+    "    \"FR\", \"DE\", \"GB\", \"IT\", \"ES\", \"PL\", \"UA\", \"SE\", \"NO\", \"NL\",\n",
+    "    \"FI\", \"BE\", \"AT\", \"CZ\", \"GR\", \"RO\", \"PT\", \"HU\", \"RS\", \"BG\",\n",
+    "    \"BY\", \"DK\", \"IE\", \"SK\", \"IS\", \"HR\", \"SI\", \"BA\", \"LT\", \"MK\",\n",
+    "    \"AL\", \"EE\", \"LU\", \"LV\", \"MD\", \"ME\", \"XK\", \"MT\", \"GI\"\n",
+    "]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1bc0079a-d36d-4b15-b071-4472fc211bea",
+   "metadata": {},
+   "source": [
+    "Now let's use a Pulpo function to retrieve the corresponding electricity markets. The function \"retrieve_activities\" of the worker allows us to specify the activity names, the reference product, or the locations and will return the best match:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "b6968ff5-93db-4545-85cd-38296333b59a",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:04.725614Z",
+     "iopub.status.busy": "2024-06-30T14:00:04.725614Z",
+     "iopub.status.idle": "2024-06-30T14:00:06.224371Z",
+     "shell.execute_reply": "2024-06-30T14:00:06.223909Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:04.725614Z"
+    }
+   },
+   "source": [
+    "activities = [\"market for electricity, high voltage\"]\n",
+    "reference_products = [\"electricity, high voltage\"]\n",
+    "# Get regional markets\n",
+    "electricity_markets = pulpo_worker.retrieve_activities(activities=activities, reference_products=reference_products, locations=country_codes)\n",
+    "# Get european market\n",
+    "electricity_markets.append(pulpo_worker.retrieve_activities(activities=[\"market group for electricity, high voltage\"],\n",
+    "                                                            reference_products=reference_products,\n",
+    "                                                            locations=[\"Europe without Switzerland\"])[0])\n",
+    "\n",
+    "print(f\"A total of {len(electricity_markets)} electricity markets have been retrieved.\")"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1d15eac8-1181-46a3-9eb4-da35af2e73ac",
+   "metadata": {},
+   "source": [
+    "Now that the electricity markets have been retrieved, let's specify the new choice \"electricity\":"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "a8347656-3735-4973-8819-3cafe0f77aa2",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:06.224979Z",
+     "iopub.status.busy": "2024-06-30T14:00:06.224979Z",
+     "iopub.status.idle": "2024-06-30T14:00:06.240604Z",
+     "shell.execute_reply": "2024-06-30T14:00:06.240604Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:06.224979Z"
+    }
+   },
+   "source": [
+    "choices['electricity']  = {market: 10 for market in electricity_markets}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8969b491-b69b-498a-9643-d4342cbc17b7",
+   "metadata": {},
+   "source": [
+    "With the expanded choces, proceed to re-instatiate the problem and re-solve:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "772aa8ef-e420-44d2-a704-c6fd04879d85",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:06.240604Z",
+     "iopub.status.busy": "2024-06-30T14:00:06.240604Z",
+     "iopub.status.idle": "2024-06-30T14:00:30.194845Z",
+     "shell.execute_reply": "2024-06-30T14:00:30.194474Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:06.240604Z"
+    }
+   },
+   "source": [
+    "instance = pulpo_worker.instantiate(choices=choices, demand=demand)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "ba9f21b2-0eb8-4d4f-a55f-01ae1753409d",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:30.195845Z",
+     "iopub.status.busy": "2024-06-30T14:00:30.194845Z",
+     "iopub.status.idle": "2024-06-30T14:00:30.248971Z",
+     "shell.execute_reply": "2024-06-30T14:00:30.248971Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:30.195845Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "5814cf9f-5efb-455a-9339-1e0a0081e3ba",
+   "metadata": {},
+   "source": [
+    "For the regional choice \"Albania\" (AL) has been chosen, supplying a total of **0.4565 kWh**, of which 0.3328 kWh go to the PP pyrolysis and the rest is used somewhere else in the technosphere. As a result, the optimized system can further reduce the GWP from **0.3733 kg CO2** (only technology choice) to **0.2385 kg CO2** (simultaneous technology and regional choice).  "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b2604581-1cba-49e0-b775-b07eb472a695",
+   "metadata": {},
+   "source": [
+    "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Alternative Indicators 🔄</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ae2cebc7-7266-4972-bfe3-50cef07b856f",
+   "metadata": {},
+   "source": [
+    "Judging from the \"*no optimization*\" case from before, the best choice for the \"**Freshwater Eutrophication (EF v3.0)**\" category will be **fossil diesel**. In this segment, let's assess what happens if we optimize for this category, while simultaneously allow for regional choices to be active."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "15da02d1-5add-412f-86e9-4527138ac2bc",
+   "metadata": {},
+   "source": [
+    "To that end, create a new worker with all the methods:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "508ef9e6-b974-4f0e-9623-b9aa0d45f7ae",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:30.249989Z",
+     "iopub.status.busy": "2024-06-30T14:00:30.249989Z",
+     "iopub.status.idle": "2024-06-30T14:00:30.266118Z",
+     "shell.execute_reply": "2024-06-30T14:00:30.264976Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:30.249989Z"
+    }
+   },
+   "source": [
+    "methods_list = {\n",
+    "    \"('IPCC 2013', 'climate change', 'GWP 100a')\": 0,\n",
+    "    \"('EF v3.0', 'climate change: land use and land use change', 'global warming potential (GWP100)')\": 0,\n",
+    "    \"('EF v3.0', 'eutrophication: freshwater', 'fraction of nutrients reaching freshwater end compartment (P)')\": 1,\n",
+    "    \"('EF v3.0', 'ionising radiation: human health', 'human exposure efficiency relative to u235')\": 0,\n",
+    "}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6df62c62-31ea-4840-8a16-4e7f9a7fefc4",
+   "metadata": {},
+   "source": [
+    "The provided dictionary is utilized within the optimization framework to allocate specific weights to various impact categories. Each key in this dictionary represents a distinct environmental impact category, as identified by its methodological definition (e.g., \"IPCC 2013\" for climate change or \"EF v3.0\" for specific Environmental Footprint categories). The value assigned to each key represents the weight that the optimization algorithm should give to that particular impact category in the decision-making process.\n",
+    "\n",
+    "- The weight of 0 for most categories (climate change, land use change's GWP, and ionising radiation's human health impact) indicates that these categories do not influence the optimization's outcome. However, they are evaluated (calculated) alongside the optimization and their final value can be printed and assessed.\n",
+    "- The weight of 1 assigned to \"eutrophication: freshwater\" signifies that the optimization process exclusively focuses on minimizing the impact in this category, while still calculating the impacts for the other categories alongside."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "196a5f1d-c9dd-48ab-a922-dae7345efc9b",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:30.267115Z",
+     "iopub.status.busy": "2024-06-30T14:00:30.267115Z",
+     "iopub.status.idle": "2024-06-30T14:00:31.773722Z",
+     "shell.execute_reply": "2024-06-30T14:00:31.773722Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:30.267115Z"
+    }
+   },
+   "source": [
+    "pulpo_worker = pulpo.PulpoOptimizer(project, database, methods_list, directory)\n",
+    "pulpo_worker.get_lci_data()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7152b664-4ee7-48ac-924a-7e2bef065ae2",
+   "metadata": {},
+   "source": [
+    "Let's re-instantiate and re-solve the problem (same choices, same demand):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "7ee4fec3-5ed0-477d-931e-18918f9d1add",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:31.773722Z",
+     "iopub.status.busy": "2024-06-30T14:00:31.773722Z",
+     "iopub.status.idle": "2024-06-30T14:00:52.567459Z",
+     "shell.execute_reply": "2024-06-30T14:00:52.567141Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:31.773722Z"
+    }
+   },
+   "source": [
+    "instance = pulpo_worker.instantiate(choices=choices, demand=demand)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "74346ac0-c795-4a77-a637-f40609077bb7",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:52.567651Z",
+     "iopub.status.busy": "2024-06-30T14:00:52.567651Z",
+     "iopub.status.idle": "2024-06-30T14:00:52.617320Z",
+     "shell.execute_reply": "2024-06-30T14:00:52.617320Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:52.567651Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8fef4987-d56f-4480-951c-4bd1630f6090",
+   "metadata": {},
+   "source": [
+    "#### Notable Findings:\n",
+    "Contrary to initial expectations based solely on technology choice, it has been observed that not fossil diesel but pyrolysis oil, powered with electricity from **Iceland (IS)**, significantly reduces the eutrophication impact from **2.86e-05 kg PO4e** to **2.00e-05  kg PO4e**.\n",
+    "\n",
+    "However, it's important to note that these findings do not definitively establish Iceland as the optimal location for diesel production of this nature. Instead, they prompt a deeper examination of Iceland's local conditions that contribute to this environmental advantage. Notably, Iceland's energy production is predominantly sourced from hydro and geothermal energy, with a particularly high reliance on geothermal sources. This unique energy landscape suggests that the extensive use of geothermal energy plays a critical role in positioning Iceland as a potentially favorable region for minimizing eutrophication impacts.\n",
+    "\n",
+    "Accordingly, these results open avenues for further analysis and understanding, potentially revealing broader insights into environmental optimization and regional energy strategies."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "6fdbd504-0ff7-4995-a975-d2d5194f6a84",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:52.617320Z",
+     "iopub.status.busy": "2024-06-30T14:00:52.617320Z",
+     "iopub.status.idle": "2024-06-30T14:00:54.065766Z",
+     "shell.execute_reply": "2024-06-30T14:00:54.065766Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:52.617320Z"
+    }
+   },
+   "source": [
+    "# Reset the objective function to IPCC GWP\n",
+    "methods_list = {\n",
+    "    \"('IPCC 2013', 'climate change', 'GWP 100a')\": 1,\n",
+    "    \"('EF v3.0', 'climate change: land use and land use change', 'global warming potential (GWP100)')\": 0,\n",
+    "    \"('EF v3.0', 'eutrophication: freshwater', 'fraction of nutrients reaching freshwater end compartment (P)')\": 0,\n",
+    "    \"('EF v3.0', 'ionising radiation: human health', 'human exposure efficiency relative to u235')\": 0,\n",
+    "}\n",
+    "\n",
+    "pulpo_worker = pulpo.PulpoOptimizer(project, database, methods_list, directory)\n",
+    "pulpo_worker.get_lci_data()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6669ce99-f828-4969-8032-16c26a99e877",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-31T16:36:49.210303Z",
+     "iopub.status.busy": "2024-03-31T16:36:49.209306Z",
+     "iopub.status.idle": "2024-03-31T16:36:49.230236Z",
+     "shell.execute_reply": "2024-03-31T16:36:49.229239Z",
+     "shell.execute_reply.started": "2024-03-31T16:36:49.210303Z"
+    }
+   },
+   "source": [
+    "<div style=\"text-align: center; background-color: #FFA500; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #FFFFFF;\">Additional Constraints</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c093463b-af88-4f0c-9cc8-5e0166643692",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-03-31T16:37:30.479157Z",
+     "iopub.status.busy": "2024-03-31T16:37:30.479157Z",
+     "iopub.status.idle": "2024-03-31T16:37:30.494163Z",
+     "shell.execute_reply": "2024-03-31T16:37:30.493166Z",
+     "shell.execute_reply.started": "2024-03-31T16:37:30.479157Z"
+    }
+   },
+   "source": [
+    "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Technosphere Capacity Constraints ⛓️</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "54a6cdc8-51d1-40f5-bd2f-e50b1d76713a",
+   "metadata": {},
+   "source": [
+    "In 2020, the demand for diesel products stood at **292.6 million tons** ([source](https://www.fuelseurope.eu/uploads/files/modules/documents/file/1663766146_TWxIVaKHUN4dlyHl7hNvrUpf0Jwzbbe6nI9DMDBa.pdf)), necessitating **1.113 kg of PP (polypropylene)** for the production of 1 kg of pyrolysis oil. Within the European Union, the total post-consumer plastic waste collected, both mixed and separately, amounted to **29.5 million tons** ([source](https://plasticseurope.org/knowledge-hub/plastics-the-facts-2022/)). This presents a clear mismatch between demand and available supply.\n",
+    "\n",
+    "Significantly, over **50% of plastic waste** is amassed through mixed waste collection schemes, with the separately collected fraction rarely categorized by plastic type. This makes acquiring \"pure\" PP for pyrolysis a challenging endeavor. \n",
+    "\n",
+    "However, for the purposes of our assessment, we'll operate under the optimistic assumption that the separately collected fraction of plastics includes **16.6% PP**, and that it's feasible to sort and extract this PP entirely for pyrolysis usage. Under this premise, approximately **2.4 million tons of raw PP material** could be rendered available for pyrolysis.\n",
+    "\n",
+    "We will proceed by incorporating this raw material limitation as a **technosphere constraint** in our analysis:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "94966000-fd32-43b5-b6e2-8acbe53a065e",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:54.068061Z",
+     "iopub.status.busy": "2024-06-30T14:00:54.067064Z",
+     "iopub.status.idle": "2024-06-30T14:00:54.087458Z",
+     "shell.execute_reply": "2024-06-30T14:00:54.087458Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:54.067064Z"
+    }
+   },
+   "source": [
+    "# Adjust the final demand for an economy-wide perspective\n",
+    "demand = {diesel_market: 292.6e9}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "437f9701-ffde-4a00-912d-ac6f764ca26e",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:54.087458Z",
+     "iopub.status.busy": "2024-06-30T14:00:54.087458Z",
+     "iopub.status.idle": "2024-06-30T14:00:54.101306Z",
+     "shell.execute_reply": "2024-06-30T14:00:54.101306Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:54.087458Z"
+    }
+   },
+   "source": [
+    "# Re-define the choices and set a reasonably high capacity constraint\n",
+    "# Note that we are not considering regional choices here!\n",
+    "choices  = {'diesel': {diesel_bio: 1e15,\n",
+    "                       diesel_fossil: 1e15,\n",
+    "                       diesel_pp: 1e15}} "
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "35ada40f-cf34-42ea-84bd-c2b9654da72d",
+   "metadata": {},
+   "source": [
+    "To incorporate the constraint on plastic availability, we must initially retrieve the relevant activity. Following this, we'll construct an \"upper_limit\" dictionary that assigns the previously derived value:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "69df4192-6da1-40a7-a19e-8a5f9b9718cb",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:54.101306Z",
+     "iopub.status.busy": "2024-06-30T14:00:54.101306Z",
+     "iopub.status.idle": "2024-06-30T14:00:54.783509Z",
+     "shell.execute_reply": "2024-06-30T14:00:54.783509Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:54.101306Z"
+    }
+   },
+   "source": [
+    "pp_waste = pulpo_worker.retrieve_activities(activities=['PP Waste collected'])[0]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "82525f47-3de4-42f6-bc55-f87e755eaf70",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:54.784453Z",
+     "iopub.status.busy": "2024-06-30T14:00:54.784453Z",
+     "iopub.status.idle": "2024-06-30T14:00:54.804789Z",
+     "shell.execute_reply": "2024-06-30T14:00:54.804789Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:54.784453Z"
+    }
+   },
+   "source": [
+    "upper_limit = {pp_waste: 2.4e9}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b2b3a7e4-3cad-4d98-b322-c88773aa4cbc",
+   "metadata": {},
+   "source": [
+    "With this, we re-instatiate and re-solve the problem:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "115d2e6f-4e58-4988-991c-e1182687c354",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:00:54.804789Z",
+     "iopub.status.busy": "2024-06-30T14:00:54.804789Z",
+     "iopub.status.idle": "2024-06-30T14:01:19.333543Z",
+     "shell.execute_reply": "2024-06-30T14:01:19.333543Z",
+     "shell.execute_reply.started": "2024-06-30T14:00:54.804789Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.instantiate(choices=choices, demand=demand, upper_limit=upper_limit)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "29330019-bdd6-4852-a83f-399ce74cf605",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:01:19.333543Z",
+     "iopub.status.busy": "2024-06-30T14:01:19.333543Z",
+     "iopub.status.idle": "2024-06-30T14:01:19.398029Z",
+     "shell.execute_reply": "2024-06-30T14:01:19.398029Z",
+     "shell.execute_reply.started": "2024-06-30T14:01:19.333543Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, constraints=upper_limit)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f7cf242f-2bd5-429c-b879-9ecb290dbcbc",
+   "metadata": {},
+   "source": [
+    "As expected, under ther technosphere constraint we obtain that 2.15 million ton of pyrolysis oil are produced and 291.10 million ton of fossil diesel. The overall minimized GWP stands at 137.7595 Mt CO2e. \n",
+    "\n",
+    "Now let's see what happens if we add further constraints to the problem. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6b39a911-3754-4284-9443-cf9c48c76415",
+   "metadata": {},
+   "source": [
+    "**No-constraint**"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "6b6b3a1b-8281-4bbe-9046-36555a425b3a",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:01:19.399026Z",
+     "iopub.status.busy": "2024-06-30T14:01:19.399026Z",
+     "iopub.status.idle": "2024-06-30T14:01:44.256505Z",
+     "shell.execute_reply": "2024-06-30T14:01:44.256505Z",
+     "shell.execute_reply.started": "2024-06-30T14:01:19.399026Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.instantiate(choices=choices, demand=demand, upper_limit={})\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "ce090c18-15b3-458c-967e-e0c8064b5461",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:01:44.257502Z",
+     "iopub.status.busy": "2024-06-30T14:01:44.257502Z",
+     "iopub.status.idle": "2024-06-30T14:01:44.302959Z",
+     "shell.execute_reply": "2024-06-30T14:01:44.302959Z",
+     "shell.execute_reply.started": "2024-06-30T14:01:44.257502Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6cc00a3e-f381-4761-a126-14befc7b14b9",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-04-25T11:54:14.519070Z",
+     "iopub.status.busy": "2024-04-25T11:54:14.519070Z",
+     "iopub.status.idle": "2024-04-25T11:54:14.536069Z",
+     "shell.execute_reply": "2024-04-25T11:54:14.535069Z",
+     "shell.execute_reply.started": "2024-04-25T11:54:14.519070Z"
+    }
+   },
+   "source": [
+    "**No-optimization**"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "7c041d66-8a96-4003-b783-8455bcc60fc7",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:01:44.302959Z",
+     "iopub.status.busy": "2024-06-30T14:01:44.302959Z",
+     "iopub.status.idle": "2024-06-30T14:02:08.396198Z",
+     "shell.execute_reply": "2024-06-30T14:02:08.396198Z",
+     "shell.execute_reply.started": "2024-06-30T14:01:44.302959Z"
+    }
+   },
+   "source": [
+    "choices  = {'diesel': {diesel_fossil: 1e15}} \n",
+    "pulpo_worker.instantiate(choices=choices, demand=demand)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "0d480e08-1035-4cb4-bc76-f9c0d3f1c849",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:08.397195Z",
+     "iopub.status.busy": "2024-06-30T14:02:08.397195Z",
+     "iopub.status.idle": "2024-06-30T14:02:08.444498Z",
+     "shell.execute_reply": "2024-06-30T14:02:08.444498Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:08.397195Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "5212bb60-b24a-4ea9-817e-5ee8994eb980",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:08.445494Z",
+     "iopub.status.busy": "2024-06-30T14:02:08.445494Z",
+     "iopub.status.idle": "2024-06-30T14:02:08.460415Z",
+     "shell.execute_reply": "2024-06-30T14:02:08.459417Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:08.445494Z"
+    }
+   },
+   "source": [
+    "choices  = {'diesel': {diesel_bio: 1e15,\n",
+    "                       diesel_fossil: 1e15,\n",
+    "                       diesel_pp: 1e15}} "
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f787a647-ee8d-4ecf-9325-aa9a90c97bd1",
+   "metadata": {},
+   "source": [
+    "<div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Indicator / Impact Constraints 📏</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a36ffcc4-5692-4e4f-abc7-7a9f127807fc",
+   "metadata": {},
+   "source": [
+    "For instance, a regulation aiming to reduce the ionising radiation impact by 5%! Using the previous result as baseline (6.5521e+10 kBq U235e) the target should be 6.2245e+10 kBq U235e. This constraint can be added as follows:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "d2b892fb-984c-47eb-8443-5d353c302e64",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:08.460415Z",
+     "iopub.status.busy": "2024-06-30T14:02:08.460415Z",
+     "iopub.status.idle": "2024-06-30T14:02:08.475468Z",
+     "shell.execute_reply": "2024-06-30T14:02:08.474473Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:08.460415Z"
+    }
+   },
+   "source": [
+    "upper_imp_limit = {\"('EF v3.0', 'ionising radiation: human health', 'human exposure efficiency relative to u235')\": 6.2245e+10}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "8535cc3a-5cb0-4bb7-9c45-e2a9ff0a7125",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:08.476656Z",
+     "iopub.status.busy": "2024-06-30T14:02:08.476464Z",
+     "iopub.status.idle": "2024-06-30T14:02:34.646795Z",
+     "shell.execute_reply": "2024-06-30T14:02:34.646795Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:08.476656Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.instantiate(choices=choices, demand=demand, upper_limit=upper_limit, upper_imp_limit=upper_imp_limit)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "4cf71269-d175-4fe4-82b9-991325ada0c8",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:34.646795Z",
+     "iopub.status.busy": "2024-06-30T14:02:34.646795Z",
+     "iopub.status.idle": "2024-06-30T14:02:34.711121Z",
+     "shell.execute_reply": "2024-06-30T14:02:34.711121Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:34.646795Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, constraints=upper_limit, zeroes=True)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8bf1c47e-7ee2-4491-a457-b96e874e8dcb",
+   "metadata": {},
+   "source": [
+    "Since the process minimizing ionising radiation has already reached its limit through the technosphere constraint, the solver selects the next best process to comply with the additional impact constraint. This results in the use of **39.09 million ton of imported biodiesel**.\n",
+    "\n",
+    "The trade-off to comply with the constraint is an increased GWP from **137.7595 Mt CO2e** to **428.6267 Mt CO2e**! In other words, to reduce one impact category by 5%, another one increased by 211.13%. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3d2761fd-9973-42d5-9885-077976f0f8ee",
+   "metadata": {},
+   "source": [
+    "This kind of assessment can help to identify technological and systemic bottlenecks in the multi-objective design of production systems."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "0490537e-5a66-40a4-a84f-a7ca104e7daf",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:34.712119Z",
+     "iopub.status.busy": "2024-06-30T14:02:34.711121Z",
+     "iopub.status.idle": "2024-06-30T14:02:35.756625Z",
+     "shell.execute_reply": "2024-06-30T14:02:35.755630Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:34.712119Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.save_results(choices=choices, demand=demand, name='plastic_showcase_constrained.xlsx')"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "748f91bd-81b8-47fe-8a78-42cf3e0ed644",
+   "metadata": {},
+   "source": [
+    " <div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Biosphere capacity constraints 🌱</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "attachments": {},
+   "cell_type": "markdown",
+   "id": "e63f768a-7fe6-45f0-8669-70dc5366efaa",
+   "metadata": {},
+   "source": [
+    "From the results saved in the spreadsheet \"plastic_showcase_constrained.xlsx\" we can identify (as expected) that the biodiesel production has a very large impact on land transformation. The biosphere entry \"Transformation, from annual crop\" takes a value of 109,643 km² which is slightly larger than the entire country of Iceland.\n",
+    "\n",
+    "Pulpo enable the restriction of biosphere flows as well. Let's repeat the previous assessment, targeting this time ionising radiation as objective, and figure out which is the possible reduction if we allow only 10,000 km² of land transformation: "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "b38a40ce-5fef-4c54-948f-327670f8016c",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:35.758619Z",
+     "iopub.status.busy": "2024-06-30T14:02:35.757622Z",
+     "iopub.status.idle": "2024-06-30T14:02:35.777279Z",
+     "shell.execute_reply": "2024-06-30T14:02:35.777279Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:35.758619Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.method[\"('IPCC 2013', 'climate change', 'GWP 100a')\"] = 0\n",
+    "pulpo_worker.method[\"('EF v3.0', 'ionising radiation: human health', 'human exposure efficiency relative to u235')\"] = 1"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "7feebe6a-2314-4f5b-9f3f-a623c1232bcd",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:35.777279Z",
+     "iopub.status.busy": "2024-06-30T14:02:35.777279Z",
+     "iopub.status.idle": "2024-06-30T14:02:35.867502Z",
+     "shell.execute_reply": "2024-06-30T14:02:35.867502Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:35.777279Z"
+    }
+   },
+   "source": [
+    "transformation = pulpo_worker.retrieve_envflows(activities=[\"Transformation, from annual crop\"])[0]"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "b5d0bf16-b732-4a53-9c4d-d93cc535e771",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:35.868498Z",
+     "iopub.status.busy": "2024-06-30T14:02:35.868498Z",
+     "iopub.status.idle": "2024-06-30T14:02:35.884233Z",
+     "shell.execute_reply": "2024-06-30T14:02:35.883503Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:35.868498Z"
+    }
+   },
+   "source": [
+    "upper_elem_limit = {transformation: 1e10}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "e9831fb1-d99a-4927-94f7-65d26b618ca1",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:35.885230Z",
+     "iopub.status.busy": "2024-06-30T14:02:35.884233Z",
+     "iopub.status.idle": "2024-06-30T14:02:57.651697Z",
+     "shell.execute_reply": "2024-06-30T14:02:57.651697Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:35.884233Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.instantiate(choices=choices, demand=demand, upper_limit=upper_limit, upper_elem_limit=upper_elem_limit)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "56db081e-ed9f-494c-a1bd-41e2e3c947ff",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:57.652693Z",
+     "iopub.status.busy": "2024-06-30T14:02:57.651697Z",
+     "iopub.status.idle": "2024-06-30T14:02:57.716270Z",
+     "shell.execute_reply": "2024-06-30T14:02:57.716270Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:57.652693Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "fc8fa995-9636-407b-a083-b07ee2d01394",
+   "metadata": {},
+   "source": [
+    "It can be observed that, as before, the maximum amount of pyrolysis oil is selected. The biodiesel however now is produced with **17.83 million ton**, reducing the overall ionising radiation to 6.522251e+10 kBq U235e (0.46% reduction)."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9d344ea8-3f02-418b-b1c1-d521eb117023",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-04-01T11:13:55.119299Z",
+     "iopub.status.busy": "2024-04-01T11:13:55.119299Z",
+     "iopub.status.idle": "2024-04-01T11:13:55.128269Z",
+     "shell.execute_reply": "2024-04-01T11:13:55.127272Z",
+     "shell.execute_reply.started": "2024-04-01T11:13:55.119299Z"
+    }
+   },
+   "source": [
+    "---\n",
+    "\n",
+    "**Disclaimer**: This example is designed for demonstrative purposes only, to showcase the functionalities of Pulpo. The figures, scenarios, and scales presented are hypothetical and may not accurately reflect real-world conditions. Any resemblance to actual data or outcomes is purely coincidental and should not be considered as accurate representations of environmental impacts or optimization results.\n",
+    "\n",
+    "---\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ae3a3f79-a1c4-446c-b1bd-d3554ecd339a",
+   "metadata": {},
+   "source": [
+    " <div style=\"text-align: center; background-color: #f0f0f0; padding: 10px;\">\n",
+    "    <h2 style=\"font-family: 'Arial', sans-serif; font-weight: bold; color: #555;\">Supply vs. demand 🔄💼</h2>\n",
+    "</div>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "564cd3d8-1428-4dc6-b3d3-364e1523173e",
+   "metadata": {},
+   "source": [
+    "Pulpo introduces a feature that pivots from defining a final demand to setting a final supply. This approach is particularly useful in scenarios where the end demand remains uncertain, yet there's clarity on a process's production capacity. Under such circumstances, the aim is to maximize production, anticipating that the eventual demand will exceed this capacity. For example, we might know the production limit for pyrolysis oil but only have an estimated demand for market-sourced diesel.\n",
+    "\n",
+    "To demonstrate this capability, we'll explore specifying a final **supply** of 1 kg of market diesel, contrasting this with a scenario based on a 1 kg diesel **demand**. It's reasonable to anticipate a somewhat reduced total impact in the supply specification scenario, considering that it accounts for diesel's internal consumption, which is normally added to the final demand."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "3832f45b-f71b-474f-8a72-d98bf6e1cf06",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:57.717271Z",
+     "iopub.status.busy": "2024-06-30T14:02:57.717271Z",
+     "iopub.status.idle": "2024-06-30T14:02:57.724556Z",
+     "shell.execute_reply": "2024-06-30T14:02:57.723439Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:57.717271Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.method[\"('IPCC 2013', 'climate change', 'GWP 100a')\"] = 1\n",
+    "pulpo_worker.method[\"('EF v3.0', 'ionising radiation: human health', 'human exposure efficiency relative to u235')\"] = 0"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "b0018abb-5af9-4f0e-ab20-050bc006ff83",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:57.725556Z",
+     "iopub.status.busy": "2024-06-30T14:02:57.725556Z",
+     "iopub.status.idle": "2024-06-30T14:02:57.743618Z",
+     "shell.execute_reply": "2024-06-30T14:02:57.743618Z",
+     "shell.execute_reply.started": "2024-06-30T14:02:57.725556Z"
+    }
+   },
+   "source": [
+    "supply = {diesel_market: 1}"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "03b0e851-2828-43d6-9583-4a900cdd890c",
+   "metadata": {},
+   "source": [
+    "The final supply is specified in the instatiation by setting a lower and upper limit of the scaling vector to the same value:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "id": "0a75bfba-4722-4ba9-82f6-6b70ead0715f",
+   "metadata": {
+    "execution": {
+     "iopub.execute_input": "2024-06-30T14:02:57.743618Z",
+     "iopub.status.busy": "2024-06-30T14:02:57.743618Z"
+    }
+   },
+   "source": [
+    "pulpo_worker.instantiate(choices=choices, lower_limit=supply, upper_limit=supply)\n",
+    "results = pulpo_worker.solve()"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "code",
+   "id": "4ebcae6c-4dc4-447f-9f47-a421b975de75",
+   "metadata": {},
+   "source": [
+    "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)"
+   ],
+   "outputs": [],
+   "execution_count": null
+  },
+  {
+   "cell_type": "markdown",
+   "id": "edfa0287-0287-4236-a653-c4917dbc3f5e",
+   "metadata": {},
+   "source": [
+    "As expected, the total impact is **slightly** lower with 0.373<span style=\"color: red;\">026</span> kg CO<sub>2</sub> compared to 0.373<span style=\"color: red;\">292</span> kg CO<sub>2</sub>e.\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "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.9.19"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/pulpo/pulpo.py b/pulpo/pulpo.py
index 974f5f4..b0ae3bc 100644
--- a/pulpo/pulpo.py
+++ b/pulpo/pulpo.py
@@ -1,9 +1,20 @@
 from pulpo.utils import optimizer, bw_parser, converter, saver
 from typing import List, Union
 import webbrowser
+from tests.rice_database import setup_rice_husk_db
+from tests.generic_database import setup_generic_db
 
 class PulpoOptimizer:
     def __init__(self, project: str, database: str, method: Union[str, List[str], dict], directory: str):
+        """
+        Initializes the PulpoOptimizer with project, database, method, and directory.
+
+        Args:
+            project (str): Name of the project.
+            database (str): Name of the database.
+            method (Union[str, List[str], dict]): Method(s) for optimization.
+            directory (str): Directory for saving results.
+        """
         self.project = project
         self.database = database
         self.intervention_matrix = 'biosphere3'
@@ -13,47 +24,190 @@ def __init__(self, project: str, database: str, method: Union[str, List[str], di
         self.instance = None
 
     def get_lci_data(self):
+        """
+        Imports LCI data for the project using the specified database and method.
+        """
         self.lci_data = bw_parser.import_data(self.project, self.database, self.method, self.intervention_matrix)
 
-    def instantiate(self, choices={}, demand={}, upper_limit={},lower_limit={}, upper_elem_limit={}):
-        # Instantiate only for those methods that are part of the objecitve
-        methods = {h: self.method[h] for h in self.method if self.method[h] !=0}
-        data = converter.combine_inputs(self.lci_data, demand, choices, upper_limit, lower_limit, upper_elem_limit, methods)
+    def instantiate(self, choices={}, demand={}, upper_limit={}, lower_limit={}, upper_elem_limit={},
+                    upper_imp_limit={}):
+        """
+        Combines inputs and instantiates the optimization model.
+
+        Args:
+            choices (dict): Choices for the model.
+            demand (dict): Demand data.
+            upper_limit (dict): Upper limit constraints.
+            lower_limit (dict): Lower limit constraints.
+            upper_elem_limit (dict): Upper elemental limit constraints.
+            upper_imp_limit (dict): Upper impact limit constraints.
+        """
+        # Instantiate only for those methods that are part of the objective or the limits
+        methods = {h: self.method[h] for h in self.method if self.method[h] != 0 or h in upper_imp_limit}
+        data = converter.combine_inputs(self.lci_data, demand, choices, upper_limit, lower_limit, upper_elem_limit,
+                                        upper_imp_limit, methods)
         self.instance = optimizer.instantiate(data)
 
-    def solve(self, GAMS_PATH=False, options=None):
-        results, self.instance = optimizer.solve_model(self.instance, GAMS_PATH, options=options)
+    def solve(self, GAMS_PATH=False, solver_name=None, options=None):
+        """
+        Solves the optimization model and calculates additional methods and inventory flows if needed.
+
+        Args:
+            GAMS_PATH (bool): Path to GAMS if needed.
+            options (dict): Additional options for the solver.
+
+        Returns:
+            results: Results of the optimization.
+        """
+        results, self.instance = optimizer.solve_model(self.instance, GAMS_PATH, solver_name=solver_name, options=options)
+
         # Post calculate additional methods, in case several methods have been specified and one of them is 0
         if not isinstance(self.method, str):
             if len(self.method) > 1 and 0 in [self.method[x] for x in self.method]:
                 self.instance = optimizer.calculate_methods(self.instance, self.lci_data, self.method)
+
         self.instance = optimizer.calculate_inv_flows(self.instance, self.lci_data)
         return results
 
+    def retrieve_processes(self, keys=None, processes=None, reference_products=None, locations=None):
+        """
+        Retrieves processes from the database based on given filters.
+
+        Args:
+            keys (list): List of keys to filter activities.
+            processes (list): List of processes to filter.
+            reference_products (list): List of reference products to filter.
+            locations (list): List of locations to filter.
+
+        Returns:
+            activities: Filtered activities from the database.
+        """
+        processes = bw_parser.retrieve_processes(self.project, self.database, keys, processes, reference_products,
+                                                  locations)
+        return processes
+
     def retrieve_activities(self, keys=None, activities=None, reference_products=None, locations=None):
-        activities = bw_parser.retrieve_processes(self.project, self.database, keys, activities, reference_products, locations)
+        """
+        Works the same as "retrieve_processes" but with a different name. Will be obsolete in future versions.
+        """
+        activities = bw_parser.retrieve_processes(self.project, self.database, keys, activities, reference_products,
+                                                  locations)
         return activities
 
     def retrieve_envflows(self, keys=None, activities=None, categories=None):
-        activities = bw_parser.retrieve_env_interventions(project=self.project, intervention_matrix=self.intervention_matrix, keys=keys, activities=activities, categories=categories)
+        """
+        Retrieves environmental flows from the database based on given filters.
+
+        Args:
+            keys (list): List of keys to filter environmental flows.
+            activities (list): List of activities to filter.
+            categories (list): List of categories to filter.
+
+        Returns:
+            activities: Filtered environmental flows from the database.
+        """
+        activities = bw_parser.retrieve_env_interventions(project=self.project,
+                                                          intervention_matrix=self.intervention_matrix, keys=keys,
+                                                          activities=activities, categories=categories)
         return activities
 
     def retrieve_methods(self, string=""):
+        """
+        Retrieves methods from the database based on a search string.
+
+        Args:
+            string (str): Search string for methods.
+
+        Returns:
+            methods: List of methods that match the search string.
+        """
         methods = bw_parser.retrieve_methods(self.project, string)
         return methods
 
     def save_results(self, choices={}, constraints={}, demand={}, name='results.xlxs'):
-        saver.save_results(self.instance, self.project, self.database, choices, constraints, demand, self.lci_data['process_map'], self.lci_data['intervention_map'], self.directory, name)
+        """
+        Saves the results of the optimization to a file.
+
+        Args:
+            choices (dict): Choices for the model.
+            constraints (dict): Constraints applied during optimization.
+            demand (dict): Demand data used in optimization.
+            name (str): Name of the file to save results.
+        """
+        saver.save_results(self.instance, self.project, self.database, choices, constraints, demand,
+                           self.lci_data['process_map'], self.lci_data['intervention_map'], self.directory, name)
 
     def summarize_results(self, choices={}, constraints={}, demand={}, zeroes=False):
-            saver.summarize_results(self.instance, self.project, self.database, choices, constraints, demand, self.lci_data['process_map'], zeroes)
+        """
+        Summarizes the results of the optimization.
+
+        Args:
+            choices (dict): Choices for the model.
+            constraints (dict): Constraints applied during optimization.
+            demand (dict): Demand data used in optimization.
+            zeroes (bool): Whether to include zero values in the summary.
+        """
+        saver.summarize_results(self.instance, choices, constraints, demand,
+                                self.lci_data['process_map'], zeroes)
+
 
 def electricity_showcase():
-    github_url = 'https://github.com/flechtenberg/pulpo/blob/develop/notebooks/electricity_showcase.ipynb'  # Replace with your GitHub URL
+    """
+    Opens the electricity showcase notebook in the web browser.
+    """
+    github_url = 'https://github.com/flechtenberg/pulpo/blob/master/notebooks/electricity_showcase.ipynb'
     nbviewer_url = 'https://nbviewer.jupyter.org/github/' + github_url.split('github.com/')[1]
     webbrowser.open(nbviewer_url)
 
+
 def hydrogen_showcase():
-    github_url = 'https://github.com/flechtenberg/pulpo/blob/develop/notebooks/hydrogen_showcase.ipynb'  # Replace with your GitHub URL
+    """
+    Opens the hydrogen showcase notebook in the web browser.
+    """
+    github_url = 'https://github.com/flechtenberg/pulpo/blob/master/notebooks/hydrogen_showcase.ipynb'
+    nbviewer_url = 'https://nbviewer.jupyter.org/github/' + github_url.split('github.com/')[1]
+    webbrowser.open(nbviewer_url)
+
+
+def plastic_showcase():
+    """
+    Opens the plastic showcase notebook in the web browser.
+    """
+    github_url = 'https://github.com/flechtenberg/pulpo/blob/master/notebooks/plastic_showcase.ipynb'
     nbviewer_url = 'https://nbviewer.jupyter.org/github/' + github_url.split('github.com/')[1]
-    webbrowser.open(nbviewer_url)
\ No newline at end of file
+    webbrowser.open(nbviewer_url)
+
+
+def install_rice_husk_db():
+    """
+    Sets up the rice husk example database.
+    """
+    setup_rice_husk_db()
+
+def install_generic_db(project="generic_db_project", database="generic_db", n_prod=5, n_proc=3, n_reg=3, n_inputs=4, n_flows=4, n_methods=2, seed=None, return_data=False):
+    """
+    Sets up the generic LCI database in Brightway2 with specified parameters.
+
+    Args:
+        project (str): Name of the Brightway2 project to create or use. Defaults to "generic_db_project".
+        database (str): Name of the database to create or use. Defaults to "generic_db".
+        n_prod (int): Number of products to generate. Defaults to 5.
+        n_proc (int): Maximum number of processes per product. Defaults to 3.
+        n_reg (int): Number of regions where processes can be active. Defaults to 3.
+        n_inputs (int): Maximum number of inputs per process. Defaults to 4.
+        n_flows (int): Number of environmental flows to generate. Defaults to 4.
+        n_methods (int): Number of impact assessment methods to create. Defaults to 2.
+        seed (int, optional): Seed for reproducibility of random data generation. Defaults to None.
+        return_data (bool): If True, returns the generated matrices (technosphere, biosphere, and
+            characterization). Defaults to False.
+
+    Returns:
+        tuple: If `return_data` is True, returns a tuple containing:
+            - technosphere_matrix (np.ndarray): The technosphere matrix.
+            - biosphere_matrix (np.ndarray): The biosphere matrix.
+            - characterization_matrices (dict): A dictionary of characterization factor matrices.
+    """
+    return setup_generic_db(project=project, database=database, n_prod=n_prod, n_proc=n_proc, n_reg=n_reg,
+                            n_inputs=n_inputs, n_flows=n_flows, n_methods=n_methods,
+                            seed=seed, return_data=return_data)
+
diff --git a/pulpo/utils/bw_parser.py b/pulpo/utils/bw_parser.py
index 12a3d93..f5cb9a6 100644
--- a/pulpo/utils/bw_parser.py
+++ b/pulpo/utils/bw_parser.py
@@ -1,47 +1,65 @@
 from typing import List, Union, Dict, Any
-from pathlib import Path
-import pickle
-import brightway2 as bw
-from scipy import sparse
+import bw2calc as bc
+import bw2data as bd
 
-def import_data(project: str, database: str, method: Union[str, List[str], dict[str, int]], intervention_matrix_name: str) -> Dict[str, Union[dict, Any]]:
+def import_data(project: str, database: str, method: Union[str, List[str], dict[str, int]],
+                intervention_matrix_name: str) -> Dict[str, Union[dict, Any]]:
     """
     Main function to import LCI data for a project from a database.
+
+    Args:
+        project (str): Name of the project.
+        database (str): Name of the database.
+        method (Union[str, List[str], dict[str, int]]): Method(s) for data retrieval.
+        intervention_matrix_name (str): Name of the intervention matrix.
+
+    Returns:
+        Dict[str, Union[dict, Any]]: Dictionary containing imported LCI data.
     """
 
     # Set project and get database
-    bw.projects.set_current(project)
-    eidb = bw.Database(database)
+    bd.projects.set_current(project)
+    eidb = bd.Database(database)
+
     # Prepare methods
     if isinstance(method, str):
         method = [method]  # Convert single string to list of strings
     method = sorted(method)
     methods = retrieve_methods(project, method)
-    # Get data
+
+    # Old
     characterization_matrices = {}
     rand_act = eidb.random()
     for method in methods:
-        lca = bw.LCA({rand_act: 1}, method)
-        lca.load_lci_data()
-        lca.load_lcia_data()
+        # Set up the LCA calculation with a functional unit of the random activity and the current method
+        lca = bc.LCA({rand_act: 1}, method)
+
+        # Load the LCI and LCIA data
+        lca.lci()
+        lca.lcia()
+
+        # Store the characterization (C) matrix for the method
         characterization_matrices[str(method)] = lca.characterization_matrix
-    technology_matrix = lca.technosphere_matrix
-    intervention_matrix = lca.biosphere_matrix
+
+    # Extract A (Technosphere) and B (Biosphere) matrices from the LCA
+    technology_matrix = lca.technosphere_matrix  # A matrix
+    intervention_matrix = lca.biosphere_matrix  # B matrix
+
     # Create activity map key --> ID | ID --> description
     process_map = lca.product_dict
-
     for act in eidb:
         process_map[process_map[act.key]] = str(act['name']) + ' | ' + str(act['reference product']) + ' | ' + str(
             act['location'])
 
-    if intervention_matrix_name in bw.databases:
-        eidb_bio = bw.Database(intervention_matrix_name)
+    if intervention_matrix_name in bd.databases:
+        eidb_bio = bd.Database(intervention_matrix_name)
         intervention_map = lca.biosphere_dict
         for act in eidb_bio:
             if act.key in intervention_map:
                 intervention_map[intervention_map[act.key]] = act['name'] + ' | ' + str(act['categories'])
     else:
-        print("The name of the biosphere is not '" + intervention_matrix_name + "'. Please specify the correct biosphere.")
+        print(
+            "The name of the biosphere is not '" + intervention_matrix_name + "'. Please specify the correct biosphere.")
         return {}
 
     lci_data = {
@@ -55,14 +73,28 @@ def import_data(project: str, database: str, method: Union[str, List[str], dict[
     return lci_data
 
 
-def retrieve_processes(project, database, keys=None, activities=None, reference_products=None, locations=None):
+def retrieve_processes(project: str, database: str, keys=None, activities=None, reference_products=None,
+                       locations=None):
     """
     Retrieve activities from a database based on specified keys, activities, reference products, and locations.
+
+    Args:
+        project (str): Name of the project.
+        database (str): Name of the database.
+        keys (list, optional): List of keys to filter activities.
+        activities (list, optional): List of activity names to filter.
+        reference_products (list, optional): List of reference products to filter.
+        locations (list, optional): List of locations to filter.
+
+    Returns:
+        list: List of matching activities from the database.
     """
 
-    bw.projects.set_current(project)
-    eidb = bw.Database(database)
+    # Set project and get database
+    bd.projects.set_current(project)
+    eidb = bd.Database(database)
 
+    # Filter by keys if provided
     if keys is not None:
         if isinstance(keys, str):
             keys = [keys]
@@ -71,6 +103,7 @@ def retrieve_processes(project, database, keys=None, activities=None, reference_
 
     matching_processes = []
 
+    # Filter by activities, reference products, and locations
     for proc in eidb:
         if (activities is None or proc['name'] in activities) and \
                 (reference_products is None or proc['reference product'] in reference_products) and \
@@ -82,14 +115,28 @@ def retrieve_processes(project, database, keys=None, activities=None, reference_
     else:
         return matching_processes
 
-def retrieve_env_interventions(project='', intervention_matrix='biosphere3', keys=None, activities=None, categories=None):
+
+def retrieve_env_interventions(project: str = '', intervention_matrix: str = 'biosphere3', keys=None, activities=None,
+                               categories=None):
     """
     Retrieve environmental flows from the biosphere database based on specified keys, activities, and categories.
+
+    Args:
+        project (str, optional): Name of the project.
+        intervention_matrix (str): Name of the intervention matrix.
+        keys (list, optional): List of keys to filter environmental flows.
+        activities (list, optional): List of activity names to filter.
+        categories (list, optional): List of categories to filter.
+
+    Returns:
+        list: List of matching environmental flows from the database.
     """
 
-    bw.projects.set_current(project)
-    eidb = bw.Database(intervention_matrix)
+    # Set project and get database
+    bd.projects.set_current(project)
+    eidb = bd.Database(intervention_matrix)
 
+    # Filter by keys if provided
     if keys is not None:
         if isinstance(keys, str):
             keys = [keys]
@@ -98,6 +145,7 @@ def retrieve_env_interventions(project='', intervention_matrix='biosphere3', key
 
     matching_flows = []
 
+    # Filter by activities and categories
     for flow in eidb:
         if (activities is None or flow['name'] in activities) and \
                 (categories is None or str(flow['categories']) in categories):
@@ -109,7 +157,16 @@ def retrieve_env_interventions(project='', intervention_matrix='biosphere3', key
         return matching_flows
 
 
-def retrieve_methods(project: str, sub_string: List[str]):
-    ''' This function retrieves all the methods that contain the specified list of substrings'''
-    bw.projects.set_current(project)
-    return [method for method in bw.methods if any([x.lower() in str(method).lower() for x in sub_string])]
+def retrieve_methods(project: str, sub_string: List[str]) -> List[str]:
+    """
+    Retrieve all methods that contain the specified list of substrings.
+
+    Args:
+        project (str): Name of the project.
+        sub_string (List[str]): List of substrings to search for in method names.
+
+    Returns:
+        List[str]: List of methods that match the substrings.
+    """
+    bd.projects.set_current(project)
+    return [method for method in bd.methods if any([x.lower() in str(method).lower() for x in sub_string])]
diff --git a/pulpo/utils/converter.py b/pulpo/utils/converter.py
index 2278950..6c036a2 100644
--- a/pulpo/utils/converter.py
+++ b/pulpo/utils/converter.py
@@ -1,44 +1,59 @@
 import scipy.sparse as sparse
 
-def combine_inputs(lci_data, demand, choices, upper_limit, lower_limit, upper_inv_limit, methods):
-    ''' This function combines all the inputs to a dictionary as an input for the optimization model'''
-    ''' Load LCIA methods into a list of matrices'''
+
+def combine_inputs(lci_data, demand, choices, upper_limit, lower_limit, upper_inv_limit, upper_imp_limit, methods):
+    """
+    Combines all the inputs into a dictionary as an input for the optimization model.
+
+    Args:
+        lci_data (dict): LCI data containing matrices and mappings.
+        demand (dict): Demand data.
+        choices (dict): Choices for the model.
+        upper_limit (dict): Upper limit constraints.
+        lower_limit (dict): Lower limit constraints.
+        upper_inv_limit (dict): Upper intervention limit constraints.
+        upper_imp_limit (dict): Upper impact limit constraints.
+        methods (dict): Methods for environmental impact assessment.
+
+    Returns:
+        dict: Combined data dictionary for the optimization model.
+    """
+
+    # Load LCI data matrices and mappings
     matrices = lci_data['matrices']
     intervention_matrix = lci_data['intervention_matrix']
     technology_matrix = lci_data['technology_matrix']
     process_map = lci_data['process_map']
     intervention_map = lci_data['intervention_map']
 
-    ''' Remove matrices that are not part of the objective '''
+    # Remove matrices that are not part of the objective
     matrices = {h: matrices[h] for h in matrices if str(h) in methods}
 
-    ''' Prepare the environmental impact matrices Q[h]*B'''
+    # Prepare the environmental impact matrices Q[h]*B
     env_cost = {h: sparse.csr_matrix.dot(matrices[h], intervention_matrix) for h in matrices}
 
-    ''' Convert sparse csr biosphere impact matrix to dictionary'''
+    # Convert sparse csr biosphere impact matrix to dictionary
     env_cost_dict = {(i - 1, env_cost[h].indices[j], h): env_cost[h].data[j]
-                      for h in matrices for i in range(1, intervention_matrix.shape[0] + 1)
-                      for j in range(env_cost[h].indptr[i - 1], env_cost[h].indptr[i])}
+                     for h in matrices for i in range(1, intervention_matrix.shape[0] + 1)
+                     for j in range(env_cost[h].indptr[i - 1], env_cost[h].indptr[i])}
 
-    ''' Convert sparse csr technology matrix to dictionary'''
+    # Convert sparse csr technology matrix to dictionary
     technology_matrix_dict = {(i - 1, technology_matrix.indices[j]): technology_matrix.data[j]
-                         for i in range(1, technology_matrix.shape[0] + 1)
-                         for j in range(technology_matrix.indptr[i - 1], technology_matrix.indptr[i])}
+                              for i in range(1, technology_matrix.shape[0] + 1)
+                              for j in range(technology_matrix.indptr[i - 1], technology_matrix.indptr[i])}
 
-    ''' Convert sparse csr intervention flow matrix to dictionary'''
+    # Convert sparse csr intervention flow matrix to dictionary
     inv_to_consider = [intervention_map[g] for g in upper_inv_limit]
     inv_dict = {(g, intervention_matrix.indices[j]): intervention_matrix.data[j]
-                      for g in inv_to_consider
-                      for j in range(intervention_matrix.indptr[g], intervention_matrix.indptr[g+1])}
+                for g in inv_to_consider
+                for j in range(intervention_matrix.indptr[g], intervention_matrix.indptr[g + 1])}
 
-    ''' Make technology matrix rectangular and update keys and product_ids'''
-    capacity_dict = {}
-    keys = {}  # store the activity keys of the choices
-    product_ids = []  # store the product IDs of the choices
+    # Make technology matrix rectangular and update keys and product_ids
+    keys = {}  # Store the activity keys of the choices
+    product_ids = []  # Store the product IDs of the choices
     for key, processes in choices.items():
         for proc in processes:
             product_id = process_map[proc.key]
-            capacity_dict[product_id] = choices[key][proc]
             keys[product_id] = key
             product_ids.append(product_id)
 
@@ -50,7 +65,7 @@ def combine_inputs(lci_data, demand, choices, upper_limit, lower_limit, upper_in
                 technology_matrix_dict[(keys[product], process)] += technology_matrix_dict[(product, process)]
             del technology_matrix_dict[(product, process)]
 
-    ''' Create sets using set comprehensions for better performance'''
+    # Create sets using set comprehensions for better performance
     PRODUCTS = {None: list({i[0] for i in technology_matrix_dict})}
     PROCESS = {None: list({i[1] for i in technology_matrix_dict})}
     PRODUCT_PROCESS = {None: list({(i[0], i[1]) for i in technology_matrix_dict})}
@@ -60,38 +75,54 @@ def combine_inputs(lci_data, demand, choices, upper_limit, lower_limit, upper_in
     INV_PROCESS = {None: list({(i[0], i[1]) for i in inv_dict})}
     INDICATOR = {None: list({h for h in matrices})}
 
-    ''' Specify the demand'''
+    # Specify the demand
     demand_dict = {prod: 0 for prod in PRODUCTS[None]}
     for dem in demand:
-        demand_dict[process_map[dem]] = demand[dem]
-
-    ''' Specify the lower limit '''
+        if dem in process_map:
+            # Case when dem is a key in process_map
+            demand_dict[process_map[dem]] = demand[dem]
+        elif dem in choices:
+            # Case when dem is already one of the elements in process_map
+            demand_dict[dem] = demand[dem]
+        else:
+            # Case when dem is neither a key nor a value in process_map
+            raise ValueError(f"'{dem}' is not found in process_map keys or values.")
+
+    # Specify the lower limit
     lower_limit_dict = {proc: -1e20 for proc in PROCESS[None]}
-    for proc in lower_limit:
-        lower_limit_dict[process_map[proc]] = lower_limit[proc]
     for choice in choices:
         for proc in choices[choice]:
             lower_limit_dict[process_map[proc]] = 0
+    for proc in lower_limit:
+        lower_limit_dict[process_map[proc]] = lower_limit[proc]
 
-    ''' Specify the upper limit '''
+    # Specify the upper limit
     upper_limit_dict = {proc: 1e20 for proc in PROCESS[None]}
     for proc in upper_limit:
         upper_limit_dict[process_map[proc]] = upper_limit[proc]
+    for choice in choices:
+        for proc in choices[choice]:
+            upper_limit_dict[process_map[proc]] = choices[choice][proc]
 
-    ''' Check if a supply has been specified '''
+    # Check if a supply has been specified
     supply_dict = {prod: 0 for prod in PRODUCTS[None]}
     for proc in list(lower_limit.keys() & upper_limit.keys()):
         supply_dict[process_map[proc]] = 1 if lower_limit[proc] == upper_limit[proc] else 0
 
-    ''' Specify the upper elementary flow limit '''
+    # Specify the upper elementary flow limit
     upper_inv_limit_dict = {elem: 1e24 for elem in INV[None]}
     for inv in upper_inv_limit:
         upper_inv_limit_dict[intervention_map[inv.key]] = upper_inv_limit[inv]
 
-    ''' Create weights'''
+    # Specify the upper impact category limit
+    upper_imp_limit_dict = {imp: 1e24 for imp in INDICATOR[None]}
+    for imp in upper_imp_limit:
+        upper_imp_limit_dict[imp] = upper_imp_limit[imp]
+
+    # Create weights
     weights = {method: 1 for method in matrices} if methods == {} else methods
 
-    ''' Assemble the final data dictionary'''
+    # Assemble the final data dictionary
     model_data = {
         None: {
             'PRODUCT': PRODUCTS,
@@ -110,13 +141,27 @@ def combine_inputs(lci_data, demand, choices, upper_limit, lower_limit, upper_in
             'LOWER_LIMIT': lower_limit_dict,
             'UPPER_LIMIT': upper_limit_dict,
             'UPPER_INV_LIMIT': upper_inv_limit_dict,
+            'UPPER_IMP_LIMIT': upper_imp_limit_dict,
             'WEIGHTS': weights,
         }
     }
     return model_data
 
+
 def convert_to_dict(input_data):
-    ''' Checks if the passed method is in the correct format '''
+    """
+    Checks if the passed method is in the correct format and converts it accordingly.
+
+    Args:
+        input_data (Union[str, list, dict]): Input data to be converted.
+
+    Returns:
+        dict: Converted dictionary with methods and their weights.
+
+    Raises:
+        ValueError: If the input data is not in the expected format.
+    """
+
     # Check if the input is a string
     if isinstance(input_data, str):
         return {input_data: 1}
@@ -133,4 +178,3 @@ def convert_to_dict(input_data):
     else:
         raise ValueError(
             "Input should be either a string, a list of strings, or a dictionary with string indices and numerical values")
-
diff --git a/pulpo/utils/optimizer.py b/pulpo/utils/optimizer.py
index 431da53..39532cd 100644
--- a/pulpo/utils/optimizer.py
+++ b/pulpo/utils/optimizer.py
@@ -1,7 +1,13 @@
 import pyomo.environ as pyo
+from pyomo.contrib import appsi
 
 def create_model():
-    """Builds an abstract model on top of the ecoinvent database."""
+    """
+    Builds an abstract model on top of the ecoinvent database.
+
+    Returns:
+        AbstractModel: The Pyomo abstract model for optimization.
+    """
     model = pyo.AbstractModel()
 
     # Sets
@@ -22,7 +28,8 @@ def create_model():
     # Parameters
     model.UPPER_LIMIT = pyo.Param(model.PROCESS, mutable=True, within=pyo.Reals, doc='Maximum production capacity of process j')
     model.LOWER_LIMIT = pyo.Param(model.PROCESS, mutable=True, within=pyo.Reals, doc='Minimum production capacity of process j')
-    model.UPPER_INV_LIMIT = pyo.Param(model.INV, mutable=True, within=pyo.Reals, doc='Maximum intervention flow flow g')
+    model.UPPER_INV_LIMIT = pyo.Param(model.INV, mutable=True, within=pyo.Reals, doc='Maximum intervention flow g')
+    model.UPPER_IMP_LIMIT = pyo.Param(model.INDICATOR, mutable=True, within=pyo.Reals, doc='Maximum impact on category h')
     model.ENV_COST_MATRIX = pyo.Param(model.ENV_COST_PROCESS, mutable=True, doc='Enviornmental cost matrix Q*B describing the environmental cost flows e associated to process j')
     model.INV_MATRIX = pyo.Param(model.INV_PROCESS, mutable=True, doc='Intervention matrix B describing the intervention flow g entering/leaving process j')
     model.FINAL_DEMAND = pyo.Param(model.PRODUCT, mutable=True, within=pyo.Reals, doc='Final demand of intermediate product flows (i.e., functional unit)')
@@ -49,6 +56,7 @@ def create_model():
     model.LOWER_CNSTR = pyo.Constraint(model.PROCESS, rule=lower_constraint)
     model.SLACK_CNSTR = pyo.Constraint(model.PRODUCT, rule=slack_constraint)
     model.INV_CNSTR = pyo.Constraint(model.INV, rule=upper_env_constraint)
+    model.IMP_CNSTR = pyo.Constraint(model.INDICATOR, rule=upper_imp_constraint)
 
     # Objective function
     model.OBJ = pyo.Objective(sense=pyo.minimize, rule=objective_function)
@@ -80,7 +88,6 @@ def demand_constraint(model, i):
     """Fixes a value in the demand vector"""
     return sum(model.TECH_MATRIX[i, j] * model.scaling_vector[j] for j in model.PROCESS_OUT[i]) == model.FINAL_DEMAND[i] + model.slack[i]
 
-
 def impact_constraint(model, h):
     """Calculates all the impact categories"""
     return model.impacts[h] == sum(sum(model.ENV_COST_MATRIX[i, j, h] * model.scaling_vector[j] for j in model.ENV_COST_OUT[i, h]) for i in model.ENV_COST_IN[h])
@@ -89,12 +96,10 @@ def inventory_constraint(model, g):
     """Calculates the environmental flows"""
     return model.inv_vector[g] == sum(model.INV_MATRIX[g, j] * model.scaling_vector[j] for j in model.INV_OUT[g])
 
-
 def upper_constraint(model, j):
     """Ensures that variables are within capacities (Maximum production constraint) """
     return model.scaling_vector[j] <= model.UPPER_LIMIT[j]
 
-
 def lower_constraint(model, j):
     """ Minimum production constraint """
     return model.scaling_vector[j] >= model.LOWER_LIMIT[j]
@@ -103,19 +108,31 @@ def upper_env_constraint(model, g):
     """Ensures that variables are within capacities (Maximum production constraint) """
     return model.inv_vector[g] <= model.UPPER_INV_LIMIT[g]
 
+def upper_imp_constraint(model, h):
+    """ Imposes upper limits on selected impact categories """
+    return model.impacts[h] <= model.UPPER_IMP_LIMIT[h]
+
+
 def slack_constraint(model, j):
     """ Slack variable upper limit for activities where supply is specified instead of demand """
     return model.slack[j] <= 1e20 * model.SUPPLY[j]
 
-
 def objective_function(model):
     """Objective is a sum over all indicators with weights. Typically, the indicator of study has weight 1, the rest 0"""
     return sum(model.impacts[h] * model.WEIGHTS[h] for h in model.INDICATOR)
 
 def calculate_methods(instance, lci_data, methods):
-    '''
-    This function calculates the impacts if a method with weight 0 has been specified
-    '''
+    """
+    Calculates the impacts if a method with weight 0 has been specified.
+
+    Args:
+        instance: The Pyomo model instance.
+        lci_data (dict): LCI data containing matrices and mappings.
+        methods (dict): Methods for environmental impact assessment.
+
+    Returns:
+        instance: The updated Pyomo model instance with calculated impacts.
+    """
     import scipy.sparse as sparse
     import numpy as np
     matrices = lci_data['matrices']
@@ -133,9 +150,16 @@ def calculate_methods(instance, lci_data, methods):
     return instance
 
 def calculate_inv_flows(instance, lci_data):
-    '''
-    This function calculates elementary flows post-optimization
-    '''
+    """
+    Calculates elementary flows post-optimization.
+
+    Args:
+        instance: The Pyomo model instance.
+        lci_data (dict): LCI data containing matrices and mappings.
+
+    Returns:
+        instance: The updated Pyomo model instance with calculated intervention flows.
+    """
     import numpy as np
     intervention_matrix = lci_data['intervention_matrix']
     try:
@@ -149,7 +173,15 @@ def calculate_inv_flows(instance, lci_data):
 
 
 def instantiate(model_data):
-    """ This function builds an instance of the optimization model with specific data and objective function"""
+    """
+    Builds an instance of the optimization model with specific data and objective function.
+
+    Args:
+        model_data (dict): Data dictionary for the optimization model.
+
+    Returns:
+        ConcreteModel: The instantiated Pyomo model.
+    """
     print('Creating Instance')
     model = create_model()
     problem = model.create_instance(model_data, report_timing=False)
@@ -157,17 +189,31 @@ def instantiate(model_data):
     return problem
 
 
-def solve_model(model_instance, gams_path, options=None):
-    """Solves the instance of the optimization model. """
-    if gams_path is not False:
+def solve_model(model_instance, gams_path=None, solver_name=None, options=None):
+    """
+    Solves the instance of the optimization model.
+
+    Args:
+        model_instance (ConcreteModel): The Pyomo model instance.
+        gams_path (str, optional): Path to the GAMS solver. If None, GAMS will not be used.
+        solver_name (str, optional): The solver to use ('highs', 'gams', or 'ipopt'). Defaults to 'highs' unless gams_path is provided.
+        options (list, optional): Additional options for the solver.
+
+    Returns:
+        tuple: Results of the optimization and the updated model instance.
+    """
+    results = None
+
+    # Use GAMS if gams_path is specified
+    if gams_path and (solver_name is None or solver_name.lower() == 'gams'):
         pyo.pyomo.common.Executable('gams').set_path(gams_path)
         solver = pyo.SolverFactory('gams')
         print('GAMS solvers library availability:', solver.available())
         print('Solver path:', solver.executable())
 
         io_options = {
-            'mtype': 'lp',                      # Type of problem (lp, nlp, mip, minlp)
-            'solver': 'CPLEX',                  # Name of solver
+            'mtype': 'lp',  # Type of problem (lp, nlp, mip, minlp)
+            'solver': 'CPLEX',  # Name of solver
         }
 
         if options is None:
@@ -188,17 +234,31 @@ def solve_model(model_instance, gams_path, options=None):
             model_instance,
             keepfiles=True,
             symbolic_solver_labels=True,
-            tee=True,
-            report_timing=True,
+            tee=False,
+            report_timing=False,
             io_options=io_options,
             add_options=options
         )
 
         model_instance.solutions.load_from(results)
 
+    # Use IPOPT if explicitly specified
+    elif solver_name and solver_name.lower() == 'ipopt':
+        opt = pyo.SolverFactory('ipopt')
+        if options:
+            for option in options:
+                opt.options[option] = True
+        results = opt.solve(model_instance)
+
+    # Default to HiGHS if no solver specified or if solver_name is 'highs'
     else:
-        from pyomo.contrib import appsi
         opt = appsi.solvers.Highs()
         results = opt.solve(model_instance)
 
+    return results, model_instance
+
+
+    print('Optimization problem solved')
+    ## TODO: Add a check for infeasibility and other solver errors
+
     return results, model_instance
\ No newline at end of file
diff --git a/pulpo/utils/saver.py b/pulpo/utils/saver.py
index f151223..9dfa6ea 100644
--- a/pulpo/utils/saver.py
+++ b/pulpo/utils/saver.py
@@ -5,8 +5,21 @@
 from IPython.display import display
 
 def save_results(instance, project, database, choices, constraints, demand, process_map, itervention_map, directory, name):
-    """ TODO Imporve readability and structure ...
-    There must be a better way to save the outputs of a pyomo model than this code I developed in 2020 """
+    """
+    Saves the results of a Pyomo optimization model to an Excel file.
+
+    Args:
+        instance: The Pyomo model instance.
+        project (str): Name of the project.
+        database (str): Name of the database.
+        choices (dict): Choices for the model.
+        constraints (dict): Constraints applied during optimization.
+        demand (dict): Demand data used in optimization.
+        process_map (dict): Mapping of process IDs to descriptions.
+        itervention_map (dict): Mapping of intervention IDs to descriptions.
+        directory (str): Directory to save the results file.
+        name (str): Name of the results file.
+    """
     # Check if data/results folder exists, if not create it
     Path(directory + '/results').mkdir(parents=True, exist_ok=True)
 
@@ -27,7 +40,7 @@ def save_results(instance, project, database, choices, constraints, demand, proc
                 data = [(k, itervention_map[k], v) for k, v in v._data.items()]
             else:
                 data = [(k, process_map[k], v) for k, v in v._data.items()]
-            df = pd.DataFrame(data, columns=['ID', 'Activity', 'Value'])
+            df = pd.DataFrame(data, columns=['ID', 'Process', 'Value'])
         except:
             data = [(k, v) for k, v in v._data.items()]
             df = pd.DataFrame(data, columns=['Key', 'Value'])
@@ -44,14 +57,17 @@ def save_results(instance, project, database, choices, constraints, demand, proc
         for alt in choices[choice]:
             temp_dict.append((alt, i, instance.scaling_vector[process_map[alt.key]]))
             i+=1
-        metadata[(choice, 'Activity')] = {'Activity ' + str(i): process_map[process_map[alt.key]] for alt, i, val in temp_dict}
-        metadata[(choice, 'Capacity')] = {'Activity ' + str(i): choices[choice][alt] for alt, i, val in temp_dict}
-        metadata[(choice, 'Value')] = {'Activity ' + str(i): x for alt, i, x in temp_dict}
+        metadata[(choice, 'Process')] = {'Process ' + str(i): process_map[process_map[alt.key]] for alt, i, val in temp_dict}
+        metadata[(choice, 'Capacity')] = {'Process ' + str(i): choices[choice][alt] for alt, i, val in temp_dict}
+        metadata[(choice, 'Value')] = {'Process ' + str(i): x for alt, i, x in temp_dict}
 
     pd.DataFrame(metadata).to_excel(writer, sheet_name='choices')
 
     metadata = {}
-    metadata['Demand'] = {process_map[process_map[key]]: demand[key] for key in demand}
+    metadata['Demand'] = {
+        process_map[process_map[key]] if key in process_map else key: demand[key]
+        for key in demand
+    }
     pd.DataFrame(metadata).to_excel(writer, sheet_name='demand')
 
     metadata = {}
@@ -62,9 +78,24 @@ def save_results(instance, project, database, choices, constraints, demand, proc
     writer.close()
     return
 
-def summarize_results(instance, project, database, choices, constraints, demand, process_map, zeroes):
+def summarize_results(instance, choices, constraints, demand, process_map, zeroes):
+    """
+    Summarizes the results of the optimization and prints them to the console.
+
+    Args:
+        instance: The Pyomo model instance.
+        choices (dict): Choices for the model.
+        constraints (dict): Constraints applied during optimization.
+        demand (dict): Demand data used in optimization.
+        process_map (dict): Mapping of process IDs to descriptions.
+        zeroes (bool): Whether to include zero values in the summary.
+    """
     metadata = {}
-    metadata['Demand'] = {process_map[process_map[key]]: demand[key] for key in demand}
+    metadata['Demand'] = {
+        process_map[process_map[key]] if key in process_map else key: demand[key]
+        for key in demand
+    }
+
     print('The following demand / functional unit has been specified: ')
     display(pd.DataFrame(metadata))
 
@@ -80,7 +111,7 @@ def summarize_results(instance, project, database, choices, constraints, demand,
         if v.name == 'impacts':
             try:
                 data = [(k, process_map[k], v) for k, v in v._data.items()]
-                df = pd.DataFrame(data, columns=['ID', 'Activity', 'Value'])
+                df = pd.DataFrame(data, columns=['ID', 'Process', 'Value'])
             except:
                 data = [(k, v) for k, v in v._data.items()]
                 df = pd.DataFrame(data, columns=['Key', 'Value'])
@@ -91,7 +122,7 @@ def summarize_results(instance, project, database, choices, constraints, demand,
         if v.name == 'impacts_calculated':
             try:
                 data = [(k, process_map[k], v) for k, v in v._data.items()]
-                df = pd.DataFrame(data, columns=['ID', 'Activity', 'Value'])
+                df = pd.DataFrame(data, columns=['ID', 'Process', 'Value'])
             except:
                 data = [(k, v) for k, v in v._data.items()]
                 df = pd.DataFrame(data, columns=['Key', 'Value'])
@@ -108,9 +139,9 @@ def summarize_results(instance, project, database, choices, constraints, demand,
             if zeroes == False or instance.scaling_vector[process_map[alt.key]] != 0:
                 temp_dict.append((alt, i, instance.scaling_vector[process_map[alt.key]]))
                 i += 1
-        metadata[(choice, 'Activity')] = {'Activity ' + str(i): process_map[process_map[alt.key]] for alt, i, val in temp_dict}
-        metadata[(choice, 'Capacity')] = {'Activity ' + str(i): choices[choice][alt] for alt, i, val in temp_dict}
-        metadata[(choice, 'Value')] = {'Activity ' + str(i): x for alt, i, x in temp_dict}
+        metadata[(choice, 'Process')] = {'Process ' + str(i): process_map[process_map[alt.key]] for alt, i, val in temp_dict}
+        metadata[(choice, 'Capacity')] = {'Process ' + str(i): choices[choice][alt] for alt, i, val in temp_dict}
+        metadata[(choice, 'Value')] = {'Process ' + str(i): x for alt, i, x in temp_dict}
         print(choice)
         display(pd.DataFrame(metadata))
 
diff --git a/requirements.txt b/requirements.txt
index c45e797..c2ad9ab 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,8 @@
-brightway2==2.4.3
-pyomo==6.6.1
-highspy==1.5.3
+bw2calc==1.8.2
+bw2data==3.6.6
+fs==2.4.16
+pyomo<=6.6.2
+highspy==1.8.0
 ipython==8.14.0
-jupyterlab
+jupyter
 tqdm
\ No newline at end of file
diff --git a/setup.py b/setup.py
index a1e717b..8c19282 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
 
 setup(
     name="pulpo-dev",
-    version="0.0.2",
+    version="0.1.2",
     description="pulpo package for optimization in LCI databases",
     author="Fabian Lechtenberg",
     author_email="fabian.lechtenberg@upc.edu",
@@ -17,11 +17,12 @@
         "Operating System :: OS Independent",
     ],
     packages=find_packages(),
-    python_requires=">=3.9, <=3.10",
+    python_requires=">=3.10, <=3.12",
     install_requires=[
-        "brightway2==2.4.3",
-        "pyomo==6.6.1",
-        "highspy==1.5.3",
+        "bw2data==3.6.6",
+        "fs==2.4.16",
+        "pyomo<=6.6.2",
+        "highspy==1.8.0",
         "ipython==8.14.0",
         "jupyterlab",
         "tqdm",


Test patch:
diff --git a/pulpo/utils/tests.py b/pulpo/utils/tests.py
index a219266..c7221ff 100644
--- a/pulpo/utils/tests.py
+++ b/pulpo/utils/tests.py
@@ -1,13 +1,12 @@
-from brightway2 import Database, projects, Method, databases, methods
-import brightway2 as bw
+import bw2data as bd
 import copy
 import pulpo as pulpo
-from pulpo.utils.bw_parser import import_data, retrieve_methods, retrieve_envflows, retrieve_activities
+from pulpo.utils.bw_parser import import_data, retrieve_methods, retrieve_env_interventions, retrieve_processes
 from pulpo import pulpo
 
 def setup_test_db():
     # Set the current project to "sample_project"
-    projects.set_current("sample_project")
+    bd.projects.set_current("sample_project")
 
     # Keys
     # Biosphere
@@ -23,8 +22,8 @@ def setup_test_db():
     e_car_key = ('technosphere', 'e-Car')
 
     # Define biosphere flows and create the "biosphere" database if it doesn't exist
-    if "biosphere" not in databases:
-        biosphere_db = Database("biosphere")
+    if "biosphere" not in bd.databases:
+        biosphere_db = bd.Database("biosphere")
         biosphere_data = {
             ("biosphere", "CO2"): {
                 "name": "Carbon dioxide, fossil",
@@ -54,8 +53,8 @@ def setup_test_db():
         biosphere_db.write(biosphere_data)
 
     # Create the "technosphere" database if it doesn't exist
-    if "technosphere" not in databases:
-        technosphere_db = Database("technosphere")
+    if "technosphere" not in bd.databases:
+        technosphere_db = bd.Database("technosphere")
         technosphere_db.write({})
         process_data = [
             ("oil extraction", "kg", "GLO", "oil"),
@@ -103,10 +102,10 @@ def setup_test_db():
 
     # Loop through the list of methods and deregister each one
     # Create a copy of the methods list
-    methods_copy = copy.deepcopy(methods)
+    methods_copy = copy.deepcopy(bd.methods)
     # Loop through the copy of methods and deregister each one
     for method in methods_copy:
-        Method(method).deregister()
+        bd.Method(method).deregister()
 
     # Define LCIA methods and CFs
     methods_data = [
@@ -116,7 +115,7 @@ def setup_test_db():
     ]
 
     for method_name, unit, num_cfs, abbreviation, description, filename, flow_code, flow_list in methods_data:
-        method = Method(("my project", method_name))
+        method = bd.Method(("my project", method_name))
         method.register(**{
             "unit": unit,
             "num_cfs": num_cfs,
@@ -128,13 +127,13 @@ def setup_test_db():
 
 def sample_lcia():
     # Load the technosphere database
-    bw.projects.set_current('sample_project')
-    technosphere_db = bw.Database("technosphere")
+    bd.projects.set_current('sample_project')
+    technosphere_db = bd.Database("technosphere")
     # Perform LCA with FU 1 for all activities
     act = {act.key: 1 for act in technosphere_db}
     # Perform Multi-LCA
-    bw.calculation_setups['multiLCA'] = {'inv': [act], 'ia': list(methods)}
-    myMultiLCA = bw.MultiLCA('multiLCA')
+    bd.calculation_setups['multiLCA'] = {'inv': [act], 'ia': list(bd.methods)}
+    myMultiLCA = bd.MultiLCA('multiLCA')
     results = [round(x, 5) for x in myMultiLCA.results[0]]
     return results
 
@@ -150,17 +149,17 @@ def test_import_data():
         "('my project', 'resources')": 1,
     }
 
-    result = import_data('sample_project', 'technosphere', methods)
+    result = import_data('sample_project', 'technosphere', methods, 'biosphere')
 
-    assert [idx for idx in result] == ['matrices', 'biosphere', 'technosphere', 'activity_map']
-    assert result['technosphere'].shape == (5, 5)
-    assert result['biosphere'].shape == (4, 5)
-    assert result['activity_map'] == {('technosphere', 'oil extraction'): 0, ('technosphere', 'lignite extraction'): 1, ('technosphere', 'steam cycle'): 2, ('technosphere', 'wind turbine'): 3, ('technosphere', 'e-Car'): 4, 2: 'steam cycle | electricity | GLO', 1: 'lignite extraction | lignite | GLO', 0: 'oil extraction | oil | GLO', 3: 'wind turbine | electricity | GLO', 4: 'e-Car | transport | GLO'}
+    assert [idx for idx in result] == ['matrices', 'intervention_matrix', 'technology_matrix', 'process_map', 'intervention_map']
+    assert result['technology_matrix'].shape == (5, 5)
+    assert result['intervention_matrix'].shape == (4, 5)
+    assert result['process_map'] == {('technosphere', 'oil extraction'): 0, ('technosphere', 'lignite extraction'): 1, ('technosphere', 'steam cycle'): 2, ('technosphere', 'wind turbine'): 3, ('technosphere', 'e-Car'): 4, 2: 'steam cycle | electricity | GLO', 1: 'lignite extraction | lignite | GLO', 0: 'oil extraction | oil | GLO', 3: 'wind turbine | electricity | GLO', 4: 'e-Car | transport | GLO'}
 
 def test_retrieve_activities():
-    key = retrieve_activities('sample_project', 'technosphere', keys=["('technosphere', 'wind turbine')"])
-    name = retrieve_activities('sample_project', 'technosphere', activities=['e-Car'])
-    location = retrieve_activities('sample_project', 'technosphere', locations=['GLO'])
+    key = retrieve_processes('sample_project', 'technosphere', keys=["('technosphere', 'wind turbine')"])
+    name = retrieve_processes('sample_project', 'technosphere', activities=['e-Car'])
+    location = retrieve_processes('sample_project', 'technosphere', locations=['GLO'])
     assert key[0]['name'] == "wind turbine"
     assert name[0]['name'] == "e-Car"
     assert len(location) == 5
@@ -172,7 +171,7 @@ def test_retrieve_methods():
     assert multi_result == [('my project', 'climate change'), ('my project', 'air quality'), ('my project', 'resources')]
 
 def test_retrieve_envflows():
-    result = retrieve_envflows('sample_project', biosphere='biosphere', keys="('biosphere', 'PM')")
+    result = retrieve_env_interventions('sample_project', intervention_matrix='biosphere', keys="('biosphere', 'PM')")
     assert result[0]['name'] == 'Particulate matter, industrial'
 
 def test_pulpo():
@@ -183,6 +182,7 @@ def test_pulpo():
                "('my project', 'resources')": 0}
 
     worker = pulpo.PulpoOptimizer(project, database, methods, '')
+    worker.intervention_matrix = 'biosphere'
     worker.get_lci_data()
     eCar = worker.retrieve_activities(reference_products='transport')
     demand = {eCar[0]: 1}
diff --git a/tests/generic_database.py b/tests/generic_database.py
new file mode 100644
index 0000000..4b859e2
--- /dev/null
+++ b/tests/generic_database.py
@@ -0,0 +1,489 @@
+import copy
+import numpy as np
+np.NaN = np.nan
+import bw2data as bd
+
+# Helper function to convert integers to Latin letters
+def int_to_latin(num):
+    """Converts an integer to a Latin letter (e.g., 1 -> 'A', 2 -> 'B')."""
+    return chr(64 + num)  # Converts 1 to 'A', 2 to 'B', etc.
+
+# Helper function to convert integers to Greek letters
+def int_to_greek(num):
+    """Converts an integer to a Greek letter (e.g., 1 -> 'α', 2 -> 'β')."""
+    greek_letters = ['α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ']
+    return greek_letters[num - 1] if num <= len(greek_letters) else str(num)
+
+class Process:
+    """Represents a production process for a specific product in a specific region."""
+
+    def __init__(self, product_id, technology_id, region_id):
+        """Initializes a process with product, technology, and region identifiers."""
+        self.product_id = product_id
+        self.technology_id = int_to_latin(technology_id)
+        self.region_id = int_to_greek(region_id)
+        self.process_id = f"{product_id}_{self.technology_id}_{self.region_id}"
+        self.inputs = []  # List of tuples [(input_market_id, quantity), ...]
+        self.environmental_flows = []  # List of tuples [(flow_id, quantity), ...]
+
+    def add_input(self, input_market, quantity):
+        """Adds an input market with a specified quantity to the process."""
+        self.inputs.append((input_market.market_id, quantity))
+
+    def add_environmental_flow(self, flow_id, quantity):
+        """Adds an environmental flow with a specified quantity to the process."""
+        self.environmental_flows.append((flow_id, quantity))
+
+    def generate_inputs(self, potential_inputs, n_inputs):
+        """Randomly generates and normalizes inputs for the process from potential markets."""
+        if len(potential_inputs) < n_inputs:
+            num_inputs = len(potential_inputs)
+        else:
+            num_inputs = np.random.randint(0, n_inputs + 1)
+
+        if num_inputs > 0:
+            selected_inputs = np.random.choice(potential_inputs, num_inputs, replace=False)
+
+            # Generate random quantities for each selected input
+            quantities = np.random.rand(num_inputs)
+
+            # Normalize to ensure the sum is <= 1 (in order to have an M-matrix --- diagonal dominance)
+            normalized_quantities = quantities / quantities.sum()
+
+            # Scale down to ensure the sum is strictly smaller than 1
+            scaling_factor = np.random.rand()  # A random factor between 0 and 1
+            normalized_quantities *= scaling_factor
+
+            for inp, quantity in zip(selected_inputs, normalized_quantities):
+                self.add_input(inp, quantity)
+
+    def generate_environmental_flows(self, flow_ids, n_flows):
+        """Randomly generates environmental flows for the process."""
+        num_flows = np.random.randint(1, n_flows + 1)
+        selected_flows = np.random.choice(flow_ids, num_flows, replace=False)
+        for flow_id in selected_flows:
+            quantity = np.random.rand()
+            self.add_environmental_flow(str(flow_id), quantity)
+
+    def __repr__(self):
+        return (f"Process(product_id={self.product_id}, "
+                f"technology_id={self.technology_id}, "
+                f"region_id={self.region_id}, "
+                f"inputs={self.inputs}, "
+                f"environmental_flows={self.environmental_flows})")
+
+class Market:
+    """Represents a market for a specific product in a specific region."""
+
+    def __init__(self, product_id, region_id):
+        """Initializes a market with product and region identifiers."""
+        self.product_id = product_id
+        self.region_id = int_to_greek(region_id)
+        self.market_id = f"{product_id}_{self.region_id}"
+        self.composition = []  # List of tuples [(process_id, share), ...]
+
+    def add_process(self, process, share):
+        """Adds a process with a specified share to the market composition."""
+        self.composition.append((process.process_id, share))
+
+    def generate_market_composition(self, candidate_processes):
+        """Randomly generates a market composition from candidate processes."""
+        included_processes = []
+        for process in candidate_processes:
+            if np.random.rand() < 0.75:
+                share = np.random.rand()
+                included_processes.append((process, share))
+
+        # Ensure at least one process is included
+        if not included_processes:
+            process = np.random.choice(candidate_processes)
+            share = np.random.rand()
+            included_processes.append((process, share))
+
+        for process, share in included_processes:
+            self.add_process(process, share)
+
+        # Normalize shares to sum to 1
+        total_share = sum(share for _, share in self.composition)
+        if total_share > 0:
+            self.composition = [(pid, share / total_share) for pid, share in self.composition]
+
+    def __repr__(self):
+        return (f"Market(product_id={self.product_id}, "
+                f"region_id={self.region_id}, "
+                f"composition={self.composition})")
+
+def create_system(n_prod, n_proc, n_reg, n_inputs, n_flows, seed=None):
+    """
+    Creates the technosphere and biosphere components for the system.
+
+    Args:
+        n_prod (int): Number of products.
+        n_proc (int): Maximum number of processes per product.
+        n_reg (int): Number of regions.
+        n_inputs (int): Maximum number of inputs per process.
+        n_flows (int): Number of environmental flows.
+        seed (int, optional): Seed for reproducibility.
+
+    Returns:
+        processes (list): List of Process instances.
+        markets (list): List of Market instances.
+        flow_ids (list): List of environmental flow IDs.
+    """
+    if seed is not None:
+        np.random.seed(seed)
+
+    processes = []
+    markets = []
+    flow_ids = [f"e{i}" for i in range(1, n_flows + 1)]  # Environmental flow IDs: e1, e2, e3, ...
+
+    # Step 1: Initialize Processes
+    for product_id in range(1, n_prod + 1):
+        regions = list(range(1, n_reg + 1))
+        np.random.shuffle(regions)
+        active_regions = regions[:np.random.randint(1, n_reg + 1)]
+
+        for region_id in active_regions:
+            technologies = list(range(1, n_proc + 1))
+            np.random.shuffle(technologies)
+            active_technologies = technologies[:np.random.randint(1, n_proc + 1)]
+
+            for technology_id in active_technologies:
+                proc = Process(product_id, technology_id, region_id)
+                proc.generate_environmental_flows(flow_ids, n_flows)
+                processes.append(proc)
+
+    # Step 2: Generate Markets
+    for product_id in range(1, n_prod + 1):
+        regions = list(range(1, n_reg + 1))
+        np.random.shuffle(regions)
+
+        for region_id in regions:
+            candidate_processes = [proc for proc in processes if proc.product_id == product_id and proc.region_id == int_to_greek(region_id)]
+            if candidate_processes:
+                market = Market(product_id, region_id)
+                market.generate_market_composition(candidate_processes)
+                markets.append(market)
+
+    # Step 3: Assign Inputs to Processes
+    for proc in processes:
+        potential_inputs = [market for market in markets if market.region_id == proc.region_id]
+        if potential_inputs:
+            proc.generate_inputs(potential_inputs, n_inputs)
+
+    return processes, markets, flow_ids
+
+def assemble_technosphere_matrix(processes, markets):
+    """
+    Assembles the technosphere matrix from processes and markets.
+
+    Args:
+        processes (list): List of Process instances.
+        markets (list): List of Market instances.
+
+    Returns:
+        np.ndarray: Technosphere matrix.
+    """
+    n = len(processes) + len(markets)
+    A = np.zeros((n, n))
+
+    process_index = {proc.process_id: idx for idx, proc in enumerate(processes)}
+    market_index = {market.market_id: idx + len(processes) for idx, market in enumerate(markets)}
+
+    # Fill in the technosphere matrix
+    for proc in processes:
+        i = process_index[proc.process_id]
+        A[i, i] = 1  # Diagonal element for the process
+
+        for market_id, qty in proc.inputs:
+            j = market_index[market_id]
+            A[j, i] = -qty  # Negative value for input
+
+    for market in markets:
+        i = market_index[market.market_id]
+        A[i, i] = 1  # Diagonal element for the market
+
+        for process_id, share in market.composition:
+            j = process_index[process_id]
+            A[j, i] = -share  # Market composition values
+
+    return A
+
+def assemble_biosphere_matrix(processes, flow_ids):
+    """
+    Assembles the biosphere matrix from processes and environmental flows.
+
+    Args:
+        processes (list): List of Process instances.
+        flow_ids (list): List of environmental flow IDs.
+
+    Returns:
+        np.ndarray: Biosphere matrix.
+    """
+    n = len(processes) + len(flow_ids)
+    B = np.zeros((len(flow_ids), len(processes)))
+
+    process_index = {proc.process_id: idx for idx, proc in enumerate(processes)}
+    flow_index = {flow_id: idx for idx, flow_id in enumerate(flow_ids)}
+
+    # Fill in the biosphere matrix
+    for proc in processes:
+        j = process_index[proc.process_id]
+        for flow_id, qty in proc.environmental_flows:
+            i = flow_index[flow_id]
+            B[i, j] = qty  # Positive value for environmental flow
+
+    return B
+
+class CharacterizationFactor:
+    """Represents a characterization factor for an environmental flow under a specific method."""
+
+    def __init__(self, method_id, flow_id, factor):
+        """Initializes a characterization factor with a method, flow, and factor value."""
+        self.method_id = method_id  # ID of the method (e.g., m1, m2, etc.)
+        self.flow_id = flow_id      # ID of the environmental flow (e.g., e1, e2, etc.)
+        self.factor = factor        # Characterization factor value
+
+    def __repr__(self):
+        return (f"CharacterizationFactor(method_id={self.method_id}, "
+                f"flow_id={self.flow_id}, "
+                f"factor={self.factor})")
+
+def create_characterization_factors(n_methods, n_flows, seed=None):
+    """
+    Creates characterization factors for each method and environmental flow.
+
+    Args:
+        n_methods (int): Number of impact assessment methods.
+        n_flows (int): Number of environmental flows.
+        seed (int, optional): Seed for reproducibility.
+
+    Returns:
+        methods (list): List of method IDs.
+        characterization_factors (list): List of CharacterizationFactor instances.
+    """
+    if seed is not None:
+        np.random.seed(seed)
+
+    methods = [f"m{i}" for i in range(1, n_methods + 1)]  # Method IDs: m1, m2, m3, ...
+    characterization_factors = []
+
+    for method_id in methods:
+        num_factors = np.random.randint(1, n_flows + 1)  # Randomly determine the number of factors for this method
+        selected_flows = np.random.choice([f"e{i}" for i in range(1, n_flows + 1)], num_factors, replace=False)
+        for flow_id in selected_flows:
+            factor = np.random.rand()  # Generate a random factor value
+            characterization_factors.append(CharacterizationFactor(method_id, flow_id, factor))
+
+    return methods, characterization_factors
+
+def assemble_characterization_matrices(characterization_factors, flow_ids, methods):
+    """
+    Assembles characterization factor matrices for each method.
+
+    Args:
+        characterization_factors (list): List of CharacterizationFactor instances.
+        flow_ids (list): List of environmental flow IDs.
+        methods (list): List of method IDs.
+
+    Returns:
+        dict: Characterization matrices keyed by method ID.
+    """
+    n_methods = len(methods)
+    n_flows = len(flow_ids)
+    C_matrices = {method: np.zeros((n_flows, 1)) for method in methods}  # Initialize matrices
+
+    flow_index = {flow_id: idx for idx, flow_id in enumerate(flow_ids)}
+
+    for cf in characterization_factors:
+        method_id = cf.method_id
+        flow_id = cf.flow_id
+        factor = cf.factor
+        i = flow_index[flow_id]
+        C_matrices[method_id][i, 0] = factor
+
+    return C_matrices
+
+def setup_biosphere_db(flow_ids):
+    """Setup or update the biosphere database with environmental flows."""
+
+    # Create biosphere database if it doesn't exist
+    if 'biosphere3' not in bd.databases:
+        biosphere_db = bd.Database('biosphere3')
+        biosphere_data = {}
+
+        # Define environmental flows
+        for flow_id in flow_ids:
+            biosphere_data[('biosphere3', flow_id)] = {
+                'name': f'Environmental flow {flow_id}',
+                'categories': ('environmental impact', ''),
+                'type': 'emission',
+                'unit': 'kg',
+            }
+
+        biosphere_db.write(biosphere_data)
+        print('Biosphere database created')
+    else:
+        print('Biosphere database already exists')
+
+def setup_technosphere_db(database, processes, markets):
+    """Setup the technosphere database with generated processes and markets."""
+    if 'generated_technosphere_db' not in bd.databases:
+        technosphere_db = bd.Database(database)
+        technosphere_db.write({})
+
+        # Register each process
+        for proc in processes:
+            act = technosphere_db.new_activity(proc.process_id)
+            act['unit'] = 'unit'  # Define the unit appropriately
+            act['location'] = proc.region_id
+            act['name'] = f'Process {proc.process_id}'
+            act['reference product'] = f'Product {proc.product_id}'
+            act.new_exchange(amount=1.0, input=act.key, type='production').save()
+            act.save()
+
+        # Register each market
+        for market in markets:
+            act = technosphere_db.new_activity(market.market_id)
+            act['unit'] = 'unit'  # Define the unit appropriately
+            act['location'] = market.region_id
+            act['name'] = f'Market {market.market_id}'
+            act['reference product'] = f'Product {market.product_id}'
+            act.new_exchange(amount=1.0, input=act.key, type='production').save()
+            act.save()
+
+        print('Technosphere database created')
+    else:
+        print('Technosphere database already exists')
+
+def add_exchanges_to_db(database, processes, markets):
+    """Add exchanges to the technosphere and market activities in the database."""
+    technosphere_db = bd.Database(database)
+    biosphere_db = bd.Database('biosphere3')
+
+    # Add exchanges for processes
+    for proc in processes:
+        act = technosphere_db.get(proc.process_id)
+
+        # Add market inputs
+        for market_id, qty in proc.inputs:
+            market_act = technosphere_db.get(market_id)
+            act.new_exchange(amount=qty, input=market_act.key, type='technosphere').save()
+
+        # Add environmental flows (biosphere exchanges)
+        for flow_id, qty in proc.environmental_flows:
+            flow_act = biosphere_db.get(flow_id)
+            act.new_exchange(amount=qty, input=flow_act.key, type='biosphere').save()
+
+        act.save()
+
+    # Add exchanges for markets
+    for market in markets:
+        act = technosphere_db.get(market.market_id)
+
+        # Add process contributions to the market
+        for process_id, share in market.composition:
+            process_act = technosphere_db.get(process_id)
+            act.new_exchange(amount=share, input=process_act.key, type='technosphere').save()
+
+        act.save()
+
+    print('Exchanges added to the technosphere and market activities in the database')
+
+def setup_lcia_methods(characterization_factors, methods):
+    """Setup LCIA methods based on generated characterization factors."""
+    # Deregister existing methods
+    methods_copy = copy.deepcopy(bd.methods)
+    for method in methods_copy:
+        bd.Method(method).deregister()
+
+    # Register new LCIA methods
+    for method_id in methods:
+        method = bd.Method(('generated_system_example', method_id))
+        method.register()
+
+        cfs = []
+        for cf in characterization_factors:
+            if cf.method_id == method_id:
+                flow_key = ('biosphere3', cf.flow_id)
+                cfs.append((flow_key, cf.factor))
+
+        method.write(cfs)
+
+    print('LCIA methods and CFs defined')
+
+def setup_generated_system(project, database, processes, markets, flow_ids, characterization_factors, methods):
+    """Main function to set up the entire Brightway2 database."""
+    bd.projects.set_current(project)
+    setup_biosphere_db(flow_ids)
+    setup_technosphere_db(database, processes, markets)
+    add_exchanges_to_db(database, processes, markets)
+    setup_lcia_methods(characterization_factors, methods)
+
+
+def setup_generic_db(project, database, n_prod, n_proc, n_reg, n_inputs, n_flows, n_methods, seed=None,
+                     return_data=False):
+    """
+    Sets up a generic LCI database in Brightway2 with specified parameters.
+
+    Args:
+        project (str): Name of the Brightway2 project to create or use.
+        database (str): Name of the Brightway2 database to create or use.
+        n_prod (int): Number of products to generate.
+        n_proc (int): Maximum number of processes per product.
+        n_reg (int): Number of regions where processes can be active.
+        n_inputs (int): Maximum number of inputs per process.
+        n_flows (int): Number of environmental flows to generate.
+        n_methods (int): Number of impact assessment methods to create.
+        seed (int, optional): Seed for reproducibility of random data generation.
+        return_data (bool): If True, returns the generated matrices (technosphere, biosphere, and
+            characterization).
+
+    Returns:
+        tuple: If `return_data` is True, returns a tuple containing:
+            - technosphere_matrix (np.ndarray): The technosphere matrix.
+            - biosphere_matrix (np.ndarray): The biosphere matrix.
+            - characterization_matrices (dict): A dictionary of characterization factor matrices.
+    """
+
+    # Set the project, deleting existing databases if the project exists
+    if project in bd.projects:
+        bd.projects.set_current(project)
+        print(f"Project '{project}' already exists. Deleting existing databases.")
+        if database in bd.databases:
+            del bd.databases[database]
+        if 'biosphere3' in bd.databases:
+            del bd.databases['biosphere3']
+    else:
+        bd.projects.set_current(project)
+        print(f"Project '{project}' does not exist. Creating new project.")
+
+    print("Proceeding with creating both technosphere and biosphere databases.")
+
+    # Step 1: Create the technosphere and biosphere components
+    processes, markets, flow_ids = create_system(n_prod, n_proc, n_reg, n_inputs, n_flows, seed=seed)
+
+    # Step 2: Assemble the technosphere matrix
+    technosphere_matrix = assemble_technosphere_matrix(processes, markets)
+
+    # Step 3: Assemble the biosphere matrix
+    biosphere_matrix = assemble_biosphere_matrix(processes, flow_ids)
+
+    # Step 4: Create the characterization factors for impact assessment methods
+    methods, characterization_factors = create_characterization_factors(n_methods, n_flows, seed=seed)
+
+    # Step 5: Assemble the characterization factor matrices
+    characterization_matrices = assemble_characterization_matrices(characterization_factors, flow_ids, methods)
+
+    # Set up the Brightway2 database using the generated data
+    setup_generated_system(project, database, processes, markets, flow_ids, characterization_factors, methods)
+
+    if return_data:
+        return technosphere_matrix, biosphere_matrix, characterization_matrices
+
+
+def main():
+    setup_generic_db(project="generic_db_project", database="generic_db", n_prod=5, n_proc=3, n_reg=3, n_inputs=4, n_flows=4, n_methods=2, seed=None, return_data=False)
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/rice_database.py b/tests/rice_database.py
new file mode 100644
index 0000000..8d7d07f
--- /dev/null
+++ b/tests/rice_database.py
@@ -0,0 +1,183 @@
+import copy
+import numpy as np
+import bw2data as bd
+
+np.NaN = np.nan  # Ensures compatibility with the latest NumPy versions
+
+def setup_rice_husk_db():
+    # Set the current project to "rice_husk_example"
+    bd.projects.set_current("rice_husk_example")
+
+    # Biosphere keys
+    co2_key = ('biosphere3', 'CO2')
+    ch4_key = ('biosphere3', 'CH4')
+    ef_key = ('biosphere3', 'EF')
+
+    # Define biosphere flows and create the 'biosphere3' database if it doesn't exist
+    if 'biosphere3' not in bd.databases:
+        biosphere_db = bd.Database('biosphere3')
+        biosphere_data = {
+            ('biosphere3', 'CO2'): {
+                'name': 'Carbon dioxide, fossil',
+                'categories': ('climate change', 'GWP 100a'),
+                'type': 'emission',
+                'unit': 'Mt',
+            },
+            ('biosphere3', 'CH4'): {
+                'name': 'Methane, agricultural',
+                'categories': ('climate change', 'GWP 100a'),
+                'type': 'emission',
+                'unit': 'Mt',
+            },
+            ('biosphere3', 'EF'): {
+                'name': 'Economic Flow',
+                'categories': ('economic flow', 'dollar'),
+                'type': 'emission',
+                'unit': 'million dollar',
+            },
+        }
+        biosphere_db.write(biosphere_data)
+
+        print('Elementary flow database created')
+
+    # Technosphere keys
+    rice_factory_key = ('rice_husk_example_db', 'Rice factory')
+    rice_farming_key = ('rice_husk_example_db', 'Rice farming')
+    rice_husk_boiler_key = ('rice_husk_example_db', 'Rice husk boiler')
+    natural_gas_boiler_key = ('rice_husk_example_db', 'Natural gas boiler')
+    wood_pellet_boiler_key = ('rice_husk_example_db', 'Wood pellet boiler')
+    rice_husk_market_key = ('rice_husk_example_db', 'Rice husk market')
+    rice_husk_collection1_key = ('rice_husk_example_db', 'Rice husk collection 1')
+    rice_husk_collection2_key = ('rice_husk_example_db', 'Rice husk collection 2')
+    rice_husk_collection3_key = ('rice_husk_example_db', 'Rice husk collection 3')
+    rice_husk_collection4_key = ('rice_husk_example_db', 'Rice husk collection 4')
+    rice_husk_collection5_key = ('rice_husk_example_db', 'Rice husk collection 5')
+    natural_gas_supply_key = ('rice_husk_example_db', 'Natural gas supply')
+    wood_pellet_supply_key = ('rice_husk_example_db', 'Wood pellet supply')
+    burning_rice_husk_key = ('rice_husk_example_db', 'Burning of rice husk')
+    power_plant_key = ('rice_husk_example_db', 'Power plant')
+    transportation_key = ('rice_husk_example_db', 'Transportation by truck')
+
+    # Create the "rice_husk_example_db" database if it doesn't exist
+    if 'rice_husk_example_db' not in bd.databases:
+        rice_husk_db = bd.Database('rice_husk_example_db')
+        rice_husk_db.write({})
+
+        process_data = [
+            ('Rice factory', 'Mt', 'GLO', 'Processed rice (in Mt)'),
+            ('Rice farming', 'Mt', 'GLO', 'Unprocessed rice (in Mt)'),
+            ('Rice husk boiler', 'TWh', 'GLO', 'Thermal energy from husk boiler (in TWh)'),
+            ('Natural gas boiler', 'TWh', 'GLO', 'Thermal energy from natural gas boiler (in TWh)'),
+            ('Wood pellet boiler', 'TWh', 'GLO', 'Thermal energy from wood pellet boiler (in TWh)'),
+            ('Rice husk market', 'Mt', 'GLO', 'Rice husk at farm (Mt)'),
+            ('Rice husk collection 1', 'Mt', 'GLO', 'Rice husk from region 1 (in Mt)'),
+            ('Rice husk collection 2', 'Mt', 'GLO', 'Rice husk from region 2 (in Mt)'),
+            ('Rice husk collection 3', 'Mt', 'GLO', 'Rice husk from region 3 (in Mt)'),
+            ('Rice husk collection 4', 'Mt', 'GLO', 'Rice husk from region 4 (in Mt)'),
+            ('Rice husk collection 5', 'Mt', 'GLO', 'Rice husk from region 5 (in Mt)'),
+            ('Natural gas supply', 'TWh', 'GLO', 'Natural gas (in TWh)'),
+            ('Wood pellet supply', 'Mt', 'GLO', 'Wood pellets (in Mt)'),
+            ('Burning of rice husk', 'Mt', 'GLO', 'Burned rice husk (in Mt)'),
+            ('Power plant', 'TWh', 'GLO', 'Electricity (in TWh)'),
+            ('Transportation by truck', 'Gt*km', 'GLO', 'Transportation (in Gt*km)'),
+        ]
+
+        # Register each process
+        for name, unit, location, ref_product in process_data:
+            act = rice_husk_db.new_activity(name)
+            act['unit'] = unit
+            act['location'] = location
+            act['name'] = name
+            act['reference product'] = ref_product
+            act.new_exchange(amount=1.0, input=act.key, type='production').save()
+            act.save()
+
+        # Define the technosphere exchanges
+        exchange_data = [
+            # Format: [input_key, target_key, amount, exchange_type]
+            [rice_farming_key, rice_factory_key, 1.15, 'technosphere'],
+            [natural_gas_boiler_key, rice_factory_key, 2.2, 'technosphere'],
+            [power_plant_key, rice_factory_key, 0.08, 'technosphere'],
+            [transportation_key, rice_factory_key, 0.35, 'technosphere'],
+            [rice_husk_market_key, rice_farming_key, -0.6, 'technosphere'],
+            [rice_husk_collection1_key, rice_husk_boiler_key, 0.23, 'technosphere'],
+            [natural_gas_supply_key, natural_gas_boiler_key, 1.1, 'technosphere'],
+            [wood_pellet_supply_key, wood_pellet_boiler_key, 0.25, 'technosphere'],
+            [rice_husk_market_key, rice_husk_collection1_key, 1, 'technosphere'],
+            [rice_husk_market_key, rice_husk_collection2_key, 1, 'technosphere'],
+            [rice_husk_market_key, rice_husk_collection3_key, 1, 'technosphere'],
+            [rice_husk_market_key, rice_husk_collection4_key, 1, 'technosphere'],
+            [rice_husk_market_key, rice_husk_collection5_key, 1, 'technosphere'],
+            [transportation_key, rice_husk_collection1_key, 0.12, 'technosphere'],
+            [transportation_key, rice_husk_collection2_key, 0.24, 'technosphere'],
+            [transportation_key, rice_husk_collection3_key, 0.36, 'technosphere'],
+            [transportation_key, rice_husk_collection4_key, 0.48, 'technosphere'],
+            [transportation_key, rice_husk_collection5_key, 0.60, 'technosphere'],
+            [rice_husk_market_key, burning_rice_husk_key, 2, 'technosphere'],
+            [burning_rice_husk_key, rice_husk_market_key, 1, 'technosphere'],
+            [co2_key, rice_farming_key, 6.14e-1, 'biosphere'],
+            [ch4_key, rice_farming_key, 1.33e-3, 'biosphere'],
+            [co2_key, natural_gas_boiler_key, 2.27e-1, 'biosphere'],
+            [ch4_key, natural_gas_boiler_key, 1.47e-3, 'biosphere'],
+            [co2_key, natural_gas_supply_key, 3.21e-2, 'biosphere'],
+            [ch4_key, natural_gas_supply_key, 1.50e-3, 'biosphere'],
+            [co2_key, wood_pellet_supply_key, 1.50e-1, 'biosphere'],
+            [ch4_key, wood_pellet_supply_key, 2.56e-4, 'biosphere'],
+            [co2_key, power_plant_key, 1.10e-0, 'biosphere'],
+            [ch4_key, power_plant_key, 9.15e-4, 'biosphere'],
+            [co2_key, transportation_key, 5.76e-2, 'biosphere'],
+            [ch4_key, transportation_key, 6.97e-5, 'biosphere'],
+            [ef_key, rice_factory_key, 5.00e1, 'biosphere'],
+            [ef_key, rice_farming_key, 3.60e2, 'biosphere'],
+            [ef_key, natural_gas_supply_key, 1.3e1, 'biosphere'],
+            [ef_key, wood_pellet_supply_key, 7.20e1, 'biosphere'],
+            [ef_key, rice_husk_collection1_key, 4.50e1, 'biosphere'],
+            [ef_key, rice_husk_collection2_key, 3.60e1, 'biosphere'],
+            [ef_key, rice_husk_collection3_key, 2.90e1, 'biosphere'],
+            [ef_key, rice_husk_collection4_key, 2.30e1, 'biosphere'],
+            [ef_key, rice_husk_collection5_key, 1.80e1, 'biosphere'],
+            [ef_key, power_plant_key, 6.50e1, 'biosphere'],
+            [ef_key, transportation_key, 1.70e2, 'biosphere'],
+        ]
+
+        # Add the exchanges to the activities
+        for input_key, target_key, amount, exchange_type in exchange_data:
+            act = [act for act in rice_husk_db if act.key == target_key][0]
+            act.new_exchange(amount=amount, input=input_key, type=exchange_type).save()
+            act.save()
+
+        print('Process database created')
+
+        # Loop through the list of methods and deregister each one
+        # Create a copy of the methods list
+        methods_copy = copy.deepcopy(bd.methods)
+        # Loop through the copy of methods and deregister each one
+        for method in methods_copy:
+            bd.Method(method).deregister()
+
+        # Define LCIA methods and CFs
+        methods_data = [
+            ('climate change', 'Mt CO2eq', 2, 'cc', 'climate change CFs', 'climate_change', 'CO2',
+             [(co2_key, 1), (ch4_key, 25)]),
+            ('air quality', 'ppm', 1, 'aq', 'air quality CFs', 'air_quality', 'PM', [(ch4_key, 25)]),
+            ('economic flow', 'million dollar', 1, 'ef', 'economic flow CFs', 'economic_flow', 'million dollar', [(ef_key, 1)]),
+        ]
+
+        for method_name, unit, num_cfs, abbreviation, description, filename, flow_code, flow_list in methods_data:
+            method = bd.Method(('my project', method_name))
+            method.register(**{
+                'unit': unit,
+                'num_cfs': num_cfs,
+                'abbreviation': abbreviation,
+                'description': description,
+                'filename': filename,
+            })
+            method.write(flow_list)
+
+        print('LCIA methods and CFs defined')
+
+def main():
+    setup_rice_husk_db()
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/sample_database.py b/tests/sample_database.py
index 0fd70b5..c4ca93b 100644
--- a/tests/sample_database.py
+++ b/tests/sample_database.py
@@ -1,10 +1,10 @@
-from brightway2 import Database, projects, Method, databases, methods
-import brightway2 as bw
+import bw2data as bd
+import bw2calc as bc
 import copy
 
 def setup_test_db():
     # Set the current project to "sample_project"
-    projects.set_current("sample_project")
+    bd.projects.set_current("sample_project")
 
     # Keys
     # Biosphere
@@ -20,8 +20,8 @@ def setup_test_db():
     e_car_key = ('technosphere', 'e-Car')
 
     # Define biosphere flows and create the "biosphere" database if it doesn't exist
-    if "biosphere" not in databases:
-        biosphere_db = Database("biosphere")
+    if "biosphere" not in bd.databases:
+        biosphere_db = bd.Database("biosphere")
         biosphere_data = {
             ("biosphere", "CO2"): {
                 "name": "Carbon dioxide, fossil",
@@ -51,8 +51,8 @@ def setup_test_db():
         biosphere_db.write(biosphere_data)
 
     # Create the "technosphere" database if it doesn't exist
-    if "technosphere" not in databases:
-        technosphere_db = Database("technosphere")
+    if "technosphere" not in bd.databases:
+        technosphere_db = bd.Database("technosphere")
         technosphere_db.write({})
         process_data = [
             ("oil extraction", "kg", "GLO", "oil"),
@@ -100,10 +100,10 @@ def setup_test_db():
 
     # Loop through the list of methods and deregister each one
     # Create a copy of the methods list
-    methods_copy = copy.deepcopy(methods)
+    methods_copy = copy.deepcopy(bd.methods)
     # Loop through the copy of methods and deregister each one
     for method in methods_copy:
-        Method(method).deregister()
+        bd.Method(method).deregister()
 
     # Define LCIA methods and CFs
     methods_data = [
@@ -113,7 +113,7 @@ def setup_test_db():
     ]
 
     for method_name, unit, num_cfs, abbreviation, description, filename, flow_code, flow_list in methods_data:
-        method = Method(("my project", method_name))
+        method = bd.Method(("my project", method_name))
         method.register(**{
             "unit": unit,
             "num_cfs": num_cfs,
@@ -125,13 +125,13 @@ def setup_test_db():
 
 def sample_lcia():
     # Load the technosphere database
-    bw.projects.set_current('sample_project')
-    technosphere_db = bw.Database("technosphere")
+    bd.projects.set_current('sample_project')
+    technosphere_db = bd.Database("technosphere")
     # Perform LCA with FU 1 for all activities
     act = {act.key: 1 for act in technosphere_db}
     # Perform Multi-LCA
-    bw.calculation_setups['multiLCA'] = {'inv': [act], 'ia': list(methods)}
-    myMultiLCA = bw.MultiLCA('multiLCA')
+    bd.calculation_setups['multiLCA'] = {'inv': [act], 'ia': list(bd.methods)}
+    myMultiLCA = bc.MultiLCA('multiLCA')
     results = [round(x, 5) for x in myMultiLCA.results[0]]
     return results