diff --git a/notebooks/vulnerability_onboarding/EU JRC global flood depth-damage functions/EU JRC global flood depth-damage functions.json b/notebooks/vulnerability_onboarding/EU JRC global flood depth-damage functions/EU JRC global flood depth-damage functions.json index 490c7228..f9bbf358 100644 --- a/notebooks/vulnerability_onboarding/EU JRC global flood depth-damage functions/EU JRC global flood depth-damage functions.json +++ b/notebooks/vulnerability_onboarding/EU JRC global flood depth-damage functions/EU JRC global flood depth-damage functions.json @@ -15,7 +15,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -27,7 +27,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Europe" }, { @@ -57,7 +57,7 @@ 0.059134697, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.01, @@ -70,7 +70,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "North America" }, { @@ -98,7 +98,7 @@ 0.0, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -110,7 +110,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "South America" }, { @@ -138,7 +138,7 @@ 0.047803103, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -150,7 +150,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Asia" }, { @@ -178,7 +178,7 @@ 0.076208799, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -190,7 +190,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Africa" }, { @@ -218,7 +218,7 @@ 0.03589275, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -230,7 +230,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Oceania" }, { @@ -238,7 +238,7 @@ "event_type": "RiverineInundation", "impact_mean": [], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -250,7 +250,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Global" }, { @@ -268,7 +268,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -280,7 +280,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Europe" }, { @@ -299,7 +299,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.01, @@ -312,7 +312,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "North America" }, { @@ -340,7 +340,7 @@ 0.0, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -352,7 +352,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "South America" }, { @@ -380,7 +380,7 @@ 0.052781064, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -392,7 +392,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Asia" }, { @@ -400,7 +400,7 @@ "event_type": "RiverineInundation", "impact_mean": [], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -412,7 +412,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Africa" }, { @@ -440,7 +440,7 @@ 0.0, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -452,7 +452,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Oceania" }, { @@ -470,7 +470,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -482,7 +482,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Global" }, { @@ -500,7 +500,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -512,7 +512,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Europe" }, { @@ -531,7 +531,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.01, @@ -544,7 +544,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "North America" }, { @@ -572,7 +572,7 @@ 0.0, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -584,7 +584,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "South America" }, { @@ -612,7 +612,7 @@ 0.079457988, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -624,7 +624,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Asia" }, { @@ -642,7 +642,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -654,7 +654,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Africa" }, { @@ -662,7 +662,7 @@ "event_type": "RiverineInundation", "impact_mean": [], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -674,7 +674,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Oceania" }, { @@ -692,7 +692,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -704,7 +704,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Global" } ] diff --git a/notebooks/vulnerability_onboarding/EU JRC global flood depth-damage functions/onboard.ipynb b/notebooks/vulnerability_onboarding/EU JRC global flood depth-damage functions/onboard.ipynb index 0e5ab42e..d0367388 100644 --- a/notebooks/vulnerability_onboarding/EU JRC global flood depth-damage functions/onboard.ipynb +++ b/notebooks/vulnerability_onboarding/EU JRC global flood depth-damage functions/onboard.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -26,16 +26,25 @@ "import numpy as np\n", "import pandas as pd\n", "\n", - "df = pd.read_csv('raw.csv')\n", + "df = pd.read_csv(\"raw.csv\")\n", "\n", "# consistent with physrisk continent definition\n", - "location_mappings = { \"Europe\": \"Europe\", \"North America\": \"North America\", \"Central & South America\": \"South America\", \"Asia\": \"Asia\", \"Africa\": \"Africa\", \"Oceania\": \"Oceania\", \"Global\": \"Global\" }\n", - "type_mappings = { \"Residential buildings\": \"Buildings/Residential\",\n", + "location_mappings = {\n", + " \"Europe\": \"Europe\",\n", + " \"North America\": \"North America\",\n", + " \"Central & South America\": \"South America\",\n", + " \"Asia\": \"Asia\",\n", + " \"Africa\": \"Africa\",\n", + " \"Oceania\": \"Oceania\",\n", + " \"Global\": \"Global\",\n", + "}\n", + "type_mappings = {\n", + " \"Residential buildings\": \"Buildings/Residential\",\n", " \"Commercial buildings\": \"Buildings/Commercial\",\n", - " \"Industrial buildings\": \"Buildings/Industrial\"\n", + " \"Industrial buildings\": \"Buildings/Industrial\",\n", "}\n", "\n", - "data = { \"items\": [] }\n", + "data = {\"items\": []}\n", "\n", "curve_list = data[\"items\"]\n", "for mapping in type_mappings:\n", @@ -46,7 +55,7 @@ " zero_as_minimum = True if location == \"North America\" else False\n", " # for North America, the 0 depth damage is for flooding of any depth. We consider that a 1 cm inundation.\n", " depth = np.concatenate([[0, 0.01], flood_depth[1:]]) if zero_as_minimum else flood_depth\n", - " \n", + "\n", " mean = type_df[location + \"_Mean\"].to_numpy()\n", " std = type_df[location + \"_Std\"].to_numpy()\n", " mean = np.concatenate([[0], mean]) if zero_as_minimum else mean\n", @@ -54,10 +63,21 @@ " if np.any(np.isnan(mean)):\n", " mean = []\n", " if np.any(np.isnan(std)):\n", - " std = [] \n", - " curve_list.append({ \"asset_type\": type_mappings[mapping], \"event_type\": \"RiverineInundation\", \"location\": location_mappings[location], \"impact_type\": \"Damage\", \"intensity\": list(depth), \"intensity_units\": \"m\", \"impact_mean\": list(mean), \"impact_std\": list(std) }) \n", + " std = []\n", + " curve_list.append(\n", + " {\n", + " \"asset_type\": type_mappings[mapping],\n", + " \"event_type\": \"RiverineInundation\",\n", + " \"location\": location_mappings[location],\n", + " \"impact_type\": \"damage\",\n", + " \"intensity\": list(depth),\n", + " \"intensity_units\": \"meters\",\n", + " \"impact_mean\": list(mean),\n", + " \"impact_std\": list(std),\n", + " }\n", + " )\n", "\n", - "with open('EU JRC global flood depth-damage functions.json', 'w') as f:\n", + "with open(\"EU JRC global flood depth-damage functions.json\", \"w\") as f:\n", " vulnerability_json = json.dumps(data, sort_keys=True, indent=4)\n", " f.write(vulnerability_json)" ] @@ -89,7 +109,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.8.2" }, "orig_nbformat": 4 }, diff --git a/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/WRI thermal power plant physical climate vulnerability factors.json b/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/WRI thermal power plant physical climate vulnerability factors.json new file mode 100644 index 00000000..dfaad447 --- /dev/null +++ b/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/WRI thermal power plant physical climate vulnerability factors.json @@ -0,0 +1,374 @@ +{ + "items": [ + { + "asset_type": "steam/recirculating", + "event_type": "water_stress", + "impact_mean": [ + 0.0, + 0.02, + 0.1, + 0.2, + 0.5, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0 + ], + "intensity_units": "unitless", + "location": "global" + }, + { + "asset_type": "steam/once_through", + "event_type": "water_stress", + "impact_mean": [ + 0.0, + 0.02, + 0.1, + 0.2, + 0.5, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0 + ], + "intensity_units": "unitless", + "location": "global" + }, + { + "asset_type": "steam/recirculating", + "event_type": "drought", + "impact_mean": [ + 0.0, + 0.0, + 0.1, + 0.2, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + -2.0, + -2.5, + -3.0, + -3.6 + ], + "intensity_units": "unitless", + "location": "global" + }, + { + "asset_type": "steam/once_through", + "event_type": "drought", + "impact_mean": [ + 0.0, + 0.0, + 0.1, + 0.2, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + -2.0, + -2.5, + -3.0, + -3.6 + ], + "intensity_units": "unitless", + "location": "global" + }, + { + "asset_type": "steam/recirculating", + "event_type": "water_temperature", + "impact_mean": [ + 0.0, + 0.003, + 0.009, + 0.017, + 0.027, + 0.041, + 0.061, + 0.089, + 0.118, + 0.157, + 0.205, + 0.257, + 0.327, + 0.411, + 0.508, + 0.629, + 0.775, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0 + ], + "intensity_units": "degrees_celsius", + "location": "global" + }, + { + "asset_type": "steam/once_through", + "event_type": "water_temperature", + "impact_mean": [ + 0.0, + 0.003, + 0.009, + 0.017, + 0.027, + 0.041, + 0.061, + 0.089, + 0.118, + 0.157, + 0.205, + 0.257, + 0.327, + 0.411, + 0.508, + 0.629, + 0.775, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0 + ], + "intensity_units": "degrees_celsius", + "location": "global" + }, + { + "asset_type": "gas", + "event_type": "air_temperature", + "impact_mean": [ + 0.0, + 0.1, + 0.25, + 0.5, + 0.8, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 10.0, + 20.0, + 30.0, + 40.0, + 50.0 + ], + "intensity_units": "degrees_celsius", + "location": "global" + }, + { + "asset_type": "steam/dry", + "event_type": "air_temperature", + "impact_mean": [ + 0.0, + 0.02, + 0.04, + 0.08, + 0.11, + 0.15, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 6.0, + 12.0, + 18.0, + 24.0, + 30.0, + 198.0 + ], + "intensity_units": "degrees_celsius", + "location": "global" + }, + { + "asset_type": "steam/recirculating", + "event_type": "inundation", + "impact_mean": [ + 0.0, + 1.0, + 2.0, + 7.0, + 14.0, + 30.0, + 60.0, + 180.0, + 365.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 1.0 + ], + "intensity_units": "metres", + "location": "global" + }, + { + "asset_type": "gas", + "event_type": "inundation", + "impact_mean": [ + 0.0, + 1.0, + 2.0, + 7.0, + 14.0, + 30.0, + 60.0, + 180.0, + 365.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 1.0 + ], + "intensity_units": "metres", + "location": "global" + }, + { + "asset_type": "steam/once_through", + "event_type": "inundation", + "impact_mean": [ + 0.0, + 1.0, + 2.0, + 7.0, + 14.0, + 30.0, + 60.0, + 180.0, + 365.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 1.0 + ], + "intensity_units": "metres", + "location": "global" + }, + { + "asset_type": "steam/dry", + "event_type": "inundation", + "impact_mean": [ + 0.0, + 1.0, + 2.0, + 7.0, + 14.0, + 30.0, + 60.0, + 180.0, + 365.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 1.0 + ], + "intensity_units": "metres", + "location": "global" + } + ] +} \ No newline at end of file diff --git a/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/onboard.ipynb b/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/onboard.ipynb new file mode 100644 index 00000000..79f2e6b9 --- /dev/null +++ b/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/onboard.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## World Resources Institute's thermal power plant physical climate vulnerability factors\n", + "\n", + "### CITATION\n", + "Tianyi Luo, Lihuan Zhou, James Falzon, Yan Cheng, Giulia Christianson, Yili Wu and Amir Habchi, Assessing Physical Climate Risks for the European Bank for Reconstruction and Development's Power Generation Project Investment Portfolio, World Resources Institute, Working paper, doi:10.46830/wriwp.21.00060.\n", + "\n", + "### LINK\n", + "https://www.wri.org/research/assessing-physical-climate-risks-european-bank-power-portfolio\n", + "\n", + "### NOTES\n", + "Data from Table B1 in Appendix B converted into CSV format in raw.csv. Ingested into vulnerability model as standard json.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "import json, pandas\n", + "\n", + "data = {\"items\": []}\n", + "\n", + "df = pandas.read_csv(\"raw.csv\")\n", + "df[\"Asset Type\"] = [\n", + " (x if pandas.isna(y) else (x + \"/\" + y)).lower().replace(\" \", \"_\")\n", + " for (x, y) in zip(df[\"Turbine Type\"], df[\"Cooling Technology\"])\n", + "]\n", + "df.drop(columns=[\"Turbine Type\", \"Cooling Technology\"], inplace=True)\n", + "\n", + "hazard_set = set(df[\"Hazard Type\"])\n", + "for hazard_type in hazard_set:\n", + " selection = df.loc[df[\"Hazard Type\"] == hazard_type]\n", + " asset_set = set(selection[\"Asset Type\"])\n", + " for asset_type in asset_set:\n", + " sub_selection = selection.loc[selection[\"Asset Type\"] == asset_type]\n", + " threshold_set = set(sub_selection[\"Threshold Type\"])\n", + " for threshold_type in threshold_set:\n", + " item = sub_selection.loc[sub_selection[\"Threshold Type\"] == threshold_type]\n", + " if (\n", + " len(set(item[\"Threshold Unit\"])) == 1\n", + " and len(set(item[\"Vulnerability Type\"])) == 1\n", + " and len(set(item[\"Vulnerability Unit\"])) == 1\n", + " ):\n", + " data[\"items\"].append(\n", + " {\n", + " \"asset_type\": asset_type.lower().replace(\" \", \"_\"),\n", + " \"event_type\": hazard_type.lower().replace(\" \", \"_\"),\n", + " \"location\": \"global\",\n", + " \"intensity\": list(item[\"Threshold\"].values),\n", + " \"intensity_units\": item[\"Threshold Unit\"].values[0].lower().replace(\" \", \"_\"),\n", + " \"impact_type\": item[\"Vulnerability Type\"].values[0].lower().replace(\" \", \"_\"),\n", + " \"impact_units\": item[\"Vulnerability Unit\"].values[0].lower().replace(\" \", \"_\"),\n", + " \"impact_mean\": list(item[\"Vulnerability\"].values),\n", + " \"impact_std\": [],\n", + " }\n", + " )\n", + "\n", + "with open(\"WRI thermal power plant physical climate vulnerability factors.json\", \"w\") as f:\n", + " vulnerability_json = json.dumps(data, sort_keys=True, indent=4)\n", + " f.write(vulnerability_json)" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + }, + "kernelspec": { + "display_name": "Python 3.8.10 64-bit", + "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.8.2" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/raw.csv b/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/raw.csv new file mode 100644 index 00000000..98e45b1b --- /dev/null +++ b/notebooks/vulnerability_onboarding/WRI thermal power plant physical climate vulnerability factors/raw.csv @@ -0,0 +1,114 @@ +Hazard Type,Turbine Type,Cooling Technology,Threshold,Threshold Type,Threshold Unit,Vulnerability,Vulnerability Type,Vulnerability Unit +Air Temperature,Gas,,0,Above P90,Degrees Celsius,0,Disruption,Days +Air Temperature,Gas,,10,Above P90,Degrees Celsius,0.1,Disruption,Days +Air Temperature,Gas,,20,Above P90,Degrees Celsius,0.25,Disruption,Days +Air Temperature,Gas,,30,Above P90,Degrees Celsius,0.5,Disruption,Days +Air Temperature,Gas,,40,Above P90,Degrees Celsius,0.8,Disruption,Days +Air Temperature,Gas,,50,Above P90,Degrees Celsius,1,Disruption,Days +Air Temperature,Steam,Dry,0,Above P90,Degrees Celsius,0,Disruption,Days +Air Temperature,Steam,Dry,6,Above P90,Degrees Celsius,0.02,Disruption,Days +Air Temperature,Steam,Dry,12,Above P90,Degrees Celsius,0.04,Disruption,Days +Air Temperature,Steam,Dry,18,Above P90,Degrees Celsius,0.08,Disruption,Days +Air Temperature,Steam,Dry,24,Above P90,Degrees Celsius,0.11,Disruption,Days +Air Temperature,Steam,Dry,30,Above P90,Degrees Celsius,0.15,Disruption,Days +Air Temperature,Steam,Dry,198,Above P90,Degrees Celsius,1,Disruption,Days +Drought,Steam,Once Through,0,SPEI,Unitless,0,Disruption,Days +Drought,Steam,Once Through,-2,SPEI,Unitless,0,Disruption,Days +Drought,Steam,Once Through,-2.5,SPEI,Unitless,0.1,Disruption,Days +Drought,Steam,Once Through,-3,SPEI,Unitless,0.2,Disruption,Days +Drought,Steam,Once Through,-3.6,SPEI,Unitless,1,Disruption,Days +Drought,Steam,Recirculating,0,SPEI,Unitless,0,Disruption,Days +Drought,Steam,Recirculating,-2,SPEI,Unitless,0,Disruption,Days +Drought,Steam,Recirculating,-2.5,SPEI,Unitless,0.1,Disruption,Days +Drought,Steam,Recirculating,-3,SPEI,Unitless,0.2,Disruption,Days +Drought,Steam,Recirculating,-3.6,SPEI,Unitless,1,Disruption,Days +Inundation,Gas,,0,Flood Depth,Metres,0,Disruption,Days +Inundation,Gas,,0.1,Flood Depth,Metres,1,Disruption,Days +Inundation,Gas,,0.2,Flood Depth,Metres,2,Disruption,Days +Inundation,Gas,,0.3,Flood Depth,Metres,7,Disruption,Days +Inundation,Gas,,0.4,Flood Depth,Metres,14,Disruption,Days +Inundation,Gas,,0.5,Flood Depth,Metres,30,Disruption,Days +Inundation,Gas,,0.6,Flood Depth,Metres,60,Disruption,Days +Inundation,Gas,,0.7,Flood Depth,Metres,180,Disruption,Days +Inundation,Gas,,1,Flood Depth,Metres,365,Disruption,Days +Inundation,Steam,Dry,0,Flood Depth,Metres,0,Disruption,Days +Inundation,Steam,Dry,0.1,Flood Depth,Metres,1,Disruption,Days +Inundation,Steam,Dry,0.2,Flood Depth,Metres,2,Disruption,Days +Inundation,Steam,Dry,0.3,Flood Depth,Metres,7,Disruption,Days +Inundation,Steam,Dry,0.4,Flood Depth,Metres,14,Disruption,Days +Inundation,Steam,Dry,0.5,Flood Depth,Metres,30,Disruption,Days +Inundation,Steam,Dry,0.6,Flood Depth,Metres,60,Disruption,Days +Inundation,Steam,Dry,0.7,Flood Depth,Metres,180,Disruption,Days +Inundation,Steam,Dry,1,Flood Depth,Metres,365,Disruption,Days +Inundation,Steam,Once Through,0,Flood Depth,Metres,0,Disruption,Days +Inundation,Steam,Once Through,0.1,Flood Depth,Metres,1,Disruption,Days +Inundation,Steam,Once Through,0.2,Flood Depth,Metres,2,Disruption,Days +Inundation,Steam,Once Through,0.3,Flood Depth,Metres,7,Disruption,Days +Inundation,Steam,Once Through,0.4,Flood Depth,Metres,14,Disruption,Days +Inundation,Steam,Once Through,0.5,Flood Depth,Metres,30,Disruption,Days +Inundation,Steam,Once Through,0.6,Flood Depth,Metres,60,Disruption,Days +Inundation,Steam,Once Through,0.7,Flood Depth,Metres,180,Disruption,Days +Inundation,Steam,Once Through,1,Flood Depth,Metres,365,Disruption,Days +Inundation,Steam,Recirculating,0,Flood Depth,Metres,0,Disruption,Days +Inundation,Steam,Recirculating,0.1,Flood Depth,Metres,1,Disruption,Days +Inundation,Steam,Recirculating,0.2,Flood Depth,Metres,2,Disruption,Days +Inundation,Steam,Recirculating,0.3,Flood Depth,Metres,7,Disruption,Days +Inundation,Steam,Recirculating,0.4,Flood Depth,Metres,14,Disruption,Days +Inundation,Steam,Recirculating,0.5,Flood Depth,Metres,30,Disruption,Days +Inundation,Steam,Recirculating,0.6,Flood Depth,Metres,60,Disruption,Days +Inundation,Steam,Recirculating,0.7,Flood Depth,Metres,180,Disruption,Days +Inundation,Steam,Recirculating,1,Flood Depth,Metres,365,Disruption,Days +Water Stress,Steam,Once Through,0,Water Supply Reduction Rate,Unitless,0,Disruption,Days +Water Stress,Steam,Once Through,0.1,Water Supply Reduction Rate,Unitless,0.02,Disruption,Days +Water Stress,Steam,Once Through,0.25,Water Supply Reduction Rate,Unitless,0.1,Disruption,Days +Water Stress,Steam,Once Through,0.5,Water Supply Reduction Rate,Unitless,0.2,Disruption,Days +Water Stress,Steam,Once Through,0.75,Water Supply Reduction Rate,Unitless,0.5,Disruption,Days +Water Stress,Steam,Once Through,1,Water Supply Reduction Rate,Unitless,1,Disruption,Days +Water Stress,Steam,Recirculating,0,Water Supply Reduction Rate,Unitless,0,Disruption,Days +Water Stress,Steam,Recirculating,0.1,Water Supply Reduction Rate,Unitless,0.02,Disruption,Days +Water Stress,Steam,Recirculating,0.25,Water Supply Reduction Rate,Unitless,0.1,Disruption,Days +Water Stress,Steam,Recirculating,0.5,Water Supply Reduction Rate,Unitless,0.2,Disruption,Days +Water Stress,Steam,Recirculating,0.75,Water Supply Reduction Rate,Unitless,0.5,Disruption,Days +Water Stress,Steam,Recirculating,1,Water Supply Reduction Rate,Unitless,1,Disruption,Days +Water Temperature,Steam,Once Through,0,Above P90,Degrees Celsius,0,Disruption,Days +Water Temperature,Steam,Once Through,1,Above P90,Degrees Celsius,0.003,Disruption,Days +Water Temperature,Steam,Once Through,2,Above P90,Degrees Celsius,0.009,Disruption,Days +Water Temperature,Steam,Once Through,3,Above P90,Degrees Celsius,0.017,Disruption,Days +Water Temperature,Steam,Once Through,4,Above P90,Degrees Celsius,0.027,Disruption,Days +Water Temperature,Steam,Once Through,5,Above P90,Degrees Celsius,0.041,Disruption,Days +Water Temperature,Steam,Once Through,6,Above P90,Degrees Celsius,0.061,Disruption,Days +Water Temperature,Steam,Once Through,7,Above P90,Degrees Celsius,0.089,Disruption,Days +Water Temperature,Steam,Once Through,8,Above P90,Degrees Celsius,0.118,Disruption,Days +Water Temperature,Steam,Once Through,9,Above P90,Degrees Celsius,0.157,Disruption,Days +Water Temperature,Steam,Once Through,10,Above P90,Degrees Celsius,0.205,Disruption,Days +Water Temperature,Steam,Once Through,11,Above P90,Degrees Celsius,0.257,Disruption,Days +Water Temperature,Steam,Once Through,12,Above P90,Degrees Celsius,0.327,Disruption,Days +Water Temperature,Steam,Once Through,13,Above P90,Degrees Celsius,0.411,Disruption,Days +Water Temperature,Steam,Once Through,14,Above P90,Degrees Celsius,0.508,Disruption,Days +Water Temperature,Steam,Once Through,15,Above P90,Degrees Celsius,0.629,Disruption,Days +Water Temperature,Steam,Once Through,16,Above P90,Degrees Celsius,0.775,Disruption,Days +Water Temperature,Steam,Once Through,17,Above P90,Degrees Celsius,1,Disruption,Days +Water Temperature,Steam,Recirculating,0,Above P90,Degrees Celsius,0,Disruption,Days +Water Temperature,Steam,Recirculating,1,Above P90,Degrees Celsius,0.003,Disruption,Days +Water Temperature,Steam,Recirculating,2,Above P90,Degrees Celsius,0.009,Disruption,Days +Water Temperature,Steam,Recirculating,3,Above P90,Degrees Celsius,0.017,Disruption,Days +Water Temperature,Steam,Recirculating,4,Above P90,Degrees Celsius,0.027,Disruption,Days +Water Temperature,Steam,Recirculating,5,Above P90,Degrees Celsius,0.041,Disruption,Days +Water Temperature,Steam,Recirculating,6,Above P90,Degrees Celsius,0.061,Disruption,Days +Water Temperature,Steam,Recirculating,7,Above P90,Degrees Celsius,0.089,Disruption,Days +Water Temperature,Steam,Recirculating,8,Above P90,Degrees Celsius,0.118,Disruption,Days +Water Temperature,Steam,Recirculating,9,Above P90,Degrees Celsius,0.157,Disruption,Days +Water Temperature,Steam,Recirculating,10,Above P90,Degrees Celsius,0.205,Disruption,Days +Water Temperature,Steam,Recirculating,11,Above P90,Degrees Celsius,0.257,Disruption,Days +Water Temperature,Steam,Recirculating,12,Above P90,Degrees Celsius,0.327,Disruption,Days +Water Temperature,Steam,Recirculating,13,Above P90,Degrees Celsius,0.411,Disruption,Days +Water Temperature,Steam,Recirculating,14,Above P90,Degrees Celsius,0.508,Disruption,Days +Water Temperature,Steam,Recirculating,15,Above P90,Degrees Celsius,0.629,Disruption,Days +Water Temperature,Steam,Recirculating,16,Above P90,Degrees Celsius,0.775,Disruption,Days +Water Temperature,Steam,Recirculating,17,Above P90,Degrees Celsius,1,Disruption,Days +Water Temperature,Steam,Once Through,27,,Degrees Celsius,0,Disruption,Days +Water Temperature,Steam,Once Through,28,,Degrees Celsius,0.1,Disruption,Days +Water Temperature,Steam,Once Through,29,,Degrees Celsius,0.2,Disruption,Days +Water Temperature,Steam,Once Through,30,,Degrees Celsius,0.4,Disruption,Days +Water Temperature,Steam,Once Through,31,,Degrees Celsius,0.5,Disruption,Days +Water Temperature,Steam,Once Through,32,,Degrees Celsius,1,Disruption,Days diff --git a/src/physrisk/data/geotiff_reader.py b/src/physrisk/data/geotiff_reader.py index 7f2cadac..51ad2913 100644 --- a/src/physrisk/data/geotiff_reader.py +++ b/src/physrisk/data/geotiff_reader.py @@ -1,5 +1,4 @@ from itertools import chain -from typing import List, Tuple import numpy as np import zarr @@ -15,8 +14,8 @@ def zarr_read(path, longitudes, latitudes): """A version that uses Zarr rather than GDAL. Typically faster than GDAL / rasterio.""" with tifffile.tifffile.Tifffile(path) as tif: - scale: Tuple[float, float, float] = tif.geotiff_metadata["ModelPixelScale"] - tie_point: List[float] = tif.geotiff_metadata["ModelTiepoint"] + scale = tif.geotiff_metadata["ModelPixelScale"] + tie_point = tif.geotiff_metadata["ModelTiepoint"] store = tif.series[0].aszarr() zarray = zarr.open(store, mode="r") # shape: List[int] = tif.series[0].shape diff --git a/src/physrisk/data/hazard_data_provider.py b/src/physrisk/data/hazard_data_provider.py index 4bcdae96..d4322fee 100644 --- a/src/physrisk/data/hazard_data_provider.py +++ b/src/physrisk/data/hazard_data_provider.py @@ -7,6 +7,12 @@ from .zarr_reader import ZarrReader +@dataclass +class HazardDataBufferZone: + delta_deg: float + n_grid: int + + @dataclass class HazardDataHint: """Requestors of hazard data may provide a hint which may be taken into account by the Hazard Model. @@ -75,6 +81,7 @@ def get_intensity_curves( scenario: str, year: int, hint: Optional[HazardDataHint] = None, + buffer_zone: Optional[HazardDataBufferZone] = None, ): """Get intensity curve for each latitude and longitude coordinate pair. @@ -91,9 +98,19 @@ def get_intensity_curves( """ path = self._get_source_path(indicator_id=indicator_id, scenario=scenario, year=year, hint=hint) - curves, return_periods = self._reader.get_curves( - path, longitudes, latitudes, self._interpolation - ) # type: ignore + if buffer_zone is None: + curves, return_periods = self._reader.get_curves( + path, longitudes, latitudes, self._interpolation + ) # type: ignore + else: + curves, return_periods = self._reader.get_max_curves( + path, + longitudes, + latitudes, + self._interpolation, + buffer_zone.delta_deg * ZarrReader.KILOMETRES_PER_DEGREE, + buffer_zone.n_grid, + ) # type: ignore return curves, return_periods diff --git a/src/physrisk/data/inventory.py b/src/physrisk/data/inventory.py index aa0d4bdf..f4ab6246 100644 --- a/src/physrisk/data/inventory.py +++ b/src/physrisk/data/inventory.py @@ -34,7 +34,7 @@ def __init__(self, hazard_resources: Iterable[HazardResource]): def json_ordered(self): sorted_resources = sorted(self.resources_by_type_id.items()) - resource_list: List[HazardResource] = [] + resource_list = [] for _, resources in sorted_resources: resource_list.extend(resources) models = HazardModels(resources=resource_list) diff --git a/src/physrisk/data/pregenerated_hazard_model.py b/src/physrisk/data/pregenerated_hazard_model.py index d2963820..7a526604 100644 --- a/src/physrisk/data/pregenerated_hazard_model.py +++ b/src/physrisk/data/pregenerated_hazard_model.py @@ -42,18 +42,25 @@ def get_hazard_events(self, requests: List[HazardDataRequest]) -> Mapping[Hazard responses: MutableMapping[HazardDataRequest, HazardDataResponse] = {} for key in batches.keys(): batch: List[HazardDataRequest] = batches[key] - hazard_type, indicator_id, scenario, year, hint = ( + hazard_type, indicator_id, scenario, year, hint, buffer_zone = ( batch[0].hazard_type, batch[0].indicator_id, batch[0].scenario, batch[0].year, batch[0].hint, + batch[0].buffer_zone, ) longitudes = [req.longitude for req in batch] latitudes = [req.latitude for req in batch] if hazard_type.kind == HazardKind.acute: # type: ignore intensities, return_periods = self.acute_hazard_data_providers[hazard_type].get_intensity_curves( - longitudes, latitudes, indicator_id=indicator_id, scenario=scenario, year=year, hint=hint + longitudes, + latitudes, + indicator_id=indicator_id, + scenario=scenario, + year=year, + hint=hint, + buffer_zone=buffer_zone, ) for i, req in enumerate(batch): diff --git a/src/physrisk/data/static/vulnerability/EU JRC global flood depth-damage functions.json b/src/physrisk/data/static/vulnerability/EU JRC global flood depth-damage functions.json index 490c7228..f9bbf358 100644 --- a/src/physrisk/data/static/vulnerability/EU JRC global flood depth-damage functions.json +++ b/src/physrisk/data/static/vulnerability/EU JRC global flood depth-damage functions.json @@ -15,7 +15,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -27,7 +27,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Europe" }, { @@ -57,7 +57,7 @@ 0.059134697, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.01, @@ -70,7 +70,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "North America" }, { @@ -98,7 +98,7 @@ 0.0, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -110,7 +110,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "South America" }, { @@ -138,7 +138,7 @@ 0.047803103, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -150,7 +150,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Asia" }, { @@ -178,7 +178,7 @@ 0.076208799, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -190,7 +190,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Africa" }, { @@ -218,7 +218,7 @@ 0.03589275, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -230,7 +230,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Oceania" }, { @@ -238,7 +238,7 @@ "event_type": "RiverineInundation", "impact_mean": [], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -250,7 +250,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Global" }, { @@ -268,7 +268,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -280,7 +280,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Europe" }, { @@ -299,7 +299,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.01, @@ -312,7 +312,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "North America" }, { @@ -340,7 +340,7 @@ 0.0, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -352,7 +352,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "South America" }, { @@ -380,7 +380,7 @@ 0.052781064, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -392,7 +392,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Asia" }, { @@ -400,7 +400,7 @@ "event_type": "RiverineInundation", "impact_mean": [], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -412,7 +412,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Africa" }, { @@ -440,7 +440,7 @@ 0.0, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -452,7 +452,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Oceania" }, { @@ -470,7 +470,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -482,7 +482,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Global" }, { @@ -500,7 +500,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -512,7 +512,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Europe" }, { @@ -531,7 +531,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.01, @@ -544,7 +544,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "North America" }, { @@ -572,7 +572,7 @@ 0.0, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -584,7 +584,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "South America" }, { @@ -612,7 +612,7 @@ 0.079457988, 0.0 ], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -624,7 +624,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Asia" }, { @@ -642,7 +642,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -654,7 +654,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Africa" }, { @@ -662,7 +662,7 @@ "event_type": "RiverineInundation", "impact_mean": [], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -674,7 +674,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Oceania" }, { @@ -692,7 +692,7 @@ 1.0 ], "impact_std": [], - "impact_type": "Damage", + "impact_type": "damage", "intensity": [ 0.0, 0.5, @@ -704,7 +704,7 @@ 5.0, 6.0 ], - "intensity_units": "m", + "intensity_units": "meters", "location": "Global" } ] diff --git a/src/physrisk/data/static/vulnerability/WRI thermal power plant physical climate vulnerability factors.json b/src/physrisk/data/static/vulnerability/WRI thermal power plant physical climate vulnerability factors.json new file mode 100644 index 00000000..dfaad447 --- /dev/null +++ b/src/physrisk/data/static/vulnerability/WRI thermal power plant physical climate vulnerability factors.json @@ -0,0 +1,374 @@ +{ + "items": [ + { + "asset_type": "steam/recirculating", + "event_type": "water_stress", + "impact_mean": [ + 0.0, + 0.02, + 0.1, + 0.2, + 0.5, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0 + ], + "intensity_units": "unitless", + "location": "global" + }, + { + "asset_type": "steam/once_through", + "event_type": "water_stress", + "impact_mean": [ + 0.0, + 0.02, + 0.1, + 0.2, + 0.5, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0 + ], + "intensity_units": "unitless", + "location": "global" + }, + { + "asset_type": "steam/recirculating", + "event_type": "drought", + "impact_mean": [ + 0.0, + 0.0, + 0.1, + 0.2, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + -2.0, + -2.5, + -3.0, + -3.6 + ], + "intensity_units": "unitless", + "location": "global" + }, + { + "asset_type": "steam/once_through", + "event_type": "drought", + "impact_mean": [ + 0.0, + 0.0, + 0.1, + 0.2, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + -2.0, + -2.5, + -3.0, + -3.6 + ], + "intensity_units": "unitless", + "location": "global" + }, + { + "asset_type": "steam/recirculating", + "event_type": "water_temperature", + "impact_mean": [ + 0.0, + 0.003, + 0.009, + 0.017, + 0.027, + 0.041, + 0.061, + 0.089, + 0.118, + 0.157, + 0.205, + 0.257, + 0.327, + 0.411, + 0.508, + 0.629, + 0.775, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0 + ], + "intensity_units": "degrees_celsius", + "location": "global" + }, + { + "asset_type": "steam/once_through", + "event_type": "water_temperature", + "impact_mean": [ + 0.0, + 0.003, + 0.009, + 0.017, + 0.027, + 0.041, + 0.061, + 0.089, + 0.118, + 0.157, + 0.205, + 0.257, + 0.327, + 0.411, + 0.508, + 0.629, + 0.775, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0 + ], + "intensity_units": "degrees_celsius", + "location": "global" + }, + { + "asset_type": "gas", + "event_type": "air_temperature", + "impact_mean": [ + 0.0, + 0.1, + 0.25, + 0.5, + 0.8, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 10.0, + 20.0, + 30.0, + 40.0, + 50.0 + ], + "intensity_units": "degrees_celsius", + "location": "global" + }, + { + "asset_type": "steam/dry", + "event_type": "air_temperature", + "impact_mean": [ + 0.0, + 0.02, + 0.04, + 0.08, + 0.11, + 0.15, + 1.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 6.0, + 12.0, + 18.0, + 24.0, + 30.0, + 198.0 + ], + "intensity_units": "degrees_celsius", + "location": "global" + }, + { + "asset_type": "steam/recirculating", + "event_type": "inundation", + "impact_mean": [ + 0.0, + 1.0, + 2.0, + 7.0, + 14.0, + 30.0, + 60.0, + 180.0, + 365.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 1.0 + ], + "intensity_units": "metres", + "location": "global" + }, + { + "asset_type": "gas", + "event_type": "inundation", + "impact_mean": [ + 0.0, + 1.0, + 2.0, + 7.0, + 14.0, + 30.0, + 60.0, + 180.0, + 365.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 1.0 + ], + "intensity_units": "metres", + "location": "global" + }, + { + "asset_type": "steam/once_through", + "event_type": "inundation", + "impact_mean": [ + 0.0, + 1.0, + 2.0, + 7.0, + 14.0, + 30.0, + 60.0, + 180.0, + 365.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 1.0 + ], + "intensity_units": "metres", + "location": "global" + }, + { + "asset_type": "steam/dry", + "event_type": "inundation", + "impact_mean": [ + 0.0, + 1.0, + 2.0, + 7.0, + 14.0, + 30.0, + 60.0, + 180.0, + 365.0 + ], + "impact_std": [], + "impact_type": "disruption", + "impact_units": "days", + "intensity": [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 1.0 + ], + "intensity_units": "metres", + "location": "global" + } + ] +} \ No newline at end of file diff --git a/src/physrisk/data/zarr_reader.py b/src/physrisk/data/zarr_reader.py index 622c6ab9..42129807 100644 --- a/src/physrisk/data/zarr_reader.py +++ b/src/physrisk/data/zarr_reader.py @@ -21,6 +21,8 @@ def get_env(key: str, default: Optional[str] = None) -> str: class ZarrReader: """Reads hazard event data from Zarr files, including OSC-format-specific attributes.""" + KILOMETRES_PER_DEGREE: float = 110.574 + # environment variable names: __access_key = "OSC_S3_ACCESS_KEY" __secret_key = "OSC_S3_SECRET_KEY" @@ -139,22 +141,27 @@ def get_max_curves(self, set_id, longitudes, latitudes, interpolation="floor", d (no. coordinate pairs, no. return periods). return_periods: return periods in years. """ - KILOMETRES_PER_DEGREE = 110.574 n_data = len(latitudes) - delta_deg = delta_km / KILOMETRES_PER_DEGREE + delta_deg = delta_km / ZarrReader.KILOMETRES_PER_DEGREE grid = np.linspace(-0.5, 0.5, n_grid) - lats_grid_baseline = np.broadcast_to(latitudes.reshape((n_data, 1, 1)), (len(latitudes), n_grid, n_grid)) - lons_grid_baseline = np.broadcast_to(longitudes.reshape((n_data, 1, 1)), (len(longitudes), n_grid, n_grid)) + lats_grid_baseline = np.broadcast_to( + np.array(latitudes).reshape(n_data, 1, 1), (len(latitudes), n_grid, n_grid) + ) + lons_grid_baseline = np.broadcast_to( + np.array(longitudes).reshape(n_data, 1, 1), (len(longitudes), n_grid, n_grid) + ) lats_grid_offsets = delta_deg * grid.reshape((1, n_grid, 1)) lons_grid_offsets = ( - delta_deg * grid.reshape((1, 1, n_grid)) / (np.cos((np.pi / 180) * latitudes).reshape(n_data, 1, 1)) + delta_deg + * grid.reshape((1, 1, n_grid)) + / (np.cos((np.pi / 180) * np.array(latitudes)).reshape(n_data, 1, 1)) ) lats_grid = lats_grid_baseline + lats_grid_offsets lons_grid = lons_grid_baseline + lons_grid_offsets - curves_, return_periods = self.get_curves( + curves, return_periods = self.get_curves( set_id, lons_grid.reshape(-1), lats_grid.reshape(-1), interpolation=interpolation ) - curves_max = np.max(curves_.reshape((n_data, n_grid * n_grid, len(return_periods))), axis=1) + curves_max = np.nanmax(curves.reshape((n_data, n_grid * n_grid, len(return_periods))), axis=1) return curves_max, return_periods @staticmethod diff --git a/src/physrisk/kernel/assets.py b/src/physrisk/kernel/assets.py index 6e65872b..afd4b8e4 100644 --- a/src/physrisk/kernel/assets.py +++ b/src/physrisk/kernel/assets.py @@ -1,7 +1,44 @@ from dataclasses import dataclass +from enum import Enum from typing import Optional +# 'primary_fuel' entries in Global Power Plant Database v1.3.0 (World Resources Institute) +# https://wri-dataportal-prod.s3.amazonaws.com/manual/global_power_plant_database_v_1_3 +class FuelKind(Enum): + biomass = 1 + coal = 2 + cogeneration = 3 + gas = 4 + geothermal = 5 + hydro = 6 + nuclear = 7 + oil = 8 + other = 9 + petcoke = 10 + solar = 11 + storage = 12 + waste = 13 + wave_and_tidal = 14 + wind = 15 + + +class CoolingKind(Enum): + # Air Temperature, Inundation + dry = 1 + + # Drought, Inundation, Water Temperature, Water Stress + once_through = 2 + + # Drought, Inundation, Water Temperature, Water Stress (TO CLARIFY), Wet-Bulb Temperature + recirculating = 3 + + +class TurbineKind(Enum): + gas = 1 + steam = 2 + + class Asset: def __init__(self, latitude: float, longitude: float, **kwargs): self.latitude = latitude @@ -10,8 +47,6 @@ def __init__(self, latitude: float, longitude: float, **kwargs): # WindFarm as separate - - @dataclass class WindTurbine(Asset): capacity: Optional[float] = None @@ -23,7 +58,59 @@ class WindTurbine(Asset): class PowerGeneratingAsset(Asset): - pass + def __init__( + self, + latitude: float, + longitude: float, + *, + type: Optional[str] = None, + location: Optional[str] = None, + capacity: Optional[float] = None + ): + super().__init__(latitude, longitude) + + self.type: Optional[str] = type + self.location: Optional[str] = location + self.capacity: Optional[float] = capacity + + if type is not None: + self.primary_fuel: Optional[FuelKind] = None + archetypes = type.split("/") + if 0 < len(archetypes): + self.primary_fuel = FuelKind[archetypes[0].lower()] + + +class ThermalPowerGeneratingAsset(PowerGeneratingAsset): + def __init__( + self, + latitude: float, + longitude: float, + *, + type: Optional[str] = None, + location: Optional[str] = None, + capacity: Optional[float] = None + ): + super().__init__(latitude, longitude, type=type, location=location, capacity=capacity) + + self.turbine: Optional[TurbineKind] = None + self.cooling: Optional[CoolingKind] = None + + if type is not None: + archetypes = type.split("/") + if 1 < len(archetypes): + self.turbine = TurbineKind[archetypes[1].lower()] + if 2 < len(archetypes): + assert self.turbine == TurbineKind.steam + self.cooling = CoolingKind[archetypes[2].lower()] + + # Designed to be protected against 250-year inundation events in the baseline + # except for "nuclear" which is designed to be protected against 10,000-year + # inundation events in the baseline + def get_inundation_protection_return_period(self): + if self.primary_fuel is not None: + if self.primary_fuel == FuelKind.nuclear: + return 10000.0 + return 250.0 class RealEstateAsset(Asset): diff --git a/src/physrisk/kernel/calculation.py b/src/physrisk/kernel/calculation.py index adea9fe6..46789da5 100644 --- a/src/physrisk/kernel/calculation.py +++ b/src/physrisk/kernel/calculation.py @@ -10,8 +10,12 @@ RealEstateCoastalInundationModel, RealEstateRiverineInundationModel, ) +from physrisk.vulnerability_models.thermal_power_generation_models import ( + ThermalPowerGenerationCoastalInundationModel, + ThermalPowerGenerationRiverineInundationModel, +) -from .assets import IndustrialActivity, PowerGeneratingAsset, RealEstateAsset, TestAsset +from .assets import IndustrialActivity, PowerGeneratingAsset, RealEstateAsset, TestAsset, ThermalPowerGeneratingAsset from .hazard_model import HazardModel from .vulnerability_model import VulnerabilityModelBase @@ -30,6 +34,10 @@ def get_default_vulnerability_models() -> Dict[type, Sequence[VulnerabilityModel RealEstateRiverineInundationModel(), ], IndustrialActivity: [ChronicHeatGZNModel()], + ThermalPowerGeneratingAsset: [ + ThermalPowerGenerationCoastalInundationModel(), + ThermalPowerGenerationRiverineInundationModel(), + ], TestAsset: [pgam.TemperatureModel()], } diff --git a/src/physrisk/kernel/exposure.py b/src/physrisk/kernel/exposure.py index b03cae0f..626ec575 100644 --- a/src/physrisk/kernel/exposure.py +++ b/src/physrisk/kernel/exposure.py @@ -54,7 +54,7 @@ def get_exposures( class JupterExposureMeasure(ExposureMeasure): def __init__(self): - self.exposure_bins: Dict[Tuple[type, str], Tuple[np.ndarray, np.ndarray]] = self.get_exposure_bins() + self.exposure_bins = self.get_exposure_bins() def get_data_requests(self, asset: Asset, *, scenario: str, year: int) -> Iterable[HazardDataRequest]: return [ @@ -88,7 +88,7 @@ def get_exposures(self, asset: Asset, data_responses: Iterable[HazardDataRespons return result def get_exposure_bins(self): - categories: Dict[Tuple[type, str], Tuple[np.ndarray, np.ndarray]] = {} + categories = {} # specify exposure bins as dataclass in case desirable to use JSON in future categories[(CombinedInundation, "flooded_fraction")] = self.bounds_to_lookup( [ diff --git a/src/physrisk/kernel/hazard_model.py b/src/physrisk/kernel/hazard_model.py index dceefd5a..711d1406 100644 --- a/src/physrisk/kernel/hazard_model.py +++ b/src/physrisk/kernel/hazard_model.py @@ -1,10 +1,11 @@ from abc import ABC, abstractmethod from collections import defaultdict +from dataclasses import dataclass from typing import Dict, List, Mapping, Optional, Protocol, Tuple import numpy as np -from physrisk.data.hazard_data_provider import HazardDataHint +from physrisk.data.hazard_data_provider import HazardDataBufferZone, HazardDataHint class HazardDataRequest: @@ -22,7 +23,8 @@ def __init__( indicator_id: str, scenario: str, year: int, - hint: Optional[HazardDataHint] = None + hint: Optional[HazardDataHint] = None, + buffer_zone: Optional[HazardDataBufferZone] = None ): """Create HazardDataRequest. @@ -41,6 +43,7 @@ def __init__( self.scenario = scenario self.year = year self.hint = hint + self.buffer_zone = buffer_zone def group_key(self): """Key used to group EventDataRequests into batches.""" diff --git a/src/physrisk/kernel/impact.py b/src/physrisk/kernel/impact.py index 3149b983..55ad10fe 100644 --- a/src/physrisk/kernel/impact.py +++ b/src/physrisk/kernel/impact.py @@ -43,8 +43,8 @@ def calculate_impacts( # noqa: C901 for asset in assets: asset_type = type(asset) mappings = vulnerability_models[asset_type] - for m in mappings: - model_assets[m].append(asset) + for mapping in mappings: + model_assets[mapping].append(asset) results = {} asset_requests, responses = _request_consolidated(hazard_model, model_assets, scenario, year) diff --git a/src/physrisk/kernel/impact_distrib.py b/src/physrisk/kernel/impact_distrib.py index 8fd5d375..54773ab0 100644 --- a/src/physrisk/kernel/impact_distrib.py +++ b/src/physrisk/kernel/impact_distrib.py @@ -40,6 +40,9 @@ def impact_bins_explicit(self): def mean_impact(self): return np.sum((self.__impact_bins[:-1] + self.__impact_bins[1:]) * self.__prob / 2) + def mean(self): + return np.sum(self.__impact_bins * self.__prob) + def to_exceedance_curve(self): return to_exceedance_curve(self.__impact_bins, self.__prob) diff --git a/src/physrisk/kernel/vulnerability_matrix_provider.py b/src/physrisk/kernel/vulnerability_matrix_provider.py index 60317b97..6405b8b4 100644 --- a/src/physrisk/kernel/vulnerability_matrix_provider.py +++ b/src/physrisk/kernel/vulnerability_matrix_provider.py @@ -37,7 +37,7 @@ def __init__( self.intensity_bin_centres = np.array(intensity_bin_centres) self.impact_cdfs = impact_cdfs - def to_prob_matrix(self, impact_bin_edges: np.ndarray) -> np.ndarray: + def to_cdf_matrix(self, impact_bin_edges: np.ndarray) -> np.ndarray: """Return probability matrix, p with dimension (number intensity bins, number impact bins) where p[i, j] is the conditional probability that given the intensity falls in bin i, the impact is in bin j. @@ -55,6 +55,19 @@ def to_prob_matrix(self, impact_bin_edges: np.ndarray) -> np.ndarray: for i, _ in enumerate(self.intensity_bin_centres): cdf_matrix[i, :] = self.impact_cdfs[i](impact_bin_edges) # type: ignore - prob_matrix = cdf_matrix[:, 1:] - cdf_matrix[:, :-1] + return cdf_matrix + + def to_prob_matrix(self, impact_bin_edges: np.ndarray) -> np.ndarray: + """Return probability matrix, p with dimension (number intensity bins, number impact bins) + where p[i, j] is the conditional probability that given the intensity falls in bin i, the impact is + in bin j. + Args: + impact_bin_edges (Iterable[float]): Bin edges of the impact bins. + + Returns: + np.ndarray: Probability matrix. + """ + cdf_matrix = self.to_cdf_matrix(impact_bin_edges) + prob_matrix = cdf_matrix[:, 1:] - cdf_matrix[:, :-1] return prob_matrix diff --git a/src/physrisk/kernel/vulnerability_model.py b/src/physrisk/kernel/vulnerability_model.py index 63047fb5..e458c642 100644 --- a/src/physrisk/kernel/vulnerability_model.py +++ b/src/physrisk/kernel/vulnerability_model.py @@ -1,13 +1,14 @@ import importlib.resources import json from abc import ABC, abstractmethod -from typing import Iterable, List, Protocol, Sequence, Tuple, Union +from typing import Iterable, List, Optional, Protocol, Sequence, Tuple, Union import numpy as np from scipy import stats import physrisk.data.static.vulnerability -from physrisk.kernel.impact_distrib import ImpactDistrib +from physrisk.data.hazard_data_provider import HazardDataBufferZone +from physrisk.kernel.impact_distrib import ImpactDistrib, ImpactType from ..api.v1.common import VulnerabilityCurve, VulnerabilityCurves from .assets import Asset @@ -57,9 +58,10 @@ def get_data_requests( class VulnerabilityModelBase(ABC, DataRequester): - def __init__(self, indicator_id: str, hazard_type: type): + def __init__(self, indicator_id: str, hazard_type: type, impact_type: ImpactType): self.indicator_id = indicator_id self.hazard_type = hazard_type + self.impact_type = impact_type self._event_types: List[type] = [] self._asset_types: List[type] = [] @@ -81,8 +83,8 @@ class VulnerabilityModelAcuteBase(VulnerabilityModelBase): Asset. """ - def __init__(self, indicator_id: str, hazard_type: type): - super().__init__(indicator_id, hazard_type) + def __init__(self, indicator_id: str, hazard_type: type, impact_type: ImpactType): + super().__init__(indicator_id=indicator_id, hazard_type=hazard_type, impact_type=impact_type) @abstractmethod def get_distributions( @@ -113,7 +115,9 @@ def get_impact_details( vulnerability_dist, event_dist = self.get_distributions(asset, event_data_responses) impact_prob = vulnerability_dist.prob_matrix.T @ event_dist.prob return ( - ImpactDistrib(vulnerability_dist.event_type, vulnerability_dist.impact_bins, impact_prob), + ImpactDistrib( + vulnerability_dist.event_type, vulnerability_dist.impact_bins, impact_prob, impact_type=self.impact_type + ), vulnerability_dist, event_dist, ) @@ -127,10 +131,18 @@ class VulnerabilityModel(VulnerabilityModelAcuteBase): """A vulnerability model that requires only specification of distributions of impacts for given intensities, by implementing get_impact_curve.""" - def __init__(self, *, indicator_id: str = "", hazard_type: type, impact_bin_edges): - self.hazard_type = hazard_type - self.indicator_id = indicator_id + def __init__( + self, + *, + indicator_id: str = "", + hazard_type: type, + impact_type: ImpactType, + impact_bin_edges, + buffer_zone: Optional[HazardDataBufferZone] = None, + ): + super().__init__(indicator_id, hazard_type, impact_type) self.impact_bin_edges = impact_bin_edges + self.buffer_zone = buffer_zone def get_data_requests( self, asset: Asset, *, scenario: str, year: int @@ -142,6 +154,7 @@ def get_data_requests( scenario=scenario, year=year, indicator_id=self.indicator_id, + buffer_zone=self.buffer_zone, ) def get_distributions( @@ -152,8 +165,8 @@ def get_distributions( intensity_curve = ExceedanceCurve(1.0 / event_data.return_periods, event_data.intensities) intensity_bin_edges, probs = intensity_curve.get_probability_bins() - intensity_bin_centres = (intensity_bin_edges[1:] + intensity_bin_edges[:-1]) / 2 + intensity_bin_centres = (intensity_bin_edges[1:] + intensity_bin_edges[:-1]) / 2 vul = VulnerabilityDistrib( self.hazard_type, intensity_bin_edges, @@ -188,26 +201,33 @@ def get_vulnerability_curve(self, asset: Asset) -> VulnerabilityCurve: def delta_cdf(y): - return lambda x: np.where(x >= y, 1, 0) + return lambda x: np.where(x < y, 0, 1) + + +def checked_beta_distrib(mean, std, scaling_factor=1.0): + if std == 0 or mean == 0 or mean == scaling_factor: + return delta_cdf(mean) + return beta_distrib(mean, std, scaling_factor) + + +def cdf_weighted_sum(weights, cdfs): + assert len(weights) == len(cdfs) + return lambda x: sum([weight * cdf(x) for weight, cdf in zip(weights, cdfs)]) -def checked_beta_distrib(mean, std): - if mean == 0: - return delta_cdf(0) - if mean == 1.0: - return delta_cdf(1) - else: - return beta_distrib(mean, std) +def cdf_max_of(cdfs): + return lambda x: [np.prod(y) for y in np.transpose([cdf(x) for cdf in cdfs])] -def beta_distrib(mean, std): +def beta_distrib(mean, std, scaling_factor): cv = std / mean - a = (1 - mean) / (cv * cv) - mean - b = a * (1 - mean) / mean - return lambda x, a=a, b=b: stats.beta.cdf(x, a, b) + a = ((scaling_factor - mean) / (cv * cv) - mean) / scaling_factor + b = a * (scaling_factor - mean) / mean + return lambda x, a=a, b=b: stats.beta.cdf(x / scaling_factor, a, b) class DeterministicVulnerabilityModel(VulnerabilityModelAcuteBase): + def __init__( self, *, @@ -215,6 +235,7 @@ def __init__( damage_curve_intensities: Sequence[float], damage_curve_impacts: Sequence[float], indicator_id: str, + buffer_zone: Optional[HazardDataBufferZone] = None, ): """A vulnerability model that requires only specification of a damage/disruption curve. This simple model contains no uncertainty around damage/disruption. The damage curve is passed via the @@ -229,10 +250,12 @@ def __init__( damage_curve_impacts (Sequence[float]): Fractional damage to asset/disruption to operation resulting from a hazard of the corresponding intensity. indicator_id (str): ID of the hazard indicator to which this applies. Defaults to "". + buffer_zone (Optional[HazardDataBufferZone]): Delimitation of the area for the hazard data. """ super().__init__(indicator_id=indicator_id, hazard_type=hazard_type) self.damage_curve_intensities = damage_curve_intensities self.damage_curve_impacts = damage_curve_impacts + self.buffer_zone = buffer_zone def get_data_requests( self, asset: Asset, *, scenario: str, year: int @@ -244,6 +267,7 @@ def get_data_requests( scenario=scenario, year=year, indicator_id=self.indicator_id, + buffer_zone=self.buffer_zone, ) def get_distributions( diff --git a/src/physrisk/vulnerability_models/chronic_heat_models.py b/src/physrisk/vulnerability_models/chronic_heat_models.py index 437b5608..4fe82b47 100644 --- a/src/physrisk/vulnerability_models/chronic_heat_models.py +++ b/src/physrisk/vulnerability_models/chronic_heat_models.py @@ -17,7 +17,9 @@ class ChronicHeatGZNModel(VulnerabilityModelBase): Average annual work hours are based on USA values reported by the OECD for 2021.""" def __init__(self, indicator_id: str = "mean_degree_days/above/32c", delta=True): - super().__init__(indicator_id, ChronicHeat) # opportunity to give a model hint, but blank here + super().__init__( + indicator_id=indicator_id, hazard_type=ChronicHeat, impact_type=ImpactType.disruption + ) # opportunity to give a model hint, but blank here self.time_lost_per_degree_day = 4.671 # This comes from the paper converted to celsius self.time_lost_per_degree_day_se = 2.2302 # This comes from the paper converted to celsius self.total_labour_hours = 107460 @@ -88,8 +90,8 @@ class ChronicHeat_Wbgt_Gzn_Model(ChronicHeatGZNModel): inherits attributes from the ChronicHeatGZN model and estimates the results based on applying both GZN and WBGT.""" - def __init__(self, model: str = "mean_work_loss_high"): - super().__init__(model, ChronicHeat) # opportunity to give a model hint, but blank here + def __init__(self, indicator_id: str = "mean_work_loss_high"): + super().__init__(indicator_id=indicator_id) # opportunity to give a model hint, but blank here def work_type_mapping(self): return {"low": ["low", "medium"], "medium": ["medium", "low", "high"], "high": ["high", "medium"]} @@ -231,7 +233,7 @@ def get_impact(self, asset: Asset, data_responses: List[HazardDataResponse]) -> total_work_loss_delta: float = baseline_work_ability - scenario_work_ability - return get_impact_distrib(total_work_loss_delta, std_delta, ChronicHeat, ImpactType.disruption) + return get_impact_distrib(total_work_loss_delta, std_delta, self.hazard_type, self.impact_type) def two_variable_joint_variance(ex, varx, ey, vary): diff --git a/src/physrisk/vulnerability_models/example_models.py b/src/physrisk/vulnerability_models/example_models.py index 7078ab58..d4edc16a 100644 --- a/src/physrisk/vulnerability_models/example_models.py +++ b/src/physrisk/vulnerability_models/example_models.py @@ -1,19 +1,22 @@ import numpy as np -import scipy.stats as stats -from ..kernel.hazards import RiverineInundation +from ..kernel.impact_distrib import ImpactType from ..kernel.vulnerability_matrix_provider import VulnMatrixProvider -from ..kernel.vulnerability_model import VulnerabilityModel, applies_to_events +from ..kernel.vulnerability_model import VulnerabilityModel, checked_beta_distrib -@applies_to_events([RiverineInundation]) class ExampleCdfBasedVulnerabilityModel(VulnerabilityModel): - def __init__(self, *, indicator_id: str, event_type: type): + def __init__(self, *, indicator_id: str, hazard_type: type): self.intensities = np.array([0, 0.01, 0.5, 1.0, 1.5, 2, 3, 4, 5, 6]) self.impact_means = np.array([0, 0.2, 0.44, 0.58, 0.68, 0.78, 0.85, 0.92, 0.96, 1.0]) self.impact_stddevs = np.array([0, 0.17, 0.14, 0.14, 0.17, 0.14, 0.13, 0.10, 0.06, 0]) impact_bin_edges = np.array([0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) - super().__init__(indicator_id=indicator_id, hazard_type=event_type, impact_bin_edges=impact_bin_edges) + super().__init__( + indicator_id=indicator_id, + hazard_type=hazard_type, + impact_type=ImpactType.damage, + impact_bin_edges=impact_bin_edges, + ) def get_impact_curve(self, intensities, asset): # we interpolate the mean and standard deviation and use this to construct distributions @@ -22,23 +25,3 @@ def get_impact_curve(self, intensities, asset): return VulnMatrixProvider( intensities, impact_cdfs=[checked_beta_distrib(m, s) for m, s in zip(impact_means, impact_stddevs)] ) - - -def delta_cdf(y): - return lambda x: np.where(x >= y, 1, 0) - - -def checked_beta_distrib(mean, std): - if mean == 0: - return delta_cdf(0) - if mean == 1.0: - return delta_cdf(1) - else: - return beta_distrib(mean, std) - - -def beta_distrib(mean, std): - cv = std / mean - a = (1 - mean) / (cv * cv) - mean - b = a * (1 - mean) / mean - return lambda x, a=a, b=b: stats.beta.cdf(x, a, b) diff --git a/src/physrisk/vulnerability_models/power_generating_asset_models.py b/src/physrisk/vulnerability_models/power_generating_asset_models.py index 46883594..ee5c8976 100644 --- a/src/physrisk/vulnerability_models/power_generating_asset_models.py +++ b/src/physrisk/vulnerability_models/power_generating_asset_models.py @@ -7,6 +7,7 @@ from ..kernel.hazard_event_distrib import HazardEventDistrib from ..kernel.hazard_model import HazardDataRequest, HazardDataResponse, HazardEventDataResponse from ..kernel.hazards import ChronicHeat, RiverineInundation +from ..kernel.impact_distrib import ImpactType from ..kernel.vulnerability_distrib import VulnerabilityDistrib from ..kernel.vulnerability_model import ( DeterministicVulnerabilityModel, @@ -24,7 +25,7 @@ def __init__(self, indicator_id="flood_depth"): self.__curve_depth = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 1]) self.__curve_impact = np.array([0, 1, 2, 7, 14, 30, 60, 180, 365]) self.__indicator_id = indicator_id - super().__init__(indicator_id, RiverineInundation) + super().__init__(indicator_id=indicator_id, hazard_type=RiverineInundation, impact_type=ImpactType.disruption) pass def get_data_requests( diff --git a/src/physrisk/vulnerability_models/real_estate_models.py b/src/physrisk/vulnerability_models/real_estate_models.py index 32e2efca..8e1f5b9c 100644 --- a/src/physrisk/vulnerability_models/real_estate_models.py +++ b/src/physrisk/vulnerability_models/real_estate_models.py @@ -5,6 +5,7 @@ from physrisk.api.v1.common import VulnerabilityCurve, VulnerabilityCurves from physrisk.kernel.assets import Asset, RealEstateAsset +from physrisk.kernel.impact_distrib import ImpactType from physrisk.kernel.vulnerability_matrix_provider import VulnMatrixProvider from physrisk.kernel.vulnerability_model import VulnerabilityModel @@ -24,7 +25,7 @@ class RealEstateInundationModel(VulnerabilityModel): def __init__( self, *, - event_type: type, + hazard_type: type, indicator_id: str, resource: str = _default_resource, impact_bin_edges=_default_impact_bin_edges @@ -49,7 +50,17 @@ def __init__( self.vuln_curves_by_type[item.asset_type].append(item) # global circulation parameter 'model' is a hint; can be overriden by hazard model - super().__init__(indicator_id=indicator_id, hazard_type=event_type, impact_bin_edges=impact_bin_edges) + impact_type = ( + ImpactType.damage + if len(self.vulnerability_curves) == 0 + else [ImpactType[self.vulnerability_curves[key].impact_type] for key in self.vulnerability_curves][0] + ) + super().__init__( + indicator_id=indicator_id, + hazard_type=hazard_type, + impact_type=impact_type, + impact_bin_edges=impact_bin_edges, + ) def get_impact_curve(self, intensity_bin_centres: np.ndarray, asset: Asset): # we interpolate the mean and standard deviation and use this to construct distributions @@ -94,7 +105,7 @@ def __init__( ): # by default include subsidence and 95% sea-level rise super().__init__( - event_type=CoastalInundation, + hazard_type=CoastalInundation, indicator_id=indicator_id, resource=resource, impact_bin_edges=impact_bin_edges, @@ -112,7 +123,7 @@ def __init__( ): # by default request HazardModel to use "MIROC-ESM-CHEM" GCM super().__init__( - event_type=RiverineInundation, + hazard_type=RiverineInundation, indicator_id=indicator_id, resource=resource, impact_bin_edges=impact_bin_edges, diff --git a/src/physrisk/vulnerability_models/thermal_power_generation_models.py b/src/physrisk/vulnerability_models/thermal_power_generation_models.py new file mode 100644 index 00000000..a2668c65 --- /dev/null +++ b/src/physrisk/vulnerability_models/thermal_power_generation_models.py @@ -0,0 +1,225 @@ +from collections import defaultdict +from typing import Iterable, List, Tuple, Union + +import numpy as np + +from physrisk.api.v1.common import VulnerabilityCurve, VulnerabilityCurves +from physrisk.data.hazard_data_provider import HazardDataBufferZone +from physrisk.kernel.assets import Asset, ThermalPowerGeneratingAsset, TurbineKind +from physrisk.kernel.impact_distrib import ImpactType +from physrisk.kernel.vulnerability_matrix_provider import VulnMatrixProvider +from physrisk.kernel.vulnerability_model import VulnerabilityModel + +from ..kernel.curve import ExceedanceCurve +from ..kernel.hazard_event_distrib import HazardEventDistrib +from ..kernel.hazard_model import HazardDataRequest, HazardDataResponse, HazardEventDataResponse +from ..kernel.hazards import CoastalInundation, RiverineInundation +from ..kernel.vulnerability_distrib import VulnerabilityDistrib +from ..kernel.vulnerability_model import ( + applies_to_assets, + applies_to_events, + cdf_max_of, + cdf_weighted_sum, + checked_beta_distrib, + get_vulnerability_curves_from_resource, +) + + +class ThermalPowerGenerationInundationModel(VulnerabilityModel): + # Number of disrupted days per year + _default_impact_bin_edges = np.array([0, 1, 2, 7, 14, 30, 60, 180, 365]) + _default_resource = "WRI thermal power plant physical climate vulnerability factors" + _default_buffer_zone = HazardDataBufferZone(delta_deg=0.01, n_grid=5) + + def __init__( + self, + *, + hazard_type: type, + indicator_id: str, + resource: str = _default_resource, + impact_bin_edges=_default_impact_bin_edges, + buffer_zone=_default_buffer_zone + ): + """ + Inundation vulnerability model for thermal power generation. + Applies to both riverine and coastal inundation. + + Args: + event_type: Event type. + model: optional identifier for hazard event model, passed to HazardModel. + resource: embedded resource identifier used to infer vulnerability matrix. + impact_bin_edges: specifies the impact (fractional damage/disruption bins). + """ + + curve_set: VulnerabilityCurves = get_vulnerability_curves_from_resource(resource) + + # for this model, key for looking up curves is asset_type, e.g. 'steam/recirculating' + self.vulnerability_curves = dict( + (c.asset_type.lower(), c) for c in curve_set.items if c.event_type == hazard_type.__base__.__name__.lower() + ) + self.vuln_curves_by_type = defaultdict(list) + for key in self.vulnerability_curves: + self.vuln_curves_by_type[TurbineKind[key.split("/")[0].lower()]].append(self.vulnerability_curves[key]) + + impact_type = ( + ImpactType.disruption + if len(self.vulnerability_curves) == 0 + else [ImpactType[self.vulnerability_curves[key].impact_type] for key in self.vulnerability_curves][0] + ) + + # global circulation parameter 'model' is a hint; can be overriden by hazard model + super().__init__( + indicator_id=indicator_id, + hazard_type=hazard_type, + impact_type=impact_type, + impact_bin_edges=impact_bin_edges, + buffer_zone=buffer_zone, + ) + + def get_data_requests( + self, asset: Asset, *, scenario: str, year: int + ) -> Union[HazardDataRequest, Iterable[HazardDataRequest]]: + """Provide the list of hazard event data requests required in order to calculate + the VulnerabilityDistrib and HazardEventDistrib for the asset.""" + request_scenario = HazardDataRequest( + self.hazard_type, + asset.longitude, + asset.latitude, + scenario=scenario, + year=year, + indicator_id=self.indicator_id, + buffer_zone=self.buffer_zone, + ) + request_baseline = HazardDataRequest( + self.hazard_type, + asset.longitude, + asset.latitude, + scenario="historical", + year=1980, + indicator_id=self.indicator_id, + buffer_zone=self.buffer_zone, + ) + return request_scenario, request_baseline + + def get_impact_curve(self, intensity_bins: np.ndarray, asset: Asset): + assert isinstance(asset, ThermalPowerGeneratingAsset) + + curves: List[VulnerabilityCurve] = [] + if asset.turbine is None: + curves = [self.vulnerability_curves[key] for key in self.vulnerability_curves] + elif asset.cooling is not None: + key = "/".join([str(asset.turbine), str(asset.cooling)]) + if key in self.vulnerability_curves: + curves = [self.vulnerability_curves[key]] + elif asset.turbine in self.vuln_curves_by_type: + curves = self.vuln_curves_by_type[asset.turbine] + + if len(curves) == 0: + return VulnMatrixProvider( + intensity_bins, impact_cdfs=[checked_beta_distrib(0.0, 0.0) for _ in intensity_bins] + ) + + scaling_factor = self.impact_bin_edges[-1] + interpolation_schemes = [ + np.transpose( + [ + np.interp( + intensity_bins, + curve.intensity, + [1.0 if i == j else 0.0 for j, _ in enumerate(curve.intensity)], + ) + for i, _ in enumerate(curve.intensity) + ] + ) + for curve in curves + ] + impact_cdfs = [ + [ + checked_beta_distrib(mean, 0.0 if len(curve.impact_std) == 0 else curve.impact_std[i], scaling_factor) + for i, mean in enumerate(curve.impact_mean) + ] + for curve in curves + ] + impact_cdfs = [ + [cdf_weighted_sum(weights, impact_cdf) for weights in interpolation_scheme] + for interpolation_scheme, impact_cdf in zip(interpolation_schemes, impact_cdfs) + ] + impact_cdfs = [cdf_max_of(cdfs) for cdfs in np.transpose(impact_cdfs)] + + return VulnMatrixProvider(intensity_bins, impact_cdfs=list(np.array(impact_cdfs))) + + def get_distributions( + self, asset: Asset, event_data_responses: Iterable[HazardDataResponse] + ) -> Tuple[VulnerabilityDistrib, HazardEventDistrib]: + assert isinstance(asset, ThermalPowerGeneratingAsset) + + (response_scenario, response_baseline) = event_data_responses + assert isinstance(response_scenario, HazardEventDataResponse) + assert isinstance(response_baseline, HazardEventDataResponse) + + baseline_curve = ExceedanceCurve(1.0 / response_baseline.return_periods, response_baseline.intensities) + inundation_protection_level = ( + 0.0 + if len(response_baseline.intensities) == 0 + else baseline_curve.get_value(1.0 / asset.get_inundation_protection_return_period()) + ) + + intensity_curve = ExceedanceCurve(1.0 / response_scenario.return_periods, response_scenario.intensities) + if intensity_curve.values[0] < inundation_protection_level: + if inundation_protection_level < intensity_curve.values[-1]: + intensity_curve = intensity_curve.add_value_point(inundation_protection_level) + + intensity_bin_edges, probs = intensity_curve.get_probability_bins() + probability_transition_matrix = self.get_impact_curve(intensity_bin_edges, asset).to_cdf_matrix( + self.impact_bin_edges + ) + probability_transition_matrix[:, 1:] -= probability_transition_matrix[:, :-1] + inundation_protection_matrix = np.diag(np.where(intensity_bin_edges <= inundation_protection_level, 0.0, 1.0)) + probability_transition_matrix = np.matmul(inundation_protection_matrix, probability_transition_matrix) + + vul = VulnerabilityDistrib( + self.hazard_type, intensity_bin_edges, self.impact_bin_edges, probability_transition_matrix + ) + probs = np.insert(probs, 0, intensity_curve.probs[0]) + probs[-1] += 1.0 - np.sum(probs) + event = HazardEventDistrib(self.hazard_type, intensity_bin_edges, probs) + + return vul, event + + +@applies_to_events([CoastalInundation]) +@applies_to_assets([ThermalPowerGeneratingAsset]) +class ThermalPowerGenerationCoastalInundationModel(ThermalPowerGenerationInundationModel): + def __init__( + self, + *, + indicator_id: str = "flood_depth", + resource: str = ThermalPowerGenerationInundationModel._default_resource, + impact_bin_edges=ThermalPowerGenerationInundationModel._default_impact_bin_edges + ): + # by default include subsidence and 95% sea-level rise + super().__init__( + hazard_type=CoastalInundation, + indicator_id=indicator_id, + resource=resource, + impact_bin_edges=impact_bin_edges, + ) + + +@applies_to_events([RiverineInundation]) +@applies_to_assets([ThermalPowerGeneratingAsset]) +class ThermalPowerGenerationRiverineInundationModel(ThermalPowerGenerationInundationModel): + def __init__( + self, + *, + indicator_id: str = "flood_depth", + resource: str = ThermalPowerGenerationInundationModel._default_resource, + impact_bin_edges=ThermalPowerGenerationInundationModel._default_impact_bin_edges + ): + # by default request HazardModel to use "MIROC-ESM-CHEM" GCM + super().__init__( + hazard_type=RiverineInundation, + indicator_id=indicator_id, + resource=resource, + impact_bin_edges=impact_bin_edges, + ) diff --git a/src/test/kernel/test_asset_impact.py b/src/test/kernel/test_asset_impact.py index 98f5c76d..7abbc21c 100644 --- a/src/test/kernel/test_asset_impact.py +++ b/src/test/kernel/test_asset_impact.py @@ -1,17 +1,15 @@ """ Test asset impact calculations.""" import unittest -from typing import Dict, List, Tuple import numpy as np -from physrisk.kernel.assets import Asset, RealEstateAsset +from physrisk.kernel.assets import RealEstateAsset from physrisk.kernel.curve import ExceedanceCurve from physrisk.kernel.hazard_event_distrib import HazardEventDistrib from physrisk.kernel.hazard_model import HazardDataRequest from physrisk.kernel.hazards import RiverineInundation from physrisk.kernel.impact import ImpactDistrib from physrisk.kernel.vulnerability_distrib import VulnerabilityDistrib -from physrisk.kernel.vulnerability_model import VulnerabilityModelBase from physrisk.vulnerability_models.real_estate_models import ( RealEstateCoastalInundationModel, RealEstateRiverineInundationModel, @@ -105,7 +103,7 @@ def test_single_asset_impact(self): def test_performance_hazardlookup(self): """Just for reference: not true test""" - assetRequests: Dict[Tuple[VulnerabilityModelBase, Asset], List[HazardDataRequest]] = {} + assetRequests = {} import time start = time.time() diff --git a/src/test/kernel/test_chronic_asset_impact.py b/src/test/kernel/test_chronic_asset_impact.py index a6d01ca2..2eb2cbbc 100644 --- a/src/test/kernel/test_chronic_asset_impact.py +++ b/src/test/kernel/test_chronic_asset_impact.py @@ -23,8 +23,10 @@ class ExampleChronicHeatModel(VulnerabilityModelBase): https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html """ - def __init__(self, model: str = "mean_degree_days_above_32c", delta: bool = True): - super().__init__(model, ChronicHeat) # opportunity to give a model hint, but blank here + def __init__(self, indicator_id: str = "mean_degree_days_above_32c", delta: bool = True): + super().__init__( + indicator_id=indicator_id, hazard_type=ChronicHeat, impact_type=ImpactType.disruption + ) # opportunity to give a model hint, but blank here self.time_lost_per_degree_day = 4.671 # This comes from the paper converted to celsius self.time_lost_per_degree_day_se = 2.2302 # This comes from the paper converted to celsius diff --git a/src/test/models/test_example_models.py b/src/test/models/test_example_models.py index 9661442a..bcc6f85d 100644 --- a/src/test/models/test_example_models.py +++ b/src/test/models/test_example_models.py @@ -10,6 +10,7 @@ from physrisk.kernel.hazard_model import HazardEventDataResponse from physrisk.kernel.hazards import Inundation, RiverineInundation from physrisk.kernel.impact import calculate_impacts +from physrisk.kernel.impact_distrib import ImpactType from physrisk.kernel.vulnerability_matrix_provider import VulnMatrixProvider from physrisk.kernel.vulnerability_model import VulnerabilityModel from physrisk.vulnerability_models.example_models import ExampleCdfBasedVulnerabilityModel @@ -21,7 +22,12 @@ def __init__(self): self.impact_means = np.array([0, 0.2, 0.44, 0.58, 0.68, 0.78, 0.85, 0.92, 0.96, 1.0]) self.impact_stddevs = np.array([0, 0.17, 0.14, 0.14, 0.17, 0.14, 0.13, 0.10, 0.06, 0]) impact_bin_edges = np.array([0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) - super().__init__(indicator_id="flood_depth", hazard_type=RiverineInundation, impact_bin_edges=impact_bin_edges) + super().__init__( + indicator_id="flood_depth", + hazard_type=RiverineInundation, + impact_bin_edges=impact_bin_edges, + impact_type=ImpactType.damage, + ) def get_impact_curve(self, intensities, asset): # we interpolate the mean and standard deviation and use this to construct distributions @@ -54,7 +60,7 @@ def beta_distrib(mean, std): class TestExampleModels(unittest.TestCase): def test_pdf_based_vulnerability_model(self): - model = ExampleCdfBasedVulnerabilityModel(indicator_id="", event_type=Inundation) + model = ExampleCdfBasedVulnerabilityModel(indicator_id="", hazard_type=Inundation) latitude, longitude = 45.268405, 19.885738 asset = Asset(latitude, longitude) diff --git a/src/test/models/test_power_generating_asset_models.py b/src/test/models/test_power_generating_asset_models.py index 4dc94eac..25129570 100644 --- a/src/test/models/test_power_generating_asset_models.py +++ b/src/test/models/test_power_generating_asset_models.py @@ -1,15 +1,17 @@ """ Test asset impact calculations.""" +import json import os import unittest from test.base_test import TestWithCredentials from typing import List import numpy as np +import pandas import physrisk.api.v1.common import physrisk.data.static.world as wd -from physrisk.kernel import Asset, PowerGeneratingAsset -from physrisk.kernel.assets import IndustrialActivity, RealEstateAsset +from physrisk.kernel import Asset, PowerGeneratingAsset, calculation +from physrisk.kernel.assets import IndustrialActivity, RealEstateAsset, ThermalPowerGeneratingAsset from physrisk.kernel.hazard_model import HazardEventDataResponse from physrisk.kernel.impact import calculate_impacts from physrisk.utils.lazy import lazy_import @@ -67,7 +69,7 @@ def test_create_synthetic_portfolios_and_test(self): # Power generating assets that are of interest assets = [ - PowerGeneratingAsset(lat, lon, generation=gen, primary_fuel=prim_fuel, location=continent, type=prim_fuel) + PowerGeneratingAsset(lat, lon, generation=gen, location=continent, type=prim_fuel) for lon, lat, gen, prim_fuel, continent in zip(longitudes, latitudes, generation, primary_fuel, continents) ] detailed_results = calculate_impacts(assets, scenario="ssp585", year=2030) @@ -108,6 +110,59 @@ def test_create_synthetic_portfolios_and_test(self): f.write(assets_out.json(indent=4)) self.assertAlmostEqual(1, 1) + @unittest.skip("example, not test") + def test_thermal_power_generation_portfolio(self): + # cache_folder = r"" + + cache_folder = os.environ.get("CREDENTIAL_DOTENV_DIR", os.getcwd()) + + asset_list = pd.read_csv(os.path.join(cache_folder, "wri-all.csv")) + filtered = asset_list.loc[asset_list["primary_fuel"].isin(["Coal", "Gas", "Nuclear", "Oil"])] + + longitudes = np.array(filtered["longitude"]) + latitudes = np.array(filtered["latitude"]) + primary_fuels = np.array([primary_fuel.lower().replace(" ", "_") for primary_fuel in filtered["primary_fuel"]]) + + # Capacity describes a maximum electric power rate. + # Generation describes the actual electricity output of the plant over a period of time. + capacities = np.array(filtered["capacity_mw"]) + + _, continents = wd.get_countries_and_continents(latitudes=latitudes, longitudes=longitudes) + + # Power generating assets that are of interest + assets = [ + ThermalPowerGeneratingAsset(latitude, longitude, type=primary_fuel, location=continent, capacity=capacity) + for latitude, longitude, capacity, primary_fuel, continent in zip( + latitudes, + longitudes, + capacities, + primary_fuels, + continents, + ) + ] + + scenario = "rcp8p5" + year = 2050 + + hazard_model = calculation.get_default_hazard_model() + vulnerability_models = calculation.get_default_vulnerability_models() + + results = calculate_impacts(assets, hazard_model, vulnerability_models, scenario=scenario, year=year) + out = [ + { + "asset": type(result.asset).__name__, + "type": getattr(result.asset, "type") if hasattr(result.asset, "type") else None, + "location": getattr(result.asset, "location") if hasattr(result.asset, "location") else None, + "latitude": result.asset.latitude, + "longitude": result.asset.longitude, + "impact_mean": results[key].impact.mean(), + "hazard_type": results[key].impact.hazard_type.__name__, + } + for result, key in zip(results, results.keys()) + ] + pandas.DataFrame.from_dict(out).to_csv(os.path.join(cache_folder, "thermal_ power_generation_example.csv")) + self.assertAlmostEqual(1, 1) + def api_assets(self, assets: List[Asset]): items = [ physrisk.api.v1.common.Asset(