-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tipping points #446
Open
dumontgoulart
wants to merge
62
commits into
main
Choose a base branch
from
tipping_points
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+752
−2
Open
Tipping points #446
Changes from 19 commits
Commits
Show all changes
62 commits
Select commit
Hold shift + click to select a range
5ceb865
Update function to handle edge case
dumontgoulart b014275
added config for benefits visualization threshold (#387)
dumontgoulart da9249d
Update tipping point#
dumontgoulart 6dfae92
update tipping_points from main (#439)
dumontgoulart 45ab27a
Merge remote-tracking branch 'origin/main' into tipping_points
dumontgoulart 4d307a4
Update TippingPointMetrics enum with additional options
dumontgoulart cf8d0c8
Edits on tp
dumontgoulart 1e91d40
more edits
dumontgoulart fd50713
updating deprecated terms
dumontgoulart 34f4034
Update TippingPointMetrics enum with additional options
dumontgoulart c2f1bfa
Update TippingPointMetrics enum with additional options
dumontgoulart 26ffea2
Update TippingPointMetrics enum with additional options
dumontgoulart 8a1a748
Update TippingPointMetrics enum with additional options
dumontgoulart 3ab3964
Merge branch 'main' into tipping_points
dumontgoulart b44fba8
chore: Refactor object_model/tipping_point.py and interface/tipping_p…
dumontgoulart 2ff6088
Update paths in dbs_controller.py for database_name
dumontgoulart 1f8f97c
refactor functions
dumontgoulart 9ce90fb
add tests
dumontgoulart f028b87
add tests for tipping points
dumontgoulart e2f102c
updating tipping points wrt main (#479)
dumontgoulart 15f63a7
update tipping_points wrt main (#480)
dumontgoulart fa09dbf
refactor functions
dumontgoulart f108e9a
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart a4f8860
chore: Refactor object_model/tipping_point.py and interface/tipping_p…
dumontgoulart 8bc416c
update tp from main (#482)
dumontgoulart c1524ed
comments
dumontgoulart 8f57e19
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart 2a620bd
update tp (#486)
dumontgoulart 7f2b41d
Merge remote-tracking branch 'origin/main' into tipping_points
dumontgoulart 0a398de
chore: Refactor object_model/tipping_point.py and interface/tipping_p…
dumontgoulart 0522ab2
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart 28df051
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart c7e59bc
update tp (#496)
dumontgoulart 4cec8c8
Merge branch 'main' into tipping_points
dumontgoulart 49477fb
chore: Refactor object_model/tipping_point.py and interface/tipping_p…
dumontgoulart bb29294
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart 914708f
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart 509f586
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart 0d87a9b
update tp from main (#518)
dumontgoulart beeb7d3
update tipping points to db and api
dumontgoulart 29a0eec
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart a65eccd
Merge branch 'main' into tipping_points
dumontgoulart 5394985
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart 4767068
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart ad8cd88
Refactor object_model/tipping_point.py to ensure availability of Data…
dumontgoulart 971d7f6
Merge branch 'main' into tipping_points
dumontgoulart a1d6f57
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart dacdb31
Merge branch 'tipping_points' of https://github.com/Deltares-research…
dumontgoulart 3360678
Refactor object_model/tipping_point.py and interface/tipping_points.py
dumontgoulart 8170ddd
Refactor TippingPoint class to update projection name and save it if …
dumontgoulart c6c53f6
Merge branch 'main' of https://github.com/Deltares-research/FloodAdap…
Santonia27 a7b16b0
adding "_tp_" to paths for easy locating
dumontgoulart 279ee5f
invert order database
dumontgoulart f638cde
Merge branch 'main' of https://github.com/Deltares-research/FloodAdap…
Santonia27 231f84a
instead of plot outside gui plot inside (still needs fixing)
Santonia27 69a7ae0
add annotation to plot
Santonia27 4e35370
add y axis label
Santonia27 bc414fc
add view plot button
Santonia27 d9506c6
fix plot
Santonia27 86821f4
Merge branch 'main' of https://github.com/Deltares-research/FloodAdap…
Santonia27 81ecfcd
remove comments
Santonia27 76b59f8
update plot labels
Santonia27 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import os | ||
from abc import ABC, abstractmethod | ||
from typing import Any, Optional, Union | ||
from enum import Enum | ||
|
||
import pandas as pd | ||
from pydantic import BaseModel | ||
|
||
|
||
class TippingPointMetrics(str, Enum): | ||
"""class describing the accepted input for the variable metric_type in TippingPoint""" | ||
|
||
# based on what I have found in floodadapt - but can be changed | ||
FloodedAll = "FloodedAll" | ||
FloodedLowVulnerability = "FloodedLowVulnerability" | ||
FloodedHighVulnerability = "FloodedHighVulnerability" | ||
TotalDamageEvent = "TotalDamageEvent" | ||
TotalResDamageEvent = "TotalResDamageEvent" | ||
ResidentialMinorCount = "ResidentialMinorCount" | ||
ResidentialMajorCount = "ResidentialMajorCount" | ||
ResidentialDestroyedCount = "ResidentialDestroyedCount" | ||
CommercialCount = "CommercialCount" | ||
CommercialMinorCount = "CommercialMinorCount" | ||
CommercialMajorCount = "CommercialMajorCount" | ||
CommercialDestroyedCount = "CommercialDestroyedCount" | ||
HealthCount = "HealthCount" | ||
HealthMinorCount = "HealthMinorCount" | ||
HealthMajorCount = "HealthMajorCount" | ||
HealthDestroyedCount = "HealthDestroyedCount" | ||
SchoolsCount = "SchoolsCount" | ||
SchoolsMinorCount = "SchoolsMinorCount" | ||
SchoolsMajorCount = "SchoolsMajorCount" | ||
SchoolsDestroyedCount = "SchoolsDestroyedCount" | ||
EmergencyCount = "EmergencyCount" | ||
EmergencyMinorCount = "EmergencyMinorCount" | ||
EmergencyMajorCount = "EmergencyMajorCount" | ||
EmergencyDestroyedCount = "EmergencyDestroyedCount" | ||
DisplacedLowVulnerability = "DisplacedLowVulnerability" | ||
DisplacedHighVulnerability = "DisplacedHighVulnerability" | ||
SlightlyFloodedRoads = "SlightlyFloodedRoads" | ||
MinorFloodedRoads = "MinorFloodedRoads" | ||
MajorFloodedRoads = "MajorFloodedRoads" | ||
FullyFloodedRoads = "FullyFloodedRoads" | ||
|
||
|
||
class TippingPointStatus(str, Enum): | ||
"""class describing the accepted input for the variable metric_type in TippingPoint""" | ||
|
||
reached = "reached" | ||
not_reached = "not_reached" | ||
completed = "completed" | ||
|
||
|
||
class TippingPointOperator(str, Enum): | ||
"""class describing the accepted input for the variable operator in TippingPoint""" | ||
|
||
greater = "greater" | ||
less = "less" | ||
|
||
|
||
class TippingPointModel(BaseModel): | ||
"""BaseModel describing the expected variables and data types of a Tipping Point analysis object""" | ||
|
||
name: str | ||
description: Optional[str] = "" | ||
strategy: str | ||
event_set: str | ||
projection: str | ||
sealevelrise: list[float] # could be a numpy array too | ||
tipping_point_metric: list[tuple[TippingPointMetrics, float, TippingPointOperator]] | ||
status: TippingPointStatus = TippingPointStatus.not_reached | ||
scenarios: list[str] = [] | ||
|
||
|
||
class ITipPoint(ABC): | ||
attrs: TippingPointModel | ||
database_input_path: Union[str, os.PathLike] | ||
results_path: Union[str, os.PathLike] | ||
scenarios: pd.DataFrame | ||
has_run: bool = False | ||
|
||
@staticmethod | ||
@abstractmethod | ||
def load_file(filepath: Union[str, os.PathLike]): | ||
"""get Tipping Point attributes from toml file""" | ||
... | ||
|
||
@staticmethod # copping from benefits.py | ||
@abstractmethod | ||
def load_dict(data: dict[str, Any]): | ||
"""get Tipping Point attributes from an object, e.g. when initialized from GUI""" | ||
... | ||
|
||
@abstractmethod | ||
def save(self, filepath: Union[str, os.PathLike]): | ||
"""save Tipping Point attributes to a toml file""" | ||
... | ||
|
||
@abstractmethod | ||
def check_scenarios_exist(self) -> pd.DataFrame: | ||
"""Check which scenarios are needed for this tipping point calculation and if they have already been created""" | ||
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
import os | ||
import shutil | ||
from pathlib import Path | ||
from typing import Any, Union | ||
|
||
import geopandas as gpd | ||
import numpy as np | ||
import numpy_financial as npf | ||
import pandas as pd | ||
import plotly.graph_objects as go | ||
import tomli | ||
import tomli_w | ||
from fiat_toolbox.metrics_writer.fiat_read_metrics_file import MetricsFileReader | ||
|
||
from flood_adapt.dbs_controller import Database | ||
from flood_adapt.object_model.interface.tipping_points import ( | ||
TippingPointModel, | ||
ITipPoint, | ||
TippingPointStatus, | ||
) | ||
from flood_adapt.object_model.scenario import Scenario | ||
from flood_adapt.object_model.io.unitfulvalue import UnitfulLength, UnitTypesLength | ||
from flood_adapt.api.static import read_database | ||
|
||
""" | ||
This script implements a Tipping Point model to analyze the impact of sea level rise (SLR) | ||
on metrics such as population exposure, economic losses, etc. | ||
The model simulates scenarios based on varying SLR projections (for now) to identify tipping points | ||
where a metric exceeds a predefined threshold. | ||
|
||
Core Functionalities: | ||
- Creates projections for different SLR values. | ||
- Iteratively runs simulations to determine tipping points for specified metrics. | ||
- Saves results and tipping point data in a structured format for analysis. | ||
|
||
Inputs: | ||
- List of sea level rise values specifying different scenario projections. | ||
- Dictionary detailing the tipping point conditions including metric name, value, unit, and type. | ||
|
||
Outputs: | ||
- Results for each scenario indicating if a tipping point is reached. | ||
- Compiled results are saved as CSV files. | ||
""" | ||
|
||
|
||
class TippingPoint(ITipPoint): | ||
"""Class holding all information related to tipping points analysis""" | ||
|
||
def __init__(self): | ||
"""Initiation function when object is created through file or dict options""" | ||
self.site_toml_path = Path(Database().static_path) / "site" / "site.toml" | ||
self.results_path = Database().output_path / "tipping_points" | ||
self.scenarios = {} | ||
|
||
def create_tp_obj(self): | ||
# Save tipping point object to the tipping_points folder and a toml file | ||
if not (Database().input_path / "tipping_points" / self.attrs.name).exists(): | ||
(Database().input_path / "tipping_points" / self.attrs.name).mkdir( | ||
parents=True | ||
) | ||
self.save( | ||
Database().input_path | ||
/ "tipping_points" | ||
/ self.attrs.name | ||
/ f"{self.attrs.name}.toml" | ||
) | ||
return self | ||
|
||
def slr_projections(self, slr): | ||
"""Create projections for sea level rise value""" | ||
new_projection_name = self.attrs.projection + "_slr" + str(slr).replace(".", "") | ||
proj = Database().projections.get(self.attrs.projection) | ||
proj.attrs.physical_projection.sea_level_rise = UnitfulLength( | ||
value=slr, units=UnitTypesLength.meters | ||
) | ||
proj.save( | ||
Database().input_path | ||
/ "projections" | ||
/ new_projection_name | ||
/ (new_projection_name + ".toml") | ||
) | ||
return self | ||
|
||
def check_scenarios_exist(self, scenario_obj): | ||
db_list = [] | ||
# check if the current scenario in the tipping point object already exists in the database | ||
for db_scenario in Database().scenarios.list_objects()["objects"]: | ||
if scenario_obj == db_scenario: | ||
db_list.append(db_scenario.attrs.name) | ||
return db_list | ||
|
||
def create_tp_scenarios(self): | ||
"""Create scenarios for each sea level rise value inside the tipping_point folder""" | ||
self.create_tp_obj() | ||
# create projections based on SLR values | ||
for i, slr in enumerate(self.attrs.sealevelrise): | ||
self.slr_projections(slr) | ||
self.attrs.sealevelrise[i] = str(slr).replace(".", "") | ||
|
||
# crete scenarios for each SLR value | ||
scenarios = { | ||
f"slr_{slr}": { | ||
"name": f"slr_{slr}", | ||
"event": self.attrs.event_set, | ||
"projection": f"{self.attrs.projection}_slr{slr}", | ||
"strategy": self.attrs.strategy, | ||
} | ||
for slr in self.attrs.sealevelrise | ||
} | ||
|
||
# create subdirectories for each scenario and .toml files | ||
for scenario in scenarios.keys(): | ||
if not (Database().input_path / "scenarios" / scenario).exists(): | ||
(Database().input_path / "scenarios" / scenario).mkdir(parents=True) | ||
|
||
scenario_obj = Scenario.load_dict( | ||
scenarios[scenario], Database().input_path | ||
) | ||
scen_exists = self.check_scenarios_exist(scenario_obj) | ||
if scen_exists: | ||
# make a dict with name and object | ||
self.scenarios[scen_exists[0]] = { | ||
"name": scen_exists[0], | ||
"object": scenario_obj, | ||
} | ||
else: | ||
Database().scenarios.save(scenario_obj) | ||
self.scenarios[scenario_obj.attrs.name] = { | ||
"name": scenario_obj.attrs.name, | ||
"object": scenario_obj, | ||
} | ||
|
||
self.attrs.scenarios = list(self.scenarios.keys()) | ||
self.save( | ||
filepath=Database().input_path | ||
/ "tipping_points" | ||
/ self.attrs.name | ||
/ f"{self.attrs.name}.toml" | ||
) # for later when we have a database_tp: TODO: Database().tipping_points.save(self) | ||
|
||
def run_tp_scenarios(self): | ||
"""Run all scenarios to determine tipping points""" | ||
for name, scenario in self.scenarios.items(): | ||
scenario_obj = scenario["object"] | ||
# check if scenario has been run, if yes skip it | ||
if not self.scenario_has_run(scenario_obj): | ||
scenario_obj.run() | ||
|
||
# if the status is reached, save the SLR and the metric value | ||
if self.check_tipping_point(scenario_obj): | ||
self.attrs.status = TippingPointStatus.reached | ||
self.scenarios[name]["tipping point reached"] = True | ||
break | ||
else: | ||
self.attrs.status = TippingPointStatus.not_reached | ||
self.scenarios[name]["tipping point reached"] = False | ||
|
||
tp_path = self.results_path.joinpath(self.attrs.name) | ||
|
||
# Save results - make directory if it doesn't exist | ||
if not tp_path.is_dir(): | ||
tp_path.mkdir(parents=True) | ||
|
||
tp_results = pd.DataFrame.from_dict(self.scenarios, orient="index").reset_index( | ||
drop=True | ||
) | ||
tp_results.to_csv(tp_path / "tipping_point_results.csv") | ||
|
||
def scenario_has_run(self, scenario_obj): | ||
# TODO: once has_run is refactored (external) we change below to make it more direct | ||
for db_scenario, finished in zip( | ||
Database().scenarios.list_objects()["objects"], | ||
Database().scenarios.list_objects()["finished"], | ||
): | ||
if scenario_obj == db_scenario and finished: | ||
return True | ||
return False | ||
|
||
def check_tipping_point(self, scenario: Scenario): | ||
"""Load results and check if the tipping point is reached""" | ||
# already optimised for multiple metrics | ||
info_df = pd.read_csv( | ||
scenario.init_object_model().direct_impacts.results_path.joinpath( | ||
f"Infometrics_{scenario.direct_impacts.name}.csv" | ||
), | ||
index_col=0, | ||
) | ||
# if any tipping point is reached, return True | ||
return any( | ||
self.evaluate_tipping_point( | ||
info_df.loc[metric[0], "Value"], | ||
metric[1], | ||
metric[2], | ||
) | ||
for metric in self.attrs.tipping_point_metric | ||
) | ||
|
||
def evaluate_tipping_point(self, current_value, threshold, operator): | ||
"""Compare current value with threshold for tipping point""" | ||
operations = {"greater": lambda x, y: x >= y, "less": lambda x, y: x <= y} | ||
return operations[operator](current_value, threshold) | ||
|
||
### standard functions ### | ||
def load_file(filepath: Union[str, Path]) -> "TippingPoint": | ||
"""create risk event from toml file""" | ||
|
||
obj = TippingPoint() | ||
with open(filepath, mode="rb") as fp: | ||
toml = tomli.load(fp) | ||
obj.attrs = TippingPointModel.model_validate(toml) | ||
return obj | ||
|
||
def load_dict(dct: Union[str, Path]) -> "TippingPoint": | ||
"""create risk event from toml file""" | ||
|
||
obj = TippingPoint() | ||
obj.attrs = TippingPointModel.model_validate(dct) | ||
return obj | ||
|
||
def save(self, filepath: Union[str, os.PathLike]): | ||
"""save tipping point to a toml file""" | ||
with open(filepath, "wb") as f: | ||
tomli_w.dump(self.attrs.model_dump(exclude_none=True), f) | ||
|
||
def __eq__(self, other): | ||
if not isinstance(other, TippingPoint): | ||
# don't attempt to compare against unrelated types | ||
raise NotImplementedError | ||
attrs_1, attrs_2 = self.attrs.model_copy(), other.attrs.model_copy() | ||
attrs_1.__delattr__("name"), attrs_2.__delattr__("name") | ||
attrs_1.__delattr__("description"), attrs_2.__delattr__("description") | ||
return attrs_1 == attrs_2 | ||
|
||
|
||
# TODO: post processing stuff still to be done | ||
# make html & plots | ||
# write to file |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a function outside of the class to call the database and avoid the circular loading issues. Then it is called during init and stored as a class attribute