Skip to content

Commit 0e7e22f

Browse files
authored
Merge branch 'main' into fix/sorter-widget-update-layer-fields
2 parents 0516d71 + 537da39 commit 0e7e22f

2 files changed

Lines changed: 61 additions & 12 deletions

File tree

loopstructural/main/model_manager.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -549,11 +549,9 @@ def update_foliation_features(self):
549549
data = []
550550
groupname = group.name
551551
stratigraphic_column[groupname] = {}
552-
for u in reversed(group.units):
552+
for u in group.units:
553553
unit_data = self.stratigraphy.get(u.name, None)
554-
if unit_data is None:
555-
continue
556-
else:
554+
if unit_data is not None:
557555
if 'contact' in unit_data:
558556
contact = unit_data['contact']
559557
if not contact.empty:

tests/qgis/test_stratigraphic_value_consistency.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,32 @@
66
name to stamp on each extracted isosurface, using its own idea of which
77
value belongs to which unit.
88
9-
These two must agree on direction (does value increase from oldest-to-
10-
youngest, or youngest-to-oldest?), or every extracted surface gets labelled
11-
with the wrong unit while keeping correct geometry -- see the "stratigraphic
12-
column was reversed" fixes in model_manager.py (2025-07-21) and the widget
13-
(2025-08-21, reverted 2025-09-08). This has flipped back and forth as this
14-
plugin and LoopStructural evolved independently; this test pins the
15-
invariant so a future change on either side fails loudly here instead of
16-
silently inverting a user's model.
9+
10+
Both walk `reversed(group.units)`, accumulating cumulative thickness the
11+
same way, so a unit's own training value must equal `u.min()` -- the
12+
cumulative thickness *before* that unit's own thickness is added. This is
13+
also each unit's true base: `add_unit(..., where='top')` (the default)
14+
appends to the end of the column, so building a column correctly means
15+
adding the truly oldest unit first and progressively younger ones after --
16+
each unit's own base is the boundary shared with the next-older neighbour
17+
processed just before it, i.e. `min()`. See LoopStructural's own
18+
`test_get_isovalues_multi_unit_group` (`tests/unit/modelling/
19+
test_stratigraphic_column.py`), whose comment states this explicitly: "the
20+
base of the oldest unit in a group is 0".
21+
22+
If training and `get_isovalues()` disagree on this, every extracted surface
23+
gets labelled with the wrong unit while keeping correct geometry -- see the
24+
"stratigraphic column was reversed" fixes in model_manager.py (2025-07-21)
25+
and the widget (2025-08-21, reverted 2025-09-08). This has flipped back and
26+
forth as this plugin and LoopStructural evolved independently; this test
27+
pins the invariant so a future change on either side fails loudly here
28+
instead of silently inverting a user's model.
29+
30+
Note this is a separate concern from whether a stratigraphic column's units
31+
were themselves *added* in the correct oldest-to-youngest order -- if they
32+
weren't, `min()`/`max()` stop corresponding to true geological base/top no
33+
matter what training does, and the fix is to reorder the column's units,
34+
not to change which value training uses.
1735
"""
1836

1937
import pandas as pd
@@ -92,6 +110,39 @@ def test_two_groups_split_by_unconformity(self, manager):
92110
for unit_name in ('basin_floor', 'basin_fill', 'cover_lower', 'cover_upper'):
93111
assert training_values[unit_name] == pytest.approx(expected_values[unit_name])
94112

113+
def test_undigitised_unit_does_not_shift_later_units_in_group(self, manager):
114+
"""Regression test for a real bug: a unit with no digitised contact
115+
or orientation data (e.g. a "Top" unit nobody has mapped points for)
116+
must still contribute its own thickness to `val` for every unit
117+
that follows it in the group -- `update_foliation_features` used to
118+
`continue` past an undigitised unit before accumulating its
119+
thickness, which shifted every later unit's trained value relative
120+
to what `get_isovalues()` expects.
121+
"""
122+
column = StratigraphicColumn()
123+
column.clear(basement=False)
124+
column.add_unit(name='basin_floor', thickness=50.0, where='top')
125+
column.add_unit(name='basin_fill', thickness=150.0, where='top')
126+
column.add_unit(name='Top', thickness=999.0, where='top')
127+
128+
manager.stratigraphic_column = column
129+
for name in ('basin_floor', 'basin_fill'):
130+
manager.stratigraphy[name]['contact'] = _contact(name)
131+
# 'Top' deliberately has no entry in manager.stratigraphy at all.
132+
133+
manager.update_foliation_features()
134+
135+
training_values = self._training_values_by_unit(manager._captured_calls)
136+
expected_values = {name: entry['value'] for name, entry in column.get_isovalues().items()}
137+
138+
for unit_name in ('basin_floor', 'basin_fill'):
139+
assert training_values[unit_name] == pytest.approx(expected_values[unit_name]), (
140+
f"'{unit_name}' was trained with val={training_values[unit_name]} but "
141+
f"get_isovalues() expects value={expected_values[unit_name]} -- an "
142+
f"undigitised unit earlier in the group must still shift later units' "
143+
f"trained values by its own thickness."
144+
)
145+
95146
@staticmethod
96147
def _training_values_by_unit(captured_calls):
97148
combined = pd.concat(captured_calls, ignore_index=True)

0 commit comments

Comments
 (0)