Skip to content

Commit

Permalink
Readme update (#155)
Browse files Browse the repository at this point in the history
* Update README

Signed-off-by: Joe Moorhouse <[email protected]>

---------

Signed-off-by: Joe Moorhouse <[email protected]>
  • Loading branch information
joemoorhouse authored Oct 21, 2023
1 parent 4943450 commit e41df41
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 167 deletions.
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
Physrisk
==============================
Physical risk calculation engine.
Physical climate risk calculation engine.

<img src="docs/images/OS-Climate-Logo.png" alt="drawing" width="150"/>


## About physrisk

An [OS-Climate](https://os-climate.org) project, physrisk is a library for assessing the physical effects of climate change and thereby the potential benefit of measures to improve resilience.

An introduction and methodology is available [here](https://github.com/os-climate/physrisk/blob/main/methodology/PhysicalRiskMethodology.pdf).

Physrisk is primarily designed to run 'bottom-up' calculations that model the impact of climate hazards on large numbers of individual assets (including natural) and operations. These calculations can be used to assess financial risks or socio-economic impacts. To do this physrisk collects:

- hazard indicators and
- models of vulnerability of assets/operations to hazards.

Hazard indicators are on-boarded from public resources or inferred from climate projections, e.g. from CMIP or CORDEX data sets. Indicators are created from code in the
[hazard repo](https://github.com/os-climate/hazard) to make calculations as transparent as possible.

Physrisk is also designed to be a hosted, e.g. to provide on-demand calculations. [physrisk-api](https://github.com/os-climate/physrisk-api) and [physrisk-ui](https://github.com/os-climate/physrisk-ui) provide an example API and user interface. A [development version of the UI](https://physrisk-ui-sandbox.apps.odh-cl1.apps.os-climate.org) is hosted by OS-Climate.

## Using the library
The library can be run locally, although access to the hazard indicator data is needed. The library is installed via:
```
pip install physrisk-lib
```

Hazard indicator data is freely available. Members of the project are able to access OS-Climate S3 buckets. Credentials are available [here](https://console-openshift-console.apps.odh-cl1.apps.os-climate.org/k8s/ns/sandbox/secrets/physrisk-s3-keys). Information about the project is available via the [community-hub](https://github.com/os-climate/OS-Climate-Community-Hub). Non-members are able to download or copy hazard indicator data.

Hazard indicator data can be downloaded or copied from the 'os-climate-public-data' bucket. A list of the keys to copy is available from
https://os-climate-public-data.s3.amazonaws.com/hazard/keys.txt

An inventory of the hazard data is maintained [here](https://github.com/os-climate/hazard/blob/main/src/inventories/hazard/inventory.json) (this is used by the physrisk library itself). The [UI hazard viewer](https://physrisk-ui-sandbox.apps.odh-cl1.apps.os-climate.org) is a convenient way to browse data sets.

Access to hazard event data requires setting of environment variables specifying the S3 Bucket, for example:

```
OSC_S3_BUCKET=physrisk-hazard-indicators
OSC_S3_ACCESS_KEY=**********6I
OSC_S3_SECRET_KEY=**********mS
OSC_S3_ACCESS_KEY=**********
OSC_S3_SECRET_KEY=**********
```

For use in a Jupyter environment, it is recommended to put the environment variables in a credentials.env file and do, for example:
Expand Down
154 changes: 17 additions & 137 deletions notebooks/vulnerability_onboarding/Wind/onboard.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/physrisk/api/v1/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def validate_type(cls, val):


class ArrayMeta(type):
def __getitem__(self, t):
def __getitem__(cls, t):
return type("Array", (TypedArray,), {"inner_type": t})


Expand Down
2 changes: 1 addition & 1 deletion src/physrisk/api/v1/hazard_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Tile(NamedTuple):

class HazardImageRequest(BaseHazardRequest):
resource: str = Field(description="Full path to the array; formed by '{path}/{id}'.")
scenarioId: str
scenario_id: str
year: int
colormap: Optional[str] = Field("heating")
format: Optional[str] = Field("PNG")
Expand Down
22 changes: 11 additions & 11 deletions src/physrisk/data/zarr_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ 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
kilometres_per_degree = 110.574
n_data = len(latitudes)
delta_deg = delta_km / KILOMETRES_PER_DEGREE
delta_deg = delta_km / 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))
Expand Down Expand Up @@ -177,7 +177,7 @@ def _linear_interp_frac_coordinates(z, image_coords, return_periods, interpolati

data = z.get_coordinate_selection((iz, iy, ix)) # type: ignore # index, row, column

NAN_VALUE = -9999.0
nan_value = -9999.0

if interpolation == "linear":
xf = image_coords[0, :][..., None] - icx # type: ignore
Expand All @@ -187,28 +187,28 @@ def _linear_interp_frac_coordinates(z, image_coords, return_periods, interpolati
w2 = (1 - yf) * xf
w3 = yf * xf
w = np.transpose(np.array([w0, w1, w2, w3]), (1, 0, 2))
mask = 1 - np.isnan(np.where(data == NAN_VALUE, np.nan, data))
mask = 1 - np.isnan(np.where(data == nan_value, np.nan, data))
w_good = w * mask
w_good_sum = np.transpose(
np.sum(w_good, axis=1).reshape(tuple([1]) + np.sum(w_good, axis=1).shape), axes=(1, 0, 2)
)
w_used = np.divide(w_good, np.where(w_good_sum == 0.0, np.nan, w_good_sum))
return np.nan_to_num(np.sum(w_used * data, axis=1), nan=NAN_VALUE)
return np.nan_to_num(np.sum(w_used * data, axis=1), nan=nan_value)

elif interpolation == "max":
data = np.where(data == NAN_VALUE, -np.inf, data)
data = np.where(data == nan_value, -np.inf, data)
return np.nan_to_num(
np.maximum.reduce([data[:, 0, :], data[:, 1, :], data[:, 2, :], data[:, 3, :]]),
nan=NAN_VALUE,
neginf=NAN_VALUE,
nan=nan_value,
neginf=nan_value,
)

elif interpolation == "min":
data = np.where(data == NAN_VALUE, np.inf, data)
data = np.where(data == nan_value, np.inf, data)
return np.nan_to_num(
np.minimum.reduce([data[:, 0, :], data[:, 1, :], data[:, 2, :], data[:, 3, :]]),
nan=NAN_VALUE,
posinf=NAN_VALUE,
nan=nan_value,
posinf=nan_value,
)

else:
Expand Down
4 changes: 2 additions & 2 deletions src/physrisk/kernel/exposure.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def calculate_exposures(
assets: List[Asset], hazard_model: HazardModel, exposure_measure: ExposureMeasure, scenario: str, year: int
) -> Dict[Asset, AssetExposureResult]:
requester_assets: Dict[DataRequester, List[Asset]] = {exposure_measure: assets}
assetRequests, responses = _request_consolidated(hazard_model, requester_assets, scenario, year)
asset_requests, responses = _request_consolidated(hazard_model, requester_assets, scenario, year)

logging.info(
"Applying exposure measure {0} to {1} assets of type {2}".format(
Expand All @@ -166,7 +166,7 @@ def calculate_exposures(
result: Dict[Asset, AssetExposureResult] = {}

for asset in assets:
requests = assetRequests[(exposure_measure, asset)] # (ordered) requests for a given asset
requests = asset_requests[(exposure_measure, asset)] # (ordered) requests for a given asset
hazard_data = [responses[req] for req in get_iterable(requests)]
result[asset] = AssetExposureResult(hazard_categories=exposure_measure.get_exposures(asset, hazard_data))

Expand Down
2 changes: 1 addition & 1 deletion src/physrisk/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def get_image(self, *, request_dict):
str(PosixPath(model.path).with_name(model.map.path))
if len(PosixPath(model.map.path).parts) == 1
else model.map.path
).format(scenario=request.scenarioId, year=request.year)
).format(scenario=request.scenario_id, year=request.year)
colormap = request.colormap if request.colormap is not None else model.map.colormap.name
creator = ImageCreator(zarr_reader) # store=ImageCreator.test_store(path))
return creator.convert(
Expand Down
2 changes: 1 addition & 1 deletion src/physrisk/vulnerability_models/chronic_heat_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def get_impact(self, asset: Asset, data_responses: List[HazardDataResponse]) ->
return get_impact_distrib(fraction_loss_mean, fraction_loss_std, ChronicHeat, ImpactType.disruption)


class ChronicHeat_Wbgt_Gzn_Model(ChronicHeatGZNModel):
class ChronicHeatWBGTGZNModel(ChronicHeatGZNModel):

"""Implementation of the WBGT/GZN chronic heat model. This model
inherits attributes from the ChronicHeatGZN model and estimates the
Expand Down
6 changes: 3 additions & 3 deletions src/test/data/hazard_model_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ def mock_hazard_model_store_heat(longitudes, latitudes):
return mock_hazard_model_store_for_parameter_sets(longitudes, latitudes, degree_day_heat_parameter_set())


def mock_hazard_model_store_heat_WBGT(longitudes, latitudes):
return mock_hazard_model_store_for_parameter_sets(longitudes, latitudes, WBGT_GZN_Joint_parameter_set())
def mock_hazard_model_store_heat_wbgt(longitudes, latitudes):
return mock_hazard_model_store_for_parameter_sets(longitudes, latitudes, wbgt_gzn_joint_parameter_set())


def mock_hazard_model_store_inundation(longitudes, latitudes, curve):
Expand Down Expand Up @@ -147,7 +147,7 @@ def degree_day_heat_parameter_set():
return dict(zip(paths, parameters))


def WBGT_GZN_Joint_parameter_set():
def wbgt_gzn_joint_parameter_set():
paths = []
# Getting paths for both hazards.
for model, scenario, year in [
Expand Down
8 changes: 4 additions & 4 deletions src/test/kernel/test_asset_impact.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,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]] = {}
asset_requests: Dict[Tuple[VulnerabilityModelBase, Asset], List[HazardDataRequest]] = {}
import time

start = time.time()
Expand All @@ -123,16 +123,16 @@ def test_performance_hazardlookup(self):
# create requests:
for v in vulnerability_models:
for a in assets:
assetRequests[(v, a)] = [
asset_requests[(v, a)] = [
HazardDataRequest(RiverineInundation, 0, 0, indicator_id="", scenario="", year=2030)
]

time_requests = time.time() - start
print(f"Time for requests dictionary creation {time_requests}s ")
start = time.time()
# read requests:
for key in assetRequests:
if assetRequests[key][0].longitude != 0:
for key in asset_requests:
if asset_requests[key][0].longitude != 0:
raise Exception()

time_responses = time.time() - start
Expand Down
4 changes: 2 additions & 2 deletions src/test/models/test_WBGT_Model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest
from test.data.hazard_model_store import TestData, mock_hazard_model_store_heat_WBGT
from test.data.hazard_model_store import TestData, mock_hazard_model_store_heat_wbgt
from typing import Iterable, List, Union, cast

import numpy as np
Expand Down Expand Up @@ -177,7 +177,7 @@ class TestChronicAssetImpact(unittest.TestCase):
"""Tests the impact on an asset of a chronic hazard model."""

def test_wbgt_vulnerability(self):
store = mock_hazard_model_store_heat_WBGT(TestData.longitudes, TestData.latitudes)
store = mock_hazard_model_store_heat_wbgt(TestData.longitudes, TestData.latitudes)
hazard_model = ZarrHazardModel(source_paths=get_default_source_paths(), store=store)
# 'chronic_heat/osc/v2/mean_work_loss_high_ACCESS-CM2_historical_2005'
scenario = "ssp585"
Expand Down
2 changes: 1 addition & 1 deletion src/test/models/test_wind_turbine_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


class SupportsEventImpact(typing.Protocol[TAsset]):
def get_impact(self, Asset: TAsset, event_data: HazardEventDataResponse) -> MultivariateDistribution:
def get_impact(self, asset: TAsset, event_data: HazardEventDataResponse) -> MultivariateDistribution:
pass


Expand Down

0 comments on commit e41df41

Please sign in to comment.