diff --git a/debian/changelog b/debian/changelog index 27919d63e8c..c8769c10ccc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -5,8 +5,8 @@ [Christopher Brooks] * Extended recurSet/recurRow epistemic uncertainties to simple fault sources and added a rateSplit uncertainty to partition rate between - an area source and embedded faults, for the BC Hydro NVA alt2 LT - (tested inside logictree/case_25) + an area source and embedded faults, for the BC Hydro NVA alt2/alt3 + LTs (tested inside logictree/case_25) [Michele Simionato] * Fixed a bug in `oq show rlz` showing wrong information for some complex trees diff --git a/openquake/calculators/tests/logictree_test.py b/openquake/calculators/tests/logictree_test.py index c635ab76a3f..2a89c234aef 100644 --- a/openquake/calculators/tests/logictree_test.py +++ b/openquake/calculators/tests/logictree_test.py @@ -516,6 +516,12 @@ def test_case_25(self): 'expected/hazard_curve-mean-PGA_alt2.csv', got_alt2) self.assertEqual(len(self.calc.full_lt.get_realizations()), 16) + self.run_calc(case_25.__file__, 'job_alt3.ini', exports='csv') + [got_alt3] = export(('hcurves', 'csv'), self.calc.datastore) + self.assertEqualFiles( + 'expected/hazard_curve-mean-PGA_alt3.csv', got_alt3) + self.assertEqual(len(self.calc.full_lt.get_realizations()), 8) + def test_case_28(self): # North Africa # MultiPointSource with modify MFD logic tree out = self.run_calc(case_28.__file__, 'job.ini', exports='csv') diff --git a/openquake/hazardlib/lt.py b/openquake/hazardlib/lt.py index 0518befdb9c..2b313ae3779 100644 --- a/openquake/hazardlib/lt.py +++ b/openquake/hazardlib/lt.py @@ -478,8 +478,11 @@ def _set_recurrow(utype, source, value): # BCHydro NVA epistemic uncertainty @apply_uncertainty.add('recurSet') def _set_recurset(utype, source, value): + # A per-source mmax_offset (parsed from the optional mmaxOffset XML + # attribute) shifts the branchset max_mag. The alt3 uses this to enforce + # bg_mmax = fault_mmax - 0.5 while still sharing one recurSet branchset source.recur_model = value["recur_model"] - source.mmax = float(value["max_mag"]) + source.mmax = float(value["max_mag"]) + getattr(source, 'mmax_offset', 0.0) return diff --git a/openquake/hazardlib/source/area.py b/openquake/hazardlib/source/area.py index 5b95fc47ccb..dd97cc0e6b5 100644 --- a/openquake/hazardlib/source/area.py +++ b/openquake/hazardlib/source/area.py @@ -129,17 +129,28 @@ def modify_set_recurrow(self, recurrow): """ Modify the recurrence parameters by values given in a dict. - If a rateSplit uncertainty has previously set self.rate_split_bg_frac, - the resulting MFD is a piecewise EvenlyDiscretizedMFD that scales - rates at or above Mmax-1 by that fraction (bg side of an alt2-style - partition between an area source and embedded faults). + Params are read from the recurrow dict, preferring bg-prefixed keys + e.g., bg_b_value, bg_ref_mag, bg_rate) so a single alt3-style + recurRow branch can carry different but correlated bg and fault + parameters; otherwise the unprefixed alt1/alt2 keys are used. + + How it works is determined by if rate_split_bg_frac is present: + + - Alt1/Alt3 (no rate_split_bg_frac): Build the TE or AC MFD from the row + params and use it as it is. + + - Alt2 (rate_split_bg_frac present): Build the same MFD, then + piecewise-scale bins at or above Mmax-1 by rate_split_bg_frac (bg + side of the alt2 partition between the area source and its faults + and make an EvenlyDiscretizedMFD. NOTE: This is currently only intended for support of the BC Hydro NVA SSC logic tree. We may expand it to become a more general capability. - :param recur_row: - Dict of values to use in given type of MFD + :param recurrow: + Dict of values to use in given type of MFD e.g.b_value, ref_mag, + rate). Alt3-style rows use prefixes of "bg" for the same keys. """ # Constants for BC Hydro NVA model b_ac = 0.3 @@ -147,12 +158,12 @@ def modify_set_recurrow(self, recurrow): gamma_eff = 0.9185 # Corrected gamma_eff from eq 1.2 of BCHydro AC memo bin_width = 0.1 - # Get params from recurRow + # Get params from recurRow, preferring bg-prefixed keys (alt3) mmax = self.mmax recur_model = self.recur_model - bval = float(recurrow["b_value"]) - ref_mag = float(recurrow["ref_mag"]) - rate = float(recurrow["rate"]) + bval = float(recurrow.get("bg_b_value", recurrow.get("b_value"))) + ref_mag = float(recurrow.get("bg_ref_mag", recurrow.get("ref_mag"))) + rate = float(recurrow.get("bg_rate", recurrow.get("rate"))) # Build the MFD if recur_model == "TE": @@ -169,7 +180,7 @@ def modify_set_recurrow(self, recurrow): total_rate=rate, ) - # Piecewise partition above Mmax-1 if a rateSplit is active + # Piecewise partition above Mmax-1 if a rate_split_bg_frac is active bg_frac = getattr(self, 'rate_split_bg_frac', None) if bg_frac is not None: bins = self.mfd.get_annual_occurrence_rates() diff --git a/openquake/hazardlib/source/simple_fault.py b/openquake/hazardlib/source/simple_fault.py index 21fe1a8e352..f6cd5939579 100644 --- a/openquake/hazardlib/source/simple_fault.py +++ b/openquake/hazardlib/source/simple_fault.py @@ -523,21 +523,38 @@ def modify_set_dip(self, dip): def modify_set_recurrow(self, recurrow): """ Rebuild the MFD from a recurrow using self.recur_model + self.mmax - set by an earlier recurSet application. Intended for the BC Hydro - NVA alt2 model where fault sources take a scenario-dependent share - of the total rate above Mmax-1 and receive no rate below. + set by an earlier recurSet application. - Requires rate_split_fault_frac to be set on the source. The per-source - rate_frac attribute (fault-length fraction of the total fault share) is - used if set, otherwise defaults to 1.0. + Params are read from the recurrow dict, preferring fault-prefixed + keys (e.g. fault_b_value, fault_ref_mag, fault_rate) so a single + alt3-style recurRow branch can carry different but correlated + background and fault parameters; otherwise the unprefixed alt2-style + keys are used. + + How it works is determined by the values of rate_split_fault_frac + (set by an earlier rateSplit application, absent otherwise) and + rate_frac (from the source's rateFrac XML attribute, defaults to + 1.0 if not set): + + - No rateSplit and rate_frac == 1.0: build the parametric TE or + AC MFD from the row params and use it as it is. + + - Alt3 (no rateSplit, rate_frac != 1.0): scale every bin of the + parametric MFD by rate_frac and make an EvenlyDiscretizedMFD. + + - Alt2 (rateSplit present): zero bins below Mmax-1 (their rate + belongs to the background via the alt2 partition, not the + fault) and scale the remaining bins by + rate_split_fault_frac * rate_frac; make an EvenlyDiscretizedMFD. NOTE: This is currently only intended for support of the BC Hydro NVA SSC logic tree. We may expand it to become a more general capability. - :param recur_row: - Dict of values to use in given type of MFD (b_value, ref_mag, - rate) + :param recurrow: + Dict of values to use in given type of MFD (e.g. b_value, + ref_mag, rate). Alt3-style rows use prefixes of "fault" for + the same keys. """ # Constants for BC Hydro NVA model b_ac = 0.3 @@ -545,12 +562,13 @@ def modify_set_recurrow(self, recurrow): gamma_eff = 0.9185 # Corrected gamma_eff from eq 1.2 of BCHydro AC memo bin_width = 0.1 - # Get params from recurRow + # Get params from recurRow, preferring fault-prefixed keys (alt3) mmax = self.mmax recur_model = self.recur_model - bval = float(recurrow["b_value"]) - ref_mag = float(recurrow["ref_mag"]) - rate = float(recurrow["rate"]) + bval = float(recurrow.get("fault_b_value", recurrow.get("b_value"))) + ref_mag = float( + recurrow.get("fault_ref_mag", recurrow.get("ref_mag"))) + rate = float(recurrow.get("fault_rate", recurrow.get("rate"))) # Build the MFD if recur_model == "TE": @@ -567,14 +585,24 @@ def modify_set_recurrow(self, recurrow): total_rate=rate, ) - # No rateSplit: fault keeps the parametric MFD fault_frac = getattr(self, 'rate_split_fault_frac', None) - if fault_frac is None: + rate_frac = getattr(self, 'rate_frac', 1.0) + + # Neither rateSplit nor rateFrac set: no BC Hydro partition to apply + if fault_frac is None and rate_frac == 1.0: self.mfd = parent return - # Zero rates below Mmax-1, scale rest by fault_frac x rate_frac - rate_frac = getattr(self, 'rate_frac', 1.0) + # Alt3-style: rate_frac scaling + if fault_frac is None: + bins = parent.get_annual_occurrence_rates() + occ = [r * rate_frac for _, r in bins] + self.mfd = EvenlyDiscretizedMFD( + min_mag=bins[0][0], bin_width=bin_width, + occurrence_rates=occ) + return + + # Alt2-style: zero rates below Mmax-1, scale rest by fault_frac x rate_frac scale = fault_frac * rate_frac threshold = mmax - 1.0 bins = parent.get_annual_occurrence_rates() diff --git a/openquake/hazardlib/sourceconverter.py b/openquake/hazardlib/sourceconverter.py index e737b69d5b3..febe25faf42 100644 --- a/openquake/hazardlib/sourceconverter.py +++ b/openquake/hazardlib/sourceconverter.py @@ -708,7 +708,7 @@ def convert_areaSource(self, node): fracs = self.convert_hypo_dip_fracs(node) if fracs is not None: hdd.hypo_dip_fracs = fracs - return source.AreaSource( + area = source.AreaSource( source_id=node['id'], name=node['name'], tectonic_region_type=node.attrib.get('tectonicRegion'), @@ -723,6 +723,12 @@ def convert_areaSource(self, node): polygon=polygon, area_discretization=area_discretization, temporal_occurrence_model=self.get_tom(node)) + # Optional per-source offset applied to recurSet's max_mag + # (used by the BC Hydro NVA alt3 bg sources: bg_mmax = fault_mmax - 0.5) + mmax_offset = node.attrib.get('mmaxOffset') + if mmax_offset is not None: + area.mmax_offset = float(mmax_offset) + return area def convert_pointSource(self, node): """ diff --git a/openquake/qa_tests_data/logictree/case_25/alt12_AB.xml b/openquake/qa_tests_data/logictree/case_25/alt12_AB.xml index 8366e14cf55..0e5de46c2d8 100644 --- a/openquake/qa_tests_data/logictree/case_25/alt12_AB.xml +++ b/openquake/qa_tests_data/logictree/case_25/alt12_AB.xml @@ -43,25 +43,12 @@ - - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/openquake/qa_tests_data/logictree/case_25/alt12_UBN.xml b/openquake/qa_tests_data/logictree/case_25/alt12_UBN.xml index 655fffd7268..40c2b1c77b4 100644 --- a/openquake/qa_tests_data/logictree/case_25/alt12_UBN.xml +++ b/openquake/qa_tests_data/logictree/case_25/alt12_UBN.xml @@ -42,25 +42,12 @@ - - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/openquake/qa_tests_data/logictree/case_25/alt2_NVA-AB.xml b/openquake/qa_tests_data/logictree/case_25/alt2_NVA-AB.xml index c8aae8299e2..c767f383b54 100644 --- a/openquake/qa_tests_data/logictree/case_25/alt2_NVA-AB.xml +++ b/openquake/qa_tests_data/logictree/case_25/alt2_NVA-AB.xml @@ -32,25 +32,13 @@ + - - - - - - - - - - - - - - - - - - + + + + + @@ -81,20 +69,12 @@ 1.0 90.0 + - - - - - - - - - - - - - + + + + @@ -123,20 +103,12 @@ 1.0 90.0 + - - - - - - - - - - - - - + + + + @@ -164,20 +136,12 @@ 1.0 90.0 + - - - - - - - - - - - - - + + + + @@ -205,20 +169,12 @@ 1.0 90.0 + - - - - - - - - - - - - - + + + + diff --git a/openquake/qa_tests_data/logictree/case_25/alt2_NVA-UBN.xml b/openquake/qa_tests_data/logictree/case_25/alt2_NVA-UBN.xml index 60fbb4696e5..dd52e9531f6 100644 --- a/openquake/qa_tests_data/logictree/case_25/alt2_NVA-UBN.xml +++ b/openquake/qa_tests_data/logictree/case_25/alt2_NVA-UBN.xml @@ -33,25 +33,13 @@ + - - - - - - - - - - - - - - - - - - + + + + + @@ -82,20 +70,12 @@ 1.0 90.0 + - - - - - - - - - - - - - + + + + @@ -124,20 +104,12 @@ 1.0 90.0 + - - - - - - - - - - - - - + + + + @@ -165,20 +137,12 @@ 1.0 90.0 + - - - - - - - - - - - - - + + + + @@ -206,20 +170,12 @@ 1.0 90.0 + - - - - - - - - - - - - - + + + + diff --git a/openquake/qa_tests_data/logictree/case_25/alt3_NVA-AB.xml b/openquake/qa_tests_data/logictree/case_25/alt3_NVA-AB.xml new file mode 100644 index 00000000000..c55905d0707 --- /dev/null +++ b/openquake/qa_tests_data/logictree/case_25/alt3_NVA-AB.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + -121.7803 49.2349 -121.9131 49.3456 -123.0849 50.2956 -125.9767 51.7779 -125.6634 51.906 -124.7858 51.9227 -123.7428 51.4624 -122.6619 51.1255 -121.9429 50.772 -121.4579 49.8895 -121.2884 49.5455 -121.2242 49.4137 -121.7803 49.2349 + + + + 0.0 + 20 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 3.25 + 1.0 + + + + + + + + + + + + + + + + + + + + + -121.7334 49.2502 -121.8623 49.5943 -122.3035 49.8454 -122.5585 50.0895 + -122.6188 50.2273 -123.0106 50.5333 -123.287 50.6697 -123.6021 50.7102 + -123.8088 50.8176 -124.2442 50.9267 -124.457 51.0984 -124.7655 51.2026 + -125.7099 51.665 -125.8893 51.8138 + + + 45.0 + 0.0 + 15.0 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 6.0 + 1.0 + + 90.0 + + + + + + + + + + + + + + -121.6843 50.2959 -121.888 50.4635 -123.0619 50.9694 -123.218 51.0624 + -124.0714 51.2956 -124.3707 51.4605 -124.797 51.7831 -125.0291 51.9188 + + + 45.0 + 0.0 + 15.0 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 6.0 + 1.0 + + 90.0 + + + + + + + + + + + + + + -121.7976 50.5025 -122.0891 50.6951 -122.5077 50.8825 -122.6765 50.9768 -122.9555 51.2188 + + + 45.0 + 0.0 + 15.0 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 6.0 + 1.0 + + 90.0 + + + + + + + + + + + + + + -121.2264 49.418 -121.4578 49.8895 -121.943 50.772 -122.6618 51.1255 -123.7429 51.4624 -124.7859 51.9227 -124.7911 51.9226 + + + 45.0 + 0.0 + 15.0 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 6.0 + 1.0 + + 90.0 + + + + + + + + + + + + diff --git a/openquake/qa_tests_data/logictree/case_25/alt3_NVA-UBN.xml b/openquake/qa_tests_data/logictree/case_25/alt3_NVA-UBN.xml new file mode 100644 index 00000000000..946a61ea4ee --- /dev/null +++ b/openquake/qa_tests_data/logictree/case_25/alt3_NVA-UBN.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + -121.2884 49.5455 -121.9131 49.3456 -122.1555 49.2666 -122.2759 49.4109 -123.0124 49.8213 -124.6899 50.7152 -125.1361 50.9117 -125.8091 51.111 -126.562 51.3272 -126.4772 51.5697 -126.2179 51.678 -125.9767 51.7779 -125.6634 51.906 -124.7858 51.9227 -123.7428 51.4624 -122.6619 51.1255 -121.9429 50.772 -121.4579 49.8895 -121.2884 49.5455 + + + + 0.0 + 20 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + + 3.25 + 1.0 + + + + + + + + + + + + + + + + + + + + + -121.7334 49.2502 -121.8623 49.5943 -122.3035 49.8454 -122.5585 50.0895 + -122.6188 50.2273 -123.0106 50.5333 -123.287 50.6697 -123.6021 50.7102 + -123.8088 50.8176 -124.2442 50.9267 -124.457 51.0984 -124.7655 51.2026 + -125.7099 51.665 -125.8893 51.8138 + + + 45.0 + 0.0 + 15.0 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 6.0 + 1.0 + + 90.0 + + + + + + + + + + + + + + -121.6843 50.2959 -121.888 50.4635 -123.0619 50.9694 -123.218 51.0624 + -124.0714 51.2956 -124.3707 51.4605 -124.797 51.7831 -125.0291 51.9188 + + + 45.0 + 0.0 + 15.0 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 6.0 + 1.0 + + 90.0 + + + + + + + + + + + + + + -121.7976 50.5025 -122.0891 50.6951 -122.5077 50.8825 -122.6765 50.9768 -122.9555 51.2188 + + + 45.0 + 0.0 + 15.0 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 6.0 + 1.0 + + 90.0 + + + + + + + + + + + + + + -121.2264 49.418 -121.4578 49.8895 -121.943 50.772 -122.6618 51.1255 -123.7429 51.4624 -124.7859 51.9227 -124.7911 51.9226 + + + 45.0 + 0.0 + 15.0 + + BCHydroNVAMSR + + linear_piecewise + + + + + + + 6.0 + 1.0 + + 90.0 + + + + + + + + + + + + diff --git a/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt1.csv b/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt1.csv index a2a5b1f6ec0..5315e90c966 100644 --- a/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt1.csv +++ b/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt1.csv @@ -1,3 +1,3 @@ -#,,,,,,,,"generated_by='OpenQuake engine 3.27.0-giteba4ab5ce3', start_date='2026-07-19T07:37:00', checksum=1753173620, kind='mean', investigation_time=50.0, imt='PGA'" +#,,,,,,,,"generated_by='OpenQuake engine 3.27.0-gitc7e2866a0b', start_date='2026-07-20T11:15:38', checksum=2161630964, kind='mean', investigation_time=50.0, imt='PGA'" lon,lat,depth,poe-0.0100000,poe-0.0500000,poe-0.1000000,poe-0.2000000,poe-0.5000000,poe-1.0000000 --121.80000,49.10640,0.00000,1.119916E-01,1.213262E-02,2.737377E-03,3.321296E-04,3.116762E-06,0.000000E+00 +-121.80000,49.10640,0.00000,1.081894E-01,1.166617E-02,2.622868E-03,3.166347E-04,2.968191E-06,0.000000E+00 diff --git a/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt2.csv b/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt2.csv index 72b5757f99f..db7da748ae9 100644 --- a/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt2.csv +++ b/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt2.csv @@ -1,3 +1,3 @@ -#,,,,,,,,"generated_by='OpenQuake engine 3.27.0-giteba4ab5ce3', start_date='2026-07-19T07:37:06', checksum=1954854747, kind='mean', investigation_time=50.0, imt='PGA'" +#,,,,,,,,"generated_by='OpenQuake engine 3.27.0-gitc7e2866a0b', start_date='2026-07-20T11:16:02', checksum=1614135368, kind='mean', investigation_time=50.0, imt='PGA'" lon,lat,depth,poe-0.0100000,poe-0.0500000,poe-0.1000000,poe-0.2000000,poe-0.5000000,poe-1.0000000 --121.80000,49.10640,0.00000,1.242654E-01,1.298556E-02,3.304175E-03,6.399101E-04,2.288344E-05,2.801620E-09 +-121.80000,49.10640,0.00000,1.256763E-01,1.314337E-02,3.338275E-03,6.434025E-04,2.291842E-05,2.779808E-09 diff --git a/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt3.csv b/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt3.csv new file mode 100644 index 00000000000..42f4de60d45 --- /dev/null +++ b/openquake/qa_tests_data/logictree/case_25/expected/hazard_curve-mean-PGA_alt3.csv @@ -0,0 +1,3 @@ +#,,,,,,,,"generated_by='OpenQuake engine 3.27.0-gitc7e2866a0b', start_date='2026-07-20T11:16:28', checksum=2600948032, kind='mean', investigation_time=50.0, imt='PGA'" +lon,lat,depth,poe-0.0100000,poe-0.0500000,poe-0.1000000,poe-0.2000000,poe-0.5000000,poe-1.0000000 +-121.80000,49.10640,0.00000,6.269630E-01,1.532334E-01,5.476460E-02,1.365837E-02,5.509108E-04,0.000000E+00 diff --git a/openquake/qa_tests_data/logictree/case_25/job_alt3.ini b/openquake/qa_tests_data/logictree/case_25/job_alt3.ini new file mode 100644 index 00000000000..792784c6854 --- /dev/null +++ b/openquake/qa_tests_data/logictree/case_25/job_alt3.ini @@ -0,0 +1,38 @@ +[general] + +description = BCHydro-style correlated uncertainties (alt3) +calculation_mode = classical +random_seed = 23 + +[geometry] + +sites = -121.8 49.1064 + +[logic_tree] + +number_of_logic_tree_samples = 0 + +[erf] +area_source_discretization = 40.0 +rupture_mesh_spacing = 15.0 +width_of_mfd_bin = 0.1 + +[site_params] + +reference_vs30_type = measured +reference_vs30_value = 760.0 +reference_depth_to_2pt5km_per_sec = 2.5 +reference_depth_to_1pt0km_per_sec = 50.0 + +[calculation] + +source_model_logic_tree_file = smlt_alt3.xml +gsim_logic_tree_file = gsim_logic_tree.xml +investigation_time = 50.0 +intensity_measure_types_and_levels = {"PGA": [0.01, 0.05, 0.1, 0.2, 0.5, 1.0]} +truncation_level = 3.0 +maximum_distance = 200.0 + +[output] + +mean = true diff --git a/openquake/qa_tests_data/logictree/case_25/smlt_alt3.xml b/openquake/qa_tests_data/logictree/case_25/smlt_alt3.xml new file mode 100644 index 00000000000..83b05fa3e7a --- /dev/null +++ b/openquake/qa_tests_data/logictree/case_25/smlt_alt3.xml @@ -0,0 +1,76 @@ + + + + + + + + alt3_NVA-UBN.xml + 0.5 + + + alt3_NVA-AB.xml + 0.5 + + + + + + + + 1.0 + + + + + + + 1.0 + + + + + + + + 0.5 + + + + 0.5 + + + + + + + 0.5 + + + + 0.5 + + + + + + + 45.0 + 0.5 + + + 60.0 + 0.5 + + + + + + + 15.0 + 1.0 + + + + +