malcolmSQ commited on
Commit ·
0dba6f7
1
Parent(s): 22579eb
Update dashboard with improved metrics and parameter handling - Fixed per hectare calculations, added milestone comparisons, improved summary stats, updated README
Browse files- README.md +70 -99
- dashboard/app.py +201 -186
README.md
CHANGED
|
@@ -4,66 +4,57 @@ A Python implementation of the Emissions Reduction (ER) model for Nigeria mangro
|
|
| 4 |
|
| 5 |
## Recent Updates and Improvements
|
| 6 |
|
| 7 |
-
### Growth Model Enhancements (2024)
|
| 8 |
-
- **Updated
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
- Rhizophora: Total = 1.938 × (DBH² H)^0.67628
|
| 10 |
- Avicennia: Total = 1.486 × (DBH² H)^0.55864
|
| 11 |
|
| 12 |
-
- **Chapman-Richards Implementation**:
|
| 13 |
-
- Improved growth parameter estimation
|
| 14 |
-
- Direct use of growth rate parameter (b) instead of deriving from Tm
|
| 15 |
-
- Separate parameterization for DBH and height growth
|
| 16 |
-
- Current parameters:
|
| 17 |
-
* Rhizophora: max DBH 11.07cm, max height 12.0m
|
| 18 |
-
* Avicennia: max DBH 17.0cm, max height 8.8m
|
| 19 |
-
|
| 20 |
-
- **Growth Rate Parameters**:
|
| 21 |
-
- b = 0.25 yr^-1 (independently estimated growth rate)
|
| 22 |
-
- c = 2.1 (shape parameter)
|
| 23 |
-
- Removed Tm-based calculation (previously b = ln(c)/Tm)
|
| 24 |
-
|
| 25 |
### Dashboard Improvements
|
| 26 |
-
-
|
| 27 |
-
-
|
| 28 |
-
-
|
| 29 |
-
-
|
| 30 |
-
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
-
|
| 34 |
-
-
|
| 35 |
-
-
|
| 36 |
-
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
-
|
| 41 |
-
-
|
| 42 |
-
-
|
| 43 |
-
-
|
| 44 |
-
- Configurable planting schedules and mortality rates
|
| 45 |
-
- Scenario comparison for different DBH ranges and growth rates
|
| 46 |
-
- Per-species carbon tracking and cumulative hectare calculations
|
| 47 |
|
| 48 |
## Model Parameters
|
| 49 |
|
| 50 |
### Species Parameters
|
| 51 |
-
- **Rhizophora**
|
| 52 |
-
-
|
| 53 |
-
-
|
|
|
|
| 54 |
- Configurable planting density and mortality rates
|
| 55 |
|
| 56 |
-
- **Avicennia**
|
| 57 |
-
-
|
| 58 |
-
-
|
|
|
|
| 59 |
- Configurable planting density and mortality rates
|
| 60 |
|
| 61 |
-
###
|
| 62 |
-
-
|
| 63 |
-
-
|
| 64 |
-
- Buffer pool
|
| 65 |
-
-
|
| 66 |
-
- Baseline emissions rate
|
| 67 |
|
| 68 |
## Installation
|
| 69 |
|
|
@@ -80,74 +71,54 @@ A Python implementation of the Emissions Reduction (ER) model for Nigeria mangro
|
|
| 80 |
pre-commit install
|
| 81 |
```
|
| 82 |
|
| 83 |
-
## Project Structure
|
| 84 |
-
|
| 85 |
-
- `src/` - Core model implementation
|
| 86 |
-
- `er_model.py` - Main ER calculation logic
|
| 87 |
-
- `allometry.py` - Species-specific allometric equations
|
| 88 |
-
- `metrics.py` - Carbon conversion and statistics
|
| 89 |
-
- `scripts/` - Command-line tools
|
| 90 |
-
- `run_pipeline.py` - Execute full ER calculation workflow
|
| 91 |
-
- `analyze_results.py` - Generate reports and visualizations
|
| 92 |
-
- `dashboard/` - Gradio web application for interactive exploration
|
| 93 |
-
- `configs/` - Default parameter files
|
| 94 |
-
- `outputs/` - Generated results and figures
|
| 95 |
-
- `tests/` - Test suite
|
| 96 |
-
|
| 97 |
-
## Dashboard Features
|
| 98 |
-
|
| 99 |
-
The interactive Gradio dashboard allows users to:
|
| 100 |
-
1. Adjust species-specific parameters:
|
| 101 |
-
- Planting density (trees/ha)
|
| 102 |
-
- Year-by-year mortality rates
|
| 103 |
-
2. Configure planting schedule:
|
| 104 |
-
- Area planted per year (ha)
|
| 105 |
-
- Up to 5-year planting schedule
|
| 106 |
-
3. Modify carbon parameters:
|
| 107 |
-
- Buffer pool percentage
|
| 108 |
-
4. Explore scenarios:
|
| 109 |
-
- Test different DBH ranges
|
| 110 |
-
- Adjust growth rate factors
|
| 111 |
-
- Compare results across 1000 ha scenarios
|
| 112 |
-
5. View results:
|
| 113 |
-
- Carbon sequestration over time
|
| 114 |
-
- Milestone year comparisons
|
| 115 |
-
- Per-species carbon tracking
|
| 116 |
-
- Cumulative hectare metrics
|
| 117 |
-
- Scenario comparison tables
|
| 118 |
-
|
| 119 |
## Usage
|
| 120 |
|
| 121 |
1. Configure parameters in `configs/params.yaml`
|
| 122 |
|
| 123 |
-
2.
|
| 124 |
-
```bash
|
| 125 |
-
python scripts/run_pipeline.py --config configs/params.yaml
|
| 126 |
-
```
|
| 127 |
-
|
| 128 |
-
3. Launch the dashboard:
|
| 129 |
```bash
|
| 130 |
-
python dashboard
|
| 131 |
```
|
| 132 |
This will start a Gradio server and open the dashboard in your default web browser.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
|
| 134 |
-
##
|
| 135 |
|
| 136 |
-
-
|
| 137 |
-
-
|
| 138 |
-
-
|
| 139 |
-
-
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
## Dependencies
|
| 142 |
|
| 143 |
Key dependencies (specified in `environment.yml`):
|
| 144 |
-
- numpy
|
| 145 |
-
- pandas
|
| 146 |
-
- gradio
|
| 147 |
- matplotlib
|
| 148 |
- pyyaml
|
| 149 |
- pytest (for development)
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
## License
|
| 152 |
|
| 153 |
[License details to be added]
|
|
|
|
| 4 |
|
| 5 |
## Recent Updates and Improvements
|
| 6 |
|
| 7 |
+
### Growth Model Enhancements (August 2024)
|
| 8 |
+
- **Updated Growth Parameters**: Back-solved from August 2024 field data (t=1.58 years):
|
| 9 |
+
- DBH growth rate (b): Changed from 0.1237 to 0.4736 yr⁻¹
|
| 10 |
+
- Height growth rate (b): Changed from 0.1237 to 0.1335 yr⁻¹
|
| 11 |
+
- Height shape parameter (c): Changed from 2.1 to 1.5
|
| 12 |
+
- Rhizophora height asymptote: Changed from 12.0m to 11.58m
|
| 13 |
+
|
| 14 |
+
- **Allometric Equations**: Using Zanvo et al. (2023) species-specific equations:
|
| 15 |
- Rhizophora: Total = 1.938 × (DBH² H)^0.67628
|
| 16 |
- Avicennia: Total = 1.486 × (DBH² H)^0.55864
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
### Dashboard Improvements
|
| 19 |
+
- **Enhanced Parameter Controls**:
|
| 20 |
+
- Direct input of Chapman-Richards parameters
|
| 21 |
+
- Separate growth parameters for DBH and height
|
| 22 |
+
- Real-time model updates
|
| 23 |
+
- Improved validation and error handling
|
| 24 |
+
|
| 25 |
+
- **Improved Visualizations**:
|
| 26 |
+
- Carbon sequestration over time
|
| 27 |
+
- Annual emission reductions
|
| 28 |
+
- Per-tree biomass growth curves
|
| 29 |
+
- Enhanced data tables and summaries
|
| 30 |
+
|
| 31 |
+
- **Detailed Metrics**:
|
| 32 |
+
- Project overview with total area and buffer pool
|
| 33 |
+
- Comprehensive carbon sequestration metrics
|
| 34 |
+
- Milestone year comparisons (10, 20, 30 years)
|
| 35 |
+
- Per hectare metrics with annual rates
|
| 36 |
+
- Species-specific results
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
## Model Parameters
|
| 39 |
|
| 40 |
### Species Parameters
|
| 41 |
+
- **Rhizophora spp.**
|
| 42 |
+
- DBH: a = 11.07 cm, b = 0.4736 yr⁻¹, c = 2.1
|
| 43 |
+
- Height: a = 11.58 m, b = 0.1335 yr⁻¹, c = 1.5
|
| 44 |
+
- Initial values: DBH = 1.0 cm, Height = 0.5 m
|
| 45 |
- Configurable planting density and mortality rates
|
| 46 |
|
| 47 |
+
- **Avicennia germinans**
|
| 48 |
+
- DBH: a = 11.07 cm, b = 0.4736 yr⁻¹, c = 2.1
|
| 49 |
+
- Height: a = 11.58 m, b = 0.1335 yr⁻¹, c = 1.5
|
| 50 |
+
- Initial values: DBH = 1.0 cm, Height = 0.5 m
|
| 51 |
- Configurable planting density and mortality rates
|
| 52 |
|
| 53 |
+
### Project Configuration
|
| 54 |
+
- Total area: 5000 ha (2500 ha each in years 1-2)
|
| 55 |
+
- Project duration: 30 years
|
| 56 |
+
- Buffer pool: 20%
|
| 57 |
+
- Species mix: Rhizophora spp. and Avicennia germinans
|
|
|
|
| 58 |
|
| 59 |
## Installation
|
| 60 |
|
|
|
|
| 71 |
pre-commit install
|
| 72 |
```
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
## Usage
|
| 75 |
|
| 76 |
1. Configure parameters in `configs/params.yaml`
|
| 77 |
|
| 78 |
+
2. Launch the dashboard:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
```bash
|
| 80 |
+
python -m dashboard.app
|
| 81 |
```
|
| 82 |
This will start a Gradio server and open the dashboard in your default web browser.
|
| 83 |
+
The dashboard will be accessible via a public URL for sharing.
|
| 84 |
+
|
| 85 |
+
3. Using the Dashboard:
|
| 86 |
+
- Adjust species parameters (density, mortality, growth)
|
| 87 |
+
- Set planting schedule (up to 5 years)
|
| 88 |
+
- Configure buffer pool percentage
|
| 89 |
+
- View real-time updates to:
|
| 90 |
+
* Carbon sequestration curves
|
| 91 |
+
* Annual emission reductions
|
| 92 |
+
* Biomass per tree growth
|
| 93 |
+
* Detailed metrics and summaries
|
| 94 |
|
| 95 |
+
## Project Structure
|
| 96 |
|
| 97 |
+
- `src/` - Core model implementation
|
| 98 |
+
- `er_model.py` - Main ER calculation logic
|
| 99 |
+
- `allometry.py` - Species-specific allometric equations
|
| 100 |
+
- `metrics.py` - Carbon conversion and statistics
|
| 101 |
+
- `dashboard/` - Gradio web application
|
| 102 |
+
- `configs/` - Parameter configuration files
|
| 103 |
+
- `tests/` - Test suite
|
| 104 |
|
| 105 |
## Dependencies
|
| 106 |
|
| 107 |
Key dependencies (specified in `environment.yml`):
|
| 108 |
+
- numpy
|
| 109 |
+
- pandas
|
| 110 |
+
- gradio
|
| 111 |
- matplotlib
|
| 112 |
- pyyaml
|
| 113 |
- pytest (for development)
|
| 114 |
|
| 115 |
+
## Development
|
| 116 |
+
|
| 117 |
+
- Code style is enforced using `black` and `isort`
|
| 118 |
+
- Type hints are required for all public functions
|
| 119 |
+
- Tests are written using `pytest`
|
| 120 |
+
- Pre-commit hooks ensure code quality
|
| 121 |
+
|
| 122 |
## License
|
| 123 |
|
| 124 |
[License details to be added]
|
dashboard/app.py
CHANGED
|
@@ -9,6 +9,7 @@ import matplotlib.pyplot as plt
|
|
| 9 |
import numpy as np
|
| 10 |
import pandas as pd
|
| 11 |
import yaml
|
|
|
|
| 12 |
|
| 13 |
from src.er_model import ERModel
|
| 14 |
|
|
@@ -21,17 +22,23 @@ def load_config(config_path: Path = Path("configs/params.yaml")) -> dict:
|
|
| 21 |
|
| 22 |
def run_model(config: dict) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
|
| 23 |
"""Run model with current parameters."""
|
| 24 |
-
#
|
| 25 |
-
temp_config = Path("configs/
|
| 26 |
-
with open(temp_config, "w") as f:
|
| 27 |
-
yaml.dump(config, f)
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
|
| 37 |
def format_number(x):
|
|
@@ -48,7 +55,13 @@ def format_number(x):
|
|
| 48 |
def create_plots_and_tables(
|
| 49 |
results: pd.DataFrame,
|
| 50 |
species_results: pd.DataFrame,
|
| 51 |
-
scenario_results: pd.DataFrame
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
) -> Tuple[plt.Figure, plt.Figure, plt.Figure, str, pd.DataFrame, pd.DataFrame]:
|
| 53 |
"""Create carbon sequestration plots, summary, and tables."""
|
| 54 |
# Clear any existing plots
|
|
@@ -96,11 +109,6 @@ def create_plots_and_tables(
|
|
| 96 |
fig3 = plt.figure(figsize=(10, 6))
|
| 97 |
ax3 = fig3.add_subplot(111)
|
| 98 |
|
| 99 |
-
# Load config to get growth parameters
|
| 100 |
-
config = load_config()
|
| 101 |
-
rhiz_params = config["species"][0]["chapman_richards"]
|
| 102 |
-
avic_params = config["species"][1]["chapman_richards"]
|
| 103 |
-
|
| 104 |
# Get initial values
|
| 105 |
rhiz_init = config["species"][0]["initial_values"]
|
| 106 |
avic_init = config["species"][1]["initial_values"]
|
|
@@ -114,12 +122,12 @@ def create_plots_and_tables(
|
|
| 114 |
return initial + (a - initial) * (1 - np.exp(-b * t)) ** c
|
| 115 |
|
| 116 |
# Calculate growth for Rhizophora
|
| 117 |
-
rhiz_dbh = chapman_richards(years,
|
| 118 |
-
rhiz_height = chapman_richards(years,
|
| 119 |
|
| 120 |
# Calculate growth for Avicennia
|
| 121 |
-
avic_dbh = chapman_richards(years,
|
| 122 |
-
avic_height = chapman_richards(years,
|
| 123 |
|
| 124 |
# Calculate biomass using Zanvo equations (convert kg to tonnes)
|
| 125 |
rhiz_biomass = 1.938 * (rhiz_dbh**2 * rhiz_height)**0.67628 / 1000 # Convert kg to tonnes
|
|
@@ -147,7 +155,9 @@ def create_plots_and_tables(
|
|
| 147 |
plt.draw()
|
| 148 |
|
| 149 |
# Calculate total area from planting schedule
|
| 150 |
-
total_area = sum(
|
|
|
|
|
|
|
| 151 |
|
| 152 |
# Get final year carbon values
|
| 153 |
final_gross = results["gross_carbon"].iloc[-1]
|
|
@@ -157,23 +167,54 @@ def create_plots_and_tables(
|
|
| 157 |
if total_area > 0:
|
| 158 |
gross_carbon_per_ha = final_gross / total_area
|
| 159 |
net_carbon_per_ha = final_net / total_area
|
|
|
|
| 160 |
else:
|
| 161 |
gross_carbon_per_ha = 0
|
| 162 |
net_carbon_per_ha = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
# Create summary text with formatted numbers
|
| 165 |
summary = f"""
|
| 166 |
-
|
| 167 |
-
---------------
|
| 168 |
-
|
| 169 |
Total Area Planted: {format_number(total_area)} ha
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
Per Hectare Metrics (Year 30):
|
| 174 |
-----------------------------
|
| 175 |
Gross Carbon per ha: {format_number(gross_carbon_per_ha)} tCO2/ha
|
| 176 |
Net Carbon per ha: {format_number(net_carbon_per_ha)} tCO2/ha
|
|
|
|
| 177 |
"""
|
| 178 |
|
| 179 |
# Format species results table with comma separators
|
|
@@ -223,12 +264,24 @@ def update_model(
|
|
| 223 |
year_4_area: float,
|
| 224 |
year_5_area: float,
|
| 225 |
buffer_percentage: float,
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
) -> Tuple[plt.Figure, plt.Figure, plt.Figure, str, pd.DataFrame, pd.DataFrame]:
|
| 233 |
"""Update model with new parameters and return plots and tables."""
|
| 234 |
# Load base config
|
|
@@ -244,6 +297,22 @@ def update_model(
|
|
| 244 |
"year_5": rhiz_mort_5
|
| 245 |
})
|
| 246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
# Update Avicennia parameters
|
| 248 |
config["species"][1]["planting_density"] = avic_density
|
| 249 |
config["species"][1]["mortality_rates"].update({
|
|
@@ -254,6 +323,22 @@ def update_model(
|
|
| 254 |
"year_5": avic_mort_5
|
| 255 |
})
|
| 256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
# Update planting schedule
|
| 258 |
config["project"]["planting_schedule"].update({
|
| 259 |
"year_1": year_1_area,
|
|
@@ -266,179 +351,109 @@ def update_model(
|
|
| 266 |
# Update carbon parameters
|
| 267 |
config["carbon"]["buffer_percentage"] = buffer_percentage
|
| 268 |
|
| 269 |
-
# Add scenario parameters
|
| 270 |
-
config["scenarios"] = {
|
| 271 |
-
"area": scenario_area,
|
| 272 |
-
"dbh_range": [dbh_range_min, dbh_range_max],
|
| 273 |
-
"height_range": [height_range_min, height_range_max],
|
| 274 |
-
"growth_rate_factor": growth_rate_factor
|
| 275 |
-
}
|
| 276 |
-
|
| 277 |
# Run model and create plots/tables
|
| 278 |
results, species_results, scenario_results = run_model(config)
|
| 279 |
-
return create_plots_and_tables(results, species_results, scenario_results)
|
| 280 |
|
| 281 |
|
| 282 |
def main():
|
| 283 |
-
"""
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
# Extract default values
|
| 287 |
-
rhiz_defaults = config["species"][0]
|
| 288 |
-
avic_defaults = config["species"][1]
|
| 289 |
-
planting_defaults = config["project"]["planting_schedule"]
|
| 290 |
-
carbon_defaults = config["carbon"]
|
| 291 |
-
scenario_defaults = config.get("scenarios", {
|
| 292 |
-
"area": 1000.0,
|
| 293 |
-
"dbh_range": [1.0, 25.0],
|
| 294 |
-
"height_range": [0.5, 15.0],
|
| 295 |
-
"growth_rate_factor": 1.0
|
| 296 |
-
})
|
| 297 |
-
|
| 298 |
-
# Create interface
|
| 299 |
-
with gr.Blocks(title="ER Model Dashboard", theme=gr.themes.Soft()) as interface:
|
| 300 |
gr.Markdown("# ER Model Dashboard")
|
| 301 |
-
gr.Markdown("Explore carbon sequestration results
|
| 302 |
|
| 303 |
-
with gr.
|
| 304 |
-
with gr.
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
value=
|
| 311 |
-
label="
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
label="Planting Density (trees/ha)"
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
value=
|
| 351 |
-
label="Buffer
|
| 352 |
-
)
|
| 353 |
-
|
| 354 |
-
# Scenario parameters
|
| 355 |
-
gr.Markdown("### Scenario Parameters")
|
| 356 |
-
scenario_area = gr.Slider(
|
| 357 |
-
minimum=100,
|
| 358 |
-
maximum=2000,
|
| 359 |
-
value=scenario_defaults["area"],
|
| 360 |
-
label="Scenario Area (ha)"
|
| 361 |
-
)
|
| 362 |
-
dbh_range_min = gr.Slider(
|
| 363 |
-
minimum=0.5,
|
| 364 |
-
maximum=10,
|
| 365 |
-
value=scenario_defaults["dbh_range"][0],
|
| 366 |
-
label="Min DBH (cm)"
|
| 367 |
-
)
|
| 368 |
-
dbh_range_max = gr.Slider(
|
| 369 |
-
minimum=10,
|
| 370 |
-
maximum=30,
|
| 371 |
-
value=scenario_defaults["dbh_range"][1],
|
| 372 |
-
label="Max DBH (cm)"
|
| 373 |
-
)
|
| 374 |
-
height_range_min = gr.Slider(
|
| 375 |
-
minimum=0.5,
|
| 376 |
-
maximum=5,
|
| 377 |
-
value=scenario_defaults["height_range"][0],
|
| 378 |
-
label="Min Height (m)"
|
| 379 |
-
)
|
| 380 |
-
height_range_max = gr.Slider(
|
| 381 |
-
minimum=5,
|
| 382 |
-
maximum=20,
|
| 383 |
-
value=scenario_defaults["height_range"][1],
|
| 384 |
-
label="Max Height (m)"
|
| 385 |
-
)
|
| 386 |
-
growth_rate_factor = gr.Slider(
|
| 387 |
-
minimum=0.5,
|
| 388 |
-
maximum=2.0,
|
| 389 |
-
value=scenario_defaults["growth_rate_factor"],
|
| 390 |
-
label="Growth Rate Factor"
|
| 391 |
-
)
|
| 392 |
-
|
| 393 |
-
# Outputs
|
| 394 |
-
with gr.Row():
|
| 395 |
-
plot1 = gr.Plot(label="Carbon Sequestration Over Time")
|
| 396 |
-
plot2 = gr.Plot(label="Annual Emission Reductions")
|
| 397 |
|
| 398 |
with gr.Row():
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
summary = gr.Textbox(label="Summary", lines=10)
|
| 402 |
|
| 403 |
-
with gr.
|
| 404 |
-
|
| 405 |
-
label="
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
)
|
|
|
|
|
|
|
| 412 |
|
| 413 |
-
#
|
| 414 |
inputs = [
|
| 415 |
rhiz_density, rhiz_mort_1, rhiz_mort_2, rhiz_mort_3, rhiz_mort_4, rhiz_mort_5,
|
| 416 |
avic_density, avic_mort_1, avic_mort_2, avic_mort_3, avic_mort_4, avic_mort_5,
|
| 417 |
year_1_area, year_2_area, year_3_area, year_4_area, year_5_area,
|
| 418 |
-
buffer_percentage,
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
]
|
| 421 |
|
| 422 |
-
outputs = [
|
| 423 |
|
| 424 |
-
#
|
| 425 |
-
|
| 426 |
-
fn=update_model,
|
| 427 |
-
inputs=inputs,
|
| 428 |
-
outputs=outputs,
|
| 429 |
-
)
|
| 430 |
|
| 431 |
-
#
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
inputs=inputs,
|
| 436 |
-
outputs=outputs
|
| 437 |
-
)
|
| 438 |
-
|
| 439 |
-
# Launch interface
|
| 440 |
-
interface.launch(share=False)
|
| 441 |
|
| 442 |
|
| 443 |
if __name__ == "__main__":
|
| 444 |
-
main()
|
|
|
|
|
|
| 9 |
import numpy as np
|
| 10 |
import pandas as pd
|
| 11 |
import yaml
|
| 12 |
+
import uuid
|
| 13 |
|
| 14 |
from src.er_model import ERModel
|
| 15 |
|
|
|
|
| 22 |
|
| 23 |
def run_model(config: dict) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
|
| 24 |
"""Run model with current parameters."""
|
| 25 |
+
# Create a temporary config file with a unique name
|
| 26 |
+
temp_config = Path(f"configs/temp_config_{uuid.uuid4()}.yaml")
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
try:
|
| 29 |
+
# Save temporary config
|
| 30 |
+
with open(temp_config, "w") as f:
|
| 31 |
+
yaml.dump(config, f)
|
| 32 |
+
|
| 33 |
+
# Run model
|
| 34 |
+
model = ERModel(temp_config)
|
| 35 |
+
results, species_results, scenario_results = model.run()
|
| 36 |
+
|
| 37 |
+
return results, species_results, scenario_results
|
| 38 |
+
finally:
|
| 39 |
+
# Clean up temp file
|
| 40 |
+
if temp_config.exists():
|
| 41 |
+
temp_config.unlink()
|
| 42 |
|
| 43 |
|
| 44 |
def format_number(x):
|
|
|
|
| 55 |
def create_plots_and_tables(
|
| 56 |
results: pd.DataFrame,
|
| 57 |
species_results: pd.DataFrame,
|
| 58 |
+
scenario_results: pd.DataFrame,
|
| 59 |
+
config: dict,
|
| 60 |
+
year_1_area: float,
|
| 61 |
+
year_2_area: float,
|
| 62 |
+
year_3_area: float,
|
| 63 |
+
year_4_area: float,
|
| 64 |
+
year_5_area: float
|
| 65 |
) -> Tuple[plt.Figure, plt.Figure, plt.Figure, str, pd.DataFrame, pd.DataFrame]:
|
| 66 |
"""Create carbon sequestration plots, summary, and tables."""
|
| 67 |
# Clear any existing plots
|
|
|
|
| 109 |
fig3 = plt.figure(figsize=(10, 6))
|
| 110 |
ax3 = fig3.add_subplot(111)
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
# Get initial values
|
| 113 |
rhiz_init = config["species"][0]["initial_values"]
|
| 114 |
avic_init = config["species"][1]["initial_values"]
|
|
|
|
| 122 |
return initial + (a - initial) * (1 - np.exp(-b * t)) ** c
|
| 123 |
|
| 124 |
# Calculate growth for Rhizophora
|
| 125 |
+
rhiz_dbh = chapman_richards(years, config["species"][0]["chapman_richards"]["dbh"], rhiz_init["dbh"])
|
| 126 |
+
rhiz_height = chapman_richards(years, config["species"][0]["chapman_richards"]["height"], rhiz_init["height"])
|
| 127 |
|
| 128 |
# Calculate growth for Avicennia
|
| 129 |
+
avic_dbh = chapman_richards(years, config["species"][1]["chapman_richards"]["dbh"], avic_init["dbh"])
|
| 130 |
+
avic_height = chapman_richards(years, config["species"][1]["chapman_richards"]["height"], avic_init["height"])
|
| 131 |
|
| 132 |
# Calculate biomass using Zanvo equations (convert kg to tonnes)
|
| 133 |
rhiz_biomass = 1.938 * (rhiz_dbh**2 * rhiz_height)**0.67628 / 1000 # Convert kg to tonnes
|
|
|
|
| 155 |
plt.draw()
|
| 156 |
|
| 157 |
# Calculate total area from planting schedule
|
| 158 |
+
total_area = sum([
|
| 159 |
+
year_1_area, year_2_area, year_3_area, year_4_area, year_5_area
|
| 160 |
+
])
|
| 161 |
|
| 162 |
# Get final year carbon values
|
| 163 |
final_gross = results["gross_carbon"].iloc[-1]
|
|
|
|
| 167 |
if total_area > 0:
|
| 168 |
gross_carbon_per_ha = final_gross / total_area
|
| 169 |
net_carbon_per_ha = final_net / total_area
|
| 170 |
+
annual_net_per_ha = net_carbon_per_ha / len(results) # Divide by actual years
|
| 171 |
else:
|
| 172 |
gross_carbon_per_ha = 0
|
| 173 |
net_carbon_per_ha = 0
|
| 174 |
+
annual_net_per_ha = 0
|
| 175 |
+
|
| 176 |
+
# Get year 10 and year 20 values for intermediate metrics
|
| 177 |
+
year_10_gross = results["gross_carbon"].iloc[9] if len(results) > 9 else 0
|
| 178 |
+
year_10_net = results["net_carbon"].iloc[9] if len(results) > 9 else 0
|
| 179 |
+
year_20_gross = results["gross_carbon"].iloc[19] if len(results) > 19 else 0
|
| 180 |
+
year_20_net = results["net_carbon"].iloc[19] if len(results) > 19 else 0
|
| 181 |
+
|
| 182 |
+
# Calculate annual averages
|
| 183 |
+
years_with_carbon = len(results[results["gross_carbon"] > 0])
|
| 184 |
+
if years_with_carbon > 0:
|
| 185 |
+
avg_annual_gross = final_gross / years_with_carbon
|
| 186 |
+
avg_annual_net = final_net / years_with_carbon
|
| 187 |
+
else:
|
| 188 |
+
avg_annual_gross = 0
|
| 189 |
+
avg_annual_net = 0
|
| 190 |
|
| 191 |
# Create summary text with formatted numbers
|
| 192 |
summary = f"""
|
| 193 |
+
Project Overview:
|
| 194 |
+
----------------
|
| 195 |
+
Duration: {len(results)} years
|
| 196 |
Total Area Planted: {format_number(total_area)} ha
|
| 197 |
+
Buffer Pool: {config["carbon"]["buffer_percentage"]}%
|
| 198 |
+
|
| 199 |
+
Carbon Sequestration:
|
| 200 |
+
-------------------
|
| 201 |
+
Total Gross Carbon (Year 30): {format_number(final_gross)} tCO2
|
| 202 |
+
Total Net Carbon (Year 30): {format_number(final_net)} tCO2
|
| 203 |
+
Average Annual Gross: {format_number(avg_annual_gross)} tCO2/yr
|
| 204 |
+
Average Annual Net: {format_number(avg_annual_net)} tCO2/yr
|
| 205 |
+
|
| 206 |
+
Milestone Years:
|
| 207 |
+
--------------
|
| 208 |
+
Year 10 Gross: {format_number(year_10_gross)} tCO2
|
| 209 |
+
Year 10 Net: {format_number(year_10_net)} tCO2
|
| 210 |
+
Year 20 Gross: {format_number(year_20_gross)} tCO2
|
| 211 |
+
Year 20 Net: {format_number(year_20_net)} tCO2
|
| 212 |
|
| 213 |
Per Hectare Metrics (Year 30):
|
| 214 |
-----------------------------
|
| 215 |
Gross Carbon per ha: {format_number(gross_carbon_per_ha)} tCO2/ha
|
| 216 |
Net Carbon per ha: {format_number(net_carbon_per_ha)} tCO2/ha
|
| 217 |
+
Average Annual Net per ha: {format_number(annual_net_per_ha)} tCO2/ha/yr
|
| 218 |
"""
|
| 219 |
|
| 220 |
# Format species results table with comma separators
|
|
|
|
| 264 |
year_4_area: float,
|
| 265 |
year_5_area: float,
|
| 266 |
buffer_percentage: float,
|
| 267 |
+
# Chapman-Richards parameters for Rhizophora
|
| 268 |
+
rhiz_dbh_a: float,
|
| 269 |
+
rhiz_dbh_b: float,
|
| 270 |
+
rhiz_dbh_c: float,
|
| 271 |
+
rhiz_height_a: float,
|
| 272 |
+
rhiz_height_b: float,
|
| 273 |
+
rhiz_height_c: float,
|
| 274 |
+
rhiz_initial_dbh: float,
|
| 275 |
+
rhiz_initial_height: float,
|
| 276 |
+
# Chapman-Richards parameters for Avicennia
|
| 277 |
+
avic_dbh_a: float,
|
| 278 |
+
avic_dbh_b: float,
|
| 279 |
+
avic_dbh_c: float,
|
| 280 |
+
avic_height_a: float,
|
| 281 |
+
avic_height_b: float,
|
| 282 |
+
avic_height_c: float,
|
| 283 |
+
avic_initial_dbh: float,
|
| 284 |
+
avic_initial_height: float,
|
| 285 |
) -> Tuple[plt.Figure, plt.Figure, plt.Figure, str, pd.DataFrame, pd.DataFrame]:
|
| 286 |
"""Update model with new parameters and return plots and tables."""
|
| 287 |
# Load base config
|
|
|
|
| 297 |
"year_5": rhiz_mort_5
|
| 298 |
})
|
| 299 |
|
| 300 |
+
# Update Rhizophora Chapman-Richards parameters
|
| 301 |
+
config["species"][0]["chapman_richards"]["dbh"].update({
|
| 302 |
+
"a": rhiz_dbh_a,
|
| 303 |
+
"b": rhiz_dbh_b,
|
| 304 |
+
"c": rhiz_dbh_c
|
| 305 |
+
})
|
| 306 |
+
config["species"][0]["chapman_richards"]["height"].update({
|
| 307 |
+
"a": rhiz_height_a,
|
| 308 |
+
"b": rhiz_height_b,
|
| 309 |
+
"c": rhiz_height_c
|
| 310 |
+
})
|
| 311 |
+
config["species"][0]["initial_values"].update({
|
| 312 |
+
"dbh": rhiz_initial_dbh,
|
| 313 |
+
"height": rhiz_initial_height
|
| 314 |
+
})
|
| 315 |
+
|
| 316 |
# Update Avicennia parameters
|
| 317 |
config["species"][1]["planting_density"] = avic_density
|
| 318 |
config["species"][1]["mortality_rates"].update({
|
|
|
|
| 323 |
"year_5": avic_mort_5
|
| 324 |
})
|
| 325 |
|
| 326 |
+
# Update Avicennia Chapman-Richards parameters
|
| 327 |
+
config["species"][1]["chapman_richards"]["dbh"].update({
|
| 328 |
+
"a": avic_dbh_a,
|
| 329 |
+
"b": avic_dbh_b,
|
| 330 |
+
"c": avic_dbh_c
|
| 331 |
+
})
|
| 332 |
+
config["species"][1]["chapman_richards"]["height"].update({
|
| 333 |
+
"a": avic_height_a,
|
| 334 |
+
"b": avic_height_b,
|
| 335 |
+
"c": avic_height_c
|
| 336 |
+
})
|
| 337 |
+
config["species"][1]["initial_values"].update({
|
| 338 |
+
"dbh": avic_initial_dbh,
|
| 339 |
+
"height": avic_initial_height
|
| 340 |
+
})
|
| 341 |
+
|
| 342 |
# Update planting schedule
|
| 343 |
config["project"]["planting_schedule"].update({
|
| 344 |
"year_1": year_1_area,
|
|
|
|
| 351 |
# Update carbon parameters
|
| 352 |
config["carbon"]["buffer_percentage"] = buffer_percentage
|
| 353 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
# Run model and create plots/tables
|
| 355 |
results, species_results, scenario_results = run_model(config)
|
| 356 |
+
return create_plots_and_tables(results, species_results, scenario_results, config, year_1_area, year_2_area, year_3_area, year_4_area, year_5_area)
|
| 357 |
|
| 358 |
|
| 359 |
def main():
|
| 360 |
+
"""Run the Gradio interface."""
|
| 361 |
+
with gr.Blocks(theme=gr.themes.Soft()) as app:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
gr.Markdown("# ER Model Dashboard")
|
| 363 |
+
gr.Markdown("Explore carbon sequestration results with different growth parameters")
|
| 364 |
|
| 365 |
+
with gr.Tab("Model Parameters"):
|
| 366 |
+
with gr.Row():
|
| 367 |
+
with gr.Column():
|
| 368 |
+
gr.Markdown("### Rhizophora Parameters")
|
| 369 |
+
rhiz_density = gr.Number(value=2500, label="Planting Density (trees/ha)")
|
| 370 |
+
rhiz_mort_1 = gr.Number(value=20, label="Year 1 Mortality (%)")
|
| 371 |
+
rhiz_mort_2 = gr.Number(value=10, label="Year 2 Mortality (%)")
|
| 372 |
+
rhiz_mort_3 = gr.Number(value=5, label="Year 3 Mortality (%)")
|
| 373 |
+
rhiz_mort_4 = gr.Number(value=2, label="Year 4 Mortality (%)")
|
| 374 |
+
rhiz_mort_5 = gr.Number(value=1, label="Year 5 Mortality (%)")
|
| 375 |
+
|
| 376 |
+
gr.Markdown("### Rhizophora Growth Parameters")
|
| 377 |
+
rhiz_dbh_a = gr.Number(value=11.07, label="DBH Asymptote (cm)")
|
| 378 |
+
rhiz_dbh_b = gr.Number(value=0.4736, label="DBH Growth Rate (yr⁻¹)")
|
| 379 |
+
rhiz_dbh_c = gr.Number(value=2.1, label="DBH Shape Parameter")
|
| 380 |
+
rhiz_height_a = gr.Number(value=11.58, label="Height Asymptote (m)")
|
| 381 |
+
rhiz_height_b = gr.Number(value=0.1335, label="Height Growth Rate (yr⁻¹)")
|
| 382 |
+
rhiz_height_c = gr.Number(value=1.5, label="Height Shape Parameter")
|
| 383 |
+
rhiz_initial_dbh = gr.Number(value=1.0, label="Initial DBH (cm)")
|
| 384 |
+
rhiz_initial_height = gr.Number(value=0.5, label="Initial Height (m)")
|
| 385 |
+
|
| 386 |
+
with gr.Column():
|
| 387 |
+
gr.Markdown("### Avicennia Parameters")
|
| 388 |
+
avic_density = gr.Number(value=2500, label="Planting Density (trees/ha)")
|
| 389 |
+
avic_mort_1 = gr.Number(value=20, label="Year 1 Mortality (%)")
|
| 390 |
+
avic_mort_2 = gr.Number(value=10, label="Year 2 Mortality (%)")
|
| 391 |
+
avic_mort_3 = gr.Number(value=5, label="Year 3 Mortality (%)")
|
| 392 |
+
avic_mort_4 = gr.Number(value=2, label="Year 4 Mortality (%)")
|
| 393 |
+
avic_mort_5 = gr.Number(value=1, label="Year 5 Mortality (%)")
|
| 394 |
+
|
| 395 |
+
gr.Markdown("### Avicennia Growth Parameters")
|
| 396 |
+
avic_dbh_a = gr.Number(value=11.07, label="DBH Asymptote (cm)")
|
| 397 |
+
avic_dbh_b = gr.Number(value=0.4736, label="DBH Growth Rate (yr⁻¹)")
|
| 398 |
+
avic_dbh_c = gr.Number(value=2.1, label="DBH Shape Parameter")
|
| 399 |
+
avic_height_a = gr.Number(value=11.58, label="Height Asymptote (m)")
|
| 400 |
+
avic_height_b = gr.Number(value=0.1335, label="Height Growth Rate (yr⁻¹)")
|
| 401 |
+
avic_height_c = gr.Number(value=1.5, label="Height Shape Parameter")
|
| 402 |
+
avic_initial_dbh = gr.Number(value=1.0, label="Initial DBH (cm)")
|
| 403 |
+
avic_initial_height = gr.Number(value=0.5, label="Initial Height (m)")
|
| 404 |
+
|
| 405 |
+
with gr.Row():
|
| 406 |
+
with gr.Column():
|
| 407 |
+
gr.Markdown("### Project Parameters")
|
| 408 |
+
year_1_area = gr.Number(value=2500, label="Year 1 Planting Area (ha)")
|
| 409 |
+
year_2_area = gr.Number(value=2500, label="Year 2 Planting Area (ha)")
|
| 410 |
+
year_3_area = gr.Number(value=0, label="Year 3 Planting Area (ha)")
|
| 411 |
+
year_4_area = gr.Number(value=0, label="Year 4 Planting Area (ha)")
|
| 412 |
+
year_5_area = gr.Number(value=0, label="Year 5 Planting Area (ha)")
|
| 413 |
+
buffer_percentage = gr.Number(value=20, label="Buffer Pool (%)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
with gr.Row():
|
| 416 |
+
update_btn = gr.Button("Update Model")
|
|
|
|
|
|
|
| 417 |
|
| 418 |
+
with gr.Tab("Results"):
|
| 419 |
+
with gr.Row():
|
| 420 |
+
carbon_plot = gr.Plot(label="Carbon Sequestration")
|
| 421 |
+
annual_plot = gr.Plot(label="Annual ERs")
|
| 422 |
+
biomass_plot = gr.Plot(label="Biomass per Tree")
|
| 423 |
+
|
| 424 |
+
summary_text = gr.Textbox(label="Summary", lines=10)
|
| 425 |
+
|
| 426 |
+
with gr.Row():
|
| 427 |
+
annual_table = gr.DataFrame(label="Annual Results")
|
| 428 |
+
species_table = gr.DataFrame(label="Species Results")
|
| 429 |
|
| 430 |
+
# Connect the update button to the model update function
|
| 431 |
inputs = [
|
| 432 |
rhiz_density, rhiz_mort_1, rhiz_mort_2, rhiz_mort_3, rhiz_mort_4, rhiz_mort_5,
|
| 433 |
avic_density, avic_mort_1, avic_mort_2, avic_mort_3, avic_mort_4, avic_mort_5,
|
| 434 |
year_1_area, year_2_area, year_3_area, year_4_area, year_5_area,
|
| 435 |
+
buffer_percentage,
|
| 436 |
+
# Chapman-Richards parameters for Rhizophora
|
| 437 |
+
rhiz_dbh_a, rhiz_dbh_b, rhiz_dbh_c,
|
| 438 |
+
rhiz_height_a, rhiz_height_b, rhiz_height_c,
|
| 439 |
+
rhiz_initial_dbh, rhiz_initial_height,
|
| 440 |
+
# Chapman-Richards parameters for Avicennia
|
| 441 |
+
avic_dbh_a, avic_dbh_b, avic_dbh_c,
|
| 442 |
+
avic_height_a, avic_height_b, avic_height_c,
|
| 443 |
+
avic_initial_dbh, avic_initial_height
|
| 444 |
]
|
| 445 |
|
| 446 |
+
outputs = [carbon_plot, annual_plot, biomass_plot, summary_text, annual_table, species_table]
|
| 447 |
|
| 448 |
+
# Connect button click event
|
| 449 |
+
update_btn.click(fn=update_model, inputs=inputs, outputs=outputs)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 450 |
|
| 451 |
+
# Initialize the UI with default values
|
| 452 |
+
app.load(fn=update_model, inputs=inputs, outputs=outputs)
|
| 453 |
+
|
| 454 |
+
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
|
| 456 |
|
| 457 |
if __name__ == "__main__":
|
| 458 |
+
app = main()
|
| 459 |
+
app.launch(share=True)
|