{ "cells": [ { "cell_type": "markdown", "id": "32e0f51f9cc4fea0", "metadata": {}, "source": [ "
\n", "

Rice Husk Example 🌾

\n", "
" ] }, { "cell_type": "markdown", "id": "c0ce4a48f693064b", "metadata": {}, "source": [ "This example is based on the following publication: [KΓ€telhΓΆn et al. (2016)](https://doi.org/10.1021/acs.est.6b04270). The original example used for introducing the TCM deals with a simultaneous technology πŸ”¨ and regional 🌐 choice problem. It can also be solved with PULPO. The superstructure can be seen below:" ] }, { "cell_type": "markdown", "id": "a3dc69e3-85bc-44b2-be72-6cc14657118e", "metadata": { "execution": { "iopub.execute_input": "2024-08-29T11:29:52.774005Z", "iopub.status.busy": "2024-08-29T11:29:52.774005Z", "iopub.status.idle": "2024-08-29T11:29:52.791202Z", "shell.execute_reply": "2024-08-29T11:29:52.790202Z", "shell.execute_reply.started": "2024-08-29T11:29:52.774005Z" } }, "source": "![Image Alt Text](data/system_boundaries_rice-superstructure_neutral.svg)" }, { "metadata": {}, "cell_type": "markdown", "source": "# Rice Example", "id": "5bbd666b9f770fe1" }, { "cell_type": "markdown", "id": "e60ff993-3fe9-4558-8931-9e7abe6c4e2f", "metadata": {}, "source": [ "#### Problem Formulation\n", "\n", "The original problem formulation deals with an economic objective function, using factor constraints. With PULPO, the intention is to solve LCO problems with environmental objectives, but a function for economic factors could be considered for implementation as well, if data availability allows so. \n", "\n", "Hence, the adapted problem formulation can be stated as follows:\n", "\n", "
\n", "\n", "

Goal and Scope

\n", "

Find the optimal system configuration, minimizing the global warming potential. First, in an unconstrained system.

\n", "\n", "
\n", "

πŸ“¦ Functional Unit

\n", "

For demonstration purposes, a final demand of 1 Mt of processed rice is specified.

\n", "
\n", "\n", "
\n", "

🎯 Objective Function

\n", "

Primary objective: Global warming potential (GWP).

\n", "
\n", "\n", "
\n", "

πŸ”„ Possible Choices

\n", " \n", "
\n", "\n", "
\n", "

βš™οΈ Additional Constraints

\n", "

None.

\n", "
\n", "\n", "
\n", "\n" ] }, { "cell_type": "markdown", "id": "892c90e9-ceec-4135-977a-9da892189afc", "metadata": {}, "source": [ "##### **Technosphere Matrix A**\n", "In the Technosphere, new choices are added in a **bottom-up** fashion, extending the square matrix:\n", "\n", "\n", "$$\n", "\\scriptsize\n", "\\begin{array}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}\n", "\\hline\n", " & \\text{Rice} & \\text{Rice} & \\text{Natural gas} & \\text{natural} & \\text{Power} & \\text{Transportation} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Wood pellet}} & \\textcolor{darkorange}{\\text{Burning of}} & \\textcolor{orange}{\\text{Rice husk}} & \\textcolor{orange}{\\text{Wood pellet}} \\\\\n", " & \\text{factory} & \\text{farming} & \\text{boiler} & \\text{gas supply} & \\text{plant} & \\text{by truck} & \\textcolor{darkorange}{\\text{collection 1}} & \\textcolor{darkorange}{\\text{collection 2}} & \\textcolor{darkorange}{\\text{collection 3}} & \\textcolor{darkorange}{\\text{collection 4}} & \\textcolor{darkorange}{\\text{collection 5}} & \\textcolor{darkorange}{\\text{supply}} & \\textcolor{darkorange}{\\text{rice husk}} & \\textcolor{orange}{\\text{boiler}} & \\textcolor{orange}{\\text{boiler}} \\\\\n", "\\hline\n", "\\text{Processed rice (in Mt)} & 1 & 0 & 0 & 0 & 0 & 0 & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{PeachPuff}{0} & \\textcolor{PeachPuff}{0} \\\\\n", "\\hline\n", "\\text{Unprocessed rice (in Mt)} & -1.15 & 1 & 0 & 0 & 0 & 0 & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{PeachPuff}{0} & \\textcolor{PeachPuff}{0} \\\\\n", "\\hline\n", "\\text{Thermal energy (in TWh)} & -2.2 & 0 & \\cellcolor{orange}\\textcolor{white}{1} & 0 & 0 & 0 & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\cellcolor{orange}\\textcolor{white}{1} & \\cellcolor{orange}\\textcolor{white}{1} \\\\\n", "\\hline\n", "\\text{Natural gas (in TWh)} & 0 & 0 & -1.11 & 1 & 0 & 0 & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{PeachPuff}{0} & \\textcolor{PeachPuff}{0} \\\\\n", "\\hline\n", "\\text{Electricity (in TWh)} & -0.08 & 0 & 0 & 0 & 1 & 0 & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{darkorange}{-0.02} & \\textcolor{LightSalmon}{0} & \\textcolor{PeachPuff}{0} & \\textcolor{PeachPuff}{0} \\\\\n", "\\hline\n", "\\text{Transportation (in Gt*km)} & -0.35 & 0 & 0 & 0 & 0 & 1 & \\textcolor{darkorange}{-0.12} & \\textcolor{darkorange}{-0.24} & \\textcolor{darkorange}{-0.36} & \\textcolor{darkorange}{-0.48} & \\textcolor{darkorange}{-0.6} & \\textcolor{darkorange}{-0.1} & \\textcolor{LightSalmon}{0} & \\textcolor{PeachPuff}{0} & \\textcolor{PeachPuff}{0} \\\\\n", "\\hline\n", "\\textcolor{DodgerBlue}{\\text{Rice husk at factory (in Mt)}} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\cellcolor{MediumOrchid}\\textcolor{white}{1} & \\cellcolor{MediumOrchid}\\textcolor{white}{1} & \\cellcolor{MediumOrchid}\\textcolor{white}{1} & \\cellcolor{MediumOrchid}\\textcolor{white}{1} & \\cellcolor{MediumOrchid}\\textcolor{white}{1} & \\textcolor{Thistle}{0} & \\textcolor{Thistle}{0} & \\textcolor{MediumOrchid}{-0.23} & \\textcolor{Thistle}{0} \\\\\n", "\\hline\n", "\\textcolor{DodgerBlue}{\\text{Rice husk at farm (in Mt)}} & \\textcolor{lightblue}{0} & \\textcolor{DodgerBlue}{0.6} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{MediumOrchid}{-1} & \\textcolor{MediumOrchid}{-1} & \\textcolor{MediumOrchid}{-1} & \\textcolor{MediumOrchid}{-1} & \\textcolor{MediumOrchid}{-1} & \\textcolor{Thistle}{0} & \\textcolor{MediumOrchid}{-1} & \\textcolor{Thistle}{0} & \\textcolor{Thistle}{0} \\\\\n", "\\hline\n", "\\textcolor{DodgerBlue}{\\text{Wood pellets (in Mt)}} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{lightblue}{0} & \\textcolor{Thistle}{0} & \\textcolor{Thistle}{0} & \\textcolor{Thistle}{0} & \\textcolor{Thistle}{0} & \\textcolor{Thistle}{0} & \\textcolor{MediumOrchid}{1} & \\textcolor{Thistle}{0} & \\textcolor{Thistle}{0} & \\textcolor{MediumOrchid}{-0.25} \\\\\n", "\\hline\n", "\\end{array}\n", "$$" ] }, { "cell_type": "markdown", "id": "a9fdfa3e-e43f-4616-906f-a0ca218cea4d", "metadata": {}, "source": [ "##### Biosphere Matrix B\n", "In the Biosphere, the corresponding elementary flows need to be added:" ] }, { "cell_type": "markdown", "id": "3433ef19-c6fb-41d7-a850-902435777607", "metadata": {}, "source": [ "$$\n", "\\scriptsize\n", "\\begin{array}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}\n", "\\hline\n", " & \\text{Rice} & \\text{Rice} & \\text{Natural gas} & \\text{natural} & \\text{Power} & \\text{Transportation} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Rice husk}} & \\textcolor{darkorange}{\\text{Wood pellet}} & \\textcolor{darkorange}{\\text{Burning of}} & \\textcolor{orange}{\\text{Rice husk}} & \\textcolor{orange}{\\text{Wood pellet}} \\\\\n", " & \\text{factory} & \\text{farming} & \\text{boiler} & \\text{gas supply} & \\text{plant} & \\text{by truck} & \\textcolor{darkorange}{\\text{collection 1}} & \\textcolor{darkorange}{\\text{collection 2}} & \\textcolor{darkorange}{\\text{collection 3}} & \\textcolor{darkorange}{\\text{collection 4}} & \\textcolor{darkorange}{\\text{collection 5}} & \\textcolor{darkorange}{\\text{supply}} & \\textcolor{darkorange}{\\text{rice husk}} & \\textcolor{orange}{\\text{boiler}} & \\textcolor{orange}{\\text{boiler}} \\\\\n", "\\hline\n", "\\text{CO2 (in Mt)} & 0 & 6.14 \\times 10^{-1} & 2.27 \\times 10^{-1} & 3.21 \\times 10^{-2} & 1.10 \\times 10^{0} & 5.76 \\times 10^{-2} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{darkorange}{1.50 \\times 10^{-1}} & \\textcolor{LightSalmon}{0} & \\textcolor{PeachPuff}{0} & \\textcolor{PeachPuff}{0} \\\\\n", "\\hline\n", "\\text{CH4 (in Mt)} & 0 & 1.33 \\times 10^{-3} & 1.47 \\times 10^{-4} & 1.50 \\times 10^{-3} & 9.15 \\times 10^{-4} & 6.97 \\times 10^{-5} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{LightSalmon}{0} & \\textcolor{darkorange}{2.56 \\times 10^{-4}} & \\textcolor{LightSalmon}{0} & \\textcolor{PeachPuff}{0} & \\textcolor{PeachPuff}{0} \\\\\n", "\\hline\n", "\\textcolor{FireBrick}{\\text{Economic flows (in million \\$)}} & \\textcolor{FireBrick}{50} & \\textcolor{FireBrick}{360} & \\textcolor{LightCoral}{0} & \\textcolor{FireBrick}{13} & \\textcolor{FireBrick}{65} & \\textcolor{FireBrick}{170} & \\textcolor{FireBrick}{45} & \\textcolor{FireBrick}{36} & \\textcolor{FireBrick}{29} & \\textcolor{FireBrick}{23} & \\textcolor{FireBrick}{18} & \\textcolor{FireBrick}{72} & \\textcolor{LightCoral}{0} & \\textcolor{LightCoral}{0} & \\textcolor{LightCoral}{0} \\\\ \\hline\n", "\\end{array}\n", "$$" ] }, { "cell_type": "markdown", "id": "e9cb19cf-06e4-4453-af0d-84d3320bd749", "metadata": {}, "source": [ "The final row for $\\textcolor{FireBrick}{\\text{economic flows}}$ is derived from the factor requirements matrix and the factor price vector from the original example. Only the cost for natural gas has been lowered slightly (16 --> 13), to enforce the economic competitiveness of the NG boiler. Although integrating economic flows into the biosphere matrix is unconventional, this approach will later be useful for demonstrating a multi-objective trade-off assessment, without the need to introduce additional elements to account for economic impacts." ] }, { "cell_type": "markdown", "id": "2d8bda34-2a28-427b-a7e5-b144a61d8bf6", "metadata": { "execution": { "iopub.execute_input": "2024-08-29T11:09:42.524415Z", "iopub.status.busy": "2024-08-29T11:09:42.524415Z", "iopub.status.idle": "2024-08-29T11:09:42.540045Z", "shell.execute_reply": "2024-08-29T11:09:42.540045Z", "shell.execute_reply.started": "2024-08-29T11:09:42.524415Z" } }, "source": [ "##### Characterization Factor Matrix Q\n", "The characterization factors are extended with a row for $\\textcolor{FireBrick}{\\text{Cost (in million \\$)}}$:\n", "\n", "$$\n", "\\scriptsize\n", "\\begin{array}{|c|c|c|c|}\n", "\\hline\n", " & \\text{CO2} & \\text{CH4} & \\textcolor{FireBrick}{\\text{Economic flows (in million \\$)}} \\\\\n", "\\hline\n", "\\text{GWP100 (in kg CO2e per kg)} & 1 & 25 & \\textcolor{LightCoral}{0} \\\\\n", "\\hline\n", "\\textcolor{FireBrick}{\\text{Cost (in million \\$)}} & \\textcolor{LightCoral}{0} & \\textcolor{LightCoral}{0} & \\textcolor{FireBrick}{1} \\\\\n", "\\hline\n", "\\end{array}\n", "$$" ] }, { "cell_type": "markdown", "id": "de2c23c7-f8bd-43ab-8008-6b315956a11b", "metadata": {}, "source": [ "_____\n", "The whole system can be explored in [activity-browser](https://github.com/LCA-ActivityBrowser/activity-browser):\n", "\n", "![Image Alt Text](data/rice_husk_database.png)\n", "\n", "![Image Alt Text](data/rice_impact_categories.png)" ] }, { "cell_type": "markdown", "id": "a90c5416-f548-4988-b4d5-22c10ff971db", "metadata": {}, "source": [ "
\n", "

Resolution with PULPO πŸ™

\n", "
" ] }, { "cell_type": "markdown", "id": "c494e29079cd5da5", "metadata": {}, "source": [ "Import the necessary libraries" ] }, { "cell_type": "code", "execution_count": null, "id": "722083eb-217f-4c52-a227-11983b4be9aa", "metadata": {}, "outputs": [], "source": [ "from pulpo import pulpo" ] }, { "cell_type": "code", "execution_count": null, "id": "initial_id", "metadata": { "ExecuteTime": { "end_time": "2024-09-03T07:59:54.016208Z", "start_time": "2024-09-03T07:59:51.781899Z" } }, "outputs": [], "source": [ "import os\n", "import sys\n", "import plot_functions\n", "import numpy as np\n", "np.NaN = np.nan\n", "import pandas as pd\n", "import copy" ] }, { "cell_type": "markdown", "id": "3c46111f96d69ea4", "metadata": {}, "source": [ "Set the project, database and method (**objective function**). In this example, the rice husk case study is used, which can be installed via a PULPO function (```install_rice_husk_db```)." ] }, { "cell_type": "code", "execution_count": null, "id": "12c6fe9d69080daa", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.219337Z", "start_time": "2024-08-30T13:27:36.204957Z" } }, "outputs": [], "source": [ "pulpo.install_rice_husk_db()\n", "project = \"rice_husk_example\" \n", "database = \"rice_husk_example_db\"\n", "method = \"('my project', 'climate change')\"" ] }, { "cell_type": "markdown", "id": "99e7a4da6f677b0b", "metadata": {}, "source": [ "Substitute with your working directory of choice. This directory will be used to store the results." ] }, { "cell_type": "code", "execution_count": null, "id": "6309f7430f7048b1", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.235218Z", "start_time": "2024-08-30T13:27:36.220336Z" } }, "outputs": [], "source": [ "notebook_dir = os.path.dirname(os.getcwd())\n", "directory = os.path.join(notebook_dir, 'data')" ] }, { "cell_type": "markdown", "id": "4b6f70b7a71ad2a8", "metadata": {}, "source": [ "Create a **PulpoOptimizer** instance. This class is used to interact with the LCI database and solve the optimization problem. It is specified by the project, database, method and directory." ] }, { "cell_type": "code", "execution_count": null, "id": "93bb0c9ec9b0199e", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.255290Z", "start_time": "2024-08-30T13:27:36.237246Z" } }, "outputs": [], "source": [ "pulpo_worker = pulpo.PulpoOptimizer(project, database, method, directory)" ] }, { "cell_type": "markdown", "id": "3d9c37df17f5e62e", "metadata": {}, "source": [ "Import LCI data. After initializing the PulpoOptimizer instance, the LCI data is imported from the database." ] }, { "cell_type": "code", "execution_count": null, "id": "a90199e7ebc45468", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.287483Z", "start_time": "2024-08-30T13:27:36.256302Z" } }, "outputs": [], "source": [ "pulpo_worker.get_lci_data()" ] }, { "cell_type": "markdown", "id": "994a5871c5f372c", "metadata": {}, "source": [ "Specify the **functional unit**. In this case, the functional unit is 1 Mt of processed rice. PULPO implements a search function (```retrieve_processes```) to find the processes that match the specified reference products (alternatively: keys, process name, region)." ] }, { "cell_type": "code", "execution_count": null, "id": "4e5080ff681972cd", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.303621Z", "start_time": "2024-08-30T13:27:36.288501Z" } }, "outputs": [], "source": [ "rice_factory = pulpo_worker.retrieve_processes(reference_products='Processed rice (in Mt)')\n", "\n", "demand = {rice_factory[0]: 1}" ] }, { "cell_type": "markdown", "id": "536a351d502c0ed3", "metadata": {}, "source": [ "Specify the **choices**. Here, the choices are regional 🌐 choices for rise husk collections, and technological ⛏ choices for boiler type selection.\n", "\n", "The auxiliar choices are needed to resolve the issue that rice, when not used in the boiler must be burned instead. \n", "\n", "(*At this point, just accept. If you are curious about how this multi-functionality is technically adressed, refer to the paper, or reach out.*)" ] }, { "cell_type": "code", "execution_count": null, "id": "7bfee38a-e8bf-4872-99ca-143962ed2ce3", "metadata": {}, "outputs": [], "source": [ "## Rise husk collection\n", "rice_husk_processes = [\"Rice husk collection 1\",\n", " \"Rice husk collection 2\",\n", " \"Rice husk collection 3\",\n", " \"Rice husk collection 4\",\n", " \"Rice husk collection 5\",]\n", "rice_husk_collections = pulpo_worker.retrieve_processes(processes=rice_husk_processes)" ] }, { "cell_type": "code", "execution_count": null, "id": "a5a9bcdf-5d04-477a-9903-74debed6fd01", "metadata": {}, "outputs": [], "source": [ "## Boilers\n", "boiler_processes = [\"Natural gas boiler\",\n", " \"Wood pellet boiler\",\n", " \"Rice husk boiler\"]\n", "boilers = pulpo_worker.retrieve_processes(processes=boiler_processes)" ] }, { "cell_type": "code", "execution_count": null, "id": "9b4caacf-ae9f-4fa9-8051-35de4c094d38", "metadata": {}, "outputs": [], "source": [ "## Auxiliar (Ignore for now!)\n", "auxiliar_processes = [\"Rice husk market\",\n", " \"Burning of rice husk\"]\n", "auxiliar = pulpo_worker.retrieve_processes(processes=auxiliar_processes)" ] }, { "cell_type": "code", "execution_count": null, "id": "60bb4cbd2df692ad", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.334419Z", "start_time": "2024-08-30T13:27:36.304822Z" } }, "outputs": [], "source": [ "## Combine to create the choices dictionary\n", "## For each kind of choice, assign a 'label' (e.g. 'boilers')\n", "## To each possible choice, assign a process capacity. In the 'unconstrained' case, set this value very high (e.g. 1e10, but depends on the scale of the functional unit)\n", "choices = {'Rice Husk (Mt)': {rice_husk_collections[0]: 1e10,\n", " rice_husk_collections[1]: 1e10,\n", " rice_husk_collections[2]: 1e10,\n", " rice_husk_collections[3]: 1e10,\n", " rice_husk_collections[4]: 1e10},\n", " 'Thermal Energy (TWh)': {boilers[0]: 1e10,\n", " boilers[1]: 1e10,\n", " boilers[2]: 1e10},\n", " 'Auxiliar': {auxiliar[0]: 1e10,\n", " auxiliar[1]: 1e10}}" ] }, { "cell_type": "markdown", "id": "4754bac2841743ac", "metadata": {}, "source": [ "**Instantiate** and **solve** the optimization model" ] }, { "cell_type": "code", "execution_count": null, "id": "59cf291fa08d55f9", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.489210Z", "start_time": "2024-08-30T13:27:36.335420Z" } }, "outputs": [], "source": [ "pulpo_worker.instantiate(choices=choices, demand=demand)\n", "results = pulpo_worker.solve()" ] }, { "cell_type": "markdown", "id": "175e0abe75cd1b44", "metadata": {}, "source": [ "**Save** and **summarize** the results" ] }, { "cell_type": "code", "execution_count": null, "id": "ee12795f6ce91fb1", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.585505Z", "start_time": "2024-08-30T13:27:36.490212Z" } }, "outputs": [], "source": [ "pulpo_worker.save_results(choices=choices, demand=demand, name='rice_example_unconstrained.xlsx')\n", "pulpo_worker.summarize_results(choices=choices, demand=demand, zeroes=True)" ] }, { "cell_type": "markdown", "id": "c2e6fb2df1316b7a", "metadata": {}, "source": [ "It can be seen that, in the unconstrained case minimizing the GWP, the optimal solution is to use the rice husk boiler and the rice husk collection 1. The minimum GWP is **0.859** Mt CO2-eq per Mt of processed rice. Let us store the relevant results:" ] }, { "cell_type": "markdown", "id": "8f827391132f860d", "metadata": {}, "source": [ "So far, we said that the case is *unconstrained*. This means that we don't consider any relevant capacity constraints on any of the processes. Let's now move to the constrained case!" ] }, { "cell_type": "markdown", "id": "6909962492a8be70", "metadata": {}, "source": [ "
\n", "

Constrained Case πŸ”

\n", "
" ] }, { "cell_type": "markdown", "id": "fe229af6-31b4-4aef-84cc-4b62c20b81fa", "metadata": {}, "source": [ "We will consider three different simple constrained cases:" ] }, { "cell_type": "markdown", "id": "92405f49-07d9-4390-9acc-33d77e21b3c9", "metadata": {}, "source": "![Image Alt Text](data/superstructure_constrained.svg)" }, { "cell_type": "markdown", "id": "b582a6f915b4e5f4", "metadata": {}, "source": [ "#### Choice Capacity Constraints ↔" ] }, { "cell_type": "markdown", "id": "f16a576c-e6bd-486b-9576-a74a4e675595", "metadata": {}, "source": [ "
\n", "

βš™οΈ Additional Constraints

\n", "

Add a constraint to the rice husk collection of 0.03 Mt per zone.

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "a20ea5ef249b6ff6", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.830468Z", "start_time": "2024-08-30T13:27:36.816377Z" } }, "outputs": [], "source": [ "choices_constrained = copy.deepcopy(choices)\n", "choices_constrained['Rice Husk (Mt)'] = {rice_husk_collections[0]: 0.03,\n", " rice_husk_collections[1]: 0.03,\n", " rice_husk_collections[2]: 0.03,\n", " rice_husk_collections[3]: 0.03,\n", " rice_husk_collections[4]: 0.03}" ] }, { "cell_type": "markdown", "id": "8c73eacd-1c0c-41a6-b1f0-bdd8913e691d", "metadata": {}, "source": [ "Repeat the solution steps ..." ] }, { "cell_type": "code", "execution_count": null, "id": "118d4c715c26f0e8", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.956465Z", "start_time": "2024-08-30T13:27:36.831467Z" } }, "outputs": [], "source": [ "pulpo_worker.instantiate(choices=choices_constrained, demand=demand)\n", "results = pulpo_worker.solve()" ] }, { "cell_type": "code", "execution_count": null, "id": "49dffb7284db43da", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:36.995531Z", "start_time": "2024-08-30T13:27:36.957449Z" } }, "outputs": [], "source": [ "pulpo_worker.summarize_results(choices=choices_constrained, demand=demand, zeroes=True)" ] }, { "cell_type": "markdown", "id": "c876c04ab79716f3", "metadata": {}, "source": [ "When constraints are specified on the availability of rice husk, the optimizer chooses to use all the available rice husk that can be transported from every zone. The remaining thermal energy is provided by the wood pellet boiler." ] }, { "cell_type": "markdown", "id": "56979f017bc2973b", "metadata": {}, "source": [ "##### Upper Limits ⬆\n", "\n", "Upper limits can be enforced as well on processes that are not part of the choices. This can be done using the `upper_limit` parameter in the `instantiate` method. Let's see an example, where we limit the transportation process to 0.37 Gt*km (*This value has no rational meaning, and is just an example to demonstrate the upper limit functionality*):" ] }, { "cell_type": "markdown", "id": "723de0da-7645-4b81-a1ae-6e5bbe13b4fa", "metadata": {}, "source": [ "
\n", "

βš™οΈ Additional Constraints

\n", "

Add a constraint of 0.37 Gt*km to the availability of transportation.

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "c732bf14ae6a75e", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:37.177178Z", "start_time": "2024-08-30T13:27:37.162581Z" } }, "outputs": [], "source": [ "# Retrieve the transport process\n", "transportation = pulpo_worker.retrieve_processes(processes=[\"Transportation by truck\"])" ] }, { "cell_type": "code", "execution_count": null, "id": "3a53c8fc0a47294e", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:37.192343Z", "start_time": "2024-08-30T13:27:37.178179Z" } }, "outputs": [], "source": [ "upper_limit = {transportation[0]: 0.37}" ] }, { "cell_type": "markdown", "id": "9e206b3f-6117-44e1-a31a-45245ddcc744", "metadata": {}, "source": [ "Repeat the solution steps, specifying now the \"upper_limit\" parameter during instantiation ..." ] }, { "cell_type": "code", "execution_count": null, "id": "f06b707f9bfd7d5", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:37.340418Z", "start_time": "2024-08-30T13:27:37.193344Z" } }, "outputs": [], "source": [ "pulpo_worker.instantiate(choices=choices_constrained, demand=demand, upper_limit=upper_limit)\n", "results = pulpo_worker.solve()" ] }, { "cell_type": "code", "execution_count": null, "id": "ac9b6c8bc4837269", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:37.372094Z", "start_time": "2024-08-30T13:27:37.342418Z" } }, "outputs": [], "source": [ "pulpo_worker.summarize_results(choices=choices_constrained, demand=demand, zeroes=True)" ] }, { "cell_type": "markdown", "id": "26c26f2a41640d98", "metadata": {}, "source": [ "It can be seen that the optimizer chooses to source rice husk from zones 1-3 and the remaining demand for thermal heat is supplied by the wood pellet boiler." ] }, { "cell_type": "markdown", "id": "82a95328-ea51-487a-93ec-946bbbe18dfc", "metadata": {}, "source": [ "##### Upper Limit on Wood Pellet Boiler" ] }, { "cell_type": "markdown", "id": "90787535-7bf2-4c04-a8fb-4bc466c76ec0", "metadata": {}, "source": [ "
\n", "

βš™οΈ Additional Constraints

\n", "

Add a constraint of 0.1 Mt to the supply of wood pellets.

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "c1575a0f-6ff0-48b0-8997-26fd345e6504", "metadata": {}, "outputs": [], "source": [ "wood_pellet_supply = pulpo_worker.retrieve_processes(processes=[\"Wood pellet supply\"])\n", "upper_limit = {wood_pellet_supply[0]: 0.1}" ] }, { "cell_type": "code", "execution_count": null, "id": "b23e8e3f-e8c9-4c80-ac97-ffe12f60a999", "metadata": {}, "outputs": [], "source": [ "pulpo_worker.instantiate(choices=choices_constrained, demand=demand, upper_limit=upper_limit)\n", "results = pulpo_worker.solve()" ] }, { "cell_type": "code", "execution_count": null, "id": "e7e21951-4779-4d1b-bb45-f7665e057bee", "metadata": {}, "outputs": [], "source": [ "pulpo_worker.summarize_results(choices=choices_constrained, demand=demand, zeroes=True)" ] }, { "cell_type": "markdown", "id": "1463721a-6ccd-4f46-a869-2275a15b4cc4", "metadata": {}, "source": [ "##### Summary of Cases:" ] }, { "cell_type": "code", "execution_count": null, "id": "cffd36f6-f4a2-4bf7-8f0f-65d907ad3beb", "metadata": {}, "outputs": [], "source": [ "plot_functions.plot_cases()" ] }, { "cell_type": "markdown", "id": "6781b0114e6b3b90", "metadata": {}, "source": [ "
\n", "

Final Demand Variation πŸ“¦

\n", "
" ] }, { "cell_type": "markdown", "id": "163fcf87-f392-4272-b1d9-2a064e2b29e7", "metadata": {}, "source": [ "In this section, we are varying the final demand and observe the consequence on the total GWP impact, as well as the technology and regional composition." ] }, { "cell_type": "markdown", "id": "0c239784-b97d-4209-b95e-04d67f6baf8d", "metadata": {}, "source": [ "The following code performs a loop over various demand values ranging from 0.05 Mt to 1.00 Mt of rice.\n", "\n", "> **Disclaimer**: \n", "> The syntax used here to access the internal variables of the optimization problem is not as intuitive and requires profound knowledge of the data structures of PULPO (e.g., the process map). We are working on simplifying this, to make it easier for users to access these variables outside the \"summarize\" and \"save\" functions." ] }, { "cell_type": "markdown", "id": "9b5af664-485e-4311-9dcd-2e5ef7c3b2bc", "metadata": {}, "source": [ "
\n", "

πŸ“¦ Functional Unit

\n", "

A final demand range of 0.05 to 1.00 Mt of processed rice is specified.

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "53e09471ca489bb2", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:39.771930Z", "start_time": "2024-08-30T13:27:37.403635Z" }, "scrolled": true }, "outputs": [], "source": [ "# List of final demand values\n", "final_demand_values = [i * 0.05 for i in range(1, 21)]\n", "\n", "# Define the entries to be analysed\n", "process_map = pulpo_worker.lci_data['process_map']\n", "boilers = ['Natural gas boiler', 'Wood pellet boiler', 'Rice husk boiler']\n", "zones = [f'Rice husk collection {i}' for i in range(1, 6)]\n", "\n", "# Mapping process IDs\n", "boiler_ids = [process_map[('rice_husk_example_db', boiler)] for boiler in boilers]\n", "zone_ids = [process_map[('rice_husk_example_db', zone)] for zone in zones]\n", "\n", "# Initialize results dictionary\n", "results_list = {\n", " 'demand': [],\n", " 'impact': [],\n", " 'natural_gas_boiler': [],\n", " 'wood_pellet_boiler': [],\n", " 'rice_husk_boiler': [],\n", " **{f'rice_husk_zone_{i}': [] for i in range(1, 6)}\n", "}\n", "\n", "for demand_value in final_demand_values:\n", " # Set the demand for processed rice\n", " demand_dict = {rice_factory[0]: demand_value}\n", " \n", " # Instantiate and solve the optimization model\n", " pulpo_worker.instantiate(choices=choices_constrained, demand=demand_dict, upper_limit=upper_limit)\n", " results = pulpo_worker.solve()\n", " print()\n", " \n", " # Append the results to the list\n", " results_list['demand'].append(demand_value)\n", " results_list['impact'].append(pulpo_worker.instance.OBJ())\n", "\n", " # Normalize and append boiler results\n", " total_boilers = sum(pulpo_worker.instance.scaling_vector[i].value for i in boiler_ids)\n", " for boiler, boiler_id in zip(boilers, boiler_ids):\n", " results_list[boiler.lower().replace(' ', '_')].append(\n", " pulpo_worker.instance.scaling_vector[boiler_id].value / total_boilers\n", " )\n", "\n", " # Normalize and append zone results\n", " total_zones = sum(pulpo_worker.instance.scaling_vector[i].value for i in zone_ids)\n", " for i, zone_id in enumerate(zone_ids, start=1):\n", " results_list[f'rice_husk_zone_{i}'].append(\n", " pulpo_worker.instance.scaling_vector[zone_id].value / total_zones\n", " )\n", "\n", "# Convert the results_list dictionary to a pandas DataFrame\n", "results_df = pd.DataFrame(results_list)" ] }, { "cell_type": "markdown", "id": "a6388ca5-c29e-4b28-a7b5-28fb0ea9c8e8", "metadata": {}, "source": [ "Plot the results in a bar chart. While this approach works, the visualization of results could benefit from standardized plots, which we are planning to implement. Currently, users need to manually extract the relevant values and create the visualizations that best meet their needs." ] }, { "cell_type": "code", "execution_count": null, "id": "9f6c66a3-6e66-4784-a74b-d19abc7f5cd4", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:40.405592Z", "start_time": "2024-08-30T13:27:39.772959Z" } }, "outputs": [], "source": [ "plot_functions.plot_demand(results_df)" ] }, { "cell_type": "markdown", "id": "a36e56a6-c83c-4386-812b-870543d48198", "metadata": {}, "source": [ "
\n", " πŸ› οΈ This could be implemented as a standard function in PULPO.\n", "
" ] }, { "cell_type": "markdown", "id": "95781796-b6bb-4513-a8ec-40096b9ea43f", "metadata": {}, "source": [ "
\n", "

Multi-Objective / Trade-off 🎯

\n", "
" ] }, { "cell_type": "markdown", "id": "887942fc-4ad2-4c19-a71f-fd522a7f4767", "metadata": {}, "source": [ "In this section, we will first optimize the system using the $\\textcolor{FireBrick}{\\text{economic flows}}$ as objective, and then analyse the trade-off. To that end, we will create a new pulpo_worker, which imports both impact categories. For now, we specify a weight of 0 for the environmental and a weight of 1 for the economic objective:" ] }, { "cell_type": "markdown", "id": "13b8ea5a-d472-4e34-8e7c-732a64cf5e79", "metadata": {}, "source": [ "
\n", "

🎯 Objective Function

\n", "

Primary objective: Cost.

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "550fca10-91e6-4408-9757-22fe58641a43", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:40.425352Z", "start_time": "2024-08-30T13:27:40.406143Z" } }, "outputs": [], "source": [ "methods = {\"('my project', 'climate change')\": 0, \"('my project', 'economic flow')\": 1}" ] }, { "cell_type": "code", "execution_count": null, "id": "7a0f9881-c2db-45a1-bfb7-bb98f97be656", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:40.456550Z", "start_time": "2024-08-30T13:27:40.426383Z" } }, "outputs": [], "source": [ "pulpo_worker_multi = pulpo.PulpoOptimizer(project, database, methods, directory)\n", "pulpo_worker_multi.get_lci_data()" ] }, { "cell_type": "markdown", "id": "c2952002-ae8e-40f7-ba63-697188384693", "metadata": {}, "source": [ "With the worker created, we can proceed to instantiate and solve the problem, now with the economic objective function:" ] }, { "cell_type": "code", "execution_count": null, "id": "a26f3371-1e4e-47a8-9bcd-7184a1a4fa0c", "metadata": {}, "outputs": [], "source": [ "pulpo_worker_multi.instantiate(choices=choices_constrained, demand=demand, upper_limit=upper_limit)\n", "results = pulpo_worker_multi.solve()" ] }, { "cell_type": "code", "execution_count": null, "id": "458f5a19-7659-4e52-985a-2a9ff8a7c437", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:40.681516Z", "start_time": "2024-08-30T13:27:40.603408Z" }, "scrolled": true }, "outputs": [], "source": [ "pulpo_worker_multi.summarize_results(choices=choices, demand=demand, zeroes=True)" ] }, { "cell_type": "markdown", "id": "4fa1bd4d-fa39-4db3-8e78-061ca9a1d20a", "metadata": {}, "source": [ "As anticipated, the natural gas option minimizes costs (0.56 $/kg processed rice). However, this cost reduction leads to a significant increase in the climate change impact (1.20 β†’ 1.60 kg CO2e/kg processed rice). Given these trade-offs, how does the Pareto front appear? For that purpose, we are going to use the epsilon-constrained method, constraining the environmental objective between the values corresponding to the environmental optimum and the economic optimum:" ] }, { "cell_type": "code", "execution_count": null, "id": "b62f06c9-a1f1-4224-80b1-04ce8bcad372", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:40.697517Z", "start_time": "2024-08-30T13:27:40.682517Z" } }, "outputs": [], "source": [ "lower_impact = pulpo_worker.instance.impacts[\"('my project', 'climate change')\"].value\n", "upper_impact = pulpo_worker_multi.instance.impacts_calculated[\"('my project', 'climate change')\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "ae612f6c-38b6-498f-9616-7e3398ff303b", "metadata": {}, "outputs": [], "source": [ "epsilon_list = np.linspace(lower_impact, upper_impact, num=5)" ] }, { "cell_type": "markdown", "id": "13401b01-11ed-4d26-ac77-aab0f92a0469", "metadata": {}, "source": [ "Indicator constraints can be specified in PULPO using the `upper_imp_limit` parameter of the instantiate function, as can be seen in the loop below:" ] }, { "cell_type": "markdown", "id": "0c799322-2a75-4184-8099-6592d041644f", "metadata": {}, "source": [ "
\n", "

🎯 Objective Function

\n", "

Primary objective: Cost.

\n", "

Secondary objective: Global warming potential (GWP) --> Constrainted in relevant range via \"upper_imp_limit\".

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "004a0177-38f7-47b4-bc73-dc059730af2c", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:41.975365Z", "start_time": "2024-08-30T13:27:40.698517Z" }, "scrolled": true }, "outputs": [], "source": [ "# Initialize an empty list to store the Pareto front data\n", "pareto_front = []\n", "\n", "gw_category = \"('my project', 'climate change')\"\n", "ef_category = \"('my project', 'economic flow')\"\n", "\n", "for epsilon in epsilon_list:\n", " # Adjust the internal objective weights\n", " epsilon_constraint = {\"('my project', 'climate change')\": epsilon}\n", " pulpo_worker_multi.instantiate(choices=choices_constrained, demand=demand, upper_limit=upper_limit, upper_imp_limit=epsilon_constraint)\n", " results = pulpo_worker_multi.solve()\n", "\n", " # Calculate the impacts\n", " impacts = {x: pulpo_worker_multi.instance.impacts[x].value for x in pulpo_worker_multi.instance.impacts}\n", " \n", " # Save the impacts in the Pareto front list (cost divided by 1000)\n", " pareto_front.append((impacts[ef_category]/1000, impacts[gw_category]))\n", " \n", " # Print the impacts for each iteration\n", " print(f\"Cost [$/kg processed rice]:{impacts[ef_category]/1000}\")\n", " print(f\"GWP [kg CO2-eq/kg processed rice]: {impacts[gw_category]}\")\n", " print()" ] }, { "cell_type": "code", "execution_count": null, "id": "05ddcbd8-5294-4acc-8768-9c52bcc0caa5", "metadata": { "ExecuteTime": { "end_time": "2024-08-30T13:27:42.052422Z", "start_time": "2024-08-30T13:27:41.977563Z" } }, "outputs": [], "source": [ "plot_functions.plot_pareto(pareto_front)" ] }, { "cell_type": "markdown", "id": "58ac4011-0610-4ab4-b7bd-f2a3e73d777d", "metadata": {}, "source": [ "This type of (_constrained_) trade-off assessment highlights the inherent compromises involved in designing a production system with multiple criteria. In this example, the two extreme points (known as **anchor** points) demonstrate that a significant reduction in GWP (from 1.60 to 1.25 kg CO2-eq) can be achieved with only a slight increase in cost (from 0.560 to 0.566 \\$). The Pareto front, defined by the 10 points shown, illustrates how balanced solutions can be realized." ] }, { "cell_type": "markdown", "id": "654c3ab4-9144-455e-a35e-9294cd906380", "metadata": {}, "source": [ "
\n", " πŸ› οΈ This could be implemented as a standard function in PULPO.\n", "
" ] }, { "cell_type": "markdown", "id": "7e01e3c7-327f-49b0-8741-60d8fd82614a", "metadata": {}, "source": [ "____" ] }, { "cell_type": "markdown", "id": "1a1f4fcb-f6e9-4ff3-8b90-75782a95da0c", "metadata": {}, "source": [ "
\n", "

Internal Constraint due to Multi-Functionality

\n", "
" ] }, { "cell_type": "markdown", "id": "6d9522ec-3916-4a55-b532-002134b88b87", "metadata": {}, "source": [ "Following the notion highlighted at the end of the previous section, let's investigate what happens if more thermal energy is required than the one that can be provided by the rice husk. This is done by adding another demand for the functional unit:" ] }, { "cell_type": "markdown", "id": "37f96354-e8b5-4f46-b81d-149cc9cec6e0", "metadata": {}, "source": [ "
\n", "

πŸ“¦ Functional Unit

\n", "

A final demand range of 1.00 Mt of processed rice is specified, in addition to 10 TWh of thermal energy.

\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "id": "0026df97-f4d9-4c76-809a-b1940ff8eedc", "metadata": {}, "outputs": [], "source": [ "demand_thermal = {rice_factory[0]: 1,\n", " 'Thermal Energy (TWh)': 10}" ] }, { "cell_type": "code", "execution_count": null, "id": "fcdf067a-c3cd-4c24-a2a2-eaa7b840e12b", "metadata": {}, "outputs": [], "source": [ "pulpo_worker.instantiate(choices=choices, demand=demand_thermal)\n", "results = pulpo_worker.solve()" ] }, { "cell_type": "code", "execution_count": null, "id": "b92a3bb5-6ad2-497c-a60d-1461e97b8e6b", "metadata": {}, "outputs": [], "source": [ "pulpo_worker.summarize_results(choices=choices, demand=demand_thermal, zeroes=True)" ] }, { "cell_type": "markdown", "id": "f5967985-0530-47d1-9784-7787db0a5bc2", "metadata": {}, "source": [ "As can be seen from the choices made by the optimizer, the rice husk boiler is used to its full capacity, conditioned by the demand for rice:\n", "\n", "$$\n", "1 \\, \\text{Mt processed rice} \\times 1.15 \\frac{\\text{Mt unprocessed rice}}{\\text{Mt processed rice}} \\times 0.6 \\frac{\\text{Mt Rice husk}}{\\text{Mt unprocessed rice}} = 0.69 \\, \\text{Mt Rice husk} \n", "$$\n", "\n", "The rest of the thermal energy is provided by the wood pellet boiler.\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.10.14" } }, "nbformat": 4, "nbformat_minor": 5 }