|
27 | 27 | from typing import Optional,List |
28 | 28 | import warnings |
29 | 29 |
|
| 30 | +import geopandas as gpd |
30 | 31 | import numpy as np |
31 | 32 | from pathos.pools import ProcessPool as Pool |
32 | 33 | from scipy import sparse |
@@ -450,6 +451,75 @@ def sanitize_event_ids(self): |
450 | 451 | LOGGER.debug('Resetting event_id.') |
451 | 452 | self.event_id = np.arange(1, self.event_id.size + 1) |
452 | 453 |
|
| 454 | + def local_return_period(self, threshold_intensities=(5., 10., 20.)): |
| 455 | + """Compute local return periods for given hazard intensities. The used method |
| 456 | + is fitting the ordered intensitites per centroid to the corresponding cummulated |
| 457 | + frequency with a step function. |
| 458 | +
|
| 459 | + Parameters |
| 460 | + ---------- |
| 461 | + threshold_intensities : np.array |
| 462 | + User-specified hazard intensities for which the return period should be calculated |
| 463 | + locally (at each centroid). Defaults to (5, 10, 20) |
| 464 | +
|
| 465 | + Returns |
| 466 | + ------- |
| 467 | + gdf : gpd.GeoDataFrame |
| 468 | + GeoDataFrame containing return periods for given threshold intensities. Each column |
| 469 | + corresponds to a threshold_intensity value, each row corresponds to a centroid. Values |
| 470 | + in the gdf correspond to the return period for the given centroid and |
| 471 | + threshold_intensity value |
| 472 | + label : str |
| 473 | + GeoDataFrame label, for reporting and plotting |
| 474 | + column_label : function |
| 475 | + Column-label-generating function, for reporting and plotting |
| 476 | + """ |
| 477 | + #check frequency unit |
| 478 | + if self.frequency_unit in ['1/year', 'annual', '1/y', '1/a']: |
| 479 | + rp_unit = 'Years' |
| 480 | + elif self.frequency_unit in ['1/month', 'monthly', '1/m']: |
| 481 | + rp_unit = 'Months' |
| 482 | + elif self.frequency_unit in ['1/week', 'weekly', '1/w']: |
| 483 | + rp_unit = 'Weeks' |
| 484 | + else: |
| 485 | + LOGGER.warning("Hazard's frequency unit %s is not known, " |
| 486 | + "years will be used as return period unit.", self.frequency_unit) |
| 487 | + rp_unit = 'Years' |
| 488 | + |
| 489 | + # Ensure threshold_intensities is a numpy array |
| 490 | + threshold_intensities = np.array(threshold_intensities) |
| 491 | + |
| 492 | + num_cen = self.intensity.shape[1] |
| 493 | + return_periods = np.zeros((len(threshold_intensities), num_cen)) |
| 494 | + |
| 495 | + # batch_centroids = number of centroids that are handled in parallel: |
| 496 | + # batch_centroids = maximal matrix size // number of events |
| 497 | + batch_centroids = CONFIG.max_matrix_size.int() // self.intensity.shape[0] |
| 498 | + if batch_centroids < 1: |
| 499 | + raise ValueError('Increase max_matrix_size configuration parameter to >' |
| 500 | + f'{self.intensity.shape[0]}') |
| 501 | + |
| 502 | + # Process the intensities in chunks of centroids |
| 503 | + for start_col in range(0, num_cen, batch_centroids): |
| 504 | + end_col = min(start_col + batch_centroids, num_cen) |
| 505 | + return_periods[:, start_col:end_col] = self._loc_return_period( |
| 506 | + threshold_intensities, |
| 507 | + self.intensity[:, start_col:end_col].toarray() |
| 508 | + ) |
| 509 | + |
| 510 | + # create the output GeoDataFrame |
| 511 | + gdf = gpd.GeoDataFrame(geometry = self.centroids.gdf['geometry'], |
| 512 | + crs = self.centroids.gdf.crs) |
| 513 | + col_names = [f'{tresh_inten}' for tresh_inten in threshold_intensities] |
| 514 | + gdf[col_names] = return_periods.T |
| 515 | + |
| 516 | + # create label and column_label |
| 517 | + label = f'Return Periods ({rp_unit})' |
| 518 | + column_label = lambda column_names: [f'Threshold Intensity: {col} {self.units}' |
| 519 | + for col in column_names] |
| 520 | + |
| 521 | + return gdf, label, column_label |
| 522 | + |
453 | 523 | def get_event_id(self, event_name): |
454 | 524 | """Get an event id from its name. Several events might have the same |
455 | 525 | name. |
@@ -614,6 +684,52 @@ def _loc_return_inten(self, return_periods, inten, exc_inten): |
614 | 684 | inten_sort[:, cen_idx], freq_sort[:, cen_idx], |
615 | 685 | self.intensity_thres, return_periods) |
616 | 686 |
|
| 687 | + def _loc_return_period(self, threshold_intensities, inten): |
| 688 | + """Compute local return periods for user-specified threshold intensities |
| 689 | + for a subset of hazard centroids |
| 690 | +
|
| 691 | + Parameters |
| 692 | + ---------- |
| 693 | + threshold_intensities: np.array |
| 694 | + User-specified hazard intensities for which the return period should be calculated |
| 695 | + locally (at each centroid). |
| 696 | + inten: np.array |
| 697 | + subarray of full hazard intensities corresponding to a subset of the centroids |
| 698 | + (rows corresponds to events, columns correspond to centroids) |
| 699 | +
|
| 700 | + Returns |
| 701 | + ------- |
| 702 | + np.array |
| 703 | + (rows corresponds to threshold_intensities, columns correspond to centroids) |
| 704 | + """ |
| 705 | + # Assuming inten is sorted and calculating cumulative frequency |
| 706 | + sort_pos = np.argsort(inten, axis=0)[::-1, :] |
| 707 | + inten_sort = inten[sort_pos, np.arange(inten.shape[1])] |
| 708 | + freq_sort = self.frequency[sort_pos] |
| 709 | + freq_sort = np.cumsum(freq_sort, axis=0) |
| 710 | + return_periods = np.zeros((len(threshold_intensities), inten.shape[1])) |
| 711 | + |
| 712 | + for cen_idx in range(inten.shape[1]): |
| 713 | + sorted_inten_cen = inten_sort[:, cen_idx] |
| 714 | + cum_freq_cen = freq_sort[:, cen_idx] |
| 715 | + |
| 716 | + for i, intensity in enumerate(threshold_intensities): |
| 717 | + # Find the first occurrence where the intensity is less than the sorted intensities |
| 718 | + exceedance_index = np.searchsorted(sorted_inten_cen[::-1], intensity, side='left') |
| 719 | + |
| 720 | + # Calculate exceedance probability |
| 721 | + if exceedance_index < len(cum_freq_cen): |
| 722 | + exceedance_probability = cum_freq_cen[-exceedance_index - 1] |
| 723 | + else: |
| 724 | + exceedance_probability = 0 # Or set a default minimal probability |
| 725 | + |
| 726 | + # Calculate and store return period |
| 727 | + if exceedance_probability > 0: |
| 728 | + return_periods[i, cen_idx] = 1 / exceedance_probability |
| 729 | + else: |
| 730 | + return_periods[i, cen_idx] = np.nan |
| 731 | + return return_periods |
| 732 | + |
617 | 733 | def _check_events(self): |
618 | 734 | """Check that all attributes but centroids contain consistent data. |
619 | 735 | Put default date, event_name and orig if not provided. Check not |
|
0 commit comments