diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..056a9d0 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,122 @@ +name: Docs + +on: + push: + branches: ["main", "dev"] + workflow_call: + inputs: + ref: + required: false + type: string + workflow_dispatch: + inputs: + ref: + description: "Optional branch, tag, or SHA to build" + required: false + type: string + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build-docs: + runs-on: ubuntu-latest + # A newer push to the same branch supersedes a build that is still running. Builds of + # different refs never cancel each other, so a dev push cannot cancel a main or tag build + concurrency: + group: docs-${{ inputs.ref || github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + # Only override the ref when one is supplied by workflow_call or + # workflow_dispatch; leaving it empty lets actions/checkout resolve the event. + ref: ${{ inputs.ref }} + fetch-depth: 0 + fetch-tags: true + + - name: Fetch tags for setuptools_scm + run: | + git fetch --force --tags + git describe --tags --long --always + + - name: Setup Python + uses: actions/setup-python@v6 + with: + # Sphinx 9.1 requires Python 3.12; pytest.yml tests the package on 3.9 to 3.12 + python-version: "3.12" + + - name: Install system dependencies for the PDF build + run: | + sudo apt-get update + sudo apt-get install -y \ + latexmk \ + texlive-luatex \ + texlive-latex-extra \ + texlive-fonts-recommended \ + texlive-pictures \ + fonts-freefont-otf + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -e .[docs] + + - name: Verify release version + if: >- + startsWith(github.ref, 'refs/tags/v') || + startsWith(inputs.ref, 'refs/tags/v') || + startsWith(inputs.ref, 'v') + shell: bash + env: + RELEASE_REF: ${{ inputs.ref || github.ref }} + run: | + tag="${RELEASE_REF##*/}" + expected="${tag#v}" + actual="$(python -c "from importlib.metadata import version; print(version('pynetdesign').split('+', 1)[0])")" + if [[ "$actual" != "$expected" ]]; then + echo "Expected release version $expected from tag $tag, got $actual" + git describe --tags --long --always + exit 1 + fi + + - name: Build HTML and PDF docs + env: + MPLBACKEND: Agg + # Invoked through bash so the build does not depend on the executable bit + run: bash build-docs.sh --pdf + + - name: Upload PDF artifact + uses: actions/upload-artifact@v7 + with: + name: pynetdesign-docs-pdf + path: docs/build/latex/pynetdesign.pdf + + - name: Setup Pages + uses: actions/configure-pages@v6 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v5 + with: + path: docs/build/html + + deploy-pages: + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') + needs: build-docs + runs-on: ubuntu-latest + # Deployments run one at a time, and one that has started always completes + concurrency: + group: pages + cancel-in-progress: false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/push-tag.yml b/.github/workflows/push-tag.yml new file mode 100644 index 0000000..9e2f61c --- /dev/null +++ b/.github/workflows/push-tag.yml @@ -0,0 +1,29 @@ +name: Push Tag + +on: + push: + tags: + - "v*" + +permissions: + contents: read + pages: write + id-token: write + +jobs: + validate-tag: + uses: ./.github/workflows/validate-tag.yml + with: + tag: ${{ github.ref_name }} + permissions: + contents: read + + docs: + needs: validate-tag + uses: ./.github/workflows/docs.yml + with: + ref: ${{ needs.validate-tag.outputs.ref }} + permissions: + contents: read + pages: write + id-token: write diff --git a/.gitignore b/.gitignore index 6e38af4..428986c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ pytest-results.xml # Documentation # docs/build docs/source/api/generated +docs/source/changelog.rst docs/source/gallery docs/source/examples docs/source/tutorials diff --git a/.zenodo.json b/.zenodo.json index 3952c93..46bbc44 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,9 +1,9 @@ { "title": "PyNetDesign: a Python framework for microseismic monitoring network design", - "version": "1.0.1", + "version": "1.1.0", "license": "MIT", "upload_type": "software", - "description": "PyNetDesign is an open-source Python library that provides a simple and functional framework for designing and testing microseismic monitoring networks. Version 1.0.1 is a bugfix release. It corrects the seismic moment calculation, which underestimated the moment by a factor of 2*pi*f because the amplitude-to-displacement spectral conversion was applied twice. Minimum detectable moment magnitudes reported by version 1.0.0 were too low by roughly 1.0 to 1.9 magnitude units, by an amount that varies with the source-receiver distance, so results produced with version 1.0.0 should be recomputed. The release also restores Python 3.9 compatibility of the visualization module, removes a non-functional location uncertainty routine that referenced functions absent from the package, and adds a test module covering the magnitude relations.", + "description": "PyNetDesign is an open-source Python library that provides a simple and functional framework for designing and testing microseismic monitoring networks. Version 1.1.0 adds phase-resolved wave modes, so that SV and SH are treated separately with their own root-mean-square radiation patterns; receiver projection computed from the arriving polarization vectors, which is correct for inclined and curved cables and for single vertical-component stations; a free surface correction applied per receiver rather than to the whole network; synthetic geometry builders for borehole, surface and dark-fibre layouts, with writers to save and decimate them; a documentation site in HTML and PDF with a methodology chapter and an example gallery; and branding. S-wave results differ from 1.0.x because the S branch now uses the phase-resolved radiation patterns instead of the combined value.", "keywords": [ "earthquakes", "microseismicity", diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e82d54..47148e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,71 @@ All notable changes to PyNetDesign will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [v1.1.0] - 2026-10-05 + +Feature release: phase-resolved wave modes, per-receiver free surface and receiver +projection, synthetic geometry builders, a documentation site in HTML and PDF, and +branding. + +### Added + +- phase-resolved wave modes: `wave_mode` accepts `'P'`, `'SV'`, `'SH'`, `'S'` and `'PS'`, where `'S'` is a composite mode keeping the more detectable of the SV and SH branches +- root-mean-square radiation-pattern magnitudes per phase, `R_P = sqrt(4/15) ~ 0.52`, `R_SV = sqrt(7/30) ~ 0.48` and `R_SH = sqrt(1/6) ~ 0.41`, averaged over the focal sphere of a double-couple source after Boore and Boatwright (1984) and Hallo and Eisner (2013), exposed through `phase_radiation_pattern` +- `rad_pattern_p`, `rad_pattern_s` and `rad_patterns` overrides on `mag_sensitivity_grid`, so that results computed with another convention stay reproducible; pass `rad_pattern_s=0.63` to use the combined S-wave value +- receiver projection from the arriving polarization vectors, through `get_phase_station_directionality`, which is correct for inclined and curved cables rather than only for a vertical one +- projection of single vertical-component stations onto the vertical axis, driven by a `Components` column; three-component stations record the full vector and are unaffected +- per-station free surface amplification through `compute_free_surface_coefficients`, with `fs_mode`, `fs_level` and `fs_deviation` parameters and an optional `Surface` column carrying per-station weights +- helpers `normalize_wave_mode`, `wave_mode_phases`, `wave_mode_has_p`, `wave_mode_has_s`, `wave_mode_s_phases`, `resolve_radiation_pattern`, `check_min_stations` and `geometry_requires_receiver_projection` +- `pynetdesign.modelling.geometry`, a module of synthetic geometry builders: `generate_geometry` dispatches to `generate_borehole_geometry` for vertical, inclined and deviated boreholes, to `generate_surface_geometry` for line, zigzag, L, square and double-line layouts, and to `generate_darkfiber_geometry` for a continuous dark-fibre path with turns. Each returns geometry text lines that `read_geometry` can read back, including a depth-dependent noise profile for boreholes +- `save_geometry`, writing either generated text lines or a geometry DataFrame back to the `read_geometry` format, with `Z` restored to an elevation so that a round trip is lossless +- `decimate_geometry`, keeping every n-th channel while preserving the metadata, for studying the effect of channel spacing on a dense array +- `read_geometry` reads the optional `Surface` and `Components` columns, so the per-station free surface weights and the single vertical component stations introduced above can be described in a geometry file +- `read_geometry` records the path it read in `attrs['file_path']` +- test modules covering wave modes and radiation patterns, receiver projection, the free surface correction, network detectability, and phase-resolved sensitivity end to end +- `test_magnitude_consistency.py`, which builds the forward displacement spectrum independently of the package and checks that `calculate_M0` inverts it exactly, that the spectral conversion `S(f)` is applied once and not twice across `retrieve_min_amps` and `calculate_M0`, and that an event at the threshold magnitude records exactly the requested signal-to-noise ratio for each of the five amplitude types, gauge-length bridge included +- Sphinx documentation under `docs/`, published to GitHub Pages at , with an overview whose quick-link panels lead to every section and to the PDF edition, a getting-started guide covering installation and the input file formats, a methodology chapter, an example gallery and a generated API reference +- `methodology.rst` documenting the theory actually implemented: spectral conventions, the attenuation operator, the peak frequency, the detection threshold and its conversion coefficients, the free surface correction, receiver projection and DAS directionality, the radiation pattern with the full root-mean-square derivation, the homogeneous seismic moment relation with a standalone derivation from the far-field point source, and the network detectability criterion +- `docs/source/references.bib` with the seven works cited from the docstrings and the methodology, so the `:cite:` roles added earlier now resolve +- `citing`, `contributing` and `credits` documentation pages, plus a changelog page that is generated from `CHANGELOG.md` when the documentation is built, so the changelog has a single source and the generated page is not committed. On that page each version links to its changes on GitHub. These pages follow the main chapters in the order Citing, Contributing, Changelog, Credits +- `docs.yml` workflow building the documentation on every push and deploying it to GitHub Pages from `main` and from release tags, including a check that the built version matches the tag +- `push-tag.yml` workflow, so that pushing a `vX.Y.Z` tag validates it and publishes versioned documentation +- `build-docs.bat` and `build-docs.sh` for building the documentation, and `serve-docs.bat` and `serve-docs.sh` for viewing it locally. `serve-docs` rebuilds incrementally before serving, so only the pages that changed are regenerated, and accepts `--port` and `--no-build`. Both accept `SPHINXOPTS`, and neither needs `make`: they call `python -m sphinx -M` directly. Both also take `--pdf`, which builds the PDF edition and places it where the documentation links to it +- a PDF edition of the documentation, produced from the same sources through lualatex and linked from the site header and the front page, so the whole manual can be read offline as one file. Each web page keeps its own reference list, while the PDF lists the cited works once, in a bibliography at its end that its citations link to. `docs.yml` builds it on every run, publishes it with the site and keeps a copy as a workflow artifact +- `install.sh` and `uninstall.sh`, the Linux and macOS counterparts of the existing Windows scripts. `install.sh` takes `--dev` or `--no-dev` to answer the development-tools question in advance, and `uninstall.sh` takes `--yes` +- branding from the PyNetDesign brand package: the logo in the documentation header and the README, and the full browser icon set, comprising `favicon.ico`, a scalable `favicon.svg`, an Apple touch icon and a web app manifest. Both themes serve the same transparent logo, the one the README uses, so the mark keeps its own clear space and is never filtered or recoloured by the theme, because the brand gray is fixed and must not be inverted. Hovering the header logo swaps in the brand's own orange-only lockup, which carries the same geometry at the same viewBox and differs only in colour, so the mark neither moves nor resizes and no filter is involved +- brand colours in the documentation stylesheet. Signal Orange reaches only 2.87:1 against white, so link text in light mode is darkened to `#B84806`, which measures 4.81:1 against the light page surface, while the unmodified orange is used on the dark surfaces where it measures 5.17:1 + +### Changed + +- **S-wave results differ from 1.0.x.** The S branch now uses the phase-resolved radiation patterns instead of the combined value 0.63, which raises S-wave thresholds by `2/3*log10(0.63/sqrt(7/30))`, about 0.077 magnitude units. Pass `rad_pattern_s=0.63` to recover the previous behaviour +- **the free surface correction is applied per receiver.** In 1.0.x `free_surface=True` amplified every receiver irrespective of its depth; it now applies only to receivers at the free surface, so geometries mixing surface and downhole receivers are treated correctly. The default mode is `'auto'`, which derives the indicator from the receiver depths or from the `Surface` column when present +- `DetectionParameters` takes `fs_mode`, `fs_level` and `fs_deviation`; the `free_surface` flag is deprecated but still accepted, mapping to `fs_mode='on'` or `'off'` +- `mag_detectable` accepts the phase-resolved wave modes +- **example 7 is now the 1-km DAS array benchmark.** It models a 1-km-long vertical fibre with 1 m channel spacing, so 1000 channels, a constant strain-rate noise level of 1.95e-9 1/s and a 10 m gauge length, in a homogeneous medium with Vp = 2370 m/s, Vp/Vs = 2, Qp = Qs = 100 and rho = 2500 kg/m^3, requiring detection on at least 60 channels. It shows the three features that follow from a fibre recording only the strain along itself: the threshold magnitude grows with offset, P-wave sensitivity is worst at the array mid-depth once the offset exceeds half the array length, and S-wave sensitivity vanishes on the cable axis and degrades below the array +- the methodology and credits pages close with a `References` section rather than a rubric, so the bibliography is reachable from the table of contents +- the credits page acknowledges the works the implementation rests on individually, adding Aki and Richards (2002) for the far-field point source, the radiation-pattern convention and the free surface, and Cerveny (2001) for the ray-theoretical global absorption factor +- receiver projection is decided from the geometry itself, so `use_station_directionality` is only needed to force tangent projection for a station geometry +- **a single environment file.** `environment.yml` now pins only the interpreter, and every dependency is declared in `pyproject.toml`, as runtime `dependencies` or in the `dev` and `docs` optional groups. Install with `conda env create -f environment.yml -n pnd` followed by `pip install -e .` or `pip install -e ".[dev,docs]"`. This removes the duplicated dependency lists, which had drifted: they advertised `scipy` and `plotly`, neither of which is imported, and omitted `cmcrameri`, which is required +- `pyproject.toml` declares `requires-python = ">=3.9,<3.13"`, the MIT license and its file, project URLs, and a fuller set of keywords and classifiers; Python 3.8 is no longer advertised, since it is not tested and the package needs 3.9 +- `install.bat` creates one environment, `pnd`, from the single file and asks whether to add the development tools, rather than choosing between two conda files and two environment names +- the README documents the documentation site and the PDF download, corrects the Python range and the dependency list, and fixes the case of the clone URL + +### Removed + +- `environment-dev.yml`, superseded by the `dev` and `docs` optional dependency groups +- the empty `__init__.py` at the repository root, which made the repository root look like an importable package and confused setuptools flat-layout discovery +- the `rays_p` and `rays_s` arguments of `get_ray_station_directionality`, which could never be populated because the homogeneous package has no ray tracer. The function is deprecated in favour of `get_phase_station_directionality` and now emits a `DeprecationWarning` + +### Fixed + +- the `.. only:: html` blocks on the front page held their content at the wrong indentation, so the directive guarded nothing and the LaTeX build was asked to typeset icon cards it cannot render +- `examples/README.txt` promised a raytracing example that this package does not ship, and carried a stale product name +- a grid point that coincides with a receiver no longer emits a spurious divide-by-zero warning. Such a point gives `r = 0`, hence `t* = 0` and an infinite peak frequency, which the corner-frequency clamp already turned into the right answer; only the warning was wrong. Coincidence is ordinary with a dense DAS cable, where every channel sits on a grid line +- `pytest.ini` named `tests` as a directory to avoid, which does not exist here, and never named the directory that does. It now sets `testpaths = pytests`, so `examples` and `data` are not searched for tests +- `test_read_geometry.py` resolved its sample geometry files relative to the working directory, so it only passed when pytest was started from the repository root. The paths are now anchored to the test file +- the station-count guard in `mag_detectable` compared against the grid axis instead of the station axis, so a request for more receivers than the geometry has was not reported clearly +- the default dark-fibre turn distances are now fractions of the cable length, at one quarter, one half and three quarters, so that `generate_geometry(mode='darkfiber')` works for any cable. The previous fixed distances of 6, 12 and 18 km exceeded the default cable length and made the default call fail; for a 24 km cable the layout is unchanged +- `uninstall.bat` could never remove an environment. `ENV_NAME` and `CONFIRM` were assigned and read inside the same `for` block, so `cmd` substituted their pre-loop empty values: the script printed an empty environment name and always took the skip branch regardless of the answer. It now uses delayed expansion, and matches environment names anchored to the start of the line so that an unrelated environment cannot be matched through its path ## [v1.0.1] - 2026-09-10 @@ -56,6 +120,6 @@ code itself is unchanged from the deposited version. - conda environment files for user and development installations, and Windows installation scripts - MIT license -[Unreleased]: https://github.com/danikiev/PyNetDesign/compare/v1.0.1...HEAD +[v1.1.0]: https://github.com/danikiev/PyNetDesign/compare/v1.0.1...v1.1.0 [v1.0.1]: https://github.com/danikiev/PyNetDesign/compare/v1.0.0...v1.0.1 [v1.0.0]: https://github.com/danikiev/PyNetDesign/releases/tag/v1.0.0 diff --git a/README.md b/README.md index 8fafe1e..d9835eb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ -# PyNetDesign +

+ PyNetDesign logo +


[![DOI](https://zenodo.org/badge/958300487.svg)](https://zenodo.org/badge/latestdoi/958300487) [![Pytest](https://github.com/danikiev/PyNetDesign/actions/workflows/pytest.yml/badge.svg)](https://github.com/danikiev/PyNetDesign/actions/workflows/pytest.yml) +[![Docs](https://github.com/danikiev/PyNetDesign/actions/workflows/docs.yml/badge.svg)](https://github.com/danikiev/PyNetDesign/actions/workflows/docs.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellowgreen.svg)](https://github.com/danikiev/PyNetDesign/blob/main/LICENSE) PyNetDesign is an open-source Python framework for testing and designing microseismic monitoring networks for homogeneous velocity models. @@ -18,15 +21,16 @@ To clone the git repository use [Git](https://git-scm.com/), a free and open sou Installation requires [Conda](https://conda.io) package manager, e.g. one can use [miniforge](https://github.com/conda-forge/miniforge) implementation. -PyNetDesign works with 3.8 <= `python` <= 3.12 and requires [`pip`](https://pypi.org/project/pip/). +PyNetDesign works with 3.9 <= `python` <= 3.12 and requires [`pip`](https://pypi.org/project/pip/). -Some key dependencies: +The runtime dependencies are declared in `pyproject.toml`: - [`numpy`](https://www.numpy.org/) -- [`scipy`](https://scipy.org/) - [`pandas`](https://pandas.pydata.org/) - [`matplotlib`](https://matplotlib.org/) -- [`plotly`](https://plotly.com/python/) +- [`cmcrameri`](https://pypi.org/project/cmcrameri/) + +Two optional groups are available: `dev` adds `pytest` and a Jupyter stack, and `docs` adds the Sphinx toolchain. The author is incredibly grateful to the developers of these and other packages used by PyNetDesign. @@ -35,15 +39,15 @@ The author is incredibly grateful to the developers of these and other packages First clone the git repository using ```sh -git clone https://github.com/danikiev/pynetdesign.git +git clone https://github.com/danikiev/PyNetDesign.git ``` ### Install -The best way is to create a new conda environment with all the required packages: +`environment.yml` pins only the interpreter, so create the environment and give it a name: ```bash -conda env create -f environment.yml +conda env create -f environment.yml -n pnd ``` **Note:** to speed up creation of the environment, use `mamba` instead of `conda`, which is a faster alternative. @@ -64,33 +68,31 @@ pip install -e . For quick installation, you can use the specially designed installation script which implement all of the above mentioned steps. -On Windows, in miniforge prompt run: - -```cmd -install.bat +```bash +./install.sh # Linux, macOS +install.bat # Windows, from a Miniforge prompt ``` -and select option 1 (user environment). +It creates the `pnd` environment and asks whether to add the development tools. +Pass `--dev` or `--no-dev` to `install.sh` to answer in advance. ### Installation for development -Development environment includes additional packages for testing and building documentation. - -For development please use `environment-dev.yml` instead of `environment.yml`: +The development install adds the testing and documentation tools through the optional +dependency groups declared in `pyproject.toml`: ```bash -conda env create -f environment-dev.yml -conda activate pnd-dev -pip install -e . +pip install -e ".[dev,docs]" ``` -On Windows you can use `install.bat` and select option 2 (developer environment): +Or answer `y` when the installer asks whether to install the development tools: -```cmd -install.bat +```bash +./install.sh --dev # Linux, macOS +install.bat # Windows, then answer y ``` -Developer environment includes more packages, e.g. for building [local documentation](#build-documentation-locally), +The `docs` group is what you need for building the [local documentation](#build-documentation-locally). ### Uninstall @@ -106,23 +108,21 @@ and then remove the appropriate environment: conda remove -n pnd --all ``` -or +You can also run the uninstallation script: ```bash -conda remove -n pnd-dev --all +./uninstall.sh # Linux, macOS +uninstall.bat # Windows ``` -On Windows you can also run the uninstallation script: - -```cmd -uninstall.bat -``` - -It will search for all associated Conda environments matching `pnd*` and will ask to delete each of them. +It finds every Conda environment whose name starts with `pnd` and asks about each one. +Pass `--yes` to `uninstall.sh` to skip the questions. ## Documentation -The latest stable documentation based on [Sphinx](https://www.sphinx-doc.org) is available online at: <>. +The latest documentation, built with [Sphinx](https://www.sphinx-doc.org), is available at +, and as a single downloadable PDF at +. It features: @@ -133,61 +133,58 @@ It features: ### Build documentation locally -Packages required for building of the documentation are included to the development environment. -To install it in the normal installation environment you have to additionally run: +The documentation tools are installed with the `docs` extra: -```cmd -conda install sphinx pydata-sphinx-theme sphinx-gallery numpydoc -``` - -and - -```cmd -pip install setuptools_scm +```bash +pip install -e ".[docs]" ``` -To build the documentation locally run: +Build it with the helper script, which does a clean build: ```bash -cd docs -make html +./build-docs.sh # Linux, macOS +build-docs.bat # Windows ``` -If you want to rebuild the documentation: +Then view it in a browser. `serve-docs` rebuilds first, incrementally, so only the +pages that actually changed are regenerated: ```bash -cd docs -make clean -make html +./serve-docs.sh # Linux, macOS +serve-docs.bat # Windows ``` -To build website also the PDF file of the website, use: +and open . Useful options: -```bash -make latexpdf -``` +| Option | Effect | +| --- | --- | +| `-p PORT`, `--port PORT` | serve on another port | +| `-n`, `--no-build` | serve what is already built, without rebuilding | +| `-i`, `--incremental` (`build-docs`) | skip the clean and rebuild only what changed | +| `--pdf` (`-pdf` on Windows) | also build the PDF, into `docs/build/html/_static` | -After a successful build, one can serve the documentation website locally: +The PDF needs a LaTeX installation providing `lualatex` and `latexmk`; the scripts check +for both before starting and say so if either is missing. + +Extra Sphinx options can be passed through `SPHINXOPTS`, for instance to build the +pages without running the examples: ```bash -cd build/html -python -m http.server +SPHINXOPTS="-D plot_gallery=0" ./build-docs.sh # Linux, macOS +set SPHINXOPTS=-D plot_gallery=0 && build-docs.bat # Windows ``` -and open in browser: . - -**Note:** check the exact port number in the output +Equivalently, without the scripts: -On Windows you can also use the script: - -```cmd -build-docs.bat +```bash +python -m sphinx -M html docs/source docs/build ``` -or, to build also the PDF file: +and, for the PDF, generate the LaTeX sources and run `latexmk` on them: -```cmd -build-docs.bat -pdf +```bash +python -m sphinx -M latex docs/source docs/build +cd docs/build/latex && latexmk -pdf -dvi- -ps- pynetdesign.tex ``` ## Changelog @@ -198,31 +195,53 @@ All notable changes are documented in [CHANGELOG.md](CHANGELOG.md), following ## Citing -If you use PyNetDesign in your research, please cite the release you used. The DOI badge -at the top of this page always resolves to the most recently archived release, and the -Zenodo record of each release also carries its own version DOI. +If you use PyNetDesign in your research, please cite it as follows: -Version 1.0.0 was deposited manually, before the automated GitHub–Zenodo integration was -enabled, so it has a DOI of its own: +**Anikiev, D. (2026).** *PyNetDesign: a Python framework for microseismic monitoring network design.* Zenodo. + -**Anikiev, D. (2025).** *PyNetDesign: a Python framework for microseismic monitoring network design* (Version v1.0.0) [Computer software]. Zenodo. - +In BibTeX format: + +```bibtex +@software{Anikiev2026PyNetDesign, + author = {Anikiev, Denis}, + title = {{PyNetDesign}: a {P}ython framework for microseismic monitoring network design}, + year = {2026}, + publisher = {Zenodo}, + doi = {10.5281/zenodo.22695001}, + url = {https://github.com/danikiev/PyNetDesign} +} +``` + +The DOI above represents all versions and will always resolve to the latest one. +To cite a particular version of PyNetDesign, please use the following format, e.g. for +version 1.0.1: + +**Anikiev, D. (2026).** *PyNetDesign: a Python framework for microseismic monitoring network design (1.0.1).* Zenodo. + In BibTeX format: ```bibtex -@software{Anikiev2025PyNetDesign, +@software{Anikiev2026PyNetDesignVersion, author = {Anikiev, Denis}, title = {{PyNetDesign}: a {P}ython framework for microseismic monitoring network design}, - year = {2025}, + year = {2026}, publisher = {Zenodo}, - version = {v1.0.0}, + version = {1.0.1}, license = {MIT}, - doi = {10.5281/zenodo.14945917}, + doi = {10.5281/zenodo.22695002}, url = {https://github.com/danikiev/PyNetDesign} } ``` +### Earlier release + +Version 1.0.0 was deposited manually, before the automated GitHub–Zenodo integration was +enabled, and therefore belongs to a separate Zenodo lineage. It is cross-linked to the +current record and remains citable on its own: + + **Note:** magnitudes computed with version 1.0.0 are affected by an error in the seismic moment calculation that was corrected in version 1.0.1. If you are citing or reusing results obtained with 1.0.0, please see [CHANGELOG.md](CHANGELOG.md) and issue diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/build-docs.bat b/build-docs.bat new file mode 100644 index 0000000..8c32128 --- /dev/null +++ b/build-docs.bat @@ -0,0 +1,135 @@ +:: Windows script for building the PyNetDesign documentation. +:: Run: `build-docs.bat` from cmd +:: `build-docs.bat -i` to rebuild only what changed +:: `build-docs.bat -pdf` to also build the PDF into the HTML tree +:: Extra Sphinx options can be passed through the SPHINXOPTS variable, e.g. +:: set SPHINXOPTS=-D plot_gallery=0 +:: Use `serve-docs.bat` to view the result in a browser. +:: Requires the docs extra: pip install -e ".[docs]" +:: The PDF additionally requires a LaTeX installation providing lualatex and latexmk. +:: D. Anikiev, 2026-09-11 + +@echo off +setlocal + +set CLEAN=1 +set BUILD_PDF=0 +for %%i in (%*) do ( + if /i "%%i" == "-i" set CLEAN=0 + if /i "%%i" == "--incremental" set CLEAN=0 + if /i "%%i" == "-pdf" set BUILD_PDF=1 + if /i "%%i" == "--pdf" set BUILD_PDF=1 + if /i "%%i" == "-h" goto usage + if /i "%%i" == "--help" goto usage +) +goto args_done + +:usage +echo Usage: build-docs.bat [-i] [-pdf] +echo -i, --incremental skip the clean and rebuild only what changed +echo -pdf, --pdf also build the PDF into docs\build\html\_static +echo. +echo Pass extra Sphinx options through SPHINXOPTS. +exit /b 0 + +:args_done + +set SOURCE_DIR=docs\source +set BUILD_DIR=docs\build +set PDF_NAME=pynetdesign.pdf +set TEX_NAME=pynetdesign.tex + +if not exist %SOURCE_DIR%\conf.py ( + echo Sphinx configuration not found: %SOURCE_DIR%\conf.py + echo Please run this script from the repository root. + exit /b 1 +) + +call python -c "import sphinx" >nul 2>nul +if %ERRORLEVEL% neq 0 ( + echo Sphinx is not installed in the active environment. + echo Install the documentation tools with: pip install -e ".[docs]" + exit /b 2 +) + +rem Inside a ( ) block %ERRORLEVEL% is expanded once, when the block is read, before +rem any command in it runs; so the checks within blocks use "if errorlevel 1" instead. +if %BUILD_PDF% equ 1 ( + where lualatex >nul 2>nul + if errorlevel 1 ( + echo lualatex not found in PATH, which is required for the PDF build. + echo Install a LaTeX distribution, or drop -pdf to build the HTML only. + exit /b 2 + ) + where latexmk >nul 2>nul + if errorlevel 1 ( + echo latexmk not found in PATH, which is required for the PDF build. + echo Install a LaTeX distribution, or drop -pdf to build the HTML only. + exit /b 2 + ) +) + +if %CLEAN% equ 1 ( + echo ################################################################## + echo Cleaning... + call python -m sphinx -M clean %SOURCE_DIR% %BUILD_DIR% + if errorlevel 1 ( + echo Error while cleaning! + exit /b 1 + ) +) + +echo ################################################################## +echo Building HTML... +call python -m sphinx -M html %SOURCE_DIR% %BUILD_DIR% %SPHINXOPTS% +if %ERRORLEVEL% neq 0 ( + echo Error during HTML build process! + exit /b 1 +) + +if %BUILD_PDF% equ 1 ( + echo ################################################################## + echo Generating LaTeX... + call python -m sphinx -M latex %SOURCE_DIR% %BUILD_DIR% %SPHINXOPTS% + if errorlevel 1 ( + echo Error while generating LaTeX! + exit /b 1 + ) + + echo ################################################################## + echo Building PDF... + rem latexmk reads the latexmkrc that Sphinx writes next to the .tex, which binds the + rem engine (lualatex) and the python.ist index style, so -pdf resolves to lualatex here. + pushd %BUILD_DIR%\latex + call latexmk -pdf -dvi- -ps- -interaction=nonstopmode -halt-on-error %TEX_NAME% + if errorlevel 1 ( + echo Error during PDF build process! + echo See %BUILD_DIR%\latex\pynetdesign.log for the LaTeX output. + popd + exit /b 1 + ) + popd + + if not exist %BUILD_DIR%\latex\%PDF_NAME% ( + echo PDF file not found after the build! + exit /b 1 + ) + + echo ################################################################## + echo Copying the PDF into the HTML tree... + if not exist %BUILD_DIR%\html\_static mkdir %BUILD_DIR%\html\_static + copy /y %BUILD_DIR%\latex\%PDF_NAME% %BUILD_DIR%\html\_static\%PDF_NAME% >nul + if errorlevel 1 ( + echo Error while copying the PDF! + exit /b 2 + ) +) + +echo ################################################################## +echo Documentation built in docs\build\html +if %BUILD_PDF% equ 1 ( + echo PDF available at docs\build\html\_static\%PDF_NAME% +) else ( + echo The PDF was not built; pass -pdf to build it as well. +) +echo Open docs\build\html\index.html, or run serve-docs.bat to serve it. diff --git a/build-docs.sh b/build-docs.sh new file mode 100644 index 0000000..4973c71 --- /dev/null +++ b/build-docs.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +# Linux/macOS script for building the PyNetDesign documentation. +# Run: ./build-docs.sh +# ./build-docs.sh --incremental rebuild only what changed +# ./build-docs.sh --pdf also build the PDF and place it in the HTML tree +# Extra Sphinx options can be passed through the SPHINXOPTS variable, e.g. +# SPHINXOPTS="-D plot_gallery=0" ./build-docs.sh build without running examples +# Use ./serve-docs.sh to view the result in a browser. +# Requires the docs extra: pip install -e ".[docs]" +# The PDF additionally requires a LaTeX installation providing lualatex and latexmk. +# D. Anikiev, 2026-09-11 + +set -u + +CLEAN=1 +BUILD_PDF=0 +for arg in "$@"; do + case "$arg" in + -i|--incremental) + CLEAN=0 + ;; + --pdf) + BUILD_PDF=1 + ;; + -h|--help) + echo "Usage: $(basename "$0") [--incremental] [--pdf]" + echo " -i, --incremental skip the clean and rebuild only what changed" + echo " --pdf also build the PDF into docs/build/html/_static" + echo + echo "Pass extra Sphinx options through SPHINXOPTS." + exit 0 + ;; + *) + echo "Unknown option: $arg" + echo "Try $(basename "$0") --help" + exit 1 + ;; + esac +done + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOURCE_DIR="$SCRIPT_DIR/docs/source" +BUILD_DIR="$SCRIPT_DIR/docs/build" +PDF_NAME="pynetdesign.pdf" + +if [ ! -f "$SOURCE_DIR/conf.py" ]; then + echo "Sphinx configuration not found: $SOURCE_DIR/conf.py" + exit 1 +fi + +if ! python -c "import sphinx" >/dev/null 2>&1; then + echo "Sphinx is not installed in the active environment." + echo "Install the documentation tools with: pip install -e \".[docs]\"" + exit 2 +fi + +if [ "$BUILD_PDF" -eq 1 ]; then + for tool in lualatex latexmk; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "$tool not found in PATH, which is required for the PDF build." + echo "Install a LaTeX distribution, or drop --pdf to build the HTML only." + exit 2 + fi + done +fi + +if [ "$CLEAN" -eq 1 ]; then + echo "##################################################################" + echo "Cleaning..." + python -m sphinx -M clean "$SOURCE_DIR" "$BUILD_DIR" + if [ $? -ne 0 ]; then + echo "Error while cleaning!" + exit 1 + fi +fi + +echo "##################################################################" +echo "Building HTML..." +# shellcheck disable=SC2086 +python -m sphinx -M html "$SOURCE_DIR" "$BUILD_DIR" ${SPHINXOPTS:-} +if [ $? -ne 0 ]; then + echo "Error during HTML build process!" + exit 1 +fi + +if [ "$BUILD_PDF" -eq 1 ]; then + echo "##################################################################" + echo "Generating LaTeX..." + # shellcheck disable=SC2086 + python -m sphinx -M latex "$SOURCE_DIR" "$BUILD_DIR" ${SPHINXOPTS:-} + if [ $? -ne 0 ]; then + echo "Error while generating LaTeX!" + exit 1 + fi + + echo "##################################################################" + echo "Building PDF..." + # latexmk reads the latexmkrc that Sphinx writes next to the .tex, which binds the + # engine (lualatex) and the python.ist index style, so -pdf resolves to lualatex here. + (cd "$BUILD_DIR/latex" && latexmk -pdf -dvi- -ps- -interaction=nonstopmode -halt-on-error "${PDF_NAME%.pdf}.tex") + if [ $? -ne 0 ] || [ ! -f "$BUILD_DIR/latex/$PDF_NAME" ]; then + echo "Error during PDF build process!" + echo "See $BUILD_DIR/latex/${PDF_NAME%.pdf}.log for the LaTeX output." + exit 1 + fi + + echo "##################################################################" + echo "Copying the PDF into the HTML tree..." + mkdir -p "$BUILD_DIR/html/_static" + cp "$BUILD_DIR/latex/$PDF_NAME" "$BUILD_DIR/html/_static/$PDF_NAME" + if [ $? -ne 0 ]; then + echo "Error while copying the PDF!" + exit 2 + fi +fi + +echo "##################################################################" +echo "Documentation built in docs/build/html" +if [ "$BUILD_PDF" -eq 1 ]; then + echo "PDF available at docs/build/html/_static/$PDF_NAME" +else + echo "The PDF was not built; pass --pdf to build it as well." +fi +echo "Open docs/build/html/index.html, or run ./serve-docs.sh to serve it." diff --git a/data/homo/test_07/TMPL_net_geometry_Noise_Constant.txt b/data/homo/test_07/TMPL_net_geometry_Noise_Constant.txt index a0f5bfe..690032f 100644 --- a/data/homo/test_07/TMPL_net_geometry_Noise_Constant.txt +++ b/data/homo/test_07/TMPL_net_geometry_Noise_Constant.txt @@ -1,62 +1,1001 @@ -Northing(m) Easting(m) Elevation(m) NoiseLevel(strain) GaugeLength=10(m) -0 0 -1.00E+01 5.00E-11 -0 0 -6.00E+01 5.00E-11 -0 0 -1.10E+02 5.00E-11 -0 0 -1.60E+02 5.00E-11 -0 0 -2.10E+02 5.00E-11 -0 0 -2.60E+02 5.00E-11 -0 0 -3.10E+02 5.00E-11 -0 0 -3.60E+02 5.00E-11 -0 0 -4.10E+02 5.00E-11 -0 0 -4.60E+02 5.00E-11 -0 0 -5.10E+02 5.00E-11 -0 0 -5.60E+02 5.00E-11 -0 0 -6.10E+02 5.00E-11 -0 0 -6.60E+02 5.00E-11 -0 0 -7.10E+02 5.00E-11 -0 0 -7.60E+02 5.00E-11 -0 0 -8.10E+02 5.00E-11 -0 0 -8.60E+02 5.00E-11 -0 0 -9.10E+02 5.00E-11 -0 0 -9.60E+02 5.00E-11 -0 0 -1.01E+03 5.00E-11 -0 0 -1.06E+03 5.00E-11 -0 0 -1.11E+03 5.00E-11 -0 0 -1.16E+03 5.00E-11 -0 0 -1.21E+03 5.00E-11 -0 0 -1.26E+03 5.00E-11 -0 0 -1.31E+03 5.00E-11 -0 0 -1.36E+03 5.00E-11 -0 0 -1.41E+03 5.00E-11 -0 0 -1.46E+03 5.00E-11 -0 0 -1.51E+03 5.00E-11 -0 0 -1.56E+03 5.00E-11 -0 0 -1.61E+03 5.00E-11 -0 0 -1.66E+03 5.00E-11 -0 0 -1.71E+03 5.00E-11 -0 0 -1.76E+03 5.00E-11 -0 0 -1.81E+03 5.00E-11 -0 0 -1.86E+03 5.00E-11 -0 0 -1.91E+03 5.00E-11 -0 0 -1.96E+03 5.00E-11 -0 0 -2.01E+03 5.00E-11 -0 0 -2.06E+03 5.00E-11 -0 0 -2.11E+03 5.00E-11 -0 0 -2.16E+03 5.00E-11 -0 0 -2.21E+03 5.00E-11 -0 0 -2.26E+03 5.00E-11 -0 0 -2.31E+03 5.00E-11 -0 0 -2.36E+03 5.00E-11 -0 0 -2.41E+03 5.00E-11 -0 0 -2.46E+03 5.00E-11 -0 0 -2.51E+03 5.00E-11 -0 0 -2.56E+03 5.00E-11 -0 0 -2.61E+03 5.00E-11 -0 0 -2.66E+03 5.00E-11 -0 0 -2.71E+03 5.00E-11 -0 0 -2.76E+03 5.00E-11 -0 0 -2.81E+03 5.00E-11 -0 0 -2.86E+03 5.00E-11 -0 0 -2.91E+03 5.00E-11 -0 0 -2.96E+03 5.00E-11 -0 0 -3.01E+03 5.00E-11 +Northing(m) Easting(m) Elevation(m) NoiseLevel(1/s) GaugeLength=10(m) +0 0 -1 1.95E-09 +0 0 -2 1.95E-09 +0 0 -3 1.95E-09 +0 0 -4 1.95E-09 +0 0 -5 1.95E-09 +0 0 -6 1.95E-09 +0 0 -7 1.95E-09 +0 0 -8 1.95E-09 +0 0 -9 1.95E-09 +0 0 -10 1.95E-09 +0 0 -11 1.95E-09 +0 0 -12 1.95E-09 +0 0 -13 1.95E-09 +0 0 -14 1.95E-09 +0 0 -15 1.95E-09 +0 0 -16 1.95E-09 +0 0 -17 1.95E-09 +0 0 -18 1.95E-09 +0 0 -19 1.95E-09 +0 0 -20 1.95E-09 +0 0 -21 1.95E-09 +0 0 -22 1.95E-09 +0 0 -23 1.95E-09 +0 0 -24 1.95E-09 +0 0 -25 1.95E-09 +0 0 -26 1.95E-09 +0 0 -27 1.95E-09 +0 0 -28 1.95E-09 +0 0 -29 1.95E-09 +0 0 -30 1.95E-09 +0 0 -31 1.95E-09 +0 0 -32 1.95E-09 +0 0 -33 1.95E-09 +0 0 -34 1.95E-09 +0 0 -35 1.95E-09 +0 0 -36 1.95E-09 +0 0 -37 1.95E-09 +0 0 -38 1.95E-09 +0 0 -39 1.95E-09 +0 0 -40 1.95E-09 +0 0 -41 1.95E-09 +0 0 -42 1.95E-09 +0 0 -43 1.95E-09 +0 0 -44 1.95E-09 +0 0 -45 1.95E-09 +0 0 -46 1.95E-09 +0 0 -47 1.95E-09 +0 0 -48 1.95E-09 +0 0 -49 1.95E-09 +0 0 -50 1.95E-09 +0 0 -51 1.95E-09 +0 0 -52 1.95E-09 +0 0 -53 1.95E-09 +0 0 -54 1.95E-09 +0 0 -55 1.95E-09 +0 0 -56 1.95E-09 +0 0 -57 1.95E-09 +0 0 -58 1.95E-09 +0 0 -59 1.95E-09 +0 0 -60 1.95E-09 +0 0 -61 1.95E-09 +0 0 -62 1.95E-09 +0 0 -63 1.95E-09 +0 0 -64 1.95E-09 +0 0 -65 1.95E-09 +0 0 -66 1.95E-09 +0 0 -67 1.95E-09 +0 0 -68 1.95E-09 +0 0 -69 1.95E-09 +0 0 -70 1.95E-09 +0 0 -71 1.95E-09 +0 0 -72 1.95E-09 +0 0 -73 1.95E-09 +0 0 -74 1.95E-09 +0 0 -75 1.95E-09 +0 0 -76 1.95E-09 +0 0 -77 1.95E-09 +0 0 -78 1.95E-09 +0 0 -79 1.95E-09 +0 0 -80 1.95E-09 +0 0 -81 1.95E-09 +0 0 -82 1.95E-09 +0 0 -83 1.95E-09 +0 0 -84 1.95E-09 +0 0 -85 1.95E-09 +0 0 -86 1.95E-09 +0 0 -87 1.95E-09 +0 0 -88 1.95E-09 +0 0 -89 1.95E-09 +0 0 -90 1.95E-09 +0 0 -91 1.95E-09 +0 0 -92 1.95E-09 +0 0 -93 1.95E-09 +0 0 -94 1.95E-09 +0 0 -95 1.95E-09 +0 0 -96 1.95E-09 +0 0 -97 1.95E-09 +0 0 -98 1.95E-09 +0 0 -99 1.95E-09 +0 0 -100 1.95E-09 +0 0 -101 1.95E-09 +0 0 -102 1.95E-09 +0 0 -103 1.95E-09 +0 0 -104 1.95E-09 +0 0 -105 1.95E-09 +0 0 -106 1.95E-09 +0 0 -107 1.95E-09 +0 0 -108 1.95E-09 +0 0 -109 1.95E-09 +0 0 -110 1.95E-09 +0 0 -111 1.95E-09 +0 0 -112 1.95E-09 +0 0 -113 1.95E-09 +0 0 -114 1.95E-09 +0 0 -115 1.95E-09 +0 0 -116 1.95E-09 +0 0 -117 1.95E-09 +0 0 -118 1.95E-09 +0 0 -119 1.95E-09 +0 0 -120 1.95E-09 +0 0 -121 1.95E-09 +0 0 -122 1.95E-09 +0 0 -123 1.95E-09 +0 0 -124 1.95E-09 +0 0 -125 1.95E-09 +0 0 -126 1.95E-09 +0 0 -127 1.95E-09 +0 0 -128 1.95E-09 +0 0 -129 1.95E-09 +0 0 -130 1.95E-09 +0 0 -131 1.95E-09 +0 0 -132 1.95E-09 +0 0 -133 1.95E-09 +0 0 -134 1.95E-09 +0 0 -135 1.95E-09 +0 0 -136 1.95E-09 +0 0 -137 1.95E-09 +0 0 -138 1.95E-09 +0 0 -139 1.95E-09 +0 0 -140 1.95E-09 +0 0 -141 1.95E-09 +0 0 -142 1.95E-09 +0 0 -143 1.95E-09 +0 0 -144 1.95E-09 +0 0 -145 1.95E-09 +0 0 -146 1.95E-09 +0 0 -147 1.95E-09 +0 0 -148 1.95E-09 +0 0 -149 1.95E-09 +0 0 -150 1.95E-09 +0 0 -151 1.95E-09 +0 0 -152 1.95E-09 +0 0 -153 1.95E-09 +0 0 -154 1.95E-09 +0 0 -155 1.95E-09 +0 0 -156 1.95E-09 +0 0 -157 1.95E-09 +0 0 -158 1.95E-09 +0 0 -159 1.95E-09 +0 0 -160 1.95E-09 +0 0 -161 1.95E-09 +0 0 -162 1.95E-09 +0 0 -163 1.95E-09 +0 0 -164 1.95E-09 +0 0 -165 1.95E-09 +0 0 -166 1.95E-09 +0 0 -167 1.95E-09 +0 0 -168 1.95E-09 +0 0 -169 1.95E-09 +0 0 -170 1.95E-09 +0 0 -171 1.95E-09 +0 0 -172 1.95E-09 +0 0 -173 1.95E-09 +0 0 -174 1.95E-09 +0 0 -175 1.95E-09 +0 0 -176 1.95E-09 +0 0 -177 1.95E-09 +0 0 -178 1.95E-09 +0 0 -179 1.95E-09 +0 0 -180 1.95E-09 +0 0 -181 1.95E-09 +0 0 -182 1.95E-09 +0 0 -183 1.95E-09 +0 0 -184 1.95E-09 +0 0 -185 1.95E-09 +0 0 -186 1.95E-09 +0 0 -187 1.95E-09 +0 0 -188 1.95E-09 +0 0 -189 1.95E-09 +0 0 -190 1.95E-09 +0 0 -191 1.95E-09 +0 0 -192 1.95E-09 +0 0 -193 1.95E-09 +0 0 -194 1.95E-09 +0 0 -195 1.95E-09 +0 0 -196 1.95E-09 +0 0 -197 1.95E-09 +0 0 -198 1.95E-09 +0 0 -199 1.95E-09 +0 0 -200 1.95E-09 +0 0 -201 1.95E-09 +0 0 -202 1.95E-09 +0 0 -203 1.95E-09 +0 0 -204 1.95E-09 +0 0 -205 1.95E-09 +0 0 -206 1.95E-09 +0 0 -207 1.95E-09 +0 0 -208 1.95E-09 +0 0 -209 1.95E-09 +0 0 -210 1.95E-09 +0 0 -211 1.95E-09 +0 0 -212 1.95E-09 +0 0 -213 1.95E-09 +0 0 -214 1.95E-09 +0 0 -215 1.95E-09 +0 0 -216 1.95E-09 +0 0 -217 1.95E-09 +0 0 -218 1.95E-09 +0 0 -219 1.95E-09 +0 0 -220 1.95E-09 +0 0 -221 1.95E-09 +0 0 -222 1.95E-09 +0 0 -223 1.95E-09 +0 0 -224 1.95E-09 +0 0 -225 1.95E-09 +0 0 -226 1.95E-09 +0 0 -227 1.95E-09 +0 0 -228 1.95E-09 +0 0 -229 1.95E-09 +0 0 -230 1.95E-09 +0 0 -231 1.95E-09 +0 0 -232 1.95E-09 +0 0 -233 1.95E-09 +0 0 -234 1.95E-09 +0 0 -235 1.95E-09 +0 0 -236 1.95E-09 +0 0 -237 1.95E-09 +0 0 -238 1.95E-09 +0 0 -239 1.95E-09 +0 0 -240 1.95E-09 +0 0 -241 1.95E-09 +0 0 -242 1.95E-09 +0 0 -243 1.95E-09 +0 0 -244 1.95E-09 +0 0 -245 1.95E-09 +0 0 -246 1.95E-09 +0 0 -247 1.95E-09 +0 0 -248 1.95E-09 +0 0 -249 1.95E-09 +0 0 -250 1.95E-09 +0 0 -251 1.95E-09 +0 0 -252 1.95E-09 +0 0 -253 1.95E-09 +0 0 -254 1.95E-09 +0 0 -255 1.95E-09 +0 0 -256 1.95E-09 +0 0 -257 1.95E-09 +0 0 -258 1.95E-09 +0 0 -259 1.95E-09 +0 0 -260 1.95E-09 +0 0 -261 1.95E-09 +0 0 -262 1.95E-09 +0 0 -263 1.95E-09 +0 0 -264 1.95E-09 +0 0 -265 1.95E-09 +0 0 -266 1.95E-09 +0 0 -267 1.95E-09 +0 0 -268 1.95E-09 +0 0 -269 1.95E-09 +0 0 -270 1.95E-09 +0 0 -271 1.95E-09 +0 0 -272 1.95E-09 +0 0 -273 1.95E-09 +0 0 -274 1.95E-09 +0 0 -275 1.95E-09 +0 0 -276 1.95E-09 +0 0 -277 1.95E-09 +0 0 -278 1.95E-09 +0 0 -279 1.95E-09 +0 0 -280 1.95E-09 +0 0 -281 1.95E-09 +0 0 -282 1.95E-09 +0 0 -283 1.95E-09 +0 0 -284 1.95E-09 +0 0 -285 1.95E-09 +0 0 -286 1.95E-09 +0 0 -287 1.95E-09 +0 0 -288 1.95E-09 +0 0 -289 1.95E-09 +0 0 -290 1.95E-09 +0 0 -291 1.95E-09 +0 0 -292 1.95E-09 +0 0 -293 1.95E-09 +0 0 -294 1.95E-09 +0 0 -295 1.95E-09 +0 0 -296 1.95E-09 +0 0 -297 1.95E-09 +0 0 -298 1.95E-09 +0 0 -299 1.95E-09 +0 0 -300 1.95E-09 +0 0 -301 1.95E-09 +0 0 -302 1.95E-09 +0 0 -303 1.95E-09 +0 0 -304 1.95E-09 +0 0 -305 1.95E-09 +0 0 -306 1.95E-09 +0 0 -307 1.95E-09 +0 0 -308 1.95E-09 +0 0 -309 1.95E-09 +0 0 -310 1.95E-09 +0 0 -311 1.95E-09 +0 0 -312 1.95E-09 +0 0 -313 1.95E-09 +0 0 -314 1.95E-09 +0 0 -315 1.95E-09 +0 0 -316 1.95E-09 +0 0 -317 1.95E-09 +0 0 -318 1.95E-09 +0 0 -319 1.95E-09 +0 0 -320 1.95E-09 +0 0 -321 1.95E-09 +0 0 -322 1.95E-09 +0 0 -323 1.95E-09 +0 0 -324 1.95E-09 +0 0 -325 1.95E-09 +0 0 -326 1.95E-09 +0 0 -327 1.95E-09 +0 0 -328 1.95E-09 +0 0 -329 1.95E-09 +0 0 -330 1.95E-09 +0 0 -331 1.95E-09 +0 0 -332 1.95E-09 +0 0 -333 1.95E-09 +0 0 -334 1.95E-09 +0 0 -335 1.95E-09 +0 0 -336 1.95E-09 +0 0 -337 1.95E-09 +0 0 -338 1.95E-09 +0 0 -339 1.95E-09 +0 0 -340 1.95E-09 +0 0 -341 1.95E-09 +0 0 -342 1.95E-09 +0 0 -343 1.95E-09 +0 0 -344 1.95E-09 +0 0 -345 1.95E-09 +0 0 -346 1.95E-09 +0 0 -347 1.95E-09 +0 0 -348 1.95E-09 +0 0 -349 1.95E-09 +0 0 -350 1.95E-09 +0 0 -351 1.95E-09 +0 0 -352 1.95E-09 +0 0 -353 1.95E-09 +0 0 -354 1.95E-09 +0 0 -355 1.95E-09 +0 0 -356 1.95E-09 +0 0 -357 1.95E-09 +0 0 -358 1.95E-09 +0 0 -359 1.95E-09 +0 0 -360 1.95E-09 +0 0 -361 1.95E-09 +0 0 -362 1.95E-09 +0 0 -363 1.95E-09 +0 0 -364 1.95E-09 +0 0 -365 1.95E-09 +0 0 -366 1.95E-09 +0 0 -367 1.95E-09 +0 0 -368 1.95E-09 +0 0 -369 1.95E-09 +0 0 -370 1.95E-09 +0 0 -371 1.95E-09 +0 0 -372 1.95E-09 +0 0 -373 1.95E-09 +0 0 -374 1.95E-09 +0 0 -375 1.95E-09 +0 0 -376 1.95E-09 +0 0 -377 1.95E-09 +0 0 -378 1.95E-09 +0 0 -379 1.95E-09 +0 0 -380 1.95E-09 +0 0 -381 1.95E-09 +0 0 -382 1.95E-09 +0 0 -383 1.95E-09 +0 0 -384 1.95E-09 +0 0 -385 1.95E-09 +0 0 -386 1.95E-09 +0 0 -387 1.95E-09 +0 0 -388 1.95E-09 +0 0 -389 1.95E-09 +0 0 -390 1.95E-09 +0 0 -391 1.95E-09 +0 0 -392 1.95E-09 +0 0 -393 1.95E-09 +0 0 -394 1.95E-09 +0 0 -395 1.95E-09 +0 0 -396 1.95E-09 +0 0 -397 1.95E-09 +0 0 -398 1.95E-09 +0 0 -399 1.95E-09 +0 0 -400 1.95E-09 +0 0 -401 1.95E-09 +0 0 -402 1.95E-09 +0 0 -403 1.95E-09 +0 0 -404 1.95E-09 +0 0 -405 1.95E-09 +0 0 -406 1.95E-09 +0 0 -407 1.95E-09 +0 0 -408 1.95E-09 +0 0 -409 1.95E-09 +0 0 -410 1.95E-09 +0 0 -411 1.95E-09 +0 0 -412 1.95E-09 +0 0 -413 1.95E-09 +0 0 -414 1.95E-09 +0 0 -415 1.95E-09 +0 0 -416 1.95E-09 +0 0 -417 1.95E-09 +0 0 -418 1.95E-09 +0 0 -419 1.95E-09 +0 0 -420 1.95E-09 +0 0 -421 1.95E-09 +0 0 -422 1.95E-09 +0 0 -423 1.95E-09 +0 0 -424 1.95E-09 +0 0 -425 1.95E-09 +0 0 -426 1.95E-09 +0 0 -427 1.95E-09 +0 0 -428 1.95E-09 +0 0 -429 1.95E-09 +0 0 -430 1.95E-09 +0 0 -431 1.95E-09 +0 0 -432 1.95E-09 +0 0 -433 1.95E-09 +0 0 -434 1.95E-09 +0 0 -435 1.95E-09 +0 0 -436 1.95E-09 +0 0 -437 1.95E-09 +0 0 -438 1.95E-09 +0 0 -439 1.95E-09 +0 0 -440 1.95E-09 +0 0 -441 1.95E-09 +0 0 -442 1.95E-09 +0 0 -443 1.95E-09 +0 0 -444 1.95E-09 +0 0 -445 1.95E-09 +0 0 -446 1.95E-09 +0 0 -447 1.95E-09 +0 0 -448 1.95E-09 +0 0 -449 1.95E-09 +0 0 -450 1.95E-09 +0 0 -451 1.95E-09 +0 0 -452 1.95E-09 +0 0 -453 1.95E-09 +0 0 -454 1.95E-09 +0 0 -455 1.95E-09 +0 0 -456 1.95E-09 +0 0 -457 1.95E-09 +0 0 -458 1.95E-09 +0 0 -459 1.95E-09 +0 0 -460 1.95E-09 +0 0 -461 1.95E-09 +0 0 -462 1.95E-09 +0 0 -463 1.95E-09 +0 0 -464 1.95E-09 +0 0 -465 1.95E-09 +0 0 -466 1.95E-09 +0 0 -467 1.95E-09 +0 0 -468 1.95E-09 +0 0 -469 1.95E-09 +0 0 -470 1.95E-09 +0 0 -471 1.95E-09 +0 0 -472 1.95E-09 +0 0 -473 1.95E-09 +0 0 -474 1.95E-09 +0 0 -475 1.95E-09 +0 0 -476 1.95E-09 +0 0 -477 1.95E-09 +0 0 -478 1.95E-09 +0 0 -479 1.95E-09 +0 0 -480 1.95E-09 +0 0 -481 1.95E-09 +0 0 -482 1.95E-09 +0 0 -483 1.95E-09 +0 0 -484 1.95E-09 +0 0 -485 1.95E-09 +0 0 -486 1.95E-09 +0 0 -487 1.95E-09 +0 0 -488 1.95E-09 +0 0 -489 1.95E-09 +0 0 -490 1.95E-09 +0 0 -491 1.95E-09 +0 0 -492 1.95E-09 +0 0 -493 1.95E-09 +0 0 -494 1.95E-09 +0 0 -495 1.95E-09 +0 0 -496 1.95E-09 +0 0 -497 1.95E-09 +0 0 -498 1.95E-09 +0 0 -499 1.95E-09 +0 0 -500 1.95E-09 +0 0 -501 1.95E-09 +0 0 -502 1.95E-09 +0 0 -503 1.95E-09 +0 0 -504 1.95E-09 +0 0 -505 1.95E-09 +0 0 -506 1.95E-09 +0 0 -507 1.95E-09 +0 0 -508 1.95E-09 +0 0 -509 1.95E-09 +0 0 -510 1.95E-09 +0 0 -511 1.95E-09 +0 0 -512 1.95E-09 +0 0 -513 1.95E-09 +0 0 -514 1.95E-09 +0 0 -515 1.95E-09 +0 0 -516 1.95E-09 +0 0 -517 1.95E-09 +0 0 -518 1.95E-09 +0 0 -519 1.95E-09 +0 0 -520 1.95E-09 +0 0 -521 1.95E-09 +0 0 -522 1.95E-09 +0 0 -523 1.95E-09 +0 0 -524 1.95E-09 +0 0 -525 1.95E-09 +0 0 -526 1.95E-09 +0 0 -527 1.95E-09 +0 0 -528 1.95E-09 +0 0 -529 1.95E-09 +0 0 -530 1.95E-09 +0 0 -531 1.95E-09 +0 0 -532 1.95E-09 +0 0 -533 1.95E-09 +0 0 -534 1.95E-09 +0 0 -535 1.95E-09 +0 0 -536 1.95E-09 +0 0 -537 1.95E-09 +0 0 -538 1.95E-09 +0 0 -539 1.95E-09 +0 0 -540 1.95E-09 +0 0 -541 1.95E-09 +0 0 -542 1.95E-09 +0 0 -543 1.95E-09 +0 0 -544 1.95E-09 +0 0 -545 1.95E-09 +0 0 -546 1.95E-09 +0 0 -547 1.95E-09 +0 0 -548 1.95E-09 +0 0 -549 1.95E-09 +0 0 -550 1.95E-09 +0 0 -551 1.95E-09 +0 0 -552 1.95E-09 +0 0 -553 1.95E-09 +0 0 -554 1.95E-09 +0 0 -555 1.95E-09 +0 0 -556 1.95E-09 +0 0 -557 1.95E-09 +0 0 -558 1.95E-09 +0 0 -559 1.95E-09 +0 0 -560 1.95E-09 +0 0 -561 1.95E-09 +0 0 -562 1.95E-09 +0 0 -563 1.95E-09 +0 0 -564 1.95E-09 +0 0 -565 1.95E-09 +0 0 -566 1.95E-09 +0 0 -567 1.95E-09 +0 0 -568 1.95E-09 +0 0 -569 1.95E-09 +0 0 -570 1.95E-09 +0 0 -571 1.95E-09 +0 0 -572 1.95E-09 +0 0 -573 1.95E-09 +0 0 -574 1.95E-09 +0 0 -575 1.95E-09 +0 0 -576 1.95E-09 +0 0 -577 1.95E-09 +0 0 -578 1.95E-09 +0 0 -579 1.95E-09 +0 0 -580 1.95E-09 +0 0 -581 1.95E-09 +0 0 -582 1.95E-09 +0 0 -583 1.95E-09 +0 0 -584 1.95E-09 +0 0 -585 1.95E-09 +0 0 -586 1.95E-09 +0 0 -587 1.95E-09 +0 0 -588 1.95E-09 +0 0 -589 1.95E-09 +0 0 -590 1.95E-09 +0 0 -591 1.95E-09 +0 0 -592 1.95E-09 +0 0 -593 1.95E-09 +0 0 -594 1.95E-09 +0 0 -595 1.95E-09 +0 0 -596 1.95E-09 +0 0 -597 1.95E-09 +0 0 -598 1.95E-09 +0 0 -599 1.95E-09 +0 0 -600 1.95E-09 +0 0 -601 1.95E-09 +0 0 -602 1.95E-09 +0 0 -603 1.95E-09 +0 0 -604 1.95E-09 +0 0 -605 1.95E-09 +0 0 -606 1.95E-09 +0 0 -607 1.95E-09 +0 0 -608 1.95E-09 +0 0 -609 1.95E-09 +0 0 -610 1.95E-09 +0 0 -611 1.95E-09 +0 0 -612 1.95E-09 +0 0 -613 1.95E-09 +0 0 -614 1.95E-09 +0 0 -615 1.95E-09 +0 0 -616 1.95E-09 +0 0 -617 1.95E-09 +0 0 -618 1.95E-09 +0 0 -619 1.95E-09 +0 0 -620 1.95E-09 +0 0 -621 1.95E-09 +0 0 -622 1.95E-09 +0 0 -623 1.95E-09 +0 0 -624 1.95E-09 +0 0 -625 1.95E-09 +0 0 -626 1.95E-09 +0 0 -627 1.95E-09 +0 0 -628 1.95E-09 +0 0 -629 1.95E-09 +0 0 -630 1.95E-09 +0 0 -631 1.95E-09 +0 0 -632 1.95E-09 +0 0 -633 1.95E-09 +0 0 -634 1.95E-09 +0 0 -635 1.95E-09 +0 0 -636 1.95E-09 +0 0 -637 1.95E-09 +0 0 -638 1.95E-09 +0 0 -639 1.95E-09 +0 0 -640 1.95E-09 +0 0 -641 1.95E-09 +0 0 -642 1.95E-09 +0 0 -643 1.95E-09 +0 0 -644 1.95E-09 +0 0 -645 1.95E-09 +0 0 -646 1.95E-09 +0 0 -647 1.95E-09 +0 0 -648 1.95E-09 +0 0 -649 1.95E-09 +0 0 -650 1.95E-09 +0 0 -651 1.95E-09 +0 0 -652 1.95E-09 +0 0 -653 1.95E-09 +0 0 -654 1.95E-09 +0 0 -655 1.95E-09 +0 0 -656 1.95E-09 +0 0 -657 1.95E-09 +0 0 -658 1.95E-09 +0 0 -659 1.95E-09 +0 0 -660 1.95E-09 +0 0 -661 1.95E-09 +0 0 -662 1.95E-09 +0 0 -663 1.95E-09 +0 0 -664 1.95E-09 +0 0 -665 1.95E-09 +0 0 -666 1.95E-09 +0 0 -667 1.95E-09 +0 0 -668 1.95E-09 +0 0 -669 1.95E-09 +0 0 -670 1.95E-09 +0 0 -671 1.95E-09 +0 0 -672 1.95E-09 +0 0 -673 1.95E-09 +0 0 -674 1.95E-09 +0 0 -675 1.95E-09 +0 0 -676 1.95E-09 +0 0 -677 1.95E-09 +0 0 -678 1.95E-09 +0 0 -679 1.95E-09 +0 0 -680 1.95E-09 +0 0 -681 1.95E-09 +0 0 -682 1.95E-09 +0 0 -683 1.95E-09 +0 0 -684 1.95E-09 +0 0 -685 1.95E-09 +0 0 -686 1.95E-09 +0 0 -687 1.95E-09 +0 0 -688 1.95E-09 +0 0 -689 1.95E-09 +0 0 -690 1.95E-09 +0 0 -691 1.95E-09 +0 0 -692 1.95E-09 +0 0 -693 1.95E-09 +0 0 -694 1.95E-09 +0 0 -695 1.95E-09 +0 0 -696 1.95E-09 +0 0 -697 1.95E-09 +0 0 -698 1.95E-09 +0 0 -699 1.95E-09 +0 0 -700 1.95E-09 +0 0 -701 1.95E-09 +0 0 -702 1.95E-09 +0 0 -703 1.95E-09 +0 0 -704 1.95E-09 +0 0 -705 1.95E-09 +0 0 -706 1.95E-09 +0 0 -707 1.95E-09 +0 0 -708 1.95E-09 +0 0 -709 1.95E-09 +0 0 -710 1.95E-09 +0 0 -711 1.95E-09 +0 0 -712 1.95E-09 +0 0 -713 1.95E-09 +0 0 -714 1.95E-09 +0 0 -715 1.95E-09 +0 0 -716 1.95E-09 +0 0 -717 1.95E-09 +0 0 -718 1.95E-09 +0 0 -719 1.95E-09 +0 0 -720 1.95E-09 +0 0 -721 1.95E-09 +0 0 -722 1.95E-09 +0 0 -723 1.95E-09 +0 0 -724 1.95E-09 +0 0 -725 1.95E-09 +0 0 -726 1.95E-09 +0 0 -727 1.95E-09 +0 0 -728 1.95E-09 +0 0 -729 1.95E-09 +0 0 -730 1.95E-09 +0 0 -731 1.95E-09 +0 0 -732 1.95E-09 +0 0 -733 1.95E-09 +0 0 -734 1.95E-09 +0 0 -735 1.95E-09 +0 0 -736 1.95E-09 +0 0 -737 1.95E-09 +0 0 -738 1.95E-09 +0 0 -739 1.95E-09 +0 0 -740 1.95E-09 +0 0 -741 1.95E-09 +0 0 -742 1.95E-09 +0 0 -743 1.95E-09 +0 0 -744 1.95E-09 +0 0 -745 1.95E-09 +0 0 -746 1.95E-09 +0 0 -747 1.95E-09 +0 0 -748 1.95E-09 +0 0 -749 1.95E-09 +0 0 -750 1.95E-09 +0 0 -751 1.95E-09 +0 0 -752 1.95E-09 +0 0 -753 1.95E-09 +0 0 -754 1.95E-09 +0 0 -755 1.95E-09 +0 0 -756 1.95E-09 +0 0 -757 1.95E-09 +0 0 -758 1.95E-09 +0 0 -759 1.95E-09 +0 0 -760 1.95E-09 +0 0 -761 1.95E-09 +0 0 -762 1.95E-09 +0 0 -763 1.95E-09 +0 0 -764 1.95E-09 +0 0 -765 1.95E-09 +0 0 -766 1.95E-09 +0 0 -767 1.95E-09 +0 0 -768 1.95E-09 +0 0 -769 1.95E-09 +0 0 -770 1.95E-09 +0 0 -771 1.95E-09 +0 0 -772 1.95E-09 +0 0 -773 1.95E-09 +0 0 -774 1.95E-09 +0 0 -775 1.95E-09 +0 0 -776 1.95E-09 +0 0 -777 1.95E-09 +0 0 -778 1.95E-09 +0 0 -779 1.95E-09 +0 0 -780 1.95E-09 +0 0 -781 1.95E-09 +0 0 -782 1.95E-09 +0 0 -783 1.95E-09 +0 0 -784 1.95E-09 +0 0 -785 1.95E-09 +0 0 -786 1.95E-09 +0 0 -787 1.95E-09 +0 0 -788 1.95E-09 +0 0 -789 1.95E-09 +0 0 -790 1.95E-09 +0 0 -791 1.95E-09 +0 0 -792 1.95E-09 +0 0 -793 1.95E-09 +0 0 -794 1.95E-09 +0 0 -795 1.95E-09 +0 0 -796 1.95E-09 +0 0 -797 1.95E-09 +0 0 -798 1.95E-09 +0 0 -799 1.95E-09 +0 0 -800 1.95E-09 +0 0 -801 1.95E-09 +0 0 -802 1.95E-09 +0 0 -803 1.95E-09 +0 0 -804 1.95E-09 +0 0 -805 1.95E-09 +0 0 -806 1.95E-09 +0 0 -807 1.95E-09 +0 0 -808 1.95E-09 +0 0 -809 1.95E-09 +0 0 -810 1.95E-09 +0 0 -811 1.95E-09 +0 0 -812 1.95E-09 +0 0 -813 1.95E-09 +0 0 -814 1.95E-09 +0 0 -815 1.95E-09 +0 0 -816 1.95E-09 +0 0 -817 1.95E-09 +0 0 -818 1.95E-09 +0 0 -819 1.95E-09 +0 0 -820 1.95E-09 +0 0 -821 1.95E-09 +0 0 -822 1.95E-09 +0 0 -823 1.95E-09 +0 0 -824 1.95E-09 +0 0 -825 1.95E-09 +0 0 -826 1.95E-09 +0 0 -827 1.95E-09 +0 0 -828 1.95E-09 +0 0 -829 1.95E-09 +0 0 -830 1.95E-09 +0 0 -831 1.95E-09 +0 0 -832 1.95E-09 +0 0 -833 1.95E-09 +0 0 -834 1.95E-09 +0 0 -835 1.95E-09 +0 0 -836 1.95E-09 +0 0 -837 1.95E-09 +0 0 -838 1.95E-09 +0 0 -839 1.95E-09 +0 0 -840 1.95E-09 +0 0 -841 1.95E-09 +0 0 -842 1.95E-09 +0 0 -843 1.95E-09 +0 0 -844 1.95E-09 +0 0 -845 1.95E-09 +0 0 -846 1.95E-09 +0 0 -847 1.95E-09 +0 0 -848 1.95E-09 +0 0 -849 1.95E-09 +0 0 -850 1.95E-09 +0 0 -851 1.95E-09 +0 0 -852 1.95E-09 +0 0 -853 1.95E-09 +0 0 -854 1.95E-09 +0 0 -855 1.95E-09 +0 0 -856 1.95E-09 +0 0 -857 1.95E-09 +0 0 -858 1.95E-09 +0 0 -859 1.95E-09 +0 0 -860 1.95E-09 +0 0 -861 1.95E-09 +0 0 -862 1.95E-09 +0 0 -863 1.95E-09 +0 0 -864 1.95E-09 +0 0 -865 1.95E-09 +0 0 -866 1.95E-09 +0 0 -867 1.95E-09 +0 0 -868 1.95E-09 +0 0 -869 1.95E-09 +0 0 -870 1.95E-09 +0 0 -871 1.95E-09 +0 0 -872 1.95E-09 +0 0 -873 1.95E-09 +0 0 -874 1.95E-09 +0 0 -875 1.95E-09 +0 0 -876 1.95E-09 +0 0 -877 1.95E-09 +0 0 -878 1.95E-09 +0 0 -879 1.95E-09 +0 0 -880 1.95E-09 +0 0 -881 1.95E-09 +0 0 -882 1.95E-09 +0 0 -883 1.95E-09 +0 0 -884 1.95E-09 +0 0 -885 1.95E-09 +0 0 -886 1.95E-09 +0 0 -887 1.95E-09 +0 0 -888 1.95E-09 +0 0 -889 1.95E-09 +0 0 -890 1.95E-09 +0 0 -891 1.95E-09 +0 0 -892 1.95E-09 +0 0 -893 1.95E-09 +0 0 -894 1.95E-09 +0 0 -895 1.95E-09 +0 0 -896 1.95E-09 +0 0 -897 1.95E-09 +0 0 -898 1.95E-09 +0 0 -899 1.95E-09 +0 0 -900 1.95E-09 +0 0 -901 1.95E-09 +0 0 -902 1.95E-09 +0 0 -903 1.95E-09 +0 0 -904 1.95E-09 +0 0 -905 1.95E-09 +0 0 -906 1.95E-09 +0 0 -907 1.95E-09 +0 0 -908 1.95E-09 +0 0 -909 1.95E-09 +0 0 -910 1.95E-09 +0 0 -911 1.95E-09 +0 0 -912 1.95E-09 +0 0 -913 1.95E-09 +0 0 -914 1.95E-09 +0 0 -915 1.95E-09 +0 0 -916 1.95E-09 +0 0 -917 1.95E-09 +0 0 -918 1.95E-09 +0 0 -919 1.95E-09 +0 0 -920 1.95E-09 +0 0 -921 1.95E-09 +0 0 -922 1.95E-09 +0 0 -923 1.95E-09 +0 0 -924 1.95E-09 +0 0 -925 1.95E-09 +0 0 -926 1.95E-09 +0 0 -927 1.95E-09 +0 0 -928 1.95E-09 +0 0 -929 1.95E-09 +0 0 -930 1.95E-09 +0 0 -931 1.95E-09 +0 0 -932 1.95E-09 +0 0 -933 1.95E-09 +0 0 -934 1.95E-09 +0 0 -935 1.95E-09 +0 0 -936 1.95E-09 +0 0 -937 1.95E-09 +0 0 -938 1.95E-09 +0 0 -939 1.95E-09 +0 0 -940 1.95E-09 +0 0 -941 1.95E-09 +0 0 -942 1.95E-09 +0 0 -943 1.95E-09 +0 0 -944 1.95E-09 +0 0 -945 1.95E-09 +0 0 -946 1.95E-09 +0 0 -947 1.95E-09 +0 0 -948 1.95E-09 +0 0 -949 1.95E-09 +0 0 -950 1.95E-09 +0 0 -951 1.95E-09 +0 0 -952 1.95E-09 +0 0 -953 1.95E-09 +0 0 -954 1.95E-09 +0 0 -955 1.95E-09 +0 0 -956 1.95E-09 +0 0 -957 1.95E-09 +0 0 -958 1.95E-09 +0 0 -959 1.95E-09 +0 0 -960 1.95E-09 +0 0 -961 1.95E-09 +0 0 -962 1.95E-09 +0 0 -963 1.95E-09 +0 0 -964 1.95E-09 +0 0 -965 1.95E-09 +0 0 -966 1.95E-09 +0 0 -967 1.95E-09 +0 0 -968 1.95E-09 +0 0 -969 1.95E-09 +0 0 -970 1.95E-09 +0 0 -971 1.95E-09 +0 0 -972 1.95E-09 +0 0 -973 1.95E-09 +0 0 -974 1.95E-09 +0 0 -975 1.95E-09 +0 0 -976 1.95E-09 +0 0 -977 1.95E-09 +0 0 -978 1.95E-09 +0 0 -979 1.95E-09 +0 0 -980 1.95E-09 +0 0 -981 1.95E-09 +0 0 -982 1.95E-09 +0 0 -983 1.95E-09 +0 0 -984 1.95E-09 +0 0 -985 1.95E-09 +0 0 -986 1.95E-09 +0 0 -987 1.95E-09 +0 0 -988 1.95E-09 +0 0 -989 1.95E-09 +0 0 -990 1.95E-09 +0 0 -991 1.95E-09 +0 0 -992 1.95E-09 +0 0 -993 1.95E-09 +0 0 -994 1.95E-09 +0 0 -995 1.95E-09 +0 0 -996 1.95E-09 +0 0 -997 1.95E-09 +0 0 -998 1.95E-09 +0 0 -999 1.95E-09 +0 0 -1000 1.95E-09 diff --git a/data/homo/test_07/TMPL_velocity_model.txt b/data/homo/test_07/TMPL_velocity_model.txt index 3efda3c..fa185a6 100644 --- a/data/homo/test_07/TMPL_velocity_model.txt +++ b/data/homo/test_07/TMPL_velocity_model.txt @@ -1,2 +1,2 @@ -Depth(m) Vp(m/s) Vp/Vs Qp Qs density(kg/m^3) Ep(anisotropy) Dt(anisotropy) Gm(anisotropy) -40000 4300 1.73 100 50 2300 0.0 0.0 0.0 +Depth(m) Vp(m/s) Vp/Vs Qp Qs density(kg/m^3) Ep(anisotropy) Dt(anisotropy) Gm(anisotropy) +40000 2370 2 100 100 2500 0.0 0.0 0.0 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..136b58a --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,26 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile cleanall + +# Custom clean target +cleanall: + @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @rm -rf $(SOURCEDIR)/api/generated + @rm -rf $(SOURCEDIR)/gallery + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..7dae404 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,43 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help +if "%1" == "cleanall" goto cleanall + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:cleanall +%SPHINXBUILD% -M clean %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +if exist %SOURCEDIR%\api\generated rmdir /s /q %SOURCEDIR%\api\generated +if exist %SOURCEDIR%\examples rmdir /s /q %SOURCEDIR%\examples +echo Cleaned Sphinx build and removed %SOURCEDIR%\api\generated and %SOURCEDIR%\examples directory. +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_ext/bibliography.py b/docs/source/_ext/bibliography.py new file mode 100644 index 0000000..3904c6e --- /dev/null +++ b/docs/source/_ext/bibliography.py @@ -0,0 +1,45 @@ +"""Send every citation to a bibliography that the current build writes. + +The HTML pages list their own references at the bottom, inside ``.. only:: html``, +while the PDF collects all of them in a single ``.. only:: latex`` bibliography, which +Sphinx moves to the end of the document. sphinxcontrib-bibtex resolves each citation to +one bibliography holding its key, chosen without regard to the builder, so the PDF +linked to lists that only the HTML has, and the HTML could link to the list that only +the PDF has. Once sphinxcontrib-bibtex has collected the citations, this extension drops +those of the bibliographies that the current builder leaves out, so that every citation +resolves to a bibliography that is actually written. +""" + +from sphinx import addnodes +from sphinxcontrib.bibtex.nodes import bibliography + + +def _is_written(node, tags): + """Whether no enclosing ``only`` directive excludes the node from this build.""" + while node is not None: + if isinstance(node, addnodes.only) and not tags.eval_condition(node["expr"]): + return False + node = node.parent + return True + + +def _drop_unwritten_citations(app, env): + domain = env.get_domain("cite") + unwritten = set() + for docname in {key.docname for key in domain.bibliographies}: + for node in env.get_doctree(docname).findall(bibliography): + if not _is_written(node, app.builder.tags): + unwritten.add((docname, node["ids"][0])) + domain.citations[:] = [ + citation + for citation in domain.citations + if (citation.bibliography_key.docname, citation.bibliography_key.id_) + not in unwritten + ] + return [] + + +def setup(app): + # sphinxcontrib-bibtex collects the citations on env-updated at the default priority + app.connect("env-updated", _drop_unwritten_citations, priority=900) + return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/docs/source/_ext/changelog.py b/docs/source/_ext/changelog.py new file mode 100644 index 0000000..479e213 --- /dev/null +++ b/docs/source/_ext/changelog.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +from pathlib import Path +import re + + +_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") +# A markdown code span closes with as many backticks as it opened with, so both `x` +# and ``x`` become an rst literal +_INLINE_CODE_RE = re.compile(r"(`+)(.+?)\1") +# Keep a Changelog writes a version heading as a link reference, "## [v1.1.0] - date", +# that a "[v1.1.0]: url" definition at the end of the file resolves +_LINK_DEFINITION_RE = re.compile(r"^\[([^\]]+)\]:\s*(\S+)\s*$") +_LINKED_TITLE_RE = re.compile(r"^\[([^\]]+)\](?!\()(.*)$") +_COMPARE_URL_RE = re.compile(r"/compare/(.+?)\.\.\.") +_HEADING_MARKERS = { + 2: "^", + 3: '"', +} + +_PAGE_HEADER = """.. _changelog: + +========= +Changelog +========= + +.. This page is generated from CHANGELOG.md by docs/source/_ext/changelog.py when the + documentation is built, so that the changelog has a single source. Do not edit it by + hand, and do not commit it: edit CHANGELOG.md instead. + +The format is based on `Keep a Changelog `_, +and this project adheres to `Semantic Versioning `_. + +""" + + +def _convert_inline_markdown(text: str) -> str: + text = _INLINE_CODE_RE.sub(r"``\2``", text) + return _LINK_RE.sub(r"`\1 <\2>`_", text) + + +def _version_link(url: str) -> str: + """Word the link under a version heading after the page it opens.""" + where = " on GitHub" if "github.com" in url else "" + compare = _COMPARE_URL_RE.search(url) + text = f"Compare with {compare.group(1)}" if compare else "Release" + return f"`{text}{where} <{url}>`__" + + +def _convert_heading(line: str, links: dict[str, str]) -> list[str] | None: + match = re.match(r"^(#{2,3})\s+(.*)$", line) + if not match: + return None + + level = len(match.group(1)) + title = match.group(2).strip() + url = None + linked = _LINKED_TITLE_RE.match(title) + if linked: + title = linked.group(1) + linked.group(2) + url = links.get(linked.group(1).lower()) + title = _convert_inline_markdown(title) + marker = _HEADING_MARKERS.get(level, "-") + block = ["", title, marker * len(title), ""] + if url: + block += [_version_link(url), ""] + return block + + +def markdown_changelog_to_rst(markdown_text: str) -> str: + output: list[str] = [] + in_releases = False + # The link definitions are placed under their version headings instead of being + # printed at the end; markdown matches link labels case-insensitively + links = { + definition.group(1).lower(): definition.group(2) + for definition in map(_LINK_DEFINITION_RE.match, markdown_text.splitlines()) + if definition + } + + for line in markdown_text.splitlines(): + if not in_releases: + if re.match(r"^##\s+", line): + in_releases = True + else: + continue + + if line.startswith("# ") or _LINK_DEFINITION_RE.match(line): + continue + + heading_block = _convert_heading(line, links) + if heading_block is not None: + output.extend(heading_block) + continue + + stripped = line.lstrip() + indent = line[: len(line) - len(stripped)] + + if stripped.startswith(("- ", "* ")): + bullet = stripped[2:] + output.append(f"{indent}- {_convert_inline_markdown(bullet)}") + continue + + output.append(_convert_inline_markdown(line)) + + return "\n".join(output).lstrip() + "\n" + + +def generate_changelog_page(project_root: Path, docs_source: Path) -> Path: + """Render CHANGELOG.md into docs/source/changelog.rst. + + The whole page is generated, so it is listed in .gitignore and never committed. + """ + changelog_md = project_root / "CHANGELOG.md" + generated_file = docs_source / "changelog.rst" + + body = markdown_changelog_to_rst(changelog_md.read_text(encoding="utf-8")) + generated_file.write_text(_PAGE_HEADER + body, encoding="utf-8") + return generated_file diff --git a/docs/source/_static/apple-touch-icon.png b/docs/source/_static/apple-touch-icon.png new file mode 100644 index 0000000..c562a48 Binary files /dev/null and b/docs/source/_static/apple-touch-icon.png differ diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css new file mode 100644 index 0000000..83c27ce --- /dev/null +++ b/docs/source/_static/css/custom.css @@ -0,0 +1,106 @@ +/* PyNetDesign brand styling for the pydata-sphinx-theme. + * + * Palette (brand guide v1.0): + * Network Gray #5F6160 + * Signal Orange #FF6A1A + * White #FFFFFF + * Website surfaces: #F3F4F5 light page, #FFFFFF light card, + * #29313D dark card header, #222832 dark card body. + * + * Signal Orange reaches only 2.87:1 on white, so it is used for accents rather than + * for body or link text in light mode; link text is darkened to #B84806, which gives + * 4.81:1 on #F3F4F5. On the dark surfaces the brand orange itself is compliant + * (5.17:1 on #222832), so it is used unchanged there. + */ + +:root { + --pnd-gray: #5F6160; + --pnd-orange: #FF6A1A; + --pnd-orange-text-light: #B84806; + --pnd-surface-light: #F3F4F5; + --pnd-surface-dark: #222832; + --pnd-surface-dark-header: #29313D; +} + +html[data-theme="light"] { + --pst-color-primary: var(--pnd-orange-text-light); + --pst-color-primary-highlight: #993C04; + --pst-color-secondary: var(--pnd-gray); + --pst-color-secondary-highlight: #4A4C4B; + --pst-color-link: var(--pnd-orange-text-light); + --pst-color-link-hover: #993C04; + --pst-color-accent: var(--pnd-orange); + --pst-color-inline-code-links: var(--pnd-orange-text-light); +} + +html[data-theme="dark"] { + --pst-color-primary: var(--pnd-orange); + --pst-color-primary-highlight: #FF7F3D; + --pst-color-secondary: #C9CBCA; + --pst-color-secondary-highlight: #E2E4E3; + --pst-color-link: var(--pnd-orange); + --pst-color-link-hover: #FF9159; + --pst-color-accent: var(--pnd-orange); + --pst-color-inline-code-links: var(--pnd-orange); +} + +/* The supplied logo carries its own clear space, so it needs no extra padding. + * Per the brand guide the gray must not be inverted or filtered by a theme, which is + * why the dark variant is a separate file with a white holding area rather than a + * CSS filter on the light one. */ +.navbar-brand img, +.navbar-brand .logo__image { + max-height: 2.6rem; +} + +/* Orange accent on the active navigation item and on section anchors */ +.bd-sidebar-primary .current > a, +.bd-sidebar-secondary .toc-entry > .nav-link.active { + border-left-color: var(--pnd-orange); +} + +/* Keep wide tables and generated API signatures readable */ +.bd-content table { + display: table; + width: 100%; +} + +.bd-content table.docutils { + table-layout: auto; +} + +/* Allow long equations to scroll rather than overflow the page */ +div.math { + overflow-x: auto; + overflow-y: hidden; +} + +/* Give sphinx-gallery thumbnails a little more room */ +.sphx-glr-thumbcontainer { + margin: 6px 8px; +} + +/* The logo on the overview page carries its own clear space, so it must not pick up a + card background or a shadow from the theme */ +.pynetdesign-index-logo, +.pynetdesign-index-logo a, +.pynetdesign-index-logo img { + background: transparent !important; + box-shadow: none !important; +} + +/* Hover state for the header logo: swap in the brand's own "orange only" lockup, which + the brand guide lists beside the colour and gray-only stems. The two files carry the + same geometry at the same viewBox and differ only in their fill and stroke colours, so + the mark neither moves nor resizes. + + This replaces the image rather than revealing a second copy underneath it. Stacking + the two fringes the result: glyph edges are anti-aliased, so every edge pixel of the + top logo is partly transparent and lets the layer behind show through, drawing an + orange halo around the gray wordmark even at rest. Only one layer is ever painted + here, so there is no halo and no crossfade to blend. Browsers that do not replace + content this way simply leave the logo unchanged on hover */ +.navbar-brand.logo:hover .logo__image { + content: url("../pynetdesign-logo-orange.svg"); +} + diff --git a/docs/source/_static/favicon.ico b/docs/source/_static/favicon.ico new file mode 100644 index 0000000..23797c5 Binary files /dev/null and b/docs/source/_static/favicon.ico differ diff --git a/docs/source/_static/favicon.svg b/docs/source/_static/favicon.svg new file mode 100644 index 0000000..97ba38d --- /dev/null +++ b/docs/source/_static/favicon.svg @@ -0,0 +1 @@ +PyNetDesign rounded-square iconApproved PyNetDesign identity. color artwork on a transparent canvas.{"brand": "PyNetDesign", "revision": "1.0", "base": "64-bold-technical", "gray": "#5F6160", "orange": "#FF6A1A", "mode": "color", "background": "transparent"} \ No newline at end of file diff --git a/docs/source/_static/pynetdesign-logo-orange.svg b/docs/source/_static/pynetdesign-logo-orange.svg new file mode 100644 index 0000000..b49a4cc --- /dev/null +++ b/docs/source/_static/pynetdesign-logo-orange.svg @@ -0,0 +1 @@ +PyNetDesign logoApproved PyNetDesign identity. orange artwork on a transparent canvas.{"brand": "PyNetDesign", "revision": "1.0", "base": "64-bold-technical", "gray": "#5F6160", "orange": "#FF6A1A", "mode": "orange", "background": "transparent"} \ No newline at end of file diff --git a/docs/source/_static/pynetdesign-logo-white-bg.svg b/docs/source/_static/pynetdesign-logo-white-bg.svg new file mode 100644 index 0000000..fb3c08d --- /dev/null +++ b/docs/source/_static/pynetdesign-logo-white-bg.svg @@ -0,0 +1 @@ +PyNetDesign logoApproved PyNetDesign identity. color artwork on a white canvas.{"brand": "PyNetDesign", "revision": "1.0", "base": "64-bold-technical", "gray": "#5F6160", "orange": "#FF6A1A", "mode": "color", "background": "white"} \ No newline at end of file diff --git a/docs/source/_static/pynetdesign-logo.png b/docs/source/_static/pynetdesign-logo.png new file mode 100644 index 0000000..391aa30 Binary files /dev/null and b/docs/source/_static/pynetdesign-logo.png differ diff --git a/docs/source/_static/pynetdesign-logo.svg b/docs/source/_static/pynetdesign-logo.svg new file mode 100644 index 0000000..8473f2d --- /dev/null +++ b/docs/source/_static/pynetdesign-logo.svg @@ -0,0 +1 @@ +PyNetDesign logoApproved PyNetDesign identity. color artwork on a transparent canvas.{"brand": "PyNetDesign", "revision": "1.0", "base": "64-bold-technical", "gray": "#5F6160", "orange": "#FF6A1A", "mode": "color", "background": "transparent"} \ No newline at end of file diff --git a/docs/source/_static/site.webmanifest b/docs/source/_static/site.webmanifest new file mode 100644 index 0000000..4c37ffa --- /dev/null +++ b/docs/source/_static/site.webmanifest @@ -0,0 +1,20 @@ +{ + "name": "PyNetDesign", + "short_name": "PyNetDesign", + "background_color": "#FFFFFF", + "theme_color": "#5F6160", + "icons": [ + { + "src": "web-app-icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "web-app-icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + } + ] +} \ No newline at end of file diff --git a/docs/source/_static/web-app-icon-192x192.png b/docs/source/_static/web-app-icon-192x192.png new file mode 100644 index 0000000..c5b5ea3 Binary files /dev/null and b/docs/source/_static/web-app-icon-192x192.png differ diff --git a/docs/source/_static/web-app-icon-512x512.png b/docs/source/_static/web-app-icon-512x512.png new file mode 100644 index 0000000..8b6d881 Binary files /dev/null and b/docs/source/_static/web-app-icon-512x512.png differ diff --git a/docs/source/_templates/autosummary/base.rst b/docs/source/_templates/autosummary/base.rst new file mode 100644 index 0000000..837a5d5 --- /dev/null +++ b/docs/source/_templates/autosummary/base.rst @@ -0,0 +1,9 @@ +{{ fullname | escape | underline }} + +.. currentmodule:: {{ module }} + +.. auto{{ objtype }}:: {{ objname }} + +.. raw:: html + +
diff --git a/docs/source/_templates/autosummary/class.rst b/docs/source/_templates/autosummary/class.rst new file mode 100644 index 0000000..dc3aeb3 --- /dev/null +++ b/docs/source/_templates/autosummary/class.rst @@ -0,0 +1,42 @@ +{{ fullname | escape | underline }} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + + .. rubric:: Methods + + .. autosummary:: + {% for item in methods %} + ~{{ objname }}.{{ item }} + {%- endfor %} + +{# .. rubric:: Attributes#} +{##} +{# .. autosummary::#} +{# {% for item in attributes %}#} +{# ~{{ objname }}.{{ item }}#} +{# {%- endfor %}#} + +.. raw:: html + +
+ + +.. include:: backreferences/{{ fullname }}.examples + +.. raw:: html + +
+ + +{#{% for item in methods %} #} +{#{% if item != '__init__' %} #} +{#.. automethod:: {{ objname }}.{{ item }} #} +{#{% endif %} #} +{#{% endfor %} #} + +{#.. raw:: html #} +{# #} +{#
#} + diff --git a/docs/source/_templates/autosummary/exception.rst b/docs/source/_templates/autosummary/exception.rst new file mode 100644 index 0000000..24f0ecb --- /dev/null +++ b/docs/source/_templates/autosummary/exception.rst @@ -0,0 +1,11 @@ +{{ fullname | escape | underline }} + +.. currentmodule:: {{ module }} + +.. autoexception:: {{ objname }} + + +.. raw:: html + +
+ diff --git a/docs/source/_templates/autosummary/function.rst b/docs/source/_templates/autosummary/function.rst new file mode 100644 index 0000000..e4ae375 --- /dev/null +++ b/docs/source/_templates/autosummary/function.rst @@ -0,0 +1,12 @@ +{{ fullname | escape | underline }} + +.. currentmodule:: {{ module }} + +.. autofunction:: {{ objname }} + +.. include:: backreferences/{{ fullname }}.examples + +.. raw:: html + +
+ diff --git a/docs/source/_templates/autosummary/module.rst b/docs/source/_templates/autosummary/module.rst new file mode 100644 index 0000000..3b1fbde --- /dev/null +++ b/docs/source/_templates/autosummary/module.rst @@ -0,0 +1,54 @@ +.. raw:: html + +
+ +``{{ fullname }}`` +{% for i in range(fullname|length + 15) %}-{% endfor %} + +.. raw:: html + +
+ +.. automodule:: {{ fullname }} + +{% block classes %} +{% if classes %} +.. rubric:: Classes + +.. autosummary:: + :toctree: ./ +{% for item in classes %} + {{ fullname }}.{{ item }} +{% endfor %} +{% endif %} +{% endblock %} + + +{% block functions %} +{% if functions %} +.. rubric:: Functions + +.. autosummary:: + :toctree: ./ +{% for item in functions %} + {{ fullname }}.{{ item }} +{% endfor %} +{% endif %} +{% endblock %} + + +{% block exceptions %} +{% if exceptions %} +.. rubric:: Exceptions + +.. autosummary:: + :toctree: ./ +{% for item in exceptions %} + {{ fullname }}.{{ item }} +{% endfor %} +{% endif %} +{% endblock %} + +.. raw:: html + +
diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html new file mode 100644 index 0000000..c607af4 --- /dev/null +++ b/docs/source/_templates/layout.html @@ -0,0 +1,13 @@ +{# Import the theme's layout. #} +{% extends "!layout.html" %} + +{% block extrahead %} + {{ super() }} + {# Icon set from the PyNetDesign brand package (brand guide, section 9). + html_favicon already emits the .ico link; these add the scalable icon, the + Apple touch icon and the web app manifest. #} + + + + +{% endblock %} diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst new file mode 100644 index 0000000..2f67dee --- /dev/null +++ b/docs/source/api/index.rst @@ -0,0 +1,215 @@ +.. _api: + +============= +API Reference +============= + +The Application Programming Interface (API) of PyNetDesign is composed of the following +modules: + +* :ref:`modelling`: algorithms for modelling related to microseismic network design + + * :ref:`core`: high-level drivers for magnitude sensitivity + * :ref:`homogeneous`: algorithms for a homogeneous velocity model + * :ref:`geometry_modelling`: synthetic geometry generators + * :ref:`io`: reading and writing geometry and velocity files + * :ref:`classes`: parameter containers + * :ref:`utils_modelling`: wave modes, radiation patterns, amplitudes and grid helpers + +* :ref:`visualization`: plotting of results and input + + * :ref:`geometry`: receiver geometry + * :ref:`sensitivity`: magnitude sensitivity results + * :ref:`utils_visualization`: general utility functions for visualization + +---- + +.. _modelling: + +Modelling +========= + +.. _core: + +Core +---- + +.. currentmodule:: pynetdesign.modelling.core + +.. autosummary:: + :toctree: generated/ + + get_mag_sensitivity + get_mag_sensitivity_local + +.. _homogeneous: + +Homogeneous +----------- + +.. currentmodule:: pynetdesign.modelling.homo + +.. autosummary:: + :toctree: generated/ + + mag_sensitivity_grid + calculate_M0 + calculate_fpeak + validate_parameters + +.. _geometry_modelling: + +Geometry generators +------------------- + +.. currentmodule:: pynetdesign.modelling.geometry + +.. autosummary:: + :toctree: generated/ + + generate_geometry + generate_borehole_geometry + generate_surface_geometry + generate_darkfiber_geometry + +.. _io: + +Input / output +-------------- + +.. currentmodule:: pynetdesign.modelling.io + +.. autosummary:: + :toctree: generated/ + + read_geometry + save_geometry + read_velocity + combine_geometry + is_combined_geometry + decimate_geometry + global2local + +.. _classes: + +Classes +------- + +.. currentmodule:: pynetdesign.modelling.classes + +.. autosummary:: + :toctree: generated/ + + DetectionParameters + +.. _utils_modelling: + +Modelling utilities +------------------- + +Wave modes and radiation patterns +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. currentmodule:: pynetdesign.modelling.utils + +.. autosummary:: + :toctree: generated/ + + normalize_wave_mode + wave_mode_phases + wave_mode_has_p + wave_mode_has_s + wave_mode_s_phases + phase_radiation_pattern + resolve_radiation_pattern + +Magnitudes and amplitudes +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + calculate_Mw + calculate_seismic_moment + get_scaling_displacement + retrieve_min_amps + +Detectability and receiver response +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + mag_detectable + check_min_stations + compute_free_surface_coefficients + get_phase_station_directionality + geometry_requires_receiver_projection + get_ray_station_directionality + +Grids and helpers +^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: generated/ + + generate_grid + reshape_grid + reshape_data + get_slice_coordinates + are_points_inside + find_indices_of_points + nth_minimum + check_zero_values + contains_only_numeric + compute_gcd_of_floats + +---- + +.. _visualization: + +Visualization +============= + +.. _geometry: + +Geometry +-------- + +.. currentmodule:: pynetdesign.visualization.geometry + +.. autosummary:: + :toctree: generated/ + + plot_stations_view + +.. _sensitivity: + +Sensitivity +----------- + +.. currentmodule:: pynetdesign.visualization.sensitivity + +.. autosummary:: + :toctree: generated/ + + plot_sensitivity_slice + +.. _utils_visualization: + +Visualization utilities +----------------------- + +.. currentmodule:: pynetdesign.visualization.utils + +.. autosummary:: + :toctree: generated/ + + generate_colorscale + is_notebook + check_spyder + +.. only:: latex + + .. bibliography:: + :style: unsrt diff --git a/docs/source/citing.rst b/docs/source/citing.rst new file mode 100644 index 0000000..e161de3 --- /dev/null +++ b/docs/source/citing.rst @@ -0,0 +1,66 @@ +.. _citing: + +====== +Citing +====== + +If you use PyNetDesign in your research, please cite it as follows: + +| **Anikiev, D. (2026).** *PyNetDesign: a Python framework for microseismic monitoring network design.* Zenodo. +| https://doi.org/10.5281/zenodo.22695001 + +.. admonition:: In BibTeX format for the collective DOI + :class: tip + :collapsible: closed + + .. code-block:: bibtex + + @software{Anikiev2026PyNetDesign, + author = {Anikiev, Denis}, + title = {{PyNetDesign}: a {P}ython framework for microseismic monitoring network design}, + year = {2026}, + publisher = {Zenodo}, + doi = {10.5281/zenodo.22695001}, + url = {https://github.com/danikiev/PyNetDesign} + } + +The DOI above represents all versions, and will always resolve to the latest one. +To cite a particular version of PyNetDesign, please use the following format, for example +for version 1.0.1: + +| **Anikiev, D. (2026).** *PyNetDesign: a Python framework for microseismic monitoring network design (1.0.1).* Zenodo. +| https://doi.org/10.5281/zenodo.22695002 + +.. admonition:: In BibTeX format for a specific version + :class: tip + :collapsible: closed + + .. code-block:: bibtex + + @software{Anikiev2026PyNetDesignVersion, + author = {Anikiev, Denis}, + title = {{PyNetDesign}: a {P}ython framework for microseismic monitoring network design}, + year = {2026}, + publisher = {Zenodo}, + version = {1.0.1}, + license = {MIT}, + doi = {10.5281/zenodo.22695002}, + url = {https://github.com/danikiev/PyNetDesign} + } + +Earlier release +=============== + +Version 1.0.0 was deposited manually, before the automated GitHub to Zenodo integration +was enabled, and therefore belongs to a separate Zenodo lineage. It is cross-linked to +the current record and remains citable on its own: + +| **Anikiev, D. (2025).** *PyNetDesign: a Python framework for microseismic monitoring network design (v1.0.0)* [Computer software]. Zenodo. +| https://doi.org/10.5281/zenodo.14945917 + +.. warning:: + + Magnitudes computed with version 1.0.0 are affected by an error in the seismic moment + calculation that was corrected in version 1.0.1. If you are citing or reusing results + obtained with 1.0.0, please see the :ref:`changelog` and + `issue #3 `_. diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..df27047 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +"""Sphinx configuration for the PyNetDesign documentation.""" +import datetime +import os +import sys +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as pkg_version +from pathlib import Path + +from sphinx_gallery.sorting import ExampleTitleSortKey + +DOCS_SOURCE = Path(__file__).resolve().parent +PROJECT_ROOT = DOCS_SOURCE.parents[1] + +# Sphinx must be able to import the package for autodoc and for the version number, +# and the local extensions under _ext +sys.path.insert(0, str(DOCS_SOURCE)) +sys.path.insert(0, os.path.abspath("../..")) + +# Render CHANGELOG.md into changelog.rst, so the changelog has a single source. +# The generated page is gitignored; edit CHANGELOG.md instead. +from _ext.changelog import generate_changelog_page # noqa: E402 + +generate_changelog_page(PROJECT_ROOT, DOCS_SOURCE) + +# -- Project information ----------------------------------------------------- + +project = "PyNetDesign" +project_summary = "A Python framework for microseismic monitoring network design" +author = "Denis Anikiev" +year = datetime.date.today().year +copyright = f"{year}, {author}" + +try: + release = pkg_version("pynetdesign") +except PackageNotFoundError: + release = "dev" + +version = release.split("+")[0] +if version == "unknown": + version = "dev" + +# -- General configuration --------------------------------------------------- + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.mathjax", + "sphinx.ext.doctest", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_design", + "sphinxcontrib.bibtex", + "_ext.bibliography", + "matplotlib.sphinxext.plot_directive", + "numpydoc", + "sphinx_gallery.gen_gallery", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "**.ipynb_checkpoints", "**.ipynb", "**.md5"] +source_suffix = {".rst": "restructuredtext"} +source_encoding = "utf-8-sig" +root_doc = "index" +pygments_style = "default" +add_function_parentheses = False + +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), +} + +# -- autodoc / autosummary / numpydoc ---------------------------------------- + +autosummary_generate = True +autodoc_member_order = "bysource" +autodoc_default_options = {"members": True} +autodoc_typehints = "none" + +numpydoc_show_class_members = False +numpydoc_show_inherited_class_members = False +numpydoc_class_members_toctree = False + +# -- Bibliography ------------------------------------------------------------ + +bibtex_bibfiles = ["references.bib"] +bibtex_reference_style = "author_year" + +suppress_warnings = [ + "bibtex.duplicate_label", + "bibtex.duplicate_citation", + # sphinx_gallery_conf holds a sort-key class and therefore cannot be pickled into + # the config cache; harmless, and unrelated to the documentation content + "config.cache", + # Fontawesome glyphs are an HTML embellishment: every icon in these sources sits + # beside the link text that carries the meaning, so the PDF loses nothing by + # dropping them. Rendering them instead would need fontawesome5.sty, which lives in + # texlive-fonts-extra and is far too heavy to install for a few decorative glyphs + "design.fa-build", +] + +# -- Example gallery --------------------------------------------------------- + +sphinx_gallery_conf = { + # Where the example scripts live + "examples_dirs": ["../../examples"], + # Where the rendered gallery is written + "gallery_dirs": ["examples"], + # Run every example + "filename_pattern": r"\.py", + "download_all_examples": False, + "within_subsection_order": ExampleTitleSortKey, + "backreferences_dir": "api/generated/backreferences", + "doc_module": "pynetdesign", + "reference_url": {"pynetdesign": None}, +} + +# Always show the source that produced a figure +plot_include_source = True +plot_formats = ["png"] + +# -- HTML output ------------------------------------------------------------- + +html_theme = "pydata_sphinx_theme" +html_title = project +html_short_title = project +html_static_path = ["_static"] +html_css_files = ["css/custom.css"] +html_last_updated_fmt = "%b %d, %Y" +html_show_sourcelink = False + +# Branding, from the PyNetDesign brand package. The transparent logo is used on both +# the light and the dark theme, so the mark keeps its own clear space and is never +# filtered or recoloured by the theme. +html_logo = "_static/pynetdesign-logo.svg" +html_favicon = "_static/favicon.ico" + +# The documentation is also published as a single PDF. On GitHub Pages the site lives +# under a project subpath, so the link has to include the repository name there. +on_github_ci = os.environ.get("GITHUB_ACTIONS") == "true" +github_repository = os.environ.get("GITHUB_REPOSITORY", "") +github_repo_name = github_repository.split("/")[-1] if github_repository else "" + +if on_github_ci and github_repo_name: + pdf_url = f"/{github_repo_name}/_static/pynetdesign.pdf" +else: + pdf_url = "/_static/pynetdesign.pdf" + +html_theme_options = { + "logo": { + "image_light": "pynetdesign-logo.svg", + "image_dark": "pynetdesign-logo.svg", + "alt_text": "PyNetDesign", + }, + "icon_links": [ + { + "name": "GitHub", + "url": "https://github.com/danikiev/PyNetDesign", + "icon": "fab fa-github", + "type": "fontawesome", + }, + { + "name": "Download as PDF", + "url": pdf_url, + "icon": "fas fa-file-pdf", + "type": "fontawesome", + }, + ], + "footer_start": ["copyright"], + "footer_center": ["sphinx-version"], + "show_nav_level": 2, + "navigation_depth": 3, + "collapse_navigation": True, +} + +# Standalone pages read top to bottom, so they do not need a secondary sidebar +html_sidebars = { + "getting_started": [], + "methodology": [], + "contributing": [], + "changelog": [], + "citing": [], + "credits": [], +} + +rst_epilog = f""" +.. |year| replace:: {year} +""" + +# -- LaTeX / PDF output ------------------------------------------------------ + +# A single PDF of the whole documentation, published alongside the HTML as +# _static/pynetdesign.pdf and linked from the navigation bar and the landing page. +latex_documents = [ + (root_doc, "pynetdesign.tex", project, author, "manual"), +] + +# LaTeX cannot include SVG, so the raster logo is used for the title page +latex_logo = "_static/pynetdesign-logo.png" + +latex_elements = { + "releasename": "version", + # The PDF is read on screen, so a chapter starts on the next page instead of the + # next right-hand one, which could leave a blank page before it + "extraclassoptions": "openany", + # Sphinx 9.1.0 tables in the default 'colorrows' style stop the PDF build with + # "TeX capacity exceeded" since the June 2026 LaTeX release: with array v2.7, + # colortbl no longer keeps its own \everycr, which the Sphinx row colouring relies + # on (sphinx-doc/sphinx#14465). Where array v2.7 is installed, load the earlier + # array and longtable releases before sphinx.sty, as Sphinx 9.1.1 does itself. + "passoptionstopackages": r""" +\IfFileExists{array-2024-06-01.sty}{% + \RequirePackage{array}[=v2.6]% + \RequirePackage{longtable}[=v4.13]% +}{} +""", + "preamble": r""" +\usepackage{csquotes} +\usepackage[titles]{tocloft} +\usepackage{qrcode} +\AtEndDocument{% +\clearpage +\thispagestyle{empty} +\begin{center} +{\Large Online documentation}\par +\vspace{0.8cm} +\qrcode[height=5cm]{https://danikiev.github.io/PyNetDesign/}\par +\vspace{0.6cm} +\href{https://danikiev.github.io/PyNetDesign/}{danikiev.github.io/PyNetDesign} +\end{center} +} + """, + "maketitle": rf""" +\begin{{titlepage}} +\centering +\vspace*{{2.2cm}} +\begin{{center}} +\sphinxincludegraphics[width=0.8\textwidth]{{pynetdesign-logo.png}}\par +\end{{center}} +\vspace{{1.2cm}} +{{\Large {project_summary} \par}} +\vfill +{{\Large {author} \par}} +\vspace{{0.3cm}} +{{\large Version {release} \par}} +\vspace{{0.8cm}} +{{\large \today \par}} +\end{{titlepage}} +\clearpage +""", +} + +# lualatex handles the unicode used in the methodology and the bibliography +latex_engine = "lualatex" +latex_use_xindy = False diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst new file mode 100644 index 0000000..c5df889 --- /dev/null +++ b/docs/source/contributing.rst @@ -0,0 +1,80 @@ +.. _contributing: + +============ +Contributing +============ + +Contributions, bug reports and suggestions are welcome. + +Reporting a problem +=================== + +Please open an issue at +`github.com/danikiev/PyNetDesign/issues `_. +A useful report states the PyNetDesign version, the Python version, what you expected, +what happened instead, and a small script or input file that reproduces the behaviour. + +For a suspected error in the physics, the most useful report compares the computed result +against a closed-form expectation, because that makes the discrepancy measurable. + +Development setup +================= + +.. code-block:: bash + + git clone https://github.com/danikiev/PyNetDesign.git + cd PyNetDesign + conda env create -f environment.yml -n pnd + conda activate pnd + pip install -e ".[dev,docs]" + +Branches and releases +===================== + +Development happens on ``dev``, which is merged into ``main`` through a pull request. +Release tags follow strict semantic versioning as ``vMAJOR.MINOR.PATCH`` and must point +at a commit contained in ``main``; the version itself is derived from the tag by +``setuptools_scm``. The full procedure is described in ``RELEASE.md``. + +Tests +===== + +Run the suite from the repository root: + +.. code-block:: bash + + pytest pytests/ + +Continuous integration runs the same suite on Python 3.9 to 3.12, validates the Zenodo +metadata, and builds this documentation. + +Please add a test with any change to the algorithms. The convention in this project is to +assert against an independently written closed form rather than against a recorded +output, so that a test states the physics instead of freezing the current behaviour. + +Documentation +============= + +Build the documentation locally and view it with the helper scripts: + +.. code-block:: bash + + ./build-docs.sh # Linux, macOS; build-docs.bat on Windows + ./serve-docs.sh # rebuilds incrementally, then serves on port 8000 + +``serve-docs`` accepts ``--port`` and ``--no-build``, and both scripts pass +``SPHINXOPTS`` through to Sphinx. Without the scripts the equivalent is +``python -m sphinx -M html docs/source docs/build``. + +Docstrings follow the +`numpydoc `_ style; equations use ``.. math::`` and +literature is cited with ``:cite:t:`` or ``:cite:p:`` against +``docs/source/references.bib``. + +Changelog +========= + +``CHANGELOG.md`` follows `Keep a Changelog `_. Add +your entry under ``[Unreleased]`` in the appropriate section, referencing the issue or +pull request number. The documentation page is generated from this file, so there is +nothing else to update. diff --git a/docs/source/credits.rst b/docs/source/credits.rst new file mode 100644 index 0000000..1b8ba1b --- /dev/null +++ b/docs/source/credits.rst @@ -0,0 +1,50 @@ +.. _credits: + +======= +Credits +======= + +Author +====== + +PyNetDesign is developed by `Denis Anikiev `_, +:fab:`github;pst-color-primary` `github.com/danikiev `_. + +Acknowledgments +=============== + +The methodology builds on published work on seismic wave propagation and on +microseismic monitoring network design, in particular: + +* :cite:t:`AkiRichards2002` for the far-field radiation of a point moment-tensor source, + the radiation-pattern convention and the free surface boundary condition; +* :cite:t:`Cerveny2001` for ray theory and the global absorption factor; +* :cite:t:`Kanamori1977` for the moment magnitude scale; +* :cite:t:`EisnerGeiEtAl2013` for the peak frequency of direct waves; +* :cite:t:`BooreBoatwright1984` for the average body-wave radiation coefficients; +* :cite:t:`Hallo2012` and :cite:t:`HalloEisner2013` for monitoring network sensitivity + and the influence of the double-couple source model. + +PyNetDesign stands on the open-source scientific Python ecosystem, and the author is +grateful to the developers of `NumPy `_, +`Pandas `_, `Matplotlib `_ and +`cmcrameri `_, as well as to the +`Sphinx `_ project and the +`PyData Sphinx theme `_ used to build this +documentation. + +License +======= + +PyNetDesign is released under the MIT License. + +---- + +.. only:: html + + References + ========== + + .. bibliography:: + :style: unsrt + :filter: docname in docnames diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst new file mode 100644 index 0000000..8709801 --- /dev/null +++ b/docs/source/getting_started.rst @@ -0,0 +1,374 @@ +.. _getting_started: + +=============== +Getting Started +=============== + +This chapter explains how to install PyNetDesign, how to describe a monitoring network +and a velocity model, and how to run a first sensitivity calculation. + +---- + +.. _installation: + +Installation +============ + +Requirements +------------ + +PyNetDesign works with Python 3.9 to 3.12. Cloning the repository requires +`Git `_, and the recommended way to create the environment is the +`Conda `_ package manager, for example through +`Miniforge `_. + +Dependencies +------------ + +The runtime dependencies are declared in ``pyproject.toml`` and installed by ``pip``: + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Package + - Purpose + * - `numpy `_ + - vectorized numerical core + * - `pandas `_ + - geometry and velocity model tables, and their metadata + * - `matplotlib `_ + - plotting + * - `cmcrameri `_ + - perceptually uniform scientific colour maps + +Two optional groups are available: ``dev`` adds ``pytest`` and an interactive Jupyter +stack, and ``docs`` adds the Sphinx toolchain used to build this documentation. + +The author is grateful to the developers of these packages. + +Install using conda and pip +--------------------------- + +``environment.yml`` pins only the interpreter; every package dependency comes from +``pyproject.toml``. Clone the repository: + +.. code-block:: bash + + git clone https://github.com/danikiev/PyNetDesign.git + cd PyNetDesign + +create and activate the environment: + +.. code-block:: bash + + conda env create -f environment.yml -n pnd + conda activate pnd + +and install the package: + +.. code-block:: bash + + pip install -e . + +To also install the test and documentation tools: + +.. code-block:: bash + + pip install -e ".[dev,docs]" + +.. tip:: + + Using ``mamba`` instead of ``conda`` speeds up environment creation considerably. + +Install using script +-------------------- + +The helper scripts perform the steps above, creating the ``pnd`` environment and +asking whether to add the development tools: + +.. code-block:: bash + + ./install.sh # Linux, macOS + install.bat # Windows, from a Miniforge prompt + +Pass ``--dev`` or ``--no-dev`` to ``install.sh`` to answer in advance. + +Uninstall +--------- + +Deactivate and remove the environment: + +.. code-block:: bash + + conda deactivate + conda remove -n pnd --all + +``./uninstall.sh`` on Linux and macOS, or ``uninstall.bat`` on Windows, finds every +environment whose name starts with ``pnd`` and asks about each one. + +---- + +.. _package_design: + +Package Design +============== + +PyNetDesign is organised into two subpackages: + +* :mod:`pynetdesign.modelling` contains the algorithms: + + * :mod:`~pynetdesign.modelling.core` provides the high-level drivers, + * :mod:`~pynetdesign.modelling.homo` implements the homogeneous-medium engine, + * :mod:`~pynetdesign.modelling.geometry` generates synthetic receiver layouts, + * :mod:`~pynetdesign.modelling.io` reads and writes input files, + * :mod:`~pynetdesign.modelling.utils` holds the wave-mode, amplitude and grid helpers. + +* :mod:`pynetdesign.visualization` plots receiver geometry and sensitivity results. + +Both subpackages are re-exported at the top level, so either style works: + +.. code-block:: python + + import pynetdesign.modelling as pndmod + import pynetdesign.visualization as pndvis + +---- + +.. _input_files: + +Input Files +=========== + +PyNetDesign reads two whitespace-delimited ASCII files: a velocity model and a +monitoring network geometry. + +.. _velocity_model: + +Velocity model +-------------- + +The velocity model file has a fixed header and, for a homogeneous medium, exactly one +row of values: + +.. code-block:: text + + Depth(m) Vp(m/s) Vp/Vs Qp Qs density(kg/m^3) Ep(anisotropy) Dt(anisotropy) Gm(anisotropy) + 0 2370 2 100 100 2500 0 0 0 + +The columns are the layer top depth, the P-wave velocity, the P-to-S velocity ratio, the +P- and S-wave quality factors, the density, and three Thomsen anisotropy parameters. + +.. note:: + + The Thomsen parameters are read and stored but not used in the calculation: the + medium is treated as isotropic. Supplying more than one row raises an error, because + PyNetDesign models a homogeneous medium only. + +Read it with :func:`~pynetdesign.modelling.io.read_velocity`: + +.. code-block:: python + + velocity_df = pndmod.io.read_velocity('velocity_model.txt') + +The S-wave velocity is derived as ``Vp / VpVsRatio`` and added as a ``Vs`` column. + +.. _geometry_file: + +Monitoring network geometry +--------------------------- + +The geometry file describes the receivers, one per row. A local-coordinate station +network looks like this: + +.. code-block:: text + + Name Northing(m) Easting(m) Elevation(m) NoiseLevel(m/s) + ST1 0 0 0 5.0E-09 + ST2 5000 0 0 5.0E-09 + ST3 2500 4330 0 5.0E-09 + +and a DAS cable declares its gauge length in the header: + +.. code-block:: text + + Northing(m) Easting(m) Elevation(m) NoiseLevel(1/s) GaugeLength=10(m) + 0 0 0 1.95E-09 + 0 0 -1 1.95E-09 + 0 0 -2 1.95E-09 + +Required columns are a northing or latitude, an easting or longitude, an elevation and a +noise level. Optional columns are: + +.. list-table:: + :header-rows: 1 + :widths: 20 80 + + * - Column + - Meaning + * - ``Name`` + - receiver label, used in plots + * - ``Surface`` + - weight in [0, 1] marking receivers at the free surface; a fractional value blends + between no amplification and full amplification + * - ``Components`` + - ``3C`` or ``Z``; a ``Z`` receiver records only the vertical component and is + projected accordingly. Not allowed together with a gauge length + +Geographic coordinates are given as ``Latitude(WGS84)`` and ``Longitude(WGS84)`` and are +converted to a local Cartesian frame with +:func:`~pynetdesign.modelling.io.global2local`. Latitude and longitude must both be +present, or neither. + +.. note:: + + ``Elevation`` is positive upwards, while the internal ``Z`` coordinate is depth, + positive downwards. A receiver written with ``Elevation = -500`` is stored as + ``Z = 500``. The writer reverses this, so a round trip through + :func:`~pynetdesign.modelling.io.save_geometry` is lossless. + +Distance units +^^^^^^^^^^^^^^ + +Coordinate columns and the gauge length accept a unit in parentheses and are converted to +metres: ``m``, ``km``, ``cm``, ``mm``, ``ft`` and ``in``. All coordinate columns of one +file must use the same unit. + +Amplitude units +^^^^^^^^^^^^^^^ + +The unit of ``NoiseLevel`` selects how the noise is converted to a displacement amplitude +spectrum, as described in Section :ref:`detection_threshold`: + +.. list-table:: + :header-rows: 1 + :widths: 30 30 40 + + * - Unit in header + - Interpreted as + - Typical receiver + * - ``m`` + - displacement + - displacement sensor + * - ``m/s`` + - particle velocity + - geophone, seismometer + * - ``m/s^2`` + - particle acceleration + - accelerometer + * - ``1/s`` + - strain rate + - DAS + * - ``strain``, ``1``, ``units``, ``none`` + - strain + - DAS + +A gauge length is required for strain and strain-rate data, and its presence is what +marks a geometry as DAS. + +Read geometry files +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + geometry_df = pndmod.io.read_geometry('net_geometry.txt') + print(geometry_df.attrs) + +The returned DataFrame has columns ``Name``, ``X``, ``Y``, ``Z`` and ``NoiseLevel``, plus +any optional columns, and carries metadata in ``attrs``: the file path, the original +coordinate unit, the noise unit and type, the coordinate system, and the gauge length +when present. + +Generate and save geometry files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Trial layouts can be built from code with +:func:`~pynetdesign.modelling.geometry.generate_geometry`, which returns geometry text +lines that :func:`~pynetdesign.modelling.io.save_geometry` writes to disk: + +.. code-block:: python + + lines = pndmod.geometry.generate_geometry( + mode='borehole', # 'borehole', 'surface' or 'darkfiber' + cable_length=999.0, # m of cable + spacing=1.0, # m between channels + noise_level=1.95e-9, # strain rate + gauge_length=10.0, # m + ) + pndmod.io.save_geometry(lines, 'das_geometry.txt') + geometry_df = pndmod.io.read_geometry('das_geometry.txt') + +Surface layouts accept a ``surface_shape`` of ``line``, ``zigzag``, ``L``, ``square`` or +``double_line``; boreholes accept an ``azimuth`` and ``dip``, and can apply a +depth-dependent noise profile. A dense cable can be thinned with +:func:`~pynetdesign.modelling.io.decimate_geometry`: + +.. code-block:: python + + sparse_df = pndmod.io.decimate_geometry(geometry_df, 5) # every 5th channel + +Combine geometries +^^^^^^^^^^^^^^^^^^ + +Several networks can be evaluated together, for instance a surface station array and a +borehole DAS cable, with :func:`~pynetdesign.modelling.io.combine_geometry`: + +.. code-block:: python + + combined_df = pndmod.io.combine_geometry(stations_df, das_df) + +Each contributing geometry keeps its own noise type and gauge length, and the +per-receiver thresholds are concatenated before the network criterion is applied. + +---- + +.. _quickstart: + +Quickstart +========== + +The following computes the minimum detectable moment magnitude on a vertical section +through a borehole DAS array and plots it: + +.. code-block:: python + + import numpy as np + import pynetdesign.modelling as pndmod + import pynetdesign.visualization as pndvis + + # Input + geometry_df = pndmod.io.read_geometry('das_geometry.txt') + velocity_df = pndmod.io.read_velocity('velocity_model.txt') + + # Grid of potential source locations + gx = np.arange(0.0, 2000.0 + 25.0, 25.0) + gy = np.array([0.0]) + gz = np.arange(25.0, 1500.0 + 25.0, 25.0) + grid_points = pndmod.utils.generate_grid(x=gx, y=gy, z=gz) + + # Detection criteria + params = pndmod.classes.DetectionParameters( + min_SNR_p=2, # SNR required on one channel + min_SNR_s=2, + min_stations_p=60, # channels required for detection + min_stations_s=60, + ) + + # Minimum detectable magnitude, requiring both P and S + mw_min = pndmod.core.get_mag_sensitivity( + grid_coords=grid_points, + geometry_df=geometry_df, + velocity_df=velocity_df, + params=params, + wave_mode='PS', + ) + + # Vertical section at Y = 0 + _ = pndvis.sensitivity.plot_sensitivity_slice( + sens_data=mw_min, xi=gx, zi=gz, + geometry_df=geometry_df, clb_title='$M_w$') + +``wave_mode`` selects the phases: ``'P'``, ``'SV'``, ``'SH'``, ``'S'`` for the composite +S branch, or ``'PS'`` for joint P and S detection. See Section +:ref:`network_detectability` for how the per-receiver thresholds are combined, and the +:ref:`examples ` for complete scripts. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..43e7a46 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,148 @@ +.. _overview: + +======== +Overview +======== + +.. only:: html + + .. container:: pynetdesign-index-logo + + .. image:: _static/pynetdesign-logo.svg + :alt: PyNetDesign logo + :align: center + :width: 520px + +PyNetDesign is an open-source Python framework for testing and designing microseismic +monitoring networks in homogeneous velocity models. + +PyNetDesign determines the **magnitude sensitivity** of a monitoring network, that is the +minimum moment magnitude that a given set of receivers can theoretically detect. It works +with conventional station networks and with distributed acoustic sensing (DAS) cables, +including both at the same time, and accounts for the directional sensitivity that makes +a DAS cable behave differently from a three-component station. Results are presented as +horizontal or vertical slices through a 3D grid of potential source locations. + +The modelling API also provides synthetic geometry generators for borehole, surface and +dark-fibre layouts, so that trial networks can be built from code rather than from +hand-written tables. + +The core algorithms are vectorized with `NumPy `_, input and output +rely on `Pandas `_, and visualization is based on +`Matplotlib `_ with perceptually uniform colour maps from +`cmcrameri `_. + +**Current PyNetDesign version:** |release| + +---- + +.. toctree:: + :maxdepth: 3 + :hidden: + :caption: Contents + + self + getting_started.rst + methodology.rst + examples/index.rst + api/index.rst + +.. toctree:: + :maxdepth: 1 + :hidden: + :caption: Project + + citing.rst + contributing.rst + changelog.rst + credits.rst + +.. only:: html + + **Quick Links:** + + .. grid:: 1 2 2 2 + :gutter: 3 + + .. grid-item-card:: + :link: getting_started + :link-type: doc + :link-alt: Getting started + + :fas:`play;pst-color-primary` **Getting Started** + ^^^ + Install PyNetDesign and prepare geometry and velocity input files. + + .. grid-item-card:: + :link: methodology + :link-type: doc + :link-alt: Methodology + + :fas:`book;pst-color-primary` **Methodology** + ^^^ + The theory behind the detectability calculation, phase by phase. + + .. grid-item-card:: + :link: examples/index + :link-type: doc + :link-alt: Examples + + :fas:`lightbulb;pst-color-primary` **Examples** + ^^^ + Worked examples for station and DAS monitoring networks. + + .. grid-item-card:: + :link: api/index + :link-type: doc + :link-alt: API Reference + + :fas:`code;pst-color-primary` **API Reference** + ^^^ + Reference documentation for every public function. + + .. grid-item-card:: + :link: citing + :link-type: doc + :link-alt: Citing + + :fas:`quote-right;pst-color-primary` **Citing** + ^^^ + Find citation information and BibTeX entries for referencing PyNetDesign in your research. + + .. grid-item-card:: + :link: contributing + :link-type: doc + :link-alt: Contributing + + :fas:`code-pull-request;pst-color-primary` **Contributing** + ^^^ + Report issues, propose changes and set up a development environment. + + .. grid-item-card:: + :link: changelog + :link-type: doc + :link-alt: Changelog + + :fas:`clock-rotate-left;pst-color-primary` **Changelog** + ^^^ + Review release notes and notable changes across versions. + + .. grid-item-card:: + :link: credits + :link-type: doc + :link-alt: Credits + + :fas:`users;pst-color-primary` **Credits** + ^^^ + Acknowledge authors, contributors and used libraries in the development of PyNetDesign. + + .. grid:: 1 + :gutter: 3 + + .. grid-item-card:: + :link: _static/pynetdesign.pdf + :link-alt: pdf + + :fas:`file-pdf;pst-color-primary` **Download as PDF** + ^^^ + Download this documentation as a standalone :fas:`file-pdf` PDF file. diff --git a/docs/source/methodology.rst b/docs/source/methodology.rst new file mode 100644 index 0000000..25331a2 --- /dev/null +++ b/docs/source/methodology.rst @@ -0,0 +1,633 @@ +.. _methodology: + +=========== +Methodology +=========== + +This chapter presents the theory behind PyNetDesign. It describes how the minimum +detectable moment magnitude of a microseismic event is evaluated for a network of +receivers in a homogeneous, isotropic and attenuating medium, separately for each +seismic phase and for each receiver type. + +.. note:: + + PyNetDesign models a homogeneous medium. The ray path between a source and a + receiver is therefore a straight line, there are no interfaces and no transmission + losses, and geometrical spreading is simply proportional to distance. + +---- + +.. _spectral_conventions: + +Spectral conventions and source-model assumption +================================================ + +We work with frequency in Hz, :math:`f`, and angular frequency :math:`\omega = 2\pi f`. + +Let :math:`|U(f)|` denote the amplitude spectrum magnitude of **particle displacement** (projected onto the considered phase polarization) recorded at the receiver. +If the instrument measures particle velocity, the corresponding spectrum is + +.. math:: + :label: velocity_displacement_relation + + |V(f)| = 2\pi f\,|U(f)|, + +which follows from :math:`v(t)=\dot{u}(t)`. + +For shear-dominated microseismic events we adopt the standard :math:`\omega^{-2}` source model :cite:p:`AkiRichards2002`. +The source displacement spectrum is + +.. math:: + + \Omega_D(f)= + \begin{cases} + C_0 M_0, & f < f_c, \\ + C_0 M_0 \left(\dfrac{f_c}{f}\right)^2, & f \ge f_c, + \end{cases} + +where :math:`M_0` is seismic moment, :math:`f_c` is corner frequency, and :math:`C_0` contains radiation-pattern and medium constants. + +We define + +.. math:: + + \Omega_0 \equiv C_0 M_0, + +which is the low-frequency (plateau) level of the far-field source displacement spectrum. + +We assume a representative frequency :math:`f` below the corner frequency (:math:`f < f_c`, often :math:`f=f_{\mathrm{peak}}`, see Section :ref:`peak_frequency`), so the source spectrum is approximately flat, :math:`\Omega_D(f) \approx \Omega_0`. +In this regime, the source spectral function reduces to its low-frequency limit and can be approximated as unity. + +---- + +.. _intrinsic_attenuation: + +Intrinsic attenuation +===================== + +The attenuation operator :math:`t^*` (known as global absorption factor :cite:p:`Cerveny2001`) is the path integral along the ray: + +.. math:: + :label: tstar + + t^* = \int_R \frac{dr}{v(r)\,Q(r)}, + +For a homogeneous medium, where :math:`v` and :math:`Q` are constant along the +straight ray of length :math:`r`, Equation :eq:`tstar` reduces to + +.. math:: + :label: tstar_homo + + t^* = \frac{r}{vQ}. + +---- + +.. _peak_frequency: + +Peak frequency +============== + +The peak frequency of an observed signal is the frequency corresponding to the maximum amplitude in Fourier spectra of the particle velocity. +As shown by :cite:t:`EisnerGeiEtAl2013`, the peak frequency can be determined for geophone recordings (i.e., particle velocity) from the global absorption factor :math:`t^*` only. +The particle velocity is convenient mainly because most of the observations are carried out on instruments recording particle velocity (e.g., geophones or even broadband seismometers). + +In Section :ref:`spectral_conventions` we have shown that the Fourier spectrum of particle displacement is flat at low frequencies with nonzero permanent displacement at the source :cite:p:`AkiRichards2002`. +At higher frequencies, the source spectrum is limited by the rise and rupture times of an earthquake fault rupture. +These limitations result in a sharp slope change of spectral power density of the particle displacement spectrum. +This abrupt change in slope occurs at a corner frequency :math:`f_c`. +Hence, the peak of this positive function must occur for frequencies at or below the corner frequency :cite:p:`EisnerGeiEtAl2013`. + +The peak frequency :math:`f_{\mathrm{peak}}` is the frequency of the maximum amplitude in the particle velocity spectra. +Propagation attenuation modifies the observed spectrum through the global absorption factor :math:`t^*` (e.g., :cite:t:`Cerveny2001`, :cite:t:`EisnerGeiEtAl2013`): + +.. math:: + + |V(f)| \propto f\, e^{-\pi f t^*}. + +Maximization of this expression for :math:`f < f_c` yields + +.. math:: + :label: fpeak + + f_{\mathrm{peak}} = \frac{1}{\pi t^*}, + +where :math:`t^*` is evaluated according to Equation :eq:`tstar`. + +.. note:: + + Equation :eq:`fpeak` is only valid for :math:`f < f_c`, i.e., when the source spectrum is approximately flat. + If not, the maximum occurs at the boundary :math:`f_{\mathrm{peak}}=f_c`. + +Similarly, the peak amplitude for particle acceleration will occur at a frequency twice bigger than those for particle velocity, i.e., at :math:`f_{\mathrm{peak}}=2/(\pi t^*)`. + +Taking into account Equation :eq:`tstar_homo`, in homogeneous medium the peak amplitude for particle velocity is + +.. math:: + :label: fpeak_homo + + f_{\mathrm{peak}} = \frac{vQ}{\pi r}, + +where :math:`v` is the body wave velocity (P- or S-wave), :math:`Q` is the attenuation factor (P- or S-wave), and :math:`r` is the distance from a source to a receiver. + +---- + + +.. _detection_threshold: + +Detection threshold +=================== + +An event is considered detected on a receiver when the amplitude of the arriving phase +exceeds the local noise level by a required factor. For receiver :math:`i` the minimum +detectable displacement amplitude spectrum is + +.. math:: + :label: min_amplitude + + |U_{\min,i}(f)| = D_{\mathrm{SNR}}\,A_{N,i}\,S(f), + +where :math:`A_{N,i}` is the measured noise level of that receiver, +:math:`D_{\mathrm{SNR}}` is the signal-to-noise ratio required for detection, and +:math:`S(f)` converts the recorded quantity to a displacement amplitude spectrum. + +Following Equation :eq:`velocity_displacement_relation`, the conversion coefficient is + +.. list-table:: + :header-rows: 1 + :widths: 30 30 40 + + * - Recorded quantity + - :math:`S(f)` + - Comment + * - displacement + - :math:`1` + - already a displacement + * - particle velocity + - :math:`(2\pi f)^{-1}` + - from :math:`|V(f)| = 2\pi f\,|U(f)|` + * - particle acceleration + - :math:`(2\pi f)^{-2}` + - two integrations + * - strain + - :math:`L_g` + - strain averaged over the gauge length :math:`L_g` + * - strain rate + - :math:`L_g / (2\pi f)` + - as strain, plus one integration in time + +For DAS, the recorded quantity is the strain or strain rate along the cable, averaged +over the gauge length :math:`L_g`. Multiplying by :math:`L_g` converts it to an +equivalent displacement, which assumes the strain to be constant over the gauge length. + +The threshold is then corrected for the receiver response of +Section :ref:`das_directionality`, giving the polarization-aligned threshold +:math:`\widetilde{U}_{\min,i}^{\phi} = C_\phi\,|U_{\min,i}(f)|` for phase :math:`\phi`, +and divided by the free-surface factor of Section :ref:`free_surface_boundary_condition` +for receivers at the surface. + +.. _network_detectability: + +Network detectability +===================== + +Inserting the threshold into Equation :eq:`seismic_moment_homogeneous` and converting to +a magnitude yields a per-receiver threshold magnitude +:math:`M_w^{\min}(\mathbf{x}, i)` for every grid point :math:`\mathbf{x}` and every +receiver :math:`i`. + +An event is only useful if it is seen on several receivers. Sorting the per-receiver +values at a grid point in ascending order, the network threshold for a phase is the +:math:`N`-th smallest value, where :math:`N` is the minimum number of receivers required +for detection. For joint P- and S-wave detection, the stricter of the two phase +thresholds applies, + +.. math:: + + M_w^{\min} = \max\!\left(M_w^{\min,P},\; M_w^{\min,S}\right). + +For the composite ``S`` mode, the SV and SH branches are evaluated separately for each +source-receiver pair and the more detectable branch, that is the smaller magnitude, is +retained. A phase whose projection vanishes is excluded rather than treated as +infinitely detectable. + +---- + +.. _free_surface_boundary_condition: + +Free surface boundary condition +=============================== + +At the free surface boundary, incoming body waves are reflected, resulting in a complex interference pattern that modifies the recorded amplitude. +For the purposes of detectability, we approximate the recorded displacement amplitude to be twice that of the incident wave. +This approximation corresponds exactly to the free-surface amplification factor for normal or near-normal incidence, as derived from the free-surface reflection equations (:cite:t:`AkiRichards2002`, Section 5.2.2). + +---- + +.. _das_directionality: + +Receiver projection and DAS directionality +========================================== + +PyNetDesign treats ordinary geophones as three-component receivers by default. +For these receivers the recorded scalar amplitude is assumed to be aligned with the arriving phase polarization, so no projection loss is applied. +If a station geometry row has :code:`Components=Z`, the arriving displacement is projected onto the vertical axis. +For DAS receivers, PyNetDesign applies the same phase-dependent projection logic onto the local fiber tangent :math:`\boldsymbol\tau`. + +In a homogeneous medium the ray is a straight line, so the phase polarization is computed from the straight receiver-to-grid direction. +The amplitude correction for phase :math:`\phi` and receiver direction :math:`\mathbf d` is + +.. math:: + + C_\phi = + \frac{1}{|\mathbf d \cdot \mathbf e_\phi|}, + +where :math:`\mathbf d` is :math:`\hat{\mathbf z}` for vertical-component geophones and :math:`\boldsymbol\tau` for DAS. +:math:`\mathbf e_P` is the longitudinal P polarization, :math:`\mathbf e_{SV}` is the transverse polarization in the local vertical ray plane, and :math:`\mathbf e_{SH}` is perpendicular to that plane. +The corrected minimum recorded amplitude is multiplied by :math:`C_\phi`. +For composite ``S`` mode, SV and SH are corrected separately and the more detectable branch is retained. + +If the projection is zero, that phase is not observable on the selected receiver component for that source-station pair. +For example, SH is horizontal in the current isotropic convention and is therefore not recorded by a vertical-component geophone. + +For nearly vertical rays, the explicit SV/SH azimuthal basis is convention-dependent because the horizontal ray azimuth is poorly defined. +PyNetDesign uses a stable horizontal fallback basis in this case; the composite ``S`` mode is the practical robust choice when only S-wave detectability is required. + +---- + +.. _radiation_pattern: + +Radiation pattern +================= + +In the far field, body-wave amplitudes from a seismic moment tensor source depend on the take-off direction and on the wave polarization. +Let :math:`\mathbf n` be the unit vector in the P-wave take-off direction at the source (pointing from source to receiver), and let :math:`\mathbf e` be the unit polarization vector of the considered phase at the source. + +Following the quantitative formulation of :cite:t:`AkiRichards2002` (Section 4.5), for a moment tensor :math:`\mathbf M`, the dimensionless scalar radiation pattern factor for the phase with polarization :math:`\mathbf e` is + +.. math:: + + R(\mathbf n,\mathbf e) + = + \frac{\mathbf e^{T}\,\mathbf M\,\mathbf n}{M_0}, + +where :math:`M_0` is the scalar seismic moment (used only for normalization of the dimensionless factor). + +For P waves, the polarization is longitudinal (:math:`\mathbf e=\mathbf n`), leading to the familiar radiation pattern factor (:cite:t:`AkiRichards2002`, Eq. 4.89): + +.. math:: + + R_P = \frac{\mathbf n^{T}\,\mathbf M\,\mathbf n}{M_0}. + +For S waves, there are two orthogonal transverse polarizations. +Denoting by :math:`\mathbf s_{SV}` the unit SV polarization (in the ray plane) and by :math:`\mathbf s_{SH}` the unit SH polarization (perpendicular to the ray plane), the corresponding radiation factors are (:cite:t:`AkiRichards2002`, Eqs. 4.90 and 4.91): + +.. math:: + + R_{SV} = \frac{\mathbf s_{SV}^{T}\,\mathbf M\,\mathbf n}{M_0}, + \qquad + R_{SH} = \frac{\mathbf s_{SH}^{T}\,\mathbf M\,\mathbf n}{M_0}. + +In this documentation and in the magnitude-sensitivity formulas, :math:`|R|` denotes the **magnitude** of the appropriate factor (:math:`|R_P|` for P, :math:`|R_{SV}|` for SV, and :math:`|R_{SH}|` for SH). +For ``wave_mode="S"``, PyNetDesign evaluates SV and SH separately and keeps the more detectable branch for each source-station pair. + +For microseismic network design, the source mechanism and take-off directions are often unknown. +In that case, it is common to use an *effective* radiation-pattern magnitude (i.e., a somehow averaged value of :math:`|R|` over a suitable ensemble of mechanisms and directions). +:cite:t:`HalloEisner2013` following :cite:t:`BooreBoatwright1984` suggest to use representative RMS values for DC seismic sources :math:`R_P^{\mathrm{eff}} = \sqrt{\langle R_P^2\rangle} \approx 0.52` and :math:`R_S^{\mathrm{eff}} = \sqrt{\langle R_S^2\rangle} \approx 0.63`. +PyNetDesign uses fixed RMS constants :math:`R_P^{\mathrm{eff}}=\sqrt{4/15}\approx 0.516`, :math:`R_{SV}^{\mathrm{eff}}=\sqrt{7/30}\approx 0.483` for SV and :math:`R_{SH}^{\mathrm{eff}} = \sqrt{1/6}\approx 0.408` for SH. +See Section :ref:`derivation_radiation_pattern` for more information. + +---- + +.. _magnitude_sensitivity: + +Magnitude sensitivity +===================== + +The magnitude sensitivity module is designed to estimate the magnitudes of microseismic events that can be theoretically detected by the network of receivers. +This sensitivity (or detectability) varies in space and hence it is presented in the form of horizontal or vertical slices in the 3D space. +PyNetDesign uses attenuation of the dominant signal (based on the quality factor of the medium :math:`Q` ), and signal-to-noise ratio (SNR) necessary for seismic event detection. +Results depend on a model of attenuation, noise, wave velocity, and detection method. +The resulting sensitivity can show the spatial distribution of the minimal technically detectable magnitude, or, in other words, the spatial distribution of the magnitude of detection catalogue completeness. +The moment magnitude of an event, according to :cite:t:`Kanamori1977`, is given by logarithm of the scalar value of the seismic moment: + +.. math:: + M_w = \frac{2}{3}\left(\log_{10}(M_0)-9.1\right), + +where :math:`M_0` is the seismic moment. + +PyNetDesign computes minimal moment magnitude of an event, which will theoretically produce amplitudes on receivers necessary for the event detection. +Amplitude levels for the event detection are dependent on local noise levels and signal-to-noise ratio necessary for particular detection method. + +.. _homogeneous_medium: + +Homogeneous medium +------------------ + +In a homogeneous isotropic medium, the scalar seismic moment :math:`M_0` can be related to the magnitude of the displacement Fourier spectrum :math:`|U(f)|` (projected onto the arriving polarization of the considered phase P or S) at frequency :math:`f` as + +.. math:: + :label: seismic_moment_homogeneous + + M_0 + = + \frac{4 \pi \rho v^3 r}{|R|} + \;|U(f)|\; + \exp\!\big(\pi f\,t^*\big), + +where :math:`\rho` is density, :math:`v` is the body-wave velocity (:math:`v=\alpha` for P waves, :math:`v=\beta` for S waves), and :math:`r` is the source-receiver distance. +The factor :math:`|R|` is the dimensionless radiation-pattern magnitude for the considered phase, consistent with the moment-tensor radiation convention of :cite:t:`AkiRichards2002` (see Section :ref:`radiation_pattern`). + +For derivation of formula :eq:`seismic_moment_homogeneous`, see Section :ref:`derivation_seismic_moment_homo`. + +The exponential term :math:`\exp(\pi f t^*)` corrects for intrinsic attenuation using the attenuation operator :math:`t^*` (see Section :ref:`intrinsic_attenuation`). + +Because P-wave velocity is typically higher than S-wave velocity, for the same propagation path and :math:`Q` the term :math:`t^*=r/(vQ)` is smaller for P waves, so the attenuation correction :math:`\exp(\pi f t^*)` is closer to unity. +In practice, detectability is also controlled by radiation pattern and noise characteristics; network-design sensitivity is therefore often evaluated separately for P and S phases :cite:p:`HalloEisner2013`. + +---- + + +.. _derivation_seismic_moment_homo: + +Derivation of the seismic moment in a homogeneous medium +======================================================== + +Here we derive Equation :eq:`seismic_moment_homogeneous` from the far-field radiation of +a point moment-tensor source. + +In an unbounded homogeneous isotropic medium, the far-field displacement produced by a +point source with moment time function :math:`M_0(t)` is, for the phase with +radiation-pattern factor :math:`R` and velocity :math:`v` (:cite:t:`AkiRichards2002`, +Section 4.3), + +.. math:: + :label: far_field_displacement + + u(t) = \frac{R}{4\pi\rho v^3 r}\,\dot{M}_0\!\left(t - \frac{r}{v}\right), + +where the :math:`1/r` factor is the geometrical spreading of a spherical wave in a +homogeneous medium and the dot denotes the time derivative. + +Taking the Fourier transform and using the fact that the moment rate spectrum tends to +the scalar seismic moment at low frequency, :math:`|\dot{M}_0(f)| \to M_0` for +:math:`f \ll f_c`, the low-frequency plateau of the displacement amplitude spectrum is + +.. math:: + :label: plateau_homo + + \Omega_0 = \frac{M_0\,|R|}{4\pi\rho v^3 r}, + +consistent with the source-model assumption of Section :ref:`spectral_conventions`. + +Intrinsic attenuation multiplies the spectrum by the dissipation filter of +Section :ref:`intrinsic_attenuation`, so the spectrum actually observed at the receiver is + +.. math:: + :label: observed_homo + + |U(f)| = \frac{M_0\,|R|}{4\pi\rho v^3 r}\,\exp\!\big(-\pi f\,t^*\big), + \qquad t^* = \frac{r}{vQ}. + +Solving Equation :eq:`observed_homo` for the seismic moment gives + +.. math:: + + M_0 + = + \frac{4\pi\rho v^3 r}{|R|}\; + |U(f)|\; + \exp\!\left(\pi f\,\frac{r}{vQ}\right), + +which is Equation :eq:`seismic_moment_homogeneous`. + +.. note:: + + The exponential appears with a **positive** exponent in the moment formula: the + amplitude observed at the receiver has already been reduced by attenuation, so + recovering the source moment means undoing that reduction. + + Note also that :math:`|U(f)|` is an amplitude **spectrum**. The conversion of a + recorded noise level to :math:`|U(f)|` is carried out once, by the coefficient + :math:`S(f)` of Section :ref:`detection_threshold`, and must not be applied a second + time here. + +---- + +.. _derivation_radiation_pattern: + +Derivation of effective radiation pattern magnitudes +==================================================== + +For microseismic network design with unknown focal mechanisms, we utilize effective averaged radiation factors. +While literature often name these as average magnitudes, they are mathematically evaluated as the **Root Mean Square (RMS)** averages over the entire focal sphere to preserve wave energy :cite:p:`BooreBoatwright1984`. + +We assume a canonical double-couple (DC) source. +In a coordinate system where the fault plane normal is aligned with the x-axis and the slip direction is aligned with the y-axis, the seismic moment tensor :math:`\mathbf{M}` is: + +.. math:: + \mathbf{M} = M_0 \begin{pmatrix} 0 & 1 & 0 \\ 1 & 0 & 0 \\ 0 & 0 & 0 \end{pmatrix}. + +Let :math:`\mathbf{\gamma}` be the unit take-off vector parameterized by polar angle :math:`\theta` and azimuthal angle :math:`\phi`: + +.. math:: + \mathbf{\gamma} = \begin{pmatrix} \sin\theta\cos\phi \\ \sin\theta\sin\phi \\ \cos\theta \end{pmatrix}. + +.. _p_wave_average: + +P-wave average +-------------- + +The P-wave radiation pattern factor is the projection of the equivalent force :math:`\mathbf{M}\mathbf{\gamma}` onto the longitudinal polarization :math:`\mathbf{\gamma}`: + +.. math:: + R_P = \frac{\mathbf{\gamma}^T \mathbf{M} \mathbf{\gamma}}{M_0} = 2 \sin^2\theta \sin\phi \cos\phi = \sin^2\theta \sin 2\phi. + +To find the RMS average, we integrate the square of :math:`R_P` over the unit sphere (where the averaging operator is :math:`\frac{1}{4\pi} \int_0^{2\pi} \int_0^\pi \dots \sin\theta \, d\theta \, d\phi`): + +.. math:: + \langle R_P^2 \rangle + = \frac{1}{4\pi} \int_0^{2\pi} \int_0^\pi \left(\sin^2\theta \sin 2\phi\right)^2 \sin\theta \, d\theta \, d\phi. + +Separating the integrals: + +.. math:: + \langle R_P^2 \rangle + = \frac{1}{4\pi} \left( \int_0^\pi \sin^5\theta \, d\theta \right) \left( \int_0^{2\pi} \sin^2 2\phi \, d\phi \right). + +Evaluating the integrals yields :math:`16/15` for the polar part and :math:`\pi` for the azimuthal part: + +.. math:: + \langle R_P^2 \rangle = \frac{1}{4\pi} \left(\frac{16}{15}\right) (\pi) = \frac{4}{15}. + +Taking the square root provides the effective P-wave magnitude: + +.. math:: + R_P^{\mathrm{rms}} = \sqrt{\frac{4}{15}} \approx 0.516 \approx 0.52. + +.. _s_wave_average: + +S-wave average +-------------- + +For S-waves, the total radiation pattern squared, :math:`R_S^2`, is the squared magnitude of the transverse component of the equivalent force. This is the total equivalent force squared minus the longitudinal (P-wave) component squared: + +.. math:: + R_S^2 = \frac{|\mathbf{M}\mathbf{\gamma}|^2}{M_0^2} - R_P^2. + +First, we compute the squared magnitude of the normalized equivalent force: + +.. math:: + \frac{|\mathbf{M}\mathbf{\gamma}|^2}{M_0^2} = (\sin\theta\sin\phi)^2 + (\sin\theta\cos\phi)^2 + 0^2 = \sin^2\theta. + +Averaging this over the focal sphere: + +.. math:: + \langle \frac{|\mathbf{M}\mathbf{\gamma}|^2}{M_0^2} \rangle + = \frac{1}{4\pi} \int_0^{2\pi} \int_0^\pi \sin^2\theta \sin\theta \, d\theta \, d\phi + = \frac{1}{4\pi} \left( \frac{4}{3} \right) (2\pi) = \frac{2}{3}. + +Subtracting the P-wave averaged from the total force average yields the total S-wave average: + +.. math:: + \langle R_S^2 \rangle = \frac{2}{3} - \langle R_P^2 \rangle = \frac{10}{15} - \frac{4}{15} = \frac{6}{15} = \frac{2}{5}. + +Taking the square root provides the effective total S-wave magnitude: + +.. math:: + R_S^{\mathrm{rms}} = \sqrt{\frac{2}{5}} \approx 0.632 \approx 0.63. + +.. _sv_sh_wave_average: + +SV- and SH-wave average +----------------------- + +The total S-wave coefficient derived above describes the magnitude of the full transverse radiation vector. +It does **not** imply equal partition between the SV and SH components. + +To obtain component-wise averages, we now switch to the standard fault-based angular convention of :cite:t:`AkiRichards2002`. +To avoid confusion with the spherical angles used above in the tensor-based derivation, we denote these fault-based angles by :math:`\Theta` and :math:`\Phi`. + +In this convention, the standard double-couple far-field radiation-pattern expressions for P, SV, and SH waves are: + +.. math:: + + R_P(\Theta,\Phi) = \sin 2\Theta \cos\Phi, + +.. math:: + + R_{SV}(\Theta,\Phi) = \cos 2\Theta \cos\Phi, + +.. math:: + + R_{SH}(\Theta,\Phi) = -\cos\Theta \sin\Phi, + +where :math:`\Theta` is the take-off angle and :math:`\Phi` is the azimuth in the fault-based coordinate system. + +The effective component-wise coefficients are defined by RMS averaging over the focal sphere: + +.. math:: + + \langle R_{SV}^2 \rangle + = + \frac{1}{4\pi} + \int_0^{2\pi}\int_0^\pi + \cos^2 2\Theta \cos^2\Phi \sin\Theta\,d\Theta\,d\Phi, + +.. math:: + + \langle R_{SH}^2 \rangle + = + \frac{1}{4\pi} + \int_0^{2\pi}\int_0^\pi + \cos^2\Theta \sin^2\Phi \sin\Theta\,d\Theta\,d\Phi. + +Using + +.. math:: + + \int_0^{2\pi}\cos^2\Phi\,d\Phi = \pi, + \qquad + \int_0^{2\pi}\sin^2\Phi\,d\Phi = \pi, + +.. math:: + + \int_0^\pi \cos^2 2\Theta \sin\Theta\,d\Theta = \frac{14}{15}, + \qquad + \int_0^\pi \cos^2\Theta \sin\Theta\,d\Theta = \frac{2}{3}, + +we obtain + +.. math:: + + \langle R_{SV}^2 \rangle + = + \frac{1}{4\pi}\,\pi\,\frac{14}{15} + = + \frac{7}{30}, + +.. math:: + + \langle R_{SH}^2 \rangle + = + \frac{1}{4\pi}\,\pi\,\frac{2}{3} + = + \frac{1}{6}. + +Therefore, the RMS effective coefficients are + +.. math:: + + R_{SV}^{\mathrm{rms}} + = + \sqrt{\langle R_{SV}^2\rangle} + = + \sqrt{\frac{7}{30}} + \approx 0.483, + +.. math:: + + R_{SH}^{\mathrm{rms}} + = + \sqrt{\langle R_{SH}^2\rangle} + = + \sqrt{\frac{1}{6}} + \approx 0.408. + +As expected, the total S-wave mean-square coefficient is the sum of the two orthogonal transverse components: + +.. math:: + + \langle R_S^2\rangle + = + \langle R_{SV}^2\rangle + \langle R_{SH}^2\rangle + = + \frac{7}{30} + \frac{1}{6} + = + \frac{2}{5}. + +Hence + +.. math:: + + R_{S}^{\mathrm{rms}} + = + \sqrt{\langle R_S^2\rangle} + = + \sqrt{\frac{2}{5}} + \approx 0.63. + +.. note:: + + Therefore, the commonly used value :math:`0.63` corresponds to the **combined S-wave amplitude**, not to SV alone and not to SH alone. + PyNetDesign uses fixed RMS constants :math:`R_P^{\mathrm{rms}}=\sqrt{4/15}`, :math:`R_{SV}^{\mathrm{rms}}=\sqrt{7/30}` for explicit SV, :math:`R_{SH}^{\mathrm{rms}}=\sqrt{1/6}` for explicit SH, and evaluates both S branches for composite ``S`` detectability. + +---- + +.. only:: html + + References + ========== + + .. bibliography:: + :style: unsrt + :filter: docname in docnames diff --git a/docs/source/references.bib b/docs/source/references.bib new file mode 100644 index 0000000..58aa82c --- /dev/null +++ b/docs/source/references.bib @@ -0,0 +1,82 @@ +% Bibliography for the PyNetDesign documentation. +% Keys are cited from the package docstrings and from methodology.rst. + +@Article{Kanamori1977, + author = {Kanamori, H.}, + journal = {Journal of Geophysical Research}, + title = {The energy release in great earthquakes}, + year = {1977}, + number = {20}, + pages = {2981--2987}, + volume = {82}, + doi = {10.1029/jb082i020p02981}, +} + +@Article{EisnerGeiEtAl2013, + author = {Eisner, L. and Gei, D. and Hallo, M. and Opršal, I. and Ali, M. Y.}, + journal = {GEOPHYSICS}, + title = {The peak frequency of direct waves for microseismic events}, + year = {2013}, + issn = {1942-2156}, + month = nov, + number = {6}, + pages = {A45--A49}, + volume = {78}, + doi = {10.1190/geo2013-0197.1}, + publisher = {Society of Exploration Geophysicists}, +} + +@InProceedings{Hallo2012, + author = {Hallo, M.}, + booktitle = {74th EAGE Conference and Exhibition incorporating EUROPEC 2012}, + title = {Microseismic Surface Monitoring Network Design - Sensitivity and Accuracy}, + year = {2012}, + month = jun, + publisher = {EAGE Publications BV}, + series = {Copenhagen2012}, + collection = {Copenhagen2012}, + doi = {10.3997/2214-4609.20148336}, + issn = {2214-4609}, +} + +@InProceedings{HalloEisner2013, + author = {Hallo, M. and Eisner, L.}, + booktitle = {London 2013, 75th eage conference en exhibition incorporating SPE Europec}, + title = {Influence of the Double-couple Source Model on the Sensitivity of Microseismic Monitoring Networks}, + year = {2013}, + publisher = {EAGE Publications BV}, + series = {London2013}, + collection = {London2013}, + doi = {10.3997/2214-4609.20131023}, + issn = {2214-4609}, +} + +@Book{AkiRichards2002, + author = {Aki, K. and Richards, P. G.}, + publisher = {University Science Books}, + title = {{Q}uantitative {S}eismology}, + year = {2002}, + isbn = {9780935702965}, +} + +@Article{BooreBoatwright1984, + author = {Boore, David M. and Boatwright, John}, + journal = {Bulletin of the Seismological Society of America}, + title = {Average body-wave radiation coefficients}, + year = {1984}, + issn = {0037-1106}, + month = oct, + number = {5}, + pages = {1615--1621}, + volume = {74}, + doi = {10.1785/bssa0740051615}, + publisher = {Seismological Society of America (SSA)}, +} + +@Book{Cerveny2001, + author = {{\v{C}}erven{\'y}, V.}, + publisher = {Cambridge University Press}, + title = {Seismic Ray Theory}, + year = {2001}, + doi = {10.1017/CBO9780511529399}, +} diff --git a/environment-dev.yml b/environment-dev.yml deleted file mode 100644 index 0612212..0000000 --- a/environment-dev.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: pnd-dev -channels: - - conda-forge - - plotly - - defaults -dependencies: - - python>=3.8, <3.13 - - pip - - numpy>=1.20, <2 - - scipy - - pandas - - matplotlib - - plotly::plotly - - cmcrameri - - ipython - - jupyter - - ipywidgets - - pytest - - sphinx - - pydata-sphinx-theme - - sphinx-gallery - - sphinxcontrib-bibtex - - sphinx-design - - numpydoc - - spyder - - pip: - - setuptools_scm diff --git a/environment.yml b/environment.yml index 899d9b9..dd3c992 100644 --- a/environment.yml +++ b/environment.yml @@ -1,18 +1,12 @@ -name: pnd -channels: - - conda-forge - - plotly +# Minimal environment: it pins only the interpreter. +# All package dependencies are declared in pyproject.toml, so install with: +# conda env create -f environment.yml -n pnd +# conda activate pnd +# pip install -e . # runtime only +# pip install -e ".[dev,docs]" # plus tests and documentation +channels: + - conda-forge - defaults dependencies: - - python>=3.8, <3.13 + - python>=3.9, <3.13 - pip - - numpy>=1.20, <2 - - scipy - - pandas - - matplotlib - - plotly::plotly - - cmcrameri - - ipython - - jupyter - - ipywidgets - \ No newline at end of file diff --git a/examples/07_1_ms_homo_test_7.py b/examples/07_1_ms_homo_test_7.py index 2fe0fa0..79e226b 100644 --- a/examples/07_1_ms_homo_test_7.py +++ b/examples/07_1_ms_homo_test_7.py @@ -1,14 +1,33 @@ r""" -07.1 Magnitude sensitivity for homogeneous model: test case 7 -============================================================= +07.1 Magnitude sensitivity for homogeneous model: 1-km DAS array benchmark +========================================================================== This is an example of computing magnitude sensitivity for a homogeneous model. +It reproduces the homogeneous benchmark of a downhole DAS monitoring study: a +1-km-long vertical fibre with a constant noise level, used to isolate the effect of +the DAS directional sensitivity before any velocity structure is introduced. + Test case 7: -* Geometry: DAS cable in a deep vertical borehole -* Data: Noise is constant along the borehole -* Velocity model: homogeneous, Vp = 4300 m/s, Vp/Vs = 1.73, Qp = 100, Qs = 50, rho = 2300 kg/m^3 -* Processing: magnitude sensitivity computation for all stations (for grid slices only). +* Geometry: 1-km-long DAS cable in a vertical borehole, 1 m channel spacing, so 1000 + channels between 1 m and 1000 m depth +* Data: constant strain-rate noise level of 1.95e-9 1/s on every channel, gauge length + of 10 m +* Velocity model: homogeneous, Vp = 2370 m/s, Vp/Vs = 2 (so Vs = 1185 m/s), Qp = Qs = + 100, rho = 2500 kg/m^3 +* Processing: magnitude sensitivity on a vertical section through the well, requiring + detection on at least 60 channels with a signal-to-noise ratio of at least 2, for + P-waves, S-waves and both together. + +Three features of the resulting sections are worth looking for, and all three follow +from the fact that a DAS cable only records the strain component along the fibre: + +1. The minimum detectable magnitude grows with offset from the monitoring well. +2. P-wave sensitivity is worst near the mid-depth of the array, but only at offsets + beyond half the array length, where the ray direction that a vertical fibre records + best is no longer available anywhere along the cable. +3. S-wave sensitivity degrades below the array, and vanishes on the cable axis itself, + where the S-wave polarization is perpendicular to the fibre. """ ############################################################################### @@ -20,7 +39,7 @@ import numpy as np from time import time -# sphinx_gallery_thumbnail_number = -2 +# sphinx_gallery_thumbnail_number = -1 #%% @@ -36,7 +55,7 @@ geometry_df = pndmod.io.read_geometry(geometry_file_path) print(geometry_df) -print(geometry_df.attrs) +print(geometry_df.attrs) ############################################################################### # Read velocity model @@ -52,38 +71,26 @@ # Set imaging grid parameters # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# Imaging grid parameters -gx = np.arange(-7000, 7000 + 250, 250) -gy = np.arange(-7000, 7000 + 250, 250) -gz = np.arange(250, 5000 + 250, 250) +# Imaging grid parameters: a vertical section through the monitoring well, +# reaching twice the array length in offset and in depth +gx = np.arange(-2000, 2000 + 50, 50) +gy = np.array([0]) +gz = np.arange(0, 2000 + 50, 50) # Generate grid points -grid_points = pndmod.utils.generate_grid(x=gx,y=gy,z=gz) -print("Number of grid points:",grid_points.size) -print("Number of grid points in x:",gx.size) -print("Number of grid points in y:",gy.size) -print("Number of grid points in z:",gz.size) -print("grid_points.shape:",grid_points.shape) +grid_points = pndmod.utils.generate_grid(x=gx, y=gy, z=gz) +print("Number of grid points:", grid_points.size) +print("Number of grid points in x:", gx.size) +print("Number of grid points in z:", gz.size) +print("grid_points.shape:", grid_points.shape) ############################################################################### # Plot geometry and imaging grid # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -_ = pndvis.geometry.plot_stations_view(geometry_df, - view='xy', - grid_points=grid_points, - grid_marker_size=1, - plot_names=False, - station_marker_size=10) _ = pndvis.geometry.plot_stations_view(geometry_df, view='xz', - grid_points=grid_points, - grid_marker_size=1, - plot_names=False, - station_marker_size=2) -_ = pndvis.geometry.plot_stations_view(geometry_df, - view='yz', - grid_points=grid_points, + grid_points=grid_points, grid_marker_size=1, plot_names=False, station_marker_size=2) @@ -95,43 +102,40 @@ # ---------------------------------------- # Get medium parameters -density = velocity_df.at[0,'Rho'] -v_p = velocity_df.at[0,'Vp'] -Q_p = velocity_df.at[0,'Qp'] -v_s = velocity_df.at[0,'Vp']/velocity_df.at[0,'VpVsRatio'] -Q_s = velocity_df.at[0,'Qs'] -print("density, v_p, Q_p, v_s, Q_s:",density,v_p,Q_p,v_s,Q_s) - -# Specify source parameters -frequency = 10 # Hz - -# Minimum S/N for detection on individual channel +density = velocity_df.at[0, 'Rho'] +v_p = velocity_df.at[0, 'Vp'] +Q_p = velocity_df.at[0, 'Qp'] +v_s = velocity_df.at[0, 'Vp']/velocity_df.at[0, 'VpVsRatio'] +Q_s = velocity_df.at[0, 'Qs'] +print("density, v_p, Q_p, v_s, Q_s:", density, v_p, Q_p, v_s, Q_s) + +# Minimum S/N for detection on an individual channel min_SNR_p = 2 min_SNR_s = 2 -# Minimum stations on which an event must be detected -min_stations_p = 3 -min_stations_s = 3 +# Minimum channels on which an event must be detected +min_stations_p = 60 +min_stations_s = 60 -# Save parameters +# Save parameters. The source frequency is left unset, so that the representative +# frequency is the peak frequency of each arrival, clamped at the corner frequency +# of 100 Hz. The free surface correction is switched off: every channel of this +# cable is downhole, so none of them sees the doubling of a surface receiver. params = pndmod.classes.DetectionParameters( - f_p=frequency, - f_s=frequency, min_SNR_p=min_SNR_p, min_SNR_s=min_SNR_s, min_stations_p=min_stations_p, - min_stations_s=min_stations_s, + min_stations_s=min_stations_s, + fs_mode='off', ) -# Define slices to use -slice_X = 0 +# Define the slice to use slice_Y = 0 -slice_Z = 2500 -# Get slice planes -X_plane_points, X_plane_indices = pndmod.utils.get_slice_coordinates(grid_points=grid_points,slice_dimension=0,slice_coordinate=slice_X) -Y_plane_points, Y_plane_indices = pndmod.utils.get_slice_coordinates(grid_points=grid_points,slice_dimension=1,slice_coordinate=slice_Y) -Z_plane_points, Z_plane_indices = pndmod.utils.get_slice_coordinates(grid_points=grid_points,slice_dimension=2,slice_coordinate=slice_Z) +# Get the slice plane +Y_plane_points, Y_plane_indices = pndmod.utils.get_slice_coordinates(grid_points=grid_points, + slice_dimension=1, + slice_coordinate=slice_Y) #%% @@ -141,53 +145,22 @@ print("Computing magnitude sensitivity...") - start_time = time() -X_slice_mag_sens_p = pndmod.core.get_mag_sensitivity(grid_coords=X_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='P') -X_slice_mag_sens_s = pndmod.core.get_mag_sensitivity(grid_coords=X_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='S') -X_slice_mag_sens_ps = pndmod.core.get_mag_sensitivity(grid_coords=X_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='PS') -Y_slice_mag_sens_p = pndmod.core.get_mag_sensitivity(grid_coords=Y_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='P') -Y_slice_mag_sens_s = pndmod.core.get_mag_sensitivity(grid_coords=Y_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='S') -Y_slice_mag_sens_ps = pndmod.core.get_mag_sensitivity(grid_coords=Y_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='PS') -Z_slice_mag_sens_p = pndmod.core.get_mag_sensitivity(grid_coords=Z_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='P') -Z_slice_mag_sens_s = pndmod.core.get_mag_sensitivity(grid_coords=Z_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='S') -Z_slice_mag_sens_ps = pndmod.core.get_mag_sensitivity(grid_coords=Z_plane_points, - geometry_df=geometry_df, - velocity_df=velocity_df, - params=params, - wave_mode='PS') +Y_slice_mag_sens_p = pndmod.core.get_mag_sensitivity(grid_coords=Y_plane_points, + geometry_df=geometry_df, + velocity_df=velocity_df, + params=params, + wave_mode='P') +Y_slice_mag_sens_s = pndmod.core.get_mag_sensitivity(grid_coords=Y_plane_points, + geometry_df=geometry_df, + velocity_df=velocity_df, + params=params, + wave_mode='S') +Y_slice_mag_sens_ps = pndmod.core.get_mag_sensitivity(grid_coords=Y_plane_points, + geometry_df=geometry_df, + velocity_df=velocity_df, + params=params, + wave_mode='PS') end_time = time() print(f"Computation time: {end_time - start_time} seconds") @@ -196,103 +169,54 @@ ############################################################################### # Plot magnitude sensitivity # -------------------------- +# The three panels correspond to the three panels of the benchmark figure: P-waves +# only, S-waves only, and detection of both. +station_marker_size = 2 +clb_range = None +plt_levels = None ############################################################################### # Plot magnitude sensitivity for P wave # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# The sensitivity is worst at the mid-depth of the array, and that loss only appears +# at offsets larger than half the array length. -station_marker_size = 25 -clb_range = None -plt_levels = None - -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= X_slice_mag_sens_p, yi=gy, zi=gz, - plot_title=f"Magnitude sensitivity at X={slice_X} m: YZ view, P-waves", - clb_title="$M_w$", - geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, - clb_range=clb_range, - plt_levels=plt_levels) - -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= Y_slice_mag_sens_p, xi=gx, zi=gz, +_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data=Y_slice_mag_sens_p, xi=gx, zi=gz, plot_title=f"Magnitude sensitivity at Y={slice_Y} m: XZ view, P-waves", - clb_title="$M_w$", - geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, - clb_range=clb_range, - plt_levels=plt_levels) - -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= Z_slice_mag_sens_p, xi=gx, yi=gy, - plot_title=f"Magnitude sensitivity at Z={slice_Z} m: XY view, P-waves", - clb_title="$M_w$", + clb_title="$M_w$", geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, + plot_names=False, + station_marker_size=station_marker_size, clb_range=clb_range, plt_levels=plt_levels) - - ############################################################################### # Plot magnitude sensitivity for S wave # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# The S-wave polarization is perpendicular to the fibre on the cable axis, so the +# cable cannot record an S-wave arriving from directly below it. -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= X_slice_mag_sens_s, yi=gy, zi=gz, - plot_title=f"Magnitude sensitivity at X={slice_X} m: YZ view, S-waves", - clb_title="$M_w$", - geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, - clb_range=clb_range, - plt_levels=plt_levels) - -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= Y_slice_mag_sens_s, xi=gx, zi=gz, +_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data=Y_slice_mag_sens_s, xi=gx, zi=gz, plot_title=f"Magnitude sensitivity at Y={slice_Y} m: XZ view, S-waves", - clb_title="$M_w$", + clb_title="$M_w$", geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, - clb_range=clb_range, - plt_levels=plt_levels) - -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= Z_slice_mag_sens_s, xi=gx, yi=gy, - plot_title=f"Magnitude sensitivity at Z={slice_Z} m: XY view, S-waves", - clb_title="$M_w$", - geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, + plot_names=False, + station_marker_size=station_marker_size, clb_range=clb_range, plt_levels=plt_levels) ############################################################################### # Plot magnitude sensitivity for P and S waves # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Requiring both phases takes the worse of the two, so this panel inherits the +# P-wave loss at mid-depth and the S-wave loss below the array. -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= X_slice_mag_sens_ps, yi=gy, zi=gz, - plot_title=f"Magnitude sensitivity at X={slice_X} m: YZ view, P- and S-waves", - clb_title="$M_w$", - geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, - clb_range=clb_range, - plt_levels=plt_levels) - -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= Y_slice_mag_sens_ps, xi=gx, zi=gz, +_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data=Y_slice_mag_sens_ps, xi=gx, zi=gz, plot_title=f"Magnitude sensitivity at Y={slice_Y} m: XZ view, P- and S-waves", - clb_title="$M_w$", - geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, - clb_range=clb_range, - plt_levels=plt_levels) - -_ = pndvis.sensitivity.plot_sensitivity_slice(sens_data= Z_slice_mag_sens_ps, xi=gx, yi=gy, - plot_title=f"Magnitude sensitivity at Z={slice_Z} m: XY view, P- and S-waves", - clb_title="$M_w$", + clb_title="$M_w$", geometry_df=geometry_df, - plot_names = False, - station_marker_size = station_marker_size, + plot_names=False, + station_marker_size=station_marker_size, clb_range=clb_range, plt_levels=plt_levels) diff --git a/examples/README.txt b/examples/README.txt index 14b13eb..42448e1 100644 --- a/examples/README.txt +++ b/examples/README.txt @@ -3,12 +3,14 @@ Examples -------- -This chapter showcases PyNetDesignPro's capabilities through practical, hands-on scenarios. -Whether you're setting up a seismic network, analyzing magnitude sensitivity or location accuracy, or visualizing results, these examples provide step-by-step guidance to help you effectively use PyNetDesignPro. -Designed to be both educational and practical, this chapter bridges the gap between theoretical concepts and real-world applications. -Explore these examples to unlock the full potential of PyNetDesignPro in your projects. +This chapter showcases the capabilities of PyNetDesign through practical, hands-on +scenarios. Whether you are laying out a monitoring network, evaluating its magnitude +sensitivity or visualizing the result, these examples give step-by-step guidance. +Designed to be both educational and practical, the chapter bridges the gap between the +theory of the :ref:`methodology` chapter and real-world application. -Examples cover 10 test monitoring network geometries for both homogeneous and layered velocity models: +The examples cover ten test monitoring network geometries in a homogeneous velocity +model: 1. Simple station geometry with 3 surface stations in the form of a triangle, from :cite:t:`Hallo2012` 2. Simple station geometry with 4 surface stations in the form of a triangle with 1 station in the center @@ -16,13 +18,11 @@ Examples cover 10 test monitoring network geometries for both homogeneous and la 4. Combined geometry with 4 surface stations and DAS in a vertical borehole 5. Geometry with a DAS cable in an inclined borehole 6. Geometry with a DAS cable in a twice inclined borehole -7. Geometry with a DAS cable in a deep vertical borehole and noise constant along the borehole +7. A 1-km-long DAS cable in a vertical borehole with 1 m channel spacing and constant noise, the homogeneous benchmark of a downhole DAS monitoring study 8. Geometry with a DAS cable in a deep vertical borehole and noise decreasing with depth 9. Geometry with a single station above a DAS cable in a deep vertical borehole and noise constant along the borehole 10. Geometry with a single station above a DAS cable in a deep vertical borehole and noise decreasing with depth -Besides, there are also an example that showcase the use of raytracing. - .. only:: html - Gallery of examples: \ No newline at end of file + Gallery of examples: diff --git a/install.bat b/install.bat index bfd5f63..7afd592 100644 --- a/install.bat +++ b/install.bat @@ -1,30 +1,31 @@ :: Windows installer script for PyNetDesign -:: This script creates a Conda environment based on the "environment.yml" or "environment-dev.yml" file and installs the package +:: This script creates the Conda environment from "environment.yml" and installs the package. +:: environment.yml pins only the interpreter; the dependencies come from pyproject.toml, +:: as runtime requirements plus the optional "dev" and "docs" groups. :: Run: install.bat from cmd :: D. Anikiev, 2025-04-01 @echo off +:: Note: do not name any variable PIP_something. pip reads PIP_