OpenPyTEA 2.0.0__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openpytea-2.0.0/src/OpenPyTEA.egg-info → openpytea-2.1.0}/PKG-INFO +59 -4
- {openpytea-2.0.0 → openpytea-2.1.0}/README.md +58 -3
- {openpytea-2.0.0 → openpytea-2.1.0}/pyproject.toml +1 -1
- {openpytea-2.0.0 → openpytea-2.1.0/src/OpenPyTEA.egg-info}/PKG-INFO +59 -4
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/analysis.py +133 -61
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/equipment.py +74 -27
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/plant.py +386 -43
- openpytea-2.1.0/tests/test_analysis.py +113 -0
- openpytea-2.1.0/tests/test_plant.py +119 -0
- openpytea-2.0.0/tests/test_analysis.py +0 -98
- openpytea-2.0.0/tests/test_plant.py +0 -36
- {openpytea-2.0.0 → openpytea-2.1.0}/LICENSE +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/setup.cfg +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/OpenPyTEA.egg-info/SOURCES.txt +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/OpenPyTEA.egg-info/dependency_links.txt +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/OpenPyTEA.egg-info/requires.txt +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/OpenPyTEA.egg-info/top_level.txt +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/__init__.py +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/data/cepci_values.csv +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/data/cost_correlations.csv +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/helpers.py +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/io.py +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/plotting.py +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/tests/test_equipment.py +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/tests/test_plotting.py +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/tests/test_run_tea.py +0 -0
- {openpytea-2.0.0 → openpytea-2.1.0}/tests/test_smoke.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: OpenPyTEA
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: OpenPyTEA: An open-source Python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation
|
|
5
5
|
Maintainer-email: "Panji B. Tamarona" <P.B.Tamarona@tudelft.nl>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -97,11 +97,30 @@ src/openpytea/
|
|
|
97
97
|
├── helpers.py # Helper functions for data handling and common operations
|
|
98
98
|
└── data/ # Cost correlations database and CEPCI data
|
|
99
99
|
examples/ # Example notebooks and case studies
|
|
100
|
-
walkthrough.ipynb #
|
|
100
|
+
walkthrough.ipynb # Walkthrough of the package
|
|
101
|
+
|
|
102
|
+
backend/ # FastAPI backend for the web GUI
|
|
103
|
+
├── app/
|
|
104
|
+
│ ├── main.py # FastAPI app with CORS and router mounting
|
|
105
|
+
│ ├── state.py # In-memory session state
|
|
106
|
+
│ ├── schemas.py # Pydantic request/response models
|
|
107
|
+
│ ├── util.py # JSON serialization utilities
|
|
108
|
+
│ ├── routers/ # API endpoints (equipment, plant, analysis, I/O)
|
|
109
|
+
│ └── presets/ # Example preset JSON files
|
|
110
|
+
└── requirements.txt
|
|
111
|
+
|
|
112
|
+
frontend/ # React + TypeScript web GUI
|
|
113
|
+
├── src/
|
|
114
|
+
│ ├── api/client.ts # Typed API client
|
|
115
|
+
│ ├── types/index.ts # TypeScript interfaces
|
|
116
|
+
│ ├── pages/ # Equipment, Plant Config, Results, Analysis, Monte Carlo, Compare
|
|
117
|
+
│ ├── App.tsx # Tab navigation + examples dropdown
|
|
118
|
+
│ └── App.css # Styling
|
|
119
|
+
└── package.json
|
|
120
|
+
|
|
101
121
|
pyproject.toml
|
|
102
122
|
README.md
|
|
103
123
|
```
|
|
104
|
-
|
|
105
124
|
---
|
|
106
125
|
|
|
107
126
|
## 🏗️ Software Architecture
|
|
@@ -112,6 +131,42 @@ Software architecture and data flow of **OpenPyTEA**, illustrating the progressi
|
|
|
112
131
|
|
|
113
132
|
---
|
|
114
133
|
|
|
134
|
+
## 🖥️ Web GUI (**work in progress**)
|
|
135
|
+
|
|
136
|
+
OpenPyTEA includes an optional web-based graphical interface for users who prefer a visual workflow over Python scripting. The GUI provides the full TEA workflow through a tabbed browser interface:
|
|
137
|
+
|
|
138
|
+
- **Equipment** — add, edit, and remove equipment with cost database lookup
|
|
139
|
+
- **Plant Config** — configure location, financial parameters, labor, products, and variable OPEX
|
|
140
|
+
- **Results** — run calculations and view metric cards, cost breakdown charts, and cash flow tables
|
|
141
|
+
- **Analysis** — sensitivity plots and tornado diagrams. Sensitivity supports a **multi-panel grid** (different parameter and metric per panel, e.g. NPV vs. interest rate, ROI vs. electricity price, all in one figure) and **multi-plant overlay** (curves for every plant added on the Compare tab share the same axes for direct comparison)
|
|
142
|
+
- **Monte Carlo** — uncertainty analysis with histogram distributions, fitted normal curves, and summary statistics. **Multi-plant overlay** shows distributions for several plants on the same axes, mirroring `plot_multiple_monte_carlo` from the library
|
|
143
|
+
- **Compare** — side-by-side comparison of saved plants (CAPEX/OPEX breakdown bars, key metric bars). Plants imported here are also reused as the overlay set on the Analysis and Monte Carlo tabs
|
|
144
|
+
- **Downloadable charts** — all plots include a download button to export as standalone PNG images with full axis labels
|
|
145
|
+
- **Examples** — built-in presets from the case study notebooks for quick demonstration
|
|
146
|
+
|
|
147
|
+
### Running the GUI
|
|
148
|
+
|
|
149
|
+
**Backend** (requires Python 3.10+):
|
|
150
|
+
```bash
|
|
151
|
+
pip install -e . # install OpenPyTEA from repo root
|
|
152
|
+
cd backend
|
|
153
|
+
pip install -r requirements.txt
|
|
154
|
+
PYTHONPATH=../src python3 -m uvicorn app.main:app --reload --port 8000
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Frontend** (requires Node.js):
|
|
158
|
+
```bash
|
|
159
|
+
cd frontend
|
|
160
|
+
npm install
|
|
161
|
+
npm run dev
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Then open http://localhost:5173. Click **Examples** in the header to load a case study preset and explore.
|
|
165
|
+
|
|
166
|
+
For detailed architecture documentation, see `GUI_ARCHITECTURE.md`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
115
170
|
## 🧠 Core Concepts
|
|
116
171
|
|
|
117
172
|
### 1. **Equipment-level costing**
|
|
@@ -299,7 +354,7 @@ Or if you prefer to cite manually, you may use:
|
|
|
299
354
|
title = {OpenPyTEA: An open-source python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation},
|
|
300
355
|
year = {2025},
|
|
301
356
|
url = {\url{https://github.com/pbtamarona/OpenPyTEA}},
|
|
302
|
-
version = {
|
|
357
|
+
version = {2.0.0},
|
|
303
358
|
note = {Accessed: YYYY-MM-DD}
|
|
304
359
|
}
|
|
305
360
|
```
|
|
@@ -73,11 +73,30 @@ src/openpytea/
|
|
|
73
73
|
├── helpers.py # Helper functions for data handling and common operations
|
|
74
74
|
└── data/ # Cost correlations database and CEPCI data
|
|
75
75
|
examples/ # Example notebooks and case studies
|
|
76
|
-
walkthrough.ipynb #
|
|
76
|
+
walkthrough.ipynb # Walkthrough of the package
|
|
77
|
+
|
|
78
|
+
backend/ # FastAPI backend for the web GUI
|
|
79
|
+
├── app/
|
|
80
|
+
│ ├── main.py # FastAPI app with CORS and router mounting
|
|
81
|
+
│ ├── state.py # In-memory session state
|
|
82
|
+
│ ├── schemas.py # Pydantic request/response models
|
|
83
|
+
│ ├── util.py # JSON serialization utilities
|
|
84
|
+
│ ├── routers/ # API endpoints (equipment, plant, analysis, I/O)
|
|
85
|
+
│ └── presets/ # Example preset JSON files
|
|
86
|
+
└── requirements.txt
|
|
87
|
+
|
|
88
|
+
frontend/ # React + TypeScript web GUI
|
|
89
|
+
├── src/
|
|
90
|
+
│ ├── api/client.ts # Typed API client
|
|
91
|
+
│ ├── types/index.ts # TypeScript interfaces
|
|
92
|
+
│ ├── pages/ # Equipment, Plant Config, Results, Analysis, Monte Carlo, Compare
|
|
93
|
+
│ ├── App.tsx # Tab navigation + examples dropdown
|
|
94
|
+
│ └── App.css # Styling
|
|
95
|
+
└── package.json
|
|
96
|
+
|
|
77
97
|
pyproject.toml
|
|
78
98
|
README.md
|
|
79
99
|
```
|
|
80
|
-
|
|
81
100
|
---
|
|
82
101
|
|
|
83
102
|
## 🏗️ Software Architecture
|
|
@@ -88,6 +107,42 @@ Software architecture and data flow of **OpenPyTEA**, illustrating the progressi
|
|
|
88
107
|
|
|
89
108
|
---
|
|
90
109
|
|
|
110
|
+
## 🖥️ Web GUI (**work in progress**)
|
|
111
|
+
|
|
112
|
+
OpenPyTEA includes an optional web-based graphical interface for users who prefer a visual workflow over Python scripting. The GUI provides the full TEA workflow through a tabbed browser interface:
|
|
113
|
+
|
|
114
|
+
- **Equipment** — add, edit, and remove equipment with cost database lookup
|
|
115
|
+
- **Plant Config** — configure location, financial parameters, labor, products, and variable OPEX
|
|
116
|
+
- **Results** — run calculations and view metric cards, cost breakdown charts, and cash flow tables
|
|
117
|
+
- **Analysis** — sensitivity plots and tornado diagrams. Sensitivity supports a **multi-panel grid** (different parameter and metric per panel, e.g. NPV vs. interest rate, ROI vs. electricity price, all in one figure) and **multi-plant overlay** (curves for every plant added on the Compare tab share the same axes for direct comparison)
|
|
118
|
+
- **Monte Carlo** — uncertainty analysis with histogram distributions, fitted normal curves, and summary statistics. **Multi-plant overlay** shows distributions for several plants on the same axes, mirroring `plot_multiple_monte_carlo` from the library
|
|
119
|
+
- **Compare** — side-by-side comparison of saved plants (CAPEX/OPEX breakdown bars, key metric bars). Plants imported here are also reused as the overlay set on the Analysis and Monte Carlo tabs
|
|
120
|
+
- **Downloadable charts** — all plots include a download button to export as standalone PNG images with full axis labels
|
|
121
|
+
- **Examples** — built-in presets from the case study notebooks for quick demonstration
|
|
122
|
+
|
|
123
|
+
### Running the GUI
|
|
124
|
+
|
|
125
|
+
**Backend** (requires Python 3.10+):
|
|
126
|
+
```bash
|
|
127
|
+
pip install -e . # install OpenPyTEA from repo root
|
|
128
|
+
cd backend
|
|
129
|
+
pip install -r requirements.txt
|
|
130
|
+
PYTHONPATH=../src python3 -m uvicorn app.main:app --reload --port 8000
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Frontend** (requires Node.js):
|
|
134
|
+
```bash
|
|
135
|
+
cd frontend
|
|
136
|
+
npm install
|
|
137
|
+
npm run dev
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Then open http://localhost:5173. Click **Examples** in the header to load a case study preset and explore.
|
|
141
|
+
|
|
142
|
+
For detailed architecture documentation, see `GUI_ARCHITECTURE.md`.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
91
146
|
## 🧠 Core Concepts
|
|
92
147
|
|
|
93
148
|
### 1. **Equipment-level costing**
|
|
@@ -275,7 +330,7 @@ Or if you prefer to cite manually, you may use:
|
|
|
275
330
|
title = {OpenPyTEA: An open-source python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation},
|
|
276
331
|
year = {2025},
|
|
277
332
|
url = {\url{https://github.com/pbtamarona/OpenPyTEA}},
|
|
278
|
-
version = {
|
|
333
|
+
version = {2.0.0},
|
|
279
334
|
note = {Accessed: YYYY-MM-DD}
|
|
280
335
|
}
|
|
281
336
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "OpenPyTEA"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.1.0"
|
|
4
4
|
description = "OpenPyTEA: An open-source Python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: OpenPyTEA
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: OpenPyTEA: An open-source Python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation
|
|
5
5
|
Maintainer-email: "Panji B. Tamarona" <P.B.Tamarona@tudelft.nl>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -97,11 +97,30 @@ src/openpytea/
|
|
|
97
97
|
├── helpers.py # Helper functions for data handling and common operations
|
|
98
98
|
└── data/ # Cost correlations database and CEPCI data
|
|
99
99
|
examples/ # Example notebooks and case studies
|
|
100
|
-
walkthrough.ipynb #
|
|
100
|
+
walkthrough.ipynb # Walkthrough of the package
|
|
101
|
+
|
|
102
|
+
backend/ # FastAPI backend for the web GUI
|
|
103
|
+
├── app/
|
|
104
|
+
│ ├── main.py # FastAPI app with CORS and router mounting
|
|
105
|
+
│ ├── state.py # In-memory session state
|
|
106
|
+
│ ├── schemas.py # Pydantic request/response models
|
|
107
|
+
│ ├── util.py # JSON serialization utilities
|
|
108
|
+
│ ├── routers/ # API endpoints (equipment, plant, analysis, I/O)
|
|
109
|
+
│ └── presets/ # Example preset JSON files
|
|
110
|
+
└── requirements.txt
|
|
111
|
+
|
|
112
|
+
frontend/ # React + TypeScript web GUI
|
|
113
|
+
├── src/
|
|
114
|
+
│ ├── api/client.ts # Typed API client
|
|
115
|
+
│ ├── types/index.ts # TypeScript interfaces
|
|
116
|
+
│ ├── pages/ # Equipment, Plant Config, Results, Analysis, Monte Carlo, Compare
|
|
117
|
+
│ ├── App.tsx # Tab navigation + examples dropdown
|
|
118
|
+
│ └── App.css # Styling
|
|
119
|
+
└── package.json
|
|
120
|
+
|
|
101
121
|
pyproject.toml
|
|
102
122
|
README.md
|
|
103
123
|
```
|
|
104
|
-
|
|
105
124
|
---
|
|
106
125
|
|
|
107
126
|
## 🏗️ Software Architecture
|
|
@@ -112,6 +131,42 @@ Software architecture and data flow of **OpenPyTEA**, illustrating the progressi
|
|
|
112
131
|
|
|
113
132
|
---
|
|
114
133
|
|
|
134
|
+
## 🖥️ Web GUI (**work in progress**)
|
|
135
|
+
|
|
136
|
+
OpenPyTEA includes an optional web-based graphical interface for users who prefer a visual workflow over Python scripting. The GUI provides the full TEA workflow through a tabbed browser interface:
|
|
137
|
+
|
|
138
|
+
- **Equipment** — add, edit, and remove equipment with cost database lookup
|
|
139
|
+
- **Plant Config** — configure location, financial parameters, labor, products, and variable OPEX
|
|
140
|
+
- **Results** — run calculations and view metric cards, cost breakdown charts, and cash flow tables
|
|
141
|
+
- **Analysis** — sensitivity plots and tornado diagrams. Sensitivity supports a **multi-panel grid** (different parameter and metric per panel, e.g. NPV vs. interest rate, ROI vs. electricity price, all in one figure) and **multi-plant overlay** (curves for every plant added on the Compare tab share the same axes for direct comparison)
|
|
142
|
+
- **Monte Carlo** — uncertainty analysis with histogram distributions, fitted normal curves, and summary statistics. **Multi-plant overlay** shows distributions for several plants on the same axes, mirroring `plot_multiple_monte_carlo` from the library
|
|
143
|
+
- **Compare** — side-by-side comparison of saved plants (CAPEX/OPEX breakdown bars, key metric bars). Plants imported here are also reused as the overlay set on the Analysis and Monte Carlo tabs
|
|
144
|
+
- **Downloadable charts** — all plots include a download button to export as standalone PNG images with full axis labels
|
|
145
|
+
- **Examples** — built-in presets from the case study notebooks for quick demonstration
|
|
146
|
+
|
|
147
|
+
### Running the GUI
|
|
148
|
+
|
|
149
|
+
**Backend** (requires Python 3.10+):
|
|
150
|
+
```bash
|
|
151
|
+
pip install -e . # install OpenPyTEA from repo root
|
|
152
|
+
cd backend
|
|
153
|
+
pip install -r requirements.txt
|
|
154
|
+
PYTHONPATH=../src python3 -m uvicorn app.main:app --reload --port 8000
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Frontend** (requires Node.js):
|
|
158
|
+
```bash
|
|
159
|
+
cd frontend
|
|
160
|
+
npm install
|
|
161
|
+
npm run dev
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Then open http://localhost:5173. Click **Examples** in the header to load a case study preset and explore.
|
|
165
|
+
|
|
166
|
+
For detailed architecture documentation, see `GUI_ARCHITECTURE.md`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
115
170
|
## 🧠 Core Concepts
|
|
116
171
|
|
|
117
172
|
### 1. **Equipment-level costing**
|
|
@@ -299,7 +354,7 @@ Or if you prefer to cite manually, you may use:
|
|
|
299
354
|
title = {OpenPyTEA: An open-source python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation},
|
|
300
355
|
year = {2025},
|
|
301
356
|
url = {\url{https://github.com/pbtamarona/OpenPyTEA}},
|
|
302
|
-
version = {
|
|
357
|
+
version = {2.0.0},
|
|
303
358
|
note = {Accessed: YYYY-MM-DD}
|
|
304
359
|
}
|
|
305
360
|
```
|
|
@@ -630,54 +630,76 @@ def monte_carlo(plant,
|
|
|
630
630
|
batch_size: int = 1000,
|
|
631
631
|
additional_capex: bool = False):
|
|
632
632
|
"""
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
633
|
+
Probabilistic analysis of a plant's economic performance by sampling
|
|
634
|
+
input parameters from truncated normal distributions and computing
|
|
635
|
+
economic metrics across all samples. Samples are processed in batches
|
|
636
|
+
to manage memory.
|
|
637
|
+
|
|
638
|
+
Parameters
|
|
639
|
+
----------
|
|
640
|
+
plant : Plant
|
|
641
|
+
A fully configured Plant instance. Baseline economic calculations
|
|
642
|
+
are run internally before sampling begins. The original plant is not
|
|
643
|
+
modified; results are stored on it after the simulation completes.
|
|
644
|
+
num_samples : int, optional
|
|
645
|
+
Total number of Monte Carlo samples. Default is 1_000_000.
|
|
646
|
+
batch_size : int, optional
|
|
647
|
+
Number of samples processed per batch. Smaller values reduce peak
|
|
648
|
+
memory at the cost of slightly more overhead. Default is 1000.
|
|
649
|
+
additional_capex : bool, optional
|
|
650
|
+
Include additional CAPEX in ROI and payback time calculations.
|
|
651
|
+
Only applies when product prices are available. Default is False.
|
|
652
|
+
|
|
653
|
+
Returns
|
|
654
|
+
-------
|
|
645
655
|
dict
|
|
646
|
-
A dictionary
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
- "metrics" : dict
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
values
|
|
656
|
+
A dictionary with the following keys:
|
|
657
|
+
|
|
658
|
+
- ``"name"`` : str — plant name.
|
|
659
|
+
- ``"metrics"`` : dict — arrays of length *num_samples*:
|
|
660
|
+
- ``"LCOP"`` — levelized cost of production (always populated).
|
|
661
|
+
- ``"NPV"`` — net present value (requires product prices).
|
|
662
|
+
- ``"ROI"`` — return on investment (requires product prices).
|
|
663
|
+
- ``"PBT"`` — payback time (requires product prices).
|
|
664
|
+
- ``"inputs"`` : dict — sampled input arrays, always containing:
|
|
665
|
+
- ``"Fixed capital factor"``
|
|
666
|
+
- ``"Fixed opex factor"``
|
|
667
|
+
- ``"Operator hourly rate"``
|
|
668
|
+
- ``"Project lifetime"``
|
|
669
|
+
- ``"Interest rate"``
|
|
670
|
+
- ``"{Item} price"`` for each variable OPEX item.
|
|
671
|
+
- ``"{Product} product price"`` for each product.
|
|
672
|
+
And conditionally (when ``std > 0`` in ``project_uncertainties``):
|
|
673
|
+
- ``"Plant utilization"``
|
|
674
|
+
- ``"Tax rate"``
|
|
675
|
+
- ``"num_samples"`` : int — number of samples generated.
|
|
676
|
+
- ``"additional_capex"`` : bool — whether additional CAPEX was
|
|
677
|
+
included.
|
|
678
|
+
- ``"currency"`` : str — currency symbol.
|
|
679
|
+
|
|
680
|
+
Notes
|
|
681
|
+
-----
|
|
682
|
+
- Sampling distributions for fixed capital factor, fixed opex factor,
|
|
683
|
+
project lifetime, interest rate, plant utilization, and tax rate are
|
|
684
|
+
controlled by the plant's ``project_uncertainties`` configuration dict
|
|
685
|
+
(see Plant class docstring). Default std, min, and max values are used
|
|
686
|
+
when a parameter is absent from that dict.
|
|
687
|
+
- ``plant_utilization`` and ``tax_rate`` have a default ``std`` of 0 and
|
|
688
|
+
are only sampled when explicitly set to a positive value in
|
|
689
|
+
``project_uncertainties``.
|
|
690
|
+
- Variable OPEX items and products are sampled using the ``std``,
|
|
691
|
+
``min``, and ``max`` fields defined within each item's own config dict.
|
|
692
|
+
- The plant is deep-copied each batch to avoid mutating the original.
|
|
693
|
+
After the run, ``monte_carlo_metrics`` and ``monte_carlo_inputs`` are
|
|
694
|
+
written back to the original plant.
|
|
695
|
+
- Progress is shown via a tqdm progress bar over batches.
|
|
696
|
+
|
|
676
697
|
Raises
|
|
677
698
|
------
|
|
678
699
|
AttributeError
|
|
679
700
|
If the plant object lacks required economic calculation methods or
|
|
680
701
|
configuration attributes.
|
|
702
|
+
|
|
681
703
|
Examples
|
|
682
704
|
--------
|
|
683
705
|
>>> results = monte_carlo(plant, num_samples=10000, batch_size=500)
|
|
@@ -702,7 +724,58 @@ def monte_carlo(plant,
|
|
|
702
724
|
"PBT": np.zeros(num_samples),
|
|
703
725
|
}
|
|
704
726
|
|
|
705
|
-
# ----
|
|
727
|
+
# ---- Resolve project uncertainty parameters ----
|
|
728
|
+
pu = plant.project_uncertainties
|
|
729
|
+
|
|
730
|
+
fc_cfg = pu.get("fixed_capital_factor", {})
|
|
731
|
+
fc_std = fc_cfg.get("std", 0.3)
|
|
732
|
+
fc_min = fc_cfg.get("min", 0.25)
|
|
733
|
+
fc_max = fc_cfg.get("max", 1.75)
|
|
734
|
+
|
|
735
|
+
fo_cfg = pu.get("fixed_opex_factor", {})
|
|
736
|
+
fo_std = fo_cfg.get("std", 0.3)
|
|
737
|
+
fo_min = fo_cfg.get("min", 0.25)
|
|
738
|
+
fo_max = fo_cfg.get("max", 1.75)
|
|
739
|
+
|
|
740
|
+
lt_cfg = pu.get("project_lifetime", {})
|
|
741
|
+
lt_std = lt_cfg.get("std", 5)
|
|
742
|
+
lt_min = lt_cfg.get("min", max(5, plant.project_lifetime - 2 * lt_std))
|
|
743
|
+
lt_max = lt_cfg.get("max", plant.project_lifetime + 2 * lt_std)
|
|
744
|
+
|
|
745
|
+
ir_cfg = pu.get("interest_rate", {})
|
|
746
|
+
ir_std = ir_cfg.get("std", 0.03)
|
|
747
|
+
ir_min = ir_cfg.get("min", max(0.02, plant.interest_rate - 2 * ir_std))
|
|
748
|
+
ir_max = ir_cfg.get("max", plant.interest_rate + 2 * ir_std)
|
|
749
|
+
|
|
750
|
+
pu_util_cfg = pu.get("plant_utilization", {})
|
|
751
|
+
pu_util_std = pu_util_cfg.get("std", 0)
|
|
752
|
+
if pu_util_std > 0:
|
|
753
|
+
pu_util_mean = plant.plant_utilization
|
|
754
|
+
pu_util_min = pu_util_cfg.get(
|
|
755
|
+
"min", max(0.0, pu_util_mean - 2 * pu_util_std)
|
|
756
|
+
)
|
|
757
|
+
pu_util_max = pu_util_cfg.get(
|
|
758
|
+
"max", min(1.0, pu_util_mean + 2 * pu_util_std)
|
|
759
|
+
)
|
|
760
|
+
plant_utilizations = _truncated_normal_samples(
|
|
761
|
+
pu_util_mean, pu_util_std, pu_util_min, pu_util_max, num_samples
|
|
762
|
+
)
|
|
763
|
+
else:
|
|
764
|
+
plant_utilizations = None
|
|
765
|
+
|
|
766
|
+
tr_cfg = pu.get("tax_rate", {})
|
|
767
|
+
tr_std = tr_cfg.get("std", 0)
|
|
768
|
+
if tr_std > 0:
|
|
769
|
+
tr_mean = plant.tax_rate
|
|
770
|
+
tr_min = tr_cfg.get("min", max(0.0, tr_mean - 2 * tr_std))
|
|
771
|
+
tr_max = tr_cfg.get("max", min(1.0, tr_mean + 2 * tr_std))
|
|
772
|
+
tax_rates = _truncated_normal_samples(
|
|
773
|
+
tr_mean, tr_std, tr_min, tr_max, num_samples
|
|
774
|
+
)
|
|
775
|
+
else:
|
|
776
|
+
tax_rates = None
|
|
777
|
+
|
|
778
|
+
# ---- Allocate all input distributions ----
|
|
706
779
|
op_cfg = plant.operator_hourly_rate
|
|
707
780
|
op_mean = op_cfg.get("rate", 38.11)
|
|
708
781
|
op_std = op_cfg.get("std", 20 / 2)
|
|
@@ -711,11 +784,11 @@ def monte_carlo(plant,
|
|
|
711
784
|
|
|
712
785
|
# ---- Sample ALL inputs once ----
|
|
713
786
|
fixed_capitals = _truncated_normal_samples(
|
|
714
|
-
1,
|
|
787
|
+
1, fc_std, fc_min, fc_max, num_samples
|
|
715
788
|
)
|
|
716
789
|
|
|
717
790
|
fixed_opexs = _truncated_normal_samples(
|
|
718
|
-
1,
|
|
791
|
+
1, fo_std, fo_min, fo_max, num_samples
|
|
719
792
|
)
|
|
720
793
|
|
|
721
794
|
operator_hourlys = _truncated_normal_samples(
|
|
@@ -723,19 +796,11 @@ def monte_carlo(plant,
|
|
|
723
796
|
)
|
|
724
797
|
|
|
725
798
|
project_lifetimes = _truncated_normal_samples(
|
|
726
|
-
plant.project_lifetime,
|
|
727
|
-
5,
|
|
728
|
-
max(5, plant.project_lifetime - 2 * 5),
|
|
729
|
-
plant.project_lifetime + 2 * 5,
|
|
730
|
-
num_samples,
|
|
799
|
+
plant.project_lifetime, lt_std, lt_min, lt_max, num_samples,
|
|
731
800
|
)
|
|
732
801
|
|
|
733
802
|
interests = _truncated_normal_samples(
|
|
734
|
-
plant.interest_rate,
|
|
735
|
-
0.03,
|
|
736
|
-
max(0.02, plant.interest_rate - 2 * 0.03),
|
|
737
|
-
plant.interest_rate + 2 * 0.03,
|
|
738
|
-
num_samples,
|
|
803
|
+
plant.interest_rate, ir_std, ir_min, ir_max, num_samples,
|
|
739
804
|
)
|
|
740
805
|
|
|
741
806
|
variable_opex_price_samples = {}
|
|
@@ -768,12 +833,15 @@ def monte_carlo(plant,
|
|
|
768
833
|
# ---- Apply sampled inputs ----
|
|
769
834
|
plant_copy.operator_hourly_rate["rate"] = operator_hourlys[start:end]
|
|
770
835
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
836
|
+
scalar_updates = {
|
|
837
|
+
"project_lifetime": project_lifetimes[start:end],
|
|
838
|
+
"interest_rate": interests[start:end],
|
|
839
|
+
}
|
|
840
|
+
if plant_utilizations is not None:
|
|
841
|
+
scalar_updates["plant_utilization"] = plant_utilizations[start:end]
|
|
842
|
+
if tax_rates is not None:
|
|
843
|
+
scalar_updates["tax_rate"] = tax_rates[start:end]
|
|
844
|
+
plant_copy.update_configuration(scalar_updates)
|
|
777
845
|
|
|
778
846
|
for item in plant.variable_opex_inputs:
|
|
779
847
|
plant_copy.variable_opex_inputs[item]["price"] = (
|
|
@@ -814,6 +882,10 @@ def monte_carlo(plant,
|
|
|
814
882
|
"Operator hourly rate": operator_hourlys,
|
|
815
883
|
"Project lifetime": project_lifetimes,
|
|
816
884
|
"Interest rate": interests,
|
|
885
|
+
**({} if plant_utilizations is None
|
|
886
|
+
else {"Plant utilization": plant_utilizations}),
|
|
887
|
+
**({} if tax_rates is None
|
|
888
|
+
else {"Tax rate": tax_rates}),
|
|
817
889
|
**{
|
|
818
890
|
f"{k.replace('_', ' ').title()} price": v
|
|
819
891
|
for k, v in variable_opex_price_samples.items()
|
|
@@ -288,6 +288,22 @@ class Equipment:
|
|
|
288
288
|
Default is None (auto-resolved).
|
|
289
289
|
target_year : int, optional
|
|
290
290
|
Target year for inflation adjustment. Default is 2024.
|
|
291
|
+
erection_factor : float | None, optional
|
|
292
|
+
Erection factor override. Default is None (use process_type table).
|
|
293
|
+
piping_factor : float | None, optional
|
|
294
|
+
Piping factor override. Default is None (use process_type table).
|
|
295
|
+
instrumentation_factor : float | None, optional
|
|
296
|
+
Instrumentation & controls factor override. Default is None.
|
|
297
|
+
electrical_factor : float | None, optional
|
|
298
|
+
Electrical factor override. Default is None (use process_type table).
|
|
299
|
+
civil_factor : float | None, optional
|
|
300
|
+
Civil factor override. Default is None (use process_type table).
|
|
301
|
+
structural_factor : float | None, optional
|
|
302
|
+
Structural steel factor override. Default is None (use process_type table).
|
|
303
|
+
lagging_factor : float | None, optional
|
|
304
|
+
Lagging & painting factor override. Default is None (use process_type table).
|
|
305
|
+
material_factor : float | None, optional
|
|
306
|
+
Material factor override. Default is None (use material table).
|
|
291
307
|
|
|
292
308
|
Methods
|
|
293
309
|
-------
|
|
@@ -387,10 +403,16 @@ class Equipment:
|
|
|
387
403
|
num_units: int | None = None,
|
|
388
404
|
purchased_cost: float | None = None,
|
|
389
405
|
cost_year: int | None = None,
|
|
390
|
-
cost_func:
|
|
391
|
-
str | None
|
|
392
|
-
) = None, # explicit correlation key
|
|
406
|
+
cost_func: str | None = None,
|
|
393
407
|
target_year: int = 2024,
|
|
408
|
+
erection_factor: float | None = None,
|
|
409
|
+
piping_factor: float | None = None,
|
|
410
|
+
instrumentation_factor: float | None = None,
|
|
411
|
+
electrical_factor: float | None = None,
|
|
412
|
+
civil_factor: float | None = None,
|
|
413
|
+
structural_factor: float | None = None,
|
|
414
|
+
lagging_factor: float | None = None,
|
|
415
|
+
material_factor: float | None = None,
|
|
394
416
|
):
|
|
395
417
|
|
|
396
418
|
self.name = name
|
|
@@ -407,9 +429,48 @@ class Equipment:
|
|
|
407
429
|
)
|
|
408
430
|
self.target_year = target_year
|
|
409
431
|
self._cost_func = cost_func
|
|
410
|
-
self._db = (
|
|
411
|
-
|
|
412
|
-
|
|
432
|
+
self._db = CostCorrelationDB()
|
|
433
|
+
|
|
434
|
+
valid_process_types = list(self.process_factors.keys())
|
|
435
|
+
if process_type not in self.process_factors:
|
|
436
|
+
raise ValueError(
|
|
437
|
+
f"Invalid process_type '{process_type}'. "
|
|
438
|
+
f"Valid options are: {valid_process_types}"
|
|
439
|
+
)
|
|
440
|
+
valid_materials = list(self.material_factors.keys())
|
|
441
|
+
if material not in self.material_factors:
|
|
442
|
+
raise ValueError(
|
|
443
|
+
f"Invalid material '{material}'. "
|
|
444
|
+
f"Valid options are: {valid_materials}"
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
_pf = self.process_factors[process_type]
|
|
448
|
+
self.erection_factor = (
|
|
449
|
+
erection_factor if erection_factor is not None else _pf["fer"]
|
|
450
|
+
)
|
|
451
|
+
self.piping_factor = (
|
|
452
|
+
piping_factor if piping_factor is not None else _pf["fp"]
|
|
453
|
+
)
|
|
454
|
+
self.instrumentation_factor = (
|
|
455
|
+
instrumentation_factor if instrumentation_factor is not None else _pf["fi"]
|
|
456
|
+
)
|
|
457
|
+
self.electrical_factor = (
|
|
458
|
+
electrical_factor if electrical_factor is not None else _pf["fel"]
|
|
459
|
+
)
|
|
460
|
+
self.civil_factor = (
|
|
461
|
+
civil_factor if civil_factor is not None else _pf["fc"]
|
|
462
|
+
)
|
|
463
|
+
self.structural_factor = (
|
|
464
|
+
structural_factor if structural_factor is not None else _pf["fs"]
|
|
465
|
+
)
|
|
466
|
+
self.lagging_factor = (
|
|
467
|
+
lagging_factor if lagging_factor is not None else _pf["fl"]
|
|
468
|
+
)
|
|
469
|
+
self.material_factor = (
|
|
470
|
+
material_factor
|
|
471
|
+
if material_factor is not None
|
|
472
|
+
else self.material_factors[material]
|
|
473
|
+
)
|
|
413
474
|
|
|
414
475
|
if purchased_cost is not None:
|
|
415
476
|
self.purchased_cost = purchased_cost
|
|
@@ -456,29 +517,15 @@ class Equipment:
|
|
|
456
517
|
)
|
|
457
518
|
|
|
458
519
|
def calculate_direct_cost(self) -> float:
|
|
459
|
-
|
|
460
|
-
if self.process_type not in self.process_factors:
|
|
461
|
-
raise ValueError(
|
|
462
|
-
f"Process type not found: {self.process_type}"
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
if self.material not in self.material_factors:
|
|
466
|
-
raise ValueError(
|
|
467
|
-
f"Material not found: {self.material}"
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
factors = self.process_factors[self.process_type]
|
|
471
|
-
fm = self.material_factors[self.material]
|
|
472
|
-
|
|
473
520
|
self.direct_cost = self.purchased_cost * (
|
|
474
|
-
(1 +
|
|
521
|
+
(1 + self.piping_factor) * self.material_factor
|
|
475
522
|
+ (
|
|
476
|
-
|
|
477
|
-
+
|
|
478
|
-
+
|
|
479
|
-
+
|
|
480
|
-
+
|
|
481
|
-
+
|
|
523
|
+
self.erection_factor
|
|
524
|
+
+ self.electrical_factor
|
|
525
|
+
+ self.instrumentation_factor
|
|
526
|
+
+ self.civil_factor
|
|
527
|
+
+ self.structural_factor
|
|
528
|
+
+ self.lagging_factor
|
|
482
529
|
)
|
|
483
530
|
)
|
|
484
531
|
return self.direct_cost
|