ertkit 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {ertkit-0.1.0 → ertkit-0.1.1}/.pre-commit-config.yaml +4 -4
  2. {ertkit-0.1.0 → ertkit-0.1.1}/CHANGELOG.md +24 -1
  3. {ertkit-0.1.0 → ertkit-0.1.1}/CITATION.cff +10 -2
  4. {ertkit-0.1.0 → ertkit-0.1.1}/PKG-INFO +11 -5
  5. {ertkit-0.1.0 → ertkit-0.1.1}/README.md +9 -4
  6. ertkit-0.1.1/docs/citing.md +17 -0
  7. {ertkit-0.1.0 → ertkit-0.1.1}/docs/user_guide/ves.md +14 -2
  8. {ertkit-0.1.0 → ertkit-0.1.1}/pyproject.toml +1 -0
  9. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/_version.py +2 -2
  10. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/ves/data.py +17 -5
  11. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/ves/smooth.py +19 -8
  12. {ertkit-0.1.0 → ertkit-0.1.1}/tests/ves/test_data.py +26 -0
  13. {ertkit-0.1.0 → ertkit-0.1.1}/tests/ves/test_invert_smooth.py +21 -1
  14. ertkit-0.1.0/docs/citing.md +0 -9
  15. {ertkit-0.1.0 → ertkit-0.1.1}/.git_archival.txt +0 -0
  16. {ertkit-0.1.0 → ertkit-0.1.1}/.gitattributes +0 -0
  17. {ertkit-0.1.0 → ertkit-0.1.1}/.gitignore +0 -0
  18. {ertkit-0.1.0 → ertkit-0.1.1}/.mailmap +0 -0
  19. {ertkit-0.1.0 → ertkit-0.1.1}/.zenodo.json +0 -0
  20. {ertkit-0.1.0 → ertkit-0.1.1}/CODE_OF_CONDUCT.md +0 -0
  21. {ertkit-0.1.0 → ertkit-0.1.1}/CONTRIBUTING.md +0 -0
  22. {ertkit-0.1.0 → ertkit-0.1.1}/LICENSE +0 -0
  23. {ertkit-0.1.0 → ertkit-0.1.1}/ci/crosscheck.txt +0 -0
  24. {ertkit-0.1.0 → ertkit-0.1.1}/docs/_ext/verification_report.py +0 -0
  25. {ertkit-0.1.0 → ertkit-0.1.1}/docs/_templates/autosummary/class.rst +0 -0
  26. {ertkit-0.1.0 → ertkit-0.1.1}/docs/api.md +0 -0
  27. {ertkit-0.1.0 → ertkit-0.1.1}/docs/changelog.md +0 -0
  28. {ertkit-0.1.0 → ertkit-0.1.1}/docs/conf.py +0 -0
  29. {ertkit-0.1.0 → ertkit-0.1.1}/docs/contributing.md +0 -0
  30. {ertkit-0.1.0 → ertkit-0.1.1}/docs/dev/design.md +0 -0
  31. {ertkit-0.1.0 → ertkit-0.1.1}/docs/dev/index.md +0 -0
  32. {ertkit-0.1.0 → ertkit-0.1.1}/docs/index.md +0 -0
  33. {ertkit-0.1.0 → ertkit-0.1.1}/docs/install.md +0 -0
  34. {ertkit-0.1.0 → ertkit-0.1.1}/docs/roadmap.md +0 -0
  35. {ertkit-0.1.0 → ertkit-0.1.1}/docs/verification.md +0 -0
  36. {ertkit-0.1.0 → ertkit-0.1.1}/examples/README.rst +0 -0
  37. {ertkit-0.1.0 → ertkit-0.1.1}/examples/plot_01_few_layer_inversion.py +0 -0
  38. {ertkit-0.1.0 → ertkit-0.1.1}/examples/plot_02_smooth_inversion.py +0 -0
  39. {ertkit-0.1.0 → ertkit-0.1.1}/examples/plot_03_equivalence.py +0 -0
  40. {ertkit-0.1.0 → ertkit-0.1.1}/examples/plot_04_static_shift.py +0 -0
  41. {ertkit-0.1.0 → ertkit-0.1.1}/examples/plot_05_read_csv.py +0 -0
  42. {ertkit-0.1.0 → ertkit-0.1.1}/examples/plot_06_check_installation.py +0 -0
  43. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/__init__.py +0 -0
  44. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/_linalg.py +0 -0
  45. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/_show_versions.py +0 -0
  46. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/_warnings.py +0 -0
  47. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/arrays.py +0 -0
  48. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/noise.py +0 -0
  49. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/plot/__init__.py +0 -0
  50. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/plot/_ves.py +0 -0
  51. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/py.typed +0 -0
  52. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/results.py +0 -0
  53. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/ves/__init__.py +0 -0
  54. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/ves/forward.py +0 -0
  55. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/ves/invert.py +0 -0
  56. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/ves/io.py +0 -0
  57. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/ves/model.py +0 -0
  58. {ertkit-0.1.0 → ertkit-0.1.1}/src/ertkit/ves/survey.py +0 -0
  59. {ertkit-0.1.0 → ertkit-0.1.1}/tests/analytic_solutions.py +0 -0
  60. {ertkit-0.1.0 → ertkit-0.1.1}/tests/conftest.py +0 -0
  61. {ertkit-0.1.0 → ertkit-0.1.1}/tests/crosscheck/test_crosscheck_ves.py +0 -0
  62. {ertkit-0.1.0 → ertkit-0.1.1}/tests/test_arrays.py +0 -0
  63. {ertkit-0.1.0 → ertkit-0.1.1}/tests/test_linalg.py +0 -0
  64. {ertkit-0.1.0 → ertkit-0.1.1}/tests/test_noise.py +0 -0
  65. {ertkit-0.1.0 → ertkit-0.1.1}/tests/test_package.py +0 -0
  66. {ertkit-0.1.0 → ertkit-0.1.1}/tests/test_plot.py +0 -0
  67. {ertkit-0.1.0 → ertkit-0.1.1}/tests/verification_report.py +0 -0
  68. {ertkit-0.1.0 → ertkit-0.1.1}/tests/ves/test_forward.py +0 -0
  69. {ertkit-0.1.0 → ertkit-0.1.1}/tests/ves/test_invert_few_layer.py +0 -0
  70. {ertkit-0.1.0 → ertkit-0.1.1}/tests/ves/test_model_survey.py +0 -0
  71. {ertkit-0.1.0 → ertkit-0.1.1}/tests/ves/test_review_regressions.py +0 -0
  72. {ertkit-0.1.0 → ertkit-0.1.1}/uv.lock +0 -0
@@ -21,19 +21,19 @@ repos:
21
21
  - id: trailing-whitespace
22
22
 
23
23
  - repo: https://github.com/astral-sh/ruff-pre-commit
24
- rev: v0.17.0
24
+ rev: v0.17.4
25
25
  hooks:
26
26
  - id: ruff-check
27
27
  args: [--fix, --show-fixes, --exit-non-zero-on-fix]
28
28
  - id: ruff-format
29
29
 
30
30
  - repo: https://github.com/abravalheri/validate-pyproject
31
- rev: v0.25
31
+ rev: v0.27
32
32
  hooks:
33
33
  - id: validate-pyproject
34
34
 
35
35
  - repo: https://github.com/python-jsonschema/check-jsonschema
36
- rev: 0.38.2
36
+ rev: 0.39.0
37
37
  hooks:
38
38
  - id: check-github-workflows
39
39
  - id: check-dependabot
@@ -52,6 +52,6 @@ repos:
52
52
  - id: rst-inline-touching-normal
53
53
 
54
54
  - repo: https://github.com/zizmorcore/zizmor-pre-commit
55
- rev: v1.30.1
55
+ rev: v1.32.0
56
56
  hooks:
57
57
  - id: zizmor
@@ -6,6 +6,28 @@ project uses [semantic versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.1] - 2026-11-09
10
+
11
+ ### Added
12
+
13
+ - The Zenodo DOI in `CITATION.cff`, the README and the documentation, and a
14
+ `Citation` link in the package metadata.
15
+
16
+ ### Fixed
17
+
18
+ - Synthetic soundings screen noise-free readings before adding log-space
19
+ noise, preventing signals below the noise floor from becoming enormous
20
+ accepted readings or overflowing. Noisy readings are screened again.
21
+ - Smooth inversion preserves the stopping reason after relaxing the misfit
22
+ target, rather than claiming the target was matched when the smoothest
23
+ model already fits better or the relaxed target remains unreachable.
24
+ - Smooth inversion requires at least three layers to span both endpoints of
25
+ `depth_range`. Two-layer fitting remains available with `invert_few_layer`.
26
+
27
+ ### Changed
28
+
29
+ - Updated pre-commit hook versions as part of the release checklist.
30
+
9
31
  ## [0.1.0] - 2026-10-15
10
32
 
11
33
  ### Added
@@ -80,5 +102,6 @@ project uses [semantic versioning](https://semver.org/).
80
102
  recorded in a verification report (`pytest -m verification
81
103
  --verification-report=PATH`).
82
104
 
83
- [Unreleased]: https://github.com/abdgafartunde/ertkit/compare/v0.1.0...HEAD
105
+ [Unreleased]: https://github.com/abdgafartunde/ertkit/compare/v0.1.1...HEAD
106
+ [0.1.1]: https://github.com/abdgafartunde/ertkit/compare/v0.1.0...v0.1.1
84
107
  [0.1.0]: https://github.com/abdgafartunde/ertkit/releases/tag/v0.1.0
@@ -7,8 +7,16 @@ authors:
7
7
  given-names: "Abd'gafar Tunde"
8
8
  orcid: "https://orcid.org/0000-0003-1641-7196"
9
9
  affiliation: "School of Mathematics, Jilin University"
10
- version: "0.1.0"
11
- date-released: "2026-10-15"
10
+ version: "0.1.1"
11
+ date-released: "2026-11-09"
12
+ doi: 10.5281/zenodo.23385097
13
+ identifiers:
14
+ - type: doi
15
+ value: 10.5281/zenodo.23385097
16
+ description: "Concept DOI: all versions, resolving to the latest"
17
+ - type: doi
18
+ value: 10.5281/zenodo.23385098
19
+ description: "Version 0.1.0"
12
20
  license: MIT
13
21
  repository-code: "https://github.com/abdgafartunde/ertkit"
14
22
  url: "https://abdgafartunde.github.io/ertkit/"
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ertkit
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Resistivity inversion for groundwater exploration (VES now, 2.5-D ERT planned), with uncertainty and depth of investigation.
5
5
  Project-URL: Documentation, https://abdgafartunde.github.io/ertkit/
6
6
  Project-URL: Source, https://github.com/abdgafartunde/ertkit
7
7
  Project-URL: Issues, https://github.com/abdgafartunde/ertkit/issues
8
8
  Project-URL: Changelog, https://github.com/abdgafartunde/ertkit/blob/main/CHANGELOG.md
9
+ Project-URL: Citation, https://doi.org/10.5281/zenodo.23385097
9
10
  Author: Abd'gafar Tunde Tiamiyu
10
11
  License-Expression: MIT
11
12
  License-File: LICENSE
@@ -37,6 +38,7 @@ Description-Content-Type: text/markdown
37
38
  [![CI](https://github.com/abdgafartunde/ertkit/actions/workflows/ci.yml/badge.svg)](https://github.com/abdgafartunde/ertkit/actions/workflows/ci.yml)
38
39
  [![Docs](https://github.com/abdgafartunde/ertkit/actions/workflows/docs.yml/badge.svg)](https://abdgafartunde.github.io/ertkit/)
39
40
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/abdgafartunde/ertkit/blob/main/LICENSE)
41
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.23385097.svg)](https://doi.org/10.5281/zenodo.23385097)
40
42
 
41
43
  **Resistivity imaging for groundwater exploration, with an honest account of
42
44
  what the data can resolve.**
@@ -107,10 +109,12 @@ deviation).
107
109
 
108
110
  ## Status
109
111
 
110
- Release 0.1 covers vertical electrical sounding: a layered-earth forward
112
+ Release 0.1.1 covers vertical electrical sounding: a layered-earth forward
111
113
  model, few-layer inversion with linearized uncertainty, smooth inversion with
112
- sensitivity and a depth of investigation, CSV input and plotting. Planned
113
- releases:
114
+ sensitivity and a depth of investigation, CSV input and plotting. This patch
115
+ screens weak synthetic signals before adding noise, corrects smooth-inversion
116
+ stopping summaries, and requires at least three layers for a smooth grid.
117
+ Two-layer fitting remains available through `invert_few_layer`. Planned releases:
114
118
 
115
119
  | Release | Content |
116
120
  |---|---|
@@ -147,7 +151,9 @@ and the [Code of Conduct](https://github.com/abdgafartunde/ertkit/blob/main/CODE
147
151
 
148
152
  ## Citing
149
153
 
150
- Each release is archived on Zenodo with a DOI. Citation metadata are in
154
+ Each release is archived on Zenodo with a DOI; to cite ertkit in general, use
155
+ the concept DOI [10.5281/zenodo.23385097](https://doi.org/10.5281/zenodo.23385097),
156
+ which always resolves to the latest version. Citation metadata are in
151
157
  [CITATION.cff](https://github.com/abdgafartunde/ertkit/blob/main/CITATION.cff);
152
158
  please also cite the methods ertkit implements.
153
159
 
@@ -4,6 +4,7 @@
4
4
  [![CI](https://github.com/abdgafartunde/ertkit/actions/workflows/ci.yml/badge.svg)](https://github.com/abdgafartunde/ertkit/actions/workflows/ci.yml)
5
5
  [![Docs](https://github.com/abdgafartunde/ertkit/actions/workflows/docs.yml/badge.svg)](https://abdgafartunde.github.io/ertkit/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/abdgafartunde/ertkit/blob/main/LICENSE)
7
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.23385097.svg)](https://doi.org/10.5281/zenodo.23385097)
7
8
 
8
9
  **Resistivity imaging for groundwater exploration, with an honest account of
9
10
  what the data can resolve.**
@@ -74,10 +75,12 @@ deviation).
74
75
 
75
76
  ## Status
76
77
 
77
- Release 0.1 covers vertical electrical sounding: a layered-earth forward
78
+ Release 0.1.1 covers vertical electrical sounding: a layered-earth forward
78
79
  model, few-layer inversion with linearized uncertainty, smooth inversion with
79
- sensitivity and a depth of investigation, CSV input and plotting. Planned
80
- releases:
80
+ sensitivity and a depth of investigation, CSV input and plotting. This patch
81
+ screens weak synthetic signals before adding noise, corrects smooth-inversion
82
+ stopping summaries, and requires at least three layers for a smooth grid.
83
+ Two-layer fitting remains available through `invert_few_layer`. Planned releases:
81
84
 
82
85
  | Release | Content |
83
86
  |---|---|
@@ -114,7 +117,9 @@ and the [Code of Conduct](https://github.com/abdgafartunde/ertkit/blob/main/CODE
114
117
 
115
118
  ## Citing
116
119
 
117
- Each release is archived on Zenodo with a DOI. Citation metadata are in
120
+ Each release is archived on Zenodo with a DOI; to cite ertkit in general, use
121
+ the concept DOI [10.5281/zenodo.23385097](https://doi.org/10.5281/zenodo.23385097),
122
+ which always resolves to the latest version. Citation metadata are in
118
123
  [CITATION.cff](https://github.com/abdgafartunde/ertkit/blob/main/CITATION.cff);
119
124
  please also cite the methods ertkit implements.
120
125
 
@@ -0,0 +1,17 @@
1
+ # Citing ertkit
2
+
3
+ Each release of ertkit is archived on Zenodo, which issues a DOI for every
4
+ version and a concept DOI that always resolves to the latest version:
5
+
6
+ - all versions (concept DOI):
7
+ [10.5281/zenodo.23385097](https://doi.org/10.5281/zenodo.23385097)
8
+ - version 0.1.0:
9
+ [10.5281/zenodo.23385098](https://doi.org/10.5281/zenodo.23385098)
10
+
11
+ To make results reproducible, cite the version you used. The citation
12
+ metadata are in
13
+ [CITATION.cff](https://github.com/abdgafartunde/ertkit/blob/main/CITATION.cff),
14
+ from which GitHub offers APA and BibTeX entries ("Cite this repository").
15
+
16
+ Please also cite the methods ertkit implements; the references are listed in
17
+ the [user guide](user_guide/ves.md#references).
@@ -87,6 +87,10 @@ $$
87
87
 
88
88
  A {class}`~ertkit.ves.Sounding` removes readings that cannot be inverted in
89
89
  log space ($R \le 0$, or $|R|$ below three times $a$) and warns about them.
90
+ {meth}`~ertkit.ves.Sounding.synthetic` screens the noise-free readings before
91
+ drawing noise, then screens the noisy readings again. A weak signal cannot
92
+ become an accepted reading just because a large log-space noise draw amplified
93
+ it. If no reading is usable, it raises `ValueError`.
90
94
 
91
95
  ## Few-layer inversion
92
96
 
@@ -162,8 +166,16 @@ Constable et al. 1987): the regularization weight $\mu$ is the largest with
162
166
  $\chi^2 = N_d$. If even the smoothest model fits better than that, ertkit
163
167
  reports it and warns that the errors may be overestimated. If no model reaches
164
168
  $\chi^2 = N_d$, which happens for a few percent of soundings even with correct
165
- errors, the inversion fits 1.1 times the smallest misfit instead (recorded in
166
- `chi2_target`), and warns only when that misfit is improbably large.
169
+ errors, the inversion targets 1.1 times the smallest misfit instead (recorded
170
+ in `chi2_target`), and warns about a poor fit only when the smallest misfit is
171
+ improbably large. The `stop_reason` distinguishes reaching the relaxed target
172
+ from finding that the smoothest model already fits better, or that the target
173
+ remains unreachable.
174
+
175
+ The smooth grid requires at least three layers, including the bottom
176
+ half-space, so that its first and last interfaces span `depth_range`. For a
177
+ two-layer fit with an estimated interface depth, use
178
+ {func}`~ertkit.ves.invert_few_layer` with `n_layers=2`.
167
179
 
168
180
  The result, a {class}`~ertkit.ves.SmoothFit`, includes:
169
181
 
@@ -51,6 +51,7 @@ Documentation = "https://abdgafartunde.github.io/ertkit/"
51
51
  Source = "https://github.com/abdgafartunde/ertkit"
52
52
  Issues = "https://github.com/abdgafartunde/ertkit/issues"
53
53
  Changelog = "https://github.com/abdgafartunde/ertkit/blob/main/CHANGELOG.md"
54
+ Citation = "https://doi.org/10.5281/zenodo.23385097"
54
55
 
55
56
  [dependency-groups]
56
57
  test = [
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.0'
22
- __version_tuple__ = version_tuple = (0, 1, 0)
21
+ __version__ = version = '0.1.1'
22
+ __version_tuple__ = version_tuple = (0, 1, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -121,7 +121,9 @@ class Sounding:
121
121
  Noise is drawn from the error model in log space:
122
122
  :math:`\ln \rho_a^{\mathrm{obs}} = \ln \rho_a + s \, \varepsilon`, with
123
123
  :math:`\varepsilon \sim N(0, 1)` and :math:`s` evaluated at the
124
- noise-free data.
124
+ noise-free data. Unusable noise-free readings are removed before
125
+ drawing noise, so a signal below the noise floor cannot be amplified
126
+ into a usable reading. Noisy readings are screened again afterwards.
125
127
 
126
128
  Parameters
127
129
  ----------
@@ -138,13 +140,23 @@ class Sounding:
138
140
  Returns
139
141
  -------
140
142
  Sounding
143
+
144
+ Raises
145
+ ------
146
+ ValueError
147
+ If no noise-free or noisy reading is usable.
148
+
149
+ Warns
150
+ -----
151
+ DataWarning
152
+ If noise-free or noisy readings are removed.
141
153
  """
142
154
  error = ErrorModel() if error is None else error
143
155
  generator = np.random.default_rng(rng)
144
- clean = forward(model, survey)
145
- s = error.log_std(clean / survey.geometric_factor)
146
- noisy = clean * np.exp(s * generator.standard_normal(clean.size))
147
- return cls(survey=survey, rhoa=noisy, error=error)
156
+ clean = cls(survey=survey, rhoa=forward(model, survey), error=error)
157
+ s = error.log_std(clean.resistance)
158
+ noisy = clean.rhoa * np.exp(s * generator.standard_normal(clean.n_data))
159
+ return cls(survey=clean.survey, rhoa=noisy, error=error)
148
160
 
149
161
  @property
150
162
  def n_data(self) -> int:
@@ -385,7 +385,9 @@ def invert_smooth(
385
385
  data : Sounding
386
386
  The data and their error model.
387
387
  n_layers : int, optional
388
- Number of layers, including the bottom half-space. Default 30.
388
+ Number of layers, including the bottom half-space. At least 3, so
389
+ that distinct first and last interfaces span ``depth_range``.
390
+ Default 30. Use :func:`~ertkit.ves.invert_few_layer` for a two-layer fit.
389
391
  depth_range : (float, float), optional
390
392
  Depths in meters of the first and last layer interfaces, which are
391
393
  spaced logarithmically. Default: 0.3 times the smallest and 3 times
@@ -405,7 +407,7 @@ def invert_smooth(
405
407
  TypeError
406
408
  If ``data`` is not a :class:`Sounding`.
407
409
  ValueError
408
- If ``n_layers`` is less than 2, ``max_iter`` is less than 1, or
410
+ If ``n_layers`` is less than 3, ``max_iter`` is less than 1, or
409
411
  ``depth_range`` is invalid.
410
412
 
411
413
  Warns
@@ -424,9 +426,11 @@ def invert_smooth(
424
426
  -----
425
427
  The method is Occam's inversion (Constable et al. 1987). If no model
426
428
  reaches :math:`\chi^2 = N_d` (which happens for a few percent of soundings
427
- even with correct errors), the inversion fits 1.1 times the smallest
428
- misfit instead, recorded in ``chi2_target`` and ``stop_reason``, and warns
429
- only if that misfit is improbably large.
429
+ even with correct errors), the inversion targets 1.1 times the smallest
430
+ misfit instead, recorded in ``chi2_target``. The ``stop_reason`` says
431
+ whether this relaxed target was reached, the smoothest model already fits
432
+ better, or the target remains unreachable. A poor-fit warning is issued
433
+ only if the smallest misfit is improbably large.
430
434
 
431
435
  The depth of investigation follows Oldenburg and Li (1999), with reference
432
436
  models of 0.1 and 10 times the geometric mean apparent resistivity (a
@@ -448,8 +452,11 @@ def invert_smooth(
448
452
  raise TypeError(f"data must be a Sounding, got {type(data_obj).__name__}")
449
453
  n_layers = operator.index(n_layers)
450
454
  max_iter = operator.index(max_iter)
451
- if n_layers < 2:
452
- raise ValueError("a smooth model needs at least 2 layers")
455
+ if n_layers < 3:
456
+ raise ValueError(
457
+ "a smooth model needs at least 3 layers to span depth_range; "
458
+ "use invert_few_layer for a two-layer fit"
459
+ )
453
460
  if max_iter < 1:
454
461
  raise ValueError("max_iter must be at least 1")
455
462
  thickness = _layer_thicknesses(data, n_layers, depth_range)
@@ -466,7 +473,11 @@ def invert_smooth(
466
473
  start, max_iter, target=target
467
474
  )
468
475
  n_iter += refit_iter
469
- reason = _TARGET_RELAXED if converged else refit_reason
476
+ reason = (
477
+ _TARGET_RELAXED
478
+ if converged and refit_reason == _TARGET_REACHED
479
+ else f"chi2 = N_d cannot be reached; relaxed target: {refit_reason}"
480
+ )
470
481
  if stats.chi2.sf(chi2_min, data.n_data) < _POOR_FIT_P_VALUE:
471
482
  n_segments = int(_segments(data.survey.mn2).max()) + 1
472
483
  warnings.warn(
@@ -26,6 +26,32 @@ class TestSounding:
26
26
  b = Sounding.synthetic(MODEL, SURVEY, rng=np.random.default_rng(7))
27
27
  np.testing.assert_array_equal(a.rhoa, b.rhoa)
28
28
 
29
+ def test_synthetic_screens_weak_signals_before_drawing_noise(self) -> None:
30
+ # Long spacings have negligible transfer resistance. Log-space noise
31
+ # must not amplify them into apparently usable, enormous readings.
32
+ model = LayeredModel(resistivity=[100.0], thickness=[])
33
+ survey = Survey.schlumberger(ab2=np.geomspace(2.5, 1e6, 30), mn2=0.5)
34
+ error = ErrorModel(relative=0.03, absolute=1e-4)
35
+ clean = forward(model, survey)
36
+ usable = error.usable(clean / survey.geometric_factor)
37
+ assert usable.any()
38
+ assert not usable.all()
39
+ with pytest.warns(DataWarning, match="removed"):
40
+ data = Sounding.synthetic(model, survey, error, rng=0)
41
+ assert np.isin(data.survey.ab2, survey.ab2[usable]).all()
42
+ assert np.all(np.isfinite(data.rhoa))
43
+ # A loose bound for this fixed noise draw around 100 ohm m; the
44
+ # regression produced enormous outliers (and eventually overflow).
45
+ assert data.rhoa.max() < 1000.0
46
+
47
+ def test_synthetic_rejects_a_survey_entirely_below_the_noise_floor(self) -> None:
48
+ model = LayeredModel(resistivity=[100.0], thickness=[])
49
+ survey = Survey.schlumberger(ab2=[1e4, 1e5], mn2=0.5)
50
+ with pytest.raises(ValueError, match="no reading is usable"):
51
+ Sounding.synthetic(
52
+ model, survey, ErrorModel(relative=0.03, absolute=1e-4), rng=0
53
+ )
54
+
29
55
  def test_synthetic_noise_has_the_stated_distribution(self) -> None:
30
56
  # 2000 soundings of 22 readings: the standardized log residuals should
31
57
  # have mean 0 and standard deviation 1. With 44,000 draws the standard
@@ -267,6 +267,23 @@ def test_unreachable_target_with_correct_errors_is_not_a_poor_fit() -> None:
267
267
  assert np.isfinite(result.doi)
268
268
 
269
269
 
270
+ def test_relaxed_target_preserves_the_smoothest_model_stop_reason() -> None:
271
+ # This coarse grid cannot fit the three-layer earth. After relaxation,
272
+ # even the smoothest model lies below the new target; it has converged,
273
+ # but must not claim to have matched that target.
274
+ survey = Survey.schlumberger(ab2=np.geomspace(2.5, 200.0, 22), mn2=0.5)
275
+ data = Sounding.synthetic(README_MODEL, survey, NOISE, rng=0)
276
+ with pytest.warns(DataWarning, match="poor fit"):
277
+ result = invert_smooth(data, n_layers=3, depth_of_investigation=False)
278
+ assert result.converged
279
+ assert result.chi2 < 0.99 * result.chi2_target
280
+ assert "relaxed target" in result.stop_reason
281
+ assert (
282
+ "the smoothest model fits better than the target misfit" in result.stop_reason
283
+ )
284
+ assert result.stop_reason != _TARGET_RELAXED
285
+
286
+
270
287
  @pytest.mark.filterwarnings("ignore:poor fit:ertkit.DataWarning")
271
288
  def test_extreme_data_do_not_overflow() -> None:
272
289
  # A contrast of 1e4 with 20% noise reported as 0.5%, on 80 layers: before
@@ -305,7 +322,10 @@ def test_too_few_iterations_warns() -> None:
305
322
  @pytest.mark.parametrize(
306
323
  ("kwargs", "match"),
307
324
  [
308
- ({"n_layers": 1}, "at least 2 layers"),
325
+ ({"n_layers": 1}, "at least 3 layers"),
326
+ ({"n_layers": 2}, "at least 3 layers"),
327
+ ({"n_layers": 2, "depth_range": (1.0, 100.0)}, "at least 3 layers"),
328
+ ({"n_layers": 2, "depth_range": (1.0, 1000.0)}, "at least 3 layers"),
309
329
  ({"max_iter": 0}, "max_iter must be at least 1"),
310
330
  ({"depth_range": (5.0, 1.0)}, "0 < top < bottom"),
311
331
  ({"depth_range": (0.0, 10.0)}, "0 < top < bottom"),
@@ -1,9 +0,0 @@
1
- # Citing ertkit
2
-
3
- Each release of ertkit is archived on Zenodo, which issues a DOI for every
4
- version and a concept DOI that always resolves to the latest version. The
5
- citation metadata are in
6
- [CITATION.cff](https://github.com/abdgafartunde/ertkit/blob/main/CITATION.cff).
7
-
8
- Please also cite the methods ertkit implements; the references are listed in
9
- the [user guide](user_guide/ves.md#references).
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes