diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..fd7571c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,65 @@ +name: Documentation + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +# Allow the deploy job to publish to GitHub Pages. +permissions: + contents: read + pages: write + id-token: write + +# One concurrent Pages deployment; do not cancel a run in progress. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + name: Build Sphinx docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + cache: pip + + - name: Install pycurious with docs extras + # examples + mapping are needed so the tutorial notebooks execute at + # build time (matplotlib/cartopy/pyproj/netCDF4). + run: python -m pip install -e ".[docs,examples,mapping]" + + - name: Copy tutorial notebooks into the docs tree + run: python docs/copy_notebooks.py + + - name: Build HTML + # Not -W: the function docstrings are pdoc-flavoured Markdown that emits + # cosmetic RST warnings under autodoc. The real quality gate is + # nb_execution_raise_on_error in conf.py — a tutorial that fails to + # execute fails the build. + run: sphinx-build -b html docs docs/_build/html + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_build/html + + deploy: + name: Deploy to GitHub Pages + # Only publish from master; PRs build above but do not deploy. + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..d445d4c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,55 @@ +name: Publish to PyPI + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build: + name: Build distributions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Build sdist and wheel + run: | + python -m pip install --upgrade build twine + python -m build + + - name: Check the distributions + run: python -m twine check dist/* + + - name: Upload distributions + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + # Trusted Publishing: PyPI mints a short-lived token from this workflow's + # OIDC identity, so no password or API token is stored. Configure the + # trusted publisher once on PyPI (project pycurious): owner brmather, + # repository pycurious, workflow publish.yml, environment pypi. + environment: + name: pypi + url: https://pypi.org/p/pycurious + permissions: + id-token: write + steps: + - name: Download distributions + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Publish + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..8a62435 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,40 @@ +name: Tests + +on: + push: + branches: ["**"] + pull_request: + workflow_dispatch: + +jobs: + test: + name: pytest (Python ${{ matrix.python-version }} on ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + include: + # one macOS leg on the newest interpreter, to catch platform issues + - os: macos-latest + python-version: "3.13" + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install pycurious with test extra + # The suite imports only pytest, numpy, scipy and pycurious itself; + # numpy/scipy are core dependencies and `import pycurious` stays light + # because mapping/download lazy-import their heavy deps. So the `test` + # extra (just pytest) is all that is needed to run everything. + run: python -m pip install -e ".[test]" + + - name: Run the full test suite + run: pytest -v diff --git a/.gitignore b/.gitignore index d86c9c1..63e011c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,38 @@ -pycurious-src/pycurious/Examples/Notebooks/Bouligand/.ipynb_checkpoints/* -pycurious-src/pycurious/Examples/Notebooks/Tanaka/.ipynb_checkpoints/* -pycurious-src/pycurious/Examples/.ipynb_checkpoints/* +# Python *.pyc +__pycache__/ +*.so .eggs -build *.egg-info -*.so +build +dist +.pytest_cache/ + +# Jupyter +.ipynb_checkpoints/ + +# macOS +.DS_Store + +# Datasets fetched by pycurious.download at notebook runtime. These are +# hundreds of megabytes and are reproducible from their URL and checksum, so +# they are cached locally rather than committed. Everything in Examples/data +# is ignored except the small synthetic anomaly the tests rely on. +Examples/data/* +!Examples/data/test_mag_data.txt + +# Rasters and grids written by the notebooks themselves, e.g. the GeoTIFF +# exported at the end of Ex5. +*.tif +*.tiff +*.npz +!tests/*.npz + +# Sphinx build output, and the tutorial notebooks + data fixture that +# docs/copy_notebooks.py stages into the source tree on every build. These are +# regenerated from Examples/, so they are never committed. +docs/_build/ +docs/tutorials/bouligand/ +docs/tutorials/tanaka/ +docs/data/ +docs/api/generated/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e0bf433..0000000 --- a/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -language: python - -# define global environment variables -env: - global: - -DEPLOY_DOCS=false - -DEPLOY_PYPI=false - -matrix: - include: - - os: linux - dist: trusty - python: 3.5 - env: - - DEPLOY_DOCS=true - - DEPLOY_PYPI=true - - os: linux - dist: bionic - python: 3.7 - - os: osx - osx_image: xcode10.2 - language: generic - -install: - - python3 -m pip install Cython numpy scipy pytest git+https://github.com/lmoresi/pdoc.git - - python3 -m pip install -e . - -script: - # run the tests - - pytest tests/ - # generate the release - - python3 setup.py sdist - # generate the docs - - mkdir docs - - cd docs - - pdoc --html -o . --force pycurious - - mv pycurious/*.html . - - rm -rf pycurious/ - - cd ../ - -deploy: - - provider: pages - skip_cleanup: true - github_token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable - keep_history: true - on: - branch: master - condition: '$DEPLOY_DOCS == "true"' - verbose: true - local_dir: docs/ - - provider: pypi - user: $PYPI_USERNAME - password: $PYPI_PASSWORD - on: - tags: true - condition: '$DEPLOY_PYPI == true' - skip_cleanup: true \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..27a9070 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,215 @@ +# PyCurious + +Estimates Curie point depth — the depth at which rock loses its magnetisation — +from the radially averaged spectrum of a magnetic anomaly. Two methods, sharing +one grid/spectrum layer: + +- **Bouligand *et al.* (2009)** — fits a four-parameter analytic spectrum + (`beta, zt, dz, C`) by optimisation. +- **Tanaka *et al.* (1999)** — the centroid method: two straight lines fitted to + separate wavenumber bands. + +Both return **uncertainties**, which is the defining change in v2. Anything that +returns a bare number without one is a pre-v2 remnant. + +## Commands + +```bash +pytest # 70 tests, ~40 s +pytest -m "not slow" # 67 tests, ~26 s -- skips the calibration tests that + # fit a few hundred realisations +pytest tests/test_tanaka.py -q +``` + +Notebooks need extras that are not runtime dependencies: +`pip install -e ".[examples,mapping,geotiff,download]"`. GDAL is deliberately +its own extra — its bindings need a matching system libgdal, and folding it into +`mapping` would break `pip install` for most users. `conda install gdal` is +usually easier than pip. + +To execute notebooks headlessly: +`MPLBACKEND=Agg jupyter nbconvert --to notebook --execute --inplace ` + +CI runs on **GitHub Actions** (`.github/workflows/`): `tests.yml` runs the suite +on every push and PR across Python 3.9-3.13; `docs.yml` builds the Sphinx docs +(and deploys to GitHub Pages from `master`); `publish.yml` uploads to PyPI via +OIDC trusted publishing when a GitHub release is published. Travis is gone. + +## Layout + +``` +pycurious/ + grid.py CurieGrid + the shared spectrum and covariance machinery + optimise_bouligand.py CurieOptimiseBouligand(CurieGrid) + optimise_tanaka.py CurieOptimiseTanaka(CurieGrid) + parallel.py CurieParallel mixin -- CurieGrid inherits it + synthetic.py fractal_anomaly(): synthetics with a known answer + mapping.py projections, netCDF, GeoTIFF (all lazily imported) + download.py cached downloads with md5 checks +``` + +`CurieGrid(CurieParallel)`, and both optimisers extend `CurieGrid`, so every +class has `subgrid`, `radial_spectrum`, `window_spectrum` and +`parallelise_routine`. + +## Conventions that are easy to get wrong + +These caused real bugs. Check them before changing anything spectral. + +**Wavenumbers are rad/km, everywhere.** `radial_spectrum` returns `k` in rad/km, +and Tanaka's fitting bands are given in the same units. They used to be +cycles/km; a guard warns when a band looks like a leftover cycles/km value, +because such a call still runs and returns a plausible number. + +**`dk = 2*pi/(N*dx)`**, the DFT fundamental — not `(N-1)`. Using `(N-1)` +understates every depth by `(N-1)/N`. + +**`power` selects which spectrum you get.** `radial_spectrum` raises `|FFT|` to +`power` before averaging. Since `Phi = |FFT|**2`: + +| method | `power` | quantity | +|---|---|---| +| Bouligand | `2.0` (default) | `ln Phi`, log power | +| Tanaka | `1.0` | `ln Phi**0.5`, log amplitude | + +`power=0.5` appears in pre-v2 code and docs and is simply wrong; it halves every +depth. + +**Depths are positive downwards.** `optimise` returns depths, not the negative +gradients the fits produce. + +**Tanaka bands have no defaults, deliberately.** Each straight-line limit holds +only over part of the spectrum: the `zt` band needs wavelengths shorter than +~4x the source thickness, the `z0` band needs `|k|d << 1`. Violating either +still yields a confident-looking fit through the wrong part of the curve. Use +`check_bands(k, zt_range, z0_range, thickness=...)`, which reports point counts, +wavelengths, `|k|d` and the bias in km. + +Beware apparent accuracy on the legacy 305 km fixture: its `|k|d` violation +biases `z0` low and unmodelled fractal magnetisation biases both depths high, +and the two partly cancel. That cancellation is a property of that particular +synthetic, not evidence the method worked. + +## Uncertainties + +The interesting machinery, and where most of the subtlety lives. + +`radial_spectrum` returns `sigma_Phi`, the **scatter of FFT cells within an +annulus**. That is not what a fit needs. `window_spectrum` converts it to the +**uncertainty of the annulus mean** and is what both optimisers call (through +their private `_spectrum`). Prefer it over `radial_spectrum` for anything fitted. + +Two corrections, both measured by Monte Carlo rather than assumed: + +1. **Within a bin**, cells are not independent — a real field is Hermitian, so + about half repeat (exactly a factor of 2 untapered), and a taper correlates + neighbours further. `_dof_factor` / `_TAPER_DOF` encode this as + `dof_inf * N / max(N - lost, 1)`; the `lost` term dominates in the sparse + inner bins, which is exactly where a centroid depth is determined. +2. **Between bins**, neighbouring residuals correlate because a taper spreads + each wavenumber over several bins. `_banded_correlation` estimates this from + the residuals and `_gls_covariance` solves + `(J^T R^-1 J)^-1`. Ignoring it understates every reported sigma by ~30%. + +Both live in `grid.py` and are shared. Tanaka builds `J` analytically (a line); +Bouligand uses finite differences. + +Beyond the covariance: `CurieOptimiseBouligand.profile()` gives profile-deviance +intervals, which matter because `dz` and `CPD` are genuinely asymmetric; +`metropolis_hastings()` samples the posterior; `sensitivity()` resamples the +spectrum. `CurieOptimiseTanaka.sensitivity()` additionally jitters the band +edges, since band placement usually dominates spectral scatter. There is +deliberately no `profile` on the Tanaka side — each band is a straight-line fit, +where profile and covariance intervals are provably identical. + +## Synthetics + +`pycurious.fractal_anomaly(n, dx, beta, zt, dz, C, seed)` filters white noise by +the square root of `bouligand2009`, so the expected spectrum *is* the model. +Returns `(data, extent)`, ready to splat: `CurieOptimiseBouligand(data, *extent)`. + +- True Curie depth is `zt + dz`; true centroid depth (Tanaka) is `zt + dz/2`. +- `beta` and `zt` recover tightly. **`dz` does not** — roughly one realisation in + five is tens of percent out, so assert on it across seeds or in the mean. +- **`C` is not recoverable**; treat it as a nuisance parameter. Log-averaging + costs the Euler-Mascheroni constant and a `np.hanning` taper costs + `ln(3/8)**2`, so a fit returns `C` about 2.5 low under hanning. + +Prefer generated synthetics over `tests/test_mag_data.txt`. That fixture is only +305 km across for a 10 km layer — too narrow to constrain the long wavelengths, +so `test_optimise.py` asserts no accuracy against it. Accuracy claims belong in +`tests/test_recovery.py`. + +## Parallelism + +`parallelise_routine(window, xc_list, yc_list, func, *args, **kwargs)` consumes +two reserved keywords rather than forwarding them: + +- `on_error` — `"raise"` (default) or `"ignore"`, which NaN-fills and warns. +- `seed` — gives each centroid an independent child seed, so results do not + depend on processor count. Only routines marked `@stochastic` accept it; + passing it elsewhere warns. + +macOS and Windows default to the **spawn** start method, so a script calling this +at module level must guard with `if __name__ == "__main__":` or every worker +re-executes it. A `taper` or `process_subgrid` defined in a notebook cell or in +`__main__` cannot be reconstructed in a spawned child; this is detected up front +and falls back to serial with a warning rather than deadlocking. + +## Notebooks + +`Examples/Notebooks/{Bouligand,Tanaka}/Ex1..Ex5`, plus `Examples/0-StartHere.ipynb`. +All are committed **without outputs** — re-running one will dirty it, so strip +outputs before committing. They quote their own numbers in prose, so changing +anything numerical means re-running *and* re-reading the surrounding markdown. + +Ex5 in both folders needs real data: EMAG2 v3 (497 MB) and Li et al. 2017 +(124 MB), fetched by `pycurious.download` into `Examples/data/`, which is +gitignored apart from `test_mag_data.txt`. There is no "EMAG3" — EMAG2 version 3 +is the current release. + +Style: explanation belongs in markdown cells, with inline code comments kept to a +minimum. + +## Style + +LGPL header on every module, then a short module docstring. Google-style +docstrings with `Args:` / `Returns:` / `Notes:` / `References:`, rendered by +Sphinx (`sphinx.ext.napoleon`) into the API reference — see `docs/`. The heavy +method narrative and math live in the hand-written theory pages +(`docs/theory/`), not the module docstrings, which were trimmed to summaries in +the pdoc→Sphinx migration; keep new math there rather than re-growing the +docstrings. Calibration numbers in docstrings are measured — if you change the +method, re-measure rather than adjusting the prose. + +## Packaging + +`MANIFEST.in` lists the eleven notebooks individually rather than globbing them. +It does not consult `.gitignore`, so a `recursive-include` shipped whatever was +in the working tree — an sdist once carried 35 notebooks, 20 of them untracked. +Adding a notebook means adding a line there. + +To check what an sdist would contain: + +```bash +rm -rf pycurious.egg-info # stale SOURCES.txt is reused otherwise, +python -m build --sdist # silently reinstating files you excluded +tar tzf dist/pycurious-*.tar.gz | grep ipynb +``` + +The `rm` matters. `SOURCES.txt` is regenerated from the *old* manifest if it is +left in place, which makes a correct `MANIFEST.in` look broken. + +## Known defects + +- **`install_documentation()` fails for an installed package.** It is still + advertised in the README, but `[tool.setuptools] packages = + ["pycurious"]` installs only the package directory, and `Examples/` sits at the + repository root. `_find_examples` looks inside the package and one level above + it, and in a wheel neither exists. Works from a source checkout only. + Pre-dates the v2 restructure that moved `Examples/` out of the package. + +`notes/bouligand-findings.md` records seven findings from the Tanaka work and how +each was resolved — including two whose prescribed fix turned out to be wrong on +measurement. Worth reading before trusting any of them. (It lives in `notes/`, +not `docs/`, so it stays out of the Sphinx build.) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61e1dcf..aba313c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,12 +6,12 @@ helping with a missing test, or it may even just be pointing out a bug or potent For bugs and suggestions, the most effective way to reach the team is by raising an issue on the github issue tracker. Github allows you to classify your issues so that we know if it is a bug report, feature request or feedback to the authors. -If you wish to contribute some changes to the code then you should submit a *pull request*. On github we can review the code that you are contributing and discuss it before the changes are merged into the development version of the code. Before embarking on changes to the code, please first take a look at the [`dev` branch](https://github.com/brmather/pycurious/tree/dev) which is where unreleased changes are staged: we may have +If you wish to contribute some changes to the code then you should submit a *pull request*. On github we can review the code that you are contributing and discuss it before the changes are merged into the development version of the code. Before embarking on changes to the code, please first check the [open issues and pull requests](https://github.com/brmather/pycurious/issues): we may have been working on something similar already. ## How to create a Pull Request (PR) -Create your own fork of the project on GitHub: log into your GitHub account, navigate to the [`pycurious` repository](https://github.com/brmather/pycurious/tree/dev) +Create your own fork of the project on GitHub: log into your GitHub account, navigate to the [`pycurious` repository](https://github.com/brmather/pycurious) and click on the *fork* button at the top right corner of this repository. You can find detailed instructions and a discussion of how to fork a repository, clone it locally and work on the changes [in the GitHub guides](https://guides.github.com/activities/forking/). @@ -31,10 +31,13 @@ We welcome all contributions to `pycurious`, but if you would like to assist us ## Tests -We use `pytest` for the unit testing framework in `pycurious`. In the source directory this means running: +We use `pytest` for the unit testing framework in `pycurious`. Install the test +extra and run the suite from the source directory: ```bash -python setup.py test +python -m pip install -e ".[test]" +pytest # the full suite +pytest -m "not slow" # skip the calibration tests that fit hundreds of realisations ``` The existing tests should be passing before you start coding (help us out with an issue if that is not the case !) and when you have finished. Any new functionality should also have tests that we can use to verify the code. It is important that you make it clear if the original tests have had to change to accomodate new code / functionality. diff --git a/Docker/Dockerfile b/Docker/Dockerfile deleted file mode 100644 index 9e00b1f..0000000 --- a/Docker/Dockerfile +++ /dev/null @@ -1,76 +0,0 @@ -################################################# -# Short docker file to distribute some notebooks -################################################# - -ARG FROMIMG_ARG=brmather/pycurious-base:0.9.1 -FROM ${FROMIMG_ARG} - -################################################## -# Non standard as the files come from the packages - -USER root -WORKDIR /home/jovyan/ - -RUN apk add \ - libxml2-dev \ - libxslt-dev - -RUN python3 -m pip install --no-cache-dir --upgrade \ - jupyter \ - pytest - -### PyCurious - Notebooks - -ENV MODULE_DIR="pycurious-src" -ADD --chown=jovyan:jovyan . / pycurious-src/ -RUN cd $MODULE_DIR && python3 -m pip install --no-cache-dir --upgrade --no-deps . - -RUN ipython3 -c 'import pycurious; pycurious.documentation.install_documentation(path="Notebooks")' - - -# change ownership of everything -ENV NB_USER jovyan -RUN chown -R jovyan:jovyan /home/jovyan -USER jovyan - - -# Run the tests with PyTest -RUN cd $MODULE_DIR && pytest tests/ - - -## These are supplied by the build script -## build-dockerfile.sh - -ARG IMAGENAME_ARG -ARG PROJ_NAME_ARG=pycurious -ARG NB_PORT_ARG=8888 -ARG NB_PASSWD_ARG="" -ARG NB_DIR_ARG="Notebooks" -ARG START_NB_ARG="0-StartHere.ipynb" - -# The args need to go into the environment so they -# can be picked up by commands/templates (defined previously) -# when the container runs - -ENV IMAGENAME=$IMAGENAME_ARG -ENV PROJ_NAME=$PROJ_NAME_ARG -ENV NB_PORT=$NB_PORT_ARG -ENV NB_PASSWD=$NB_PASSWD_ARG -ENV NB_DIR=$NB_DIR_ARG -ENV START_NB=$START_NB_ARG - - -# Trust all notebooks -RUN find -name \*.ipynb -print0 | xargs -0 jupyter trust - -# expose notebook port server port -EXPOSE $NB_PORT - -VOLUME /home/jovyan/$NB_DIR/user_data - - -ENTRYPOINT ["/sbin/tini", "--"] - -# launch notebook -ADD --chown=jovyan:jovyan Docker/scripts/run-jupyter.sh scripts/run-jupyter.sh -CMD scripts/run-jupyter.sh \ No newline at end of file diff --git a/Docker/Dockerfile-base b/Docker/Dockerfile-base deleted file mode 100644 index 261a846..0000000 --- a/Docker/Dockerfile-base +++ /dev/null @@ -1,80 +0,0 @@ -FROM alpine:edge - -LABEL maintainer="brmather1@gmail.com" -LABEL repo="https://github.com/brmather/pycurious" - -# Install things -RUN apk add --update --no-cache \ - gcc \ - g++ \ - gfortran \ - build-base \ - linux-headers \ - python3-dev \ - py3-pip \ - py3-numpy \ - py3-scipy \ - py3-zmq \ - cmake \ - curl \ - wget \ - tini \ - libzmq \ - zlib-dev \ - musl-dev \ - freetype-dev \ - libpng-dev \ - libxml2-dev \ - openblas-dev - -RUN echo "http://mirror.leaseweb.com/alpine/edge/testing" >> /etc/apk/repositories -RUN apk add --update --no-cache \ - geos-dev \ - proj4-dev - -RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories -RUN apk add --update --no-cache \ - libressl2.7-libcrypto \ - gdal-dev - - -RUN ln -s /usr/include/locale.h /usr/include/xlocale.h -RUN python3 -m pip install --no-cache-dir --upgrade pip && \ - python3 -m pip install --no-cache-dir --upgrade --force-reinstall numpy scipy && \ - python3 -m pip install --no-cache-dir setuptools wheel Cython && \ - python3 -m pip install --no-cache-dir \ - packaging \ - gdal \ - jupyter \ - tornado \ - pyepsg \ - cartopy && \ - python3 -m pip install --no-cache-dir matplotlib && \ - python3 -m pip install --no-cache-dir --upgrade --ignore-installed pyzmq - -# expose notebook port -EXPOSE 8888 - - -# add a notebook profile -RUN mkdir -p -m 700 /root/.jupyter/ && \ - echo "c.NotebookApp.ip = '0.0.0.0'" >> /root/.jupyter/jupyter_notebook_config.py && \ - echo "c.NotebookApp.token = ''" >> /root/.jupyter/jupyter_notebook_config.py - - -RUN addgroup jovyan && \ - adduser -D jovyan -G jovyan - -WORKDIR /home/jovyan -RUN chown -R jovyan:jovyan /home/jovyan -USER jovyan - - -VOLUME /home/jovyan/$NB_DIR/user_data - - -ENTRYPOINT ["/sbin/tini", "--"] - -# launch notebook -# CMD scripts/run-jupyter.sh -CMD ["jupyter", "notebook", "--ip='0.0.0.0'", "--NotebookApp.token='' ", "--no-browser"] \ No newline at end of file diff --git a/Docker/scripts/run-jupyter.sh b/Docker/scripts/run-jupyter.sh deleted file mode 100755 index 7443d1b..0000000 --- a/Docker/scripts/run-jupyter.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env sh - -PASS=${NB_PASSWD:-""} -OPEN=${START_NB:-""} -PORT=${NB_PORT:-8888} - -cd /home/jovyan/Notebooks - -jupyter-notebook --port=$NB_PORT --ip='0.0.0.0' --no-browser --allow-root \ - --NotebookApp.token=$NB_PASSWD --NotebookApp.default_url=/tree/$OPEN - -# Don't exit - -while true; do - sleep 600 -done diff --git a/pycurious/Examples/0-StartHere.ipynb b/Examples/0-StartHere.ipynb similarity index 77% rename from pycurious/Examples/0-StartHere.ipynb rename to Examples/0-StartHere.ipynb index 888bab7..1d9e9a3 100644 --- a/pycurious/Examples/0-StartHere.ipynb +++ b/Examples/0-StartHere.ipynb @@ -8,7 +8,7 @@ "\n", "---\n", "\n", - "Magnetic data is one of the most common geophysics datasets available on the surface of the Earth. Curie depth is the depth at which rocks lose their magnetism. The most prevalent magnetic mineral is magnetite, which has a Curie point of 580°C, thus the Curie depth is often interpreted as the 580°C isotherm.\n", + "Magnetic data is one of the most common geophysics datasets available on the surface of the Earth. Curie depth is the depth at which rocks lose their magnetism. The most prevalent magnetic mineral is magnetite, which has a Curie point of 580\u00b0C, thus the Curie depth is often interpreted as the 580\u00b0C isotherm.\n", "\n", "Current methods to derive Curie depth first compute the (fast) Fourier transform over a square window of a magnetic anomaly that has been reduced to the pole. The depth and thickness of magnetic sources is estimated from the slope of the radial power spectrum. `pycurious` implements the Tanaka *et al.* (1999) and Bouligand *et al.* (2009) methods for computing the thickness of a buried magnetic source, which are covered within Jupyter notebooks.\n", "\n", @@ -31,9 +31,11 @@ "\n", "### Tanaka\n", "\n", - "- [Ex1-Plot-power-spectrum.ipynb](./Notebooks/Tanaka/Ex1-Plot-power-spectrum.ipynb)\n", + "- [Ex1-Plot-amplitude-spectrum.ipynb](./Notebooks/Tanaka/Ex1-Plot-amplitude-spectrum.ipynb)\n", "- [Ex2-Compute-Curie-depth.ipynb](./Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb)\n", "- [Ex3-Parameter-exploration.ipynb](./Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb)\n", + "- [Ex4-Spatial-variation-of-Curie-depth.ipynb](./Notebooks/Tanaka/Ex4-Spatial-variation-of-Curie-depth.ipynb)\n", + "- [Ex5-Mapping-Curie-depth-EMAG2.ipynb](./Notebooks/Tanaka/Ex5-Mapping-Curie-depth-EMAG2.ipynb)\n", "\n", "### Bouligand\n", "\n", @@ -47,29 +49,31 @@ "\n", "### Dependencies\n", "\n", - "You will need **Python 2.7 or 3.5+**.\n", + "You will need **Python 3.9 or newer**.\n", "Also, the following packages are required:\n", "\n", "- [`numpy`](http://numpy.org)\n", "- [`scipy`](https://scipy.org)\n", - "- [`cython`](https://cython.org/)\n", "\n", "__Optional dependencies__ for mapping module and running the Notebooks:\n", "\n", "- [`matplotlib`](https://matplotlib.org/)\n", "- [`pyproj`](https://github.com/jswhit/pyproj)\n", "- [`cartopy`](https://scitools.org.uk/cartopy/docs/latest/)\n", + "- [`netCDF4`](https://unidata.github.io/netcdf4-python/)\n", + "- [`requests`](https://requests.readthedocs.io/)\n", "\n", "### Installing using pip\n", "\n", "You can install `pycurious` using the\n", - "[`pip package manager`](https://pypi.org/project/pip/) with either version of Python:\n", + "[`pip package manager`](https://pypi.org/project/pip/):\n", "\n", "```bash\n", - "python2 -m pip install pycurious\n", "python3 -m pip install pycurious\n", "```\n", - "All the dependencies will be automatically installed by `pip`.\n", + "All the required dependencies will be automatically installed by `pip`.\n", + "The optional dependencies are grouped into the `download`, `mapping` and\n", + "`examples` extras, e.g. `pip install pycurious[mapping]`.\n", "\n", "### Installing using Docker\n", "\n", @@ -83,10 +87,11 @@ "\n", "## Usage\n", "\n", - "PyCurious consists of 2 classes:\n", + "PyCurious consists of 3 classes:\n", "\n", "- `CurieGrid`: base class that computes radial power spectrum, centroids for processing, decomposition of subgrids.\n", - "- `CurieOptimise`: optimisation module for fitting the synthetic power spectrum (inherits CurieGrid).\n", + "- `CurieOptimiseBouligand`: optimisation module for fitting the synthetic power spectrum of Bouligand *et al.* (2009) (inherits CurieGrid).\n", + "- `CurieOptimiseTanaka`: optimisation module for the centroid method of Tanaka *et al.* (1999) (inherits CurieGrid).\n", "\n", "Also included is a `mapping` module for gridding scattered data points, and converting between coordinate reference systems (CRS).\n", "\n", @@ -95,8 +100,8 @@ "```python\n", "import pycurious\n", "\n", - "# initialise CurieOptimise object with 2D magnetic anomaly\n", - "grid = pycurious.CurieOptimise(mag_anomaly, xmin, xmax, ymin, ymax)\n", + "# initialise CurieOptimiseBouligand object with 2D magnetic anomaly\n", + "grid = pycurious.CurieOptimiseBouligand(mag_anomaly, xmin, xmax, ymin, ymax)\n", "\n", "# extract a square window of the magnetic anomaly\n", "subgrid = grid.subgrid(window_size, x, y)\n", @@ -121,8 +126,8 @@ "\n", "## References\n", "\n", - "1. Bouligand, C., Glen, J. M. G., & Blakely, R. J. (2009). Mapping Curie temperature depth in the western United States with a fractal model for crustal magnetization. Journal of Geophysical Research, 114(B11104), 1–25. https://doi.org/10.1029/2009JB006494\n", - "2. Tanaka, A., Okubo, Y., & Matsubayashi, O. (1999). Curie point depth based on spectrum analysis of the magnetic anomaly data in East and Southeast Asia. Tectonophysics, 306(3–4), 461–470. https://doi.org/10.1016/S0040-1951(99)00072-4\n" + "1. Bouligand, C., Glen, J. M. G., & Blakely, R. J. (2009). Mapping Curie temperature depth in the western United States with a fractal model for crustal magnetization. Journal of Geophysical Research, 114(B11104), 1\u201325. https://doi.org/10.1029/2009JB006494\n", + "2. Tanaka, A., Okubo, Y., & Matsubayashi, O. (1999). Curie point depth based on spectrum analysis of the magnetic anomaly data in East and Southeast Asia. Tectonophysics, 306(3\u20134), 461\u2013470. https://doi.org/10.1016/S0040-1951(99)00072-4\n" ] }, { @@ -149,7 +154,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.13.14" } }, "nbformat": 4, diff --git a/pycurious/Examples/Images/pycurious-logo.pdf b/Examples/Images/pycurious-logo.pdf similarity index 100% rename from pycurious/Examples/Images/pycurious-logo.pdf rename to Examples/Images/pycurious-logo.pdf diff --git a/pycurious/Examples/Images/pycurious-logo.png b/Examples/Images/pycurious-logo.png similarity index 100% rename from pycurious/Examples/Images/pycurious-logo.png rename to Examples/Images/pycurious-logo.png diff --git a/pycurious/Examples/Notebooks/.ipynb_checkpoints/Ex1-Plot-power-spectrum-checkpoint.ipynb b/Examples/Notebooks/.ipynb_checkpoints/Ex1-Plot-power-spectrum-checkpoint.ipynb similarity index 100% rename from pycurious/Examples/Notebooks/.ipynb_checkpoints/Ex1-Plot-power-spectrum-checkpoint.ipynb rename to Examples/Notebooks/.ipynb_checkpoints/Ex1-Plot-power-spectrum-checkpoint.ipynb diff --git a/pycurious/Examples/Notebooks/.ipynb_checkpoints/Ex2a-Compute-Curie-depth-Tanaka-approach-checkpoint.ipynb b/Examples/Notebooks/.ipynb_checkpoints/Ex2a-Compute-Curie-depth-Tanaka-approach-checkpoint.ipynb similarity index 100% rename from pycurious/Examples/Notebooks/.ipynb_checkpoints/Ex2a-Compute-Curie-depth-Tanaka-approach-checkpoint.ipynb rename to Examples/Notebooks/.ipynb_checkpoints/Ex2a-Compute-Curie-depth-Tanaka-approach-checkpoint.ipynb diff --git a/pycurious/Examples/Notebooks/.ipynb_checkpoints/Ex2b-Compute-Curie-depth-Bouligand-approach-checkpoint.ipynb b/Examples/Notebooks/.ipynb_checkpoints/Ex2b-Compute-Curie-depth-Bouligand-approach-checkpoint.ipynb similarity index 100% rename from pycurious/Examples/Notebooks/.ipynb_checkpoints/Ex2b-Compute-Curie-depth-Bouligand-approach-checkpoint.ipynb rename to Examples/Notebooks/.ipynb_checkpoints/Ex2b-Compute-Curie-depth-Bouligand-approach-checkpoint.ipynb diff --git a/pycurious/Examples/Notebooks/.ipynb_checkpoints/Ex3-Optimisation-routine-checkpoint.ipynb b/Examples/Notebooks/.ipynb_checkpoints/Ex3-Optimisation-routine-checkpoint.ipynb similarity index 100% rename from pycurious/Examples/Notebooks/.ipynb_checkpoints/Ex3-Optimisation-routine-checkpoint.ipynb rename to Examples/Notebooks/.ipynb_checkpoints/Ex3-Optimisation-routine-checkpoint.ipynb diff --git a/Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb b/Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb new file mode 100644 index 0000000..ce84c2c --- /dev/null +++ b/Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 1 - Plot power spectra\n", + "\n", + "The Bouligand *et al.* (2009) model describes the radially averaged power spectrum of a magnetic anomaly produced by a fractally magnetised layer:\n", + "\n", + "$$(1) \\quad \\ln \\Phi_{\\Delta T}(|k|) = C - 2|k|z_t - (\\beta-1)\\ln|k| - |k|\\Delta z + \\ln A(|k|, \\beta, \\Delta z)$$\n", + "\n", + "Four parameters: the depth to the top of the source $z_t$, its thickness $\\Delta z$, the fractal parameter of the magnetisation $\\beta$, and a constant $C$ setting the level. The Curie depth is $z_t + \\Delta z$.\n", + "\n", + "This notebook builds intuition for what each parameter does to the spectrum, and then checks that a real spectrum computed from gridded data looks like the analytic one.\n", + "\n", + "### Contents\n", + "\n", + "- [What each parameter does](#What-each-parameter-does)\n", + "- [A spectrum from gridded data](#A-spectrum-from-gridded-data)\n", + "- [Choice of taper](#Choice-of-taper)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "import pycurious" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What each parameter does\n", + "\n", + "`pycurious.bouligand2009` evaluates equation (1) directly. Varying one parameter at a time shows which part of the spectrum each controls, which is worth knowing before trying to fit them.\n", + "\n", + "The curves below are offset vertically so their shapes can be compared; $C$ only slides a curve up or down." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "k = np.linspace(1e-3, 3, 10000)\n", + "beta, zt, dz, C = 3.0, 1.0, 20.0, 0.0\n", + "\n", + "fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))\n", + "\n", + "for zti in np.arange(0.0, 2.5, 0.5):\n", + " Phi = pycurious.bouligand2009(k, beta, zti, dz, C)\n", + " ax1.semilogx(k, Phi - Phi.max(), label=r'$z_t$ = {} km'.format(zti))\n", + "\n", + "for dzi in [10., 20., 50., 100., 200.]:\n", + " Phi = pycurious.bouligand2009(k, beta, zt, dzi, C)\n", + " ax2.semilogx(k, Phi - Phi.min(), label=r'$\\Delta z$ = {} km'.format(dzi))\n", + "\n", + "for betai in np.arange(0, 5, 1):\n", + " Phi = pycurious.bouligand2009(k, betai, zt, dz, C)\n", + " ax3.semilogx(k, Phi - Phi[-1], label=r'$\\beta$ = {}'.format(betai))\n", + "\n", + "ax1.set_ylim(-20, 0)\n", + "ax2.set_ylim(0, 20)\n", + "for ax in (ax1, ax2, ax3):\n", + " ax.set_xlabel(r'$|k|$ [rad km$^{-1}$]')\n", + " ax.legend()\n", + "ax1.set_ylabel(r'$\\ln \\Phi_{\\Delta T}$ (offset)')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading these left to right:\n", + "\n", + "- **$z_t$** sets the slope at *short* wavelengths, on the right. A deeper top attenuates the high wavenumbers more steeply, through the $-2|k|z_t$ term.\n", + "- **$\\Delta z$** acts only at *long* wavelengths, on the left, and its effect saturates: 100 km and 200 km are nearly indistinguishable. A layer thicker than the longest wavelength a window resolves looks the same as one thicker still, which is why $\\Delta z$ is the hardest of the four to pin down.\n", + "- **$\\beta$** tilts the whole curve, through the $-(\\beta-1)\\ln|k|$ term. It is constrained across the entire spectrum rather than at one end.\n", + "\n", + "That difference matters when fitting: $\\beta$ and $z_t$ are determined by the bulk of the data, while $\\Delta z$ rests on a handful of the longest-wavelength bins.\n", + "\n", + "## A spectrum from gridded data\n", + "\n", + "`pycurious.fractal_anomaly` synthesises an anomaly whose expected spectrum is equation (1) for parameters of our choosing, so the measured spectrum can be compared against the model it was built from." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "beta_true, zt_true, dz_true = 3.0, 1.0, 20.0\n", + "\n", + "data, extent = pycurious.fractal_anomaly(\n", + " n=512, dx=2.0, beta=beta_true, zt=zt_true, dz=dz_true, C=5.0, seed=1)\n", + "\n", + "grid = pycurious.CurieGrid(data, *extent)\n", + "\n", + "xpt = 0.5*(extent[0] + extent[1])\n", + "ypt = 0.5*(extent[2] + extent[3])\n", + "window_size = 1000e3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(7, 6))\n", + "im = ax.imshow(data, extent=np.array(extent)*1e-3, cmap='RdBu_r', origin='lower')\n", + "ax.set_xlabel('easting [km]')\n", + "ax.set_ylabel('northing [km]')\n", + "fig.colorbar(im, ax=ax, label='nT')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`window_spectrum` takes a square window, computes the radially averaged spectrum and returns the uncertainty of each binned value. `power=2` gives $\\ln \\Phi_{\\Delta T}$, which is what equation (1) describes; `power=1` would give the log *amplitude* spectrum that the Tanaka method works with instead.\n", + "\n", + "The analytic curve is drawn with the parameters the field was generated from. Its level $C$ is not recoverable \u2014 it absorbs the log-averaging of the spectrum and the power a taper removes \u2014 so it is matched to the data here rather than taken from the generator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "k, Phi, sigma_Phi = grid.window_spectrum(\n", + " window_size, xpt, ypt, taper=np.hanning, power=2.0)\n", + "\n", + "model = pycurious.bouligand2009(k, beta_true, zt_true, dz_true, 0.0)\n", + "model += (Phi - model).mean()\n", + "\n", + "fig, ax = plt.subplots(figsize=(9, 6))\n", + "ax.errorbar(k, Phi, yerr=sigma_Phi, fmt='o', ms=3, elinewidth=0.8,\n", + " label='measured')\n", + "ax.plot(k, model, 'r-', lw=2, label='analytic, true parameters')\n", + "ax.set_xlabel(r'$|k|$ [rad km$^{-1}$]')\n", + "ax.set_ylabel(r'$\\ln \\Phi_{\\Delta T}$')\n", + "ax.legend()\n", + "plt.show()\n", + "\n", + "residual = Phi - model\n", + "print(\"rms departure from the model: {:.3f} log units\".format(residual.std()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The measured spectrum scatters about the model rather than lying on it, because this is one random realisation of a fractal field rather than an average over many. That scatter is the reason a fit returns a range rather than a value, and [Ex2](./Ex2-Compute-Curie-depth.ipynb) turns it into an uncertainty on the Curie depth.\n", + "\n", + "Note the error bars grow towards the left. Each point is the mean over an annulus in the wavenumber plane, and the innermost annuli contain only a handful of cells, so their means are the least well determined. Those are also the points that carry the information about $\\Delta z$.\n", + "\n", + "## Choice of taper\n", + "\n", + "A window cuts the field off abruptly at its edges, and that discontinuity leaks power across the spectrum. A taper rolls the data down to zero instead." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(9, 6))\n", + "\n", + "for label, taper in [('none', None), ('hanning', np.hanning), ('hamming', np.hamming)]:\n", + " ki, Phii, _ = grid.window_spectrum(window_size, xpt, ypt, taper=taper, power=2.0)\n", + " ax.plot(ki, Phii, '-o', ms=3, label=label)\n", + "\n", + "ax.set_xlabel(r'$|k|$ [rad km$^{-1}$]')\n", + "ax.set_ylabel(r'$\\ln \\Phi_{\\Delta T}$')\n", + "ax.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Tapering costs a constant offset in level, absorbed by $C$, and changes the shape a little at the shortest wavelengths where the leaked power was worst.\n", + "\n", + "It is worth tapering even so, because an untapered fit is measurably **biased**. Over 120 independent realisations of this synthetic it returns $\\beta = 3.057$ against a true 3.0 and $z_t = 0.945$ against a true 1.0 \u2014 about a standard deviation out in each, in the same direction every time. With `np.hanning` the same fits give 3.003 and 1.001.\n", + "\n", + "The cost is that a taper correlates neighbouring bins of the spectrum with each other, which has to be accounted for when forming the uncertainties; see [Ex3](./Ex3-Posing-the-inverse-problem.ipynb).\n", + "\n", + "### References\n", + "\n", + "Bouligand, C., Glen, J. M. G., & Blakely, R. J. (2009). Mapping Curie temperature depth in the western United States with a fractal model for crustal magnetization. *Journal of Geophysical Research*, 114, B11104. doi:10.1029/2009JB006494" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb b/Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb new file mode 100644 index 0000000..fe3c3c9 --- /dev/null +++ b/Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb @@ -0,0 +1,377 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 2 - Compute Curie depth\n", + "\n", + "Bouligand *et al.* (2009) model the crust as a layer between depths $z_t$ and $z_t + \\Delta z$ whose magnetisation is **fractal** rather than spatially random, with a fractal parameter $\\beta$. The radially averaged power spectrum of the anomaly is then\n", + "\n", + "$$(1) \\quad \\ln \\Phi_{\\Delta T}(|k|) = C - 2|k|z_t - (\\beta-1)\\ln|k| - |k|\\Delta z + \\ln A(|k|, \\beta, \\Delta z)$$\n", + "\n", + "where $A$ collects a $\\cosh$ and a modified Bessel function of $|k|\\Delta z$, and $C$ is a constant setting the overall level. `pycurious.bouligand2009` evaluates the whole expression.\n", + "\n", + "Unlike the centroid method of [Tanaka](../Tanaka/Ex2-Compute-Curie-depth.ipynb), which takes two limits of a simpler layer model and fits a straight line to each, this is fitted **whole**, over the entire spectrum at once, for all four parameters together. Two consequences follow, and they are the reason to prefer it:\n", + "\n", + "- there are no bands to choose, and so no chance of fitting outside the range where an approximation holds;\n", + "- $\\beta$ is estimated from the data rather than assumed, so a fractal source introduces no bias to be corrected for afterwards.\n", + "\n", + "The Curie point depth is then simply the base of the layer,\n", + "\n", + "$$(2) \\quad Z_b = z_t + \\Delta z$$\n", + "\n", + "This notebook generates an anomaly whose parameters we choose, so that every number below can be checked against a known answer.\n", + "\n", + "### Contents\n", + "\n", + "- [A synthetic with a known answer](#A-synthetic-with-a-known-answer)\n", + "- [The radial power spectrum](#The-radial-power-spectrum)\n", + "- [Fitting the four parameters](#Fitting-the-four-parameters)\n", + "- [An honest interval on the Curie depth](#An-honest-interval-on-the-Curie-depth)\n", + "- [How wide a window?](#How-wide-a-window)\n", + "- [Adding a prior](#Adding-a-prior)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "import pycurious" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A synthetic with a known answer\n", + "\n", + "`pycurious.fractal_anomaly` filters white noise by the square root of equation (1), so the expected power spectrum of the result *is* the model, for whichever $\\beta$, $z_t$ and $\\Delta z$ we ask for.\n", + "\n", + "Here the source runs from 1 km down to 21 km, so the Curie depth we are trying to recover is 21 km." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "beta_true, zt_true, dz_true = 3.0, 1.0, 20.0\n", + "\n", + "data, extent = pycurious.fractal_anomaly(\n", + " n=512, dx=2.0, beta=beta_true, zt=zt_true, dz=dz_true, C=5.0, seed=1)\n", + "\n", + "grid = pycurious.CurieOptimiseBouligand(data, *extent)\n", + "\n", + "xpt = 0.5*(extent[0] + extent[1])\n", + "ypt = 0.5*(extent[2] + extent[3])\n", + "window_size = 1000e3\n", + "\n", + "print(\"grid is {:.0f} km across at {:.0f} km spacing\".format(\n", + " (extent[1] - extent[0])*1e-3, grid.dx*1e-3))\n", + "print(\"true Curie depth = {:.1f} km\".format(zt_true + dz_true))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(7, 6))\n", + "im = ax.imshow(data, extent=np.array(extent)*1e-3, cmap='RdBu_r', origin='lower')\n", + "ax.set_xlabel('easting [km]')\n", + "ax.set_ylabel('northing [km]')\n", + "fig.colorbar(im, ax=ax, label='nT')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The radial power spectrum\n", + "\n", + "`window_spectrum` extracts a square window, computes the radially averaged spectrum, and returns the uncertainty of each binned value ready for fitting. That last part is not the scatter of the FFT cells within an annulus, which is what `radial_spectrum` reports: it is the uncertainty of their *mean*, deflated because the cells are not independent of one another.\n", + "\n", + "`power=2` gives $\\ln \\Phi_{\\Delta T}$, the log power spectrum that equation (1) describes. Wavenumbers come back in rad/km." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "k, Phi, sigma_Phi = grid.window_spectrum(\n", + " window_size, xpt, ypt, taper=np.hanning, power=2.0)\n", + "\n", + "print(\"{} bins, from {:.4f} to {:.3f} rad/km\".format(len(k), k.min(), k.max()))\n", + "print(\"wavelengths {:.1f} km down to {:.1f} km\".format(2*np.pi/k.min(), 2*np.pi/k.max()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fitting the four parameters\n", + "\n", + "`optimise` returns each parameter followed by its standard deviation. The uncertainties come from the curvature of the misfit at the solution, corrected for the fact that neighbouring spectral bins are correlated \u2014 a taper spreads each wavenumber over a main lobe several bins wide, and ignoring that would make every error bar about 30% too narrow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "beta, zt, dz, C, sigma_beta, sigma_zt, sigma_dz, sigma_C = grid.optimise(\n", + " window_size, xpt, ypt, taper=np.hanning)\n", + "\n", + "print(\" recovered true\")\n", + "print(\"beta {:8.3f} +/- {:5.3f} {:8.3f}\".format(beta, sigma_beta, beta_true))\n", + "print(\"z_t {:8.3f} +/- {:5.3f} {:8.3f}\".format(zt, sigma_zt, zt_true))\n", + "print(\"dz {:8.3f} +/- {:5.3f} {:8.3f}\".format(dz, sigma_dz, dz_true))\n", + "print(\"C {:8.3f} +/- {:5.3f} --\".format(C, sigma_C))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$\\beta$ and $z_t$ come back within a standard deviation of the truth. $\\Delta z$ is high by 3.5 km, rather more than its own error bar suggests, and that is the honest behaviour of this parameter rather than a failure \u2014 we return to it below.\n", + "\n", + "$C$ has no true value to compare against. It is a level rather than a depth, and absorbs every constant between the noise and the spectrum: the generator normalises out the grid size, but the log-averaging of the spectrum and the power a taper removes both remain, leaving it about 2.5 low. Treat it as a nuisance parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(9, 6))\n", + "\n", + "ax.errorbar(k, Phi, yerr=sigma_Phi, fmt='o', ms=3, color='C0',\n", + " elinewidth=0.8, label='measured spectrum')\n", + "ax.plot(k, pycurious.bouligand2009(k, beta, zt, dz, C), 'r-', lw=2,\n", + " label='fit')\n", + "ax.plot(k, pycurious.bouligand2009(k, beta_true, zt_true, dz_true, C), 'k--', lw=1.5,\n", + " label='true parameters')\n", + "\n", + "ax.set_xlabel(r'$|k|$ [rad km$^{-1}$]')\n", + "ax.set_ylabel(r'$\\ln \\Phi_{\\Delta T}$')\n", + "ax.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The fit and the truth are hard to tell apart, which is the point: over most of the spectrum a 17% error in $\\Delta z$ is nearly invisible. Only the handful of longest-wavelength bins on the left carry information about the base of the layer, because that is where the $|k|\\Delta z$ terms in equation (1) are of order one.\n", + "\n", + "That is the whole difficulty of the method, and it is worth seeing plotted rather than described.\n", + "\n", + "## An honest interval on the Curie depth\n", + "\n", + "`calculate_CPD` applies equation (2) and combines the two uncertainties in quadrature." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CPD, sigma_CPD = grid.calculate_CPD(zt, dz, sigma_zt, sigma_dz)\n", + "\n", + "print(\"Curie depth = {:.2f} +/- {:.2f} km (true {:.1f} km)\".format(\n", + " CPD, sigma_CPD, zt_true + dz_true))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That $\\pm$ is symmetric, and the Curie depth is not. The likelihood in $\\Delta z$ has a long upper tail (Mather & Fullea, 2019): a layer thicker than the data can resolve looks much like one that is merely thick, whereas a much thinner one does not fit at all. So the plausible values run further above the estimate than below it, and a symmetric error bar describes neither end well.\n", + "\n", + "`profile` scans one quantity, re-optimising everything else at each step, and reports where the misfit rises by the $\\chi^2$ threshold for the requested confidence. It makes no assumption of symmetry. Asking it for `\"CPD\"` profiles the Curie depth directly, by substituting $\\Delta z = Z_b - z_t$, rather than propagating a symmetric $\\sigma_{\\Delta z}$ through equation (2)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "values, deviance, lower, upper = grid.profile(\n", + " window_size, xpt, ypt, \"CPD\", level=0.95, taper=np.hanning)\n", + "\n", + "print(\"profile 95% [{:6.2f}, {:6.2f}] km\".format(lower, upper))\n", + "print(\"symmetric 95% [{:6.2f}, {:6.2f}] km\".format(\n", + " CPD - 1.96*sigma_CPD, CPD + 1.96*sigma_CPD))\n", + "print(\"true value {:6.2f} km\".format(zt_true + dz_true))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(9, 5))\n", + "\n", + "ax.plot(values, deviance, 'C0-', lw=2)\n", + "ax.axhline(3.84, color='k', ls='--', lw=1, label=r'95% threshold, $\\chi^2_1$')\n", + "ax.axvspan(lower, upper, color='C0', alpha=0.15, label='95% interval')\n", + "ax.axvline(zt_true + dz_true, color='r', lw=2, label='true Curie depth')\n", + "\n", + "ax.set_xlabel('Curie depth [km]')\n", + "ax.set_ylabel(r'deviance $2(F - F_{min})$')\n", + "ax.set_ylim(0, 15)\n", + "ax.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The curve is visibly steeper on the left than on the right, and the interval inherits that: it reaches 7.0 km above the estimate but only 4.7 km below. The true value sits comfortably inside.\n", + "\n", + "Note what the symmetric interval got wrong. Its upper end is close to the profile's, but its lower end reaches down to 17 km \u2014 a Curie depth the data actually rule out. Reporting $\\pm\\sigma$ here would understate how well the *shallow* side is constrained while understating how far the deep side can reach.\n", + "\n", + "## How wide a window?\n", + "\n", + "Only the longest wavelengths constrain $\\Delta z$, and a window can only resolve wavelengths up to its own size. So the window has to be several times the depth being sought, and the consequence of ignoring that is severe." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "windows = np.array([300e3, 500e3, 700e3, 1000e3])\n", + "\n", + "print(\" window dz +/- sigma CPD\")\n", + "results = []\n", + "for w in windows:\n", + " o = grid.optimise(w, xpt, ypt, taper=np.hanning)\n", + " results.append(o)\n", + " print(\"{:5.0f} km {:6.2f} +/- {:6.2f} {:6.2f}\".format(\n", + " w*1e-3, o[2], o[6], o[1] + o[2]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))\n", + "\n", + "dz_fit = np.array([o[2] for o in results])\n", + "dz_err = np.array([o[6] for o in results])\n", + "beta_fit = np.array([o[0] for o in results])\n", + "beta_err = np.array([o[4] for o in results])\n", + "\n", + "ax1.errorbar(windows*1e-3, dz_fit, yerr=dz_err, fmt='o-', capsize=4)\n", + "ax1.axhline(dz_true, color='k', ls='--', label=r'true $\\Delta z$')\n", + "ax1.set_ylabel(r'$\\Delta z$ [km]')\n", + "ax1.legend()\n", + "\n", + "ax2.errorbar(windows*1e-3, beta_fit, yerr=beta_err, fmt='o-', capsize=4, color='C1')\n", + "ax2.axhline(beta_true, color='k', ls='--', label=r'true $\\beta$')\n", + "ax2.set_ylabel(r'$\\beta$')\n", + "ax2.legend()\n", + "\n", + "for ax in (ax1, ax2):\n", + " ax.set_xlabel('window size [km]')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$\\beta$ is recovered at every window size, because it is set by the *slope* of the spectrum across the whole wavenumber range and a small window still resolves that. $\\Delta z$ is not: at 300 km it comes back at 46 km against a true 20, more than twice the answer.\n", + "\n", + "But look at the error bar it reports \u2014 $\\pm 67$ km. The fit is not quietly wrong; it says plainly that it cannot constrain the parameter. That is the practical value of carrying uncertainties through: a 300 km window over a 21 km Curie depth produces a number that must not be believed, and the number itself tells you so.\n", + "\n", + "> Suggestion: use a window at least **10 times** the expected Curie depth, and read the reported $\\sigma_{\\Delta z}$ before trusting the result.\n", + "\n", + "## Adding a prior\n", + "\n", + "Where the data cannot constrain a parameter, independent knowledge can. `add_prior` accepts a mean and standard deviation, or any frozen distribution from `scipy.stats`, and the prior enters the fit as one additional observation.\n", + "\n", + "$\\beta$ for continental crust is often taken to be near 3, so it is the natural candidate." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grid.add_prior(beta=(3.0, 0.05))\n", + "constrained = grid.optimise(window_size, xpt, ypt, taper=np.hanning)\n", + "grid.reset_priors()\n", + "\n", + "print(\" no prior beta prior\")\n", + "print(\"beta {:12.3f} {:12.3f}\".format(beta, constrained[0]))\n", + "print(\"dz {:12.3f} {:12.3f}\".format(dz, constrained[2]))\n", + "print(\"sigma_dz {:12.3f} {:12.3f}\".format(sigma_dz, constrained[6]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pinning $\\beta$ tightens $\\sigma_{\\Delta z}$ from 3.7 to 3.3 km. The improvement is real but modest, which is itself informative: $\\beta$ and $\\Delta z$ are not so strongly traded off against each other that fixing one determines the other. A prior on $\\beta$ is not a substitute for a window wide enough to see the base of the layer.\n", + "\n", + "### Summary\n", + "\n", + "- Fit the whole spectrum at once. There are no bands to choose, and $\\beta$ is estimated rather than assumed.\n", + "- Use a window at least ten times the expected Curie depth. $\\beta$ and $z_t$ survive a small window; $\\Delta z$ does not.\n", + "- Read $\\sigma_{\\Delta z}$. When the window is too small it becomes large enough to disqualify the estimate, which is the fit telling you the truth.\n", + "- Report the Curie depth with `profile`, not $\\pm\\sigma$. Its uncertainty is genuinely asymmetric, and the symmetric interval misdescribes both ends.\n", + "\n", + "### References\n", + "\n", + "Bouligand, C., Glen, J. M. G., & Blakely, R. J. (2009). Mapping Curie temperature depth in the western United States with a fractal model for crustal magnetization. *Journal of Geophysical Research*, 114, B11104. doi:10.1029/2009JB006494\n", + "\n", + "Mather, B., & Fullea, J. (2019). Constraining the geotherm beneath the British Isles from Bayesian inversion of Curie depth: integrated modelling of magnetic, geothermal, and seismic data. *Solid Earth*, 10, 839-850. doi:10.5194/se-10-839-2019" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb b/Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb new file mode 100644 index 0000000..d93a683 --- /dev/null +++ b/Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 3 - Posing the inverse problem\n", + "\n", + "[Ex2](./Ex2-Compute-Curie-depth.ipynb) fitted four parameters and reported a standard deviation for each. This notebook asks where those uncertainties come from, and how far to trust them.\n", + "\n", + "Fitting the spectrum is a Bayesian inverse problem. Writing $\\mathbf{m} = (\\beta, z_t, \\Delta z, C)$ for the parameters and $\\Phi_d$ for the observed spectrum,\n", + "\n", + "$$(1) \\quad P(\\mathbf{m}|\\Phi_d) \\propto P(\\Phi_d|\\mathbf{m}) \\, P(\\mathbf{m})$$\n", + "\n", + "With Gaussian errors on each spectral bin the negative log of the right-hand side is\n", + "\n", + "$$(2) \\quad F(\\mathbf{m}) = \\frac{1}{2}\\sum_i \\left( \\frac{\\Phi(|k_i|, \\mathbf{m}) - \\Phi_{d,i}}{\\sigma_i} \\right)^2 \\; + \\; \\frac{1}{2}\\sum_j \\left( \\frac{m_j - p_j}{\\sigma_{p_j}} \\right)^2$$\n", + "\n", + "which is what `min_func` returns. The first sum is the data misfit; the second adds one term per prior, so a Gaussian prior is simply one more observation. Minimising $F$ gives the most probable model, and the *shape* of $F$ around that minimum gives the uncertainty.\n", + "\n", + "There are four ways to characterise that shape, and they cost very different amounts. This notebook runs all four on the same data, and ends with the reason they must not be used to check one another.\n", + "\n", + "### Contents\n", + "\n", + "- [The misfit surface](#The-misfit-surface)\n", + "- [1. The fit covariance](#1.-The-fit-covariance)\n", + "- [2. Profile likelihood](#2.-Profile-likelihood)\n", + "- [3. Resampling the spectrum](#3.-Resampling-the-spectrum)\n", + "- [4. Markov chain Monte Carlo](#4.-Markov-chain-Monte-Carlo)\n", + "- [Why agreement is not validation](#Why-agreement-is-not-validation)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from scipy import stats\n", + "%matplotlib inline\n", + "\n", + "import pycurious" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "beta_true, zt_true, dz_true = 3.0, 1.0, 20.0\n", + "CPD_true = zt_true + dz_true\n", + "\n", + "data, extent = pycurious.fractal_anomaly(\n", + " n=512, dx=2.0, beta=beta_true, zt=zt_true, dz=dz_true, C=5.0, seed=1)\n", + "\n", + "grid = pycurious.CurieOptimiseBouligand(data, *extent)\n", + "\n", + "xpt = 0.5*(extent[0] + extent[1])\n", + "ypt = 0.5*(extent[2] + extent[3])\n", + "window_size = 1000e3\n", + "\n", + "k, Phi, sigma_Phi = grid.window_spectrum(\n", + " window_size, xpt, ypt, taper=np.hanning, power=2.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The misfit surface\n", + "\n", + "Equation (2) over the two parameters that are hardest to separate, with $z_t$ and $C$ held at their fitted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "*fit, cov = grid.optimise(window_size, xpt, ypt, taper=np.hanning,\n", + " return_cov=True)\n", + "beta, zt, dz, C = fit[:4]\n", + "sigma_beta, sigma_zt, sigma_dz, sigma_C = fit[4:]\n", + "\n", + "nb = 60\n", + "beta_range = np.linspace(beta - 0.25, beta + 0.25, nb)\n", + "dz_range = np.linspace(max(dz - 15.0, 1.0), dz + 25.0, nb)\n", + "\n", + "misfit = np.array([[grid.min_func([b, zt, d, C], k, Phi, sigma_Phi)\n", + " for d in dz_range] for b in beta_range])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True, figsize=(13, 5))\n", + "X, Y = np.meshgrid(dz_range, beta_range)\n", + "\n", + "im1 = ax1.pcolormesh(X, Y, misfit, shading='auto')\n", + "im2 = ax2.pcolormesh(X, Y, np.exp(-(misfit - misfit.min())), shading='auto')\n", + "\n", + "for ax in (ax1, ax2):\n", + " ax.scatter(dz, beta, c='r', s=40, zorder=5, label='best fit')\n", + " ax.scatter(dz_true, beta_true, c='w', s=40, marker='x', zorder=5, label='truth')\n", + " ax.set_xlabel(r'$\\Delta z$ [km]')\n", + " ax.legend()\n", + "\n", + "ax1.set_ylabel(r'$\\beta$')\n", + "ax1.set_title(r'misfit $F(\\mathbf{m})$')\n", + "ax2.set_title(r'$P(\\mathbf{m}|\\Phi_d) \\propto e^{-F}$')\n", + "fig.colorbar(im1, ax=ax1)\n", + "fig.colorbar(im2, ax=ax2)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The probability is a narrow ridge, not a round hill. It is tight in $\\beta$, slack in $\\Delta z$, tilted because the two trade off against each other, and \u2014 this is the part that matters \u2014 **not symmetric in $\\Delta z$**: the contours reach further right than left. Everything below follows from that picture.\n", + "\n", + "## 1. The fit covariance\n", + "\n", + "The cheapest description. Near its minimum $F$ is approximately quadratic, and the curvature of that quadratic is the inverse covariance of the parameters. `optimise` computes it from the Jacobian at the solution and returns the square root of its diagonal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"beta = {:6.3f} +/- {:.3f}\".format(beta, sigma_beta))\n", + "print(\"z_t = {:6.3f} +/- {:.3f}\".format(zt, sigma_zt))\n", + "print(\"dz = {:6.3f} +/- {:.3f}\".format(dz, sigma_dz))\n", + "print(\"C = {:6.3f} +/- {:.3f}\".format(C, sigma_C))\n", + "\n", + "corr = cov / np.outer(np.sqrt(np.diag(cov)), np.sqrt(np.diag(cov)))\n", + "\n", + "labels = [r'$\\beta$', r'$z_t$', r'$\\Delta z$', r'$C$']\n", + "print(\"\\ncorrelation matrix\")\n", + "print(\" \" + \"\".join(\"{:>8s}\".format(s) for s in ['beta', 'zt', 'dz', 'C']))\n", + "for i, name in enumerate(['beta', 'zt', 'dz', 'C']):\n", + " print(\"{:>6s} \".format(name) + \"\".join(\"{:8.2f}\".format(v) for v in corr[i]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The off-diagonal terms are large. $\\beta$ and $z_t$ are strongly anticorrelated, and $z_t$ and $C$ almost perfectly correlated: the data constrain certain *combinations* far better than they constrain individual parameters. That is what the tilted ridge above looks like numerically.\n", + "\n", + "This is why the sampler in section 4 has to propose moves along the covariance rather than one parameter at a time.\n", + "\n", + "## 2. Profile likelihood\n", + "\n", + "The covariance assumes the ridge is an ellipse. For $\\Delta z$ it is not. The profile makes no such assumption: it fixes one quantity at a series of values, re-optimises everything else at each, and reports where the misfit has risen by the $\\chi^2_1$ threshold." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "values, deviance, lower, upper = grid.profile(\n", + " window_size, xpt, ypt, \"dz\", level=0.95, taper=np.hanning)\n", + "\n", + "fig, ax = plt.subplots(figsize=(9, 5))\n", + "ax.plot(values, deviance, lw=2, label='profile deviance')\n", + "ax.plot(values, ((values - dz)/sigma_dz)**2, 'C1--', lw=1.5,\n", + " label='quadratic implied by the covariance')\n", + "ax.axhline(3.84, color='k', ls=':', label=r'95% threshold')\n", + "ax.axvline(dz_true, color='r', lw=2, label=r'true $\\Delta z$')\n", + "ax.set_xlabel(r'$\\Delta z$ [km]')\n", + "ax.set_ylabel(r'$2(F - F_{min})$')\n", + "ax.set_ylim(0, 12)\n", + "ax.legend()\n", + "plt.show()\n", + "\n", + "print(\"profile 95% [{:6.2f}, {:6.2f}]\".format(lower, upper))\n", + "print(\"symmetric 95% [{:6.2f}, {:6.2f}]\".format(dz - 1.96*sigma_dz, dz + 1.96*sigma_dz))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The dashed parabola is what the covariance believes. It matches the true curve near the minimum and departs from it further out, falling below on the right and above on the left \u2014 so it overstates how far $\\Delta z$ can plausibly fall and understates how far it can rise.\n", + "\n", + "## 3. Resampling the spectrum\n", + "\n", + "A different question: if the spectrum had scattered differently, what would the fit have returned? `sensitivity` redraws each spectral value within its uncertainty and refits, many times. It also redraws the centre of any prior, so a prior contributes its own uncertainty rather than acting as a hard constraint." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ensemble = np.array(grid.sensitivity(\n", + " window_size, xpt, ypt, 300, taper=np.hanning, seed=1))\n", + "\n", + "print(\" mean std\")\n", + "for i, name in enumerate(['beta', 'zt', 'dz', 'C']):\n", + " print(\"{:>6s} {:8.3f} {:8.3f}\".format(name, ensemble[i].mean(), ensemble[i].std()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Markov chain Monte Carlo\n", + "\n", + "The most complete description, and the most expensive: rather than characterising the shape of $F$ near its minimum, walk over it and collect where the walk spends its time.\n", + "\n", + "`metropolis_hastings` starts the chain at the mode, proposes moves along the fit covariance from section 1, and tunes the step length during burn-in towards an acceptance rate of 0.234. Proposing one parameter at a time instead would leave the chain unable to move along the ridge, and it would sit still." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "posterior, info = grid.metropolis_hastings(\n", + " window_size, xpt, ypt, 20000, 4000,\n", + " taper=np.hanning, seed=1, return_diagnostics=True)\n", + "posterior = np.array(posterior)\n", + "\n", + "print(\"acceptance rate {:.3f}\".format(info[\"acceptance\"]))\n", + "print(\"burn-in acceptance {:.3f}\".format(info[\"burnin_acceptance\"]))\n", + "print(\"distinct states {} of {}\".format(len(np.unique(posterior[0])), posterior.shape[1]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(4, 2, figsize=(13, 10),\n", + " gridspec_kw={'width_ratios': [3, 1]})\n", + "truths = [beta_true, zt_true, dz_true, None]\n", + "\n", + "for i, (label, truth) in enumerate(zip(labels, truths)):\n", + " axes[i, 0].plot(posterior[i], lw=0.4)\n", + " axes[i, 0].set_ylabel(label)\n", + " axes[i, 1].hist(posterior[i], bins=50, density=True,\n", + " orientation='horizontal', color='C0')\n", + " if truth is not None:\n", + " for ax in axes[i]:\n", + " ax.axhline(truth, color='r', lw=1.5)\n", + " axes[i, 1].set_yticklabels([])\n", + "\n", + "axes[3, 0].set_xlabel('iteration')\n", + "axes[0, 0].set_title('chain')\n", + "axes[0, 1].set_title('marginal')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The traces wander across their range rather than drifting or sticking, which is what a chain that is mixing looks like. The marginal for $\\Delta z$ leans right \u2014 its mean sits above its median \u2014 while $\\beta$ and $z_t$ are close to symmetric.\n", + "\n", + "## Why agreement is not validation\n", + "\n", + "All four methods, on the Curie depth:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CPD, sigma_CPD = grid.calculate_CPD(zt, dz, sigma_zt, sigma_dz)\n", + "_, _, cpd_lo, cpd_hi = grid.profile(window_size, xpt, ypt, \"CPD\",\n", + " level=0.95, taper=np.hanning)\n", + "cpd_sens = ensemble[1] + ensemble[2]\n", + "cpd_mcmc = posterior[1] + posterior[2]\n", + "\n", + "print(\" 95% interval width\")\n", + "print(\"covariance +/-2s [{:6.2f}, {:6.2f}] {:6.2f}\".format(\n", + " CPD - 1.96*sigma_CPD, CPD + 1.96*sigma_CPD, 2*1.96*sigma_CPD))\n", + "print(\"profile [{:6.2f}, {:6.2f}] {:6.2f}\".format(\n", + " cpd_lo, cpd_hi, cpd_hi - cpd_lo))\n", + "lo, hi = np.percentile(cpd_sens, [2.5, 97.5])\n", + "print(\"sensitivity [{:6.2f}, {:6.2f}] {:6.2f}\".format(lo, hi, hi - lo))\n", + "lo, hi = np.percentile(cpd_mcmc, [2.5, 97.5])\n", + "print(\"MCMC [{:6.2f}, {:6.2f}] {:6.2f}\".format(lo, hi, hi - lo))\n", + "print(\"\\ntrue Curie depth {:6.2f}\".format(CPD_true))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(9, 5))\n", + "\n", + "ax.hist(cpd_mcmc, bins=60, density=True, alpha=0.5, label='MCMC')\n", + "ax.hist(cpd_sens, bins=40, density=True, histtype='step', lw=2, label='sensitivity')\n", + "ax.axvspan(cpd_lo, cpd_hi, color='C2', alpha=0.15, label='profile 95%')\n", + "ax.axvline(CPD_true, color='r', lw=2, label='true Curie depth')\n", + "ax.set_xlabel('Curie depth [km]')\n", + "ax.set_ylabel('density')\n", + "ax.set_xlim(15, 45)\n", + "ax.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Three of the four agree closely, and every one of them contains the truth. The symmetric interval is the odd one out, and wrongly so: its lower end reaches down to a Curie depth the data rule out.\n", + "\n", + "It would be tempting to read the agreement between the last three as confirmation that they are right. **It is not, and this is the most important point in the notebook.**\n", + "\n", + "`sensitivity` redraws each spectral bin independently. `metropolis_hastings` uses `min_func`, which sums over bins as though they were independent. They share an assumption, so they agree with each other whether or not that assumption holds \u2014 and it does not. A taper spreads each wavenumber over a main lobe several bins wide, so neighbouring bins of the spectrum are correlated; measured over 400 realisations, the correlation between adjacent bins is 0.36 under `np.hanning` and 0.003 with no taper at all.\n", + "\n", + "The covariance in section 1 corrects for this, which is why its $\\sigma_\\beta$ of 0.056 sits about 40% above the 0.040 that the ensembles report. That is not the covariance being conservative. It is the ensembles being narrow.\n", + "\n", + "The only way to tell which is right is to step outside the assumption entirely: generate many independent realisations of the field, fit each, and compare the spread of the results against what each method claimed. Doing that over 200 realisations gives a ratio of true spread to reported $\\sigma$ of 0.997 for $\\beta$, 0.989 for $z_t$ and 0.991 for $C$ \u2014 the corrected covariance is right, and the ensembles are 30% too narrow. `tests/test_bouligand.py` keeps that check.\n", + "\n", + "### Summary\n", + "\n", + "- The fit covariance is free and is honest for $\\beta$, $z_t$ and $C$.\n", + "- Use `profile` for $\\Delta z$ and the Curie depth. Their uncertainty is genuinely asymmetric and a $\\pm\\sigma$ misdescribes both ends.\n", + "- `sensitivity` and `metropolis_hastings` describe the posterior more fully, and MCMC is the only one that gives its shape, but both treat the spectral bins as independent and so report intervals about 30% too narrow.\n", + "- Two methods agreeing tells you they share an assumption. Only an ensemble over independent realisations tests whether the assumption is true.\n", + "\n", + "### References\n", + "\n", + "Mather, B., & Fullea, J. (2019). Constraining the geotherm beneath the British Isles from Bayesian inversion of Curie depth: integrated modelling of magnetic, geothermal, and seismic data. *Solid Earth*, 10, 839-850. doi:10.5194/se-10-839-2019\n", + "\n", + "Sambridge, M. (2013). A parallel tempering algorithm for probabilistic sampling and multimodal optimization. *Geophysical Journal International*, 196(1), 357-374. doi:10.1093/gji/ggt342" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb b/Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb new file mode 100644 index 0000000..08f77d1 --- /dev/null +++ b/Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb @@ -0,0 +1,301 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 4 - Spatial variation of Curie depth\n", + "\n", + "Mapping Curie depth means repeating the fit of [Ex2](./Ex2-Compute-Curie-depth.ipynb) over a grid of overlapping windows. `create_centroid_list` lays out the centroids and `optimise_routine` runs them in parallel, returning one array per quantity \u2014 including one per uncertainty, so a map of $\\sigma$ comes out alongside the map of the parameter.\n", + "\n", + "The synthetic used here has a Curie depth that is **the same everywhere**, 21 km. Every feature in the maps below is therefore an artefact, and the point of the notebook is to see how large those artefacts are before trusting a map made from real data.\n", + "\n", + "### Contents\n", + "\n", + "- [Laying out the windows](#Laying-out-the-windows)\n", + "- [Mapping the parameters](#Mapping-the-parameters)\n", + "- [The Curie depth map](#The-Curie-depth-map)\n", + "- [How much of this is real?](#How-much-of-this-is-real)\n", + "- [Uncertainty at a single window](#Uncertainty-at-a-single-window)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "import pycurious" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Laying out the windows\n", + "\n", + "A window has to be several times the Curie depth to constrain the thickness of the layer at all, and the grid has to be several times the window to hold more than one of them. That is a demanding combination, and it is why Curie depth maps are coarse.\n", + "\n", + "Here a 4092 km grid carries 1200 km windows on a 300 km spacing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "beta_true, zt_true, dz_true = 3.0, 1.0, 20.0\n", + "CPD_true = zt_true + dz_true\n", + "\n", + "data, extent = pycurious.fractal_anomaly(\n", + " n=1024, dx=4.0, beta=beta_true, zt=zt_true, dz=dz_true, C=5.0, seed=1)\n", + "\n", + "grid = pycurious.CurieOptimiseBouligand(data, *extent)\n", + "\n", + "window_size = 1200e3\n", + "xc_list, yc_list = grid.create_centroid_list(\n", + " window_size, spacingX=300e3, spacingY=300e3)\n", + "\n", + "print(\"grid {:.0f} km across, {:.0f} km windows, {} centroids\".format(\n", + " (extent[1] - extent[0])*1e-3, window_size*1e-3, len(xc_list)))\n", + "print(\"true Curie depth is {:.1f} km at every one of them\".format(CPD_true))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "beta, zt, dz, C, sigma_beta, sigma_zt, sigma_dz, sigma_C = grid.optimise_routine(\n", + " window_size, xc_list, yc_list, taper=np.hanning)\n", + "\n", + "nx = len(np.unique(xc_list))\n", + "ny = len(np.unique(yc_list))\n", + "shape = (ny, nx)\n", + "map_extent = np.array([xc_list.min(), xc_list.max(),\n", + " yc_list.min(), yc_list.max()])*1e-3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One window raised a warning: at that centroid the fit pushed $z_t$ onto its lower bound of zero, so the curvature the uncertainty is derived from describes a solution the optimiser was not free to find. `pycurious` says so rather than reporting a meaningless $\\sigma$ silently.\n", + "\n", + "## Mapping the parameters\n", + "\n", + "Each parameter beside its uncertainty. Remember that the true value is constant across all of these." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fields = [(beta, sigma_beta, r'$\\beta$', beta_true),\n", + " (zt, sigma_zt, r'$z_t$ [km]', zt_true),\n", + " (dz, sigma_dz, r'$\\Delta z$ [km]', dz_true)]\n", + "\n", + "fig, axes = plt.subplots(3, 2, figsize=(12, 15))\n", + "\n", + "for row, (value, sigma, label, truth) in enumerate(fields):\n", + " im = axes[row, 0].imshow(value.reshape(shape), extent=map_extent,\n", + " origin='lower', cmap='viridis')\n", + " axes[row, 0].set_title('{} (true {:g})'.format(label, truth))\n", + " fig.colorbar(im, ax=axes[row, 0])\n", + "\n", + " im = axes[row, 1].imshow(sigma.reshape(shape), extent=map_extent,\n", + " origin='lower', cmap='magma')\n", + " axes[row, 1].set_title(r'$\\sigma$ of ' + label)\n", + " fig.colorbar(im, ax=axes[row, 1])\n", + "\n", + "for ax in axes.ravel():\n", + " ax.set_xlabel('easting [km]')\n", + " ax.set_ylabel('northing [km]')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The Curie depth map\n", + "\n", + "`calculate_CPD` combines the two depths and their uncertainties, and vectorises over the whole map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CPD, sigma_CPD = grid.calculate_CPD(zt, dz, sigma_zt, sigma_dz)\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))\n", + "\n", + "im1 = ax1.imshow(CPD.reshape(shape), extent=map_extent, origin='lower',\n", + " cmap='RdYlBu')\n", + "ax1.set_title('Curie depth [km] (true {:.0f} km everywhere)'.format(CPD_true))\n", + "fig.colorbar(im1, ax=ax1, label='km')\n", + "\n", + "im2 = ax2.imshow(sigma_CPD.reshape(shape), extent=map_extent, origin='lower',\n", + " cmap='magma')\n", + "ax2.set_title(r'$\\sigma$ of Curie depth [km]')\n", + "fig.colorbar(im2, ax=ax2, label='km')\n", + "\n", + "for ax in (ax1, ax2):\n", + " ax.set_xlabel('easting [km]')\n", + " ax.set_ylabel('northing [km]')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How much of this is real?\n", + "\n", + "None of it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"true Curie depth {:6.2f} km everywhere\".format(CPD_true))\n", + "print(\"mapped, mean {:6.2f} km\".format(CPD.mean()))\n", + "print(\"mapped, range {:6.2f} to {:.2f} km\".format(CPD.min(), CPD.max()))\n", + "print(\"mapped, std deviation {:6.2f} km\".format(CPD.std()))\n", + "print(\"mean reported sigma {:6.2f} km\".format(sigma_CPD.mean()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A field with a Curie depth of exactly 21 km everywhere produces a map running from 5 km to 35 km \u2014 a factor of seven, with structure that looks entirely plausible as geology. The mean is close to the truth, but no individual window is reliable.\n", + "\n", + "Two things are worth taking from this.\n", + "\n", + "**The apparent variation is a noise floor, not a signal.** Before reading a low in a real Curie depth map as thinned crust or elevated heat flow, the variation has to exceed what the method invents on a field with no variation at all. Here that floor is about 6.7 km standard deviation.\n", + "\n", + "**The scatter is wider than the reported uncertainty.** The windows scatter by 6.7 km while reporting an average $\\sigma$ of 4.4 km. That gap is the long tail of $\\Delta z$ again: a symmetric $\\sigma$ cannot describe a distribution that reaches much further up than down, so it understates the spread. `profile` gives the honest interval at any window that matters.\n", + "\n", + "Note also that these windows are 1200 km wide on a 300 km spacing, so each overlaps its neighbours by three quarters. Adjacent estimates share most of their data and are strongly correlated \u2014 the maps are smoother than independent sampling would give, and that smoothness is not evidence that the features are real either." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(9, 5))\n", + "ax.hist(CPD, bins=25, color='C0', alpha=0.75)\n", + "ax.axvline(CPD_true, color='r', lw=2, label='true Curie depth')\n", + "ax.axvline(CPD.mean(), color='k', ls='--', lw=2, label='mean of the map')\n", + "ax.set_xlabel('Curie depth [km]')\n", + "ax.set_ylabel('number of windows')\n", + "ax.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Uncertainty at a single window\n", + "\n", + "For one window the full machinery of [Ex3](./Ex3-Posing-the-inverse-problem.ipynb) is available. `sensitivity` resamples the spectrum; `profile` gives an asymmetric interval; both are far more informative than the single number the map carries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpt = 0.5*(extent[0] + extent[1])\n", + "ypt = 0.5*(extent[2] + extent[3])\n", + "\n", + "posterior = np.array(grid.sensitivity(\n", + " window_size, xpt, ypt, 300, taper=np.hanning, seed=1))\n", + "cpd_samples = posterior[1] + posterior[2]\n", + "\n", + "_, _, lower, upper = grid.profile(\n", + " window_size, xpt, ypt, \"CPD\", level=0.95, taper=np.hanning)\n", + "\n", + "print(\"resampled Curie depth {:.2f} +/- {:.2f} km\".format(\n", + " cpd_samples.mean(), cpd_samples.std()))\n", + "print(\"profile 95% interval [{:.2f}, {:.2f}] km\".format(lower, upper))\n", + "print(\"true {:.2f} km\".format(CPD_true))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(9, 5))\n", + "ax.hist(cpd_samples, bins=40, density=True, alpha=0.7, label='resampled')\n", + "ax.axvspan(lower, upper, color='C2', alpha=0.15, label='profile 95%')\n", + "ax.axvline(CPD_true, color='r', lw=2, label='true Curie depth')\n", + "ax.set_xlabel('Curie depth [km]')\n", + "ax.set_ylabel('density')\n", + "ax.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This window is a good illustration of the notebook's point, because it is one of the unlucky ones: the true 21 km sits just outside the 95% interval, which reaches only to 20.8 km. A 95% interval is wrong one time in twenty by construction, and with 100 windows on this map several are expected to miss.\n", + "\n", + "That is not a reason to distrust the interval \u2014 it is the interval behaving exactly as advertised. It *is* a reason not to build an interpretation on any single window.\n", + "\n", + "### Summary\n", + "\n", + "- `optimise_routine` returns a $\\sigma$ map alongside every parameter map. Plot them together; a feature that is not large compared with its own uncertainty is not a feature.\n", + "- Test the method against a synthetic with no variation before interpreting variation in real data. On this grid a constant 21 km Curie depth maps as 5-35 km.\n", + "- Overlapping windows produce smooth maps whether or not anything smooth is there.\n", + "- The map's per-window $\\sigma$ understates the spread, because the Curie depth's uncertainty is asymmetric. Use `profile` wherever a number is going to be relied on.\n", + "- A 95% interval misses one time in twenty. On a map of 100 windows, expect several to exclude the truth." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb b/Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb new file mode 100644 index 0000000..15ae131 --- /dev/null +++ b/Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb @@ -0,0 +1,413 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 5 - Mapping Curie depth from EMAG2\n", + "\n", + "Using global compilations of the magnetic anomaly, such as EMAG2, means there is no limitation on window sizes. The mapping module in *PyCurious* has several functions to simplify:\n", + "\n", + "1. Translating between Coordinate Reference Systems (CRS)\n", + "2. Griding scattered point data\n", + "3. Importing and exporting Geotiffs\n", + "\n", + "These can be accessed from `pycurious.mapping`\n", + "\n", + "This requires some extra dependencies:\n", + "\n", + "- pyproj\n", + "- pyepsg\n", + "- cartopy (for visualisation)\n", + "\n", + "which can be installed via `pip`\n", + "\n", + "```shell\n", + "pip install [--user] pyproj pyepsg cartopy\n", + "```\n", + "\n", + "### Contents\n", + "\n", + "- [Import EMAG2 data](#Import-EMAG2-data)\n", + "- [Interpolate onto grid projection](#Interpolate-onto-grid-projection)\n", + "- [Compute Curie depth](#Compute-Curie-depth)\n", + "- [Compare to reference models](#Compare-to-reference-models)\n", + "- [Export GeoTIFF](#Export-GeoTIFF)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "import pycurious\n", + "from pycurious import mapping\n", + "\n", + "import cartopy.crs as ccrs\n", + "import cartopy.feature as cfeature\n", + "import cartopy.io.shapereader as shpreader" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import EMAG2 data\n", + "\n", + "The [EMAG2 (v3)](https://www.ngdc.noaa.gov/geomag/emag2.html) Earth magnetic anomaly grid$^1$ has been reformatted from a CSV file to a compressed `.npz` NumPy archive to efficiently load within Python workflows. This dataset can be downloaded from [Zenodo](https://zenodo.org/record/3245551). We have added some download tools accessed under `pycurious.download` to ease the download process and checksum validation.\n", + "\n", + "### References\n", + "\n", + "$^1$ Brian Meyer, Richard Saltus, Arnaud Chulliat (2017): EMAG2: Earth Magnetic Anomaly Grid (2-arc-minute resolution) Version 3. National Centers for Environmental Information, NOAA. Model. doi:[10.7289/V5H70CVX](https://data.nodc.noaa.gov/cgi-bin/iso?id=gov.noaa.ngdc.mgg.geophysical_models:EMAG2_V3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "file_resources = [\n", + " # EMAG2 v3\n", + " {\n", + " \"local_file\":\"../../data/EMAG2_V3_20170530.npz\",\n", + " \"md5\":'c0898b6a91efb3f13783873a8b67380c',\n", + " \"url\":\"https://zenodo.org/record/3245551/files/EMAG2_V3_20170530.npz?download=1\",\n", + " \"expected_size\":\"500Mb\"\n", + " },\n", + " \n", + " # Li et al. 2017\n", + " {\n", + " \"local_file\":\"../../data/Li_et_al_2017.txt\",\n", + " \"md5\":'5f0ea0af3e27c6c21cd7b776c979aa80',\n", + " \"url\":\"https://static-content.springer.com/esm/art%3A10.1038%2Fsrep45129/MediaObjects/41598_2017_BFsrep45129_MOESM71_ESM.txt\",\n", + " \"expected_size\":\"15Mb\"\n", + " }, \n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pycurious.download import download_cached_file\n", + "\n", + "for resource in file_resources:\n", + " print (\"\\nDownloading {:s}\".format(resource[\"local_file\"]))\n", + " download_cached_file(resource[\"url\"], resource[\"local_file\"], resource[\"md5\"], resource[\"expected_size\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once loaded, we trim the data to a region of interest - being careful to allow some buffer to accommodate the maximum window size.\n", + "\n", + "**EMAG2_V3_20170530.npz** contains the following columns:\n", + "\n", + "1. LON ; Geographic Longitude WGS84 (decimal degrees)\n", + "2. LAT ; Geographic Latitude WGS84 (decimal degrees)\n", + "3. SeaLevel ; Magnetic Anomaly Value at Sea Level(nT)\n", + "4. UpCont ; Magnetic Anomaly Value at continuous 4km altitude (nT)\n", + "5. Code ; Data Source Code (see table below)\n", + "6. Error ; Error estimate (nT)\n", + "\n", + "Code 888 is assigned in certain cells on grid edges where the data source is ambiguous and assigned an error of -888 nT\n", + "\n", + "Code 999 is assigned in cells where no data is reported with the anomaly value assigned 99999 nT and an error of -999 nT" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "emag2_file = file_resources[0][\"local_file\"]\n", + "with np.load(emag2_file) as f:\n", + " mag_data = f['data']\n", + "\n", + "# filter NaNs\n", + "mag_data = mag_data[mag_data[:,3] != 99999.]\n", + "mag_data = mag_data[mag_data[:,4] != -888.]\n", + "mag_data = mag_data[mag_data[:,4] != -999.]\n", + "\n", + "# print min/max\n", + "mincols = mag_data.min(axis=0)\n", + "maxcols = mag_data.max(axis=0)\n", + "\n", + "fmt = \"min/max {:5.2f} -> {:5.2f}\"\n", + "for col in range(mag_data.shape[1]):\n", + " print(fmt.format(mincols[col], maxcols[col]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interpolate onto grid projection\n", + "\n", + "We choose an arbitrary section of the Earth's surface and a suitable projection. PyCurious handles transformation between different CRS using a EPSG reference code. EMAG2 is in lons and lats (in decimal degrees), which we want to convert into eastings and northings (in metres). For this example, we transform WGS84 coordinates (EPSG:4326) to the IRENET95 projection (EPSG:2157).\n", + "\n", + "> **IMPORTANT:** The power spectrum must be in eastings & northings to compute the power spectrum in rad/km." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# grid extent - grid of the British Isles in Irenet 95 grid projection\n", + "xmin = 0.0\n", + "xmax = 1900000.0\n", + "ymin = 0.0\n", + "ymax = 1700000.0\n", + "extent_grid = [xmin, xmax, ymin, ymax]\n", + "\n", + "# extent on the sphere (WGS84)\n", + "extent_sphere = mapping.convert_extent(extent_grid, epsg_in=2157, epsg_out=4326)\n", + "\n", + "# map extent - should be narrower than extent_sphere\n", + "extent_map = [-10.8, 2, 49.5, 59]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dx, dy = 1e3, 1e3 # 1 km grid resolution\n", + "nx, ny = int(round((xmax-xmin)/dx)), int(round((ymax-ymin)/dy))\n", + "\n", + "# interpolate onto grid extent (for computation)\n", + "mag_grid = mapping.grid(mag_data[:,:2], mag_data[:,3], extent_grid, shape=(ny,nx), epsg_in=4326, epsg_out=2157)\n", + "\n", + "# interpolate onto extent on the sphere (for mapping)\n", + "mag_sphere = mapping.grid(mag_data[:,:2], mag_data[:,3], extent_sphere, shape=(ny,nx))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "proj = ccrs.UTM(30)\n", + "\n", + "fig = plt.figure(figsize=(12,10))\n", + "ax = plt.axes(projection=proj)\n", + "ax.set_extent(extent_map)\n", + "ax.coastlines(resolution='50m', linewidth=1.5)\n", + "ax.gridlines()\n", + "\n", + "im1 = ax.imshow(mag_sphere, extent=extent_sphere, transform=ccrs.PlateCarree(),\n", + " cmap='Spectral_r', vmin=-200, vmax=200, zorder=0)\n", + "\n", + "fig.colorbar(im1, label='nT')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute Curie depth\n", + "\n", + "For this example we use the Bouligand *et al.*, 2009 approach we outlined in previous notebooks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grid = pycurious.CurieOptimiseBouligand(mag_grid, xmin, xmax, ymin, ymax)\n", + "\n", + "window_size = 400e3\n", + "\n", + "# centroid spacing of 50 km\n", + "xc_list, yc_list = grid.create_centroid_list(window_size, spacingX=50e3, spacingY=50e3)\n", + "print(\"number of centroids = {}\".format(len(xc_list)))\n", + "\n", + "\n", + "(beta, zt, dz, C,\n", + " sigma_beta, sigma_zt, sigma_dz, sigma_C) = grid.optimise_routine(\n", + " window_size, xc_list, yc_list, taper=np.hamming)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "curie_depth, sigma_curie_depth = grid.calculate_CPD(zt, dz, sigma_zt, sigma_dz)\n", + "centroids = np.column_stack([xc_list, yc_list])\n", + "\n", + "curie_depth_sphere = mapping.grid(centroids, curie_depth, extent_sphere, (ny,nx), epsg_in=2157, epsg_out=4326)\n", + "sigma_sphere = mapping.grid(centroids, sigma_curie_depth, extent_sphere, (ny,nx), epsg_in=2157, epsg_out=4326)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "proj = ccrs.UTM(30)\n", + "\n", + "fig = plt.figure(figsize=(12,10))\n", + "ax = plt.axes(projection=proj)\n", + "ax.set_extent(extent_map)\n", + "ax.coastlines(resolution='50m', linewidth=1.5)\n", + "ax.gridlines()\n", + "\n", + "im1 = ax.imshow(curie_depth_sphere, extent=extent_sphere, transform=ccrs.PlateCarree(),\n", + " cmap='BrBG', vmin=10, vmax=40, zorder=0)\n", + "\n", + "fig.colorbar(im1, label='km')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`optimise_routine` returns a standard deviation for every parameter, so the uncertainty maps exactly as the depth does. Read the two together: a feature in the left-hand map that is not large compared with the right-hand one is not a feature.\n", + "\n", + "Note these are symmetric uncertainties, and the Curie depth's is not \u2014 see [Ex3](./Ex3-Posing-the-inverse-problem.ipynb). Treat this as a lower bound, and use `profile` anywhere a number is going to be relied on." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "proj = ccrs.UTM(30)\n", + "\n", + "fig = plt.figure(figsize=(12,10))\n", + "ax = plt.axes(projection=proj)\n", + "ax.set_extent(extent_map)\n", + "ax.coastlines(resolution='50m', linewidth=1.5)\n", + "ax.gridlines()\n", + "\n", + "im1 = ax.imshow(sigma_sphere, extent=extent_sphere, transform=ccrs.PlateCarree(),\n", + " cmap='magma', zorder=0)\n", + "\n", + "fig.colorbar(im1, label='km')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compare to reference models\n", + "\n", + "A global Curie depth reference model has been created by [Li *et al.*, 2017](https://www.nature.com/articles/srep45129), which is a useful resource to compare these results. Download the supplementary material from the open access [article](https://www.nature.com/articles/srep45129#supplementary-information) to the same directory as EMAG2." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "li2017_cpd_file = file_resources[1]['local_file']\n", + "\n", + "li_cpd = np.loadtxt(li2017_cpd_file, skiprows=1)\n", + "li_cpd = li_cpd[~np.isnan(li_cpd[:,2])]\n", + "\n", + "li_grid = mapping.grid(li_cpd[:,:2], li_cpd[:,2], extent_sphere, shape=(ny,nx))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "proj = ccrs.UTM(30)\n", + "\n", + "fig = plt.figure(figsize=(12,10))\n", + "ax = plt.axes(projection=proj)\n", + "ax.set_extent(extent_map)\n", + "ax.coastlines(resolution='50m', linewidth=1.5)\n", + "ax.gridlines()\n", + "\n", + "im1 = ax.imshow(li_grid, extent=extent_sphere, transform=ccrs.PlateCarree(),\n", + " cmap='BrBG', zorder=0, vmin=10, vmax=40)\n", + "\n", + "fig.colorbar(im1, label='km')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Export GeoTIFF\n", + "\n", + "The GeoTIFF format contains projection information, which makes it ideal for GIS applications such as [QGIS](https://www.qgis.org/en/site/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mapping.export_geotiff(\"my-curie-depth.tiff\", curie_depth_sphere, extent_sphere, epsg=4326)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also import a GeoTIFF using the `mapping` module. the `import_geotiff` function prints a wealth of information on the reference spheriod and the projection, including EPSG code." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "GTiff, GTextent = mapping.import_geotiff(\"my-curie-depth.tiff\")\n", + "\n", + "print(GTextent)\n", + "print(GTiff.shape)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pycurious/Examples/Notebooks/Tanaka/Ex1-Plot-power-spectrum.ipynb b/Examples/Notebooks/Tanaka/Ex1-Plot-amplitude-spectrum.ipynb similarity index 52% rename from pycurious/Examples/Notebooks/Tanaka/Ex1-Plot-power-spectrum.ipynb rename to Examples/Notebooks/Tanaka/Ex1-Plot-amplitude-spectrum.ipynb index d1728c5..8174c3b 100644 --- a/pycurious/Examples/Notebooks/Tanaka/Ex1-Plot-power-spectrum.ipynb +++ b/Examples/Notebooks/Tanaka/Ex1-Plot-amplitude-spectrum.ipynb @@ -4,16 +4,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Example 1 - Plot power spectra\n", + "# Example 1 - Plot amplitude spectra\n", "\n", - "`PyCurious` offers convenience functions to extract the radial power spectrum. This has been covered in the Bouligand folder of jupyter notebooks and is largely duplicated here.\n", + "`PyCurious` offers convenience functions to extract the radial amplitude spectrum. This has been covered in the Bouligand folder of jupyter notebooks and is largely duplicated here.\n", "\n", - "In this notebook we plot the radial power spectrum using the analytical expression, and the spectrum computed from a synthetic magnetic anomaly. (This can be generated from `Bouligand_forward.py` in the `tests` directory.)\n", + "In this notebook we plot the amplitude power spectrum using the analytical expression, and the spectrum computed from a synthetic magnetic anomaly. (This can be generated from `Bouligand_forward.py` in the `tests` directory.)\n", "\n", "### Contents\n", "\n", - "- [Radial power spectrum](#Power-spectrum-from-FFT)\n", - "- [Azimuthal power spectrum](#Azimuthal-power-spectrum)" + "- [Radial amplitude spectrum](#Power-spectrum-from-FFT)" ] }, { @@ -33,19 +32,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Radial power spectrum\n", + "## Radial amplitude spectrum\n", "\n", - "The radial power spectrum is computed from a square window of the magnetic anomaly. Methods to select window sizes and compute the Fast Fourier Transform (FFT) belong to the `CurieGrid` object.\n", + "The radial amplitude spectrum is computed from a square window of the magnetic anomaly. Methods to select window sizes and compute the Fast Fourier Transform (FFT) belong to the `CurieGrid` object.\n", "\n", "`CurieGrid` achieves the following purposes:\n", "\n", "- Upward continuation\n", "- Reduction to the pole\n", - "- Compute the radial power spectrum using FFT\n", + "- Compute the radial amplitude spectrum using FFT\n", "\n", - "The shape of the radial power spectrum is heavily dependent on window size. Resolution of long wavelength features require large windows.\n", + "The shape of the radial amplitude spectrum is heavily dependent on window size. Resolution of long wavelength features require large windows.\n", "\n", - "> Suggestion: use a window size **> 4 times** the maximum Curie depth.\n" + "> Suggestion: use a window size **> 4 times** the maximum Curie depth." ] }, { @@ -98,6 +97,22 @@ "subgrid = grid.subgrid(window_size, xpt, ypt)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Amplitude or power?**\n", + "\n", + "`radial_spectrum` raises the FFT of the anomaly to `power` before averaging, so the two Curie depth methods in `PyCurious` ask for different values:\n", + "\n", + "| method | `power` | quantity returned |\n", + "|---|---|---|\n", + "| Bouligand *et al.* (2009) | `2` (default) | $\\ln \\Phi_{\\Delta T}$, the log power spectrum |\n", + "| Tanaka *et al.* (1999) | `1` | $\\ln \\Phi_{\\Delta T}^{1/2}$, the log amplitude spectrum |\n", + "\n", + "Tanaka's equations are written in terms of the amplitude spectrum $\\Phi_{\\Delta T}^{1/2}$, and because $\\Phi_{\\Delta T} = |\\mathrm{FFT}|^2$, that is `power=1`. Getting this wrong rescales every depth the method returns." + ] + }, { "cell_type": "code", "execution_count": null, @@ -105,11 +120,11 @@ "outputs": [], "source": [ "# compute radial power spectrum\n", - "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid)\n", + "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, taper=None, power=1)\n", "\n", "# plot radial power spectrum\n", "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial power spectrum\")\n", + "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial amplitude spectrum\")\n", "ax1.plot(k, Phi, '-o')\n", "plt.show()" ] @@ -120,7 +135,7 @@ "source": [ "**Choice of taper**\n", "\n", - "The default taper is the hanning filter (see [`numpy.hanning`](#hanning) for more details), but other functions can be passed to taper, or simply set it to `None`. There is a significant offset in the power spectrum with tapering functions, however, it is the *slope* of the spectrum with wavenumber that is most important when it comes to determining Curie depth." + "The default taper is the hanning filter (see [`numpy.hanning`](#hanning) for more details), but other functions can be passed to taper, or simply set it to `None`. There is a significant offset in the amplitude spectrum with tapering functions, however, it is the *slope* of the spectrum with wavenumber that is most important when it comes to determining Curie depth." ] }, { @@ -130,83 +145,24 @@ "outputs": [], "source": [ "\n", - "k1, Phi1, sigma_Phi1 = grid.radial_spectrum(subgrid, taper=None)\n", - "k2, Phi2, sigma_Phi2 = grid.radial_spectrum(subgrid, taper=np.hanning)\n", - "k3, Phi3, sigma_Phi2 = grid.radial_spectrum(subgrid, taper=np.hamming)\n", + "k1, Phi1, sigma_Phi1 = grid.radial_spectrum(subgrid, taper=None, power=1)\n", + "k2, Phi2, sigma_Phi2 = grid.radial_spectrum(subgrid, taper=np.hanning, power=1)\n", + "k3, Phi3, sigma_Phi2 = grid.radial_spectrum(subgrid, taper=np.hamming, power=1)\n", "\n", "\n", "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial power spectrum\")\n", + "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial amplitude spectrum\")\n", "ax1.plot(k1, Phi1, '-o', label='none')\n", "ax1.plot(k2, Phi2, '-o', label='hanning')\n", "ax1.plot(k3, Phi3, '-o', label='hamming')\n", "ax1.legend()\n", "plt.show()" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Azimuthal power spectrum\n", - "\n", - "The azimuthal spectrum computes the FFT on a square window that is polarised at a range of radii. Subdividing the transforms into bins by azimuth is useful to explore linear trends in the magnetic anomaly that may align with a particular foliation or strike orientation.\n", - "\n", - "```python\n", - "azimuthal_spectrum(subgrid, power=2.0, theta=5.0)\n", - "```\n", - "\n", - "`theta` controls the bin size of each azimuth (in degrees). The FFT of the magnetic anomaly is raised to the `power=2`(default) which is compatible with Bouligand _et al._ (2009) computation of Curie depth. For Tanaka *et al.* (1999), however, we need to take the square root which is set with `power=0.5`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "k, Phi, theta = grid.azimuthal_spectrum(subgrid, power=0.5, theta=20.0)\n", - "\n", - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, ylabel=r'azimuth $\\theta$', xlabel='wavenumber (rad/km)')\n", - "im1 = ax1.pcolor(k, theta, Phi)\n", - "fig.colorbar(im1, label='radial power spectrum')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plotting each azimuth on the power spectrum-wavenumber axis highlights the different in slope for different polarisations of the magnetic anomaly." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial power spectrum\")\n", - "\n", - "for i, angle in enumerate(theta):\n", - " ax1.plot(k, Phi[i], label=r'$\\theta = {}^\\circ$'.format(angle))\n", - "\n", - "ax1.legend(bbox_to_anchor=(1,1))\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -220,7 +176,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.13.14" } }, "nbformat": 4, diff --git a/Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb b/Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb new file mode 100644 index 0000000..97f13c1 --- /dev/null +++ b/Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb @@ -0,0 +1,364 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 2 - Compute Curie depth\n", + "\n", + "Tanaka *et al.* (1999) assume the crust is randomly magnetised between a top $Z_t$ and a bottom $Z_b$, which gives a radially averaged power spectrum\n", + "\n", + "$$(1) \\quad \\Phi_{\\Delta T}(|k|)=Ae^{-2|k|Z_t}\\left(1-e^{-|k|(Z_b-Z_t)}\\right)^2$$\n", + "\n", + "where $k$ is the spatial wavenumber ($k=2\\pi/\\lambda$, for wavelength $\\lambda$) and $Z_b-Z_t$ is the thickness of the magnetic source.\n", + "\n", + "Rather than fit equation (1) directly, the method takes two limits of it, each of which is a straight line over a different range of wavenumbers.\n", + "\n", + "**Short wavelengths.** For wavelengths shorter than twice the source thickness the layer looks like a half space, and the second factor tends to 1:\n", + "\n", + "$$(2) \\quad \\ln \\left[\\Phi_{\\Delta T}(|k|)^{1/2}\\right] = \\ln B-|k|Z_t $$\n", + "\n", + "so a straight line fitted here has gradient $-Z_t$.\n", + "\n", + "**Long wavelengths.** Equation (1) can be rewritten about the centroid depth $Z_o$ of the source,\n", + "\n", + "$$(3) \\quad \\Phi_{\\Delta T}(|k|)^{1/2} = Ce^{-|k|Z_o}\\left(e^{-|k|(Z_t-Z_o)}-e^{-|k|(Z_b-Z_o)}\\right) $$\n", + "\n", + "Writing $d$ for the *half* thickness, $Z_t-Z_o=-d$ and $Z_b-Z_o=+d$, so the bracket becomes a hyperbolic sine:\n", + "\n", + "$$(4) \\quad \\Phi_{\\Delta T}(|k|)^{1/2} = Ce^{-|k|Z_o}\\left(e^{+|k|d}-e^{-|k|d}\\right) = 2Ce^{-|k|Z_o}\\sinh(|k|d) \\approx 2Ce^{-|k|Z_o}|k|d $$\n", + "\n", + "**That last step is an approximation, valid only when $|k|d \\ll 1$.** It matters more than it looks, and we return to it below. Dividing through by $|k|$ leaves a second straight line,\n", + "\n", + "$$(5) \\quad \\ln \\left\\{\\Phi_{\\Delta T}(|k|)^{1/2}/|k|\\right\\}=\\ln D-|k|Z_o $$\n", + "\n", + "whose gradient is $-Z_o$. The base of the magnetic source, taken to be the Curie point depth, then follows from the two gradients:\n", + "\n", + "$$(6) \\quad Z_b=Z_o-(Z_t-Z_o) = 2Z_o-Z_t $$\n", + "\n", + "`PyCurious` fits both lines with `scipy.optimize.curve_fit`, weighted by the measured scatter of the spectrum, so each gradient carries an uncertainty that propagates through equation (6).\n", + "\n", + "### Contents\n", + "\n", + "- [Radial amplitude spectrum](#Radial-amplitude-spectrum)\n", + "- [Choosing the two bands](#Choosing-the-two-bands)\n", + "- [Computing the Curie depth](#Computing-the-Curie-depth)\n", + "- [Fractal magnetisation](#Fractal-magnetisation)\n", + "- [How far can we trust this?](#How-far-can-we-trust-this)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "import pycurious" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load x,y,anomaly\n", + "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", + "\n", + "nx, ny = 305, 305\n", + "\n", + "x = mag_data[:,0]\n", + "y = mag_data[:,1]\n", + "d = mag_data[:,2].reshape(ny,nx)\n", + "\n", + "xmin, xmax = x.min(), x.max()\n", + "ymin, ymax = y.min(), y.max()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Radial amplitude spectrum\n", + "\n", + "The spectrum is computed from a square window of the magnetic anomaly, using methods that belong to the `CurieGrid` object.\n", + "\n", + "By default `radial_spectrum` raises the FFT of the anomaly to the power 2, giving the log *power* spectrum $\\ln \\Phi_{\\Delta T}$ that Bouligand *et al.* (2009) fit. Tanaka's equations are written in terms of the *amplitude* spectrum $\\Phi_{\\Delta T}^{1/2}$, so here we want `power=1`.\n", + "\n", + "Wavenumbers come back in **rad/km**, and the fitting bands below are given in the same units.\n", + "\n", + "The anomaly used here is a synthetic with a known answer: the source sits between 0.305 km and 10.305 km depth, so $Z_t = 0.305$, $Z_o = 5.305$ and $Z_b = 10.305$ km." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grid = pycurious.CurieOptimiseTanaka(d, xmin, xmax, ymin, ymax)\n", + "\n", + "xpt = 0.5*(xmin + xmax)\n", + "ypt = 0.5*(ymin + ymax)\n", + "\n", + "window_size = 200e3\n", + "subgrid = grid.subgrid(window_size, xpt, ypt)\n", + "\n", + "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, taper=np.hanning, power=1)\n", + "\n", + "print(\"{} bins, spaced {:.4f} rad/km, reaching {:.2f} rad/km\".format(\n", + " len(k), k[1]-k[0], k.max()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Phi_n = Phi - np.log(k)\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14,5))\n", + "\n", + "ax1.plot(k, Phi, '-o', markersize=3)\n", + "ax1.set_title('Amplitude spectrum')\n", + "ax1.set_xlabel(r'$|k|$ [rad km$^{-1}$]')\n", + "ax1.set_ylabel(r'$\\ln \\Phi_{\\Delta T}(|k|)^{1/2}$')\n", + "\n", + "ax2.plot(k, Phi_n, '-o', markersize=3)\n", + "ax2.set_title(r'Amplitude spectrum divided by $|k|$')\n", + "ax2.set_xlabel(r'$|k|$ [rad km$^{-1}$]')\n", + "ax2.set_ylabel(r'$\\ln \\left(\\Phi_{\\Delta T}(|k|)^{1/2}/|k|\\right)$')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Choosing the two bands\n", + "\n", + "Each straight line is valid only over part of the spectrum:\n", + "\n", + "- the $Z_t$ band needs wavelengths **shorter** than twice the source thickness, so the half-space approximation behind equation (2) holds;\n", + "- the $Z_o$ band needs $|k|d \\ll 1$, the approximation made in equation (4).\n", + "\n", + "Neither condition is visible on the plots above. A band that violates one still yields a confident-looking straight-line fit \u2014 just through the wrong part of the curve. `check_bands` tests a choice explicitly, given a rough guess at the source thickness.\n", + "\n", + "The bands below are the ones this notebook has historically used, converted from cycles/km to rad/km." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zt_range = (1.257, 1.885)\n", + "z0_range = (0.0, 0.628)\n", + "\n", + "diagnostics = grid.check_bands(k, zt_range, z0_range, thickness=10.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Computing the Curie depth\n", + "\n", + "`check_bands` warns that the $Z_o$ band reaches $|k|d \\approx 3$, which is not $\\ll 1$, and estimates that this alone drags the Curie depth about 5 km low. Hold that thought until we have a number in hand.\n", + "\n", + "`optimise` fits both bands and returns the two depths, their intercepts and the standard deviation of each depth, with depths positive downwards. `calculate_CPD` applies equation (6) and combines the uncertainties in quadrature." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zt, z0, zt_int, z0_int, sigma_zt, sigma_z0 = grid.optimise(\n", + " window_size, xpt, ypt, zt_range, z0_range, taper=np.hanning)\n", + "\n", + "CPD, sigma_CPD = grid.calculate_CPD(zt, z0, sigma_zt, sigma_z0)\n", + "\n", + "print(\"Top of magnetic source = {:5.2f} +/- {:.2f} km (true 0.305)\".format(zt, sigma_zt))\n", + "print(\"Centroid of magnetic source = {:5.2f} +/- {:.2f} km (true 5.305)\".format(z0, sigma_z0))\n", + "print(\"Curie point depth = {:5.2f} +/- {:.2f} km (true 10.305)\".format(CPD, sigma_CPD))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mask_zt = np.logical_and(k >= zt_range[0], k <= zt_range[1])\n", + "mask_z0 = np.logical_and(k >= z0_range[0], k <= z0_range[1])\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14,5))\n", + "\n", + "ax1.plot(k, Phi, '-o', markersize=3, label='Amplitude spectrum')\n", + "ax1.plot(k[mask_zt], -zt*k[mask_zt] + zt_int, 'r-', linewidth=3,\n", + " label=r'fit, $Z_t$ = {:.2f} km'.format(zt))\n", + "ax1.set_xlabel(r'$|k|$ [rad km$^{-1}$]')\n", + "ax1.set_ylabel(r'$\\ln \\Phi_{\\Delta T}(|k|)^{1/2}$')\n", + "ax1.legend()\n", + "\n", + "ax2.plot(k, Phi_n, '-o', markersize=3, label='Amplitude spectrum')\n", + "ax2.plot(k[mask_z0], -z0*k[mask_z0] + z0_int, 'r-', linewidth=3,\n", + " label=r'fit, $Z_o$ = {:.2f} km'.format(z0))\n", + "ax2.set_xlabel(r'$|k|$ [rad km$^{-1}$]')\n", + "ax2.set_ylabel(r'$\\ln \\left(\\Phi_{\\Delta T}(|k|)^{1/2}/|k|\\right)$')\n", + "ax2.legend()\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fractal magnetisation\n", + "\n", + "The Curie depth comes out close to the true 10.305 km with a small error bar. That looks like a success, so it is worth checking whether it is one.\n", + "\n", + "Tanaka's derivation assumes the magnetisation is spatially **random**. Real crust \u2014 and this synthetic \u2014 is better described as **fractal**, which adds a term $-\\tfrac{1}{2}(\\beta-1)\\ln|k|$ to the log amplitude spectrum. Its gradient is $-(\\beta-1)/2|k|$, so it biases $Z_t$ high by $(\\beta-1)/2\\bar{k}$ while leaving the line looking perfectly straight.\n", + "\n", + "This synthetic was built with $\\beta = 3$. Passing `beta` removes that contribution before fitting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zt_c, z0_c, _, _, sigma_zt_c, sigma_z0_c = grid.optimise(\n", + " window_size, xpt, ypt, zt_range, z0_range, taper=np.hanning, beta=3.0)\n", + "\n", + "CPD_c, sigma_CPD_c = grid.calculate_CPD(zt_c, z0_c, sigma_zt_c, sigma_z0_c)\n", + "\n", + "print(\" uncorrected beta-corrected true\")\n", + "print(\"Top Z_t [km] {:8.2f} {:12.2f} {:8.3f}\".format(zt, zt_c, 0.305))\n", + "print(\"Centroid Z_o [km] {:8.2f} {:12.2f} {:8.3f}\".format(z0, z0_c, 5.305))\n", + "print(\"Curie Z_b [km] {:8.2f} {:12.2f} {:8.3f}\".format(CPD, CPD_c, 10.305))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How far can we trust this?\n", + "\n", + "Correcting for $\\beta$ recovers $Z_t$ almost exactly \u2014 0.29 km against a true 0.305 km, where the uncorrected fit gave 0.93 km. So the correction is doing precisely what the algebra says it should.\n", + "\n", + "But the Curie depth gets *worse*, falling from 11.0 km to about 5 km. That is the important result in this notebook, and it is not a bug.\n", + "\n", + "Two separate errors were present all along, pulling in opposite directions:\n", + "\n", + "1. the $Z_o$ band violates $|k|d \\ll 1$ by a factor of three, which biases $Z_o$ **low**;\n", + "2. the unmodelled fractal magnetisation biases both depths **high**.\n", + "\n", + "In the uncorrected fit these two largely cancelled, which is why the answer looked good. Removing one of them exposes the other. **The original agreement with the true value was a coincidence, not a validation** \u2014 and a coincidence that depended on this particular $\\beta$ and this particular source thickness, so it would not survive on real data.\n", + "\n", + "The honest fix is to narrow the $Z_o$ band until $|k|d \\ll 1$ actually holds. On this grid that is not possible:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " grid.optimise(window_size, xpt, ypt, zt_range, (0.0, 0.1), taper=np.hanning, beta=3.0)\n", + "except ValueError as e:\n", + " print(\"ValueError:\", e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A 200 km window resolves the spectrum in steps of about 0.03 rad/km, so a band satisfying $|k|d \\lesssim 0.5$ contains barely two points \u2014 not enough to fit a line through. **This dataset can constrain $Z_t$, but it cannot constrain the centroid depth**, and therefore cannot constrain the Curie depth either. Resolving a centroid at 5 km depth needs a window of many hundreds of kilometres, so that there are enough long-wavelength bins below the $|k|d \\ll 1$ limit.\n", + "\n", + "`tests/test_recovery.py` in the source repository demonstrates the method recovering a Curie depth correctly on synthetics generated wide enough to support the fit.\n", + "\n", + "### Reporting an uncertainty\n", + "\n", + "The $\\pm$ values above come from the fit covariance alone, and describe only the scatter of the spectrum. They say nothing about where the band edges were placed, which on this data matters far more. `sensitivity` resamples the spectrum *and* jitters both band edges:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zt_s, z0_s, CPD_s = grid.sensitivity(\n", + " window_size, xpt, ypt, 500, zt_range, z0_range,\n", + " taper=np.hanning, band_scale=0.1, seed=1)\n", + "\n", + "print(\"fit covariance only : {:.2f} +/- {:.2f} km\".format(CPD, sigma_CPD))\n", + "print(\"including band-edge jitter : {:.2f} +/- {:.2f} km\".format(CPD_s.mean(), CPD_s.std()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(7,4.5))\n", + "ax.hist(CPD_s, bins=40, color='0.7', edgecolor='none')\n", + "ax.axvline(10.305, color='k', linestyle='--', linewidth=2, label='true Curie depth')\n", + "ax.axvline(CPD_s.mean(), color='r', linewidth=2, label='sampled mean')\n", + "ax.set_xlabel('Curie point depth [km]')\n", + "ax.set_ylabel('count')\n", + "ax.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Widening the error bar in this way is more honest, but it still does not cover the two systematic biases discussed above: those shift both fits coherently rather than scattering them, so no amount of resampling will reveal them. Only `check_bands`, and a source model appropriate to the data, can.\n", + "\n", + "### Summary\n", + "\n", + "- Fit the $Z_t$ band where wavelengths are shorter than twice the source thickness, and the $Z_o$ band where $|k|d \\ll 1$. Use `check_bands` rather than assuming.\n", + "- Pass `beta` if the magnetisation is fractal, which for real crust it generally is.\n", + "- Treat the fit covariance as a lower bound on the uncertainty, and `sensitivity` as a better one.\n", + "- An answer that matches expectation is not evidence that the bands were valid." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb b/Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb new file mode 100644 index 0000000..d3396b3 --- /dev/null +++ b/Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb @@ -0,0 +1,290 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 3 - Parameter Exploration\n", + "\n", + "The Curie depth returned by the centroid method depends on three choices the user makes: where in $k$-space each fitting band sits, how wide those bands are, and how large a window of magnetic data is used. None of these is determined by the data, so it is worth knowing how much they matter.\n", + "\n", + "`CurieOptimiseTanaka.optimise` reports an uncertainty from the fit covariance, which captures the scatter of the spectrum but *not* the sensitivity to these choices. The sweeps below explore that sensitivity directly.\n", + "\n", + "Bands are specified in **rad/km**. Where the original version of this notebook used spatial frequency in cycles/km, the values are multiplied by $2\\pi$.\n", + "\n", + "### Contents\n", + "\n", + "1. Where in $k$-space the bands sit\n", + "2. How wide the bands are\n", + "3. How large the data window is" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "import pycurious" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load x,y,anomaly\n", + "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", + "\n", + "nx, ny = 305, 305\n", + "\n", + "x = mag_data[:,0]\n", + "y = mag_data[:,1]\n", + "d = mag_data[:,2].reshape(ny,nx)\n", + "\n", + "xmin, xmax = x.min(), x.max()\n", + "ymin, ymax = y.min(), y.max()\n", + "\n", + "# initialise object\n", + "grid = pycurious.CurieOptimiseTanaka(d, xmin, xmax, ymin, ymax)\n", + "\n", + "# pick centroid\n", + "xpt = xmin + (xmax-xmin)/2\n", + "ypt = ymin + (ymax-ymin)/2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Varying spatial frequency range\n", + "\n", + "This test explores where in $k$-space is specific window used, separately for both the power and $k$-weighted power." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 1) Where in k-space do we locate the bands?\n", + "import warnings\n", + "\n", + "window_size = 304e3\n", + "band_width = 2*np.pi*0.1\n", + "\n", + "zt_array = 2*np.pi*np.arange(0.0, 0.5, 0.05)\n", + "z0_array = 2*np.pi*np.arange(0.0, 0.5, 0.05)\n", + "\n", + "z0q, ztq = np.meshgrid(z0_array, zt_array)\n", + "\n", + "CPD_grid = np.full((zt_array.size, z0_array.size), np.nan)\n", + "sigma_CPD_grid = np.full((zt_array.size, z0_array.size), np.nan)\n", + "\n", + "# the sweep deliberately visits bands that are too low or too sparse to fit,\n", + "# so suppress the band warnings and record those as NaN\n", + "with warnings.catch_warnings():\n", + " warnings.simplefilter(\"ignore\")\n", + "\n", + " for row, zt_min in enumerate(zt_array):\n", + " for col, z0_min in enumerate(z0_array):\n", + " try:\n", + " zt, z0, zt_int, z0_int, sigma_zt, sigma_z0 = grid.optimise(\n", + " window_size, xpt, ypt,\n", + " (zt_min, zt_min + band_width),\n", + " (z0_min, z0_min + band_width),\n", + " taper=None)\n", + " except ValueError:\n", + " continue\n", + "\n", + " CPD, sigma_CPD = grid.calculate_CPD(zt, z0, sigma_zt, sigma_z0)\n", + " CPD_grid[row, col] = CPD\n", + " sigma_CPD_grid[row, col] = sigma_CPD\n", + "\n", + "print(\"{} of {} band combinations produced a fit\".format(\n", + " np.isfinite(CPD_grid).sum(), CPD_grid.size))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14,5.5))\n", + "\n", + "sc1 = ax1.scatter(ztq.flat, z0q.flat, c=CPD_grid.flat, s=260, marker='s')\n", + "fig.colorbar(sc1, ax=ax1, label='CPD [km]')\n", + "ax1.set_xlabel(r'$Z_t$ band start [rad km$^{-1}$]')\n", + "ax1.set_ylabel(r'$Z_o$ band start [rad km$^{-1}$]')\n", + "ax1.set_title('Curie depth')\n", + "\n", + "sc2 = ax2.scatter(ztq.flat, z0q.flat, c=sigma_CPD_grid.flat, s=260, marker='s')\n", + "fig.colorbar(sc2, ax=ax2, label='CPD stdev [km]')\n", + "ax2.set_xlabel(r'$Z_t$ band start [rad km$^{-1}$]')\n", + "ax2.set_ylabel(r'$Z_o$ band start [rad km$^{-1}$]')\n", + "ax2.set_title('Reported uncertainty')\n", + "\n", + "plt.show()\n", + "\n", + "print(\"CPD ranges from {:.1f} to {:.1f} km across these bands (true value 10.3 km),\".format(\n", + " np.nanmin(CPD_grid), np.nanmax(CPD_grid)))\n", + "print(\"while the reported uncertainty is at most {:.1f} km.\".format(np.nanmax(sigma_CPD_grid)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The spread across band positions is far larger than any individual error bar. That is the central caveat of the method: the uncertainty returned by `optimise` describes the scatter of the spectrum about a line, not whether the line was fitted in the right place.\n", + "\n", + "## Varying the bandwidth\n", + "\n", + "Widening a band brings in more spectral estimates and stabilises the gradient, at the cost of fitting a straight line across a curve that is not straight." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 2) How wide a band in k-space do we want?\n", + "\n", + "nbins = 30\n", + "bandwidths = 2*np.pi*np.linspace(0.05, 0.1, nbins)\n", + "\n", + "CPD_grid = np.full(nbins, np.nan)\n", + "sigma_CPD_grid = np.full(nbins, np.nan)\n", + "\n", + "zt_min = 2*np.pi*0.2\n", + "z0_min = 0.0\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.simplefilter(\"ignore\")\n", + "\n", + " for i, bandwidth in enumerate(bandwidths):\n", + " try:\n", + " zt, z0, zt_int, z0_int, sigma_zt, sigma_z0 = grid.optimise(\n", + " window_size, xpt, ypt,\n", + " (zt_min, zt_min + bandwidth),\n", + " (z0_min, z0_min + bandwidth),\n", + " taper=None)\n", + " except ValueError:\n", + " continue\n", + "\n", + " CPD_grid[i], sigma_CPD_grid[i] = grid.calculate_CPD(\n", + " zt, z0, sigma_zt, sigma_z0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax2 = plt.subplots(figsize=(8,6))\n", + "ax2.errorbar(bandwidths, CPD_grid, yerr=sigma_CPD_grid, capsize=3)\n", + "ax2.axhline(10.305, color='k', linestyle='--', label='true Curie depth')\n", + "ax2.invert_yaxis()\n", + "ax2.set_xlabel(r'Bandwidth $k_{max} - k_{min}$ [rad km$^{-1}$]')\n", + "ax2.set_ylabel('CPD [km]')\n", + "ax2.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The estimate settles down once the band is wide enough to contain a reasonable number of spectral estimates. Widening it further keeps shrinking the error bar, but that shrinkage is misleading: the spectrum is not truly linear over a wide range of $k$, so a wider band trades random error for systematic error.\n", + "\n", + "## Varying window size\n", + "\n", + "The window controls the spectral resolution, $\\Delta k = 2\\pi / (N \\Delta x)$. A narrow window gives few long-wavelength bins, which is exactly what the centroid fit depends on." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 3) How large a window of magnetic data is required?\n", + "\n", + "n_windows = 50\n", + "window_sizes = np.linspace(50e3, 304e3, n_windows)\n", + "\n", + "zt_range = (2*np.pi*0.2, 2*np.pi*0.3)\n", + "z0_range = (0.0, 2*np.pi*0.1)\n", + "\n", + "CPD_grid = np.full(n_windows, np.nan)\n", + "sigma_CPD_grid = np.full(n_windows, np.nan)\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.simplefilter(\"ignore\")\n", + "\n", + " for i, window in enumerate(window_sizes):\n", + " try:\n", + " zt, z0, zt_int, z0_int, sigma_zt, sigma_z0 = grid.optimise(\n", + " window, xpt, ypt, zt_range, z0_range, taper=None)\n", + " except ValueError:\n", + " continue\n", + "\n", + " CPD_grid[i], sigma_CPD_grid[i] = grid.calculate_CPD(\n", + " zt, z0, sigma_zt, sigma_z0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax1 = plt.subplots(figsize=(8,6))\n", + "ax1.errorbar(window_sizes/1e3, CPD_grid, yerr=sigma_CPD_grid, capsize=3)\n", + "ax1.axhline(10.305, color='k', linestyle='--', label='true Curie depth')\n", + "ax1.set_xlabel('Window size [km]')\n", + "ax1.set_ylabel('CPD [km]')\n", + "ax1.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is considerable scatter at every window size, so a range of windows should be tested for any dataset rather than trusting a single one. The reported uncertainty does shrink as the window grows, since a finer $\\Delta k$ puts more spectral estimates into each band.\n", + "\n", + "Be careful how much comfort to take from that. A larger window narrows the error bar whether or not the bands are valid, and on this 305 km grid the $Z_o$ band never satisfies $|k|d \\ll 1$ \u2014 see [Ex2](./Ex2-Compute-Curie-depth.ipynb). Precision and accuracy are not the same thing here." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Examples/Notebooks/Tanaka/Ex4-Spatial-variation-of-Curie-depth.ipynb b/Examples/Notebooks/Tanaka/Ex4-Spatial-variation-of-Curie-depth.ipynb new file mode 100644 index 0000000..d984d15 --- /dev/null +++ b/Examples/Notebooks/Tanaka/Ex4-Spatial-variation-of-Curie-depth.ipynb @@ -0,0 +1,185 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 4 - Spatial variation of Curie depth\n", + "\n", + "In Examples 2 and 3 we computed the Curie depth at a single point. Here we map it across the magnetic anomaly, with an uncertainty at every centroid.\n", + "\n", + "### Contents\n", + "\n", + "- [Optimisation routine](#Optimisation-routine)\n", + "- [Uncertainty analysis](#Uncertainty-analysis)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "import pycurious" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load x,y,anomaly\n", + "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", + "\n", + "nx, ny = 305, 305\n", + "\n", + "x = mag_data[:,0]\n", + "y = mag_data[:,1]\n", + "d = mag_data[:,2].reshape(ny,nx)\n", + "\n", + "xmin, xmax = x.min(), x.max()\n", + "ymin, ymax = y.min(), y.max()\n", + "\n", + "# initialise CurieOptimise object\n", + "grid = pycurious.CurieOptimiseTanaka(d, xmin, xmax, ymin, ymax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Optimisation routine\n", + "\n", + "`optimise_routine` evaluates `optimise` at every centroid, distributing them across processors. It takes the same arguments, with lists of coordinates in place of a single pair, and returns one array per output.\n", + "\n", + "The fitting bands are required, in rad/km \u2014 there is no default, because a band that is valid for one dataset is rarely valid for another. See [Ex2](./Ex2-Compute-Curie-depth.ipynb) for how to check a choice with `check_bands`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get centroids\n", + "\n", + "window_size = 200e3\n", + "xc_list, yc_list = grid.create_centroid_list(window_size, spacingX=10e3, spacingY=10e3)\n", + "\n", + "print(\"number of centroids = {}\".format(len(xc_list)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zt_range = (1.257, 1.885)\n", + "z0_range = (0.0, 0.628)\n", + "\n", + "zt, z0, zt_int, z0_int, zt_stdev, z0_stdev = grid.optimise_routine(\n", + " window_size, xc_list, yc_list, zt_range, z0_range, taper=np.hanning)\n", + "\n", + "CPD, sigma_CPD = grid.calculate_CPD(zt, z0, zt_stdev, z0_stdev)\n", + "\n", + "print(\"CPD ranges from {:.1f} to {:.1f} km\".format(CPD.min(), CPD.max()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get dimensions of domain\n", + "xcoords = np.unique(xc_list)\n", + "ycoords = np.unique(yc_list)\n", + "nc, nr = xcoords.size, ycoords.size\n", + "\n", + "\n", + "# plot results\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True, figsize=(12,4.))\n", + "\n", + "im1 = ax1.imshow(CPD.reshape(nr,nc))\n", + "im2 = ax2.imshow(sigma_CPD.reshape(nr,nc))\n", + "\n", + "fig.colorbar(im1, ax=ax1, label=\"CPD (km)\")\n", + "fig.colorbar(im2, ax=ax2, label=\"CPD stdev (km)\")\n", + "\n", + "ax1.set_title('Curie depth')\n", + "ax2.set_title('Uncertainty')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Uncertainty analysis\n", + "\n", + "The map above shows the uncertainty from the fit covariance, which reflects only the scatter of the spectrum. It does not account for the choice of fitting band, which usually matters more.\n", + "\n", + "`sensitivity` resamples the spectrum *and* jitters both band edges, giving a more realistic spread. It is expensive, so here we run it at a single centroid rather than across the whole grid." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpt = 0.5*(xmin + xmax)\n", + "ypt = 0.5*(ymin + ymax)\n", + "\n", + "zt_s, z0_s, CPD_s = grid.sensitivity(\n", + " window_size, xpt, ypt, 500, zt_range, z0_range,\n", + " taper=np.hanning, band_scale=0.1, seed=1)\n", + "\n", + "i = np.argmin((xc_list - xpt)**2 + (yc_list - ypt)**2)\n", + "print(\"fit covariance only : {:.2f} +/- {:.2f} km\".format(CPD[i], sigma_CPD[i]))\n", + "print(\"including band-edge jitter : {:.2f} +/- {:.2f} km\".format(CPD_s.mean(), CPD_s.std()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(7,4.5))\n", + "ax.hist(CPD_s, bins=40, color='0.7', edgecolor='none')\n", + "ax.axvline(CPD_s.mean(), color='r', linewidth=2, label='sampled mean')\n", + "ax.set_xlabel('Curie point depth [km]')\n", + "ax.set_ylabel('count')\n", + "ax.legend()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pycurious/Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb b/Examples/Notebooks/Tanaka/Ex5-Mapping-Curie-depth-EMAG2.ipynb similarity index 87% rename from pycurious/Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb rename to Examples/Notebooks/Tanaka/Ex5-Mapping-Curie-depth-EMAG2.ipynb index 9d7b9a2..834379e 100644 --- a/pycurious/Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb +++ b/Examples/Notebooks/Tanaka/Ex5-Mapping-Curie-depth-EMAG2.ipynb @@ -85,7 +85,7 @@ " {\n", " \"local_file\":\"../../data/Li_et_al_2017.txt\",\n", " \"md5\":'5f0ea0af3e27c6c21cd7b776c979aa80',\n", - " \"url\":\"https://media.nature.com/original/nature-assets/srep/2017/170321/srep45129/extref/srep45129-s1.txt?download=1\",\n", + " \"url\":\"https://static-content.springer.com/esm/art%3A10.1038%2Fsrep45129/MediaObjects/41598_2017_BFsrep45129_MOESM71_ESM.txt\",\n", " \"expected_size\":\"15Mb\"\n", " }, \n", "]" @@ -221,7 +221,7 @@ "source": [ "## Compute Curie depth\n", "\n", - "For this example we use the Bouligand *et al.*, 2009 approach we outlined in previous notebooks." + "For this example we use the centroid method of Tanaka *et al.* (1999), as outlined in the previous notebooks. Both bands are given in rad/km, and `check_bands` is worth running against a guess at the source thickness before relying on them." ] }, { @@ -230,7 +230,12 @@ "metadata": {}, "outputs": [], "source": [ - "grid = pycurious.CurieOptimise(mag_grid, xmin, xmax, ymin, ymax)\n", + "# fitting bands in rad/km -- see Ex2 for how to check these with\n", + "# grid.check_bands() before relying on them\n", + "zt_range = (1.257, 1.885)\n", + "z0_range = (0.0, 0.628)\n", + "\n", + "grid = pycurious.CurieOptimiseTanaka(mag_grid, xmin, xmax, ymin, ymax)\n", "\n", "window_size = 400e3\n", "\n", @@ -239,7 +244,10 @@ "print(\"number of centroids = {}\".format(len(xc_list)))\n", "\n", "\n", - "beta, zt, dz, C = grid.optimise_routine(window_size, xc_list, yc_list)" + "zt, z0, zt_int, z0_int, zt_stdev, z0_stdev = grid.optimise_routine(window_size, xc_list, yc_list,\n", + " zt_range, z0_range, taper=np.hamming)\n", + "\n", + "CPD, sigma_CPD = grid.calculate_CPD(zt, z0, zt_stdev, z0_stdev)" ] }, { @@ -248,10 +256,10 @@ "metadata": {}, "outputs": [], "source": [ - "curie_depth = zt + dz\n", "centroids = np.column_stack([xc_list, yc_list])\n", "\n", - "curie_depth_sphere = mapping.grid(centroids, curie_depth, extent_sphere, (ny,nx), epsg_in=2157, epsg_out=4326)" + "CPD_sphere = mapping.grid(centroids, CPD, extent_sphere, (ny,nx), epsg_in=2157, epsg_out=4326)\n", + "sigma_CPD_sphere = mapping.grid(centroids, sigma_CPD, extent_sphere, (ny,nx), epsg_in=2157, epsg_out=4326)" ] }, { @@ -270,10 +278,10 @@ "ax.coastlines(resolution='50m', linewidth=1.5)\n", "ax.gridlines()\n", "\n", - "im1 = ax.imshow(curie_depth_sphere, extent=extent_sphere, transform=ccrs.PlateCarree(),\n", - " cmap='BrBG', vmin=10, vmax=40, zorder=0)\n", + "im1 = ax.imshow(CPD_sphere, extent=extent_sphere, transform=ccrs.PlateCarree(),\n", + " cmap='BrBG', zorder=0)\n", "\n", - "fig.colorbar(im1, label='nT')" + "fig.colorbar(im1, label='km')" ] }, { @@ -314,7 +322,7 @@ "ax.gridlines()\n", "\n", "im1 = ax.imshow(li_grid, extent=extent_sphere, transform=ccrs.PlateCarree(),\n", - " cmap='BrBG', zorder=0, vmin=10, vmax=40)\n", + " cmap='BrBG', zorder=0, )\n", "\n", "fig.colorbar(im1, label='km')" ] @@ -334,7 +342,7 @@ "metadata": {}, "outputs": [], "source": [ - "mapping.export_geotiff(\"my-curie-depth.tiff\", curie_depth_sphere, extent_sphere, epsg=4326)" + "mapping.export_geotiff(\"my-curie-depth.tiff\", CPD_sphere, extent_sphere, epsg=4326)" ] }, { @@ -359,7 +367,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -373,7 +381,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.13.14" } }, "nbformat": 4, diff --git a/pycurious/Examples/data/test_mag_data.txt b/Examples/data/test_mag_data.txt similarity index 100% rename from pycurious/Examples/data/test_mag_data.txt rename to Examples/data/test_mag_data.txt diff --git a/MANIFEST.in b/MANIFEST.in index 4cbb4df..799f641 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,9 +3,30 @@ include MANIFEST.in include CONTRIBUTING.md include COPYING include COPYING.LESSER -include setup.cfg -include src/* -recursive-include pycurious/ *.py -recursive-include pycurious/ *.ipynb -include pycurious/Examples/data/test_mag_data.txt +include pyproject.toml +recursive-include pycurious *.py include tests/*.py +include Examples/data/test_mag_data.txt + +# Notebooks are listed one by one rather than globbed. MANIFEST.in does not +# consult .gitignore, so `recursive-include Examples *.ipynb` shipped whatever +# happened to be sitting in the working tree: Jupyter checkpoints, scratch +# analyses, collaborator data. Adding a notebook here is a deliberate act. +include Examples/0-StartHere.ipynb + +include Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb +include Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb +include Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb +include Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb +include Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb + +include Examples/Notebooks/Tanaka/Ex1-Plot-amplitude-spectrum.ipynb +include Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb +include Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb +include Examples/Notebooks/Tanaka/Ex4-Spatial-variation-of-Curie-depth.ipynb +include Examples/Notebooks/Tanaka/Ex5-Mapping-Curie-depth-EMAG2.ipynb + +# Belt and braces for the globs above, and for anything setuptools pulls in of +# its own accord. +global-exclude *-checkpoint.ipynb +global-exclude .DS_Store diff --git a/README.md b/README.md index da7e01e..f81634e 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,14 @@ -![PyCurious](https://github.com/brmather/pycurious/blob/master/pycurious/Examples/Images/pycurious-logo.png?raw=true) +![PyCurious](https://github.com/brmather/pycurious/blob/master/Examples/Images/pycurious-logo.png?raw=true) -[![Docker Cloud Automated build](https://img.shields.io/docker/cloud/automated/brmather/pycurious.svg)](https://hub.docker.com/r/brmather/pycurious) +[![Tests](https://github.com/brmather/pycurious/actions/workflows/tests.yml/badge.svg)](https://github.com/brmather/pycurious/actions/workflows/tests.yml) +[![Documentation](https://img.shields.io/badge/docs-online-blue.svg)](https://brmather.github.io/pycurious/) [![PyPI](https://img.shields.io/pypi/v/pycurious.svg)](https://pypi.org/project/pycurious/) [![DOI](https://zenodo.org/badge/123281222.svg)](https://zenodo.org/badge/latestdoi/123281222) -[![Build Status](https://travis-ci.org/brmather/pycurious.svg?branch=master)](https://travis-ci.org/brmather/pycurious) Magnetic data is one of the most common geophysics datasets available on the surface of the Earth. Curie depth is the depth at which rocks lose their magnetism. The most prevalent magnetic mineral is magnetite, which has a Curie point of 580°C, thus the Curie depth is often interpreted as the 580°C isotherm. Current methods to derive Curie depth first compute the (fast) Fourier transform over a square window of a magnetic anomaly that has been reduced to the pole. The depth and thickness of magnetic sources is estimated from the slope of the radial power spectrum. `pycurious` implements the Tanaka *et al.* (1999) and Bouligand *et al.* (2009) methods for computing the thickness of a buried magnetic source. `pycurious` ingests maps of the magnetic anomaly and distributes the computation of Curie depth across multiple CPUs. Common computational workflows and geospatial manipulation of magnetic data are covered in the Jupyter notebooks bundled with this package. -#### Binder - -Launch the demonstration at [mybinder.org](https://mybinder.org/v2/gh/brmather/pycurious/binder?filepath=Notebooks%2F0-StartHere.ipynb) - -[![badge](https://img.shields.io/badge/launch-pycurious-E66581.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/brmather/pycurious/binder?filepath=Notebooks%2F0-StartHere.ipynb) - #### Citation [![DOI](http://joss.theoj.org/papers/10.21105/joss.01544/status.svg)](https://doi.org/10.21105/joss.01544) @@ -34,29 +28,30 @@ pycurious.install_documentation(path="Notebooks") ### Tanaka -- [Ex1-Plot-power-spectrum.ipynb](pycurious/Examples/Notebooks/Tanaka/Ex1-Plot-power-spectrum.ipynb) -- [Ex2-Compute-Curie-depth.ipynb](pycurious/Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb) -- [Ex3-Parameter-exploration.ipynb](pycurious/Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb) +- [Ex1-Plot-amplitude-spectrum.ipynb](Examples/Notebooks/Tanaka/Ex1-Plot-amplitude-spectrum.ipynb) +- [Ex2-Compute-Curie-depth.ipynb](Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb) +- [Ex3-Parameter-exploration.ipynb](Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb) +- [Ex4-Spatial-variation-of-Curie-depth.ipynb](Examples/Notebooks/Tanaka/Ex4-Spatial-variation-of-Curie-depth.ipynb) +- [Ex5-Mapping-Curie-depth-EMAG2.ipynb](Examples/Notebooks/Tanaka/Ex5-Mapping-Curie-depth-EMAG2.ipynb) ### Bouligand -- [Ex1-Plot-power-spectrum.ipynb](pycurious/Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb) -- [Ex2-Compute-Curie-depth.ipynb](pycurious/Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb) -- [Ex3-Posing-the-inverse-problem.ipynb](pycurious/Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb) -- [Ex4-Spatial-variation-of-Curie-depth.ipynb](pycurious/Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb) -- [Ex5-Mapping-Curie-depth-EMAG2.ipynb](pycurious/Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb) +- [Ex1-Plot-power-spectrum.ipynb](Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb) +- [Ex2-Compute-Curie-depth.ipynb](Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb) +- [Ex3-Posing-the-inverse-problem.ipynb](Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb) +- [Ex4-Spatial-variation-of-Curie-depth.ipynb](Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb) +- [Ex5-Mapping-Curie-depth-EMAG2.ipynb](Examples/Notebooks/Bouligand/Ex5-Mapping-Curie-depth-EMAG2.ipynb) ## Installation ### Dependencies -You will need **Python 2.7 or 3.5+**. +You will need **Python 3.9 or newer**. Also, the following packages are required: - [`numpy`](http://numpy.org) - [`scipy`](https://scipy.org) -- [`cython`](https://cython.org/) __Optional dependencies__ for mapping module and running the Notebooks: @@ -64,17 +59,27 @@ __Optional dependencies__ for mapping module and running the Notebooks: - [`matplotlib`](https://matplotlib.org/) - [`pyproj`](https://github.com/jswhit/pyproj) - [`cartopy`](https://scitools.org.uk/cartopy/docs/latest/) +- [`netCDF4`](https://unidata.github.io/netcdf4-python/) +- [`requests`](https://requests.readthedocs.io/) ### Installing using pip You can install `pycurious` using the -[`pip package manager`](https://pypi.org/project/pip/) with either version of Python: +[`pip package manager`](https://pypi.org/project/pip/): ```bash -python2 -m pip install pycurious python3 -m pip install pycurious ``` -All the dependencies will be automatically installed by `pip`. +All the required dependencies will be automatically installed by `pip`. + +The optional dependencies are grouped into extras, so you can install only +what you need: + +```bash +python3 -m pip install pycurious[download] # requests +python3 -m pip install pycurious[mapping] # pyproj, netCDF4 +python3 -m pip install pycurious[examples] # matplotlib, jupyter, cartopy +``` ### Installing with conda @@ -82,13 +87,13 @@ You can install `pycurious` using the [conda package manager](https://conda.io). Its required dependencies can be easily installed with: ```bash -conda install numpy scipy cython +conda install numpy scipy ``` And the full set of dependencies with: ```bash -conda install numpy scipy cython matplotlib pyproj cartopy +conda install numpy scipy matplotlib pyproj cartopy netcdf4 requests ``` Then `pycurious` can be installed with `pip`: @@ -136,22 +141,13 @@ conda install gxx_linux-64 And then install `pycurious` normally. - -### Installing using Docker - -A more straightforward installation for `pycurious` and all of its dependencies may be deployed with [Docker](https://www.docker.com). -To install the docker image and start the Jupyter notebook examples: - -```bash -docker run --name pycurious -p 127.0.0.1:8888:8888 brmather/pycurious:latest -``` - ## Usage -PyCurious consists of 2 classes: +PyCurious consists of 3 classes: - `CurieGrid`: base class that computes radial power spectrum, centroids for processing, decomposition of subgrids. -- `CurieOptimise`: optimisation module for fitting the synthetic power spectrum (inherits CurieGrid). +- `CurieOptimiseBouligand`: optimisation module for fitting the synthetic power spectrum of Bouligand *et al.* (2009) (inherits CurieGrid). +- `CurieOptimiseTanaka`: optimisation module for the centroid method of Tanaka *et al.* (1999) (inherits CurieGrid). Also included is a `mapping` module for gridding scattered data points, and converting between coordinate reference systems (CRS). @@ -160,8 +156,8 @@ Below is a simple workflow to calculate the radial power spectrum: ```python import pycurious -# initialise CurieOptimise object with 2D magnetic anomaly -grid = pycurious.CurieOptimise(mag_anomaly, xmin, xmax, ymin, ymax) +# initialise CurieOptimiseBouligand object with 2D magnetic anomaly +grid = pycurious.CurieOptimiseBouligand(mag_anomaly, xmin, xmax, ymin, ymax) # extract a square window of the magnetic anomaly subgrid = grid.subgrid(window_size, x, y) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..a729f2b --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,29 @@ +# Minimal makefile for Sphinx documentation +# +# Run `make html` from the docs/ directory to build the site locally. The +# `copy-notebooks` prerequisite stages the tutorial notebooks into the source +# tree first (see copy_notebooks.py). + +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build +PYTHON ?= python3 + +.PHONY: help html copy-notebooks clean Makefile + +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +copy-notebooks: + $(PYTHON) copy_notebooks.py + +html: copy-notebooks + @$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O) + +clean: + rm -rf "$(BUILDDIR)" tutorials/bouligand tutorials/tanaka data + +# Catch-all target: route unknown targets to sphinx-build after staging notebooks. +%: copy-notebooks Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..05cbf17 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,20 @@ +/* PyCurious documentation — small tweaks on top of the Furo theme. */ + +/* Keep the sidebar logo a sensible size. */ +.sidebar-logo { + max-width: 180px; + margin: 0 auto; +} + +/* Let wide tables and math scroll rather than overflow the page body. */ +.rst-content table.docutils, +div.math { + overflow-x: auto; +} + +/* A little more breathing room around API signatures. */ +dl.py.class > dt, +dl.py.function > dt, +dl.py.method > dt { + margin-top: 0.6em; +} diff --git a/docs/_static/pycurious-logo.png b/docs/_static/pycurious-logo.png new file mode 100644 index 0000000..2c50377 Binary files /dev/null and b/docs/_static/pycurious-logo.png differ diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..d1334c1 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,65 @@ +# API reference + +The public API of PyCurious. Everything below is importable directly from the +top-level `pycurious` namespace. + +## Grid and spectra + +The base class shared by both optimisers — subgrid decomposition, the radial and +window spectra, and the covariance machinery behind the uncertainties. + +```{autoclass} pycurious.CurieGrid +:members: +``` + +## Bouligand optimiser + +```{autoclass} pycurious.CurieOptimiseBouligand +:members: +``` + +## Tanaka optimiser + +```{autoclass} pycurious.CurieOptimiseTanaka +:members: +``` + +## Synthetic and analytic spectra + +```{autofunction} pycurious.fractal_anomaly +``` + +```{autofunction} pycurious.bouligand2009 +``` + +```{autofunction} pycurious.maus1995 +``` + +## Mapping + +Projections, netCDF, and GeoTIFF I/O (all lazily imported — see the +[`mapping` and `geotiff` extras](../dependencies.md)). + +```{automodule} pycurious.mapping +:members: +``` + +## Downloads + +```{automodule} pycurious.download +:members: +``` + +## Deprecated + +`tanaka1999` and `ComputeTanaka` implement the centroid method without +uncertainties and are superseded by +{py:class}`~pycurious.CurieOptimiseTanaka`. They are retained for backward +compatibility. + +```{autofunction} pycurious.tanaka1999 +``` + +```{autoclass} pycurious.ComputeTanaka +:members: +``` diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..5d3e662 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,120 @@ +# Copyright 2018-2019 Ben Mather, Robert Delhaye +# +# This file is part of PyCurious. +# +# PyCurious is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or any later version. +# +# PyCurious is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PyCurious. If not, see . + +"""Sphinx configuration for the PyCurious documentation.""" + +from importlib.metadata import version as _version + +# -- Project information ----------------------------------------------------- + +project = "PyCurious" +copyright = "2018-2019, Ben Mather, Robert Delhaye" +author = "Ben Mather, Robert Delhaye" + +# The single source of truth for the version is pyproject.toml; read it back +# from the installed metadata rather than duplicating it here. +try: + release = _version("pycurious") +except Exception: # not installed (e.g. a bare docs checkout) + release = "2.0" +version = release + +# -- General configuration --------------------------------------------------- + +extensions = [ + "myst_nb", # Markdown pages + executable notebooks (pulls in myst-parser) + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "sphinx_copybutton", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] + +# The function docstrings are written in pdoc-flavoured Markdown (single-backtick +# code spans, the odd blockquote and bullet list). Rendering single backticks as +# inline literals matches that intent instead of treating them as RST +# interpreted text. +default_role = "literal" + +# Warnings we accept rather than fix. The tutorial notebooks are shipped +# verbatim and carry internal anchor links and H1->H3 jumps that MyST flags; and +# the notebooks are executed from their own directory (so they can read the +# bundled data via relative paths), which myst-nb notes. None affect the output. +suppress_warnings = [ + "myst.xref_missing", + "myst.header", + "mystnb.local_cwd", +] + +# -- autodoc / autosummary --------------------------------------------------- + +autosummary_generate = True +autodoc_default_options = { + "members": True, + "inherited-members": True, + "show-inheritance": True, +} +autodoc_typehints = "description" +autodoc_member_order = "bysource" + +# The docstrings use Google-style section headers (Args:/Returns:/Notes:) with +# NumPy-style `name : type` bodies, so enable both napoleon dialects. +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = False +napoleon_use_rtype = True + +# -- MyST / myst-nb ---------------------------------------------------------- + +myst_enable_extensions = ["dollarmath", "amsmath", "colon_fence", "deflist"] +myst_heading_anchors = 3 + +# Execute the self-contained tutorials at build time. The two Ex5 notebooks +# each pull ~600 MB of external data (EMAG2 v3 + Li et al.), so they are +# excluded from execution and render as code with a note pointing at the data. +nb_execution_mode = "cache" +nb_execution_excludepatterns = ["**/Ex5-*.ipynb"] +nb_execution_timeout = 300 +nb_execution_raise_on_error = True + +# -- intersphinx ------------------------------------------------------------- + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "scipy": ("https://docs.scipy.org/doc/scipy/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), +} + +# -- HTML output ------------------------------------------------------------- + +html_theme = "furo" +html_title = "PyCurious" +html_static_path = ["_static"] +html_css_files = ["custom.css"] +html_logo = "_static/pycurious-logo.png" +html_favicon = "_static/pycurious-logo.png" + +html_theme_options = { + "source_repository": "https://github.com/brmather/pycurious/", + "source_branch": "master", + "source_directory": "docs/", +} diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..e4e690a --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,44 @@ +% The contributing guide is maintained as CONTRIBUTING.md at the repository root +% (GitHub surfaces it on the issue/PR pages) and included here verbatim. + +```{include} ../CONTRIBUTING.md +``` + +## Building the documentation + +The documentation is built with [Sphinx](https://www.sphinx-doc.org/) using the +[Furo](https://pradyunsg.me/furo/) theme, with [MyST-NB](https://myst-nb.readthedocs.io/) +rendering both the Markdown pages and the tutorial notebooks. To build it +locally: + +```bash +python3 -m pip install -e ".[docs,examples,mapping]" +python3 docs/copy_notebooks.py # stage notebooks into docs/tutorials/ +sphinx-build -b html docs docs/_build/html +``` + +Open `docs/_build/html/index.html`. The self-contained tutorials (Ex1–Ex4 for +both methods) execute at build time; the two `Ex5` notebooks each need ~600 MB of +external data and render as code only. + +## Releasing (maintainers) + +Continuous integration runs on GitHub Actions: + +- **`tests.yml`** — runs the test suite on every push and pull request across + Python 3.9–3.13. +- **`docs.yml`** — builds these docs on every push and pull request, and deploys + them to GitHub Pages from `master`. +- **`publish.yml`** — builds the sdist and wheel and publishes them to PyPI when + a GitHub release is published. + +A release is cut by publishing a GitHub release for the tagged version; the +`publish.yml` workflow then uploads to PyPI automatically. Two settings are +configured once on the repository: + +- **GitHub Pages** — *Settings → Pages → Source* set to **GitHub Actions**, so + `docs.yml` can deploy to . +- **PyPI Trusted Publishing** — on the `pycurious` project at PyPI, add a trusted + publisher pointing at owner `brmather`, repository `pycurious`, workflow + `publish.yml`, environment `pypi`. This lets Actions publish over OIDC with no + stored password or API token. diff --git a/docs/copy_notebooks.py b/docs/copy_notebooks.py new file mode 100644 index 0000000..96367bd --- /dev/null +++ b/docs/copy_notebooks.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# Copyright 2018-2019 Ben Mather, Robert Delhaye +# +# This file is part of PyCurious. +# +# PyCurious is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or any later version. +# +# PyCurious is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PyCurious. If not, see . + +""" +Stage the tutorial notebooks into the Sphinx source tree. + +myst-nb can only build notebooks that live inside the documentation source +directory, so before ``sphinx-build`` the canonical example notebooks are copied +from ``Examples/`` into ``docs/tutorials/{bouligand,tanaka}/``. The bundled test +fixture is copied to ``docs/data/`` as well, because the Tanaka notebooks load it +through the relative path ``../../data/test_mag_data.txt`` and myst-nb executes +each notebook from its own directory. + +The copied notebooks and data are gitignored -- this script regenerates them on +every build. Only the eleven canonical notebooks are staged (the same list as +``MANIFEST.in``); the ``Ex5`` notebooks are staged but not executed (see +``nb_execution_excludepatterns`` in ``conf.py``). +""" + +import shutil +from pathlib import Path + +DOCS = Path(__file__).resolve().parent +REPO = DOCS.parent +EXAMPLES = REPO / "Examples" + +# (source relative to Examples/Notebooks) -> (destination folder under tutorials) +NOTEBOOKS = { + "Bouligand": [ + "Ex1-Plot-power-spectrum.ipynb", + "Ex2-Compute-Curie-depth.ipynb", + "Ex3-Posing-the-inverse-problem.ipynb", + "Ex4-Spatial-variation-of-Curie-depth.ipynb", + "Ex5-Mapping-Curie-depth-EMAG2.ipynb", + ], + "Tanaka": [ + "Ex1-Plot-amplitude-spectrum.ipynb", + "Ex2-Compute-Curie-depth.ipynb", + "Ex3-Parameter-exploration.ipynb", + "Ex4-Spatial-variation-of-Curie-depth.ipynb", + "Ex5-Mapping-Curie-depth-EMAG2.ipynb", + ], +} + +# Small data fixture the executed Tanaka notebooks read via ../../data/... +DATA_FILES = ["test_mag_data.txt"] + + +def main(): + for method, notebooks in NOTEBOOKS.items(): + dest = DOCS / "tutorials" / method.lower() + dest.mkdir(parents=True, exist_ok=True) + for name in notebooks: + src = EXAMPLES / "Notebooks" / method / name + if not src.exists(): + raise FileNotFoundError(f"missing tutorial notebook: {src}") + shutil.copy2(src, dest / name) + print(f"staged {src.relative_to(REPO)} -> {(dest / name).relative_to(REPO)}") + + data_dest = DOCS / "data" + data_dest.mkdir(parents=True, exist_ok=True) + for name in DATA_FILES: + src = EXAMPLES / "data" / name + if not src.exists(): + raise FileNotFoundError(f"missing data fixture: {src}") + shutil.copy2(src, data_dest / name) + print(f"staged {src.relative_to(REPO)} -> {(data_dest / name).relative_to(REPO)}") + + +if __name__ == "__main__": + main() diff --git a/docs/dependencies.md b/docs/dependencies.md new file mode 100644 index 0000000..3e1f3ae --- /dev/null +++ b/docs/dependencies.md @@ -0,0 +1,68 @@ +# Software dependencies + +PyCurious keeps a deliberately small import-time footprint: only **numpy** and +**scipy** are imported when you `import pycurious`. Everything else is imported +lazily inside the function that needs it, so it lives in an optional *extra* and +is pulled in only when you ask for it. + +## Core dependencies + +| Package | Purpose | +|---|---| +| [`numpy`](https://numpy.org) (≥ 1.20) | arrays, FFTs, the grid and spectrum layer | +| [`scipy`](https://scipy.org) (≥ 1.5) | optimisation, special functions, banded linear algebra | + +Requires **Python 3.9 or newer**. + +## Optional extras + +Install an extra with `pip install "pycurious[]"`. They compose, e.g. +`pip install "pycurious[mapping,download]"`. + +### `download` + +- [`requests`](https://requests.readthedocs.io/) + +Backs {py:mod}`pycurious.download` — cached downloads with md5 checks, used by the +mapping tutorials to fetch the EMAG2 and reference datasets. + +### `mapping` + +- [`pyproj`](https://pyproj4.github.io/pyproj/) — coordinate reference system transforms +- [`netCDF4`](https://unidata.github.io/netcdf4-python/) — reading and writing gridded data + +Backs the projection and netCDF parts of {py:mod}`pycurious.mapping`. + +### `geotiff` + +- [`gdal`](https://gdal.org/) + +Backs the GeoTIFF import/export in {py:mod}`pycurious.mapping`. It is kept **out +of `mapping`** on purpose: the GDAL Python bindings need a matching system +`libgdal` already installed, so folding it into `mapping` would make +`pip install "pycurious[mapping]"` fail for most users. `conda install gdal` is +usually easier than installing it from pip. + +### `examples` + +- [`matplotlib`](https://matplotlib.org/), [`jupyter`](https://jupyter.org/), + [`cartopy`](https://scitools.org.uk/cartopy/docs/latest/), + [`pyproj`](https://pyproj4.github.io/pyproj/) + +Everything needed to run the bundled [Tutorials](tutorials/index.md). + +### `test` + +- [`pytest`](https://pytest.org/) + +The test suite imports only `pytest`, `numpy`, `scipy`, and `pycurious`, so +`pip install -e ".[test]" && pytest` runs the whole suite. + +### `docs` + +- [`sphinx`](https://www.sphinx-doc.org/), [`furo`](https://pradyunsg.me/furo/), + [`myst-nb`](https://myst-nb.readthedocs.io/), + [`sphinx-copybutton`](https://sphinx-copybutton.readthedocs.io/) + +Builds this documentation. Building the tutorials additionally needs the +`examples` and `mapping` extras so the notebooks execute. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..bf6e12a --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,102 @@ +# Getting started + +## Installation + +You will need **Python 3.9 or newer**. Only `numpy` and `scipy` are required at +import time; everything else is optional and grouped into +[extras](dependencies.md). + +### Installing with pip + +Install `pycurious` from [PyPI](https://pypi.org/project/pycurious/) with the +[pip package manager](https://pypi.org/project/pip/): + +```bash +python3 -m pip install pycurious +``` + +The optional dependencies are grouped into extras, so you install only what you +need: + +```bash +python3 -m pip install "pycurious[download]" # requests +python3 -m pip install "pycurious[mapping]" # pyproj, netCDF4 +python3 -m pip install "pycurious[examples]" # matplotlib, jupyter, cartopy +``` + +### Installing with conda + +The required dependencies install cleanly from conda-forge: + +```bash +conda install numpy scipy +``` + +and the full set for the notebooks with: + +```bash +conda install numpy scipy matplotlib pyproj cartopy netcdf4 requests +``` + +then `pycurious` itself with pip: + +```bash +pip install pycurious +``` + +Alternatively, create a dedicated environment from the bundled `environment.yml`: + +```bash +git clone https://github.com/brmather/pycurious +cd pycurious +conda env create -f environment.yml +conda activate pycurious +pip install pycurious +``` + +```{note} +If installation fails due to +[an issue with `gcc` and Anaconda](https://github.com/Anaconda-Platform/anaconda-project/issues/183), +install `gxx_linux-64` with conda (`conda install gxx_linux-64`) and try again. +``` + +See [Software dependencies](dependencies.md) for what each extra provides and the +GDAL caveat behind the `geotiff` extra. + +## A first spectrum + +PyCurious exposes three classes: `CurieGrid` (the shared grid and spectrum +layer), `CurieOptimiseBouligand`, and `CurieOptimiseTanaka`. A minimal workflow +to compute the radial power spectrum of a window: + +```python +import pycurious + +# initialise a CurieOptimiseBouligand object with a 2D magnetic anomaly +grid = pycurious.CurieOptimiseBouligand(mag_anomaly, xmin, xmax, ymin, ymax) + +# extract a square window of the magnetic anomaly +subgrid = grid.subgrid(window_size, x, y) + +# compute the radial power spectrum +k, Phi, sigma_Phi = grid.radial_spectrum(subgrid) +``` + +Here `k` is the wavenumber in **rad/km**, `Phi` the radially averaged spectrum, +and `sigma_Phi` the scatter of the FFT cells within each annulus. To fit a Curie +depth, work through the [Tutorials](tutorials/index.md), which build a synthetic +anomaly with a known answer and recover it with both methods, complete with +uncertainties. + +## Running the tests + +The test suite runs with [`pytest`](https://pypi.org/project/pytest/) once the +`test` extra is installed: + +```bash +git clone https://github.com/brmather/pycurious +cd pycurious +python3 -m pip install -e ".[test]" +pytest # the full suite +pytest -m "not slow" # skip the calibration tests that fit hundreds of realisations +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..88148bf --- /dev/null +++ b/docs/index.md @@ -0,0 +1,52 @@ +# PyCurious + +**PyCurious** estimates the **Curie point depth** — the depth at which rock loses +its magnetisation — from the radially averaged spectrum of a magnetic anomaly. + +Magnetic data is one of the most common geophysics datasets available at the +surface of the Earth. The most prevalent magnetic mineral is magnetite, whose +Curie point is 580 °C, so the Curie depth is often interpreted as the 580 °C +isotherm. PyCurious computes the (fast) Fourier transform over square windows of +a magnetic anomaly reduced to the pole and estimates the depth and thickness of +the magnetic source from the slope of the radial spectrum. It implements two +methods, sharing one grid and spectrum layer, and — the defining feature of v2 — +**both return uncertainties**: + +- **Bouligand *et al.* (2009)** — fits a four-parameter analytic spectrum + (`beta, zt, dz, C`) by optimisation, with a full Bayesian toolkit + (profile-deviance intervals, Metropolis–Hastings sampling, sensitivity + analysis). +- **Tanaka *et al.* (1999)** — the centroid method: two straight lines fitted to + separate wavenumber bands, each weighted by the measured spectral scatter. + +PyCurious ingests maps of the magnetic anomaly and distributes the computation of +Curie depth across multiple CPUs. + +## Citation + +> Mather, B. and Delhaye, R. (2019). PyCurious: A Python module for computing the +> Curie depth from the magnetic anomaly. *Journal of Open Source Software*, +> 4(39), 1544, + +```{toctree} +:maxdepth: 2 +:caption: Contents + +getting-started +dependencies +tutorials/index +theory/index +api/index +contributing +``` + +## References + +1. Bouligand, C., Glen, J. M. G., & Blakely, R. J. (2009). Mapping Curie + temperature depth in the western United States with a fractal model for + crustal magnetization. *Journal of Geophysical Research*, 114(B11104), 1–25. + +2. Tanaka, A., Okubo, Y., & Matsubayashi, O. (1999). Curie point depth based on + spectrum analysis of the magnetic anomaly data in East and Southeast Asia. + *Tectonophysics*, 306(3–4), 461–470. + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..412ec3f --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +if "%PYTHON%" == "" ( + set PYTHON=python +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Install the docs extra with + echo. pip install -e ".[docs,examples,mapping]" + exit /b 1 +) + +if "%1" == "" goto help + +REM Stage the tutorial notebooks into the source tree before building. +%PYTHON% copy_notebooks.py + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/theory/bouligand.md b/docs/theory/bouligand.md new file mode 100644 index 0000000..c94bd13 --- /dev/null +++ b/docs/theory/bouligand.md @@ -0,0 +1,92 @@ +# The Bouligand fractal model + +Bouligand *et al.* (2009) model the crust as a magnetic layer with **fractal** +(self-similar) magnetisation, rather than the spatially random magnetisation +Tanaka assumes. Their equation (4) gives the radially averaged power spectrum +$\Phi$ as a function of four parameters, and PyCurious fits all four at once. + +## The analytic spectrum + +{py:func}`pycurious.bouligand2009` returns the **log power spectrum** + +$$ \ln \Phi(|k|) = C - 2|k|Z_t - (\beta - 1)\ln|k| - |k|\,\Delta z + \ln A $$ + +with + +$$ A = \frac{\sqrt{\pi}}{\Gamma\!\left(1 + \tfrac{\beta}{2}\right)} + \left[ \tfrac{1}{2}\cosh(|k|\,\Delta z)\,\Gamma\!\left(\tfrac{1+\beta}{2}\right) + - K_{-\frac{1+\beta}{2}}(|k|\,\Delta z) + \left(\tfrac{|k|\,\Delta z}{2}\right)^{\frac{1+\beta}{2}} \right] $$ + +where $K_\nu$ is the modified Bessel function of the second kind. The four +parameters are + +| parameter | meaning | +|---|---| +| $\beta$ | fractal parameter (spectral exponent of the magnetisation) | +| $Z_t$ | depth to the top of the magnetic source | +| $\Delta z$ | thickness of the magnetic source | +| $C$ | field constant (Maus *et al.*, 1997) | + +The Curie point depth is $Z_t + \Delta z$. {py:func}`pycurious.maus1995` is a +simplified version of the same model without the higher-order Bessel integration. + +Because the spectrum is fitted in log power, `power=2.0` (the default of +{py:meth}`~pycurious.CurieGrid.radial_spectrum`) is the right choice for this +method. + +## The inverse problem + +{py:class}`~pycurious.CurieOptimiseBouligand` poses the recovery of the four +parameters as a Bayesian inverse problem. The posterior is + +$$ P(\mathbf{m} \mid \mathbf{d}) = P(\beta, Z_t, \Delta z, C \mid \Phi_d) $$ + +where $\Phi_d$ is the radial power spectrum measured from the FFT over square +windows of the magnetic anomaly. The class provides a flexible objective function +that accepts *a priori* and likelihood terms, so priors can be placed on any +parameter, and it decomposes the computation across CPUs to map Curie depth over a +whole grid. + +## Uncertainties + +The likelihood is weighted by the **uncertainty of the annulus mean** returned by +{py:meth}`~pycurious.CurieGrid.window_spectrum`, not the raw within-annulus +scatter. Two corrections, both measured by Monte Carlo rather than assumed, turn +that scatter into an honest weight: + +1. **Within a bin**, the FFT cells are not independent — a real field is + Hermitian, so about half of them repeat (exactly a factor of two untapered), + and a taper correlates neighbours further. The lost degrees of freedom + dominate the sparse inner bins, which is exactly where the depth is + determined. +2. **Between bins**, neighbouring residuals correlate because a taper spreads each + wavenumber over several bins. Estimating this from the residuals and solving a + generalised least-squares covariance matters: ignoring it understates every + reported $\sigma$ by about 30 %. + +Beyond the covariance, +{py:meth}`~pycurious.CurieOptimiseBouligand.profile` gives profile-deviance +intervals — which matter because $\Delta z$ and the Curie depth are genuinely +asymmetric — {py:meth}`~pycurious.CurieOptimiseBouligand.metropolis_hastings` +samples the posterior, and +{py:meth}`~pycurious.CurieOptimiseBouligand.sensitivity` resamples the spectrum. + +```{note} +`C` is not recoverable in practice and is best treated as a nuisance parameter: +log-averaging costs the Euler–Mascheroni constant and a Hanning taper costs a +further fixed offset. `beta` and `Z_t` recover tightly, but `dz` is noisy — assert +on it across seeds or in the mean, not on a single realisation. +``` + +## References + +Bouligand, C., Glen, J. M. G., & Blakely, R. J. (2009). Mapping Curie temperature +depth in the western United States with a fractal model for crustal +magnetization. *Journal of Geophysical Research*, 114(B11104), 1–25. +[doi:10.1029/2009JB006494](https://doi.org/10.1029/2009JB006494) + +Maus, S., Gordon, D., & Fairhead, D. (1997). Curie temperature depth estimation +using a self-similar magnetization model. *Geophysical Journal International*, +129, 163–168. +[doi:10.1111/j.1365-246X.1997.tb00945.x](https://doi.org/10.1111/j.1365-246X.1997.tb00945.x) diff --git a/docs/theory/index.md b/docs/theory/index.md new file mode 100644 index 0000000..963f215 --- /dev/null +++ b/docs/theory/index.md @@ -0,0 +1,29 @@ +# Theory + +Both methods estimate the depth and thickness of a buried magnetic source from +the slope of the radially averaged spectrum of the magnetic anomaly. They differ +in the model fitted to that spectrum and in how the fit is turned into a Curie +depth with an uncertainty. + +```{toctree} +:maxdepth: 2 + +bouligand +tanaka +``` + +## Conventions + +A few conventions run through the whole package and are easy to get wrong: + +- **Wavenumbers are in rad/km, everywhere.** {py:meth}`~pycurious.CurieGrid.radial_spectrum` + returns `k` in rad/km, and the Tanaka fitting bands are given in the same units. +- **The DFT fundamental is $dk = 2\pi/(N\,dx)$**, not $2\pi/((N-1)\,dx)$; using + $N-1$ understates every depth by a factor $(N-1)/N$. +- **Depths are positive downwards.** The optimisers return depths, not the + negative gradients the fits produce. +- **The `power` argument selects which spectrum you get.** + {py:meth}`~pycurious.CurieGrid.radial_spectrum` raises $|\mathrm{FFT}|$ to + `power` before averaging. Since $\Phi = |\mathrm{FFT}|^2$, Bouligand fits the + log power spectrum (`power=2.0`, the default) while Tanaka fits the log + amplitude spectrum (`power=1.0`). diff --git a/docs/theory/tanaka.md b/docs/theory/tanaka.md new file mode 100644 index 0000000..2384a25 --- /dev/null +++ b/docs/theory/tanaka.md @@ -0,0 +1,100 @@ +# The Tanaka centroid method + +The centroid method of Tanaka *et al.* (1999) assumes a randomly magnetised layer +between depths $Z_t$ and $Z_b$, for which the radially averaged power spectrum is + +$$ \Phi_{\Delta T}(|k|) = A\, e^{-2 |k| Z_t} \left( 1 - e^{-|k|(Z_b - Z_t)} \right)^2 $$ + +Two limits of this expression each give a straight line, fitted over a different +band of wavenumbers. + +## The two bands + +At wavelengths **short** compared with the source thickness the layer looks like +a half space, and the log amplitude spectrum has slope $-Z_t$: + +$$ \ln \Phi_{\Delta T}^{1/2} = \ln B - |k| Z_t $$ + +At **long** wavelengths the expression is rewritten about the centroid depth +$Z_0$, with $d$ for the *half* thickness. Since +$\Phi^{1/2} \propto e^{-|k|Z_t} - e^{-|k|Z_b}$, and $Z_t - Z_0 = -d$ while +$Z_b - Z_0 = +d$, factoring out $e^{-|k|Z_0}$ turns the difference into a +hyperbolic sine: + +$$ \Phi_{\Delta T}^{1/2} = C\, e^{-|k| Z_0} \left( e^{+|k|d} - e^{-|k|d} \right) + = 2 C\, e^{-|k| Z_0} \sinh(|k| d) $$ + +For $|k| d \ll 1$ the sinh is approximately $|k| d$, so dividing through by $|k|$ +leaves a line of slope $-Z_0$: + +$$ \ln \left( \Phi_{\Delta T}^{1/2} / |k| \right) = \ln D - |k| Z_0 $$ + +Note the factor $e^{-|k|Z_0}$ has to be taken out of the exponential prefactor for +this to work: it is not the bracket of the first equation that becomes a sinh, but +the whole right-hand side once it is re-centred. + +The base of the magnetic source, taken to be the Curie point depth, then follows +from the two: + +$$ Z_b = 2 Z_0 - Z_t $$ + +## What PyCurious adds + +The original method requires the user to pick both bands by eye and read the +gradients off a plot, yielding a single number with no error bar. In +{py:class}`~pycurious.CurieOptimiseTanaka` both bands are fitted with +{py:func}`scipy.optimize.curve_fit`, weighted by the measured scatter of the +spectrum, so the fit covariance propagates into an uncertainty on the Curie +depth. + +## Choosing the bands + +There are no default bands, deliberately. Each fit is only valid over the range of +wavenumbers where its approximation holds, and that depends on the data: + +- the $Z_t$ band needs wavelengths shorter than twice the source thickness; +- the $Z_0$ band needs $|k| d \ll 1$. + +Violating the second is easy to do and biases $Z_0$ low, which is not obvious from +the plot because the fit still looks straight. Use +{py:meth}`~pycurious.CurieOptimiseTanaka.check_bands` to test a choice before +relying on it — it reports point counts, wavelengths, $|k|d$, and the bias in km. + +Bands are given in **rad/km**, the same units +{py:meth}`~pycurious.CurieGrid.radial_spectrum` returns. (They used to be +cycles/km; a guard warns when a band looks like a leftover cycles/km value, +because such a call still runs and returns a plausible number.) + +## Fractal magnetisation + +Tanaka assumes the magnetisation is spatially random. Real crust is better +described as fractal, and a fractal source adds a term +$-\tfrac{1}{2}(\beta - 1)\ln|k|$ to the log amplitude spectrum, which biases $Z_t$ +high by $(\beta - 1) / 2\bar{k}$. Pass `beta` to remove it — see +{py:func}`pycurious.bouligand2009`, which fits $\beta$ directly. + +**The correction is verified for $Z_t$ only.** Removing the $\ln|k|$ term recovers +the top of the source almost exactly, but it does not make the two models agree: +`bouligand2009` also carries $\beta$ inside its $\cosh$/Bessel factor, and what is +left over falls on the centroid. Fitting an exact, noiseless `bouligand2009` +spectrum with $Z_t = 1$, $\Delta z = 20$ and the correction applied: + +| $\beta$ | $Z_t$ | $Z_b$ | error in $Z_b$ | +|---|---|---|---| +| 1 | 0.999 | 37.6 | +16.6 | +| 2 | 0.999 | 25.2 | +4.2 | +| 3 | 0.998 | 21.6 | +0.6 | +| 4 | 0.997 | 20.5 | −0.5 | + +$Z_t$ comes back to three decimal places throughout; the Curie depth does not. +Near $\beta = 3$ the residual happens to cancel the opposing $|k|d$ bias, which is +a coincidence of that one value and not a validation. For a fractal source, fit +$\beta$ directly with {py:class}`~pycurious.CurieOptimiseBouligand` rather than +correcting for it here. + +## References + +Tanaka, A., Okubo, Y., & Matsubayashi, O. (1999). Curie point depth based on +spectrum analysis of the magnetic anomaly data in East and Southeast Asia. +*Tectonophysics*, 306(3–4), 461–470. +[doi:10.1016/S0040-1951(99)00072-4](https://doi.org/10.1016/S0040-1951(99)00072-4) diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md new file mode 100644 index 0000000..c600508 --- /dev/null +++ b/docs/tutorials/index.md @@ -0,0 +1,41 @@ +# Tutorials + +Two matching sets of Jupyter notebooks work through PyCurious end to end — one for +the **Bouligand** optimiser and one for the **Tanaka** centroid method. Each set +starts from a synthetic anomaly with a known answer, recovers it with +uncertainties, and finishes by mapping Curie depth over real data. + +The notebooks also ship with the package: install the `examples` extra and grab +your own copies from the [repository](https://github.com/brmather/pycurious/tree/master/Examples/Notebooks). + +```{note} +The **Ex5** notebooks map Curie depth over the EMAG2 v3 magnetic anomaly and +compare against the Li *et al.* (2017) global Curie-depth model. Each downloads +~600 MB of external data, so they are **rendered here as code without executing**. +Run them locally after installing the `examples`, `mapping`, and `download` +extras; {py:mod}`pycurious.download` fetches and caches the datasets on first run. +``` + +## Bouligand — fitting the fractal spectrum + +```{toctree} +:maxdepth: 1 + +bouligand/Ex1-Plot-power-spectrum +bouligand/Ex2-Compute-Curie-depth +bouligand/Ex3-Posing-the-inverse-problem +bouligand/Ex4-Spatial-variation-of-Curie-depth +bouligand/Ex5-Mapping-Curie-depth-EMAG2 +``` + +## Tanaka — the centroid method + +```{toctree} +:maxdepth: 1 + +tanaka/Ex1-Plot-amplitude-spectrum +tanaka/Ex2-Compute-Curie-depth +tanaka/Ex3-Parameter-exploration +tanaka/Ex4-Spatial-variation-of-Curie-depth +tanaka/Ex5-Mapping-Curie-depth-EMAG2 +``` diff --git a/environment.yml b/environment.yml index 13a75b9..3a231f2 100644 --- a/environment.yml +++ b/environment.yml @@ -1,16 +1,17 @@ name: pycurious channels: - - default - conda-forge + - defaults dependencies: - - python=3.7 + - python=3.13 - pip - numpy - scipy - - cython - pytest # Optional dependencies - jupyter - matplotlib - pyproj - cartopy + - netcdf4 + - requests diff --git a/notes/bouligand-findings.md b/notes/bouligand-findings.md new file mode 100644 index 0000000..97b776b --- /dev/null +++ b/notes/bouligand-findings.md @@ -0,0 +1,204 @@ +# Findings for the Bouligand workstream + +Collected while finishing `CurieOptimiseTanaka` on the v2 series. None of these +are Tanaka problems, so none were fixed there. They are recorded here rather +than left to be rediscovered. + +Ordered by impact. + +**Status.** All seven are now addressed, across two commits on +`tanaka-probabilistic`. The findings are kept as written, with the resolution +noted under each, because two of them turned out to be wrong on measurement and +that is worth preserving rather than quietly overwriting — see the correction +under item 2 in particular, where the prescribed fix measures worse than the +bug it replaces. + +One thing none of these anticipated, and the largest of the lot: the spectral +bins are correlated with each other, not merely within themselves. A taper +spreads each wavenumber over a main lobe several bins wide, and treating the +bins as independent understated every reported uncertainty by about 30%. +Neither `sensitivity` nor `metropolis_hastings` could have caught it, since +both make the same independence assumption; only an ensemble over independent +realisations of the field does. See +`CurieOptimiseBouligand.optimise` and `pycurious.grid._banded_correlation`. + +## 1. The legacy test fixture is inadequate — the optimiser is fine + +The optimiser recovers its parameters well when the data can support it. +Against synthetics generated by `pycurious.fractal_anomaly` (1024 km wide, 2 km +spacing, spectrum matching `bouligand2009` exactly): + +| truth (beta, zt, dz) | recovered beta | recovered zt | recovered dz | +|---|---|---|---| +| 3.0, 1.0, 20.0 | 2.97 – 3.08 | 0.94 – 1.03 | 18.1 – 19.9 | +| 2.0, 5.0, 15.0 | 1.99 – 2.12 | 4.91 – 5.02 | 13.4 – 14.6 | +| 3.5, 2.0, 25.0 | 3.49 – 3.54 | 1.96 – 2.02 | 21.7 – 27.6 | + +`beta` to ±0.1 and `zt` to ±0.07, in 0.1 s. `dz` is the loosest of the three, +which is expected — it is the least constrained parameter. + +The problem is `tests/test_mag_data.txt`. It is 305 km across with a 10 km +layer, so the window is only ~30× the source thickness and there are few +low-`k` bins to constrain the long wavelengths. Sweeping window size and moving +the centroid by one window width over that fixture, against a truth of +`beta=3.0`, `zt=0.305`, `dz=10.0`: + +| configuration | beta | zt | dz | C | +|---|---|---|---|---| +| centre, 150 km | 2.815 | 0.386 | 6.356 | −19.00 | +| centre, 200 km | 2.702 | 0.415 | 8.010 | −18.40 | +| centre, 250 km | 2.681 | 0.425 | 8.125 | −17.94 | +| centre, 300 km | 2.668 | 0.429 | 8.513 | −17.57 | +| 200 km, x−40 km | 2.893 | 0.338 | 11.902 | −18.59 | +| 200 km, x+40 km | 2.785 | 0.401 | 7.110 | −18.39 | + +`dz` ranges over 6.4–11.9 km — a ±30% spread on the parameter the method exists +to estimate — for centroids that overlap heavily and sample nearly the same +data. `beta` never reaches its true 3.0. + +`test_optimise.py::test_optimisation` had tolerances of `[0.3, 0.1, 2.0]` and +was passing with `zt` at 0.091 of its 0.1 budget. The v2-core wavenumber fix +(item 6) moved it 0.33% and two parameters went out of tolerance, so the +tolerances were widened to `[0.4, 0.15, 4.0]`. That test now only demonstrates +the optimiser lands in the right region. + +**The fix is not to condition the fit better — it is to stop testing accuracy +against this fixture.** `tests/test_recovery.py` does that properly, with tight +tolerances on generated data. Consider retiring `test_optimisation`, or keeping +it only as a smoke test against the legacy fixture. + +That said, item 2 is still worth fixing on its own merits. + +> **Resolved.** `test_optimisation` is now `test_optimisation_smoke` and asserts +> only that the fit is finite and physically sensible. `test_recovery.py` drops +> its per-seed `dz` tolerance too, for the same reason one step further on: `dz` +> has a long upper tail, so any per-seed bound tight enough to be interesting +> fails on roughly one seed in five. It asserts the profile interval covers the +> truth, and that the mean over eight seeds is within 10%. + +## 2. `min_func` fits the spectrum unweighted + +> **RESOLVED, but the fix below is wrong.** Read the correction before acting +> on it. The weighting landed in "Give CurieOptimiseBouligand real +> uncertainties". + +`optimise_bouligand.py:228` takes `sigma_Phi` and never uses it: + +```python +def min_func(self, x, kh, Phi, sigma_Phi): + ... + misfit = self.objective_function(Phi_syn, Phi, 1.0) +``` + +The `1.0` should be `sigma_Phi`. Every wavenumber bin therefore carries equal +weight, even though the scatter varies systematically across the spectrum and +the low-`k` bins average only ~12 FFT cells against ~600 at high `k`. The fit +is dominated by the many high-`k` bins. + +`radial_spectrum(..., return_counts=True)` now exists (added in v2-core) if the +standard error of the binned mean is wanted rather than the raw scatter. + +### Correction + +**`sigma_Phi` is the wrong weight, and using it is worse than the bug.** +Measured over 30 fits, three cases by ten seeds: + +| weighting | beta err | zt err | dz err | chi2_red | +|---|---|---|---|---| +| `1.0` (the bug) | 0.073 | 0.055 | 20.4% | — | +| `sigma_Phi` (as prescribed above) | 0.119 | 0.086 | 22.3% | 0.01 | +| standard error of the binned mean | 0.050 | 0.024 | 17.8% | 1.14 | + +The premise that "the scatter varies systematically across the spectrum" is +what does not hold. `sigma_Phi` is nearly flat, at the theoretical +`power * sqrt(pi^2/6) / 2 = 1.28` for a complex-Gaussian FFT, so it carries +almost no information — only estimator noise from the 8-24 cell low-`k` bins, +where it is measurably unreliable. It also puts `test_optimisation` out of +tolerance. + +What varies across the spectrum is the number of cells averaged, so the weight +is the uncertainty of the binned mean: +`sigma_Phi / sqrt(counts / dof)`. That is now +`CurieGrid.window_spectrum`, shared with the Tanaka path. + +## 3. `sensitivity` perturbs by the wrong sigma + +`optimise_bouligand.py:604` (now ~`:610`) draws + +```python +rPhi = np.random.normal(Phi, sigma_Phi) +``` + +`Phi` is the *mean* of `ln|FFT|` over each annulus, so its uncertainty is the +standard error, not the per-cell scatter `sigma_Phi`. Perturbing by the raw +`sigma_Phi` overstates the spread substantially — measured at roughly 9× on the +equivalent Tanaka path (±1.80 vs ±0.20 km). + +Note the correction is not simply `sigma_Phi/sqrt(counts)`. The FFT cells are +not independent: a real field has Hermitian symmetry, so about half are +redundant, and tapering correlates neighbours. Measured effective counts are +`N/2.0` untapered and `N/3.3` with `np.hanning`. `CurieGrid._dof_factor` +implements this, and both optimisers now share it. + +> **Resolved**, and the deflation turned out to depend on the bin as well as the +> taper. Those asymptotes are right, but a fixed number of cells is lost to +> correlation however few the annulus holds, which only matters for the +> innermost bins — and matters a lot there. The reported sigma of the lowest bin +> was 0.65x the true scatter under `np.hanning`; it is now 1.05x. + +## 4. `calculate_CPD` is a stub + +`optimise_bouligand.py:618` is a bare `return zt + dz` with no docstring, and +its signature is incompatible with the Tanaka sibling: + +| | signature | returns | +|---|---|---| +| Bouligand | `calculate_CPD(zt, dz)` | `CPD` | +| Tanaka | `calculate_CPD(zt, z0, *, sigma_zt=0, sigma_z0=0)` | `(CPD, CPD_stdev)` | + +Either give the Bouligand version an uncertainty and matching keyword-only +sigmas, or rename one of them. As it stands, code written against one breaks +silently against the other. + +> **Resolved.** `calculate_CPD(zt, dz, sigma_zt=0.0, sigma_dz=0.0)` returning +> `(CPD, CPD_stdev)`, matching the Tanaka sibling. Note the table above misreads +> that sibling: its sigmas are ordinary defaulted parameters, not keyword-only. +> Use `profile(target="CPD")` where the symmetry matters. + +## 5. `Bouligand/Ex3` will not execute + +Cell 8 does `from scipy.signal import tukey`. That moved to +`scipy.signal.windows.tukey` and the old location is gone, so the notebook +cannot be run headless. + +## 6. Reference values shifted in v2-core + +`_taper_spectrum` used `dk = 2*pi/((N-1)*dx)` where the DFT fundamental is +`2*pi/(N*dx)`, so every depth in the package was low by `(N-1)/N` — 0.5% at +N=201, worse for smaller windows. This is fixed on v2-core and guarded by +`test_grid.py::test_wavenumber_grid_matches_dft` and +`test_radial_spectrum_recovers_injected_depth`. + +Any published Bouligand numbers need regenerating. The shift is small in +isolation but, per item 1, the fit amplifies it: a 0.33% wavenumber change +moved `zt` by 8%. + +> Still true, and now compounded: the weighting, the per-bin deflation and the +> correlation correction all move the numbers again. Everything published from +> this package before v2 needs regenerating. + +## 7. Parallel behaviour changed in v2-core + +`parallelise_routine` no longer deadlocks when a worker dies, takes +`on_error="raise"|"ignore"`, and accepts `seed=` for reproducible stochastic +routines. Relevant to `sensitivity` and `metropolis_hastings`: under the fork +start method every worker previously inherited the same RNG state and drew an +identical sequence, which would put spatially coherent artefacts into a +sensitivity map. Both methods should be given a `seed` keyword so +`parallelise_routine` can pass per-centroid child seeds through. + +> **Resolved**, on `optimise` as well. `optimise` is deterministic and ignores +> the seed, but it has to accept one: without it the keyword falls through to +> the taper and raises from inside `np.hanning`. Worse, with `taper=None` it was +> swallowed silently, so seeding appeared to work and did nothing. +> `_taper_spectrum` now rejects keywords it cannot forward. diff --git a/pycurious/Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb b/pycurious/Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb deleted file mode 100644 index f7a2f77..0000000 --- a/pycurious/Examples/Notebooks/Bouligand/Ex1-Plot-power-spectrum.ipynb +++ /dev/null @@ -1,410 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example 1 - Plot power spectra\n", - "\n", - "`PyCurious` offers convenience functions to extract the radial power spectrum. This has been derived analytically by Bouligand *et al.* 2009.\n", - "\n", - "In this notebook we plot the radial power spectrum using the analytical expression, and the spectrum computed from a synthetic magnetic anomaly. (This can be generated from `Bouligand_forward.py` in the `tests` directory.)\n", - "\n", - "### Contents\n", - "\n", - "- [Analytical solution](#Analytical-solution)\n", - "- [Radial power spectrum](#Power-spectrum-from-FFT)\n", - "- [Azimuthal power spectrum](#Azimuthal-power-spectrum)" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib inline\n", - "\n", - "import pycurious" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analytical solution\n", - "\n", - "The analytic solution to the power spectrum is given in Equation 4 of [Bouligand *et al.*, 2009](http://doi.wiley.com/10.1029/2009JB006494),\n", - "\n", - "$$\\begin{align}\n", - "\\Phi_{B1D}(|k|)=&C-2|k|z_t-|k|\\Delta z-\\beta \\ln (|k|)\\\\\n", - "&+\\ln\\left[\\int_0^\\infty\\left(\\cosh(|k|\\Delta z)-\\cos(k_z\\Delta z)\\right)\\left(1+\\left(\\frac{k_z}{|k|}\\right)^2\\right)^{-1-\\frac{\\beta}{2}}\\text{d}k_z\\right]\n", - "\\end{align}$$\n", - "\n", - "where the shape of the curve depends on 4 variables:\n", - "\n", - "- $\\beta$ - a fractal parameter\n", - "- $z_t$ - top of magnetic sources\n", - "- $\\Delta z$ - thickness of the magnetic layer\n", - "- $C$ - a field constant\n", - "\n", - "We vary each of the parameters below to observe the different shapes of the power spectra." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# wavenumber range\n", - "k = np.linspace(1e-3, 3, 10000)\n", - "\n", - "# Bouligand 2009 test parameters\n", - "beta = 3.0\n", - "zt = 1.0\n", - "dz = 20.0\n", - "C = 0.0" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# plot Bouligand et al. 2009 curves\n", - "\n", - "fig, (ax1, ax2, ax3) = plt.subplots(1,3, figsize=(14,8),)\n", - "\n", - "# vary zt\n", - "for zti in np.arange(0.0, 2.5, 0.5):\n", - " Phi = pycurious.bouligand2009(k, beta, zti, dz, C)\n", - " Phi -= Phi.max()\n", - " ax1.semilogx(k, Phi, label=r'$z_t$ = {} km'.format(zti))\n", - "\n", - "# vary dz\n", - "for dzi in [10., 20., 50., 100., 200.]:\n", - " Phi = pycurious.bouligand2009(k, beta, zt, dzi, C)\n", - " Phi -= Phi.min()\n", - " ax2.semilogx(k, Phi, label=r'$\\Delta z$ = {} km'.format(dzi))\n", - "\n", - "# vary beta\n", - "for betai in np.arange(0, 5, 1):\n", - " Phi = pycurious.bouligand2009(k, betai, zt, dz, C)\n", - " Phi -= Phi[-1]\n", - " ax3.semilogx(k, Phi, label=r'$\\beta$ = {}'.format(betai))\n", - "\n", - "ax1.set_ylim(-20, 0)\n", - "ax2.set_ylim(0, 20)\n", - "\n", - "ax1.legend()\n", - "ax2.legend()\n", - "ax3.legend()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Radial power spectrum\n", - "\n", - "The radial power spectrum is computed from a square window of the magnetic anomaly. Methods to select window sizes and compute the Fast Fourier Transform (FFT) belong to the `CurieGrid` object.\n", - "\n", - "`CurieGrid` achieves the following purposes:\n", - "\n", - "- Upward continuation\n", - "- Reduction to the pole\n", - "- Compute the radial power spectrum using FFT\n", - "\n", - "The shape of the radial power spectrum is heavily dependent on window size. Resolution of long wavelength features require large windows.\n", - "\n", - "> Suggestion: use a window size **> 4 times** the maximum Curie depth.\n", - "\n", - "Let's compare the computed radial power spectrum with random fractal noise from the test data that was generated from `Bouligand_forward.py` with the following parameters:\n", - "\n", - "- $\\beta$ = 3.0\n", - "- $z_t$ = 0.305 km\n", - "- $\\Delta z$ = 10 km" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# load magnetic anomaly - i.e. random fractal noise\n", - "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", - "\n", - "nx, ny = 305, 305\n", - "\n", - "x = mag_data[:,0]\n", - "y = mag_data[:,1]\n", - "d = mag_data[:,2].reshape(ny,nx)\n", - "\n", - "xmin, xmax = x.min(), x.max()\n", - "ymin, ymax = y.min(), y.max()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "## Plot random fractal noise\n", - "\n", - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111)\n", - "im1 = ax1.imshow(d)\n", - "fig.colorbar(im1, label='nT')" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "# initialise CurieGrid object\n", - "grid = pycurious.CurieGrid(d, xmin, xmax, ymin, ymax)\n", - "\n", - "# pick the centroid\n", - "xpt = 0.5*(xmin + xmax)\n", - "ypt = 0.5*(ymin + ymax)\n", - "\n", - "window_size = 304e3\n", - "subgrid = grid.subgrid(window_size, xpt, ypt)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYoAAAEKCAYAAAAMzhLIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJzt3Xd80/X2+PHXSZoOlkWWUERwALKkgCiiF3GBAwVEr+N63f5w3utA5Xqv+165znuver9Xvc6LV0HBsodYhetmL9kIQkFFoFCgtGlyfn98EmhLmoY2adLmPB+PPEw+K+9PIzl5r/MWVcUYY4ypiCveBTDGGJPYLFAYY4wJywKFMcaYsCxQGGOMCcsChTHGmLAsUBhjjAkrLoFCRC4TkeUi4heRXiH2txGRPSJyXzzKZ4wx5qB41SiWAUOBORXsfwGYVnPFMcYYU5GUeLypqq4AEJFD9onIYGA9sLeGi2WMMSaEuASKiohIfeAB4Fwg4manpk2batu2bWNVLGOMqZPmz5//i6o2q+y4mAUKEZkFHBVi10OqOqGC0x4DXlDVPaFqG+WufwtwC0CbNm2YN29edYprjDFJR0Q2RnJczAKFqp5ThdNOAYaJyNNAJuAXkf2q+lKI678KvArQq1cvS1hljDExklBNT6p6RvC5iDwK7AkVJIwxxtSceA2PHSIim4E+wBQRmRGPchhjjKlcvEY9fQR8VMkxj9ZMaYwxxoRjM7ONMcaEZYHCGGNMWBYojDHGhJXcgWJXHsx4CPb+Eu+SGGNMwkruQFG0G756CRa9G++SGGNMwkruQNH8RGjTB+a/BX5/vEtjjDEJKbkDBUCvG2DHevh+drxLYowxCSmhZmbHxYkXQ8YDMO8Ncna355kZq9iSX0irzAxGDOjA4OyseJfQGGPiymoUnnTofhX+lVN4fvwc8vILUSAvv5CR45eSszAv3iU0xpi4skAB0PN6XOpjkD+3zOZCr497xy62YGGMSWoWKACaHs/nvs5cmZKLi7Kd2j5Vq1kYY5KaBYqAaekX0Fp+oZ9r8SH7Cr0+npmxKg6lMsaY+LNAEdB74G/YpkdwtXtWyP1b8gtruETGGJMYLFAEXNKzLVNTzqG/axGtOHSmdqvMjDiUyhhj4s8CRYDfr7xT3A8R+E3qoXMq9hWXWD+FMSYpWaAIyMsvZJ23KT8268v1GXNoml52ze6d+7zWqW2MSUoWKALWbtsDwJ4uvyWjaBvneRYecox1ahtjkpEFioB1PzuBommPQdAoi4H7p4U8zjq1jTHJxgJFwNqf93Bk/VSObFgPelzLr9xLaSM/HXKcdWobY5KNBQogZ2Ee4xfksWNvMX1H5TI99Vz84uY6zydljsvwuBkxoEOcSmmMMfGR9IEiZ2EeI8cvodjnzMjOyy/k7mk/s6XluVyTOpuWGSUANG+YxlNDu1qSQGNM0kn6QPHMjFUUesum7Sj0+njyl1/hKSkg5/TNAIy8oKMFCWNMUkr6QFFR5/SM3cdAy+40/+4t6nmEJZt31XDJjDEmMSR9oKioc7pVZj049VZk+2qubLLOAoUxJmklfaAYMaADHnfZyXUHOq07D4H6zRm0fyLzN+6k3YNT6Dsq1ybdGWOSStIHisHZWQf6HgTIysw42GmdksbK1sPovv9b2slWW9DIGJOUkj5QABzfrAEAyx4bwBcPnlWm03rEhl4Uq5vfumce2GYztI0xycQCBbB7vxe3S6iX6j5k37Jd6Uz29+Ey92wasu/AdpuhbYxJFhYogIL9JTRMT0FEDtnXKjODN0sG0kD2c7n70zLbjTEmGVigAHYXemmYnhJy34gBHVibcgJf+0/kppRpeCixGdrGmKRigQKnRtEo3RNy3+DsLJ4a2pX/pgylpezgqoyvbYa2MSapWKDA6aOoqEYBTrB4ZuQ9rNQ23JE2lcEntazB0hljTHxZoCB8jSIozZPCuPRLabZ/Azf/8UmbT2GMSRoWKAh2ZocPFDkL83inoCebtSm3pEyy+RTGmKRhgQKnM7tRRsVNT+AkDyzyu3it5EJOdq2mp6yi0Ovj3rGLLVgYY+q0pA8Ufr+yp7jyGkVw3sRYXz92aAOGp0wCwKdqNQtjTJ0Wl0AhIpeJyHIR8YtIr3L7uonIV4H9S0UkPZZlKSgqQRUahenMhoPzJgpJ5+2SAZzrXsAJ4qQgD87UzlmYR99RuZYTyhhTp8SrRrEMGArMKb1RRFKA0cBwVe0MnAl4Y1mQgv3O5SvrzB4xoAMZHmfm9tu+89iradyZ8tGB/cE+i7z8QssJZYypU+ISKFR1haqGSpZ0HrBEVRcHjtuuqr5YlmV3obOCXbjhsXBwPoVbhHwa8pZvABe5vj5Qq3CLUOgtW1TLCWWMqQsSrY+iPaAiMkNEFojI/RUdKCK3iMg8EZm3bdu2Kr/hgRpFRvgaBTjB4rnLTyLD4+a1kgvZSzq/SxlHhseNTzXkOZYTyhhT28UsUIjILBFZFuJxSZjTUoDTgasD/x0iImeHOlBVX1XVXqraq1mzZlUuZ8H+yGoUQcGahS+9MW/6BnCR+xtePjuVrAoXQLKcUMaY2i2yb8cqUNVzqnDaZmC2qv4CICJTgR7AJ9EsW2m7I+yjKG1wdhYN0lK45518bsv4hLN+eoPdA/7KvR8sxuc/WLOwnFDGmLog0ZqeZgDdRKReoGO7H/BdLN/wcGsUQV1bH8FuGrDk6KthxSQGH/ULxzatd2B/swZplhPKGFMnxGt47BAR2Qz0AaaIyAwAVd0JPA/MBRYBC1R1SizLsrvQqVFUNo+ivBaN0mneMI0PUgZB+hHoZ0/xy55i+ndwmsH+X79jLUgYY+qEeI16+khVW6tqmqq2UNUBpfaNVtXOqtpFVSvszI6WgqIS0j0uUlMO/0/RNesI5v7ogz53Iqum0bpwFWd2aE5WZgbzN+6MQWmNMabmJVrTU40r2O897NpEkMctrP15D12ntSWfBtyf8j4dWzSgRaM0Ziz/0SbeGWPqhKQPFLsLSyqdlR1KzsI8clc6w3ILqMffvUM4w72M1996lcWbduFXbOKdMaZOsEBRxRrFMzNWUezzH3g92ncuG/wtuE9Gg5aUOdYm3hljajMLFPtLIppsV175iXReUhhVciXtXXlc5p5d6fHGGFNbVBooRKSdiDwvIuNFZGLwUROFqwkFlaxuV5FQE+mm+09mrr8996Z8QD32V3q8McbUBpHUKHKADcCLwHOlHnWC00dx+DWK0kkCDxL+7P0NzWQX/y9lcpk9+4pLrJ/CGFMrRfJTer+q/iPmJYmTgv3eKnVmB+dIPDpxOfmFBxPcLtLjmezrwy3uyfy35Cx+4kgAdu7zMnL80jLnGmNMbRBJjeLvIvKIiPQRkR7BR8xLVgOKSnwUlfir1PQEzhf+okfO42+/7k5WZgYCZGVmkDrgMdzi50HPe2WOtxXxjDG1USTfkF2Ba4CzgOAwHw28rtXGztsEwLMzV/Pet5sYMaBDlX7tD87OOuS8l6ZfxB0pOYzx9edrf6cD24Mr4gXPM8aYRBdJjWIIcKyq9lPV/oFHrQ8SOQvzeHLyigOvoz3fYXz9K9jkb8YTKW/iwYbLGmNqr0gCxWIgM9YFqWnPzFhFUYm/zLZofoHfNbAbf9HrOcGVx43uqYfst+GyxpjaIpKmpxbAShGZCxQFN6rqxTErVQ2o6Is6Wl/gTrPSdcwcn8tdKR8xydeHPA6um2HDZY0xtUUkNYpHcJqf/kIdGh5b0Rd1NL/AB2dnoQNHAfCI550y+2y4rDGmtqg0UKjq7FCPmihcLIWaBxGLhYYG9D2Z9Z3v4Dz3fM52zT+wPThc1oKFMSbRRTIzu0BEdgce+0XEJyK7a6JwsRRc0rT0sNZYLTTUZeiDfC9H84TnTRqy78B269Q2xtQGlfZRqGrD0q9FZDDQO2YlqkGhhrXGREoqd++/mXGpj/DHlNE8UHLLgV3WqW2MSXSHnRRQVXOoA3Moatq2I7ryim8Qv075jDNdiw5sd4lY85MxJqFF0vQ0tNRjmIiMwplwZw7DiAEd+Jdczip/a0Z5XqMRe4CDE/AsWBhjElUkNYpBpR4DgALgklgWqi4anJ3F40N78EDJcJqyi4c9ow/ss74KY0wiC9tHISJuYImqvlBD5anTBmdncfeYY/mn72LuSslhqq83uX4nbZb1VRhjElXYGoWq+oBaPbEu0bTKzODFkqGs8Lfhac+rNGMnYH0VxpjEFUnT05ci8pKInFHXssfGw4gBHUjxpHGn9w7qs58XPP9E8Jfpq8hZmEffUbm0e3AKfUflWgAxxsRVJCk8Tgv89/FS2+pE9th4CA7HvXesn0dKruVpz2vc6p/EP32XUOj1cfeYRaS4Ba/PGS8QTFZY+lxjjKlJkdQobiyVNba/qvYHbop1weqywdlZ+FUZ6zuTSb5TuSflA3rIasCJwMEgEWTrWBhj4imSQPFhiG0fRLsgycbJKSX8wXsTW7QJ/0h96cCQ2VBsGK0xJl4qDBQi0lFELgWOKDeX4jogvcZKWEcFc00VUI87vXfSgp0863kFwV/hOTaM1hgTD+FqFB2Ai3DWoig9l6IHcHPsi1a3BXNNuUVYrMfzVMlVnOeez+3uCWHPs2G0xpiaVmFntqpOACaISB9V/aoGy5Q0gp3TI8cv5Q3vQLq61nNPyod8p23J9WeHPMfWsTDG1LRI+iiGi8iBFe5EpLGIvBHDMiWVg1ls6zHSexMrOYa/p77M2c0KDkmDDraOhTGm5kUSKLqpan7wharuBEL/3DVVMjg7iy8ePIt+ndpwS/HdeNXFyIInuLJ7YzIzPGWOtXUsjDE1LZJA4RKRxsEXInIkkc2/MIchZ2Een63exmZtxh3eu2ineZy25CHc+A451jq1jTE1KZJA8RzO7OwnRORx4Evg6dgWK/k8M2MVRSXOiKcv/V14ouQazpG5DPe+E/J469Q2xtSUSBYuekdE5uHMxBZgqKp+F/OSJZnyX/xv+QZyjPzEzSlT+UGb8x/feWX2W6e2MaamRLpw0ZHAXlV9EdgmIu1iWKakFOqL/4mSa/iMnjya8jZnuRYc2B6Ltb2NMaYikSxc9AjwADAysMkDjK74DFMVwQl4paV5PBRc+Aq7MzvxcuqLdJH1wME+CuvQNsbUhEhqFENwUo3vBVDVLUDDsGdUQkQuE5HlIuIXkV6ltntE5G0RWSoiK0RkZLjr1CUHh8lmIEBWZgZPDe3KoJNPoPFNH6EZR/Jm6jMcIz8CB5MFWrAwxsSaqIZf1VREvlXV3iKyQFV7iEh94CtV7VblNxU5EfADrwD3qeq8wPargItV9QoRqQd8B5ypqhvCXa9Xr146b968qhanVrjqL2/zYtFDFJLGpUWP8hNHltmflZnBiAEdLMOsMSZiIjJfVXtVdlwkNYqxIvIKkCkiNwOzgNeqUzhVXaGqocZ3KlBfRFKADKAY2F2d96orvtrdlGuLH+AI9jI69Skal/uz5OUX8vsxi8h+fKbVMowxUVVpoFDVZ3EyyI4D2gMPBzq1Y+FDnCaurcAPwLOquiNG71WrtMrMYJkey03F93G0/MxbqU9Tn0OHyNqEPGNMtEU66mkp8D9gTuB5pURklogsC/G4JMxpvQEf0ApoB9wrIsdWcP1bRGSeiMzbtm1bhLdRewU7u7/RE7nN+zs6yUZeT32WDPYfcmxw/QpbIc8YEw2RjHq6CfgWGAoMA74WkRsqO09Vz1HVLiEe4dKjXgVMV1Wvqv4MfAGEbD9T1VdVtZeq9mrWrFllxan1SmebzfX34B7vrZwsK3nDEzpY+FRRrNPbGFN9kaTiGAFkq+p2ABFpgjM7OxaJAX8AzhKR0UA94FTgbzF4n1qpdLbZSd7TEK/yguefvOF5lhu891FYwTIhpVN+PDNjFVvyC2llnd/GmAhF0vS0GSgo9boA2FSdNxWRISKyGegDTBGRGYFdLwMNgGXAXOBNVV1Snfeqa0oPo53o78s93tvo7VpRYc0iKNjZnZdfaDUNY8xhiWR47DtAV2ACzqikS3CaolYDqOrzMS5jpZJheGw48yb+i+wFI5nr78CNxfexl8jTe2RlZvDFg2fFsHTGmEQVzeGx64AcnCABTsDYijPprloT70x09Lp4OO6hr9LbvZrRqU9xpOyN+FxLLmiMqUwkSQEfCz4XERfQQFVtbkOi6XYZs9fv5rSF9/Nfz+NcU/wg22hc6WmWXNAYU5lIRj39V0QaBWZkfwesEpERsS+aORw5C/O4bX4W13vv52j5mQ9SH6e1hB82bMkFjTGRiKTpqVOgBjEYmAq0Aa6JaanMYXtmxioKvT6+9HfhN8V/oLEU8GHqo3SUH0Ie7xJ4amhXG/VkjKlUJIHCIyIenEAxQVW9HOyvMAmidF/DQj2By4sfRhHGpj5GH9fyA/sa1/MwpHsr/AqnHHtkqEsZY0wZkQSKV4ANQH1gjogcg+VfSjjl+xpWaRuGFj3Gj9qEtz2juK7hXP726+4sfPg8bjzDmez+7feWHcUYU7lIcj39Q1WzVPUCdcbS/gD0j33RzOEItZ5Fvqc5ay76kNR2p/Go9wUG7x0Lqqz+sQABfvf+IkvxYYypVKXzKGqDZJ9HEZSzMC/0zOuSIsi5FZaNY+PRgxm0YRi7vWV/IzSu5+GRQZ2tz8KYJBLpPAoLFMnC74fZf4XZo5jnb8/w4rv5hSMOOcwChjHJIyqBIjBv4lRV/TKahYs2CxSRu/0Pf+JZz7/YQUNuLr6X77RtyOPqeVykedzk7/NaXihj6qiozMxWVT/wXNRKZeJuUaP+DCt+BEH5MPUxBri+DXncPq+fnfu8lhfKGBPRqKeZInKpiEjMS2NibsSADqxPOZ5Lip5glR7NK6l/4073eAR/2PNKZ6A1xiSXSALFPcAHQLGI7BaRAhGx4bG1VDD7rDejOVcU/5FxvtO51/Mhr3qepxF7wp5reaGMSU6R5HqyxH91zODsLAZnZ5GzMI9HJ9RjqfdYHkp5l8mpD3Gr9/cs13Yhz7O8UMYkp0hyPYmI/EZE/hR4fbSI9I590UysDc7OYtGjA+g+7EFulMdIER/jUx/lSvcnlJ98Xz4vVM7CPPqOyrXlVo1JApGsR/F/gB84S1VPFJHGwExVPbkmChgJG/UUHVO/XkrTmbfT27+YSfTjTyXXk1+SChwcNgvw6MTl5Bd6y5yb4XFb7ihjapmozaMQkQWq2kNEFqpqdmDbYlU9KUplrTYLFFHk98Hsp9HZf2WNtubW4rtYp5F9+btFeO7ykyxYGFNLRHPhIq+IuAm0RYhIM6hkiIypvVxu6D+Sezx/ogn5TE59iKtCNEWF4lO1YbTG1EGRBIp/AB8BzUXkz8DnwF9iWioTdzkFHRlYNIq5/g78xfM6r3qep3EEuSBtGK0xdU8kSQHfBe4HnsJZAnWwqn4Q64KZ+GqVmcE2GnOt9wGe8F7Nma5FTEsbyWmuZZWem5dfaLUKY+qQSEY9PQ4cDbylqi+p6orYF8vEWzAbreLidd+FDCl+gr2azmjPUzyY8h4eSsKeb01QxtQdkTQ9bQCuBOaJyLci8pyIXBLbYpl4C07MywrMnfhO23JR8Z9533cmw1MmMSH1T2R7NuFxh56wb01QxtQdEWePFZGjgMuB+4DGiTQRz0Y9xV7pFOaXN1zCY67XSPfu5rv2tzJo0cn4cIc8T8CSChqToKI5PPbfQCfgJ+B/OJ3ZC1Q1fNtDDbJAEQd7t8PU+2D5eFbI8dy5/xbWauuwpzSu5+HCbi35dOW2Q9fMMMbUuGgGio+AVsB3wGxgjqquj0opo8QCRRwt/4iiCb+Hor08VzKMf/suxB9Ri6bD4xIapKdYOnNj4iBq8yhUdYiqngI8DWQCn4rI5iiU0dQFnYeQdtdctrfqxx887zE+9RE6yg8Rn+71q6UzNybBRTLq6SIR+SvwBjAcyAUejnXBTC3SoDmtbvmQhz330Fq2MSn1Ie5LGUMaxYd9KesENybxVJo9FjgfmAP8XVW3xLg8prYS4T8FvZhIBx5KeZc7UiZwgesbRnpv5hs98bAuZenMjUkskTQ93Q58BvQI1C6ax7xUplZqlZlBPg0ZUTKcq4tH4sbPmLQneCrltUrXuih/HWNM4oik6eky4FvgMpzhsd+IyLBYF8zUPsFJegBf+LsyoPivvOa7iMvcs8lNu49h7tmVrqQHsK+4hD/mLLU05sYkiEhGPS0GzlXVnwOvmwGzLHusCaX0fIsDo5ha7mD72DtpsmMh8/ztecZ1Ewu8R+P1RTaHBw6mMQcOvb6NkjKmSqI5PHapqnYt9doFLC69Ld4sUNQCfj8sfg8+fhgKd7Cu7ZXctuV8Vu9y4RLBF8HEz8wMD0Ulfgq9vgPbbB0MY6oummnGp4vIDBG5TkSuA6YAU6tbQJNkXC7IvhrunAe9buS4De8xI+Vuvr9iN36NLGt9fqG3TJAAGyVlTE2IpDN7BPAK0A04CXhVVR+IdcFMHZXRGC58Fm7+FDKPgZzh5GQ8yYmyscqXtFFSxsRWpFNov8SZlZ0LfBW74pik0ao73PgxXPwiHT1bmZL6B55KeY1m5B/+pWyUlDExFcmop5twRj0NAYYBX4vIDbEumEkCLhf0+C1pdy9i/fG/ZVjKHD5Nu4fb3DmHNVlvX3GJjYoyJoYi6cxeBZymqtsDr5sAX6pqhyq/qcgzwCCgGFgHXK+q+YF9I4EbAR9wl6rOqOx61pldR2xfx9YP7qPlj7nkaVOeLrmSCb5TcXLQhldRp3bIUVjW8W0MEN3O7M1AQanXBcCmqhYs4GOgi6p2A1YDIwFEpBNwBdAZGAj8M7Bet0kGTY6j5fCP4NpJZLVsyd89L5KT9ijZsqbSUwu9Pu4du/hAzSJnYR7dH5vJ78csIi+/0HJJGVMNkdQo3gG6AhMABS7BaYpaDaCqz1erACJDgGGqenWgNoGqPhXYNwN4VFXD9otYjaIO8vtg0X/ZP+NR0ot+YaKvDy+U/JrvNXxiAI9L8LiFfd6KR1JlZWbwxYNnRbvExtQ60axRrANycIIEOAFjK9Aw8KiuG4BpgedZlK2tbA5sM8nG5YYe15B+z2L41QguTl/Mpxn3seGMXLoesb/C07x+DRskoOya3jkL82wGuDGViHiFu8O+sMgs4KgQux5S1QmBYx4CegFDVVVF5GXgK1UdHdj/OjBVVceFuP4twC0Abdq06blxY9WHV5paoOBHmP00LHibEvHwmvd8Xi6+gD3Uq9LlKqp52AQ+k0yiNjM7VkTkWpy05Wer6r7ANmt6MuFtXwe5T8Ly8ezQBrxcMpjRvnMoIjVqb2FNUyZZRLPpKepEZCDwAHBxMEgETASuEJE0EWkHnIDTH2KMo8lxcNmbcMtnlDTvxp88o/kkkHDQja/S0yNhE/iMKSsugQJ4Cad/42MRWSQi/wJQ1eXAWJxlV6cDt6tqdP71m7qlVTbNb5/G56e9zg5txLOeV5iZej8Xu77AVS5Dravy0bVlL20T+Iwpo8KmJxF5kYMd2IdQ1btiVajDZU1PyS1nwWZmffQmt8tYTnT9wBp/Fn8ruZSv0vry8MVO7sqR45cekieqIo3reXhkUGfrpzB1XqRNT+FWuLNvXlMrDO7RGuQGbp7eh24FcxiRNp6XXf+AZrMgbSR0vAiAe8cujihL7c59Xn4/ZhGPTVp+IGDYxD2TzOLWmR1NVqMwZfh9sPwj+GwUbF8DR3WFM0fS7i1FI5jlXVqGx82lPbMYNz/P0pubOiea61E0w+l47gSkB7erasIMC7FAYULy+2DphzB7FOxYzxppy9+LBjHVfwr+w+iec1ewXoZbhOcuP8mCham1ojnq6V1gBdAOeAzYAMytVumMqQkuN5z0a7h9Lgx5haPqu3gp9UU+Th3BMPdsUighw+OmcT1P2MtU1FzlU7WUICYpRFKjmK+qPUVkSSA3EyIyW1X71UgJI2A1ChMRv59vp71F5rx/0F6/ZyvN+KnbrfzQZggPTFhdYWe3CFTWQptl/RamFopmjcIb+O9WEblQRLKB1tUqnTHx4HLR+8IbaP/wQrhqLC1bt6P7kse5+LPz+aDbPFqml4Q8LZJuPEs4aOqySALFkyJyBHAvcB/wb+DumJbKmFgSgfYD4MaZcO0kaNaeLsuf4auM3zOt+1e0Sa84l1Q4tiyrqavCDY8FQFUnB57uAvrHtjjG1CARaPcr57HpW5jzLCeufJHppDMmpR+v+y5gszY7rEvarG5TF1UYKETkflV9uqKJd4k04c6Yaju6N1w9Fn5cxvSXH+A37llc4/6Yqf5TeKXkIpZru4gu4xIhZ2Ge9VWYOiXczOxBqjopkLzvEKr6dkxLdhisM9tEU99RufjzN3N9ynSudOfSUAr5wteZV30XMdvfjQxPSsi5FaW5BPwaupPbJu+ZRJHw2WOjyQKFiaachXkHUn40ZB9XuT/h+pTpHCU7WSvHsKv7cHpeeBM5S7dFPNu7cT0PF3ZryeTFW8kv9Ibcb2lDTE2rdqAQkUmEz/V0cdWLF10WKEy0lf/Vf/+57bjE9SV8+SJsWwGNsqD3zXSf3Ip8GkTlPW22t6lp0QgUwXkSQ3EWIBodeH0lsEFV/xCNgkaDBQpTY1Rhzcfw5T9gw//YTyrjSs7gTd8A1mr1R43bbG9Tk6KZwmOOqv6qsm3xZIHCxMWPy9gw9XlabpxImniZ4+vKG77zme3vhlYjg7/VLExNiWagWAFcqKrrA6/b4SxPemJUShoFFihMPE39einrp7/EZTqdFpLPOn9L3vQNZLzvDPYdTI9WJVmZGfTv2IxPV26zzm8TddEMFAOBV4H1gU1tgf+nqjOqW8hosUBhEsGE+d+zcPrbDC6aQHfXenZpPd739ec/vnPZrM2j+l7W+W2iIaqjnkQkDegYeLlSVYuqWb6oskBhEooqbJ7L5mnPcdSWmbhUyfV3Z7Tv3APNUsFRUO99symiUVMVCQ7DDWa4tZxT5nBEO1B04dA04+9Uq4RRZIHCJKxdeayc8iLNVr9HE/LJkxbsPPE3dLnoDqh3ZJmhuNFifRwmUtFsenoEOBMnUEwFzgc+V9VhUShnVFigMAmvpBhWToK5r8PGL8CdBl0uhZNvIufnFtx/rFRgAAAT3klEQVT7wZJq1SzKy8rM4IsHE2bJGJOgopk9dhhwNvCjql4PnASkVbN8xiSXlFQnMFw/FW79CnpcAysmwr/PYvDcq/nglLU09oTOXlsVlnPKRFMkgaJQVf1AiYg0An4Gjo1tsYypw1p0ggufg3tWwAXPQkkRPRb9iW/S7+Cp+u/TTrYe5oKthwrmnDImGiJpevon8AfgCpxU43uARYHaRUKwpidTq6nCxi9h7r+dWoa/BI45nXlNBjFieVs27PLRKjBMtqIUIBWxzm4TTlT6KEREgNaquinwui3QSFWXRKmcUWGBwtQZBT/CondhwTuwcwNkNIZuV0DPa6G5M3UpmF4kL7+wwvW8wwl2dgOWnDDJRbMze76q9oxayWLAAoWpc/x++H42LHgbVkwGvxeOPgV6XAudh0BqvTKHt3twSsWJ2UIQDk3kZqOlkk80O7O/FpGTo1AmY0ykXC44rj9c9hbcuxLOexL2bYcJt8FzHWDKvbD1YMW+VWbGYV0+VFCxFfpMRSKpUXwHtAc2AnsJ/BhR1W6xL15krEZhkkKwL2PB27A8B3xF0CobelzLZH8fRkzaUO35GAJ8P+rC6JTXJLxoNj0dE2q7qm6sYtmizgKFSTr7dsCSsU7Q+Pk7SEln01Fn8+TmHszc36HKSQkte21ysYWLjEkGqrBlASx8F5Z9CPt3sS/jKMYWn8Eb+04jj6Oq3NkdDBa2Il/dZYHCmGTj3Q+rpjhBY10uoHBMX+h+FZ3HZLC3CplsrdO7brNAYUwy25UHS953gsaOdewjnSklvfnQ149vtCNUc0qfAJn1POTv85apZVjto3axQGGMcZqmNn3Lhk9epemGKTSQQjb6mzPO9yumuM5kh6cFO/dFPoGvIhkeN5f2zGLc/LxDOtQtJXriskBhjClj0tw1LJj5H87ZP4u+7uUA/NLkZP6+LZsJxSezm/rVun64yX/WXJWYLFAYYyq2c6MzamrJ+7B9LUXqYZY/mxzf6Xzm746XlKi/pWW0TTwWKIwxlVOFLQtZ98nrZK6bSBPZzU5twGTfqXzkO50FegLV7c8oTcD6LhKIBQpjzGGZMH8Dc6Z/wK/253Keax4ZUsxGbU6O73Q+SenHCm9zvL7ofF9YvqnEYIHCGFN1RQVOjqklY5ycU+pnR2ZX3tpzKu/u6Ykvo8lhZbENJTPDQ1GJ3zq/4yihA4WIPAMMAoqBdcD1qpovIucCo4DUwL4Rqppb2fUsUBgTQ7u3OpP5loyBH5eCuOG4/jy58UTGFJxEAfUqv0YVRBIwbDhu9SR6oDgPyFXVEhH5K4CqPiAi2cBPqrolsE73DFWt9FO3QGFMDfnpO1g6FpaNg/wfKFIPn/q7M8nXh0/82ewvtfhlVVKghxJcUyMrsCbHpyu3sSW/kCMyPOwtLinTHGajqw5PQgeKMgUQGQIMU9Wry20X4BeglaoWhbuGBQpjapgq5M1nXe5bNFw/mebsZK+m8bG/J5N8fZjr7sFub/Q6wQ+H5auKXG0KFJOAMao6utz2YcBwVT2ngvNuAW4BaNOmTc+NGxMmR6ExycXv4/NPJvLL1/+lX8mXNJY9FHsaMcPfm/cLT+Zrfyd8uGu0SFaziEzcA4WIzAKOCrHrIVWdEDjmIaAXMFRLFUREOgMTgfNUdV1l72U1CmMShM8L6z+DZePwLp+Ip2Qv27QRU3ynMsnXhwV6QpUz2x4um7dRubgHikrfWORaYDhwtqruK7W9NZCL08H9RSTXskBhTALyFvLNzDHsmT+Gvr65pIuXPG3KJN+pTPWdwhI9ltJzNEIlIKwOW1ujcgkdKERkIPA80E9Vt5XangnMBh5X1XGRXs8ChTEJrqgAVk3jxy/epclP/8ODj03+Zkz192aa7xRWpbTnqaHOWmiPTlxe7aG34NQoRgzoYKOiwkj0QLEWSAO2BzZ9rarDReSPwEhgTanDz1PVn8NdzwKFMbVI4U4WzHyXwsXjOdm3iFTxsS+jJfW6XwqdLoGsXuQs3sozM1aRl19YpZqGxyV43MI+r7/Mduu7KCuhA0W0WaAwppYqzIdV0+C7CbDuE/AVQ6MsOPFi6DwYWvcGl6vMfIlQw2I9LqFBekpEmXCt7+IgCxTGmNpl/y5YPcNZD3ztLGdN8IYtnaDR6RJocyq4nNFToSbaATwwbglFJf5w7wLA337d3dbPwAKFMaY2278b1syE5R85QaNkPzRocTBoHHPagaAR1HdULnn5hdV+69IT/Op64Ig0UEQ/l7AxxlRXeiPoOsx5FO2BNYGaxsLRMPc1qNcUOl4AHQfBsf0gJY0tUQgS4AQJgLz8QkaOXwpQp4NFJCxQGGMSW1oD6HKp8yje69Q0VkyCZR/BgncgtSGccC7XNGzHuIJO7CUjam9d6PVx79jFQHIHC2t6MsbUTiVF8P0cJ2isnAL7fqFIPXzu78IMfy9m+Xqyg0YHmpKqqy5mtbU+CmNM8vD7YNM3rJ3zPvXWT6OV/owPFzub9GBry3P4/eLWrPMeWe23qWvDay1QGGOSk6qTDn3FJFg5GX7+DoDvaMdUby9m+E9mjWZR1ZX76tLwWgsUxhgDsH3dwaCxeS4A6/1H8bH/ZKb7erGtURfOPLEFn67cFtGoqbqUGsQChTHGlLd7K6ya4qzet+F/4C+B+s2hw0DocAG060fO8p2MHL/0kJX3giqqUQTnZOTlFx5Yi6P8fxNtyK0FCmOMCadwJ6z5GFZNhTWzoLgAUjLguP4szOjDvYuOYv3++oecFuwcL/3l379jM8bNz6swuJSWSP0cFiiMMSZSJcWw8XMnnciqabBrEyBsb3wSo3d2YlJRd9ZWo1+jvESpaVigMMaYqlCFn5bByqlObWPrIgA2+Fswy9+DWf6ezPV3iPpiTMGaBlBjaUUsUBhjTBT0efAdznIv5BzXfE5zLSdNSsjX+uT6s5nl68Ecfzf2UC8q71VRptxYzeGwFB7GGBMFrszWvJvfhHd951CfQk53LeVc9wLOci1gqPtzitXN1/5O5PqzyfVn84O2qPJ7VfSzfec+b1zTiViNwhhjwshZmBdyFJQLPz1kNee4F3Cuaz7HubYCsNbfis/82Xzi784C7UiRRq+JKtpzOKzpyRhjoiTU0NfyzUTHyI8M8CzmphZraL5jnrO2RlojOK4/T65pw0cFndjOEdUuSzBFejRYoDDGmBircD2Loj2w/jNYPd0ZgrvnR/wqLNFjyfVlk+vvznJti+I67PcMLtKUv89b7c5uCxTGGJMIVGHrYlbM+RD/qhmc6F+DS5TCtGZM3NuZXH82n/u7VCvrbVU7uy1QGGNMItqzDdZ+DKtnsOe7mTRgH8Xq5hv/iQc6xDfTkitPOZrRX/8Q8WWrMpHPAoUxxiS4CfM3MC5nHH11Pme7FnK8awsABfXb0rDrBdw9vxlTC9pRRGpE1zvczm4bHmuMMQnukp5tUdcVPDMjm1H5hfRqtIs/ddhEt33fwNzXecFXxJ/T0vjS34nP/N35zH8Sm7V5hdeL1ip/5VmgMMaYOBqcnRW6uah4H2z4Hz9+lcMJ62dxjnshEBx+exKf+bvzrb8jxXgOnNIqM3qr+5VmgcIYYxJRaj1oP4Bj2w8gZ8Fmhn80kz7+BZzpWsQ17lnclDKNfZrGu76z+XPJb8jwuBkxoENMimKBwhhjEtzgHq1BBvDMjLa8mX8+xx4hXNtqE/V/yGVLSZOYJxW0zmxjjElSkXZmH/5sD2OMMUnFAoUxxpiwLFAYY4wJywKFMcaYsCxQGGOMCcsChTHGmLAsUBhjjAnLAoUxxpiw6sSEOxHZBmw8zNOaAr/EoDg1ye4hMdg9JAa7h8N3jKo2q+ygOhEoqkJE5kUyIzGR2T0kBruHxGD3EDvW9GSMMSYsCxTGGGPCSuZA8Wq8CxAFdg+Jwe4hMdg9xEjS9lEYY4yJTDLXKIwxxkSgTgcKERkoIqtEZK2IPBhif5qIjAns/0ZE2tZ8KcOL4B6uE5FtIrIo8LgpHuUMR0TeEJGfRWRZBftFRP4RuMclItKjpstYmQju4UwR2VXqc3i4pstYGRE5WkQ+FZEVIrJcRH4X4piE/iwivIeE/ixEJF1EvhWRxYF7eCzEMYn13aSqdfIBuIF1wLFAKrAY6FTumNuAfwWeXwGMiXe5q3AP1wEvxbusldzHr4AewLIK9l8ATAMEOBX4Jt5lrsI9nAlMjnc5K7mHlkCPwPOGwOoQ/z8l9GcR4T0k9GcR+Ns2CDz3AN8Ap5Y7JqG+m+pyjaI3sFZV16tqMfA+cEm5Yy4B3g48/xA4W0SkBstYmUjuIeGp6hxgR5hDLgHeUcfXQKaItKyZ0kUmgntIeKq6VVUXBJ4XACuA8mtnJvRnEeE9JLTA33ZP4KUn8CjfWZxQ3011OVBkAZtKvd7Mof9DHThGVUuAXUCTGildZCK5B4BLA80EH4rI0TVTtKiK9D4TXZ9Ac8I0Eekc78KEE2jKyMb5NVtarfkswtwDJPhnISJuEVkE/Ax8rKoVfg6J8N1UlwNFqOhbPmpHckw8RVK+SUBbVe0GzOLgr5DaJNE/h0gswEmHcBLwIpAT5/JUSEQaAOOA36vq7vK7Q5yScJ9FJfeQ8J+FqvpUtTvQGugtIl3KHZJQn0NdDhSbgdK/rlsDWyo6RkRSgCNIrOaFSu9BVberalHg5WtAzxoqWzRF8lklNFXdHWxOUNWpgEdEmsa5WIcQEQ/OF+y7qjo+xCEJ/1lUdg+15bMAUNV84DNgYLldCfXdVJcDxVzgBBFpJyKpOB1CE8sdMxG4NvB8GJCrgd6jBFHpPZRrP74Yp822tpkI/DYw4uZUYJeqbo13oQ6HiBwVbEMWkd44/7a2x7dUZQXK9zqwQlWfr+CwhP4sIrmHRP8sRKSZiGQGnmcA5wAryx2WUN9NKfF641hT1RIRuQOYgTN66A1VXS4ijwPzVHUizv9w/xGRtTjR+or4lfhQEd7DXSJyMVCCcw/Xxa3AFRCR93BGojQVkc3AIzgdeKjqv4CpOKNt1gL7gOvjU9KKRXAPw4BbRaQEKASuSLAfHQB9gWuApYH2cYA/AG2g1nwWkdxDon8WLYG3RcSNE8TGqurkRP5uspnZxhhjwqrLTU/GGGOiwAKFMcaYsCxQGGOMCcsChTHGmLAsUBhjjAnLAoUxxpiwLFAYEwUi8qiI3Bfla34oIsdW4/y25dOii8h8EUkVkT0VnRfBde8QkUSbX2FiyAKFMXEWmAXtKretM+BW1fUhjndX8X3aAnmBTMTV8QZwVzWvYWoRCxQm7kTkfhG5K/D8BRHJDTw/W0RGB57/n4jMK73Qi4icLyJjS13nTBGZFHh+noh8JSILROSDQBI5RGSDiDwW2L5URDoGtpepEYjIssAv8rYislJE/h3Y9q6InCMiX4jImkCKiKCTRCQ3sP3mUtcaISJzxcnwGyx7W3EW3/knThK78ll/rwYmlLrGHhF5XES+wcmM+nDgmstE5NVSKSt6ipM19Svg9nLXPB+YXu5v3zTwd7ow8PebLSJjRWS1iIwSkavFWWRnqYgcB6Cq+4AN5e7d1GEWKEwimAOcEXjeC2ggTuK304H/BbY/pKq9gG5APxHpBnwMnCoi9QPH/BoYI04CuD8C56hqD2AecE+p9/slsP3/gEiai44H/h54747AVYGy3YeTPiKoG3Ah0Ad4WERaich5wAk4a4t0B3qKyK8Cx3fAWfshW1U3lnvPvsD8Uq/r4yyadIqqfo6zWNXJqtoFyAAuChz3JnCXqvYJcR8DKRUoRKQFMAV4WFWnBDafBPwO6IqTKqO9qvYG/g3cWepa8zj4mZk6zgKFSQTzcb5AGwJFwFc4AeMMDgaKy0VkAbAQ6IyzqlkJzhffIHEybF6I8yv8VKAT8EUgH9C1wDGl3i+YcXQ+0DaC8n2vqktV1Q8sBz4J5A5aWu78CapaqKq/AJ/iBIfzAo+FODWHjjiBA2BjYHGgUFoC20q99uFkTA3qL84SmUuBs4DOInIEkKmqswPH/Cd4sDhJJVuXasryAJ8A96vqx6WuOzewOFARzuqKMwPby9/rz0CrCspu6pg6mxTQ1B6q6hWRDTgJ6L4ElgD9geOAFSLSDufX+8mqulNE3gLSA6ePwWli2YHzJVcQaIb5WFWvrOAtg2nZfRz8N1BC2R9O6SGOB/CXeu2n7L+h8onTFGddgadU9ZXSOwL9BXsrKB84yexKl2G/qvoC56YD/wR6qeomEXk0cKyEKEPQGcDnpV6X4ATKAcDsUtsjvdf0QBlNErAahUkUc3CCwRycWsRwYFHgl3sjnC/VXYHmkvNLnfcZzlrWN+MEDYCvgb4icjyAiNQTkfaVvP+GwHUQkR5AuyrcwyUiki4iTXAyzc7Fyfx7Q6k+kiwRaR7BtVbgNHmFEgwgvwSuOwwOrG2wS0ROD+y/utQ5A3HWwg5S4Aago4g8GEF5ymsPLKv0KFMnWKAwieJ/OM0tX6nqT8D+wDZUdTFO081ynBE3XwRPCvzKnowTPCYHtm3DSbf+nogswQkcHSt5/3HAkYGmqluB1VW4h29x2vy/Bp5Q1S2qOhP4L/BVoJnoQ6BhBNeaghNsDhEICK/hNAfl4ASkoOuBlwOd2aV/8Z9J2ZpD8G93BU4z1m0RlKm0vjgrKpokYGnGjUlA4ixo8ynQN9jkVI1rtQZeU9XzKz04sutlA/eo6jXRuJ5JfBYojElQIjIAZyW3H+JdltJE5FxgjapuiHdZTM2wQGGMMSYs66MwxhgTlgUKY4wxYVmgMMYYE5YFCmOMMWFZoDDGGBPW/webOn3tQ1IxigAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# compute radial power spectrum\n", - "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid)\n", - "\n", - "# analytic formulation\n", - "Phi2 = pycurious.bouligand2009(k, 3.0, 0.305, 10.0, -18)\n", - "\n", - "\n", - "# plot radial power spectrum\n", - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial power spectrum\")\n", - "ax1.plot(k, Phi, '-o')\n", - "ax1.plot(k, Phi2)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Choice of taper**\n", - "\n", - "The default taper is the hanning filter (see [`numpy.hanning`](#hanning) for more details), but other functions can be passed to taper, or simply set it to `None`. There is a significant offset in the power spectrum with tapering functions, however, it is the *slope* of the spectrum with wavenumber that is most important when it comes to determining Curie depth." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYoAAAEKCAYAAAAMzhLIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJzt3Xl8VPW5+PHPM5OBhICErRJAL3BvFRfCIlJaRLFq0F+KIlW63fvTeqvX3l9L7GLB5WLUeyutbRW9vV1cWlvbCsWK0uiFKu5LFWUR92qxAsFCIAhZYDLz/P44Z8JkMstJ5kxmkjzv1ysvkjNnzvkOgXnmuz2PqCrGGGNMKoF8N8AYY0xhs0BhjDEmLQsUxhhj0rJAYYwxJi0LFMYYY9KyQGGMMSYtCxTGGGPSskBhjDEmLQsUxhhj0irKdwP8MHz4cB07dmy+m2GMMT3Kyy+/vFtVR2Q6r1cEirFjx7J+/fp8N8MYY3oUEXnfy3k29GSMMSYtCxTGGGPSskBhjDEmrV4xR2GM6Z3C4TDbtm2jpaUl303p0YqLixkzZgyhUKhLz7dAYYwpWNu2bWPQoEGMHTsWEcl3c3okVaW+vp5t27Yxbty4Ll3DAgWwasN2bl7zFjsamhlVVsKVc45l3pTR+W6WMX1eS0uLBYksiQjDhg1j165dXb5Gnw8UqzZs56o/vEpzOALA9oZmrvrDqwAWLIwpABYkspft32Gfn8y+ec1bbUEipjkc4VsrNrFqw/Y8tcoYYwpHnw8UOxqakx6PqHLVH161YGGM6fP6fKAYVVaS8rHmcISb17zVja0xxmRj1YbtzFy6jnGLa5m5dJ190PNJnw8UV845ln7B1H8NqXocxpjCEptv3N7QjHJ4vjHbYLF161aOO+44Lr30Uk444QQqKytpbm5m48aNzJgxg4qKCs4//3z27t0LwOzZs1m0aBHTp0/nmGOO4emnnwYgEolw5ZVXcvLJJ1NRUcHPfvazbF9yt+nzk9nzpoxm07a9/OLZ5ClP0vU4jDHd5/rVr/H6jo9SPr7hbw0cikTbHWsOR/jOys387sW/JX3O8aOO4Lq5J2S89zvvvMPvfvc77rjjDhYsWMD999/P97//fW6//XZOO+00lixZwvXXX8+tt94KQGtrKy+++CIPP/ww119/PY8++ih33XUXgwcP5qWXXuLgwYPMnDmTysrKLi9Z7U59PlAATBozBHif/kUBDrYe/odWEgpy5Zxj89cwY4xniUEi0/HOGDduHJMnTwbgpJNO4t1336WhoYHTTjsNgIsuuogLL7yw7fz58+e3nbt161YA1q5dy+bNm1m5ciUA+/bt45133rFA0VO0uKueFp8zgR8//hd2HzjEkAEhrpt7gi2RNaZAZPrkP3PpOrYnGSoeXVbC8n/7ZFb37t+/f9v3wWCQhoYGT+cHg0FaW1sBZ+Pb7bffzpw5c7JqSz70+TkKoK0Xce6kUbx49ZkM6h+kORzhG8s32oSYMT3ElXOOpSQUbHcsV6MCgwcPZsiQIW3zD7/+9a/behepzJkzh5/85CeEw2EA3n77bRobG31vWy5Yj4LDPYriUJCHNu2gKRwlElXANuAZ01PE/n92V5aFe+65h8svv5ympibGjx/PL37xi7Tnf+UrX2Hr1q1MnToVVWXEiBGsWrUqJ23zm6hqvtuQtWnTpmk2hYuWPfoOtzz6Nu9+9/9w6vcfT9l9fXbxp7NppjGmk9544w2OO+64fDejV0j2dykiL6vqtEzPtaEnoKU1QigoBAOScjns9oZmG4IyxvRJFihwhp6Ki5yxzXTLYW2ntjGmL7JAAbSEo/R3J8GSTYjF2E5tY0xfZJPZwMFwhOKQEzNjE19XLN+Y9Nz4oSlLT26M6Qvy0qMQkQtF5DURiYpIh4kUETlaRA6IyLe7oz0trRGK43oR86aMZnSKIajY0FSu0gUYY0yhydfQ0xZgPvBUisdvAR7prsa0hKNtPYqYTGuyU6Unt6EpY0xvk5dAoapvqGrSd1QRmQe8B7zWXe2Jn8yOmTdlNDfNn8iIQc4OyyEDQtw0fyLzpoxm1YbtSZfQgiURNKa32bp1KyeeeGJO7/GVr3yF119/Paf3yEZBzVGISCmwCDgL6JZhJ3ACxYB+Hf8q5k0ZTVVFORU1azlvsjP3MPn6tTQ0h1Ney5IIGpNHm1fAYzfAvm0weAycsQQqFuS7VRndeeed+W5CWjnrUYjIoyKyJcnXeWmedj1wi6oe8HD9y0RkvYisz6YWLCQfeooJBQOMKivmV89v5YrlG9MGCUsiaEwebV4BqxfCvg8Adf5cvdA5nqVIJNIhzfgdd9zBySefzKRJk/jsZz9LU1MTABdffDELFy7kU5/6FOPHj29LAvjEE08we/ZsLrjgAiZMmMCXvvQlYhueZ8+eTWzT8MCBA7nmmmuYNGkSM2bM4MMPPwTg3XffZcaMGZx88sksWbKEgQMHZv26vMpZj0JVz+zC0z4BXCAi3wfKgKiItKjqfye5/s+Bn4OzMzubtra0RtqWxyZatWE779c3EfVwh9jQlDEmBx5ZDDtfTf34tpcgcrD9sXAzPPg1ePme5M8ZORHOWZrx1snSjM+fP59LL70UgGuvvZa77rqLr3/96wDU1dXxzDPP8Oabb3LuuedywQUXALBhwwZee+01Ro0axcyZM3n22Wc55ZRT2t2rsbGRGTNm8F//9V985zvf4Y477uDaa6+lurqa6upqvvCFL/DTn/40Y5v9VFD7KFR1lqqOVdWxwK3Ad5MFCb8dDEc7zFHE3LzmLVo9RIlhpf0sSBiTT4lBItPxTkhMM75161a2bNnCrFmzmDhxIr/5zW947bXD06rz5s0jEAhw/PHHt/UIAKZPn86YMWMIBAJMnjy5LQV5vH79+vGZz3ym3b0Ann/++bZU5l/84hezfk2dkZc5ChE5H7gdGAHUishGVc1b7t2WuH0UibxOTp92zHA/m2SMSZTpk/8tJ7rDTgkGHwVfrs3q1olpxpubm7n44otZtWoVkyZN4pe//CVPPPFE0vPj8+klXieWgjxeKBRCRNKe093yterpAVUdo6r9VfXIZEFCVWtU9Qfd0R4nUCTvUWSanB4yIET/oDCoOJSLphljvDpjCYQS/r+GSpzjObB//37Ky8sJh8P85je/yck94s2YMYP7778fgPvuuy/n94tXUENP+dLSmnoyO1VKjyEDQtz6uclsWFLJx0cO4q/1TblupjEmnYoFMPc2pweBOH/OvS1nq55uvPFGPvGJT3DWWWcxYcKEnNwj3q233sqPfvQjpk+fTl1dHYMHD875PWP6fJrxcCTKx695hG+ddQxfP+PjSc/JlKrja799hc3b9vHNs46xlB7G+MjSjB/W1NRESUkJIsJ9993H7373Ox588EHPz88mzXhB7aPIh/iiRanMmzI67Rv+uOGl/HFzHYv/sJmWsFMtzwoeGWP89PLLL/O1r30NVaWsrIy777672+5tgcJ9Y0819OTFnsaD7a4VE0vpYYHCGJOtWbNmsWnTprzcu8/PUcR6FP1DQWrfq6VyZSUV91RQubKS2vcyr5RYtWE7K19OnQhwe0Mz4xbXWu1tY0yP1ecDxcFWJ1C8uf9Jap6roa6xDkWpa6xj8dOLmXXfrLQB4+Y1b3GwNZryccCyyxpjerQ+Hyhiw0WP7vwFLZGWDo83HGyg5rmalMGiM0kALbusMaYnskDhDj01HPp76nMiLSx7ZVnSxzqbBNBqbxtjehoLFG6P4shI+mXCOxvrkh5PVzo1lSuWb2TKDWstYBjTA3RHmvFU1q9fz8KFC/Ny73i26sntUVTX7+HqEUNRd+t8opEpAklsRVNs/4TXXSl7m8K2fNYYn9W+V8uyV5axs3EnI0tHUj21mqrxVfluVpdNmzaNadMybnPIOetRuJPZZ0kZRaqEolFI3ISoSjPRlPMU86aM5tnFn+avS6tSllBNxuYsjPFP7Xu1HRakpJtf7IzOphn/6le/yumnn8748eN58sknueSSSzjuuOO4+OKL2645cOBAFi1axEknncSZZ57Jiy++yOzZsxk/fjwPPfQQ4KQmjyUIrKmp4ZJLLmk757bbbmu71o033siECRM466yz+MIXvsAPfuBv9iPrUbhDT69N+zfC79/JDbvq6afKTUOHsC8YABEQoSEYpOa5GoC0n1CunHMsV/3h1Q5lUlOxinjGePO9F7/Hm3veTPn45l2bORQ91O5YS6SFJc8uYeXbK5M+Z8LQCSyavijjvTubZnzv3r2sW7eOhx56iLlz5/Lss89y5513cvLJJ7Nx40YmT55MY2Mjs2fP5nvf+x7nn38+1157LX/60594/fXXueiiizj33HM7tOPNN9/k8ccfZ//+/Rx77LF89atfZdOmTdx///1s2LCB1tZWpk6dykknnZTxNXWG9SjcN/S3R/wDAMe3QlVjEwNUnSARf26aSe2YWAnV0WUlCFBWEiIUTD6cBRAQsbkKY3yQGCQyHe+MzqYZnzt3LiLCxIkTOfLII5k4cSKBQIATTjihLW14v379OPvsswGYOHEip512GqFQiIkTJyZNPw5QVVVF//79GT58OB/72Mf48MMPeeaZZzjvvPMoKSlh0KBBzJ07N+vXm8h6FG6g2LrrJUKqjK+8CYpK2PnKDUnPTzWpHS8x5ceqDdupeei1pNXxIqo2V2GMB5k++VeurKQuyf/P8tJyfnH2L7K6d1fTjAcCgXbPDQQCbWnD49OJx58Xf06mdrS2ttId+fr6fI/iYGuUoiM28Pu/PUwYqHrnF9QOLGVkij10qSa105k3ZTQbr6vk1s9NJphksrw5HOFbKzZZz8KYLFRPraY4WNzuWHGwmOqp1Tm5X3enGU/llFNOYfXq1bS0tHDgwAFqa7Ofk0nU5wPFa/sep7j8DxzSVhChrqWemudqOPXAAYqj7aNFcTRKdf2eLt9r3pTRRFNE/1jPwoKFMV1TNb6Kmk/VUF5ajiCUl5ZT86manK166u4046mcfPLJnHvuuUyaNIn58+czbdo031OQ9/k045/49ek0RXd3OF4eUarr6/nusCF8FAzysdZWvrmngaqiYfCNLV1u68yl69ieZgJ7dFkJzy7+dJevb0xvYmnGvTlw4AADBw6kqamJU089lZ///OdMnTq13TnZpBnv8z2KpkjHIAGwMyhUHVJ+/OEuAK6u30vVIc26WlamDXq2CsoY01mXXXYZkydPZurUqXz2s5/tECSy1ecns4+MKB8WdZw3GBlRmHsbE9ZcQ5Eqrw4czBmn3Jh1tazYhPW3VmwikqQ319mUIMYY89vf/jan1+/zPYor9uyhKOENu20uomIBxd/YwjGHWtkyZJRvJRXnTRnNDxdM6tCzKAkFuXLOsb7cw5jeojcMj+dbtn+HfT5QzDhYyuxGZ0elqFIebqVm9x6qioY6JxT154hAiBcP7qLilydSefeJ1D7xH1nf9/B+C2eVRnFRgJvmT7QlssbEKS4upr6+3oJFFlSV+vp6iouLM5+cQp8felpxxJcZEr2TwZEIz/zNXXEUKoE5zlxE7RP/wctF2pYDqi4INX99AICq2Tdmde/YfouL7v4zT769m28s38jNa96yWtvGuMaMGcO2bdvYtWtXvpvSoxUXFzNmzJguP7/PB4on+p1OMasZEtkLCAwe40xYu8NMy957gHDCzuqWgLDsvQeyDhTgbMZ7/j1nyW18gSOwDXjGhEIhxo0bl+9m9HkZA4WIjAO+DoyNP19VOyYi6YFaWiM0FxcxNBKB/9gFwVC7x3emGJxLdbyzbl7zFodak9fajj2+o6GZUWUl1tMwxuSFlx7FKuAuYDWQvuZnD9QSjqAlBxkalQ5BAmBk1BluSnbcD6mWw25vaObKlZsIuzvBradhjMkXL5+LW1T1NlV9XFWfjH3lvGXdpCUcpTFwiCGBfkkfrx5/PsXRJGnH+w3wJX1xuuWw4YR0IZaW3BiTD14CxTIRuU5EPikiU2NfOW9ZN2kOhzkgEYYE+yd9vGr2jdSMO5/B8XUqRGiINPuS676zFfJsQ54xprt5CRQTgUuBpcAP3S9/q2LkUUvkACowtKg05TlVs29kwMBRXUo7nklsmaxXlpbcGNPdvASK84Hxqnqaqp7ufvWaZESH+AiAoaFBac/b2bQz6fG6xrq2XkXte7VUrqyk4p4KKldWeu5tzJsy2nNlPEseaIzpbl4CxSagLNcNyYdoVGllPwBD+qfPtpguvXjNczX85wv/mVUZxs4MQdlchTGmO3lZ9XQk8KaIvAQcjB3sDctjD7ZGkWAjAENKhqU9t7p+DzXDh9AS6BhbWyIt/P7t3xPVaIfjy15Z5inNcaYcUIm2NzRz7apXefzNXbZ81hiTU14CxXV+31RELgRqgOOA6aq6Pu6xCuBnwBE4y3FPVtUWv9sAztLYUHAfAMNKPpb23KqiobC7nsUjhnWYqwA6BImYnY3Jh6ySib3Je625fe8Lf2v73pbPGmNyJePQU/ySWB+Xx24B5gNPxR8UkSLgXuByVT0BmA10rB/qkwc3bae4aC8Adz23L/24/xlLqDqklLcmfwMPpOgFjAwd0ak2dbbmdjwbkjLG5IKXndn7cbJLAPQDQkCjqnbuHTCOqr7hXjvxoUpgs6pucs+r7+o9Mlm1YTs3PfwmZcM/YkAkwgdNxfwy3SdyN6VH9ZpqaoYPbTcEVRyNct7+A6w8YhCRuNdUHI1Svbeh021LVnP7iuUbPT3Xls8aY/zmpUcxSFWPcL+Kgc8C/52j9hwDqIisEZFXROQ7OboPN695i4OtUYLB/QyNRGlgYOZP5BULqCoaRs3uPXzMLX5+RCRCze49XLungWGRSNtei+GtbhbaXduybmtnVkVZPQtjjN86nbFIVVcBGZfHisijIrIlydd5aZ5WBJwCfMn983wROSPF9S8TkfUisr4rmSVjn7wl2MjQaIR9WtrueEruENRjH+zgH8JhprYcpOqQ0jhgKLuCQS7YfwCAL+/bT1Vjk5Nk0AdeVkUVhwJcOedYVm3Yzsyl6xi3uJaZS9fZUlpjTFa8DD3Nj/sxAEzj8FBUSqp6Zhfasw14UlV3u/d+GJgKPJbk+j8Hfg5OzezO3mhUWQnbG5qJFDUzpDXKXyltO55WrHjRmqs4MtzKkwNKqCgdwNCiUrS1kdObmnm+pISNxf35v82tWZdOjYkNRd285q12NbeFw7+MlnCUmodeo/FQq+WIMsb4xkuPYm7c1xxgP5CuV5CNNUCFiAxwJ7ZPA17PxY1in9BbgwcZGonQoKXeK8xVLKB27k1sKClGRVCgvtVZZnvViGEENMpjA0qoGDOCyrfv9CUnFDhv9FfOOZb+RYd/bYkRsqE5bDmijDG+StujEJEgzuTyLX7eVETOB24HRgC1IrJRVeeo6l4R+RHwEs574MOq6s+7bIJ5U0azYc9j/OGDML8fNBD5+I/5/LhLPX/qXrblTsJJlsl+FAzyUSDQtoQ2tvEO8LSfIpPY3Epn2SS3Maar0vYoVDUC+L6xTlUfUNUxqtpfVY9U1Tlxj92rqieo6omqmrPJ7Nr3aqmtu80ZuxFBi/byxx23ef70n3Z/RA5yQsV09Q3fJrmNMV3lZejpORH5bxGZ1Zuyxy57ZRkHIwfbHevMG3pn90fE54TKRlfe8EMBaDrUapPbxpgu8RIoPgWcANxAL8oem6pH4HUndfXeBoqjnRsCykdacoBwFPY2hduVWrVgYYzxykug+Ne4rLGnq+rpwFdy3bBcG1k6slPHE1Xt2kbN7j0Mbj28d6JNil3afqYlj+3cHl1Wwj/POJpgkvmSVGxy2xjTGV4Cxcokx37vd0O6W/XUaoqDxe2OFQeLqZ5a7e0Cg8dQ1djEMx9sZ+muesrDrYgq5eFWPvfR/pTBwo8hqHlTRvPs4k/z16VVPLv40/znvIn8cMGkTvU0tjc0W6/CGONJylVPIjIBZ8hpcMJeiiOA4uTP6jliK5CWPfEddgYDjCwtp3pqtfeVSWcsgdULIdxMVWOTs7kuzlMDBlAXSv7X6+cqqJj4fRY7Gpozb3QB219hjPFENMUnX3cH9TycVU8PxT20H7hPVZ/LffO8mTZtmq5fvz7ziYlU4YZhMLMazuxCktzNK+CRRdC8p/3xQIjaAf2oGZY8LTlAeWk5ay9Y2/l7ejRz6bp2G/NSGV1WwrOLe00dKmNMJ4jIy6o6LdN5KYeeVPVBVf0y8BlV/XLc18JCChJZeeVXoBF45kdwy4nOG39nVCyARX+F+XfA4KMAcf6c9z9UHWiiZveelENQnUk/3hVeJ71tf4UxJpOUPYq2E0TuAapVtcH9eQjwQ1W9pBva50mXehSbV8BDX4fWuFIXoRKYe9vhNB3ZuOVE2PcBlWNGJR2CCiB8d9ZNvg4/JVq1YXvbUFRAJGlBpKB7PPbnaLcAEhwexrKiSMb0Tl57FF4CxQZVnZLpWD51KVC4b+QdDD4KvrEl+0ZtXgGrF1LbTzqkJY8pDhZT86manAaLmFUbtnsuiBQKCAjtUoGUhILcNH+iBQtjepGsh57iz3F7EbELD8VbZbzCti9F+u9UxzurYgHMvY2qRmcIKllhIz93bGcSW1ZbVhLKeG44qknzRX1rxSbbtGdMH+QlUPwQZ3f2jSJyA/Ac8P3cNqsbpEr/7VNacMAJFoOPoqqxKeUqpFzPVcSbN2U0pf27HuMjqrZpz5g+yEvhol/hFCv6ENgFzFfVX+e6YTl3xhJnTiJeqMS3tOCJ9xmZooSqgG/ZZb3wa/LaNu0Z03d4LVw0FKf86e3ALhEZl8M2dQ93aKjdaiW/JrKT3Ke64aOkKT+iqC+pPbzyMzmgrZgypm/IGChE5DpgEXCVeygE3JvLRnWbigXOxHVNg/On30Ei7j5VBxoLYq6iK7miUrGMtMb0DV56FOfjbLprBFDVHcCgXDaqV3JTfqSaq/Aru2wmyXJF3fq5yXjPFHVY3b5mxi6u5R+vepixNsltTK/lZWbzkKqqiCiAiJTmuE29k5vyY2RrJGNqD3DSoO9s3MnI0pGdSy3iwbwpozssc00ssepF1I16sf0ZVnbVmN7Jyz6KbwMfB84CbgIuAX7rzlcUhC6n8Ohum1dQu6Y65b6KmFAgRDgabvu5O/ZbdGafRSapNvFZ8DCmsPi2j0JVf4CTQfZ+4BhgSSEFiR6lYgFVRcPSpvYA2gUJ6J45jMQhqc6kLU8U62Ek9jRsWMqYnsnrqqdXgaeBp9zvTVedsYSqQ0p5iuWyqXTHfov49OXJ0pZ3PXTYclpjejIvq56+ArwIzAcuAF4QkYLJ89TjxJbLdrJCXnfvt0g26f2lGUdntWLKltMa0zN5maN4C/iUqta7Pw8DnlPVY7uhfZ70mDmKeLecSG1rPVePGEbU4zBPsYSoOeXGbskNlUos0eD2hua2OQivgiL8cMEkm6swpkB4naPwsuppG04Nipj9QJJseqZTzlhC1eqFsKu+4+R27M03IYC0aJhlL+Q242wmyVZMjVtc66lQUkTVVkUZ0wN5maPYDvxZRGrczXcvAH8RkW+KyDdz27xeLCFpYLva2yIdgkRM3aGGbh2C8qIzG++awxGuWL7R9l0Y04N4CRTvAqug7UPjg0AdzqY723iXjbikgQNUUwaHdkS6NeWHF8l2e3uZy9je0MwVyzcy/qpaCxzGFLCMQ0+qen3sexEJAANV9aOctqovcTfi7SzyPkkcWy6bzyGoeIn1umOFjrxu4ott3LMNe8YUpoyBQkR+C1wORICXgcEi8iNVvTnXjesT3PxSI9ffQF2yWJGip7GzsS7HDeucZHMXQKc38cXqXsSuaYzJPy9DT8e7PYh5wMPA0cC/5LRVfU3FAqrHn99huWxxNEpZiiW0IyPeVxvlS2yJbWc378UmvW0YypjC4CVQhEQkhBMoHlTVMHha5GI6oWrDA9Ts3kN5uBVRpTzcSs3uPSyu39txv4UqzUQLap4ilXlTRifdvJeJTXobUzi87KNYiJNmfBNQhdOjuFdVZ+W+ed70yH0UiWrKSBV/a0sHcNPQIewLBtoNQxVJEQP7DWTfwX05SR7op/j9F0LnP2mEAsLA4iIamsJtcyA2NGVMdrzuo8gYKJJcWICgqrZ2tXF+6xWB4pYTYV+S7SklQ6G1mcojh6TMOhvTHckD/RIfOLqiJBTkpvkTLVgYk4WcBQo/iMiFQA1wHDBdVde7x0PAncBUnIn2X6nqTZmu1ysCxeYVsHohhOPeOEMlTtU9oOKVG1APY/0BCfDdU77bI4JFTLaZay07rTFdU+iB4jggCvwM+HZcoPgicK6qfl5EBgCvA7NVdWu66/WKQAFOsHjsBti3DQaPcZbOuquiKu8+kbqgt0nhnjQkFbNqw3a+tWJTp1KCJAqIs9TWAocx3viSwsPdNzFDVZ/zrWWAqr7hXr/DQ0CpiBQBJcAhoO/s2ahYkLIca3X9HmqGD0lbxyKmVVtpONgAOJXzYgWRCjlYxN7Us+lZxO/HuGL5Rq5f/RrXzT3BAoYxWUr7rqOqUeCH3dQWcOpeNOLs/P4b8ANV3dON9y9YVUVDU9bczqQ7a3JnIz5jrR/2NoVTLrNdtWE7M5euY5ytqjImIy+rnq4HNgN/0E6MU4nIo8DIJA9do6oPuuc8Qfuhp5nAvwMXA0NwamCco6rvJbn+ZcBlAEcfffRJ77//vtem9UzuHEZtP8lYIS+dgASIapTy0vKCH5Lyq+peUIQvfOIoHn9zFzsamhlcEqLxUCvhuL0oNjlu+iLf5ihEZD9QirMzuxmnNIKq6hE+NPIJ2geKHwMvqOqv3Z/vBv5XVVeku06vmaPIZPMKeOByagf0Z9mQMnYWBTkiEqUpGCDchYp0PWGVVGx1VKo3eD+NLivh2cWfzsm1jSlEvqUZV9XuTPz3N+DTInIvMACYAdzajfcvbO78RdXqhVRt29F2ONU+i0wKLWdUMompQbLdj5GOFVYyJjkvFe5ERP5ZRP7D/fkoEZmezU1F5HwR2QZ8EqgVkTXuQz8GBgJbgJeAX6jq5mzu1eu46ckZfJR7QDqXfTZBoeWMyiRWrnXr0ir+urSKWz83uW1OI5tSrQABEZurMCYJL0NPP8EmNU7RAAAezUlEQVRZyvppVT1ORIYAa1X15O5ooBd9Zugplc0rPO+zSFQeUdZesiUHjcoPP5bZDhkQstVSpk/wOvTkZUb0E6r6/4AWAFXdC/TLsn3GTxULGDlwVKefVhyNUl3fuxaVec0tlS6k7m0Kc8XyjUy5Ya31MIzBW6AIi0gQdzhYREbg9DBMAake/gmKowmfohM/VasiqqBKQJXrdu+hqmho9zWym8SW2ZaVhFKe46W/kW55rTF9iZea2bcBDwAfE5H/Ai4Ars1pq0ynVW14AFrr21ZDjWyNcGpTE08NGND2c/XeBqoam1g1sJT/GDGMY6IBZ/d3LxSbBM921VRzOMLNa94COhZmsqEp01d4SuEhIhOAM3B67I/FdlYXij4/RwFps88munfQQL433OlJ9KR9FdmauXRdl5MQhoJi+y5Mr+PnPoobcDa+PaeqjT61z1cWKEidfTZeqITamZdS87c/0pIi+W9Z/zIWT18MwLJXlrGzcWePyReVybjFtb4up8207yK+N2O9EFOI/AwUlwCn4Cxl3Y8TNJ6K7a4uBBYoSJ59NhCC/oOgeW9bksHKLbdRF96X9lJFUoSIEI6G2471hM15maTqUcSSCNY89BoNzeEkz0xtyIAQe5vCBEWIqLZdCzrmrbJeiCk0vmePFZGRwALg28CQbt6Il5YFClea7LMxFb88sUvLaAHKS8tZe8FaP1qaF8lSgiS+ea/asJ0rlm/M6j4loSDFoQB7mzoGHdv9bQqJbzuzReRO4HjgQ5zexAXAK1m30PgvTfbZmJGtkYwFkFLZ2bizS88rFLFgkG44aN6U0VkVVAJnAjxVfirb/W16Ii/vGMOAINAA7AF2F1J1O9M51QeD1ASjXUoqODKUdXqvvEtMCZLMlXOO9SUZYTKjfMqMa0x38pLr6XxoKzY0B3hcRIKqOibXjTP+q5q1BB69kmVHDKCuKG5TWobhqOJolOq9DTluXWGI73n4mVOqJBTkyjnH2iS36XG8TGZ/BpgFnIqT+vt54GlVvTv3zfPG5ig6afMKeGQRNDu7smtLB3D1iGFEUwULVUpU6a/KvmARI0tHcuqYU3lq21O9alVUKn4mIiyzFOemgPi56unHwFM4wWFH2pPzxAJFF8UFjNrSAR3rXHQi0WCxhKg55cZeGyxiEjfwiZB00rqzbJLb5IOvq55E5EgglgTwRVX9e5bt85UFiiy5AaM20NK2s1sgdQ8jhQDCd2fd1OuDRTJ+FFm69XOTk/YqbKjK5IqfPYoLgR8AT+DszJ4FXKmqK31opy8sUPgkbnltxdgxXVpG2xv2W3SVH5lrA+LU/o7ty0g21BUKCAOLi2hoClvgMFnxbXksTl6nk2O9CDcp4KM49a1NbxK3vHbkb0/JuDEvmZZIC1c/czVAnwsWsTfrbHoWsbyOsWCTLOSEo9o23LW9oZmr/vBqu/sb4zcvayQDCUNN9R6fZ3qw6r0NFEe7liQ4qlFqnquh9r1an1tV+GKZa0eXlSA4k9ehYLYlldJrDke4YvlGxi6uZebSdZbt1vjOS4/if90KdL9zf/4c8HDummQKQdWubVBa0q4296GA0CziaYK7J5RZzZVk5Vuz3e3tlfUwTC54ncyej5PvSXDyPD2Q64Z1hs1R5ECKJIO1pQPaBY+mYIBwhsARn6G2Ly2rjZdN5tquCIrwwwWTLFiYtPxe9TQS+AROwaKXVLWgcjlYoMiBZEkGE0mA2gHF6fdgeBDLWNubA0ayVVGhgBAKCk3h3NUBs7KuJh3fSqGKyFeAF4HzcfI8veBmlDW9WcUCmHsbDD4KECgZ6nwhzrH5d8D5P6PqkPLdXfVdns8AaDjYwOKnFzPrvlm9dl4jce5idFkJN184iddvPIdbPzeZ0W5qj6AbcBP/7Opch5V1NX7wsjz2LeBTqlrv/jwMpzbFsd3QPk+sR5FHm1fAA5dTO6B/1j0L6Dsb97oi22p9yTLl2v6Mvs3PfRSPAeeo6iH3537Aw6p6pi8t9YEFijxzh6kqRg/vcgrzeOWhwaz94jM+NKx3i08t4lVQhKhq0kBjqUT6Hj/3UWwH/iwiD+Is6z4PeFFEvgmgqj/KqqWm54vtvXjpOuqKupbCPN7OQw3Uvlfb6yrs+S1+dZXXneGx/RnJCjTF6oNboDCJvPQorkv3uKpe72uLusB6FIWh9uZR1Awf0jFfVEx8byNdHqkkj8V2fEPvK9Hql1UbtnepSl+if55xNI+/uattSOr0CSPa/WxDVL2H7xXuCpkFigJxy4nUtta3LZ8d2Rqhem8DVY1N7ZbVjmyNcGpTE/9bWsq+YMBz4sFk+nLKkFT8Chip2BBV72GBwnQ/L0tqE2RMce5BTy/Rmiu53OhXVhKitH+R9TJ6OD/nKIzxJlaGNb5u98cr4Z21zuY9CYJGnGW2hw5A5BBVjU1cNWJYVrfd2VjnQ+N7Hz/KuqbS0Bxu67HE7waH9KVmTc9kPQqTH3GZaiuPKs9qErw8oqy9ZIuPjes9/Eh/7pUARUHpsGTXNv0VrqyHnkTkdtIU81LVhV1vnr8sUPRsSSfBvVKlLBpl8eybk85T2Oqp7Pdf+MUCRuHxY+jJ3nlNt6gqGgq7nUnwuqIgAZxcMQHSFE+KrYwSoSEYpOa5GudacUGg9r1aap6roSXSAkBdY13S83q7ZEkK44eHTp8wgj9uqsvZ5HfM3qawJSzsoWzoyeRfiknwVOVZBZJu7AtIgO+e8l2qxldR+14tVz9zNVHtmFrEJr+Tiw8gAbdwUi5YwsLC4efO7BHAIuB4oDh2XFW7XOBXRG4G5gKHgHeBL6tqg/vYVcC/AhFgoaquyXQ9CxS9QNucRdyktwSpHdC/w3Lbq0YMy3oH+NJZS9sCSl8fmkom13MbiVX6bK9GfvgZKNYCy4FvA5cDFwG7VHVRFo2rBNapaquIfA9AVReJyPE4dS+mA6NwKukdo6pp/7VaoOilUvQ0KseMoi6U3YK9IikiFAjRHGl/bduXcVimuY1QQEDI2XyHzWnknp+B4mVVPUlENqtqhXvsSVU9zaeGng9coKpfcnsTqOpN7mNrgBpVfT7dNSxQ9GJJehpJh6R8ZENTySVLIggk3dznZwp1Cxi542egeEFVZ7hv2rcBO4CVqvqPPjV0NbBcVe8Vkf8GXlDVe93H7gIeUdUO9blF5DLgMoCjjz76pPfff9+P5piewN0B7ke22mQE2HzRqxnPM4elykS7asN2vrViky/zHQFxaoqPtqEq3/gZKD4DPA0cBdwOHAFcr6oPZXjeo8DIJA9do6oPuudcA0wD5quqisiPgecTAsXDqnp/untZj6KPcYekavtJTnoWti/DX921lyM+kFjg8Ma3ndmq+kf3233A6V4bkCkNuYhcBHwGOEMPR6ttOAEpZgxOD8aYw9wd4FUPXA6793DT0CGpc0apUqJKq0jGkq2x83cGYOI9EykvLbfJbR/E3rD96lmkEnUvbXXD/ZcyUIjId1T1+6k23mWz4U5EzsZZSXWaqjbFPfQQ8FsR+RHOZPbHcarrGdNeLFisXkjVB9vbkg7G78MoT0hKmHaoKm5fRuwfe/y+C7CstdmIvWF31y7xWMp0sJQifki3M3uuqq52P/l3oKr3dPmmIn8B+gP17qEXVPVy97FrgEuAVuAKVX0k0/Vs6KkPcyvskW5hXKgUomEqjjoy+bLadCnPgcH9BnMwcrBt4x7Y6qiu8rKSKpd1xC3zbXuWPdb0Hamy1pYMhXO+5/Q+Nq+gMlVhpQyBIpX4DX6ma9JNgucqVfroshKeXdzlbWC9ih+5nlaTPtfTuV1vnr8sUJj4JIMMHgNnLDmczdaVLKdUcTRKsSoNwWBWt7f5jNzoSrnXTAT469Kqdtfvq0NTfgSK2D6J+Tirl+51f/4CsFVVr/ajoX6wQGE8SVFYCUi9eir2/8NDj8OGo3Ir2eqp2A7vvU2d73mUWd1wX5fHPqWqp2Y6lk8WKIwnyYaoQiVQVEJtoKVtMrxNF4ajbLNebqXrAYxbXJt6CKQT+lIuKj8DxRtAlaq+5/48Dmdvw3G+tNQHFiiMZ8mGqKBdAMk2RUgsj5TpXjOXrvN1iKov7MvwM1CcDfwceM89NBb4Ny/J+rqLBQqTtc0r4JFF0LyHirFHZZV0MDYEBcmX1FoiwtzI9ca+3hg4fF31JCL9gQnuj2+q6sEs2+crCxTGN+lWR8XE/5/pRECxRIS5l2xoKlflYBNzUPXEiXG/A8WJdEwz/qusWugjCxTGT0kr7rn/TxI38S0eMaxLcxmJbKlt7uSypxGb/IaOmwnjHyvUAOLn0NN1wGycQPEwcA7wjKpe4EM7fWGBwvgqxeqoqsamhBOFyjHlWac8j1fWv4zF0xdbwPBZ/DJbIc26f5+VlYQ42BpNGkAKIVj4GSheBSYBG1R1kogcCdypqnP9aWr2LFAYX6XawBcTKoG5twFQu/Yb1Awr8zUxYeJQlM1p+C9f5WBjCmVllZ+B4kVVnS4iL+MkBdwPbFHVE/xpavYsUBjfxa+OKhniHGve23EzX00ZtaUlvg1BeWFzGrnTnT2PQuhZ+Bko/ge4Gvg88C3gALBRVb/sR0P9YIHC5M0tJ8K+D3yputcZsZ3g1tPIrVwHjqAIUdUO6Uti9wy6tctztdLKl0AhIgKMUdUP3J/HAkeo6maf2ukLCxQmb9LVxkiV4ryzqc87weY4csvPQkyJMiVEzEUPxGugSDuw6taJWBX389ZCCxLG5FXFAph7G1XRYmp276E83IqoUh5uZemuel58fxs37qpPeXxwa6T9ctssNRxsoOa5Gmrfq/XtmuaweVNG88MFkygJJc8Nlk3YD0c1bdbc+NTp3c3L0NOPgV+q6kvd06TOsx6FKQjx9b07MVCRsVZGF1gqkdxKNTx0+oQR3P/y9pzX3PBrSMrPOYrXgWOA94FG3P8BqlrRpZblgAUKU5DiA4cEnZoZsT8HHwUfr4TXHoDmPdSWDvC9rKulEsmPdEHk3hf+lpN7Jm7+88rPQPEPyY6r6vudalEOWaAwPZpbfKl2QP8OVfra/dmFHocgKEpAAkQ1aunQ88zvfFTxujKHYYWLjOlJMuzdSNvj6GThpUy5qEzu5DofVWeLMnkNFN23ns8Yk1psX0aKOY7YrvBkdcFPbWpi+RGDPAeLlkgLi59e3O5YfH1wCxa5E/u0n64cLBweSupsnqodOeqtWI/CmEKVmBI9bk4jkV/7OGwSvPulSybY2R5IrnoUFiiM6WmSTJL7NRkuwOaLXvWnncYXySbHk62pszmKDCxQmD7PTSVy09Ah7AsGupxOJKDKhRM+z1PbnrK5iwLnR1pzCxTG9CVuKhFwJr4T5zKArHJRxVZP2aqp3sUChTF9STarpjqpWEKcd8z8pL0Oy3Tbs1igMKaviSvnmkwuehoxxcFizvun83jwLw/SEmlp95jlnypcFiiM6auS7QhPobuy3lpq9MJkgcIYc1jcHEa8XKQOScWW3hYeX7LHGmN6iTOWOJX54oVK2rLeJs1i6/OHyLrGOiruqaByZaVlt+1hbGe2MX1Bu53f2w5X6gOqVi+k6oPtbXMYsTrhpzY18dSAAdQVuSm1fZjLULTdLnBw0ojUNdZZPqoCZkNPxvR1blLCdHMZSYeo0uWY8ph/KhQIEY4mr1Ntk+C5Z7mejDHexHobaZbXxueaiu9xPDhoYPL5DRFPwSJVkIDDRZjgcP6pxOW3p4451TYHdoO89ChE5GZgLnAIeBf4sqo2iMhZwFKgn/vYlaq6LtP1rEdhjA+S5ZZ6Z63zc8kQOHQAIofaPaW2dACLRwxLHhA6mdXWL9YT8a6gVz2JSCWwTlVbReR7AKq6SESmAB+q6g4RORFYo6oZ96RboDCmGyQGkkON0Lwn5RLbgKqvVfs6w5bjelPQq55Uda2qtro/vgCMcY9vUNUd7vHXgGIR6Z+PNhpjElQsgG9sgZoG589zvgehEqr3NlAcbV/ruTga5cKP9nc43l1aIi0se2VZXu7dGxXCHMUlwPIkxz8LbFDVg93cHmOMF+7cRtUji2D3nnbzF9UfNVH1UQNTDh5qO35EJEpTMEA43QQ4+DZctbOxzpfrmBwOPYnIo8DIJA9do6oPuudcA0wD5mtcQ0TkBOAhoFJV301x/cuAywCOPvrok95/v2AqsxrT9yQOS52xJK4I02G1pQOSZrgtjkap2e2kHrl6xDBfhqxiQ1+27Da1gp6jABCRi4DLgTNUtSnu+BhgHc4E97NermVzFMYUoDSJChP3bFTvbWhbWeXLbvEUE+k2d9FeQS+PFZGzgUXAaQlBogyoBa7yGiSMMQUq2SY/t0pfVeOetsAAQCAEJUOheU/Ksq+xPwcnGcIqikYJAc0iToBI0SNpibRw9TNXA1i2207I16qnvwD9gXr30AuqermIXAtcBbwTd3qlqv493fWsR2FMD5NsqCoWWDKkTIfkPZJlQ8p8SXDYl2pvFPzQk58sUBjTy8RnwE1a+DNBIETF0SNRn5fj9vahqoJeHmuMMWm1LcXd5yzHnX8HDD4KEOfPaf/a/ud5/8PIHKzEjQ1V9fUkhtajMMb0CrU3j6Jm+JCO+ajAlyW3vXHHd0FPZhtjjN+qiobC7voOcxeALzU3kuWe6issUBhjeoczljgp07ftSPpwsv0bbTzmpYrt+LZAYYwxPVG75bhxZWBLhlLVcqBdzY34Zbflnay9kWnHd2/McGtzFMaY3s9DzY2YjBv+3N5H4o5vgJv+fBP7Du1Le/1CWklly2ONMSZeqv0ZEgCNHu6BkDrViF9iQSbf6UVsMtsYY+KlKgcbOx6zeUW78rB+5Z6KF9Vouz+TlYctpKEq61EYY0yizSvgkUXQvIeKsUf5vpGvs3K1NNc23BljTFdVLIBFf4X5dzAykv8P07Glufna+GeBwhhjUqlYQHWLdK0Ak8+jNfncJW6Bwhhj0qjatY2a3XsoD7eCKgFVJwjEvuK5x8rDrXzuo/2Uh1uR2HN8ENVoXnoWNkdhjDHp3HJihwJMMenqaiSe12HJrQ/pRbJdLWVzFMYY44czlkCoJOlDVY1NrN22g81bP2Dtth1UHdK4hIU4S26BqqaDbb0ScXscS3fVkzZEePgQX9dYx+KnFzPrvlk57WXY8lhjjEkn3bLadHU14sWW3CakF0lVQyOgyoUf7ef3RwzytDQ313mobOjJGGO6Q5KgUrvmig4Zb2P1w6samzpdFra8tJy1F6z13CTbcGeMMYWkYkGH3kbVYzckzXhb1XQQpv0rVevvAvC86S9THqquskBhjDH5kizjbagEzv+pE1TeWUuVO5HupWeRqz0fNpltjDH5UrEA5t7Wvlrf3NsO9zzcifSqxqZ2S3STLc0tjkaprt+Tk2Zaj8IYY/IpyZBUu8cAHllEVeOedktvky7NLRqWkyZaoDDGmEIWCyRtk+EfAEJVY1P7PRuhEpizJCdNsEBhjDE9QWLPw+vSXB9YoDDGmJ4o3ZCVz2wy2xhjTFoWKIwxxqRlgcIYY0xaFiiMMcakZYHCGGNMWr0iKaCI7ALe7+TThgO7c9Cc7mSvoTDYaygM9ho67x9UdUSmk3pFoOgKEVnvJWtiIbPXUBjsNRQGew25Y0NPxhhj0rJAYYwxJq2+HCh+nu8G+MBeQ2Gw11AY7DXkSJ+dozDGGONNX+5RGGOM8aBXBwoROVtE3hKRv4jI4iSP9xeR5e7jfxaRsd3fyvQ8vIaLRWSXiGx0v76Sj3amIyJ3i8jfRWRLisdFRG5zX+NmEZna3W3MxMNrmC0i++J+D7nJ95wFETlKRB4XkTdE5DURqU5yTkH/Ljy+hoL+XYhIsYi8KCKb3NdwfZJzCuu9SVV75RcQBN4FxgP9gE3A8Qnn/DvwU/f7zwPL893uLryGi4H/zndbM7yOU4GpwJYUj/8f4BFAgBnAn/Pd5i68htnAH/PdzgyvoRyY6n4/CHg7yb+ngv5deHwNBf27cP9uB7rfh4A/AzMSzimo96be3KOYDvxFVd9T1UPAfcB5CeecB9zjfr8SOEPEQwXz7uPlNRQ8VX0KSFej8TzgV+p4ASgTkfLuaZ03Hl5DwVPVOlV9xf1+P/AGMDrhtIL+XXh8DQXN/bs94P4Ycr8SJ4sL6r2pNweK0cAHcT9vo+M/qLZzVLUV2AfkppZg13h5DQCfdYcJVorIUd3TNF95fZ2F7pPucMIjInJCvhuTjjuUMQXn02y8HvO7SPMaoMB/FyISFJGNwN+BP6lqyt9DIbw39eZAkSz6JkZtL+fkk5f2rQbGqmoF8CiHP4X0JIX+e/DiFZx0CJOA24FVeW5PSiIyELgfuEJVP0p8OMlTCu53keE1FPzvQlUjqjoZGANMF5ETE04pqN9Dbw4U24D4T9djgB2pzhGRImAwhTW8kPE1qGq9qh50f7wDOKmb2uYnL7+rgqaqH8WGE1T1YSAkIsPz3KwORCSE8wb7G1X9Q5JTCv53kek19JTfBYCqNgBPAGcnPFRQ7029OVC8BHxcRMaJSD+cCaGHEs55CLjI/f4CYJ26s0cFIuNrSBg/PhdnzLaneQj4v+6KmxnAPlWty3ejOkNERsbGkEVkOs7/rfr8tqo9t313AW+o6o9SnFbQvwsvr6HQfxciMkJEytzvS4AzgTcTTiuo96ZeWzNbVVtF5GvAGpzVQ3er6msicgOwXlUfwvkH92sR+QtOtP58/lrckcfXsFBEzgVacV7DxXlrcAoi8juclSjDRWQbcB3OBB6q+lPgYZzVNn8BmoAv56elqXl4DRcAXxWRVqAZ+HyBfegAmAn8C/CqOz4OcDVwNPSY34WX11Dov4ty4B4RCeIEsRWq+sdCfm+yndnGGGPS6s1DT8YYY3xggcIYY0xaFiiMMcakZYHCGGNMWhYojDHGpGWBwhhjTFoWKIzxgYjUiMi3fb7mShEZn8XzxyamRReRl0Wkn4gcSPU8D9f9mogU2v4Kk0MWKIzJM3cXdCDh2AlAUFXfS3J+sIv3GQtsdzMRZ+NuYGGW1zA9iAUKk3ci8h0RWeh+f4uIrHO/P0NE7nW//4mIrI8v9CIi54jIirjrzBaR1e73lSLyvIi8IiK/d5PIISJbReR69/irIjLBPd6uRyAiW9xP5GNF5E0RudM99hsROVNEnhWRd9wUETGTRGSde/zSuGtdKSIviZPhN9b2seIU3/kfnCR2iVl/vwQ8GHeNAyJyg4j8GScz6hL3mltE5OdxKStOEidr6vPA/0u45jnA/yb83Q93/56q3L+/J0VkhYi8LSJLReRL4hTZeVVE/hFAVZuArQmv3fRiFihMIXgKmOV+Pw0YKE7it1OAp93j16jqNKACOE1EKoA/ATNEpNQ953PAcnESwF0LnKmqU4H1wDfj7rfbPf4TwMtw0T8By9x7TwC+6Lbt2zjpI2IqgCrgk8ASERklIpXAx3Fqi0wGThKRU93zj8Wp/TBFVd9PuOdM4OW4n0txiiZ9QlWfwSlWdbKqngiUAJ9xz/sFsFBVP5nkdZxNXKAQkSOBWmCJqta6hycB1cBEnFQZx6jqdOBO4Otx11rP4d+Z6eUsUJhC8DLOG+gg4CDwPE7AmMXhQLFARF4BNgAn4FQ1a8V545srTobNKpxP4TOA44Fn3XxAFwH/EHe/WMbRl4GxHtr3V1V9VVWjwGvAY27uoFcTnv+gqjar6m7gcZzgUOl+bcDpOUzACRwA77vFgZIpB3bF/RzByZgac7o4JTJfBT4NnCAig4EyVX3SPefXsZPFSSo5Jm4oKwQ8BnxHVf8Ud92X3OJAB3GqK651jye+1r8Do1K03fQyvTYpoOk5VDUsIltxEtA9B2wGTgf+EXhDRMbhfHo/WVX3isgvgWL36ctxhlj24LzJ7XeHYf6kql9IcctYWvYIh/8PtNL+g1NxkvMBonE/R2n/fygxcZri1BW4SVV/Fv+AO1/QmKJ94CSzi29Di6pG3OcWA/8DTFPVD0Skxj1XkrQhZhbwTNzPrTiBcg7wZNxxr6+12G2j6QOsR2EKxVM4weApnF7E5cBG95P7EThvqvvc4ZJz4p73BE4t60txggbAC8BMEfknABEZICLHZLj/Vvc6iMhUYFwXXsN5IlIsIsNwMs2+hJP595K4OZLRIvIxD9d6A2fIK5lYANntXvcCaKttsE9ETnEf/1Lcc87GqYUdo8AlwAQRWeyhPYmOAbZkPMv0ChYoTKF4Gme45XlV/RBocY+hqptwhm5ew1lx82zsSe6n7D/iBI8/usd24aRb/52IbMYJHBMy3P9+YKg7VPVV4O0uvIYXccb8XwBuVNUdqroW+C3wvDtMtBIY5OFatTjBpgM3INyBMxy0CicgxXwZ+LE7mR3/iX827XsOsb+7z+MMY/27hzbFm4lTUdH0AZZm3JgCJE5Bm8eBmbEhpyyuNQa4Q1XPyXiyt+tNAb6pqv/ix/VM4bNAYUyBEpE5OJXc/pbvtsQTkbOAd1R1a77bYrqHBQpjjDFp2RyFMcaYtCxQGGOMScsChTHGmLQsUBhjjEnLAoUxxpi0/j8kigQkiY59sgAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "k1, Phi1, sigma_Phi2 = grid.radial_spectrum(subgrid, taper=None)\n", - "k2, Phi2, sigma_Phi2 = grid.radial_spectrum(subgrid, taper=np.hanning)\n", - "k3, Phi3, sigma_Phi2 = grid.radial_spectrum(subgrid, taper=np.hamming)\n", - "\n", - "\n", - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial power spectrum\")\n", - "ax1.plot(k1, Phi1, '-o', label='none')\n", - "ax1.plot(k2, Phi2, '-o', label='hanning')\n", - "ax1.plot(k3, Phi3, '-o', label='hamming')\n", - "ax1.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Azimuthal power spectrum\n", - "\n", - "The azimuthal spectrum computes the FFT on a square window that is polarised at a range of radii. Subdividing the transforms into bins by azimuth is useful to explore linear trends in the magnetic anomaly that may align with a particular foliation or strike orientation.\n", - "\n", - "```python\n", - "azimuthal_spectrum(subgrid, power=2.0, theta=5.0)\n", - "```\n", - "\n", - "`theta` controls the bin size of each azimuth (in degrees). The FFT of the magnetic anomaly is raised to the `power=2` (default) which is compatible with Bouligand _et al._ (2009) computation of Curie depth." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "k, Phi, theta = grid.azimuthal_spectrum(subgrid, theta=20.0)\n", - "\n", - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, ylabel=r'azimuth $\\theta$', xlabel='wavenumber (rad/km)')\n", - "im1 = ax1.pcolor(k, theta, Phi)\n", - "fig.colorbar(im1, label='radial power spectrum')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plotting each azimuth on the power spectrum-wavenumber axis highlights the different in slope for different polarisations of the magnetic anomaly." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial power spectrum\")\n", - "\n", - "for i, angle in enumerate(theta):\n", - " ax1.plot(k, Phi[i], label=r'$\\theta = {}^\\circ$'.format(angle))\n", - "\n", - "ax1.legend(bbox_to_anchor=(1,1))\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pycurious/Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb b/pycurious/Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb deleted file mode 100644 index ca8607d..0000000 --- a/pycurious/Examples/Notebooks/Bouligand/Ex2-Compute-Curie-depth.ipynb +++ /dev/null @@ -1,265 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example 2 - Compute Curie depth\n", - "\n", - "Bouligand *et al.* (2009) formulated an expression for the radial power assuming a fractal model for crustal magnetisation (see [Ex1-Plot-power-spectra](./Ex1-Plot-power-spectrum.ipynb) for analytic solution), which depends on 4 parameters:\n", - "\n", - "- $\\beta$ - a fractal parameter\n", - "- $z_t$ - top of magnetic sources\n", - "- $\\Delta z$ - thickness of the magnetic layer\n", - "- $C$ - a field constant\n", - "\n", - "These parameters can be fitted to the radial power spectrum computed from FFT to determine Curie depth ($z_\\mathrm{curie} = z_t + \\Delta z$).\n", - "\n", - "### Contents\n", - "\n", - "- [Plot radial power spectrum](#Plot-radial-power-spectrum)\n", - "- [Optimise parameters](#Optimise-parameters)\n", - "- [Varying window sizes](#Varying-window-sizes)\n", - "- [Add prior constraints](#Add-prior-constraints)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib inline\n", - "\n", - "import pycurious" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# load x,y,anomaly\n", - "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", - "\n", - "nx, ny = 305, 305\n", - "\n", - "x = mag_data[:,0]\n", - "y = mag_data[:,1]\n", - "d = mag_data[:,2].reshape(ny,nx)\n", - "\n", - "xmin, xmax = x.min(), x.max()\n", - "ymin, ymax = y.min(), y.max()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plot radial power spectrum\n", - "\n", - "The radial power spectrum is computed from a square window of the magnetic anomaly. Methods to select window sizes and compute the Fast Fourier Transform (FFT) belong to the `CurieGrid` object. We apply the default `np.hanning` taper to the power spectrum as in [Ex1-Plot-power-spectrum](#./Ex1-Plot-power-spectrum.ipynb).\n", - "\n", - "By default the FFT of the magnetic anomaly is raised to the power 2:\n", - "\n", - "```python\n", - "grid.radial_spectrum(subgrid, taper=None, power=2.0, **kwargs)\n", - "```\n", - "\n", - "which is compatible with Bouligand _et al._ (2009) computation of Curie depth." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax)\n", - "\n", - "# pick the centroid\n", - "xpt = 0.5*(xmin + xmax)\n", - "ypt = 0.5*(ymin + ymax)\n", - "\n", - "window_size = 200e3\n", - "subgrid = grid.subgrid(window_size, xpt, ypt)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid)\n", - "\n", - "# plot radial power spectrum\n", - "plt.plot(k, Phi, '-o')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimise parameters\n", - "\n", - "The `optimise` routine determines the Curie parameters ($\\beta, z_t, \\Delta z, C$) by minimising the misfit between the analytic power spectum and the computed power spectrum. Optimisation is handles by the nonlinear least-squares algorithm implemented in `scipy.optimize.minimize`.\n", - "\n", - "```python\n", - "optimise(window, xc, yc, beta=3.0, zt=1.0, dz=10.0, C=5.0)\n", - "```\n", - "\n", - "These are sane defaults to initiate the optimisation, however, values close to the final solution will result in faster convergence and stability." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "beta, zt, dz, C = grid.optimise(window_size, xpt, ypt)\n", - "print(\"beta = {:.2f}\\n zt = {:.2f}\\n dz = {:.2f}\\n C = {:.2f}\".format(beta,zt,dz,C))\n", - "\n", - "Phi2 = pycurious.bouligand2009(k, beta, zt, dz, C)\n", - "\n", - "# plot radial power spectrum - compare analytic against computed\n", - "plt.plot(k, Phi, '-o')\n", - "plt.plot(k, Phi2, linewidth=2)\n", - "\n", - "print(\"\\nCurie depth is {:.2f} km\".format(zt + dz))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Varying window sizes\n", - "\n", - "Here, we observe the effect decreasing the window size has on the optimisation of Curie parameters. The number of points in the power spectrum decrease with smaller window sizes, particularly at low wavenumbers (long wavelengths). This significantly reduces the quality of Curie depth determinations for window sizes less than 200 km.\n", - "\n", - "As a rule of thumb, the window size should be > 4 times the maximum possible Curie depth that could be resolved from a study area." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "# window size in kilometres\n", - "window_size_range = [304e3, 200e3, 100e3, 50e3]\n", - "\n", - "\n", - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111)\n", - "\n", - "for i, window_size in enumerate(window_size_range):\n", - " \n", - " # compute radial power spectrum\n", - " subgrid = grid.subgrid(window_size, xpt, ypt)\n", - " k, Phi, sigma_Phi = grid.radial_spectrum(subgrid)\n", - " ax1.plot(k, Phi, '-o', color='C{}'.format(i))\n", - " \n", - " # optimise Curie variables\n", - " beta, zt, dz, C = grid.optimise(window_size, xpt, ypt)\n", - " print(\"beta = {:.2f}\\n zt = {:.2f}\\n dz = {:.2f}\\n C = {:.2f}\\n\".format(beta,zt,dz,C))\n", - " \n", - " Phi2 = pycurious.bouligand2009(k, beta, zt, dz, C)\n", - " ax1.plot(k, Phi2, color='C{}'.format(i), linewidth=2, label=\"window size = {}km\".format(window_size/1e3))\n", - "\n", - "ax1.legend()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Add prior constraints\n", - "\n", - "Spurious Curie depth values can often be obtained if windows of the magnetic anomaly do not contain long wavelengths. This often arises from selecting too small a small window size, but can also occur from uncorrected artefacts in the magnetic anomaly, or simply the absence of magnetic minerals close to the surface of the crust. Adding *a priori* information on $\\beta, z_t, \\Delta z, C$ can reduce the incidence of anomalous values if there is reasonable grounds to do so.\n", - "\n", - "> **IMPORTANT:** strict priors can unfairly bias the results - *only* add priors if you have good reason to do so. (This is **not recommended** for most use cases.)\n", - "\n", - "```python\n", - "from scipy import stats\n", - "\n", - "# define normal distributions\n", - "beta_p = stats.norm(3.0, 1.0)\n", - "zt_p = stats.norm(0.0, 1.0)\n", - "dz_p = stats.norm(10.0, 8.0)\n", - "C_p = stats.norm(10.0, 10.0)\n", - "\n", - "# add to grid object\n", - "grid.add_prior(beta=beta_p, zt=zt_p, dz=dz_p, C=C_p)\n", - "```\n", - "\n", - "Priors can be accessed from a dictionary:\n", - "\n", - "```python\n", - "prior = grid.prior_pdf['beta'] # stats.norm object\n", - "prior = grid.prior['beta'] # stats.norm object arguments (3.0, 1.0)\n", - "```\n", - "\n", - "A combination of priors can be added. `reset_priors()` removes all prior information." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grid.reset_priors()\n", - "\n", - "from scipy import stats\n", - "beta_p = stats.norm(3.0, 0.01)\n", - "grid.add_prior(beta=beta_p)\n", - "\n", - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111, xlabel=\"wavenumber (rad/km)\", ylabel=\"radial power spectrum\")\n", - "\n", - "for i, window_size in enumerate(window_size_range):\n", - " \n", - " # compute radial power spectrum\n", - " subgrid = grid.subgrid(window_size, xpt, ypt)\n", - " k, Phi, sigma_Phi = grid.radial_spectrum(subgrid)\n", - " ax1.plot(k, Phi, '-o', color='C{}'.format(i), label='{} km window size'.format(window_size*1e-3))\n", - " \n", - " # optimise Curie variables\n", - " beta, zt, dz, C = grid.optimise(window_size, xpt, ypt)\n", - " print(\"beta = {:.2f}\\n zt = {:.2f}\\n dz = {:.2f}\\n C = {:.2f}\\n\".format(beta,zt,dz,C))\n", - " \n", - " Phi2 = pycurious.bouligand2009(k, beta, zt, dz, C)\n", - " ax1.plot(k, Phi2, color='C{}'.format(i), linewidth=2)\n", - " \n", - "ax1.legend()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pycurious/Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb b/pycurious/Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb deleted file mode 100644 index 8b2660b..0000000 --- a/pycurious/Examples/Notebooks/Bouligand/Ex3-Posing-the-inverse-problem.ipynb +++ /dev/null @@ -1,719 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example 3 - Posing the Inverse Problem\n", - "\n", - "It is notoriously difficult to estimate uncertainties from Curie depth determinations. Intuitively, the fewer points in the power spectrum should result in higher uncertainty, but it is difficult to quantify these uncertainties in practise. For [Bouligand *et al.*, 2009](http://doi.wiley.com/10.1029/2009JB006494) it is difficult to determine the values of $\\beta$ and $\\Delta z$ since both control the slope of the power spectrum at low wavenumbers. Similarly, for [Tanaka *et al.*, 1999](http://linkinghub.elsevier.com/retrieve/pii/S0040195199000724) the lower and upper ranges of the power spectrum used to compute $z_b$ and $z_t$ is highly subjective and can result in significantly different Curie depths.\n", - "\n", - "Here, we pose the problem of finding the Curie depth from the radial power spectrum within a Bayesian framework. Thus, we can effectively estimate the uncertainty of our Curie depth estimates.\n", - "\n", - "### Contents\n", - "\n", - "- [Bayesian inverse framework](#Bayesian-inverse-framework)\n", - "- [Metropolis-Hastings algorithm](#Metropolis-Hastings-algorithm)\n", - "- [Appraising the ensemble](#Appraising-the-ensemble)" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from scipy import stats\n", - "%matplotlib inline\n", - "\n", - "import pycurious" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# load x,y,anomaly\n", - "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", - "\n", - "nx, ny = 305, 305\n", - "\n", - "x = mag_data[:,0]\n", - "y = mag_data[:,1]\n", - "d = mag_data[:,2].reshape(ny,nx)\n", - "\n", - "xmin, xmax = x.min(), x.max()\n", - "ymin, ymax = y.min(), y.max()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax)\n", - "\n", - "# pick the centroid\n", - "xpt = 0.5*(xmin + xmax)\n", - "ypt = 0.5*(ymin + ymax)\n", - "\n", - "window_size = 200e3\n", - "subgrid = grid.subgrid(window_size, xpt, ypt)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Bayesian inverse framwork\n", - "\n", - "Geophysical inversion often casts problems in a Bayesian framework, where information on input parameters are represented in probabilistic terms. The solution is given by _a posteriori_ probability, $P(\\mathbf{m}|\\mathbf{d})$, which is proportional to the product of the likelihood function $P(\\mathbf{d}|\\mathbf{m})$ and the _a priori_ probability $P(\\mathbf{m})$,\n", - "\n", - "$$\n", - "P(\\mathbf{m}|\\mathbf{d}) \\propto P(\\mathbf{d}|\\mathbf{m}) \\cdot P(\\mathbf{m})\n", - "$$\n", - "\n", - "The likelihood function is the probability of reproducing the data $\\mathbf{d}$ given a particular model $\\mathbf{m}$, and the _a priori_ model is what we know about the model before assimilating the data. The posterior probability can be evaluated through an objective function, $S(\\mathbf{m})$, which jointly compares the misfit between data and prior information, $\\mathbf{m}_p$,\n", - "\n", - "$$\n", - "P(\\mathbf{m}|\\mathbf{d}) = A \\, \\exp (-S(\\mathbf{m}))\n", - "$$\n", - "\n", - "where $A$ is a constant. We seek the maximum _a posteriori_ (MAP) estimate, which may be obtained by minimising the $\\ell_p$-norm objective function if data and prior information are both uncorrelated,\n", - "\n", - "$$\n", - "S(\\mathbf{m}) = \\frac{1}{s} \\sum_{i} \\frac{\\vert g^i(\\mathbf{m}) - d^i \\vert^s}{(\\sigma^i_d)^s} + \\frac{1}{r} \\sum_{j} \\frac{\\vert \\mathrm{m}^j - \\mathrm{m}^j_p \\vert^r}{(\\sigma^j_p)^r}\n", - "$$\n", - "\n", - "where $\\mathbf{g}(\\mathbf{m})$ is the forward operator that forms an explicit link with the data. In our case $\\mathbf{g}(\\mathbf{m}) = \\Phi$ and the objective function quantifies how well the analytic expression for the power spectrum, computed from model parameters $\\Phi(\\mathbf{m})$, fits the power spectrum computed from the magnetic anomaly, $\\Phi_d$,\n", - "\n", - "$$\n", - "S(\\mathbf{m}) = \\frac{1}{s} \\sum_i \\frac{\\lvert \\Phi^i(\\mathbf{m}) - \\Phi_d^i \\lvert^s}{(\\sigma_{\\Phi}^i)^s} + \\frac{1}{r} \\sum_{j} \\frac{\\vert \\mathrm{m}^j - \\mathrm{m}^j_p \\vert^r}{(\\sigma^j_p)^r}\n", - "$$\n", - "\n", - "By default, PyCurious assumes Gaussian uncertainties on $\\Phi$ ($s=2$), and a uniform prior over a large range ($r=\\infty$) for each model parameter where $-\\sigma_p^j \\leq \\mathrm{m}^j - \\mathrm{m}_p^j \\leq +\\sigma_p^j$. The prior can easily be defined by the user as we will demonstrate later. We have already covered the `optimise` method that uses gradient-based optimisation to quickly iterate to the MAP estimate in [Ex2-Compute-Curie-depth](./Ex2-Compute-Curie-depth.ipynb).\n", - "\n", - "\n", - "> **Note:** The objective function can be accessed (and modified) under `grid.objective_function`." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "beta_opt, zt_opt, dz_opt, C_opt = grid.optimise(window_size, xpt, ypt, taper=None)\n", - "\n", - "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, taper=None)\n", - "\n", - "# define a range of parameters over which to analyse the misfit\n", - "# use the optimum parameters to help constrain an appropriate range\n", - "nb = 50\n", - "beta_range = np.linspace(beta_opt - 0.5, beta_opt + 0.5,nb)\n", - "dz_range = np.linspace(dz_opt - 5.0, dz_opt + 5.0, nb)\n", - "\n", - "# constant values for zt and C\n", - "zt = zt_opt\n", - "C = C_opt\n", - "\n", - "# misfit matrix\n", - "misfit = np.zeros((nb,nb))\n", - "\n", - "for i, beta in enumerate(beta_range):\n", - " for j, dz in enumerate(dz_range):\n", - "\n", - " m = [beta, zt, dz, C]\n", - " \n", - " misfit[i,j] = grid.min_func(m, k, Phi, sigma_Phi)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAs8AAAEaCAYAAADuabq2AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJzs3XucZHV57/vP09WXuQqDIMIAihGNSgzEEd3HfaIi6pgL6H6ZLRojJnjYZsuJHnUrGF9oUBOMiZdEduJEiCRRCQEvE88YRIFtPDsggxJgQMMICOMgMMwMt5npS9Vz/lhrhupaT3f/qmt1Va2q75tXvej69W9dqqb7t3696nl+j7k7IiIiIiKysJFen4CIiIiISFVo8iwiIiIikkiTZxERERGRRJo8i4iIiIgk0uRZRERERCSRJs8iIiIiIok0eRYRERERSaTJs4iIiIhIIk2epXRmtsXMXpbQ79lm9kMze9TM/iB1uzbO40/M7F0d7uP7Zva8ss5JRKRdZY+NZTOzu83slLK3bX3dzX27+Z50ei0xsw+b2Yfn+f4XzMzN7NNNbbr29DFNnqV07v48d782oev7gGvdfbW7/0XzdqmDsZn9tpndaGa7zWynmV1rZmvN7DDgLcDnOnktwJ8B53e4DxGpiHzs2Wtmj5nZ/Wb2t2a2qsP9LWpiuV8bY+pAme91L+Z6MRczW5NPXh8zsz1mtn3/ZLnEa0m7dO3pY5o8Sy89Ddiy2I3N7K3AHwFnAWuAZwH/AOwC3gpscve9HZ7jRuDlZnZEh/sRker4TXdfBfwK8ELgg704CTMbrfL2FXIC8KC7r3L3FcDvA58ys6Mo71rSLl17+pgmz5Ik/8v+f5jZzWb2uJldZGaHm9k387CLb5vZmqa+pzRt+34z+1ne78dm9gozuxp4OfDZ/K/9Z+3fzsz+HjgG+Of8e++b47TOBD7n7jd6Zoe7f97d9wCvAf5Xy2vw/PE+M7srv1v9PjP7P/Pz2m1mf9G8jbvvA24EXlXWeyki1eDuPwO+CRwPYGbPyT/d2p2HDZy6v+8c41w4lpnZkWZ2hZk9mI9Ff9C0n7vzfd0MPG5mo8GYOt95FLZvfV15n3PN7DYz25XfXV82z/HnPF6TF86xv3PM7Cf5+3Kbmb2ujW3nC+mY83qRX6uuaOn/l9YUFtHiBOCGpufX5/8fp6RrScv2/9nMbsmvpX8HLGvto2tPn3N3PfRY8AHcDVwHHA6sBR4AfgCcCEwAVwMfaup7Sv71s4F7gSPz508HfiH/+lrgbS3HOKX163nO6VvA7cB/BQ5t+d6DwAtb2jx/3A98Kf+6kT+/BNibt53Sst1fAJ/s9b+BHnrosfSPlnHoaLJPxz4CjAFbgQ+QTapOBh7Nx7j5xrlZYxnZTasbgfPy/TwDuBN4dVP/m/JjLw/Oac7zmGv7OV7jrXmfQ4D/D/hotP1Cx0vY328BR+av+w3A48ARbZzLKXP824Rf58+PyI9zcP58lOya9YI53o+/44nr18HARcBmwCjhWgJ8GPhw0/535d//DtkfZ/X8+adbjqNrT58+dOdZ2vGX7n6/Z3dj/hW43t1/6O6TwFfJJtKt6mST6+ea2Zi73+3uPynpfN4CXEkWG3a/mf2zmT0l/97BZAN85D3u/ibgp2SD4yXufgawKf9+6+t4NN+fiAyHr5nZbuB7ZHcd/xh4MbAKuMDdp9z9auAbwBtpb5x7IXCYu5+f7+dO4G+A05v6/IW73+txqMB855Gy/X6fzfvsBD42z/Ypx5tzf+7+T+6+3d0b7v6PwB3ASW2cS9vc/T7gu2QTd4D1wA53v3GOTU4A/oeZ7ST7w8bJQneccq8lAL+R73Mr2eT6NWR/rER07elTmjxLO+5v+npv8LyQVOPuW4F3kf3l/YCZXWpmR5ZxMu7+c3d/l7sfQzYYPx94f/7tXcDqOTa9Pf//7vz/P87/v3+AXNnSf3VTXxEZfK9194Pd/Wnu/t/zSeSRwL3u3mjq91NgbZvj3NOAI/OP9nfnk/QPkH2qt9+985zbnOeRuH3U56f5fqPvpRxvzv2Z2VvM7Kam13o8cGgb57JYlwBvzr9+M/D3USczmwCeA/yiux/i7r/g7m/LJ+BQ7rUEnnjf7sgn5wD/Mcf+de3pU5o8y5Jz9y+5+38mu2g48PGUzdo8xo3ALTwxWN1MlkAYqS/wvNVzgH9v53xEZOBsB442s+br5jHAz2Deca51LLsXuCufnO9/rHb3X2vqM9/4N+95JGy/39Et22+fY/uU44X7M7Onkd1VPxt4srsfTBaiYW2cS4ro9X4NeL6ZHU92t/eLc2x7PPC4u2+b4/tlXkvgifftODPb/z7MtX9de/qUJs+ypCxby/nk/K/7fWR3qFMGmPvJYgHn2u85ZvYSM5vIH28FXgb8bd5lE/DSjk6eA3clXgBc1em+RKTSrieLo32fmY1ZtsbwbwKXLjDOtY5l3wceyZPylptZzcyON7MXdnoebb6ed5jZUWZ2CNmd73/s8HjR/laSTWwfBDCz3yVPvlzkucylcL3wLOHucrKY5O+7+z1zbHsi86/6VMq1pMn/CzwMPBP4tpl9kyC8Q9ee/qbJsyy1CeACYAfwc+ApZIPjQv4E+GD+Ud97g+8/iWyi/BBwD1kiyivcfX+W9N8Bv2Zmyzs8/1PJ1qJu906IiAwQd58iGw9eQzae/U/gLe7+I+Yf52aNZe5eJ5t8ngDclW/zeeCgEs6jHV8iS7q+M398tMPjFfbn7rcBfw78G9kE95fIEgIXdS7zmOt6cUl+zDBkI3cC2d3wuZR1LQHA3XeRvZ9bgP9ENpG+Iuiqa08fsydCbkQGi5n9MfCAu8+1PFHKPq4HznT3+QZXEZHKMLO7yVY6+navz2UpmdkxwI+Ap7r7Ix3sp6NrieXVBd39w21so2tPHxuWBdBlCLl7yh3uhfbxojLORUREuieP0X43cGknE2co51qyiGPq2tPHNHkWERGRgWFmK8nCRH5Ktkxdr13b6xOQcilsQ0REREQkkRIGRUREREQSVT5sY2xipU+sOGRWmzWKd9MtusGeetd9qW/Ot6542U7HxG09+Rhph03llrhx6jFSX28nx0hVwXNZrK68BmDf/dt2uPthi9n21S9f6Q/tTFkFEW68efJKd++Hj3OHzrhN+LKwdoSIVM2j7OqbMdvM1gOfAWrA5939gjn6vR74J7Ky65vztnOBM8mWmPwDd79yvmNVfvI8seIQTjj5nbPaansbhX61yWKb1YO2aOJdL3H2HEwmPbj/7yPRRDnaNuiXuL+wLZoQ1RLPOdg2Pm5av2hy5rVyjxseI/G1xdsmvqedHCPlnDvYV9gv6Fb2MQC2fOLdP42/s7CHdtb5/pXHJPWtHXFHa4Uz6ZJlrORF9open4aIlODbfnlfjNlmVgMuBF4JbANuMLON+XKJzf1WA39Atn75/rbnAqcDzyOrbvltM3tWvqxkSGEbIjIQHGgk/iciIr1V8ph9ErDV3e/M1ya/FDgt6PcR4E/JihntdxrZqiyT7n4XsDXf35w0eRaRgeA4015PeoiISG+1OWYfamabmx5ntexuLXBv0/NtedsBZnYicLS7f6PdbVtVPmxDRGQ/3VUWEamONsbsHe6+bp7vR8GAByIO83W/PwW8td1tI5WfPBtgLe99GLcctI3MBO9NuG3wj5vy7514X9+iWOYo0S6MAV58bHRyvHQ9LY433F9yjHLwb9FBfLgHGaJRvHR6THHiv0fwOjqLZU48bmtb2THPZcc3L0GyoePUtfSmiEgllDxmbwOObnp+FNBc2nw1cDxwbT7neiqw0cxOTdi2oPKTZxGR/RpLvjSOiIiUpcQx+wbgODM7FvgZWQLgm/Z/090fBg4kHZrZtcB73X2zme0FvmRmnyRLGDwO+P58B9PkWUQGggN1TZ5FRCqhzDHb3WfM7GzgSrLPvC929y1mdj6w2d03zrPtFjO7DLgNmAHeMd9KG6DJs4gMEN15FhGpjjLHbHffBGxqaTtvjr4va3n+MeBjqcfq2mobZrbMzL5vZv9uZlvM7I/m6ft6M3Mzmy84XETkAAem3ZMeIiLSW1Ues7t553kSONndHzOzMeB7ZvZNd7+uuVO0gPW83AuVAlsTCLO2xOTAmahwSrDDlH/M1BWxgsS41CRCC47hI8W/iaL8w06SDVOTF30mtVhJ1C94j8PEx7QkvfSkv7QExE6KuISvN+wXvLaUAisdJf0tMklxjn7J23bIcYVtSMb6aBVW1wowIpEqj9ldG2E881j+dCx/RO9atIC1iMj8HOqJj4XM9UmZmX3BzO4ys5vyxwlL/bJERAZSiWN2t3U15jkvn3gj8EzgQne/vuX7BxawNrP3zrOfs4CzACaWH7yEZywiVZFVqypN+ElZ/r3/4e6Xl3eowdc8Zi9jRY/PRkT6Qcljdld19bMtd6+7+wlka+idZGbH7/9e0wLW70nYzwZ3X+fu68bGVy7dCYtIhRj1xMdC2vikTBLMGrOZ6PXpiEhfKG/M7raerLbh7rvzNfbWA7fmzXMuYO3um+fbX2tYqAXxyBbc949imcP45sRtSxXGnaYVNbEwBrjceOkwZjeKeY62DWOK0/YXxwB3ED+ceozU9yAlHhk6i0lOiatOjqmO9pUY393D+OZIlnySfLBDzax5XNng7huaO0SflJnZ7wMfM7PzgO8A57j7ZOdnL4uWGN9sUc5DyTzKrVls/LVipWXAtTlm95WuTZ7N7DBgOp84LwdOAT6+//vzLWDdrXMUkerK1gxNHogXKvVKvs7nCWZ2MPDV/JOyc4GfA+PABuD9wPmLPmkRkSHV5pjdV7oZtnEEcI2Z3UxWCeaqPLb5/Lw8oohIRxpuSY92uPtu4Fpgvbvfl4d0TAJ/C5xU/qsQERkOSzFmd0PX7jy7+83AiUF70gLWIiLzKfMuxlyflJnZEe5+n2WxZa/libAzERFpQ5XvPKvCoIgMBMeol/dh2hHAJXnc8whwWf5J2dX5xNqAm4C3l3VAEZFhUvKY3VWDMXluTRiMFgVMTSIMiqSQUiSl7NyOKBEw6hcmEUZJK4nJhkGFmSiJMNy2nlpgJTHRLthfnEgZbBsmLwYdZ8pNQIyTIYPzS/w3Co87E+yvzCIpYRGb1P0lvoYlUtbHe/N8UnZyKQeQxekkObALhVMsSOZdbOKfNzo4XyUbSkX0Y0hGisGYPIvI0HOMqWgpEhER6TtVHrM1eRaRgZAtuF/NjwBFRIZNlcdsTZ5FZGBUNflERGQYVXXMHojJcyH0NAonjRavj2KZF1skJdp/2VLjh4NY3DDcL7EQSxgHHRYXibZNiz221BjlxPjmMP46MUa3oxjqTgqsJBYnSYqr7ug8on7FprL7dcrdqIcB2zLIUuObuxIHHcYaFz+WDouptOgkfrqjeOl4h+XuT4Rqj9kDMXkWEQFoVPQuhojIMKrqmK3Js4gMhCz5REOaiEgVVHnMruZZi4i0qHLyiYjIsKnymD08k+dgnedwbeZoPegwDrqlLdp/1NaJRtrHG1GccbzOc2oMdRRTHBw4Wg869VyC/YXx0sExwnNJfB09i6FOXHd70XHVybHMi1+rOtRBrHUZ6hVdM1Q60El8cy113ei0fh5dK6L9hdejhOtF8PMdxU93Fi8d5V5ozWlZGmWO2Wa2HvgMWaLB5939gpbvvx14B9mM4zHgLHe/zcyeDtwO/Djvep27z1sAa3gmzyIy0KpcrUpEZNiUOWbn1WAvBF4JbANuMLON7n5bU7cvuftf5/1PBT4JrM+/9xN3PyH1eJo8i8jAaFQ0c1tEZBiVOGafBGx19zsBzOxS4DTgwOTZ3R9p6r+ScG22NJo8i8hAcNCdZxGRimhzzD7UzDY3Pd/g7huanq8F7m16vg14UetOzOwdwLuBceDkpm8da2Y/BB4BPuju/zrfyWjyLCIDwTGmK1rqVURk2LQ5Zu9w93XzfD8Kni7cWXb3C4ELzexNwAeBM4D7gGPc/SEzewHwNTN7Xsud6lmqP3l2iokWicmBUdKGBQVRCsmBUCymEiVZRIkSnSQRpiZZRckdUQJNlICYWOgkTjaMksWC/fV7wZaSExDDbRPfg8UmJZadkBjprOhK+UWF3KnsgvuSJkwEjDsW24LkQKsFF+7UxOfosFFj6pjfek0JtosSEpOTD1MTs6LfzQ6S/lSwReZS8pi9DTi66flRwPZ5+l8K/FV2Hj4JTOZf32hmPwGeBWyea2NdaURkQBiNxIeIiPRaqWP2DcBxZnasmY0DpwMbZx3N7Limp78O3JG3H5YnHGJmzwCOA+6c72DVv/MsIkIeP6c7zyIilVDmmO3uM2Z2NnAl2VJ1F7v7FjM7H9js7huBs83sFGAa2EUWsgHwq8D5ZjZDtozd291953zH0+RZRAaGEgZFRKqjzDHb3TcBm1razmv6+p1zbHcFcEU7x6r+5NmI429TpBY2idrq9YX7hHFn5cd6FgRxwcnvURgDmxhDnRov3Y2CLcnHjV5b2v5SY6ij/YXvS2CxcdWlx1RHyi7+0iHHaKhIytAJ46BT45vHipfA5DjoTiRcZ7z1GgNY6jUl2DYs4BLl/YT5CMF7EsQel12wJaIY6sFR5TG7+pNnERGyjwCnXUOaiEgVVHnMruZZi4gUGHUlA4qIVER1x2xNnkVkIGSrVirmWUSkCqo8ZmvyLCIDo6p3MUREhlFVx+yhmTyHC8l3kuTX2hYVUkk+ZgcJC6mJLFFBlEiYoJaYgNhJsmHi+ZWdgBi/tvDASW2dJBGWub+lTkiEOCkxPGpqomKH3K20uxhmtgz4LjBBNk5e7u4fMrNjyRbXPwT4AfA77j5VykFltrCaUlo/i8bFKDlwNLgEjo0V24IEREY6qGaZUEDLUopzQZgcmJpEGF7vEvfXjYItlUxAjA+y9MeooDLH7G6r5lmLiLTIkk9qSY8Ek8DJ7v7LwAnAejN7MfBx4FPufhzZOqFnLtXrEREZZCWP2V2lybOIDAij7iNJj4V45rH86Vj+cOBk4PK8/RLgtUvxSkREBl95Y3a3DU3YhogMtiz5JDke5FAz29z0fIO7b2jukJdrvRF4JnAh8BNgt7vP5F22AWs7OmkRkSHV5pjdV4Z78txJHHRrW2qMWeoxU0UxcMlFLhL7RfGDHcVQdxKj3EEMdWoRlw4KyoRFXDqIoU5ua1V2THW0u9TXFViKIinQVrWqHe6+br4O7l4HTjCzg4GvAs+JurV3htIV0c96WCQliG+eGC+2Bdv6aFRMJe1c4utMy2ZhfHNiHPTMTKGpoxjqaNvU61ticZbBiaEOzi81fr8TFY2rrmpV2OGePIvIwFiqalXuvtvMrgVeDBxsZqP53eejgO2lH1BEZAhUucJgNaf8IiKBBiNJj4WY2WH5HWfMbDlwCnA7cA3w+rzbGcDXl+iliIgMvLLG7G7TnWcRGQjuMF3eslNHAJfkcc8jwGXu/g0zuw241Mw+CvwQuKisA4qIDJOSx+yu0uRZRAZC9hFgOQOxu98MnBi03wmcVMpBRESGWJljdrcNxOR5yUNmEha07yg5MEgw8JKD/y01YSFMvktMSiw7ATG1qEl4jJITEDsokhJKPEZyAmLCvpJfQ7C7Tgq9RJLfpzZVtVqVlCwaY6K2KOlvvJhE6OPFS6WPRUmExWMkJ+C2XhuCa4XNBEl1QVs0Zof9ZoLrVtTWKLZZ1C8sxBKdXwcJ9kG/KNkw0rMExHjjxI5px+1KUmKkw5Tpqo7ZAzF5FhGp8rJHIiLDpspjdtcmz3OVu23p827gbcAM8CDwe+7+026do4hUWXU/AhQRGT7VHbO7edZzlbtt9kNgnbs/n6yK15928fxEpOIaWNJDRER6r8wx28zWm9mPzWyrmZ0TfP/tZnaLmd1kZt8zs+c2fe/cfLsfm9mrFzpW1+48u7sDUbnb5j7XND29Dnhz0s5b39fU2NGknbP4wibRQvBhTFhi0FAHxVS8dQV+iONTg9NLLRoSxlWXHUMdxS1GOomh7qBISkdFXMJDdFBkJmG70guilF2gpw1Z5nZqwKEMjNQchcQiKVF8c2N5sV99ori/xngQB12Lfu+Kp9LKonjaaLiaDuKbg6ImYb8ohno6LZY5PYa6WLAlKroSF4VJjKFOva72qohLpM/jqlPOo+N9ljhm5ysjXQi8kqz66w1mttHdb2vq9iV3/+u8/6nAJ8lu5D4XOB14HnAk8G0ze1ZeKCvU1fvlZlYzs5uAB4Cr3P36ebqfCXxzjv2cZWabzWzz9NTjS3GqIlIx+xfcT3lId80as5ns9emISB8oecw+Cdjq7ne6+xRwKXDarOO5P9L0dCVP3MA9DbjU3Sfd/S5gKwusqtTVhMHWcrdmdry739raz8zeDKwDXjrHfjYAGwBWH3yUyuOKCIBCMvpU85j9JDtEY7aIAKWO2WuBe5uebwNe1NrJzN4BvBsYB05u2va6lm3XznewnkRqu/tu4Fpgfev3zOwU4A+BU91dtyhEJMn+zG3deRYR6X9tjtmH7v/0Kn+c1bK7cJXVQoP7he7+C8D7gQ+2s22zbq62cRgw7e67m8rdfrylz4nA54D17v5At85NRAZDVTO3RUSGURtj9g53XzfP97cBRzc9PwrYPk//S4G/WuS2XQ3bmKvc7fnAZnffCHwCWAX8U54odY+7n7rQjgs3klJvLJWZtJSaONBJEkPqcVOlvv4wiTAoBpCalNhRAmLi+9JJAmL0epOLjnShiEvK/jopVtLBv1nqcVMTFdvhbsxo8jw4wmSnDpKLwiTCYBwLip/UlxUvlTMrgn4Txf01xorHbURX3tbzi4qkRAmDQT7eyExx26jNgiTC2lRaYmGYgBgkG4YJiGGxl8SiK2HyYmpxlrS2KLE/SuAsOwEx0o2kxMJm7SQpdpCTWPKYfQNwnJkdC/yMLAHwTc0dzOw4d78jf/rrwP6vNwJfMrNPkiUMHgd8f76DdXO1jbnK3Z7X9PUp3TofERk8CskQEamOssZsd58xs7OBK8n+yr7Y3be03KA9Ow8NngZ2AWfk224xs8uA28jqjLxjvpU2QBUGRWRAVLlalYjIsCl7zHb3TcCmlrbmG7TvnGfbjwEfSz2WJs8iMjA0eRYRqY6qjtmVnzy7Gd4SjxnGU5YdCtkahxTFNEUxe12IkeqGMA4r7JhaiCWIPbTgGIlFXMJ46UgUQ91JoY9uFHGJLDLmedEx1W0do4MCRW3Yv2aoDJnU+M+oSFDw+9qIYp6DWOaZ5cW26ZXFtvpEEPNcrLlCsVZE8HsTvFQLxsSorTYdxEFPF19rbSrqV2wL9xfES4dtYcGWtHjp9MIuicVUovjmaNtOiriE8dKp/bowZ2jZNoxvnuv3bDrtEOFhKzxmV37yLCKyn9Z5FhGpjqqO2Zo8i8hAcIeZhlbbEBGpgiqP2Zo8i8jAqOpHgCIiw6iqY/ZATJ4LywRGf8ikrmfbC6mxSp2s6ZwaFxgJ4l2jdTBDqesNlxxDHcUypsdfD0gMdcr+O1mXOvkY3bmzUOX4OemR6Ecz+FlvjAexzMuCOOgVxW2nlxcPUV9WbCvEQQfnFv14R7/CUczzyHRx45EgXnVkptivNhX0C2Kja1NBDPVk1C+KjY7iqqP45tQ1pxPXoQ7XjY7imxcfQx1ta6nX+Gh/qWs/Lzauup35x764OUWVx+yBmDyLiEC26L6IiFRDVcdsTZ5FZGBUNflERGQYVXXM1uRZRAaCe3Xj50REhk2Vx+xqpjmKiBQY9cZI0mPBPZkdbWbXmNntZrbFzN6Zt3/YzH5mZjflj19b8pclIjKQyhuzu636d56NYpGUKMEtNWGwk7ZeSE0EjBIHUiXmBsaJYWnn550kDEbdOtlfIQMVnMREwG4kIEZazyX157Psoi6R1KTEEpQYPzcDvMfdf2Bmq4Ebzeyq/Hufcvc/K+tAks6D8S78F49+/xPHhODXP2yrjxfbZoJEwJmVQduKYH/LZp+fjwaJ5LW012DB76vVg7YgYbA2lZhYOJmaWBj0myy+oVFxlrAtSkAMC7YkFnEJi65ERVyCttRkw2jboChMXIglSvqLtg1+NxablJiakAjwaNycSjHPIiI95JT3EaC73wfcl3/9qJndDqwtZeciIlLqmN1t/XcvXERkMTy7YZLyAA41s81Nj7Pm2q2ZPR04Ebg+bzrbzG42s4vNbM1SvywRkYHU3pjdV3TnWUQGRhuZ2zvcfd1CncxsFXAF8C53f8TM/gr4CNlNk48Afw783iJPV0RkqGm1jR7ylnXZo5jnsNBHELNp0bYp8amp8Z9RDFKkk0XQo911UiQlWRD/lVpcI4oVTo2hTi3i0o146Ui/xFBH/xbRz2MnRV3C43avSEqZiSVmNkY2cf6iu38FwN3vb/r+3wDfKO2AsjhhIYjEWM/UcPzgZz2Kg24EV9SoIMrMquKBGytn/7KPTBR/+UfHim0jI2njVSOIg27MFF/E1HSx0AlTxX4WxC2H8c37ghjqIDY66hfFX9cmi9uG+0uNoY5io8MY6qg4S9AvjG9OjKuOYqjraYVT4n6Jvxut/RJjqjtV9pjdTQMxeRYRgfI+3jMzAy4Cbnf3Tza1H5HHQwO8Dri1nCOKiAyffgzJSKHJs4gMjBIzt18C/A5wi5ndlLd9AHijmZ1A9hHI3cB/K+uAIiLDRqttiIj0UJZYUtpqG98jXgVtUykHEBEZcmWO2d1WzWATEZFAwy3pISIivVfmmG1m683sx2a21czOCb7/bjO7LV8t6Ttm9rSm79Wbil9tXOhY1b/zbMXEjXCR+1rw5kdtURJUmES4yAtwlDwVJbd1IDk5sJPCKYm8owIraRtb6mQosTBJXxVsiaQkIHaQfBhJL+pSclJim6oaPyeLFxZOSSkEQVyowmaiYhOLL7DSGAv6TRR/J0ZXzMx6vnLlvkKflRPFzLhlo8UKJuO14uuKJiAzQbLWvpniCU/OFKcKeyeL/aamiv2mJoNpxr60BMQwiTBqi5IIwyIuqYVdosTCYiJlcrJh8DOVmoBoQdJftL/wZzlKIoySF1vH6JQ+JSkxT6UGXAi8EtgG3GBmG939tqZuPwTWufseM/t94E+BN+Tf2+vuJ6RiGBPzAAAgAElEQVQer/qTZxERssztRkUzt0VEhk3JY/ZJwFZ3vxPAzC4FTgMOTJ7d/Zqm/tcBb17swXSlEZGB4YkPERHpvTbG7IUKW60F7m16vo35q8KeCXyz6fmyfL/XmdlrFzpv3XkWkcFQ4eQTEZGh096YvVBhq2hH4b0SM3szsA54aVPzMe6+3cyeAVxtZre4+0/mOljlJ89OMZ45im8OC6dEC98H21oUn9naFhVcCeJEw+IY3dBXxVSKbGTx74snf+yTWMSlk4ItUa9exFB3ED8ddrPUOPqSY6jbpdvKwycY2zyIzwxjNsM46KjIRRSLWtxd+GsSNPposW1sfHbM85OWF2OeD1/xWKFtzfieQtvq0eK2Y9EJByaDSi976+OFtkdnJgptj0wVK8I8HLQ9NlncNoyh3ltsm95XjD22oK0WxTynFmwJth0J4qpr04n7i2Kog2I0Uax1GC8dtSUXbFk4rjrsE+yrFOWN2duAo5ueHwVsb+1kZqcAfwi81N0P/Ku6+/b8/3ea2bXAicCck2eFbYjIwHC3pIeIiPReiWP2DcBxZnasmY0DpwOzVs0wsxOBzwGnuvsDTe1rzGwi//pQsnX+mxMNCyp/51lEBLIbGFEJYhER6T9ljtnuPmNmZwNXAjXgYnffYmbnA5vdfSPwCWAV8E95RME97n4q8Bzgc2bWILupfEHLKh0FmjyLyGBwksNGRESkx0oes919Ey2FrNz9vKavT5lju/8N/FI7x9LkWUQGhtZ5FhGpjqqO2dWfPBs0WuLuUxMGw+TAqIhJSuGUaLsoCS41kSvsV+5PWa+SAyOdnUtiImB03OTkwMUfo6MExOjnNiqqk/BzFf5spxbo6VUCYrv650dalkKY+FxMvCIoLOHTM4U2mym2jUwFbUEyVq1Ym4SR4qbYTFQ4KEown32MiVpxZ08a21toO2Li4ULb4WPFttW1YhLhMiu+iHqQCjXtxff48UYx6e/h+vJC267plYW2nUHbrqkVxX77ivuLkg0f21dsm9wXJCBOBj8re6PEveJ7MBIVZwmTDYttI6nFWVITEMPkwOi4icmGLcVULNp/VHClDBUds6s/eRYRAUDJgCIi1VHdMVuTZxEZHBW9iyEiMpQqOmZr8iwig8HBtdqGiEg1VHjMrv7k2aA1HKs1BhrmiIOuBUVMgrhQC/ox0nKQcMX8hFjpufr1u8SiK10RFaPpIIY6tWBLerz04o+x6Hjp1FjpSGqxlm7EULetgr9L0pHod92i8Sn62YzioIOY59q+qC0qzBG0BTGrFsTA1uuzf5/qQfGnWpArsCIIsj1k9PFC21NHdxfaVgeVP5ZZEDwbmA7yGx5vFIup7G4UY5l314ttO2aeVGybXlVoe3BqdaEtjJeeLMZLP7IvKNgSxEtPBQVbZoJ/25ngZ2AkLLCS2BbE0Yex0cHPT7htFEOdEC8dxk8H25WjmmN29SfPIiL7VfQjQBGRoVTRMbtrFQbNbJmZfd/M/t3MtpjZHwV9JszsH81sq5ldb2ZP79b5icgA8MSHiIj0XkXH7G6W554ETnb3XwZOANab2Ytb+pwJ7HL3ZwKfAj7exfMTkSrbv+B+ykNERHqrwmN218I23N2Bx/KnY/mj9e+J04AP519fDnzWzCzfNt4v0Bid/cbG8c1pbYRrRAdx0CnrPAdxnRbF50b7D15y+DYEcXFxoGxwelFcbB+t/Zysk/jrQY2X7mD/S73e9FKq6oL7Uq7wdzj4eY3WeWaqGDxa21vsN7qnGBc7uqf4Ozb6eLDG76piv6mW/T26shiL++h0MWZ3slE8j8hYMCgcHCwivDr4FV5mQSJRME7UKa4lvceL8dePBtet3cG60Q/Vg5jnKDZ6phgHHcdLF9t2ThbXnH54svg+PxK07dlXjPGemixOq6J4aYK2KBY+io2uJcY8R2tEh2tJt2wbxzwvzdhe1TG7m3eeMbOamd0EPABc5e7Xt3RZC9wLWZ1y4GHgycF+zjKzzWa2eWZf8RdTRIZUw9Ie0lXNY/Y0QRUJERlOFR2zuzp5dve6u58AHAWcZGbHt3RJKqvn7hvcfZ27rxtdVvyrUUSGk3naY8H9mB1tZteY2e15jsY78/ZDzOwqM7sj//+apX5Ng6B5zB6jeIdRRIZTWWN2t3V18ryfu+8GrgXWt3xrG3A0gJmNAgcBO7t6ciJSTamJJ2kD8QzwHnd/DvBi4B1m9lzgHOA77n4c8J38uYiItKvcMbururnaxmFmdnD+9XLgFOBHLd02AmfkX78euHq+eGcRkSckJp4kJJ+4+33u/oP860eB28nCyk4DLsm7XQK8dolejIjIgCtvzO62bq7zfARwiZnVyCbtl7n7N8zsfGCzu28ELgL+3sy2kt1xPn3BvUZFUoJX5aPFNz+9SEpU7KSlX1RIJarW0giSvaIkq9S24E+yOBEwOL9+KnTSK6nvQWKyXWqyYfRvlHou4b9l0jE7+PdOPmZasuGSWYI/tfMlM08ErgcOd/f7IJtgm9lTyj+itCX6vYkutvViP4+KpEwWs6xsX7EtShgcfywopLGieC4zjwZFUpbN3t8jy4tFPu4fLya8rRkvJtCtCYqkHFwL2ryY4Lfaiu/ThBUvqhOWlqh4EMX9HRJktx3mxVj4p9b2FtoeGn2k0LYzKLrywFiQWBi8Vzsmiu/pQ9PFcNBdU8W23VNpiYWPTxYTC/cFhVimg2TD+lTxZ6o+WRyPo8IpUQJilGzYWhcnLOwTJCSWoqK3R7u52sbNZBeg1vbzmr7eB/xWt85JRAZM+t8Hh5rZ5qbnG9x9Q2snM1sFXAG8y90fsSpWAxUR6VcVvYenCoMiMhj2rxmaZoe7r5uvg5mNkU2cv+juX8mb7zezI/K7zkeQrRwkIiLtam/M7is9SRgUSfVyv4d/8E1c6ZfzD76Jl/s9vT4l6WMlrrZhZGFkt7v7J5u+1ZyXcQbw9bJfg0ilfeUR7IV3YUfekf3/K4/2+oykj1V1tY3q33m2Yoxza9EUiMOPwzjo0SAOuhYsZj7a8llDFMs8ExSRCGOqi/uP8iTDwinFI4QFMqJ41yh2Nu4XHCWKAS45hvrlfg/v5kaW5S/ocPbwbm4Eh2vsmFKPtaCqxUb3qPhLaJEx2otS3iD7EuB3gFvytekBPgBcAFxmZmcC96Aws74U/axb9HsTFUmZLgZ32r5i2+jjxbaxZcWxfHxZMba1PhFco8Zn/55MjReX9Pt5rRizO1YrDvgjHcw2GrWHi23B2twHBWPCsq/uY+S9D2J78+Nvm2HkvQ/iGPyXJ859uRVf2yjF924siI0es+K5rAwCcldYMXD34NqeQttBwXu6ZqwY37xjrFiIZfd4MS5913gQGz1RjIN+bKr4HjwWxUZPpcVGN6ajQizFsbc+FcRLtxRAqUcFV5aoSEqZMc9mth74DFADPu/uF7R8/93A28hWU3oQ+D13/2n+vTOAD+ZdP+rulzCPBSfPZvY2sozyK4AvA+/OT+xr7n5LG69LpC1ncuuBifN+y6hzJrdyDV2ePMtQcffvEa87D/CKbp5LP9H1QOYz8ic7n5g452yvw588hP+X4iRVpCz5YhQXAq8kW/b4BjPb6O63NXX7IbDO3feY2e8Dfwq8wcwOAT4ErCObzt+Yb7trruOl3BJ6L9lapi8CbgCeBdwP/GU+UxdZEodRvEswX7tIVT8CrBBdD2RuPwvu5M/XLkOvxDH7JGCru9/p7lPApWRLix7g7te4+/4JxHVkBfsAXk1W9XpnPmG+imIdkllSwjam3P1WM3sXsINs1j5pZpcA/8oTa56KlOpBVnB4MFF+kOKyRCI4fVnGdcDoeiBzWzsK24KJ8trqR4jKEmhvzF5ohaS1wL1Nz7eR/ZE/lzOBb86z7dr5TiblzvNXzezrwGuA/+5+YDHGaeDQhO1FFuUijmdfSxzcPmpcRGtVd5FcRatVVYiuBzKnxrmH4MtnT4Z8ueHnPrlHZyR9L33M3uHu65oerUuLphXCAMzszWQhGp9od9v9Fvxz0N0/ZGavAk4FXmBmHwXuACaAXWb2HODH7r2puOEUkwFbi6bAXEmExbaRoB9BP29psyARMCycEp1cVETR05Kswn/xaH/JyYElJxFGEn9UrrFjwLPY58PYw4Os4CKO736yYDui15b6voS7SyuCk3QekX5JcCyJQjKWVr9fD+bj9SCTOmqbihIGi0lqFhRJGRsvju+tiYAAjbGgX8s1xUeKl+e9VkxQu7fQAjPBOD4VVA97rF5MZNs98WChbe1oMfTzsKDoyurTRljRWM1BFzxGbXuD+pEj7D3nSUy9dhQaT3yKWJsznWC2ejB/ibYdC37xV44UEwYbwf3CRrBIQC0oFFMLzmXMij8/Y8G24yPFu/HLasW28aBt71gxiXDPWPFnb3K6+O87PZaWWNiaRNgIkgOXKmGwxDF7G3B00/OjgO2F45mdAvwh8NKmP/63AS9r2fba+Q6W9FmKu38L+FZ+YAOeTVbw5ASyzMZnA09L2ZdIO66xY5QcKOk0eV5yuh7IfPa8bgV7XvdEaN0KC24YiexX3ph9A3CcmR0L/IysQvWbmjuY2YnA54D17t68Rv+VwB+b2Zr8+auAc+c7WNuBSJ7d1vxR/vhyu9uLiCwZTZ67StcDEelISWO2u8+Y2dlkE+EacLG7bzGz84HN7r6RLExjFfBPebXYe9z9VHffaWYfIZuAA5zv7jvnO56i+EVkIGglDRGR6ih7zHb3TcCmlrbzmr4+ZZ5tLwYuTj1W9SfPBo2W8J8wvnls4bjlrC0oLhHERNlMy0dRtSDEbzR4e6PY0agIS1QQJYwzLr6GsOhKVDml5DjoSDdio4fNouOg450V23oRo10WrbYhEP5ch+NYFPMcFU6ZKsbP2p69hbZacK0YG4sKbwXXnkLeTJQ/ULym7Jsprj50TxDX+nhQlOOh1cWCHj9fflCh7chlxZjnI8d2F9oOqT1WaDt4pLhi0rIgBng8uEZFQ0cjyBuaCgqs7AtivPcF7189sTz0mAVxy0ERl+W14s/KdJDD1EiM+05lwSw0OsJMMFepj8x+/6LCcFGOWCkqOmZXf/IsIpLTnWcRkeqo6pitybOIDI6KDsQiIkOpomO2Js8iMhgU8ywiUh0VHrM1eRaRwVHRgVhEZChVdMyu/uTZijkUrQmE0EYSYdAvKYmwHiWjpBZOCX56gqIrcSJgIEiCKTuJkCBBI04W60JioZSrwkmEQX0CkTmFhVOmg4TBWlA4JRqjgyTx0aiAVpDoDbMvXBYmOEbFK4pj8fRUsfjJA/uK5/bwnmK/n69aXWg7dPkhxbZlxeTAw8aLbWvGgmIqI/sKbSuCoiZRkl4t8VZllAg46cXJwVSQRLinERQmqRcTLqN+e+vFtskgeXGmtbob0AjOeSRKBAzaaiNBW5AcGB2jUZu9rTeChNvEwm3tquqYrdmJiIiIiEii6t95FhHZr6IfAYqIDKWKjtmaPIvIYKhw8omIyNCp8Jhd+cmzh0VSiv3itiD2JyqIEixy3xrPXCiaAjAa/FRE8c1hW7C/QBQ5F/4sBscIt40CkKJF5JPjmxNjo6OX20GRlDCGus91tZjIfKoca169f3bplsTCKVHOSFQkJSxQFcQ3jwT9gstMYYy2IHlnJLjO1KaK+6/tKx5g+vFiLO7U6uKF8b5VxdjeHStXFdpWLD+40LZ62WSh7aDxYnzzqrHi+7litLjtRFBMJWqL4oJTRTHA01EhlmASMVkvtkX99gX9orbJmbRjRP2mg5+NmSDvqhHEzXuhUEkXr0UVHbMrP3kWETmgogOxiMhQquiYrcmziAwEo7qZ2yIiw6bKY7YmzyIyGCocPyciMnQqPGZr8iwig6OiA7GIyFCq6Jhd/cmzFZMBk5MDgyIpUdvIVFAkpaXYSaFoCsQFR6JEwChhsANhImBi8p1Fi/cH51d+YmGwuyiZJ1HZCYh9r0+S/Hqe9FjSr5KZXQz8BvCAux+ft30Y+L+AB/NuH3D3TeUcUfpGNE4ESYQeJREGP//RmBr9to62HNaisXO6mERYmywOdqP7giS4PUHbY8UzmVlRTCycWVk87sPLiwVWHl5efJ/uGy+2jY0Xk/7Gx4LkwNFi22hQ+GOsVjzGSDAYRMVFIh4VEgmurPXgGhW3Be99UCSlHiT4RUl/Ub+orTETzF2ChMFCkbeZ4Oc42q4MFZ0898cVV0SkBOZpjwRfANYH7Z9y9xPyhybOIiIdKHHM7qrq33kWEdmvpEHW3b9rZk8vZ28iIhLqw4lxCt15FpHB4FnmdsqjA2eb2c1mdrGZrSnpzEVEhk93xuwlUf07z1GRlGJ4VtwWFklJi4MuFEkJ4peopxVECbctmQUxV+EffIkxz6XHRoenkvgnaXIsc9r7XMUCKyl6Ho/cbKlitNP/6Q41s81Nzze4+4YFtvkr4CP5UT4C/Dnwe+2eovSR1MIpFGNqLfoZnpou7i/YW/SbONKYfS4WxFmPTBfPtzZZvLiN7iuOddN7gsIpK4JY3KCtvqzYNrO8eIzGRBDHO1F8BybHi237xoLrx1jx9VpUfGyk2DYSxEZHb3x0KQvjBBKvW2HNs+D6G/1gxP2Ca3cUfxxsG8YpBzWAWvtF8wWWagJb4uXWzNYDnyG72H/e3S9o+f6vAp8Gng+c7u6XN32vDtySP73H3U+d71jVnzyLiOTaiI3b4e7r2tm3u99/4DhmfwN8o53tRURktrLimc2sBlwIvBLYBtxgZhvd/bambvcAbwXeG+xir7ufkHo8hW2IyODwxMcimNkRTU9fB9zawZmKiEh5Y/ZJwFZ3v9Pdp4BLgdNmHcr9bne/mRLuo+vOs4gMhg4mxq3M7MvAy8jCO7YBHwJeZmYn5Ee5G/hv5RxNRGQItTdmLxRqtxa4t+n5NuBFbZzNsnz/M8AF7v61+TpXfvLsBo2W+Kl4/ebitqlx0D4WrZXYcswgTnakXoz/siguOPhX6Cg6NQriagSxY1G/ehADGMUUlx0bHZwftbRtU2OZozWnI+Ea0an6eS3pPlkLeqkY5X0E6O5vDJovKmfv0tdS46CDmORIuO5+1K9lf1HMc206iL2eKq7LXNsXxEHvKV5oxoK45ZllwVrDy4OY5yAOuj5RaIqvx+Op1+ji+UWlEqLaDh7EQYdlFsKY56ht8YOLpeb5BIcIk+VS+0Vx0NHlvLDGeNoxO9XmmL1QqF3qr9pcjnH37Wb2DOBqM7vF3X8yV+euXU3N7Ggzu8bMbjezLWb2zqDPQWb2z2b273mf3+3W+YlI9VV1zVARkWFU4pi9DTi66flRwPbU83D37fn/7wSuBU6cr383b0XNAO9x9+cALwbeYWbPbenzDuA2d/9lso9M/9zMin9Wi4hEljDmWURESlbemH0DcJyZHZvPG08HNqZsaGZrzGwi//pQ4CXAbfNt07XJs7vf5+4/yL9+FLidLEZlVjdgtWWf9a8CdpJNukVEFqbJs4hIdZQ0Zrv7DHA2cCXZ/PIyd99iZueb2akAZvbCPIflt4DPmdmWfPPnAJvN7N+Ba8hinuedPPck5jmv3HUicH3Ltz5L9pfCdmA18AYPAm7N7CzgLIDaGtUpEBGyBfc1Me5LzWP2Mlb0+GxEpC+UPGa7+yZgU0vbeU1f30AWztG63f8GfqmdY3V98mxmq4ArgHe5+yMt3341cBNwMvALwFVm9q+t/fIMyw0AE8cc7d6yuHqYjBC80nqQyFALgkTqM0HQ/UxLkZSZaIH3oC3IWEhOKAnaQhYkskT376NkvmDbcMH04Ce+o8TCWpQFEhVYSftNS05ATJV43OTkRVkamjz3peYx+0l2yED8K3liwmAoGp9aEpqj5HJmosIpxcHd9hUvZCP7ihfB2kQxS28sKnSyLCp+EiQMjhc/zI6ux/UwOTAxiTBK+osSBoNrT5gwGH3+HtYqia6DadsmS/3NiBIGo20T2woJg9F2FSiS0k1dTb83szGyifMX3f0rQZffBb7ima3AXcAvdvMcRaS6qlrqVURkGFV1zO7mahtGttTT7e7+yTm63QO8Iu9/OPBs4M7unKGIVJ1W2xARqY6qjtndDNt4CfA7wC1mdlPe9gHgGAB3/2vgI8AXzOwWsg8/3u/uO7p4jiJSVUoGFBGpjgqP2V2bPLv791ggGihfZ+9Vbe3YHB+ffU+/MRrEXSUWSYlisUaiWKz67DaLFtGPYtaCppEoBrjYLT0OOorNikTnl1hgJSy6EgWPRccIipUkx0tHOomhTpUc89zBITo5vx6wkT4sulLRgVj6XDg+ReNdUGAlCI22qF9rQxRTPRPEN0dtU9PFtsnixW1kvNjm40FsdNAWFQ9rjBfH3UbUL4xvjmKUo/yltJwmH1l8zHNU0yTaX2p8c2KNlNIlx0EXtkubu5SiomN25SsMiohA29WqRESkh6o8ZmvyLCIDwxJLsIuISO9VdczW5FlEBkOF4+dERIZOhcdsTZ5FZGBU9SNAEZFhVNUxu/qT5xGgNWFwvPivERZO6WSh9pbCKRYkMURJhGEcfpTFEAiTCINkBwuKuiQnAkYFVurBtlGyWPTioqSX4LfFolUTO0k2jKQmIEa6kDBonZyfZCo6EEsFRUl/UeI4wRgYZJC1tniUCB20eT1I3g6KqTBdTCJksjgFsLHiBa82WrxG+ViQWBgk6/tYsG3Urxa0hf0SEwujflHSX9QvTBgstkXXlOTkwC4kGy56YtrNcbSiY3b1J88iIrmq3sUQERlGVR2zNXkWkcFR0YFYRGQoVXTM1uRZRAaD92cZVxERCVR4zK7+5NmckfHZ8V2NYEH35FjmqHDKeHDYpCIpxSYSf1DC8hNRTGzYVox3C+Npo1jmqF8tMZY5tehKHPhdbIukFmeJJB9j8X8KJ8dfR4KYv1BFl/ZZalVeM1QGRGIxFQ9/h1vG1KhPNO5GBaHCAitBv1pQTGVqqthvNIiNjvJegthoC7YNc2YSY56jbaOY5+R+UdxyWBAl6lfs1lkcdCcBzovftKBL42iVx+zqT55FRPbrQmKniIiUpKJjtibPIjIwqnoXQ0RkGFV1zNbkWUQGQ4UX3BcRGToVHrMTgyxFRPqfNdIeC+7H7GIze8DMbm1qO8TMrjKzO/L/r1nK1yIiMujKGrO7rfJ3ns1gtCVhcCookhIl/dWCtnqwjnxUdGRkpmW7qBZII0hSTE1uC0RJf2EiYGISQ7Q4vNWDjYNF+JMTAUei5MBgf4kFUZKLs3SQgJj8J2VUxCB1407ivNJq6hQNQaJhiYPsF4DPAn/X1HYO8B13v8DMzsmfv7+0I8pgSkwibi2wYiNBEZbgOhP+XqcmG4bXhSixcKbYFiTVWbRtmBwY9Iuub1ES9UiQlBgl+EXHDa+XicmBUbJhJDmxP21/XnbxrD67ZVrmxNjM1gOfIbtKft7dL2j5/q8CnwaeD5zu7pc3fe8M4IP504+6+yXzHavP3kYRkUVysj9KUh4L7cr9u8DOlubTgP0D6iXAa0s9fxGRYVLimG1mNeBC4DXAc4E3mtlzW7rdA7wV+FLLtocAHwJeBJwEfGihTxY1eRaRgWGe9gAONbPNTY+zEnZ/uLvfB5D//ylL+FJERAZeG2P2Qk4Ctrr7ne4+BVxKdsPjAHe/291vprho8KuBq9x9p7vvAq4C1s93sMqHbYiIHJAembLD3dct4ZmIiMhC0sfsQ81sc9PzDe6+oen5WuDepufbyO4kp4i2XTvfBpWfPJs5E+Oz47GmxotBNI3x4k32+ngQyxysDx8WTmkJH4vim+tBWyztA4C4cEpix7BIShRPFgQgpcZGRx+tRPHSUdx3asxzarx0JDWGOtJJjHLZ61hGcdopBvxzpi4suH+/mR3h7veZ2RHAA0t6NBkuLWNbawz0XMLY6Jm0Yi1RrLBHY3Zrkg+kx0t3EHtswTmHxaTC/SX2S2wLr+aJ8dKh6LWF3TqIte5zbY7ZC93wiN6A1L23ve2AX05FZGi4Y420xyJtBM7Ivz4D+Hop5y0iMozKHbO3AUc3PT8K2J54Jm1vq8mziAwOT3wswMy+DPwb8Gwz22ZmZwIXAK80szuAV+bPRURksUoas4EbgOPM7FgzGwdOJ7vhkeJK4FVmtiZPFHxV3janyodtiIjsV1bYhru/cY5vvaKcI4iISIlj9oyZnU026a0BF7v7FjM7H9js7hvN7IXAV4E1wG+a2R+5+/PcfaeZfYRsAg5wvru3rrY0S+UnzyMjzvLx2YszPzZeXM+yMV6MxWqMBeswB2s/N4Jwr0Z99rb1ehSf20kMUhSPXGyK4qFGpoM4tiD+y6K4uChOOzU2OlznOThu6rrMYcxzB22pH9cnrsvaUax1J1LjtJda2bHcnXKGYi1rGRKLXB96LsnrRkezmWiJ6OR46Q7igpPjpRPHxE5irSNlxyOXvqZzn1wr5lLymO3um4BNLW3nNX19A1lIRrTtxcDFqceq/ORZROQAzZ1FRKqjomO2Js8iMjCWeLUNEREpUVXHbE2eRWRgdLCShoiIdFlVx2xNnkVkMKRnZYuISK9VeMyu/OS5Zg1WT+yb1bZ7Ynmh3+RE8aU2JoLCJkGynU0XmrDWBIog0S76OKKTjyg8SGIYCQ8SNEUJjVGyQz1KqIiS74J+QQKZBdt6aoJfcM5hsmGkkwREgoX+OzlupOy/tlOTHEs9Zn+NetmC+/11TiJLruTEwig7ME4ODDbtINkwEl3zQmUn7nWSaNdJ0l+vjtsjVR6zKz95FhE5oAd/Q4iIyCJVdMzW5FlEBkZV72KIiAyjqo7ZmjyLyGCocPyciMjQqfCYXfnJc82cg1tinh+cKAYpT42PFdoa48X4osZ4UDglLJIy+7kFHz3UOyqSUhSGdUWhx0Hc1EgUPzwTtNWC2O1g2yh+zhrRAvkdFDUJQo+JjtFRHHSxqaO46kgU3xy9tk6O0brDfiHMiwIAAA9PSURBVPprvmvn4pXN3BZZcql5EUGcsSf/XnUQL524v+j8om6R5FjreOO0g6RKjdPuRN/HQVd3zK785FlE5IB++qNBRETmV9ExW5NnERkMHn8CJCIifajCY7YmzyIyOCp6F0NEZChVdMzW5FlEBkc1x2ERkeFU0TG7a5NnMzsa+DvgqWRpWhvc/TNBv5cBnwbGgB3u/tL59js60mDN+J5ZbSvGiwmDj08UMwrqE8WsrahIykjQVp9oaYg+eoizDqLG4qZhImCQeBEk5I0EiYAetI0E20bB+2GBlahfVCgmSlRMLVYSvqdB0kZqAmJicmBYxCVSemJhWrdIqcv9VPROAMyRtCoi6UouuJRenCVVWlJieC7JiYqBTiqcRTo4l9TXWwVVHbO7eed5BniPu//AzFYDN5rZVe5+2/4OZnYw8D+B9e5+j5k9pYvnJyJV5lR2wX0RkaFT4TG7a5Nnd78PuC//+lEzux1YC9zW1O1NwFfc/Z683wPdOj8RqTbDK7vgvojIsKnymF325ylJzOzpwInA9S3fehawxsyuNbMbzewtc2x/lpltNrPNe3fti7qIyDByT3tIVzWP2dNM9vp0RKRfVHTM7nrCoJmtAq4A3uXujwTn8wLgFcBy4N/M7Dp3/4/mTu6+AdgA8NTnHeKHjD0+ayerJ4oT6kcmWoOUYc9E8eU3pop/T9QngljexvzPITW6Oe4Z1ldJLpJSbAvqpuBRjHJYOCWIlw4LpwSnF2ybGvMcFmcJNg3/ck2Ooe4gbrnkWOZO/gJPjtMubrjoY3ZkqY7bh4OszB6zn2SH6B9pmJQcQ91ZEZdOLD5IuewY5Y5it/tNRcfsrt55NrMxsonzF939K0GXbcC/uPvj7r4D+C7wy908RxGpqP3xcykPERHprZLHbDNbb2Y/NrOtZnZO8P0JM/vH/PvX51EQmNnTzWyvmd2UP/56oWN1c7UNAy4Cbnf3T87R7evAZ81sFBgHXgR8qkunKCIVV9XMbRGRYVTWmG1mNeBC4JVkN2JvMLONzYtSAGcCu9z9mWZ2OvBx4A35937i7iekHq+bYRsvAX4HuMXMbsrbPgAcA+Duf+3ut5vZvwA3k/2t8Xl3v7WL5ygilVVubJyZ3Q08SvZ57Yy7rytt5yIiQ6/UMfskYKu73wlgZpcCpzF7UYrTgA/nX19OdrN2UTE13Vxt43skhAG7+yeATyz9GYnIQHGWIn7u5XkImYiIlKm9MftQM9vc9HxDnkux31rg3qbn28iiF4j6uPuMmT0MPDn/3rFm9kPgEeCD7v6v851M5SsMjlqdQ8cem9V28HgxYXDnxIpC2+T4eKFtZqIYBt6YCYqk1FvakoukpPHoj6FgkfaocMrITLC/qF9UwCSIgo+Kn3hY/CTaNjFhMDHZMEwsjN7nqF9wjDDRLvHfLTlRMRKdS+oxIv1cJKWbkRSK2hAZbGUnIJatZwmNFZX+z7ljgU//opuzrW/8XH3uA45x94fM7AXA18zsecGiFgf0ZKk6EZGlYO5JD/K7GE2Ps4LdOfCtfNnM6PsiItKBNsbshWwDjm56fhSwfa4+eW7dQcBOd59094cA3P1G4CdkSyfPqfJ3nkVEDki/a77QXQyAl7j79rzS6VVm9iN3/25nJygiIgeU90nnDcBxZnYs8DPgdLLCe802AmcA/wa8Hrja3d3MDiObRNfN7BnAccCd8x1Mk2cRGQzuUC/vI113357//wEz+ypZQoomzyIiZShxzM5jmM8GrgRqwMXuvsXMzgc2u/tGshXf/t7MtgI7ySbYAL8KnG9mM2QJ4m93953zHa/yk+cxq3P42MOz2p488Vih34PjKwtteyaKMc/1oEhKYzooJlKf3S+KC04V/t0VFkQJYo9H0uKgrRYcN1ho3YL47jA2uhYVjonijIMXEu0vKn4SFlMJ+iUWRIljlItNqXHV4b9bYqx1KNg2Pkbi/lrPo5O/8KsSB13SeZrZSmDE3R/Nv34VcH4pOxeRwdXvMdn9psRri7tvAja1tJ3X9PU+4LeC7a4gq0GSrPKTZxGRA8obiA8HvpqvYjQKfMnd/6WsnYuICJWtMKjJs4gMBgdKymrP1wpVdVMRkaVS4pjdbZo8i8iAcH1kKiJSGdUdszV5FpHB4JSaMCgiIkuowmN25SfPY9R56ujuWW1PHnu80G/Nsr2FtkenJgpt09PFzLqZejGJsN6aCBcWSQmS6lI/oQjy7DxYlTtKDoz6WZCk562FXmgjsTBqC5IDU5MI4+TAKFEzsV8HiYDJSYmR1ETFSOrPRsL+khMcF7n/OY/b63GwovFzIiJDqaJjduUnzyIiB1R0IBYRGUoVHbM1eRaRAeGVHYhFRIZPdcdsTZ5FZDA40Oh13IiIiCSp8Jhd+cnzmNV5au3RWW1PGX+k0G/7+EGFtl3jywtt+ybGCm2NmSDmuSVeuNEIAo0jUaGTxPjm5DjoIG55JCh+EscyJ8ZGR7G9qW0dxEszmhbfnBxDHf3RGy6dE735Qa/EWOtIcjx8Qr9OYrRTRcfo+T2Eit7FEBEZShUdsys/eRYRyZRbnltERJZSdcdsTZ5FZDA4eEXXDBURGToVHrM1eRaRwVHRalUiIkOpomN25SfPowaH12YH0T519OFCv8PG1xTadi9bUWjbMz1eaIvWeW60tEVhOw0S46CjeNrEmGebKbalxkFH244E8c1RPP9IEBsdxzyXGy8dxhlHMdRhIHnqOs8lrxvdSbx0JGWwiV5/IPmY4TH6cNDrx3MSEZFYRcfsyk+eRUSAbBCuaOa2iMjQqfCYrcmziAyOit7FEBEZShUdszV5FpEB4Xg9iOEREZE+VN0xW5NnERkMTmWTT0REhk6Fx+zKT57HGOEptVWz2qKEwahwys6JlYW2x4KEwal6MduuNWFwOkjQSk4ijPLJpoL9BZuOTAfbBsmBHiQHWpRYmFjUxKNEwLD4SdS2+GTDMNEu2jZM8OugX5i8uPjEwkjquUQ/MEmJf2UXa0lMhOyqii57JCIylCo6ZqcuByEi0tcc8IYnPUREpLfKHrPNbL2Z/djMtprZOcH3J8zsH/PvX29mT2/63rl5+4/N7NULHavyd55FRIDsznpF72KIiAydEsdsM6sBFwKvBLYBN5jZRne/ranbmcAud3+mmZ0OfBx4g5k9FzgdeB5wJPBtM3uWu88ZkK07zyIyMLxeT3qIiEjvlThmnwRsdfc73X0KuBQ4raXPacAl+deXA68wM8vbL3X3SXe/C9ia729Olb/zfOPNkztqR9zx04V73rH0JzO/Q4EdvT6JDlX9NVT9/GHwX8PTFrvTR9l15bf98kMTu1f9PaysR9m149t+ecKY3XOD/rtWBVU/fxj819CtMXuZmW1uer7B3Tc0PV8L3Nv0fBvwopZ9HOjj7jNm9jDw5Lz9upZt1853MpWfPLv7Yb0+hxRmttnd1/X6PDpR9ddQ9fMHvYb5uPv6svcp5dOY3T1Vfw1VP3/Qa5hPyWN2VFa3NVh6rj4p286isA0RERERqbJtwNFNz48Cts/Vx8xGgYOAnYnbzqLJs4iIiIhU2Q3AcWZ2rJmNkyUAbmzpsxE4I//69cDV7u55++n5ahzHAscB35/vYJUP26iQDQt36XtVfw1VP3/QaxDplkH4Oa36a6j6+YNeQ1fkMcxnA1cCNeBid99iZucDm919I3AR8PdmtpXsjvPp+bZbzOwy4DZgBnjHfCttAJhXtK64iIiIiEi3KWxDRERERCSRJs8iIiIiIok0ee4CM6uZ2Q/N7Bu9PpfFMLODzexyM/uRmd1uZv+p1+fULjP7f8xsi5ndamZfNrNlvT6nhZjZxWb2gJnd2tR2iJldZWZ35P9f08tznM8c5/+J/OfoZjP7qpkd3MtzFIlozO49jdm9oXE7jSbP3fFO4PZen0QHPgP8i7v/IvDLVOy1mNla4A+Ade5+PFkywem9PaskXwBa18E8B/iOux8HfCd/3q++QPH8rwKOd/fnA/8BnNvtkxJJoDG7hzRm99QX0Li9IE2el5iZHQX8OvD5Xp/LYpjZk4BfJctSxd2n3H13b89qUUaB5fnajitYYA3HfuDu3yXLCG7WXF70EuC1XT2pNkTn7+7fcveZ/Ol1ZOtpivQNjdl9Q2N2D2jcTqPJ89L7NPA+oNHrE1mkZwAPAn+bf4z5eTNb2euTaoe7/wz4M+Ae4D7gYXf/Vm/PatEOd/f7APL/P6XH59OJ3wO+2euTEGmhMbvHNGb3NY3baPK8pMzsN4AH3P3GXp9LB0aBXwH+yt1PBB6n/z92miWPMTsNOBY4ElhpZm/u7VkNNzP7Q7L1NL/Y63MR2U9jdn/QmN2fNG4/QZPnpfUS4FQzuxu4FDjZzP6ht6fUtm3ANne/Pn9+OdnAXCWnAHe5+4PuPg18Bfg/enxOi3W/mR0BkP//gR6fT9vM7AzgN4Dfdi00L/1FY3Z/0JjdZzRuz6bJ8xJy93Pd/Sh3fzpZssPV7l6pv57d/efAvWb27LzpFWRVeKrkHuDFZrbCzIzsNVQqgaZJc3nRM4Cv9/Bc2mZm64H3A6e6+55en49IM43ZfUNjdh/RuF2k8tyS4v8GvpjXi78T+N0en09b3P16M7sc+AHZR04/pALlRs3sy8DLgEPNbBvwIeAC4DIzO5PsAvNbvTvD+c1x/ucCE8BV2TWR69z97T07SZHBpDG7B6o+ZoPG7VQqzy0iIiIikkhhGyIiIiIiiTR5FhERERFJpMmziIiIiEgiTZ5FRERERBJp8iwiIiIikkiTZxERERGRRJo8i4iIiIgk0uRZ+pKZvc7M3Mx+sdfnIiIi89OYLcNEk2fpV28ENpOVyBURkf6mMVuGhibP0nfMbBXwUuBMsgF5f/vVZnZT/thnZn1d5lREZBhozJZhM9rrExAJvBb4trvfbGaPm9mvuPsP3P1kADP7feDlwFd6epYiIgIas2XI6M6z9KM3ApflX1/G7DsZbwFeA/y2u9d7cG4iIjKbxmwZKubuvT4HkQPM7MnAj4Cj3H3SzI4F/hfwNOD1wNuA09x9Xw9PU0RE0Jgtw0lhG9JvXg9scvdJAHe/y8x+DnwQOBn4DQ3CIiJ9Q2O2DB3deZa+YmbXAs8HHmlqfjKwCtgKPJ63/aW7X9TdsxMRkWYas2UYafIsIiIiIv9/+3YsAAAAACDM3zqFADaKnpgMgwAAMIlnAACYxDMAAEziGQAAJvEMAACTeAYAgEk8AwDAFEb+uxvQ5M++AAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# Plot joint probability\n", - "fig, (ax1,ax2) = plt.subplots(1,2, sharey=True, figsize=(12,4))\n", - "\n", - "X, Y = np.meshgrid(dz_range, beta_range)\n", - "\n", - "im1 = ax1.pcolormesh(X, Y, misfit)\n", - "im2 = ax2.pcolormesh(X, Y, np.exp(-misfit))\n", - "ax1.scatter(dz_opt, beta_opt, c='r')\n", - "ax2.scatter(dz_opt, beta_opt, c='r')\n", - "\n", - "ax1.set_ylabel(r\"$\\beta$\")\n", - "ax2.set_ylabel(r\"$\\beta$\")\n", - "ax1.set_xlabel(r\"$\\Delta z$\")\n", - "ax2.set_xlabel(r\"$\\Delta z$\")\n", - "ax1.set_title(r\"misfit $S(\\mathbf{m})$\")\n", - "ax2.set_title(r\"Posterior probability $P(\\mathbf{m}|\\mathbf{d})$\")\n", - "\n", - "fig.colorbar(im1, ax=ax1)\n", - "fig.colorbar(im2, ax=ax2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Metropolis-Hastings algorithm\n", - "\n", - "A practical approach to sample the posterior is to use MCMC methods such as the Metropolis-Hastings algorithm. This implements a random walk where $k=1,2,\\ldots,n$ for a Markov Chain $n$ samples long.\n", - "\n", - "1. Generate a candidate $\\mathbf{m}'$ for the next sample by picking from the prior distribution $P(\\mathbf{m})$.\n", - "2. Calculate the acceptance ratio between each sample of the posterior $\\alpha = P(\\mathbf{m}'|\\mathbf{d})/P(\\mathbf{m}_{k}|\\mathbf{d})$\n", - "3. Generate a random number $u: [0,1]$ which will be used to decide if a candidate is accepted or rejected.\n", - " - accept if $u \\leq \\alpha$ and set $\\mathbf{m}_{k+1} = \\mathbf{m}'$\n", - " - reject if $u > \\alpha$ and set $\\mathbf{m}_{k+1} = \\mathbf{m}_k$ instead\n", - "\n", - "The random walk should be initialised not too far from the MAP estimate. MCMC methods require tuning to adequately sample the posterior. Parameters to tweak include:\n", - "\n", - "- `burnin`: the number of burn-in simulations\n", - "- `nsim`: total simulation after burnin\n", - "- `x_scale`: scaling factor $\\gamma$ to generate a new candidate $\\mathbf{m}' = \\gamma \\mathbf{m}$.\n", - "\n", - "```python\n", - "posterior = metropolis_hastings(window_size, xpt, ypt, nsim, burnin, x_scale)\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 140, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.signal import tukey\n", - "\n", - "window_size = 304e3\n", - "\n", - "def tukey_taper(A):\n", - " return tukey(A, alpha=0.1)" - ] - }, - { - "cell_type": "code", - "execution_count": 149, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, taper=tukey_taper)\n", - "\n", - "x_scale = [0.25, 0.1, 1.0, 1.0]\n", - "\n", - "burnin = 1000\n", - "nsim = 100000\n", - "\n", - "# C can be initialised close to its MAP very easily\n", - "posterior = grid.metropolis_hastings(window_size, xpt, ypt, nsim, burnin, x_scale, C=Phi.mean(), taper=tukey_taper)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "10.1 s ± 426 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], - "source": [ - "%%timeit\n", - "posterior = grid.metropolis_hastings(window_size, xpt, ypt, nsim, burnin, x_scale, C=Phi.mean(), taper=None)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Appraising the ensemble\n", - "\n", - "How best to visualise the ensemble after the MCMC simulation? We cover some of the following:\n", - "\n", - "- Histograms\n", - "- Scatter plots & joint probabilities\n", - "- Trajectory of the random walk\n", - "- MAP estimate" - ] - }, - { - "cell_type": "code", - "execution_count": 150, - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/lib/python3.7/site-packages/matplotlib/axes/_axes.py:6571: UserWarning: The 'normed' kwarg is deprecated, and has been replaced by the 'density' kwarg.\n", - " warnings.warn(\"The 'normed' kwarg is deprecated, and has been \"\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "labels = [r'$\\beta$', r'$z_t$', r'$\\Delta z$', r'$C$']\n", - "\n", - "fig, axes = plt.subplots(1,4, figsize=(14,5))\n", - "for i, ax in enumerate(axes):\n", - " ax.hist(posterior[i], bins=20, normed=True)\n", - " ax.set_title(labels[i])" - ] - }, - { - "cell_type": "code", - "execution_count": 151, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "11.80734126384078" - ] - }, - "execution_count": 151, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "from scipy.stats import skewnorm\n", - "\n", - "fit_res = stats.skewnorm.fit(posterior[2])\n", - "skew_pdf = stats.skewnorm(*fit_res)\n", - "\n", - "skew_pdf.mean()" - ] - }, - { - "cell_type": "code", - "execution_count": 152, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(2.6063838927177105, 0.43236684227774075)" - ] - }, - "execution_count": 152, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.mean(posterior[0]), np.mean(posterior[1])" - ] - }, - { - "cell_type": "code", - "execution_count": 120, - "metadata": {}, - "outputs": [ - { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mposterior\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/usr/lib/python3.7/site-packages/scipy/stats/stats.py\u001b[0m in \u001b[0;36mmode\u001b[0;34m(a, axis, nan_policy)\u001b[0m\n\u001b[1;32m 444\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mscore\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mscores\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 445\u001b[0m \u001b[0mtemplate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 446\u001b[0;31m \u001b[0mcounts\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexpand_dims\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtemplate\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 447\u001b[0m \u001b[0mmostfrequent\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwhere\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcounts\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0moldcounts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moldmostfreq\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 448\u001b[0m \u001b[0moldcounts\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmaximum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcounts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moldcounts\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/lib/python3.7/site-packages/numpy/core/fromnumeric.py\u001b[0m in \u001b[0;36msum\u001b[0;34m(a, axis, dtype, out, keepdims, initial)\u001b[0m\n\u001b[1;32m 2074\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2075\u001b[0m return _wrapreduction(a, np.add, 'sum', axis, dtype, out, keepdims=keepdims,\n\u001b[0;32m-> 2076\u001b[0;31m initial=initial)\n\u001b[0m\u001b[1;32m 2077\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2078\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/lib/python3.7/site-packages/numpy/core/fromnumeric.py\u001b[0m in \u001b[0;36m_wrapreduction\u001b[0;34m(obj, ufunc, method, axis, dtype, out, **kwargs)\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mreduction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mpasskwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 85\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 86\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mufunc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreduce\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdtype\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mpasskwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 87\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 88\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mKeyboardInterrupt\u001b[0m: " - ] - } - ], - "source": [ - "\n", - "mode(posterior, axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 154, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/lib/python3.7/site-packages/matplotlib/axes/_axes.py:6571: UserWarning: The 'normed' kwarg is deprecated, and has been replaced by the 'density' kwarg.\n", - " warnings.warn(\"The 'normed' kwarg is deprecated, and has been \"\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "grid.bounds = [(None,None)]*4\n", - "grid.bounds = [(0,None), (0,None), (0,None), (None,None)]\n", - "posterior = grid.sensitivity(window_size, xpt, ypt, 1000, taper=None)\n", - "\n", - "labels = [r'$\\beta$', r'$z_t$', r'$\\Delta z$', r'$C$']\n", - "\n", - "fig, axes = plt.subplots(1,4, figsize=(14,5))\n", - "for i, ax in enumerate(axes):\n", - " ax.hist(posterior[i], bins=20, normed=True)\n", - " ax.set_title(labels[i])" - ] - }, - { - "cell_type": "code", - "execution_count": 105, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 2.7409911 , 0.38893203, 8.57367629, -18.46820934])" - ] - }, - "execution_count": 105, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "grid.optimise(window_size, xpt, ypt,)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "17.3 s ± 1.08 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], - "source": [ - "%%timeit\n", - "posterior = grid.sensitivity(window_size, xpt, ypt, 1000, taper=None)" - ] - }, - { - "cell_type": "code", - "execution_count": 101, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.optimize import minimize\n", - "\n", - "def sensitivity(\n", - " self,\n", - " window,\n", - " xc,\n", - " yc,\n", - " nsim,\n", - " beta=3.0,\n", - " zt=1.0,\n", - " dz=10.0,\n", - " C=5.0,\n", - " taper=np.hanning,\n", - " process_subgrid=None,\n", - " **kwargs\n", - "):\n", - " \"\"\"\n", - " Iterate through a list of centroids to compute the mean and\n", - " standard deviation of \\\\( \\\\beta, z_t, \\\\Delta z, C \\\\) by\n", - " perturbing their prior distributions\n", - " (if provided by the user - see add_prior).\n", - "\n", - " Args:\n", - " nsim : int\n", - " number of Monte Carlo simulations\n", - " window : float\n", - " size of window in metres\n", - " xc : float\n", - " centroid x values\n", - " yc : float\n", - " centroid y values\n", - " nsim : int\n", - " number of simulations\n", - " beta : float\n", - " starting fractal parameter \n", - " zt : float\n", - " starting top of magnetic layer\n", - " dz : float\n", - " starting thickness of magnetic layer\n", - " C : float\n", - " starting field constant\n", - "\n", - "\n", - " Returns:\n", - " beta : ndarray shape (nsim,)\n", - " fractal parameters\n", - " zt : ndarray shape (nsim,)\n", - " top of magnetic layer\n", - " dz : ndarray shape (nsim,)\n", - " thickness of magnetic layer\n", - " C : ndarray shape (nsim,)\n", - " field constant\n", - " \"\"\"\n", - " if process_subgrid is None:\n", - " # dummy function\n", - " def process_subgrid(subgrid):\n", - " return subgrid\n", - "\n", - " samples = np.empty((nsim, 4))\n", - " x0 = np.array([beta, zt, dz, C])\n", - "\n", - " use_keys = []\n", - " for key in self.prior_pdf:\n", - " prior_pdf = self.prior_pdf[key]\n", - " if prior_pdf is not None:\n", - " use_keys.append(key)\n", - "\n", - " # get subgrid\n", - " subgrid = self.subgrid(window, xc, yc)\n", - " subgrid = process_subgrid(subgrid)\n", - "\n", - " # compute radial spectrum\n", - " k, Phi, sigma_Phi = self.radial_spectrum(subgrid, taper=taper, **kwargs)\n", - "\n", - " for sim in range(0, nsim):\n", - " # randomly generate new prior values within PDF\n", - " for key in use_keys:\n", - " prior_pdf = self.prior_pdf[key]\n", - " self.prior[key][0] = prior_pdf.rvs()\n", - "\n", - " # minimise function\n", - " rPhi = np.random.normal(Phi, sigma_Phi)\n", - " res = minimize(\n", - " self.min_func, x0, args=(k, rPhi, sigma_Phi), method='L-BFGS-B')\n", - " samples[sim] = res.x\n", - "\n", - " # restore priors\n", - " for key in use_keys:\n", - " prior_pdf = self.prior_pdf[key]\n", - " self.prior[key] = list(prior_pdf.args)\n", - "\n", - " return list(samples.T)" - ] - }, - { - "cell_type": "code", - "execution_count": 102, - "metadata": {}, - "outputs": [], - "source": [ - "posterior = sensitivity(grid, window_size, xpt, ypt, 1000, taper=None)" - ] - }, - { - "cell_type": "code", - "execution_count": 103, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/lib/python3.7/site-packages/matplotlib/axes/_axes.py:6571: UserWarning: The 'normed' kwarg is deprecated, and has been replaced by the 'density' kwarg.\n", - " warnings.warn(\"The 'normed' kwarg is deprecated, and has been \"\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzIAAAFACAYAAABnQEnOAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJzt3X24ZXV93/33J4AiKAVlIBOGyWBCjcQqcJ9SEnp7oWiCYMW0mEKrEkPuSVI1miaXDsl9hzap7aRJfMiVRjsVBCMBKWLgEmIlKCVpBR0eVGCwoE5hBJkTwadqVOB7/7HX4OZwHjbn7L3WXue8X9d1rr33b619zmc/rLP3d/1+67dSVUiSJElSn/xQ1wEkSZIk6cmykJEkSZLUOxYykiRJknrHQkaSJElS71jISJIkSeodCxlJkiRJvWMhI0mSJKl3LGQkSZIk9Y6FTE8lOSLJXyZ5KMmXk7yu60ySpH5Kcl3zefLUrrNIfZHkGUn+fZK7k3wzyZeS/EmSdV1nWyssZPrrMuAa4GDg/wH+327jSNMnyaVJvjX0U0ne0HUuaZok2QQ8H9gBvKLTMFJPJDkQ+GvgJ4CXVdUzgP8b2Af40S6zrSUWMj2U5PnAs6rq7VX1SNM822UmaRpV1c9X1dOr6unA7wC3An/ecSxp2rwW+DBwAXDW8AJ3BkgLegfwIHB6Vd0FUFW7quqXq2p7t9HWDguZfjoB+JskP5Tk/wLeDry740zS1EryJgZf1l5SVQ8meVGzF1rSYNu4mEFP/4uSHLpngTsDpCdKcjjwGuC3q+rRrvOsZRYy/XQ0sB34RHP5beDyThNJU6rZe3w2gyLmq03zLwLpLpU0HZL8Y2B/4BNV9SDwceBfzLPe43YGtJtSmjovAWar6pNdB1nrLGT66Wjg08CLgB9n0LX5HztNJE2hJL8K/DJwUlX9bdP2CuCfAO9L8tou80lT4Czgg0PDlC/micPL5tsZIK1lhwL3dB1CFjK9k2Qv4LnALVX1aFV9AfgfHceSpk6SzcAbGHz5Gj6G7CMMtp8Tq+r93aSTupfkacDPMyhe9rgC+PEkL2jWecLOAEncAxyWxO/RHfMF6J/nAPsBL0uyV5KjGewpu7DbWNLU+Y/AjwFfGDpQ+TUMejE/3200aSq8kkGP/meS7JtkX+AR4GrgtYvsDJDWuo80l1uTHJBknyT/IMl5Tr3crr27DqAn7RjgDuCPGMww8wXg16rqhi5DSdOmqg6crz3JK4E7W44jTaOzgE3Ad+ZZ9gDwdAbfE76QPHZI2a9W1Z+1kk6aUlX1jSQvZrDD7C7gacC9wBUW/e1KVXWdQU9Ckj8AHqyq/9B1FqmPkhwFXAr8VVW9ues8kiRpeeyR6Z9jgD/pOoTUV1V1B/C8rnNIkqSV8RiZ/nkBDouRJEnSGufQMkmSJEm9Y4+MJEmSpN6xkJEkSZLUO60e7H/wwQfXpk2b2vyT0pN20003/W1VdT4PvNuLpp3bijQ6txdpdKNuL60WMps2bWL79u1t/knpSUvyv7vOAG4vmn5uK9Lo3F6k0Y26vTi0TJIkSVLvWMhIkiYmyb5JPpXkM0luT/Jvm/YjktyY5K4kH0zylKb9qc3tu5vlm7rML0maXhYykqRJ+i7w4qp6AXA0cHKS44HfB95RVUcCDwFnN+ufDTxUVT8OvKNZT5KkJ7CQkSRNTA18q7m5T/NTwIuBy5r2C4FXNtdPa27TLD8pSVqKK0nqkSULmSTnJ9md5LahtmcmuaYZEnBNkoMmG1Pqh/m2l6Flv5mkkhzcRTapK0n2SnIrsBu4BvgC8LWqerhZZRdwWHP9MOBegGb514FnzfM7NyfZnmT77OzspB+CJGkKjdIjcwFw8py2LcC1zZCAa5vbkubfXkhyOPBS4J62A0ldq6pHqupoYANwHPDc+VZrLufrfaknNFRtq6qZqppZt67zGW0lSR1YspCpquuBB+c0D3f9Dw8JkNa0BbYXGIz1fwvzfCGT1oqq+hpwHXA8cGCSPacA2ADc11zfBRwO0Cz/e8y/TUmS1rjlHiNzaFXdD9BcHjK+SNLqkuQVwJer6jMjrOtwGa0qSdYlObC5/jTgJcAO4BPA6c1qZwFXNNevbG7TLP94VbkDQJL0BBM/IWaSzcBmgI0bN076z0lTJcl+wG8DPzPK+lW1DdgGMDMz45c3rQbrgQuT7MVg59mlVfWRJHcAlyT5d8AtwHnN+ucBf5bkbgY9MWd0EVqSNP2WW8g8kGR9Vd2fZD2DAzjn5RczrXE/BhwBfKaZeGkDcHOS46rqK50mk1pQVZ8Fjpmn/YsMjpeZ2/53wKtaiCZJ6rnlDi0b7vofHhIgaUhVfa6qDqmqTVW1icH4/2MtYiRJklZmyR6ZJBcDJwIHJ9kFnAtsBS5NcjaDWZjcezYhm7ZctejynVtPbSmJRjHf9lJV5y1+L42L24s0usW2F7cVrTVLfX4sxu2lO0sWMlV15gKLThpzFqn3Ftle9izf1FIUSZKkVW25Q8skSZIkqTMWMpIkSZJ6x0JGkiRJUu9YyEiSJEnqnYmfEFOLW8ksGZIkSdJaZY+MJEmSpN6xkJEkSZLUOxYykiRJknrHQkaSJElS71jISJIkSeodCxlJkiRJveP0y5LWjMWmO9+59dQWk0iSpJWyR0aSJElS71jISJIkSeodCxlJkiRJvWMhI0mSJKl3LGQkSZIk9Y6FjCRJkqTesZCRJEmS1DsWMpIkSZJ6x0JGkiRJUu9YyEiSJEnqHQsZSZIkSb1jISNJkiSpdyxkJEmSJPXO3l0HkCRJ02XTlqsWXb5z66ktJdEeSc4HXg7srqrnNW3PBD4IbAJ2Aj9fVQ91lVFqmz0ykiRJ0+8C4OQ5bVuAa6vqSODa5ra0Ztgj03OL7TVzj5kkSatDVV2fZNOc5tOAE5vrFwLXAW9tLZTUMXtkpDFKcn6S3UluG2r7gyR3Jvlskg8nObDLjJKkVePQqrofoLk8pOM8UqvskZHG6wLgT4D3D7VdA5xTVQ8n+X3gHNxjtixLjduXJD1Rks3AZoCNGzd2nEYaH3tkpDGqquuBB+e0fayqHm5u3gBsaD2YJGk1eiDJeoDmcvd8K1XVtqqaqaqZdevWtRpQmiR7ZFYxZ52ZSr/IYIYZSZJW6krgLGBrc3lFt3GkdtkjI7UkyW8DDwMXLbLO5iTbk2yfnZ1tL5w0IUkOT/KJJDuS3J7kTU37v0ny5SS3Nj+nDN3nnCR3J/l8kp/tLr00PZJcDHwSeE6SXUnOZlDAvDTJXcBLm9vSmmGPjNSCJGcxmP//pKqqhdarqm3ANoCZmZkF15N65GHgN6rq5iTPAG5Kck2z7B1V9YfDKyc5CjgD+EngR4C/SvL3q+qRVlNLU6aqzlxg0UmtBpGmiD0y0oQlOZnBwf2vqKpvd51HalNV3V9VNzfXvwnsAA5b5C6nAZdU1Xer6kvA3cBxk08qSeobe2SkMWq6/k8EDk6yCziXwSxlTwWuSQJwQ1X9SmchpY4058A4BrgROAF4Q5LXAtsZ9No8xKDIuWHobrtYvPCRpE55THJ3LGSkMVqg6/+81oNIUybJ04EPAW+uqm8keTfwe0A1l3/EYDKMzHP3JwyzdDpZSZJDyyRJE5VkHwZFzEVVdTlAVT1QVY9U1aPAf+EHw8d2AYcP3X0DcN/c3+l0spIkCxlJ0sRkMJ7yPGBHVb19qH390Go/B9zWXL8SOCPJU5McARwJfKqtvJKk/nBomSRpkk4AXgN8LsmtTdtvAWcmOZrBsLGdwC8DVNXtSS4F7mAw49nrnbFMkjQfCxlJ0sRU1d8w/3EvVy9yn7cBb5tYKEnSquDQMkmSJEm9YyEjSZIkqXcsZCRJkiT1zooKmSS/nuT2JLcluTjJvuMKJkmSJEkLWXYhk+Qw4NeAmap6HrAXcMa4gkmSJEnSQlY6tGxv4GlJ9gb2Y56TlkmSJEnSuC17+uWq+nKSPwTuAb4DfKyqPja2ZJIkadk2bbmq6wiSNFErGVp2EHAacATwI8D+SV49z3qbk2xPsn12dnb5SSVJkiSpsZITYr4E+FJVzQIkuRz4aeADwytV1TZgG8DMzEyt4O9pzJbaW7dz66ktJZEkSZKenJUcI3MPcHyS/ZIEOAnYMZ5YkiRJkrSwlRwjc2OSy4CbgYeBW2h6XiSpb+yhlCSpX1YytIyqOhc4d0xZJEmSJGkkK51+WZIkSZJaZyEjSZIkqXcsZCRJkiT1joWMJEmSpN6xkJEkSZLUOxYykiRJknrHQkaSJElS71jISJIkSeqdFZ0QU5IkrT2btly16PKdW09tKYmktcweGUmSJEm9YyEjSZIkqXccWiZJkiRNiEMxJ8ceGUmSJEm9YyEjjVGS85PsTnLbUNszk1yT5K7m8qAuM0qSJK0GFjLSeF0AnDynbQtwbVUdCVzb3JYkSdIKeIyMNEZVdX2STXOaTwNObK5fCFwHvLW1UJLUssWOCfB4AEnjYiEjTd6hVXU/QFXdn+SQhVZMshnYDLBx48aW4mkc/OImSVK7HFomTZGq2lZVM1U1s27duq7jSJIkTS0LGWnyHkiyHqC53N1xHknSKpLk15PcnuS2JBcn2bfrTFIbHFrWgqXmD9eqdyVwFrC1ubyi2ziSpNUiyWHArwFHVdV3klwKnMFg8hlpVbNHRhqjJBcDnwSek2RXkrMZFDAvTXIX8NLmtiRJ47I38LQkewP7Afd1nEdqhT0y0hhV1ZkLLDqp1SCSpDWhqr6c5A+Be4DvAB+rqo8Nr+NEMlqt7JGRJEnqqeYky6cBRwA/Auyf5NXD6ziRjFYrCxlJ0sQkOTzJJ5LsaA5GflPT/swk1yS5q7k8qGlPkj9OcneSzyY5tttHIE29lwBfqqrZqvo+cDnw0x1nklphISNJmqSHgd+oqucCxwOvT3IUsAW4tqqOBK5tbgO8DDiy+dkMvLv9yFKv3AMcn2S/JGEwlHlHx5mkVniMjCSNwNkHl6c5GeyeE8J+M8kO4DAGQ2FObFa7ELgOeGvT/v6qKuCGJAcmWb/npLKSHq+qbkxyGXAzgx0HtwDbuk0ltcNCRpLUiiSbgGOAG4FD9xQnVXV/kkOa1Q4D7h26266mzUJGWkBVnQuc23WOLi21s2nn1lNbSqI2ObRMkjRxSZ4OfAh4c1V9Y7FV52mreX7f5iTbk2yfnZ0dV0xJUo9YyEiSJirJPgyKmIuq6vKm+YEk65vl64HdTfsu4PChu29gnnNiOAuTJMlCRpI0Mc3Bx+cBO6rq7UOLrgTOaq6fBVwx1P7aZvay44Gve3yMJGk+HiMjSZqkE4DXAJ9LcmvT9lvAVuDSJGczmHXpVc2yq4FTgLuBbwOvazeuJKkvLGQkSRNTVX/D/Me9wGCa2LnrF/D6iYaStOY48+Tq5NAySZIkSb1jj4wkST3kHmZJa509MpIkSZJ6x0JGkiRJUu9YyEiSJEnqHQsZSZIkSb1jISNJkiSpdyxkJEmSJPWOhYwkSZKk3rGQkSRJktQ7FjKSJEmSemdFhUySA5NcluTOJDuS/NS4gkmSJEnSQvZe4f3fBXy0qk5P8hRgvzFkkiRJkqRFLbuQSXIA8ELgFwCq6nvA98YTS5IkSZIWtpKhZc8GZoH3JbklyXuT7D+mXJIkSZK0oJUMLdsbOBZ4Y1XdmORdwBbg/xteKclmYDPAxo0bV/DnJK0Gm7ZcteCynVtPbTGJJEnqs5X0yOwCdlXVjc3tyxgUNo9TVduqaqaqZtatW7eCPydJkiRJA8suZKrqK8C9SZ7TNJ0E3DGWVJIkSZK0iJXOWvZG4KJmxrIvAq9beSRpdUry68AvAQV8DnhdVf1dt6kkSZL6aUWFTFXdCsyMKYu0aiU5DPg14Kiq+k6SS4EzgAs6DTZlFjt+RpIkadiKTogp6UnZG3hakr0ZnHPpvo7zSJIk9ZaFjNSCqvoy8IfAPcD9wNer6mPdppIkSeqvlR4jIxwOo6UlOQg4DTgC+BrwX5O8uqo+MGc9pyuXJEkagT0yUjteAnypqmar6vvA5cBPz13J6colSZJGYyEjteMe4Pgk+yUJg+nKd3ScSZIkqbcsZKQWNCeOvQy4mcHUyz8EbOs0lCRJUo95jIzUkqo6Fzi36xySJEmrgT0ykiRJknrHQkaSJElS71jISJIkSeodCxlJkiRJvWMhI0mS1GNJDkxyWZI7k+xI8lNdZ5La4KxlkiRJ/fYu4KNVdXqSpwD7dR1IaoOFjCRJUk8lOQB4IfALAFX1PeB7XWaS2uLQMkmSpP56NjALvC/JLUnem2T/rkNJbbCQkSRNVJLzk+xOcttQ279J8uUktzY/pwwtOyfJ3Uk+n+Rnu0kt9cbewLHAu6vqGOD/AFuGV0iyOcn2JNtnZ2e7yChNhIWMJGnSLgBOnqf9HVV1dPNzNUCSo4AzgJ9s7vOnSfZqLanUP7uAXVV1Y3P7MgaFzWOqaltVzVTVzLp161oPKE2KhYwkaaKq6nrgwRFXPw24pKq+W1VfAu4GjptYOKnnquorwL1JntM0nQTc0WEkqTUWMpKkrrwhyWeboWcHNW2HAfcOrbOraZO0sDcCFyX5LHA08O87ziO1wkJGktSFdwM/xuBL1/3AHzXtmWfdmtvgmH/pB6rq1mbo2POr6pVV9VDXmaQ2WMhIklpXVQ9U1SNV9SjwX/jB8LFdwOFDq24A7pvn/o75l6Q1zkJGktS6JOuHbv4csGdGsyuBM5I8NckRwJHAp9rOJ0mafp4QU5I0UUkuBk4EDk6yCzgXODHJ0QyGje0Efhmgqm5PcimDg5UfBl5fVY90kVuSNN0sZCRJE1VVZ87TfN4i678NeNvkEkmSVgOHlkmSJEnqHQsZSZIkSb1jISNJkiSpdyxkJEmSJPWOB/tLkiRp6m3aclXXETRl7JGRJEmS1DsWMpIkSZJ6x0JGkiRJUu9YyEiSJEnqHQsZSZIkSb3jrGWSJKk1S808tXPrqS0lkdR39shIkiRJ6h0LGaklSQ5MclmSO5PsSPJTXWeSJEnqK4eWaUF2/4/du4CPVtXpSZ4C7Nd1ILXDbUmStJDFPiP8fFichYzUgiQHAC8EfgGgqr4HfK/LTJIkSX3m0DKpHc8GZoH3JbklyXuT7D93pSSbk2xPsn12drb9lJIkST1hISO1Y2/gWODdVXUM8H+ALXNXqqptVTVTVTPr1q1rO6MkSVJvWMhI7dgF7KqqG5vblzEobCRJkrQMFjJSC6rqK8C9SZ7TNJ0E3NFhJEmSpF5b8cH+SfYCtgNfrqqXrzyStGq9EbiombHsi8DrOs4zEUvN0CVJkjQO45i17E3ADuCAMfwuadWqqluBma5zSJIkrQYrGlqWZANwKvDe8cSRJEmSpKWt9BiZdwJvAR4dQxZJkiRJGsmyC5kkLwd2V9VNS6zneTEkSZIkjdVKemROAF6RZCdwCfDiJB+Yu5LnxZAkSZI0bssuZKrqnKraUFWbgDOAj1fVq8eWTJIkSZIW4HlkJEmSJPXOOKZfpqquA64bx++SJEmSpKXYIyNJkiSpdyxkJEmSJPWOhYwkSZKk3rGQkSRJktQ7YznYX5IkjdemLVd1HUGSppo9MpIkST2WZK8ktyT5SNdZpDbZIyNJktRvbwJ2AAd0HWQl7IXUk2WPjCRJUk8l2QCcCry36yxS2yxkJEkTleT8JLuT3DbU9swk1yS5q7k8qGlPkj9OcneSzyY5trvkUi+8E3gL8GjXQaS2WchIkibtAuDkOW1bgGur6kjg2uY2wMuAI5ufzcC7W8oo9U6SlwO7q+qmJdbbnGR7ku2zs7MtpZMmz2NkJEkTVVXXJ9k0p/k04MTm+oXAdcBbm/b3V1UBNyQ5MMn6qrq/nbRSr5wAvCLJKcC+wAFJPlBVrx5eqaq2AdsAZmZmqv2YWq6ljhvaufXUlpJMJ3tkJEldOHRPcdJcHtK0HwbcO7TerqbtcdzDLEFVnVNVG6pqE3AG8PG5RYy0mlnISJKmSeZpe8Ie5KraVlUzVTWzbt26FmJJkqaNhYwkqQsPJFkP0Fzubtp3AYcPrbcBuK/lbFLvVNV1VfXyrnNIbfIYGS3bYuM21/qYTUlLuhI4C9jaXF4x1P6GJJcA/wj4usfHSJLmYyEjSZqoJBczOLD/4CS7gHMZFDCXJjkbuAd4VbP61cApwN3At4HXtR5YktQLFjKSpImqqjMXWHTSPOsW8PrJJpIkrQYeIyNJkiSpd+yRkaSOeZ4ASZKePHtkJEmSJPWOhYwkSZKk3rGQkVqUZK8ktyT5SNdZJEmS+sxCRmrXm4AdXYeQJEnqOwsZqSVJNgCnAu/tOoskSVLfWchI7Xkn8Bbg0a6DSJIk9Z2FjNSCJC8HdlfVTUustznJ9iTbZ2dnW0onSZLUP55HZgRLneNBGsEJwCuSnALsCxyQ5ANV9erhlapqG7ANYGZmptqPKUmS1A/2yEgtqKpzqmpDVW0CzgA+PreIkSRJ0ugsZCRJkiT1jkPLpJZV1XXAdR3HkCRJ6jV7ZCRJkiT1joWMJEmSpN6xkJEkSZLUOxYykiRJknrHQkaSJElS71jISJIkSeodCxlJkiRJveN5ZCRJ6simLVd1HUGSestCRtLjLPXFaufWU1tKImkt8n+QpFE5tEySJElS71jISJIkSeodCxlJkiRJvWMhI0mSJKl3PNhfE+HBmpIkSZqkZRcySQ4H3g/8MPAosK2q3jWuYJKmk9PFSpI0Hdb6juOV9Mg8DPxGVd2c5BnATUmuqao7xpRNkiRJkua17GNkqur+qrq5uf5NYAdw2LiCSZIkSdJCxnKwf5JNwDHAjfMs25xke5Lts7Oz4/hzkiRJkta4FRcySZ4OfAh4c1V9Y+7yqtpWVTNVNbNu3bqV/jlJkiRJWlkhk2QfBkXMRVV1+XgiSZIkSdLill3IJAlwHrCjqt4+vkiSJEkaRZLDk3wiyY4ktyd5U9eZpLaspEfmBOA1wIuT3Nr8nDKmXJIkSVranllknwscD7w+yVEdZ5Jasezpl6vqb4CMMYskaY1JshP4JvAI8HBVzSR5JvBBYBOwE/j5qnqoq4yaLmv9vBlzVdX9wP3N9W8m2TOLrKfD0Ko3llnLJElagRdV1dFVNdPc3gJcW1VHAtc2tyUtYbFZZKXVyEJGkjRtTgMubK5fCLyywyxSLyw2i6ynwtBqZSEjSepSAR9LclOSzU3boc1wmT3DZg7pLJ3UA0vNIuupMLRaLfsYGUlSO1b5MQEnVNV9SQ4Brkly5yh3aoqezQAbN26cZD5pqjmLrNYyCxlJUmeq6r7mcneSDwPHAQ8kWV9V9ydZD+ye537bgG0AMzMz1WZmacrsmUX2c0lubdp+q6qu7jDTvJbaKSM9WRYykqROJNkf+KFmpqX9gZ8Bfhe4EjgL2NpcXtFdSmm6OYus1jILGakFSQ4H3g/8MPAosK2q3tVtKqlzhwIfHoyMYW/gz6vqo0k+DVya5GzgHuBVHWaUJE0pCxmpHXtOWHZzkmcANyW5pqqc519rVlV9EXjBPO1fBU5qP5EkqU8sZHDMpibPE5ZJkiSNl9MvSy1b7IRlzvUvSZI0GgsZqUWLnbAMnOtfkiRpVBYyUkuWOmGZJEmSRmchI7XAE5ZJkiSNl4WM1I49Jyx7cZJbm59Tug4lSZLUV85aJrXAE5ZpkhabeXHn1lNbTCJJUnvskZEkSZLUOxYykiRJknrHQkaSJElS73iMjLQGLXZMhVaXpV5rj6GRJPWVhYwkSZJWzJ1kaptDyyRJkiT1zprpkXEvgSRJkrR62CMjSZIkqXcsZCRJkiT1zpoZWiZJkiStJYsdWrEaZq20R0aSJElS71jISJIkSeodCxlJkiRJvWMhI0mSJKl3LGQkSZIk9Y6FjCRJkqTecfpldWKx6QBhdUwJKPWB2+JkLfX8avxW+3Szkn7AHhlJkiRJvWMhI0mSJKl3HFomrUIOZ5EkSavdqilk/OImSZIkrR2rppCR1hILd0mStNZZyEiStEzuVJCk7ljISJK0CIsVSZpOFjKaSp4HQJIkSYtx+mVJkiRJvWOPjCRJkkbiUMvVY6Wv5TSMkLGQkSRJYukvdtPwxU3SD6yokElyMvAuYC/gvVW1dSyppFXI7UUanduLJmG19ia4vWitWvYxMkn2Av4T8DLgKODMJEeNK5i0mri9SKNze5FG5/aitWwlPTLHAXdX1RcBklwCnAbcsdxf6ExVGkVPx3SOfXuRVrGxbi+rdS+82jelQ8/cXjSV2vhev5JZyw4D7h26vatpk/REbi/S6NxepNG5vWjNWkmPTOZpqyeslGwGNjc3v5Xk88v6Y7+/nHtNxMHA33YdogWr9nEOvZcWeow/Ook/O0/bxLaXIX18HfuYGfqZe8nMS/zvncS2AiNsLwtsK9P4GphpNNOYCZ5krp5tLysxra9XW9by43/ssa/ku/kI9x1pe1lJIbMLOHzo9gbgvrkrVdU2YNsK/s5USbK9qma6zjFpa+FxtvwYO9le+vg69jEz9DP3FGdecnuZb1uZxsdjptFMYyaY3lxzLGt7WYmePC8Ts5Yf/7Q99pUMLfs0cGSSI5I8BTgDuHI8saRVx+1FGp3bizQ6txetWcvukamqh5O8AfhvDKb7O7+qbh9bMmkVcXuRRuf2Io3O7UVr2YrOI1NVVwNXjylLX6yaYXJLWAuPs9XH2NH20sfXsY+ZoZ+5pzbzMreXaXw8ZhrNNGaC6c31OB18vvTieZmgtfz4p+qxp+oJxxtLkiRJ0lRbyTEykiRJktQJC5l5JDk/ye4kty2wPEn+OMndST6b5Ni2M47DCI/zJ5J8Msl3k/xm2/nGYYTH+C+b1/CzSf5nkhe0nXE5kuxM8rkktybZPs/y05rHdGuS7Un+8dCyR5r2W5O0ekDoUrmH1vuHTc7Th9rOSnJX83NWO4lXnHlqn+skJyb5+lC+3xladnKSzzf/47a0mPlNSW5LcnuSN8+z/KAkH27e259K8rymfd/m9mea+/7bofsckeTG5n3zweZgaJI8tbl9d7N8U4uZLmqe39ua/1H7NO0LviYtZLogyZeG/vbRTXsy4ufdhHL99VCm+5L8RRvP1dDyvZLckuQjQ20rek/1RZb+/Fzwc2ahgWkEAAAHUUlEQVQ1WOrxD633hP/9fTfCaz/S9teKqvJnzg/wQuBY4LYFlp8C/CWDuduPB27sOvOEHuchwD8E3gb8Ztd5J/QYfxo4qLn+sr68lsBO4OBFlj+dHwwdfT5w59Cyb01r7madvYCPMxjvfXrT9kzgi83lQc31g6Y587Q/18CJwEcWeCxfAJ4NPAX4DHBUC3mfB9wG7Mfg+M2/Ao6cs84fAOc2138CuLa5HuDpzfV9gBuB45vblwJnNNffA/xqc/1fAe9prp8BfLDFTKc0ywNcPJRp3tekpUwXDL93h37XSJ93k8o15/4fAl7bxnM1tPxfA38+/LdW8p7q0w9Lf34u+DmzGn6WevzNOvP+7+/7zwiv/ZLbX1s/9sjMo6quBx5cZJXTgPfXwA3AgUnWt5NufJZ6nFW1u6o+DXy/vVTjNcJj/J9V9VBz8wYG8+/3XlV9q5r/NsD+zHPyzSn2RgZfWHYPtf0scE1VPdi8XtcAJ3cRbgHzZe6r44C7q+qLVfU94BIG//Mm7bnADVX17ap6GPjvwM/NWeco4FqAqroT2JTk0OZ/8beadfZpfipJgBcDlzXLLgRe2Vw/rblNs/ykZv2JZmrWu7pZXsCneHL/dyaSaRGjft5NNFeSZzB4Lf9iibxjydT8zQ3AqcB7h3Ks9D3VGyN8fvb5c2ZJI3wXhNX1v/8xIz72qWAhszyHAfcO3d7VtKnfzmaw57EPCvhYkpsyOGPzEyT5uSR3AlcBvzi0aN9mGMANSV45330naNHcSQ5j8EXjPXMWdbnNLTczTPFz3fipZjjPXyb5yaatq+f6NuCFSZ6VZD8GPQGHz1nnM8A/BUhyHIMzP29obu+V5FYGXyiuqaobgWcBX2u+xMLjH8tjj7NZ/vVm/UlnekwGQ8peA3x0qHm+16StTG9rhgq9I8lT5z5PjYXeDxN9rhhsY9dW1TeG2ib6XAHvBN4CPDq0/krfU6vKIp8zq94S//vXgqW2v1asaPrlNWy+PSyrak/EWpPkRQwKmb6M8T2hqu5LcghwTZI7mz0oj6mqDwMfTvJC4PeAlzSLNjb3fTbw8SSfq6ovTEnudwJvrapH5uzI7HKbW25mmO7n+mbgR6vqW0lOYbCn+0g6eq6rakeS32fQ2/YtBl8wH56z2lbgXc0X3s8Bt+xZp6oeAY5OciCD9/3zgAfm+1PN5ZKPcxKZqmp4zPmfAtdX1V83txd6TdrIdA7wFQbDCbcBbwV+d5TnqaXn6kyGekYm/VwleTmwu6puSnLi0PqLPR9r7rvBIp8za8Fi//tXuyW3v7bYI7M8u3j8Xp0NwH0dZdEKJXk+gw/I06rqq13nGUVV3ddc7gY+zGA40ELrXg/8WJKD59z3i8B1wDGTzjuUZancM8AlSXYCpwN/2vRkdLbNrSDzVD/XVfWNPcN5anAOin2a90iXz/V5VXVsVb2QwbCGu+bJ/LqqOhp4LbAO+NKcdb7G4Lk+GfhbBkOh9uy0G34sjz3OZvnfY56hFBPIRPM3z23W/ddzftd8r8nEM1XV/c0Qr+8C7+MH75eR3w8TfK6e1eS5qsXn6gTgFc12fQnw4iQfYAzvqdVo7ufMGrHg//7VbtTtrw0WMstzJfDaDBwPfL2q7u86lJ68JBuBy4HXVNX/6jrPKJLsn8F4cZLsD/wMgyEUw+v8eJpdRBnMMvQU4KsZzNDz1Kb9YAYf1ndMS+6qOqKqNlXVJgZjzP9VVf0FgzNW/0yT/6Dmvv9tmjNP+3Od5IeH3iPHMfg8+CrwaeDIDGZmegqDg5ZbmXGt6T3as13+UwYHwg8vP7DJBPBLDHozvpFkXbMnnyRPY7BX+M6qKuATDL5kAJwFXNFcv7K5TbP84836E83U3P4lBsd9nVlVjw79roVekzYyrW8uw+CYjz3vl5E/7yaRq/EqBgcW/11bz1VVnVNVG5rt+gwG749Xj+M9tVos9DnTbar2LPJ5teqNuv21waFl80hyMYMZGQ5Osgs4l8HBh1TVexjMTnEKcDfwbeB13SRdmaUeZ5IfBrYDBwCPZjB15VH1+DHKU22E1/J3GIxh/tNmm3y4qma6STuyQxl05cNgG/7zqvpokl+Bxx7XP2Pw5eP7wHeAf15VleS5wH9O8iiDfzxbq6qVL9cj5p5XVT2Y5PcYfMkG+N2qamNP57IzMzjQeJqf69OBX03yMIP3yBnNl66Hk7yBQaG4F3B+Vd3eUu4PNXvfvw+8vqoempP5ucD7kzzCoCg8u7nfeuDCJHsxeK4vrao90+W+lcFe03/HYNjQeU37ecCfJbmbwV7zM1rM9B7gfwOfbF6jy6vqd1n4NWkj00VJ1jEYHnUr8CtN+5P5vJtELhi8Nlvn/K1JP1eLWel7qhdG+Pyc93Omo7hjN8LjX7VGeOyjbn+Tz7qK3nOSJEmS1giHlkmSJEnqHQsZSZIkSb1jISNJkiSpdyxkJEmSJPWOhYwkSZKk3rGQkSRJktQ7FjKSJEmSesdCRpIkSVLv/P+i6HBzFWFxEwAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "labels = [r'$\\beta$', r'$z_t$', r'$\\Delta z$', r'$C$']\n", - "\n", - "fig, axes = plt.subplots(1,4, figsize=(14,5))\n", - "for i, ax in enumerate(axes):\n", - " ax.hist(posterior[i], bins=20, normed=True)\n", - " ax.set_title(labels[i])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Below are some joint probabilities visualised with the trajectory of the random walk." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot joint probability and trajectory of random walk\n", - "fig, (ax1,ax2) = plt.subplots(1,2, figsize=(12,4))\n", - "\n", - "\n", - "H1, xedges, yedges = np.histogram2d(posterior[1], posterior[3], normed=True)\n", - "X1, Y1 = np.meshgrid(xedges, yedges)\n", - "\n", - "H2, xedges, yedges = np.histogram2d(posterior[2], posterior[0], normed=True)\n", - "X2, Y2 = np.meshgrid(xedges, yedges)\n", - "\n", - "\n", - "ax1.pcolormesh(X1, Y1, H1)\n", - "ax1.plot(posterior[1], posterior[3], c='w')\n", - "\n", - "ax2.pcolormesh(X2, Y2, H2)\n", - "ax2.plot(posterior[2], posterior[0], c='w')\n", - "\n", - "ax1.set_xlabel(r\"$z_t$\")\n", - "ax1.set_ylabel(r\"$C$\")\n", - "ax2.set_xlabel(r\"$\\Delta z$\")\n", - "ax2.set_ylabel(r\"$\\beta$\")\n", - "\n", - "fig.colorbar(im1, ax=ax1)\n", - "fig.colorbar(im2, ax=ax2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mode_result = stats.mode(np.vstack(posterior).T, axis=0)\n", - "\n", - "x_opt1 = grid.optimise(window_size, xpt, ypt, taper=None)\n", - "x_opt2 = mode_result.mode.ravel()\n", - "\n", - "str_fmt = \"beta = {:.2f} zt = {:.2f} dz = {:5.2f} C = {:.2f}\"\n", - "\n", - "print(str_fmt.format(*x_opt1))\n", - "print(str_fmt.format(*x_opt2))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, (ax1, ax2) = plt.subplots(1,2,sharey=True,figsize=(12,4))\n", - "\n", - "Phi_opt1 = pycurious.bouligand2009(k, *x_opt1)\n", - "Phi_opt2 = pycurious.bouligand2009(k, *x_opt2)\n", - "\n", - "ax1.plot(k, Phi, 'o', c='0.5')\n", - "ax1.plot(k, Phi_opt1, linewidth=2, label='MAP (optimised)')\n", - "ax1.plot(k, Phi_opt2, linewidth=2, label='MAP (MCMC)')\n", - "ax2.semilogx(k, Phi, 'o', c='0.5')\n", - "ax2.semilogx(k, Phi_opt1, linewidth=2, label='MAP (optimised)')\n", - "ax2.semilogx(k, Phi_opt2, linewidth=2, label='MAP (MCMC)')\n", - "# ax2.semilogx(k, S2)\n", - "\n", - "ax1.set_ylabel('radial power spectrum')\n", - "ax1.set_xlabel('wavenumber (rad/km)')\n", - "ax2.set_xlabel('log wavenumber (rad/km)')\n", - "\n", - "ax1.legend()\n", - "ax2.legend()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The MAP estimate obtained from `grid.optimise` should be identical to that from `grid.metropolis_hastings`, but may in differ in practice if `nsim` is too few or `x_scale` is not properly tuned. Running multiple chains in parallel should alleviate most concerns. Otherwise parts of the probability space may be non-unique." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pycurious/Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb b/pycurious/Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb deleted file mode 100644 index aed8b01..0000000 --- a/pycurious/Examples/Notebooks/Bouligand/Ex4-Spatial-variation-of-Curie-depth.ipynb +++ /dev/null @@ -1,306 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example 4 - Spatial variation of Curie depth\n", - "\n", - "In Example 2 and 3 we computed the Curie depth for a single point using a fixed window. Here, compute the Curie depth over a magnetic anomaly and estimate its uncertainty.\n", - "\n", - "### Contents\n", - "\n", - "- [Optimisation routine](#Optimisation-routine)\n", - "- [Uncertainty analysis](#Uncertainty-analysis)\n", - "- [Sensitivity analysis](#Sensitivity-analysis)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib inline\n", - "\n", - "import pycurious" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# load x,y,anomaly\n", - "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", - "\n", - "nx, ny = 305, 305\n", - "\n", - "x = mag_data[:,0]\n", - "y = mag_data[:,1]\n", - "d = mag_data[:,2].reshape(ny,nx)\n", - "\n", - "xmin, xmax = x.min(), x.max()\n", - "ymin, ymax = y.min(), y.max()\n", - "\n", - "# initialise CurieOptimise object\n", - "grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimisation routine\n", - "\n", - "Here we iteratively evaluate the Curie depth across the magnetic anomaly. We use gradient-based inversion which is deterministic, because it doesn't include uncertainty, but a quick means to recover Curie depth from the magnetic anomaly." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# get centroids\n", - "\n", - "window_size = 200e3\n", - "xc_list, yc_list = grid.create_centroid_list(window_size, spacingX=10e3, spacingY=10e3)\n", - "\n", - "print(\"number of centroids = {}\".format(len(xc_list)))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# no priors\n", - "grid.reset_priors()\n", - "\n", - "beta, zt, dz, C = grid.optimise_routine(window_size, xc_list, yc_list)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# get dimensions of domain\n", - "xcoords = np.unique(xc_list)\n", - "ycoords = np.unique(yc_list)\n", - "nc, nr = xcoords.size, ycoords.size\n", - "\n", - "\n", - "# plot results\n", - "fig, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4, sharex=True, sharey=True, figsize=(17,3.))\n", - "\n", - "im1 = ax1.imshow(beta.reshape(nr,nc))\n", - "im2 = ax2.imshow(zt.reshape(nr,nc))\n", - "im3 = ax3.imshow(dz.reshape(nr,nc))\n", - "im4 = ax4.imshow(C.reshape(nr,nc))\n", - "\n", - "fig.colorbar(im1, ax=ax1, label=r\"$\\beta$\")\n", - "fig.colorbar(im2, ax=ax2, label=r\"$z_t$\")\n", - "fig.colorbar(im3, ax=ax3, label=r\"$\\Delta z$\")\n", - "fig.colorbar(im4, ax=ax4, label=r\"$C$\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# plot Curie depth\n", - "\n", - "curie_depth = zt + dz\n", - "\n", - "fig = plt.figure()\n", - "ax1 = fig.add_subplot(111)\n", - "im1 = ax1.imshow(curie_depth.reshape(nr,nc), cmap=plt.cm.BrBG)\n", - "fig.colorbar(im1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Uncertainty analysis\n", - "\n", - "The variation of Curie depth is not particularly useful without any estimate of the uncertainty. We conduct the same MCMC simulation we performed in the previous example [Ex3-Posing-the-inverse-problem](./Ex3-Posing-the-inverse-problem.ipynb) to sample the posterior." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x_scale = [0.25, 0.1, 1.0, 0.5]\n", - "\n", - "# run more simulations for production runs\n", - "burnin = 1000\n", - "nsim = 5000\n", - "\n", - "# mean across the domain\n", - "mu_beta, mu_zt, mu_dz, mu_C = beta.mean(), zt.mean(), dz.mean(), C.mean()\n", - "\n", - "pt_post = []\n", - "\n", - "# This will take some time\n", - "for xc, yc in zip(xc_list, yc_list):\n", - " posterior = grid.metropolis_hastings(window_size, xc, yc, nsim, burnin, x_scale,\\\n", - " mu_beta, mu_zt, mu_dz, mu_C, taper=None)\n", - " pt_post.append( posterior )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "curie_depth = np.zeros_like(xc_list)\n", - "uncertainty = np.zeros_like(xc_list)\n", - "\n", - "for i, pt in enumerate(pt_post):\n", - " betaP, ztP, dzP, CP = pt\n", - " cpd = ztP + dzP\n", - " curie_depth[i] = np.mean(cpd)\n", - " uncertainty[i] = np.std(cpd)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# plot Curie depth\n", - "\n", - "curie_depth = zt + dz\n", - "\n", - "fig, (ax1,ax2) = plt.subplots(1,2, figsize=(11,4))\n", - "im1 = ax1.imshow(curie_depth.reshape(nr,nc), cmap=plt.cm.BrBG)\n", - "im2 = ax2.imshow(uncertainty.reshape(nr,nc), cmap=plt.cm.Blues)\n", - "fig.colorbar(im1, ax=ax1)\n", - "fig.colorbar(im2, ax=ax2)\n", - "\n", - "ax1.set_title(\"Curie depth (km)\")\n", - "ax2.set_title(\"Uncertainty (km)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Sensitivity analysis\n", - "\n", - "The sensitivity analysis is a gradient-based solution to approximate the posterior distribution (read the motivation for this in Tarantola: *Inverse Problem Theory*). The radial power spectrum $\\Phi$ is randomly perturbed within its uncertainty $\\sigma_{\\Phi}$ to sample locally around the MAP estimate.\n", - "\n", - "$$\n", - "\\Phi_{d}^* = \\Phi_d + \\delta \\Phi_d\n", - "$$\n", - "\n", - "where $\\delta \\Phi_d$ is a random perturbation generated from the probability density of $\\Phi_d$. This is equivalent to pertubing the likelihood function,\n", - "\n", - "$$\n", - "P(\\Phi|\\mathbf{m}) = \\frac{1}{(\\sqrt{2\\pi} \\sigma_\\Phi)^n} \\exp \\left( - \\frac{\\lvert \\Phi(\\mathbf{m}) - \\Phi_d^* \\lvert^2}{2 \\sigma_\\Phi^2} \\right)\n", - "$$\n", - "\n", - "where $\\mathbf{m}^*$ is a perturbed prior distribution: $\\mathbf{m}_p^* = \\mathbf{m}_p + \\delta \\mathbf{m}_p$. If the prior is a Gaussian normal distribution, then the perturbed prior is\n", - "\n", - "$$\n", - "P(\\mathbf{m}) = \\frac{1}{\\sqrt{2\\pi} \\sigma_p} \\exp \\left( - \\frac{\\lvert \\mathbf{m} - \\mathbf{m}_p^* \\lvert^2}{2 \\sigma_p^2} \\right)\n", - "$$\n", - "\n", - "Prior distributions can be added by\n", - "\n", - "```python\n", - "from scipy import stats\n", - "beta_p = stats.norm(3.0, 1.0)\n", - "grid.add_prior(beta=beta_p)\n", - "```\n", - "\n", - "We repeat the inversion multiple times, sampling different values from $P(\\mathbf{m})$ and $P(\\Phi|\\mathbf{m})$ to evaluate the uncertainty in each variable." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nsim = 100\n", - "pt_post = []\n", - "\n", - "for xc, yc in zip(xc_list, yc_list):\n", - " sensitivity = grid.sensitivity(window_size, xc, yc, nsim, mu_beta, mu_zt, mu_dz, mu_C, taper=None)\n", - " pt_post.append( sensitivity )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "curie_depth = np.zeros_like(xc_list)\n", - "uncertainty = np.zeros_like(xc_list)\n", - "\n", - "for i, pt in enumerate(pt_post):\n", - " betaP, ztP, dzP, CP = pt\n", - " cpd = ztP + dzP\n", - " curie_depth[i] = np.mean(cpd)\n", - " uncertainty[i] = np.std(cpd)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# plot Curie depth\n", - "\n", - "curie_depth = zt + dz\n", - "\n", - "fig, (ax1,ax2) = plt.subplots(1,2, figsize=(11,4))\n", - "im1 = ax1.imshow(curie_depth.reshape(nr,nc), cmap=plt.cm.BrBG)\n", - "im2 = ax2.imshow(uncertainty.reshape(nr,nc), cmap=plt.cm.Blues)\n", - "fig.colorbar(im1, ax=ax1)\n", - "fig.colorbar(im2, ax=ax2)\n", - "\n", - "ax1.set_title(\"Curie depth (km)\")\n", - "ax2.set_title(\"Uncertainty (km)\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pycurious/Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb b/pycurious/Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb deleted file mode 100644 index be69111..0000000 --- a/pycurious/Examples/Notebooks/Tanaka/Ex2-Compute-Curie-depth.ipynb +++ /dev/null @@ -1,213 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example 2 - Compute Curie depth\n", - "Tanaka *et al.* (1999) based their method upon an expression for the radial power spectrum that assumes random magnetisation of the crust, and a radially averaged power spectra $\\Phi_{\\Delta T}(|k|)$:\n", - "\n", - "$$(1) \\Phi_{\\Delta T}(|k|)=Ae^{-2|k|Z_t}(1-e^{-|k|(Z_b-Z_t)})^2$$\n", - "\n", - "where:\n", - " - $k$ - spatial wavenumber ($k=2\\pi/\\lambda$, where $\\lambda$ is wavelength).\n", - " - $Z_t$ - top of assumed magnetic source.\n", - " - $Z_b$ - bottom of assumed magnetic source.\n", - " - ($Z_b-Z_t$ is hence the thickness of the magnetic source)\n", - "\n", - "For wavelengths less than twice the source thickness, this simplifies to:\n", - "\n", - "$$(2) \\ln [\\Phi_{\\Delta T}(|k|)^{1/2}] = \\ln B-|k|Z_t $$\n", - "\n", - "where $B$ is another constant. Conversely, (1) can be rewritten, with $Z_o$ as the centroid depth of the magnetic source:\n", - "\n", - "$$(3) \\Phi_{\\Delta T}(|k|)^{1/2} = Ce^{-|k|Z_o}(e^{-|k|(Z_t-Z_o)}-e^{-|k|(Z_b-Z_o)}) $$\n", - "\n", - "For long wavelengths, where $2d$ is the magnetic source thickness:\n", - "$$(4) \\Phi_{\\Delta T}(|k|)^{1/2} = Ce^{-|k|Z_o}(e^{-|k|(-d)}-e^{-|k|(-d)})\\approx Ce^{-|k|Z_o}2|k|d $$\n", - "\n", - "$$ \\ln \\{\\Phi_{\\Delta T}(|k|)^{1/2}/|k|\\}=\\ln D-|k|Z_o $$\n", - "\n", - "\n", - "Estimates of $Z_t$ and $Z_o$ can be estimated from Equations (2) and (5), and hence the base of the magnetic source (assumed to be at the Curie point depth):\n", - "\n", - "$$(6) Z_b=Z_o-(Z_t-Z_o) = 2Z_o-Z_t $$\n", - "\n", - "### Contents\n", - "\n", - "- [Plot radial power spectrum](#Plot-radial-power-spectrum)\n", - "- [Compute CPD Estimate](#Compute-cpd-estimate)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib inline\n", - "\n", - "import pycurious" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# load x,y,anomaly\n", - "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", - "\n", - "nx, ny = 305, 305\n", - "\n", - "x = mag_data[:,0]\n", - "y = mag_data[:,1]\n", - "d = mag_data[:,2].reshape(ny,nx)\n", - "\n", - "xmin, xmax = x.min(), x.max()\n", - "ymin, ymax = y.min(), y.max()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plot radial power spectrum\n", - "The radial power spectrum is computed from a square window of the magnetic anomaly. Methods to select window sizes and compute the Fast Fourier Transform (FFT) belong to the `CurieGrid` object. We apply the default `np.hanning` taper to the power spectrum as in [Ex1-Plot-power-spectrum](#./Ex1-Plot-power-spectrum.ipynb).\n", - "\n", - "By default the FFT of the magnetic anomaly is raised to the power 2:\n", - "\n", - "```python\n", - "grid.radial_spectrum(subgrid, taper=None, power=2.0, **kwargs)\n", - "```\n", - "\n", - "which is compatible with Bouligand _et al._ (2009) computation of Curie depth. For Tanaka *et al.* (1999), however, we need to take the square root which is set with `power=0.5`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# initialise CurieOptimise object\n", - "grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax)\n", - "#grid = pycurious.CurieGrid(d, xmin, xmax, ymin, ymax)\n", - "\n", - "# pick the centroid\n", - "xpt = 0.5*(xmin + xmax)\n", - "ypt = 0.5*(ymin + ymax)\n", - "\n", - "window_size = 200e3\n", - "subgrid = grid.subgrid(window_size, xpt, ypt)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, taper=None, power=0.5)\n", - "\n", - "# Plot of power spectrum, as function of spatial frequency\n", - "plt.plot(k/(2*np.pi), Phi/(2*np.pi), '-o')\n", - "plt.title('Power Spectrum')\n", - "plt.xlabel(r'$|k|/(2\\pi)$ [km$^{-1}$]')\n", - "plt.ylabel(r'$\\ln(\\Phi_{\\Delta T}(|k|)^{1/2})/(2\\pi)$')\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Phi_n = np.log(np.exp(Phi)/k)\n", - "plt.plot(k/(2*np.pi), Phi_n/(2*np.pi), '-o')\n", - "plt.title('Power Spectrum, weighted by k')\n", - "plt.xlabel(r'$|k|/(2\\pi)$ [km$^{-1}$]')\n", - "plt.ylabel(r'$\\ln(\\Phi_{\\Delta T}(|k|)^{1/2}/|k|)/(2\\pi)$')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Compute CPD Estimate\n", - "As described above, once the power spectrum (and weighted spectrum) are computed, then the gradient of linear fits of certain wavenumber/spatial frequency windows can be taken as estimates of $Z_t$ and $Z_o$. The weighted linear fitting of the respective spectral window is implemented in the `CurieGrid` parent class as `tanaka1999`, and takes the spectrum, wave number, and spectrum error as inputs. The function also has functionality to pass specific spectral windows for each spectra, however, the default is to consider spatial frequencies of 0.05 - 0.2 (recalling that $k$, the wavenumber, is proportional to the spatial frequency $\\nu$ by $k=2\\pi\\nu$).\n", - "\n", - "\n", - "The function has two tuple returns: the gradient estimate, intercept and errors of $Z_t$ and $Z_o$. The gradient and errors can then be passed into the `CurieGrid.ComputeTanaka` method to obtain estimates of $Zb$ and $\\varepsilon Zb$." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "(Ztr,btr,dZtr), (Zor, bor, dZor) = pycurious.tanaka1999(k, Phi, sigma_Phi, (0,0.1), (0.2,0.3))\n", - "Zb, eZb = pycurious.ComputeTanaka(Ztr, dZtr, Zor, dZor)\n", - "\n", - "print('Zb estimate: {:.2f} +/- {:.2f} km'.format(Zb, eZb))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# make two plots with Z-lines overlain.\n", - "# Plot of power spectrum, as function of spatial frequency\n", - "fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,6),)\n", - "\n", - "ax1.plot(k/(2*np.pi), Phi/(2*np.pi), '-bo', k/(2*np.pi), (Ztr*k/(2*np.pi)+btr)/(2*np.pi),'r-')\n", - "#ax1.title('Power Spectrum')\n", - "ax1.set_xlabel(r'$|k|/(2\\pi)$ [km$^{-1}$]')\n", - "ax1.set_ylabel(r'$\\ln(\\Phi_{\\Delta T}(|k|)^{1/2})/(2\\pi)$')\n", - "\n", - "ax2.plot(k/(2*np.pi), Phi_n/(2*np.pi), '-o',k/(2*np.pi),(Zor*k/(2*np.pi)+bor)/(2*np.pi),'r-')\n", - "ax2.set_xlabel(r'$|k|/(2\\pi)$ [km$^{-1}$]')\n", - "ax2.set_ylabel(r'$\\ln(\\Phi_{\\Delta T}(|k|)^{1/2}/|k|)/(2\\pi)$')\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pycurious/Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb b/pycurious/Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb deleted file mode 100644 index 7399a49..0000000 --- a/pycurious/Examples/Notebooks/Tanaka/Ex3-Parameter-exploration.ipynb +++ /dev/null @@ -1,264 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example 3 - Parameter Exploration of Spectral Analysis method\n", - "\n", - "Due to the deterministic approach that [Tanaka *et al.*, 1999](http://linkinghub.elsevier.com/retrieve/pii/S0040195199000724) uses for estimating $Z_b$, the uncertainties returned by `CurieGrid.tanaka1999` are the simple results of error propagation from the initial spectra uncertainties. The spectra uncertainties represent the variance within the sampled sector of the 2D FFT, and may comprise both noise and anisotropic signal.\n", - "\n", - "We present here several tests of the `CurieGrid.tanaka1999` function in order to explore how the input parameters affect the resulting Curie point depth estimates. In particular, we consider the following:\n", - "\n", - "1. Effect of where in $k$-space is specific window used, separately for both the power and $k$-weighted power spectra (i.e., which $k$ do we consider?).\n", - "2. Effect of $k$-space window size (i.e., how much of the $k$-domain do we consider?). It is important to note that increasing the $k$-bandwidth also stabilises the estimation by increasing the statistics of the spectrum portion.\n", - "3. Effect of magnetic data window size (i.e., domain of magnetic data before computing FFT).\n", - "\n", - "### Contents\n", - "\n", - "- [Varying spatial frequency range](#Varying-spatial-frequency-range)\n", - "- [Varying spatial frequency size](#Varying-spatial-frequency-size)\n", - "- [Varying window size of magnetic anomaly](#Varying-window-size-of-magnetic-anomaly)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib inline\n", - "\n", - "import pycurious" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# load x,y,anomaly\n", - "mag_data = np.loadtxt(\"../../data/test_mag_data.txt\")\n", - "\n", - "nx, ny = 305, 305\n", - "\n", - "x = mag_data[:,0]\n", - "y = mag_data[:,1]\n", - "d = mag_data[:,2].reshape(ny,nx)\n", - "\n", - "xmin, xmax = x.min(), x.max()\n", - "ymin, ymax = y.min(), y.max()\n", - "\n", - "# initialise CurieOptimise object\n", - "grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax)\n", - "\n", - "# pick centroid\n", - "xpt = xmin + (xmax-xmin)/2\n", - "ypt = ymin + (ymax-ymin)/2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Varying spatial frequency range\n", - "\n", - "This test explores where in $k$-space is specific window used, separately for both the power and $k$-weighted power." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1) Where in k-space do we locate window?\n", - "window_size = 304e3\n", - "subgrid = grid.subgrid(window_size, xpt, ypt)\n", - "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, taper=None, power=0.5)\n", - "\n", - "# Number of bins to divide k-range into\n", - "nbins = 10\n", - "hmap = np.zeros((nbins**2,3))\n", - "\n", - "# k-range divided between 0 (i.e., DC), and 0.3 (i.e., spatial wavelengths of ~20 km)\n", - "kmin_range = np.linspace(0., 0.3, nbins)\n", - "kmax_range = kmin_range + (0.3/nbins)\n", - "\n", - "for j in range(0,nbins):\n", - " for l in range(0,nbins):\n", - " kmin0 = kmin_range[j]\n", - " kmin1 = kmax_range[j]\n", - " kmax0 = kmin_range[l]\n", - " kmax1 = kmax_range[l]\n", - " \n", - " (Ztr,btr,dZtr), (Zor, bor, dZor) = pycurious.tanaka1999(k, Phi, sigma_Phi, (kmin0, kmin1), (kmax0, kmax1))\n", - " Zb,eZb = pycurious.ComputeTanaka(Ztr, dZtr, Zor, dZor)\n", - " \n", - " hmap[j*nbins+l,0] = (kmin0+kmin1)/2\n", - " hmap[j*nbins+l,1] = (kmax0+kmax1)/2\n", - " hmap[j*nbins+l,2] = Zb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig2 = plt.figure(figsize=(16,16))\n", - "\n", - "ax2 = fig2.add_subplot(111)\n", - "ax2.set_xlabel(r'$\\nu$ window of $\\Phi/|k|$ (km$^{-1}$) (for Zo)')\n", - "ax2.set_ylabel(r'$\\nu$ window of $\\Phi$ (for Zt)')\n", - "\n", - "# SCATTER PLOT\n", - "im2 = ax2.scatter(hmap[:,1], hmap[:,0], 2000, np.clip(hmap[:,2],10,20), marker='s')\n", - "fig2.colorbar(im2, label='CPD (km)')\n", - "plt.title('Heatmap of CPD')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Results of this test suggest that the precise $k$ location of $Z_t$ is significantly less influential than the $k$ location of $Z_o$. Furthermore, for this test case with a known $Z_b$ of 16 km, a spatial frequency $\\nu$ window of $0.125-0.2$ (i.e., $k\\in\\{0.8-1.25\\}$ gives estimates consistent with the true $Z_b$. For simplicity, based upon this test we see minimal reason to not use the same $\\nu/k$ window for each spectrum." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Varying spatial frequency size\n", - "\n", - "Explore the effect of $k$-space window size (i.e., how much of the $k$-domain do we consider?). Increasing the $k$-bandwidth also stabilises the estimation by increasing the statistics of the spectrum portion." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 2) How big a bandwidth in k-space do we want?\n", - "\n", - "k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, taper=None, power=0.5)\n", - "\n", - "# We are testing this by taking one central spatial frequency (k=1), and estimating Zb with increasing k-bandwidths.\n", - "# Recall k=2*pi*nu\n", - "nu = 1.0/(2.0*np.pi)\n", - "\n", - "nbins = 30\n", - "output = np.zeros((nbins,3))\n", - "nr = np.linspace(0., 0.3, nbins+1)\n", - "output[:,0] = nr[1:]\n", - "\n", - "for j in range(0,nbins,1):\n", - " kmin = nu - 0.5*output[j,0]\n", - " kmax = nu + 0.5*output[j,0]\n", - " \n", - " (Ztr,btr,dZtr), (Zor, bor, dZor) = pycurious.tanaka1999(k, Phi, sigma_Phi, (kmin, kmax), (kmin, kmax))\n", - " Zb, eZb = pycurious.ComputeTanaka(Ztr, dZtr, Zor, dZor)\n", - " output[j,1] = Zb\n", - " output[j,2] = eZb\n", - " \n", - "fig2 = plt.figure(figsize=(8,8))\n", - "ax2 = fig2.add_subplot(111)\n", - "ax2.errorbar(output[:,0], output[:,1], yerr=output[:,2])\n", - "ax2.invert_yaxis()\n", - "ax2.set_xlabel('$\\\\nu$-bandwidth')\n", - "ax2.set_ylabel('CPD depth (km)')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The CPD estimates resulting from this test become consistent for $\\nu$-bandwidths greater than approximately 0.1. Statistics improve with larger bandwidths, however, it is worth remaining aware of the typically non-linear trend of the spectrum, as it may not be appropriate for a linear fit over a large domain of $\\nu$." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Varying window size\n", - "\n", - "Effect of magnetic data window size - i.e., domain of magnetic data before computing the FFT." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 3) How large a window width of magnetic data is required?\n", - "grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax)\n", - "xpt = xmin + (xmax-xmin)/2\n", - "ypt = ymin + (ymax-ymin)/2\n", - "\n", - "nwin = 50\n", - "baseW = 10000\n", - "inc = 2500\n", - "\n", - "output = np.zeros((nwin,3))\n", - "\n", - "# Define 'ws' as variable window size index\n", - "for i in range(0, nwin):\n", - " ws = baseW + i*inc\n", - " subgrid = grid.subgrid(ws, xpt, ypt)\n", - " k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, taper=None, power=0.5)\n", - " (Ztr,btr,dZtr), (Zor, bor, dZor) = pycurious.tanaka1999(k, Phi, sigma_Phi, (0.11, 0.21), (0.11, 0.21))\n", - " Zb, eZb = pycurious.ComputeTanaka(Ztr, dZtr, Zor, dZor)\n", - " \n", - " output[i] = [ws, Zb, eZb]\n", - " #print('Zb estimate for '+str(ws)+' m: '+np.array2string(Zb)+', +/- '+np.array2string(eZb)+' km')\n", - "\n", - "fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,6),)\n", - "ax1.plot(output[:,0], output[:,1])\n", - "ax1.set_ylim(0,30)\n", - "ax1.invert_yaxis()\n", - "ax1.set_xlabel('Data window size (m)')\n", - "ax1.set_ylabel('CPD estimate (km)')\n", - "\n", - "ax2.plot(output[:,0], output[:,2])\n", - "ax2.set_xlabel('Data window size (m)')\n", - "ax2.set_ylabel('CPD misfit (km)')\n", - "#plt.gca().invert_yaxis()\n", - "#plt.xlabel('Data window size (km)')\n", - "#plt.ylabel('CPD depth (km)')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The results of this test highlight that there is a significant amount of variability in the CPD estimates at all window sizes, inferring that where possible, a range of window sizes should be tested for any data set. However, the righthand plot of propagated error in the CPD estimates clarifies that estimates from larger windows are more robust. In particular, with the prior knowledge that the CPD of the test data is 16 km, window sizes of greater than 80 km show a reduced improvement in statistics. " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pycurious/__init__.py b/pycurious/__init__.py index 87fb5dc..21e5f93 100644 --- a/pycurious/__init__.py +++ b/pycurious/__init__.py @@ -15,135 +15,32 @@ # along with PyCurious. If not, see . """ +PyCurious estimates the **Curie point depth** -- the depth at which rock loses +its magnetisation -- from the radially averaged spectrum of a magnetic anomaly. -> PyCurious is a Python package for computing the Curie depth from the magnetic anomaly. +Two methods share one grid and spectrum layer, and both return uncertainties: -## Licence - -Copyright 2018-2019 Ben Mather, Robert Delhaye - -PyCurious is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or any later version. - -PyCurious is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PyCurious. If not, see . - -## Installation - -### Dependencies - -You will need **Python 2.7 or 3.5+**. -Also, the following packages are required: - -- [`numpy`](http://numpy.org) -- [`scipy`](https://scipy.org) -- [`cython`](https://cython.org/) - -__Optional dependencies__ for mapping module and running the Notebooks: - -- [`matplotlib`](https://matplotlib.org/) -- [`pyproj`](https://github.com/jswhit/pyproj) -- [`cartopy`](https://scitools.org.uk/cartopy/docs/latest/) - -### Installing using pip - -You can install `pycurious` using the -[`pip package manager`](https://pypi.org/project/pip/) with either -version of Python: - ->>> python2 -m pip install pycurious ->>> python3 -m pip install pycurious - -All the dependencies will be automatically installed by `pip`. - -### Installing using Docker - -A more straightforward installation for `pycurious` and all of its -dependencies may be deployed with [Docker](https://www.docker.com). -To install the docker image and start the Jupyter notebook examples: - ->>> docker pull brmather/pycurious:latest ->>> docker run --name pycurious -p 8888:8888 brmather/pycurious:latest - -## Documenation / Notebooks - -Jupyter notebooks that demonstrate the functionality of PyCurious and -some common workflows may be installed to a local directory with - -```python -import pycurious -pycurious.install_documentation(path="Notebooks") -``` - -## Contributing to pycurious - -We welcome contributions to `pycurious`, large or small [\\*](#footnote1). -That can be in the form of new code, improvements to the documentation, -helping with a missing test, or it may even just be pointing out a bug or -potential improvement to the team. - -For bugs and suggestions, the most effective way to reach the team is by -raising an issue on the github issue tracker. Github allows you to classify -your issues so that we know if it is a bug report, feature request or -feedback to the authors. - -If you wish to contribute some changes to the code then you should submit a -*pull request*. On GitHub we can review the code that you are contributing -and discuss it before the changes are merged into the development version of -the code. Before embarking on changes to the code, please first take a look -at the [`dev` branch](https://github.com/brmather/pycurious/tree/dev) which -is where unreleased changes are staged: we may have been working on something -similar already. - -### How to create a Pull Request (PR) - -Create your own fork of the project on github: log into your github account, -navigate to the [`pycurious` repository](https://github.com/brmather/pycurious/tree/dev) -and click on the *fork* button at the top right corner of this repository. -You can find detailed instructions and a discussion of how to fork a -repository, clone it locally and work on the changes -[in the GitHub guides](https://guides.github.com/activities/forking/). - -### When to make your pull request - -It is much easier to merge in small changes to the code than extensive ones -that touch multiple files. If a small incremental change is possible, please -issue a request for that change rather than saving everything up. - -It is a good idea to commit frequently and with commit messages that refer -to the changes that have been make and why they have been made. The commit -message is usually browsed without seeing the actual changes themselves and -which sections of the code have been altered so a helpful message provides -relevant details. - -### Tests - -We use [`pytest`](https://pypi.org/project/pytest/) for the unit testing -framework in `pycurious`. In the source directory this means running: - ->>> python setup.py test - -The existing tests should be passing before you start coding (help us out with -an issue if that is not the case !) and when you have finished. Any new -functionality should also have tests that we can use to verify the code. -It is important that you make it clear if the original tests have had to -change to accomodate new code / functionality. - -* _Small changes are our favorites as they are much -easier to quickly merge into the existing code. Proofreading typos, bug fixes, -... the little stuff is especially welcome._ +- ``CurieOptimiseBouligand`` fits the four-parameter analytic spectrum of + Bouligand *et al.* (2009) by optimisation. +- ``CurieOptimiseTanaka`` implements the centroid method of Tanaka *et al.* + (1999), fitting two straight lines to separate wavenumber bands. +Full documentation -- a getting started guide, tutorials, the theory behind each +method, and the API reference -- lives at https://brmather.github.io/pycurious/. """ # -*- coding: utf-8 -*- +from importlib.metadata import PackageNotFoundError, version as _version + +try: + __version__ = _version("pycurious") +except PackageNotFoundError: # running from a source tree without an install + __version__ = "2.0" + from .documentation import install_documentation from .grid import CurieGrid, bouligand2009, tanaka1999, maus1995, ComputeTanaka -from .optimise import CurieOptimise +from .optimise_bouligand import CurieOptimiseBouligand +from .optimise_tanaka import CurieOptimiseTanaka +from .synthetic import fractal_anomaly from . import mapping from . import download diff --git a/pycurious/documentation.py b/pycurious/documentation.py index 8415e62..e52de7c 100644 --- a/pycurious/documentation.py +++ b/pycurious/documentation.py @@ -20,8 +20,8 @@ """ -import pkg_resources as _pkg_resources -from distutils import dir_util as _dir_util +import importlib.resources as _resources +import shutil as _shutil import os @@ -48,19 +48,37 @@ def install_documentation(path="./PyCurious-Examples"): """ - Notebooks_Path = _pkg_resources.resource_filename( - "pycurious", os.path.join("Examples") - ) + Notebooks_Path = _find_examples() - ct = _dir_util.copy_tree( - Notebooks_Path, - path, - preserve_mode=1, - preserve_times=1, - preserve_symlinks=1, - update=0, - verbose=1, - dry_run=0, - ) + _shutil.copytree(Notebooks_Path, path, symlinks=True, dirs_exist_ok=True) return + + +def _find_examples(): + """ + Locate the bundled Examples directory. + + In an installed wheel the notebooks sit inside the package. In a source + checkout (including `pip install -e .`) they live at the repository root, + one level above the package, so fall back to that. + """ + + package_dir = _resources.files("pycurious") + + candidates = [ + os.path.join(str(package_dir), "Examples"), + os.path.join(os.path.dirname(str(package_dir)), "Examples"), + ] + + for candidate in candidates: + if os.path.isdir(candidate): + return candidate + + raise FileNotFoundError( + "Could not locate the PyCurious Examples directory. Looked in:\n " + + "\n ".join(candidates) + + "\nIf you installed PyCurious from PyPI, the notebooks may not have " + "been bundled; fetch them from " + "https://github.com/brmather/pycurious instead." + ) diff --git a/pycurious/grid.py b/pycurious/grid.py index 3978894..dc9700e 100644 --- a/pycurious/grid.py +++ b/pycurious/grid.py @@ -15,38 +15,182 @@ # along with PyCurious. If not, see . """ -This PyCurious module contains the `pycurious.grid.CurieGrid` class, -which can be initialised with a magnetic grid of equal spacing in the x and y direction. -It contains methods for the following functionality: +The ``CurieGrid`` class and the shared spectrum and covariance machinery. -- Decomposition of subgrids for processing square windows of the magnetic anomaly -- Removing linear trends from the magnetic anomaly -- Upward continuation -- Reduction to the pole +``CurieGrid`` is initialised with a magnetic grid of equal spacing in x and y and +provides: -Other functions within this module are useful to compute analytical solutions -of the radial power spectrum, \\( \\Phi \\) according to Bouligand *et al.* (2009), -Maus and Dimri (1995), and the decomposition of \\( \\Phi \\) from the magnetic -anomaly according to Tanaka *et al.* (1999): - -- `bouligand2009`: analytic solution used in `pycurious.optimise.CurieOptimise` -- `maus1995`: simplified version of `bouligand2009` without higher order integration. -- `tanaka1999`: to be used in conjunction with `ComputeTanaka` +- decomposition of subgrids for processing square windows of the anomaly; +- radially averaged spectra, either raw (``radial_spectrum``) or weighted ready + for fitting (``window_spectrum``); +- removing linear trends, upward continuation, and reduction to the pole. +The module also holds the analytic spectra used by the optimisers -- +``bouligand2009`` and its simplified form ``maus1995`` -- and the covariance +machinery both methods share. ``tanaka1999`` and ``ComputeTanaka`` implement the +centroid method without uncertainties and are **deprecated** in favour of +``pycurious.optimise_tanaka.CurieOptimiseTanaka``. """ # -*- coding: utf-8 -*- +from .parallel import CurieParallel import numpy as np +from scipy.linalg import solveh_banded from scipy.special import gamma, kv import warnings -try: - range = xrange -except: - pass +# How much larger the scatter of the binned mean is than sigma_Phi/sqrt(N), +# because the FFT cells in an annulus are not independent. There are two +# effects, and the deflation is stored as (dof_inf, lost): +# +# dof(N) = dof_inf * N / max(N - lost, 1) +# +# `dof_inf` is the asymptotic redundancy. A real field has Hermitian symmetry, +# so about half of its cells repeat -- the factor of 2 with no taper, which is +# exact -- and tapering correlates neighbours further. +# +# `lost` is a fixed number of cells given up to correlation however many the +# annulus holds, so it only matters for the innermost bins. It matters a lot +# there: at N = 8 it costs another factor of 2.6 under np.hanning, and those +# are the bins a centroid depth leans on hardest. +# +# Measured by Monte Carlo over 400 realisations at n = 128, 256 and 512. +# Both terms are stable to about 6% across that range, and depend on the bin +# count rather than on the grid size. +_TAPER_DOF = { + None: (2.0, 3.4), + "hanning": (3.3, 4.9), + "hamming": (3.0, 4.8), +} + + +# How many off-diagonals of the residual correlation to estimate. The measured +# correlation length is about 1.4 bins, so two is enough. +_CORRELATION_BANDS = 2 + +# Cap on the total off-diagonal weight. By Gershgorin, keeping it below one +# leaves the banded matrix positive definite and so factorisable. +_CORRELATION_LIMIT = 0.95 + + +def _banded_correlation(r): + """ + Correlation of neighbouring fit residuals, as a band. + + Neighbouring radial bins are not independent: a taper spreads each + wavenumber over a main lobe several bins wide, so their residuals + correlate. Treating them as independent understates the uncertainty of + every fitted parameter by around 30% under `numpy.hanning`. + + Estimating from the residuals rather than tabulating per taper means this + holds for a taper that has not been calibrated. On a correct model it + recovers the taper: 0.008 with no taper against a measured 0.003, and 0.383 + under `numpy.hanning` against a measured 0.363. + + Args: + r : 1D array + residuals, already whitened by their own uncertainties + + Returns: + ab : 2D array shape (`_CORRELATION_BANDS` + 1, len(r)) + lower-form band of the correlation matrix, as + `scipy.linalg.solveh_banded` takes it + + Notes: + The estimate also picks up smooth model error, which is likewise + correlated between neighbours. That is a feature rather than a flaw -- + a model that cannot follow the data genuinely leaves its parameters + less well determined -- but it does mean the result describes the fit + as a whole and not the taper alone. + """ + r = np.asarray(r, dtype=float) + n = r.size + + rho = np.zeros(_CORRELATION_BANDS + 1) + rho[0] = 1.0 + + denominator = np.sum(r * r) + if denominator > 0.0: + for lag in range(1, _CORRELATION_BANDS + 1): + if n > lag: + rho[lag] = np.sum(r[:-lag] * r[lag:]) / denominator + + # a negative estimate is noise about zero, and would not describe a taper + # spreading power into its neighbours + rho[1:] = np.clip(rho[1:], 0.0, None) + + total = 2.0 * rho[1:].sum() + if total > _CORRELATION_LIMIT: + rho[1:] *= _CORRELATION_LIMIT / total + + ab = np.zeros((_CORRELATION_BANDS + 1, n)) + for lag in range(_CORRELATION_BANDS + 1): + if n > lag: + ab[lag, : n - lag] = rho[lag] -class CurieGrid(object): + return ab + + +def _gls_covariance(J, r, ncorrelated): + """ + Parameter covariance allowing for correlation between residuals. + + Generalised least squares, \\( (J^T R^{-1} J)^{-1} \\), with `R` the banded + correlation of the first `ncorrelated` residuals from + `_banded_correlation`. Any rows beyond that -- the prior terms of a fit -- + are independent of the spectrum and of each other, so they keep unit weight + and never enter the solve. + + Both optimisers use this. They differ in how `J` is obtained, analytically + for a straight line and by finite differences for the four-parameter + spectral model, but not in what is done with it. + + Args: + J : 2D array shape (n, m) + Jacobian of the whitened residuals + r : 1D array shape (n,) + those residuals + ncorrelated : int + how many leading rows are the correlated spectrum + + Returns: + cov : 2D array shape (m, m), or None if the fit is singular + """ + RiJ = np.array(J, dtype=float) + ab = _banded_correlation(r[:ncorrelated]) + + try: + RiJ[:ncorrelated] = solveh_banded(ab, J[:ncorrelated], lower=True) + return np.linalg.inv(J.T.dot(RiJ)) + except (np.linalg.LinAlgError, ValueError): + return None + + +def _dof_factor(taper, counts=None, dof_factor=None): + """ + Effective-degrees-of-freedom deflation for a taper. + + Returns the asymptotic scalar when `counts` is None, and the per-bin + factor otherwise. An explicit `dof_factor` overrides both. + + An uncalibrated taper falls back to the untapered entry, which is + conservative in the sense that Hermitian redundancy holds exactly whatever + the taper does. + """ + if dof_factor is not None: + return float(dof_factor) + + dof_inf, lost = _TAPER_DOF.get(getattr(taper, "__name__", None), _TAPER_DOF[None]) + + if counts is None: + return dof_inf + + counts = np.asarray(counts, dtype=float) + return dof_inf * counts / np.maximum(counts - lost, 1.0) + + +class CurieGrid(CurieParallel): """ Accepts a 2D array and Cartesian coordinates specifying the bounding box of the array @@ -95,7 +239,9 @@ class CurieGrid(object): in incorrect Curie depth calculations. """ - def __init__(self, grid, xmin, xmax, ymin, ymax): + def __init__(self, grid, xmin, xmax, ymin, ymax, **kwargs): + + super(CurieGrid, self).__init__() self.data = np.array(grid) ny, nx = self.data.shape @@ -203,6 +349,15 @@ def remove_trend_linear(self, data): This may come in handy if the magnetic data has not been reduced to the pole. + The trend is the least-squares plane. Over a regular grid the centred + row and column indices are mutually orthogonal and both orthogonal to + the constant, so the normal equations decouple and the plane's three + coefficients are one mean and two 1-D inner products -- no design + matrix and no SVD. This is an order of magnitude cheaper than the + equivalent ``lstsq`` fit (the trend is subtracted once per window when + computing a spectrum), and unlike an ``(nr, nc)`` vs ``(nc, nr)`` + design matrix it stays correct when the grid is not square. + Args: data : 2D numpy array @@ -210,18 +365,24 @@ def remove_trend_linear(self, data): data : 2D numpy array """ nr, nc = data.shape - yq, xq = np.mgrid[0:nc, 0:nr] - A = np.c_[xq.ravel(), yq.ravel(), np.ones(xq.size)] - c, resid, rank, sigma = np.linalg.lstsq(A, data.ravel(), rcond=None) - return data - np.dot(A, c).reshape(data.shape) + i = np.arange(nr) - (nr - 1) / 2.0 # centred row index + j = np.arange(nc) - (nc - 1) / 2.0 # centred column index + mean = data.mean() + # Centring the means before the inner product drops the constant + # term's contribution (sum(i) == sum(j) == 0) and keeps the sum well + # conditioned, so the fit matches lstsq to machine precision. + # A slope is only identifiable along an axis with more than one node; + # a singleton axis carries no trend and its (i*i).sum() is zero, so + # take a zero slope there rather than dividing by zero into a NaN. + sii = (i * i).sum() + sjj = (j * j).sum() + ci = (i * (data.mean(axis=1) - mean)).sum() / sii if sii else 0.0 + cj = (j * (data.mean(axis=0) - mean)).sum() / sjj if sjj else 0.0 + return data - (mean + ci * i[:, None] + cj * j[None, :]) def _taper_spectrum(self, subgrid, taper=np.hanning, scale=0.001, **kwargs): """ - Template for tapering the power spectrum used in: - - - `radial_spectrum` - - `radial_spectrum_log` - - `azimuthal_spectrum` + Template for tapering the power spectrum used in `radial_spectrum`. """ data = subgrid nr, nc = data.shape @@ -231,6 +392,16 @@ def _taper_spectrum(self, subgrid, taper=np.hanning, scale=0.001, **kwargs): # control taper if taper is None: + # nothing downstream consumes kwargs once there is no taper to pass + # them to, so an unrecognised one would be silently ignored rather + # than raising as it does when a taper is present + if kwargs: + raise TypeError( + "unexpected keyword argument(s) {} -- with taper=None there " + "is no taper function to pass them to".format( + ", ".join(repr(key) for key in sorted(kwargs)) + ) + ) vtaper = 1.0 else: rt = taper(nr, **kwargs) @@ -240,17 +411,17 @@ def _taper_spectrum(self, subgrid, taper=np.hanning, scale=0.001, **kwargs): # scaling factor to transform wavenumber into units of rad/km dx_scale = self.dx * scale - dk = 2.0 * np.pi / (nr - 1) / dx_scale + # the DFT fundamental is 2*pi/(N*dx). Using (N-1) overstates every + # wavenumber by N/(N-1) and so understates every depth by (N-1)/N. + dk = 2.0 * np.pi / nr / dx_scale kbins = np.arange(dk, dk * nr / 2, dk) return vtaper, dk, kbins def _FFT_spectrum(self, subgrid, vtaper, dk, kbins, const): """ - Template for computing the (fast) Fourier transform used in: - - - `radial_spectrum` - - `radial_spectrum_log` + Template for computing the (fast) Fourier transform used in + `radial_spectrum`. A constant `const` should be applied to the FFT of the magnetic anomaly to convert `S` and `sigma` to specific units for further analysis. @@ -260,34 +431,96 @@ def _FFT_spectrum(self, subgrid, vtaper, dk, kbins, const): ```python 2*log(FFT) == log(FFT**2) ``` + + Returns `(k, S, sigma, counts)`, where `counts` is the number of FFT + cells averaged into each radial bin. + + The transform is `numpy.fft.rfft2`, not the full `fft2`. The anomaly is + real, so `|FFT|` is symmetric under reflection through the origin and + the discarded half carries no new information; using it halves both the + transform and the number of cells to bin, for around twice the speed + with no change to the result. To keep `counts` the full-spectrum count + -- which `window_spectrum` deflates `sigma` by, and whose Hermitian + factor of two is baked into the `_TAPER_DOF` calibration -- each + retained cell is weighted by how many full-spectrum cells it stands in + for: the columns whose mirror was dropped count twice, the self-mirrored + DC and (for even `nc`) Nyquist columns count once. Dropping that weight + would halve `counts` and inflate every reported uncertainty by ~sqrt(2). + + > This method returned three values prior to v2. Subclasses that + > override it must now also return `counts`. """ data = subgrid nr, nc = data.shape nbins = kbins.size - 1 - # fast Fourier transform and shift - FT = np.abs(np.fft.fft2(data * vtaper)) - FT = np.fft.fftshift(FT) - - S = np.empty(nbins) - k = np.empty(nbins) - sigma = np.empty(nbins) - - i0 = int((nr - 1) // 2) - ix, iy = np.mgrid[0:nr, 0:nr] - kk = np.hypot((ix - i0) * dk, (iy - i0) * dk) + # real-input transform: only the non-redundant half plane, nc//2 + 1 + # columns wide, is computed. No fftshift -- rows stay in FFT order and + # columns are the non-negative frequencies 0 .. nc//2. + FT = np.abs(np.fft.rfft2(data * vtaper)) + ncol = nc // 2 + 1 + + # signed integer row frequencies (0, 1, .. -1), built exactly rather + # than via fftfreq so a cell on the kx axis lands on the same bin edge + # as the old centred grid did, to the bit -- counts are a partition and + # must match a full fft2 exactly. Only |k| is binned, so the sign of the + # row frequency does not matter. + row_freq = np.arange(nr) + row_freq[row_freq > (nr - 1) // 2] -= nr + ix = (row_freq * dk)[:, np.newaxis] + iy = (np.arange(ncol) * dk)[np.newaxis, :] + kk = np.hypot(ix, iy).ravel() + + # a dropped mirror means every interior column stands for two cells of + # the full spectrum; the DC column and, when nc is even, the Nyquist + # column are their own mirror and stand for one. + weight = np.full(ncol, 2.0) + weight[0] = 1.0 + if nc % 2 == 0: + weight[-1] = 1.0 + weight = np.broadcast_to(weight, (nr, ncol)).ravel() + + # bin every cell once and reduce with bincount. Masking the whole array + # per bin instead is O(nbins * nr * nc), which is cubic in the window + # and dominates a large run -- over 20x slower at nr = 2001. + idx = np.digitize(kk, kbins) - 1 + # digitize is half-open above, matching the annuli, so a cell landing on + # an edge is counted once rather than in both neighbours. The final bin + # is the exception: it is closed, so a cell exactly at kbins[-1] belongs + # to it rather than falling off the end. + idx[(idx == nbins) & (kk <= kbins[-1])] = nbins - 1 + keep = (idx >= 0) & (idx < nbins) + idx = idx[keep] + kk = kk[keep] + weight = weight[keep] + # log only the cells that land in a bin, so a zero outside the binned + # range cannot raise a divide-by-zero that the per-bin masking never saw + rr = const * np.log(FT.ravel()[keep]) + + counts = np.bincount(idx, weights=weight, minlength=nbins) + with np.errstate(invalid="ignore", divide="ignore"): + S = np.bincount(idx, weights=weight * rr, minlength=nbins) / counts + k = np.bincount(idx, weights=weight * kk, minlength=nbins) / counts + # two-pass variance. The one-pass E[x^2] - E[x]^2 form is cheaper + # but cancels: ln|FFT| is O(10) with O(1) scatter, so it loses + # three digits of sigma, and more on a near-constant bin. + dev = rr - S[idx] + sigma = np.sqrt( + np.bincount(idx, weights=weight * dev * dev, minlength=nbins) / counts + ) - for i in range(nbins): - mask = np.logical_and(kk >= kbins[i], kk <= kbins[i + 1]) - rr = const * np.log(FT[mask]) - S[i] = rr.mean() - k[i] = kk[mask].mean() - sigma[i] = np.std(rr) + # an empty annulus averages nothing -- mirrors the mean of an empty + # slice the per-bin form produced, without the warning + empty = counts == 0 + S[empty] = k[empty] = sigma[empty] = np.nan - return k, S, sigma + # counts are integer-valued (sums of the weights 1 and 2); round before + # casting so a float sum landing a hair below the integer is not + # truncated downward. + return k, S, sigma, np.rint(counts).astype(int) - def radial_spectrum(self, subgrid, taper=np.hanning, power=2.0, **kwargs): + def radial_spectrum(self, subgrid, taper=np.hanning, power=2.0, return_counts=False, **kwargs): """ Compute the radial spectrum for a square grid. @@ -300,8 +533,12 @@ def radial_spectrum(self, subgrid, taper=np.hanning, power=2.0, **kwargs): taper function, set to None for no taper function power : float raise the FFT of the magnetic anomaly to the power. - - 2.0 for Bouligand _et al._ (2009) use cases - - 0.5 for Tanaka _et al.__ (1999) use cases + - 2.0 for Bouligand _et al._ (2009) use cases, which gives + the log power spectrum \\( \\ln \\Phi_{\\Delta T} \\) + - 1.0 for Tanaka _et al._ (1999) use cases, which gives the + log amplitude spectrum \\( \\ln \\Phi_{\\Delta T}^{1/2} \\) + return_counts : bool (default=False) + also return the number of FFT cells averaged into each bin kwargs : keyword arguments keyword arguments to pass to `taper` @@ -311,9 +548,19 @@ def radial_spectrum(self, subgrid, taper=np.hanning, power=2.0, **kwargs): Phi : 1D array shape (n,) Radial power spectrum sigma_Phi : 1D array shape (n,) - Standard deviation of Phi + Standard deviation of Phi within each radial bin + counts : 1D array shape (n,) + number of FFT cells in each radial bin. + Only returned if `return_counts=True`. Notes: + `Phi` is the mean of \\( \\ln |FFT| \\) over each annulus, so + `sigma_Phi` describes the scatter of the individual cells, not + the uncertainty of that mean. Dividing by the square root of + `counts` gives the standard error, though note the cells are not + independent -- a real field has Hermitian symmetry, so roughly + half of them are redundant, and tapering correlates neighbours. + While `subgrid` is projected in eastings / northings (in metres), the wavenumber, \\( k \\), is returned in units of rad/km. This is because both Bouligand *et al.* (2009) and Tanaka *et al.* @@ -336,71 +583,93 @@ def radial_spectrum(self, subgrid, taper=np.hanning, power=2.0, **kwargs): # calculate the Fourier transform and apply scaling constant to retrieve # values compatible with Bouligand or Tanaka analysis - return self._FFT_spectrum(subgrid, vtaper, dk, kbins, power) + k, Phi, sigma_Phi, counts = self._FFT_spectrum( + subgrid, vtaper, dk, kbins, power + ) - def azimuthal_spectrum( - self, subgrid, taper=np.hanning, power=2.0, theta=5.0, **kwargs + if return_counts: + return k, Phi, sigma_Phi, counts + return k, Phi, sigma_Phi + + def window_spectrum( + self, + window, + xc, + yc, + taper=np.hanning, + power=2.0, + process_subgrid=None, + dof_factor=None, + **kwargs ): """ - Compute azimuthal spectrum for a square grid. + Radial spectrum of one window, weighted ready for fitting. - > Wavenumber is returned in values of __rad/km__ + Extracts the subgrid, computes its radial spectrum, and converts the + within-annulus scatter into the uncertainty of the annulus *mean*, + which is what a fit needs. Both `pycurious.optimise_bouligand` and + `pycurious.optimise_tanaka` build on this. Args: - subgrid : 2D array - window of the original data (see subgrid method) + window : float + size of the window in metres + xc, yc : float + centroid of the window taper : function (default=np.hanning) - taper function, set to None for no taper function - theta : float - angle increment in degrees - args : arguments - arguments o pass to taper + taper function, or None for no taper + power : float + raise the FFT of the anomaly to this power -- 2.0 for the log + power spectrum that Bouligand *et al.* (2009) fit, 1.0 for the + log amplitude spectrum of Tanaka *et al.* (1999) + process_subgrid : function, optional + applied to the subgrid before the spectrum is computed + dof_factor : float, optional + override the effective-degrees-of-freedom deflation (see Notes) + kwargs : keyword arguments + passed to `taper` Returns: - k : 1D array shape (n,) + k : 1D array wavenumber in rad/km - Phi : 1D array shape (n,) - Radial power spectrum - sigma_Phi : 1D array shape (n,) - Standard deviation of Phi + Phi : 1D array + log spectrum, raised to `power` + sigma : 1D array + uncertainty of the binned mean - Notes: - While `subgrid` is projected in eastings / northings (in metres), - the wavenumber, \\( k \\), is returned in units of rad/km. - This is because both Bouligand *et al.* (2009) and Tanaka *et al.* - (1999) require the computation of Curie depth in these units. + Usage: + >>> k, Phi, sigma = grid.window_spectrum(200e3, xc, yc, power=2) - References: - Bouligand, C., J. M. G. Glen, and R. J. Blakely (2009), Mapping Curie - temperature depth in the western United States with a fractal model for - crustal magnetization, J. Geophys. Res., 114, B11104, - doi:10.1029/2009JB006494 - - Tanaka, A., Okubo, Y., & Matsubayashi, O. (1999). Curie point depth - based on spectrum analysis of the magnetic anomaly data in East and - Southeast Asia. Tectonophysics, 306(3–4), 461–470. - doi:10.1016/S0040-1951(99)00072-4 + Notes: + `radial_spectrum` returns the scatter of the FFT cells within each + annulus, whereas a fit needs the uncertainty of the annulus mean. + That is the standard error, except that the cells are not + independent: Hermitian symmetry makes about half of them redundant, + and tapering correlates neighbours. The correction is calibrated + per taper and varies with the number of cells in the bin, since a + fixed number of them is lost to correlation however few there are. + `dof_factor` overrides it with a constant. + + The cells of *neighbouring* annuli are correlated too, which this + does not address -- it inflates the uncertainty of a fitted + parameter rather than of any individual bin. See + `pycurious.optimise_bouligand.CurieOptimiseBouligand.optimise`. """ - from pycurious import radon - - vtaper, dk, kbins = self._taper_spectrum(subgrid, taper, **kwargs) + if process_subgrid is None: + # dummy function + def process_subgrid(subgrid): + return subgrid - dtheta = np.arange(0.0, 180.0, theta) - sinogram = radon.radon2d(subgrid, np.pi * dtheta / 180.0) - S = np.zeros((dtheta.size, kbins.size)) + subgrid = self.subgrid(window, xc, yc) + subgrid = process_subgrid(subgrid) - # control taper - if taper is None: - vtaper = 1.0 - else: - vtaper = taper(sinogram.shape[0], **kwargs) + kwargs.pop("return_counts", None) + k, Phi, sigma_Phi, counts = self.radial_spectrum( + subgrid, taper=taper, power=power, return_counts=True, **kwargs + ) - nk = 1 + 2 * kbins.size - for i in range(0, dtheta.size): - PSD = np.abs(np.fft.fft(vtaper * sinogram[:, i], n=nk)) - S[i, :] = power * np.log(PSD[1 : kbins.size + 1]) + sigma = sigma_Phi / np.sqrt(counts / _dof_factor(taper, counts, dof_factor)) - return kbins, S, dtheta + return k, Phi, sigma def reduce_to_pole(self, data, inc, dec, sinc=None, sdec=None): """ @@ -624,7 +893,25 @@ def tanaka1999(k, lnPhi, sigma_lnPhi, kmin_range=(0.05, 0.2), kmax_range=(0.05, lower_source : tuple (Zor,bor,dZor) gradient, intercept, error for the bottom of magnetic sources + Notes: + .. deprecated:: + Use `pycurious.optimise_tanaka.CurieOptimiseTanaka.optimise`, + which fits both bands with `scipy.optimize.curve_fit` and returns + depths positive downwards with their uncertainties. + + This hand-rolled weighted least squares squares an already-squared + error term, so it weights by 1/sigma**4 rather than 1/sigma**2, and it + subtracts ln(k) from a standard deviation. Its uncertainties are + therefore not meaningful. It is retained only so existing scripts keep + running. """ + warnings.warn( + "tanaka1999 is deprecated, use CurieOptimiseTanaka.optimise instead. " + "Its uncertainties are not meaningful -- see the docstring.", + FutureWarning, + stacklevel=2, + ) + # for now... S = lnPhi sigma2 = sigma_lnPhi ** 2 @@ -674,29 +961,47 @@ def compute_coefficients(X, Y, E): return (Ztr, btr, dZtr), (Zor, bor, dZor) -def ComputeTanaka(Ztr, dZtr, Zor, dZor): +def ComputeTanaka(zT, dzT, z0, dz0): """ - Compute the Curie depth from the results of tanaka1999 + Compute the Curie depth from the results of `tanaka1999`. + + .. deprecated:: + Use `pycurious.optimise_tanaka.CurieOptimiseTanaka.calculate_CPD`. Args: - Ztr : float / 1D array + zT : float / 1D array top of the magnetic source - dZtr : float / 1D array - error of Ztr - Zor : float / 1D array + dzT : float / 1D array + standard deviation of zT + z0 : float / 1D array centroid depth of the magnetic source - dZor : float / 1D array - error of Zor + dz0 : float / 1D array + standard deviation of z0 Returns: - Zb : float / 1D array + CPD : float / 1D array estimated Curie point depth at bottom of magnetic source - eZb : float / 1D array - error of `Zb` + CPD_stdev : float / 1D array + standard deviation + + Notes: + The arguments interleave the depths with their standard deviations, + whereas `calculate_CPD` groups them. Renaming a call without also + reordering the arguments computes nonsense. """ - Zb = 2.0 * Zor - Ztr - dZb = 2.0 * dZor + dZtr - return abs(Zb), dZb + warnings.warn( + "ComputeTanaka is deprecated, use " + "CurieOptimiseTanaka.calculate_CPD(zt, z0, sigma_zt, sigma_z0) " + "instead. Note the argument order differs: ComputeTanaka takes " + "(zt, sigma_zt, z0, sigma_z0), interleaving each depth with its " + "standard deviation. Note also that the returned standard deviation " + "changed in v2, from 2*dz0 + dzT to the quadrature sum.", + FutureWarning, + stacklevel=2, + ) + CPD = abs(2.0 * z0 - zT) + CPD_stdev = np.sqrt(dzT ** 2 + (dz0 * 2) ** 2) + return CPD, CPD_stdev def maus1995(beta, zt, kh, C=0.0): diff --git a/pycurious/mapping.py b/pycurious/mapping.py index ffefd56..e0c49c9 100644 --- a/pycurious/mapping.py +++ b/pycurious/mapping.py @@ -292,7 +292,15 @@ def import_geotiff(file_path): bounding box in the projection of the GeoTIFF e.g. [xmin, xmax, ymin, ymax] """ - from osgeo import gdal, osr + try: + from osgeo import gdal, osr + except ImportError: + raise ImportError( + "GeoTIFF support needs the GDAL Python bindings, which are not " + "installed. They require a matching libgdal on the system, so " + "conda install gdal is usually easier than pip install " + "pycurious[geotiff]." + ) gtiff = gdal.Open(file_path) data = gtiff.ReadAsArray() @@ -338,7 +346,15 @@ def export_geotiff(file_path, array, extent, epsg): e.g. 4326 for WGS84 """ - from osgeo import gdal, osr + try: + from osgeo import gdal, osr + except ImportError: + raise ImportError( + "GeoTIFF support needs the GDAL Python bindings, which are not " + "installed. They require a matching libgdal on the system, so " + "conda install gdal is usually easier than pip install " + "pycurious[geotiff]." + ) # import ogr, gdal, osr, os @@ -386,12 +402,12 @@ def export_netcdf4(file_path, array, extent): with netCDF4.Dataset(str(file_path), 'w') as cdf: cdf.createDimension('x', nx) cdf.createDimension('y', ny) - cdf_x = cdf.createVariable('x', np.float, ('x',), zlib=True) - cdf_y = cdf.createVariable('y', np.float, ('y',), zlib=True) + cdf_x = cdf.createVariable('x', np.float64, ('x',), zlib=True) + cdf_y = cdf.createVariable('y', np.float64, ('y',), zlib=True) cdf_x[:] = np.linspace(xmin, xmax, nx) cdf_y[:] = np.linspace(ymin, ymax, ny) - cdf_data = cdf.createVariable('z', np.float, ('y','x'), zlib=True) + cdf_data = cdf.createVariable('z', np.float64, ('y','x'), zlib=True) cdf_data[:,:] = array diff --git a/pycurious/optimise.py b/pycurious/optimise.py deleted file mode 100644 index 7439968..0000000 --- a/pycurious/optimise.py +++ /dev/null @@ -1,725 +0,0 @@ -# Copyright 2018-2019 Ben Mather, Robert Delhaye -# -# This file is part of PyCurious. -# -# PyCurious is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or any later version. -# -# PyCurious is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with PyCurious. If not, see . - -""" -This PyCurious module contains the `pycurious.optimise.CurieOptimise` class, -which inherits the `pycurious.grid.CurieGrid` class with added functionality for: - -- Fitting the synthetic power spectrum \\( \\Phi \\) computed with `pycurious.grid.bouligand2009` -- Defining an objective function with a flexible interface for adding *a priori* and likelihood functions -- Parallel decomposition of routines to compute the optimal radial power spectrum, and thus Curie depth -- Metropolis-Hastings algorithm and sensitivity analysis to estimate the uncertainty of the posterior - -The posterior is defined as - -\\( P(\\mathbf{m}|\\mathbf{d}) = P(\\beta, z_t, \\Delta z, C|\\Phi_d) \\) - -where \\( \\beta, z_t, \\Delta z, C \\) are input parameters to `pycurious.grid.bouligand2009` and -\\( \\Phi_d \\) is the radial power spectrum computed from a FFT over square windows of the magnetic -anomaly in `pycurious.grid.CurieGrid.radial_spectrum`. -""" - -# -*- coding: utf-8 -*- -from .grid import CurieGrid, bouligand2009 -import numpy as np -import warnings -from scipy.optimize import minimize -from scipy.special import polygamma -from scipy import stats -from multiprocessing import Pool, Process, Queue, cpu_count - -try: - range = xrange -except: - pass - - -class CurieOptimise(CurieGrid): - """ - Extends the `pycurious.grid.CurieGrid` class to include - optimisation routines see `scipy.optimize.minimize` for - a description of the algorithm. - - Args: - grid : 2D numpy array - 2D array of magnetic data - xmin : float - minimum x bound in metres - xmax : float - maximum x bound in metres - ymin : float - minimum y bound in metres - ymax : float - maximum y bound in metres - - Attributes: - bounds : list of tuples - lower and upper bounds for \\( \\beta, z_t, \\Delta z, C \\) - prior : dict - dictionary of priors for \\( \\beta, z_t, \\Delta z, C \\) - grid : 2D numpy array - 2D array of magnetic data - xmin : float - minimum x bound in metres - xmax : float - maximum x bound in metres - ymin : float - minimum y bound in metres - ymax : float - maximum y bound in metres - dx : float - grid spacing in the x-direction in metres - dy : float - grid spacing in the y-direction in metres - nx : int - number of nodes in the x-direction - ny : int - number of nodes in the y-direction - xcoords : 1D numpy array - 1D numpy array of coordinates in the x-direction - ycoords : 1D numpy array - 1D numpy array of coordinates in the y-direction - - Notes: - In all instances `x` indicates eastings in metres and `y` indicates northings. - Using a grid of longitude / latitudinal coordinates (degrees) will result - in incorrect Curie depth calculations. - """ - - def __init__(self, grid, xmin, xmax, ymin, ymax, **kwargs): - - super(CurieOptimise, self).__init__(grid, xmin, xmax, ymin, ymax) - - # initialise prior dictionary - self.reset_priors() - - # lower / upper bounds - # [beta, zt, dz, C] - lb = [0.0, 0.0, 0.0, None] - ub = [None] * len(lb) - bounds = list(zip(lb, ub)) - self.bounds = bounds - - self.max_processors = kwargs.pop("max_processors", cpu_count()) - - return - - def add_prior(self, **kwargs): - """ - Add a prior to the dictionary (tuple) - Available priors are \\( \\beta, z_t, \\Delta z, C \\) - - Assumes a normal distribution or - define another distribution from `scipy.stats` - - Usage: - >>> add_prior(beta=(p, sigma_p)) - - >>> add_prior(beta=scipy.stats.norm(p, sigma_p)) - """ - - for key in kwargs: - if key in self.prior: - prior = kwargs[key] - if type(prior) == tuple: - p, sigma_p = prior - pdf = stats.norm(p, sigma_p) - elif type(prior) == stats._distn_infrastructure.rv_frozen: - pdf = prior - else: - raise ValueError("Use a distribution from scipy.stats module") - - # add prior PDF to dictionary - self.prior_pdf[key] = pdf - self.prior[key] = list(pdf.args) - - else: - raise ValueError("prior must be one of {}".format(self.prior.keys())) - - def reset_priors(self): - """ - Reset priors to uniform distribution - """ - self.prior = {"beta": None, "zt": None, "dz": None, "C": None} - self.prior_pdf = {"beta": None, "zt": None, "dz": None, "C": None} - - def objective_routine(self, **kwargs): - """ - Evaluate the objective routine to find the misfit with priors - Only keys stored in self.prior will be added to the total misfit - - Usage: - >>> objective_routine(beta=2.5) - - Returns: - misfit : float - misfit integrated over all observations and priors - """ - c = 0.0 - - for key in kwargs: - val = kwargs[key] - if key in self.prior: - prior_args = self.prior[key] - if prior_args is not None: - c += self.objective_function(val, *prior_args) - return c - - def objective_function(self, x, x0, sigma_x0, *args): - """ - Objective function used in `objective_routine` - Evaluates the l2-norm misfit - - Args: - x : float, ndarray - x0 : float, ndarray - sigma_x0 : float, ndarray - - Returns: - misfit : float - """ - return 0.5 * np.sum((x - x0) ** 2 / sigma_x0 ** 2) - - def min_func(self, x, kh, Phi, sigma_Phi): - """ - Function to minimise - - Args: - x : array shape (n,) - array of variables \\( \\beta, z_t, \\Delta z, C \\) - kh : array shape (n,) - wavenumbers (rad/km) - Phi : array shape (n,) - radial power spectrum \\( \\Phi \\) - sigma_Phi : array shape (n,) - standard deviation of Phi, \\( \\sigma_{\\Phi} \\) - - Returns: - misfit : float - sum of misfit (scalar) - - Notes: - We purposely ignore all warnings raised by the `pycurious.grid.bouligand2009` - function because some combinations of input parameters will - trigger an out-of-range warning that will crash the minimiser. - Instead, the misfit is set to a very large number when this occurs. - """ - beta, zt, dz, C = x - with warnings.catch_warnings() as w: - warnings.simplefilter("ignore") - Phi_syn = bouligand2009(kh, beta, zt, dz, C) - - misfit = self.objective_function(Phi_syn, Phi, 1.0) - if not np.isfinite(misfit): - misfit = 1e99 - else: - misfit += self.objective_routine(beta=beta, zt=zt, dz=dz, C=C) - return misfit - - def optimise( - self, - window, - xc, - yc, - beta=3.0, - zt=1.0, - dz=10.0, - C=5.0, - taper=np.hanning, - process_subgrid=None, - **kwargs - ): - """ - Find the optimal parameters of \\( \\beta, z_t, \\Delta z, C \\) - for a given centroid (xc,yc) and window size. - - Args: - window : float - size of window in metres - xc : float - centroid x values - yc : float - centroid y values - beta : float - fractal parameter (starting value) - zt : float - top of magnetic layer (starting value) - dz : float - thickness of magnetic layer (starting value) - C : float - field constant (starting value) - taper : taper (default=`numpy.hanning`) - taper function, set to None for no taper function - process_subgrids : function - a custom function to process the subgrid - kwargs : keyword arguments - to pass to radial_spectrum. - - Returns: - beta : float - fractal parameters - zt : float - top of magnetic layer - dz : float - thickness of magnetic layer - C : float - field constant - """ - - if process_subgrid is None: - # dummy function - def process_subgrid(subgrid): - return subgrid - - # initial constants for minimisation - # w = 1.0 # weight low frequency? - - x0 = np.array([beta, zt, dz, C]) - - # get subgrid - subgrid = self.subgrid(window, xc, yc) - subgrid = process_subgrid(subgrid) - - # compute radial spectrum - k, Phi, sigma_Phi = self.radial_spectrum(subgrid, taper=taper, **kwargs) - - # minimise function - res = minimize(self.min_func, x0, args=(k, Phi, sigma_Phi), bounds=self.bounds) - return res.x - - def _func_queue(self, func, q_in, q_out, window, *args, **kwargs): - """ Retrive processes from the queue """ - while True: - pos, xc, yc = q_in.get() - if pos is None: - break - - pass_args = [window, xc, yc] - pass_args.extend(args) - - res = func(*pass_args, **kwargs) - q_out.put((pos, res)) - return - - def parallelise_routine(self, window, xc_list, yc_list, func, *args, **kwargs): - """ - Implements shared memory multiprocessing to split multiple - evaluations of a function centroids across processors. - - Supply the window size and lists of x,y coordinates to a function - along with any additional arguments or keyword arguments. - - Args: - window : float - size of window in metres - xc_list : array shape (l,) - centroid x values - yc_list : array shape (l,) - centroid y values - func : function - Python function to evaluate in parallel - args : arguments - additional arguments to pass to `func` - kwargs : keyword arguments - additional keyword arguments to pass to `func` - - Returns: - out : list of lists - (depends on output of `func` - see notes) - - Usage: - An obvious use case is to compute the Curie depth for many - centroids in parallel. - - >>> self.parallelise_routine(window, xc_list, yc_list, self.optimise) - - Each centroid is assigned a new process and sent to a free processor - to compute. In this case, the output is separate lists of shape(l,) - for \\( \\beta, z_t, \\Delta z, C \\). If `len(xc_list)=2` then, - - >>> self.parallelise_routine(window, [x1,x2], [y1, y2], self.optimise) - [[beta1 beta2], [zt1 zt2], [dz1 dz2], [C1 C2]] - - Another example is to parallelise the sensitivity analysis: - - >>> self.parallelise_routine(window, xc_list, yc_list, self.sensitivity, nsim) - - This time the output will be a list of lists for \\( \\beta, z_t, \\Delta z, C \\) - i.e. if `len(xc_list)=2` is the number of centroids and `nsim=4` is the number of - simulations then separatee lists will be returned for \\( \\beta, z_t, \\Delta z, C \\). - - >>> self.parallelise_routine(window, [x1,x2], [y1,y2], self.sensitivity, 4) - - which would return: - - ```python - [[[ beta1a , beta1b , beta1c , beta1d ], # centroid 1 (x1,y1) - [ beta2a , beta2b , beta2c , beta2d ]], # centroid 2 (x2,y2) - [[ zt1a , zt1b , zt1c , zt1d ], # centroid 1 (x1,y1) - [ zt2a , zt2b , zt2c , zt2d ]], # centroid 2 (x2,y2) - [[ dz1a , dz1b , dz1c , dz1d ], # centroid 1 (x1,y1) - [ dz2a , dz2b , dz2c , dz2d ]] # centroid 2 (x2,y2) - [[ C1a , C1b , C1c , C1d ], # centroid 1 (x1,y1) - [ C2a , C2b , C2c , C2d ]]] # centroid 2 (x2,y2) - ``` - """ - - n = len(xc_list) - if n != len(yc_list): - raise ValueError("xc_list and yc_list must be the same size") - - xOpt = [[] for i in range(n)] - processes = [] - q_in = Queue(1) - q_out = Queue() - - nprocs = self.max_processors - - if nprocs == 1: - # skip all the OpenMP cruft - for i in range(n): - xc = xc_list[i] - yc = yc_list[i] - - res = func(window, xc, yc, *args, **kwargs) - xOpt[i] = res - - elif nprocs > 1: - # more than one processor - for i in range(nprocs): - pass_args = [func, q_in, q_out, window] - pass_args.extend(args) - - p = Process(target=self._func_queue, args=tuple(pass_args), kwargs=kwargs) - - processes.append(p) - - for p in processes: - p.daemon = True - p.start() - - # put items in the queue - sent = [q_in.put((i, xc_list[i], yc_list[i])) for i in range(n)] - [q_in.put((None, None, None)) for _ in range(nprocs)] - - # get the results - for i in range(len(sent)): - index, res = q_out.get() - xOpt[index] = res - - # wait until each processor has finished - [p.join() for p in processes] - - else: - raise ValueError("{} processors is invalid, specify a positive integer value".format(nprocs)) - - # process dimensions of output - ndim = np.array(res).ndim - - if ndim == 1: - # return separate lists of beta, zt, dz, C - xOpt = np.vstack(xOpt) - return list(xOpt.T) - elif ndim > 1: - # return lists of beta, zt, dz, C for each centroid - xOpt = np.hstack(xOpt) - out = list(xOpt) - for i in range(len(out)): - out[i] = np.split(out[i], n) - return out - else: - raise ValueError("Cannot determine shape of output") - - def optimise_routine( - self, - window, - xc_list, - yc_list, - beta=3.0, - zt=1.0, - dz=10.0, - C=5.0, - taper=np.hanning, - process_subgrid=None, - **kwargs - ): - """ - Iterate through a list of centroids to compute the optimal values - of \\( \\beta, z_t, \\Delta z, C \\) for a given window size. - - Args: - window : float - size of window in metres - xc_list : ndarray shape (l,) - centroid x values - yc_list : ndarray shape (l,) - centroid y values - beta : float - fractal parameter - zt : float - top of magnetic layer - dz : float - thickness of magnetic layer - C : float - field constant - taper : function - taper function (default=`numpy.hanning`) - set to None for no taper function - process_subgrids : func - a custom function to process the subgrid - kwargs : keyword arguments - to pass to radial_spectrum. - - Returns: - beta : ndarray shape (l,) - fractal parameters - zt : ndarray shape (l,) - top of magnetic layer - dz : ndarray shape (l,) - thickness of magnetic layer - C : ndarray shape (l,) - field constant - - """ - return self.parallelise_routine( - window, - xc_list, - yc_list, - self.optimise, - beta, - zt, - dz, - C, - taper, - process_subgrid, - **kwargs - ) - - def metropolis_hastings( - self, - window, - xc, - yc, - nsim, - burnin, - x_scale=None, - beta=3.0, - zt=1.0, - dz=10.0, - C=5.0, - taper=np.hanning, - process_subgrid=None, - **kwargs - ): - """ - MCMC algorithm using a Metropolis-Hastings sampler. - - Evaluates a Markov-Chain for starting values of - \\( \\beta, z_t, \\Delta z, C \\) and returns the - ensemble of model realisations. - - Args: - window : float - size of window in metres - xc : float - centroid x values - yc : float - centroid y values - nsim : int - number of simulations - burnin : int - number of burn-in simulations before to nsim - x_scale: float(4) (optional) - scaling factor for new proposals - (default=`[1,1,1,1]` for `[beta, zt, dz, C]`) - - see notes - beta : float - fractal parameter (starting value) - zt : float - top of magnetic layer (starting value) - dz : float - thickness of magnetic layer (starting value) - C : float - field constant (starting value) - - Returns: - beta : ndarray shape (nsim,) - fractal parameter - zt : ndarray shape (nsim,) - top of magnetic layer - dz : ndarray shape (nsim,) - thickness of magnetic layer - C : ndarray shape (nsim,) - field constant - - Notes: - `nsim`, `burnin`, and `x_scale` should be tweaked for optimal performance - Use starting values of \\( \\beta, z_t, \\Delta z, C \\) relatively - close to the solution - \\( C \\) can easily found from the mean of the - radial power spectrum. - - During the burn-in stage we apply tempering to the PDF to iterate closer - towards the solution. This has the effect of smoothing out the posterior - so that minima can be more easily found. This is necessary here because - large portions of the posterior probability are zero. - see see Sambridge 2013, DOI:10.1093/gji/ggt342 for more information. - """ - if process_subgrid is None: - # dummy function - def process_subgrid(subgrid): - return subgrid - - samples = np.empty((nsim, 4)) - x0 = np.array([beta, zt, dz, C]) - - if x_scale is None: - x_scale = np.ones(4) - - # get subgrid - subgrid = self.subgrid(window, xc, yc) - subgrid = process_subgrid(subgrid) - - # compute radial spectrum - k, Phi, sigma_Phi = self.radial_spectrum(subgrid, taper=taper, **kwargs) - - P0 = np.exp(-self.min_func(x0, k, Phi, sigma_Phi) / 1000) - - # Burn-in phase - for i in range(burnin): - # add random perturbation - x1 = x0 + np.random.normal(size=4) * x_scale - - # evaluate proposal probability + tempering - P1 = np.exp(-self.min_func(x1, k, Phi, sigma_Phi) / 1000) - - # iterate towards MAP estimate - if P1 > P0: - x0 = x1 - P0 = P1 - - P0 = np.exp(-self.min_func(x0, k, Phi, sigma_Phi)) - - # Now sample posterior - for i in range(nsim): - # add random perturbation - x1 = x0 + np.random.normal(size=4) * x_scale - - # evaluate proposal probability - P0 = max(P0, 1e-99) - P1 = np.exp(-self.min_func(x1, k, Phi, sigma_Phi)) - - P = min(P1 / P0, 1.0) - - # randomly accept probability - if np.random.rand() <= P: - x0 = x1 - P0 = P1 - - samples[i] = x0 - - return list(samples.T) - - def sensitivity( - self, - window, - xc, - yc, - nsim, - beta=3.0, - zt=1.0, - dz=10.0, - C=5.0, - taper=np.hanning, - process_subgrid=None, - **kwargs - ): - """ - Iterate through a list of centroids to compute the mean and - standard deviation of \\( \\beta, z_t, \\Delta z, C \\) by - perturbing their prior distributions - (if provided by the user - see add_prior). - - Args: - nsim : int - number of Monte Carlo simulations - window : float - size of window in metres - xc : float - centroid x values - yc : float - centroid y values - nsim : int - number of simulations - beta : float - starting fractal parameter - zt : float - starting top of magnetic layer - dz : float - starting thickness of magnetic layer - C : float - starting field constant - - - Returns: - beta : ndarray shape (nsim,) - fractal parameters - zt : ndarray shape (nsim,) - top of magnetic layer - dz : ndarray shape (nsim,) - thickness of magnetic layer - C : ndarray shape (nsim,) - field constant - """ - if process_subgrid is None: - # dummy function - def process_subgrid(subgrid): - return subgrid - - samples = np.empty((nsim, 4)) - x0 = np.array([beta, zt, dz, C]) - - use_keys = [] - for key in self.prior_pdf: - prior_pdf = self.prior_pdf[key] - if prior_pdf is not None: - use_keys.append(key) - - # get subgrid - subgrid = self.subgrid(window, xc, yc) - subgrid = process_subgrid(subgrid) - - # compute radial spectrum - k, Phi, sigma_Phi = self.radial_spectrum(subgrid, taper=taper, **kwargs) - - for sim in range(0, nsim): - # randomly generate new prior values within PDF - for key in use_keys: - prior_pdf = self.prior_pdf[key] - self.prior[key][0] = prior_pdf.rvs() - - # minimise function - rPhi = np.random.normal(Phi, sigma_Phi) - res = minimize( - self.min_func, x0, args=(k, rPhi, sigma_Phi), bounds=self.bounds - ) - samples[sim] = res.x - - # restore priors - for key in use_keys: - prior_pdf = self.prior_pdf[key] - self.prior[key] = list(prior_pdf.args) - - return list(samples.T) diff --git a/pycurious/optimise_bouligand.py b/pycurious/optimise_bouligand.py new file mode 100644 index 0000000..8962e2d --- /dev/null +++ b/pycurious/optimise_bouligand.py @@ -0,0 +1,1268 @@ +# Copyright 2018-2019 Ben Mather, Robert Delhaye +# +# This file is part of PyCurious. +# +# PyCurious is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or any later version. +# +# PyCurious is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PyCurious. If not, see . + +""" +Fit the fractal spectrum of Bouligand *et al.* (2009), with uncertainties. + +``CurieOptimiseBouligand`` inherits ``pycurious.grid.CurieGrid`` and recovers the +four parameters of ``bouligand2009`` (beta, zt, dz, C) by optimisation, posing +the recovery as a Bayesian inverse problem with a flexible objective function +that accepts *a priori* and likelihood terms. Beyond the fit it offers +profile-deviance intervals, Metropolis-Hastings posterior sampling, and a +sensitivity analysis, and it decomposes the computation across CPUs to map Curie +depth over a grid. The model and the uncertainty machinery are documented in the +online theory guide. +""" + +# -*- coding: utf-8 -*- +from .grid import CurieGrid, bouligand2009, _gls_covariance +from .parallel import stochastic +import numpy as np +import warnings +from scipy.optimize import minimize, brentq +from scipy import stats +from multiprocessing import cpu_count + +# bouligand2009 evaluates cosh(|k| dz), which overflows a float64 above about +# |k| dz = 710. `dz` is bounded just inside that, at `_max_thickness`, so the +# optimiser and the sampler cannot wander into the region where the forward +# model stops returning a number. +# +# This is a numerical guard, not a physical prior, and the distinction matters. +# A Curie depth on Earth sits in the mid crust, so anything past a few tens of +# km is already meaningless -- but bounding it *there* would clip the upper +# tail of a genuinely skewed posterior and pile probability against the wall, +# which misrepresents the distribution rather than reporting it. The bound is +# therefore set far beyond any physical value, where it never binds on data +# that constrain the base at all. A fit that does reach it is not a deep +# source; it is a window too small to see one, and `_warn_on_bounds` says so. +_COSH_OVERFLOW = 700.0 + +# Stand-in for a residual that came back non-finite. Inside the bound above +# that cannot arise from the forward model, so this is a backstop for anyone +# calling `residuals` directly rather than something the fit relies on. One +# such residual contributes its square to the misfit, a penalty of 1e6 per +# unusable bin: big enough to dominate, small enough that prior terms added +# alongside it stay representable, which a flat 1e99 would not be. +_OVERFLOW_RESIDUAL = 1.0e3 + +# Parameters of bouligand2009, in the order the optimiser sees them. +_PARAMETERS = ("beta", "zt", "dz", "C") + +# Relative step for the finite-difference Jacobian behind the covariance. +_JACOBIAN_STEP = 1.0e-6 + +# Acceptance rate the burn-in tunes the proposal towards, the usual optimum +# for a random walk over a smooth multivariate target. +_TARGET_ACCEPTANCE = 0.234 + +# Tolerance for the root find that refines a profile interval, in the units of +# whatever is being profiled. A depth is wanted to a metre at most, and each +# evaluation is a full re-fit, so the default 2e-12 would spend a dozen fits +# resolving digits far past anything the uncertainty supports. +_PROFILE_XTOL = 1.0e-3 + +# `profile` can also work on the Curie depth, which is not a parameter of the +# forward model but a sum of two of them. +_CPD = "CPD" + + +def _prior_loc_scale(pdf): + """ + Return `(loc, scale)` of a frozen `scipy.stats` distribution, however it was + constructed. + + `objective_function` takes the prior as `(x0, sigma_x0)`, so a prior is only + ever stored as its centre and width. Reading `pdf.args` alone is not enough: + a distribution built with keywords -- `stats.norm(loc=p, scale=s)` -- has an + empty `args` and carries the values in `kwds` instead. + """ + + loc = pdf.kwds.get("loc") + scale = pdf.kwds.get("scale") + + if loc is None or scale is None: + # scipy orders positional arguments as (*shapes, loc, scale) + nshapes = len(pdf.dist.shapes.split(",")) if pdf.dist.shapes else 0 + positional = pdf.args[nshapes:] + if loc is None and len(positional) > 0: + loc = positional[0] + if scale is None and len(positional) > 1: + scale = positional[1] + + # fall back to the scipy defaults for a standard distribution + if loc is None: + loc = 0.0 + if scale is None: + scale = 1.0 + + # a tuple, so a caller cannot perturb a stored prior in place + return (loc, scale) + + +class CurieOptimiseBouligand(CurieGrid): + """ + Extends the `pycurious.grid.CurieGrid` class to include + optimisation routines see `scipy.optimize.minimize` for + a description of the algorithm. + + Args: + grid : 2D numpy array + 2D array of magnetic data + xmin : float + minimum x bound in metres + xmax : float + maximum x bound in metres + ymin : float + minimum y bound in metres + ymax : float + maximum y bound in metres + + Attributes: + bounds : list of tuples + lower and upper bounds for \\( \\beta, z_t, \\Delta z, C \\). + \\( \\Delta z \\) is capped where the forward model stops + evaluating, which depends on the grid spacing and is hundreds of km + -- far beyond any Curie depth on Earth, so it never binds on data + that constrain the base. Reassign this attribute to impose a + tighter one, bearing in mind that a bound near the physical range + will truncate the upper tail of a skewed posterior rather than + report it. + prior : dict + dictionary of priors for \\( \\beta, z_t, \\Delta z, C \\) + grid : 2D numpy array + 2D array of magnetic data + xmin : float + minimum x bound in metres + xmax : float + maximum x bound in metres + ymin : float + minimum y bound in metres + ymax : float + maximum y bound in metres + dx : float + grid spacing in the x-direction in metres + dy : float + grid spacing in the y-direction in metres + nx : int + number of nodes in the x-direction + ny : int + number of nodes in the y-direction + xcoords : 1D numpy array + 1D numpy array of coordinates in the x-direction + ycoords : 1D numpy array + 1D numpy array of coordinates in the y-direction + + Notes: + In all instances `x` indicates eastings in metres and `y` indicates northings. + Using a grid of longitude / latitudinal coordinates (degrees) will result + in incorrect Curie depth calculations. + """ + + def __init__(self, grid, xmin, xmax, ymin, ymax, **kwargs): + + super(CurieOptimiseBouligand, self).__init__(grid, xmin, xmax, ymin, ymax) + + # initialise prior dictionary + self.reset_priors() + + # lower / upper bounds for [beta, zt, dz, C]. Only the thickness gets + # a ceiling: it is the one the data routinely fail to constrain, and + # the one that can therefore run away far enough to stop the forward + # model evaluating. beta and zt are pinned by the bulk of the spectrum. + lb = [0.0, 0.0, 0.0, None] + ub = [None, None, self._max_thickness(), None] + self.bounds = list(zip(lb, ub)) + + self.max_processors = kwargs.pop("max_processors", cpu_count()) + + def _max_thickness(self): + """ + Largest `dz` this grid can evaluate, in km. + + The radial spectrum reaches the Nyquist wavenumber, + \\( \\pi/\\Delta x \\) in rad/km, whatever the window size, so the depth at which + `pycurious.grid.bouligand2009` overflows is fixed by the grid spacing + alone: 446 km at 2 km spacing, 111 km at 500 m. Both are far beyond any + Curie depth on Earth, which is the point -- see `_COSH_OVERFLOW`. + """ + return _COSH_OVERFLOW * (self.dx * 1.0e-3) / np.pi + + def add_prior(self, **kwargs): + """ + Add a prior to the dictionary (tuple) + Available priors are \\( \\beta, z_t, \\Delta z, C \\) + + Assumes a normal distribution or + define another distribution from `scipy.stats` + + Usage: + >>> add_prior(beta=(p, sigma_p)) + + >>> add_prior(beta=scipy.stats.norm(p, sigma_p)) + """ + + for key in kwargs: + if key in self.prior: + prior = kwargs[key] + if isinstance(prior, tuple): + p, sigma_p = prior + pdf = stats.norm(p, sigma_p) + elif isinstance(prior, stats.distributions.rv_frozen): + pdf = prior + else: + raise ValueError("Use a distribution from scipy.stats module") + + # add prior PDF to dictionary + self.prior_pdf[key] = pdf + self.prior[key] = _prior_loc_scale(pdf) + + else: + raise ValueError("prior must be one of {}".format(self.prior.keys())) + + def reset_priors(self): + """ + Reset priors to uniform distribution + """ + self.prior = {"beta": None, "zt": None, "dz": None, "C": None} + self.prior_pdf = {"beta": None, "zt": None, "dz": None, "C": None} + + def objective_routine(self, **kwargs): + """ + Evaluate the objective routine to find the misfit with priors + Only keys carrying a prior will be added to the total misfit + + Args: + kwargs : parameter values to test against their priors + + Usage: + >>> objective_routine(beta=2.5) + + Returns: + misfit : float + misfit integrated over all observations and priors + """ + prior = self.prior + + c = 0.0 + + for key in kwargs: + val = kwargs[key] + if key in prior: + prior_args = prior[key] + if prior_args is not None: + c += self.objective_function(val, *prior_args) + return c + + def objective_function(self, x, x0, sigma_x0, *args): + """ + Objective function used in `objective_routine` + Evaluates the l2-norm misfit + + Args: + x : float, ndarray + x0 : float, ndarray + sigma_x0 : float, ndarray + + Returns: + misfit : float + """ + return 0.5 * np.sum((x - x0) ** 2 / sigma_x0 ** 2) + + def residuals(self, x, kh, Phi, sigma_Phi, prior=None): + """ + Whitened residuals of the fit: the spectrum first, then one entry per + prior. + + `min_func` is the half sum of squares of this vector, and the fit + covariance comes from its Jacobian, so the two cannot drift apart. + A Gaussian prior \\( N(p, \\sigma_p) \\) on a parameter \\( m \\) is + just another observation, contributing a residual + \\( (m - p)/\\sigma_p \\). + + Args: + x : array shape (4,) + \\( \\beta, z_t, \\Delta z, C \\) + kh : array shape (n,) + wavenumbers (rad/km) + Phi : array shape (n,) + radial power spectrum \\( \\Phi \\) + sigma_Phi : array shape (n,) + uncertainty of \\( \\Phi \\), as returned by + `pycurious.grid.CurieGrid.window_spectrum` + prior : dict, optional + priors to use in place of `self.prior` + + Returns: + residuals : array shape (n + number of priors,) + + Notes: + Warnings from `pycurious.grid.bouligand2009` are suppressed because + some combinations of parameters overflow, which would otherwise + crash the minimiser. Any residual that comes back non-finite is + replaced by a large finite value, so that one unusable bin costs + the fit a fixed penalty rather than poisoning the whole vector. + """ + beta, zt, dz, C = x + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Phi_syn = bouligand2009(kh, beta, zt, dz, C) + + r = (Phi_syn - Phi) / sigma_Phi + r = np.where(np.isfinite(r), r, _OVERFLOW_RESIDUAL) + + if prior is None: + prior = self.prior + + rows = [r] + for key, value in zip(_PARAMETERS, x): + prior_args = prior.get(key) + if prior_args is not None: + loc, scale = prior_args + rows.append(np.array([(value - loc) / scale])) + + return np.concatenate(rows) + + def min_func(self, x, kh, Phi, sigma_Phi, prior=None): + """ + Function to minimise: the negative log posterior, up to a constant. + + Args: + x : array shape (4,) + array of variables \\( \\beta, z_t, \\Delta z, C \\) + kh : array shape (n,) + wavenumbers (rad/km) + Phi : array shape (n,) + radial power spectrum \\( \\Phi \\) + sigma_Phi : array shape (n,) + uncertainty of \\( \\Phi \\), as returned by + `pycurious.grid.CurieGrid.window_spectrum` + prior : dict, optional + priors to use in place of `self.prior` + + Returns: + misfit : float + sum of misfit (scalar) + + Notes: + `sigma_Phi` should be the uncertainty of the binned *mean*, not the + scatter of the cells within each annulus. Weighting by the latter + recovers parameters no better than not weighting at all, because + it is nearly flat across the spectrum and so carries almost no + information -- see `pycurious.grid.CurieGrid.window_spectrum`. + """ + return 0.5 * np.sum(self.residuals(x, kh, Phi, sigma_Phi, prior) ** 2) + + def _spectrum(self, window, xc, yc, taper, process_subgrid, dof_factor, **kwargs): + """ + Radial power spectrum of one window, weighted ready for fitting. + + Pins `power=2`, which is what `pycurious.grid.bouligand2009` describes, + so the four routines that need a spectrum cannot drift apart on it. + """ + return self.window_spectrum( + window, + xc, + yc, + taper=taper, + power=2.0, + process_subgrid=process_subgrid, + dof_factor=dof_factor, + **kwargs + ) + + def _jacobian(self, x, r, args): + """ + Central-difference Jacobian of `residuals` at `x`, given `r` there. + + Eight evaluations for four parameters, so it costs nothing beside the + fit itself. The step is relative, since the parameters differ in scale + by more than an order of magnitude. + """ + x = np.asarray(x, dtype=float) + J = np.empty((r.size, x.size)) + + for i in range(x.size): + h = _JACOBIAN_STEP * max(abs(x[i]), 1.0) + xp, xm = x.copy(), x.copy() + xp[i] += h + xm[i] -= h + J[:, i] = (self.residuals(xp, *args) - self.residuals(xm, *args)) / (2.0 * h) + + return J + + def _covariance(self, x, kh, Phi, sigma_Phi): + """ + Covariance of the fitted parameters at `x`. + + The spectral residuals are correlated between neighbouring bins, so + this is generalised least squares rather than \\( (J^T J)^{-1} \\) -- + see `pycurious.grid._gls_covariance`, which the Tanaka sibling shares. + """ + args = (kh, Phi, sigma_Phi) + r = self.residuals(x, *args) + J = self._jacobian(x, r, args) + + cov = _gls_covariance(J, r, np.size(kh)) + if cov is None: + warnings.warn( + "the fit is singular, so no covariance could be formed. This " + "usually means a parameter is unconstrained by the data.", + RuntimeWarning, + stacklevel=2, + ) + return np.full((np.size(x), np.size(x)), np.nan) + return cov + + def optimise( + self, + window, + xc, + yc, + beta=3.0, + zt=1.0, + dz=10.0, + C=5.0, + taper=np.hanning, + process_subgrid=None, + dof_factor=None, + return_cov=False, + **kwargs + ): + """ + Find the optimal parameters of \\( \\beta, z_t, \\Delta z, C \\) + for a given centroid (xc,yc) and window size, with their + uncertainties. + + Args: + window : float + size of window in metres + xc : float + centroid x values + yc : float + centroid y values + beta : float + fractal parameter (starting value) + zt : float + top of magnetic layer (starting value) + dz : float + thickness of magnetic layer (starting value) + C : float + field constant (starting value) + taper : taper (default=`numpy.hanning`) + taper function, set to None for no taper function + process_subgrids : function + a custom function to process the subgrid + dof_factor : float, optional + override the effective-degrees-of-freedom deflation applied to + the spectral uncertainties, see + `pycurious.grid.CurieGrid.window_spectrum` + return_cov : bool (default=False) + also return the 4x4 parameter covariance matrix + kwargs : keyword arguments + to pass to radial_spectrum. + + Returns: + beta : float + fractal parameter + zt : float + top of magnetic layer + dz : float + thickness of magnetic layer + C : float + field constant + sigma_beta, sigma_zt, sigma_dz, sigma_C : float + standard deviation of each of the above + cov : 2D array shape (4,4) + parameter covariance. Only returned if `return_cov=True`. + + Usage: + >>> beta, zt, dz, C, s_beta, s_zt, s_dz, s_C = grid.optimise( + ... 200e3, xc, yc) + >>> CPD, sigma_CPD = grid.calculate_CPD(zt, dz, s_zt, s_dz) + + Notes: + The uncertainties come from the curvature of the misfit at the + solution, corrected for the correlation between neighbouring + spectral bins -- see `_covariance`. They describe the scatter of + the spectrum at this window and this model. They do **not** include + the systematic error from the choice of window size or centroid, + which on a small grid is larger: sweeping those over + `tests/test_mag_data.txt` moves \\( \\Delta z \\) by 5.5 km, where + the fit reports about 3.3 km. + + `sigma_dz` in particular should be read as a lower bound. The + likelihood in \\( \\Delta z \\) has a long upper tail (Mather & + Fullea, 2019), so a symmetric interval is the wrong shape for it. + Measured over 200 independent synthetics, `sigma_beta`, `sigma_zt` + and `sigma_C` reproduce the true spread to within 1%, while + `sigma_dz` understates it by about 40%. Use `profile` for an honest + interval on \\( \\Delta z \\) and on the Curie depth. + """ + + x0 = np.array([beta, zt, dz, C]) + + k, Phi, sigma_Phi = self._spectrum( + window, xc, yc, taper, process_subgrid, dof_factor, **kwargs + ) + + # minimise function + res = minimize(self.min_func, x0, args=(k, Phi, sigma_Phi), bounds=self.bounds) + x = res.x + + self._warn_on_bounds(x) + cov = self._covariance(x, k, Phi, sigma_Phi) + sigma = np.sqrt(np.diag(cov)) + + if return_cov: + return tuple(x) + tuple(sigma) + (cov,) + return tuple(x) + tuple(sigma) + + def _warn_on_bounds(self, x, rtol=1.0e-6): + """ + Warn when a parameter has been driven onto one of `self.bounds`. + + The covariance is derived from the curvature of an interior minimum, + so at an active bound it describes a solution the optimiser was not + free to find, and the corresponding uncertainty is meaningless. + """ + for name, value, (lb, ub) in zip(_PARAMETERS, x, self.bounds): + for edge in (lb, ub): + if edge is not None and np.isclose(value, edge, rtol=rtol, atol=rtol): + warnings.warn( + "{} converged onto its bound at {:g}, so its " + "uncertainty is not meaningful -- the fit was not free " + "to move it.".format(name, edge), + RuntimeWarning, + stacklevel=3, + ) + + def optimise_routine( + self, + window, + xc_list, + yc_list, + beta=3.0, + zt=1.0, + dz=10.0, + C=5.0, + taper=np.hanning, + process_subgrid=None, + dof_factor=None, + **kwargs + ): + """ + Iterate through a list of centroids to compute the optimal values + of \\( \\beta, z_t, \\Delta z, C \\) for a given window size. + + Args: + window : float + size of window in metres + xc_list : ndarray shape (l,) + centroid x values + yc_list : ndarray shape (l,) + centroid y values + beta : float + fractal parameter + zt : float + top of magnetic layer + dz : float + thickness of magnetic layer + C : float + field constant + taper : function + taper function (default=`numpy.hanning`) + set to None for no taper function + process_subgrids : func + a custom function to process the subgrid + kwargs : keyword arguments + to pass to radial_spectrum. + + Returns: + beta : ndarray shape (l,) + fractal parameters + zt : ndarray shape (l,) + top of magnetic layer + dz : ndarray shape (l,) + thickness of magnetic layer + C : ndarray shape (l,) + field constant + sigma_beta, sigma_zt, sigma_dz, sigma_C : ndarray shape (l,) + standard deviation of each of the above, so a map of the + uncertainty comes out alongside the map of the parameter + + Notes: + The covariance matrix is deliberately not available here. + `pycurious.parallel.CurieParallel.parallelise_routine` collects one + array per returned quantity, which a 4x4 matrix per centroid does + not fit. Call `optimise` directly with `return_cov=True` for that. + """ + return self.parallelise_routine( + window, + xc_list, + yc_list, + self.optimise, + beta, + zt, + dz, + C, + taper, + process_subgrid, + dof_factor, + **kwargs + ) + + def _profiled_misfit(self, target, value, x_hat, args): + """ + Smallest misfit attainable with `target` held at `value`. + + For a parameter of the forward model that means fixing it and + minimising over the other three. The Curie depth is the same thing one + step along: \\( \\Delta z \\) becomes the constrained coordinate + through \\( \\Delta z = \\mathrm{CPD} - z_t \\), leaving + \\( \\beta, z_t, C \\) free -- so it is profiled directly rather than + propagated from `dz`, whose uncertainty is not symmetric. + """ + curie = target == _CPD + fixed = _PARAMETERS.index("dz" if curie else target) + free = [j for j in range(len(_PARAMETERS)) if j != fixed] + + def expand(y): + x = np.empty(len(_PARAMETERS)) + x[free] = y + # for the Curie depth the constraint depends on zt, which is free + x[fixed] = value - x[1] if curie else value + return x + + res = minimize( + lambda y: self.min_func(expand(y), *args), + np.asarray(x_hat)[free], + bounds=[self.bounds[j] for j in free], + ) + return res.fun + + def profile( + self, + window, + xc, + yc, + target, + level=0.95, + npoints=21, + bracket=None, + beta=3.0, + zt=1.0, + dz=10.0, + C=5.0, + taper=np.hanning, + process_subgrid=None, + dof_factor=None, + **kwargs + ): + """ + Confidence interval for one parameter, or for the Curie depth, without + assuming the posterior is symmetric. + + Each point of the scan holds `target` fixed and re-optimises everything + else, tracing the deviance \\( 2(F - F_{min}) \\). The interval is + where that crosses \\( \\chi^2_1 \\) at the requested level, which is + the usual likelihood-ratio construction. + + This matters most for \\( \\Delta z \\) and hence the Curie depth. Both + have a long upper tail (Mather & Fullea, 2019), so the symmetric + \\( \\pm \\sigma \\) that `optimise` reports understates how far the + parameter can plausibly reach -- by about 40% on synthetics. + + Args: + window : float + size of window in metres + xc, yc : float + centroid of the window + target : str + one of `"beta"`, `"zt"`, `"dz"`, `"C"` or `"CPD"` + level : float (default=0.95) + confidence level + npoints : int (default=21) + nodes in the scan. The endpoints are then refined by root + finding, so this sets the resolution of the returned curve + rather than of the interval. + bracket : tuple, optional + (min, max) of the scan. Defaults to a range either side of the + fitted value, wider above than below because of the tail. + beta, zt, dz, C : float + starting values for the underlying fit + taper : function (default=np.hanning) + taper function, or None for no taper + process_subgrid : function, optional + applied to the subgrid before the spectrum is computed + dof_factor : float, optional + see `pycurious.grid.CurieGrid.window_spectrum` + kwargs : keyword arguments + passed to `radial_spectrum` + + Returns: + values : 1D array shape (npoints,) + where the target was held + deviance : 1D array shape (npoints,) + \\( 2(F - F_{min}) \\) at each of those + lower : float + lower end of the interval, `-inf` if the scan never crossed + upper : float + upper end of the interval, `inf` if the scan never crossed + + Usage: + >>> values, deviance, lo, hi = grid.profile(200e3, xc, yc, "CPD") + >>> print("Curie depth {:.1f} ({:.1f} to {:.1f}) km".format(cpd, lo, hi)) + + Notes: + This is a profile *posterior* deviance rather than a profile + likelihood: any priors added with `add_prior` contribute to `F`. A + Gaussian prior is one more observation, so the calibration still + holds, but it is not the marginal an MCMC would report -- profiling + takes the ridge of the posterior rather than integrating over it, + and so is a little narrower for a skewed one. + + Like the covariance from `optimise`, the interval describes the + scatter of the spectrum at a fixed window, centroid and model. It + does not cover the systematic error from choosing those: on + `tests/test_mag_data.txt` the interval for \\( \\Delta z \\) is + about 3.3 km wide, where sweeping the window size and centroid + moves \\( \\Delta z \\) over 5.5 km. + """ + if target not in _PARAMETERS + (_CPD,): + raise ValueError( + "target must be one of {}, not {!r}".format( + _PARAMETERS + (_CPD,), target + ) + ) + + k, Phi, sigma_Phi = self._spectrum( + window, xc, yc, taper, process_subgrid, dof_factor, **kwargs + ) + args = (k, Phi, sigma_Phi) + + x0 = np.array([beta, zt, dz, C]) + res = minimize(self.min_func, x0, args=args, bounds=self.bounds) + x_hat, F_min = res.x, res.fun + + # every constrained fit is cached, so the root finding below reuses the + # scan nodes it lands on rather than paying for them twice + cache = {} + + def constrained(value): + value = float(value) + if value not in cache: + cache[value] = self._profiled_misfit(target, value, x_hat, args) + return cache[value] + + if bracket is None: + bracket = self._profile_bracket(target, x_hat, args) + lo, hi = float(bracket[0]), float(bracket[1]) + + values = np.linspace(lo, hi, int(npoints)) + + # carry the fitted value itself as a node. Without it a coarse or + # badly placed bracket can step over the minimum entirely, leaving + # every node above the threshold and the interval collapsed onto a + # single point with nothing to say so. + hat = x_hat[1] + x_hat[2] if target == _CPD else x_hat[_PARAMETERS.index(target)] + if lo < hat < hi: + values = np.unique(np.append(values, hat)) + + misfit = np.array([constrained(v) for v in values]) + + # a constrained fit can land below the unconstrained one when the + # latter stopped early, which would put the deviance negative + F_min = min(F_min, misfit.min()) + deviance = 2.0 * (misfit - F_min) + + threshold = stats.chi2.ppf(level, 1) + centre = values[np.argmin(deviance)] + + if deviance.min() > threshold: + warnings.warn( + "the {} scan over {:.4g} to {:.4g} never came within the " + "threshold of the best fit, so the interval it returns is " + "meaningless. Widen `bracket` around {:.4g}, or raise " + "`npoints`.".format(target, values[0], values[-1], hat), + RuntimeWarning, + stacklevel=2, + ) + + def gap(value): + return 2.0 * (constrained(value) - F_min) - threshold + + # walk outwards from the best fit in each direction + below = values <= centre + above = values >= centre + lower = self._profile_root( + values[below][::-1], deviance[below][::-1], threshold, gap, + -np.inf, target, level, + ) + upper = self._profile_root( + values[above], deviance[above], threshold, gap, np.inf, target, level + ) + + return values, deviance, lower, upper + + def _profile_bracket(self, target, x_hat, args): + """ + Default scan range: a few standard deviations either side of the fit, + reaching further above than below because the tail is on that side. + """ + cov = self._covariance(x_hat, *args) + sigma = np.sqrt(np.abs(np.diag(cov))) + + if target == _CPD: + centre = x_hat[1] + x_hat[2] + width = np.hypot(sigma[1], sigma[2]) + else: + index = _PARAMETERS.index(target) + centre, width = x_hat[index], sigma[index] + + if not np.isfinite(width) or width <= 0.0: + width = max(abs(centre), 1.0) + + lo, hi = centre - 5.0 * width, centre + 12.0 * width + + # depths are positive, and the scan must stay inside the range the + # forward model can be evaluated over + if target in ("dz", _CPD): + lo, hi = max(lo, 0.0), min(hi, self._max_thickness()) + elif target in ("zt", "beta"): + lo = max(lo, 0.0) + + return lo, hi + + @staticmethod + def _profile_root(values, deviance, threshold, gap, unbounded, target, level): + """ + Where the deviance crosses `threshold`, refined off the scan grid. + + `values` and `deviance` run outwards from the best fit, so this is the + same walk in either direction and the caller supplies the reflection + and the sign of `unbounded`. + + Reading the crossing off the nearest node would quantise the interval + at the node spacing, which is a large fraction of its width for a + sensible `npoints`; root finding between the two bracketing nodes costs + a handful of extra fits and removes that. + """ + crossed = np.nonzero(deviance > threshold)[0] + + if crossed.size == 0: + warnings.warn( + "the {} profile never reached the {:.0%} threshold within " + "{:.4g} to {:.4g}, so that side of the interval is unbounded. " + "The data do not constrain it; widen `bracket` to confirm." + .format(target, level, min(values), max(values)), + RuntimeWarning, + stacklevel=3, + ) + return unbounded + + j = crossed[0] + if j == 0: + # already over the threshold at the best fit: nothing to bracket + return float(values[0]) + + return float(brentq(gap, values[j - 1], values[j], xtol=_PROFILE_XTOL)) + + @stochastic + def metropolis_hastings( + self, + window, + xc, + yc, + nsim, + burnin, + x_scale=None, + beta=3.0, + zt=1.0, + dz=10.0, + C=5.0, + taper=np.hanning, + process_subgrid=None, + dof_factor=None, + adapt=True, + seed=None, + return_diagnostics=False, + **kwargs + ): + """ + MCMC algorithm using a Metropolis-Hastings sampler. + + Evaluates a Markov chain for starting values of + \\( \\beta, z_t, \\Delta z, C \\) and returns the ensemble of model + realisations. + + Args: + window : float + size of window in metres + xc : float + centroid x values + yc : float + centroid y values + nsim : int + number of simulations + burnin : int + number of burn-in simulations before to nsim + x_scale : float(4), optional + initial width of the proposal in each parameter + (default=`[1,1,1,1]` for `[beta, zt, dz, C]`). With + `adapt=True` this is only a starting point. + beta : float + fractal parameter (starting value for the search) + zt : float + top of magnetic layer (starting value for the search) + dz : float + thickness of magnetic layer (starting value for the search) + C : float + field constant (starting value for the search) + taper : function (default=np.hanning) + taper function, or None for no taper + process_subgrid : function, optional + applied to the subgrid before the spectrum is computed + dof_factor : float, optional + see `pycurious.grid.CurieGrid.window_spectrum` + adapt : bool (default=True) + tune the proposal during burn-in -- see Notes. Turning this off + is only sensible if you have a good `x_scale` already. + seed : int, optional + seed for reproducibility + return_diagnostics : bool (default=False) + also return a dict of `acceptance`, `burnin_acceptance`, + `x_scale` + + Returns: + beta : ndarray shape (nsim,) + fractal parameter + zt : ndarray shape (nsim,) + top of magnetic layer + dz : ndarray shape (nsim,) + thickness of magnetic layer + C : ndarray shape (nsim,) + field constant + diagnostics : dict + only if `return_diagnostics=True` + + Usage: + >>> posterior, info = grid.metropolis_hastings( + ... 200e3, xc, yc, 10000, 2000, seed=1, return_diagnostics=True) + >>> print("acceptance {:.2f}".format(info["acceptance"])) + + Notes: + Acceptance is decided in log space. Comparing + \\( e^{-F} \\) directly underflows to zero for any real spectrum -- + \\( F \\) runs to hundreds -- at which point every proposal is + rejected and the chain returns a handful of distinct states + dressed up as a posterior. + + The chain starts at the mode, found with the same minimiser + `optimise` uses and from the same starting values. That costs a + fraction of a second and removes the job the burn-in is worst at. + + There is no tempering. It was tried -- annealing the burn-in + after Sambridge (2013), doi:10.1093/gji/ggt342 -- and made every + case worse. What motivated it was that large parts of the posterior + evaluated to zero, and that was the \\( e^{-F} \\) underflow rather + than a property of the problem, so log-space acceptance removes the + reason for it. It also fights the proposal tuning below: a high + temperature makes almost everything acceptable, driving the scale + up, and the scale then collapses as the temperature falls, freezing + the chain wherever the hot phase left it. + + The shape of the proposal matters more than any of the above. The + four parameters are strongly correlated -- \\( \\beta \\) with + \\( z_t \\) at about -0.92, \\( z_t \\) with \\( C \\) at about + 0.87 -- and their marginal widths differ by a factor of thirty, so + a proposal with one width per parameter cannot move along the ridge + they lie on, and the chain sits still. The proposal is therefore + drawn along the fit covariance, the same one `optimise` reports, + with the burn-in tuning only a scalar multiplier on it towards an + acceptance rate of 0.234 (Robbins-Monro). `x_scale` sets where that + multiplier starts, and `adapt=False` fixes it there. + + The chain respects `self.bounds`, which the optimiser has always + done but the sampler previously did not. + + Both this and `sensitivity` treat the spectral bins as independent, + which they are not, so the posterior is narrower than the spread + over independent realisations of the field. See `optimise`. + """ + rng = np.random.default_rng(seed) + ndim = len(_PARAMETERS) + + k, Phi, sigma_Phi = self._spectrum( + window, xc, yc, taper, process_subgrid, dof_factor, **kwargs + ) + + lower = np.array( + [-np.inf if b[0] is None else b[0] for b in self.bounds], dtype=float + ) + upper = np.array( + [np.inf if b[1] is None else b[1] for b in self.bounds], dtype=float + ) + + def log_posterior(x): + if np.any(x < lower) or np.any(x > upper): + return -np.inf + return -self.min_func(x, k, Phi, sigma_Phi) + + def step(x, F, scale, chol): + """One Metropolis move.""" + proposal = x + scale * chol.dot(rng.normal(size=ndim)) + + F1 = log_posterior(proposal) + accepted = np.isfinite(F1) and np.log(rng.random()) < F1 - F + + if accepted: + return proposal, F1, True + return x, F, False + + # Start the chain at the mode rather than at the caller's guess. The + # optimiser finds it in a fraction of the time a random walk takes to + # wander there, and a chain started away from it spends its whole + # burn-in travelling instead of tuning. Measured on a synthetic, the + # posterior mean from a default start sits at a misfit of 121 against + # the mode's 50; started here it lands on 50.1. + start = minimize( + self.min_func, + np.array([beta, zt, dz, C], dtype=float), + args=(k, Phi, sigma_Phi), + bounds=self.bounds, + ) + + x = start.x + F = log_posterior(x) + + # Propose along the fit covariance. That already describes the ridge + # the parameters lie on -- it is what `optimise` reports -- so there is + # no reason to rediscover it by watching the chain, and every reason + # not to: a burn-in started at the mode with too small a step learns a + # covariance narrower than the truth, proposes from it, and confirms + # itself. Measured that way the chain reported a sigma on dz of 0.8 + # against a true 8.7. + chol = self._proposal_cholesky(self._covariance(x, k, Phi, sigma_Phi)) + + # `scale` is a scalar multiplier on an already correctly shaped + # proposal, so there is one thing for the burn-in to tune + scale = 1.0 if x_scale is None else float(np.mean(x_scale)) + + burnin_accepted = 0 + + for i in range(int(burnin)): + x, F, accepted = step(x, F, scale, chol) + burnin_accepted += accepted + + if adapt: + # Robbins-Monro: nudge towards 0.234, with a decaying step so + # the scale settles rather than rattling around + scale = scale * np.exp((accepted - _TARGET_ACCEPTANCE) / (i + 1.0) ** 0.6) + + samples = np.empty((int(nsim), ndim)) + accepted_total = 0 + for i in range(int(nsim)): + x, F, accepted = step(x, F, scale, chol) + accepted_total += accepted + samples[i] = x + + if return_diagnostics: + diagnostics = { + "acceptance": accepted_total / max(int(nsim), 1), + "burnin_acceptance": burnin_accepted / max(int(burnin), 1), + "x_scale": scale, + } + return list(samples.T), diagnostics + + # the default return shape has to stay a plain list of arrays: + # pycurious.parallel dispatches on the dimensionality of the result + return list(samples.T) + + @staticmethod + def _proposal_cholesky(cov): + """ + Scaled Cholesky factor of a covariance, for use as a proposal. + + Proposing along the covariance lets the chain move down the correlated + ridge the parameters lie on, which no proposal with one width per + parameter can follow: `beta` and `zt` correlate at about -0.92 and + their marginal widths differ by a factor of thirty. The factor of + \\( 2.38/\\sqrt{d} \\) is the usual optimal scaling for a Gaussian + target. + + Falls back to the identity when the covariance is unusable, so the + chain still runs -- isotropically, and badly -- rather than drawing + from a degenerate distribution or failing outright. + """ + ndim = np.shape(cov)[0] if cov is not None else len(_PARAMETERS) + scaling = 2.38 / np.sqrt(ndim) + + if cov is not None and np.all(np.isfinite(cov)): + try: + return np.linalg.cholesky(cov) * scaling + except np.linalg.LinAlgError: + pass + + return np.eye(ndim) * scaling + + @stochastic + def sensitivity( + self, + window, + xc, + yc, + nsim, + beta=3.0, + zt=1.0, + dz=10.0, + C=5.0, + taper=np.hanning, + process_subgrid=None, + dof_factor=None, + seed=None, + **kwargs + ): + """ + Sample the uncertainty of \\( \\beta, z_t, \\Delta z, C \\) by + resampling the spectrum, and the centre of each prior distribution + (if provided by the user - see add_prior). + + Args: + window : float + size of window in metres + xc : float + centroid x values + yc : float + centroid y values + nsim : int + number of Monte Carlo simulations + beta : float + starting fractal parameter + zt : float + starting top of magnetic layer + dz : float + starting thickness of magnetic layer + C : float + starting field constant + dof_factor : float, optional + override the effective-degrees-of-freedom deflation, see + `pycurious.grid.CurieGrid.window_spectrum` + seed : int, optional + seed for reproducibility + + Returns: + beta : ndarray shape (nsim,) + fractal parameters + zt : ndarray shape (nsim,) + top of magnetic layer + dz : ndarray shape (nsim,) + thickness of magnetic layer + C : ndarray shape (nsim,) + field constant + + Notes: + Each bin of the spectrum is resampled independently, so this shares + the assumption behind the fit covariance that the bins are + independent. They are not -- a taper correlates neighbouring + annuli -- so agreement between the two is not evidence that either + is right. Only an ensemble over independent realisations of the + field calibrates that. + """ + rng = np.random.default_rng(seed) + + samples = np.empty((nsim, 4)) + x0 = np.array([beta, zt, dz, C]) + + use_keys = [key for key, pdf in self.prior_pdf.items() if pdf is not None] + + k, Phi, sigma_Phi = self._spectrum( + window, xc, yc, taper, process_subgrid, dof_factor, **kwargs + ) + + # Every resampled spectrum lands in the same basin, so start each + # simulation from the unresampled solution rather than from the + # caller's guess. One extra fit up front, and about a third off the + # total for any useful `nsim`. + x0 = minimize( + self.min_func, x0, args=(k, Phi, sigma_Phi), bounds=self.bounds + ).x + + for sim in range(0, nsim): + # a fresh set of prior centres, drawn without disturbing the ones + # stored on the instance + prior = dict(self.prior) + for key in use_keys: + loc = self.prior_pdf[key].rvs(random_state=rng) + prior[key] = (loc, self.prior[key][1]) + + rPhi = rng.normal(Phi, sigma_Phi) + res = minimize( + self.min_func, + x0, + args=(k, rPhi, sigma_Phi, prior), + bounds=self.bounds, + ) + samples[sim] = res.x + + return list(samples.T) + + def calculate_CPD(self, zt, dz, sigma_zt=0.0, sigma_dz=0.0): + """ + Compute the Curie depth from the results of `optimise`. + + Args: + zt : float / 1D array + depth to the top of the magnetic source + dz : float / 1D array + thickness of the magnetic source + sigma_zt : float / 1D array + standard deviation of `zt` + sigma_dz : float / 1D array + standard deviation of `dz` + + Returns: + CPD : float / 1D array + estimated Curie point depth at the base of the magnetic source + CPD_stdev : float / 1D array + standard deviation of `CPD` + + Usage: + >>> beta, zt, dz, C, s_beta, s_zt, s_dz, s_C = grid.optimise( + ... 200e3, xc, yc) + >>> CPD, sigma_CPD = grid.calculate_CPD(zt, dz, s_zt, s_dz) + + Notes: + \\( Z_b = z_t + \\Delta z \\), so the uncertainties combine as + \\( \\sqrt{\\sigma_{z_t}^2 + \\sigma_{\\Delta z}^2} \\). The two are + correlated -- about 0.6 -- but \\( \\sigma_{\\Delta z} \\) exceeds + \\( \\sigma_{z_t} \\) by four orders of magnitude, so including the + covariance changes the answer by around 1%. `optimise` will hand + over the full matrix with `return_cov=True` for anyone who wants it. + + The far larger effect is that this is symmetric and the Curie depth + is not: \\( \\Delta z \\) has a long upper tail, so `CPD_stdev` + understates how deep the base can plausibly lie. Use `profile` with + `target="CPD"` for an interval that does not assume symmetry. + + Matches the signature and return of + `pycurious.optimise_tanaka.CurieOptimiseTanaka.calculate_CPD`, so + code written against one behaves the same against the other. + """ + CPD = zt + dz + CPD_stdev = np.sqrt(np.asarray(sigma_zt) ** 2 + np.asarray(sigma_dz) ** 2) + return (CPD, CPD_stdev) diff --git a/pycurious/optimise_tanaka.py b/pycurious/optimise_tanaka.py new file mode 100644 index 0000000..b23f4bd --- /dev/null +++ b/pycurious/optimise_tanaka.py @@ -0,0 +1,703 @@ +# Copyright 2018-2019 Ben Mather, Robert Delhaye +# +# This file is part of PyCurious. +# +# PyCurious is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or any later version. +# +# PyCurious is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PyCurious. If not, see . + +""" +The centroid method of Tanaka *et al.* (1999), with uncertainties. + +``CurieOptimiseTanaka`` fits two straight lines to separate wavenumber bands of +the log amplitude spectrum -- a short-wavelength band whose slope gives the depth +to the top of the source, and a long-wavelength band whose slope gives the +centroid depth -- and propagates the fit covariance into an uncertainty on the +Curie depth. The bands have no defaults and must be chosen for the data; use +``check_bands`` to test a choice. The derivation, the conditions each band relies +on, and the fractal-magnetisation correction are documented in the online theory +guide. + +References: + Tanaka, A., Okubo, Y., & Matsubayashi, O. (1999). Curie point depth based on + spectrum analysis of the magnetic anomaly data in East and Southeast Asia. + Tectonophysics, 306(3-4), 461-470. doi:10.1016/S0040-1951(99)00072-4 +""" + +import warnings +from multiprocessing import cpu_count + +import numpy as np +from scipy.optimize import curve_fit + +from .grid import CurieGrid, _gls_covariance +from .parallel import stochastic + +# Below this many points in a band the residual autocorrelation is estimated +# from too little to be worth anything, and a noisy inflation is worse than +# none. The bands used in practice hold two or three times this. +_MIN_POINTS_FOR_CORRELATION = 8 + +# Truncating sinh(|k|d) at |k|d biases the centroid, and hence the Curie +# depth, low. Measured against exact layer spectra the shortfall is +# proportional to both the thickness and |k|d: +# +# Z_b bias = -0.17 * thickness * |k|d +# +# holding to within 6% over thicknesses of 10-40 km and |k|d of 0.2-1.0. That +# lets check_bands quote a bias in km rather than leave the user to judge +# whether |k|d is small enough. +_CENTROID_BIAS = -0.17 + +# Warn once the bias reaches about 5% of the source thickness. The previous +# threshold of 1.0 only fired when the Curie depth was already 3.3 km out on a +# 20 km layer, having said nothing at |k|d = 0.5 where it is 1.7 km out. +_KD_LIMIT = 0.3 + +# The zt fit wants wavelengths short compared with the source thickness. The +# previous rule warned above twice the thickness, where the measured zt error +# is 0.05% of it -- so it complained about a 10 m error while staying silent +# about the kilometres above. At four times the thickness the error is about +# 1%, which is the point at which it starts to matter. +_HALF_SPACE_RATIO = 4.0 + +# A zt band fits the short-wavelength end of the spectrum, so its upper edge +# sitting this far down the available range means the numbers are much more +# likely to be cycles/km left over from before bands were given in rad/km. +# Judged relative to the spectrum rather than absolutely, since the Nyquist +# wavenumber depends on the grid spacing. +_CYCLES_PER_KM_SUSPICION = 0.1 + + +def _linear_func(x, a, b): + """Straight line, fitted to each band.""" + return a * x + b + + +class CurieOptimiseTanaka(CurieGrid): + """ + Extends `pycurious.grid.CurieGrid` with the centroid method of + Tanaka *et al.* (1999). + + Two straight lines are fitted to separate bands of the radial amplitude + spectrum, giving the top and centroid depths of the magnetic source and, + from those, the Curie point depth with an uncertainty. + + Args: + grid : 2D array + 2D array of magnetic data + xmin, xmax : float + minimum/maximum x bounds of the grid + ymin, ymax : float + minimum/maximum y bounds of the grid + max_processors : int, optional + processors to use in `optimise_routine` (default=all) + + Attributes: + max_processors : int + processors used by the parallel routines + (plus all attributes inherited from `pycurious.grid.CurieGrid`) + + Notes: + The grid must be projected in eastings/northings (metres), not + degrees, since depths are computed in km. + """ + + def __init__(self, grid, xmin, xmax, ymin, ymax, **kwargs): + + super(CurieOptimiseTanaka, self).__init__(grid, xmin, xmax, ymin, ymax) + + self.max_processors = kwargs.pop("max_processors", cpu_count()) + + def check_bands(self, k, zt_range, z0_range, thickness=None, verbose=True): + """ + Report whether two fitting bands are usable, before relying on them. + + Both of Tanaka's straight-line approximations hold only over part of + the spectrum. A band outside that range still produces a confident + looking fit, so this is worth checking explicitly. + + Args: + k : 1D array + wavenumbers from `radial_spectrum`, in rad/km + zt_range : tuple + (min, max) wavenumber of the \\( Z_t \\) band, rad/km + z0_range : tuple + (min, max) wavenumber of the \\( Z_0 \\) band, rad/km + thickness : float, optional + estimated source thickness in km. Without it the validity of + each approximation cannot be assessed, only the point counts. + verbose : bool (default=True) + print a summary. Set False to receive any problems as + `UserWarning` instead, for use in a script. + + Returns: + diagnostics : dict + `dk`, `n_zt`, `n_z0`, `lambda_zt`, `lambda_z0` and, if + `thickness` was given, `kd_max`, `CPD_bias` and + `lambda_zt_min_required`. `CPD_bias` is an estimate in km of + how far the \\( |k|d \\) approximation drags the Curie depth, + and is negative. + + Usage: + >>> k, Phi, sigma_Phi = grid.radial_spectrum(subgrid, power=1) + >>> grid.check_bands(k, (0.2, 0.6), (0.0, 0.05), thickness=20.0) + """ + k = np.asarray(k) + _warn_if_cycles_per_km(zt_range, k) + + mask_zt = np.logical_and(k >= zt_range[0], k <= zt_range[1]) + mask_z0 = np.logical_and(k >= z0_range[0], k <= z0_range[1]) + + n_zt = int(np.count_nonzero(mask_zt)) + n_z0 = int(np.count_nonzero(mask_z0)) + dk = float(np.min(np.diff(k))) if k.size > 1 else np.nan + + diagnostics = { + "dk": dk, + "n_zt": n_zt, + "n_z0": n_z0, + "lambda_zt": _wavelength_range(k[mask_zt]), + "lambda_z0": _wavelength_range(k[mask_z0]), + } + + messages = [] + for name, count in (("zt_range", n_zt), ("z0_range", n_z0)): + if count < 3: + messages.append( + "{} holds {} points, need at least 3".format(name, count) + ) + elif count < 8: + messages.append( + "{} holds only {} points, so its gradient will be poorly " + "determined. A wider window resolves the spectrum more " + "finely.".format(name, count) + ) + + if thickness is not None: + half = 0.5 * float(thickness) + kd_max = float(k[mask_z0].max() * half) if n_z0 else np.nan + cpd_bias = _CENTROID_BIAS * float(thickness) * kd_max + diagnostics["kd_max"] = kd_max + diagnostics["CPD_bias"] = cpd_bias + diagnostics["lambda_zt_min_required"] = _HALF_SPACE_RATIO * float(thickness) + + if n_z0 and kd_max > _KD_LIMIT: + messages.append( + "z0_range reaches |k|d = {:.2f}, which biases the Curie " + "depth low by about {:.1f} km. The centroid fit assumes " + "|k|d << 1. Lower the upper edge of z0_range, or use a " + "wider window so there are enough points below " + "it.".format(kd_max, abs(cpd_bias)) + ) + if n_zt and diagnostics["lambda_zt"][1] > _HALF_SPACE_RATIO * thickness: + messages.append( + "zt_range reaches a wavelength of {:.1f} km, more than {:g} " + "times the source thickness ({:.1f} km). The half-space " + "approximation behind the zt fit weakens there.".format( + diagnostics["lambda_zt"][1], + _HALF_SPACE_RATIO, + thickness, + ) + ) + + if verbose: + print("spectral resolution dk = {:.4f} rad/km".format(dk)) + print( + "zt band: {} points, wavelengths {:.1f}-{:.1f} km".format( + n_zt, *diagnostics["lambda_zt"] + ) + ) + print( + "z0 band: {} points, wavelengths {:.1f}-{:.1f} km".format( + n_z0, *diagnostics["lambda_z0"] + ) + ) + if thickness is not None: + print( + "z0 band reaches |k|d = {:.2f}, biasing the Curie depth by " + "about {:+.1f} km".format(kd_max, cpd_bias) + ) + for message in messages: + print("WARNING: {}".format(message)) + if not messages: + print("both bands look usable") + else: + # when verbose the messages have already been printed; warning as + # well just prints everything twice + for message in messages: + warnings.warn(message, UserWarning, stacklevel=2) + + return diagnostics + + def _fit_band(self, k, Phi, sigma, band, absolute_sigma=True): + """ + Fit a straight line over one band and return the depth it implies. + + Returns `(depth, intercept, depth_stdev)`, where `depth` is the + negated gradient and so is positive downwards. + + The gradient is the ordinary weighted least-squares one. Its + uncertainty is not: neighbouring spectral bins are correlated, and + `curve_fit` assumes they are not, so the covariance it returns is too + small -- on the legacy fixture the centroid band reports 0.126 km where + the residuals imply 0.282. The correlation is estimated from the fit + residuals and folded in, as it is on the Bouligand side. + """ + mask = np.logical_and(k >= band[0], k <= band[1]) + mask &= np.isfinite(Phi) & np.isfinite(sigma) & (sigma > 0.0) + + n = int(np.count_nonzero(mask)) + if n < 3: + raise ValueError( + "only {} usable points in the band {}-{} rad/km, need at least " + "3. Widen the band, or use a larger window to resolve the " + "spectrum more finely.".format(n, band[0], band[1]) + ) + + (gradient, intercept), covariance = curve_fit( + _linear_func, + k[mask], + Phi[mask], + sigma=sigma[mask], + absolute_sigma=absolute_sigma, + ) + + stdev = np.sqrt(np.diag(covariance))[0] + if absolute_sigma: + stdev = self._correlated_gradient_stdev( + k[mask], Phi[mask], sigma[mask], gradient, intercept, stdev + ) + + return -gradient, intercept, stdev + + @staticmethod + def _correlated_gradient_stdev(k, Phi, sigma, gradient, intercept, fallback): + """ + Standard deviation of a fitted gradient, allowing for correlation + between neighbouring spectral bins. + + \\( (X^T R^{-1} X)^{-1} \\) for the whitened design matrix `X` of the + straight line, with `R` the banded correlation of the residuals. + + Falls back to the uncorrelated value when the band holds too few points + to estimate a correlation from, or when the result is singular. Below + about eight points the estimate is noise, and a noisy inflation is + worse than none. + """ + if k.size < _MIN_POINTS_FOR_CORRELATION: + return fallback + + residual = (Phi - _linear_func(k, gradient, intercept)) / sigma + + # for a straight line the Jacobian of the whitened residual is just the + # design matrix, so there is nothing to differentiate numerically + J = np.column_stack([k, np.ones_like(k)]) / sigma[:, None] + + cov = _gls_covariance(J, residual, k.size) + if cov is None: + return fallback + + stdev = np.sqrt(np.diag(cov))[0] + return stdev if np.isfinite(stdev) else fallback + + def _spectrum(self, window, xc, yc, taper, beta, process_subgrid, dof_factor, + **kwargs): + """ + Radial amplitude spectrum of one window, prepared for both fits. + + Returns `(k, Phi, Phi_n, sigma)` where `Phi` is the log amplitude + spectrum, `Phi_n` is that divided by `|k|`, and `sigma` is the + uncertainty of the binned mean -- shared by both, since `ln|k|` is + deterministic and so does not alter it. + """ + # power=1 gives ln of the amplitude spectrum, Tanaka's ln(Phi^1/2) + k, Phi, sigma = self.window_spectrum( + window, + xc, + yc, + taper=taper, + power=1, + process_subgrid=process_subgrid, + dof_factor=dof_factor, + **kwargs + ) + + if beta is not None: + # remove the fractal contribution -0.5*(beta-1)*ln|k|, matching the + # parameterisation of pycurious.grid.bouligand2009 + Phi = Phi + 0.5 * (beta - 1.0) * np.log(k) + + # ln|k| is deterministic, so subtracting it leaves sigma untouched + Phi_n = Phi - np.log(k) + + return k, Phi, Phi_n, sigma + + def optimise( + self, + window, + xc, + yc, + zt_range, + z0_range, + taper=np.hanning, + beta=None, + process_subgrid=None, + absolute_sigma=True, + dof_factor=None, + **kwargs + ): + """ + Estimate the top and centroid depths of the magnetic source for one + centroid, with their uncertainties. + + Args: + window : float + size of the window in metres + xc, yc : float + centroid of the window + zt_range : tuple + (min, max) wavenumber in **rad/km** over which to fit + \\( Z_t \\). Should cover wavelengths shorter than twice the + source thickness. + z0_range : tuple + (min, max) wavenumber in **rad/km** over which to fit + \\( Z_0 \\). Must satisfy \\( |k| d \\ll 1 \\) -- check with + `check_bands`. + taper : function (default=np.hanning) + taper function, or None for no taper + beta : float, optional + fractal parameter of the magnetisation. If given, its + contribution is removed before fitting. Leave unset for the + method exactly as Tanaka published it; `beta=1` is equivalent. + process_subgrid : function, optional + applied to the subgrid before the spectrum is computed + absolute_sigma : bool (default=True) + treat the spectral uncertainties as absolute, so the reported + errors carry their units. Set False to rescale the covariance + by the reduced chi-squared instead. + dof_factor : float, optional + override the effective-degrees-of-freedom deflation applied to + the spectral uncertainties (see Notes) + kwargs : keyword arguments + passed to `radial_spectrum` + + Returns: + zt : float + depth to the top of the magnetic source, km + z0 : float + centroid depth of the magnetic source, km + zt_intercept : float + intercept of the \\( Z_t \\) fit + z0_intercept : float + intercept of the \\( Z_0 \\) fit + sigma_zt : float + standard deviation of `zt` + sigma_z0 : float + standard deviation of `z0` + + Usage: + >>> zt, z0, zt_i, z0_i, sigma_zt, sigma_z0 = grid.optimise( + ... 200e3, xc, yc, (0.2, 0.6), (0.0, 0.05)) + >>> CPD, sigma_CPD = grid.calculate_CPD( + ... zt, z0, sigma_zt=sigma_zt, sigma_z0=sigma_z0) + + Notes: + Depths are returned positive downwards, i.e. the negated gradient + of each fit. + + The reported uncertainties describe the scatter of the spectrum + only. They do not include the systematic error from the choice of + band, which is usually larger -- see `sensitivity`. + + `radial_spectrum` returns the scatter of the FFT cells within each + annulus, whereas the fit needs the uncertainty of the annulus + mean. That is the standard error, except that the cells are not + independent: Hermitian symmetry makes about half of them + redundant, and tapering correlates neighbours. The correction is + calibrated per taper; `dof_factor` overrides it. + + Cells in *neighbouring* annuli are correlated too, which `_fit_band` + allows for. Against 80 independent synthetics the ratio of the true + spread to the reported `sigma_zt` improves from 1.48 to 1.12 with + that correction in place. + + `sigma_z0` remains understated, at a ratio of about 1.34, and no + covariance can fix it. The centroid gradient is fitted over a + handful of the longest wavelengths the window resolves, and its + distribution is heavy-tailed: on a 4000 km grid with a true + \\( Z_0 \\) of 11 km, the middle 90% of estimates spanned 4.2 to + 25.3 km. Treat `sigma_z0`, and the Curie depth that follows from + it, as a lower bound. + + There is no profile-likelihood alternative here, as there is on the + Bouligand side. Each band is a straight-line fit, so its misfit is + exactly quadratic in the gradient and the profile interval is + provably the same as the covariance one -- verified, a deviance of + 3.841459 against a threshold of 3.841459 on both bands. It would + return the number `_fit_band` already returns. The equivalence + holds only for `absolute_sigma=True`; setting it False rescales the + covariance by the reduced chi-squared afterwards, which the profile + construction does not do. + """ + k, Phi, Phi_n, sigma = self._spectrum( + window, xc, yc, taper, beta, process_subgrid, dof_factor, **kwargs + ) + + _warn_if_cycles_per_km(zt_range, k) + + zt, zt_intercept, sigma_zt = self._fit_band( + k, Phi, sigma, zt_range, absolute_sigma + ) + z0, z0_intercept, sigma_z0 = self._fit_band( + k, Phi_n, sigma, z0_range, absolute_sigma + ) + + return (zt, z0, zt_intercept, z0_intercept, sigma_zt, sigma_z0) + + def optimise_routine( + self, + window, + xc_list, + yc_list, + zt_range, + z0_range, + taper=np.hanning, + beta=None, + process_subgrid=None, + absolute_sigma=True, + dof_factor=None, + **kwargs + ): + """ + Iterate `optimise` over a list of centroids, in parallel. + + Takes the same arguments as `optimise`, with lists of centroids in + place of a single one. See + `pycurious.parallel.CurieParallel.parallelise_routine` for the + `on_error` and `seed` keywords. + + Returns: + zt, z0, zt_intercept, z0_intercept, sigma_zt, sigma_z0 : + 1D arrays, one entry per centroid + + Usage: + >>> xc_list, yc_list = grid.create_centroid_list(window, 10e3, 10e3) + >>> zt, z0, zt_i, z0_i, sigma_zt, sigma_z0 = grid.optimise_routine( + ... window, xc_list, yc_list, (0.2, 0.6), (0.0, 0.05)) + """ + return self.parallelise_routine( + window, + xc_list, + yc_list, + self.optimise, + zt_range, + z0_range, + taper, + beta, + process_subgrid, + absolute_sigma, + dof_factor, + **kwargs + ) + + @stochastic + def sensitivity( + self, + window, + xc, + yc, + nsim, + zt_range, + z0_range, + taper=np.hanning, + beta=None, + band_scale=0.1, + process_subgrid=None, + absolute_sigma=True, + dof_factor=None, + seed=None, + **kwargs + ): + """ + Sample the uncertainty of the Curie depth by perturbing both the + spectrum and the fitting bands. + + The fit covariance alone understates the uncertainty, because where + the band edges are placed usually matters more than the scatter of the + spectrum. Each simulation therefore redraws the spectrum within its + uncertainty *and* jitters both band edges. + + Args: + window : float + size of the window in metres + xc, yc : float + centroid of the window + nsim : int + number of simulations + zt_range, z0_range : tuple + as `optimise`, in rad/km. These are the centres about which + the band edges are jittered. + band_scale : float (default=0.1) + standard deviation of the jitter applied to each band edge, as + a fraction of that band's width. Set to 0 to perturb only the + spectrum, which recovers the analytic covariance. + seed : int, optional + seed for reproducibility + (remaining arguments as `optimise`) + + Returns: + zt : 1D array shape (nsim,) + sampled top depths + z0 : 1D array shape (nsim,) + sampled centroid depths + CPD : 1D array shape (nsim,) + sampled Curie point depths + + Usage: + >>> zt, z0, CPD = grid.sensitivity( + ... 200e3, xc, yc, 500, (0.2, 0.6), (0.0, 0.05)) + >>> print(CPD.mean(), CPD.std()) + + Notes: + This samples the statistical uncertainty and the sensitivity to + band placement. It does not capture the systematic error from + fitting outside the range where each approximation holds, nor from + unmodelled fractal magnetisation -- both bias the two fits + coherently rather than scattering them. Use `check_bands` and + `beta` for those. + """ + rng = np.random.default_rng(seed) + + # the spectrum is computed once and resampled, as in + # CurieOptimiseBouligand.sensitivity + k, Phi, Phi_n, sigma = self._spectrum( + window, xc, yc, taper, beta, process_subgrid, dof_factor, **kwargs + ) + + _warn_if_cycles_per_km(zt_range, k) + + zt_width = zt_range[1] - zt_range[0] + z0_width = z0_range[1] - z0_range[0] + + zt_samples = np.empty(nsim) + z0_samples = np.empty(nsim) + + i = 0 + attempts = 0 + max_attempts = 100 * nsim + while i < nsim: + attempts += 1 + if attempts > max_attempts: + raise RuntimeError( + "only {} of {} simulations produced a usable fit. The " + "bands are probably too narrow to survive jittering -- " + "widen them or lower band_scale.".format(i, nsim) + ) + + rPhi = rng.normal(Phi, sigma) + rPhi_n = rPhi - np.log(k) + + zt_band = _jitter(zt_range, band_scale * zt_width, rng) + z0_band = _jitter(z0_range, band_scale * z0_width, rng) + + try: + zt, _, _ = self._fit_band(k, rPhi, sigma, zt_band, absolute_sigma) + z0, _, _ = self._fit_band(k, rPhi_n, sigma, z0_band, absolute_sigma) + except (ValueError, RuntimeError): + # a jittered band can fall off the end of the spectrum + continue + + zt_samples[i] = zt + z0_samples[i] = z0 + i += 1 + + CPD, _ = self.calculate_CPD(zt_samples, z0_samples) + return [zt_samples, z0_samples, CPD] + + def calculate_CPD(self, zt, z0, sigma_zt=0.0, sigma_z0=0.0): + """ + Compute the Curie depth from the results of `optimise`. + + Args: + zt : float / 1D array + depth to the top of the magnetic source + z0 : float / 1D array + centroid depth of the magnetic source + sigma_zt : float / 1D array + standard deviation of `zt` + sigma_z0 : float / 1D array + standard deviation of `z0` + + Returns: + CPD : float / 1D array + estimated Curie point depth at the base of the magnetic source + CPD_stdev : float / 1D array + standard deviation of `CPD` + + Notes: + \\( Z_b = 2 Z_0 - Z_t \\), so the uncertainties combine as + \\( \\sqrt{\\sigma_{Z_t}^2 + 4\\sigma_{Z_0}^2} \\). This assumes + the two fits are independent, which holds well enough in practice + -- they use disjoint bands, and the measured correlation between + them is about 0.05. + + `zt` and `z0` are expected positive downwards, as returned by + `optimise`. + """ + CPD = 2.0 * z0 - zt + CPD_stdev = np.sqrt(np.asarray(sigma_zt) ** 2 + (2.0 * np.asarray(sigma_z0)) ** 2) + return (CPD, CPD_stdev) + + +def _jitter(band, scale, rng): + """Perturb both edges of a band, keeping it ordered and non-negative.""" + lo, hi = rng.normal(band[0], scale), rng.normal(band[1], scale) + lo, hi = min(lo, hi), max(lo, hi) + return (max(0.0, lo), hi) + + +def _wavelength_range(k): + """Wavelengths spanned by a set of wavenumbers, shortest first.""" + if k.size == 0: + return (np.nan, np.nan) + kmax = k.max() + kmin = k[k > 0].min() if np.any(k > 0) else np.nan + return (2.0 * np.pi / kmax, 2.0 * np.pi / kmin if kmin == kmin else np.inf) + + +def _warn_if_cycles_per_km(zt_range, k): + """ + Catch bands left over from when they were specified in cycles/km. + + Such a call still runs, and at a large enough window returns a plausible + looking number rather than raising, so it is worth flagging. A cycles/km + value is 2*pi too small, which drags a zt band -- which should sit at the + short-wavelength end -- down to the bottom of the spectrum. + """ + kmax = np.max(k) + upper = np.max(zt_range) + + # only complain if reading them as rad/km puts the band implausibly low + # *and* the cycles/km reading would land somewhere sensible + if upper < _CYCLES_PER_KM_SUSPICION * kmax and upper * 2.0 * np.pi <= kmax: + warnings.warn( + "zt_range={} may be in cycles/km. Bands are specified in rad/km, " + "and this one covers only the lowest {:.0%} of the spectrum, which " + "is unusual for a zt fit. Multiplying by 2*pi would give " + "{}.".format( + tuple(zt_range), + upper / kmax, + tuple(np.round(np.asarray(zt_range) * 2.0 * np.pi, 3)), + ), + UserWarning, + stacklevel=3, + ) diff --git a/pycurious/parallel.py b/pycurious/parallel.py new file mode 100644 index 0000000..4872743 --- /dev/null +++ b/pycurious/parallel.py @@ -0,0 +1,416 @@ +# Copyright 2018-2019 Ben Mather, Robert Delhaye +# +# This file is part of PyCurious. +# +# PyCurious is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or any later version. +# +# PyCurious is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PyCurious. If not, see . + +""" +Shared-memory parallelism for evaluating a routine over many centroids. + +`CurieParallel` is a mixin inherited by `pycurious.grid.CurieGrid`, and hence +by both optimisation classes. It distributes centroids across processors and +reassembles the results. + +## Running in parallel from a script + +On macOS and Windows the default start method is *spawn*, which re-imports the +calling module in every worker. A script that calls `parallelise_routine` at +module level must guard its entry point, or each worker will re-execute the +whole script: + +```python +if __name__ == "__main__": + grid.optimise_routine(window, xc_list, yc_list, ...) +``` + +Jupyter notebooks are unaffected. + +Spawn also requires every argument to be picklable. A `taper` or +`process_subgrid` function defined in a notebook cell or in `__main__` cannot +be pickled; `parallelise_routine` detects this before starting any workers and +falls back to serial evaluation with a warning rather than deadlocking. +""" + +import pickle +import queue as _queue +import warnings +from multiprocessing import Process, Queue, cpu_count, get_start_method + +import numpy as np + + +def stochastic(func): + """ + Mark a routine as one that `CurieParallel.parallelise_routine` should seed. + + Routines that draw random numbers take a `seed` keyword and carry this + marker; deterministic ones do neither, and are left alone. Keeping the + distinction here rather than in each routine's signature means a + deterministic routine needs no `seed` parameter to absorb and ignore, and + that adding a new one cannot silently produce an unseeded map. + + Signature inspection would not do instead: every routine here accepts + `**kwargs`, so it cannot distinguish one that wants a seed from one that + would forward it to the taper and fail there. + """ + func.wants_seed = True + return func + + +class CurieParallel(object): + def __init__(self, **kwargs): + + self.max_processors = kwargs.pop("max_processors", cpu_count()) + + def _func_queue(self, func, q_in, q_out, window, *args, **kwargs): + """ + Retrieve processes from the queue. + + Exceptions are captured and returned through the output queue rather + than propagating. A worker that died without posting a result would + leave the parent blocking on `q_out.get()` indefinitely. + """ + while True: + pos, xc, yc, seed = q_in.get() + if pos is None: + break + + pass_args = [window, xc, yc] + pass_args.extend(args) + + call_kwargs = kwargs + if seed is not None: + call_kwargs = dict(kwargs, seed=seed) + + try: + res = func(*pass_args, **call_kwargs) + except Exception as e: + res = _CentroidError(xc, yc, e) + + q_out.put((pos, res)) + return + + def parallelise_routine(self, window, xc_list, yc_list, func, *args, **kwargs): + """ + Implements shared memory multiprocessing to split multiple + evaluations of a function centroids across processors. + + Supply the window size and lists of x,y coordinates to a function + along with any additional arguments or keyword arguments. + + Args: + window : float + size of window in metres + xc_list : array shape (l,) + centroid x values + yc_list : array shape (l,) + centroid y values + func : function + Python function to evaluate in parallel + args : arguments + additional arguments to pass to `func` + kwargs : keyword arguments + additional keyword arguments to pass to `func`. + Two keys are reserved and consumed here rather than + forwarded: + + - `on_error` : {"raise", "ignore"} (default="raise") + what to do when `func` fails for a centroid. `"ignore"` + fills that centroid with NaN and warns. + - `seed` : int or None (default=None) + seeds any stochastic routine reproducibly. Each centroid + receives an independent child seed derived from + `numpy.random.SeedSequence`, passed to `func` as a `seed` + keyword, so results do not depend on how many processors + were used. `func` must accept a `seed` keyword if this is + supplied. + + Returns: + out : list of lists + (depends on output of `func` - see notes) + + Usage: + An obvious use case is to compute the Curie depth for many + centroids in parallel. + + >>> self.parallelise_routine(window, xc_list, yc_list, self.optimise) + + Each centroid is assigned a new process and sent to a free processor + to compute. In this case, the output is separate lists of shape(l,) + for \\( \\beta, z_t, \\Delta z, C \\). If `len(xc_list)=2` then, + + >>> self.parallelise_routine(window, [x1,x2], [y1, y2], self.optimise) + [[beta1 beta2], [zt1 zt2], [dz1 dz2], [C1 C2]] + + Another example is to parallelise the sensitivity analysis: + + >>> self.parallelise_routine(window, xc_list, yc_list, self.sensitivity, nsim) + + This time the output will be a list of lists for \\( \\beta, z_t, \\Delta z, C \\) + i.e. if `len(xc_list)=2` is the number of centroids and `nsim=4` is the number of + simulations then separatee lists will be returned for \\( \\beta, z_t, \\Delta z, C \\). + + >>> self.parallelise_routine(window, [x1,x2], [y1,y2], self.sensitivity, 4) + + which would return: + + ```python + [[[ beta1a , beta1b , beta1c , beta1d ], # centroid 1 (x1,y1) + [ beta2a , beta2b , beta2c , beta2d ]], # centroid 2 (x2,y2) + [[ zt1a , zt1b , zt1c , zt1d ], # centroid 1 (x1,y1) + [ zt2a , zt2b , zt2c , zt2d ]], # centroid 2 (x2,y2) + [[ dz1a , dz1b , dz1c , dz1d ], # centroid 1 (x1,y1) + [ dz2a , dz2b , dz2c , dz2d ]] # centroid 2 (x2,y2) + [[ C1a , C1b , C1c , C1d ], # centroid 1 (x1,y1) + [ C2a , C2b , C2c , C2d ]]] # centroid 2 (x2,y2) + ``` + + Notes: + See the module docstring for the `if __name__ == "__main__":` + requirement when calling this from a script. + """ + + on_error = kwargs.pop("on_error", "raise") + if on_error not in ("raise", "ignore"): + raise ValueError("on_error must be 'raise' or 'ignore'") + seed = kwargs.pop("seed", None) + + if seed is not None and not getattr(func, "wants_seed", False): + warnings.warn( + "{} is deterministic, so seed has no effect on it. Only the " + "stochastic routines -- sensitivity, metropolis_hastings -- " + "consume one.".format(getattr(func, "__name__", func)), + RuntimeWarning, + stacklevel=2, + ) + seed = None + + n = len(xc_list) + if n != len(yc_list): + raise ValueError("xc_list and yc_list must be the same size") + if n == 0: + raise ValueError("xc_list is empty, there are no centroids to evaluate") + + if seed is None: + child_seeds = [None] * n + else: + # independent streams per centroid, so the result does not depend + # on how the work happened to be distributed across processors + child_seeds = [ + int(s.generate_state(1)[0]) + for s in np.random.SeedSequence(seed).spawn(n) + ] + + xOpt = [None for i in range(n)] + + nprocs = self.max_processors + if nprocs < 1: + raise ValueError( + "{} processors is invalid, specify a positive integer value".format( + nprocs + ) + ) + + unpicklable = _find_unpicklable(func, args, kwargs) + if nprocs > 1 and unpicklable is not None: + warnings.warn( + "cannot send {} to worker processes under the '{}' start " + "method. Falling back to serial evaluation. Define it at module " + "level in an importable module to run in parallel.".format( + unpicklable, get_start_method() + ), + RuntimeWarning, + stacklevel=2, + ) + nprocs = 1 + + if nprocs == 1: + # skip all the OpenMP cruft + for i in range(n): + xc = xc_list[i] + yc = yc_list[i] + + call_kwargs = kwargs + if child_seeds[i] is not None: + call_kwargs = dict(kwargs, seed=child_seeds[i]) + + try: + xOpt[i] = func(window, xc, yc, *args, **call_kwargs) + except Exception as e: + xOpt[i] = _CentroidError(xc, yc, e) + + else: + # more than one processor + processes = [] + # unbounded, so feeding the queue can never block the parent if the + # workers die before draining it + q_in = Queue() + q_out = Queue() + + for i in range(nprocs): + pass_args = [func, q_in, q_out, window] + pass_args.extend(args) + + p = Process(target=self._func_queue, args=tuple(pass_args), kwargs=kwargs) + + processes.append(p) + + for p in processes: + p.daemon = True + p.start() + + # put items in the queue + for i in range(n): + q_in.put((i, xc_list[i], yc_list[i], child_seeds[i])) + [q_in.put((None, None, None, None)) for _ in range(nprocs)] + + # Collect results, watching for workers that died without posting + # one. A worker can fail before reaching our try/except -- an + # argument that unpickles only in the parent, or the process being + # killed -- and waiting unconditionally would deadlock. + received = 0 + while received < n: + try: + index, res = q_out.get(timeout=1.0) + except _queue.Empty: + if not any(p.is_alive() for p in processes): + for p in processes: + p.join() + raise RuntimeError( + "all {} worker processes exited after returning {} of " + "{} results. This usually means an argument could not " + "be reconstructed in the worker under the '{}' start " + "method -- a function defined in a notebook cell or in " + "__main__, for instance. Define it in an importable " + "module, or set max_processors=1 to run " + "serially.".format( + nprocs, received, n, get_start_method() + ) + ) + continue + xOpt[index] = res + received += 1 + + # wait until each processor has finished + [p.join() for p in processes] + + return _collect(xOpt, n, on_error) + + +class _CentroidError(object): + """A failure at one centroid, ferried back from a worker process.""" + + def __init__(self, xc, yc, exception): + self.xc = xc + self.yc = yc + self.exception = exception + + def __str__(self): + return "centroid ({}, {}): {}: {}".format( + self.xc, self.yc, type(self.exception).__name__, self.exception + ) + + +def _find_unpicklable(func, args, kwargs): + """ + Return a description of the first argument a worker could not reconstruct, + or None. + + Checked up front because such an argument kills every worker on startup, + and the parent would otherwise wait on results that never arrive. + + Two distinct failure modes are caught. Lambdas, closures and locally + defined functions fail `pickle.dumps` outright. Functions defined at the + top level of `__main__` -- a script, or a notebook cell -- are subtler: + they pickle by qualified name and so succeed *here*, but under the spawn + start method the child has no `__main__` to look them up in, and dies on + unpickling. + """ + spawning = get_start_method() != "fork" + + candidates = [("func", func)] + candidates += [("positional argument {}".format(i), a) for i, a in enumerate(args)] + candidates += [("keyword argument '{}'".format(k), v) for k, v in kwargs.items()] + + for label, obj in candidates: + if obj is None: + continue + + name = getattr(obj, "__qualname__", None) or repr(obj) + + try: + pickle.dumps(obj) + except Exception: + return "{} ({})".format(label, name) + + # a bound method carries its instance, whose class must also be + # importable in the child -- check the underlying function's module + owner = getattr(obj, "__self__", None) + module = getattr(obj, "__module__", None) + if owner is not None: + module = getattr(type(owner), "__module__", module) + + if spawning and callable(obj) and module in ("__main__", "__mp_main__"): + return "{} ({}, defined in {})".format(label, name, module) + + return None + + +def _collect(xOpt, n, on_error): + """ + Reassemble per-centroid results, handling any that failed. + + The output shape is taken from the first *successful* result rather than + whichever one happened to arrive last. + """ + failures = [r for r in xOpt if isinstance(r, _CentroidError)] + + if failures and on_error == "raise": + raise RuntimeError( + "{} of {} centroids failed. First failure -- {}. Pass " + "on_error='ignore' to fill failures with NaN instead.".format( + len(failures), n, failures[0] + ) + ) from failures[0].exception + + template = next((r for r in xOpt if not isinstance(r, _CentroidError)), None) + if template is None: + raise RuntimeError( + "all {} centroids failed. First failure -- {}".format(n, failures[0]) + ) from failures[0].exception + + if failures: + warnings.warn( + "{} of {} centroids failed and were filled with NaN. " + "First failure -- {}".format(len(failures), n, failures[0]), + RuntimeWarning, + stacklevel=3, + ) + nan_fill = np.full(np.shape(template), np.nan, dtype=float) + xOpt = [nan_fill if isinstance(r, _CentroidError) else r for r in xOpt] + + ndim = np.ndim(template) + + if ndim == 1: + # return separate lists of beta, zt, dz, C + xOpt = np.vstack(xOpt) + return list(xOpt.T) + elif ndim > 1: + # return lists of beta, zt, dz, C for each centroid + xOpt = np.hstack(xOpt) + out = list(xOpt) + for i in range(len(out)): + out[i] = np.split(out[i], n) + return out + else: + raise ValueError("Cannot determine shape of output") diff --git a/pycurious/synthetic.py b/pycurious/synthetic.py new file mode 100644 index 0000000..486f982 --- /dev/null +++ b/pycurious/synthetic.py @@ -0,0 +1,110 @@ +# Copyright 2018-2019 Ben Mather, Robert Delhaye +# +# This file is part of PyCurious. +# +# PyCurious is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or any later version. +# +# PyCurious is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PyCurious. If not, see . + +""" +Synthetic magnetic anomalies with a known Curie depth. + +Filtering white noise by the square root of `pycurious.grid.bouligand2009` +produces a field whose expected radial power spectrum *is* the analytic model, +for any choice of \\( \\beta, z_t \\) and \\( \\Delta z \\). That makes it +possible to ask whether a method recovers the parameters it was given, rather +than whether it reproduces one hard-coded number. + +Because the field is a single random realisation its measured spectrum scatters +about the model, so recovery is approximate. \\( \\beta \\) and \\( z_t \\) come +back tightly; \\( \\Delta z \\) does not, and one realisation in five puts it +tens of percent out. That is a property of the problem rather than of the +generator -- see `pycurious.optimise_bouligand.CurieOptimiseBouligand.profile`. + +Averaging over seeds tightens it. +""" + +import numpy as np + +from .grid import bouligand2009 + + +def fractal_anomaly(n=512, dx=2.0, beta=3.0, zt=1.0, dz=20.0, C=5.0, seed=0): + """ + Synthesise a magnetic anomaly with a prescribed radial power spectrum. + + Args: + n : int + number of points per side + dx : float + grid spacing in km + beta : float + fractal parameter of the magnetisation + zt : float + depth to the top of the magnetic source, km + dz : float + thickness of the magnetic source, km + C : float + field constant + seed : int + seed for the white noise realisation + + Returns: + data : 2D array shape (n,n) + the magnetic anomaly + extent : tuple + (xmin, xmax, ymin, ymax) in metres, for `pycurious.grid.CurieGrid` + + Usage: + >>> data, extent = pycurious.fractal_anomaly(beta=3.0, zt=1.0, dz=20.0) + >>> grid = pycurious.CurieOptimiseBouligand(data, *extent) + + Notes: + The Curie depth of the result is `zt + dz`, and its centroid depth + (as sought by the Tanaka method) is `zt + dz/2`. Those two, and + `beta`, are recoverable. + + `C` is not, quite. It is a level rather than a depth, and it absorbs + every constant factor between the noise and the spectrum. Normalising + the transform removes the dependence on `n`, but two offsets remain + and both depend on how the spectrum is measured rather than on how the + field was made: + + - the radial spectrum averages \\( \\ln |FFT| \\) rather than taking + the log of the mean, which is lower by the Euler-Mascheroni constant, + 0.577; + - a taper removes power, by \\( \\ln (3/8)^2 = -1.96 \\) for a + separable `numpy.hanning`. + + So a fit through `numpy.hanning` returns `C` about 2.5 low, and through + no taper about 0.6 low. Treat the recovered `C` as a nuisance + parameter, not as something with a true value to check against. + """ + rng = np.random.default_rng(seed) + + # radial wavenumber grid in rad/km, matching the DFT of an n-point series + kx = 2.0 * np.pi * np.fft.fftfreq(n, d=dx) + kh = np.hypot(*np.meshgrid(kx, kx, indexing="ij")) + + # bouligand2009 returns the log power spectrum, so the amplitude filter is + # exp(Phi/2). kh=0 is undefined (log kh) and only sets the mean, so drop it. + amplitude = np.zeros_like(kh) + nonzero = kh > 0.0 + amplitude[nonzero] = np.exp(0.5 * bouligand2009(kh[nonzero], beta, zt, dz, C)) + + # white real noise keeps the transform Hermitian, so the result is real. + # Dividing by n undoes the scaling of an unnormalised fft2, without which + # the recovered C would shift by 2*ln(n) with the size of the grid. + noise = np.fft.fft2(rng.normal(size=(n, n))) / n + data = np.real(np.fft.ifft2(noise * amplitude)) + + extent = (0.0, (n - 1) * dx * 1e3, 0.0, (n - 1) * dx * 1e3) + return data, extent diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cde7862 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,70 @@ +[build-system] +requires = ["setuptools>=77", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "pycurious" +version = "2.0" +description = "Python tool for computing the Curie depth from magnetic data" +readme = "README.md" +requires-python = ">=3.9" +license = "LGPL-3.0-or-later" +license-files = ["COPYING", "COPYING.LESSER"] +authors = [ + { name = "Ben Mather", email = "brmather1@gmail.com" }, +] +keywords = ["geophysics", "magnetics", "curie depth", "spectral analysis"] + +# Only numpy and scipy are imported at package import time. Everything else is +# imported lazily inside the function that needs it, so it belongs in an extra. +dependencies = [ + "numpy>=1.20", + "scipy>=1.5", +] + +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] + +[project.urls] +Homepage = "https://github.com/brmather/pycurious" +Repository = "https://github.com/brmather/pycurious" + +[project.optional-dependencies] +# pycurious.download.download_file and friends +download = ["requests"] +# pycurious.mapping: projections and netCDF I/O +mapping = ["pyproj", "netCDF4"] +# pycurious.mapping.export_geotiff / import_geotiff. Kept out of `mapping` +# because the GDAL bindings need a matching libgdal already on the system, +# so requiring them would make `pip install pycurious[mapping]` fail for +# most users. Conda installs this more easily than pip. +geotiff = ["gdal"] +# running the example notebooks +examples = ["matplotlib", "jupyter", "cartopy", "pyproj"] +test = ["pytest"] +# building the Sphinx documentation. myst-nb pulls in myst-parser, so a single +# parser handles both the Markdown pages and the tutorial notebooks. Building +# the tutorials also needs the `examples` (and `mapping`) extras so the +# notebooks execute. +docs = ["sphinx", "furo", "myst-nb", "sphinx-copybutton"] + +[tool.setuptools] +packages = ["pycurious"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = [ + # calibrating a reported uncertainty means fitting a few hundred + # independent realisations, which is minutes rather than seconds. + # Run the rest with -m "not slow". + "slow: takes minutes, not seconds", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 7295b60..0000000 --- a/setup.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[aliases] -test=pytest - -[bdist_wheel] - -# This flag says that the code is written to work on both Python 2 and Python -# 3. If at all possible, it is good practice to do this. If you cannot, you -# will need to generate wheels for each Python version that you support. - -universal=1 -[metadata] -description-file = README.md diff --git a/setup.py b/setup.py index 529f3dc..132c867 100644 --- a/setup.py +++ b/setup.py @@ -1,98 +1,14 @@ -## To install locally: python setup.py build && python setup.py install -## (If there are problems with installation of the documentation, it may be that -## the egg file is out of sync and will need to be manually deleted - see error message -## for details of the corrupted zip file. ) +## Project metadata now lives in pyproject.toml. ## -## To push a version through to pip. -## - Make sure it installs correctly locally as above -## - Update the version information in this file -## - python setup.py sdist upload -r pypitest # for the test version -## - python setup.py sdist upload -r pypi # for the real version +## This file is kept only as a compatibility shim for tooling that still shells +## out to setup.py. Do not add metadata here -- it will conflict with the +## [project] table in pyproject.toml. ## -## (see http://peterdowns.com/posts/first-time-with-pypi.html) +## To install locally: python -m pip install -e . +## To build a release: python -m build +## To upload to PyPI: python -m twine upload dist/* - -import numpy as np -from setuptools import setup, Extension -from Cython.Build import cythonize -from os import path -import io - -## in development set version to none and ... -PYPI_VERSION = "1.1.1" - -# Return the git revision as a string (from numpy) -def git_version(): - def _minimal_ext_cmd(cmd): - # construct minimal environment - env = {} - for k in ["SYSTEMROOT", "PATH"]: - v = os.environ.get(k) - if v is not None: - env[k] = v - # LANGUAGE is used on win32 - env["LANGUAGE"] = "C" - env["LANG"] = "C" - env["LC_ALL"] = "C" - out = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env).communicate()[0] - return out - - try: - out = _minimal_ext_cmd(["git", "rev-parse", "--short", "HEAD"]) - GIT_REVISION = out.strip().decode("ascii") - except OSError: - GIT_REVISION = "Unknown" - - return GIT_REVISION - - -if PYPI_VERSION is None: - PYPI_VERSION = git_version() - -this_directory = path.abspath(path.dirname(__file__)) -with open(path.join(this_directory, "README.md"), encoding='utf-8') as f: - long_description = f.read() - - -ext = Extension( - name="pycurious.radon", - sources=["src/radon.pyx", "src/cradon.c"], - include_dirs=[np.get_include(), "src"], - library_dirs=["pycurious"], - extra_compile_args=["-std=c99"], -) +from setuptools import setup if __name__ == "__main__": - setup( - name="pycurious", - long_description=long_description, - long_description_content_type="text/markdown", - author="Ben Mather", - author_email="brmather1@gmail.com", - url="https://github.com/brmather/pycurious", - version=PYPI_VERSION, - description="Python tool for computing the Curie depth from magnetic data", - ext_modules=cythonize([ext]), - install_requires=["numpy", "scipy>=0.15.0", "Cython>=0.25.2"], - python_requires=">=2.7, >=3.5", - setup_requires=["pytest-runner", "webdav"], - tests_require=["pytest", "webdav"], - packages=["pycurious"], - package_data={ - "pycurious": [ - "Examples/data/test_mag_data.txt", - "Examples/*.ipynb", - "Examples/Notebooks/Tanaka/*.ipynb", - "Examples/Notebooks/Bouligand/*.ipynb", - "Examples/Scripts/*.py", - ] - }, - classifiers=[ - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - ], - ) + setup() diff --git a/src/cradon.c b/src/cradon.c deleted file mode 100644 index d61c0f8..0000000 --- a/src/cradon.c +++ /dev/null @@ -1,246 +0,0 @@ - -#include "cradon.h" - -int radon2d(const double* Tx, - const double* Rx, - const size_t nTx, - const size_t nx, - const size_t ny, - PyObject* L) { - - const double small=1.e-10; - - size_t nCells = nx * ny; - size_t nLmax = nTx * nx * ny/2; - double percent_sp = (nLmax*1.0)/(nTx*nCells*1.0); - - double* data_p = (double*)malloc( nLmax*sizeof(double) ); - int64_t* indices_p = (int64_t*)malloc( nLmax*sizeof(int64_t) ); - int64_t* indptr_p = (int64_t*)malloc( (nTx+1)*sizeof(int64_t) ); - - double* grx = (double*)malloc( (nx+1)*sizeof(double) ); - for ( size_t n=0; nxr ) { /* on va de s à r, on veut x croissant */ - double dtmp = xs; - xs = xr; - xr = dtmp; - dtmp = ys; - ys = yr; - yr = dtmp; - //printf(" xs, ys, xr, yr = %f %f %f %f\n", xs, ys, xr, yr); - } - - /* points de depart */ - double x = xs; - double y = ys; - - if ( fabs(ys-yr)=nLmax){ - size_t oldnymax = nLmax; - percent_sp += 0.1; - nLmax = (size_t)ceil((double)nTx*(double)nCells*percent_sp); - - /* make sure nzmax increases at least by 1 */ - if (oldnymax == nLmax) nLmax++; - - data_p = (double*)realloc( data_p, nLmax*sizeof(double) ); - indices_p = (int64_t*)realloc( indices_p, nLmax*sizeof(int64_t) ); - } - - ix++; - x = grx[ix]; - } - } - else if ( fabs(xs-xr) yr ) { /* on va de s à r, on veut y croissant */ - double dtmp = ys; - ys = yr; - yr = dtmp; - } - y = ys; - - for ( ix=0; ix=nLmax){ - size_t oldnymax = nLmax; - percent_sp += 0.1; - nLmax = (size_t)ceil((double)nTx*(double)nCells*percent_sp); - - /* make sure nymax increases at least by 1 */ - if (oldnymax == nLmax) nLmax++; - - data_p = (double*)realloc( data_p, nLmax*sizeof(double) ); - indices_p = (int64_t*)realloc( indices_p, nLmax*sizeof(int64_t) ); - } - - iy++; - y = gry[iy]; - } - } - else { /* rai oblique */ - /* pente du rai */ - double m = (yr-ys)/(xr-xs); - double b = yr - m*xr; - int up = m>0; - - for ( ix=0; ix1.5) { - printf("up - dl = %f, xe = %f, x = %f, ye = %f, y = %f\n", dl, xe, x, ye, y); - printf(" xs = %f, ys = %f, xr = %f, yr = %f\n", xs, ys, xr, yr); - printf(" m = %f, b = %f, yi = %f\n", m, b, yi); - } - - indices_p[k] = iCell; - data_p[k] = dl; - k++; - - if (k>=nLmax){ - size_t oldnymax = nLmax; - percent_sp += 0.1; - nLmax = (size_t)ceil((double)nTx*(double)nCells*percent_sp); - - /* make sure nymax increases at least by 1 */ - if (oldnymax == nLmax) nLmax++; - - data_p = (double*)realloc( data_p, nLmax*sizeof(double) ); - indices_p = (int64_t*)realloc( indices_p, nLmax*sizeof(int64_t) ); - } - - x = xe; - y = ye; - if ( fabs(y-gry[iy+1]) yi && y > yr ) { - int64_t iCell = ix*ny + iy; - - double ye = gry[iy]>yi ? gry[iy] : yi; - ye = ye>yr ? ye : yr; - double xe = (ye-b)/m; - double dlx = xe - x; - double dly = ye - y; - double dl = sqrt( dlx*dlx + dly*dly ); - - if (dl>1.5) { - printf("down - dl = %f, %f %f %f %f\n", dl, xe, x, ye, y); - printf(" xs, ys, xr, yr = %f %f %f %f\n", xs, ys, xr, yr); - printf(" m = %f, b = %f, yi = %f\n", m, b, yi); - } - indices_p[k] = iCell; - data_p[k] = dl; - k++; - - if (k>=nLmax){ - size_t oldnymax = nLmax; - percent_sp += 0.1; - nLmax = (size_t)ceil((double)nTx*(double)nCells*percent_sp); - - /* make sure nymax increases at least by 1 */ - if (oldnymax == nLmax) nLmax++; - - data_p = (double*)realloc( data_p, nLmax*sizeof(double) ); - indices_p = (int64_t*)realloc( indices_p, nLmax*sizeof(int64_t) ); - } - - x = xe; - y = ye; - if ( fabs(y-gry[iy]) -#ifndef offsetof - #define offsetof(type, member) ( (size_t) & ((type*)0) -> member ) -#endif -#if !defined(WIN32) && !defined(MS_WINDOWS) - #ifndef __stdcall - #define __stdcall - #endif - #ifndef __cdecl - #define __cdecl - #endif - #ifndef __fastcall - #define __fastcall - #endif -#endif -#ifndef DL_IMPORT - #define DL_IMPORT(t) t -#endif -#ifndef DL_EXPORT - #define DL_EXPORT(t) t -#endif -#define __PYX_COMMA , -#ifndef HAVE_LONG_LONG - #if PY_VERSION_HEX >= 0x02070000 - #define HAVE_LONG_LONG - #endif -#endif -#ifndef PY_LONG_LONG - #define PY_LONG_LONG LONG_LONG -#endif -#ifndef Py_HUGE_VAL - #define Py_HUGE_VAL HUGE_VAL -#endif -#ifdef PYPY_VERSION - #define CYTHON_COMPILING_IN_PYPY 1 - #define CYTHON_COMPILING_IN_PYSTON 0 - #define CYTHON_COMPILING_IN_CPYTHON 0 - #undef CYTHON_USE_TYPE_SLOTS - #define CYTHON_USE_TYPE_SLOTS 0 - #undef CYTHON_USE_PYTYPE_LOOKUP - #define CYTHON_USE_PYTYPE_LOOKUP 0 - #if PY_VERSION_HEX < 0x03050000 - #undef CYTHON_USE_ASYNC_SLOTS - #define CYTHON_USE_ASYNC_SLOTS 0 - #elif !defined(CYTHON_USE_ASYNC_SLOTS) - #define CYTHON_USE_ASYNC_SLOTS 1 - #endif - #undef CYTHON_USE_PYLIST_INTERNALS - #define CYTHON_USE_PYLIST_INTERNALS 0 - #undef CYTHON_USE_UNICODE_INTERNALS - #define CYTHON_USE_UNICODE_INTERNALS 0 - #undef CYTHON_USE_UNICODE_WRITER - #define CYTHON_USE_UNICODE_WRITER 0 - #undef CYTHON_USE_PYLONG_INTERNALS - #define CYTHON_USE_PYLONG_INTERNALS 0 - #undef CYTHON_AVOID_BORROWED_REFS - #define CYTHON_AVOID_BORROWED_REFS 1 - #undef CYTHON_ASSUME_SAFE_MACROS - #define CYTHON_ASSUME_SAFE_MACROS 0 - #undef CYTHON_UNPACK_METHODS - #define CYTHON_UNPACK_METHODS 0 - #undef CYTHON_FAST_THREAD_STATE - #define CYTHON_FAST_THREAD_STATE 0 - #undef CYTHON_FAST_PYCALL - #define CYTHON_FAST_PYCALL 0 - #undef CYTHON_PEP489_MULTI_PHASE_INIT - #define CYTHON_PEP489_MULTI_PHASE_INIT 0 - #undef CYTHON_USE_TP_FINALIZE - #define CYTHON_USE_TP_FINALIZE 0 - #undef CYTHON_USE_DICT_VERSIONS - #define CYTHON_USE_DICT_VERSIONS 0 - #undef CYTHON_USE_EXC_INFO_STACK - #define CYTHON_USE_EXC_INFO_STACK 0 -#elif defined(PYSTON_VERSION) - #define CYTHON_COMPILING_IN_PYPY 0 - #define CYTHON_COMPILING_IN_PYSTON 1 - #define CYTHON_COMPILING_IN_CPYTHON 0 - #ifndef CYTHON_USE_TYPE_SLOTS - #define CYTHON_USE_TYPE_SLOTS 1 - #endif - #undef CYTHON_USE_PYTYPE_LOOKUP - #define CYTHON_USE_PYTYPE_LOOKUP 0 - #undef CYTHON_USE_ASYNC_SLOTS - #define CYTHON_USE_ASYNC_SLOTS 0 - #undef CYTHON_USE_PYLIST_INTERNALS - #define CYTHON_USE_PYLIST_INTERNALS 0 - #ifndef CYTHON_USE_UNICODE_INTERNALS - #define CYTHON_USE_UNICODE_INTERNALS 1 - #endif - #undef CYTHON_USE_UNICODE_WRITER - #define CYTHON_USE_UNICODE_WRITER 0 - #undef CYTHON_USE_PYLONG_INTERNALS - #define CYTHON_USE_PYLONG_INTERNALS 0 - #ifndef CYTHON_AVOID_BORROWED_REFS - #define CYTHON_AVOID_BORROWED_REFS 0 - #endif - #ifndef CYTHON_ASSUME_SAFE_MACROS - #define CYTHON_ASSUME_SAFE_MACROS 1 - #endif - #ifndef CYTHON_UNPACK_METHODS - #define CYTHON_UNPACK_METHODS 1 - #endif - #undef CYTHON_FAST_THREAD_STATE - #define CYTHON_FAST_THREAD_STATE 0 - #undef CYTHON_FAST_PYCALL - #define CYTHON_FAST_PYCALL 0 - #undef CYTHON_PEP489_MULTI_PHASE_INIT - #define CYTHON_PEP489_MULTI_PHASE_INIT 0 - #undef CYTHON_USE_TP_FINALIZE - #define CYTHON_USE_TP_FINALIZE 0 - #undef CYTHON_USE_DICT_VERSIONS - #define CYTHON_USE_DICT_VERSIONS 0 - #undef CYTHON_USE_EXC_INFO_STACK - #define CYTHON_USE_EXC_INFO_STACK 0 -#else - #define CYTHON_COMPILING_IN_PYPY 0 - #define CYTHON_COMPILING_IN_PYSTON 0 - #define CYTHON_COMPILING_IN_CPYTHON 1 - #ifndef CYTHON_USE_TYPE_SLOTS - #define CYTHON_USE_TYPE_SLOTS 1 - #endif - #if PY_VERSION_HEX < 0x02070000 - #undef CYTHON_USE_PYTYPE_LOOKUP - #define CYTHON_USE_PYTYPE_LOOKUP 0 - #elif !defined(CYTHON_USE_PYTYPE_LOOKUP) - #define CYTHON_USE_PYTYPE_LOOKUP 1 - #endif - #if PY_MAJOR_VERSION < 3 - #undef CYTHON_USE_ASYNC_SLOTS - #define CYTHON_USE_ASYNC_SLOTS 0 - #elif !defined(CYTHON_USE_ASYNC_SLOTS) - #define CYTHON_USE_ASYNC_SLOTS 1 - #endif - #if PY_VERSION_HEX < 0x02070000 - #undef CYTHON_USE_PYLONG_INTERNALS - #define CYTHON_USE_PYLONG_INTERNALS 0 - #elif !defined(CYTHON_USE_PYLONG_INTERNALS) - #define CYTHON_USE_PYLONG_INTERNALS 1 - #endif - #ifndef CYTHON_USE_PYLIST_INTERNALS - #define CYTHON_USE_PYLIST_INTERNALS 1 - #endif - #ifndef CYTHON_USE_UNICODE_INTERNALS - #define CYTHON_USE_UNICODE_INTERNALS 1 - #endif - #if PY_VERSION_HEX < 0x030300F0 - #undef CYTHON_USE_UNICODE_WRITER - #define CYTHON_USE_UNICODE_WRITER 0 - #elif !defined(CYTHON_USE_UNICODE_WRITER) - #define CYTHON_USE_UNICODE_WRITER 1 - #endif - #ifndef CYTHON_AVOID_BORROWED_REFS - #define CYTHON_AVOID_BORROWED_REFS 0 - #endif - #ifndef CYTHON_ASSUME_SAFE_MACROS - #define CYTHON_ASSUME_SAFE_MACROS 1 - #endif - #ifndef CYTHON_UNPACK_METHODS - #define CYTHON_UNPACK_METHODS 1 - #endif - #ifndef CYTHON_FAST_THREAD_STATE - #define CYTHON_FAST_THREAD_STATE 1 - #endif - #ifndef CYTHON_FAST_PYCALL - #define CYTHON_FAST_PYCALL 1 - #endif - #ifndef CYTHON_PEP489_MULTI_PHASE_INIT - #define CYTHON_PEP489_MULTI_PHASE_INIT (PY_VERSION_HEX >= 0x03050000) - #endif - #ifndef CYTHON_USE_TP_FINALIZE - #define CYTHON_USE_TP_FINALIZE (PY_VERSION_HEX >= 0x030400a1) - #endif - #ifndef CYTHON_USE_DICT_VERSIONS - #define CYTHON_USE_DICT_VERSIONS (PY_VERSION_HEX >= 0x030600B1) - #endif - #ifndef CYTHON_USE_EXC_INFO_STACK - #define CYTHON_USE_EXC_INFO_STACK (PY_VERSION_HEX >= 0x030700A3) - #endif -#endif -#if !defined(CYTHON_FAST_PYCCALL) -#define CYTHON_FAST_PYCCALL (CYTHON_FAST_PYCALL && PY_VERSION_HEX >= 0x030600B1) -#endif -#if CYTHON_USE_PYLONG_INTERNALS - #include "longintrepr.h" - #undef SHIFT - #undef BASE - #undef MASK - #ifdef SIZEOF_VOID_P - enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) }; - #endif -#endif -#ifndef __has_attribute - #define __has_attribute(x) 0 -#endif -#ifndef __has_cpp_attribute - #define __has_cpp_attribute(x) 0 -#endif -#ifndef CYTHON_RESTRICT - #if defined(__GNUC__) - #define CYTHON_RESTRICT __restrict__ - #elif defined(_MSC_VER) && _MSC_VER >= 1400 - #define CYTHON_RESTRICT __restrict - #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L - #define CYTHON_RESTRICT restrict - #else - #define CYTHON_RESTRICT - #endif -#endif -#ifndef CYTHON_UNUSED -# if defined(__GNUC__) -# if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) -# define CYTHON_UNUSED __attribute__ ((__unused__)) -# else -# define CYTHON_UNUSED -# endif -# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER)) -# define CYTHON_UNUSED __attribute__ ((__unused__)) -# else -# define CYTHON_UNUSED -# endif -#endif -#ifndef CYTHON_MAYBE_UNUSED_VAR -# if defined(__cplusplus) - template void CYTHON_MAYBE_UNUSED_VAR( const T& ) { } -# else -# define CYTHON_MAYBE_UNUSED_VAR(x) (void)(x) -# endif -#endif -#ifndef CYTHON_NCP_UNUSED -# if CYTHON_COMPILING_IN_CPYTHON -# define CYTHON_NCP_UNUSED -# else -# define CYTHON_NCP_UNUSED CYTHON_UNUSED -# endif -#endif -#define __Pyx_void_to_None(void_result) ((void)(void_result), Py_INCREF(Py_None), Py_None) -#ifdef _MSC_VER - #ifndef _MSC_STDINT_H_ - #if _MSC_VER < 1300 - typedef unsigned char uint8_t; - typedef unsigned int uint32_t; - #else - typedef unsigned __int8 uint8_t; - typedef unsigned __int32 uint32_t; - #endif - #endif -#else - #include -#endif -#ifndef CYTHON_FALLTHROUGH - #if defined(__cplusplus) && __cplusplus >= 201103L - #if __has_cpp_attribute(fallthrough) - #define CYTHON_FALLTHROUGH [[fallthrough]] - #elif __has_cpp_attribute(clang::fallthrough) - #define CYTHON_FALLTHROUGH [[clang::fallthrough]] - #elif __has_cpp_attribute(gnu::fallthrough) - #define CYTHON_FALLTHROUGH [[gnu::fallthrough]] - #endif - #endif - #ifndef CYTHON_FALLTHROUGH - #if __has_attribute(fallthrough) - #define CYTHON_FALLTHROUGH __attribute__((fallthrough)) - #else - #define CYTHON_FALLTHROUGH - #endif - #endif - #if defined(__clang__ ) && defined(__apple_build_version__) - #if __apple_build_version__ < 7000000 - #undef CYTHON_FALLTHROUGH - #define CYTHON_FALLTHROUGH - #endif - #endif -#endif - -#ifndef CYTHON_INLINE - #if defined(__clang__) - #define CYTHON_INLINE __inline__ __attribute__ ((__unused__)) - #elif defined(__GNUC__) - #define CYTHON_INLINE __inline__ - #elif defined(_MSC_VER) - #define CYTHON_INLINE __inline - #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L - #define CYTHON_INLINE inline - #else - #define CYTHON_INLINE - #endif -#endif - -#if CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x02070600 && !defined(Py_OptimizeFlag) - #define Py_OptimizeFlag 0 -#endif -#define __PYX_BUILD_PY_SSIZE_T "n" -#define CYTHON_FORMAT_SSIZE_T "z" -#if PY_MAJOR_VERSION < 3 - #define __Pyx_BUILTIN_MODULE_NAME "__builtin__" - #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\ - PyCode_New(a+k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos) - #define __Pyx_DefaultClassType PyClass_Type -#else - #define __Pyx_BUILTIN_MODULE_NAME "builtins" -#if PY_VERSION_HEX >= 0x030800A4 && PY_VERSION_HEX < 0x030800B2 - #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\ - PyCode_New(a, 0, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos) -#else - #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\ - PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos) -#endif - #define __Pyx_DefaultClassType PyType_Type -#endif -#ifndef Py_TPFLAGS_CHECKTYPES - #define Py_TPFLAGS_CHECKTYPES 0 -#endif -#ifndef Py_TPFLAGS_HAVE_INDEX - #define Py_TPFLAGS_HAVE_INDEX 0 -#endif -#ifndef Py_TPFLAGS_HAVE_NEWBUFFER - #define Py_TPFLAGS_HAVE_NEWBUFFER 0 -#endif -#ifndef Py_TPFLAGS_HAVE_FINALIZE - #define Py_TPFLAGS_HAVE_FINALIZE 0 -#endif -#ifndef METH_STACKLESS - #define METH_STACKLESS 0 -#endif -#if PY_VERSION_HEX <= 0x030700A3 || !defined(METH_FASTCALL) - #ifndef METH_FASTCALL - #define METH_FASTCALL 0x80 - #endif - typedef PyObject *(*__Pyx_PyCFunctionFast) (PyObject *self, PyObject *const *args, Py_ssize_t nargs); - typedef PyObject *(*__Pyx_PyCFunctionFastWithKeywords) (PyObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames); -#else - #define __Pyx_PyCFunctionFast _PyCFunctionFast - #define __Pyx_PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords -#endif -#if CYTHON_FAST_PYCCALL -#define __Pyx_PyFastCFunction_Check(func)\ - ((PyCFunction_Check(func) && (METH_FASTCALL == (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST | METH_KEYWORDS | METH_STACKLESS))))) -#else -#define __Pyx_PyFastCFunction_Check(func) 0 -#endif -#if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Malloc) - #define PyObject_Malloc(s) PyMem_Malloc(s) - #define PyObject_Free(p) PyMem_Free(p) - #define PyObject_Realloc(p) PyMem_Realloc(p) -#endif -#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX < 0x030400A1 - #define PyMem_RawMalloc(n) PyMem_Malloc(n) - #define PyMem_RawRealloc(p, n) PyMem_Realloc(p, n) - #define PyMem_RawFree(p) PyMem_Free(p) -#endif -#if CYTHON_COMPILING_IN_PYSTON - #define __Pyx_PyCode_HasFreeVars(co) PyCode_HasFreeVars(co) - #define __Pyx_PyFrame_SetLineNumber(frame, lineno) PyFrame_SetLineNumber(frame, lineno) -#else - #define __Pyx_PyCode_HasFreeVars(co) (PyCode_GetNumFree(co) > 0) - #define __Pyx_PyFrame_SetLineNumber(frame, lineno) (frame)->f_lineno = (lineno) -#endif -#if !CYTHON_FAST_THREAD_STATE || PY_VERSION_HEX < 0x02070000 - #define __Pyx_PyThreadState_Current PyThreadState_GET() -#elif PY_VERSION_HEX >= 0x03060000 - #define __Pyx_PyThreadState_Current _PyThreadState_UncheckedGet() -#elif PY_VERSION_HEX >= 0x03000000 - #define __Pyx_PyThreadState_Current PyThreadState_GET() -#else - #define __Pyx_PyThreadState_Current _PyThreadState_Current -#endif -#if PY_VERSION_HEX < 0x030700A2 && !defined(PyThread_tss_create) && !defined(Py_tss_NEEDS_INIT) -#include "pythread.h" -#define Py_tss_NEEDS_INIT 0 -typedef int Py_tss_t; -static CYTHON_INLINE int PyThread_tss_create(Py_tss_t *key) { - *key = PyThread_create_key(); - return 0; -} -static CYTHON_INLINE Py_tss_t * PyThread_tss_alloc(void) { - Py_tss_t *key = (Py_tss_t *)PyObject_Malloc(sizeof(Py_tss_t)); - *key = Py_tss_NEEDS_INIT; - return key; -} -static CYTHON_INLINE void PyThread_tss_free(Py_tss_t *key) { - PyObject_Free(key); -} -static CYTHON_INLINE int PyThread_tss_is_created(Py_tss_t *key) { - return *key != Py_tss_NEEDS_INIT; -} -static CYTHON_INLINE void PyThread_tss_delete(Py_tss_t *key) { - PyThread_delete_key(*key); - *key = Py_tss_NEEDS_INIT; -} -static CYTHON_INLINE int PyThread_tss_set(Py_tss_t *key, void *value) { - return PyThread_set_key_value(*key, value); -} -static CYTHON_INLINE void * PyThread_tss_get(Py_tss_t *key) { - return PyThread_get_key_value(*key); -} -#endif -#if CYTHON_COMPILING_IN_CPYTHON || defined(_PyDict_NewPresized) -#define __Pyx_PyDict_NewPresized(n) ((n <= 8) ? PyDict_New() : _PyDict_NewPresized(n)) -#else -#define __Pyx_PyDict_NewPresized(n) PyDict_New() -#endif -#if PY_MAJOR_VERSION >= 3 || CYTHON_FUTURE_DIVISION - #define __Pyx_PyNumber_Divide(x,y) PyNumber_TrueDivide(x,y) - #define __Pyx_PyNumber_InPlaceDivide(x,y) PyNumber_InPlaceTrueDivide(x,y) -#else - #define __Pyx_PyNumber_Divide(x,y) PyNumber_Divide(x,y) - #define __Pyx_PyNumber_InPlaceDivide(x,y) PyNumber_InPlaceDivide(x,y) -#endif -#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500A1 && CYTHON_USE_UNICODE_INTERNALS -#define __Pyx_PyDict_GetItemStr(dict, name) _PyDict_GetItem_KnownHash(dict, name, ((PyASCIIObject *) name)->hash) -#else -#define __Pyx_PyDict_GetItemStr(dict, name) PyDict_GetItem(dict, name) -#endif -#if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND) - #define CYTHON_PEP393_ENABLED 1 - #define __Pyx_PyUnicode_READY(op) (likely(PyUnicode_IS_READY(op)) ?\ - 0 : _PyUnicode_Ready((PyObject *)(op))) - #define __Pyx_PyUnicode_GET_LENGTH(u) PyUnicode_GET_LENGTH(u) - #define __Pyx_PyUnicode_READ_CHAR(u, i) PyUnicode_READ_CHAR(u, i) - #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u) PyUnicode_MAX_CHAR_VALUE(u) - #define __Pyx_PyUnicode_KIND(u) PyUnicode_KIND(u) - #define __Pyx_PyUnicode_DATA(u) PyUnicode_DATA(u) - #define __Pyx_PyUnicode_READ(k, d, i) PyUnicode_READ(k, d, i) - #define __Pyx_PyUnicode_WRITE(k, d, i, ch) PyUnicode_WRITE(k, d, i, ch) - #define __Pyx_PyUnicode_IS_TRUE(u) (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u))) -#else - #define CYTHON_PEP393_ENABLED 0 - #define PyUnicode_1BYTE_KIND 1 - #define PyUnicode_2BYTE_KIND 2 - #define PyUnicode_4BYTE_KIND 4 - #define __Pyx_PyUnicode_READY(op) (0) - #define __Pyx_PyUnicode_GET_LENGTH(u) PyUnicode_GET_SIZE(u) - #define __Pyx_PyUnicode_READ_CHAR(u, i) ((Py_UCS4)(PyUnicode_AS_UNICODE(u)[i])) - #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u) ((sizeof(Py_UNICODE) == 2) ? 65535 : 1114111) - #define __Pyx_PyUnicode_KIND(u) (sizeof(Py_UNICODE)) - #define __Pyx_PyUnicode_DATA(u) ((void*)PyUnicode_AS_UNICODE(u)) - #define __Pyx_PyUnicode_READ(k, d, i) ((void)(k), (Py_UCS4)(((Py_UNICODE*)d)[i])) - #define __Pyx_PyUnicode_WRITE(k, d, i, ch) (((void)(k)), ((Py_UNICODE*)d)[i] = ch) - #define __Pyx_PyUnicode_IS_TRUE(u) (0 != PyUnicode_GET_SIZE(u)) -#endif -#if CYTHON_COMPILING_IN_PYPY - #define __Pyx_PyUnicode_Concat(a, b) PyNumber_Add(a, b) - #define __Pyx_PyUnicode_ConcatSafe(a, b) PyNumber_Add(a, b) -#else - #define __Pyx_PyUnicode_Concat(a, b) PyUnicode_Concat(a, b) - #define __Pyx_PyUnicode_ConcatSafe(a, b) ((unlikely((a) == Py_None) || unlikely((b) == Py_None)) ?\ - PyNumber_Add(a, b) : __Pyx_PyUnicode_Concat(a, b)) -#endif -#if CYTHON_COMPILING_IN_PYPY && !defined(PyUnicode_Contains) - #define PyUnicode_Contains(u, s) PySequence_Contains(u, s) -#endif -#if CYTHON_COMPILING_IN_PYPY && !defined(PyByteArray_Check) - #define PyByteArray_Check(obj) PyObject_TypeCheck(obj, &PyByteArray_Type) -#endif -#if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Format) - #define PyObject_Format(obj, fmt) PyObject_CallMethod(obj, "__format__", "O", fmt) -#endif -#define __Pyx_PyString_FormatSafe(a, b) ((unlikely((a) == Py_None || (PyString_Check(b) && !PyString_CheckExact(b)))) ? PyNumber_Remainder(a, b) : __Pyx_PyString_Format(a, b)) -#define __Pyx_PyUnicode_FormatSafe(a, b) ((unlikely((a) == Py_None || (PyUnicode_Check(b) && !PyUnicode_CheckExact(b)))) ? PyNumber_Remainder(a, b) : PyUnicode_Format(a, b)) -#if PY_MAJOR_VERSION >= 3 - #define __Pyx_PyString_Format(a, b) PyUnicode_Format(a, b) -#else - #define __Pyx_PyString_Format(a, b) PyString_Format(a, b) -#endif -#if PY_MAJOR_VERSION < 3 && !defined(PyObject_ASCII) - #define PyObject_ASCII(o) PyObject_Repr(o) -#endif -#if PY_MAJOR_VERSION >= 3 - #define PyBaseString_Type PyUnicode_Type - #define PyStringObject PyUnicodeObject - #define PyString_Type PyUnicode_Type - #define PyString_Check PyUnicode_Check - #define PyString_CheckExact PyUnicode_CheckExact - #define PyObject_Unicode PyObject_Str -#endif -#if PY_MAJOR_VERSION >= 3 - #define __Pyx_PyBaseString_Check(obj) PyUnicode_Check(obj) - #define __Pyx_PyBaseString_CheckExact(obj) PyUnicode_CheckExact(obj) -#else - #define __Pyx_PyBaseString_Check(obj) (PyString_Check(obj) || PyUnicode_Check(obj)) - #define __Pyx_PyBaseString_CheckExact(obj) (PyString_CheckExact(obj) || PyUnicode_CheckExact(obj)) -#endif -#ifndef PySet_CheckExact - #define PySet_CheckExact(obj) (Py_TYPE(obj) == &PySet_Type) -#endif -#if CYTHON_ASSUME_SAFE_MACROS - #define __Pyx_PySequence_SIZE(seq) Py_SIZE(seq) -#else - #define __Pyx_PySequence_SIZE(seq) PySequence_Size(seq) -#endif -#if PY_MAJOR_VERSION >= 3 - #define PyIntObject PyLongObject - #define PyInt_Type PyLong_Type - #define PyInt_Check(op) PyLong_Check(op) - #define PyInt_CheckExact(op) PyLong_CheckExact(op) - #define PyInt_FromString PyLong_FromString - #define PyInt_FromUnicode PyLong_FromUnicode - #define PyInt_FromLong PyLong_FromLong - #define PyInt_FromSize_t PyLong_FromSize_t - #define PyInt_FromSsize_t PyLong_FromSsize_t - #define PyInt_AsLong PyLong_AsLong - #define PyInt_AS_LONG PyLong_AS_LONG - #define PyInt_AsSsize_t PyLong_AsSsize_t - #define PyInt_AsUnsignedLongMask PyLong_AsUnsignedLongMask - #define PyInt_AsUnsignedLongLongMask PyLong_AsUnsignedLongLongMask - #define PyNumber_Int PyNumber_Long -#endif -#if PY_MAJOR_VERSION >= 3 - #define PyBoolObject PyLongObject -#endif -#if PY_MAJOR_VERSION >= 3 && CYTHON_COMPILING_IN_PYPY - #ifndef PyUnicode_InternFromString - #define PyUnicode_InternFromString(s) PyUnicode_FromString(s) - #endif -#endif -#if PY_VERSION_HEX < 0x030200A4 - typedef long Py_hash_t; - #define __Pyx_PyInt_FromHash_t PyInt_FromLong - #define __Pyx_PyInt_AsHash_t PyInt_AsLong -#else - #define __Pyx_PyInt_FromHash_t PyInt_FromSsize_t - #define __Pyx_PyInt_AsHash_t PyInt_AsSsize_t -#endif -#if PY_MAJOR_VERSION >= 3 - #define __Pyx_PyMethod_New(func, self, klass) ((self) ? PyMethod_New(func, self) : (Py_INCREF(func), func)) -#else - #define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass) -#endif -#if CYTHON_USE_ASYNC_SLOTS - #if PY_VERSION_HEX >= 0x030500B1 - #define __Pyx_PyAsyncMethodsStruct PyAsyncMethods - #define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async) - #else - #define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved)) - #endif -#else - #define __Pyx_PyType_AsAsync(obj) NULL -#endif -#ifndef __Pyx_PyAsyncMethodsStruct - typedef struct { - unaryfunc am_await; - unaryfunc am_aiter; - unaryfunc am_anext; - } __Pyx_PyAsyncMethodsStruct; -#endif - -#if defined(WIN32) || defined(MS_WINDOWS) - #define _USE_MATH_DEFINES -#endif -#include -#ifdef NAN -#define __PYX_NAN() ((float) NAN) -#else -static CYTHON_INLINE float __PYX_NAN() { - float value; - memset(&value, 0xFF, sizeof(value)); - return value; -} -#endif -#if defined(__CYGWIN__) && defined(_LDBL_EQ_DBL) -#define __Pyx_truncl trunc -#else -#define __Pyx_truncl truncl -#endif - - -#define __PYX_ERR(f_index, lineno, Ln_error) \ -{ \ - __pyx_filename = __pyx_f[f_index]; __pyx_lineno = lineno; __pyx_clineno = __LINE__; goto Ln_error; \ -} - -#ifndef __PYX_EXTERN_C - #ifdef __cplusplus - #define __PYX_EXTERN_C extern "C" - #else - #define __PYX_EXTERN_C extern - #endif -#endif - -#define __PYX_HAVE__pycurious__radon -#define __PYX_HAVE_API__pycurious__radon -/* Early includes */ -#include -#include -#include "numpy/arrayobject.h" -#include "numpy/ufuncobject.h" -#include "cradon.h" -#ifdef _OPENMP -#include -#endif /* _OPENMP */ - -#if defined(PYREX_WITHOUT_ASSERTIONS) && !defined(CYTHON_WITHOUT_ASSERTIONS) -#define CYTHON_WITHOUT_ASSERTIONS -#endif - -typedef struct {PyObject **p; const char *s; const Py_ssize_t n; const char* encoding; - const char is_unicode; const char is_str; const char intern; } __Pyx_StringTabEntry; - -#define __PYX_DEFAULT_STRING_ENCODING_IS_ASCII 0 -#define __PYX_DEFAULT_STRING_ENCODING_IS_UTF8 0 -#define __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT (PY_MAJOR_VERSION >= 3 && __PYX_DEFAULT_STRING_ENCODING_IS_UTF8) -#define __PYX_DEFAULT_STRING_ENCODING "" -#define __Pyx_PyObject_FromString __Pyx_PyBytes_FromString -#define __Pyx_PyObject_FromStringAndSize __Pyx_PyBytes_FromStringAndSize -#define __Pyx_uchar_cast(c) ((unsigned char)c) -#define __Pyx_long_cast(x) ((long)x) -#define __Pyx_fits_Py_ssize_t(v, type, is_signed) (\ - (sizeof(type) < sizeof(Py_ssize_t)) ||\ - (sizeof(type) > sizeof(Py_ssize_t) &&\ - likely(v < (type)PY_SSIZE_T_MAX ||\ - v == (type)PY_SSIZE_T_MAX) &&\ - (!is_signed || likely(v > (type)PY_SSIZE_T_MIN ||\ - v == (type)PY_SSIZE_T_MIN))) ||\ - (sizeof(type) == sizeof(Py_ssize_t) &&\ - (is_signed || likely(v < (type)PY_SSIZE_T_MAX ||\ - v == (type)PY_SSIZE_T_MAX))) ) -static CYTHON_INLINE int __Pyx_is_valid_index(Py_ssize_t i, Py_ssize_t limit) { - return (size_t) i < (size_t) limit; -} -#if defined (__cplusplus) && __cplusplus >= 201103L - #include - #define __Pyx_sst_abs(value) std::abs(value) -#elif SIZEOF_INT >= SIZEOF_SIZE_T - #define __Pyx_sst_abs(value) abs(value) -#elif SIZEOF_LONG >= SIZEOF_SIZE_T - #define __Pyx_sst_abs(value) labs(value) -#elif defined (_MSC_VER) - #define __Pyx_sst_abs(value) ((Py_ssize_t)_abs64(value)) -#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L - #define __Pyx_sst_abs(value) llabs(value) -#elif defined (__GNUC__) - #define __Pyx_sst_abs(value) __builtin_llabs(value) -#else - #define __Pyx_sst_abs(value) ((value<0) ? -value : value) -#endif -static CYTHON_INLINE const char* __Pyx_PyObject_AsString(PyObject*); -static CYTHON_INLINE const char* __Pyx_PyObject_AsStringAndSize(PyObject*, Py_ssize_t* length); -#define __Pyx_PyByteArray_FromString(s) PyByteArray_FromStringAndSize((const char*)s, strlen((const char*)s)) -#define __Pyx_PyByteArray_FromStringAndSize(s, l) PyByteArray_FromStringAndSize((const char*)s, l) -#define __Pyx_PyBytes_FromString PyBytes_FromString -#define __Pyx_PyBytes_FromStringAndSize PyBytes_FromStringAndSize -static CYTHON_INLINE PyObject* __Pyx_PyUnicode_FromString(const char*); -#if PY_MAJOR_VERSION < 3 - #define __Pyx_PyStr_FromString __Pyx_PyBytes_FromString - #define __Pyx_PyStr_FromStringAndSize __Pyx_PyBytes_FromStringAndSize -#else - #define __Pyx_PyStr_FromString __Pyx_PyUnicode_FromString - #define __Pyx_PyStr_FromStringAndSize __Pyx_PyUnicode_FromStringAndSize -#endif -#define __Pyx_PyBytes_AsWritableString(s) ((char*) PyBytes_AS_STRING(s)) -#define __Pyx_PyBytes_AsWritableSString(s) ((signed char*) PyBytes_AS_STRING(s)) -#define __Pyx_PyBytes_AsWritableUString(s) ((unsigned char*) PyBytes_AS_STRING(s)) -#define __Pyx_PyBytes_AsString(s) ((const char*) PyBytes_AS_STRING(s)) -#define __Pyx_PyBytes_AsSString(s) ((const signed char*) PyBytes_AS_STRING(s)) -#define __Pyx_PyBytes_AsUString(s) ((const unsigned char*) PyBytes_AS_STRING(s)) -#define __Pyx_PyObject_AsWritableString(s) ((char*) __Pyx_PyObject_AsString(s)) -#define __Pyx_PyObject_AsWritableSString(s) ((signed char*) __Pyx_PyObject_AsString(s)) -#define __Pyx_PyObject_AsWritableUString(s) ((unsigned char*) __Pyx_PyObject_AsString(s)) -#define __Pyx_PyObject_AsSString(s) ((const signed char*) __Pyx_PyObject_AsString(s)) -#define __Pyx_PyObject_AsUString(s) ((const unsigned char*) __Pyx_PyObject_AsString(s)) -#define __Pyx_PyObject_FromCString(s) __Pyx_PyObject_FromString((const char*)s) -#define __Pyx_PyBytes_FromCString(s) __Pyx_PyBytes_FromString((const char*)s) -#define __Pyx_PyByteArray_FromCString(s) __Pyx_PyByteArray_FromString((const char*)s) -#define __Pyx_PyStr_FromCString(s) __Pyx_PyStr_FromString((const char*)s) -#define __Pyx_PyUnicode_FromCString(s) __Pyx_PyUnicode_FromString((const char*)s) -static CYTHON_INLINE size_t __Pyx_Py_UNICODE_strlen(const Py_UNICODE *u) { - const Py_UNICODE *u_end = u; - while (*u_end++) ; - return (size_t)(u_end - u - 1); -} -#define __Pyx_PyUnicode_FromUnicode(u) PyUnicode_FromUnicode(u, __Pyx_Py_UNICODE_strlen(u)) -#define __Pyx_PyUnicode_FromUnicodeAndLength PyUnicode_FromUnicode -#define __Pyx_PyUnicode_AsUnicode PyUnicode_AsUnicode -#define __Pyx_NewRef(obj) (Py_INCREF(obj), obj) -#define __Pyx_Owned_Py_None(b) __Pyx_NewRef(Py_None) -static CYTHON_INLINE PyObject * __Pyx_PyBool_FromLong(long b); -static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject*); -static CYTHON_INLINE int __Pyx_PyObject_IsTrueAndDecref(PyObject*); -static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x); -#define __Pyx_PySequence_Tuple(obj)\ - (likely(PyTuple_CheckExact(obj)) ? __Pyx_NewRef(obj) : PySequence_Tuple(obj)) -static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject*); -static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t); -#if CYTHON_ASSUME_SAFE_MACROS -#define __pyx_PyFloat_AsDouble(x) (PyFloat_CheckExact(x) ? PyFloat_AS_DOUBLE(x) : PyFloat_AsDouble(x)) -#else -#define __pyx_PyFloat_AsDouble(x) PyFloat_AsDouble(x) -#endif -#define __pyx_PyFloat_AsFloat(x) ((float) __pyx_PyFloat_AsDouble(x)) -#if PY_MAJOR_VERSION >= 3 -#define __Pyx_PyNumber_Int(x) (PyLong_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Long(x)) -#else -#define __Pyx_PyNumber_Int(x) (PyInt_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Int(x)) -#endif -#define __Pyx_PyNumber_Float(x) (PyFloat_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Float(x)) -#if PY_MAJOR_VERSION < 3 && __PYX_DEFAULT_STRING_ENCODING_IS_ASCII -static int __Pyx_sys_getdefaultencoding_not_ascii; -static int __Pyx_init_sys_getdefaultencoding_params(void) { - PyObject* sys; - PyObject* default_encoding = NULL; - PyObject* ascii_chars_u = NULL; - PyObject* ascii_chars_b = NULL; - const char* default_encoding_c; - sys = PyImport_ImportModule("sys"); - if (!sys) goto bad; - default_encoding = PyObject_CallMethod(sys, (char*) "getdefaultencoding", NULL); - Py_DECREF(sys); - if (!default_encoding) goto bad; - default_encoding_c = PyBytes_AsString(default_encoding); - if (!default_encoding_c) goto bad; - if (strcmp(default_encoding_c, "ascii") == 0) { - __Pyx_sys_getdefaultencoding_not_ascii = 0; - } else { - char ascii_chars[128]; - int c; - for (c = 0; c < 128; c++) { - ascii_chars[c] = c; - } - __Pyx_sys_getdefaultencoding_not_ascii = 1; - ascii_chars_u = PyUnicode_DecodeASCII(ascii_chars, 128, NULL); - if (!ascii_chars_u) goto bad; - ascii_chars_b = PyUnicode_AsEncodedString(ascii_chars_u, default_encoding_c, NULL); - if (!ascii_chars_b || !PyBytes_Check(ascii_chars_b) || memcmp(ascii_chars, PyBytes_AS_STRING(ascii_chars_b), 128) != 0) { - PyErr_Format( - PyExc_ValueError, - "This module compiled with c_string_encoding=ascii, but default encoding '%.200s' is not a superset of ascii.", - default_encoding_c); - goto bad; - } - Py_DECREF(ascii_chars_u); - Py_DECREF(ascii_chars_b); - } - Py_DECREF(default_encoding); - return 0; -bad: - Py_XDECREF(default_encoding); - Py_XDECREF(ascii_chars_u); - Py_XDECREF(ascii_chars_b); - return -1; -} -#endif -#if __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT && PY_MAJOR_VERSION >= 3 -#define __Pyx_PyUnicode_FromStringAndSize(c_str, size) PyUnicode_DecodeUTF8(c_str, size, NULL) -#else -#define __Pyx_PyUnicode_FromStringAndSize(c_str, size) PyUnicode_Decode(c_str, size, __PYX_DEFAULT_STRING_ENCODING, NULL) -#if __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT -static char* __PYX_DEFAULT_STRING_ENCODING; -static int __Pyx_init_sys_getdefaultencoding_params(void) { - PyObject* sys; - PyObject* default_encoding = NULL; - char* default_encoding_c; - sys = PyImport_ImportModule("sys"); - if (!sys) goto bad; - default_encoding = PyObject_CallMethod(sys, (char*) (const char*) "getdefaultencoding", NULL); - Py_DECREF(sys); - if (!default_encoding) goto bad; - default_encoding_c = PyBytes_AsString(default_encoding); - if (!default_encoding_c) goto bad; - __PYX_DEFAULT_STRING_ENCODING = (char*) malloc(strlen(default_encoding_c) + 1); - if (!__PYX_DEFAULT_STRING_ENCODING) goto bad; - strcpy(__PYX_DEFAULT_STRING_ENCODING, default_encoding_c); - Py_DECREF(default_encoding); - return 0; -bad: - Py_XDECREF(default_encoding); - return -1; -} -#endif -#endif - - -/* Test for GCC > 2.95 */ -#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95))) - #define likely(x) __builtin_expect(!!(x), 1) - #define unlikely(x) __builtin_expect(!!(x), 0) -#else /* !__GNUC__ or GCC < 2.95 */ - #define likely(x) (x) - #define unlikely(x) (x) -#endif /* __GNUC__ */ -static CYTHON_INLINE void __Pyx_pretend_to_initialize(void* ptr) { (void)ptr; } - -static PyObject *__pyx_m = NULL; -static PyObject *__pyx_d; -static PyObject *__pyx_b; -static PyObject *__pyx_cython_runtime = NULL; -static PyObject *__pyx_empty_tuple; -static PyObject *__pyx_empty_bytes; -static PyObject *__pyx_empty_unicode; -static int __pyx_lineno; -static int __pyx_clineno = 0; -static const char * __pyx_cfilenm= __FILE__; -static const char *__pyx_filename; - -/* Header.proto */ -#if !defined(CYTHON_CCOMPLEX) - #if defined(__cplusplus) - #define CYTHON_CCOMPLEX 1 - #elif defined(_Complex_I) - #define CYTHON_CCOMPLEX 1 - #else - #define CYTHON_CCOMPLEX 0 - #endif -#endif -#if CYTHON_CCOMPLEX - #ifdef __cplusplus - #include - #else - #include - #endif -#endif -#if CYTHON_CCOMPLEX && !defined(__cplusplus) && defined(__sun__) && defined(__GNUC__) - #undef _Complex_I - #define _Complex_I 1.0fj -#endif - - -static const char *__pyx_f[] = { - "src/radon.pyx", - "__init__.pxd", - "type.pxd", -}; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":776 - * # in Cython to enable them only on the right systems. - * - * ctypedef npy_int8 int8_t # <<<<<<<<<<<<<< - * ctypedef npy_int16 int16_t - * ctypedef npy_int32 int32_t - */ -typedef npy_int8 __pyx_t_5numpy_int8_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":777 - * - * ctypedef npy_int8 int8_t - * ctypedef npy_int16 int16_t # <<<<<<<<<<<<<< - * ctypedef npy_int32 int32_t - * ctypedef npy_int64 int64_t - */ -typedef npy_int16 __pyx_t_5numpy_int16_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":778 - * ctypedef npy_int8 int8_t - * ctypedef npy_int16 int16_t - * ctypedef npy_int32 int32_t # <<<<<<<<<<<<<< - * ctypedef npy_int64 int64_t - * #ctypedef npy_int96 int96_t - */ -typedef npy_int32 __pyx_t_5numpy_int32_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":779 - * ctypedef npy_int16 int16_t - * ctypedef npy_int32 int32_t - * ctypedef npy_int64 int64_t # <<<<<<<<<<<<<< - * #ctypedef npy_int96 int96_t - * #ctypedef npy_int128 int128_t - */ -typedef npy_int64 __pyx_t_5numpy_int64_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":783 - * #ctypedef npy_int128 int128_t - * - * ctypedef npy_uint8 uint8_t # <<<<<<<<<<<<<< - * ctypedef npy_uint16 uint16_t - * ctypedef npy_uint32 uint32_t - */ -typedef npy_uint8 __pyx_t_5numpy_uint8_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":784 - * - * ctypedef npy_uint8 uint8_t - * ctypedef npy_uint16 uint16_t # <<<<<<<<<<<<<< - * ctypedef npy_uint32 uint32_t - * ctypedef npy_uint64 uint64_t - */ -typedef npy_uint16 __pyx_t_5numpy_uint16_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":785 - * ctypedef npy_uint8 uint8_t - * ctypedef npy_uint16 uint16_t - * ctypedef npy_uint32 uint32_t # <<<<<<<<<<<<<< - * ctypedef npy_uint64 uint64_t - * #ctypedef npy_uint96 uint96_t - */ -typedef npy_uint32 __pyx_t_5numpy_uint32_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":786 - * ctypedef npy_uint16 uint16_t - * ctypedef npy_uint32 uint32_t - * ctypedef npy_uint64 uint64_t # <<<<<<<<<<<<<< - * #ctypedef npy_uint96 uint96_t - * #ctypedef npy_uint128 uint128_t - */ -typedef npy_uint64 __pyx_t_5numpy_uint64_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":790 - * #ctypedef npy_uint128 uint128_t - * - * ctypedef npy_float32 float32_t # <<<<<<<<<<<<<< - * ctypedef npy_float64 float64_t - * #ctypedef npy_float80 float80_t - */ -typedef npy_float32 __pyx_t_5numpy_float32_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":791 - * - * ctypedef npy_float32 float32_t - * ctypedef npy_float64 float64_t # <<<<<<<<<<<<<< - * #ctypedef npy_float80 float80_t - * #ctypedef npy_float128 float128_t - */ -typedef npy_float64 __pyx_t_5numpy_float64_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":800 - * # The int types are mapped a bit surprising -- - * # numpy.int corresponds to 'l' and numpy.long to 'q' - * ctypedef npy_long int_t # <<<<<<<<<<<<<< - * ctypedef npy_longlong long_t - * ctypedef npy_longlong longlong_t - */ -typedef npy_long __pyx_t_5numpy_int_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":801 - * # numpy.int corresponds to 'l' and numpy.long to 'q' - * ctypedef npy_long int_t - * ctypedef npy_longlong long_t # <<<<<<<<<<<<<< - * ctypedef npy_longlong longlong_t - * - */ -typedef npy_longlong __pyx_t_5numpy_long_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":802 - * ctypedef npy_long int_t - * ctypedef npy_longlong long_t - * ctypedef npy_longlong longlong_t # <<<<<<<<<<<<<< - * - * ctypedef npy_ulong uint_t - */ -typedef npy_longlong __pyx_t_5numpy_longlong_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":804 - * ctypedef npy_longlong longlong_t - * - * ctypedef npy_ulong uint_t # <<<<<<<<<<<<<< - * ctypedef npy_ulonglong ulong_t - * ctypedef npy_ulonglong ulonglong_t - */ -typedef npy_ulong __pyx_t_5numpy_uint_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":805 - * - * ctypedef npy_ulong uint_t - * ctypedef npy_ulonglong ulong_t # <<<<<<<<<<<<<< - * ctypedef npy_ulonglong ulonglong_t - * - */ -typedef npy_ulonglong __pyx_t_5numpy_ulong_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":806 - * ctypedef npy_ulong uint_t - * ctypedef npy_ulonglong ulong_t - * ctypedef npy_ulonglong ulonglong_t # <<<<<<<<<<<<<< - * - * ctypedef npy_intp intp_t - */ -typedef npy_ulonglong __pyx_t_5numpy_ulonglong_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":808 - * ctypedef npy_ulonglong ulonglong_t - * - * ctypedef npy_intp intp_t # <<<<<<<<<<<<<< - * ctypedef npy_uintp uintp_t - * - */ -typedef npy_intp __pyx_t_5numpy_intp_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":809 - * - * ctypedef npy_intp intp_t - * ctypedef npy_uintp uintp_t # <<<<<<<<<<<<<< - * - * ctypedef npy_double float_t - */ -typedef npy_uintp __pyx_t_5numpy_uintp_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":811 - * ctypedef npy_uintp uintp_t - * - * ctypedef npy_double float_t # <<<<<<<<<<<<<< - * ctypedef npy_double double_t - * ctypedef npy_longdouble longdouble_t - */ -typedef npy_double __pyx_t_5numpy_float_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":812 - * - * ctypedef npy_double float_t - * ctypedef npy_double double_t # <<<<<<<<<<<<<< - * ctypedef npy_longdouble longdouble_t - * - */ -typedef npy_double __pyx_t_5numpy_double_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":813 - * ctypedef npy_double float_t - * ctypedef npy_double double_t - * ctypedef npy_longdouble longdouble_t # <<<<<<<<<<<<<< - * - * ctypedef npy_cfloat cfloat_t - */ -typedef npy_longdouble __pyx_t_5numpy_longdouble_t; -/* Declarations.proto */ -#if CYTHON_CCOMPLEX - #ifdef __cplusplus - typedef ::std::complex< float > __pyx_t_float_complex; - #else - typedef float _Complex __pyx_t_float_complex; - #endif -#else - typedef struct { float real, imag; } __pyx_t_float_complex; -#endif -static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_from_parts(float, float); - -/* Declarations.proto */ -#if CYTHON_CCOMPLEX - #ifdef __cplusplus - typedef ::std::complex< double > __pyx_t_double_complex; - #else - typedef double _Complex __pyx_t_double_complex; - #endif -#else - typedef struct { double real, imag; } __pyx_t_double_complex; -#endif -static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(double, double); - - -/*--- Type declarations ---*/ - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":815 - * ctypedef npy_longdouble longdouble_t - * - * ctypedef npy_cfloat cfloat_t # <<<<<<<<<<<<<< - * ctypedef npy_cdouble cdouble_t - * ctypedef npy_clongdouble clongdouble_t - */ -typedef npy_cfloat __pyx_t_5numpy_cfloat_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":816 - * - * ctypedef npy_cfloat cfloat_t - * ctypedef npy_cdouble cdouble_t # <<<<<<<<<<<<<< - * ctypedef npy_clongdouble clongdouble_t - * - */ -typedef npy_cdouble __pyx_t_5numpy_cdouble_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":817 - * ctypedef npy_cfloat cfloat_t - * ctypedef npy_cdouble cdouble_t - * ctypedef npy_clongdouble clongdouble_t # <<<<<<<<<<<<<< - * - * ctypedef npy_cdouble complex_t - */ -typedef npy_clongdouble __pyx_t_5numpy_clongdouble_t; - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":819 - * ctypedef npy_clongdouble clongdouble_t - * - * ctypedef npy_cdouble complex_t # <<<<<<<<<<<<<< - * - * cdef inline object PyArray_MultiIterNew1(a): - */ -typedef npy_cdouble __pyx_t_5numpy_complex_t; - -/* --- Runtime support code (head) --- */ -/* Refnanny.proto */ -#ifndef CYTHON_REFNANNY - #define CYTHON_REFNANNY 0 -#endif -#if CYTHON_REFNANNY - typedef struct { - void (*INCREF)(void*, PyObject*, int); - void (*DECREF)(void*, PyObject*, int); - void (*GOTREF)(void*, PyObject*, int); - void (*GIVEREF)(void*, PyObject*, int); - void* (*SetupContext)(const char*, int, const char*); - void (*FinishContext)(void**); - } __Pyx_RefNannyAPIStruct; - static __Pyx_RefNannyAPIStruct *__Pyx_RefNanny = NULL; - static __Pyx_RefNannyAPIStruct *__Pyx_RefNannyImportAPI(const char *modname); - #define __Pyx_RefNannyDeclarations void *__pyx_refnanny = NULL; -#ifdef WITH_THREAD - #define __Pyx_RefNannySetupContext(name, acquire_gil)\ - if (acquire_gil) {\ - PyGILState_STATE __pyx_gilstate_save = PyGILState_Ensure();\ - __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__);\ - PyGILState_Release(__pyx_gilstate_save);\ - } else {\ - __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__);\ - } -#else - #define __Pyx_RefNannySetupContext(name, acquire_gil)\ - __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__) -#endif - #define __Pyx_RefNannyFinishContext()\ - __Pyx_RefNanny->FinishContext(&__pyx_refnanny) - #define __Pyx_INCREF(r) __Pyx_RefNanny->INCREF(__pyx_refnanny, (PyObject *)(r), __LINE__) - #define __Pyx_DECREF(r) __Pyx_RefNanny->DECREF(__pyx_refnanny, (PyObject *)(r), __LINE__) - #define __Pyx_GOTREF(r) __Pyx_RefNanny->GOTREF(__pyx_refnanny, (PyObject *)(r), __LINE__) - #define __Pyx_GIVEREF(r) __Pyx_RefNanny->GIVEREF(__pyx_refnanny, (PyObject *)(r), __LINE__) - #define __Pyx_XINCREF(r) do { if((r) != NULL) {__Pyx_INCREF(r); }} while(0) - #define __Pyx_XDECREF(r) do { if((r) != NULL) {__Pyx_DECREF(r); }} while(0) - #define __Pyx_XGOTREF(r) do { if((r) != NULL) {__Pyx_GOTREF(r); }} while(0) - #define __Pyx_XGIVEREF(r) do { if((r) != NULL) {__Pyx_GIVEREF(r);}} while(0) -#else - #define __Pyx_RefNannyDeclarations - #define __Pyx_RefNannySetupContext(name, acquire_gil) - #define __Pyx_RefNannyFinishContext() - #define __Pyx_INCREF(r) Py_INCREF(r) - #define __Pyx_DECREF(r) Py_DECREF(r) - #define __Pyx_GOTREF(r) - #define __Pyx_GIVEREF(r) - #define __Pyx_XINCREF(r) Py_XINCREF(r) - #define __Pyx_XDECREF(r) Py_XDECREF(r) - #define __Pyx_XGOTREF(r) - #define __Pyx_XGIVEREF(r) -#endif -#define __Pyx_XDECREF_SET(r, v) do {\ - PyObject *tmp = (PyObject *) r;\ - r = v; __Pyx_XDECREF(tmp);\ - } while (0) -#define __Pyx_DECREF_SET(r, v) do {\ - PyObject *tmp = (PyObject *) r;\ - r = v; __Pyx_DECREF(tmp);\ - } while (0) -#define __Pyx_CLEAR(r) do { PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);} while(0) -#define __Pyx_XCLEAR(r) do { if((r) != NULL) {PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);}} while(0) - -/* PyObjectGetAttrStr.proto */ -#if CYTHON_USE_TYPE_SLOTS -static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name); -#else -#define __Pyx_PyObject_GetAttrStr(o,n) PyObject_GetAttr(o,n) -#endif - -/* GetBuiltinName.proto */ -static PyObject *__Pyx_GetBuiltinName(PyObject *name); - -/* RaiseArgTupleInvalid.proto */ -static void __Pyx_RaiseArgtupleInvalid(const char* func_name, int exact, - Py_ssize_t num_min, Py_ssize_t num_max, Py_ssize_t num_found); - -/* RaiseDoubleKeywords.proto */ -static void __Pyx_RaiseDoubleKeywordsError(const char* func_name, PyObject* kw_name); - -/* ParseKeywords.proto */ -static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[],\ - PyObject *kwds2, PyObject *values[], Py_ssize_t num_pos_args,\ - const char* function_name); - -/* PyDictVersioning.proto */ -#if CYTHON_USE_DICT_VERSIONS && CYTHON_USE_TYPE_SLOTS -#define __PYX_DICT_VERSION_INIT ((PY_UINT64_T) -1) -#define __PYX_GET_DICT_VERSION(dict) (((PyDictObject*)(dict))->ma_version_tag) -#define __PYX_UPDATE_DICT_CACHE(dict, value, cache_var, version_var)\ - (version_var) = __PYX_GET_DICT_VERSION(dict);\ - (cache_var) = (value); -#define __PYX_PY_DICT_LOOKUP_IF_MODIFIED(VAR, DICT, LOOKUP) {\ - static PY_UINT64_T __pyx_dict_version = 0;\ - static PyObject *__pyx_dict_cached_value = NULL;\ - if (likely(__PYX_GET_DICT_VERSION(DICT) == __pyx_dict_version)) {\ - (VAR) = __pyx_dict_cached_value;\ - } else {\ - (VAR) = __pyx_dict_cached_value = (LOOKUP);\ - __pyx_dict_version = __PYX_GET_DICT_VERSION(DICT);\ - }\ -} -static CYTHON_INLINE PY_UINT64_T __Pyx_get_tp_dict_version(PyObject *obj); -static CYTHON_INLINE PY_UINT64_T __Pyx_get_object_dict_version(PyObject *obj); -static CYTHON_INLINE int __Pyx_object_dict_version_matches(PyObject* obj, PY_UINT64_T tp_dict_version, PY_UINT64_T obj_dict_version); -#else -#define __PYX_GET_DICT_VERSION(dict) (0) -#define __PYX_UPDATE_DICT_CACHE(dict, value, cache_var, version_var) -#define __PYX_PY_DICT_LOOKUP_IF_MODIFIED(VAR, DICT, LOOKUP) (VAR) = (LOOKUP); -#endif - -/* GetModuleGlobalName.proto */ -#if CYTHON_USE_DICT_VERSIONS -#define __Pyx_GetModuleGlobalName(var, name) {\ - static PY_UINT64_T __pyx_dict_version = 0;\ - static PyObject *__pyx_dict_cached_value = NULL;\ - (var) = (likely(__pyx_dict_version == __PYX_GET_DICT_VERSION(__pyx_d))) ?\ - (likely(__pyx_dict_cached_value) ? __Pyx_NewRef(__pyx_dict_cached_value) : __Pyx_GetBuiltinName(name)) :\ - __Pyx__GetModuleGlobalName(name, &__pyx_dict_version, &__pyx_dict_cached_value);\ -} -#define __Pyx_GetModuleGlobalNameUncached(var, name) {\ - PY_UINT64_T __pyx_dict_version;\ - PyObject *__pyx_dict_cached_value;\ - (var) = __Pyx__GetModuleGlobalName(name, &__pyx_dict_version, &__pyx_dict_cached_value);\ -} -static PyObject *__Pyx__GetModuleGlobalName(PyObject *name, PY_UINT64_T *dict_version, PyObject **dict_cached_value); -#else -#define __Pyx_GetModuleGlobalName(var, name) (var) = __Pyx__GetModuleGlobalName(name) -#define __Pyx_GetModuleGlobalNameUncached(var, name) (var) = __Pyx__GetModuleGlobalName(name) -static CYTHON_INLINE PyObject *__Pyx__GetModuleGlobalName(PyObject *name); -#endif - -/* PyCFunctionFastCall.proto */ -#if CYTHON_FAST_PYCCALL -static CYTHON_INLINE PyObject *__Pyx_PyCFunction_FastCall(PyObject *func, PyObject **args, Py_ssize_t nargs); -#else -#define __Pyx_PyCFunction_FastCall(func, args, nargs) (assert(0), NULL) -#endif - -/* PyFunctionFastCall.proto */ -#if CYTHON_FAST_PYCALL -#define __Pyx_PyFunction_FastCall(func, args, nargs)\ - __Pyx_PyFunction_FastCallDict((func), (args), (nargs), NULL) -#if 1 || PY_VERSION_HEX < 0x030600B1 -static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs, PyObject *kwargs); -#else -#define __Pyx_PyFunction_FastCallDict(func, args, nargs, kwargs) _PyFunction_FastCallDict(func, args, nargs, kwargs) -#endif -#define __Pyx_BUILD_ASSERT_EXPR(cond)\ - (sizeof(char [1 - 2*!(cond)]) - 1) -#ifndef Py_MEMBER_SIZE -#define Py_MEMBER_SIZE(type, member) sizeof(((type *)0)->member) -#endif - static size_t __pyx_pyframe_localsplus_offset = 0; - #include "frameobject.h" - #define __Pxy_PyFrame_Initialize_Offsets()\ - ((void)__Pyx_BUILD_ASSERT_EXPR(sizeof(PyFrameObject) == offsetof(PyFrameObject, f_localsplus) + Py_MEMBER_SIZE(PyFrameObject, f_localsplus)),\ - (void)(__pyx_pyframe_localsplus_offset = ((size_t)PyFrame_Type.tp_basicsize) - Py_MEMBER_SIZE(PyFrameObject, f_localsplus))) - #define __Pyx_PyFrame_GetLocalsplus(frame)\ - (assert(__pyx_pyframe_localsplus_offset), (PyObject **)(((char *)(frame)) + __pyx_pyframe_localsplus_offset)) -#endif - -/* PyObjectCall.proto */ -#if CYTHON_COMPILING_IN_CPYTHON -static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw); -#else -#define __Pyx_PyObject_Call(func, arg, kw) PyObject_Call(func, arg, kw) -#endif - -/* PyObjectCall2Args.proto */ -static CYTHON_UNUSED PyObject* __Pyx_PyObject_Call2Args(PyObject* function, PyObject* arg1, PyObject* arg2); - -/* PyObjectCallMethO.proto */ -#if CYTHON_COMPILING_IN_CPYTHON -static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg); -#endif - -/* PyObjectCallOneArg.proto */ -static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg); - -/* PyThreadStateGet.proto */ -#if CYTHON_FAST_THREAD_STATE -#define __Pyx_PyThreadState_declare PyThreadState *__pyx_tstate; -#define __Pyx_PyThreadState_assign __pyx_tstate = __Pyx_PyThreadState_Current; -#define __Pyx_PyErr_Occurred() __pyx_tstate->curexc_type -#else -#define __Pyx_PyThreadState_declare -#define __Pyx_PyThreadState_assign -#define __Pyx_PyErr_Occurred() PyErr_Occurred() -#endif - -/* PyErrFetchRestore.proto */ -#if CYTHON_FAST_THREAD_STATE -#define __Pyx_PyErr_Clear() __Pyx_ErrRestore(NULL, NULL, NULL) -#define __Pyx_ErrRestoreWithState(type, value, tb) __Pyx_ErrRestoreInState(PyThreadState_GET(), type, value, tb) -#define __Pyx_ErrFetchWithState(type, value, tb) __Pyx_ErrFetchInState(PyThreadState_GET(), type, value, tb) -#define __Pyx_ErrRestore(type, value, tb) __Pyx_ErrRestoreInState(__pyx_tstate, type, value, tb) -#define __Pyx_ErrFetch(type, value, tb) __Pyx_ErrFetchInState(__pyx_tstate, type, value, tb) -static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb); -static CYTHON_INLINE void __Pyx_ErrFetchInState(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb); -#if CYTHON_COMPILING_IN_CPYTHON -#define __Pyx_PyErr_SetNone(exc) (Py_INCREF(exc), __Pyx_ErrRestore((exc), NULL, NULL)) -#else -#define __Pyx_PyErr_SetNone(exc) PyErr_SetNone(exc) -#endif -#else -#define __Pyx_PyErr_Clear() PyErr_Clear() -#define __Pyx_PyErr_SetNone(exc) PyErr_SetNone(exc) -#define __Pyx_ErrRestoreWithState(type, value, tb) PyErr_Restore(type, value, tb) -#define __Pyx_ErrFetchWithState(type, value, tb) PyErr_Fetch(type, value, tb) -#define __Pyx_ErrRestoreInState(tstate, type, value, tb) PyErr_Restore(type, value, tb) -#define __Pyx_ErrFetchInState(tstate, type, value, tb) PyErr_Fetch(type, value, tb) -#define __Pyx_ErrRestore(type, value, tb) PyErr_Restore(type, value, tb) -#define __Pyx_ErrFetch(type, value, tb) PyErr_Fetch(type, value, tb) -#endif - -/* RaiseException.proto */ -static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, PyObject *cause); - -/* RaiseTooManyValuesToUnpack.proto */ -static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected); - -/* RaiseNeedMoreValuesToUnpack.proto */ -static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index); - -/* IterFinish.proto */ -static CYTHON_INLINE int __Pyx_IterFinish(void); - -/* UnpackItemEndCheck.proto */ -static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected); - -/* GetItemInt.proto */ -#define __Pyx_GetItemInt(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\ - (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\ - __Pyx_GetItemInt_Fast(o, (Py_ssize_t)i, is_list, wraparound, boundscheck) :\ - (is_list ? (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL) :\ - __Pyx_GetItemInt_Generic(o, to_py_func(i)))) -#define __Pyx_GetItemInt_List(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\ - (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\ - __Pyx_GetItemInt_List_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\ - (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL)) -static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i, - int wraparound, int boundscheck); -#define __Pyx_GetItemInt_Tuple(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\ - (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\ - __Pyx_GetItemInt_Tuple_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\ - (PyErr_SetString(PyExc_IndexError, "tuple index out of range"), (PyObject*)NULL)) -static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i, - int wraparound, int boundscheck); -static PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j); -static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i, - int is_list, int wraparound, int boundscheck); - -/* PyFloatBinop.proto */ -#if !CYTHON_COMPILING_IN_PYPY -static PyObject* __Pyx_PyFloat_EqObjC(PyObject *op1, PyObject *op2, double floatval, int inplace, int zerodivision_check); -#else -#define __Pyx_PyFloat_EqObjC(op1, op2, floatval, inplace, zerodivision_check)\ - (PyObject_RichCompare(op1, op2, Py_EQ)) - #endif - -/* ObjectGetItem.proto */ -#if CYTHON_USE_TYPE_SLOTS -static CYTHON_INLINE PyObject *__Pyx_PyObject_GetItem(PyObject *obj, PyObject* key); -#else -#define __Pyx_PyObject_GetItem(obj, key) PyObject_GetItem(obj, key) -#endif - -/* PyIntBinop.proto */ -#if !CYTHON_COMPILING_IN_PYPY -static PyObject* __Pyx_PyInt_SubtractObjC(PyObject *op1, PyObject *op2, long intval, int inplace, int zerodivision_check); -#else -#define __Pyx_PyInt_SubtractObjC(op1, op2, intval, inplace, zerodivision_check)\ - (inplace ? PyNumber_InPlaceSubtract(op1, op2) : PyNumber_Subtract(op1, op2)) -#endif - -/* PyFloatBinop.proto */ -#if !CYTHON_COMPILING_IN_PYPY -static PyObject* __Pyx_PyFloat_SubtractCObj(PyObject *op1, PyObject *op2, double floatval, int inplace, int zerodivision_check); -#else -#define __Pyx_PyFloat_SubtractCObj(op1, op2, floatval, inplace, zerodivision_check)\ - (inplace ? PyNumber_InPlaceSubtract(op1, op2) : PyNumber_Subtract(op1, op2)) -#endif - -/* PyFloatBinop.proto */ -#if !CYTHON_COMPILING_IN_PYPY -static PyObject* __Pyx_PyFloat_DivideObjC(PyObject *op1, PyObject *op2, double floatval, int inplace, int zerodivision_check); -#else -#define __Pyx_PyFloat_DivideObjC(op1, op2, floatval, inplace, zerodivision_check)\ - ((inplace ? __Pyx_PyNumber_InPlaceDivide(op1, op2) : __Pyx_PyNumber_Divide(op1, op2))) - #endif - -/* None.proto */ -static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname); - -/* ExtTypeTest.proto */ -static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type); - -/* PyObjectCallNoArg.proto */ -#if CYTHON_COMPILING_IN_CPYTHON -static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func); -#else -#define __Pyx_PyObject_CallNoArg(func) __Pyx_PyObject_Call(func, __pyx_empty_tuple, NULL) -#endif - -/* DictGetItem.proto */ -#if PY_MAJOR_VERSION >= 3 && !CYTHON_COMPILING_IN_PYPY -static PyObject *__Pyx_PyDict_GetItem(PyObject *d, PyObject* key); -#define __Pyx_PyObject_Dict_GetItem(obj, name)\ - (likely(PyDict_CheckExact(obj)) ?\ - __Pyx_PyDict_GetItem(obj, name) : PyObject_GetItem(obj, name)) -#else -#define __Pyx_PyDict_GetItem(d, key) PyObject_GetItem(d, key) -#define __Pyx_PyObject_Dict_GetItem(obj, name) PyObject_GetItem(obj, name) -#endif - -/* RaiseNoneIterError.proto */ -static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void); - -/* GetTopmostException.proto */ -#if CYTHON_USE_EXC_INFO_STACK -static _PyErr_StackItem * __Pyx_PyErr_GetTopmostException(PyThreadState *tstate); -#endif - -/* SaveResetException.proto */ -#if CYTHON_FAST_THREAD_STATE -#define __Pyx_ExceptionSave(type, value, tb) __Pyx__ExceptionSave(__pyx_tstate, type, value, tb) -static CYTHON_INLINE void __Pyx__ExceptionSave(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb); -#define __Pyx_ExceptionReset(type, value, tb) __Pyx__ExceptionReset(__pyx_tstate, type, value, tb) -static CYTHON_INLINE void __Pyx__ExceptionReset(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb); -#else -#define __Pyx_ExceptionSave(type, value, tb) PyErr_GetExcInfo(type, value, tb) -#define __Pyx_ExceptionReset(type, value, tb) PyErr_SetExcInfo(type, value, tb) -#endif - -/* PyErrExceptionMatches.proto */ -#if CYTHON_FAST_THREAD_STATE -#define __Pyx_PyErr_ExceptionMatches(err) __Pyx_PyErr_ExceptionMatchesInState(__pyx_tstate, err) -static CYTHON_INLINE int __Pyx_PyErr_ExceptionMatchesInState(PyThreadState* tstate, PyObject* err); -#else -#define __Pyx_PyErr_ExceptionMatches(err) PyErr_ExceptionMatches(err) -#endif - -/* GetException.proto */ -#if CYTHON_FAST_THREAD_STATE -#define __Pyx_GetException(type, value, tb) __Pyx__GetException(__pyx_tstate, type, value, tb) -static int __Pyx__GetException(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb); -#else -static int __Pyx_GetException(PyObject **type, PyObject **value, PyObject **tb); -#endif - -/* TypeImport.proto */ -#ifndef __PYX_HAVE_RT_ImportType_proto -#define __PYX_HAVE_RT_ImportType_proto -enum __Pyx_ImportType_CheckSize { - __Pyx_ImportType_CheckSize_Error = 0, - __Pyx_ImportType_CheckSize_Warn = 1, - __Pyx_ImportType_CheckSize_Ignore = 2 -}; -static PyTypeObject *__Pyx_ImportType(PyObject* module, const char *module_name, const char *class_name, size_t size, enum __Pyx_ImportType_CheckSize check_size); -#endif - -/* Import.proto */ -static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level); - -/* ImportFrom.proto */ -static PyObject* __Pyx_ImportFrom(PyObject* module, PyObject* name); - -/* CLineInTraceback.proto */ -#ifdef CYTHON_CLINE_IN_TRACEBACK -#define __Pyx_CLineForTraceback(tstate, c_line) (((CYTHON_CLINE_IN_TRACEBACK)) ? c_line : 0) -#else -static int __Pyx_CLineForTraceback(PyThreadState *tstate, int c_line); -#endif - -/* CodeObjectCache.proto */ -typedef struct { - PyCodeObject* code_object; - int code_line; -} __Pyx_CodeObjectCacheEntry; -struct __Pyx_CodeObjectCache { - int count; - int max_count; - __Pyx_CodeObjectCacheEntry* entries; -}; -static struct __Pyx_CodeObjectCache __pyx_code_cache = {0,0,NULL}; -static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line); -static PyCodeObject *__pyx_find_code_object(int code_line); -static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object); - -/* AddTraceback.proto */ -static void __Pyx_AddTraceback(const char *funcname, int c_line, - int py_line, const char *filename); - -/* CIntToPy.proto */ -static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value); - -/* Print.proto */ -static int __Pyx_Print(PyObject*, PyObject *, int); -#if CYTHON_COMPILING_IN_PYPY || PY_MAJOR_VERSION >= 3 -static PyObject* __pyx_print = 0; -static PyObject* __pyx_print_kwargs = 0; -#endif - -/* RealImag.proto */ -#if CYTHON_CCOMPLEX - #ifdef __cplusplus - #define __Pyx_CREAL(z) ((z).real()) - #define __Pyx_CIMAG(z) ((z).imag()) - #else - #define __Pyx_CREAL(z) (__real__(z)) - #define __Pyx_CIMAG(z) (__imag__(z)) - #endif -#else - #define __Pyx_CREAL(z) ((z).real) - #define __Pyx_CIMAG(z) ((z).imag) -#endif -#if defined(__cplusplus) && CYTHON_CCOMPLEX\ - && (defined(_WIN32) || defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 5 || __GNUC__ == 4 && __GNUC_MINOR__ >= 4 )) || __cplusplus >= 201103) - #define __Pyx_SET_CREAL(z,x) ((z).real(x)) - #define __Pyx_SET_CIMAG(z,y) ((z).imag(y)) -#else - #define __Pyx_SET_CREAL(z,x) __Pyx_CREAL(z) = (x) - #define __Pyx_SET_CIMAG(z,y) __Pyx_CIMAG(z) = (y) -#endif - -/* Arithmetic.proto */ -#if CYTHON_CCOMPLEX - #define __Pyx_c_eq_float(a, b) ((a)==(b)) - #define __Pyx_c_sum_float(a, b) ((a)+(b)) - #define __Pyx_c_diff_float(a, b) ((a)-(b)) - #define __Pyx_c_prod_float(a, b) ((a)*(b)) - #define __Pyx_c_quot_float(a, b) ((a)/(b)) - #define __Pyx_c_neg_float(a) (-(a)) - #ifdef __cplusplus - #define __Pyx_c_is_zero_float(z) ((z)==(float)0) - #define __Pyx_c_conj_float(z) (::std::conj(z)) - #if 1 - #define __Pyx_c_abs_float(z) (::std::abs(z)) - #define __Pyx_c_pow_float(a, b) (::std::pow(a, b)) - #endif - #else - #define __Pyx_c_is_zero_float(z) ((z)==0) - #define __Pyx_c_conj_float(z) (conjf(z)) - #if 1 - #define __Pyx_c_abs_float(z) (cabsf(z)) - #define __Pyx_c_pow_float(a, b) (cpowf(a, b)) - #endif - #endif -#else - static CYTHON_INLINE int __Pyx_c_eq_float(__pyx_t_float_complex, __pyx_t_float_complex); - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_sum_float(__pyx_t_float_complex, __pyx_t_float_complex); - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_diff_float(__pyx_t_float_complex, __pyx_t_float_complex); - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_prod_float(__pyx_t_float_complex, __pyx_t_float_complex); - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_quot_float(__pyx_t_float_complex, __pyx_t_float_complex); - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_neg_float(__pyx_t_float_complex); - static CYTHON_INLINE int __Pyx_c_is_zero_float(__pyx_t_float_complex); - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_conj_float(__pyx_t_float_complex); - #if 1 - static CYTHON_INLINE float __Pyx_c_abs_float(__pyx_t_float_complex); - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_pow_float(__pyx_t_float_complex, __pyx_t_float_complex); - #endif -#endif - -/* Arithmetic.proto */ -#if CYTHON_CCOMPLEX - #define __Pyx_c_eq_double(a, b) ((a)==(b)) - #define __Pyx_c_sum_double(a, b) ((a)+(b)) - #define __Pyx_c_diff_double(a, b) ((a)-(b)) - #define __Pyx_c_prod_double(a, b) ((a)*(b)) - #define __Pyx_c_quot_double(a, b) ((a)/(b)) - #define __Pyx_c_neg_double(a) (-(a)) - #ifdef __cplusplus - #define __Pyx_c_is_zero_double(z) ((z)==(double)0) - #define __Pyx_c_conj_double(z) (::std::conj(z)) - #if 1 - #define __Pyx_c_abs_double(z) (::std::abs(z)) - #define __Pyx_c_pow_double(a, b) (::std::pow(a, b)) - #endif - #else - #define __Pyx_c_is_zero_double(z) ((z)==0) - #define __Pyx_c_conj_double(z) (conj(z)) - #if 1 - #define __Pyx_c_abs_double(z) (cabs(z)) - #define __Pyx_c_pow_double(a, b) (cpow(a, b)) - #endif - #endif -#else - static CYTHON_INLINE int __Pyx_c_eq_double(__pyx_t_double_complex, __pyx_t_double_complex); - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_sum_double(__pyx_t_double_complex, __pyx_t_double_complex); - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_diff_double(__pyx_t_double_complex, __pyx_t_double_complex); - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_prod_double(__pyx_t_double_complex, __pyx_t_double_complex); - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_quot_double(__pyx_t_double_complex, __pyx_t_double_complex); - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_neg_double(__pyx_t_double_complex); - static CYTHON_INLINE int __Pyx_c_is_zero_double(__pyx_t_double_complex); - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_conj_double(__pyx_t_double_complex); - #if 1 - static CYTHON_INLINE double __Pyx_c_abs_double(__pyx_t_double_complex); - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_pow_double(__pyx_t_double_complex, __pyx_t_double_complex); - #endif -#endif - -/* CIntToPy.proto */ -static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value); - -/* CIntToPy.proto */ -static CYTHON_INLINE PyObject* __Pyx_PyInt_From_enum__NPY_TYPES(enum NPY_TYPES value); - -/* CIntFromPy.proto */ -static CYTHON_INLINE size_t __Pyx_PyInt_As_size_t(PyObject *); - -/* PrintOne.proto */ -static int __Pyx_PrintOne(PyObject* stream, PyObject *o); - -/* CIntFromPy.proto */ -static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *); - -/* CIntFromPy.proto */ -static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *); - -/* FastTypeChecks.proto */ -#if CYTHON_COMPILING_IN_CPYTHON -#define __Pyx_TypeCheck(obj, type) __Pyx_IsSubtype(Py_TYPE(obj), (PyTypeObject *)type) -static CYTHON_INLINE int __Pyx_IsSubtype(PyTypeObject *a, PyTypeObject *b); -static CYTHON_INLINE int __Pyx_PyErr_GivenExceptionMatches(PyObject *err, PyObject *type); -static CYTHON_INLINE int __Pyx_PyErr_GivenExceptionMatches2(PyObject *err, PyObject *type1, PyObject *type2); -#else -#define __Pyx_TypeCheck(obj, type) PyObject_TypeCheck(obj, (PyTypeObject *)type) -#define __Pyx_PyErr_GivenExceptionMatches(err, type) PyErr_GivenExceptionMatches(err, type) -#define __Pyx_PyErr_GivenExceptionMatches2(err, type1, type2) (PyErr_GivenExceptionMatches(err, type1) || PyErr_GivenExceptionMatches(err, type2)) -#endif -#define __Pyx_PyException_Check(obj) __Pyx_TypeCheck(obj, PyExc_Exception) - -/* CheckBinaryVersion.proto */ -static int __Pyx_check_binary_version(void); - -/* InitStrings.proto */ -static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); - - -/* Module declarations from 'cpython.buffer' */ - -/* Module declarations from 'libc.string' */ - -/* Module declarations from 'libc.stdio' */ - -/* Module declarations from '__builtin__' */ - -/* Module declarations from 'cpython.type' */ -static PyTypeObject *__pyx_ptype_7cpython_4type_type = 0; - -/* Module declarations from 'cpython' */ - -/* Module declarations from 'cpython.object' */ - -/* Module declarations from 'cpython.ref' */ - -/* Module declarations from 'cpython.mem' */ - -/* Module declarations from 'numpy' */ - -/* Module declarations from 'numpy' */ -static PyTypeObject *__pyx_ptype_5numpy_dtype = 0; -static PyTypeObject *__pyx_ptype_5numpy_flatiter = 0; -static PyTypeObject *__pyx_ptype_5numpy_broadcast = 0; -static PyTypeObject *__pyx_ptype_5numpy_ndarray = 0; -static PyTypeObject *__pyx_ptype_5numpy_ufunc = 0; -static CYTHON_INLINE char *__pyx_f_5numpy__util_dtypestring(PyArray_Descr *, char *, char *, int *); /*proto*/ - -/* Module declarations from 'cradon' */ - -/* Module declarations from 'pycurious.radon' */ -#define __Pyx_MODULE_NAME "pycurious.radon" -extern int __pyx_module_is_main_pycurious__radon; -int __pyx_module_is_main_pycurious__radon = 0; - -/* Implementation of 'pycurious.radon' */ -static PyObject *__pyx_builtin_xrange; -static PyObject *__pyx_builtin_ValueError; -static PyObject *__pyx_builtin_RuntimeError; -static PyObject *__pyx_builtin_range; -static PyObject *__pyx_builtin_ImportError; -static const char __pyx_k_L[] = "L"; -static const char __pyx_k_M[] = "M"; -static const char __pyx_k_N[] = "N"; -static const char __pyx_k_n[] = "n"; -static const char __pyx_k_p[] = "p"; -static const char __pyx_k_Rx[] = "Rx "; -static const char __pyx_k_Tx[] = "Tx "; -static const char __pyx_k_np[] = "np"; -static const char __pyx_k_nt[] = "nt"; -static const char __pyx_k_nw[] = "nw"; -static const char __pyx_k_nx[] = "nx"; -static const char __pyx_k_ny[] = "ny"; -static const char __pyx_k_pi[] = "pi"; -static const char __pyx_k_x0[] = "x0"; -static const char __pyx_k_x1[] = "x1"; -static const char __pyx_k_xk[] = "xk"; -static const char __pyx_k_xs[] = "xs"; -static const char __pyx_k_y0[] = "y0"; -static const char __pyx_k_y1[] = "y1"; -static const char __pyx_k_yk[] = "yk"; -static const char __pyx_k_ys[] = "ys"; -static const char __pyx_k_abs[] = "abs"; -static const char __pyx_k_cos[] = "cos"; -static const char __pyx_k_dot[] = "dot"; -static const char __pyx_k_end[] = "end"; -static const char __pyx_k_max[] = "max"; -static const char __pyx_k_min[] = "min"; -static const char __pyx_k_nTx[] = "nTx"; -static const char __pyx_k_sin[] = "sin"; -static const char __pyx_k_tan[] = "tan"; -static const char __pyx_k_Rx_2[] = "Rx"; -static const char __pyx_k_Tx_2[] = "Tx"; -static const char __pyx_k_data[] = "data"; -static const char __pyx_k_file[] = "file"; -static const char __pyx_k_main[] = "__main__"; -static const char __pyx_k_name[] = "__name__"; -static const char __pyx_k_size[] = "size"; -static const char __pyx_k_test[] = "__test__"; -static const char __pyx_k_Ldata[] = "Ldata"; -static const char __pyx_k_numpy[] = "numpy"; -static const char __pyx_k_print[] = "print"; -static const char __pyx_k_range[] = "range"; -static const char __pyx_k_shape[] = "shape"; -static const char __pyx_k_theta[] = "theta"; -static const char __pyx_k_zeros[] = "zeros"; -static const char __pyx_k_import[] = "__import__"; -static const char __pyx_k_xrange[] = "xrange"; -static const char __pyx_k_flatten[] = "flatten"; -static const char __pyx_k_radon2d[] = "radon2d"; -static const char __pyx_k_sinogram[] = "sinogram"; -static const char __pyx_k_ValueError[] = "ValueError"; -static const char __pyx_k_csr_matrix[] = "csr_matrix"; -static const char __pyx_k_ImportError[] = "ImportError"; -static const char __pyx_k_RuntimeError[] = "RuntimeError"; -static const char __pyx_k_scipy_sparse[] = "scipy.sparse"; -static const char __pyx_k_src_radon_pyx[] = "src/radon.pyx"; -static const char __pyx_k_pycurious_radon[] = "pycurious.radon"; -static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback"; -static const char __pyx_k_ndarray_is_not_C_contiguous[] = "ndarray is not C contiguous"; -static const char __pyx_k_theta_should_be_within_0_pi[] = "theta should be within [0 pi)"; -static const char __pyx_k_data_should_be_a_square_array[] = "data should be a square array"; -static const char __pyx_k_numpy_core_multiarray_failed_to[] = "numpy.core.multiarray failed to import"; -static const char __pyx_k_unknown_dtype_code_in_numpy_pxd[] = "unknown dtype code in numpy.pxd (%d)"; -static const char __pyx_k_Format_string_allocated_too_shor[] = "Format string allocated too short, see comment in numpy.pxd"; -static const char __pyx_k_Non_native_byte_order_not_suppor[] = "Non-native byte order not supported"; -static const char __pyx_k_ndarray_is_not_Fortran_contiguou[] = "ndarray is not Fortran contiguous"; -static const char __pyx_k_numpy_core_umath_failed_to_impor[] = "numpy.core.umath failed to import"; -static const char __pyx_k_Format_string_allocated_too_shor_2[] = "Format string allocated too short."; -static PyObject *__pyx_kp_u_Format_string_allocated_too_shor; -static PyObject *__pyx_kp_u_Format_string_allocated_too_shor_2; -static PyObject *__pyx_n_s_ImportError; -static PyObject *__pyx_n_s_L; -static PyObject *__pyx_n_s_Ldata; -static PyObject *__pyx_n_s_M; -static PyObject *__pyx_n_s_N; -static PyObject *__pyx_kp_u_Non_native_byte_order_not_suppor; -static PyObject *__pyx_n_s_RuntimeError; -static PyObject *__pyx_kp_s_Rx; -static PyObject *__pyx_n_s_Rx_2; -static PyObject *__pyx_kp_s_Tx; -static PyObject *__pyx_n_s_Tx_2; -static PyObject *__pyx_n_s_ValueError; -static PyObject *__pyx_n_s_abs; -static PyObject *__pyx_n_s_cline_in_traceback; -static PyObject *__pyx_n_s_cos; -static PyObject *__pyx_n_s_csr_matrix; -static PyObject *__pyx_n_s_data; -static PyObject *__pyx_kp_s_data_should_be_a_square_array; -static PyObject *__pyx_n_s_dot; -static PyObject *__pyx_n_s_end; -static PyObject *__pyx_n_s_file; -static PyObject *__pyx_n_s_flatten; -static PyObject *__pyx_n_s_import; -static PyObject *__pyx_n_s_main; -static PyObject *__pyx_n_s_max; -static PyObject *__pyx_n_s_min; -static PyObject *__pyx_n_s_n; -static PyObject *__pyx_n_s_nTx; -static PyObject *__pyx_n_s_name; -static PyObject *__pyx_kp_u_ndarray_is_not_C_contiguous; -static PyObject *__pyx_kp_u_ndarray_is_not_Fortran_contiguou; -static PyObject *__pyx_n_s_np; -static PyObject *__pyx_n_s_nt; -static PyObject *__pyx_n_s_numpy; -static PyObject *__pyx_kp_s_numpy_core_multiarray_failed_to; -static PyObject *__pyx_kp_s_numpy_core_umath_failed_to_impor; -static PyObject *__pyx_n_s_nw; -static PyObject *__pyx_n_s_nx; -static PyObject *__pyx_n_s_ny; -static PyObject *__pyx_n_s_p; -static PyObject *__pyx_n_s_pi; -static PyObject *__pyx_n_s_print; -static PyObject *__pyx_n_s_pycurious_radon; -static PyObject *__pyx_n_s_radon2d; -static PyObject *__pyx_n_s_range; -static PyObject *__pyx_n_s_scipy_sparse; -static PyObject *__pyx_n_s_shape; -static PyObject *__pyx_n_s_sin; -static PyObject *__pyx_n_s_sinogram; -static PyObject *__pyx_n_s_size; -static PyObject *__pyx_kp_s_src_radon_pyx; -static PyObject *__pyx_n_s_tan; -static PyObject *__pyx_n_s_test; -static PyObject *__pyx_n_s_theta; -static PyObject *__pyx_kp_s_theta_should_be_within_0_pi; -static PyObject *__pyx_kp_u_unknown_dtype_code_in_numpy_pxd; -static PyObject *__pyx_n_s_x0; -static PyObject *__pyx_n_s_x1; -static PyObject *__pyx_n_s_xk; -static PyObject *__pyx_n_s_xrange; -static PyObject *__pyx_n_s_xs; -static PyObject *__pyx_n_s_y0; -static PyObject *__pyx_n_s_y1; -static PyObject *__pyx_n_s_yk; -static PyObject *__pyx_n_s_ys; -static PyObject *__pyx_n_s_zeros; -static PyObject *__pyx_pf_9pycurious_5radon_radon2d(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_data, PyObject *__pyx_v_theta); /* proto */ -static int __pyx_pf_5numpy_7ndarray___getbuffer__(PyArrayObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags); /* proto */ -static void __pyx_pf_5numpy_7ndarray_2__releasebuffer__(PyArrayObject *__pyx_v_self, Py_buffer *__pyx_v_info); /* proto */ -static PyObject *__pyx_float_1_; -static PyObject *__pyx_float_0_0; -static PyObject *__pyx_float_2_0; -static PyObject *__pyx_float_1_eneg_10; -static PyObject *__pyx_int_0; -static PyObject *__pyx_int_1; -static PyObject *__pyx_int_2; -static PyObject *__pyx_tuple_; -static PyObject *__pyx_slice__3; -static PyObject *__pyx_tuple__2; -static PyObject *__pyx_tuple__4; -static PyObject *__pyx_tuple__5; -static PyObject *__pyx_tuple__6; -static PyObject *__pyx_tuple__7; -static PyObject *__pyx_tuple__8; -static PyObject *__pyx_tuple__9; -static PyObject *__pyx_tuple__10; -static PyObject *__pyx_tuple__11; -static PyObject *__pyx_codeobj__12; -/* Late includes */ - -/* "src/radon.pyx":12 - * except: pass - * - * def radon2d(data, theta): # <<<<<<<<<<<<<< - * - * if np.min(theta) < 0.0 or np.max(theta) >= np.pi: - */ - -/* Python wrapper */ -static PyObject *__pyx_pw_9pycurious_5radon_1radon2d(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_9pycurious_5radon_1radon2d = {"radon2d", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_9pycurious_5radon_1radon2d, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_9pycurious_5radon_1radon2d(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { - PyObject *__pyx_v_data = 0; - PyObject *__pyx_v_theta = 0; - PyObject *__pyx_r = 0; - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("radon2d (wrapper)", 0); - { - static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_data,&__pyx_n_s_theta,0}; - PyObject* values[2] = {0,0}; - if (unlikely(__pyx_kwds)) { - Py_ssize_t kw_args; - const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args); - switch (pos_args) { - case 2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1); - CYTHON_FALLTHROUGH; - case 1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0); - CYTHON_FALLTHROUGH; - case 0: break; - default: goto __pyx_L5_argtuple_error; - } - kw_args = PyDict_Size(__pyx_kwds); - switch (pos_args) { - case 0: - if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_data)) != 0)) kw_args--; - else goto __pyx_L5_argtuple_error; - CYTHON_FALLTHROUGH; - case 1: - if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_theta)) != 0)) kw_args--; - else { - __Pyx_RaiseArgtupleInvalid("radon2d", 1, 2, 2, 1); __PYX_ERR(0, 12, __pyx_L3_error) - } - } - if (unlikely(kw_args > 0)) { - if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "radon2d") < 0)) __PYX_ERR(0, 12, __pyx_L3_error) - } - } else if (PyTuple_GET_SIZE(__pyx_args) != 2) { - goto __pyx_L5_argtuple_error; - } else { - values[0] = PyTuple_GET_ITEM(__pyx_args, 0); - values[1] = PyTuple_GET_ITEM(__pyx_args, 1); - } - __pyx_v_data = values[0]; - __pyx_v_theta = values[1]; - } - goto __pyx_L4_argument_unpacking_done; - __pyx_L5_argtuple_error:; - __Pyx_RaiseArgtupleInvalid("radon2d", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 12, __pyx_L3_error) - __pyx_L3_error:; - __Pyx_AddTraceback("pycurious.radon.radon2d", __pyx_clineno, __pyx_lineno, __pyx_filename); - __Pyx_RefNannyFinishContext(); - return NULL; - __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_9pycurious_5radon_radon2d(__pyx_self, __pyx_v_data, __pyx_v_theta); - - /* function exit code */ - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -static PyObject *__pyx_pf_9pycurious_5radon_radon2d(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_data, PyObject *__pyx_v_theta) { - PyObject *__pyx_v_nx = NULL; - PyObject *__pyx_v_ny = NULL; - PyObject *__pyx_v_nw = NULL; - PyObject *__pyx_v_sinogram = NULL; - PyObject *__pyx_v_Tx = NULL; - PyObject *__pyx_v_Rx = NULL; - size_t __pyx_v_nTx; - PyObject *__pyx_v_nt = NULL; - PyObject *__pyx_v_n = NULL; - PyObject *__pyx_v_xs = NULL; - PyObject *__pyx_v_ys = NULL; - PyObject *__pyx_v_xk = NULL; - PyObject *__pyx_v_yk = NULL; - PyObject *__pyx_v_x0 = NULL; - PyObject *__pyx_v_y0 = NULL; - PyObject *__pyx_v_x1 = NULL; - PyObject *__pyx_v_y1 = NULL; - PyObject *__pyx_v_Ldata = NULL; - size_t __pyx_v_M; - PyObject *__pyx_v_N = NULL; - PyObject *__pyx_v_L = NULL; - PyObject *__pyx_v_p = NULL; - PyObject *__pyx_r = NULL; - __Pyx_RefNannyDeclarations - int __pyx_t_1; - PyObject *__pyx_t_2 = NULL; - PyObject *__pyx_t_3 = NULL; - PyObject *__pyx_t_4 = NULL; - int __pyx_t_5; - PyObject *__pyx_t_6 = NULL; - PyObject *(*__pyx_t_7)(PyObject *); - size_t __pyx_t_8; - Py_ssize_t __pyx_t_9; - PyObject *(*__pyx_t_10)(PyObject *); - Py_ssize_t __pyx_t_11; - PyObject *(*__pyx_t_12)(PyObject *); - PyObject *__pyx_t_13 = NULL; - PyObject *__pyx_t_14 = NULL; - PyObject *__pyx_t_15 = NULL; - PyObject *__pyx_t_16 = NULL; - size_t __pyx_t_17; - __Pyx_RefNannySetupContext("radon2d", 0); - - /* "src/radon.pyx":14 - * def radon2d(data, theta): - * - * if np.min(theta) < 0.0 or np.max(theta) >= np.pi: # <<<<<<<<<<<<<< - * raise ValueError('theta should be within [0 pi)') - * - */ - __Pyx_GetModuleGlobalName(__pyx_t_3, __pyx_n_s_np); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_3, __pyx_n_s_min); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_3 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_4))) { - __pyx_t_3 = PyMethod_GET_SELF(__pyx_t_4); - if (likely(__pyx_t_3)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_4); - __Pyx_INCREF(__pyx_t_3); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_4, function); - } - } - __pyx_t_2 = (__pyx_t_3) ? __Pyx_PyObject_Call2Args(__pyx_t_4, __pyx_t_3, __pyx_v_theta) : __Pyx_PyObject_CallOneArg(__pyx_t_4, __pyx_v_theta); - __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0; - if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = PyObject_RichCompare(__pyx_t_2, __pyx_float_0_0, Py_LT); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_5 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_5 < 0)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (!__pyx_t_5) { - } else { - __pyx_t_1 = __pyx_t_5; - goto __pyx_L4_bool_binop_done; - } - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_max); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_3))) { - __pyx_t_2 = PyMethod_GET_SELF(__pyx_t_3); - if (likely(__pyx_t_2)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3); - __Pyx_INCREF(__pyx_t_2); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_3, function); - } - } - __pyx_t_4 = (__pyx_t_2) ? __Pyx_PyObject_Call2Args(__pyx_t_3, __pyx_t_2, __pyx_v_theta) : __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_theta); - __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; - if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __Pyx_GetModuleGlobalName(__pyx_t_3, __pyx_n_s_np); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_3, __pyx_n_s_pi); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_3 = PyObject_RichCompare(__pyx_t_4, __pyx_t_2, Py_GE); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_5 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_5 < 0)) __PYX_ERR(0, 14, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_1 = __pyx_t_5; - __pyx_L4_bool_binop_done:; - if (unlikely(__pyx_t_1)) { - - /* "src/radon.pyx":15 - * - * if np.min(theta) < 0.0 or np.max(theta) >= np.pi: - * raise ValueError('theta should be within [0 pi)') # <<<<<<<<<<<<<< - * - * nx, ny = data.shape - */ - __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple_, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_Raise(__pyx_t_3, 0, 0, 0); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __PYX_ERR(0, 15, __pyx_L1_error) - - /* "src/radon.pyx":14 - * def radon2d(data, theta): - * - * if np.min(theta) < 0.0 or np.max(theta) >= np.pi: # <<<<<<<<<<<<<< - * raise ValueError('theta should be within [0 pi)') - * - */ - } - - /* "src/radon.pyx":17 - * raise ValueError('theta should be within [0 pi)') - * - * nx, ny = data.shape # <<<<<<<<<<<<<< - * if nx != ny: - * raise RuntimeError('data should be a square array') - */ - __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_data, __pyx_n_s_shape); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 17, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - if ((likely(PyTuple_CheckExact(__pyx_t_3))) || (PyList_CheckExact(__pyx_t_3))) { - PyObject* sequence = __pyx_t_3; - Py_ssize_t size = __Pyx_PySequence_SIZE(sequence); - if (unlikely(size != 2)) { - if (size > 2) __Pyx_RaiseTooManyValuesError(2); - else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size); - __PYX_ERR(0, 17, __pyx_L1_error) - } - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - if (likely(PyTuple_CheckExact(sequence))) { - __pyx_t_2 = PyTuple_GET_ITEM(sequence, 0); - __pyx_t_4 = PyTuple_GET_ITEM(sequence, 1); - } else { - __pyx_t_2 = PyList_GET_ITEM(sequence, 0); - __pyx_t_4 = PyList_GET_ITEM(sequence, 1); - } - __Pyx_INCREF(__pyx_t_2); - __Pyx_INCREF(__pyx_t_4); - #else - __pyx_t_2 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 17, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_4 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 17, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - #endif - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - } else { - Py_ssize_t index = -1; - __pyx_t_6 = PyObject_GetIter(__pyx_t_3); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 17, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_7 = Py_TYPE(__pyx_t_6)->tp_iternext; - index = 0; __pyx_t_2 = __pyx_t_7(__pyx_t_6); if (unlikely(!__pyx_t_2)) goto __pyx_L6_unpacking_failed; - __Pyx_GOTREF(__pyx_t_2); - index = 1; __pyx_t_4 = __pyx_t_7(__pyx_t_6); if (unlikely(!__pyx_t_4)) goto __pyx_L6_unpacking_failed; - __Pyx_GOTREF(__pyx_t_4); - if (__Pyx_IternextUnpackEndCheck(__pyx_t_7(__pyx_t_6), 2) < 0) __PYX_ERR(0, 17, __pyx_L1_error) - __pyx_t_7 = NULL; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - goto __pyx_L7_unpacking_done; - __pyx_L6_unpacking_failed:; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_7 = NULL; - if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index); - __PYX_ERR(0, 17, __pyx_L1_error) - __pyx_L7_unpacking_done:; - } - __pyx_v_nx = __pyx_t_2; - __pyx_t_2 = 0; - __pyx_v_ny = __pyx_t_4; - __pyx_t_4 = 0; - - /* "src/radon.pyx":18 - * - * nx, ny = data.shape - * if nx != ny: # <<<<<<<<<<<<<< - * raise RuntimeError('data should be a square array') - * nw = nx - */ - __pyx_t_3 = PyObject_RichCompare(__pyx_v_nx, __pyx_v_ny, Py_NE); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 18, __pyx_L1_error) - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 18, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (unlikely(__pyx_t_1)) { - - /* "src/radon.pyx":19 - * nx, ny = data.shape - * if nx != ny: - * raise RuntimeError('data should be a square array') # <<<<<<<<<<<<<< - * nw = nx - * - */ - __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_RuntimeError, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 19, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_Raise(__pyx_t_3, 0, 0, 0); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __PYX_ERR(0, 19, __pyx_L1_error) - - /* "src/radon.pyx":18 - * - * nx, ny = data.shape - * if nx != ny: # <<<<<<<<<<<<<< - * raise RuntimeError('data should be a square array') - * nw = nx - */ - } - - /* "src/radon.pyx":20 - * if nx != ny: - * raise RuntimeError('data should be a square array') - * nw = nx # <<<<<<<<<<<<<< - * - * sinogram = np.zeros((nw,theta.size)) - */ - __Pyx_INCREF(__pyx_v_nx); - __pyx_v_nw = __pyx_v_nx; - - /* "src/radon.pyx":22 - * nw = nx - * - * sinogram = np.zeros((nw,theta.size)) # <<<<<<<<<<<<<< - * - * Tx = np.zeros((nw,2)) - */ - __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 22, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_zeros); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 22, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_v_theta, __pyx_n_s_size); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 22, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_6 = PyTuple_New(2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 22, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_INCREF(__pyx_v_nw); - __Pyx_GIVEREF(__pyx_v_nw); - PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_v_nw); - __Pyx_GIVEREF(__pyx_t_4); - PyTuple_SET_ITEM(__pyx_t_6, 1, __pyx_t_4); - __pyx_t_4 = 0; - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_3 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_4, __pyx_t_6) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_t_6); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 22, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_v_sinogram = __pyx_t_3; - __pyx_t_3 = 0; - - /* "src/radon.pyx":24 - * sinogram = np.zeros((nw,theta.size)) - * - * Tx = np.zeros((nw,2)) # <<<<<<<<<<<<<< - * Rx = np.zeros((nw,2)) - * - */ - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 24, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_zeros); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 24, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 24, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_INCREF(__pyx_v_nw); - __Pyx_GIVEREF(__pyx_v_nw); - PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_v_nw); - __Pyx_INCREF(__pyx_int_2); - __Pyx_GIVEREF(__pyx_int_2); - PyTuple_SET_ITEM(__pyx_t_2, 1, __pyx_int_2); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_6))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_6); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_6, function); - } - } - __pyx_t_3 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_6, __pyx_t_4, __pyx_t_2) : __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_t_2); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 24, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_v_Tx = __pyx_t_3; - __pyx_t_3 = 0; - - /* "src/radon.pyx":25 - * - * Tx = np.zeros((nw,2)) - * Rx = np.zeros((nw,2)) # <<<<<<<<<<<<<< - * - * cdef size_t nTx = Tx.shape[0] - */ - __Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_np); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 25, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_6, __pyx_n_s_zeros); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 25, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_6 = PyTuple_New(2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 25, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_INCREF(__pyx_v_nw); - __Pyx_GIVEREF(__pyx_v_nw); - PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_v_nw); - __Pyx_INCREF(__pyx_int_2); - __Pyx_GIVEREF(__pyx_int_2); - PyTuple_SET_ITEM(__pyx_t_6, 1, __pyx_int_2); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_3 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_4, __pyx_t_6) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_t_6); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 25, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_v_Rx = __pyx_t_3; - __pyx_t_3 = 0; - - /* "src/radon.pyx":27 - * Rx = np.zeros((nw,2)) - * - * cdef size_t nTx = Tx.shape[0] # <<<<<<<<<<<<<< - * - * - */ - __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_Tx, __pyx_n_s_shape); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 27, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_2 = __Pyx_GetItemInt(__pyx_t_3, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 27, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_8 = __Pyx_PyInt_As_size_t(__pyx_t_2); if (unlikely((__pyx_t_8 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 27, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_v_nTx = __pyx_t_8; - - /* "src/radon.pyx":30 - * - * - * for nt in range(theta.size): # <<<<<<<<<<<<<< - * - * if theta[nt] == 0.0: - */ - __Pyx_GetModuleGlobalName(__pyx_t_3, __pyx_n_s_range); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 30, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_v_theta, __pyx_n_s_size); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 30, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_3))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_3); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_3, function); - } - } - __pyx_t_2 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_3, __pyx_t_4, __pyx_t_6) : __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_t_6); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 30, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (likely(PyList_CheckExact(__pyx_t_2)) || PyTuple_CheckExact(__pyx_t_2)) { - __pyx_t_3 = __pyx_t_2; __Pyx_INCREF(__pyx_t_3); __pyx_t_9 = 0; - __pyx_t_10 = NULL; - } else { - __pyx_t_9 = -1; __pyx_t_3 = PyObject_GetIter(__pyx_t_2); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 30, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_10 = Py_TYPE(__pyx_t_3)->tp_iternext; if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 30, __pyx_L1_error) - } - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - for (;;) { - if (likely(!__pyx_t_10)) { - if (likely(PyList_CheckExact(__pyx_t_3))) { - if (__pyx_t_9 >= PyList_GET_SIZE(__pyx_t_3)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_2 = PyList_GET_ITEM(__pyx_t_3, __pyx_t_9); __Pyx_INCREF(__pyx_t_2); __pyx_t_9++; if (unlikely(0 < 0)) __PYX_ERR(0, 30, __pyx_L1_error) - #else - __pyx_t_2 = PySequence_ITEM(__pyx_t_3, __pyx_t_9); __pyx_t_9++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 30, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - #endif - } else { - if (__pyx_t_9 >= PyTuple_GET_SIZE(__pyx_t_3)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_2 = PyTuple_GET_ITEM(__pyx_t_3, __pyx_t_9); __Pyx_INCREF(__pyx_t_2); __pyx_t_9++; if (unlikely(0 < 0)) __PYX_ERR(0, 30, __pyx_L1_error) - #else - __pyx_t_2 = PySequence_ITEM(__pyx_t_3, __pyx_t_9); __pyx_t_9++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 30, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - #endif - } - } else { - __pyx_t_2 = __pyx_t_10(__pyx_t_3); - if (unlikely(!__pyx_t_2)) { - PyObject* exc_type = PyErr_Occurred(); - if (exc_type) { - if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear(); - else __PYX_ERR(0, 30, __pyx_L1_error) - } - break; - } - __Pyx_GOTREF(__pyx_t_2); - } - __Pyx_XDECREF_SET(__pyx_v_nt, __pyx_t_2); - __pyx_t_2 = 0; - - /* "src/radon.pyx":32 - * for nt in range(theta.size): - * - * if theta[nt] == 0.0: # <<<<<<<<<<<<<< - * for n in range(nw): - * Tx[n,0] = n - */ - __pyx_t_2 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 32, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_6 = __Pyx_PyFloat_EqObjC(__pyx_t_2, __pyx_float_0_0, 0.0, 0, 0); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 32, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_6); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 32, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":33 - * - * if theta[nt] == 0.0: - * for n in range(nw): # <<<<<<<<<<<<<< - * Tx[n,0] = n - * Tx[n,1] = 0 - */ - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_range); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 33, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_6 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_4, __pyx_v_nw) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_v_nw); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 33, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (likely(PyList_CheckExact(__pyx_t_6)) || PyTuple_CheckExact(__pyx_t_6)) { - __pyx_t_2 = __pyx_t_6; __Pyx_INCREF(__pyx_t_2); __pyx_t_11 = 0; - __pyx_t_12 = NULL; - } else { - __pyx_t_11 = -1; __pyx_t_2 = PyObject_GetIter(__pyx_t_6); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 33, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_12 = Py_TYPE(__pyx_t_2)->tp_iternext; if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 33, __pyx_L1_error) - } - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - for (;;) { - if (likely(!__pyx_t_12)) { - if (likely(PyList_CheckExact(__pyx_t_2))) { - if (__pyx_t_11 >= PyList_GET_SIZE(__pyx_t_2)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_6 = PyList_GET_ITEM(__pyx_t_2, __pyx_t_11); __Pyx_INCREF(__pyx_t_6); __pyx_t_11++; if (unlikely(0 < 0)) __PYX_ERR(0, 33, __pyx_L1_error) - #else - __pyx_t_6 = PySequence_ITEM(__pyx_t_2, __pyx_t_11); __pyx_t_11++; if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 33, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - #endif - } else { - if (__pyx_t_11 >= PyTuple_GET_SIZE(__pyx_t_2)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_6 = PyTuple_GET_ITEM(__pyx_t_2, __pyx_t_11); __Pyx_INCREF(__pyx_t_6); __pyx_t_11++; if (unlikely(0 < 0)) __PYX_ERR(0, 33, __pyx_L1_error) - #else - __pyx_t_6 = PySequence_ITEM(__pyx_t_2, __pyx_t_11); __pyx_t_11++; if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 33, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - #endif - } - } else { - __pyx_t_6 = __pyx_t_12(__pyx_t_2); - if (unlikely(!__pyx_t_6)) { - PyObject* exc_type = PyErr_Occurred(); - if (exc_type) { - if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear(); - else __PYX_ERR(0, 33, __pyx_L1_error) - } - break; - } - __Pyx_GOTREF(__pyx_t_6); - } - __Pyx_XDECREF_SET(__pyx_v_n, __pyx_t_6); - __pyx_t_6 = 0; - - /* "src/radon.pyx":34 - * if theta[nt] == 0.0: - * for n in range(nw): - * Tx[n,0] = n # <<<<<<<<<<<<<< - * Tx[n,1] = 0 - * Rx[n,0] = n - */ - __pyx_t_6 = PyTuple_New(2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 34, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_0); - __Pyx_GIVEREF(__pyx_int_0); - PyTuple_SET_ITEM(__pyx_t_6, 1, __pyx_int_0); - if (unlikely(PyObject_SetItem(__pyx_v_Tx, __pyx_t_6, __pyx_v_n) < 0)) __PYX_ERR(0, 34, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - - /* "src/radon.pyx":35 - * for n in range(nw): - * Tx[n,0] = n - * Tx[n,1] = 0 # <<<<<<<<<<<<<< - * Rx[n,0] = n - * Rx[n,1] = ny-1 - */ - __pyx_t_6 = PyTuple_New(2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 35, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_1); - __Pyx_GIVEREF(__pyx_int_1); - PyTuple_SET_ITEM(__pyx_t_6, 1, __pyx_int_1); - if (unlikely(PyObject_SetItem(__pyx_v_Tx, __pyx_t_6, __pyx_int_0) < 0)) __PYX_ERR(0, 35, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - - /* "src/radon.pyx":36 - * Tx[n,0] = n - * Tx[n,1] = 0 - * Rx[n,0] = n # <<<<<<<<<<<<<< - * Rx[n,1] = ny-1 - * - */ - __pyx_t_6 = PyTuple_New(2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 36, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_0); - __Pyx_GIVEREF(__pyx_int_0); - PyTuple_SET_ITEM(__pyx_t_6, 1, __pyx_int_0); - if (unlikely(PyObject_SetItem(__pyx_v_Rx, __pyx_t_6, __pyx_v_n) < 0)) __PYX_ERR(0, 36, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - - /* "src/radon.pyx":37 - * Tx[n,1] = 0 - * Rx[n,0] = n - * Rx[n,1] = ny-1 # <<<<<<<<<<<<<< - * - * elif np.abs(theta[nt] - np.pi/2) < 1.e-10: - */ - __pyx_t_6 = __Pyx_PyInt_SubtractObjC(__pyx_v_ny, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 37, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_4 = PyTuple_New(2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 37, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_1); - __Pyx_GIVEREF(__pyx_int_1); - PyTuple_SET_ITEM(__pyx_t_4, 1, __pyx_int_1); - if (unlikely(PyObject_SetItem(__pyx_v_Rx, __pyx_t_4, __pyx_t_6) < 0)) __PYX_ERR(0, 37, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - - /* "src/radon.pyx":33 - * - * if theta[nt] == 0.0: - * for n in range(nw): # <<<<<<<<<<<<<< - * Tx[n,0] = n - * Tx[n,1] = 0 - */ - } - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":32 - * for nt in range(theta.size): - * - * if theta[nt] == 0.0: # <<<<<<<<<<<<<< - * for n in range(nw): - * Tx[n,0] = n - */ - goto __pyx_L11; - } - - /* "src/radon.pyx":39 - * Rx[n,1] = ny-1 - * - * elif np.abs(theta[nt] - np.pi/2) < 1.e-10: # <<<<<<<<<<<<<< - * - * for n in range(nw): - */ - __Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_np); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_6, __pyx_n_s_abs); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_6 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_GetModuleGlobalName(__pyx_t_13, __pyx_n_s_np); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_13, __pyx_n_s_pi); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = __Pyx_PyNumber_Divide(__pyx_t_14, __pyx_int_2); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyNumber_Subtract(__pyx_t_6, __pyx_t_13); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_4))) { - __pyx_t_13 = PyMethod_GET_SELF(__pyx_t_4); - if (likely(__pyx_t_13)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_4); - __Pyx_INCREF(__pyx_t_13); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_4, function); - } - } - __pyx_t_2 = (__pyx_t_13) ? __Pyx_PyObject_Call2Args(__pyx_t_4, __pyx_t_13, __pyx_t_14) : __Pyx_PyObject_CallOneArg(__pyx_t_4, __pyx_t_14); - __Pyx_XDECREF(__pyx_t_13); __pyx_t_13 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = PyObject_RichCompare(__pyx_t_2, __pyx_float_1_eneg_10, Py_LT); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 39, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":41 - * elif np.abs(theta[nt] - np.pi/2) < 1.e-10: - * - * for n in range(nw): # <<<<<<<<<<<<<< - * Tx[n,0] = 0 - * Tx[n,1] = n - */ - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_range); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 41, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_14 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_14 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_14)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_14); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_4 = (__pyx_t_14) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_14, __pyx_v_nw) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_v_nw); - __Pyx_XDECREF(__pyx_t_14); __pyx_t_14 = 0; - if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 41, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (likely(PyList_CheckExact(__pyx_t_4)) || PyTuple_CheckExact(__pyx_t_4)) { - __pyx_t_2 = __pyx_t_4; __Pyx_INCREF(__pyx_t_2); __pyx_t_11 = 0; - __pyx_t_12 = NULL; - } else { - __pyx_t_11 = -1; __pyx_t_2 = PyObject_GetIter(__pyx_t_4); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 41, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_12 = Py_TYPE(__pyx_t_2)->tp_iternext; if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 41, __pyx_L1_error) - } - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - for (;;) { - if (likely(!__pyx_t_12)) { - if (likely(PyList_CheckExact(__pyx_t_2))) { - if (__pyx_t_11 >= PyList_GET_SIZE(__pyx_t_2)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_4 = PyList_GET_ITEM(__pyx_t_2, __pyx_t_11); __Pyx_INCREF(__pyx_t_4); __pyx_t_11++; if (unlikely(0 < 0)) __PYX_ERR(0, 41, __pyx_L1_error) - #else - __pyx_t_4 = PySequence_ITEM(__pyx_t_2, __pyx_t_11); __pyx_t_11++; if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 41, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - #endif - } else { - if (__pyx_t_11 >= PyTuple_GET_SIZE(__pyx_t_2)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_4 = PyTuple_GET_ITEM(__pyx_t_2, __pyx_t_11); __Pyx_INCREF(__pyx_t_4); __pyx_t_11++; if (unlikely(0 < 0)) __PYX_ERR(0, 41, __pyx_L1_error) - #else - __pyx_t_4 = PySequence_ITEM(__pyx_t_2, __pyx_t_11); __pyx_t_11++; if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 41, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - #endif - } - } else { - __pyx_t_4 = __pyx_t_12(__pyx_t_2); - if (unlikely(!__pyx_t_4)) { - PyObject* exc_type = PyErr_Occurred(); - if (exc_type) { - if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear(); - else __PYX_ERR(0, 41, __pyx_L1_error) - } - break; - } - __Pyx_GOTREF(__pyx_t_4); - } - __Pyx_XDECREF_SET(__pyx_v_n, __pyx_t_4); - __pyx_t_4 = 0; - - /* "src/radon.pyx":42 - * - * for n in range(nw): - * Tx[n,0] = 0 # <<<<<<<<<<<<<< - * Tx[n,1] = n - * Rx[n,0] = nx-1 - */ - __pyx_t_4 = PyTuple_New(2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 42, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_0); - __Pyx_GIVEREF(__pyx_int_0); - PyTuple_SET_ITEM(__pyx_t_4, 1, __pyx_int_0); - if (unlikely(PyObject_SetItem(__pyx_v_Tx, __pyx_t_4, __pyx_int_0) < 0)) __PYX_ERR(0, 42, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - - /* "src/radon.pyx":43 - * for n in range(nw): - * Tx[n,0] = 0 - * Tx[n,1] = n # <<<<<<<<<<<<<< - * Rx[n,0] = nx-1 - * Rx[n,1] = n - */ - __pyx_t_4 = PyTuple_New(2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 43, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_1); - __Pyx_GIVEREF(__pyx_int_1); - PyTuple_SET_ITEM(__pyx_t_4, 1, __pyx_int_1); - if (unlikely(PyObject_SetItem(__pyx_v_Tx, __pyx_t_4, __pyx_v_n) < 0)) __PYX_ERR(0, 43, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - - /* "src/radon.pyx":44 - * Tx[n,0] = 0 - * Tx[n,1] = n - * Rx[n,0] = nx-1 # <<<<<<<<<<<<<< - * Rx[n,1] = n - * - */ - __pyx_t_4 = __Pyx_PyInt_SubtractObjC(__pyx_v_nx, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 44, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_14 = PyTuple_New(2); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 44, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_14, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_0); - __Pyx_GIVEREF(__pyx_int_0); - PyTuple_SET_ITEM(__pyx_t_14, 1, __pyx_int_0); - if (unlikely(PyObject_SetItem(__pyx_v_Rx, __pyx_t_14, __pyx_t_4) < 0)) __PYX_ERR(0, 44, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - - /* "src/radon.pyx":45 - * Tx[n,1] = n - * Rx[n,0] = nx-1 - * Rx[n,1] = n # <<<<<<<<<<<<<< - * - * elif theta[nt] < np.pi/2: - */ - __pyx_t_4 = PyTuple_New(2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 45, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_1); - __Pyx_GIVEREF(__pyx_int_1); - PyTuple_SET_ITEM(__pyx_t_4, 1, __pyx_int_1); - if (unlikely(PyObject_SetItem(__pyx_v_Rx, __pyx_t_4, __pyx_v_n) < 0)) __PYX_ERR(0, 45, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - - /* "src/radon.pyx":41 - * elif np.abs(theta[nt] - np.pi/2) < 1.e-10: - * - * for n in range(nw): # <<<<<<<<<<<<<< - * Tx[n,0] = 0 - * Tx[n,1] = n - */ - } - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":39 - * Rx[n,1] = ny-1 - * - * elif np.abs(theta[nt] - np.pi/2) < 1.e-10: # <<<<<<<<<<<<<< - * - * for n in range(nw): - */ - goto __pyx_L11; - } - - /* "src/radon.pyx":47 - * Rx[n,1] = n - * - * elif theta[nt] < np.pi/2: # <<<<<<<<<<<<<< - * - * xs = nw/2 * (1. - np.cos(theta[nt]) ) - */ - __pyx_t_2 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 47, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 47, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_pi); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 47, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = __Pyx_PyNumber_Divide(__pyx_t_14, __pyx_int_2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 47, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyObject_RichCompare(__pyx_t_2, __pyx_t_4, Py_LT); __Pyx_XGOTREF(__pyx_t_14); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 47, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_14); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 47, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":49 - * elif theta[nt] < np.pi/2: - * - * xs = nw/2 * (1. - np.cos(theta[nt]) ) # <<<<<<<<<<<<<< - * ys = nw/2 * (1. - np.sin(theta[nt]) ) - * - */ - __pyx_t_14 = __Pyx_PyNumber_Divide(__pyx_v_nw, __pyx_int_2); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 49, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 49, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_13 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_cos); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 49, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 49, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_6 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_13))) { - __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_13); - if (likely(__pyx_t_6)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_13); - __Pyx_INCREF(__pyx_t_6); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_13, function); - } - } - __pyx_t_4 = (__pyx_t_6) ? __Pyx_PyObject_Call2Args(__pyx_t_13, __pyx_t_6, __pyx_t_2) : __Pyx_PyObject_CallOneArg(__pyx_t_13, __pyx_t_2); - __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 49, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = __Pyx_PyFloat_SubtractCObj(__pyx_float_1_, __pyx_t_4, 1., 0, 0); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 49, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = PyNumber_Multiply(__pyx_t_14, __pyx_t_13); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 49, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __Pyx_XDECREF_SET(__pyx_v_xs, __pyx_t_4); - __pyx_t_4 = 0; - - /* "src/radon.pyx":50 - * - * xs = nw/2 * (1. - np.cos(theta[nt]) ) - * ys = nw/2 * (1. - np.sin(theta[nt]) ) # <<<<<<<<<<<<<< - * - * for n in range(nw): - */ - __pyx_t_4 = __Pyx_PyNumber_Divide(__pyx_v_nw, __pyx_int_2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 50, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_GetModuleGlobalName(__pyx_t_14, __pyx_n_s_np); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 50, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_14, __pyx_n_s_sin); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 50, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 50, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __pyx_t_6 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_6)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_6); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_13 = (__pyx_t_6) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_6, __pyx_t_14) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_t_14); - __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 50, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = __Pyx_PyFloat_SubtractCObj(__pyx_float_1_, __pyx_t_13, 1., 0, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 50, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = PyNumber_Multiply(__pyx_t_4, __pyx_t_2); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 50, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_XDECREF_SET(__pyx_v_ys, __pyx_t_13); - __pyx_t_13 = 0; - - /* "src/radon.pyx":52 - * ys = nw/2 * (1. - np.sin(theta[nt]) ) - * - * for n in range(nw): # <<<<<<<<<<<<<< - * xk = xs + n * np.cos(theta[nt]) - * yk = ys + n * np.sin(theta[nt]) - */ - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_range); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 52, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_13 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_4, __pyx_v_nw) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_v_nw); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 52, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (likely(PyList_CheckExact(__pyx_t_13)) || PyTuple_CheckExact(__pyx_t_13)) { - __pyx_t_2 = __pyx_t_13; __Pyx_INCREF(__pyx_t_2); __pyx_t_11 = 0; - __pyx_t_12 = NULL; - } else { - __pyx_t_11 = -1; __pyx_t_2 = PyObject_GetIter(__pyx_t_13); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 52, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_12 = Py_TYPE(__pyx_t_2)->tp_iternext; if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 52, __pyx_L1_error) - } - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - for (;;) { - if (likely(!__pyx_t_12)) { - if (likely(PyList_CheckExact(__pyx_t_2))) { - if (__pyx_t_11 >= PyList_GET_SIZE(__pyx_t_2)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_13 = PyList_GET_ITEM(__pyx_t_2, __pyx_t_11); __Pyx_INCREF(__pyx_t_13); __pyx_t_11++; if (unlikely(0 < 0)) __PYX_ERR(0, 52, __pyx_L1_error) - #else - __pyx_t_13 = PySequence_ITEM(__pyx_t_2, __pyx_t_11); __pyx_t_11++; if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 52, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - #endif - } else { - if (__pyx_t_11 >= PyTuple_GET_SIZE(__pyx_t_2)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_13 = PyTuple_GET_ITEM(__pyx_t_2, __pyx_t_11); __Pyx_INCREF(__pyx_t_13); __pyx_t_11++; if (unlikely(0 < 0)) __PYX_ERR(0, 52, __pyx_L1_error) - #else - __pyx_t_13 = PySequence_ITEM(__pyx_t_2, __pyx_t_11); __pyx_t_11++; if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 52, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - #endif - } - } else { - __pyx_t_13 = __pyx_t_12(__pyx_t_2); - if (unlikely(!__pyx_t_13)) { - PyObject* exc_type = PyErr_Occurred(); - if (exc_type) { - if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear(); - else __PYX_ERR(0, 52, __pyx_L1_error) - } - break; - } - __Pyx_GOTREF(__pyx_t_13); - } - __Pyx_XDECREF_SET(__pyx_v_n, __pyx_t_13); - __pyx_t_13 = 0; - - /* "src/radon.pyx":53 - * - * for n in range(nw): - * xk = xs + n * np.cos(theta[nt]) # <<<<<<<<<<<<<< - * yk = ys + n * np.sin(theta[nt]) - * - */ - __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 53, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_cos); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 53, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 53, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_6 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_14))) { - __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_14); - if (likely(__pyx_t_6)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_14); - __Pyx_INCREF(__pyx_t_6); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_14, function); - } - } - __pyx_t_13 = (__pyx_t_6) ? __Pyx_PyObject_Call2Args(__pyx_t_14, __pyx_t_6, __pyx_t_4) : __Pyx_PyObject_CallOneArg(__pyx_t_14, __pyx_t_4); - __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 53, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyNumber_Multiply(__pyx_v_n, __pyx_t_13); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 53, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = PyNumber_Add(__pyx_v_xs, __pyx_t_14); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 53, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_XDECREF_SET(__pyx_v_xk, __pyx_t_13); - __pyx_t_13 = 0; - - /* "src/radon.pyx":54 - * for n in range(nw): - * xk = xs + n * np.cos(theta[nt]) - * yk = ys + n * np.sin(theta[nt]) # <<<<<<<<<<<<<< - * - * x0 = 0.0 - */ - __Pyx_GetModuleGlobalName(__pyx_t_14, __pyx_n_s_np); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 54, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_14, __pyx_n_s_sin); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 54, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 54, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __pyx_t_6 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_4))) { - __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_4); - if (likely(__pyx_t_6)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_4); - __Pyx_INCREF(__pyx_t_6); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_4, function); - } - } - __pyx_t_13 = (__pyx_t_6) ? __Pyx_PyObject_Call2Args(__pyx_t_4, __pyx_t_6, __pyx_t_14) : __Pyx_PyObject_CallOneArg(__pyx_t_4, __pyx_t_14); - __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 54, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = PyNumber_Multiply(__pyx_v_n, __pyx_t_13); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 54, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = PyNumber_Add(__pyx_v_ys, __pyx_t_4); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 54, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_XDECREF_SET(__pyx_v_yk, __pyx_t_13); - __pyx_t_13 = 0; - - /* "src/radon.pyx":56 - * yk = ys + n * np.sin(theta[nt]) - * - * x0 = 0.0 # <<<<<<<<<<<<<< - * y0 = yk + (xk-x0) / np.tan(theta[nt]) - * if y0 > ny-1: - */ - __Pyx_INCREF(__pyx_float_0_0); - __Pyx_XDECREF_SET(__pyx_v_x0, __pyx_float_0_0); - - /* "src/radon.pyx":57 - * - * x0 = 0.0 - * y0 = yk + (xk-x0) / np.tan(theta[nt]) # <<<<<<<<<<<<<< - * if y0 > ny-1: - * y0 = ny-1 - */ - __pyx_t_13 = PyNumber_Subtract(__pyx_v_xk, __pyx_v_x0); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 57, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_GetModuleGlobalName(__pyx_t_14, __pyx_n_s_np); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 57, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_14, __pyx_n_s_tan); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 57, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 57, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __pyx_t_15 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_6))) { - __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_6); - if (likely(__pyx_t_15)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6); - __Pyx_INCREF(__pyx_t_15); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_6, function); - } - } - __pyx_t_4 = (__pyx_t_15) ? __Pyx_PyObject_Call2Args(__pyx_t_6, __pyx_t_15, __pyx_t_14) : __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_t_14); - __Pyx_XDECREF(__pyx_t_15); __pyx_t_15 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 57, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_6 = __Pyx_PyNumber_Divide(__pyx_t_13, __pyx_t_4); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 57, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = PyNumber_Add(__pyx_v_yk, __pyx_t_6); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 57, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_XDECREF_SET(__pyx_v_y0, __pyx_t_4); - __pyx_t_4 = 0; - - /* "src/radon.pyx":58 - * x0 = 0.0 - * y0 = yk + (xk-x0) / np.tan(theta[nt]) - * if y0 > ny-1: # <<<<<<<<<<<<<< - * y0 = ny-1 - * x0 = xk - (y0-yk) * np.tan(theta[nt]) - */ - __pyx_t_4 = __Pyx_PyInt_SubtractObjC(__pyx_v_ny, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 58, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_6 = PyObject_RichCompare(__pyx_v_y0, __pyx_t_4, Py_GT); __Pyx_XGOTREF(__pyx_t_6); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 58, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_6); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 58, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":59 - * y0 = yk + (xk-x0) / np.tan(theta[nt]) - * if y0 > ny-1: - * y0 = ny-1 # <<<<<<<<<<<<<< - * x0 = xk - (y0-yk) * np.tan(theta[nt]) - * x1 = nx-1 - */ - __pyx_t_6 = __Pyx_PyInt_SubtractObjC(__pyx_v_ny, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 59, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF_SET(__pyx_v_y0, __pyx_t_6); - __pyx_t_6 = 0; - - /* "src/radon.pyx":60 - * if y0 > ny-1: - * y0 = ny-1 - * x0 = xk - (y0-yk) * np.tan(theta[nt]) # <<<<<<<<<<<<<< - * x1 = nx-1 - * y1 = yk - (x1-xk) / np.tan(theta[nt]) - */ - __pyx_t_6 = PyNumber_Subtract(__pyx_v_y0, __pyx_v_yk); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 60, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_GetModuleGlobalName(__pyx_t_13, __pyx_n_s_np); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 60, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_13, __pyx_n_s_tan); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 60, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 60, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __pyx_t_15 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_14))) { - __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_14); - if (likely(__pyx_t_15)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_14); - __Pyx_INCREF(__pyx_t_15); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_14, function); - } - } - __pyx_t_4 = (__pyx_t_15) ? __Pyx_PyObject_Call2Args(__pyx_t_14, __pyx_t_15, __pyx_t_13) : __Pyx_PyObject_CallOneArg(__pyx_t_14, __pyx_t_13); - __Pyx_XDECREF(__pyx_t_15); __pyx_t_15 = 0; - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 60, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyNumber_Multiply(__pyx_t_6, __pyx_t_4); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 60, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = PyNumber_Subtract(__pyx_v_xk, __pyx_t_14); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 60, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF_SET(__pyx_v_x0, __pyx_t_4); - __pyx_t_4 = 0; - - /* "src/radon.pyx":58 - * x0 = 0.0 - * y0 = yk + (xk-x0) / np.tan(theta[nt]) - * if y0 > ny-1: # <<<<<<<<<<<<<< - * y0 = ny-1 - * x0 = xk - (y0-yk) * np.tan(theta[nt]) - */ - } - - /* "src/radon.pyx":61 - * y0 = ny-1 - * x0 = xk - (y0-yk) * np.tan(theta[nt]) - * x1 = nx-1 # <<<<<<<<<<<<<< - * y1 = yk - (x1-xk) / np.tan(theta[nt]) - * if y1 < 0.0: - */ - __pyx_t_4 = __Pyx_PyInt_SubtractObjC(__pyx_v_nx, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 61, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_XDECREF_SET(__pyx_v_x1, __pyx_t_4); - __pyx_t_4 = 0; - - /* "src/radon.pyx":62 - * x0 = xk - (y0-yk) * np.tan(theta[nt]) - * x1 = nx-1 - * y1 = yk - (x1-xk) / np.tan(theta[nt]) # <<<<<<<<<<<<<< - * if y1 < 0.0: - * y1 = 0.0 - */ - __pyx_t_4 = PyNumber_Subtract(__pyx_v_x1, __pyx_v_xk); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 62, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_np); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 62, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_13 = __Pyx_PyObject_GetAttrStr(__pyx_t_6, __pyx_n_s_tan); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 62, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_6 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 62, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_15 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_13))) { - __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_13); - if (likely(__pyx_t_15)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_13); - __Pyx_INCREF(__pyx_t_15); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_13, function); - } - } - __pyx_t_14 = (__pyx_t_15) ? __Pyx_PyObject_Call2Args(__pyx_t_13, __pyx_t_15, __pyx_t_6) : __Pyx_PyObject_CallOneArg(__pyx_t_13, __pyx_t_6); - __Pyx_XDECREF(__pyx_t_15); __pyx_t_15 = 0; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 62, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = __Pyx_PyNumber_Divide(__pyx_t_4, __pyx_t_14); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 62, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyNumber_Subtract(__pyx_v_yk, __pyx_t_13); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 62, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __Pyx_XDECREF_SET(__pyx_v_y1, __pyx_t_14); - __pyx_t_14 = 0; - - /* "src/radon.pyx":63 - * x1 = nx-1 - * y1 = yk - (x1-xk) / np.tan(theta[nt]) - * if y1 < 0.0: # <<<<<<<<<<<<<< - * y1 = 0.0 - * x1 = xk + (yk-y1) * np.tan(theta[nt]) - */ - __pyx_t_14 = PyObject_RichCompare(__pyx_v_y1, __pyx_float_0_0, Py_LT); __Pyx_XGOTREF(__pyx_t_14); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 63, __pyx_L1_error) - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_14); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 63, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":64 - * y1 = yk - (x1-xk) / np.tan(theta[nt]) - * if y1 < 0.0: - * y1 = 0.0 # <<<<<<<<<<<<<< - * x1 = xk + (yk-y1) * np.tan(theta[nt]) - * - */ - __Pyx_INCREF(__pyx_float_0_0); - __Pyx_DECREF_SET(__pyx_v_y1, __pyx_float_0_0); - - /* "src/radon.pyx":65 - * if y1 < 0.0: - * y1 = 0.0 - * x1 = xk + (yk-y1) * np.tan(theta[nt]) # <<<<<<<<<<<<<< - * - * Tx[n,0] = x0 - */ - __pyx_t_14 = PyNumber_Subtract(__pyx_v_yk, __pyx_v_y1); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 65, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 65, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_tan); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 65, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 65, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_15 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_6))) { - __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_6); - if (likely(__pyx_t_15)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6); - __Pyx_INCREF(__pyx_t_15); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_6, function); - } - } - __pyx_t_13 = (__pyx_t_15) ? __Pyx_PyObject_Call2Args(__pyx_t_6, __pyx_t_15, __pyx_t_4) : __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_t_4); - __Pyx_XDECREF(__pyx_t_15); __pyx_t_15 = 0; - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 65, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_6 = PyNumber_Multiply(__pyx_t_14, __pyx_t_13); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 65, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = PyNumber_Add(__pyx_v_xk, __pyx_t_6); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 65, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_DECREF_SET(__pyx_v_x1, __pyx_t_13); - __pyx_t_13 = 0; - - /* "src/radon.pyx":63 - * x1 = nx-1 - * y1 = yk - (x1-xk) / np.tan(theta[nt]) - * if y1 < 0.0: # <<<<<<<<<<<<<< - * y1 = 0.0 - * x1 = xk + (yk-y1) * np.tan(theta[nt]) - */ - } - - /* "src/radon.pyx":67 - * x1 = xk + (yk-y1) * np.tan(theta[nt]) - * - * Tx[n,0] = x0 # <<<<<<<<<<<<<< - * Tx[n,1] = y0 - * Rx[n,0] = x1 - */ - __pyx_t_13 = PyTuple_New(2); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 67, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_13, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_0); - __Pyx_GIVEREF(__pyx_int_0); - PyTuple_SET_ITEM(__pyx_t_13, 1, __pyx_int_0); - if (unlikely(PyObject_SetItem(__pyx_v_Tx, __pyx_t_13, __pyx_v_x0) < 0)) __PYX_ERR(0, 67, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - - /* "src/radon.pyx":68 - * - * Tx[n,0] = x0 - * Tx[n,1] = y0 # <<<<<<<<<<<<<< - * Rx[n,0] = x1 - * Rx[n,1] = y1 - */ - __pyx_t_13 = PyTuple_New(2); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 68, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_13, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_1); - __Pyx_GIVEREF(__pyx_int_1); - PyTuple_SET_ITEM(__pyx_t_13, 1, __pyx_int_1); - if (unlikely(PyObject_SetItem(__pyx_v_Tx, __pyx_t_13, __pyx_v_y0) < 0)) __PYX_ERR(0, 68, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - - /* "src/radon.pyx":69 - * Tx[n,0] = x0 - * Tx[n,1] = y0 - * Rx[n,0] = x1 # <<<<<<<<<<<<<< - * Rx[n,1] = y1 - * - */ - __pyx_t_13 = PyTuple_New(2); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 69, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_13, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_0); - __Pyx_GIVEREF(__pyx_int_0); - PyTuple_SET_ITEM(__pyx_t_13, 1, __pyx_int_0); - if (unlikely(PyObject_SetItem(__pyx_v_Rx, __pyx_t_13, __pyx_v_x1) < 0)) __PYX_ERR(0, 69, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - - /* "src/radon.pyx":70 - * Tx[n,1] = y0 - * Rx[n,0] = x1 - * Rx[n,1] = y1 # <<<<<<<<<<<<<< - * - * - */ - __pyx_t_13 = PyTuple_New(2); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 70, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_13, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_1); - __Pyx_GIVEREF(__pyx_int_1); - PyTuple_SET_ITEM(__pyx_t_13, 1, __pyx_int_1); - if (unlikely(PyObject_SetItem(__pyx_v_Rx, __pyx_t_13, __pyx_v_y1) < 0)) __PYX_ERR(0, 70, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - - /* "src/radon.pyx":52 - * ys = nw/2 * (1. - np.sin(theta[nt]) ) - * - * for n in range(nw): # <<<<<<<<<<<<<< - * xk = xs + n * np.cos(theta[nt]) - * yk = ys + n * np.sin(theta[nt]) - */ - } - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":47 - * Rx[n,1] = n - * - * elif theta[nt] < np.pi/2: # <<<<<<<<<<<<<< - * - * xs = nw/2 * (1. - np.cos(theta[nt]) ) - */ - goto __pyx_L11; - } - - /* "src/radon.pyx":75 - * else: - * - * xs = nw/2 * (1. - np.cos(theta[nt]) ) # <<<<<<<<<<<<<< - * ys = nw/2 * (1. - np.sin(theta[nt]) ) - * - */ - /*else*/ { - __pyx_t_2 = __Pyx_PyNumber_Divide(__pyx_v_nw, __pyx_int_2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 75, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_np); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 75, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_6, __pyx_n_s_cos); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 75, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_6 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 75, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_14))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_14); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_14); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_14, function); - } - } - __pyx_t_13 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_14, __pyx_t_4, __pyx_t_6) : __Pyx_PyObject_CallOneArg(__pyx_t_14, __pyx_t_6); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 75, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = __Pyx_PyFloat_SubtractCObj(__pyx_float_1_, __pyx_t_13, 1., 0, 0); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 75, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = PyNumber_Multiply(__pyx_t_2, __pyx_t_14); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 75, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_XDECREF_SET(__pyx_v_xs, __pyx_t_13); - __pyx_t_13 = 0; - - /* "src/radon.pyx":76 - * - * xs = nw/2 * (1. - np.cos(theta[nt]) ) - * ys = nw/2 * (1. - np.sin(theta[nt]) ) # <<<<<<<<<<<<<< - * - * for n in range(nw): - */ - __pyx_t_13 = __Pyx_PyNumber_Divide(__pyx_v_nw, __pyx_int_2); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 76, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 76, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_sin); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 76, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 76, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_6))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_6); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_6, function); - } - } - __pyx_t_14 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_6, __pyx_t_4, __pyx_t_2) : __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_t_2); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 76, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_6 = __Pyx_PyFloat_SubtractCObj(__pyx_float_1_, __pyx_t_14, 1., 0, 0); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 76, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyNumber_Multiply(__pyx_t_13, __pyx_t_6); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 76, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_XDECREF_SET(__pyx_v_ys, __pyx_t_14); - __pyx_t_14 = 0; - - /* "src/radon.pyx":78 - * ys = nw/2 * (1. - np.sin(theta[nt]) ) - * - * for n in range(nw): # <<<<<<<<<<<<<< - * xk = xs + n * np.cos(theta[nt]) - * yk = ys + n * np.sin(theta[nt]) - */ - __Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_range); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 78, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_13 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_6))) { - __pyx_t_13 = PyMethod_GET_SELF(__pyx_t_6); - if (likely(__pyx_t_13)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6); - __Pyx_INCREF(__pyx_t_13); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_6, function); - } - } - __pyx_t_14 = (__pyx_t_13) ? __Pyx_PyObject_Call2Args(__pyx_t_6, __pyx_t_13, __pyx_v_nw) : __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_v_nw); - __Pyx_XDECREF(__pyx_t_13); __pyx_t_13 = 0; - if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 78, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - if (likely(PyList_CheckExact(__pyx_t_14)) || PyTuple_CheckExact(__pyx_t_14)) { - __pyx_t_6 = __pyx_t_14; __Pyx_INCREF(__pyx_t_6); __pyx_t_11 = 0; - __pyx_t_12 = NULL; - } else { - __pyx_t_11 = -1; __pyx_t_6 = PyObject_GetIter(__pyx_t_14); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 78, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_12 = Py_TYPE(__pyx_t_6)->tp_iternext; if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 78, __pyx_L1_error) - } - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - for (;;) { - if (likely(!__pyx_t_12)) { - if (likely(PyList_CheckExact(__pyx_t_6))) { - if (__pyx_t_11 >= PyList_GET_SIZE(__pyx_t_6)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_14 = PyList_GET_ITEM(__pyx_t_6, __pyx_t_11); __Pyx_INCREF(__pyx_t_14); __pyx_t_11++; if (unlikely(0 < 0)) __PYX_ERR(0, 78, __pyx_L1_error) - #else - __pyx_t_14 = PySequence_ITEM(__pyx_t_6, __pyx_t_11); __pyx_t_11++; if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 78, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - #endif - } else { - if (__pyx_t_11 >= PyTuple_GET_SIZE(__pyx_t_6)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_14 = PyTuple_GET_ITEM(__pyx_t_6, __pyx_t_11); __Pyx_INCREF(__pyx_t_14); __pyx_t_11++; if (unlikely(0 < 0)) __PYX_ERR(0, 78, __pyx_L1_error) - #else - __pyx_t_14 = PySequence_ITEM(__pyx_t_6, __pyx_t_11); __pyx_t_11++; if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 78, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - #endif - } - } else { - __pyx_t_14 = __pyx_t_12(__pyx_t_6); - if (unlikely(!__pyx_t_14)) { - PyObject* exc_type = PyErr_Occurred(); - if (exc_type) { - if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear(); - else __PYX_ERR(0, 78, __pyx_L1_error) - } - break; - } - __Pyx_GOTREF(__pyx_t_14); - } - __Pyx_XDECREF_SET(__pyx_v_n, __pyx_t_14); - __pyx_t_14 = 0; - - /* "src/radon.pyx":79 - * - * for n in range(nw): - * xk = xs + n * np.cos(theta[nt]) # <<<<<<<<<<<<<< - * yk = ys + n * np.sin(theta[nt]) - * - */ - __Pyx_GetModuleGlobalName(__pyx_t_13, __pyx_n_s_np); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 79, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_13, __pyx_n_s_cos); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 79, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 79, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_14 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_4, __pyx_t_13) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_t_13); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 79, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = PyNumber_Multiply(__pyx_v_n, __pyx_t_14); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 79, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyNumber_Add(__pyx_v_xs, __pyx_t_2); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 79, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_XDECREF_SET(__pyx_v_xk, __pyx_t_14); - __pyx_t_14 = 0; - - /* "src/radon.pyx":80 - * for n in range(nw): - * xk = xs + n * np.cos(theta[nt]) - * yk = ys + n * np.sin(theta[nt]) # <<<<<<<<<<<<<< - * - * x0 = nx-1 - */ - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 80, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_13 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_sin); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 80, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 80, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_4 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_13))) { - __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_13); - if (likely(__pyx_t_4)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_13); - __Pyx_INCREF(__pyx_t_4); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_13, function); - } - } - __pyx_t_14 = (__pyx_t_4) ? __Pyx_PyObject_Call2Args(__pyx_t_13, __pyx_t_4, __pyx_t_2) : __Pyx_PyObject_CallOneArg(__pyx_t_13, __pyx_t_2); - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 80, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = PyNumber_Multiply(__pyx_v_n, __pyx_t_14); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 80, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyNumber_Add(__pyx_v_ys, __pyx_t_13); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 80, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __Pyx_XDECREF_SET(__pyx_v_yk, __pyx_t_14); - __pyx_t_14 = 0; - - /* "src/radon.pyx":82 - * yk = ys + n * np.sin(theta[nt]) - * - * x0 = nx-1 # <<<<<<<<<<<<<< - * y0 = yk + (x0-xk) * np.tan(theta[nt]-np.pi/2.0) - * if y0 > ny-1: - */ - __pyx_t_14 = __Pyx_PyInt_SubtractObjC(__pyx_v_nx, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 82, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_XDECREF_SET(__pyx_v_x0, __pyx_t_14); - __pyx_t_14 = 0; - - /* "src/radon.pyx":83 - * - * x0 = nx-1 - * y0 = yk + (x0-xk) * np.tan(theta[nt]-np.pi/2.0) # <<<<<<<<<<<<<< - * if y0 > ny-1: - * y0 = ny-1 - */ - __pyx_t_14 = PyNumber_Subtract(__pyx_v_x0, __pyx_v_xk); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_tan); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_GetModuleGlobalName(__pyx_t_15, __pyx_n_s_np); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_15); - __pyx_t_16 = __Pyx_PyObject_GetAttrStr(__pyx_t_15, __pyx_n_s_pi); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0; - __pyx_t_15 = __Pyx_PyFloat_DivideObjC(__pyx_t_16, __pyx_float_2_0, 2.0, 0, 0); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_15); - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - __pyx_t_16 = PyNumber_Subtract(__pyx_t_2, __pyx_t_15); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0; - __pyx_t_15 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_4))) { - __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_4); - if (likely(__pyx_t_15)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_4); - __Pyx_INCREF(__pyx_t_15); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_4, function); - } - } - __pyx_t_13 = (__pyx_t_15) ? __Pyx_PyObject_Call2Args(__pyx_t_4, __pyx_t_15, __pyx_t_16) : __Pyx_PyObject_CallOneArg(__pyx_t_4, __pyx_t_16); - __Pyx_XDECREF(__pyx_t_15); __pyx_t_15 = 0; - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = PyNumber_Multiply(__pyx_t_14, __pyx_t_13); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = PyNumber_Add(__pyx_v_yk, __pyx_t_4); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 83, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_XDECREF_SET(__pyx_v_y0, __pyx_t_13); - __pyx_t_13 = 0; - - /* "src/radon.pyx":84 - * x0 = nx-1 - * y0 = yk + (x0-xk) * np.tan(theta[nt]-np.pi/2.0) - * if y0 > ny-1: # <<<<<<<<<<<<<< - * y0 = ny-1 - * x0 = xk + (y0-yk) / np.tan(theta[nt]-np.pi/2.0) - */ - __pyx_t_13 = __Pyx_PyInt_SubtractObjC(__pyx_v_ny, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 84, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_y0, __pyx_t_13, Py_GT); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 84, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 84, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":85 - * y0 = yk + (x0-xk) * np.tan(theta[nt]-np.pi/2.0) - * if y0 > ny-1: - * y0 = ny-1 # <<<<<<<<<<<<<< - * x0 = xk + (y0-yk) / np.tan(theta[nt]-np.pi/2.0) - * x1 = 0.0 - */ - __pyx_t_4 = __Pyx_PyInt_SubtractObjC(__pyx_v_ny, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 85, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF_SET(__pyx_v_y0, __pyx_t_4); - __pyx_t_4 = 0; - - /* "src/radon.pyx":86 - * if y0 > ny-1: - * y0 = ny-1 - * x0 = xk + (y0-yk) / np.tan(theta[nt]-np.pi/2.0) # <<<<<<<<<<<<<< - * x1 = 0.0 - * y1 = yk - (xk-x1) * np.tan(theta[nt]-np.pi/2.0) - */ - __pyx_t_4 = PyNumber_Subtract(__pyx_v_y0, __pyx_v_yk); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_GetModuleGlobalName(__pyx_t_14, __pyx_n_s_np); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __pyx_t_16 = __Pyx_PyObject_GetAttrStr(__pyx_t_14, __pyx_n_s_tan); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_GetModuleGlobalName(__pyx_t_15, __pyx_n_s_np); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_15); - __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_15, __pyx_n_s_pi); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0; - __pyx_t_15 = __Pyx_PyFloat_DivideObjC(__pyx_t_2, __pyx_float_2_0, 2.0, 0, 0); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_15); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = PyNumber_Subtract(__pyx_t_14, __pyx_t_15); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0; - __pyx_t_15 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_16))) { - __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_16); - if (likely(__pyx_t_15)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_16); - __Pyx_INCREF(__pyx_t_15); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_16, function); - } - } - __pyx_t_13 = (__pyx_t_15) ? __Pyx_PyObject_Call2Args(__pyx_t_16, __pyx_t_15, __pyx_t_2) : __Pyx_PyObject_CallOneArg(__pyx_t_16, __pyx_t_2); - __Pyx_XDECREF(__pyx_t_15); __pyx_t_15 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - __pyx_t_16 = __Pyx_PyNumber_Divide(__pyx_t_4, __pyx_t_13); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = PyNumber_Add(__pyx_v_xk, __pyx_t_16); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 86, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - __Pyx_DECREF_SET(__pyx_v_x0, __pyx_t_13); - __pyx_t_13 = 0; - - /* "src/radon.pyx":84 - * x0 = nx-1 - * y0 = yk + (x0-xk) * np.tan(theta[nt]-np.pi/2.0) - * if y0 > ny-1: # <<<<<<<<<<<<<< - * y0 = ny-1 - * x0 = xk + (y0-yk) / np.tan(theta[nt]-np.pi/2.0) - */ - } - - /* "src/radon.pyx":87 - * y0 = ny-1 - * x0 = xk + (y0-yk) / np.tan(theta[nt]-np.pi/2.0) - * x1 = 0.0 # <<<<<<<<<<<<<< - * y1 = yk - (xk-x1) * np.tan(theta[nt]-np.pi/2.0) - * if y1 < 0.0: - */ - __Pyx_INCREF(__pyx_float_0_0); - __Pyx_XDECREF_SET(__pyx_v_x1, __pyx_float_0_0); - - /* "src/radon.pyx":88 - * x0 = xk + (y0-yk) / np.tan(theta[nt]-np.pi/2.0) - * x1 = 0.0 - * y1 = yk - (xk-x1) * np.tan(theta[nt]-np.pi/2.0) # <<<<<<<<<<<<<< - * if y1 < 0.0: - * y1 = 0.0 - */ - __pyx_t_13 = PyNumber_Subtract(__pyx_v_xk, __pyx_v_x1); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_tan); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_GetModuleGlobalName(__pyx_t_15, __pyx_n_s_np); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_15); - __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_15, __pyx_n_s_pi); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0; - __pyx_t_15 = __Pyx_PyFloat_DivideObjC(__pyx_t_14, __pyx_float_2_0, 2.0, 0, 0); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_15); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = PyNumber_Subtract(__pyx_t_4, __pyx_t_15); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0; - __pyx_t_15 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_15)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_15); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_16 = (__pyx_t_15) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_15, __pyx_t_14) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_t_14); - __Pyx_XDECREF(__pyx_t_15); __pyx_t_15 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = PyNumber_Multiply(__pyx_t_13, __pyx_t_16); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - __pyx_t_16 = PyNumber_Subtract(__pyx_v_yk, __pyx_t_2); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 88, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_XDECREF_SET(__pyx_v_y1, __pyx_t_16); - __pyx_t_16 = 0; - - /* "src/radon.pyx":89 - * x1 = 0.0 - * y1 = yk - (xk-x1) * np.tan(theta[nt]-np.pi/2.0) - * if y1 < 0.0: # <<<<<<<<<<<<<< - * y1 = 0.0 - * x1 = xk - (yk-y1) / np.tan(theta[nt]-np.pi/2.0) - */ - __pyx_t_16 = PyObject_RichCompare(__pyx_v_y1, __pyx_float_0_0, Py_LT); __Pyx_XGOTREF(__pyx_t_16); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 89, __pyx_L1_error) - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_16); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 89, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":90 - * y1 = yk - (xk-x1) * np.tan(theta[nt]-np.pi/2.0) - * if y1 < 0.0: - * y1 = 0.0 # <<<<<<<<<<<<<< - * x1 = xk - (yk-y1) / np.tan(theta[nt]-np.pi/2.0) - * - */ - __Pyx_INCREF(__pyx_float_0_0); - __Pyx_DECREF_SET(__pyx_v_y1, __pyx_float_0_0); - - /* "src/radon.pyx":91 - * if y1 < 0.0: - * y1 = 0.0 - * x1 = xk - (yk-y1) / np.tan(theta[nt]-np.pi/2.0) # <<<<<<<<<<<<<< - * - * Tx[n,0] = x0 - */ - __pyx_t_16 = PyNumber_Subtract(__pyx_v_yk, __pyx_v_y1); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_GetModuleGlobalName(__pyx_t_13, __pyx_n_s_np); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_13, __pyx_n_s_tan); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __pyx_t_13 = __Pyx_PyObject_GetItem(__pyx_v_theta, __pyx_v_nt); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_13); - __Pyx_GetModuleGlobalName(__pyx_t_15, __pyx_n_s_np); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_15); - __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_15, __pyx_n_s_pi); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0; - __pyx_t_15 = __Pyx_PyFloat_DivideObjC(__pyx_t_4, __pyx_float_2_0, 2.0, 0, 0); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_15); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = PyNumber_Subtract(__pyx_t_13, __pyx_t_15); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0; - __pyx_t_15 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_14))) { - __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_14); - if (likely(__pyx_t_15)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_14); - __Pyx_INCREF(__pyx_t_15); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_14, function); - } - } - __pyx_t_2 = (__pyx_t_15) ? __Pyx_PyObject_Call2Args(__pyx_t_14, __pyx_t_15, __pyx_t_4) : __Pyx_PyObject_CallOneArg(__pyx_t_14, __pyx_t_4); - __Pyx_XDECREF(__pyx_t_15); __pyx_t_15 = 0; - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = __Pyx_PyNumber_Divide(__pyx_t_16, __pyx_t_2); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = PyNumber_Subtract(__pyx_v_xk, __pyx_t_14); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 91, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF_SET(__pyx_v_x1, __pyx_t_2); - __pyx_t_2 = 0; - - /* "src/radon.pyx":89 - * x1 = 0.0 - * y1 = yk - (xk-x1) * np.tan(theta[nt]-np.pi/2.0) - * if y1 < 0.0: # <<<<<<<<<<<<<< - * y1 = 0.0 - * x1 = xk - (yk-y1) / np.tan(theta[nt]-np.pi/2.0) - */ - } - - /* "src/radon.pyx":93 - * x1 = xk - (yk-y1) / np.tan(theta[nt]-np.pi/2.0) - * - * Tx[n,0] = x0 # <<<<<<<<<<<<<< - * Tx[n,1] = y0 - * Rx[n,0] = x1 - */ - __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 93, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_0); - __Pyx_GIVEREF(__pyx_int_0); - PyTuple_SET_ITEM(__pyx_t_2, 1, __pyx_int_0); - if (unlikely(PyObject_SetItem(__pyx_v_Tx, __pyx_t_2, __pyx_v_x0) < 0)) __PYX_ERR(0, 93, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":94 - * - * Tx[n,0] = x0 - * Tx[n,1] = y0 # <<<<<<<<<<<<<< - * Rx[n,0] = x1 - * Rx[n,1] = y1 - */ - __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 94, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_1); - __Pyx_GIVEREF(__pyx_int_1); - PyTuple_SET_ITEM(__pyx_t_2, 1, __pyx_int_1); - if (unlikely(PyObject_SetItem(__pyx_v_Tx, __pyx_t_2, __pyx_v_y0) < 0)) __PYX_ERR(0, 94, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":95 - * Tx[n,0] = x0 - * Tx[n,1] = y0 - * Rx[n,0] = x1 # <<<<<<<<<<<<<< - * Rx[n,1] = y1 - * - */ - __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 95, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_0); - __Pyx_GIVEREF(__pyx_int_0); - PyTuple_SET_ITEM(__pyx_t_2, 1, __pyx_int_0); - if (unlikely(PyObject_SetItem(__pyx_v_Rx, __pyx_t_2, __pyx_v_x1) < 0)) __PYX_ERR(0, 95, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":96 - * Tx[n,1] = y0 - * Rx[n,0] = x1 - * Rx[n,1] = y1 # <<<<<<<<<<<<<< - * - * if np.max(Rx) > nx-1: - */ - __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 96, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_INCREF(__pyx_v_n); - __Pyx_GIVEREF(__pyx_v_n); - PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_v_n); - __Pyx_INCREF(__pyx_int_1); - __Pyx_GIVEREF(__pyx_int_1); - PyTuple_SET_ITEM(__pyx_t_2, 1, __pyx_int_1); - if (unlikely(PyObject_SetItem(__pyx_v_Rx, __pyx_t_2, __pyx_v_y1) < 0)) __PYX_ERR(0, 96, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":78 - * ys = nw/2 * (1. - np.sin(theta[nt]) ) - * - * for n in range(nw): # <<<<<<<<<<<<<< - * xk = xs + n * np.cos(theta[nt]) - * yk = ys + n * np.sin(theta[nt]) - */ - } - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - } - __pyx_L11:; - - /* "src/radon.pyx":98 - * Rx[n,1] = y1 - * - * if np.max(Rx) > nx-1: # <<<<<<<<<<<<<< - * print('Rx '+str(n)) - * print(Rx) - */ - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 98, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_max); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 98, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_14))) { - __pyx_t_2 = PyMethod_GET_SELF(__pyx_t_14); - if (likely(__pyx_t_2)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_14); - __Pyx_INCREF(__pyx_t_2); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_14, function); - } - } - __pyx_t_6 = (__pyx_t_2) ? __Pyx_PyObject_Call2Args(__pyx_t_14, __pyx_t_2, __pyx_v_Rx) : __Pyx_PyObject_CallOneArg(__pyx_t_14, __pyx_v_Rx); - __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; - if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 98, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_14 = __Pyx_PyInt_SubtractObjC(__pyx_v_nx, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 98, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __pyx_t_2 = PyObject_RichCompare(__pyx_t_6, __pyx_t_14, Py_GT); __Pyx_XGOTREF(__pyx_t_2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 98, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_2); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 98, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":99 - * - * if np.max(Rx) > nx-1: - * print('Rx '+str(n)) # <<<<<<<<<<<<<< - * print(Rx) - * if np.max(Tx) > nx-1: - */ - if (unlikely(!__pyx_v_n)) { __Pyx_RaiseUnboundLocalError("n"); __PYX_ERR(0, 99, __pyx_L1_error) } - __pyx_t_2 = __Pyx_PyObject_CallOneArg(((PyObject *)(&PyString_Type)), __pyx_v_n); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 99, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_14 = PyNumber_Add(__pyx_kp_s_Rx, __pyx_t_2); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 99, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (__Pyx_PrintOne(0, __pyx_t_14) < 0) __PYX_ERR(0, 99, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - - /* "src/radon.pyx":100 - * if np.max(Rx) > nx-1: - * print('Rx '+str(n)) - * print(Rx) # <<<<<<<<<<<<<< - * if np.max(Tx) > nx-1: - * print('Tx '+str(n)) - */ - if (__Pyx_PrintOne(0, __pyx_v_Rx) < 0) __PYX_ERR(0, 100, __pyx_L1_error) - - /* "src/radon.pyx":98 - * Rx[n,1] = y1 - * - * if np.max(Rx) > nx-1: # <<<<<<<<<<<<<< - * print('Rx '+str(n)) - * print(Rx) - */ - } - - /* "src/radon.pyx":101 - * print('Rx '+str(n)) - * print(Rx) - * if np.max(Tx) > nx-1: # <<<<<<<<<<<<<< - * print('Tx '+str(n)) - * print(Tx) - */ - __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 101, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_max); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 101, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __pyx_t_2 = NULL; - if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_6))) { - __pyx_t_2 = PyMethod_GET_SELF(__pyx_t_6); - if (likely(__pyx_t_2)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6); - __Pyx_INCREF(__pyx_t_2); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_6, function); - } - } - __pyx_t_14 = (__pyx_t_2) ? __Pyx_PyObject_Call2Args(__pyx_t_6, __pyx_t_2, __pyx_v_Tx) : __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_v_Tx); - __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; - if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 101, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_6 = __Pyx_PyInt_SubtractObjC(__pyx_v_nx, __pyx_int_1, 1, 0, 0); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 101, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_2 = PyObject_RichCompare(__pyx_t_14, __pyx_t_6, Py_GT); __Pyx_XGOTREF(__pyx_t_2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 101, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_2); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 101, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (__pyx_t_1) { - - /* "src/radon.pyx":102 - * print(Rx) - * if np.max(Tx) > nx-1: - * print('Tx '+str(n)) # <<<<<<<<<<<<<< - * print(Tx) - * - */ - if (unlikely(!__pyx_v_n)) { __Pyx_RaiseUnboundLocalError("n"); __PYX_ERR(0, 102, __pyx_L1_error) } - __pyx_t_2 = __Pyx_PyObject_CallOneArg(((PyObject *)(&PyString_Type)), __pyx_v_n); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 102, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_6 = PyNumber_Add(__pyx_kp_s_Tx, __pyx_t_2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 102, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - if (__Pyx_PrintOne(0, __pyx_t_6) < 0) __PYX_ERR(0, 102, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - - /* "src/radon.pyx":103 - * if np.max(Tx) > nx-1: - * print('Tx '+str(n)) - * print(Tx) # <<<<<<<<<<<<<< - * - * - */ - if (__Pyx_PrintOne(0, __pyx_v_Tx) < 0) __PYX_ERR(0, 103, __pyx_L1_error) - - /* "src/radon.pyx":101 - * print('Rx '+str(n)) - * print(Rx) - * if np.max(Tx) > nx-1: # <<<<<<<<<<<<<< - * print('Tx '+str(n)) - * print(Tx) - */ - } - - /* "src/radon.pyx":106 - * - * - * Ldata = ([0.0], [0.0], [0.0]) # <<<<<<<<<<<<<< - * - * cradon.radon2d( np.PyArray_DATA(Tx), np.PyArray_DATA(Rx), nTx, nx, ny, Ldata) - */ - __pyx_t_6 = PyList_New(1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 106, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __Pyx_INCREF(__pyx_float_0_0); - __Pyx_GIVEREF(__pyx_float_0_0); - PyList_SET_ITEM(__pyx_t_6, 0, __pyx_float_0_0); - __pyx_t_2 = PyList_New(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 106, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_INCREF(__pyx_float_0_0); - __Pyx_GIVEREF(__pyx_float_0_0); - PyList_SET_ITEM(__pyx_t_2, 0, __pyx_float_0_0); - __pyx_t_14 = PyList_New(1); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 106, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_INCREF(__pyx_float_0_0); - __Pyx_GIVEREF(__pyx_float_0_0); - PyList_SET_ITEM(__pyx_t_14, 0, __pyx_float_0_0); - __pyx_t_16 = PyTuple_New(3); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 106, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_GIVEREF(__pyx_t_6); - PyTuple_SET_ITEM(__pyx_t_16, 0, __pyx_t_6); - __Pyx_GIVEREF(__pyx_t_2); - PyTuple_SET_ITEM(__pyx_t_16, 1, __pyx_t_2); - __Pyx_GIVEREF(__pyx_t_14); - PyTuple_SET_ITEM(__pyx_t_16, 2, __pyx_t_14); - __pyx_t_6 = 0; - __pyx_t_2 = 0; - __pyx_t_14 = 0; - __Pyx_XDECREF_SET(__pyx_v_Ldata, ((PyObject*)__pyx_t_16)); - __pyx_t_16 = 0; - - /* "src/radon.pyx":108 - * Ldata = ([0.0], [0.0], [0.0]) - * - * cradon.radon2d( np.PyArray_DATA(Tx), np.PyArray_DATA(Rx), nTx, nx, ny, Ldata) # <<<<<<<<<<<<<< - * - * M = nTx - */ - if (!(likely(((__pyx_v_Tx) == Py_None) || likely(__Pyx_TypeTest(__pyx_v_Tx, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 108, __pyx_L1_error) - if (!(likely(((__pyx_v_Rx) == Py_None) || likely(__Pyx_TypeTest(__pyx_v_Rx, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 108, __pyx_L1_error) - __pyx_t_8 = __Pyx_PyInt_As_size_t(__pyx_v_nx); if (unlikely((__pyx_t_8 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 108, __pyx_L1_error) - __pyx_t_17 = __Pyx_PyInt_As_size_t(__pyx_v_ny); if (unlikely((__pyx_t_17 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 108, __pyx_L1_error) - (void)(radon2d(((double *)PyArray_DATA(((PyArrayObject *)__pyx_v_Tx))), ((double *)PyArray_DATA(((PyArrayObject *)__pyx_v_Rx))), __pyx_v_nTx, __pyx_t_8, __pyx_t_17, __pyx_v_Ldata)); - - /* "src/radon.pyx":110 - * cradon.radon2d( np.PyArray_DATA(Tx), np.PyArray_DATA(Rx), nTx, nx, ny, Ldata) - * - * M = nTx # <<<<<<<<<<<<<< - * N = nx * ny - * L = csr_matrix(Ldata, shape=(M,N)) - */ - __pyx_v_M = __pyx_v_nTx; - - /* "src/radon.pyx":111 - * - * M = nTx - * N = nx * ny # <<<<<<<<<<<<<< - * L = csr_matrix(Ldata, shape=(M,N)) - * - */ - __pyx_t_16 = PyNumber_Multiply(__pyx_v_nx, __pyx_v_ny); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 111, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __Pyx_XDECREF_SET(__pyx_v_N, __pyx_t_16); - __pyx_t_16 = 0; - - /* "src/radon.pyx":112 - * M = nTx - * N = nx * ny - * L = csr_matrix(Ldata, shape=(M,N)) # <<<<<<<<<<<<<< - * - * p = L.dot(data.flatten()) - */ - __Pyx_GetModuleGlobalName(__pyx_t_16, __pyx_n_s_csr_matrix); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 112, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __pyx_t_14 = PyTuple_New(1); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 112, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_INCREF(__pyx_v_Ldata); - __Pyx_GIVEREF(__pyx_v_Ldata); - PyTuple_SET_ITEM(__pyx_t_14, 0, __pyx_v_Ldata); - __pyx_t_2 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 112, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_6 = __Pyx_PyInt_FromSize_t(__pyx_v_M); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 112, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_6); - __pyx_t_4 = PyTuple_New(2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 112, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_GIVEREF(__pyx_t_6); - PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_t_6); - __Pyx_INCREF(__pyx_v_N); - __Pyx_GIVEREF(__pyx_v_N); - PyTuple_SET_ITEM(__pyx_t_4, 1, __pyx_v_N); - __pyx_t_6 = 0; - if (PyDict_SetItem(__pyx_t_2, __pyx_n_s_shape, __pyx_t_4) < 0) __PYX_ERR(0, 112, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_16, __pyx_t_14, __pyx_t_2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 112, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_XDECREF_SET(__pyx_v_L, __pyx_t_4); - __pyx_t_4 = 0; - - /* "src/radon.pyx":114 - * L = csr_matrix(Ldata, shape=(M,N)) - * - * p = L.dot(data.flatten()) # <<<<<<<<<<<<<< - * sinogram[:,nt] = p - * - */ - __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_L, __pyx_n_s_dot); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 114, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __pyx_t_16 = __Pyx_PyObject_GetAttrStr(__pyx_v_data, __pyx_n_s_flatten); if (unlikely(!__pyx_t_16)) __PYX_ERR(0, 114, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_16); - __pyx_t_6 = NULL; - if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_16))) { - __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_16); - if (likely(__pyx_t_6)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_16); - __Pyx_INCREF(__pyx_t_6); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_16, function); - } - } - __pyx_t_14 = (__pyx_t_6) ? __Pyx_PyObject_CallOneArg(__pyx_t_16, __pyx_t_6) : __Pyx_PyObject_CallNoArg(__pyx_t_16); - __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0; - if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 114, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_14); - __Pyx_DECREF(__pyx_t_16); __pyx_t_16 = 0; - __pyx_t_16 = NULL; - if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_2))) { - __pyx_t_16 = PyMethod_GET_SELF(__pyx_t_2); - if (likely(__pyx_t_16)) { - PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2); - __Pyx_INCREF(__pyx_t_16); - __Pyx_INCREF(function); - __Pyx_DECREF_SET(__pyx_t_2, function); - } - } - __pyx_t_4 = (__pyx_t_16) ? __Pyx_PyObject_Call2Args(__pyx_t_2, __pyx_t_16, __pyx_t_14) : __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_t_14); - __Pyx_XDECREF(__pyx_t_16); __pyx_t_16 = 0; - __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0; - if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 114, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_XDECREF_SET(__pyx_v_p, __pyx_t_4); - __pyx_t_4 = 0; - - /* "src/radon.pyx":115 - * - * p = L.dot(data.flatten()) - * sinogram[:,nt] = p # <<<<<<<<<<<<<< - * - * - */ - __pyx_t_4 = PyTuple_New(2); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 115, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_INCREF(__pyx_slice__3); - __Pyx_GIVEREF(__pyx_slice__3); - PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_slice__3); - __Pyx_INCREF(__pyx_v_nt); - __Pyx_GIVEREF(__pyx_v_nt); - PyTuple_SET_ITEM(__pyx_t_4, 1, __pyx_v_nt); - if (unlikely(PyObject_SetItem(__pyx_v_sinogram, __pyx_t_4, __pyx_v_p) < 0)) __PYX_ERR(0, 115, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - - /* "src/radon.pyx":30 - * - * - * for nt in range(theta.size): # <<<<<<<<<<<<<< - * - * if theta[nt] == 0.0: - */ - } - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - - /* "src/radon.pyx":120 - * - * - * return sinogram # <<<<<<<<<<<<<< - */ - __Pyx_XDECREF(__pyx_r); - __Pyx_INCREF(__pyx_v_sinogram); - __pyx_r = __pyx_v_sinogram; - goto __pyx_L0; - - /* "src/radon.pyx":12 - * except: pass - * - * def radon2d(data, theta): # <<<<<<<<<<<<<< - * - * if np.min(theta) < 0.0 or np.max(theta) >= np.pi: - */ - - /* function exit code */ - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_2); - __Pyx_XDECREF(__pyx_t_3); - __Pyx_XDECREF(__pyx_t_4); - __Pyx_XDECREF(__pyx_t_6); - __Pyx_XDECREF(__pyx_t_13); - __Pyx_XDECREF(__pyx_t_14); - __Pyx_XDECREF(__pyx_t_15); - __Pyx_XDECREF(__pyx_t_16); - __Pyx_AddTraceback("pycurious.radon.radon2d", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = NULL; - __pyx_L0:; - __Pyx_XDECREF(__pyx_v_nx); - __Pyx_XDECREF(__pyx_v_ny); - __Pyx_XDECREF(__pyx_v_nw); - __Pyx_XDECREF(__pyx_v_sinogram); - __Pyx_XDECREF(__pyx_v_Tx); - __Pyx_XDECREF(__pyx_v_Rx); - __Pyx_XDECREF(__pyx_v_nt); - __Pyx_XDECREF(__pyx_v_n); - __Pyx_XDECREF(__pyx_v_xs); - __Pyx_XDECREF(__pyx_v_ys); - __Pyx_XDECREF(__pyx_v_xk); - __Pyx_XDECREF(__pyx_v_yk); - __Pyx_XDECREF(__pyx_v_x0); - __Pyx_XDECREF(__pyx_v_y0); - __Pyx_XDECREF(__pyx_v_x1); - __Pyx_XDECREF(__pyx_v_y1); - __Pyx_XDECREF(__pyx_v_Ldata); - __Pyx_XDECREF(__pyx_v_N); - __Pyx_XDECREF(__pyx_v_L); - __Pyx_XDECREF(__pyx_v_p); - __Pyx_XGIVEREF(__pyx_r); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":258 - * # experimental exception made for __getbuffer__ and __releasebuffer__ - * # -- the details of this may change. - * def __getbuffer__(ndarray self, Py_buffer* info, int flags): # <<<<<<<<<<<<<< - * # This implementation of getbuffer is geared towards Cython - * # requirements, and does not yet fulfill the PEP. - */ - -/* Python wrapper */ -static CYTHON_UNUSED int __pyx_pw_5numpy_7ndarray_1__getbuffer__(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags); /*proto*/ -static CYTHON_UNUSED int __pyx_pw_5numpy_7ndarray_1__getbuffer__(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags) { - int __pyx_r; - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__getbuffer__ (wrapper)", 0); - __pyx_r = __pyx_pf_5numpy_7ndarray___getbuffer__(((PyArrayObject *)__pyx_v_self), ((Py_buffer *)__pyx_v_info), ((int)__pyx_v_flags)); - - /* function exit code */ - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -static int __pyx_pf_5numpy_7ndarray___getbuffer__(PyArrayObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags) { - int __pyx_v_i; - int __pyx_v_ndim; - int __pyx_v_endian_detector; - int __pyx_v_little_endian; - int __pyx_v_t; - char *__pyx_v_f; - PyArray_Descr *__pyx_v_descr = 0; - int __pyx_v_offset; - int __pyx_r; - __Pyx_RefNannyDeclarations - int __pyx_t_1; - int __pyx_t_2; - PyObject *__pyx_t_3 = NULL; - int __pyx_t_4; - int __pyx_t_5; - int __pyx_t_6; - PyArray_Descr *__pyx_t_7; - PyObject *__pyx_t_8 = NULL; - char *__pyx_t_9; - if (__pyx_v_info == NULL) { - PyErr_SetString(PyExc_BufferError, "PyObject_GetBuffer: view==NULL argument is obsolete"); - return -1; - } - __Pyx_RefNannySetupContext("__getbuffer__", 0); - __pyx_v_info->obj = Py_None; __Pyx_INCREF(Py_None); - __Pyx_GIVEREF(__pyx_v_info->obj); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":265 - * - * cdef int i, ndim - * cdef int endian_detector = 1 # <<<<<<<<<<<<<< - * cdef bint little_endian = ((&endian_detector)[0] != 0) - * - */ - __pyx_v_endian_detector = 1; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":266 - * cdef int i, ndim - * cdef int endian_detector = 1 - * cdef bint little_endian = ((&endian_detector)[0] != 0) # <<<<<<<<<<<<<< - * - * ndim = PyArray_NDIM(self) - */ - __pyx_v_little_endian = ((((char *)(&__pyx_v_endian_detector))[0]) != 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":268 - * cdef bint little_endian = ((&endian_detector)[0] != 0) - * - * ndim = PyArray_NDIM(self) # <<<<<<<<<<<<<< - * - * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) - */ - __pyx_v_ndim = PyArray_NDIM(__pyx_v_self); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":270 - * ndim = PyArray_NDIM(self) - * - * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) # <<<<<<<<<<<<<< - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): - * raise ValueError(u"ndarray is not C contiguous") - */ - __pyx_t_2 = (((__pyx_v_flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) != 0); - if (__pyx_t_2) { - } else { - __pyx_t_1 = __pyx_t_2; - goto __pyx_L4_bool_binop_done; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":271 - * - * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): # <<<<<<<<<<<<<< - * raise ValueError(u"ndarray is not C contiguous") - * - */ - __pyx_t_2 = ((!(PyArray_CHKFLAGS(__pyx_v_self, NPY_ARRAY_C_CONTIGUOUS) != 0)) != 0); - __pyx_t_1 = __pyx_t_2; - __pyx_L4_bool_binop_done:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":270 - * ndim = PyArray_NDIM(self) - * - * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) # <<<<<<<<<<<<<< - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): - * raise ValueError(u"ndarray is not C contiguous") - */ - if (unlikely(__pyx_t_1)) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":272 - * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): - * raise ValueError(u"ndarray is not C contiguous") # <<<<<<<<<<<<<< - * - * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) - */ - __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__4, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 272, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_Raise(__pyx_t_3, 0, 0, 0); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __PYX_ERR(1, 272, __pyx_L1_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":270 - * ndim = PyArray_NDIM(self) - * - * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) # <<<<<<<<<<<<<< - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): - * raise ValueError(u"ndarray is not C contiguous") - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":274 - * raise ValueError(u"ndarray is not C contiguous") - * - * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) # <<<<<<<<<<<<<< - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): - * raise ValueError(u"ndarray is not Fortran contiguous") - */ - __pyx_t_2 = (((__pyx_v_flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) != 0); - if (__pyx_t_2) { - } else { - __pyx_t_1 = __pyx_t_2; - goto __pyx_L7_bool_binop_done; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":275 - * - * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): # <<<<<<<<<<<<<< - * raise ValueError(u"ndarray is not Fortran contiguous") - * - */ - __pyx_t_2 = ((!(PyArray_CHKFLAGS(__pyx_v_self, NPY_ARRAY_F_CONTIGUOUS) != 0)) != 0); - __pyx_t_1 = __pyx_t_2; - __pyx_L7_bool_binop_done:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":274 - * raise ValueError(u"ndarray is not C contiguous") - * - * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) # <<<<<<<<<<<<<< - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): - * raise ValueError(u"ndarray is not Fortran contiguous") - */ - if (unlikely(__pyx_t_1)) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":276 - * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): - * raise ValueError(u"ndarray is not Fortran contiguous") # <<<<<<<<<<<<<< - * - * info.buf = PyArray_DATA(self) - */ - __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__5, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 276, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_Raise(__pyx_t_3, 0, 0, 0); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __PYX_ERR(1, 276, __pyx_L1_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":274 - * raise ValueError(u"ndarray is not C contiguous") - * - * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) # <<<<<<<<<<<<<< - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): - * raise ValueError(u"ndarray is not Fortran contiguous") - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":278 - * raise ValueError(u"ndarray is not Fortran contiguous") - * - * info.buf = PyArray_DATA(self) # <<<<<<<<<<<<<< - * info.ndim = ndim - * if sizeof(npy_intp) != sizeof(Py_ssize_t): - */ - __pyx_v_info->buf = PyArray_DATA(__pyx_v_self); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":279 - * - * info.buf = PyArray_DATA(self) - * info.ndim = ndim # <<<<<<<<<<<<<< - * if sizeof(npy_intp) != sizeof(Py_ssize_t): - * # Allocate new buffer for strides and shape info. - */ - __pyx_v_info->ndim = __pyx_v_ndim; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":280 - * info.buf = PyArray_DATA(self) - * info.ndim = ndim - * if sizeof(npy_intp) != sizeof(Py_ssize_t): # <<<<<<<<<<<<<< - * # Allocate new buffer for strides and shape info. - * # This is allocated as one block, strides first. - */ - __pyx_t_1 = (((sizeof(npy_intp)) != (sizeof(Py_ssize_t))) != 0); - if (__pyx_t_1) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":283 - * # Allocate new buffer for strides and shape info. - * # This is allocated as one block, strides first. - * info.strides = PyObject_Malloc(sizeof(Py_ssize_t) * 2 * ndim) # <<<<<<<<<<<<<< - * info.shape = info.strides + ndim - * for i in range(ndim): - */ - __pyx_v_info->strides = ((Py_ssize_t *)PyObject_Malloc((((sizeof(Py_ssize_t)) * 2) * ((size_t)__pyx_v_ndim)))); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":284 - * # This is allocated as one block, strides first. - * info.strides = PyObject_Malloc(sizeof(Py_ssize_t) * 2 * ndim) - * info.shape = info.strides + ndim # <<<<<<<<<<<<<< - * for i in range(ndim): - * info.strides[i] = PyArray_STRIDES(self)[i] - */ - __pyx_v_info->shape = (__pyx_v_info->strides + __pyx_v_ndim); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":285 - * info.strides = PyObject_Malloc(sizeof(Py_ssize_t) * 2 * ndim) - * info.shape = info.strides + ndim - * for i in range(ndim): # <<<<<<<<<<<<<< - * info.strides[i] = PyArray_STRIDES(self)[i] - * info.shape[i] = PyArray_DIMS(self)[i] - */ - __pyx_t_4 = __pyx_v_ndim; - __pyx_t_5 = __pyx_t_4; - for (__pyx_t_6 = 0; __pyx_t_6 < __pyx_t_5; __pyx_t_6+=1) { - __pyx_v_i = __pyx_t_6; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":286 - * info.shape = info.strides + ndim - * for i in range(ndim): - * info.strides[i] = PyArray_STRIDES(self)[i] # <<<<<<<<<<<<<< - * info.shape[i] = PyArray_DIMS(self)[i] - * else: - */ - (__pyx_v_info->strides[__pyx_v_i]) = (PyArray_STRIDES(__pyx_v_self)[__pyx_v_i]); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":287 - * for i in range(ndim): - * info.strides[i] = PyArray_STRIDES(self)[i] - * info.shape[i] = PyArray_DIMS(self)[i] # <<<<<<<<<<<<<< - * else: - * info.strides = PyArray_STRIDES(self) - */ - (__pyx_v_info->shape[__pyx_v_i]) = (PyArray_DIMS(__pyx_v_self)[__pyx_v_i]); - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":280 - * info.buf = PyArray_DATA(self) - * info.ndim = ndim - * if sizeof(npy_intp) != sizeof(Py_ssize_t): # <<<<<<<<<<<<<< - * # Allocate new buffer for strides and shape info. - * # This is allocated as one block, strides first. - */ - goto __pyx_L9; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":289 - * info.shape[i] = PyArray_DIMS(self)[i] - * else: - * info.strides = PyArray_STRIDES(self) # <<<<<<<<<<<<<< - * info.shape = PyArray_DIMS(self) - * info.suboffsets = NULL - */ - /*else*/ { - __pyx_v_info->strides = ((Py_ssize_t *)PyArray_STRIDES(__pyx_v_self)); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":290 - * else: - * info.strides = PyArray_STRIDES(self) - * info.shape = PyArray_DIMS(self) # <<<<<<<<<<<<<< - * info.suboffsets = NULL - * info.itemsize = PyArray_ITEMSIZE(self) - */ - __pyx_v_info->shape = ((Py_ssize_t *)PyArray_DIMS(__pyx_v_self)); - } - __pyx_L9:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":291 - * info.strides = PyArray_STRIDES(self) - * info.shape = PyArray_DIMS(self) - * info.suboffsets = NULL # <<<<<<<<<<<<<< - * info.itemsize = PyArray_ITEMSIZE(self) - * info.readonly = not PyArray_ISWRITEABLE(self) - */ - __pyx_v_info->suboffsets = NULL; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":292 - * info.shape = PyArray_DIMS(self) - * info.suboffsets = NULL - * info.itemsize = PyArray_ITEMSIZE(self) # <<<<<<<<<<<<<< - * info.readonly = not PyArray_ISWRITEABLE(self) - * - */ - __pyx_v_info->itemsize = PyArray_ITEMSIZE(__pyx_v_self); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":293 - * info.suboffsets = NULL - * info.itemsize = PyArray_ITEMSIZE(self) - * info.readonly = not PyArray_ISWRITEABLE(self) # <<<<<<<<<<<<<< - * - * cdef int t - */ - __pyx_v_info->readonly = (!(PyArray_ISWRITEABLE(__pyx_v_self) != 0)); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":296 - * - * cdef int t - * cdef char* f = NULL # <<<<<<<<<<<<<< - * cdef dtype descr = PyArray_DESCR(self) - * cdef int offset - */ - __pyx_v_f = NULL; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":297 - * cdef int t - * cdef char* f = NULL - * cdef dtype descr = PyArray_DESCR(self) # <<<<<<<<<<<<<< - * cdef int offset - * - */ - __pyx_t_7 = PyArray_DESCR(__pyx_v_self); - __pyx_t_3 = ((PyObject *)__pyx_t_7); - __Pyx_INCREF(__pyx_t_3); - __pyx_v_descr = ((PyArray_Descr *)__pyx_t_3); - __pyx_t_3 = 0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":300 - * cdef int offset - * - * info.obj = self # <<<<<<<<<<<<<< - * - * if not PyDataType_HASFIELDS(descr): - */ - __Pyx_INCREF(((PyObject *)__pyx_v_self)); - __Pyx_GIVEREF(((PyObject *)__pyx_v_self)); - __Pyx_GOTREF(__pyx_v_info->obj); - __Pyx_DECREF(__pyx_v_info->obj); - __pyx_v_info->obj = ((PyObject *)__pyx_v_self); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":302 - * info.obj = self - * - * if not PyDataType_HASFIELDS(descr): # <<<<<<<<<<<<<< - * t = descr.type_num - * if ((descr.byteorder == c'>' and little_endian) or - */ - __pyx_t_1 = ((!(PyDataType_HASFIELDS(__pyx_v_descr) != 0)) != 0); - if (__pyx_t_1) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":303 - * - * if not PyDataType_HASFIELDS(descr): - * t = descr.type_num # <<<<<<<<<<<<<< - * if ((descr.byteorder == c'>' and little_endian) or - * (descr.byteorder == c'<' and not little_endian)): - */ - __pyx_t_4 = __pyx_v_descr->type_num; - __pyx_v_t = __pyx_t_4; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":304 - * if not PyDataType_HASFIELDS(descr): - * t = descr.type_num - * if ((descr.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< - * (descr.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") - */ - __pyx_t_2 = ((__pyx_v_descr->byteorder == '>') != 0); - if (!__pyx_t_2) { - goto __pyx_L15_next_or; - } else { - } - __pyx_t_2 = (__pyx_v_little_endian != 0); - if (!__pyx_t_2) { - } else { - __pyx_t_1 = __pyx_t_2; - goto __pyx_L14_bool_binop_done; - } - __pyx_L15_next_or:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":305 - * t = descr.type_num - * if ((descr.byteorder == c'>' and little_endian) or - * (descr.byteorder == c'<' and not little_endian)): # <<<<<<<<<<<<<< - * raise ValueError(u"Non-native byte order not supported") - * if t == NPY_BYTE: f = "b" - */ - __pyx_t_2 = ((__pyx_v_descr->byteorder == '<') != 0); - if (__pyx_t_2) { - } else { - __pyx_t_1 = __pyx_t_2; - goto __pyx_L14_bool_binop_done; - } - __pyx_t_2 = ((!(__pyx_v_little_endian != 0)) != 0); - __pyx_t_1 = __pyx_t_2; - __pyx_L14_bool_binop_done:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":304 - * if not PyDataType_HASFIELDS(descr): - * t = descr.type_num - * if ((descr.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< - * (descr.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") - */ - if (unlikely(__pyx_t_1)) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":306 - * if ((descr.byteorder == c'>' and little_endian) or - * (descr.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") # <<<<<<<<<<<<<< - * if t == NPY_BYTE: f = "b" - * elif t == NPY_UBYTE: f = "B" - */ - __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__6, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 306, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_Raise(__pyx_t_3, 0, 0, 0); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __PYX_ERR(1, 306, __pyx_L1_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":304 - * if not PyDataType_HASFIELDS(descr): - * t = descr.type_num - * if ((descr.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< - * (descr.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":307 - * (descr.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") - * if t == NPY_BYTE: f = "b" # <<<<<<<<<<<<<< - * elif t == NPY_UBYTE: f = "B" - * elif t == NPY_SHORT: f = "h" - */ - switch (__pyx_v_t) { - case NPY_BYTE: - __pyx_v_f = ((char *)"b"); - break; - case NPY_UBYTE: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":308 - * raise ValueError(u"Non-native byte order not supported") - * if t == NPY_BYTE: f = "b" - * elif t == NPY_UBYTE: f = "B" # <<<<<<<<<<<<<< - * elif t == NPY_SHORT: f = "h" - * elif t == NPY_USHORT: f = "H" - */ - __pyx_v_f = ((char *)"B"); - break; - case NPY_SHORT: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":309 - * if t == NPY_BYTE: f = "b" - * elif t == NPY_UBYTE: f = "B" - * elif t == NPY_SHORT: f = "h" # <<<<<<<<<<<<<< - * elif t == NPY_USHORT: f = "H" - * elif t == NPY_INT: f = "i" - */ - __pyx_v_f = ((char *)"h"); - break; - case NPY_USHORT: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":310 - * elif t == NPY_UBYTE: f = "B" - * elif t == NPY_SHORT: f = "h" - * elif t == NPY_USHORT: f = "H" # <<<<<<<<<<<<<< - * elif t == NPY_INT: f = "i" - * elif t == NPY_UINT: f = "I" - */ - __pyx_v_f = ((char *)"H"); - break; - case NPY_INT: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":311 - * elif t == NPY_SHORT: f = "h" - * elif t == NPY_USHORT: f = "H" - * elif t == NPY_INT: f = "i" # <<<<<<<<<<<<<< - * elif t == NPY_UINT: f = "I" - * elif t == NPY_LONG: f = "l" - */ - __pyx_v_f = ((char *)"i"); - break; - case NPY_UINT: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":312 - * elif t == NPY_USHORT: f = "H" - * elif t == NPY_INT: f = "i" - * elif t == NPY_UINT: f = "I" # <<<<<<<<<<<<<< - * elif t == NPY_LONG: f = "l" - * elif t == NPY_ULONG: f = "L" - */ - __pyx_v_f = ((char *)"I"); - break; - case NPY_LONG: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":313 - * elif t == NPY_INT: f = "i" - * elif t == NPY_UINT: f = "I" - * elif t == NPY_LONG: f = "l" # <<<<<<<<<<<<<< - * elif t == NPY_ULONG: f = "L" - * elif t == NPY_LONGLONG: f = "q" - */ - __pyx_v_f = ((char *)"l"); - break; - case NPY_ULONG: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":314 - * elif t == NPY_UINT: f = "I" - * elif t == NPY_LONG: f = "l" - * elif t == NPY_ULONG: f = "L" # <<<<<<<<<<<<<< - * elif t == NPY_LONGLONG: f = "q" - * elif t == NPY_ULONGLONG: f = "Q" - */ - __pyx_v_f = ((char *)"L"); - break; - case NPY_LONGLONG: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":315 - * elif t == NPY_LONG: f = "l" - * elif t == NPY_ULONG: f = "L" - * elif t == NPY_LONGLONG: f = "q" # <<<<<<<<<<<<<< - * elif t == NPY_ULONGLONG: f = "Q" - * elif t == NPY_FLOAT: f = "f" - */ - __pyx_v_f = ((char *)"q"); - break; - case NPY_ULONGLONG: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":316 - * elif t == NPY_ULONG: f = "L" - * elif t == NPY_LONGLONG: f = "q" - * elif t == NPY_ULONGLONG: f = "Q" # <<<<<<<<<<<<<< - * elif t == NPY_FLOAT: f = "f" - * elif t == NPY_DOUBLE: f = "d" - */ - __pyx_v_f = ((char *)"Q"); - break; - case NPY_FLOAT: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":317 - * elif t == NPY_LONGLONG: f = "q" - * elif t == NPY_ULONGLONG: f = "Q" - * elif t == NPY_FLOAT: f = "f" # <<<<<<<<<<<<<< - * elif t == NPY_DOUBLE: f = "d" - * elif t == NPY_LONGDOUBLE: f = "g" - */ - __pyx_v_f = ((char *)"f"); - break; - case NPY_DOUBLE: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":318 - * elif t == NPY_ULONGLONG: f = "Q" - * elif t == NPY_FLOAT: f = "f" - * elif t == NPY_DOUBLE: f = "d" # <<<<<<<<<<<<<< - * elif t == NPY_LONGDOUBLE: f = "g" - * elif t == NPY_CFLOAT: f = "Zf" - */ - __pyx_v_f = ((char *)"d"); - break; - case NPY_LONGDOUBLE: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":319 - * elif t == NPY_FLOAT: f = "f" - * elif t == NPY_DOUBLE: f = "d" - * elif t == NPY_LONGDOUBLE: f = "g" # <<<<<<<<<<<<<< - * elif t == NPY_CFLOAT: f = "Zf" - * elif t == NPY_CDOUBLE: f = "Zd" - */ - __pyx_v_f = ((char *)"g"); - break; - case NPY_CFLOAT: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":320 - * elif t == NPY_DOUBLE: f = "d" - * elif t == NPY_LONGDOUBLE: f = "g" - * elif t == NPY_CFLOAT: f = "Zf" # <<<<<<<<<<<<<< - * elif t == NPY_CDOUBLE: f = "Zd" - * elif t == NPY_CLONGDOUBLE: f = "Zg" - */ - __pyx_v_f = ((char *)"Zf"); - break; - case NPY_CDOUBLE: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":321 - * elif t == NPY_LONGDOUBLE: f = "g" - * elif t == NPY_CFLOAT: f = "Zf" - * elif t == NPY_CDOUBLE: f = "Zd" # <<<<<<<<<<<<<< - * elif t == NPY_CLONGDOUBLE: f = "Zg" - * elif t == NPY_OBJECT: f = "O" - */ - __pyx_v_f = ((char *)"Zd"); - break; - case NPY_CLONGDOUBLE: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":322 - * elif t == NPY_CFLOAT: f = "Zf" - * elif t == NPY_CDOUBLE: f = "Zd" - * elif t == NPY_CLONGDOUBLE: f = "Zg" # <<<<<<<<<<<<<< - * elif t == NPY_OBJECT: f = "O" - * else: - */ - __pyx_v_f = ((char *)"Zg"); - break; - case NPY_OBJECT: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":323 - * elif t == NPY_CDOUBLE: f = "Zd" - * elif t == NPY_CLONGDOUBLE: f = "Zg" - * elif t == NPY_OBJECT: f = "O" # <<<<<<<<<<<<<< - * else: - * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) - */ - __pyx_v_f = ((char *)"O"); - break; - default: - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":325 - * elif t == NPY_OBJECT: f = "O" - * else: - * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) # <<<<<<<<<<<<<< - * info.format = f - * return - */ - __pyx_t_3 = __Pyx_PyInt_From_int(__pyx_v_t); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 325, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_8 = PyUnicode_Format(__pyx_kp_u_unknown_dtype_code_in_numpy_pxd, __pyx_t_3); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 325, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_8); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_3 = __Pyx_PyObject_CallOneArg(__pyx_builtin_ValueError, __pyx_t_8); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 325, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; - __Pyx_Raise(__pyx_t_3, 0, 0, 0); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __PYX_ERR(1, 325, __pyx_L1_error) - break; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":326 - * else: - * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) - * info.format = f # <<<<<<<<<<<<<< - * return - * else: - */ - __pyx_v_info->format = __pyx_v_f; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":327 - * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) - * info.format = f - * return # <<<<<<<<<<<<<< - * else: - * info.format = PyObject_Malloc(_buffer_format_string_len) - */ - __pyx_r = 0; - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":302 - * info.obj = self - * - * if not PyDataType_HASFIELDS(descr): # <<<<<<<<<<<<<< - * t = descr.type_num - * if ((descr.byteorder == c'>' and little_endian) or - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":329 - * return - * else: - * info.format = PyObject_Malloc(_buffer_format_string_len) # <<<<<<<<<<<<<< - * info.format[0] = c'^' # Native data types, manual alignment - * offset = 0 - */ - /*else*/ { - __pyx_v_info->format = ((char *)PyObject_Malloc(0xFF)); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":330 - * else: - * info.format = PyObject_Malloc(_buffer_format_string_len) - * info.format[0] = c'^' # Native data types, manual alignment # <<<<<<<<<<<<<< - * offset = 0 - * f = _util_dtypestring(descr, info.format + 1, - */ - (__pyx_v_info->format[0]) = '^'; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":331 - * info.format = PyObject_Malloc(_buffer_format_string_len) - * info.format[0] = c'^' # Native data types, manual alignment - * offset = 0 # <<<<<<<<<<<<<< - * f = _util_dtypestring(descr, info.format + 1, - * info.format + _buffer_format_string_len, - */ - __pyx_v_offset = 0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":332 - * info.format[0] = c'^' # Native data types, manual alignment - * offset = 0 - * f = _util_dtypestring(descr, info.format + 1, # <<<<<<<<<<<<<< - * info.format + _buffer_format_string_len, - * &offset) - */ - __pyx_t_9 = __pyx_f_5numpy__util_dtypestring(__pyx_v_descr, (__pyx_v_info->format + 1), (__pyx_v_info->format + 0xFF), (&__pyx_v_offset)); if (unlikely(__pyx_t_9 == ((char *)NULL))) __PYX_ERR(1, 332, __pyx_L1_error) - __pyx_v_f = __pyx_t_9; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":335 - * info.format + _buffer_format_string_len, - * &offset) - * f[0] = c'\0' # Terminate format string # <<<<<<<<<<<<<< - * - * def __releasebuffer__(ndarray self, Py_buffer* info): - */ - (__pyx_v_f[0]) = '\x00'; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":258 - * # experimental exception made for __getbuffer__ and __releasebuffer__ - * # -- the details of this may change. - * def __getbuffer__(ndarray self, Py_buffer* info, int flags): # <<<<<<<<<<<<<< - * # This implementation of getbuffer is geared towards Cython - * # requirements, and does not yet fulfill the PEP. - */ - - /* function exit code */ - __pyx_r = 0; - goto __pyx_L0; - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_3); - __Pyx_XDECREF(__pyx_t_8); - __Pyx_AddTraceback("numpy.ndarray.__getbuffer__", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = -1; - if (__pyx_v_info->obj != NULL) { - __Pyx_GOTREF(__pyx_v_info->obj); - __Pyx_DECREF(__pyx_v_info->obj); __pyx_v_info->obj = 0; - } - goto __pyx_L2; - __pyx_L0:; - if (__pyx_v_info->obj == Py_None) { - __Pyx_GOTREF(__pyx_v_info->obj); - __Pyx_DECREF(__pyx_v_info->obj); __pyx_v_info->obj = 0; - } - __pyx_L2:; - __Pyx_XDECREF((PyObject *)__pyx_v_descr); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":337 - * f[0] = c'\0' # Terminate format string - * - * def __releasebuffer__(ndarray self, Py_buffer* info): # <<<<<<<<<<<<<< - * if PyArray_HASFIELDS(self): - * PyObject_Free(info.format) - */ - -/* Python wrapper */ -static CYTHON_UNUSED void __pyx_pw_5numpy_7ndarray_3__releasebuffer__(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info); /*proto*/ -static CYTHON_UNUSED void __pyx_pw_5numpy_7ndarray_3__releasebuffer__(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__releasebuffer__ (wrapper)", 0); - __pyx_pf_5numpy_7ndarray_2__releasebuffer__(((PyArrayObject *)__pyx_v_self), ((Py_buffer *)__pyx_v_info)); - - /* function exit code */ - __Pyx_RefNannyFinishContext(); -} - -static void __pyx_pf_5numpy_7ndarray_2__releasebuffer__(PyArrayObject *__pyx_v_self, Py_buffer *__pyx_v_info) { - __Pyx_RefNannyDeclarations - int __pyx_t_1; - __Pyx_RefNannySetupContext("__releasebuffer__", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":338 - * - * def __releasebuffer__(ndarray self, Py_buffer* info): - * if PyArray_HASFIELDS(self): # <<<<<<<<<<<<<< - * PyObject_Free(info.format) - * if sizeof(npy_intp) != sizeof(Py_ssize_t): - */ - __pyx_t_1 = (PyArray_HASFIELDS(__pyx_v_self) != 0); - if (__pyx_t_1) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":339 - * def __releasebuffer__(ndarray self, Py_buffer* info): - * if PyArray_HASFIELDS(self): - * PyObject_Free(info.format) # <<<<<<<<<<<<<< - * if sizeof(npy_intp) != sizeof(Py_ssize_t): - * PyObject_Free(info.strides) - */ - PyObject_Free(__pyx_v_info->format); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":338 - * - * def __releasebuffer__(ndarray self, Py_buffer* info): - * if PyArray_HASFIELDS(self): # <<<<<<<<<<<<<< - * PyObject_Free(info.format) - * if sizeof(npy_intp) != sizeof(Py_ssize_t): - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":340 - * if PyArray_HASFIELDS(self): - * PyObject_Free(info.format) - * if sizeof(npy_intp) != sizeof(Py_ssize_t): # <<<<<<<<<<<<<< - * PyObject_Free(info.strides) - * # info.shape was stored after info.strides in the same block - */ - __pyx_t_1 = (((sizeof(npy_intp)) != (sizeof(Py_ssize_t))) != 0); - if (__pyx_t_1) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":341 - * PyObject_Free(info.format) - * if sizeof(npy_intp) != sizeof(Py_ssize_t): - * PyObject_Free(info.strides) # <<<<<<<<<<<<<< - * # info.shape was stored after info.strides in the same block - * - */ - PyObject_Free(__pyx_v_info->strides); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":340 - * if PyArray_HASFIELDS(self): - * PyObject_Free(info.format) - * if sizeof(npy_intp) != sizeof(Py_ssize_t): # <<<<<<<<<<<<<< - * PyObject_Free(info.strides) - * # info.shape was stored after info.strides in the same block - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":337 - * f[0] = c'\0' # Terminate format string - * - * def __releasebuffer__(ndarray self, Py_buffer* info): # <<<<<<<<<<<<<< - * if PyArray_HASFIELDS(self): - * PyObject_Free(info.format) - */ - - /* function exit code */ - __Pyx_RefNannyFinishContext(); -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":821 - * ctypedef npy_cdouble complex_t - * - * cdef inline object PyArray_MultiIterNew1(a): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(1, a) - * - */ - -static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__pyx_v_a) { - PyObject *__pyx_r = NULL; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - __Pyx_RefNannySetupContext("PyArray_MultiIterNew1", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":822 - * - * cdef inline object PyArray_MultiIterNew1(a): - * return PyArray_MultiIterNew(1, a) # <<<<<<<<<<<<<< - * - * cdef inline object PyArray_MultiIterNew2(a, b): - */ - __Pyx_XDECREF(__pyx_r); - __pyx_t_1 = PyArray_MultiIterNew(1, ((void *)__pyx_v_a)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 822, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - __pyx_r = __pyx_t_1; - __pyx_t_1 = 0; - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":821 - * ctypedef npy_cdouble complex_t - * - * cdef inline object PyArray_MultiIterNew1(a): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(1, a) - * - */ - - /* function exit code */ - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("numpy.PyArray_MultiIterNew1", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = 0; - __pyx_L0:; - __Pyx_XGIVEREF(__pyx_r); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":824 - * return PyArray_MultiIterNew(1, a) - * - * cdef inline object PyArray_MultiIterNew2(a, b): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(2, a, b) - * - */ - -static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__pyx_v_a, PyObject *__pyx_v_b) { - PyObject *__pyx_r = NULL; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - __Pyx_RefNannySetupContext("PyArray_MultiIterNew2", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":825 - * - * cdef inline object PyArray_MultiIterNew2(a, b): - * return PyArray_MultiIterNew(2, a, b) # <<<<<<<<<<<<<< - * - * cdef inline object PyArray_MultiIterNew3(a, b, c): - */ - __Pyx_XDECREF(__pyx_r); - __pyx_t_1 = PyArray_MultiIterNew(2, ((void *)__pyx_v_a), ((void *)__pyx_v_b)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 825, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - __pyx_r = __pyx_t_1; - __pyx_t_1 = 0; - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":824 - * return PyArray_MultiIterNew(1, a) - * - * cdef inline object PyArray_MultiIterNew2(a, b): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(2, a, b) - * - */ - - /* function exit code */ - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("numpy.PyArray_MultiIterNew2", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = 0; - __pyx_L0:; - __Pyx_XGIVEREF(__pyx_r); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":827 - * return PyArray_MultiIterNew(2, a, b) - * - * cdef inline object PyArray_MultiIterNew3(a, b, c): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(3, a, b, c) - * - */ - -static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c) { - PyObject *__pyx_r = NULL; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - __Pyx_RefNannySetupContext("PyArray_MultiIterNew3", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":828 - * - * cdef inline object PyArray_MultiIterNew3(a, b, c): - * return PyArray_MultiIterNew(3, a, b, c) # <<<<<<<<<<<<<< - * - * cdef inline object PyArray_MultiIterNew4(a, b, c, d): - */ - __Pyx_XDECREF(__pyx_r); - __pyx_t_1 = PyArray_MultiIterNew(3, ((void *)__pyx_v_a), ((void *)__pyx_v_b), ((void *)__pyx_v_c)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 828, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - __pyx_r = __pyx_t_1; - __pyx_t_1 = 0; - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":827 - * return PyArray_MultiIterNew(2, a, b) - * - * cdef inline object PyArray_MultiIterNew3(a, b, c): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(3, a, b, c) - * - */ - - /* function exit code */ - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("numpy.PyArray_MultiIterNew3", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = 0; - __pyx_L0:; - __Pyx_XGIVEREF(__pyx_r); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":830 - * return PyArray_MultiIterNew(3, a, b, c) - * - * cdef inline object PyArray_MultiIterNew4(a, b, c, d): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(4, a, b, c, d) - * - */ - -static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c, PyObject *__pyx_v_d) { - PyObject *__pyx_r = NULL; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - __Pyx_RefNannySetupContext("PyArray_MultiIterNew4", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":831 - * - * cdef inline object PyArray_MultiIterNew4(a, b, c, d): - * return PyArray_MultiIterNew(4, a, b, c, d) # <<<<<<<<<<<<<< - * - * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e): - */ - __Pyx_XDECREF(__pyx_r); - __pyx_t_1 = PyArray_MultiIterNew(4, ((void *)__pyx_v_a), ((void *)__pyx_v_b), ((void *)__pyx_v_c), ((void *)__pyx_v_d)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 831, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - __pyx_r = __pyx_t_1; - __pyx_t_1 = 0; - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":830 - * return PyArray_MultiIterNew(3, a, b, c) - * - * cdef inline object PyArray_MultiIterNew4(a, b, c, d): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(4, a, b, c, d) - * - */ - - /* function exit code */ - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("numpy.PyArray_MultiIterNew4", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = 0; - __pyx_L0:; - __Pyx_XGIVEREF(__pyx_r); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":833 - * return PyArray_MultiIterNew(4, a, b, c, d) - * - * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(5, a, b, c, d, e) - * - */ - -static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c, PyObject *__pyx_v_d, PyObject *__pyx_v_e) { - PyObject *__pyx_r = NULL; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - __Pyx_RefNannySetupContext("PyArray_MultiIterNew5", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":834 - * - * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e): - * return PyArray_MultiIterNew(5, a, b, c, d, e) # <<<<<<<<<<<<<< - * - * cdef inline tuple PyDataType_SHAPE(dtype d): - */ - __Pyx_XDECREF(__pyx_r); - __pyx_t_1 = PyArray_MultiIterNew(5, ((void *)__pyx_v_a), ((void *)__pyx_v_b), ((void *)__pyx_v_c), ((void *)__pyx_v_d), ((void *)__pyx_v_e)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 834, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - __pyx_r = __pyx_t_1; - __pyx_t_1 = 0; - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":833 - * return PyArray_MultiIterNew(4, a, b, c, d) - * - * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e): # <<<<<<<<<<<<<< - * return PyArray_MultiIterNew(5, a, b, c, d, e) - * - */ - - /* function exit code */ - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("numpy.PyArray_MultiIterNew5", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = 0; - __pyx_L0:; - __Pyx_XGIVEREF(__pyx_r); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":836 - * return PyArray_MultiIterNew(5, a, b, c, d, e) - * - * cdef inline tuple PyDataType_SHAPE(dtype d): # <<<<<<<<<<<<<< - * if PyDataType_HASSUBARRAY(d): - * return d.subarray.shape - */ - -static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__pyx_v_d) { - PyObject *__pyx_r = NULL; - __Pyx_RefNannyDeclarations - int __pyx_t_1; - __Pyx_RefNannySetupContext("PyDataType_SHAPE", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":837 - * - * cdef inline tuple PyDataType_SHAPE(dtype d): - * if PyDataType_HASSUBARRAY(d): # <<<<<<<<<<<<<< - * return d.subarray.shape - * else: - */ - __pyx_t_1 = (PyDataType_HASSUBARRAY(__pyx_v_d) != 0); - if (__pyx_t_1) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":838 - * cdef inline tuple PyDataType_SHAPE(dtype d): - * if PyDataType_HASSUBARRAY(d): - * return d.subarray.shape # <<<<<<<<<<<<<< - * else: - * return () - */ - __Pyx_XDECREF(__pyx_r); - __Pyx_INCREF(((PyObject*)__pyx_v_d->subarray->shape)); - __pyx_r = ((PyObject*)__pyx_v_d->subarray->shape); - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":837 - * - * cdef inline tuple PyDataType_SHAPE(dtype d): - * if PyDataType_HASSUBARRAY(d): # <<<<<<<<<<<<<< - * return d.subarray.shape - * else: - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":840 - * return d.subarray.shape - * else: - * return () # <<<<<<<<<<<<<< - * - * cdef inline char* _util_dtypestring(dtype descr, char* f, char* end, int* offset) except NULL: - */ - /*else*/ { - __Pyx_XDECREF(__pyx_r); - __Pyx_INCREF(__pyx_empty_tuple); - __pyx_r = __pyx_empty_tuple; - goto __pyx_L0; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":836 - * return PyArray_MultiIterNew(5, a, b, c, d, e) - * - * cdef inline tuple PyDataType_SHAPE(dtype d): # <<<<<<<<<<<<<< - * if PyDataType_HASSUBARRAY(d): - * return d.subarray.shape - */ - - /* function exit code */ - __pyx_L0:; - __Pyx_XGIVEREF(__pyx_r); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":842 - * return () - * - * cdef inline char* _util_dtypestring(dtype descr, char* f, char* end, int* offset) except NULL: # <<<<<<<<<<<<<< - * # Recursive utility function used in __getbuffer__ to get format - * # string. The new location in the format string is returned. - */ - -static CYTHON_INLINE char *__pyx_f_5numpy__util_dtypestring(PyArray_Descr *__pyx_v_descr, char *__pyx_v_f, char *__pyx_v_end, int *__pyx_v_offset) { - PyArray_Descr *__pyx_v_child = 0; - int __pyx_v_endian_detector; - int __pyx_v_little_endian; - PyObject *__pyx_v_fields = 0; - PyObject *__pyx_v_childname = NULL; - PyObject *__pyx_v_new_offset = NULL; - PyObject *__pyx_v_t = NULL; - char *__pyx_r; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - Py_ssize_t __pyx_t_2; - PyObject *__pyx_t_3 = NULL; - PyObject *__pyx_t_4 = NULL; - int __pyx_t_5; - int __pyx_t_6; - int __pyx_t_7; - long __pyx_t_8; - char *__pyx_t_9; - __Pyx_RefNannySetupContext("_util_dtypestring", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":847 - * - * cdef dtype child - * cdef int endian_detector = 1 # <<<<<<<<<<<<<< - * cdef bint little_endian = ((&endian_detector)[0] != 0) - * cdef tuple fields - */ - __pyx_v_endian_detector = 1; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":848 - * cdef dtype child - * cdef int endian_detector = 1 - * cdef bint little_endian = ((&endian_detector)[0] != 0) # <<<<<<<<<<<<<< - * cdef tuple fields - * - */ - __pyx_v_little_endian = ((((char *)(&__pyx_v_endian_detector))[0]) != 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":851 - * cdef tuple fields - * - * for childname in descr.names: # <<<<<<<<<<<<<< - * fields = descr.fields[childname] - * child, new_offset = fields - */ - if (unlikely(__pyx_v_descr->names == Py_None)) { - PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable"); - __PYX_ERR(1, 851, __pyx_L1_error) - } - __pyx_t_1 = __pyx_v_descr->names; __Pyx_INCREF(__pyx_t_1); __pyx_t_2 = 0; - for (;;) { - if (__pyx_t_2 >= PyTuple_GET_SIZE(__pyx_t_1)) break; - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_3 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_2); __Pyx_INCREF(__pyx_t_3); __pyx_t_2++; if (unlikely(0 < 0)) __PYX_ERR(1, 851, __pyx_L1_error) - #else - __pyx_t_3 = PySequence_ITEM(__pyx_t_1, __pyx_t_2); __pyx_t_2++; if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 851, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - #endif - __Pyx_XDECREF_SET(__pyx_v_childname, __pyx_t_3); - __pyx_t_3 = 0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":852 - * - * for childname in descr.names: - * fields = descr.fields[childname] # <<<<<<<<<<<<<< - * child, new_offset = fields - * - */ - if (unlikely(__pyx_v_descr->fields == Py_None)) { - PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable"); - __PYX_ERR(1, 852, __pyx_L1_error) - } - __pyx_t_3 = __Pyx_PyDict_GetItem(__pyx_v_descr->fields, __pyx_v_childname); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 852, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - if (!(likely(PyTuple_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(1, 852, __pyx_L1_error) - __Pyx_XDECREF_SET(__pyx_v_fields, ((PyObject*)__pyx_t_3)); - __pyx_t_3 = 0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":853 - * for childname in descr.names: - * fields = descr.fields[childname] - * child, new_offset = fields # <<<<<<<<<<<<<< - * - * if (end - f) - (new_offset - offset[0]) < 15: - */ - if (likely(__pyx_v_fields != Py_None)) { - PyObject* sequence = __pyx_v_fields; - Py_ssize_t size = __Pyx_PySequence_SIZE(sequence); - if (unlikely(size != 2)) { - if (size > 2) __Pyx_RaiseTooManyValuesError(2); - else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size); - __PYX_ERR(1, 853, __pyx_L1_error) - } - #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - __pyx_t_3 = PyTuple_GET_ITEM(sequence, 0); - __pyx_t_4 = PyTuple_GET_ITEM(sequence, 1); - __Pyx_INCREF(__pyx_t_3); - __Pyx_INCREF(__pyx_t_4); - #else - __pyx_t_3 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 853, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 853, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - #endif - } else { - __Pyx_RaiseNoneNotIterableError(); __PYX_ERR(1, 853, __pyx_L1_error) - } - if (!(likely(((__pyx_t_3) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_3, __pyx_ptype_5numpy_dtype))))) __PYX_ERR(1, 853, __pyx_L1_error) - __Pyx_XDECREF_SET(__pyx_v_child, ((PyArray_Descr *)__pyx_t_3)); - __pyx_t_3 = 0; - __Pyx_XDECREF_SET(__pyx_v_new_offset, __pyx_t_4); - __pyx_t_4 = 0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":855 - * child, new_offset = fields - * - * if (end - f) - (new_offset - offset[0]) < 15: # <<<<<<<<<<<<<< - * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") - * - */ - __pyx_t_4 = __Pyx_PyInt_From_int((__pyx_v_offset[0])); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 855, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyNumber_Subtract(__pyx_v_new_offset, __pyx_t_4); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 855, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_5 = __Pyx_PyInt_As_int(__pyx_t_3); if (unlikely((__pyx_t_5 == (int)-1) && PyErr_Occurred())) __PYX_ERR(1, 855, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = ((((__pyx_v_end - __pyx_v_f) - ((int)__pyx_t_5)) < 15) != 0); - if (unlikely(__pyx_t_6)) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":856 - * - * if (end - f) - (new_offset - offset[0]) < 15: - * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") # <<<<<<<<<<<<<< - * - * if ((child.byteorder == c'>' and little_endian) or - */ - __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_RuntimeError, __pyx_tuple__7, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 856, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_Raise(__pyx_t_3, 0, 0, 0); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __PYX_ERR(1, 856, __pyx_L1_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":855 - * child, new_offset = fields - * - * if (end - f) - (new_offset - offset[0]) < 15: # <<<<<<<<<<<<<< - * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") - * - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":858 - * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") - * - * if ((child.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< - * (child.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") - */ - __pyx_t_7 = ((__pyx_v_child->byteorder == '>') != 0); - if (!__pyx_t_7) { - goto __pyx_L8_next_or; - } else { - } - __pyx_t_7 = (__pyx_v_little_endian != 0); - if (!__pyx_t_7) { - } else { - __pyx_t_6 = __pyx_t_7; - goto __pyx_L7_bool_binop_done; - } - __pyx_L8_next_or:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":859 - * - * if ((child.byteorder == c'>' and little_endian) or - * (child.byteorder == c'<' and not little_endian)): # <<<<<<<<<<<<<< - * raise ValueError(u"Non-native byte order not supported") - * # One could encode it in the format string and have Cython - */ - __pyx_t_7 = ((__pyx_v_child->byteorder == '<') != 0); - if (__pyx_t_7) { - } else { - __pyx_t_6 = __pyx_t_7; - goto __pyx_L7_bool_binop_done; - } - __pyx_t_7 = ((!(__pyx_v_little_endian != 0)) != 0); - __pyx_t_6 = __pyx_t_7; - __pyx_L7_bool_binop_done:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":858 - * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") - * - * if ((child.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< - * (child.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") - */ - if (unlikely(__pyx_t_6)) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":860 - * if ((child.byteorder == c'>' and little_endian) or - * (child.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") # <<<<<<<<<<<<<< - * # One could encode it in the format string and have Cython - * # complain instead, BUT: < and > in format strings also imply - */ - __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__6, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 860, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __Pyx_Raise(__pyx_t_3, 0, 0, 0); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __PYX_ERR(1, 860, __pyx_L1_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":858 - * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") - * - * if ((child.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< - * (child.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":870 - * - * # Output padding bytes - * while offset[0] < new_offset: # <<<<<<<<<<<<<< - * f[0] = 120 # "x"; pad byte - * f += 1 - */ - while (1) { - __pyx_t_3 = __Pyx_PyInt_From_int((__pyx_v_offset[0])); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 870, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_t_3, __pyx_v_new_offset, Py_LT); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 870, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 870, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (!__pyx_t_6) break; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":871 - * # Output padding bytes - * while offset[0] < new_offset: - * f[0] = 120 # "x"; pad byte # <<<<<<<<<<<<<< - * f += 1 - * offset[0] += 1 - */ - (__pyx_v_f[0]) = 0x78; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":872 - * while offset[0] < new_offset: - * f[0] = 120 # "x"; pad byte - * f += 1 # <<<<<<<<<<<<<< - * offset[0] += 1 - * - */ - __pyx_v_f = (__pyx_v_f + 1); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":873 - * f[0] = 120 # "x"; pad byte - * f += 1 - * offset[0] += 1 # <<<<<<<<<<<<<< - * - * offset[0] += child.itemsize - */ - __pyx_t_8 = 0; - (__pyx_v_offset[__pyx_t_8]) = ((__pyx_v_offset[__pyx_t_8]) + 1); - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":875 - * offset[0] += 1 - * - * offset[0] += child.itemsize # <<<<<<<<<<<<<< - * - * if not PyDataType_HASFIELDS(child): - */ - __pyx_t_8 = 0; - (__pyx_v_offset[__pyx_t_8]) = ((__pyx_v_offset[__pyx_t_8]) + __pyx_v_child->elsize); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":877 - * offset[0] += child.itemsize - * - * if not PyDataType_HASFIELDS(child): # <<<<<<<<<<<<<< - * t = child.type_num - * if end - f < 5: - */ - __pyx_t_6 = ((!(PyDataType_HASFIELDS(__pyx_v_child) != 0)) != 0); - if (__pyx_t_6) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":878 - * - * if not PyDataType_HASFIELDS(child): - * t = child.type_num # <<<<<<<<<<<<<< - * if end - f < 5: - * raise RuntimeError(u"Format string allocated too short.") - */ - __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_child->type_num); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 878, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_XDECREF_SET(__pyx_v_t, __pyx_t_4); - __pyx_t_4 = 0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":879 - * if not PyDataType_HASFIELDS(child): - * t = child.type_num - * if end - f < 5: # <<<<<<<<<<<<<< - * raise RuntimeError(u"Format string allocated too short.") - * - */ - __pyx_t_6 = (((__pyx_v_end - __pyx_v_f) < 5) != 0); - if (unlikely(__pyx_t_6)) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":880 - * t = child.type_num - * if end - f < 5: - * raise RuntimeError(u"Format string allocated too short.") # <<<<<<<<<<<<<< - * - * # Until ticket #99 is fixed, use integers to avoid warnings - */ - __pyx_t_4 = __Pyx_PyObject_Call(__pyx_builtin_RuntimeError, __pyx_tuple__8, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 880, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_Raise(__pyx_t_4, 0, 0, 0); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __PYX_ERR(1, 880, __pyx_L1_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":879 - * if not PyDataType_HASFIELDS(child): - * t = child.type_num - * if end - f < 5: # <<<<<<<<<<<<<< - * raise RuntimeError(u"Format string allocated too short.") - * - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":883 - * - * # Until ticket #99 is fixed, use integers to avoid warnings - * if t == NPY_BYTE: f[0] = 98 #"b" # <<<<<<<<<<<<<< - * elif t == NPY_UBYTE: f[0] = 66 #"B" - * elif t == NPY_SHORT: f[0] = 104 #"h" - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_BYTE); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 883, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 883, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 883, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 98; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":884 - * # Until ticket #99 is fixed, use integers to avoid warnings - * if t == NPY_BYTE: f[0] = 98 #"b" - * elif t == NPY_UBYTE: f[0] = 66 #"B" # <<<<<<<<<<<<<< - * elif t == NPY_SHORT: f[0] = 104 #"h" - * elif t == NPY_USHORT: f[0] = 72 #"H" - */ - __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_UBYTE); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 884, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 884, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 884, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 66; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":885 - * if t == NPY_BYTE: f[0] = 98 #"b" - * elif t == NPY_UBYTE: f[0] = 66 #"B" - * elif t == NPY_SHORT: f[0] = 104 #"h" # <<<<<<<<<<<<<< - * elif t == NPY_USHORT: f[0] = 72 #"H" - * elif t == NPY_INT: f[0] = 105 #"i" - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_SHORT); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 885, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 885, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 885, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 0x68; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":886 - * elif t == NPY_UBYTE: f[0] = 66 #"B" - * elif t == NPY_SHORT: f[0] = 104 #"h" - * elif t == NPY_USHORT: f[0] = 72 #"H" # <<<<<<<<<<<<<< - * elif t == NPY_INT: f[0] = 105 #"i" - * elif t == NPY_UINT: f[0] = 73 #"I" - */ - __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_USHORT); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 886, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 886, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 886, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 72; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":887 - * elif t == NPY_SHORT: f[0] = 104 #"h" - * elif t == NPY_USHORT: f[0] = 72 #"H" - * elif t == NPY_INT: f[0] = 105 #"i" # <<<<<<<<<<<<<< - * elif t == NPY_UINT: f[0] = 73 #"I" - * elif t == NPY_LONG: f[0] = 108 #"l" - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_INT); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 887, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 887, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 887, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 0x69; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":888 - * elif t == NPY_USHORT: f[0] = 72 #"H" - * elif t == NPY_INT: f[0] = 105 #"i" - * elif t == NPY_UINT: f[0] = 73 #"I" # <<<<<<<<<<<<<< - * elif t == NPY_LONG: f[0] = 108 #"l" - * elif t == NPY_ULONG: f[0] = 76 #"L" - */ - __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_UINT); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 888, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 888, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 888, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 73; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":889 - * elif t == NPY_INT: f[0] = 105 #"i" - * elif t == NPY_UINT: f[0] = 73 #"I" - * elif t == NPY_LONG: f[0] = 108 #"l" # <<<<<<<<<<<<<< - * elif t == NPY_ULONG: f[0] = 76 #"L" - * elif t == NPY_LONGLONG: f[0] = 113 #"q" - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_LONG); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 889, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 889, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 889, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 0x6C; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":890 - * elif t == NPY_UINT: f[0] = 73 #"I" - * elif t == NPY_LONG: f[0] = 108 #"l" - * elif t == NPY_ULONG: f[0] = 76 #"L" # <<<<<<<<<<<<<< - * elif t == NPY_LONGLONG: f[0] = 113 #"q" - * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" - */ - __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_ULONG); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 890, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 890, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 890, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 76; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":891 - * elif t == NPY_LONG: f[0] = 108 #"l" - * elif t == NPY_ULONG: f[0] = 76 #"L" - * elif t == NPY_LONGLONG: f[0] = 113 #"q" # <<<<<<<<<<<<<< - * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" - * elif t == NPY_FLOAT: f[0] = 102 #"f" - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_LONGLONG); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 891, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 891, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 891, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 0x71; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":892 - * elif t == NPY_ULONG: f[0] = 76 #"L" - * elif t == NPY_LONGLONG: f[0] = 113 #"q" - * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" # <<<<<<<<<<<<<< - * elif t == NPY_FLOAT: f[0] = 102 #"f" - * elif t == NPY_DOUBLE: f[0] = 100 #"d" - */ - __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_ULONGLONG); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 892, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 892, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 892, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 81; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":893 - * elif t == NPY_LONGLONG: f[0] = 113 #"q" - * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" - * elif t == NPY_FLOAT: f[0] = 102 #"f" # <<<<<<<<<<<<<< - * elif t == NPY_DOUBLE: f[0] = 100 #"d" - * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_FLOAT); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 893, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 893, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 893, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 0x66; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":894 - * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" - * elif t == NPY_FLOAT: f[0] = 102 #"f" - * elif t == NPY_DOUBLE: f[0] = 100 #"d" # <<<<<<<<<<<<<< - * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" - * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf - */ - __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_DOUBLE); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 894, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 894, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 894, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 0x64; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":895 - * elif t == NPY_FLOAT: f[0] = 102 #"f" - * elif t == NPY_DOUBLE: f[0] = 100 #"d" - * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" # <<<<<<<<<<<<<< - * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf - * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_LONGDOUBLE); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 895, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 895, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 895, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 0x67; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":896 - * elif t == NPY_DOUBLE: f[0] = 100 #"d" - * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" - * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf # <<<<<<<<<<<<<< - * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd - * elif t == NPY_CLONGDOUBLE: f[0] = 90; f[1] = 103; f += 1 # Zg - */ - __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_CFLOAT); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 896, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 896, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 896, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 90; - (__pyx_v_f[1]) = 0x66; - __pyx_v_f = (__pyx_v_f + 1); - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":897 - * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" - * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf - * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd # <<<<<<<<<<<<<< - * elif t == NPY_CLONGDOUBLE: f[0] = 90; f[1] = 103; f += 1 # Zg - * elif t == NPY_OBJECT: f[0] = 79 #"O" - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_CDOUBLE); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 897, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 897, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 897, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 90; - (__pyx_v_f[1]) = 0x64; - __pyx_v_f = (__pyx_v_f + 1); - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":898 - * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf - * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd - * elif t == NPY_CLONGDOUBLE: f[0] = 90; f[1] = 103; f += 1 # Zg # <<<<<<<<<<<<<< - * elif t == NPY_OBJECT: f[0] = 79 #"O" - * else: - */ - __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_CLONGDOUBLE); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 898, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 898, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 898, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - if (__pyx_t_6) { - (__pyx_v_f[0]) = 90; - (__pyx_v_f[1]) = 0x67; - __pyx_v_f = (__pyx_v_f + 1); - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":899 - * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd - * elif t == NPY_CLONGDOUBLE: f[0] = 90; f[1] = 103; f += 1 # Zg - * elif t == NPY_OBJECT: f[0] = 79 #"O" # <<<<<<<<<<<<<< - * else: - * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) - */ - __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_OBJECT); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 899, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 899, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 899, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (likely(__pyx_t_6)) { - (__pyx_v_f[0]) = 79; - goto __pyx_L15; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":901 - * elif t == NPY_OBJECT: f[0] = 79 #"O" - * else: - * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) # <<<<<<<<<<<<<< - * f += 1 - * else: - */ - /*else*/ { - __pyx_t_3 = __Pyx_PyUnicode_FormatSafe(__pyx_kp_u_unknown_dtype_code_in_numpy_pxd, __pyx_v_t); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 901, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_3); - __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_builtin_ValueError, __pyx_t_3); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 901, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_4); - __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - __Pyx_Raise(__pyx_t_4, 0, 0, 0); - __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - __PYX_ERR(1, 901, __pyx_L1_error) - } - __pyx_L15:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":902 - * else: - * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) - * f += 1 # <<<<<<<<<<<<<< - * else: - * # Cython ignores struct boundary information ("T{...}"), - */ - __pyx_v_f = (__pyx_v_f + 1); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":877 - * offset[0] += child.itemsize - * - * if not PyDataType_HASFIELDS(child): # <<<<<<<<<<<<<< - * t = child.type_num - * if end - f < 5: - */ - goto __pyx_L13; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":906 - * # Cython ignores struct boundary information ("T{...}"), - * # so don't output it - * f = _util_dtypestring(child, f, end, offset) # <<<<<<<<<<<<<< - * return f - * - */ - /*else*/ { - __pyx_t_9 = __pyx_f_5numpy__util_dtypestring(__pyx_v_child, __pyx_v_f, __pyx_v_end, __pyx_v_offset); if (unlikely(__pyx_t_9 == ((char *)NULL))) __PYX_ERR(1, 906, __pyx_L1_error) - __pyx_v_f = __pyx_t_9; - } - __pyx_L13:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":851 - * cdef tuple fields - * - * for childname in descr.names: # <<<<<<<<<<<<<< - * fields = descr.fields[childname] - * child, new_offset = fields - */ - } - __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":907 - * # so don't output it - * f = _util_dtypestring(child, f, end, offset) - * return f # <<<<<<<<<<<<<< - * - * - */ - __pyx_r = __pyx_v_f; - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":842 - * return () - * - * cdef inline char* _util_dtypestring(dtype descr, char* f, char* end, int* offset) except NULL: # <<<<<<<<<<<<<< - * # Recursive utility function used in __getbuffer__ to get format - * # string. The new location in the format string is returned. - */ - - /* function exit code */ - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_1); - __Pyx_XDECREF(__pyx_t_3); - __Pyx_XDECREF(__pyx_t_4); - __Pyx_AddTraceback("numpy._util_dtypestring", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = NULL; - __pyx_L0:; - __Pyx_XDECREF((PyObject *)__pyx_v_child); - __Pyx_XDECREF(__pyx_v_fields); - __Pyx_XDECREF(__pyx_v_childname); - __Pyx_XDECREF(__pyx_v_new_offset); - __Pyx_XDECREF(__pyx_v_t); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1022 - * int _import_umath() except -1 - * - * cdef inline void set_array_base(ndarray arr, object base): # <<<<<<<<<<<<<< - * Py_INCREF(base) # important to do this before stealing the reference below! - * PyArray_SetBaseObject(arr, base) - */ - -static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_arr, PyObject *__pyx_v_base) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("set_array_base", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1023 - * - * cdef inline void set_array_base(ndarray arr, object base): - * Py_INCREF(base) # important to do this before stealing the reference below! # <<<<<<<<<<<<<< - * PyArray_SetBaseObject(arr, base) - * - */ - Py_INCREF(__pyx_v_base); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1024 - * cdef inline void set_array_base(ndarray arr, object base): - * Py_INCREF(base) # important to do this before stealing the reference below! - * PyArray_SetBaseObject(arr, base) # <<<<<<<<<<<<<< - * - * cdef inline object get_array_base(ndarray arr): - */ - (void)(PyArray_SetBaseObject(__pyx_v_arr, __pyx_v_base)); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1022 - * int _import_umath() except -1 - * - * cdef inline void set_array_base(ndarray arr, object base): # <<<<<<<<<<<<<< - * Py_INCREF(base) # important to do this before stealing the reference below! - * PyArray_SetBaseObject(arr, base) - */ - - /* function exit code */ - __Pyx_RefNannyFinishContext(); -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1026 - * PyArray_SetBaseObject(arr, base) - * - * cdef inline object get_array_base(ndarray arr): # <<<<<<<<<<<<<< - * base = PyArray_BASE(arr) - * if base is NULL: - */ - -static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__pyx_v_arr) { - PyObject *__pyx_v_base; - PyObject *__pyx_r = NULL; - __Pyx_RefNannyDeclarations - int __pyx_t_1; - __Pyx_RefNannySetupContext("get_array_base", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1027 - * - * cdef inline object get_array_base(ndarray arr): - * base = PyArray_BASE(arr) # <<<<<<<<<<<<<< - * if base is NULL: - * return None - */ - __pyx_v_base = PyArray_BASE(__pyx_v_arr); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1028 - * cdef inline object get_array_base(ndarray arr): - * base = PyArray_BASE(arr) - * if base is NULL: # <<<<<<<<<<<<<< - * return None - * return base - */ - __pyx_t_1 = ((__pyx_v_base == NULL) != 0); - if (__pyx_t_1) { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1029 - * base = PyArray_BASE(arr) - * if base is NULL: - * return None # <<<<<<<<<<<<<< - * return base - * - */ - __Pyx_XDECREF(__pyx_r); - __pyx_r = Py_None; __Pyx_INCREF(Py_None); - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1028 - * cdef inline object get_array_base(ndarray arr): - * base = PyArray_BASE(arr) - * if base is NULL: # <<<<<<<<<<<<<< - * return None - * return base - */ - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1030 - * if base is NULL: - * return None - * return base # <<<<<<<<<<<<<< - * - * # Versions of the import_* functions which are more suitable for - */ - __Pyx_XDECREF(__pyx_r); - __Pyx_INCREF(((PyObject *)__pyx_v_base)); - __pyx_r = ((PyObject *)__pyx_v_base); - goto __pyx_L0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1026 - * PyArray_SetBaseObject(arr, base) - * - * cdef inline object get_array_base(ndarray arr): # <<<<<<<<<<<<<< - * base = PyArray_BASE(arr) - * if base is NULL: - */ - - /* function exit code */ - __pyx_L0:; - __Pyx_XGIVEREF(__pyx_r); - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1034 - * # Versions of the import_* functions which are more suitable for - * # Cython code. - * cdef inline int import_array() except -1: # <<<<<<<<<<<<<< - * try: - * _import_array() - */ - -static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) { - int __pyx_r; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - PyObject *__pyx_t_2 = NULL; - PyObject *__pyx_t_3 = NULL; - int __pyx_t_4; - PyObject *__pyx_t_5 = NULL; - PyObject *__pyx_t_6 = NULL; - PyObject *__pyx_t_7 = NULL; - PyObject *__pyx_t_8 = NULL; - __Pyx_RefNannySetupContext("import_array", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1035 - * # Cython code. - * cdef inline int import_array() except -1: - * try: # <<<<<<<<<<<<<< - * _import_array() - * except Exception: - */ - { - __Pyx_PyThreadState_declare - __Pyx_PyThreadState_assign - __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3); - __Pyx_XGOTREF(__pyx_t_1); - __Pyx_XGOTREF(__pyx_t_2); - __Pyx_XGOTREF(__pyx_t_3); - /*try:*/ { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1036 - * cdef inline int import_array() except -1: - * try: - * _import_array() # <<<<<<<<<<<<<< - * except Exception: - * raise ImportError("numpy.core.multiarray failed to import") - */ - __pyx_t_4 = _import_array(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 1036, __pyx_L3_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1035 - * # Cython code. - * cdef inline int import_array() except -1: - * try: # <<<<<<<<<<<<<< - * _import_array() - * except Exception: - */ - } - __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0; - __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0; - goto __pyx_L8_try_end; - __pyx_L3_error:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1037 - * try: - * _import_array() - * except Exception: # <<<<<<<<<<<<<< - * raise ImportError("numpy.core.multiarray failed to import") - * - */ - __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0]))); - if (__pyx_t_4) { - __Pyx_AddTraceback("numpy.import_array", __pyx_clineno, __pyx_lineno, __pyx_filename); - if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 1037, __pyx_L5_except_error) - __Pyx_GOTREF(__pyx_t_5); - __Pyx_GOTREF(__pyx_t_6); - __Pyx_GOTREF(__pyx_t_7); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1038 - * _import_array() - * except Exception: - * raise ImportError("numpy.core.multiarray failed to import") # <<<<<<<<<<<<<< - * - * cdef inline int import_umath() except -1: - */ - __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__9, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 1038, __pyx_L5_except_error) - __Pyx_GOTREF(__pyx_t_8); - __Pyx_Raise(__pyx_t_8, 0, 0, 0); - __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; - __PYX_ERR(1, 1038, __pyx_L5_except_error) - } - goto __pyx_L5_except_error; - __pyx_L5_except_error:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1035 - * # Cython code. - * cdef inline int import_array() except -1: - * try: # <<<<<<<<<<<<<< - * _import_array() - * except Exception: - */ - __Pyx_XGIVEREF(__pyx_t_1); - __Pyx_XGIVEREF(__pyx_t_2); - __Pyx_XGIVEREF(__pyx_t_3); - __Pyx_ExceptionReset(__pyx_t_1, __pyx_t_2, __pyx_t_3); - goto __pyx_L1_error; - __pyx_L8_try_end:; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1034 - * # Versions of the import_* functions which are more suitable for - * # Cython code. - * cdef inline int import_array() except -1: # <<<<<<<<<<<<<< - * try: - * _import_array() - */ - - /* function exit code */ - __pyx_r = 0; - goto __pyx_L0; - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_5); - __Pyx_XDECREF(__pyx_t_6); - __Pyx_XDECREF(__pyx_t_7); - __Pyx_XDECREF(__pyx_t_8); - __Pyx_AddTraceback("numpy.import_array", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = -1; - __pyx_L0:; - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1040 - * raise ImportError("numpy.core.multiarray failed to import") - * - * cdef inline int import_umath() except -1: # <<<<<<<<<<<<<< - * try: - * _import_umath() - */ - -static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) { - int __pyx_r; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - PyObject *__pyx_t_2 = NULL; - PyObject *__pyx_t_3 = NULL; - int __pyx_t_4; - PyObject *__pyx_t_5 = NULL; - PyObject *__pyx_t_6 = NULL; - PyObject *__pyx_t_7 = NULL; - PyObject *__pyx_t_8 = NULL; - __Pyx_RefNannySetupContext("import_umath", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1041 - * - * cdef inline int import_umath() except -1: - * try: # <<<<<<<<<<<<<< - * _import_umath() - * except Exception: - */ - { - __Pyx_PyThreadState_declare - __Pyx_PyThreadState_assign - __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3); - __Pyx_XGOTREF(__pyx_t_1); - __Pyx_XGOTREF(__pyx_t_2); - __Pyx_XGOTREF(__pyx_t_3); - /*try:*/ { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1042 - * cdef inline int import_umath() except -1: - * try: - * _import_umath() # <<<<<<<<<<<<<< - * except Exception: - * raise ImportError("numpy.core.umath failed to import") - */ - __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 1042, __pyx_L3_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1041 - * - * cdef inline int import_umath() except -1: - * try: # <<<<<<<<<<<<<< - * _import_umath() - * except Exception: - */ - } - __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0; - __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0; - goto __pyx_L8_try_end; - __pyx_L3_error:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1043 - * try: - * _import_umath() - * except Exception: # <<<<<<<<<<<<<< - * raise ImportError("numpy.core.umath failed to import") - * - */ - __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0]))); - if (__pyx_t_4) { - __Pyx_AddTraceback("numpy.import_umath", __pyx_clineno, __pyx_lineno, __pyx_filename); - if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 1043, __pyx_L5_except_error) - __Pyx_GOTREF(__pyx_t_5); - __Pyx_GOTREF(__pyx_t_6); - __Pyx_GOTREF(__pyx_t_7); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1044 - * _import_umath() - * except Exception: - * raise ImportError("numpy.core.umath failed to import") # <<<<<<<<<<<<<< - * - * cdef inline int import_ufunc() except -1: - */ - __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__10, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 1044, __pyx_L5_except_error) - __Pyx_GOTREF(__pyx_t_8); - __Pyx_Raise(__pyx_t_8, 0, 0, 0); - __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; - __PYX_ERR(1, 1044, __pyx_L5_except_error) - } - goto __pyx_L5_except_error; - __pyx_L5_except_error:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1041 - * - * cdef inline int import_umath() except -1: - * try: # <<<<<<<<<<<<<< - * _import_umath() - * except Exception: - */ - __Pyx_XGIVEREF(__pyx_t_1); - __Pyx_XGIVEREF(__pyx_t_2); - __Pyx_XGIVEREF(__pyx_t_3); - __Pyx_ExceptionReset(__pyx_t_1, __pyx_t_2, __pyx_t_3); - goto __pyx_L1_error; - __pyx_L8_try_end:; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1040 - * raise ImportError("numpy.core.multiarray failed to import") - * - * cdef inline int import_umath() except -1: # <<<<<<<<<<<<<< - * try: - * _import_umath() - */ - - /* function exit code */ - __pyx_r = 0; - goto __pyx_L0; - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_5); - __Pyx_XDECREF(__pyx_t_6); - __Pyx_XDECREF(__pyx_t_7); - __Pyx_XDECREF(__pyx_t_8); - __Pyx_AddTraceback("numpy.import_umath", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = -1; - __pyx_L0:; - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -/* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1046 - * raise ImportError("numpy.core.umath failed to import") - * - * cdef inline int import_ufunc() except -1: # <<<<<<<<<<<<<< - * try: - * _import_umath() - */ - -static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) { - int __pyx_r; - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - PyObject *__pyx_t_2 = NULL; - PyObject *__pyx_t_3 = NULL; - int __pyx_t_4; - PyObject *__pyx_t_5 = NULL; - PyObject *__pyx_t_6 = NULL; - PyObject *__pyx_t_7 = NULL; - PyObject *__pyx_t_8 = NULL; - __Pyx_RefNannySetupContext("import_ufunc", 0); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1047 - * - * cdef inline int import_ufunc() except -1: - * try: # <<<<<<<<<<<<<< - * _import_umath() - * except Exception: - */ - { - __Pyx_PyThreadState_declare - __Pyx_PyThreadState_assign - __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3); - __Pyx_XGOTREF(__pyx_t_1); - __Pyx_XGOTREF(__pyx_t_2); - __Pyx_XGOTREF(__pyx_t_3); - /*try:*/ { - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1048 - * cdef inline int import_ufunc() except -1: - * try: - * _import_umath() # <<<<<<<<<<<<<< - * except Exception: - * raise ImportError("numpy.core.umath failed to import") - */ - __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 1048, __pyx_L3_error) - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1047 - * - * cdef inline int import_ufunc() except -1: - * try: # <<<<<<<<<<<<<< - * _import_umath() - * except Exception: - */ - } - __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0; - __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; - __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0; - goto __pyx_L8_try_end; - __pyx_L3_error:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1049 - * try: - * _import_umath() - * except Exception: # <<<<<<<<<<<<<< - * raise ImportError("numpy.core.umath failed to import") - */ - __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0]))); - if (__pyx_t_4) { - __Pyx_AddTraceback("numpy.import_ufunc", __pyx_clineno, __pyx_lineno, __pyx_filename); - if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 1049, __pyx_L5_except_error) - __Pyx_GOTREF(__pyx_t_5); - __Pyx_GOTREF(__pyx_t_6); - __Pyx_GOTREF(__pyx_t_7); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1050 - * _import_umath() - * except Exception: - * raise ImportError("numpy.core.umath failed to import") # <<<<<<<<<<<<<< - */ - __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__10, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 1050, __pyx_L5_except_error) - __Pyx_GOTREF(__pyx_t_8); - __Pyx_Raise(__pyx_t_8, 0, 0, 0); - __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; - __PYX_ERR(1, 1050, __pyx_L5_except_error) - } - goto __pyx_L5_except_error; - __pyx_L5_except_error:; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1047 - * - * cdef inline int import_ufunc() except -1: - * try: # <<<<<<<<<<<<<< - * _import_umath() - * except Exception: - */ - __Pyx_XGIVEREF(__pyx_t_1); - __Pyx_XGIVEREF(__pyx_t_2); - __Pyx_XGIVEREF(__pyx_t_3); - __Pyx_ExceptionReset(__pyx_t_1, __pyx_t_2, __pyx_t_3); - goto __pyx_L1_error; - __pyx_L8_try_end:; - } - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1046 - * raise ImportError("numpy.core.umath failed to import") - * - * cdef inline int import_ufunc() except -1: # <<<<<<<<<<<<<< - * try: - * _import_umath() - */ - - /* function exit code */ - __pyx_r = 0; - goto __pyx_L0; - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_5); - __Pyx_XDECREF(__pyx_t_6); - __Pyx_XDECREF(__pyx_t_7); - __Pyx_XDECREF(__pyx_t_8); - __Pyx_AddTraceback("numpy.import_ufunc", __pyx_clineno, __pyx_lineno, __pyx_filename); - __pyx_r = -1; - __pyx_L0:; - __Pyx_RefNannyFinishContext(); - return __pyx_r; -} - -static PyMethodDef __pyx_methods[] = { - {0, 0, 0, 0} -}; - -#if PY_MAJOR_VERSION >= 3 -#if CYTHON_PEP489_MULTI_PHASE_INIT -static PyObject* __pyx_pymod_create(PyObject *spec, PyModuleDef *def); /*proto*/ -static int __pyx_pymod_exec_radon(PyObject* module); /*proto*/ -static PyModuleDef_Slot __pyx_moduledef_slots[] = { - {Py_mod_create, (void*)__pyx_pymod_create}, - {Py_mod_exec, (void*)__pyx_pymod_exec_radon}, - {0, NULL} -}; -#endif - -static struct PyModuleDef __pyx_moduledef = { - PyModuleDef_HEAD_INIT, - "radon", - 0, /* m_doc */ - #if CYTHON_PEP489_MULTI_PHASE_INIT - 0, /* m_size */ - #else - -1, /* m_size */ - #endif - __pyx_methods /* m_methods */, - #if CYTHON_PEP489_MULTI_PHASE_INIT - __pyx_moduledef_slots, /* m_slots */ - #else - NULL, /* m_reload */ - #endif - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL /* m_free */ -}; -#endif -#ifndef CYTHON_SMALL_CODE -#if defined(__clang__) - #define CYTHON_SMALL_CODE -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) - #define CYTHON_SMALL_CODE __attribute__((cold)) -#else - #define CYTHON_SMALL_CODE -#endif -#endif - -static __Pyx_StringTabEntry __pyx_string_tab[] = { - {&__pyx_kp_u_Format_string_allocated_too_shor, __pyx_k_Format_string_allocated_too_shor, sizeof(__pyx_k_Format_string_allocated_too_shor), 0, 1, 0, 0}, - {&__pyx_kp_u_Format_string_allocated_too_shor_2, __pyx_k_Format_string_allocated_too_shor_2, sizeof(__pyx_k_Format_string_allocated_too_shor_2), 0, 1, 0, 0}, - {&__pyx_n_s_ImportError, __pyx_k_ImportError, sizeof(__pyx_k_ImportError), 0, 0, 1, 1}, - {&__pyx_n_s_L, __pyx_k_L, sizeof(__pyx_k_L), 0, 0, 1, 1}, - {&__pyx_n_s_Ldata, __pyx_k_Ldata, sizeof(__pyx_k_Ldata), 0, 0, 1, 1}, - {&__pyx_n_s_M, __pyx_k_M, sizeof(__pyx_k_M), 0, 0, 1, 1}, - {&__pyx_n_s_N, __pyx_k_N, sizeof(__pyx_k_N), 0, 0, 1, 1}, - {&__pyx_kp_u_Non_native_byte_order_not_suppor, __pyx_k_Non_native_byte_order_not_suppor, sizeof(__pyx_k_Non_native_byte_order_not_suppor), 0, 1, 0, 0}, - {&__pyx_n_s_RuntimeError, __pyx_k_RuntimeError, sizeof(__pyx_k_RuntimeError), 0, 0, 1, 1}, - {&__pyx_kp_s_Rx, __pyx_k_Rx, sizeof(__pyx_k_Rx), 0, 0, 1, 0}, - {&__pyx_n_s_Rx_2, __pyx_k_Rx_2, sizeof(__pyx_k_Rx_2), 0, 0, 1, 1}, - {&__pyx_kp_s_Tx, __pyx_k_Tx, sizeof(__pyx_k_Tx), 0, 0, 1, 0}, - {&__pyx_n_s_Tx_2, __pyx_k_Tx_2, sizeof(__pyx_k_Tx_2), 0, 0, 1, 1}, - {&__pyx_n_s_ValueError, __pyx_k_ValueError, sizeof(__pyx_k_ValueError), 0, 0, 1, 1}, - {&__pyx_n_s_abs, __pyx_k_abs, sizeof(__pyx_k_abs), 0, 0, 1, 1}, - {&__pyx_n_s_cline_in_traceback, __pyx_k_cline_in_traceback, sizeof(__pyx_k_cline_in_traceback), 0, 0, 1, 1}, - {&__pyx_n_s_cos, __pyx_k_cos, sizeof(__pyx_k_cos), 0, 0, 1, 1}, - {&__pyx_n_s_csr_matrix, __pyx_k_csr_matrix, sizeof(__pyx_k_csr_matrix), 0, 0, 1, 1}, - {&__pyx_n_s_data, __pyx_k_data, sizeof(__pyx_k_data), 0, 0, 1, 1}, - {&__pyx_kp_s_data_should_be_a_square_array, __pyx_k_data_should_be_a_square_array, sizeof(__pyx_k_data_should_be_a_square_array), 0, 0, 1, 0}, - {&__pyx_n_s_dot, __pyx_k_dot, sizeof(__pyx_k_dot), 0, 0, 1, 1}, - {&__pyx_n_s_end, __pyx_k_end, sizeof(__pyx_k_end), 0, 0, 1, 1}, - {&__pyx_n_s_file, __pyx_k_file, sizeof(__pyx_k_file), 0, 0, 1, 1}, - {&__pyx_n_s_flatten, __pyx_k_flatten, sizeof(__pyx_k_flatten), 0, 0, 1, 1}, - {&__pyx_n_s_import, __pyx_k_import, sizeof(__pyx_k_import), 0, 0, 1, 1}, - {&__pyx_n_s_main, __pyx_k_main, sizeof(__pyx_k_main), 0, 0, 1, 1}, - {&__pyx_n_s_max, __pyx_k_max, sizeof(__pyx_k_max), 0, 0, 1, 1}, - {&__pyx_n_s_min, __pyx_k_min, sizeof(__pyx_k_min), 0, 0, 1, 1}, - {&__pyx_n_s_n, __pyx_k_n, sizeof(__pyx_k_n), 0, 0, 1, 1}, - {&__pyx_n_s_nTx, __pyx_k_nTx, sizeof(__pyx_k_nTx), 0, 0, 1, 1}, - {&__pyx_n_s_name, __pyx_k_name, sizeof(__pyx_k_name), 0, 0, 1, 1}, - {&__pyx_kp_u_ndarray_is_not_C_contiguous, __pyx_k_ndarray_is_not_C_contiguous, sizeof(__pyx_k_ndarray_is_not_C_contiguous), 0, 1, 0, 0}, - {&__pyx_kp_u_ndarray_is_not_Fortran_contiguou, __pyx_k_ndarray_is_not_Fortran_contiguou, sizeof(__pyx_k_ndarray_is_not_Fortran_contiguou), 0, 1, 0, 0}, - {&__pyx_n_s_np, __pyx_k_np, sizeof(__pyx_k_np), 0, 0, 1, 1}, - {&__pyx_n_s_nt, __pyx_k_nt, sizeof(__pyx_k_nt), 0, 0, 1, 1}, - {&__pyx_n_s_numpy, __pyx_k_numpy, sizeof(__pyx_k_numpy), 0, 0, 1, 1}, - {&__pyx_kp_s_numpy_core_multiarray_failed_to, __pyx_k_numpy_core_multiarray_failed_to, sizeof(__pyx_k_numpy_core_multiarray_failed_to), 0, 0, 1, 0}, - {&__pyx_kp_s_numpy_core_umath_failed_to_impor, __pyx_k_numpy_core_umath_failed_to_impor, sizeof(__pyx_k_numpy_core_umath_failed_to_impor), 0, 0, 1, 0}, - {&__pyx_n_s_nw, __pyx_k_nw, sizeof(__pyx_k_nw), 0, 0, 1, 1}, - {&__pyx_n_s_nx, __pyx_k_nx, sizeof(__pyx_k_nx), 0, 0, 1, 1}, - {&__pyx_n_s_ny, __pyx_k_ny, sizeof(__pyx_k_ny), 0, 0, 1, 1}, - {&__pyx_n_s_p, __pyx_k_p, sizeof(__pyx_k_p), 0, 0, 1, 1}, - {&__pyx_n_s_pi, __pyx_k_pi, sizeof(__pyx_k_pi), 0, 0, 1, 1}, - {&__pyx_n_s_print, __pyx_k_print, sizeof(__pyx_k_print), 0, 0, 1, 1}, - {&__pyx_n_s_pycurious_radon, __pyx_k_pycurious_radon, sizeof(__pyx_k_pycurious_radon), 0, 0, 1, 1}, - {&__pyx_n_s_radon2d, __pyx_k_radon2d, sizeof(__pyx_k_radon2d), 0, 0, 1, 1}, - {&__pyx_n_s_range, __pyx_k_range, sizeof(__pyx_k_range), 0, 0, 1, 1}, - {&__pyx_n_s_scipy_sparse, __pyx_k_scipy_sparse, sizeof(__pyx_k_scipy_sparse), 0, 0, 1, 1}, - {&__pyx_n_s_shape, __pyx_k_shape, sizeof(__pyx_k_shape), 0, 0, 1, 1}, - {&__pyx_n_s_sin, __pyx_k_sin, sizeof(__pyx_k_sin), 0, 0, 1, 1}, - {&__pyx_n_s_sinogram, __pyx_k_sinogram, sizeof(__pyx_k_sinogram), 0, 0, 1, 1}, - {&__pyx_n_s_size, __pyx_k_size, sizeof(__pyx_k_size), 0, 0, 1, 1}, - {&__pyx_kp_s_src_radon_pyx, __pyx_k_src_radon_pyx, sizeof(__pyx_k_src_radon_pyx), 0, 0, 1, 0}, - {&__pyx_n_s_tan, __pyx_k_tan, sizeof(__pyx_k_tan), 0, 0, 1, 1}, - {&__pyx_n_s_test, __pyx_k_test, sizeof(__pyx_k_test), 0, 0, 1, 1}, - {&__pyx_n_s_theta, __pyx_k_theta, sizeof(__pyx_k_theta), 0, 0, 1, 1}, - {&__pyx_kp_s_theta_should_be_within_0_pi, __pyx_k_theta_should_be_within_0_pi, sizeof(__pyx_k_theta_should_be_within_0_pi), 0, 0, 1, 0}, - {&__pyx_kp_u_unknown_dtype_code_in_numpy_pxd, __pyx_k_unknown_dtype_code_in_numpy_pxd, sizeof(__pyx_k_unknown_dtype_code_in_numpy_pxd), 0, 1, 0, 0}, - {&__pyx_n_s_x0, __pyx_k_x0, sizeof(__pyx_k_x0), 0, 0, 1, 1}, - {&__pyx_n_s_x1, __pyx_k_x1, sizeof(__pyx_k_x1), 0, 0, 1, 1}, - {&__pyx_n_s_xk, __pyx_k_xk, sizeof(__pyx_k_xk), 0, 0, 1, 1}, - {&__pyx_n_s_xrange, __pyx_k_xrange, sizeof(__pyx_k_xrange), 0, 0, 1, 1}, - {&__pyx_n_s_xs, __pyx_k_xs, sizeof(__pyx_k_xs), 0, 0, 1, 1}, - {&__pyx_n_s_y0, __pyx_k_y0, sizeof(__pyx_k_y0), 0, 0, 1, 1}, - {&__pyx_n_s_y1, __pyx_k_y1, sizeof(__pyx_k_y1), 0, 0, 1, 1}, - {&__pyx_n_s_yk, __pyx_k_yk, sizeof(__pyx_k_yk), 0, 0, 1, 1}, - {&__pyx_n_s_ys, __pyx_k_ys, sizeof(__pyx_k_ys), 0, 0, 1, 1}, - {&__pyx_n_s_zeros, __pyx_k_zeros, sizeof(__pyx_k_zeros), 0, 0, 1, 1}, - {0, 0, 0, 0, 0, 0, 0} -}; -static CYTHON_SMALL_CODE int __Pyx_InitCachedBuiltins(void) { - #if PY_MAJOR_VERSION >= 3 - __pyx_builtin_xrange = __Pyx_GetBuiltinName(__pyx_n_s_range); if (!__pyx_builtin_xrange) __PYX_ERR(0, 9, __pyx_L1_error) - #else - __pyx_builtin_xrange = __Pyx_GetBuiltinName(__pyx_n_s_xrange); if (!__pyx_builtin_xrange) __PYX_ERR(0, 9, __pyx_L1_error) - #endif - __pyx_builtin_ValueError = __Pyx_GetBuiltinName(__pyx_n_s_ValueError); if (!__pyx_builtin_ValueError) __PYX_ERR(0, 15, __pyx_L1_error) - __pyx_builtin_RuntimeError = __Pyx_GetBuiltinName(__pyx_n_s_RuntimeError); if (!__pyx_builtin_RuntimeError) __PYX_ERR(0, 19, __pyx_L1_error) - __pyx_builtin_range = __Pyx_GetBuiltinName(__pyx_n_s_range); if (!__pyx_builtin_range) __PYX_ERR(1, 285, __pyx_L1_error) - __pyx_builtin_ImportError = __Pyx_GetBuiltinName(__pyx_n_s_ImportError); if (!__pyx_builtin_ImportError) __PYX_ERR(1, 1038, __pyx_L1_error) - return 0; - __pyx_L1_error:; - return -1; -} - -static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__Pyx_InitCachedConstants", 0); - - /* "src/radon.pyx":15 - * - * if np.min(theta) < 0.0 or np.max(theta) >= np.pi: - * raise ValueError('theta should be within [0 pi)') # <<<<<<<<<<<<<< - * - * nx, ny = data.shape - */ - __pyx_tuple_ = PyTuple_Pack(1, __pyx_kp_s_theta_should_be_within_0_pi); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 15, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple_); - __Pyx_GIVEREF(__pyx_tuple_); - - /* "src/radon.pyx":19 - * nx, ny = data.shape - * if nx != ny: - * raise RuntimeError('data should be a square array') # <<<<<<<<<<<<<< - * nw = nx - * - */ - __pyx_tuple__2 = PyTuple_Pack(1, __pyx_kp_s_data_should_be_a_square_array); if (unlikely(!__pyx_tuple__2)) __PYX_ERR(0, 19, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__2); - __Pyx_GIVEREF(__pyx_tuple__2); - - /* "src/radon.pyx":115 - * - * p = L.dot(data.flatten()) - * sinogram[:,nt] = p # <<<<<<<<<<<<<< - * - * - */ - __pyx_slice__3 = PySlice_New(Py_None, Py_None, Py_None); if (unlikely(!__pyx_slice__3)) __PYX_ERR(0, 115, __pyx_L1_error) - __Pyx_GOTREF(__pyx_slice__3); - __Pyx_GIVEREF(__pyx_slice__3); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":272 - * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): - * raise ValueError(u"ndarray is not C contiguous") # <<<<<<<<<<<<<< - * - * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) - */ - __pyx_tuple__4 = PyTuple_Pack(1, __pyx_kp_u_ndarray_is_not_C_contiguous); if (unlikely(!__pyx_tuple__4)) __PYX_ERR(1, 272, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__4); - __Pyx_GIVEREF(__pyx_tuple__4); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":276 - * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) - * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): - * raise ValueError(u"ndarray is not Fortran contiguous") # <<<<<<<<<<<<<< - * - * info.buf = PyArray_DATA(self) - */ - __pyx_tuple__5 = PyTuple_Pack(1, __pyx_kp_u_ndarray_is_not_Fortran_contiguou); if (unlikely(!__pyx_tuple__5)) __PYX_ERR(1, 276, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__5); - __Pyx_GIVEREF(__pyx_tuple__5); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":306 - * if ((descr.byteorder == c'>' and little_endian) or - * (descr.byteorder == c'<' and not little_endian)): - * raise ValueError(u"Non-native byte order not supported") # <<<<<<<<<<<<<< - * if t == NPY_BYTE: f = "b" - * elif t == NPY_UBYTE: f = "B" - */ - __pyx_tuple__6 = PyTuple_Pack(1, __pyx_kp_u_Non_native_byte_order_not_suppor); if (unlikely(!__pyx_tuple__6)) __PYX_ERR(1, 306, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__6); - __Pyx_GIVEREF(__pyx_tuple__6); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":856 - * - * if (end - f) - (new_offset - offset[0]) < 15: - * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") # <<<<<<<<<<<<<< - * - * if ((child.byteorder == c'>' and little_endian) or - */ - __pyx_tuple__7 = PyTuple_Pack(1, __pyx_kp_u_Format_string_allocated_too_shor); if (unlikely(!__pyx_tuple__7)) __PYX_ERR(1, 856, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__7); - __Pyx_GIVEREF(__pyx_tuple__7); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":880 - * t = child.type_num - * if end - f < 5: - * raise RuntimeError(u"Format string allocated too short.") # <<<<<<<<<<<<<< - * - * # Until ticket #99 is fixed, use integers to avoid warnings - */ - __pyx_tuple__8 = PyTuple_Pack(1, __pyx_kp_u_Format_string_allocated_too_shor_2); if (unlikely(!__pyx_tuple__8)) __PYX_ERR(1, 880, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__8); - __Pyx_GIVEREF(__pyx_tuple__8); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1038 - * _import_array() - * except Exception: - * raise ImportError("numpy.core.multiarray failed to import") # <<<<<<<<<<<<<< - * - * cdef inline int import_umath() except -1: - */ - __pyx_tuple__9 = PyTuple_Pack(1, __pyx_kp_s_numpy_core_multiarray_failed_to); if (unlikely(!__pyx_tuple__9)) __PYX_ERR(1, 1038, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__9); - __Pyx_GIVEREF(__pyx_tuple__9); - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1044 - * _import_umath() - * except Exception: - * raise ImportError("numpy.core.umath failed to import") # <<<<<<<<<<<<<< - * - * cdef inline int import_ufunc() except -1: - */ - __pyx_tuple__10 = PyTuple_Pack(1, __pyx_kp_s_numpy_core_umath_failed_to_impor); if (unlikely(!__pyx_tuple__10)) __PYX_ERR(1, 1044, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__10); - __Pyx_GIVEREF(__pyx_tuple__10); - - /* "src/radon.pyx":12 - * except: pass - * - * def radon2d(data, theta): # <<<<<<<<<<<<<< - * - * if np.min(theta) < 0.0 or np.max(theta) >= np.pi: - */ - __pyx_tuple__11 = PyTuple_Pack(24, __pyx_n_s_data, __pyx_n_s_theta, __pyx_n_s_nx, __pyx_n_s_ny, __pyx_n_s_nw, __pyx_n_s_sinogram, __pyx_n_s_Tx_2, __pyx_n_s_Rx_2, __pyx_n_s_nTx, __pyx_n_s_nt, __pyx_n_s_n, __pyx_n_s_xs, __pyx_n_s_ys, __pyx_n_s_xk, __pyx_n_s_yk, __pyx_n_s_x0, __pyx_n_s_y0, __pyx_n_s_x1, __pyx_n_s_y1, __pyx_n_s_Ldata, __pyx_n_s_M, __pyx_n_s_N, __pyx_n_s_L, __pyx_n_s_p); if (unlikely(!__pyx_tuple__11)) __PYX_ERR(0, 12, __pyx_L1_error) - __Pyx_GOTREF(__pyx_tuple__11); - __Pyx_GIVEREF(__pyx_tuple__11); - __pyx_codeobj__12 = (PyObject*)__Pyx_PyCode_New(2, 0, 24, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__11, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_src_radon_pyx, __pyx_n_s_radon2d, 12, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__12)) __PYX_ERR(0, 12, __pyx_L1_error) - __Pyx_RefNannyFinishContext(); - return 0; - __pyx_L1_error:; - __Pyx_RefNannyFinishContext(); - return -1; -} - -static CYTHON_SMALL_CODE int __Pyx_InitGlobals(void) { - if (__Pyx_InitStrings(__pyx_string_tab) < 0) __PYX_ERR(0, 1, __pyx_L1_error); - __pyx_float_1_ = PyFloat_FromDouble(1.); if (unlikely(!__pyx_float_1_)) __PYX_ERR(0, 1, __pyx_L1_error) - __pyx_float_0_0 = PyFloat_FromDouble(0.0); if (unlikely(!__pyx_float_0_0)) __PYX_ERR(0, 1, __pyx_L1_error) - __pyx_float_2_0 = PyFloat_FromDouble(2.0); if (unlikely(!__pyx_float_2_0)) __PYX_ERR(0, 1, __pyx_L1_error) - __pyx_float_1_eneg_10 = PyFloat_FromDouble(1.e-10); if (unlikely(!__pyx_float_1_eneg_10)) __PYX_ERR(0, 1, __pyx_L1_error) - __pyx_int_0 = PyInt_FromLong(0); if (unlikely(!__pyx_int_0)) __PYX_ERR(0, 1, __pyx_L1_error) - __pyx_int_1 = PyInt_FromLong(1); if (unlikely(!__pyx_int_1)) __PYX_ERR(0, 1, __pyx_L1_error) - __pyx_int_2 = PyInt_FromLong(2); if (unlikely(!__pyx_int_2)) __PYX_ERR(0, 1, __pyx_L1_error) - return 0; - __pyx_L1_error:; - return -1; -} - -static CYTHON_SMALL_CODE int __Pyx_modinit_global_init_code(void); /*proto*/ -static CYTHON_SMALL_CODE int __Pyx_modinit_variable_export_code(void); /*proto*/ -static CYTHON_SMALL_CODE int __Pyx_modinit_function_export_code(void); /*proto*/ -static CYTHON_SMALL_CODE int __Pyx_modinit_type_init_code(void); /*proto*/ -static CYTHON_SMALL_CODE int __Pyx_modinit_type_import_code(void); /*proto*/ -static CYTHON_SMALL_CODE int __Pyx_modinit_variable_import_code(void); /*proto*/ -static CYTHON_SMALL_CODE int __Pyx_modinit_function_import_code(void); /*proto*/ - -static int __Pyx_modinit_global_init_code(void) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__Pyx_modinit_global_init_code", 0); - /*--- Global init code ---*/ - __Pyx_RefNannyFinishContext(); - return 0; -} - -static int __Pyx_modinit_variable_export_code(void) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__Pyx_modinit_variable_export_code", 0); - /*--- Variable export code ---*/ - __Pyx_RefNannyFinishContext(); - return 0; -} - -static int __Pyx_modinit_function_export_code(void) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__Pyx_modinit_function_export_code", 0); - /*--- Function export code ---*/ - __Pyx_RefNannyFinishContext(); - return 0; -} - -static int __Pyx_modinit_type_init_code(void) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__Pyx_modinit_type_init_code", 0); - /*--- Type init code ---*/ - __Pyx_RefNannyFinishContext(); - return 0; -} - -static int __Pyx_modinit_type_import_code(void) { - __Pyx_RefNannyDeclarations - PyObject *__pyx_t_1 = NULL; - __Pyx_RefNannySetupContext("__Pyx_modinit_type_import_code", 0); - /*--- Type import code ---*/ - __pyx_t_1 = PyImport_ImportModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 9, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - __pyx_ptype_7cpython_4type_type = __Pyx_ImportType(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type", - #if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x050B0000 - sizeof(PyTypeObject), - #else - sizeof(PyHeapTypeObject), - #endif - __Pyx_ImportType_CheckSize_Warn); - if (!__pyx_ptype_7cpython_4type_type) __PYX_ERR(2, 9, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - __pyx_t_1 = PyImport_ImportModule("numpy"); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 206, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - __pyx_ptype_5numpy_dtype = __Pyx_ImportType(__pyx_t_1, "numpy", "dtype", sizeof(PyArray_Descr), __Pyx_ImportType_CheckSize_Ignore); - if (!__pyx_ptype_5numpy_dtype) __PYX_ERR(1, 206, __pyx_L1_error) - __pyx_ptype_5numpy_flatiter = __Pyx_ImportType(__pyx_t_1, "numpy", "flatiter", sizeof(PyArrayIterObject), __Pyx_ImportType_CheckSize_Warn); - if (!__pyx_ptype_5numpy_flatiter) __PYX_ERR(1, 229, __pyx_L1_error) - __pyx_ptype_5numpy_broadcast = __Pyx_ImportType(__pyx_t_1, "numpy", "broadcast", sizeof(PyArrayMultiIterObject), __Pyx_ImportType_CheckSize_Warn); - if (!__pyx_ptype_5numpy_broadcast) __PYX_ERR(1, 233, __pyx_L1_error) - __pyx_ptype_5numpy_ndarray = __Pyx_ImportType(__pyx_t_1, "numpy", "ndarray", sizeof(PyArrayObject), __Pyx_ImportType_CheckSize_Ignore); - if (!__pyx_ptype_5numpy_ndarray) __PYX_ERR(1, 242, __pyx_L1_error) - __pyx_ptype_5numpy_ufunc = __Pyx_ImportType(__pyx_t_1, "numpy", "ufunc", sizeof(PyUFuncObject), __Pyx_ImportType_CheckSize_Warn); - if (!__pyx_ptype_5numpy_ufunc) __PYX_ERR(1, 918, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - __Pyx_RefNannyFinishContext(); - return 0; - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_1); - __Pyx_RefNannyFinishContext(); - return -1; -} - -static int __Pyx_modinit_variable_import_code(void) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__Pyx_modinit_variable_import_code", 0); - /*--- Variable import code ---*/ - __Pyx_RefNannyFinishContext(); - return 0; -} - -static int __Pyx_modinit_function_import_code(void) { - __Pyx_RefNannyDeclarations - __Pyx_RefNannySetupContext("__Pyx_modinit_function_import_code", 0); - /*--- Function import code ---*/ - __Pyx_RefNannyFinishContext(); - return 0; -} - - -#if PY_MAJOR_VERSION < 3 -#ifdef CYTHON_NO_PYINIT_EXPORT -#define __Pyx_PyMODINIT_FUNC void -#else -#define __Pyx_PyMODINIT_FUNC PyMODINIT_FUNC -#endif -#else -#ifdef CYTHON_NO_PYINIT_EXPORT -#define __Pyx_PyMODINIT_FUNC PyObject * -#else -#define __Pyx_PyMODINIT_FUNC PyMODINIT_FUNC -#endif -#endif - - -#if PY_MAJOR_VERSION < 3 -__Pyx_PyMODINIT_FUNC initradon(void) CYTHON_SMALL_CODE; /*proto*/ -__Pyx_PyMODINIT_FUNC initradon(void) -#else -__Pyx_PyMODINIT_FUNC PyInit_radon(void) CYTHON_SMALL_CODE; /*proto*/ -__Pyx_PyMODINIT_FUNC PyInit_radon(void) -#if CYTHON_PEP489_MULTI_PHASE_INIT -{ - return PyModuleDef_Init(&__pyx_moduledef); -} -static CYTHON_SMALL_CODE int __Pyx_check_single_interpreter(void) { - #if PY_VERSION_HEX >= 0x030700A1 - static PY_INT64_T main_interpreter_id = -1; - PY_INT64_T current_id = PyInterpreterState_GetID(PyThreadState_Get()->interp); - if (main_interpreter_id == -1) { - main_interpreter_id = current_id; - return (unlikely(current_id == -1)) ? -1 : 0; - } else if (unlikely(main_interpreter_id != current_id)) - #else - static PyInterpreterState *main_interpreter = NULL; - PyInterpreterState *current_interpreter = PyThreadState_Get()->interp; - if (!main_interpreter) { - main_interpreter = current_interpreter; - } else if (unlikely(main_interpreter != current_interpreter)) - #endif - { - PyErr_SetString( - PyExc_ImportError, - "Interpreter change detected - this module can only be loaded into one interpreter per process."); - return -1; - } - return 0; -} -static CYTHON_SMALL_CODE int __Pyx_copy_spec_to_module(PyObject *spec, PyObject *moddict, const char* from_name, const char* to_name, int allow_none) { - PyObject *value = PyObject_GetAttrString(spec, from_name); - int result = 0; - if (likely(value)) { - if (allow_none || value != Py_None) { - result = PyDict_SetItemString(moddict, to_name, value); - } - Py_DECREF(value); - } else if (PyErr_ExceptionMatches(PyExc_AttributeError)) { - PyErr_Clear(); - } else { - result = -1; - } - return result; -} -static CYTHON_SMALL_CODE PyObject* __pyx_pymod_create(PyObject *spec, CYTHON_UNUSED PyModuleDef *def) { - PyObject *module = NULL, *moddict, *modname; - if (__Pyx_check_single_interpreter()) - return NULL; - if (__pyx_m) - return __Pyx_NewRef(__pyx_m); - modname = PyObject_GetAttrString(spec, "name"); - if (unlikely(!modname)) goto bad; - module = PyModule_NewObject(modname); - Py_DECREF(modname); - if (unlikely(!module)) goto bad; - moddict = PyModule_GetDict(module); - if (unlikely(!moddict)) goto bad; - if (unlikely(__Pyx_copy_spec_to_module(spec, moddict, "loader", "__loader__", 1) < 0)) goto bad; - if (unlikely(__Pyx_copy_spec_to_module(spec, moddict, "origin", "__file__", 1) < 0)) goto bad; - if (unlikely(__Pyx_copy_spec_to_module(spec, moddict, "parent", "__package__", 1) < 0)) goto bad; - if (unlikely(__Pyx_copy_spec_to_module(spec, moddict, "submodule_search_locations", "__path__", 0) < 0)) goto bad; - return module; -bad: - Py_XDECREF(module); - return NULL; -} - - -static CYTHON_SMALL_CODE int __pyx_pymod_exec_radon(PyObject *__pyx_pyinit_module) -#endif -#endif -{ - PyObject *__pyx_t_1 = NULL; - PyObject *__pyx_t_2 = NULL; - PyObject *__pyx_t_3 = NULL; - PyObject *__pyx_t_4 = NULL; - PyObject *__pyx_t_5 = NULL; - __Pyx_RefNannyDeclarations - #if CYTHON_PEP489_MULTI_PHASE_INIT - if (__pyx_m) { - if (__pyx_m == __pyx_pyinit_module) return 0; - PyErr_SetString(PyExc_RuntimeError, "Module 'radon' has already been imported. Re-initialisation is not supported."); - return -1; - } - #elif PY_MAJOR_VERSION >= 3 - if (__pyx_m) return __Pyx_NewRef(__pyx_m); - #endif - #if CYTHON_REFNANNY -__Pyx_RefNanny = __Pyx_RefNannyImportAPI("refnanny"); -if (!__Pyx_RefNanny) { - PyErr_Clear(); - __Pyx_RefNanny = __Pyx_RefNannyImportAPI("Cython.Runtime.refnanny"); - if (!__Pyx_RefNanny) - Py_FatalError("failed to import 'refnanny' module"); -} -#endif - __Pyx_RefNannySetupContext("__Pyx_PyMODINIT_FUNC PyInit_radon(void)", 0); - if (__Pyx_check_binary_version() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #ifdef __Pxy_PyFrame_Initialize_Offsets - __Pxy_PyFrame_Initialize_Offsets(); - #endif - __pyx_empty_tuple = PyTuple_New(0); if (unlikely(!__pyx_empty_tuple)) __PYX_ERR(0, 1, __pyx_L1_error) - __pyx_empty_bytes = PyBytes_FromStringAndSize("", 0); if (unlikely(!__pyx_empty_bytes)) __PYX_ERR(0, 1, __pyx_L1_error) - __pyx_empty_unicode = PyUnicode_FromStringAndSize("", 0); if (unlikely(!__pyx_empty_unicode)) __PYX_ERR(0, 1, __pyx_L1_error) - #ifdef __Pyx_CyFunction_USED - if (__pyx_CyFunction_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - #ifdef __Pyx_FusedFunction_USED - if (__pyx_FusedFunction_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - #ifdef __Pyx_Coroutine_USED - if (__pyx_Coroutine_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - #ifdef __Pyx_Generator_USED - if (__pyx_Generator_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - #ifdef __Pyx_AsyncGen_USED - if (__pyx_AsyncGen_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - #ifdef __Pyx_StopAsyncIteration_USED - if (__pyx_StopAsyncIteration_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - /*--- Library function declarations ---*/ - /*--- Threads initialization code ---*/ - #if defined(__PYX_FORCE_INIT_THREADS) && __PYX_FORCE_INIT_THREADS - #ifdef WITH_THREAD /* Python build with threading support? */ - PyEval_InitThreads(); - #endif - #endif - /*--- Module creation code ---*/ - #if CYTHON_PEP489_MULTI_PHASE_INIT - __pyx_m = __pyx_pyinit_module; - Py_INCREF(__pyx_m); - #else - #if PY_MAJOR_VERSION < 3 - __pyx_m = Py_InitModule4("radon", __pyx_methods, 0, 0, PYTHON_API_VERSION); Py_XINCREF(__pyx_m); - #else - __pyx_m = PyModule_Create(&__pyx_moduledef); - #endif - if (unlikely(!__pyx_m)) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - __pyx_d = PyModule_GetDict(__pyx_m); if (unlikely(!__pyx_d)) __PYX_ERR(0, 1, __pyx_L1_error) - Py_INCREF(__pyx_d); - __pyx_b = PyImport_AddModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_b)) __PYX_ERR(0, 1, __pyx_L1_error) - Py_INCREF(__pyx_b); - __pyx_cython_runtime = PyImport_AddModule((char *) "cython_runtime"); if (unlikely(!__pyx_cython_runtime)) __PYX_ERR(0, 1, __pyx_L1_error) - Py_INCREF(__pyx_cython_runtime); - if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) __PYX_ERR(0, 1, __pyx_L1_error); - /*--- Initialize various global constants etc. ---*/ - if (__Pyx_InitGlobals() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #if PY_MAJOR_VERSION < 3 && (__PYX_DEFAULT_STRING_ENCODING_IS_ASCII || __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT) - if (__Pyx_init_sys_getdefaultencoding_params() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - if (__pyx_module_is_main_pycurious__radon) { - if (PyObject_SetAttr(__pyx_m, __pyx_n_s_name, __pyx_n_s_main) < 0) __PYX_ERR(0, 1, __pyx_L1_error) - } - #if PY_MAJOR_VERSION >= 3 - { - PyObject *modules = PyImport_GetModuleDict(); if (unlikely(!modules)) __PYX_ERR(0, 1, __pyx_L1_error) - if (!PyDict_GetItemString(modules, "pycurious.radon")) { - if (unlikely(PyDict_SetItemString(modules, "pycurious.radon", __pyx_m) < 0)) __PYX_ERR(0, 1, __pyx_L1_error) - } - } - #endif - /*--- Builtin init code ---*/ - if (__Pyx_InitCachedBuiltins() < 0) goto __pyx_L1_error; - /*--- Constants init code ---*/ - if (__Pyx_InitCachedConstants() < 0) goto __pyx_L1_error; - /*--- Global type/function init code ---*/ - (void)__Pyx_modinit_global_init_code(); - (void)__Pyx_modinit_variable_export_code(); - (void)__Pyx_modinit_function_export_code(); - (void)__Pyx_modinit_type_init_code(); - if (unlikely(__Pyx_modinit_type_import_code() != 0)) goto __pyx_L1_error; - (void)__Pyx_modinit_variable_import_code(); - (void)__Pyx_modinit_function_import_code(); - /*--- Execution code ---*/ - #if defined(__Pyx_Generator_USED) || defined(__Pyx_Coroutine_USED) - if (__Pyx_patch_abc() < 0) __PYX_ERR(0, 1, __pyx_L1_error) - #endif - - /* "src/radon.pyx":2 - * # -*- coding: utf-8 -*- - * import numpy as np # <<<<<<<<<<<<<< - * cimport numpy as np - * - */ - __pyx_t_1 = __Pyx_Import(__pyx_n_s_numpy, 0, -1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 2, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - if (PyDict_SetItem(__pyx_d, __pyx_n_s_np, __pyx_t_1) < 0) __PYX_ERR(0, 2, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - - /* "src/radon.pyx":5 - * cimport numpy as np - * - * from scipy.sparse import csr_matrix # <<<<<<<<<<<<<< - * - * cimport cradon - */ - __pyx_t_1 = PyList_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 5, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - __Pyx_INCREF(__pyx_n_s_csr_matrix); - __Pyx_GIVEREF(__pyx_n_s_csr_matrix); - PyList_SET_ITEM(__pyx_t_1, 0, __pyx_n_s_csr_matrix); - __pyx_t_2 = __Pyx_Import(__pyx_n_s_scipy_sparse, __pyx_t_1, -1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 5, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - __pyx_t_1 = __Pyx_ImportFrom(__pyx_t_2, __pyx_n_s_csr_matrix); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 5, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_1); - if (PyDict_SetItem(__pyx_d, __pyx_n_s_csr_matrix, __pyx_t_1) < 0) __PYX_ERR(0, 5, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":9 - * cimport cradon - * - * try: range=xrange # <<<<<<<<<<<<<< - * except: pass - * - */ - { - __Pyx_PyThreadState_declare - __Pyx_PyThreadState_assign - __Pyx_ExceptionSave(&__pyx_t_3, &__pyx_t_4, &__pyx_t_5); - __Pyx_XGOTREF(__pyx_t_3); - __Pyx_XGOTREF(__pyx_t_4); - __Pyx_XGOTREF(__pyx_t_5); - /*try:*/ { - if (PyDict_SetItem(__pyx_d, __pyx_n_s_range, __pyx_builtin_xrange) < 0) __PYX_ERR(0, 9, __pyx_L2_error) - } - __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0; - __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0; - __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0; - goto __pyx_L7_try_end; - __pyx_L2_error:; - __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0; - __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":10 - * - * try: range=xrange - * except: pass # <<<<<<<<<<<<<< - * - * def radon2d(data, theta): - */ - /*except:*/ { - __Pyx_ErrRestore(0,0,0); - goto __pyx_L3_exception_handled; - } - __pyx_L3_exception_handled:; - __Pyx_XGIVEREF(__pyx_t_3); - __Pyx_XGIVEREF(__pyx_t_4); - __Pyx_XGIVEREF(__pyx_t_5); - __Pyx_ExceptionReset(__pyx_t_3, __pyx_t_4, __pyx_t_5); - __pyx_L7_try_end:; - } - - /* "src/radon.pyx":12 - * except: pass - * - * def radon2d(data, theta): # <<<<<<<<<<<<<< - * - * if np.min(theta) < 0.0 or np.max(theta) >= np.pi: - */ - __pyx_t_2 = PyCFunction_NewEx(&__pyx_mdef_9pycurious_5radon_1radon2d, NULL, __pyx_n_s_pycurious_radon); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 12, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - if (PyDict_SetItem(__pyx_d, __pyx_n_s_radon2d, __pyx_t_2) < 0) __PYX_ERR(0, 12, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "src/radon.pyx":1 - * # -*- coding: utf-8 -*- # <<<<<<<<<<<<<< - * import numpy as np - * cimport numpy as np - */ - __pyx_t_2 = __Pyx_PyDict_NewPresized(0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 1, __pyx_L1_error) - __Pyx_GOTREF(__pyx_t_2); - if (PyDict_SetItem(__pyx_d, __pyx_n_s_test, __pyx_t_2) < 0) __PYX_ERR(0, 1, __pyx_L1_error) - __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - - /* "../../.local/lib/python3.7/site-packages/Cython/Includes/numpy/__init__.pxd":1046 - * raise ImportError("numpy.core.umath failed to import") - * - * cdef inline int import_ufunc() except -1: # <<<<<<<<<<<<<< - * try: - * _import_umath() - */ - - /*--- Wrapped vars code ---*/ - - goto __pyx_L0; - __pyx_L1_error:; - __Pyx_XDECREF(__pyx_t_1); - __Pyx_XDECREF(__pyx_t_2); - if (__pyx_m) { - if (__pyx_d) { - __Pyx_AddTraceback("init pycurious.radon", __pyx_clineno, __pyx_lineno, __pyx_filename); - } - Py_CLEAR(__pyx_m); - } else if (!PyErr_Occurred()) { - PyErr_SetString(PyExc_ImportError, "init pycurious.radon"); - } - __pyx_L0:; - __Pyx_RefNannyFinishContext(); - #if CYTHON_PEP489_MULTI_PHASE_INIT - return (__pyx_m != NULL) ? 0 : -1; - #elif PY_MAJOR_VERSION >= 3 - return __pyx_m; - #else - return; - #endif -} - -/* --- Runtime support code --- */ -/* Refnanny */ -#if CYTHON_REFNANNY -static __Pyx_RefNannyAPIStruct *__Pyx_RefNannyImportAPI(const char *modname) { - PyObject *m = NULL, *p = NULL; - void *r = NULL; - m = PyImport_ImportModule(modname); - if (!m) goto end; - p = PyObject_GetAttrString(m, "RefNannyAPI"); - if (!p) goto end; - r = PyLong_AsVoidPtr(p); -end: - Py_XDECREF(p); - Py_XDECREF(m); - return (__Pyx_RefNannyAPIStruct *)r; -} -#endif - -/* PyObjectGetAttrStr */ -#if CYTHON_USE_TYPE_SLOTS -static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name) { - PyTypeObject* tp = Py_TYPE(obj); - if (likely(tp->tp_getattro)) - return tp->tp_getattro(obj, attr_name); -#if PY_MAJOR_VERSION < 3 - if (likely(tp->tp_getattr)) - return tp->tp_getattr(obj, PyString_AS_STRING(attr_name)); -#endif - return PyObject_GetAttr(obj, attr_name); -} -#endif - -/* GetBuiltinName */ -static PyObject *__Pyx_GetBuiltinName(PyObject *name) { - PyObject* result = __Pyx_PyObject_GetAttrStr(__pyx_b, name); - if (unlikely(!result)) { - PyErr_Format(PyExc_NameError, -#if PY_MAJOR_VERSION >= 3 - "name '%U' is not defined", name); -#else - "name '%.200s' is not defined", PyString_AS_STRING(name)); -#endif - } - return result; -} - -/* RaiseArgTupleInvalid */ -static void __Pyx_RaiseArgtupleInvalid( - const char* func_name, - int exact, - Py_ssize_t num_min, - Py_ssize_t num_max, - Py_ssize_t num_found) -{ - Py_ssize_t num_expected; - const char *more_or_less; - if (num_found < num_min) { - num_expected = num_min; - more_or_less = "at least"; - } else { - num_expected = num_max; - more_or_less = "at most"; - } - if (exact) { - more_or_less = "exactly"; - } - PyErr_Format(PyExc_TypeError, - "%.200s() takes %.8s %" CYTHON_FORMAT_SSIZE_T "d positional argument%.1s (%" CYTHON_FORMAT_SSIZE_T "d given)", - func_name, more_or_less, num_expected, - (num_expected == 1) ? "" : "s", num_found); -} - -/* RaiseDoubleKeywords */ -static void __Pyx_RaiseDoubleKeywordsError( - const char* func_name, - PyObject* kw_name) -{ - PyErr_Format(PyExc_TypeError, - #if PY_MAJOR_VERSION >= 3 - "%s() got multiple values for keyword argument '%U'", func_name, kw_name); - #else - "%s() got multiple values for keyword argument '%s'", func_name, - PyString_AsString(kw_name)); - #endif -} - -/* ParseKeywords */ -static int __Pyx_ParseOptionalKeywords( - PyObject *kwds, - PyObject **argnames[], - PyObject *kwds2, - PyObject *values[], - Py_ssize_t num_pos_args, - const char* function_name) -{ - PyObject *key = 0, *value = 0; - Py_ssize_t pos = 0; - PyObject*** name; - PyObject*** first_kw_arg = argnames + num_pos_args; - while (PyDict_Next(kwds, &pos, &key, &value)) { - name = first_kw_arg; - while (*name && (**name != key)) name++; - if (*name) { - values[name-argnames] = value; - continue; - } - name = first_kw_arg; - #if PY_MAJOR_VERSION < 3 - if (likely(PyString_CheckExact(key)) || likely(PyString_Check(key))) { - while (*name) { - if ((CYTHON_COMPILING_IN_PYPY || PyString_GET_SIZE(**name) == PyString_GET_SIZE(key)) - && _PyString_Eq(**name, key)) { - values[name-argnames] = value; - break; - } - name++; - } - if (*name) continue; - else { - PyObject*** argname = argnames; - while (argname != first_kw_arg) { - if ((**argname == key) || ( - (CYTHON_COMPILING_IN_PYPY || PyString_GET_SIZE(**argname) == PyString_GET_SIZE(key)) - && _PyString_Eq(**argname, key))) { - goto arg_passed_twice; - } - argname++; - } - } - } else - #endif - if (likely(PyUnicode_Check(key))) { - while (*name) { - int cmp = (**name == key) ? 0 : - #if !CYTHON_COMPILING_IN_PYPY && PY_MAJOR_VERSION >= 3 - (PyUnicode_GET_SIZE(**name) != PyUnicode_GET_SIZE(key)) ? 1 : - #endif - PyUnicode_Compare(**name, key); - if (cmp < 0 && unlikely(PyErr_Occurred())) goto bad; - if (cmp == 0) { - values[name-argnames] = value; - break; - } - name++; - } - if (*name) continue; - else { - PyObject*** argname = argnames; - while (argname != first_kw_arg) { - int cmp = (**argname == key) ? 0 : - #if !CYTHON_COMPILING_IN_PYPY && PY_MAJOR_VERSION >= 3 - (PyUnicode_GET_SIZE(**argname) != PyUnicode_GET_SIZE(key)) ? 1 : - #endif - PyUnicode_Compare(**argname, key); - if (cmp < 0 && unlikely(PyErr_Occurred())) goto bad; - if (cmp == 0) goto arg_passed_twice; - argname++; - } - } - } else - goto invalid_keyword_type; - if (kwds2) { - if (unlikely(PyDict_SetItem(kwds2, key, value))) goto bad; - } else { - goto invalid_keyword; - } - } - return 0; -arg_passed_twice: - __Pyx_RaiseDoubleKeywordsError(function_name, key); - goto bad; -invalid_keyword_type: - PyErr_Format(PyExc_TypeError, - "%.200s() keywords must be strings", function_name); - goto bad; -invalid_keyword: - PyErr_Format(PyExc_TypeError, - #if PY_MAJOR_VERSION < 3 - "%.200s() got an unexpected keyword argument '%.200s'", - function_name, PyString_AsString(key)); - #else - "%s() got an unexpected keyword argument '%U'", - function_name, key); - #endif -bad: - return -1; -} - -/* PyDictVersioning */ -#if CYTHON_USE_DICT_VERSIONS && CYTHON_USE_TYPE_SLOTS -static CYTHON_INLINE PY_UINT64_T __Pyx_get_tp_dict_version(PyObject *obj) { - PyObject *dict = Py_TYPE(obj)->tp_dict; - return likely(dict) ? __PYX_GET_DICT_VERSION(dict) : 0; -} -static CYTHON_INLINE PY_UINT64_T __Pyx_get_object_dict_version(PyObject *obj) { - PyObject **dictptr = NULL; - Py_ssize_t offset = Py_TYPE(obj)->tp_dictoffset; - if (offset) { -#if CYTHON_COMPILING_IN_CPYTHON - dictptr = (likely(offset > 0)) ? (PyObject **) ((char *)obj + offset) : _PyObject_GetDictPtr(obj); -#else - dictptr = _PyObject_GetDictPtr(obj); -#endif - } - return (dictptr && *dictptr) ? __PYX_GET_DICT_VERSION(*dictptr) : 0; -} -static CYTHON_INLINE int __Pyx_object_dict_version_matches(PyObject* obj, PY_UINT64_T tp_dict_version, PY_UINT64_T obj_dict_version) { - PyObject *dict = Py_TYPE(obj)->tp_dict; - if (unlikely(!dict) || unlikely(tp_dict_version != __PYX_GET_DICT_VERSION(dict))) - return 0; - return obj_dict_version == __Pyx_get_object_dict_version(obj); -} -#endif - -/* GetModuleGlobalName */ -#if CYTHON_USE_DICT_VERSIONS -static PyObject *__Pyx__GetModuleGlobalName(PyObject *name, PY_UINT64_T *dict_version, PyObject **dict_cached_value) -#else -static CYTHON_INLINE PyObject *__Pyx__GetModuleGlobalName(PyObject *name) -#endif -{ - PyObject *result; -#if !CYTHON_AVOID_BORROWED_REFS -#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500A1 - result = _PyDict_GetItem_KnownHash(__pyx_d, name, ((PyASCIIObject *) name)->hash); - __PYX_UPDATE_DICT_CACHE(__pyx_d, result, *dict_cached_value, *dict_version) - if (likely(result)) { - return __Pyx_NewRef(result); - } else if (unlikely(PyErr_Occurred())) { - return NULL; - } -#else - result = PyDict_GetItem(__pyx_d, name); - __PYX_UPDATE_DICT_CACHE(__pyx_d, result, *dict_cached_value, *dict_version) - if (likely(result)) { - return __Pyx_NewRef(result); - } -#endif -#else - result = PyObject_GetItem(__pyx_d, name); - __PYX_UPDATE_DICT_CACHE(__pyx_d, result, *dict_cached_value, *dict_version) - if (likely(result)) { - return __Pyx_NewRef(result); - } - PyErr_Clear(); -#endif - return __Pyx_GetBuiltinName(name); -} - -/* PyCFunctionFastCall */ -#if CYTHON_FAST_PYCCALL -static CYTHON_INLINE PyObject * __Pyx_PyCFunction_FastCall(PyObject *func_obj, PyObject **args, Py_ssize_t nargs) { - PyCFunctionObject *func = (PyCFunctionObject*)func_obj; - PyCFunction meth = PyCFunction_GET_FUNCTION(func); - PyObject *self = PyCFunction_GET_SELF(func); - int flags = PyCFunction_GET_FLAGS(func); - assert(PyCFunction_Check(func)); - assert(METH_FASTCALL == (flags & ~(METH_CLASS | METH_STATIC | METH_COEXIST | METH_KEYWORDS | METH_STACKLESS))); - assert(nargs >= 0); - assert(nargs == 0 || args != NULL); - /* _PyCFunction_FastCallDict() must not be called with an exception set, - because it may clear it (directly or indirectly) and so the - caller loses its exception */ - assert(!PyErr_Occurred()); - if ((PY_VERSION_HEX < 0x030700A0) || unlikely(flags & METH_KEYWORDS)) { - return (*((__Pyx_PyCFunctionFastWithKeywords)(void*)meth)) (self, args, nargs, NULL); - } else { - return (*((__Pyx_PyCFunctionFast)(void*)meth)) (self, args, nargs); - } -} -#endif - -/* PyFunctionFastCall */ -#if CYTHON_FAST_PYCALL -static PyObject* __Pyx_PyFunction_FastCallNoKw(PyCodeObject *co, PyObject **args, Py_ssize_t na, - PyObject *globals) { - PyFrameObject *f; - PyThreadState *tstate = __Pyx_PyThreadState_Current; - PyObject **fastlocals; - Py_ssize_t i; - PyObject *result; - assert(globals != NULL); - /* XXX Perhaps we should create a specialized - PyFrame_New() that doesn't take locals, but does - take builtins without sanity checking them. - */ - assert(tstate != NULL); - f = PyFrame_New(tstate, co, globals, NULL); - if (f == NULL) { - return NULL; - } - fastlocals = __Pyx_PyFrame_GetLocalsplus(f); - for (i = 0; i < na; i++) { - Py_INCREF(*args); - fastlocals[i] = *args++; - } - result = PyEval_EvalFrameEx(f,0); - ++tstate->recursion_depth; - Py_DECREF(f); - --tstate->recursion_depth; - return result; -} -#if 1 || PY_VERSION_HEX < 0x030600B1 -static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs, PyObject *kwargs) { - PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func); - PyObject *globals = PyFunction_GET_GLOBALS(func); - PyObject *argdefs = PyFunction_GET_DEFAULTS(func); - PyObject *closure; -#if PY_MAJOR_VERSION >= 3 - PyObject *kwdefs; -#endif - PyObject *kwtuple, **k; - PyObject **d; - Py_ssize_t nd; - Py_ssize_t nk; - PyObject *result; - assert(kwargs == NULL || PyDict_Check(kwargs)); - nk = kwargs ? PyDict_Size(kwargs) : 0; - if (Py_EnterRecursiveCall((char*)" while calling a Python object")) { - return NULL; - } - if ( -#if PY_MAJOR_VERSION >= 3 - co->co_kwonlyargcount == 0 && -#endif - likely(kwargs == NULL || nk == 0) && - co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) { - if (argdefs == NULL && co->co_argcount == nargs) { - result = __Pyx_PyFunction_FastCallNoKw(co, args, nargs, globals); - goto done; - } - else if (nargs == 0 && argdefs != NULL - && co->co_argcount == Py_SIZE(argdefs)) { - /* function called with no arguments, but all parameters have - a default value: use default values as arguments .*/ - args = &PyTuple_GET_ITEM(argdefs, 0); - result =__Pyx_PyFunction_FastCallNoKw(co, args, Py_SIZE(argdefs), globals); - goto done; - } - } - if (kwargs != NULL) { - Py_ssize_t pos, i; - kwtuple = PyTuple_New(2 * nk); - if (kwtuple == NULL) { - result = NULL; - goto done; - } - k = &PyTuple_GET_ITEM(kwtuple, 0); - pos = i = 0; - while (PyDict_Next(kwargs, &pos, &k[i], &k[i+1])) { - Py_INCREF(k[i]); - Py_INCREF(k[i+1]); - i += 2; - } - nk = i / 2; - } - else { - kwtuple = NULL; - k = NULL; - } - closure = PyFunction_GET_CLOSURE(func); -#if PY_MAJOR_VERSION >= 3 - kwdefs = PyFunction_GET_KW_DEFAULTS(func); -#endif - if (argdefs != NULL) { - d = &PyTuple_GET_ITEM(argdefs, 0); - nd = Py_SIZE(argdefs); - } - else { - d = NULL; - nd = 0; - } -#if PY_MAJOR_VERSION >= 3 - result = PyEval_EvalCodeEx((PyObject*)co, globals, (PyObject *)NULL, - args, (int)nargs, - k, (int)nk, - d, (int)nd, kwdefs, closure); -#else - result = PyEval_EvalCodeEx(co, globals, (PyObject *)NULL, - args, (int)nargs, - k, (int)nk, - d, (int)nd, closure); -#endif - Py_XDECREF(kwtuple); -done: - Py_LeaveRecursiveCall(); - return result; -} -#endif -#endif - -/* PyObjectCall */ -#if CYTHON_COMPILING_IN_CPYTHON -static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw) { - PyObject *result; - ternaryfunc call = func->ob_type->tp_call; - if (unlikely(!call)) - return PyObject_Call(func, arg, kw); - if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object"))) - return NULL; - result = (*call)(func, arg, kw); - Py_LeaveRecursiveCall(); - if (unlikely(!result) && unlikely(!PyErr_Occurred())) { - PyErr_SetString( - PyExc_SystemError, - "NULL result without error in PyObject_Call"); - } - return result; -} -#endif - -/* PyObjectCall2Args */ -static CYTHON_UNUSED PyObject* __Pyx_PyObject_Call2Args(PyObject* function, PyObject* arg1, PyObject* arg2) { - PyObject *args, *result = NULL; - #if CYTHON_FAST_PYCALL - if (PyFunction_Check(function)) { - PyObject *args[2] = {arg1, arg2}; - return __Pyx_PyFunction_FastCall(function, args, 2); - } - #endif - #if CYTHON_FAST_PYCCALL - if (__Pyx_PyFastCFunction_Check(function)) { - PyObject *args[2] = {arg1, arg2}; - return __Pyx_PyCFunction_FastCall(function, args, 2); - } - #endif - args = PyTuple_New(2); - if (unlikely(!args)) goto done; - Py_INCREF(arg1); - PyTuple_SET_ITEM(args, 0, arg1); - Py_INCREF(arg2); - PyTuple_SET_ITEM(args, 1, arg2); - Py_INCREF(function); - result = __Pyx_PyObject_Call(function, args, NULL); - Py_DECREF(args); - Py_DECREF(function); -done: - return result; -} - -/* PyObjectCallMethO */ -#if CYTHON_COMPILING_IN_CPYTHON -static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg) { - PyObject *self, *result; - PyCFunction cfunc; - cfunc = PyCFunction_GET_FUNCTION(func); - self = PyCFunction_GET_SELF(func); - if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object"))) - return NULL; - result = cfunc(self, arg); - Py_LeaveRecursiveCall(); - if (unlikely(!result) && unlikely(!PyErr_Occurred())) { - PyErr_SetString( - PyExc_SystemError, - "NULL result without error in PyObject_Call"); - } - return result; -} -#endif - -/* PyObjectCallOneArg */ -#if CYTHON_COMPILING_IN_CPYTHON -static PyObject* __Pyx__PyObject_CallOneArg(PyObject *func, PyObject *arg) { - PyObject *result; - PyObject *args = PyTuple_New(1); - if (unlikely(!args)) return NULL; - Py_INCREF(arg); - PyTuple_SET_ITEM(args, 0, arg); - result = __Pyx_PyObject_Call(func, args, NULL); - Py_DECREF(args); - return result; -} -static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) { -#if CYTHON_FAST_PYCALL - if (PyFunction_Check(func)) { - return __Pyx_PyFunction_FastCall(func, &arg, 1); - } -#endif - if (likely(PyCFunction_Check(func))) { - if (likely(PyCFunction_GET_FLAGS(func) & METH_O)) { - return __Pyx_PyObject_CallMethO(func, arg); -#if CYTHON_FAST_PYCCALL - } else if (PyCFunction_GET_FLAGS(func) & METH_FASTCALL) { - return __Pyx_PyCFunction_FastCall(func, &arg, 1); -#endif - } - } - return __Pyx__PyObject_CallOneArg(func, arg); -} -#else -static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) { - PyObject *result; - PyObject *args = PyTuple_Pack(1, arg); - if (unlikely(!args)) return NULL; - result = __Pyx_PyObject_Call(func, args, NULL); - Py_DECREF(args); - return result; -} -#endif - -/* PyErrFetchRestore */ -#if CYTHON_FAST_THREAD_STATE -static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb) { - PyObject *tmp_type, *tmp_value, *tmp_tb; - tmp_type = tstate->curexc_type; - tmp_value = tstate->curexc_value; - tmp_tb = tstate->curexc_traceback; - tstate->curexc_type = type; - tstate->curexc_value = value; - tstate->curexc_traceback = tb; - Py_XDECREF(tmp_type); - Py_XDECREF(tmp_value); - Py_XDECREF(tmp_tb); -} -static CYTHON_INLINE void __Pyx_ErrFetchInState(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) { - *type = tstate->curexc_type; - *value = tstate->curexc_value; - *tb = tstate->curexc_traceback; - tstate->curexc_type = 0; - tstate->curexc_value = 0; - tstate->curexc_traceback = 0; -} -#endif - -/* RaiseException */ -#if PY_MAJOR_VERSION < 3 -static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, - CYTHON_UNUSED PyObject *cause) { - __Pyx_PyThreadState_declare - Py_XINCREF(type); - if (!value || value == Py_None) - value = NULL; - else - Py_INCREF(value); - if (!tb || tb == Py_None) - tb = NULL; - else { - Py_INCREF(tb); - if (!PyTraceBack_Check(tb)) { - PyErr_SetString(PyExc_TypeError, - "raise: arg 3 must be a traceback or None"); - goto raise_error; - } - } - if (PyType_Check(type)) { -#if CYTHON_COMPILING_IN_PYPY - if (!value) { - Py_INCREF(Py_None); - value = Py_None; - } -#endif - PyErr_NormalizeException(&type, &value, &tb); - } else { - if (value) { - PyErr_SetString(PyExc_TypeError, - "instance exception may not have a separate value"); - goto raise_error; - } - value = type; - type = (PyObject*) Py_TYPE(type); - Py_INCREF(type); - if (!PyType_IsSubtype((PyTypeObject *)type, (PyTypeObject *)PyExc_BaseException)) { - PyErr_SetString(PyExc_TypeError, - "raise: exception class must be a subclass of BaseException"); - goto raise_error; - } - } - __Pyx_PyThreadState_assign - __Pyx_ErrRestore(type, value, tb); - return; -raise_error: - Py_XDECREF(value); - Py_XDECREF(type); - Py_XDECREF(tb); - return; -} -#else -static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, PyObject *cause) { - PyObject* owned_instance = NULL; - if (tb == Py_None) { - tb = 0; - } else if (tb && !PyTraceBack_Check(tb)) { - PyErr_SetString(PyExc_TypeError, - "raise: arg 3 must be a traceback or None"); - goto bad; - } - if (value == Py_None) - value = 0; - if (PyExceptionInstance_Check(type)) { - if (value) { - PyErr_SetString(PyExc_TypeError, - "instance exception may not have a separate value"); - goto bad; - } - value = type; - type = (PyObject*) Py_TYPE(value); - } else if (PyExceptionClass_Check(type)) { - PyObject *instance_class = NULL; - if (value && PyExceptionInstance_Check(value)) { - instance_class = (PyObject*) Py_TYPE(value); - if (instance_class != type) { - int is_subclass = PyObject_IsSubclass(instance_class, type); - if (!is_subclass) { - instance_class = NULL; - } else if (unlikely(is_subclass == -1)) { - goto bad; - } else { - type = instance_class; - } - } - } - if (!instance_class) { - PyObject *args; - if (!value) - args = PyTuple_New(0); - else if (PyTuple_Check(value)) { - Py_INCREF(value); - args = value; - } else - args = PyTuple_Pack(1, value); - if (!args) - goto bad; - owned_instance = PyObject_Call(type, args, NULL); - Py_DECREF(args); - if (!owned_instance) - goto bad; - value = owned_instance; - if (!PyExceptionInstance_Check(value)) { - PyErr_Format(PyExc_TypeError, - "calling %R should have returned an instance of " - "BaseException, not %R", - type, Py_TYPE(value)); - goto bad; - } - } - } else { - PyErr_SetString(PyExc_TypeError, - "raise: exception class must be a subclass of BaseException"); - goto bad; - } - if (cause) { - PyObject *fixed_cause; - if (cause == Py_None) { - fixed_cause = NULL; - } else if (PyExceptionClass_Check(cause)) { - fixed_cause = PyObject_CallObject(cause, NULL); - if (fixed_cause == NULL) - goto bad; - } else if (PyExceptionInstance_Check(cause)) { - fixed_cause = cause; - Py_INCREF(fixed_cause); - } else { - PyErr_SetString(PyExc_TypeError, - "exception causes must derive from " - "BaseException"); - goto bad; - } - PyException_SetCause(value, fixed_cause); - } - PyErr_SetObject(type, value); - if (tb) { -#if CYTHON_COMPILING_IN_PYPY - PyObject *tmp_type, *tmp_value, *tmp_tb; - PyErr_Fetch(&tmp_type, &tmp_value, &tmp_tb); - Py_INCREF(tb); - PyErr_Restore(tmp_type, tmp_value, tb); - Py_XDECREF(tmp_tb); -#else - PyThreadState *tstate = __Pyx_PyThreadState_Current; - PyObject* tmp_tb = tstate->curexc_traceback; - if (tb != tmp_tb) { - Py_INCREF(tb); - tstate->curexc_traceback = tb; - Py_XDECREF(tmp_tb); - } -#endif - } -bad: - Py_XDECREF(owned_instance); - return; -} -#endif - -/* RaiseTooManyValuesToUnpack */ -static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected) { - PyErr_Format(PyExc_ValueError, - "too many values to unpack (expected %" CYTHON_FORMAT_SSIZE_T "d)", expected); -} - -/* RaiseNeedMoreValuesToUnpack */ -static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index) { - PyErr_Format(PyExc_ValueError, - "need more than %" CYTHON_FORMAT_SSIZE_T "d value%.1s to unpack", - index, (index == 1) ? "" : "s"); -} - -/* IterFinish */ -static CYTHON_INLINE int __Pyx_IterFinish(void) { -#if CYTHON_FAST_THREAD_STATE - PyThreadState *tstate = __Pyx_PyThreadState_Current; - PyObject* exc_type = tstate->curexc_type; - if (unlikely(exc_type)) { - if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) { - PyObject *exc_value, *exc_tb; - exc_value = tstate->curexc_value; - exc_tb = tstate->curexc_traceback; - tstate->curexc_type = 0; - tstate->curexc_value = 0; - tstate->curexc_traceback = 0; - Py_DECREF(exc_type); - Py_XDECREF(exc_value); - Py_XDECREF(exc_tb); - return 0; - } else { - return -1; - } - } - return 0; -#else - if (unlikely(PyErr_Occurred())) { - if (likely(PyErr_ExceptionMatches(PyExc_StopIteration))) { - PyErr_Clear(); - return 0; - } else { - return -1; - } - } - return 0; -#endif -} - -/* UnpackItemEndCheck */ -static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected) { - if (unlikely(retval)) { - Py_DECREF(retval); - __Pyx_RaiseTooManyValuesError(expected); - return -1; - } else { - return __Pyx_IterFinish(); - } - return 0; -} - -/* GetItemInt */ -static PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j) { - PyObject *r; - if (!j) return NULL; - r = PyObject_GetItem(o, j); - Py_DECREF(j); - return r; -} -static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i, - CYTHON_NCP_UNUSED int wraparound, - CYTHON_NCP_UNUSED int boundscheck) { -#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - Py_ssize_t wrapped_i = i; - if (wraparound & unlikely(i < 0)) { - wrapped_i += PyList_GET_SIZE(o); - } - if ((!boundscheck) || likely(__Pyx_is_valid_index(wrapped_i, PyList_GET_SIZE(o)))) { - PyObject *r = PyList_GET_ITEM(o, wrapped_i); - Py_INCREF(r); - return r; - } - return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i)); -#else - return PySequence_GetItem(o, i); -#endif -} -static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i, - CYTHON_NCP_UNUSED int wraparound, - CYTHON_NCP_UNUSED int boundscheck) { -#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS - Py_ssize_t wrapped_i = i; - if (wraparound & unlikely(i < 0)) { - wrapped_i += PyTuple_GET_SIZE(o); - } - if ((!boundscheck) || likely(__Pyx_is_valid_index(wrapped_i, PyTuple_GET_SIZE(o)))) { - PyObject *r = PyTuple_GET_ITEM(o, wrapped_i); - Py_INCREF(r); - return r; - } - return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i)); -#else - return PySequence_GetItem(o, i); -#endif -} -static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i, int is_list, - CYTHON_NCP_UNUSED int wraparound, - CYTHON_NCP_UNUSED int boundscheck) { -#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS && CYTHON_USE_TYPE_SLOTS - if (is_list || PyList_CheckExact(o)) { - Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyList_GET_SIZE(o); - if ((!boundscheck) || (likely(__Pyx_is_valid_index(n, PyList_GET_SIZE(o))))) { - PyObject *r = PyList_GET_ITEM(o, n); - Py_INCREF(r); - return r; - } - } - else if (PyTuple_CheckExact(o)) { - Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyTuple_GET_SIZE(o); - if ((!boundscheck) || likely(__Pyx_is_valid_index(n, PyTuple_GET_SIZE(o)))) { - PyObject *r = PyTuple_GET_ITEM(o, n); - Py_INCREF(r); - return r; - } - } else { - PySequenceMethods *m = Py_TYPE(o)->tp_as_sequence; - if (likely(m && m->sq_item)) { - if (wraparound && unlikely(i < 0) && likely(m->sq_length)) { - Py_ssize_t l = m->sq_length(o); - if (likely(l >= 0)) { - i += l; - } else { - if (!PyErr_ExceptionMatches(PyExc_OverflowError)) - return NULL; - PyErr_Clear(); - } - } - return m->sq_item(o, i); - } - } -#else - if (is_list || PySequence_Check(o)) { - return PySequence_GetItem(o, i); - } -#endif - return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i)); -} - -/* PyFloatBinop */ -#if !CYTHON_COMPILING_IN_PYPY -static PyObject* __Pyx_PyFloat_EqObjC(PyObject *op1, PyObject *op2, double floatval, int inplace, int zerodivision_check) { - const double b = floatval; - double a; - (void)inplace; - (void)zerodivision_check; - if (op1 == op2) { - Py_RETURN_TRUE; - } - if (likely(PyFloat_CheckExact(op1))) { - a = PyFloat_AS_DOUBLE(op1); - - } else - #if PY_MAJOR_VERSION < 3 - if (likely(PyInt_CheckExact(op1))) { - a = (double) PyInt_AS_LONG(op1); - - } else - #endif - if (likely(PyLong_CheckExact(op1))) { - #if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)op1)->ob_digit; - const Py_ssize_t size = Py_SIZE(op1); - switch (size) { - case 0: a = 0.0; break; - case -1: a = -(double) digits[0]; break; - case 1: a = (double) digits[0]; break; - case -2: - case 2: - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (1 * PyLong_SHIFT < 53))) { - a = (double) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (2 * PyLong_SHIFT < 53) || (a < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -2) - a = -a; - break; - } - } - CYTHON_FALLTHROUGH; - case -3: - case 3: - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (2 * PyLong_SHIFT < 53))) { - a = (double) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (3 * PyLong_SHIFT < 53) || (a < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -3) - a = -a; - break; - } - } - CYTHON_FALLTHROUGH; - case -4: - case 4: - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (3 * PyLong_SHIFT < 53))) { - a = (double) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (4 * PyLong_SHIFT < 53) || (a < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -4) - a = -a; - break; - } - } - CYTHON_FALLTHROUGH; - default: - #else - { - #endif - return ( - PyFloat_Type.tp_richcompare(op2, op1, Py_EQ)); - } - } else { - return ( - PyObject_RichCompare(op1, op2, Py_EQ)); - } - if (a == b) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } -} -#endif - -/* ObjectGetItem */ - #if CYTHON_USE_TYPE_SLOTS -static PyObject *__Pyx_PyObject_GetIndex(PyObject *obj, PyObject* index) { - PyObject *runerr; - Py_ssize_t key_value; - PySequenceMethods *m = Py_TYPE(obj)->tp_as_sequence; - if (unlikely(!(m && m->sq_item))) { - PyErr_Format(PyExc_TypeError, "'%.200s' object is not subscriptable", Py_TYPE(obj)->tp_name); - return NULL; - } - key_value = __Pyx_PyIndex_AsSsize_t(index); - if (likely(key_value != -1 || !(runerr = PyErr_Occurred()))) { - return __Pyx_GetItemInt_Fast(obj, key_value, 0, 1, 1); - } - if (PyErr_GivenExceptionMatches(runerr, PyExc_OverflowError)) { - PyErr_Clear(); - PyErr_Format(PyExc_IndexError, "cannot fit '%.200s' into an index-sized integer", Py_TYPE(index)->tp_name); - } - return NULL; -} -static PyObject *__Pyx_PyObject_GetItem(PyObject *obj, PyObject* key) { - PyMappingMethods *m = Py_TYPE(obj)->tp_as_mapping; - if (likely(m && m->mp_subscript)) { - return m->mp_subscript(obj, key); - } - return __Pyx_PyObject_GetIndex(obj, key); -} -#endif - -/* PyIntBinop */ - #if !CYTHON_COMPILING_IN_PYPY -static PyObject* __Pyx_PyInt_SubtractObjC(PyObject *op1, PyObject *op2, CYTHON_UNUSED long intval, int inplace, int zerodivision_check) { - (void)inplace; - (void)zerodivision_check; - #if PY_MAJOR_VERSION < 3 - if (likely(PyInt_CheckExact(op1))) { - const long b = intval; - long x; - long a = PyInt_AS_LONG(op1); - x = (long)((unsigned long)a - b); - if (likely((x^a) >= 0 || (x^~b) >= 0)) - return PyInt_FromLong(x); - return PyLong_Type.tp_as_number->nb_subtract(op1, op2); - } - #endif - #if CYTHON_USE_PYLONG_INTERNALS - if (likely(PyLong_CheckExact(op1))) { - const long b = intval; - long a, x; -#ifdef HAVE_LONG_LONG - const PY_LONG_LONG llb = intval; - PY_LONG_LONG lla, llx; -#endif - const digit* digits = ((PyLongObject*)op1)->ob_digit; - const Py_ssize_t size = Py_SIZE(op1); - if (likely(__Pyx_sst_abs(size) <= 1)) { - a = likely(size) ? digits[0] : 0; - if (size == -1) a = -a; - } else { - switch (size) { - case -2: - if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) { - a = -(long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - break; -#ifdef HAVE_LONG_LONG - } else if (8 * sizeof(PY_LONG_LONG) - 1 > 2 * PyLong_SHIFT) { - lla = -(PY_LONG_LONG) (((((unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0])); - goto long_long; -#endif - } - CYTHON_FALLTHROUGH; - case 2: - if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) { - a = (long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - break; -#ifdef HAVE_LONG_LONG - } else if (8 * sizeof(PY_LONG_LONG) - 1 > 2 * PyLong_SHIFT) { - lla = (PY_LONG_LONG) (((((unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0])); - goto long_long; -#endif - } - CYTHON_FALLTHROUGH; - case -3: - if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) { - a = -(long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - break; -#ifdef HAVE_LONG_LONG - } else if (8 * sizeof(PY_LONG_LONG) - 1 > 3 * PyLong_SHIFT) { - lla = -(PY_LONG_LONG) (((((((unsigned PY_LONG_LONG)digits[2]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0])); - goto long_long; -#endif - } - CYTHON_FALLTHROUGH; - case 3: - if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) { - a = (long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - break; -#ifdef HAVE_LONG_LONG - } else if (8 * sizeof(PY_LONG_LONG) - 1 > 3 * PyLong_SHIFT) { - lla = (PY_LONG_LONG) (((((((unsigned PY_LONG_LONG)digits[2]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0])); - goto long_long; -#endif - } - CYTHON_FALLTHROUGH; - case -4: - if (8 * sizeof(long) - 1 > 4 * PyLong_SHIFT) { - a = -(long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - break; -#ifdef HAVE_LONG_LONG - } else if (8 * sizeof(PY_LONG_LONG) - 1 > 4 * PyLong_SHIFT) { - lla = -(PY_LONG_LONG) (((((((((unsigned PY_LONG_LONG)digits[3]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[2]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0])); - goto long_long; -#endif - } - CYTHON_FALLTHROUGH; - case 4: - if (8 * sizeof(long) - 1 > 4 * PyLong_SHIFT) { - a = (long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - break; -#ifdef HAVE_LONG_LONG - } else if (8 * sizeof(PY_LONG_LONG) - 1 > 4 * PyLong_SHIFT) { - lla = (PY_LONG_LONG) (((((((((unsigned PY_LONG_LONG)digits[3]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[2]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0])); - goto long_long; -#endif - } - CYTHON_FALLTHROUGH; - default: return PyLong_Type.tp_as_number->nb_subtract(op1, op2); - } - } - x = a - b; - return PyLong_FromLong(x); -#ifdef HAVE_LONG_LONG - long_long: - llx = lla - llb; - return PyLong_FromLongLong(llx); -#endif - - - } - #endif - if (PyFloat_CheckExact(op1)) { - const long b = intval; - double a = PyFloat_AS_DOUBLE(op1); - double result; - PyFPE_START_PROTECT("subtract", return NULL) - result = ((double)a) - (double)b; - PyFPE_END_PROTECT(result) - return PyFloat_FromDouble(result); - } - return (inplace ? PyNumber_InPlaceSubtract : PyNumber_Subtract)(op1, op2); -} -#endif - -/* PyFloatBinop */ - #if !CYTHON_COMPILING_IN_PYPY -static PyObject* __Pyx_PyFloat_SubtractCObj(PyObject *op1, PyObject *op2, double floatval, int inplace, int zerodivision_check) { - const double a = floatval; - double b, result; - (void)inplace; - (void)zerodivision_check; - if (likely(PyFloat_CheckExact(op2))) { - b = PyFloat_AS_DOUBLE(op2); - - } else - #if PY_MAJOR_VERSION < 3 - if (likely(PyInt_CheckExact(op2))) { - b = (double) PyInt_AS_LONG(op2); - - } else - #endif - if (likely(PyLong_CheckExact(op2))) { - #if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)op2)->ob_digit; - const Py_ssize_t size = Py_SIZE(op2); - switch (size) { - case 0: b = 0.0; break; - case -1: b = -(double) digits[0]; break; - case 1: b = (double) digits[0]; break; - case -2: - case 2: - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (1 * PyLong_SHIFT < 53))) { - b = (double) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (2 * PyLong_SHIFT < 53) || (b < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -2) - b = -b; - break; - } - } - CYTHON_FALLTHROUGH; - case -3: - case 3: - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (2 * PyLong_SHIFT < 53))) { - b = (double) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (3 * PyLong_SHIFT < 53) || (b < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -3) - b = -b; - break; - } - } - CYTHON_FALLTHROUGH; - case -4: - case 4: - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (3 * PyLong_SHIFT < 53))) { - b = (double) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (4 * PyLong_SHIFT < 53) || (b < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -4) - b = -b; - break; - } - } - CYTHON_FALLTHROUGH; - default: - #else - { - #endif - b = PyLong_AsDouble(op2); - if (unlikely(b == -1.0 && PyErr_Occurred())) return NULL; - - } - } else { - return (inplace ? PyNumber_InPlaceSubtract : PyNumber_Subtract)(op1, op2); - } - - PyFPE_START_PROTECT("subtract", return NULL) - result = a - b; - PyFPE_END_PROTECT(result) - return PyFloat_FromDouble(result); -} -#endif - -/* PyFloatBinop */ - #if !CYTHON_COMPILING_IN_PYPY -static PyObject* __Pyx_PyFloat_DivideObjC(PyObject *op1, PyObject *op2, double floatval, int inplace, int zerodivision_check) { - const double b = floatval; - double a, result; - (void)inplace; - (void)zerodivision_check; - if (likely(PyFloat_CheckExact(op1))) { - a = PyFloat_AS_DOUBLE(op1); - - } else - #if PY_MAJOR_VERSION < 3 - if (likely(PyInt_CheckExact(op1))) { - a = (double) PyInt_AS_LONG(op1); - - } else - #endif - if (likely(PyLong_CheckExact(op1))) { - #if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)op1)->ob_digit; - const Py_ssize_t size = Py_SIZE(op1); - switch (size) { - case 0: a = 0.0; break; - case -1: a = -(double) digits[0]; break; - case 1: a = (double) digits[0]; break; - case -2: - case 2: - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (1 * PyLong_SHIFT < 53))) { - a = (double) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (2 * PyLong_SHIFT < 53) || (a < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -2) - a = -a; - break; - } - } - CYTHON_FALLTHROUGH; - case -3: - case 3: - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (2 * PyLong_SHIFT < 53))) { - a = (double) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (3 * PyLong_SHIFT < 53) || (a < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -3) - a = -a; - break; - } - } - CYTHON_FALLTHROUGH; - case -4: - case 4: - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT && ((8 * sizeof(unsigned long) < 53) || (3 * PyLong_SHIFT < 53))) { - a = (double) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0])); - if ((8 * sizeof(unsigned long) < 53) || (4 * PyLong_SHIFT < 53) || (a < (double) ((PY_LONG_LONG)1 << 53))) { - if (size == -4) - a = -a; - break; - } - } - CYTHON_FALLTHROUGH; - default: - #else - { - #endif - a = PyLong_AsDouble(op1); - if (unlikely(a == -1.0 && PyErr_Occurred())) return NULL; - - } - } else { - return (inplace ? __Pyx_PyNumber_InPlaceDivide(op1, op2) : __Pyx_PyNumber_Divide(op1, op2)); - } - - PyFPE_START_PROTECT("divide", return NULL) - result = a / b; - PyFPE_END_PROTECT(result) - return PyFloat_FromDouble(result); -} -#endif - -/* None */ - static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname) { - PyErr_Format(PyExc_UnboundLocalError, "local variable '%s' referenced before assignment", varname); -} - -/* ExtTypeTest */ - static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type) { - if (unlikely(!type)) { - PyErr_SetString(PyExc_SystemError, "Missing type object"); - return 0; - } - if (likely(__Pyx_TypeCheck(obj, type))) - return 1; - PyErr_Format(PyExc_TypeError, "Cannot convert %.200s to %.200s", - Py_TYPE(obj)->tp_name, type->tp_name); - return 0; -} - -/* PyObjectCallNoArg */ - #if CYTHON_COMPILING_IN_CPYTHON -static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func) { -#if CYTHON_FAST_PYCALL - if (PyFunction_Check(func)) { - return __Pyx_PyFunction_FastCall(func, NULL, 0); - } -#endif -#ifdef __Pyx_CyFunction_USED - if (likely(PyCFunction_Check(func) || __Pyx_CyFunction_Check(func))) -#else - if (likely(PyCFunction_Check(func))) -#endif - { - if (likely(PyCFunction_GET_FLAGS(func) & METH_NOARGS)) { - return __Pyx_PyObject_CallMethO(func, NULL); - } - } - return __Pyx_PyObject_Call(func, __pyx_empty_tuple, NULL); -} -#endif - -/* DictGetItem */ - #if PY_MAJOR_VERSION >= 3 && !CYTHON_COMPILING_IN_PYPY -static PyObject *__Pyx_PyDict_GetItem(PyObject *d, PyObject* key) { - PyObject *value; - value = PyDict_GetItemWithError(d, key); - if (unlikely(!value)) { - if (!PyErr_Occurred()) { - if (unlikely(PyTuple_Check(key))) { - PyObject* args = PyTuple_Pack(1, key); - if (likely(args)) { - PyErr_SetObject(PyExc_KeyError, args); - Py_DECREF(args); - } - } else { - PyErr_SetObject(PyExc_KeyError, key); - } - } - return NULL; - } - Py_INCREF(value); - return value; -} -#endif - -/* RaiseNoneIterError */ - static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void) { - PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable"); -} - -/* GetTopmostException */ - #if CYTHON_USE_EXC_INFO_STACK -static _PyErr_StackItem * -__Pyx_PyErr_GetTopmostException(PyThreadState *tstate) -{ - _PyErr_StackItem *exc_info = tstate->exc_info; - while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && - exc_info->previous_item != NULL) - { - exc_info = exc_info->previous_item; - } - return exc_info; -} -#endif - -/* SaveResetException */ - #if CYTHON_FAST_THREAD_STATE -static CYTHON_INLINE void __Pyx__ExceptionSave(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) { - #if CYTHON_USE_EXC_INFO_STACK - _PyErr_StackItem *exc_info = __Pyx_PyErr_GetTopmostException(tstate); - *type = exc_info->exc_type; - *value = exc_info->exc_value; - *tb = exc_info->exc_traceback; - #else - *type = tstate->exc_type; - *value = tstate->exc_value; - *tb = tstate->exc_traceback; - #endif - Py_XINCREF(*type); - Py_XINCREF(*value); - Py_XINCREF(*tb); -} -static CYTHON_INLINE void __Pyx__ExceptionReset(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb) { - PyObject *tmp_type, *tmp_value, *tmp_tb; - #if CYTHON_USE_EXC_INFO_STACK - _PyErr_StackItem *exc_info = tstate->exc_info; - tmp_type = exc_info->exc_type; - tmp_value = exc_info->exc_value; - tmp_tb = exc_info->exc_traceback; - exc_info->exc_type = type; - exc_info->exc_value = value; - exc_info->exc_traceback = tb; - #else - tmp_type = tstate->exc_type; - tmp_value = tstate->exc_value; - tmp_tb = tstate->exc_traceback; - tstate->exc_type = type; - tstate->exc_value = value; - tstate->exc_traceback = tb; - #endif - Py_XDECREF(tmp_type); - Py_XDECREF(tmp_value); - Py_XDECREF(tmp_tb); -} -#endif - -/* PyErrExceptionMatches */ - #if CYTHON_FAST_THREAD_STATE -static int __Pyx_PyErr_ExceptionMatchesTuple(PyObject *exc_type, PyObject *tuple) { - Py_ssize_t i, n; - n = PyTuple_GET_SIZE(tuple); -#if PY_MAJOR_VERSION >= 3 - for (i=0; icurexc_type; - if (exc_type == err) return 1; - if (unlikely(!exc_type)) return 0; - if (unlikely(PyTuple_Check(err))) - return __Pyx_PyErr_ExceptionMatchesTuple(exc_type, err); - return __Pyx_PyErr_GivenExceptionMatches(exc_type, err); -} -#endif - -/* GetException */ - #if CYTHON_FAST_THREAD_STATE -static int __Pyx__GetException(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) -#else -static int __Pyx_GetException(PyObject **type, PyObject **value, PyObject **tb) -#endif -{ - PyObject *local_type, *local_value, *local_tb; -#if CYTHON_FAST_THREAD_STATE - PyObject *tmp_type, *tmp_value, *tmp_tb; - local_type = tstate->curexc_type; - local_value = tstate->curexc_value; - local_tb = tstate->curexc_traceback; - tstate->curexc_type = 0; - tstate->curexc_value = 0; - tstate->curexc_traceback = 0; -#else - PyErr_Fetch(&local_type, &local_value, &local_tb); -#endif - PyErr_NormalizeException(&local_type, &local_value, &local_tb); -#if CYTHON_FAST_THREAD_STATE - if (unlikely(tstate->curexc_type)) -#else - if (unlikely(PyErr_Occurred())) -#endif - goto bad; - #if PY_MAJOR_VERSION >= 3 - if (local_tb) { - if (unlikely(PyException_SetTraceback(local_value, local_tb) < 0)) - goto bad; - } - #endif - Py_XINCREF(local_tb); - Py_XINCREF(local_type); - Py_XINCREF(local_value); - *type = local_type; - *value = local_value; - *tb = local_tb; -#if CYTHON_FAST_THREAD_STATE - #if CYTHON_USE_EXC_INFO_STACK - { - _PyErr_StackItem *exc_info = tstate->exc_info; - tmp_type = exc_info->exc_type; - tmp_value = exc_info->exc_value; - tmp_tb = exc_info->exc_traceback; - exc_info->exc_type = local_type; - exc_info->exc_value = local_value; - exc_info->exc_traceback = local_tb; - } - #else - tmp_type = tstate->exc_type; - tmp_value = tstate->exc_value; - tmp_tb = tstate->exc_traceback; - tstate->exc_type = local_type; - tstate->exc_value = local_value; - tstate->exc_traceback = local_tb; - #endif - Py_XDECREF(tmp_type); - Py_XDECREF(tmp_value); - Py_XDECREF(tmp_tb); -#else - PyErr_SetExcInfo(local_type, local_value, local_tb); -#endif - return 0; -bad: - *type = 0; - *value = 0; - *tb = 0; - Py_XDECREF(local_type); - Py_XDECREF(local_value); - Py_XDECREF(local_tb); - return -1; -} - -/* TypeImport */ - #ifndef __PYX_HAVE_RT_ImportType -#define __PYX_HAVE_RT_ImportType -static PyTypeObject *__Pyx_ImportType(PyObject *module, const char *module_name, const char *class_name, - size_t size, enum __Pyx_ImportType_CheckSize check_size) -{ - PyObject *result = 0; - char warning[200]; - Py_ssize_t basicsize; -#ifdef Py_LIMITED_API - PyObject *py_basicsize; -#endif - result = PyObject_GetAttrString(module, class_name); - if (!result) - goto bad; - if (!PyType_Check(result)) { - PyErr_Format(PyExc_TypeError, - "%.200s.%.200s is not a type object", - module_name, class_name); - goto bad; - } -#ifndef Py_LIMITED_API - basicsize = ((PyTypeObject *)result)->tp_basicsize; -#else - py_basicsize = PyObject_GetAttrString(result, "__basicsize__"); - if (!py_basicsize) - goto bad; - basicsize = PyLong_AsSsize_t(py_basicsize); - Py_DECREF(py_basicsize); - py_basicsize = 0; - if (basicsize == (Py_ssize_t)-1 && PyErr_Occurred()) - goto bad; -#endif - if ((size_t)basicsize < size) { - PyErr_Format(PyExc_ValueError, - "%.200s.%.200s size changed, may indicate binary incompatibility. " - "Expected %zd from C header, got %zd from PyObject", - module_name, class_name, size, basicsize); - goto bad; - } - if (check_size == __Pyx_ImportType_CheckSize_Error && (size_t)basicsize != size) { - PyErr_Format(PyExc_ValueError, - "%.200s.%.200s size changed, may indicate binary incompatibility. " - "Expected %zd from C header, got %zd from PyObject", - module_name, class_name, size, basicsize); - goto bad; - } - else if (check_size == __Pyx_ImportType_CheckSize_Warn && (size_t)basicsize > size) { - PyOS_snprintf(warning, sizeof(warning), - "%s.%s size changed, may indicate binary incompatibility. " - "Expected %zd from C header, got %zd from PyObject", - module_name, class_name, size, basicsize); - if (PyErr_WarnEx(NULL, warning, 0) < 0) goto bad; - } - return (PyTypeObject *)result; -bad: - Py_XDECREF(result); - return NULL; -} -#endif - -/* Import */ - static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level) { - PyObject *empty_list = 0; - PyObject *module = 0; - PyObject *global_dict = 0; - PyObject *empty_dict = 0; - PyObject *list; - #if PY_MAJOR_VERSION < 3 - PyObject *py_import; - py_import = __Pyx_PyObject_GetAttrStr(__pyx_b, __pyx_n_s_import); - if (!py_import) - goto bad; - #endif - if (from_list) - list = from_list; - else { - empty_list = PyList_New(0); - if (!empty_list) - goto bad; - list = empty_list; - } - global_dict = PyModule_GetDict(__pyx_m); - if (!global_dict) - goto bad; - empty_dict = PyDict_New(); - if (!empty_dict) - goto bad; - { - #if PY_MAJOR_VERSION >= 3 - if (level == -1) { - if (strchr(__Pyx_MODULE_NAME, '.')) { - module = PyImport_ImportModuleLevelObject( - name, global_dict, empty_dict, list, 1); - if (!module) { - if (!PyErr_ExceptionMatches(PyExc_ImportError)) - goto bad; - PyErr_Clear(); - } - } - level = 0; - } - #endif - if (!module) { - #if PY_MAJOR_VERSION < 3 - PyObject *py_level = PyInt_FromLong(level); - if (!py_level) - goto bad; - module = PyObject_CallFunctionObjArgs(py_import, - name, global_dict, empty_dict, list, py_level, (PyObject *)NULL); - Py_DECREF(py_level); - #else - module = PyImport_ImportModuleLevelObject( - name, global_dict, empty_dict, list, level); - #endif - } - } -bad: - #if PY_MAJOR_VERSION < 3 - Py_XDECREF(py_import); - #endif - Py_XDECREF(empty_list); - Py_XDECREF(empty_dict); - return module; -} - -/* ImportFrom */ - static PyObject* __Pyx_ImportFrom(PyObject* module, PyObject* name) { - PyObject* value = __Pyx_PyObject_GetAttrStr(module, name); - if (unlikely(!value) && PyErr_ExceptionMatches(PyExc_AttributeError)) { - PyErr_Format(PyExc_ImportError, - #if PY_MAJOR_VERSION < 3 - "cannot import name %.230s", PyString_AS_STRING(name)); - #else - "cannot import name %S", name); - #endif - } - return value; -} - -/* CLineInTraceback */ - #ifndef CYTHON_CLINE_IN_TRACEBACK -static int __Pyx_CLineForTraceback(PyThreadState *tstate, int c_line) { - PyObject *use_cline; - PyObject *ptype, *pvalue, *ptraceback; -#if CYTHON_COMPILING_IN_CPYTHON - PyObject **cython_runtime_dict; -#endif - if (unlikely(!__pyx_cython_runtime)) { - return c_line; - } - __Pyx_ErrFetchInState(tstate, &ptype, &pvalue, &ptraceback); -#if CYTHON_COMPILING_IN_CPYTHON - cython_runtime_dict = _PyObject_GetDictPtr(__pyx_cython_runtime); - if (likely(cython_runtime_dict)) { - __PYX_PY_DICT_LOOKUP_IF_MODIFIED( - use_cline, *cython_runtime_dict, - __Pyx_PyDict_GetItemStr(*cython_runtime_dict, __pyx_n_s_cline_in_traceback)) - } else -#endif - { - PyObject *use_cline_obj = __Pyx_PyObject_GetAttrStr(__pyx_cython_runtime, __pyx_n_s_cline_in_traceback); - if (use_cline_obj) { - use_cline = PyObject_Not(use_cline_obj) ? Py_False : Py_True; - Py_DECREF(use_cline_obj); - } else { - PyErr_Clear(); - use_cline = NULL; - } - } - if (!use_cline) { - c_line = 0; - PyObject_SetAttr(__pyx_cython_runtime, __pyx_n_s_cline_in_traceback, Py_False); - } - else if (use_cline == Py_False || (use_cline != Py_True && PyObject_Not(use_cline) != 0)) { - c_line = 0; - } - __Pyx_ErrRestoreInState(tstate, ptype, pvalue, ptraceback); - return c_line; -} -#endif - -/* CodeObjectCache */ - static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line) { - int start = 0, mid = 0, end = count - 1; - if (end >= 0 && code_line > entries[end].code_line) { - return count; - } - while (start < end) { - mid = start + (end - start) / 2; - if (code_line < entries[mid].code_line) { - end = mid; - } else if (code_line > entries[mid].code_line) { - start = mid + 1; - } else { - return mid; - } - } - if (code_line <= entries[mid].code_line) { - return mid; - } else { - return mid + 1; - } -} -static PyCodeObject *__pyx_find_code_object(int code_line) { - PyCodeObject* code_object; - int pos; - if (unlikely(!code_line) || unlikely(!__pyx_code_cache.entries)) { - return NULL; - } - pos = __pyx_bisect_code_objects(__pyx_code_cache.entries, __pyx_code_cache.count, code_line); - if (unlikely(pos >= __pyx_code_cache.count) || unlikely(__pyx_code_cache.entries[pos].code_line != code_line)) { - return NULL; - } - code_object = __pyx_code_cache.entries[pos].code_object; - Py_INCREF(code_object); - return code_object; -} -static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object) { - int pos, i; - __Pyx_CodeObjectCacheEntry* entries = __pyx_code_cache.entries; - if (unlikely(!code_line)) { - return; - } - if (unlikely(!entries)) { - entries = (__Pyx_CodeObjectCacheEntry*)PyMem_Malloc(64*sizeof(__Pyx_CodeObjectCacheEntry)); - if (likely(entries)) { - __pyx_code_cache.entries = entries; - __pyx_code_cache.max_count = 64; - __pyx_code_cache.count = 1; - entries[0].code_line = code_line; - entries[0].code_object = code_object; - Py_INCREF(code_object); - } - return; - } - pos = __pyx_bisect_code_objects(__pyx_code_cache.entries, __pyx_code_cache.count, code_line); - if ((pos < __pyx_code_cache.count) && unlikely(__pyx_code_cache.entries[pos].code_line == code_line)) { - PyCodeObject* tmp = entries[pos].code_object; - entries[pos].code_object = code_object; - Py_DECREF(tmp); - return; - } - if (__pyx_code_cache.count == __pyx_code_cache.max_count) { - int new_max = __pyx_code_cache.max_count + 64; - entries = (__Pyx_CodeObjectCacheEntry*)PyMem_Realloc( - __pyx_code_cache.entries, (size_t)new_max*sizeof(__Pyx_CodeObjectCacheEntry)); - if (unlikely(!entries)) { - return; - } - __pyx_code_cache.entries = entries; - __pyx_code_cache.max_count = new_max; - } - for (i=__pyx_code_cache.count; i>pos; i--) { - entries[i] = entries[i-1]; - } - entries[pos].code_line = code_line; - entries[pos].code_object = code_object; - __pyx_code_cache.count++; - Py_INCREF(code_object); -} - -/* AddTraceback */ - #include "compile.h" -#include "frameobject.h" -#include "traceback.h" -static PyCodeObject* __Pyx_CreateCodeObjectForTraceback( - const char *funcname, int c_line, - int py_line, const char *filename) { - PyCodeObject *py_code = 0; - PyObject *py_srcfile = 0; - PyObject *py_funcname = 0; - #if PY_MAJOR_VERSION < 3 - py_srcfile = PyString_FromString(filename); - #else - py_srcfile = PyUnicode_FromString(filename); - #endif - if (!py_srcfile) goto bad; - if (c_line) { - #if PY_MAJOR_VERSION < 3 - py_funcname = PyString_FromFormat( "%s (%s:%d)", funcname, __pyx_cfilenm, c_line); - #else - py_funcname = PyUnicode_FromFormat( "%s (%s:%d)", funcname, __pyx_cfilenm, c_line); - #endif - } - else { - #if PY_MAJOR_VERSION < 3 - py_funcname = PyString_FromString(funcname); - #else - py_funcname = PyUnicode_FromString(funcname); - #endif - } - if (!py_funcname) goto bad; - py_code = __Pyx_PyCode_New( - 0, - 0, - 0, - 0, - 0, - __pyx_empty_bytes, /*PyObject *code,*/ - __pyx_empty_tuple, /*PyObject *consts,*/ - __pyx_empty_tuple, /*PyObject *names,*/ - __pyx_empty_tuple, /*PyObject *varnames,*/ - __pyx_empty_tuple, /*PyObject *freevars,*/ - __pyx_empty_tuple, /*PyObject *cellvars,*/ - py_srcfile, /*PyObject *filename,*/ - py_funcname, /*PyObject *name,*/ - py_line, - __pyx_empty_bytes /*PyObject *lnotab*/ - ); - Py_DECREF(py_srcfile); - Py_DECREF(py_funcname); - return py_code; -bad: - Py_XDECREF(py_srcfile); - Py_XDECREF(py_funcname); - return NULL; -} -static void __Pyx_AddTraceback(const char *funcname, int c_line, - int py_line, const char *filename) { - PyCodeObject *py_code = 0; - PyFrameObject *py_frame = 0; - PyThreadState *tstate = __Pyx_PyThreadState_Current; - if (c_line) { - c_line = __Pyx_CLineForTraceback(tstate, c_line); - } - py_code = __pyx_find_code_object(c_line ? -c_line : py_line); - if (!py_code) { - py_code = __Pyx_CreateCodeObjectForTraceback( - funcname, c_line, py_line, filename); - if (!py_code) goto bad; - __pyx_insert_code_object(c_line ? -c_line : py_line, py_code); - } - py_frame = PyFrame_New( - tstate, /*PyThreadState *tstate,*/ - py_code, /*PyCodeObject *code,*/ - __pyx_d, /*PyObject *globals,*/ - 0 /*PyObject *locals*/ - ); - if (!py_frame) goto bad; - __Pyx_PyFrame_SetLineNumber(py_frame, py_line); - PyTraceBack_Here(py_frame); -bad: - Py_XDECREF(py_code); - Py_XDECREF(py_frame); -} - -/* CIntToPy */ - static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) { - const long neg_one = (long) ((long) 0 - (long) 1), const_zero = (long) 0; - const int is_unsigned = neg_one > const_zero; - if (is_unsigned) { - if (sizeof(long) < sizeof(long)) { - return PyInt_FromLong((long) value); - } else if (sizeof(long) <= sizeof(unsigned long)) { - return PyLong_FromUnsignedLong((unsigned long) value); -#ifdef HAVE_LONG_LONG - } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) { - return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value); -#endif - } - } else { - if (sizeof(long) <= sizeof(long)) { - return PyInt_FromLong((long) value); -#ifdef HAVE_LONG_LONG - } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) { - return PyLong_FromLongLong((PY_LONG_LONG) value); -#endif - } - } - { - int one = 1; int little = (int)*(unsigned char *)&one; - unsigned char *bytes = (unsigned char *)&value; - return _PyLong_FromByteArray(bytes, sizeof(long), - little, !is_unsigned); - } -} - -/* CIntFromPyVerify */ - #define __PYX_VERIFY_RETURN_INT(target_type, func_type, func_value)\ - __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, 0) -#define __PYX_VERIFY_RETURN_INT_EXC(target_type, func_type, func_value)\ - __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, 1) -#define __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, exc)\ - {\ - func_type value = func_value;\ - if (sizeof(target_type) < sizeof(func_type)) {\ - if (unlikely(value != (func_type) (target_type) value)) {\ - func_type zero = 0;\ - if (exc && unlikely(value == (func_type)-1 && PyErr_Occurred()))\ - return (target_type) -1;\ - if (is_unsigned && unlikely(value < zero))\ - goto raise_neg_overflow;\ - else\ - goto raise_overflow;\ - }\ - }\ - return (target_type) value;\ - } - -/* Print */ - #if !CYTHON_COMPILING_IN_PYPY && PY_MAJOR_VERSION < 3 -static PyObject *__Pyx_GetStdout(void) { - PyObject *f = PySys_GetObject((char *)"stdout"); - if (!f) { - PyErr_SetString(PyExc_RuntimeError, "lost sys.stdout"); - } - return f; -} -static int __Pyx_Print(PyObject* f, PyObject *arg_tuple, int newline) { - int i; - if (!f) { - if (!(f = __Pyx_GetStdout())) - return -1; - } - Py_INCREF(f); - for (i=0; i < PyTuple_GET_SIZE(arg_tuple); i++) { - PyObject* v; - if (PyFile_SoftSpace(f, 1)) { - if (PyFile_WriteString(" ", f) < 0) - goto error; - } - v = PyTuple_GET_ITEM(arg_tuple, i); - if (PyFile_WriteObject(v, f, Py_PRINT_RAW) < 0) - goto error; - if (PyString_Check(v)) { - char *s = PyString_AsString(v); - Py_ssize_t len = PyString_Size(v); - if (len > 0) { - switch (s[len-1]) { - case ' ': break; - case '\f': case '\r': case '\n': case '\t': case '\v': - PyFile_SoftSpace(f, 0); - break; - default: break; - } - } - } - } - if (newline) { - if (PyFile_WriteString("\n", f) < 0) - goto error; - PyFile_SoftSpace(f, 0); - } - Py_DECREF(f); - return 0; -error: - Py_DECREF(f); - return -1; -} -#else -static int __Pyx_Print(PyObject* stream, PyObject *arg_tuple, int newline) { - PyObject* kwargs = 0; - PyObject* result = 0; - PyObject* end_string; - if (unlikely(!__pyx_print)) { - __pyx_print = PyObject_GetAttr(__pyx_b, __pyx_n_s_print); - if (!__pyx_print) - return -1; - } - if (stream) { - kwargs = PyDict_New(); - if (unlikely(!kwargs)) - return -1; - if (unlikely(PyDict_SetItem(kwargs, __pyx_n_s_file, stream) < 0)) - goto bad; - if (!newline) { - end_string = PyUnicode_FromStringAndSize(" ", 1); - if (unlikely(!end_string)) - goto bad; - if (PyDict_SetItem(kwargs, __pyx_n_s_end, end_string) < 0) { - Py_DECREF(end_string); - goto bad; - } - Py_DECREF(end_string); - } - } else if (!newline) { - if (unlikely(!__pyx_print_kwargs)) { - __pyx_print_kwargs = PyDict_New(); - if (unlikely(!__pyx_print_kwargs)) - return -1; - end_string = PyUnicode_FromStringAndSize(" ", 1); - if (unlikely(!end_string)) - return -1; - if (PyDict_SetItem(__pyx_print_kwargs, __pyx_n_s_end, end_string) < 0) { - Py_DECREF(end_string); - return -1; - } - Py_DECREF(end_string); - } - kwargs = __pyx_print_kwargs; - } - result = PyObject_Call(__pyx_print, arg_tuple, kwargs); - if (unlikely(kwargs) && (kwargs != __pyx_print_kwargs)) - Py_DECREF(kwargs); - if (!result) - return -1; - Py_DECREF(result); - return 0; -bad: - if (kwargs != __pyx_print_kwargs) - Py_XDECREF(kwargs); - return -1; -} -#endif - -/* Declarations */ - #if CYTHON_CCOMPLEX - #ifdef __cplusplus - static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_from_parts(float x, float y) { - return ::std::complex< float >(x, y); - } - #else - static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_from_parts(float x, float y) { - return x + y*(__pyx_t_float_complex)_Complex_I; - } - #endif -#else - static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_from_parts(float x, float y) { - __pyx_t_float_complex z; - z.real = x; - z.imag = y; - return z; - } -#endif - -/* Arithmetic */ - #if CYTHON_CCOMPLEX -#else - static CYTHON_INLINE int __Pyx_c_eq_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { - return (a.real == b.real) && (a.imag == b.imag); - } - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_sum_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { - __pyx_t_float_complex z; - z.real = a.real + b.real; - z.imag = a.imag + b.imag; - return z; - } - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_diff_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { - __pyx_t_float_complex z; - z.real = a.real - b.real; - z.imag = a.imag - b.imag; - return z; - } - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_prod_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { - __pyx_t_float_complex z; - z.real = a.real * b.real - a.imag * b.imag; - z.imag = a.real * b.imag + a.imag * b.real; - return z; - } - #if 1 - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_quot_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { - if (b.imag == 0) { - return __pyx_t_float_complex_from_parts(a.real / b.real, a.imag / b.real); - } else if (fabsf(b.real) >= fabsf(b.imag)) { - if (b.real == 0 && b.imag == 0) { - return __pyx_t_float_complex_from_parts(a.real / b.real, a.imag / b.imag); - } else { - float r = b.imag / b.real; - float s = (float)(1.0) / (b.real + b.imag * r); - return __pyx_t_float_complex_from_parts( - (a.real + a.imag * r) * s, (a.imag - a.real * r) * s); - } - } else { - float r = b.real / b.imag; - float s = (float)(1.0) / (b.imag + b.real * r); - return __pyx_t_float_complex_from_parts( - (a.real * r + a.imag) * s, (a.imag * r - a.real) * s); - } - } - #else - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_quot_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { - if (b.imag == 0) { - return __pyx_t_float_complex_from_parts(a.real / b.real, a.imag / b.real); - } else { - float denom = b.real * b.real + b.imag * b.imag; - return __pyx_t_float_complex_from_parts( - (a.real * b.real + a.imag * b.imag) / denom, - (a.imag * b.real - a.real * b.imag) / denom); - } - } - #endif - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_neg_float(__pyx_t_float_complex a) { - __pyx_t_float_complex z; - z.real = -a.real; - z.imag = -a.imag; - return z; - } - static CYTHON_INLINE int __Pyx_c_is_zero_float(__pyx_t_float_complex a) { - return (a.real == 0) && (a.imag == 0); - } - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_conj_float(__pyx_t_float_complex a) { - __pyx_t_float_complex z; - z.real = a.real; - z.imag = -a.imag; - return z; - } - #if 1 - static CYTHON_INLINE float __Pyx_c_abs_float(__pyx_t_float_complex z) { - #if !defined(HAVE_HYPOT) || defined(_MSC_VER) - return sqrtf(z.real*z.real + z.imag*z.imag); - #else - return hypotf(z.real, z.imag); - #endif - } - static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_pow_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { - __pyx_t_float_complex z; - float r, lnr, theta, z_r, z_theta; - if (b.imag == 0 && b.real == (int)b.real) { - if (b.real < 0) { - float denom = a.real * a.real + a.imag * a.imag; - a.real = a.real / denom; - a.imag = -a.imag / denom; - b.real = -b.real; - } - switch ((int)b.real) { - case 0: - z.real = 1; - z.imag = 0; - return z; - case 1: - return a; - case 2: - return __Pyx_c_prod_float(a, a); - case 3: - z = __Pyx_c_prod_float(a, a); - return __Pyx_c_prod_float(z, a); - case 4: - z = __Pyx_c_prod_float(a, a); - return __Pyx_c_prod_float(z, z); - } - } - if (a.imag == 0) { - if (a.real == 0) { - return a; - } else if (b.imag == 0) { - z.real = powf(a.real, b.real); - z.imag = 0; - return z; - } else if (a.real > 0) { - r = a.real; - theta = 0; - } else { - r = -a.real; - theta = atan2f(0.0, -1.0); - } - } else { - r = __Pyx_c_abs_float(a); - theta = atan2f(a.imag, a.real); - } - lnr = logf(r); - z_r = expf(lnr * b.real - theta * b.imag); - z_theta = theta * b.real + lnr * b.imag; - z.real = z_r * cosf(z_theta); - z.imag = z_r * sinf(z_theta); - return z; - } - #endif -#endif - -/* Declarations */ - #if CYTHON_CCOMPLEX - #ifdef __cplusplus - static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(double x, double y) { - return ::std::complex< double >(x, y); - } - #else - static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(double x, double y) { - return x + y*(__pyx_t_double_complex)_Complex_I; - } - #endif -#else - static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(double x, double y) { - __pyx_t_double_complex z; - z.real = x; - z.imag = y; - return z; - } -#endif - -/* Arithmetic */ - #if CYTHON_CCOMPLEX -#else - static CYTHON_INLINE int __Pyx_c_eq_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { - return (a.real == b.real) && (a.imag == b.imag); - } - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_sum_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { - __pyx_t_double_complex z; - z.real = a.real + b.real; - z.imag = a.imag + b.imag; - return z; - } - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_diff_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { - __pyx_t_double_complex z; - z.real = a.real - b.real; - z.imag = a.imag - b.imag; - return z; - } - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_prod_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { - __pyx_t_double_complex z; - z.real = a.real * b.real - a.imag * b.imag; - z.imag = a.real * b.imag + a.imag * b.real; - return z; - } - #if 1 - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_quot_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { - if (b.imag == 0) { - return __pyx_t_double_complex_from_parts(a.real / b.real, a.imag / b.real); - } else if (fabs(b.real) >= fabs(b.imag)) { - if (b.real == 0 && b.imag == 0) { - return __pyx_t_double_complex_from_parts(a.real / b.real, a.imag / b.imag); - } else { - double r = b.imag / b.real; - double s = (double)(1.0) / (b.real + b.imag * r); - return __pyx_t_double_complex_from_parts( - (a.real + a.imag * r) * s, (a.imag - a.real * r) * s); - } - } else { - double r = b.real / b.imag; - double s = (double)(1.0) / (b.imag + b.real * r); - return __pyx_t_double_complex_from_parts( - (a.real * r + a.imag) * s, (a.imag * r - a.real) * s); - } - } - #else - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_quot_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { - if (b.imag == 0) { - return __pyx_t_double_complex_from_parts(a.real / b.real, a.imag / b.real); - } else { - double denom = b.real * b.real + b.imag * b.imag; - return __pyx_t_double_complex_from_parts( - (a.real * b.real + a.imag * b.imag) / denom, - (a.imag * b.real - a.real * b.imag) / denom); - } - } - #endif - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_neg_double(__pyx_t_double_complex a) { - __pyx_t_double_complex z; - z.real = -a.real; - z.imag = -a.imag; - return z; - } - static CYTHON_INLINE int __Pyx_c_is_zero_double(__pyx_t_double_complex a) { - return (a.real == 0) && (a.imag == 0); - } - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_conj_double(__pyx_t_double_complex a) { - __pyx_t_double_complex z; - z.real = a.real; - z.imag = -a.imag; - return z; - } - #if 1 - static CYTHON_INLINE double __Pyx_c_abs_double(__pyx_t_double_complex z) { - #if !defined(HAVE_HYPOT) || defined(_MSC_VER) - return sqrt(z.real*z.real + z.imag*z.imag); - #else - return hypot(z.real, z.imag); - #endif - } - static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_pow_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { - __pyx_t_double_complex z; - double r, lnr, theta, z_r, z_theta; - if (b.imag == 0 && b.real == (int)b.real) { - if (b.real < 0) { - double denom = a.real * a.real + a.imag * a.imag; - a.real = a.real / denom; - a.imag = -a.imag / denom; - b.real = -b.real; - } - switch ((int)b.real) { - case 0: - z.real = 1; - z.imag = 0; - return z; - case 1: - return a; - case 2: - return __Pyx_c_prod_double(a, a); - case 3: - z = __Pyx_c_prod_double(a, a); - return __Pyx_c_prod_double(z, a); - case 4: - z = __Pyx_c_prod_double(a, a); - return __Pyx_c_prod_double(z, z); - } - } - if (a.imag == 0) { - if (a.real == 0) { - return a; - } else if (b.imag == 0) { - z.real = pow(a.real, b.real); - z.imag = 0; - return z; - } else if (a.real > 0) { - r = a.real; - theta = 0; - } else { - r = -a.real; - theta = atan2(0.0, -1.0); - } - } else { - r = __Pyx_c_abs_double(a); - theta = atan2(a.imag, a.real); - } - lnr = log(r); - z_r = exp(lnr * b.real - theta * b.imag); - z_theta = theta * b.real + lnr * b.imag; - z.real = z_r * cos(z_theta); - z.imag = z_r * sin(z_theta); - return z; - } - #endif -#endif - -/* CIntToPy */ - static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value) { - const int neg_one = (int) ((int) 0 - (int) 1), const_zero = (int) 0; - const int is_unsigned = neg_one > const_zero; - if (is_unsigned) { - if (sizeof(int) < sizeof(long)) { - return PyInt_FromLong((long) value); - } else if (sizeof(int) <= sizeof(unsigned long)) { - return PyLong_FromUnsignedLong((unsigned long) value); -#ifdef HAVE_LONG_LONG - } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) { - return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value); -#endif - } - } else { - if (sizeof(int) <= sizeof(long)) { - return PyInt_FromLong((long) value); -#ifdef HAVE_LONG_LONG - } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) { - return PyLong_FromLongLong((PY_LONG_LONG) value); -#endif - } - } - { - int one = 1; int little = (int)*(unsigned char *)&one; - unsigned char *bytes = (unsigned char *)&value; - return _PyLong_FromByteArray(bytes, sizeof(int), - little, !is_unsigned); - } -} - -/* CIntToPy */ - static CYTHON_INLINE PyObject* __Pyx_PyInt_From_enum__NPY_TYPES(enum NPY_TYPES value) { - const enum NPY_TYPES neg_one = (enum NPY_TYPES) ((enum NPY_TYPES) 0 - (enum NPY_TYPES) 1), const_zero = (enum NPY_TYPES) 0; - const int is_unsigned = neg_one > const_zero; - if (is_unsigned) { - if (sizeof(enum NPY_TYPES) < sizeof(long)) { - return PyInt_FromLong((long) value); - } else if (sizeof(enum NPY_TYPES) <= sizeof(unsigned long)) { - return PyLong_FromUnsignedLong((unsigned long) value); -#ifdef HAVE_LONG_LONG - } else if (sizeof(enum NPY_TYPES) <= sizeof(unsigned PY_LONG_LONG)) { - return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value); -#endif - } - } else { - if (sizeof(enum NPY_TYPES) <= sizeof(long)) { - return PyInt_FromLong((long) value); -#ifdef HAVE_LONG_LONG - } else if (sizeof(enum NPY_TYPES) <= sizeof(PY_LONG_LONG)) { - return PyLong_FromLongLong((PY_LONG_LONG) value); -#endif - } - } - { - int one = 1; int little = (int)*(unsigned char *)&one; - unsigned char *bytes = (unsigned char *)&value; - return _PyLong_FromByteArray(bytes, sizeof(enum NPY_TYPES), - little, !is_unsigned); - } -} - -/* CIntFromPy */ - static CYTHON_INLINE size_t __Pyx_PyInt_As_size_t(PyObject *x) { - const size_t neg_one = (size_t) ((size_t) 0 - (size_t) 1), const_zero = (size_t) 0; - const int is_unsigned = neg_one > const_zero; -#if PY_MAJOR_VERSION < 3 - if (likely(PyInt_Check(x))) { - if (sizeof(size_t) < sizeof(long)) { - __PYX_VERIFY_RETURN_INT(size_t, long, PyInt_AS_LONG(x)) - } else { - long val = PyInt_AS_LONG(x); - if (is_unsigned && unlikely(val < 0)) { - goto raise_neg_overflow; - } - return (size_t) val; - } - } else -#endif - if (likely(PyLong_Check(x))) { - if (is_unsigned) { -#if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)x)->ob_digit; - switch (Py_SIZE(x)) { - case 0: return (size_t) 0; - case 1: __PYX_VERIFY_RETURN_INT(size_t, digit, digits[0]) - case 2: - if (8 * sizeof(size_t) > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) >= 2 * PyLong_SHIFT) { - return (size_t) (((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - } - break; - case 3: - if (8 * sizeof(size_t) > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) >= 3 * PyLong_SHIFT) { - return (size_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - } - break; - case 4: - if (8 * sizeof(size_t) > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) >= 4 * PyLong_SHIFT) { - return (size_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - } - break; - } -#endif -#if CYTHON_COMPILING_IN_CPYTHON - if (unlikely(Py_SIZE(x) < 0)) { - goto raise_neg_overflow; - } -#else - { - int result = PyObject_RichCompareBool(x, Py_False, Py_LT); - if (unlikely(result < 0)) - return (size_t) -1; - if (unlikely(result == 1)) - goto raise_neg_overflow; - } -#endif - if (sizeof(size_t) <= sizeof(unsigned long)) { - __PYX_VERIFY_RETURN_INT_EXC(size_t, unsigned long, PyLong_AsUnsignedLong(x)) -#ifdef HAVE_LONG_LONG - } else if (sizeof(size_t) <= sizeof(unsigned PY_LONG_LONG)) { - __PYX_VERIFY_RETURN_INT_EXC(size_t, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x)) -#endif - } - } else { -#if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)x)->ob_digit; - switch (Py_SIZE(x)) { - case 0: return (size_t) 0; - case -1: __PYX_VERIFY_RETURN_INT(size_t, sdigit, (sdigit) (-(sdigit)digits[0])) - case 1: __PYX_VERIFY_RETURN_INT(size_t, digit, +digits[0]) - case -2: - if (8 * sizeof(size_t) - 1 > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, long, -(long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) - 1 > 2 * PyLong_SHIFT) { - return (size_t) (((size_t)-1)*(((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); - } - } - break; - case 2: - if (8 * sizeof(size_t) > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) - 1 > 2 * PyLong_SHIFT) { - return (size_t) ((((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); - } - } - break; - case -3: - if (8 * sizeof(size_t) - 1 > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, long, -(long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) - 1 > 3 * PyLong_SHIFT) { - return (size_t) (((size_t)-1)*(((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); - } - } - break; - case 3: - if (8 * sizeof(size_t) > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) - 1 > 3 * PyLong_SHIFT) { - return (size_t) ((((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); - } - } - break; - case -4: - if (8 * sizeof(size_t) - 1 > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, long, -(long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) - 1 > 4 * PyLong_SHIFT) { - return (size_t) (((size_t)-1)*(((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); - } - } - break; - case 4: - if (8 * sizeof(size_t) > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(size_t) - 1 > 4 * PyLong_SHIFT) { - return (size_t) ((((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); - } - } - break; - } -#endif - if (sizeof(size_t) <= sizeof(long)) { - __PYX_VERIFY_RETURN_INT_EXC(size_t, long, PyLong_AsLong(x)) -#ifdef HAVE_LONG_LONG - } else if (sizeof(size_t) <= sizeof(PY_LONG_LONG)) { - __PYX_VERIFY_RETURN_INT_EXC(size_t, PY_LONG_LONG, PyLong_AsLongLong(x)) -#endif - } - } - { -#if CYTHON_COMPILING_IN_PYPY && !defined(_PyLong_AsByteArray) - PyErr_SetString(PyExc_RuntimeError, - "_PyLong_AsByteArray() not available in PyPy, cannot convert large numbers"); -#else - size_t val; - PyObject *v = __Pyx_PyNumber_IntOrLong(x); - #if PY_MAJOR_VERSION < 3 - if (likely(v) && !PyLong_Check(v)) { - PyObject *tmp = v; - v = PyNumber_Long(tmp); - Py_DECREF(tmp); - } - #endif - if (likely(v)) { - int one = 1; int is_little = (int)*(unsigned char *)&one; - unsigned char *bytes = (unsigned char *)&val; - int ret = _PyLong_AsByteArray((PyLongObject *)v, - bytes, sizeof(val), - is_little, !is_unsigned); - Py_DECREF(v); - if (likely(!ret)) - return val; - } -#endif - return (size_t) -1; - } - } else { - size_t val; - PyObject *tmp = __Pyx_PyNumber_IntOrLong(x); - if (!tmp) return (size_t) -1; - val = __Pyx_PyInt_As_size_t(tmp); - Py_DECREF(tmp); - return val; - } -raise_overflow: - PyErr_SetString(PyExc_OverflowError, - "value too large to convert to size_t"); - return (size_t) -1; -raise_neg_overflow: - PyErr_SetString(PyExc_OverflowError, - "can't convert negative value to size_t"); - return (size_t) -1; -} - -/* PrintOne */ - #if !CYTHON_COMPILING_IN_PYPY && PY_MAJOR_VERSION < 3 -static int __Pyx_PrintOne(PyObject* f, PyObject *o) { - if (!f) { - if (!(f = __Pyx_GetStdout())) - return -1; - } - Py_INCREF(f); - if (PyFile_SoftSpace(f, 0)) { - if (PyFile_WriteString(" ", f) < 0) - goto error; - } - if (PyFile_WriteObject(o, f, Py_PRINT_RAW) < 0) - goto error; - if (PyFile_WriteString("\n", f) < 0) - goto error; - Py_DECREF(f); - return 0; -error: - Py_DECREF(f); - return -1; - /* the line below is just to avoid C compiler - * warnings about unused functions */ - return __Pyx_Print(f, NULL, 0); -} -#else -static int __Pyx_PrintOne(PyObject* stream, PyObject *o) { - int res; - PyObject* arg_tuple = PyTuple_Pack(1, o); - if (unlikely(!arg_tuple)) - return -1; - res = __Pyx_Print(stream, arg_tuple, 1); - Py_DECREF(arg_tuple); - return res; -} -#endif - -/* CIntFromPy */ - static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *x) { - const int neg_one = (int) ((int) 0 - (int) 1), const_zero = (int) 0; - const int is_unsigned = neg_one > const_zero; -#if PY_MAJOR_VERSION < 3 - if (likely(PyInt_Check(x))) { - if (sizeof(int) < sizeof(long)) { - __PYX_VERIFY_RETURN_INT(int, long, PyInt_AS_LONG(x)) - } else { - long val = PyInt_AS_LONG(x); - if (is_unsigned && unlikely(val < 0)) { - goto raise_neg_overflow; - } - return (int) val; - } - } else -#endif - if (likely(PyLong_Check(x))) { - if (is_unsigned) { -#if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)x)->ob_digit; - switch (Py_SIZE(x)) { - case 0: return (int) 0; - case 1: __PYX_VERIFY_RETURN_INT(int, digit, digits[0]) - case 2: - if (8 * sizeof(int) > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) >= 2 * PyLong_SHIFT) { - return (int) (((((int)digits[1]) << PyLong_SHIFT) | (int)digits[0])); - } - } - break; - case 3: - if (8 * sizeof(int) > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) >= 3 * PyLong_SHIFT) { - return (int) (((((((int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0])); - } - } - break; - case 4: - if (8 * sizeof(int) > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) >= 4 * PyLong_SHIFT) { - return (int) (((((((((int)digits[3]) << PyLong_SHIFT) | (int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0])); - } - } - break; - } -#endif -#if CYTHON_COMPILING_IN_CPYTHON - if (unlikely(Py_SIZE(x) < 0)) { - goto raise_neg_overflow; - } -#else - { - int result = PyObject_RichCompareBool(x, Py_False, Py_LT); - if (unlikely(result < 0)) - return (int) -1; - if (unlikely(result == 1)) - goto raise_neg_overflow; - } -#endif - if (sizeof(int) <= sizeof(unsigned long)) { - __PYX_VERIFY_RETURN_INT_EXC(int, unsigned long, PyLong_AsUnsignedLong(x)) -#ifdef HAVE_LONG_LONG - } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) { - __PYX_VERIFY_RETURN_INT_EXC(int, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x)) -#endif - } - } else { -#if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)x)->ob_digit; - switch (Py_SIZE(x)) { - case 0: return (int) 0; - case -1: __PYX_VERIFY_RETURN_INT(int, sdigit, (sdigit) (-(sdigit)digits[0])) - case 1: __PYX_VERIFY_RETURN_INT(int, digit, +digits[0]) - case -2: - if (8 * sizeof(int) - 1 > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, long, -(long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) - 1 > 2 * PyLong_SHIFT) { - return (int) (((int)-1)*(((((int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); - } - } - break; - case 2: - if (8 * sizeof(int) > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) - 1 > 2 * PyLong_SHIFT) { - return (int) ((((((int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); - } - } - break; - case -3: - if (8 * sizeof(int) - 1 > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, long, -(long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) - 1 > 3 * PyLong_SHIFT) { - return (int) (((int)-1)*(((((((int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); - } - } - break; - case 3: - if (8 * sizeof(int) > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) - 1 > 3 * PyLong_SHIFT) { - return (int) ((((((((int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); - } - } - break; - case -4: - if (8 * sizeof(int) - 1 > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, long, -(long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) - 1 > 4 * PyLong_SHIFT) { - return (int) (((int)-1)*(((((((((int)digits[3]) << PyLong_SHIFT) | (int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); - } - } - break; - case 4: - if (8 * sizeof(int) > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(int) - 1 > 4 * PyLong_SHIFT) { - return (int) ((((((((((int)digits[3]) << PyLong_SHIFT) | (int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); - } - } - break; - } -#endif - if (sizeof(int) <= sizeof(long)) { - __PYX_VERIFY_RETURN_INT_EXC(int, long, PyLong_AsLong(x)) -#ifdef HAVE_LONG_LONG - } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) { - __PYX_VERIFY_RETURN_INT_EXC(int, PY_LONG_LONG, PyLong_AsLongLong(x)) -#endif - } - } - { -#if CYTHON_COMPILING_IN_PYPY && !defined(_PyLong_AsByteArray) - PyErr_SetString(PyExc_RuntimeError, - "_PyLong_AsByteArray() not available in PyPy, cannot convert large numbers"); -#else - int val; - PyObject *v = __Pyx_PyNumber_IntOrLong(x); - #if PY_MAJOR_VERSION < 3 - if (likely(v) && !PyLong_Check(v)) { - PyObject *tmp = v; - v = PyNumber_Long(tmp); - Py_DECREF(tmp); - } - #endif - if (likely(v)) { - int one = 1; int is_little = (int)*(unsigned char *)&one; - unsigned char *bytes = (unsigned char *)&val; - int ret = _PyLong_AsByteArray((PyLongObject *)v, - bytes, sizeof(val), - is_little, !is_unsigned); - Py_DECREF(v); - if (likely(!ret)) - return val; - } -#endif - return (int) -1; - } - } else { - int val; - PyObject *tmp = __Pyx_PyNumber_IntOrLong(x); - if (!tmp) return (int) -1; - val = __Pyx_PyInt_As_int(tmp); - Py_DECREF(tmp); - return val; - } -raise_overflow: - PyErr_SetString(PyExc_OverflowError, - "value too large to convert to int"); - return (int) -1; -raise_neg_overflow: - PyErr_SetString(PyExc_OverflowError, - "can't convert negative value to int"); - return (int) -1; -} - -/* CIntFromPy */ - static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *x) { - const long neg_one = (long) ((long) 0 - (long) 1), const_zero = (long) 0; - const int is_unsigned = neg_one > const_zero; -#if PY_MAJOR_VERSION < 3 - if (likely(PyInt_Check(x))) { - if (sizeof(long) < sizeof(long)) { - __PYX_VERIFY_RETURN_INT(long, long, PyInt_AS_LONG(x)) - } else { - long val = PyInt_AS_LONG(x); - if (is_unsigned && unlikely(val < 0)) { - goto raise_neg_overflow; - } - return (long) val; - } - } else -#endif - if (likely(PyLong_Check(x))) { - if (is_unsigned) { -#if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)x)->ob_digit; - switch (Py_SIZE(x)) { - case 0: return (long) 0; - case 1: __PYX_VERIFY_RETURN_INT(long, digit, digits[0]) - case 2: - if (8 * sizeof(long) > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) >= 2 * PyLong_SHIFT) { - return (long) (((((long)digits[1]) << PyLong_SHIFT) | (long)digits[0])); - } - } - break; - case 3: - if (8 * sizeof(long) > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) >= 3 * PyLong_SHIFT) { - return (long) (((((((long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0])); - } - } - break; - case 4: - if (8 * sizeof(long) > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) >= 4 * PyLong_SHIFT) { - return (long) (((((((((long)digits[3]) << PyLong_SHIFT) | (long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0])); - } - } - break; - } -#endif -#if CYTHON_COMPILING_IN_CPYTHON - if (unlikely(Py_SIZE(x) < 0)) { - goto raise_neg_overflow; - } -#else - { - int result = PyObject_RichCompareBool(x, Py_False, Py_LT); - if (unlikely(result < 0)) - return (long) -1; - if (unlikely(result == 1)) - goto raise_neg_overflow; - } -#endif - if (sizeof(long) <= sizeof(unsigned long)) { - __PYX_VERIFY_RETURN_INT_EXC(long, unsigned long, PyLong_AsUnsignedLong(x)) -#ifdef HAVE_LONG_LONG - } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) { - __PYX_VERIFY_RETURN_INT_EXC(long, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x)) -#endif - } - } else { -#if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)x)->ob_digit; - switch (Py_SIZE(x)) { - case 0: return (long) 0; - case -1: __PYX_VERIFY_RETURN_INT(long, sdigit, (sdigit) (-(sdigit)digits[0])) - case 1: __PYX_VERIFY_RETURN_INT(long, digit, +digits[0]) - case -2: - if (8 * sizeof(long) - 1 > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, long, -(long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) { - return (long) (((long)-1)*(((((long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); - } - } - break; - case 2: - if (8 * sizeof(long) > 1 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) { - return (long) ((((((long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); - } - } - break; - case -3: - if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, long, -(long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) { - return (long) (((long)-1)*(((((((long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); - } - } - break; - case 3: - if (8 * sizeof(long) > 2 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) { - return (long) ((((((((long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); - } - } - break; - case -4: - if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, long, -(long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) - 1 > 4 * PyLong_SHIFT) { - return (long) (((long)-1)*(((((((((long)digits[3]) << PyLong_SHIFT) | (long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); - } - } - break; - case 4: - if (8 * sizeof(long) > 3 * PyLong_SHIFT) { - if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { - __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) - } else if (8 * sizeof(long) - 1 > 4 * PyLong_SHIFT) { - return (long) ((((((((((long)digits[3]) << PyLong_SHIFT) | (long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); - } - } - break; - } -#endif - if (sizeof(long) <= sizeof(long)) { - __PYX_VERIFY_RETURN_INT_EXC(long, long, PyLong_AsLong(x)) -#ifdef HAVE_LONG_LONG - } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) { - __PYX_VERIFY_RETURN_INT_EXC(long, PY_LONG_LONG, PyLong_AsLongLong(x)) -#endif - } - } - { -#if CYTHON_COMPILING_IN_PYPY && !defined(_PyLong_AsByteArray) - PyErr_SetString(PyExc_RuntimeError, - "_PyLong_AsByteArray() not available in PyPy, cannot convert large numbers"); -#else - long val; - PyObject *v = __Pyx_PyNumber_IntOrLong(x); - #if PY_MAJOR_VERSION < 3 - if (likely(v) && !PyLong_Check(v)) { - PyObject *tmp = v; - v = PyNumber_Long(tmp); - Py_DECREF(tmp); - } - #endif - if (likely(v)) { - int one = 1; int is_little = (int)*(unsigned char *)&one; - unsigned char *bytes = (unsigned char *)&val; - int ret = _PyLong_AsByteArray((PyLongObject *)v, - bytes, sizeof(val), - is_little, !is_unsigned); - Py_DECREF(v); - if (likely(!ret)) - return val; - } -#endif - return (long) -1; - } - } else { - long val; - PyObject *tmp = __Pyx_PyNumber_IntOrLong(x); - if (!tmp) return (long) -1; - val = __Pyx_PyInt_As_long(tmp); - Py_DECREF(tmp); - return val; - } -raise_overflow: - PyErr_SetString(PyExc_OverflowError, - "value too large to convert to long"); - return (long) -1; -raise_neg_overflow: - PyErr_SetString(PyExc_OverflowError, - "can't convert negative value to long"); - return (long) -1; -} - -/* FastTypeChecks */ - #if CYTHON_COMPILING_IN_CPYTHON -static int __Pyx_InBases(PyTypeObject *a, PyTypeObject *b) { - while (a) { - a = a->tp_base; - if (a == b) - return 1; - } - return b == &PyBaseObject_Type; -} -static CYTHON_INLINE int __Pyx_IsSubtype(PyTypeObject *a, PyTypeObject *b) { - PyObject *mro; - if (a == b) return 1; - mro = a->tp_mro; - if (likely(mro)) { - Py_ssize_t i, n; - n = PyTuple_GET_SIZE(mro); - for (i = 0; i < n; i++) { - if (PyTuple_GET_ITEM(mro, i) == (PyObject *)b) - return 1; - } - return 0; - } - return __Pyx_InBases(a, b); -} -#if PY_MAJOR_VERSION == 2 -static int __Pyx_inner_PyErr_GivenExceptionMatches2(PyObject *err, PyObject* exc_type1, PyObject* exc_type2) { - PyObject *exception, *value, *tb; - int res; - __Pyx_PyThreadState_declare - __Pyx_PyThreadState_assign - __Pyx_ErrFetch(&exception, &value, &tb); - res = exc_type1 ? PyObject_IsSubclass(err, exc_type1) : 0; - if (unlikely(res == -1)) { - PyErr_WriteUnraisable(err); - res = 0; - } - if (!res) { - res = PyObject_IsSubclass(err, exc_type2); - if (unlikely(res == -1)) { - PyErr_WriteUnraisable(err); - res = 0; - } - } - __Pyx_ErrRestore(exception, value, tb); - return res; -} -#else -static CYTHON_INLINE int __Pyx_inner_PyErr_GivenExceptionMatches2(PyObject *err, PyObject* exc_type1, PyObject *exc_type2) { - int res = exc_type1 ? __Pyx_IsSubtype((PyTypeObject*)err, (PyTypeObject*)exc_type1) : 0; - if (!res) { - res = __Pyx_IsSubtype((PyTypeObject*)err, (PyTypeObject*)exc_type2); - } - return res; -} -#endif -static int __Pyx_PyErr_GivenExceptionMatchesTuple(PyObject *exc_type, PyObject *tuple) { - Py_ssize_t i, n; - assert(PyExceptionClass_Check(exc_type)); - n = PyTuple_GET_SIZE(tuple); -#if PY_MAJOR_VERSION >= 3 - for (i=0; ip) { - #if PY_MAJOR_VERSION < 3 - if (t->is_unicode) { - *t->p = PyUnicode_DecodeUTF8(t->s, t->n - 1, NULL); - } else if (t->intern) { - *t->p = PyString_InternFromString(t->s); - } else { - *t->p = PyString_FromStringAndSize(t->s, t->n - 1); - } - #else - if (t->is_unicode | t->is_str) { - if (t->intern) { - *t->p = PyUnicode_InternFromString(t->s); - } else if (t->encoding) { - *t->p = PyUnicode_Decode(t->s, t->n - 1, t->encoding, NULL); - } else { - *t->p = PyUnicode_FromStringAndSize(t->s, t->n - 1); - } - } else { - *t->p = PyBytes_FromStringAndSize(t->s, t->n - 1); - } - #endif - if (!*t->p) - return -1; - if (PyObject_Hash(*t->p) == -1) - return -1; - ++t; - } - return 0; -} - -static CYTHON_INLINE PyObject* __Pyx_PyUnicode_FromString(const char* c_str) { - return __Pyx_PyUnicode_FromStringAndSize(c_str, (Py_ssize_t)strlen(c_str)); -} -static CYTHON_INLINE const char* __Pyx_PyObject_AsString(PyObject* o) { - Py_ssize_t ignore; - return __Pyx_PyObject_AsStringAndSize(o, &ignore); -} -#if __PYX_DEFAULT_STRING_ENCODING_IS_ASCII || __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT -#if !CYTHON_PEP393_ENABLED -static const char* __Pyx_PyUnicode_AsStringAndSize(PyObject* o, Py_ssize_t *length) { - char* defenc_c; - PyObject* defenc = _PyUnicode_AsDefaultEncodedString(o, NULL); - if (!defenc) return NULL; - defenc_c = PyBytes_AS_STRING(defenc); -#if __PYX_DEFAULT_STRING_ENCODING_IS_ASCII - { - char* end = defenc_c + PyBytes_GET_SIZE(defenc); - char* c; - for (c = defenc_c; c < end; c++) { - if ((unsigned char) (*c) >= 128) { - PyUnicode_AsASCIIString(o); - return NULL; - } - } - } -#endif - *length = PyBytes_GET_SIZE(defenc); - return defenc_c; -} -#else -static CYTHON_INLINE const char* __Pyx_PyUnicode_AsStringAndSize(PyObject* o, Py_ssize_t *length) { - if (unlikely(__Pyx_PyUnicode_READY(o) == -1)) return NULL; -#if __PYX_DEFAULT_STRING_ENCODING_IS_ASCII - if (likely(PyUnicode_IS_ASCII(o))) { - *length = PyUnicode_GET_LENGTH(o); - return PyUnicode_AsUTF8(o); - } else { - PyUnicode_AsASCIIString(o); - return NULL; - } -#else - return PyUnicode_AsUTF8AndSize(o, length); -#endif -} -#endif -#endif -static CYTHON_INLINE const char* __Pyx_PyObject_AsStringAndSize(PyObject* o, Py_ssize_t *length) { -#if __PYX_DEFAULT_STRING_ENCODING_IS_ASCII || __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT - if ( -#if PY_MAJOR_VERSION < 3 && __PYX_DEFAULT_STRING_ENCODING_IS_ASCII - __Pyx_sys_getdefaultencoding_not_ascii && -#endif - PyUnicode_Check(o)) { - return __Pyx_PyUnicode_AsStringAndSize(o, length); - } else -#endif -#if (!CYTHON_COMPILING_IN_PYPY) || (defined(PyByteArray_AS_STRING) && defined(PyByteArray_GET_SIZE)) - if (PyByteArray_Check(o)) { - *length = PyByteArray_GET_SIZE(o); - return PyByteArray_AS_STRING(o); - } else -#endif - { - char* result; - int r = PyBytes_AsStringAndSize(o, &result, length); - if (unlikely(r < 0)) { - return NULL; - } else { - return result; - } - } -} -static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject* x) { - int is_true = x == Py_True; - if (is_true | (x == Py_False) | (x == Py_None)) return is_true; - else return PyObject_IsTrue(x); -} -static CYTHON_INLINE int __Pyx_PyObject_IsTrueAndDecref(PyObject* x) { - int retval; - if (unlikely(!x)) return -1; - retval = __Pyx_PyObject_IsTrue(x); - Py_DECREF(x); - return retval; -} -static PyObject* __Pyx_PyNumber_IntOrLongWrongResultType(PyObject* result, const char* type_name) { -#if PY_MAJOR_VERSION >= 3 - if (PyLong_Check(result)) { - if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, - "__int__ returned non-int (type %.200s). " - "The ability to return an instance of a strict subclass of int " - "is deprecated, and may be removed in a future version of Python.", - Py_TYPE(result)->tp_name)) { - Py_DECREF(result); - return NULL; - } - return result; - } -#endif - PyErr_Format(PyExc_TypeError, - "__%.4s__ returned non-%.4s (type %.200s)", - type_name, type_name, Py_TYPE(result)->tp_name); - Py_DECREF(result); - return NULL; -} -static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) { -#if CYTHON_USE_TYPE_SLOTS - PyNumberMethods *m; -#endif - const char *name = NULL; - PyObject *res = NULL; -#if PY_MAJOR_VERSION < 3 - if (likely(PyInt_Check(x) || PyLong_Check(x))) -#else - if (likely(PyLong_Check(x))) -#endif - return __Pyx_NewRef(x); -#if CYTHON_USE_TYPE_SLOTS - m = Py_TYPE(x)->tp_as_number; - #if PY_MAJOR_VERSION < 3 - if (m && m->nb_int) { - name = "int"; - res = m->nb_int(x); - } - else if (m && m->nb_long) { - name = "long"; - res = m->nb_long(x); - } - #else - if (likely(m && m->nb_int)) { - name = "int"; - res = m->nb_int(x); - } - #endif -#else - if (!PyBytes_CheckExact(x) && !PyUnicode_CheckExact(x)) { - res = PyNumber_Int(x); - } -#endif - if (likely(res)) { -#if PY_MAJOR_VERSION < 3 - if (unlikely(!PyInt_Check(res) && !PyLong_Check(res))) { -#else - if (unlikely(!PyLong_CheckExact(res))) { -#endif - return __Pyx_PyNumber_IntOrLongWrongResultType(res, name); - } - } - else if (!PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, - "an integer is required"); - } - return res; -} -static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject* b) { - Py_ssize_t ival; - PyObject *x; -#if PY_MAJOR_VERSION < 3 - if (likely(PyInt_CheckExact(b))) { - if (sizeof(Py_ssize_t) >= sizeof(long)) - return PyInt_AS_LONG(b); - else - return PyInt_AsSsize_t(b); - } -#endif - if (likely(PyLong_CheckExact(b))) { - #if CYTHON_USE_PYLONG_INTERNALS - const digit* digits = ((PyLongObject*)b)->ob_digit; - const Py_ssize_t size = Py_SIZE(b); - if (likely(__Pyx_sst_abs(size) <= 1)) { - ival = likely(size) ? digits[0] : 0; - if (size == -1) ival = -ival; - return ival; - } else { - switch (size) { - case 2: - if (8 * sizeof(Py_ssize_t) > 2 * PyLong_SHIFT) { - return (Py_ssize_t) (((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - break; - case -2: - if (8 * sizeof(Py_ssize_t) > 2 * PyLong_SHIFT) { - return -(Py_ssize_t) (((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - break; - case 3: - if (8 * sizeof(Py_ssize_t) > 3 * PyLong_SHIFT) { - return (Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - break; - case -3: - if (8 * sizeof(Py_ssize_t) > 3 * PyLong_SHIFT) { - return -(Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - break; - case 4: - if (8 * sizeof(Py_ssize_t) > 4 * PyLong_SHIFT) { - return (Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - break; - case -4: - if (8 * sizeof(Py_ssize_t) > 4 * PyLong_SHIFT) { - return -(Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); - } - break; - } - } - #endif - return PyLong_AsSsize_t(b); - } - x = PyNumber_Index(b); - if (!x) return -1; - ival = PyInt_AsSsize_t(x); - Py_DECREF(x); - return ival; -} -static CYTHON_INLINE PyObject * __Pyx_PyBool_FromLong(long b) { - return b ? __Pyx_NewRef(Py_True) : __Pyx_NewRef(Py_False); -} -static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t ival) { - return PyInt_FromSize_t(ival); -} - - -#endif /* Py_PYTHON_H */ diff --git a/src/radon.pyx b/src/radon.pyx deleted file mode 100644 index 87ce25f..0000000 --- a/src/radon.pyx +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -cimport numpy as np - -from scipy.sparse import csr_matrix - -cimport cradon - -try: range=xrange -except: pass - -def radon2d(data, theta): - - if np.min(theta) < 0.0 or np.max(theta) >= np.pi: - raise ValueError('theta should be within [0 pi)') - - nx, ny = data.shape - if nx != ny: - raise RuntimeError('data should be a square array') - nw = nx - - sinogram = np.zeros((nw,theta.size)) - - Tx = np.zeros((nw,2)) - Rx = np.zeros((nw,2)) - - cdef size_t nTx = Tx.shape[0] - - - for nt in range(theta.size): - - if theta[nt] == 0.0: - for n in range(nw): - Tx[n,0] = n - Tx[n,1] = 0 - Rx[n,0] = n - Rx[n,1] = ny-1 - - elif np.abs(theta[nt] - np.pi/2) < 1.e-10: - - for n in range(nw): - Tx[n,0] = 0 - Tx[n,1] = n - Rx[n,0] = nx-1 - Rx[n,1] = n - - elif theta[nt] < np.pi/2: - - xs = nw/2 * (1. - np.cos(theta[nt]) ) - ys = nw/2 * (1. - np.sin(theta[nt]) ) - - for n in range(nw): - xk = xs + n * np.cos(theta[nt]) - yk = ys + n * np.sin(theta[nt]) - - x0 = 0.0 - y0 = yk + (xk-x0) / np.tan(theta[nt]) - if y0 > ny-1: - y0 = ny-1 - x0 = xk - (y0-yk) * np.tan(theta[nt]) - x1 = nx-1 - y1 = yk - (x1-xk) / np.tan(theta[nt]) - if y1 < 0.0: - y1 = 0.0 - x1 = xk + (yk-y1) * np.tan(theta[nt]) - - Tx[n,0] = x0 - Tx[n,1] = y0 - Rx[n,0] = x1 - Rx[n,1] = y1 - - - else: - - xs = nw/2 * (1. - np.cos(theta[nt]) ) - ys = nw/2 * (1. - np.sin(theta[nt]) ) - - for n in range(nw): - xk = xs + n * np.cos(theta[nt]) - yk = ys + n * np.sin(theta[nt]) - - x0 = nx-1 - y0 = yk + (x0-xk) * np.tan(theta[nt]-np.pi/2.0) - if y0 > ny-1: - y0 = ny-1 - x0 = xk + (y0-yk) / np.tan(theta[nt]-np.pi/2.0) - x1 = 0.0 - y1 = yk - (xk-x1) * np.tan(theta[nt]-np.pi/2.0) - if y1 < 0.0: - y1 = 0.0 - x1 = xk - (yk-y1) / np.tan(theta[nt]-np.pi/2.0) - - Tx[n,0] = x0 - Tx[n,1] = y0 - Rx[n,0] = x1 - Rx[n,1] = y1 - - if np.max(Rx) > nx-1: - print('Rx '+str(n)) - print(Rx) - if np.max(Tx) > nx-1: - print('Tx '+str(n)) - print(Tx) - - - Ldata = ([0.0], [0.0], [0.0]) - - cradon.radon2d( np.PyArray_DATA(Tx), np.PyArray_DATA(Rx), nTx, nx, ny, Ldata) - - M = nTx - N = nx * ny - L = csr_matrix(Ldata, shape=(M,N)) - - p = L.dot(data.flatten()) - sinogram[:,nt] = p - - - - - return sinogram diff --git a/tests/Bouligand_forward.py b/tests/Bouligand_forward.py index f398b12..fe62082 100644 --- a/tests/Bouligand_forward.py +++ b/tests/Bouligand_forward.py @@ -106,8 +106,8 @@ ##Mf(1,1,1)=mean([Mf(2,1,1) Mf(1,2,1) Mf(1,1,2)]) Mi = np.fft.ifftn(Mf) - TFANO = np.zeros((nx, ny), dtype=np.complex) - ANO = np.zeros((nx, ny), dtype=np.complex) + TFANO = np.zeros((nx, ny), dtype=complex) + ANO = np.zeros((nx, ny), dtype=complex) # input("Press Enter to continue.") ## executes fine up to here. for k in range(0, nz): diff --git a/tests/conftest.py b/tests/conftest.py index c818161..11b3074 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,6 @@ import pytest +from functools import lru_cache + import pycurious import numpy as np @@ -35,3 +37,22 @@ def load_magnetic_anomaly(): } return mag_dict + + +@lru_cache(maxsize=None) +def synthetic_grid(cls, beta=3.0, zt=1.0, dz=20.0, n=512, dx=2.0, seed=1): + """ + A synthetic anomaly with a known Curie depth, and its centre. + + Cached because generating a 512x512 or 1024x1024 field costs more than the + fit that follows, and the recovery tests ask for the same few fields + repeatedly -- once per parametrised case, and again for each test over the + same case. + """ + data, extent = pycurious.fractal_anomaly( + n=n, dx=dx, beta=beta, zt=zt, dz=dz, C=5.0, seed=seed + ) + grid = cls(data, *extent) + xc = 0.5 * (extent[0] + extent[1]) + yc = 0.5 * (extent[2] + extent[3]) + return grid, xc, yc, (n - 1) * dx * 1e3 diff --git a/tests/test_0_imports.py b/tests/test_0_imports.py index 39423ed..6f54a66 100644 --- a/tests/test_0_imports.py +++ b/tests/test_0_imports.py @@ -15,17 +15,13 @@ def test_scipy_import(): print("\t\t You have scipy version {}".format(scipy.__version__)) -def test_cython_import(): - import Cython - - return - - def test_pycurious_modules(): import pycurious from pycurious import documentation from pycurious import CurieGrid - from pycurious import CurieOptimise + from pycurious import CurieOptimiseBouligand + from pycurious import CurieOptimiseTanaka + from pycurious import fractal_anomaly from pycurious import mapping from pycurious import download @@ -46,5 +42,4 @@ def test_pycurious_modules(): if __name__ == "__main__": test_numpy_import() test_scipy_import() - test_cython_import() test_pycurious_modules() diff --git a/tests/test_bouligand.py b/tests/test_bouligand.py new file mode 100644 index 0000000..c97ef58 --- /dev/null +++ b/tests/test_bouligand.py @@ -0,0 +1,592 @@ +""" +Behaviour of CurieOptimiseBouligand, and guards against defects fixed in v2. + +Each test names the defect it protects, because several of them are the kind +that leave the code producing plausible numbers rather than failing. +""" + +import copy +import warnings +from multiprocessing import cpu_count + +import numpy as np +import pytest + +import pycurious + +from conftest import synthetic_grid + +TRUTH = dict(beta=3.0, zt=1.0, dz=20.0) +WINDOW = 1000e3 + + +def _grid(n=512, seed=1): + return synthetic_grid( + pycurious.CurieOptimiseBouligand, n=n, seed=seed, **TRUTH + )[:3] + + +@pytest.fixture(scope="module") +def bouligand(): + return _grid() + + +def _uncorrelated_sigma(grid, x, k, Phi, sigma): + """What inv(J^T J) would report, i.e. treating the bins as independent.""" + args = (k, Phi, sigma) + r = grid.residuals(x, *args) + J = grid._jacobian(x, r, args) + return np.sqrt(np.diag(np.linalg.inv(J.T.dot(J)))) + + +def test_min_func_is_weighted(bouligand): + """ + min_func must divide by sigma_Phi. + + Before v2 it passed a literal 1.0, so every bin carried equal weight + however well determined it was. Doubling every uncertainty must quarter + the misfit; if the weighting is dropped again the ratio goes to 1. + """ + grid, xc, yc = bouligand + grid.reset_priors() + k, Phi, sigma = grid.window_spectrum(WINDOW, xc, yc, taper=np.hanning, power=2.0) + x = np.array([3.0, 1.0, 20.0, 15.0]) + + ratio = grid.min_func(x, k, Phi, 2.0 * sigma) / grid.min_func(x, k, Phi, sigma) + assert ratio == pytest.approx(0.25) + + +def test_reduced_chi_squared_is_about_one(bouligand): + """ + The weights must be the uncertainty of the binned *mean*. + + This is the strongest single guard in the suite. Weighting by the raw + within-annulus scatter gives 0.01, dropping the per-bin degrees-of-freedom + deflation gives 1.9, and not weighting at all is not on this scale. + + The taper is pinned because the guard only separates those cases with one: + untapered, every plausible deflation lands inside the window. + """ + grid, xc, yc = bouligand + grid.reset_priors() + x = grid.optimise(WINDOW, xc, yc, taper=np.hanning)[:4] + k, Phi, sigma = grid.window_spectrum(WINDOW, xc, yc, taper=np.hanning, power=2.0) + + chi2_red = 2.0 * grid.min_func(x, k, Phi, sigma) / (k.size - len(x)) + assert 0.7 < chi2_red < 1.6, "chi2_red = {:.3f}".format(chi2_red) + + +def test_optimise_returns_uncertainties(bouligand): + """optimise reports a sigma per parameter, not just the parameters.""" + grid, xc, yc = bouligand + grid.reset_priors() + out = grid.optimise(WINDOW, xc, yc, taper=np.hanning) + + assert len(out) == 8 + beta, zt, dz, C, s_beta, s_zt, s_dz, s_C = out + assert np.abs(beta - TRUTH["beta"]) < 0.2 + assert np.abs(zt - TRUTH["zt"]) < 0.25 + for sigma in (s_beta, s_zt, s_dz, s_C): + assert np.isfinite(sigma) and sigma > 0.0 + + +def test_covariance_is_opt_in(bouligand): + """ + The covariance must not ride in the default return tuple. + + parallel._collect dispatches on the dimensionality of the result, so an + 8-tuple of floats plus a 4x4 array raises "inhomogeneous shape" the moment + optimise_routine is used. + """ + grid, xc, yc = bouligand + grid.reset_priors() + out = grid.optimise(WINDOW, xc, yc, taper=np.hanning, return_cov=True) + + assert len(out) == 9 + cov = out[8] + assert cov.shape == (4, 4) + np.testing.assert_allclose(cov, cov.T, rtol=1e-8) + # the sigmas reported alongside it are its diagonal + np.testing.assert_allclose(np.array(out[4:8]), np.sqrt(np.diag(cov)), rtol=1e-10) + + +def test_correlation_between_bins_inflates_sigma(bouligand): + """ + Neighbouring radial bins are correlated, and ignoring it understates every + uncertainty by about 35% under a hanning taper. + + The covariance therefore has to be generalised least squares. Compare it + against what the naive inv(J^T J) would report. + """ + grid, xc, yc = bouligand + grid.reset_priors() + x = np.array(grid.optimise(WINDOW, xc, yc, taper=np.hanning)[:4]) + + k, Phi, sigma = grid.window_spectrum(WINDOW, xc, yc, taper=np.hanning, power=2.0) + gls = np.sqrt(np.diag(grid._covariance(x, k, Phi, sigma))) + iid = _uncorrelated_sigma(grid, x, k, Phi, sigma) + + assert np.all(gls > iid), "GLS must widen the interval, not narrow it" + ratio = (gls / iid)[[0, 1, 3]] # beta, zt, C + assert np.all(ratio > 1.15) and np.all(ratio < 1.6), ratio + + # With no taper there is nothing to correlate the bins, so the two agree. + # Refit first: evaluating the hanning solution against untapered data + # leaves residuals dominated by smooth model mismatch, which the estimator + # would correctly read as correlated. + x = np.array(grid.optimise(WINDOW, xc, yc, taper=None)[:4]) + k, Phi, sigma = grid.window_spectrum(WINDOW, xc, yc, taper=None, power=2.0) + gls = np.sqrt(np.diag(grid._covariance(x, k, Phi, sigma))) + iid = _uncorrelated_sigma(grid, x, k, Phi, sigma) + np.testing.assert_allclose((gls / iid)[[0, 1, 3]], 1.0, rtol=0.15) + + +def test_profile_is_asymmetric_for_dz(bouligand): + """ + dz has a long upper tail (Mather & Fullea, 2019), so a symmetric sigma is + the wrong shape for it. The profile interval must show that. + """ + grid, xc, yc = bouligand + grid.reset_priors() + values, deviance, lower, upper = grid.profile( + WINDOW, xc, yc, "dz", taper=np.hanning + ) + + centre = values[np.argmin(deviance)] + assert lower < centre < upper + assert lower <= TRUTH["dz"] <= upper, "truth outside [{:.2f}, {:.2f}]".format( + lower, upper + ) + assert (upper - centre) > 1.2 * (centre - lower), "interval is not skewed" + + +def test_profile_of_curie_depth(bouligand): + """CPD is profiled directly, not propagated from a symmetric sigma_dz.""" + grid, xc, yc = bouligand + grid.reset_priors() + values, deviance, lower, upper = grid.profile( + WINDOW, xc, yc, "CPD", taper=np.hanning + ) + truth = TRUTH["zt"] + TRUTH["dz"] + assert lower <= truth <= upper, "truth outside [{:.2f}, {:.2f}]".format(lower, upper) + + +def test_profile_result_does_not_depend_on_the_bracket(bouligand): + """ + A coarse or badly placed bracket used to step over the minimum entirely, + collapsing the interval onto a single node without saying so. The fitted + value is now always carried as a node. + """ + grid, xc, yc = bouligand + grid.reset_priors() + _, _, lo_default, hi_default = grid.profile(WINDOW, xc, yc, "dz", taper=np.hanning) + _, _, lo_coarse, hi_coarse = grid.profile( + WINDOW, xc, yc, "dz", bracket=(1.0, 900.0), npoints=15, taper=np.hanning + ) + assert lo_coarse == pytest.approx(lo_default, rel=0.02) + assert hi_coarse == pytest.approx(hi_default, rel=0.02) + + +def test_profile_survives_the_overflow_region(bouligand): + """ + bouligand2009 overflows cosh past |k|dz ~ 710. A scan that reaches there + must return a finite deviance rather than raising. + """ + grid, xc, yc = bouligand + grid.reset_priors() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + values, deviance, _, _ = grid.profile( + WINDOW, xc, yc, "dz", bracket=(1.0, 2000.0), npoints=11, taper=np.hanning + ) + assert np.isfinite(deviance).all() + + +def test_profile_unbounded_returns_inf(): + """ + A window too small to constrain dz has no upper bound. That must come back + as inf with a warning, not as the edge of the scan dressed up as an answer. + """ + grid, xc, yc = _grid(n=256) + grid.reset_priors() + with pytest.warns(RuntimeWarning, match="unbounded"): + _, _, lower, upper = grid.profile(60e3, xc, yc, "dz", taper=np.hanning) + assert np.isinf(upper) + assert np.isfinite(lower) + + +def test_profile_rejects_unknown_target(bouligand): + grid, xc, yc = bouligand + with pytest.raises(ValueError, match="target must be one of"): + grid.profile(WINDOW, xc, yc, "curie_depth") + + +def test_sensitivity_does_not_disturb_priors(bouligand): + """ + sensitivity used to redraw each prior centre by mutating self.prior in + place and restoring it afterwards, which left the instance corrupted if + anything raised in between. + """ + grid, xc, yc = bouligand + grid.reset_priors() + grid.add_prior(beta=(3.0, 0.1), zt=(1.0, 0.2)) + before = copy.deepcopy(grid.prior) + + grid.sensitivity(300e3, xc, yc, 4, taper=np.hanning, seed=1) + assert grid.prior == before + + # and still intact when a simulation blows up part way through + calls = {"n": 0} + original = grid.min_func + + def exploding(*args, **kwargs): + calls["n"] += 1 + if calls["n"] > 5: + raise RuntimeError("boom") + return original(*args, **kwargs) + + grid.min_func = exploding + try: + with pytest.raises(RuntimeError, match="boom"): + grid.sensitivity(300e3, xc, yc, 4, taper=np.hanning, seed=1) + finally: + del grid.min_func + assert grid.prior == before + + grid.reset_priors() + + +def test_sensitivity_is_reproducible(bouligand): + """ + Seeded so a parallel sensitivity map cannot inherit one RNG state across + workers, which under fork would have drawn the same sequence at every + centroid and painted coherent artefacts across the map. + """ + grid, xc, yc = bouligand + grid.reset_priors() + kwargs = dict(taper=np.hanning) + a = grid.sensitivity(300e3, xc, yc, 6, seed=7, **kwargs)[2] + b = grid.sensitivity(300e3, xc, yc, 6, seed=7, **kwargs)[2] + c = grid.sensitivity(300e3, xc, yc, 6, seed=8, **kwargs)[2] + np.testing.assert_allclose(a, b) + assert not np.allclose(a, c) + + +def test_prior_values_are_immutable(bouligand): + """Stored as tuples, so a caller cannot perturb a prior in place.""" + grid, _, _ = bouligand + grid.reset_priors() + grid.add_prior(beta=(3.0, 0.1)) + with pytest.raises(TypeError): + grid.prior["beta"][0] = 99.0 + grid.reset_priors() + + +def test_min_func_accepts_an_explicit_prior(bouligand): + """ + Passing a prior through rather than reaching for self.prior is what lets + sensitivity resample prior centres without touching the instance. + """ + grid, xc, yc = bouligand + grid.reset_priors() + k, Phi, sigma = grid.window_spectrum(WINDOW, xc, yc, taper=np.hanning, power=2.0) + x = np.array([3.0, 1.0, 20.0, 15.0]) + + flat = grid.min_func(x, k, Phi, sigma) + tight = grid.min_func(x, k, Phi, sigma, {"beta": (1.0, 0.01), "zt": None, + "dz": None, "C": None}) + assert tight > flat + assert grid.prior["beta"] is None, "self.prior must be untouched" + + +def test_warns_when_a_parameter_hits_a_bound(bouligand): + """ + The covariance describes the curvature of an interior minimum, so on an + active bound the uncertainty it reports is meaningless. + """ + grid, xc, yc = bouligand + grid.reset_priors() + original = list(grid.bounds) + try: + grid.bounds = [(0.0, None), (0.0, 0.0), (0.0, None), (None, None)] + with pytest.warns(RuntimeWarning, match="bound"): + grid.optimise(WINDOW, xc, yc, taper=np.hanning) + finally: + grid.bounds = original + + +def test_taper_none_rejects_unknown_keywords(bouligand): + """ + With a taper present an unrecognised keyword raises from inside the taper. + With taper=None it used to be swallowed, which is how a seed= silently + failed to reach anything on the notebooks that pass taper=None. + """ + grid, xc, yc = bouligand + with pytest.raises(TypeError, match="unexpected keyword"): + grid.window_spectrum(300e3, xc, yc, taper=None, bogus=7) + + +@pytest.mark.slow +def test_reported_sigma_matches_the_spread_over_realisations(): + """ + The calibration that matters, and the only one that can catch a wrong + covariance. + + sensitivity resamples each bin independently and metropolis_hastings uses + the same diagonal weighting, so both share the assumption the covariance + makes. They agree with it to within 10% whether or not it is right. Only an + ensemble over independent realisations of the field is an outside check. + + Measured over 200 seeds: 0.997, 0.989 and 0.991 for beta, zt and C. dz sits + near 1.44 because its likelihood is skewed, which no symmetric sigma can + fix -- that is what profile() is for. + """ + fits, reported = [], [] + for seed in range(60): + grid, xc, yc = _grid(seed=2000 + seed) + out = grid.optimise(WINDOW, xc, yc, taper=np.hanning) + fits.append(out[:4]) + reported.append(out[4:]) + + spread = np.array(fits).std(axis=0, ddof=1) + mean_sigma = np.array(reported).mean(axis=0) + ratio = spread / mean_sigma + + for i, name in enumerate(("beta", "zt", "C")): + j = i if i < 2 else 3 + assert 0.8 < ratio[j] < 1.2, "{} ratio {:.3f}".format(name, ratio[j]) + + # dz is understated, and knowingly so + assert ratio[2] > 1.15, "dz ratio {:.3f}".format(ratio[2]) + + +def test_calculate_CPD_matches_the_tanaka_sibling(bouligand): + """ + Both classes must return (CPD, CPD_stdev) from the same shaped call. + + The Bouligand version used to be a bare `return zt+dz` with no uncertainty, + so code written against one sibling misbehaved silently against the other. + """ + grid, _, _ = bouligand + tanaka = pycurious.CurieOptimiseTanaka(np.zeros((9, 9)), 0.0, 8e3, 0.0, 8e3) + + CPD, sigma = grid.calculate_CPD(1.0, 10.0, 0.3, 0.4) + assert CPD == pytest.approx(11.0) + assert sigma == pytest.approx(np.hypot(0.3, 0.4)) + + # same arity and return shape as Tanaka, which parameterises by z0 instead + assert len(grid.calculate_CPD(1.0, 10.0)) == len(tanaka.calculate_CPD(1.0, 6.0)) + + # and vectorises over a map of centroids + CPD, sigma = grid.calculate_CPD( + np.array([1.0, 2.0]), np.array([10.0, 11.0]), 0.0, 0.0 + ) + np.testing.assert_allclose(CPD, [11.0, 13.0]) + np.testing.assert_allclose(sigma, [0.0, 0.0]) + + +def test_metropolis_hastings_acceptance_is_in_a_usable_band(bouligand): + """ + The chain must actually move. + + Comparing exp(-F) directly underflows to zero for any real spectrum, so + every proposal was rejected: the old sampler returned 9 distinct states in + 2000 draws while reporting an acceptance rate of 0.004. Acceptance is + decided in log space now, and the proposal is drawn along the fit + covariance rather than a diagonal, which is what lets it move along the + ridge beta and zt lie on. + """ + grid, xc, yc = bouligand + grid.reset_priors() + posterior, info = grid.metropolis_hastings( + WINDOW, xc, yc, 2000, 500, taper=np.hanning, seed=1, return_diagnostics=True + ) + chain = np.array(posterior) + + assert 0.15 < info["acceptance"] < 0.6, info["acceptance"] + assert len(np.unique(chain[0])) > 0.1 * chain.shape[1] + + +def test_metropolis_hastings_is_invariant_to_a_constant_misfit(bouligand): + """ + A log-space acceptance ratio sees only differences, so shifting the misfit + by a constant leaves the chain where it was. The old exp(-F) form with its + 1e-99 clamp did not have that property -- which is precisely why it stalled + once F grew to a few hundred: exp(-F) underflowed to zero and every + proposal was rejected. + + Agreement is close but not bit-exact, and the reason is not the acceptance + rule. min_func also drives the search for the mode the chain starts from, + and scipy's convergence tolerance is relative to the value of the + objective, so offsetting it moves where the minimiser stops -- by about + 3e-06 here. Reintroducing exp() would not miss this tolerance narrowly; it + would freeze the chain outright. + """ + grid, xc, yc = bouligand + grid.reset_priors() + kwargs = dict(taper=np.hanning, seed=3, adapt=False) + + before = np.array(grid.metropolis_hastings(WINDOW, xc, yc, 200, 100, **kwargs)) + + original = grid.min_func + grid.min_func = lambda *a, **kw: original(*a, **kw) + 1000.0 + try: + after = np.array(grid.metropolis_hastings(WINDOW, xc, yc, 200, 100, **kwargs)) + finally: + del grid.min_func + + np.testing.assert_allclose(before, after, rtol=1e-4) + # and neither chain is frozen, which is what the old form produced + assert len(np.unique(before[0])) > 20 + + +def test_metropolis_hastings_agrees_with_the_other_estimators(bouligand): + """ + beta, zt and C are near-Gaussian, so the posterior width should match what + the covariance and the resampling ensemble report. + + dz is deliberately excluded: its posterior is skewed, so its marginal is + wider than a curvature-based sigma by construction. + """ + grid, xc, yc = bouligand + grid.reset_priors() + sigma = np.array(grid.optimise(WINDOW, xc, yc, taper=np.hanning)[4:8]) + chain = np.array( + grid.metropolis_hastings(WINDOW, xc, yc, 4000, 1000, taper=np.hanning, seed=1) + ) + + for i, name in ((0, "beta"), (1, "zt"), (3, "C")): + ratio = chain[i].std() / sigma[i] + assert 0.5 < ratio < 1.6, "{} ratio {:.3f}".format(name, ratio) + + +def test_metropolis_hastings_respects_bounds_and_is_reproducible(bouligand): + """ + The optimiser has always honoured self.bounds; the chain used to ignore + them and could wander to a negative thickness. + """ + grid, xc, yc = bouligand + grid.reset_priors() + kwargs = dict(taper=np.hanning) + a = np.array(grid.metropolis_hastings(WINDOW, xc, yc, 300, 100, seed=5, **kwargs)) + b = np.array(grid.metropolis_hastings(WINDOW, xc, yc, 300, 100, seed=5, **kwargs)) + c = np.array(grid.metropolis_hastings(WINDOW, xc, yc, 300, 100, seed=6, **kwargs)) + + np.testing.assert_allclose(a, b) + assert not np.allclose(a, c) + assert (a[0] >= 0.0).all() and (a[1] >= 0.0).all() and (a[2] >= 0.0).all() + + +def test_only_stochastic_routines_are_seeded(bouligand): + """ + `parallelise_routine` decides once who gets a seed, rather than every + routine growing a parameter to absorb one. + + Before, `optimise` had to accept a `seed` it ignored purely so the parallel + routine could pass one uniformly -- and that was done on the Bouligand + sibling only, so the identical call raised from inside `np.hanning` on the + Tanaka one. + """ + grid, xc, yc = _grid(n=128) + grid.max_processors = 1 + window = 200e3 + xs, ys = np.array([xc]), np.array([yc]) + + assert getattr(grid.sensitivity, "wants_seed", False) + assert getattr(grid.metropolis_hastings, "wants_seed", False) + assert not getattr(grid.optimise, "wants_seed", False) + + # a deterministic routine says so rather than failing inside the taper + with pytest.warns(RuntimeWarning, match="deterministic"): + grid.optimise_routine(window, xs, ys, taper=np.hanning, seed=7) + + # and a stochastic one is actually seeded, per centroid + a = grid.parallelise_routine(window, xs, ys, grid.sensitivity, 3, + taper=np.hanning, seed=11) + b = grid.parallelise_routine(window, xs, ys, grid.sensitivity, 3, + taper=np.hanning, seed=11) + np.testing.assert_allclose(np.array(a, dtype=float), np.array(b, dtype=float)) + + +def test_metropolis_hastings_default_return_shape_is_unchanged(bouligand): + """ + pycurious.parallel dispatches on the dimensionality of a routine's result, + so the diagnostics have to be opt-in or every parallel MCMC call breaks. + + Also checks a burn-in too short to condition a 4x4 covariance, which + test_routines.py exercises at burnin=10. + """ + grid, xc, yc = bouligand + grid.reset_priors() + + plain = grid.metropolis_hastings(WINDOW, xc, yc, 60, 10, taper=np.hanning, seed=1) + assert isinstance(plain, list) and len(plain) == 4 + assert all(np.asarray(a).shape == (60,) for a in plain) + + _, info = grid.metropolis_hastings( + WINDOW, xc, yc, 60, 10, taper=np.hanning, seed=1, return_diagnostics=True + ) + assert set(info) == {"acceptance", "burnin_acceptance", "x_scale"} + + +def test_thickness_is_bounded_below_the_overflow(bouligand): + """ + dz is bounded where the forward model stops evaluating, not where physics + stops being plausible. + + bouligand2009 overflows around |k|dz = 710, and |k| reaches the Nyquist + wavenumber whatever the window, so the ceiling follows from the grid + spacing: 446 km at 2 km spacing. That is far past any Curie depth on Earth, + which is deliberate -- a bound placed near the physical range would clip + the upper tail of a skewed posterior and pile probability against the wall + instead of reporting the shape. Reaching this one means the window cannot + constrain the base at all. + """ + from pycurious.optimise_bouligand import _COSH_OVERFLOW + + grid, xc, yc = bouligand + grid.reset_priors() + + ceiling = grid.bounds[2][1] + assert ceiling == pytest.approx(_COSH_OVERFLOW * grid.dx * 1e-3 / np.pi) + # far beyond anything physical, so it never binds on usable data + assert ceiling > 300.0 + # the parameters the spectrum does pin down are left free + assert grid.bounds[0][1] is None and grid.bounds[1][1] is None + + # the model still evaluates at the bound, which is the whole point + k, Phi, sigma = grid.window_spectrum(WINDOW, xc, yc, taper=np.hanning, power=2.0) + assert np.isfinite(grid.min_func([3.0, 1.0, ceiling, 15.0], k, Phi, sigma)) + + # and a chain on an under-constrained window stays inside it + chain = np.array( + grid.metropolis_hastings(300e3, xc, yc, 400, 200, taper=np.hanning, seed=1) + ) + assert chain[2].max() <= ceiling + + +def test_max_processors_is_honoured(): + """ + The constructor keyword must reach CurieParallel. + + It previously did not: the assignment sat after the return in + _max_thickness, where it was unreachable, so the argument was silently + ignored and every routine used cpu_count() regardless. Nothing caught it + because parallelise_routine still works -- just not serially when asked. + """ + data = np.zeros((9, 9)) + extent = (0.0, 8e3, 0.0, 8e3) + + assert pycurious.CurieOptimiseBouligand(*(data,) + extent, + max_processors=1).max_processors == 1 + assert pycurious.CurieOptimiseBouligand(*(data,) + extent, + max_processors=3).max_processors == 3 + + # and the two optimisers agree, as they did not before + assert ( + pycurious.CurieOptimiseBouligand(*(data,) + extent, max_processors=2).max_processors + == pycurious.CurieOptimiseTanaka(*(data,) + extent, max_processors=2).max_processors + ) + + # the default is still every core + assert pycurious.CurieOptimiseBouligand(*(data,) + extent).max_processors == cpu_count() diff --git a/tests/test_grid.py b/tests/test_grid.py index 4e7b93e..6ed8fa1 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -1,5 +1,6 @@ import pytest import pycurious +from pycurious.grid import _dof_factor import numpy as np from conftest import load_magnetic_anomaly @@ -23,6 +24,234 @@ def test_subgrid(load_magnetic_anomaly): assert subgrid.shape[1] < grid.data.shape[1], error_msg +def test_wavenumber_grid_matches_dft(): + """ + The radial wavenumber grid must be the true DFT grid. + + The fundamental is 2*pi/(N*dx); using (N-1) overstates every wavenumber + by N/(N-1) and so understates every depth by (N-1)/N. + """ + N, dx_km = 201, 1.0 + grid = pycurious.CurieGrid( + np.zeros((N, N)), 0.0, (N - 1) * 1e3, 0.0, (N - 1) * 1e3 + ) + _, dk, _ = grid._taper_spectrum(grid.data, None) + + np.testing.assert_allclose(dk, 2.0 * np.pi / (N * dx_km), rtol=1e-12) + + kx = np.fft.fftshift(2.0 * np.pi * np.fft.fftfreq(N, d=dx_km)) + KX, KY = np.meshgrid(kx, kx, indexing="ij") + i0 = N // 2 + ix, iy = np.mgrid[0:N, 0:N] + + np.testing.assert_allclose( + np.hypot((ix - i0) * dk, (iy - i0) * dk), np.hypot(KX, KY), atol=1e-12 + ) + + +def test_radial_spectrum_recovers_injected_depth(): + """ + A field built as white noise * exp(-|k|z) has a log amplitude spectrum of + slope -z, so the depth must come back out of radial_spectrum directly. + This pins the wavenumber scaling end to end. + """ + from scipy.optimize import curve_fit + + N, z = 201, 4.0 + rng = np.random.default_rng(0) + kx = 2.0 * np.pi * np.fft.fftfreq(N, d=1.0) + KX, KY = np.meshgrid(kx, kx, indexing="ij") + spectrum = np.fft.fft2(rng.normal(size=(N, N))) * np.exp(-np.hypot(KX, KY) * z) + field = np.real(np.fft.ifft2(spectrum)) + + grid = pycurious.CurieGrid(field, 0.0, (N - 1) * 1e3, 0.0, (N - 1) * 1e3) + k, Phi, _ = grid.radial_spectrum(grid.data, taper=None, power=1) + + mask = np.logical_and(k > 0.15, k < 1.5) + (slope, _), _ = curve_fit(lambda x, a, b: a * x + b, k[mask], Phi[mask]) + + assert np.abs(-slope - z) < 0.05, "recovered {:.4f} km, injected {} km".format( + -slope, z + ) + + +def test_radial_spectrum_counts(): + """Bin counts must partition the wavenumber plane without double counting.""" + grid = pycurious.CurieGrid(np.random.rand(101, 101), 0.0, 100e3, 0.0, 100e3) + + assert len(grid.radial_spectrum(grid.data, taper=None)) == 3 + + k, Phi, sigma_Phi, counts = grid.radial_spectrum( + grid.data, taper=None, return_counts=True + ) + assert counts.shape == k.shape + assert counts.min() > 0 + # bins cover the inscribed circle, never more than the whole grid + assert counts.sum() <= grid.data.size + + +def _reference_FFT_spectrum(subgrid, vtaper, dk, kbins, const): + """ + Explicit per-bin reference for `_FFT_spectrum`, kept deliberately naive. + + This is the pre-vectorised implementation. It is O(nbins * N**2) and far + too slow to use, but it states the binning semantics unambiguously, so the + bincount version is pinned against it rather than against stored numbers. + """ + nr, nc = subgrid.shape + nbins = kbins.size - 1 + + FT = np.fft.fftshift(np.abs(np.fft.fft2(subgrid * vtaper))) + i0, j0 = int(nr // 2), int(nc // 2) + ix, iy = np.mgrid[0:nr, 0:nc] + kk = np.hypot((ix - i0) * dk, (iy - j0) * dk) + + S = np.empty(nbins) + k = np.empty(nbins) + sigma = np.empty(nbins) + counts = np.empty(nbins, dtype=int) + for i in range(nbins): + # half-open above, except the last bin which is closed + if i == nbins - 1: + mask = np.logical_and(kk >= kbins[i], kk <= kbins[i + 1]) + else: + mask = np.logical_and(kk >= kbins[i], kk < kbins[i + 1]) + rr = const * np.log(FT[mask]) + S[i] = rr.mean() + k[i] = kk[mask].mean() + sigma[i] = np.std(rr) + counts[i] = rr.size + return k, S, sigma, counts + + +@pytest.mark.parametrize("n", [64, 65, 128, 301]) +@pytest.mark.parametrize("taper", [None, np.hanning]) +@pytest.mark.parametrize("power", [1.0, 2.0]) +def test_FFT_spectrum_matches_per_bin_reference(n, taper, power): + """ + The vectorised binning must reproduce the per-bin loop exactly. + + The bin-edge semantics are easy to break: annuli are half-open `[lo, hi)` + so a cell landing on an edge is counted once, *except* the last bin which + is closed. Cells sit on edges constantly -- everything on the kx or ky + axis has `kk` an exact multiple of `dk` -- so an off-by-one here silently + shifts counts between neighbouring annuli. + """ + data, extent = pycurious.fractal_anomaly(n, 1.0, 3.0, 1.0, 20.0, 5.0, seed=n) + grid = pycurious.CurieGrid(data, *extent) + vtaper, dk, kbins = grid._taper_spectrum(data, taper) + + got = grid._FFT_spectrum(data, vtaper, dk, kbins, power) + want = _reference_FFT_spectrum(data, vtaper, dk, kbins, power) + + # counts must be identical, not close -- they are a partition + np.testing.assert_array_equal(got[3], want[3]) + for name, g, w in zip(("k", "S", "sigma"), got, want): + np.testing.assert_allclose(g, w, rtol=1e-12, atol=0, err_msg=name) + + +def test_FFT_spectrum_sigma_is_population_std(): + """ + `sigma` is the population standard deviation (ddof=0) of `const*ln|FFT|` + within the annulus, computed two-pass. The cheaper `E[x**2] - E[x]**2` + form cancels badly here -- `ln|FFT|` is O(10) with O(1) scatter -- and + loses three digits, more on a near-constant bin. + """ + n = 128 + data, extent = pycurious.fractal_anomaly(n, 1.0, 3.0, 1.0, 20.0, 5.0, seed=2) + grid = pycurious.CurieGrid(data, *extent) + vtaper, dk, kbins = grid._taper_spectrum(data, np.hanning) + + k, S, sigma, counts = grid._FFT_spectrum(data, vtaper, dk, kbins, 2.0) + + FT = np.fft.fftshift(np.abs(np.fft.fft2(data * vtaper))) + i0 = j0 = int(n // 2) + ix, iy = np.mgrid[0:n, 0:n] + kk = np.hypot((ix - i0) * dk, (iy - j0) * dk) + + for i in (0, 1, len(k) // 2, len(k) - 1): + if i == len(k) - 1: + mask = np.logical_and(kk >= kbins[i], kk <= kbins[i + 1]) + else: + mask = np.logical_and(kk >= kbins[i], kk < kbins[i + 1]) + cells = 2.0 * np.log(FT[mask]) + assert counts[i] == cells.size + np.testing.assert_allclose(sigma[i], np.std(cells), rtol=1e-13) + np.testing.assert_allclose(S[i], cells.mean(), rtol=1e-13) + + +@pytest.mark.filterwarnings("ignore:subgrid is not square") +@pytest.mark.parametrize("shape", [(48, 80), (81, 50), (64, 65), (65, 64), (128, 127)]) +@pytest.mark.parametrize("taper", [None, np.hanning]) +def test_FFT_spectrum_rectangular_matches_full_fft2(shape, taper): + """ + The rfft2 binning must reproduce a full complex fft2 for non-square windows + and odd sizes too. + + rfft2 only halves the last (column) axis, so a rectangle and an odd or even + `nc` are exactly where the Hermitian column weighting -- interior columns + counted twice, the self-mirrored DC and even-`nc` Nyquist columns once -- is + easiest to get wrong. The square case is pinned above; this covers the rest + against the same per-bin reference. + """ + nr, nc = shape + rng = np.random.default_rng(nr * 1000 + nc) + data = rng.normal(size=(nr, nc)) + # equal node spacing so CurieGrid accepts the rectangle + grid = pycurious.CurieGrid(data, 0.0, (nc - 1) * 1e3, 0.0, (nr - 1) * 1e3) + vtaper, dk, kbins = grid._taper_spectrum(data, taper) + + got = grid._FFT_spectrum(data, vtaper, dk, kbins, 2.0) + want = _reference_FFT_spectrum(data, vtaper, dk, kbins, 2.0) + + # counts are a partition -- identical, not merely close + np.testing.assert_array_equal(got[3], want[3]) + for name, g, w in zip(("k", "S", "sigma"), got, want): + np.testing.assert_allclose(g, w, rtol=1e-12, atol=0, err_msg=name) + + +def test_FFT_spectrum_hermitian_weighting_restores_full_counts(): + """ + `counts` must be the full-spectrum count, not rfft2's half plane. + + `window_spectrum` deflates `sigma` by `counts`, and the `_TAPER_DOF` + calibration folds in the Hermitian factor of two directly -- its untapered + `dof_inf` is 2 -- so halving `counts` would inflate every reported + uncertainty by ~sqrt(2). This pins the column weighting that reconstructs + the full count, and shows that binning the half plane without it is + detectably wrong (which is what a naive rfft2 swap would do). + """ + n = 96 # even, so there is a self-mirrored Nyquist column to weight + data, extent = pycurious.fractal_anomaly(n, 1.0, 3.0, 1.0, 20.0, 5.0, seed=7) + grid = pycurious.CurieGrid(data, *extent) + vtaper, dk, kbins = grid._taper_spectrum(data, np.hanning) + nbins = kbins.size - 1 + + counts = grid._FFT_spectrum(data, vtaper, dk, kbins, 2.0)[3] + + def binned(kk): + idx = np.digitize(kk, kbins) - 1 + idx[(idx == nbins) & (kk <= kbins[-1])] = nbins - 1 + keep = (idx >= 0) & (idx < nbins) + return np.bincount(idx[keep], minlength=nbins) + + # full complex fft2 plane, binned by brute force + i0 = int(n // 2) + ix, iy = np.mgrid[0:n, 0:n] + full = binned(np.hypot((ix - i0) * dk, (iy - i0) * dk).ravel()) + + # the same half plane rfft2 sees, but binned WITHOUT the weighting + ncol = n // 2 + 1 + rf = np.arange(n) + rf[rf > (n - 1) // 2] -= n + kk_half = np.hypot((rf * dk)[:, None], (np.arange(ncol) * dk)[None, :]).ravel() + naive = binned(kk_half) + + np.testing.assert_array_equal(counts, full) # weighting reproduces the full count + assert not np.array_equal(naive, full) # ... and the unweighted half does not + assert naive.sum() < 0.6 * full.sum() # it is roughly half, as expected + + def test_FFT(load_magnetic_anomaly): d = load_magnetic_anomaly["mag_data"] xc = load_magnetic_anomaly["xc"] @@ -83,24 +312,132 @@ def test_taper_functions(load_magnetic_anomaly): def test_Tanaka(load_magnetic_anomaly): + """ + The centroid method returns a sane, positive Curie depth on the legacy + fixture. + + No accuracy is asserted here. This grid is only 305 km across for a 10 km + layer, so the z0 band cannot satisfy |k|d << 1 with enough points left to + fit -- see tests/test_recovery.py, which checks accuracy against + synthetics generated wide enough to support the fit. + """ d = load_magnetic_anomaly["mag_data"] xc = load_magnetic_anomaly["xc"] yc = load_magnetic_anomaly["yc"] xmin, xmax, ymin, ymax = load_magnetic_anomaly["extent"] - grid = pycurious.CurieGrid(d, xmin, xmax, ymin, ymax) + grid = pycurious.CurieOptimiseTanaka(d, xmin, xmax, ymin, ymax) - # wavenumber bands for Z0 and Zt, respectively - kwin_Z0 = (0.005, 0.03) - kwin_Zt = (0.03, 0.7) + # wavenumber bands in rad/km + zt_range = (1.26, 1.89) + z0_range = (0.0, 0.63) - k, Phi, sigma_Phi = grid.radial_spectrum(grid.data, taper=np.hanning, power=0.5) - (Ztr, btr, dZtr), (Zor, bor, dZor) = pycurious.tanaka1999( - k, Phi, sigma_Phi, kwin_Z0, kwin_Zt + zt, z0, zt_i, z0_i, sigma_zt, sigma_z0 = grid.optimise( + 300e3, xc, yc, zt_range, z0_range, taper=np.hanning ) - Zb, eZb = pycurious.ComputeTanaka(Ztr, dZtr, Zor, dZor) + CPD, sigma_CPD = grid.calculate_CPD(zt, z0, sigma_zt, sigma_z0) - error_msg = "FAILED! Tanaka CPD is {:.4f} different from expected, uncertainty is {:.4f}".format( - Zb - 10.0, eZb - ) - assert np.abs(Zb - 10.0) < 2.0 and eZb < Zb, error_msg + # depths are returned positive downwards + assert zt > 0.0, "zt should be positive downwards, got {:.4f}".format(zt) + assert z0 > zt, "centroid {:.4f} should lie below the top {:.4f}".format(z0, zt) + assert CPD > z0, "CPD {:.4f} should lie below the centroid {:.4f}".format(CPD, z0) + assert sigma_CPD > 0.0, "uncertainty should be positive" + assert np.isfinite([zt, z0, CPD, sigma_CPD]).all() + + +def test_tanaka_deprecated_functions(load_magnetic_anomaly): + """ + The pre-v2 module functions still run, and say they are deprecated. + + Their numbers are deliberately not asserted: tanaka1999 weights by + 1/sigma**4 and subtracts ln(k) from a standard deviation, so agreement + with any particular value would not be meaningful. + """ + d = load_magnetic_anomaly["mag_data"] + xmin, xmax, ymin, ymax = load_magnetic_anomaly["extent"] + + grid = pycurious.CurieGrid(d, xmin, xmax, ymin, ymax) + k, Phi, sigma_Phi = grid.radial_spectrum(grid.data, taper=np.hanning, power=1) + + with pytest.warns(FutureWarning, match="deprecated"): + (Ztr, btr, dZtr), (Zor, bor, dZor) = pycurious.tanaka1999( + k, Phi, sigma_Phi, (0.005, 0.03), (0.03, 0.7) + ) + + with pytest.warns(FutureWarning, match="argument order differs"): + Zb, eZb = pycurious.ComputeTanaka(Ztr, dZtr, Zor, dZor) + + # abs() is applied internally, so this cannot come back negative + assert Zb > 0.0 + assert np.isfinite([Zb, eZb]).all() + + +def test_dof_factor_deflates_counts(): + """ + The uncertainty of the binned mean is not sigma/sqrt(N): the FFT cells are + not independent. Hermitian symmetry alone makes half of them redundant, and + a fixed number more is lost to correlation however few the annulus holds -- + which is why the deflation depends on the count. + """ + assert _dof_factor(None) == 2.0 + assert _dof_factor(np.hanning) > 2.0 + assert _dof_factor(np.hamming) > 2.0 + # an uncalibrated taper falls back to the exact Hermitian factor + assert _dof_factor(np.bartlett) == 2.0 + # and an explicit override wins + assert _dof_factor(np.hanning, dof_factor=1.0) == 1.0 + + counts = np.array([8, 16, 64, 1024]) + per_bin = _dof_factor(np.hanning, counts) + assert per_bin.shape == counts.shape + # sparse bins are deflated hardest, and a full one tends to the asymptote + assert np.all(np.diff(per_bin) < 0.0) + assert per_bin[0] > 2.0 * per_bin[-1] + assert per_bin[-1] == pytest.approx(3.3, rel=0.02) + + +def test_remove_trend_linear(): + """ + remove_trend_linear subtracts the least-squares plane. + + It must reduce an exact plane to zero -- on non-square grids as well as + square ones -- and agree with a correctly aligned lstsq plane fit on + arbitrary data. The non-square case is a regression guard: a design matrix + built as (nc, nr) instead of (nr, nc) passes the square tests but leaves a + finite trend on a rectangular grid. + """ + # remove_trend_linear reads only its argument's shape, so any valid grid + # will do; the constructor just requires equal node spacing in x and y. + grid = pycurious.CurieGrid(np.zeros((8, 8)), 0.0, 7e3, 0.0, 7e3) + + def lstsq_detrend(data): + nr, nc = data.shape + ii, jj = np.mgrid[0:nr, 0:nc] # aligned with data's own (nr, nc) layout + A = np.c_[ii.ravel(), jj.ravel(), np.ones(data.size)] + coef, *_ = np.linalg.lstsq(A, data.ravel(), rcond=None) + return data - (A @ coef).reshape(data.shape) + + rng = np.random.default_rng(0) + for nr, nc in [(64, 64), (40, 25), (25, 40)]: + ii, jj = np.mgrid[0:nr, 0:nc] + plane = 3.0 + 0.5 * ii - 0.25 * jj + + # an exact plane is removed to zero, whatever the aspect ratio + detrended = grid.remove_trend_linear(plane.astype(float)) + np.testing.assert_allclose(detrended, 0.0, atol=1e-9) + + # and on noisy data it matches the lstsq plane fit + data = plane + rng.standard_normal((nr, nc)) + np.testing.assert_allclose( + grid.remove_trend_linear(data), lstsq_detrend(data), atol=1e-9 + ) + + # a singleton axis carries no identifiable slope: the separable fit divides + # the inner product by (i*i).sum(), which is zero along a length-1 axis, so + # without a guard it returns all-NaN. It must stay finite and still remove + # the trend along the long axis, matching the (rank-deficient) lstsq fit. + for shape in [(1, 12), (12, 1)]: + line = rng.standard_normal(shape) + detrended = grid.remove_trend_linear(line) + assert np.all(np.isfinite(detrended)) + np.testing.assert_allclose(detrended, lstsq_detrend(line), atol=1e-9) diff --git a/tests/test_mag_data.txt b/tests/test_mag_data.txt index 13d9149..eb8972b 120000 --- a/tests/test_mag_data.txt +++ b/tests/test_mag_data.txt @@ -1 +1 @@ -../pycurious/Examples/data/test_mag_data.txt \ No newline at end of file +../Examples/data/test_mag_data.txt \ No newline at end of file diff --git a/tests/test_optimise.py b/tests/test_optimise.py index 712816a..454e69f 100644 --- a/tests/test_optimise.py +++ b/tests/test_optimise.py @@ -1,63 +1,73 @@ import pytest import pycurious import numpy as np -import numpy.testing as npt from scipy.optimize import minimize from conftest import load_magnetic_anomaly -def test_optimisation(load_magnetic_anomaly): +def test_optimisation_smoke(load_magnetic_anomaly): + """ + The optimiser lands in a physically sensible region on the legacy fixture. + + This deliberately asserts no accuracy. `tests/test_mag_data.txt` is 305 km + across for a 10 km layer, so it has too few low-wavenumber bins to pin dz + down: sweeping the window size and moving the centroid by one window width + moves dz over 6.4-11.9 km against a truth of 10.0. A tolerance tight enough + to be meaningful would break on any legitimate change, and a tolerance + loose enough to pass says nothing. Accuracy is asserted against generated + synthetics in tests/test_recovery.py instead. + + Measured here for reference, hanning taper, whole grid as one window: + + beta 2.7688 +/- 0.0985 (truth 3.0) + zt 0.3813 +/- 0.0304 (truth 0.305) + dz 9.0703 +/- 1.9921 (truth 10.0) + C -17.6561 +/- 0.0551 + """ d = load_magnetic_anomaly["mag_data"] xc = load_magnetic_anomaly["xc"] yc = load_magnetic_anomaly["yc"] xmin, xmax, ymin, ymax = load_magnetic_anomaly["extent"] max_window = load_magnetic_anomaly["max_window"] - grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax) - beta, zt, dz, C = grid.optimise(max_window, xc, yc, taper=np.hanning) - - x_opt = np.array([beta, zt, dz]) - - # hard-coded parameters used to generate the magnetic anomaly - zt0 = 0.305 - dz0 = 10.0 + zt0 - beta0 = 3.0 - - x0 = np.array([beta0, zt0, dz0]) - - # compare if they are close or not - # some parameters should be more similar than others - tol = np.array([0.3, 0.1, 2.0]) + grid = pycurious.CurieOptimiseBouligand(d, xmin, xmax, ymin, ymax) + beta, zt, dz, C, s_beta, s_zt, s_dz, s_C = grid.optimise( + max_window, xc, yc, taper=np.hanning + ) - parameters = ["beta", "zt", "dz"] - err_msg = "FAILED! {} = {:.4f} is not within an acceptable tolerance of {}" + for name, value in [("beta", beta), ("zt", zt), ("dz", dz), ("C", C)]: + assert np.isfinite(value), "{} = {} is not finite".format(name, value) + for name, value in [("sigma_beta", s_beta), ("sigma_zt", s_zt), + ("sigma_dz", s_dz), ("sigma_C", s_C)]: + assert np.isfinite(value) and value > 0.0, "{} = {}".format(name, value) - for i in range(x0.size): - npt.assert_allclose( - x_opt[i], - x0[i], - atol=tol[i], - err_msg=err_msg.format(parameters[i], x_opt[i], tol[i]), - ) + # a magnetic layer with a positive thickness, below the surface, and a + # fractal parameter in the range reported for continental crust + assert zt >= 0.0 + assert dz > 0.0 + assert 1.0 < beta < 5.0 def test_priors(load_magnetic_anomaly): + """A prior pulls the parameter it constrains towards its centre.""" d = load_magnetic_anomaly["mag_data"] xc = load_magnetic_anomaly["xc"] yc = load_magnetic_anomaly["yc"] xmin, xmax, ymin, ymax = load_magnetic_anomaly["extent"] max_window = load_magnetic_anomaly["max_window"] - grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax) - beta0, zt0, dz0, C0 = grid.optimise(max_window, xc, yc) + grid = pycurious.CurieOptimiseBouligand(d, xmin, xmax, ymin, ymax) + beta0 = grid.optimise(max_window, xc, yc)[0] grid.add_prior(beta=(1.0, 0.1)) - beta1, zt1, dz1, C1 = grid.optimise(max_window, xc, yc) + beta1 = grid.optimise(max_window, xc, yc)[0] - assert abs(beta1 - 1.0) < abs( - beta0 - 1.0 - ), "FAILED! Optimisation with priors failed" + assert abs(beta1 - 1.0) < abs(beta0 - 1.0), "the prior did not pull beta" + # quantitative, so the test cannot pass on a negligible shift. Measured + # 2.7688 -> 2.3295 once the fit is weighted; it was 2.668 -> 1.228 when the + # fit was unweighted and the data barely competed with the prior. + assert beta0 - beta1 > 0.3, "beta moved only {:.3f}".format(beta0 - beta1) def test_valid_numbers(load_magnetic_anomaly): @@ -178,7 +188,7 @@ def test_valid_numbers(load_magnetic_anomaly): sigma_S = np.ones_like(S) - grid = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax) + grid = pycurious.CurieOptimiseBouligand(d, xmin, xmax, ymin, ymax) beta0 = 3.0 zt0 = 1.0 diff --git a/tests/test_recovery.py b/tests/test_recovery.py new file mode 100644 index 0000000..5b975db --- /dev/null +++ b/tests/test_recovery.py @@ -0,0 +1,214 @@ +""" +Parameter recovery against synthetics with a known Curie depth. + +Unlike the fixed `test_mag_data.txt` fixture, these synthetics are generated +with prescribed parameters (see `pycurious.synthetic`), so the tests ask whether +each method recovers what it was given rather than whether it reproduces one +hard-coded number. +""" + +import numpy as np +import pytest + +import pycurious + +from conftest import synthetic_grid + +# (beta, zt, dz) -- a shallow thick layer and a deeper thinner one +CASES = [(3.0, 1.0, 20.0), (2.0, 5.0, 15.0)] +SEEDS = [1, 2] + + +def _bouligand(beta, zt, dz, seed): + return synthetic_grid( + pycurious.CurieOptimiseBouligand, beta=beta, zt=zt, dz=dz, seed=seed + )[:3] + + +@pytest.mark.parametrize("beta,zt,dz", CASES) +@pytest.mark.parametrize("seed", SEEDS) +def test_bouligand_recovers_beta_and_zt(beta, zt, dz, seed): + """ + beta and zt are tightly determined and near-Gaussian, so a per-seed + tolerance is meaningful for them. Across 200 realisations their mean errors + are 0.05 and 0.02. + + dz is deliberately not asserted here -- see the two tests below. + """ + grid, xc, yc = _bouligand(beta, zt, dz, seed) + beta_r, zt_r = grid.optimise(1000e3, xc, yc, taper=np.hanning)[:2] + + assert np.abs(beta_r - beta) < 0.2, "beta {:.3f} != {}".format(beta_r, beta) + assert np.abs(zt_r - zt) < 0.25, "zt {:.3f} != {}".format(zt_r, zt) + + +@pytest.mark.parametrize("beta,zt,dz", CASES) +@pytest.mark.parametrize("seed", SEEDS) +def test_bouligand_curie_depth_interval_contains_the_truth(beta, zt, dz, seed): + """ + The right per-seed claim for a long-tailed parameter is that the interval + covers the truth, not that the point estimate is close to it. + + dz has a long upper tail (Mather & Fullea, 2019). A single realisation can + put it 85% high at a *lower* misfit than the truth, so any per-seed + tolerance tight enough to be interesting fails on roughly one seed in five. + """ + grid, xc, yc = _bouligand(beta, zt, dz, seed) + _, _, lower, upper = grid.profile(1000e3, xc, yc, "CPD", taper=np.hanning) + + truth = zt + dz + assert lower <= truth <= upper, "CPD interval [{:.2f}, {:.2f}] misses {}".format( + lower, upper, truth + ) + + +@pytest.mark.slow +@pytest.mark.parametrize("beta,zt,dz", CASES) +def test_bouligand_dz_is_recovered_in_the_mean(beta, zt, dz): + """ + dz is only well determined in aggregate, so that is what to assert. + + Per-seed values for the first case run 13.5 to 34.9 against a truth of + 20.0. Averaging eight of them gives 21.32 and 16.03 for the two cases, + i.e. +6.6% and +6.9% -- both high, because the tail is on that side and it + pulls the mean with it. The mean is the right statistic to assert, but it + is not an unbiased one; see `profile` for the honest interval. + """ + recovered = [] + for seed in range(1, 9): + grid, xc, yc = _bouligand(beta, zt, dz, seed) + recovered.append(grid.optimise(1000e3, xc, yc, taper=np.hanning)[2]) + + mean_dz = float(np.mean(recovered)) + assert np.abs(mean_dz - dz) < 0.10 * dz, "mean dz {:.2f} != {} (from {})".format( + mean_dz, dz, np.round(recovered, 1).tolist() + ) + + +def _tanaka_grid(beta=3.0, zt=1.0, dz=20.0): + """A grid wide enough for the centroid band to satisfy |k|d << 1.""" + return synthetic_grid( + pycurious.CurieOptimiseTanaka, beta=beta, zt=zt, dz=dz, n=1024, dx=4.0 + ) + + +def test_tanaka_recovers_curie_depth(): + beta, zt, dz = 3.0, 1.0, 20.0 + grid, xc, yc, window = _tanaka_grid(beta, zt, dz) + + zt_r, z0_r, _, _, sigma_zt, sigma_z0 = grid.optimise( + window, xc, yc, (0.20, 0.60), (0.0, 0.05), taper=np.hanning, beta=beta + ) + CPD, sigma_CPD = grid.calculate_CPD(zt_r, z0_r, sigma_zt, sigma_z0) + + assert np.abs(zt_r - zt) < 0.3, "zt {:.3f} != {}".format(zt_r, zt) + assert np.abs(z0_r - (zt + dz / 2)) < 2.0, "z0 {:.3f}".format(z0_r) + assert np.abs(CPD - (zt + dz)) < 3.5, "CPD {:.3f} != {}".format(CPD, zt + dz) + assert sigma_CPD > 0.0 + + +@pytest.mark.parametrize("beta", [2.0, 3.0, 4.0]) +def test_tanaka_beta_correction_is_verified_for_zt_only(beta): + """ + Supplying `beta` recovers the top of the source at any beta, but not the + Curie depth. + + Removing the -(beta-1)ln|k| term does not make the two models agree: + bouligand2009 also carries beta inside its cosh/Bessel factor, and the + remainder lands on the centroid. Fitting an exact noiseless spectrum with + zt=1 and dz=20, the error in Zb runs +16.6 km at beta=1, +4.2 at 2, +0.6 at + 3 and -0.5 at 4, while zt comes back to three decimal places throughout. + + So `test_tanaka_recovers_curie_depth` passing at beta=3 is a coincidence of + that one value, where the residual cancels the opposing |k|d bias. This + test pins the part that is actually general. + """ + zt, dz = 1.0, 20.0 + k = np.arange(1, 600) * 0.0034 + sigma = np.full_like(k, 0.01) + grid = pycurious.CurieOptimiseTanaka(np.zeros((9, 9)), 0.0, 8e3, 0.0, 8e3) + + # power=1 is the amplitude spectrum, half the log power spectrum + Phi = 0.5 * pycurious.bouligand2009(k, beta, zt, dz, 0.0) + corrected = Phi + 0.5 * (beta - 1.0) * np.log(k) + + zt_r, _, _ = grid._fit_band(k, corrected, sigma, (0.20, 0.60)) + assert np.abs(zt_r - zt) < 0.01, "zt {:.4f} at beta={}".format(zt_r, beta) + + +def test_tanaka_beta_correction_removes_fractal_bias(): + """ + Without the correction, zt is biased high by (beta-1)/(2*kbar). Supplying + beta should remove that and leave zt near the truth. + """ + beta, zt, dz = 3.0, 1.0, 20.0 + grid, xc, yc, window = _tanaka_grid(beta, zt, dz) + band = (0.20, 0.60) + + zt_classic = grid.optimise(window, xc, yc, band, (0.0, 0.05), beta=None)[0] + zt_corrected = grid.optimise(window, xc, yc, band, (0.0, 0.05), beta=beta)[0] + + k, _, _ = grid.radial_spectrum(grid.subgrid(window, xc, yc), power=1) + kbar = k[np.logical_and(k >= band[0], k <= band[1])].mean() + predicted_bias = (beta - 1.0) / (2.0 * kbar) + + assert zt_classic > zt_corrected, "correction should reduce zt" + np.testing.assert_allclose(zt_classic - zt_corrected, predicted_bias, rtol=0.15) + assert np.abs(zt_corrected - zt) < 0.3 + + +def test_tanaka_exact_spectrum(): + """ + Fit an exact, noiseless layer spectrum. The zt band should return zt almost + exactly, and the z0 bias should shrink as the band respects |k|d << 1. + """ + zt, dz = 1.0, 20.0 + z0, half = zt + dz / 2, dz / 2 + k = np.arange(1, 600) * 0.0034 + Phi = -k * zt + np.log(1.0 - np.exp(-k * dz)) + sigma = np.full_like(k, 0.01) + + grid = pycurious.CurieOptimiseTanaka(np.zeros((9, 9)), 0.0, 8e3, 0.0, 8e3) + + zt_r, _, _ = grid._fit_band(k, Phi, sigma, (0.20, 0.60)) + assert np.abs(zt_r - zt) < 0.05, "zt {:.4f} != {}".format(zt_r, zt) + + Phi_n = Phi - np.log(k) + errors = [] + for kmax in (0.20, 0.10, 0.05, 0.02): + z0_r, _, _ = grid._fit_band(k, Phi_n, sigma, (0.0, kmax)) + errors.append(abs(z0_r - z0)) + assert z0_r < z0, "the |k|d approximation biases z0 low" + + # monotonically better as |k|d falls, and converging on the truth + assert errors == sorted(errors, reverse=True), errors + assert errors[-1] < 0.5, "z0 error at |k|d=0.2 is {:.3f}".format(errors[-1]) + + +def test_synthetic_matches_forward_model(): + """ + The measured spectrum of the generated field matches the model it was built + from, so the wavenumber grid, the radial binning and the generator agree. + + This is *not* a check against circularity, whatever it may look like. The + generator filters noise by exp(bouligand2009/2) and this refits + bouligand2009, so the two cannot disagree about the model itself -- only + about the machinery in between, which is what it actually exercises. An + independent check would need a forward model built some other way, such as + integrating a magnetisation over depth as tests/Bouligand_forward.py does. + """ + beta, zt, dz, C = 3.0, 1.0, 20.0, 5.0 + data, extent = pycurious.fractal_anomaly( + n=512, dx=2.0, beta=beta, zt=zt, dz=dz, C=C, seed=1 + ) + + grid = pycurious.CurieGrid(data, *extent) + k, Phi, sigma_Phi = grid.radial_spectrum(grid.data, taper=None, power=2) + model = pycurious.bouligand2009(k, beta, zt, dz, C) + + # the white-noise normalisation is an arbitrary constant, absorbed into C, + # so compare the shape rather than the level + residual = (Phi - model) - (Phi - model).mean() + assert residual.std() < 0.3, "spectrum shape differs, rms {:.3f}".format( + residual.std() + ) diff --git a/tests/test_routines.py b/tests/test_routines.py index 3622045..874c8b9 100644 --- a/tests/test_routines.py +++ b/tests/test_routines.py @@ -33,21 +33,23 @@ def test_CurieGrid_routines(load_magnetic_anomaly): assert time_routine(cpd.create_centroid_list, 0.1 * max_window) assert time_routine(cpd.remove_trend_linear, cpd.data) assert time_routine(cpd.radial_spectrum, cpd.data) - assert time_routine(cpd.azimuthal_spectrum, cpd.data) assert time_routine(cpd.reduce_to_pole, cpd.data, 30.0, 30.0) assert time_routine(cpd.upward_continuation, cpd.data, 10e3) -def test_CurieOptimise_routines(load_magnetic_anomaly): +def test_CurieOptimiseBouligand_routines(load_magnetic_anomaly): d = load_magnetic_anomaly["mag_data"] xc = load_magnetic_anomaly["xc"] yc = load_magnetic_anomaly["yc"] xmin, xmax, ymin, ymax = load_magnetic_anomaly["extent"] max_window = load_magnetic_anomaly["max_window"] - cpd = pycurious.CurieOptimise(d, xmin, xmax, ymin, ymax) + cpd = pycurious.CurieOptimiseBouligand(d, xmin, xmax, ymin, ymax) + # 15 km spacing rather than 5 gives 121 centroids instead of 961. This is a + # smoke test of the parallel collection path, and 961 windows overlapping + # by 97% exercise nothing 121 do not -- they just cost eight seconds. xc_list, yc_list = cpd.create_centroid_list( - 0.5 * max_window, spacingX=5e3, spacingY=5e3 + 0.5 * max_window, spacingX=15e3, spacingY=15e3 ) xc = xc_list[0] yc = yc_list[0] diff --git a/tests/test_tanaka.py b/tests/test_tanaka.py new file mode 100644 index 0000000..719c67e --- /dev/null +++ b/tests/test_tanaka.py @@ -0,0 +1,182 @@ +"""Behaviour of CurieOptimiseTanaka, and guards against defects fixed in v2.""" + +import warnings + +import numpy as np +import pytest + +import pycurious + +from conftest import load_magnetic_anomaly + +ZT_RANGE = (1.26, 1.89) +Z0_RANGE = (0.0, 0.63) + + +@pytest.fixture +def tanaka(load_magnetic_anomaly): + d = load_magnetic_anomaly["mag_data"] + xmin, xmax, ymin, ymax = load_magnetic_anomaly["extent"] + return ( + pycurious.CurieOptimiseTanaka(d, xmin, xmax, ymin, ymax), + load_magnetic_anomaly["xc"], + load_magnetic_anomaly["yc"], + ) + + +def test_depths_are_positive_downwards(tanaka): + """ + optimise returns depths, not gradients. + + Before v2 it returned the raw negative gradients, so callers saw a + "top of magnetic source" of -0.95 km. + """ + grid, xc, yc = tanaka + zt, z0, _, _, sigma_zt, sigma_z0 = grid.optimise( + 300e3, xc, yc, ZT_RANGE, Z0_RANGE, taper=np.hanning + ) + assert zt > 0.0 and z0 > 0.0 + assert sigma_zt > 0.0 and sigma_z0 > 0.0 + + +def test_centroid_sigma_equals_spectrum_sigma(tanaka): + """ + Dividing the amplitude spectrum by |k| must not change its uncertainty. + + Before v2 the centroid branch computed log(exp(sigma)/k), subtracting + ln(k) from a standard deviation. That inflated sigma from ~0.6 to ~4.0 at + the lowest wavenumbers -- exactly the bins the centroid fit relies on -- + and dragged z0 with it. + """ + grid, xc, yc = tanaka + k, Phi, Phi_n, sigma = grid._spectrum( + 300e3, xc, yc, np.hanning, None, None, None + ) + + # Phi_n is Phi shifted by a deterministic ln(k) ... + np.testing.assert_allclose(Phi_n, Phi - np.log(k)) + # ... so a single sigma serves both fits, and it is strictly positive + assert np.all(sigma > 0.0) + assert np.all(np.isfinite(sigma)) + + +def test_warns_when_bands_look_like_cycles_per_km(tanaka): + """ + Bands were specified in cycles/km before v2. Such a call still runs and + returns a plausible looking number, so it has to be flagged. + """ + grid, xc, yc = tanaka + with pytest.warns(UserWarning, match="cycles/km"): + grid.optimise(300e3, xc, yc, (0.2, 0.3), (0.0, 0.1), taper=np.hanning) + + +def test_no_false_positive_on_valid_bands(tanaka): + """The units guard must stay quiet for legitimate rad/km bands.""" + grid, xc, yc = tanaka + with warnings.catch_warnings(): + warnings.simplefilter("error", UserWarning) + grid.optimise(300e3, xc, yc, ZT_RANGE, Z0_RANGE, taper=np.hanning) + + +def test_check_bands_flags_invalid_centroid_band(tanaka): + """|k|d << 1 is the assumption most easily violated, and least visible.""" + grid, xc, yc = tanaka + k, _, _ = grid.radial_spectrum(grid.subgrid(300e3, xc, yc), power=1) + + with pytest.warns(UserWarning, match=r"\|k\|d"): + diagnostics = grid.check_bands( + k, ZT_RANGE, (0.0, 2.0), thickness=10.0, verbose=False + ) + assert diagnostics["kd_max"] > 1.0 + assert diagnostics["n_zt"] >= 3 + + +def test_too_few_points_raises(tanaka): + grid, xc, yc = tanaka + with pytest.raises(ValueError, match="at least 3"): + grid.optimise(300e3, xc, yc, (2.0, 2.001), Z0_RANGE, taper=np.hanning) + + +def test_sensitivity_tracks_the_covariance_without_band_jitter(tanaka): + """ + With band_scale=0 only the spectrum is resampled, which should track the + analytic fit covariance -- but land below it by a known factor. + + `sensitivity` redraws each bin independently, and the covariance no longer + assumes they are: a taper correlates neighbouring annuli, which inflates + the reported sigma by about 1.35 under np.hanning. So the two agreeing + exactly would mean the correlation correction had been dropped. Measured + 0.272 against 0.370, a ratio of 1.36. + + Jittering the bands then widens it further, since band placement dominates + over spectral scatter on this fixture. + """ + grid, xc, yc = tanaka + _, _, _, _, sigma_zt, sigma_z0 = grid.optimise( + 300e3, xc, yc, ZT_RANGE, Z0_RANGE, taper=np.hanning + ) + _, sigma_CPD = grid.calculate_CPD(0.0, 0.0, sigma_zt, sigma_z0) + + zt_s, z0_s, cpd_s = grid.sensitivity( + 300e3, xc, yc, 400, ZT_RANGE, Z0_RANGE, + taper=np.hanning, band_scale=0.0, seed=42, + ) + inflation = sigma_CPD / cpd_s.std() + assert 1.15 < inflation < 1.6, "inflation {:.3f}".format(inflation) + + _, _, cpd_jitter = grid.sensitivity( + 300e3, xc, yc, 400, ZT_RANGE, Z0_RANGE, + taper=np.hanning, band_scale=0.1, seed=42, + ) + assert cpd_jitter.std() > cpd_s.std() + + +def test_sensitivity_is_reproducible(tanaka): + grid, xc, yc = tanaka + kwargs = dict(taper=np.hanning, band_scale=0.1) + a = grid.sensitivity(300e3, xc, yc, 100, ZT_RANGE, Z0_RANGE, seed=7, **kwargs)[2] + b = grid.sensitivity(300e3, xc, yc, 100, ZT_RANGE, Z0_RANGE, seed=7, **kwargs)[2] + c = grid.sensitivity(300e3, xc, yc, 100, ZT_RANGE, Z0_RANGE, seed=8, **kwargs)[2] + np.testing.assert_allclose(a, b) + assert not np.allclose(a, c) + + +def test_calculate_CPD_propagates_uncertainty(): + grid = pycurious.CurieOptimiseTanaka(np.zeros((9, 9)), 0.0, 8e3, 0.0, 8e3) + + CPD, sigma = grid.calculate_CPD(1.0, 6.0, 0.3, 0.4) + assert CPD == pytest.approx(11.0) + assert sigma == pytest.approx(np.sqrt(0.3 ** 2 + 0.8 ** 2)) + + # vectorises over arrays of centroids + CPD, sigma = grid.calculate_CPD( + np.array([1.0, 2.0]), np.array([6.0, 7.0]), 0.0, 0.0 + ) + np.testing.assert_allclose(CPD, [11.0, 12.0]) + np.testing.assert_allclose(sigma, [0.0, 0.0]) + + +def test_CurieOptimiseTanaka_routines(load_magnetic_anomaly): + """optimise_routine returns one array per output, one entry per centroid.""" + d = load_magnetic_anomaly["mag_data"] + xmin, xmax, ymin, ymax = load_magnetic_anomaly["extent"] + max_window = load_magnetic_anomaly["max_window"] + + grid = pycurious.CurieOptimiseTanaka(d, xmin, xmax, ymin, ymax) + xc_list, yc_list = grid.create_centroid_list( + 0.5 * max_window, spacingX=40e3, spacingY=40e3 + ) + + results = grid.optimise_routine( + 0.5 * max_window, xc_list, yc_list, ZT_RANGE, Z0_RANGE, taper=np.hanning + ) + + assert len(results) == 6 + for array in results: + assert len(array) == len(xc_list) + assert np.isfinite(array).all() + + zt, z0, _, _, sigma_zt, sigma_z0 = results + CPD, sigma_CPD = grid.calculate_CPD(zt, z0, sigma_zt, sigma_z0) + assert CPD.shape == (len(xc_list),) + assert np.all(sigma_CPD > 0.0)