diff --git a/debian/changelog b/debian/changelog
index 158b5e1fd22..ccead81e84c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,14 @@
[Michele Simionato]
+ * Fixed a bug affecting previous versions of the engine: with few sites
+ only the ruptures of the first atomic group of a task were stored,
+ so the disaggregation missed the ruptures of the other atomic groups
* Fixed a bug affecting previous versions of the engine: with
- site_labels the atomic groups (cluster or mutex sources) were
- computed only for the sites of the last label. This affects the
- USA model near New Madrid and Memphis
+ disagg_by_src the rates of multiple atomic groups were all attributed
+ to the source of the first group in mean_rates_by_src, and the
+ ruptures of the other groups were not stored; with site_labels the
+ atomic groups (cluster or mutex sources) were computed only for the
+ sites of the last label. This affects the USA model near New Madrid
+ and Memphis
[Anirudh Rao]
* Added support for generating regular UTM site grids from exposure,
diff --git a/openquake/calculators/classical.py b/openquake/calculators/classical.py
index db304b0226a..9e8a32628e3 100644
--- a/openquake/calculators/classical.py
+++ b/openquake/calculators/classical.py
@@ -309,7 +309,7 @@ def geometry_key(srcs, bvals):
# e.g. logictree/case_18_bis: the sources of 'first' have two keys, one
# for setLowerSeismDepthAbsolute 10.0 and one for 20.0, with 9 sets each
# (3 abGR x 3 maxMagGR), i.e. two groups of 9 variants for make_results:
- # (('bs6', '10.0'),) and (('bs6', '20.0'),)
+ # (('bs6', '10.0'),) and (('bs6', '20.0'),)
return tuple((bset.id, repr(value)) for bset, value in bvals
if bset.uncertainty_type not in GEOMETRY_PRESERVING and
any(get_bset_value(bset, value, src)[0] for src in srcs))
@@ -363,8 +363,33 @@ def _make_results(pairs, sites, remove_zeros, as_rmap):
return dics
+def _atomic_results(grps, sites, cmaker, remove_zeros, as_rmap,
+ merge_atomic):
+ # yield the results of the atomic groups collapsed in a single
+ # task (see get_allargs and bysrc_results); they contribute to the
+ # same RateMap in the master, with the gids of all the
+ # realizations; the uncertainties are not applied, which is fine
+ # because the sources of an atomic group are nonparametric (the
+ # mutex ones must be, since mutually exclusive ruptures are
+ # modelled with nonparametric sources)
+ if merge_atomic:
+ # with many sites there are no rup_data nor mean_rates_by_src
+ yield baseclassical(grps, sites, cmaker, remove_zeros,
+ as_rmap=as_rmap)
+ else:
+ # one result per group, since the rates are attributed to the
+ # source of each result (see 'basename' in RmapMaker.make) and
+ # the rup_data of different groups cannot be concatenated
+ # (different shapes of probs_occur); the groups are
+ # independent, so the sum of the results is the same;
+ # tested in classical/case_27_other and disagg/case_18
+ for grp in grps:
+ yield baseclassical([grp], sites, cmaker, remove_zeros,
+ as_rmap=as_rmap)
+
+
def bysrc_results(grps, sites, cmaker, gid_dic, full_lt, remove_zeros,
- as_rmap=True):
+ as_rmap=True, merge_atomic=True):
"""
Yield the results of a classical task as RateMaps: the CSM is built without
applying the uncertainties, so cmakers_groups_gkeys applies them one
@@ -379,17 +404,12 @@ def bysrc_results(grps, sites, cmaker, gid_dic, full_lt, remove_zeros,
:param remove_zeros: if True the sites with zero rates are removed
:param as_rmap: if False the rates are returned as arrays of rates,
stored right away by the master, see baseclassical
+ :param merge_atomic: if True the atomic groups of the task are
+ returned in a single result, otherwise in a result per group
"""
if len(grps) > 1:
- # the atomic groups collapsed in a single task (see get_allargs)
- # contribute to the same RateMap in the master, so they must be
- # returned in a single result, with the gids of all the
- # realizations; the uncertainties are not applied, which is fine
- # because the sources of an atomic group are nonparametric (the
- # mutex ones must be, since mutually exclusive ruptures are
- # modelled with nonparametric sources) and no uncertainty can be
- # applied to a nonparametric source (the calculation would fail)
- yield baseclassical(grps, sites, cmaker, remove_zeros, as_rmap=as_rmap)
+ yield from _atomic_results(grps, sites, cmaker, remove_zeros,
+ as_rmap, merge_atomic)
return
grp = grps[0]
if grp.atomic:
@@ -496,8 +516,13 @@ def classical_bysrc(grp_keys, tilegetter, cmaker, dstore, monitor):
"""
grps, sites, gid_dic, full_lt = read_task_input(
grp_keys, tilegetter, cmaker, dstore, monitor)
- yield from bysrc_results(grps, sites, cmaker, gid_dic, full_lt,
- remove_zeros=False)
+ # the RateMap in the master is the one of the first group (see
+ # get_rmap), also for the results of the other atomic groups
+ rmap_id = int(grp_keys[0].split('-')[0])
+ for dic in bysrc_results(grps, sites, cmaker, gid_dic, full_lt,
+ remove_zeros=False, merge_atomic=False):
+ dic['rmap_id'] = rmap_id
+ yield dic
def classical(grp_keys, tilegetter, cmaker, dstore, monitor):
@@ -751,8 +776,10 @@ def agg_dicts(self, acc, dic):
chunkno = dic.get('chunkno') # None with the current impl.
_store(rmap, self.num_chunks, self.datastore, chunkno)
else:
- # aggregating rates is ultra-fast compared to storing
- self.rmap[grp_id] += rmap
+ # aggregating rates is ultra-fast compared to storing; NB: the
+ # results of the atomic groups go in the RateMap of the first
+ # group of the task, see classical_bysrc
+ self.rmap[dic.pop('rmap_id', grp_id)] += rmap
return acc
def create_rup(self):
diff --git a/openquake/calculators/tests/classical_test.py b/openquake/calculators/tests/classical_test.py
index 221b21334b8..27a02448105 100644
--- a/openquake/calculators/tests/classical_test.py
+++ b/openquake/calculators/tests/classical_test.py
@@ -396,7 +396,9 @@ def test_case_27(self): # Nankai mutex model
# make sure probs_occur are stored as expected
probs_occur = self.calc.datastore['rup/probs_occur'][:]
tot_probs_occur = sum(len(po) for po in probs_occur)
- self.assertEqual(tot_probs_occur, 4) # 2 x 2
+ # 2 atomic groups x 2 ruptures x 2 probabilities; there were 4
+ # when the ruptures of the second atomic group were not stored
+ self.assertEqual(tot_probs_occur, 8)
# check mean_rates_by_src
[fname] = export(('mean_rates_by_src', 'csv'), self.calc.datastore)
@@ -419,6 +421,22 @@ def test_case_27(self): # Nankai mutex model
fname = general.gettemp(text_table(rel, ext='org'))
self.assertEqualFiles('expected/rel_source.org', fname)
+ def test_case_27_other(self):
+ # the second atomic group has sources named other:03, other:04;
+ # with disagg_by_src the rates of each atomic group must be
+ # attributed to its own source, while they were all attributed to
+ # the first group of the task, i.e. case=0 and other=9.39E-3
+ self.run_calc(case_27.__file__, 'job_other.ini')
+ mrs = self.calc.datastore['mean_rates_by_src']
+ rates = dict(zip(mrs.src_id, mrs.array[0, 0, 0])) # site 0, PGV
+ aac([rates['case'], rates['other']], [5.1798E-3, 4.2115E-3],
+ rtol=1E-4)
+ # the total is the same as in case_27, where both groups are 'case'
+ aac(rates['case'] + rates['other'], 9.3913E-3, rtol=1E-4)
+ # the ruptures of both atomic groups are stored, see test_case_27
+ probs_occur = self.calc.datastore['rup/probs_occur'][:]
+ self.assertEqual(sum(len(po) for po in probs_occur), 8)
+
def test_case_27_labels(self):
# two close sites with different labels and the same gsim: the
# atomic groups must be computed for both, while they were
diff --git a/openquake/calculators/tests/disagg_test.py b/openquake/calculators/tests/disagg_test.py
index 34f85e91206..a2ef77e6f12 100644
--- a/openquake/calculators/tests/disagg_test.py
+++ b/openquake/calculators/tests/disagg_test.py
@@ -29,7 +29,7 @@
from openquake.qa_tests_data.disagg import (
case_1, case_2, case_3, case_4, case_5, case_6, case_7, case_8, case_9,
case_10, case_11, case_12, case_13, case_14, case_15, case_16, case_17,
- case_master)
+ case_18, case_master)
aae = numpy.testing.assert_almost_equal
ae = numpy.testing.assert_equal
@@ -287,6 +287,20 @@ def test_case_17(self):
# it depends on the maxMagGRRelative uncertainty on the source 1
aae(dstore.read_df("mce").MCE, [0.46, 1.6310, 1.51], 4)
+ def test_case_18(self):
+ # Nankai mutex model with two atomic groups in the same task (see
+ # concurrent_tasks in job.ini): the ruptures of both groups must
+ # be stored, while only the ones of the first group were stored
+ # (4 probs_occur instead of 8), so the disaggregation missed the
+ # large Nankai ruptures of the second group (Mag sum .19 vs .41)
+ self.run_calc(case_18.__file__, 'job.ini')
+ probs_occur = self.calc.datastore['rup/probs_occur'][:]
+ self.assertEqual(sum(len(po) for po in probs_occur), 8)
+ fnames = export(('disagg-rlzs', 'csv'), self.calc.datastore)
+ for fname in fnames:
+ self.assertEqualFiles(
+ 'expected_output/' + strip_calc_id(fname), fname)
+
def test_case_master(self):
# this tests exercise the case of a complex logic tree
self.run_calc(case_master.__file__, 'job.ini')
diff --git a/openquake/qa_tests_data/classical/README.md b/openquake/qa_tests_data/classical/README.md
index 1724a680ad6..1f6ef0debbf 100644
--- a/openquake/qa_tests_data/classical/README.md
+++ b/openquake/qa_tests_data/classical/README.md
@@ -22,6 +22,7 @@
|case\_25|Test negative depths|
|case\_26|Test YoungsCoppersmith1985MFD|
|case\_27|Nankai mutex model, including disaggregation|
+|case\_27\_other|Nankai mutex model with disagg\_by\_src and atomic groups with different source names|
|case\_27\_labels|Nankai mutex model with site\_labels|
|case\_28|Test hypoDepthDist (and down-dip fractions for hypo placement) with a simple fault source|
|case\_29|Non-parametric source with one rupture represente by 2 kite surfaces|
diff --git a/openquake/qa_tests_data/classical/case_27/job_other.ini b/openquake/qa_tests_data/classical/case_27/job_other.ini
new file mode 100644
index 00000000000..bbfb9aac60f
--- /dev/null
+++ b/openquake/qa_tests_data/classical/case_27/job_other.ini
@@ -0,0 +1,41 @@
+[general]
+
+description = Nankai mutex model with atomic groups with different source names
+calculation_mode = classical
+random_seed = 1066
+
+[geometry]
+
+sites = 135.68 35.68
+
+[logic_tree]
+
+# end branch enumeration
+number_of_logic_tree_samples = 0
+
+[erf]
+
+# km
+rupture_mesh_spacing = 1.
+width_of_mfd_bin = 1.
+area_source_discretization =
+
+[site_params]
+
+reference_vs30_type = measured
+reference_vs30_value = 400.0
+reference_depth_to_2pt5km_per_sec = 1.
+reference_depth_to_1pt0km_per_sec = 100.
+
+[calculation]
+
+source_model_logic_tree_file = source_model_logic_tree_other.xml
+gsim_logic_tree_file = gsim_logic_tree.xml
+# years
+investigation_time = 50.
+intensity_measure_types_and_levels = {"PGV": [20]}
+# km
+maximum_distance = 200.
+truncation_level = 99.
+disagg_by_src = true
+poes = .02
diff --git a/openquake/qa_tests_data/classical/case_27/source_model_logic_tree_other.xml b/openquake/qa_tests_data/classical/case_27/source_model_logic_tree_other.xml
new file mode 100644
index 00000000000..f0887b1b75d
--- /dev/null
+++ b/openquake/qa_tests_data/classical/case_27/source_model_logic_tree_other.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ source_model_other.xml
+ 1.0
+
+
+
+
+
diff --git a/openquake/qa_tests_data/classical/case_27/source_model_other.hdf5 b/openquake/qa_tests_data/classical/case_27/source_model_other.hdf5
new file mode 100644
index 00000000000..d9e868cf0ef
Binary files /dev/null and b/openquake/qa_tests_data/classical/case_27/source_model_other.hdf5 differ
diff --git a/openquake/qa_tests_data/classical/case_27/source_model_other.xml b/openquake/qa_tests_data/classical/case_27/source_model_other.xml
new file mode 100644
index 00000000000..dfe6a9de052
--- /dev/null
+++ b/openquake/qa_tests_data/classical/case_27/source_model_other.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 135.705 35.898
+
+
+
+ 64.995
+
+
+ 65.00500000000001
+
+
+
+ PointMSR
+
+
+ 1
+
+
+
+ 5.0 5.1 5.199999999999999 5.299999999999999 5.399999999999999 5.499999999999998 5.599999999999998 5.6999999999999975 5.799999999999997 5.899999999999997 5.9999999999999964 6.099999999999996 6.199999999999996 6.299999999999995 6.399999999999995 6.499999999999995 6.599999999999994 6.699999999999994 6.799999999999994 6.899999999999993 6.999999999999993 7.0999999999999925 7.199999999999992 7.299999999999992 7.3999999999999915 7.499999999999991
+
+
+ 0.0017404297316440736 0.0014146743971195982 0.0011498905204148596 0.0009346661052410158 0.0007597251327641438 0.0006175277718075182 0.0005019454175039715 0.00040799655279595776 0.0003316320486023118 0.00026956064924197487 0.00021910712166088434 0.00017809695479484682 0.00014476263969314965 0.00011766749114306046 9.56437275615485e-05 7.774214044171201e-05 6.319118414293873e-05 5.136372282392555e-05 4.175000133508116e-05 3.393567513504588e-05 2.758395233639655e-05 2.242107821543914e-05 1.8224536578811047e-05 1.48134594742065e-05 1.2040831910595241e-05 9.7871556169343e-06
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 135.805 35.898
+
+
+
+ 60.995
+
+
+ 61.5
+
+
+
+ PointMSR
+
+
+ 1
+
+
+
+ 5.0 5.1 5.199999999999999 5.299999999999999 5.399999999999999 5.499999999999998 5.599999999999998 5.6999999999999975 5.799999999999997 5.899999999999997 5.9999999999999964 6.099999999999996 6.199999999999996 6.299999999999995 6.399999999999995 6.499999999999995 6.599999999999994 6.699999999999994 6.799999999999994 6.899999999999993 6.999999999999993 7.0999999999999925 7.199999999999992 7.299999999999992 7.3999999999999915 7.499999999999991
+
+
+ 0.0017404297316440736 0.0014146743971195982 0.0011498905204148596 0.0009346661052410158 0.0007597251327641438 0.0006175277718075182 0.0005019454175039715 0.00040799655279595776 0.0003316320486023118 0.00026956064924197487 0.00021910712166088434 0.00017809695479484682 0.00014476263969314965 0.00011766749114306046 9.56437275615485e-05 7.774214044171201e-05 6.319118414293873e-05 5.136372282392555e-05 4.175000133508116e-05 3.393567513504588e-05 2.758395233639655e-05 2.242107821543914e-05 1.8224536578811047e-05 1.48134594742065e-05 1.2040831910595241e-05 9.7871556169343e-06
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/openquake/qa_tests_data/disagg/README.md b/openquake/qa_tests_data/disagg/README.md
index 093d14fac83..690a4638225 100644
--- a/openquake/qa_tests_data/disagg/README.md
+++ b/openquake/qa_tests_data/disagg/README.md
@@ -13,5 +13,6 @@
| case_11 | |
| case_12 | Source fragments and alternatives of fragments |
| case_16 | Check the NZ22 K20 GMM has required attributes |
+| case_18 | Nankai mutex model with two atomic groups in the same task |
diff --git a/openquake/qa_tests_data/disagg/case_18/__init__.py b/openquake/qa_tests_data/disagg/case_18/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/openquake/qa_tests_data/disagg/case_18/expected_output/Dist-0.csv b/openquake/qa_tests_data/disagg/case_18/expected_output/Dist-0.csv
new file mode 100644
index 00000000000..b12fb44cc28
--- /dev/null
+++ b/openquake/qa_tests_data/disagg/case_18/expected_output/Dist-0.csv
@@ -0,0 +1,22 @@
+#,,,,"generated_by='OpenQuake engine 3.27.0-gitc1eb1c6f35', start_date='2026-10-18T02:42:48', checksum=2496305347, investigation_time=50.0, mag_bin_edges=[5.0, 5.1000000000000005, 5.2, 5.300000000000001, 5.4, 5.5, 5.6000000000000005, 5.7, 5.800000000000001, 5.9, 6.0, 6.1000000000000005, 6.2, 6.300000000000001, 6.4, 6.5, 6.6000000000000005, 6.7, 6.800000000000001, 6.9, 7.0, 7.1000000000000005, 7.2, 7.300000000000001, 7.4, 7.5, 7.6000000000000005, 7.7, 7.800000000000001, 7.9, 8.0, 8.1, 8.200000000000001, 8.3, 8.4, 8.5, 8.6, 8.700000000000001, 8.8, 8.9, 9.0, 9.1, 9.200000000000001], dist_bin_edges=[0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0, 140.0, 150.0, 160.0, 170.0, 180.0, 190.0, 200.0], lon_bin_edges=[133.46570921391037, 135.68, 137.89429078608964], lat_bin_edges=[33.88136, 35.68, 37.47864], eps_bin_edges=[-3.0, 3.0], tectonic_region_types=['Subduction Interface'], lon=135.68, lat=35.68, weights=[1.0], rlz_ids=[0]"
+imt,iml,poe,dist,rlz0
+PGV,2.00000E+01,2.00000E-02,5.00000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.50000E+01,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,2.50000E+01,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,3.50000E+01,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,4.50000E+01,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.50000E+01,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,6.50000E+01,4.80213E-03
+PGV,2.00000E+01,2.00000E-02,7.50000E+01,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.50000E+01,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,9.50000E+01,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.05000E+02,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.15000E+02,2.72122E-01
+PGV,2.00000E+01,2.00000E-02,1.25000E+02,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.35000E+02,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.45000E+02,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.55000E+02,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.65000E+02,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.75000E+02,1.19650E-01
+PGV,2.00000E+01,2.00000E-02,1.85000E+02,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,1.95000E+02,0.00000E+00
diff --git a/openquake/qa_tests_data/disagg/case_18/expected_output/Mag-0.csv b/openquake/qa_tests_data/disagg/case_18/expected_output/Mag-0.csv
new file mode 100644
index 00000000000..36d82ea7f7a
--- /dev/null
+++ b/openquake/qa_tests_data/disagg/case_18/expected_output/Mag-0.csv
@@ -0,0 +1,44 @@
+#,,,,"generated_by='OpenQuake engine 3.27.0-gitc1eb1c6f35', start_date='2026-10-18T02:42:48', checksum=2496305347, investigation_time=50.0, mag_bin_edges=[5.0, 5.1000000000000005, 5.2, 5.300000000000001, 5.4, 5.5, 5.6000000000000005, 5.7, 5.800000000000001, 5.9, 6.0, 6.1000000000000005, 6.2, 6.300000000000001, 6.4, 6.5, 6.6000000000000005, 6.7, 6.800000000000001, 6.9, 7.0, 7.1000000000000005, 7.2, 7.300000000000001, 7.4, 7.5, 7.6000000000000005, 7.7, 7.800000000000001, 7.9, 8.0, 8.1, 8.200000000000001, 8.3, 8.4, 8.5, 8.6, 8.700000000000001, 8.8, 8.9, 9.0, 9.1, 9.200000000000001], dist_bin_edges=[0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0, 140.0, 150.0, 160.0, 170.0, 180.0, 190.0, 200.0], lon_bin_edges=[133.46570921391037, 135.68, 137.89429078608964], lat_bin_edges=[33.88136, 35.68, 37.47864], eps_bin_edges=[-3.0, 3.0], tectonic_region_types=['Subduction Interface'], lon=135.68, lat=35.68, weights=[1.0], rlz_ids=[0]"
+imt,iml,poe,mag,rlz0
+PGV,2.00000E+01,2.00000E-02,5.05000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.15000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.25000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.35000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.45000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.55000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.65000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.75000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.85000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,5.95000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,6.05000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,6.15000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,6.25000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,6.35000E+00,1.37909E-05
+PGV,2.00000E+01,2.00000E-02,6.45000E+00,1.25290E-04
+PGV,2.00000E+01,2.00000E-02,6.55000E+00,1.41311E-04
+PGV,2.00000E+01,2.00000E-02,6.65000E+00,2.16021E-04
+PGV,2.00000E+01,2.00000E-02,6.75000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,6.85000E+00,3.04486E-04
+PGV,2.00000E+01,2.00000E-02,6.95000E+00,8.91542E-04
+PGV,2.00000E+01,2.00000E-02,7.05000E+00,5.70299E-04
+PGV,2.00000E+01,2.00000E-02,7.15000E+00,6.25585E-04
+PGV,2.00000E+01,2.00000E-02,7.25000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,7.35000E+00,6.52384E-04
+PGV,2.00000E+01,2.00000E-02,7.45000E+00,1.27113E-03
+PGV,2.00000E+01,2.00000E-02,7.55000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,7.65000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,7.75000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,7.85000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,7.95000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.05000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.15000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.25000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.35000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.45000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.55000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.65000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.75000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,8.85000E+00,5.73661E-02
+PGV,2.00000E+01,2.00000E-02,8.95000E+00,2.24905E-01
+PGV,2.00000E+01,2.00000E-02,9.05000E+00,0.00000E+00
+PGV,2.00000E+01,2.00000E-02,9.15000E+00,1.22968E-01
diff --git a/openquake/qa_tests_data/disagg/case_18/job.ini b/openquake/qa_tests_data/disagg/case_18/job.ini
new file mode 100644
index 00000000000..eabdc04715a
--- /dev/null
+++ b/openquake/qa_tests_data/disagg/case_18/job.ini
@@ -0,0 +1,48 @@
+[general]
+
+description = Nankai mutex model with two atomic groups in the same task
+calculation_mode = disaggregation
+random_seed = 1066
+# NB: concurrent_tasks = 1 puts both atomic groups in the same task, so
+# the disaggregation needs the ruptures of both, while only the ones of
+# the first group used to be stored
+concurrent_tasks = 1
+
+[geometry]
+
+sites = 135.68 35.68
+
+[logic_tree]
+
+number_of_logic_tree_samples = 0
+
+[erf]
+
+rupture_mesh_spacing = 1.
+width_of_mfd_bin = 1.
+area_source_discretization =
+
+[site_params]
+
+reference_vs30_type = measured
+reference_vs30_value = 400.0
+reference_depth_to_2pt5km_per_sec = 1.
+reference_depth_to_1pt0km_per_sec = 100.
+
+[calculation]
+
+source_model_logic_tree_file = ../../classical/case_27/source_model_logic_tree_other.xml
+gsim_logic_tree_file = ../../classical/case_27/gsim_logic_tree.xml
+investigation_time = 50.
+intensity_measure_types_and_levels = {"PGV": [20]}
+maximum_distance = 200.
+truncation_level = 3
+
+[disaggregation]
+
+poes_disagg = 0.02
+mag_bin_width = 0.1
+distance_bin_width = 10.0
+coordinate_bin_width = 1.0
+num_epsilon_bins = 6
+disagg_outputs = Mag Dist