diff --git a/docs/handbook/onboarding.rst b/docs/handbook/onboarding.rst index 5da648af..04437838 100644 --- a/docs/handbook/onboarding.rst +++ b/docs/handbook/onboarding.rst @@ -19,38 +19,43 @@ Probability bins can be obtained from the exceedance curve, by subtracting one c Note that in this case - which is the standard case - linear interpolation between points of the exceedance curve is assumed which corresponds to flat probability density within a bin. -In code this can be done using the :code:`ExceedanceCurve`: -:: - exceedance_curve = ExceedanceCurve(1.0 / return_periods, event_intensities) - intensity_bins, probs = exceedance_curve.get_probability_bins() - -Vulnerability/Event Model +In code, this can be done using the :code:`ExceedanceCurve`: + +.. code-block:: + + exceedance_curve = ExceedanceCurve(1.0 / return_periods, event_intensities) + intensity_bins, probs = exceedance_curve.get_probability_bins() + +Vulnerability/Event Model ------------------------- -In general a Vulnerability/Event Model is responsible for obtaining for a particular asset instances of: +In general, a Vulnerability/Event Model is responsible for obtaining for a particular asset instances of: -#. :code:`HazardEventDistrib` that provides probabilities of hazard event intensentities for the asset, and +#. :code:`HazardEventDistrib` that provides probabilities of hazard event intensities for the asset, and #. :code:`VulnerabilityDistrib` that provides conditional probabilities that given a hazard event of given intensity has occurred, a damage/disruption of a given level will occur. The damage or disruption is sometimes referred to as the 'impact'. -The current implementation is non-parametric and based on discete bins - although continuous versions of :code:`HazardEventDistrib`/:code:`VulnerabilityDistrib` could certainly be added, based on particular parametric distributions. +The current implementation is non-parametric and based on discrete bins - although continuous versions of :code:`HazardEventDistrib`/:code:`VulnerabilityDistrib` could certainly be added, based on particular parametric distributions. :code:`HazardEventDistrib` is in this non-parametric version a histogram of hazard event intensities: defines a set of intensity bins and the annual probability of occurrence. -:code:`VulnerabilityDistrib` is a matix that provides the probability that if an event occurs with intensity falling in a particular intensity bin, an impact in a particular impact bin occurs. +:code:`VulnerabilityDistrib` is a matrix that provides the probability that if an event occurs with intensity falling in a particular intensity bin, an impact in a particular impact bin occurs. The Vulnerability/Event Model (henceforth 'model') is in general responsible for + * Defining its hazard event data requirements by implementing method :code:`get_event_data_requests` * Using the data to construct instances of :code:`HazardEventDistrib` and :code:`VulnerabilityDistrib` that will be used in the impact calculation. This is done by implementing method :code:`get_distributions` -:code:`HazardEventDistrib` and :code:`VulnerabilityDistrib` can be constructed in a single method to ensure their alignment, although this is method is probably only required in most bespoke cases. :code:`get_event_data_requests` is done as a separate step for performance reasons: it is desirable that all models state their data requirements 'up-front' in order that requests can be batched for fast retrieval. +:code:`HazardEventDistrib` and :code:`VulnerabilityDistrib` can be constructed in a single method to ensure their alignment, although this method is probably only required in most bespoke cases. :code:`get_event_data_requests` is done as a separate step for performance reasons: it is desirable that all models state their data requirements 'up-front' in order that requests can be batched for fast retrieval. The model applies to: + * A type of hazard event (Inundation, Wildfire, Drought, etc) * A type of asset (residential property, power generating asset, etc) + Decorators are used to 'tag' a model, so that an appropriate model can be selected for a given asset and hazard type; configuration allows types of :code:`Model` to be used in preference to other candidates. -Specific types of model also exist for common modelling approaches. In particular, although in general it is desirable that a model has the flexibility to define its hazard event distribution and vulnerability distribution, in many cases the former will be sourced directly from a data set and it only remains to define the vulnerability distribution. The :code:`Model` class allows the general form of the Model to be implemented. The :code:`VulnerabilityModel` class is for cases where only the vulnerability is to be specified. +Specific types of model also exist for common modelling approaches. In particular, although in general it is desirable that a model has the flexibility to define its hazard event distribution and vulnerability distribution, in many cases the former will be sourced directly from a data set and it only remains to define the vulnerability distribution. The :code:`Model` class allows the general form of the model to be implemented. The :code:`VulnerabilityModel` class is for cases where only the vulnerability is to be specified. On-boarding a model based on a damage/disruption curve ------------------------------------------------------ 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 ed3fef45..490c7228 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 @@ -256,10 +256,30 @@ { "asset_type": "Buildings/Commercial", "event_type": "RiverineInundation", - "impact_mean": [], + "impact_mean": [ + 0.0, + 0.15, + 0.3, + 0.45, + 0.55, + 0.75, + 0.9, + 1.0, + 1.0 + ], "impact_std": [], "impact_type": "Damage", - "intensity": [], + "intensity": [ + 0.0, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0 + ], "intensity_units": "m", "location": "Europe" }, @@ -267,15 +287,30 @@ "asset_type": "Buildings/Commercial", "event_type": "RiverineInundation", "impact_mean": [ - 0.0 - ], - "impact_std": [ - 0.0 + 0.0, + 0.018404908, + 0.239263804, + 0.374233129, + 0.466257669, + 0.552147239, + 0.687116564, + 0.82208589, + 0.90797546, + 1.0 ], + "impact_std": [], "impact_type": "Damage", "intensity": [ 0.0, - 0.01 + 0.01, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0 ], "intensity_units": "m", "location": "North America" @@ -283,20 +318,80 @@ { "asset_type": "Buildings/Commercial", "event_type": "RiverineInundation", - "impact_mean": [], - "impact_std": [], + "impact_mean": [ + 0.0, + 0.611477587, + 0.839531094, + 0.923588457, + 0.991972477, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "impact_std": [ + 0.0, + 0.077023435, + 0.035924027, + 0.026876525, + 0.016055046, + 0.0, + 0.0, + 0.0, + 0.0 + ], "impact_type": "Damage", - "intensity": [], + "intensity": [ + 0.0, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0 + ], "intensity_units": "m", "location": "South America" }, { "asset_type": "Buildings/Commercial", "event_type": "RiverineInundation", - "impact_mean": [], - "impact_std": [], + "impact_mean": [ + 0.0, + 0.376789623, + 0.537681619, + 0.659336684, + 0.762845232, + 0.883348656, + 0.941854895, + 0.98075938, + 1.0 + ], + "impact_std": [ + 0.0, + 0.240462285, + 0.240596279, + 0.243605156, + 0.250253511, + 0.171703625, + 0.11240992, + 0.052781064, + 0.0 + ], "impact_type": "Damage", - "intensity": [], + "intensity": [ + 0.0, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0 + ], "intensity_units": "m", "location": "Asia" }, @@ -306,27 +401,87 @@ "impact_mean": [], "impact_std": [], "impact_type": "Damage", - "intensity": [], + "intensity": [ + 0.0, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0 + ], "intensity_units": "m", "location": "Africa" }, { "asset_type": "Buildings/Commercial", "event_type": "RiverineInundation", - "impact_mean": [], - "impact_std": [], + "impact_mean": [ + 0.0, + 0.238953575, + 0.481199682, + 0.673795091, + 0.864583333, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "impact_std": [ + 0.0, + 0.142878204, + 0.204113206, + 0.190903594, + 0.178000078, + 0.0, + 0.0, + 0.0, + 0.0 + ], "impact_type": "Damage", - "intensity": [], + "intensity": [ + 0.0, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0 + ], "intensity_units": "m", "location": "Oceania" }, { "asset_type": "Buildings/Commercial", "event_type": "RiverineInundation", - "impact_mean": [], + "impact_mean": [ + 0.0, + 0.323296918, + 0.506529105, + 0.63459558, + 0.744309656, + 0.864093044, + 0.932788157, + 0.977746968, + 1.0 + ], "impact_std": [], "impact_type": "Damage", - "intensity": [], + "intensity": [ + 0.0, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0 + ], "intensity_units": "m", "location": "Global" }, diff --git a/src/test/models/test_power_generating_asset_models.py b/src/test/models/test_power_generating_asset_models.py index c2bd2ff6..4dc94eac 100644 --- a/src/test/models/test_power_generating_asset_models.py +++ b/src/test/models/test_power_generating_asset_models.py @@ -21,7 +21,7 @@ class TestPowerGeneratingAssetModels(TestWithCredentials): """Tests World Resource Institute (WRI) models for power generating assets.""" - def test_innundation(self): + def test_inundation(self): # exceedance curve return_periods = np.array([2.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0]) base_depth = np.array( diff --git a/src/test/models/test_real_estate_models.py b/src/test/models/test_real_estate_models.py index d1fb00a6..b0760f7f 100644 --- a/src/test/models/test_real_estate_models.py +++ b/src/test/models/test_real_estate_models.py @@ -8,7 +8,7 @@ from physrisk.hazard_models.core_hazards import get_default_source_paths from physrisk.kernel.assets import RealEstateAsset from physrisk.kernel.hazards import CoastalInundation, RiverineInundation -from physrisk.kernel.impact import calculate_impacts +from physrisk.kernel.impact import ImpactKey, calculate_impacts from physrisk.vulnerability_models.real_estate_models import ( RealEstateCoastalInundationModel, RealEstateRiverineInundationModel, @@ -36,8 +36,8 @@ def test_real_estate_model_details(self): results = calculate_impacts(assets, hazard_model, vulnerability_models, scenario=scenario, year=year) - hazard_bin_edges = results[(assets[0], RiverineInundation)].event.intensity_bin_edges - hazard_bin_probs = results[(assets[0], RiverineInundation)].event.prob + hazard_bin_edges = results[ImpactKey(assets[0], RiverineInundation)].event.intensity_bin_edges + hazard_bin_probs = results[ImpactKey(assets[0], RiverineInundation)].event.prob # check one: # the probability of inundation greater than 0.505m in a year is 1/10.0 @@ -46,24 +46,28 @@ def test_real_estate_model_details(self): np.testing.assert_almost_equal(hazard_bin_edges[1:3], np.array([0.333, 0.505])) np.testing.assert_almost_equal(hazard_bin_probs[1], 0.1) - # check that intensity bin edges for vilnerability matrix are same as for hazard - vulnerability_intensity_bin_edges = results[(assets[0], RiverineInundation)].vulnerability.intensity_bins + # check that intensity bin edges for vulnerability matrix are same as for hazard + vulnerability_intensity_bin_edges = results[ + ImpactKey(assets[0], RiverineInundation) + ].vulnerability.intensity_bins np.testing.assert_almost_equal(vulnerability_intensity_bin_edges, hazard_bin_edges) # check the impact distribution the matrix is size [len(intensity_bins) - 1, len(impact_bins) - 1] - cond_probs = results[(assets[0], RiverineInundation)].vulnerability.prob_matrix[1, :] + cond_probs = results[ImpactKey(assets[0], RiverineInundation)].vulnerability.prob_matrix[1, :] # check conditional prob for inundation intensity 0.333..0.505 mean, std = np.mean(cond_probs), np.std(cond_probs) np.testing.assert_almost_equal(cond_probs.sum(), 1) np.testing.assert_allclose([mean, std], [0.09090909, 0.08184968], rtol=1e-6) # probability that impact occurs between impact bin edge 1 and impact bin edge 2 - prob_impact = np.dot(hazard_bin_probs, results[(assets[0], RiverineInundation)].vulnerability.prob_matrix[:, 1]) + prob_impact = np.dot( + hazard_bin_probs, results[ImpactKey(assets[0], RiverineInundation)].vulnerability.prob_matrix[:, 1] + ) np.testing.assert_almost_equal(prob_impact, 0.19350789547968042) # no check with pre-calculated values for others: np.testing.assert_allclose( - results[(assets[0], RiverineInundation)].impact.prob, + results[ImpactKey(assets[0], RiverineInundation)].impact.prob, np.array( [ 0.02815762, @@ -102,7 +106,7 @@ def test_coastal_real_estate_model(self): results = calculate_impacts(assets, hazard_model, vulnerability_models, scenario=scenario, year=year) np.testing.assert_allclose( - results[(assets[0], CoastalInundation)].impact.prob, + results[ImpactKey(assets[0], CoastalInundation)].impact.prob, np.array( [ 2.78081230e-02, @@ -120,3 +124,94 @@ def test_coastal_real_estate_model(self): ), rtol=2e-6, ) + + def test_commercial_real_estate_model_details(self): + curve = np.array( + [2.8302893e-06, 0.09990284, 0.21215445, 0.531271, 0.7655724, 0.99438345, 1.2871761, 1.502281, 1.7134278] + ) + store = mock_hazard_model_store_inundation(TestData.longitudes, TestData.latitudes, curve) + hazard_model = ZarrHazardModel(source_paths=get_default_source_paths(), store=store) + + # location="South America", type="Buildings/Commercial" + assets = [ + RealEstateAsset(lat, lon, location="South America", type="Buildings/Commercial") + for lon, lat in zip(TestData.longitudes[-4:-3], TestData.latitudes[-4:-3]) + ] + + scenario = "rcp8p5" + year = 2080 + + # impact bin edges are calibrated so that hazard_bin_probs == impact_bin_probs + # when the impact standard deviation is negligible: + vulnerability_models = { + RealEstateAsset: [ + RealEstateRiverineInundationModel( + impact_bin_edges=np.array( + [ + 0, + 0.030545039098059, + 0.125953058445539, + 0.322702019487674, + 0.566880882840096, + 0.731980974578735, + 0.823993215529066, + 0.884544511664047, + 0.922115133960502, + 0.969169745946688, + 1.0, + ] + ) + ) + ] + } + + results = calculate_impacts(assets, hazard_model, vulnerability_models, scenario=scenario, year=year) + + hazard_bin_edges = results[ImpactKey(assets[0], RiverineInundation)].event.intensity_bin_edges + hazard_bin_probs = results[ImpactKey(assets[0], RiverineInundation)].event.prob + + # check one: + # the probability of inundation greater than 0.531271m in a year is 1/25 + # the probability of inundation greater than 0.21215445m in a year is 1/10 + # therefore the probability of an inundation between 0.21215445 and 0.531271 in a year is 1/10 - 1/25 + np.testing.assert_almost_equal(hazard_bin_edges[2:4], np.array([0.21215445, 0.531271])) + np.testing.assert_almost_equal(hazard_bin_probs[2], 0.06) + + # check that intensity bin edges for vulnerability matrix are same as for hazard + vulnerability_intensity_bin_edges = results[ + ImpactKey(assets[0], RiverineInundation) + ].vulnerability.intensity_bins + np.testing.assert_almost_equal(vulnerability_intensity_bin_edges, hazard_bin_edges) + + # check the impact distribution the matrix is size [len(intensity_bins) - 1, len(impact_bins) - 1] + cond_probs = results[ImpactKey(assets[0], RiverineInundation)].vulnerability.prob_matrix[2, :] + # check conditional prob for inundation intensity at 0.371712725m + mean, std = np.mean(cond_probs), np.std(cond_probs) + np.testing.assert_almost_equal(cond_probs.sum(), 1) + np.testing.assert_allclose([mean, std], [0.1, 0.2884275164878624], rtol=1e-6) + + # probability that impact occurs between impact bin edge 2 and impact bin edge 3 + prob_impact = np.dot( + hazard_bin_probs, results[ImpactKey(assets[0], RiverineInundation)].vulnerability.prob_matrix[:, 2] + ) + np.testing.assert_almost_equal(prob_impact, 0.10040196672295522) + + # no check with pre-calculated values for others: + np.testing.assert_allclose( + results[ImpactKey(assets[0], RiverineInundation)].impact.prob, + np.array( + [ + 2.009085e-07, + 3.001528e-01, + 1.004020e-01, + 5.885136e-02, + 1.760415e-02, + 1.159864e-02, + 6.130639e-03, + 2.729225e-03, + 1.446537e-03, + 8.450993e-05, + ] + ), + rtol=2e-6, + )