Skip to content

[seismology] Add DepthLookup abstract interface#199

Open
comoglu wants to merge 9 commits into
SeisComP:mainfrom
comoglu:feature/defaultdepthsetter-interface
Open

[seismology] Add DepthLookup abstract interface#199
comoglu wants to merge 9 commits into
SeisComP:mainfrom
comoglu:feature/defaultdepthsetter-interface

Conversation

@comoglu

@comoglu comoglu commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces an abstract DepthLookup interface for region- and slab-based
depth lookup in SeisComP processing modules (primarily scautoloc).

Interface (libs/seiscomp/seismology/depthlookup.h):

class DepthLookup : public Core::BaseObject {
  virtual bool   init(const Config::Config &) = 0;
  virtual double fetch(double lat, double lon) const = 0;
  virtual double fetchMaxDepth(double lat, double lon) const = 0;
};
DEFINE_INTERFACE_FACTORY(DepthLookup);
#define REGISTER_DEPTH_LOOKUP(Class, Service) ...

Each backend owns all depth knowledge including fallback; no fallback
parameter on the caller side.

Two built-in backends (depthlookup.cpp):

  • "Constant" — returns depths.constant.value for any location
  • "Polygon" — queries GeoFeatureSet polygons (each must carry a
    defaultDepth attribute); fallback from depths.polygon.fallback;
    regions listed in depths.polygon.regions

A third "Slab2" backend ships in SeisComP/main as the dlslab2
plugin (see SeisComP/main#121).

Test plan

  • Build seiscomp_core — clean compile, no errors
  • Unit test: DepthLookupConstant returns depths.constant.value
  • Unit test: DepthLookupPolygon returns polygon depth inside region, fallback outside

@cla-bot cla-bot Bot added the cla-signed The CLA has been signed by all contributors label May 20, 2026
Comment thread libs/seiscomp/seismology/defaultdepthsetter.cpp Outdated
Comment thread libs/seiscomp/seismology/defaultdepthsetter.cpp Outdated
Comment thread libs/seiscomp/seismology/defaultdepthsetter.h Outdated
@gempa-jabe

Copy link
Copy Markdown
Contributor

I am not in favour of the name DefaultDepthSetter. This is very specific to the intended use-case but it should be a versatile interface to be used by other components as well. As you describe the interface as "depth-lookup" why not calling it DepthLookup or DepthQuery or DepthProvider?. I would appreciate if we could discuss such ideas and names prior to a PR to avoid discussions and additional work later on for you as well.

Comment thread libs/seiscomp/seismology/depthlookup.h Outdated
@comoglu

comoglu commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

I am not in favour of the name DefaultDepthSetter. This is very specific to the intended use-case but it should be a versatile interface to be used by other components as well. As you describe the interface as "depth-lookup" why not calling it DepthLookup or DepthQuery or DepthProvider?. I would appreciate if we could discuss such ideas and names prior to a PR to avoid discussions and additional work later on for you as well.

Yeah you are right. Which one you prefer me to go with?
DepthLookup looks good to me?

@gempa-jabe

Copy link
Copy Markdown
Contributor

Just thinking, if we already have a class doing a similar thing ... Regions comes into my mind, so not very helpful ;)
Yes, DepthLookup sounds reasonable. At least not Setter because it does not set anything, it just replies to a query.

@comoglu comoglu force-pushed the feature/defaultdepthsetter-interface branch from 051c31b to 744ed10 Compare May 21, 2026 10:15
@comoglu comoglu changed the title [seismology] Add DefaultDepthSetter abstract interface [seismology] Add DepthLookup abstract interface May 21, 2026
@comoglu comoglu requested a review from gempa-jabe May 21, 2026 22:13
@comoglu comoglu marked this pull request as ready for review June 4, 2026 09:45
comoglu added 3 commits June 4, 2026 22:55
Adds an extensible depth-lookup interface used by scautoloc and other
processing modules to select region- or slab-specific default and maximum
depths instead of a single global value.

Two built-in implementations: "Constant" (passthrough) and "Polygon"
(named GeoFeatureSet regions with defaultDepth/maxDepth attributes).
Third-party backends register via REGISTER_DEPTH_LOOKUP in a plugin.
Rename getDefaultDepth/getMaxDepth → fetch/fetchMaxDepth; remove
caller-supplied fallback parameter. Each backend now owns its full
depth knowledge including fallback via its own config namespace:
  depths.constant.value, depths.polygon.regions,
  depths.polygon.fallback.
Per Jan's review: fetch() and fetchMaxDepth() now throw std::out_of_range
when no region/zone contains the given location. Callers that need a
fallback value use the utility functions fetchDepth() and fetchMaxDepth()
which catch and return the fallback.

Removes the fallback ownership from DepthLookupPolygon — callers decide
what to do when outside all configured regions.
@comoglu comoglu force-pushed the feature/defaultdepthsetter-interface branch from a886635 to eff40d7 Compare June 4, 2026 12:56
@comoglu

comoglu commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Addressed Jan's review feedback:

  • Class name: renamed DefaultDepthSetterDepthLookup (done in previous commit)
  • Copyright / code style: old defaultdepthsetter.* files replaced entirely
  • Config namespace: backends use depths.<backend>.* (done in previous commit)
  • Throw-based API (main change in this update): fetch() and fetchMaxDepth() now throw std::out_of_range when no region/zone contains the given location. Two utility free functions fetchDepth() and fetchMaxDepth() are provided for callers that need a fallback value. DepthLookupPolygon no longer owns a fallback or reads depths.polygon.fallback.

@jsaul

jsaul commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Do we have the possibility to retrieve more than one depth? I am referring to the situation where there is deep seismicity along a subducting slab, but also shallow seismicity right above it. That separation can be significant and we need to be careful not to trust a potentially wrong depth.

@gempa-jabe

Copy link
Copy Markdown
Contributor

Thank you for the changes. What I still do not like very much are the utility functions. I proposed that initially to provide a fallback rather than incorporating it into the fetch methods. The better design worked out later was that the client (scautoloc) does not need to provide a default depth. This is currently required as the methods throw exceptions and someone needs to deal with that and provide a fallback. Where does that fallback come from? I prefer that knowledge to be completely encapsulated in the DepthLookup backend and not in the client.

@comoglu

comoglu commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Do we have the possibility to retrieve more than one depth? I am referring to the situation where there is deep seismicity along a subducting slab, but also shallow seismicity right above it. That separation can be significant and we need to be careful not to trust a potentially wrong depth.

Good point , the current interface only returns a single depth. Are you thinking the interface should return a list of candidate depths for scautoloc to try, or more that scautoloc itself should decide to try both a shallow and a slab-based seed independently?

@comoglu

comoglu commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the changes. What I still do not like very much are the utility functions. I proposed that initially to provide a fallback rather than incorporating it into the fetch methods. The better design worked out later was that the client (scautoloc) does not need to provide a default depth. This is currently required as the methods throw exceptions and someone needs to deal with that and provide a fallback. Where does that fallback come from? I prefer that knowledge to be completely encapsulated in the DepthLookup backend and not in the client.

Understood, fetch() should always return a value, with the fallback knowledge fully encapsulated in the backend. The client should never need to handle an exception or supply a depth. Will remove the utility functions and have each backend return its own configured fallback when no zone matches.

@gempa-jabe

gempa-jabe commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Why do I have the feeling chatting with Claude AI rather than yourself? ;)

@comoglu

comoglu commented Jun 4, 2026 via email

Copy link
Copy Markdown
Contributor Author

Reworked based on Jan's feedback: fetch() and fetchMaxDepth() always
return a value, fallback is configured inside the backend. Removed
the utility functions that required the client to supply a fallback.
@comoglu

comoglu commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the changes. What I still do not like very much are the utility functions. I proposed that initially to provide a fallback rather than incorporating it into the fetch methods. The better design worked out later was that the client (scautoloc) does not need to provide a default depth. This is currently required as the methods throw exceptions and someone needs to deal with that and provide a fallback. Where does that fallback come from? I prefer that knowledge to be completely encapsulated in the DepthLookup backend and not in the client.

Done, removed the utility functions and the fallback is back where it belongs, inside the backend. fetch() and fetchMaxDepth() always return a value now, the client doesn't know or care about fallbacks.

@comoglu

comoglu commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Sorry for the long silence on this; let me provide a quick status.

The last commit (243e39f) moves the fallback fully inside the backend, so fetch() and fetchMaxDepth() always return a value now. The client doesn't catch an exception or supply a default anymore, it doesn't know or care about fallbacks. That's the design we agreed on, so from my side this one is done.

On Joachim's multiple-depths point: right now the interface returns back one depth plus a maxDepth bound, and that maxDepth already keeps the client from over-trusting a deep slab. For the shallow-above-slab case I'd rather keep this interface single-depth and let scautoloc try a shallow seed and a slab seed on its own. If you'd sooner have it in the interface, I can add a fetchCandidates() that returns a short list as a follow-up. I just don't want to widen this PR; it's meant to be the minimal foundation.

@gempa-jabe When you get a chance, could you have another look? I think it's ready to go. What do you think? :)

Comment thread libs/seiscomp/seismology/depthlookup.cpp
Comment thread libs/seiscomp/seismology/depthlookup.cpp Outdated
@gempa-jabe

Copy link
Copy Markdown
Contributor

I am fine with the code, I left some comments. Furthermore we need a final decision on the multiple depths thing as this will have an impact on the interface which we cannot change within one major release.

@gempa-jabe gempa-jabe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my inline comments.

- Move DepthLookupConstant and DepthLookupPolygon into anonymous
  namespace so no concrete-class symbols are exported
- Add fetchCandidates() virtual method with default implementation
  returning {fetch(lat,lon)}, resolving Joachim's open question on
  multi-depth support without breaking existing backends
@comoglu

comoglu commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in c069340:

  • Anonymous namespace: moved both DepthLookupConstant and DepthLookupPolygon inside the anonymous namespace so no concrete-class symbols are exported.

  • fetchCandidates(): added as a virtual method with a default implementation of {fetch(lat, lon)}. For the current Constant and Polygon backends this is transparent — they get the default for free. A future backend covering dual-seismicity zones (shallow crustal + deep slab) can override it and return both depths. scautoloc can call fetchCandidates() and evaluate each seed independently using its existing tryDefaultDepth mechanism. This resolves Joachim's question without widening the scope of this PR further.

@comoglu comoglu requested a review from gempa-jabe July 7, 2026 08:55
DepthLookupConstant was returning _value (10 km default) for both
fetch() and fetchMaxDepth(). Any module using fetchMaxDepth() as a
depth-rejection threshold would silently discard all events deeper
than 10 km. Added a separate _maxDepth{1000.0} field configurable
via depths.constant.maxDepth.

Same issue in DepthLookupPolygon: fetchMaxDepth() fell back to
_fallback (10 km) for unmatched locations and for polygons without a
maxDepth attribute. Added _maxDepthFallback{1000.0} configurable via
depths.polygon.maxDepth.
@comoglu

comoglu commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Self-review before merge — caught a correctness bug (49e9ca5):

fetchMaxDepth returning defaultDepth in both backends

DepthLookupConstant was returning _value (10 km default) for both fetch() and fetchMaxDepth(). Any module using fetchMaxDepth() as a depth-rejection threshold (which is the intended use in scautoloc) would silently discard all events deeper than 10 km with default config. Added a separate _maxDepth{1000.0} field, configurable via depths.constant.maxDepth.

Same issue in DepthLookupPolygon: fetchMaxDepth() fell back to _fallback (10 km) both for unmatched locations and for polygons without a maxDepth attribute. Added _maxDepthFallback{1000.0} configurable via depths.polygon.maxDepth.

- DepthLookupPolygon docblock: add depths.polygon.maxDepth key
- depthlookup.h class docstring: document maxDepth config keys for
  both backends; remove reference to dlslab2 plugin (does not exist)

@gempa-jabe gempa-jabe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the fetchCandidates prototype is good enough for @jsaul is out of my scope. Please comment on that.

Comment thread libs/seiscomp/seismology/depthlookup.h Outdated
* contains the given location the backend returns its own
* configured fallback depth.
*/
virtual double fetch(double lat, double lon) const = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a value is always returned, we can add noexcept specifier to the interface to indicate that it always returns a value and never throws. If the methods would throw an exception then a fallback case must be provided at client-side.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect that fetch() throws if there is no default depth configured for the location and let the client deal with that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have already discussed that starting at that comment. So you do not have to deal with fallback configuration in, e.g. scautoloc or scolv. Ask for a value and get a value anytime you ask.

Comment thread libs/seiscomp/seismology/depthlookup.cpp
- Add noexcept to fetch(), fetchMaxDepth(), fetchCandidates() in the
  interface and all concrete overrides — makes the always-returns
  contract explicit at the type-system level

- DepthLookupPolygon now inherits GeoFeatureSetObserver: registers on
  init(), stores _names as a member, rebuilds _entries via
  _buildEntries() on geoFeatureSetUpdated(). GeoFeatureSetObserver
  destructor handles deregistration automatically. Fixes dangling
  pointer crash if the global GeoFeatureSet is reloaded (e.g. user
  reloads map layers in scolv).
@comoglu

comoglu commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in d0d433a:

noexcept — added to fetch(), fetchMaxDepth(), and fetchCandidates() in the interface and all concrete overrides. The contract "always returns a value, never throws" is now enforced at the type-system level.

GeoFeatureSetObserverDepthLookupPolygon now inherits Geo::GeoFeatureSetObserver. On init() it registers with the singleton and builds _entries via _buildEntries(). geoFeatureSetUpdated() clears and rebuilds _entries from the current set. The region name list is now stored as _names (member) so it is available for rebuilding. GeoFeatureSetObserver's own destructor handles deregistration automatically — no custom destructor needed.

@gempa-jabe

Copy link
Copy Markdown
Contributor

Awesome new world! 😉

@comoglu

comoglu commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

What am I doing now? Which direction? @jsaul ? Are we ok with the current state?

@gempa-jabe

Copy link
Copy Markdown
Contributor

Despite the open question on fetchCandidates I am fine with it.

@jsaul

jsaul commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

To me fetchCandidates looks fine. I would call it fetchCandidateDepth to be more clear but ultimately that is your decision.

In principle one might think of ranking the returned depths. If a locator shall try a relocation with each of the returned candidate depths, there may be cases where the results are similar in terms of RMS etc. Such an ambiguity might be resolved by preferring one depth over the other. This might reflect the fact that at the given location far more events occur at the subducting slab interface than in the crust above the slab.

Would it perhaps make sense to return vector<Depth> rather than vector<double>? In principle then Depth can be anything incl. mentioned ranking, but also additional attributes like depth slope could later be accommodated more easily. Just thinking loudly.

@comoglu

comoglu commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

OK I will rename it. The vector idea is also reasonable, particularly regarding the ranking point. But I'd rather keep that for a follow-up once we see how the candidates are actually used in e.g. scautoloc. vector would be straightforward to extend later without breaking existing callers. What do you think @gempa-jabe ??

@gempa-jabe

gempa-jabe commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Would it perhaps make sense to return vector<Depth> rather than vector<double>?

I see your point. But if the Depth type needs to be extended then we will break the binary interface and therefore the API and ABI. That would require a major release update. So better think now about what you actually need. The ranking could be represented by the order of the returned depth values.

@jsaul

jsaul commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Of course I would like to keep the interface as simple as possible. On the other hand we cannot always anticipate what is needed in the future. At least I cannot. Extending Depth seems like less pain than changing the return value from vector<double> to vector<Depth> though.

In the end this shall not only be used in scautoloc but wherever default depths are needed and I don't know if vector<double> to vector<Depth> would be the better fit.

@jsaul

jsaul commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

That said for the current problem to be solved, vector<double> is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The CLA has been signed by all contributors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants