├── .github ├── scripts │ └── update_citation.py └── workflows │ ├── citation.yaml │ ├── pypi.yaml │ └── test.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── .testignore ├── CHANGELOG.rst ├── CITATION.cff ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── 1sphere-basics.ipynb ├── 1sphere-gain-derivation.ipynb ├── 1sphere-gain-graphs.ipynb ├── 1sphere-gain-testing.ipynb ├── 1sphere-measurements.ipynb ├── 1sphere-random-points.ipynb ├── 2sphere-gain-derivation.ipynb ├── 2sphere-gain-testing.ipynb ├── ad-basics.ipynb ├── ad-quadrature.ipynb ├── ad-redistribution.ipynb ├── ad-sample.ipynb ├── ad-thin.ipynb ├── changelog.rst ├── conf.py ├── iad-with-spheres.ipynb ├── iadpython.rst ├── impossible.ipynb ├── index.rst ├── nist.ipynb ├── performance.ipynb ├── requirements.txt ├── sphere-T.png ├── sphere-wall.png ├── sphere.png ├── sphere2.png ├── sphereMR.png ├── sphereMT.png ├── sphereMT2.png ├── sphereMT2a.png ├── sphereMT2b.png ├── sphereMT3.png └── two-sphere-basic.png ├── iadpython ├── __init__.py ├── ad.py ├── combine.py ├── constants.py ├── data │ └── M38597.csv ├── double.py ├── fresnel.py ├── grid.py ├── iad.py ├── iadc.py ├── iadcommand.py ├── nist.py ├── port.py ├── quadrature.py ├── redistribution.py ├── rxt.py ├── sphere.py ├── start.py └── txt.py ├── pyproject.toml ├── release.txt ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── conftest.py ├── data │ ├── basic-A.rxt │ ├── basic-A.txt │ ├── basic-B.rxt │ ├── basic-B.txt │ ├── basic-C.rxt │ ├── basic-C.txt │ ├── basic-D.rxt │ ├── basic-D.txt │ ├── basic-E.rxt │ ├── basic-E.txt │ ├── sample-A.rxt │ ├── sample-A.txt │ ├── sample-B.rxt │ ├── sample-B.txt │ ├── sample-C.rxt │ ├── sample-C.txt │ ├── sample-D.rxt │ ├── sample-D.txt │ ├── sample-E.rxt │ ├── sample-E.txt │ ├── sample-F.rxt │ ├── sample-F.txt │ ├── sample-G.rxt │ └── sample-G.txt ├── test_all_notebooks.py ├── test_boundary.py ├── test_combo.py ├── test_double.py ├── test_fresnel.py ├── test_grid.py ├── test_iad.py ├── test_iadcommand.py ├── test_layer.py ├── test_layers.py ├── test_nist.py ├── test_one_sphere.py ├── test_port.py ├── test_quadrature.py ├── test_redistribution.py ├── test_rxt.py ├── test_sphere.py ├── test_start.py ├── test_txt.py └── test_ur1_uru.py ├── tests_iadc ├── test_iadc.py └── test_performance.py └── todo.txt /.github/scripts/update_citation.py: -------------------------------------------------------------------------------- 1 | """Update date and version in citation.""" 2 | import json 3 | import requests 4 | import yaml 5 | 6 | # Replace with your username and repo name 7 | USERNAME = "scottprahl" 8 | REPO = "iadpython" 9 | 10 | # Fetch latest release date 11 | response = requests.get(f"https://api.github.com/repos/{USERNAME}/{REPO}/releases/latest") 12 | release_info = json.loads(response.text) 13 | release_date = release_info["published_at"].split("T")[0] 14 | version = release_info["tag_name"] 15 | 16 | # Read the existing CITATION.cff file 17 | with open("CITATION.cff", "r") as f: 18 | cff_data = yaml.safe_load(f) 19 | 20 | # Create a flag to track if any change is made 21 | changed = False 22 | 23 | # Update the date-released field only if it's different 24 | if cff_data.get("date-released") != release_date: 25 | cff_data["date-released"] = release_date 26 | changed = True 27 | 28 | # Update the version field only if it's different 29 | if cff_data.get("version") != version: 30 | cff_data["version"] = version 31 | changed = True 32 | 33 | # Save the updated data back to CITATION.cff only if there was a change 34 | if changed: 35 | with open("CITATION.cff", "w") as f: 36 | yaml.dump(cff_data, f) 37 | else: 38 | print("No change in release date or version. No update needed.") 39 | -------------------------------------------------------------------------------- /.github/workflows/citation.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: citation 3 | 4 | on: # yamllint disable-line rule:truthy 5 | release: 6 | types: [published] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | update-citation: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: actions/setup-python@v5 17 | with: 18 | python-version: 3.12 19 | 20 | - name: Install dependencies needed for update_citation.py 21 | run: | 22 | pip install requests PyYAML 23 | 24 | - name: Update CITATION.cff 25 | run: | 26 | python .github/scripts/update_citation.py 27 | 28 | - name: Commit and Push CITATION.cff Update 29 | run: | 30 | git config --local user.email "action@github.com" 31 | git config --local user.name "GitHub Action" 32 | git diff --exit-code || ( 33 | git add CITATION.cff && 34 | git commit -m "Update CITATION.cff with latest release date" && 35 | git push 36 | ) 37 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: pypi 3 | 4 | on: # yamllint disable-line rule:truthy 5 | release: 6 | types: [published] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | 13 | environment: 14 | name: pypi repository 15 | url: https://pypi.org/p/iadpython 16 | 17 | permissions: 18 | id-token: write 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: actions/setup-python@v5 24 | with: 25 | python-version: 3.12 26 | 27 | - name: Build 28 | run: | 29 | pip install setuptools wheel twine build 30 | pip install -r requirements.txt 31 | python -m build 32 | 33 | - name: Publish package to PyPI 34 | uses: pypa/gh-action-pypi-publish@release/v1 35 | with: 36 | verbose: true 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: test 3 | 4 | on: # yamllint disable-line rule:truthy 5 | push: 6 | branches: [master] 7 | pull_request: 8 | branches: [master] 9 | workflow_dispatch: # allow manual triggering 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | name: Build and test 15 | 16 | strategy: 17 | matrix: 18 | python-version: ['3.7', '3.12'] 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | 29 | - name: Install package and dependencies 30 | run: | 31 | pip install -r requirements.txt 32 | pip install -e . # current package in editable mode 33 | 34 | - name: Tests 35 | run: | 36 | pytest tests/test_boundary.py 37 | pytest tests/test_combo.py 38 | pytest tests/test_fresnel.py 39 | pytest tests/test_grid.py 40 | pytest tests/test_iad.py 41 | pytest tests/test_layer.py 42 | pytest tests/test_layers.py 43 | pytest tests/test_nist.py 44 | pytest tests/test_quadrature.py 45 | pytest tests/test_redistribution.py 46 | pytest tests/test_rxt.py 47 | pytest tests/test_sphere.py 48 | pytest tests/test_start.py 49 | pytest tests/test_ur1_uru.py 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | iadpython/__pycache__/* 4 | iadpython/ad-simple.nb 5 | docs/.ipynb_checkpoints/ 6 | iadpython.egg-info/* 7 | dist/* 8 | docs/_build/* 9 | docs/doi.org/ 10 | docs/*.graffle 11 | docs/api/ 12 | iadpython/.ipynb_checkpoints/ 13 | iadp -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 24.4.2 4 | hooks: 5 | - id: black 6 | language_version: python3.12 7 | 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v4.6.0 10 | hooks: 11 | - id: trailing-whitespace 12 | - id: double-quote-string-fixer 13 | - id: end-of-file-fixer 14 | - id: check-yaml 15 | - id: check-merge-conflict 16 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # Set the Python requirements 19 | python: 20 | install: 21 | - requirements: docs/requirements.txt 22 | -------------------------------------------------------------------------------- /.testignore: -------------------------------------------------------------------------------- 1 | docs/IAD-with-spheres.ipynb 2 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog for `iadpython` package 2 | ================================= 3 | 4 | 0.6.0 5 | ------ 6 | * fully support single integrating spheres 7 | * add command-line support 8 | * add Monte Carlo sphere simulations 9 | * add Port class for Sphere class 10 | * change entrance port to empty port 11 | * improve sphere-single.ipynb 12 | * add sphere-random.ipynb 13 | * improve single sphere testing 14 | * add port testing 15 | * add notebook on measurements 16 | * rearrange documentation 17 | 18 | 19 | 0.5.3 20 | ------ 21 | * add github actions 22 | * add citation with zenodo DOI 23 | * add copyright to docs 24 | * add conda support 25 | * improve badges 26 | * add github auto testing 27 | * lint files 28 | * start fixing math in docstrings 29 | * remove tox 30 | 31 | v0.5.2 32 | ------ 33 | * fix search, one-sphere round-trips now 34 | * fix packaging 35 | * fix html generation 36 | * linting 37 | 38 | v0.5.1 39 | ------ 40 | * inverse calculation works for 0 spheres 41 | * much more testing 42 | * basic multiple layers support 43 | * single location for version number 44 | * revert to sphinx_rtd_theme 45 | 46 | v0.4.0 47 | ------ 48 | * forward adding-doubling calculation is pure python now 49 | * create pure python packages 50 | * include wheel file 51 | * package as python3 only 52 | * use sphinx_book_theme for docs 53 | 54 | v0.3.0 55 | ------ 56 | * improve packaging 57 | * improve documentation with Sphinx 58 | * use tox 59 | * add better diagnostics for finding libiad library 60 | 61 | v0.2.5 62 | ------ 63 | * add doc files to distribution 64 | * promise only python 3 and non-zip 65 | * improve long description 66 | 67 | v0.2.4 68 | * fix bugs found by C Regan 69 | * (unreleased??) 70 | 71 | v0.2.3 72 | ------ 73 | * fix __init__.py so "import iadpython" works 74 | 75 | v0.2.2 76 | ------ 77 | * necessary because setup.py version did not get updated 78 | 79 | v0.2.1 80 | ------ 81 | * remove ctypes as a requirement 82 | 83 | v0.2.0 84 | ------ 85 | * initial checkin 86 | * Convert to markdown format 87 | * update to correct contents 88 | * add ctypes as dependancy, fix test path 89 | * update my notes on how to cut a release 90 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | authors: 2 | - family-names: Prahl 3 | given-names: Scott 4 | orcid: https://orcid.org/0000-0003-1468-6851 5 | cff-version: 1.2.0 6 | date-released: '2023-09-19' 7 | doi: 10.5281/zenodo.8361415 8 | message: If you use this software, please cite it as below. 9 | title: 'iadpython: a python module implementation of adding-doubling.' 10 | url: https://zenodo.org/badge/latestdoi/102148844 11 | version: 0.5.3 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017-2021 Scott Prahl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.rst 2 | include CITATION.cff 3 | include LICENSE.txt 4 | include README.rst 5 | include requirements.txt 6 | include requirements-dev.txt 7 | 8 | include iadpython/data/M38597.csv 9 | include tests/data/*.rxt 10 | include tests/data/*.txt 11 | include tests/*.py 12 | include tests_iadc/test_iadc.py 13 | include tests_iadc/test_performance.py 14 | 15 | exclude todo.txt 16 | exclude .testignore -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS ?= 2 | SPHINXBUILD ?= sphinx-build 3 | SOURCEDIR = docs 4 | BUILDDIR = docs/_build 5 | 6 | html: 7 | $(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) 8 | open docs/_build/index.html 9 | 10 | lint: 11 | -pylint iadpython/ad.py 12 | -pylint iadpython/combine.py 13 | -pylint iadpython/constants.py 14 | -pylint iadpython/fresnel.py 15 | -pylint iadpython/grid.py 16 | -pylint iadpython/iad.py 17 | -pylint iadpython/nist.py 18 | -pylint --ignored-modules=scipy.special iadpython/quadrature.py 19 | -pylint --ignored-modules=scipy.special iadpython/redistribution.py 20 | -pylint iadpython/rxt.py 21 | -pylint iadpython/sphere.py 22 | -pylint iadpython/port.py 23 | -pylint iadpython/start.py 24 | -pylint iadpython/txt.py 25 | 26 | -pylint tests/test_boundary.py 27 | -pylint tests/test_combo.py 28 | -pylint tests/test_fresnel.py 29 | -pylint tests/test_grid.py 30 | -pylint tests/test_iad.py 31 | -pylint tests/test_layer.py 32 | -pylint tests/test_layers.py 33 | -pylint tests/test_quadrature.py 34 | -pylint tests/test_redistribution.py 35 | -pylint tests/test_start.py 36 | -pylint tests/test_ur1_uru.py 37 | -pylint tests_iadc/test_iadc.py 38 | -pylint tests_iadc/test_performance.py 39 | 40 | lintc: 41 | -pylint iadpython/iadc.py 42 | -pylint tests_iadc/test_iadc.py 43 | 44 | notetest: 45 | make clean 46 | pytest --notebooks tests/test_all_notebooks.py 47 | rm -rf __pycache__ 48 | 49 | test: 50 | pytest --verbose tests/test_boundary.py 51 | pytest --verbose tests/test_combo.py 52 | pytest --verbose tests/test_fresnel.py 53 | pytest --verbose tests/test_grid.py 54 | pytest --verbose tests/test_layer.py 55 | pytest --verbose tests/test_layers.py 56 | pytest --verbose tests/test_nist.py 57 | pytest --verbose tests/test_one_sphere.py 58 | pytest --verbose tests/test_quadrature.py 59 | pytest --verbose tests/test_redistribution.py 60 | pytest --verbose tests/test_rxt.py 61 | pytest --verbose tests/test_txt.py 62 | pytest --verbose tests/test_sphere.py 63 | pytest --verbose tests/test_port.py 64 | pytest --verbose tests/test_start.py 65 | pytest --verbose tests/test_ur1_uru.py 66 | pytest --verbose tests/test_iad.py 67 | 68 | testc: 69 | pytest --verbose tests_iadc/test_iadc.py 70 | pytest --verbose tests_iadc/test_performance.py 71 | 72 | rcheck: 73 | -ruff check 74 | -flake8 . 75 | make lint 76 | make lintc 77 | make test 78 | make testc 79 | pyroma -d . 80 | check-manifest 81 | make notetest 82 | 83 | clean: 84 | rm -rf .pytest_cache 85 | rm -rf __pycache__ 86 | rm -rf dist 87 | rm -rf iadpython.egg-info 88 | rm -rf iadpython/__pycache__ 89 | rm -rf iadpython/__init__.pyc 90 | rm -rf iadpython/.ipynb_checkpoints 91 | rm -rf docs/_build 92 | rm -rf docs/api 93 | rm -rf docs/doi.org/ 94 | rm -rf docs/.ipynb_checkpoints 95 | rm -rf tests/__pycache__ 96 | rm -rf tests/tests_iadc/__pycache__ 97 | 98 | realclean: 99 | make clean 100 | 101 | .PHONY: clean realclean test lintpylint pydoc html 102 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. |pypi| image:: https://img.shields.io/pypi/v/iadpython?color=68CA66 2 | :target: https://pypi.org/project/iadpython/ 3 | :alt: pypi 4 | 5 | .. |github| image:: https://img.shields.io/github/v/tag/scottprahl/iadpython?label=github&color=68CA66 6 | :target: https://github.com/scottprahl/iadpython 7 | :alt: github 8 | 9 | .. |conda| image:: https://img.shields.io/conda/vn/conda-forge/iadpython?label=conda&color=68CA66 10 | :target: https://github.com/conda-forge/iadpython-feedstock 11 | :alt: conda 12 | 13 | .. |doi| image:: https://zenodo.org/badge/102148844.svg 14 | :target: https://zenodo.org/badge/latestdoi/102148844 15 | :alt: doi 16 | 17 | .. |license| image:: https://img.shields.io/github/license/scottprahl/iadpython?color=68CA66 18 | :target: https://github.com/scottprahl/iadpython/blob/main/LICENSE.txt 19 | :alt: License 20 | 21 | .. |test| image:: https://github.com/scottprahl/iadpython/actions/workflows/test.yaml/badge.svg 22 | :target: https://github.com/scottprahl/iadpython/actions/workflows/test.yaml 23 | :alt: Testing 24 | 25 | .. |docs| image:: https://readthedocs.org/projects/iadpython/badge 26 | :target: https://iadpython.readthedocs.io 27 | :alt: Docs 28 | 29 | .. |downloads| image:: https://img.shields.io/pypi/dm/iadpython?color=68CA66 30 | :target: https://pypi.org/project/iadpython/ 31 | :alt: Downloads 32 | 33 | iadpython 34 | ========= 35 | 36 | by Scott Prahl 37 | 38 | |pypi| |github| |conda| |doi| 39 | 40 | |license| |test| |docs| |downloads| 41 | 42 | `iadpython` will be a pure Python module to do forward and inverse multiple light 43 | scattering (radiative transport) in layered materials. Calculations are done using 44 | van de Hulst's adding-doubling technique. 45 | 46 | The original adding-doubling algorithm was developed by van de Hulst to model light 47 | propagation through layered media. I extended it to handle Fresnel 48 | reflection at boundaries as well as interactions with integrating spheres. 49 | 50 | Finally, the code was further extended to handle lost light using 51 | Monte Carlo techniques for inverse calculations. 52 | 53 | Version v0.4.0 started the migration to a pure-python implementation. This 54 | version includes the ability to do forward calculations of light transport through 55 | layered 1D structures. 56 | 57 | The long-term goal is rewrite the integrating sphere, inverse algorithm, and 58 | lost light calculations in pure python so that one can do 59 | inverse calculations (i.e., reflection and transmission measurements to 60 | intrinsic absorption and scattering properties). 61 | 62 | Both inverse and forward calculations are currently possible using the `iadc` framework. 63 | This is a python interface to the inverse 64 | adding-doubling package written in C by Scott Prahl 65 | . This works now 66 | but is a nuisance to install and maintain because of its dependence on compiling 67 | and installing a C library. 68 | 69 | See for full documentation of `iadpython`. 70 | 71 | Usage 72 | ----- 73 | 74 | The following will do a forward calculation:: 75 | 76 | import iadpython as iad 77 | 78 | mu_s = 10 # scattering coefficient [1/mm] 79 | mu_a = 0.1 # absorption coefficient [1/mm] 80 | g = 0.9 # scattering anisotropy 81 | d = 1 # thickness mm 82 | 83 | a = mu_s/(mu_a+mu_s) 84 | b = mu_s/(mu_a+mu_s) * d 85 | 86 | # air / glass / sample / glass / air 87 | s = iadpython.Sample(a=a, b=b, g=g, n=1.4, n_above=1.5, n_below=1.5) 88 | ur1, ut1, uru, utu = s.rt() 89 | 90 | print('Collimated light incident perpendicular to sample') 91 | print(' total reflection = %.5f' % ur1) 92 | print(' total transmission = %.5f' % ut1) 93 | 94 | print('Diffuse light incident on sample') 95 | print(' total reflection = %.5f' % uru) 96 | print(' total transmission = %.5f' % utu) 97 | 98 | 99 | Installation 100 | ------------ 101 | 102 | Use ``pip``:: 103 | 104 | pip install iadpython 105 | 106 | or ``conda``:: 107 | 108 | conda install -c conda-forge iadpython 109 | 110 | or use immediately by clicking the Google Colaboratory button below 111 | 112 | .. image:: https://colab.research.google.com/assets/colab-badge.svg 113 | :target: https://colab.research.google.com/github/scottprahl/iadpython/blob/main 114 | :alt: Colab 115 | 116 | Inverse Calculations 117 | --------------------- 118 | 119 | As of version 0.5.3, the forward and inverse calculations work fine as long as you do not need to 120 | include integrating sphere effects or lost-light calculations. 121 | 122 | If you want to do these, then you're probably best served by downloading and compiling 123 | the C-code (on unix or macos) or the `.exe` file for Windows. 124 | 125 | 126 | Dependencies 127 | ------------ 128 | 129 | Required Python modules: numpy, matplotlib, ctypes, scipy 130 | 131 | 132 | License 133 | ------- 134 | 135 | `iadpython` is licensed under the terms of the MIT license. -------------------------------------------------------------------------------- /docs/ad-redistribution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# The Redistribution Function\n", 8 | "\n", 9 | "**Scott Prahl**\n", 10 | "\n", 11 | "**May 2024**" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import numpy as np\n", 21 | "import matplotlib.pyplot as plt\n", 22 | "import iadpython" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "## Redistribution function.\n", 30 | "\n", 31 | "The single scattering phase function $p(\\nu)$ for a tissue determines the\n", 32 | "amount of light scattered at an angle $\\nu=\\cos\\theta$ from the direction of\n", 33 | "incidence. The\n", 34 | "subtended angle $\\nu$ is the dot product\n", 35 | "of the unit vectors $\\hat{\\bf s}_i$ and $\\hat{\\bf s}_j$\n", 36 | "\n", 37 | "$$\n", 38 | "\\nu=\\hat{\\bf s}_i\\cdot\\hat{\\bf s}_j=\\nu_i\\nu_j+\\sqrt{1-\\nu_i^2}\\sqrt{1-\\nu_j^2}\\cos\\phi\n", 39 | "$$\n", 40 | "\n", 41 | "where $\\hat{\\bf s}_i$ is the incident and $\\hat{\\bf s}_j$ is the scattered light directions\n", 42 | "\n", 43 | "The redistribution function ${\\bf h}_{ij}$ determines the fraction of light\n", 44 | "scattered from an incidence cone with angle $\\nu_i$ into a cone with angle\n", 45 | "$\\nu_j$. The redistribution function is calculated by averaging the phase\n", 46 | "function over all possible azimuthal angles for fixed angles $\\nu_i$ and\n", 47 | "$\\nu_j$,\n", 48 | "\n", 49 | "$$\n", 50 | "h(\\nu_i,\\nu_j) = {1\\over2\\pi}\n", 51 | " \\int_0^{2\\pi} p(\\nu_i\\nu_j+\\sqrt{1-\\nu_i^2}\\sqrt{1-\\nu_j^2}\\cos\\phi)\\,d\\phi\n", 52 | "$$\n", 53 | "\n", 54 | "Note that the angles $\\nu_i$ and $\\nu_j$ may also be negative (light\n", 55 | "travelling in the opposite direction). The full redistribution matrix may be\n", 56 | "expressed in terms a $2\\times2$ matrix of |n|$\\times$|n| matrices\n", 57 | "\n", 58 | "$$\n", 59 | "\\mathbf{h}=\\left[\\matrix{\\mathbf{h}^{--}&\\mathbf{h}^{-+}\\cr\n", 60 | " \\mathbf{h}^{+-}&\\mathbf{h}^{++}\\cr}\n", 61 | "\\right] \n", 62 | "$$\n", 63 | "\n", 64 | "The first plus or minus sign indicates the sign in front of the incident \n", 65 | "angle and the second is the sign of the direction of the scattered light. \n", 66 | "\n", 67 | "When the cosine of the angle of incidence or exitance is unity ($\\nu_i=1$ or\n", 68 | "$\\nu_j=1$), then the redistribution function $h(1,\\nu_j)$ is equivalent to the phase\n", 69 | "function $p(\\nu_j)$. In the case of isotropic scattering, the\n", 70 | "redistribution function is a constant\n", 71 | "\n", 72 | "$$\n", 73 | "h(\\nu_i,\\nu_j) = p(\\nu) = {1\\over4\\pi}.\n", 74 | "$$\n", 75 | "\n", 76 | "Other phase functions require numerical integration of the phase\n", 77 | "function. If the phase function is highly anisotropic, then the\n", 78 | "integration over the azimuthal angle is particularly difficult and care\n", 79 | "must be taken to ensure that the integration is accurate. This is\n", 80 | "important because errors in the redistribution function enter directly\n", 81 | "into the reflection and transmission matrices for thin layers. Any\n", 82 | "errors will be doubled with each successive addition of layers and small\n", 83 | "errors will rapidly increase." 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "## Redistribution Matrix using Legendre Polynomials\n", 91 | "\n", 92 | "One way to calculate the redistribution function is the\n", 93 | "$\\delta$-$M$ method of Wiscombe. This method works especially\n", 94 | "well for highly anisotropic phase functions. The number of quadrature\n", 95 | "points is specified by $M$. The $\\delta$-$M$ method approximates the\n", 96 | "true phase function by a phase function consisting of a Dirac delta\n", 97 | "function and $M-1$ Legendre polynomials\n", 98 | "\n", 99 | "$$\n", 100 | "p^*(\\nu)= 2 g^M\\delta(1-\\nu) + (1-g^M) \\sum_{k=0}^{M-1} (2k+1)\\chi_k^* P_k(\\nu)\n", 101 | "$$\n", 102 | "\n", 103 | "where \n", 104 | "\n", 105 | "$$\n", 106 | "\\chi_k^*={\\chi_k-g^M\\over 1-g^M}\n", 107 | "\\qquad\\mbox{and}\\qquad\n", 108 | "\\chi_k = {1\\over2}\\int_0^1 p(\\nu) P_k(\\nu) \\,d\\nu\n", 109 | "$$\n", 110 | "\n", 111 | "When the $\\delta$-$M$ method substitutes $p^*(\\nu)\\rightarrow p(\\nu)$, \n", 112 | "then both the albedo and optical thickness must also be changed,\n", 113 | "$a^*\\rightarrow a$ and $\\tau^*\\rightarrow\\tau$. This approximation is\n", 114 | "analogous to the similarity transformation often used to improve the\n", 115 | "diffusion approximation by moving a part ($g^M$) of the scattered light\n", 116 | "into the unscattered component. The new optical\n", 117 | "thickness and albedo are\n", 118 | "\n", 119 | "$$\n", 120 | "\\tau^*=(1-ag^M)\\tau \n", 121 | "\\qquad\\mbox{and}\\qquad\n", 122 | "a^* = a {1-g^M\\over1-ag^M}\n", 123 | "$$\n", 124 | "\n", 125 | "This is equivalent transforming the scattering coefficient as\n", 126 | "$\\mu_s^* = \\mu_s(1-g^M)$. The redistribution function can now be written\n", 127 | "as\n", 128 | "\n", 129 | "$$\n", 130 | "h^*(\\nu_i,\\nu_j) = \\sum_{k=0}^{M-1} (2k+1)\\chi_k^* P_k(\\nu_i)P_k(\\nu_j)\n", 131 | "$$\n", 132 | "\n", 133 | "For the special case of a Henyey-Greenstein phase function,\n", 134 | "$$\n", 135 | "\\chi_k^*={g^k-g^M\\over1-g^M}.\n", 136 | "$$\n", 137 | "\n", 138 | "The current implementation is somewhat inefficient, but it works." 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 2, 144 | "metadata": {}, 145 | "outputs": [ 146 | { 147 | "name": "stdout", 148 | "output_type": "stream", 149 | "text": [ 150 | "[[ 1.57558022 1.43478008 0.6702228 -0.09855935]\n", 151 | " [ 1.43478008 1.78554993 1.42038122 0.65844447]\n", 152 | " [ 0.6702228 1.42038122 2.73731157 3.69901785]\n", 153 | " [-0.09855935 0.65844447 3.69901785 6.84908404]]\n", 154 | "\n", 155 | "[[ 1.49114428 1.10817646 0.3889397 -0.08632955]\n", 156 | " [ 1.10817646 0.49081177 0.10073799 0.22945985]\n", 157 | " [ 0.3889397 0.10073799 0.09249519 0.22802648]\n", 158 | " [-0.08632955 0.22945985 0.22802648 -0.37394591]]\n" 159 | ] 160 | } 161 | ], 162 | "source": [ 163 | "s = iadpython.Sample(g=0.9, quad_pts=4)\n", 164 | "\n", 165 | "hp, hm = iadpython.hg_legendre(s)\n", 166 | "\n", 167 | "print(hp)\n", 168 | "print()\n", 169 | "print(hm)" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "## Redistribution Matrix using Elliptic Functions\n", 177 | "\n", 178 | "For Henyey-Greenstein scattering, the redistribution function can be expressed\n", 179 | "in terms of the complete elliptic integral of the second kind $E(x)$ \n", 180 | "$$\n", 181 | "h(\\nu_i,\\nu_j) = {2\\over\\pi}{1-g^2\\over (\\alpha-\\gamma)\\sqrt{\\alpha+\\gamma} }\n", 182 | "\t\t\t\t \\,E\\left(\\sqrt{2\\gamma\\over\\alpha+\\gamma}\\,\\right)\n", 183 | "$$\n", 184 | "where $g$ is the average cosine of the Henyey-Greenstein phase function and\n", 185 | "$$\n", 186 | "\\alpha=1+g^2-2 g \\nu_i \\nu_j \n", 187 | "\\qquad\\mbox{and}\\qquad\n", 188 | "\\gamma=2 g \\sqrt{1-\\nu_i^2}\\sqrt{1-\\nu_j^2} \n", 189 | "$$\n", 190 | "The function $E(x)$ may be calculated using `scipy.special.ellipe()`.\n", 191 | "\n", 192 | "The drawback to this approach is that the $\\delta-M$ method cannot be used and therefore it doesn't work well for highly anisotropic phase functions." 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 3, 198 | "metadata": {}, 199 | "outputs": [ 200 | { 201 | "name": "stdout", 202 | "output_type": "stream", 203 | "text": [ 204 | "[[ 1.57558022 1.43478008 0.6702228 -0.09855935]\n", 205 | " [ 1.43478008 1.78554993 1.42038122 0.65844447]\n", 206 | " [ 0.6702228 1.42038122 2.73731157 3.69901785]\n", 207 | " [-0.09855935 0.65844447 3.69901785 6.84908404]]\n", 208 | "\n", 209 | "[[ 1.49114428 1.10817646 0.3889397 -0.08632955]\n", 210 | " [ 1.10817646 0.49081177 0.10073799 0.22945985]\n", 211 | " [ 0.3889397 0.10073799 0.09249519 0.22802648]\n", 212 | " [-0.08632955 0.22945985 0.22802648 -0.37394591]]\n" 213 | ] 214 | } 215 | ], 216 | "source": [ 217 | "s = iadpython.Sample(g=0.9, quad_pts=4)\n", 218 | "\n", 219 | "hpe, hme = iadpython.hg_elliptic(s)\n", 220 | "\n", 221 | "print(hp)\n", 222 | "print()\n", 223 | "print(hm)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "These results match those of the Legendre approach above." 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [] 239 | } 240 | ], 241 | "metadata": { 242 | "kernelspec": { 243 | "display_name": "Python 3 (ipykernel)", 244 | "language": "python", 245 | "name": "python3" 246 | }, 247 | "language_info": { 248 | "codemirror_mode": { 249 | "name": "ipython", 250 | "version": 3 251 | }, 252 | "file_extension": ".py", 253 | "mimetype": "text/x-python", 254 | "name": "python", 255 | "nbconvert_exporter": "python", 256 | "pygments_lexer": "ipython3", 257 | "version": "3.12.3" 258 | } 259 | }, 260 | "nbformat": 4, 261 | "nbformat_minor": 4 262 | } 263 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | # pylint: disable=consider-using-f-string 3 | """ 4 | Configuration file for building documentation. 5 | 6 | Sphinx builds the docs using couple of external modules: napoleon and nbsphinx. 7 | 8 | The overall format is controlled by `.rst` files. The top level file is `index.rst` 9 | 10 | `napoleon` builds the API in HTML assuming that the code is documented with 11 | docstrings that follow the Google docstring format. 12 | 13 | `nbsphinx` convert the Jupyter notebooks to html with nbsphinx, will 14 | """ 15 | 16 | import re 17 | import os.path 18 | 19 | project = 'iadpython' 20 | master_doc = 'index' 21 | 22 | def get_init_property(prop): 23 | """Return property from __init__.py.""" 24 | here = os.path.abspath(os.path.dirname(__file__)) 25 | file_name = os.path.join(here, '..', project, '__init__.py') 26 | regex = r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop) 27 | with open(file_name, 'r', encoding='utf-8') as file: 28 | result = re.search(regex, file.read()) 29 | return result.group(1) 30 | 31 | release = get_init_property("__version__") 32 | author = get_init_property("__author__") 33 | copyright = get_init_property("__copyright__") 34 | 35 | # -- General configuration --------------------------------------------------- 36 | 37 | # Sphinx extension modules 38 | extensions = [ 39 | 'sphinx.ext.napoleon', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.mathjax', 42 | 'sphinx_automodapi.automodapi', 43 | 'nbsphinx', 44 | ] 45 | numpydoc_show_class_members = False 46 | napoleon_use_param = False 47 | napoleon_use_rtype = False 48 | 49 | # List of patterns, relative to source directory, of files to ignore 50 | exclude_patterns = ['_build', 51 | '**.ipynb_checkpoints', 52 | 'IAD-with-spheres.ipynb', 53 | ] 54 | 55 | # I execute the notebooks manually in advance. 56 | nbsphinx_execute = 'never' 57 | nbsphinx_allow_errors = True 58 | 59 | # -- Options for HTML output ------------------------------------------------- 60 | 61 | html_theme = 'sphinx_rtd_theme' 62 | html_scaled_image_link = False 63 | html_sourcelink_suffix = '' 64 | -------------------------------------------------------------------------------- /docs/iadpython.rst: -------------------------------------------------------------------------------- 1 | API for `iadpython` package 2 | =========================== 3 | 4 | .. automodapi:: iadpython.ad 5 | :no-inheritance-diagram: 6 | 7 | .. automodapi:: iadpython.combine 8 | :no-inheritance-diagram: 9 | 10 | .. automodapi:: iadpython.fresnel 11 | :no-inheritance-diagram: 12 | 13 | .. automodapi:: iadpython.grid 14 | :no-inheritance-diagram: 15 | 16 | .. automodapi:: iadpython.iad 17 | :no-inheritance-diagram: 18 | 19 | .. 20 | .. automodapi:: iadpython.iadc 21 | :no-inheritance-diagram: 22 | 23 | .. automodapi:: iadpython.nist 24 | :no-inheritance-diagram: 25 | 26 | .. automodapi:: iadpython.quadrature 27 | :no-inheritance-diagram: 28 | 29 | .. automodapi:: iadpython.redistribution 30 | :no-inheritance-diagram: 31 | 32 | .. automodapi:: iadpython.sphere 33 | :no-inheritance-diagram: 34 | 35 | .. automodapi:: iadpython.start 36 | :no-inheritance-diagram: 37 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | 3 | Example Usage 4 | ============= 5 | .. toctree:: 6 | :caption: Adding-Doubling 7 | :hidden: 8 | 9 | ad-basics 10 | 11 | ad-quadrature 12 | 13 | ad-redistribution 14 | 15 | ad-thin 16 | 17 | ad-sample 18 | 19 | .. toctree:: 20 | :caption: Single Integrating Spheres 21 | :hidden: 22 | 23 | 1sphere-basics.ipynb 24 | 25 | 1sphere-gain-graphs.ipynb 26 | 27 | 1sphere-measurements.ipynb 28 | 29 | 1sphere-examples.ipynb 30 | 31 | 1sphere-gain-derivation.ipynb 32 | 33 | 1sphere-gain-testing.ipynb 34 | 35 | 1sphere-random-points.ipynb 36 | 37 | .. toctree:: 38 | :caption: Double Integrating Spheres 39 | :hidden: 40 | 41 | 2sphere-gain-derivation.ipynb 42 | 43 | 2sphere-gain-testing.ipynb 44 | 45 | .. toctree:: 46 | :caption: Measurements 47 | :hidden: 48 | 49 | measurements 50 | 51 | nist 52 | 53 | impossible 54 | 55 | .. toctree:: 56 | :caption: API 57 | :hidden: 58 | 59 | iadpython 60 | 61 | .. toctree:: 62 | :caption: Versions 63 | :hidden: 64 | 65 | changelog 66 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | scipy 4 | 5 | nbsphinx 6 | sphinx-automodapi 7 | sphinx-rtd-theme==1.3.0 8 | 9 | iadpython -------------------------------------------------------------------------------- /docs/sphere-T.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphere-T.png -------------------------------------------------------------------------------- /docs/sphere-wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphere-wall.png -------------------------------------------------------------------------------- /docs/sphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphere.png -------------------------------------------------------------------------------- /docs/sphere2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphere2.png -------------------------------------------------------------------------------- /docs/sphereMR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphereMR.png -------------------------------------------------------------------------------- /docs/sphereMT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphereMT.png -------------------------------------------------------------------------------- /docs/sphereMT2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphereMT2.png -------------------------------------------------------------------------------- /docs/sphereMT2a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphereMT2a.png -------------------------------------------------------------------------------- /docs/sphereMT2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphereMT2b.png -------------------------------------------------------------------------------- /docs/sphereMT3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/sphereMT3.png -------------------------------------------------------------------------------- /docs/two-sphere-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/docs/two-sphere-basic.png -------------------------------------------------------------------------------- /iadpython/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Forward and inverse adding-doubling radiative transport calculations. 3 | 4 | Extensive documentation is at 5 | 6 | `iadpython` is a pure Python module to do radiative transport calculations 7 | in layered slabs. The forward calculation and the inverse calculation work 8 | fine as long as integrating spheres are not used. That code has yet to be 9 | ported to pure python. 10 | 11 | An example:: 12 | 13 | >>> import iadpython as iad 14 | 15 | >>> mu_s = 10 # scattering coefficient [1/mm] 16 | >>> mu_a = 0.1 # absorption coefficient [1/mm] 17 | >>> g = 0.9 # scattering anisotropy 18 | >>> d = 1 # thickness mm 19 | 20 | >>> a = mu_s/(mu_a+mu_s) 21 | >>> b = mu_s/(mu_a+mu_s) * d 22 | 23 | >>> # air / glass / sample / glass / air 24 | >>> s = iadpython.Sample(a=a, b=b, g=g, n=1.4, n_above=1.5, n_below=1.5) 25 | >>> ur1, ut1, uru, utu = s.rt() 26 | 27 | >>> print('Collimated light incident perpendicular to sample') 28 | >>> print(' total reflection = %.5f' % ur1) 29 | >>> print(' total transmission = %.5f' % ut1) 30 | 31 | >>> print('Diffuse light incident on sample') 32 | >>> print(' total reflection = %.5f' % uru) 33 | >>> print(' total transmission = %.5f' % utu) 34 | """ 35 | __version__ = '0.6.0' 36 | __author__ = 'Scott Prahl' 37 | __email__ = 'scott.prahl@oit.edu' 38 | __copyright__ = '2018-24, Scott Prahl' 39 | __license__ = 'MIT' 40 | __url__ = 'https://github.com/scottprahl/iadpython' 41 | 42 | from .constants import * 43 | from .fresnel import * 44 | from .start import * 45 | from .ad import * 46 | from .quadrature import * 47 | from .combine import * 48 | from .redistribution import * 49 | from .sphere import * 50 | from .nist import * 51 | from .iad import * 52 | from .grid import * 53 | from .rxt import * 54 | from .txt import * 55 | from .port import * 56 | from .double import * 57 | -------------------------------------------------------------------------------- /iadpython/constants.py: -------------------------------------------------------------------------------- 1 | """List of constants used in inverse adding-doubling.""" 2 | 3 | AD_MAX_THICKNESS = 1e6 4 | -------------------------------------------------------------------------------- /iadpython/data/M38597.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottprahl/iadpython/19dd38d22a00b03d3ade5275cad1f52e4b295a72/iadpython/data/M38597.csv -------------------------------------------------------------------------------- /iadpython/grid.py: -------------------------------------------------------------------------------- 1 | """Class for doing inverse adding-doubling calculations for a sample. 2 | 3 | Example:: 4 | 5 | >>> import iadpython 6 | 7 | >>> exp = iadpython.Experiment(0.5, 0.1, default_g=0.5) 8 | >>> grid = iadpython.Grid() 9 | >>> grid.calc(exp) 10 | >>> print(grid) 11 | """ 12 | 13 | import numpy as np 14 | 15 | 16 | class Grid(): 17 | """Class to track pre-calculated R & T values. 18 | 19 | There is a long story associated with these routines. I spent a lot of time 20 | trying to find an empirical function to allow a guess at a starting value for 21 | the inversion routine. Basically nothing worked very well. There were 22 | too many special cases and what not. So I decided to calculate a whole bunch 23 | of reflection and transmission values and keep their associated optical 24 | properties linked nearby. 25 | """ 26 | 27 | def __init__(self, search=None, default=None, N=21): 28 | """Object initialization.""" 29 | self.search = search 30 | self.default = default 31 | self.N = N 32 | self.a = np.zeros((N, N)) 33 | self.b = np.zeros((N, N)) 34 | self.g = np.zeros((N, N)) 35 | self.ur1 = np.zeros((N, N)) 36 | self.ut1 = np.zeros((N, N)) 37 | 38 | def __str__(self): 39 | """Return basic details as a string for printing.""" 40 | s = "---------------- Grid ---------------\n" 41 | if self.search is None: 42 | s += "search = None\n" 43 | else: 44 | s += "search = %s\n" % self.search 45 | 46 | if self.default is None: 47 | s += "default = None\n" 48 | else: 49 | s += "default = %.5f\n" % self.default 50 | 51 | s += matrix_as_string(self.a, "a") 52 | s += matrix_as_string(self.b, "b") 53 | s += matrix_as_string(self.g, "g") 54 | s += matrix_as_string(self.ur1, "ur1") 55 | s += matrix_as_string(self.ut1, "ut1") 56 | 57 | return s 58 | 59 | def calc(self, exp, default=None): 60 | """Precalculate a grid.""" 61 | if default is not None: 62 | self.default = default 63 | 64 | a = np.linspace(0, 1, self.N) 65 | b = np.linspace(0, 10, self.N) 66 | g = np.linspace(-0.99, 0.99, self.N) 67 | 68 | self.search = exp.search 69 | 70 | if self.search == 'find_ab': 71 | self.g = np.full((self.N, self.N), self.default) 72 | self.a, self.b = np.meshgrid(a, b) 73 | 74 | if self.search == 'find_ag': 75 | self.b = np.full((self.N, self.N), self.default) 76 | self.a, self.g = np.meshgrid(a, g) 77 | 78 | if self.search == 'find_bg': 79 | self.a = np.full((self.N, self.N), self.default) 80 | self.b, self.g = np.meshgrid(b, g) 81 | 82 | for i in range(self.N): 83 | for j in range(self.N): 84 | exp.sample.a = self.a[i, j] 85 | exp.sample.b = self.b[i, j] 86 | exp.sample.g = self.g[i, j] 87 | self.ur1[i, j], self.ut1[i, j], _, _ = exp.sample.rt() 88 | 89 | def min_abg(self, mr, mt): 90 | """Find closest a, b, g closest to mr and mt.""" 91 | if self.ur1 is None: 92 | raise ValueError("Grid.calc(exp) must be called before Grid.min_abg") 93 | 94 | A = np.abs(mr - self.ur1) + np.abs(mt - self.ut1) 95 | ii_flat = A.argmin() 96 | i, j = ii_flat // A.shape[1], ii_flat % A.shape[1] 97 | return self.a[i, j], self.b[i, j], self.g[i, j] 98 | 99 | def is_stale(self, default): 100 | """Decide if current grid is still useful.""" 101 | if self.default is None: 102 | return True 103 | if self.default != default: 104 | return True 105 | return False 106 | 107 | 108 | def matrix_as_string(x, label=''): 109 | """Return matrix as a string.""" 110 | if x is None: 111 | return "" 112 | 113 | n, m = x.shape 114 | 115 | ndashes = (80 - len(label) - 2) // 2 116 | s = "\n" 117 | s += '-' * ndashes + ' ' + label + ' ' + '-' * ndashes 118 | s += "\n[\n" 119 | for i in range(n): 120 | s += '[' 121 | for j in range(m): 122 | s += "%6.3f, " % x[i, j] 123 | s += "], \n" 124 | s += "]\n" 125 | return s 126 | -------------------------------------------------------------------------------- /iadpython/nist.py: -------------------------------------------------------------------------------- 1 | """Module for importing reflection spectra from NIST database. 2 | 3 | Example:: 4 | 5 | >>> import numpy as np 6 | >>> import matplotlib.pyplot as plt 7 | >>> import iadpython as iad 8 | 9 | >>> # Retrieve and plot subject 5 10 | 11 | >>> subject_number = 5 12 | >>> lambda0, R = iad.subject_average_reflectance(subject_number) 13 | >>> plt.plot(lambda0, R) 14 | >>> plt.xlabel("Wavelength (nm)") 15 | >>> plt.ylabel("Total Reflectance") 16 | >>> plt.title("Subject #%d" % subject_number) 17 | >>> plt.show() 18 | 19 | >>> # Retrieve and plot all subjects 20 | 21 | >>> lambda0, R = iad.all_average_reflectances() 22 | >>> for i in range(100): 23 | >>> plt.plot(lambda0, R[:, i]) 24 | >>> plt.xlabel("Wavelength (nm)") 25 | >>> plt.ylabel("Total Reflectance") 26 | >>> plt.title("All Subjects") 27 | >>> plt.show() 28 | 29 | Reference: 30 | 31 | nist_db = 'https://doi.org/10.18434/M38597' 32 | """ 33 | 34 | import os 35 | import numpy as np 36 | 37 | __all__ = ('subject_reflectances', 38 | 'subject_average_reflectance', 39 | 'all_average_reflectances', 40 | ) 41 | 42 | 43 | def get_subject_data(cols): 44 | """ 45 | Load and return data from a CSV file based on specified columns. 46 | 47 | This function reads data from a CSV file located in the 'data' directory relative 48 | to the script's location and allows you to specify the columns to extract. 49 | 50 | Args: 51 | cols: A tuple of column indices to extract from the CSV file. 52 | 53 | Returns: 54 | An array containing the requested data. 55 | 56 | Example: 57 | To extract columns 1 and 2 from the CSV file, you can call the function like this: 58 | >>> cols_to_extract = (1, 2) 59 | >>> data = get_subject_data(cols_to_extract) 60 | """ 61 | script_dir = os.path.dirname(__file__) # Path to directory of this file 62 | data_file_path = os.path.join(script_dir, 'data', 'M38597.csv') 63 | data = np.loadtxt(data_file_path, skiprows=8, usecols=cols, delimiter=',', encoding='latin1') 64 | return data 65 | 66 | 67 | def subject_reflectances(subject_number): 68 | """Extract all reflection data for one subject.""" 69 | if subject_number <= 0 or subject_number > 100: 70 | raise ValueError("subject_number must be 1 to 100") 71 | 72 | col = (subject_number - 1) * 4 + 1 73 | 74 | cols = (0, col, col + 1, col + 2, col + 3) 75 | 76 | data = get_subject_data(cols) 77 | 78 | lambda0 = data[:, 0] 79 | r_1 = data[:, 1] 80 | r_2 = data[:, 2] 81 | r_3 = data[:, 3] 82 | r_ave = data[:, 4] 83 | 84 | return lambda0, r_1, r_2, r_3, r_ave 85 | 86 | 87 | def subject_average_reflectance(subject_number): 88 | """Extract average reflection for one subject.""" 89 | if subject_number <= 0 or subject_number > 100: 90 | raise ValueError("subject_number must be 1 to 100") 91 | 92 | col = (subject_number - 1) * 4 + 4 93 | 94 | cols = (0, col) 95 | 96 | data = get_subject_data(cols) 97 | 98 | lambda0 = data[:, 0] 99 | r_ave = data[:, 1] 100 | 101 | return lambda0, r_ave 102 | 103 | 104 | def all_average_reflectances(): 105 | """Extract average reflectance for all subjects.""" 106 | cols = [4 * i for i in range(101)] 107 | 108 | data = get_subject_data(cols) 109 | 110 | lambda0 = data[:, 0] 111 | r_ave = data[:, 1:] 112 | 113 | return lambda0, r_ave 114 | -------------------------------------------------------------------------------- /iadpython/quadrature.py: -------------------------------------------------------------------------------- 1 | """Module for obtaining quadrature points and weights. 2 | 3 | Three types of gaussian quadrature are supported: normal Gaussian, 4 | Radau quadrature, and Lobatto quadrature. The first method does not 5 | include either endpoint of integration, Radau quadrature includes one 6 | endpoint of the integration range, and Lobatto quadrature includes both 7 | endpoints. 8 | 9 | Example:: 10 | 11 | >>> import iad.quadrature 12 | 13 | >>> n=8 14 | >>> x, w = iad.quadrature.gauss(n) 15 | >>> print(" i x weight") 16 | >>> for i,x in enumerate(xi): 17 | >>> print("%2d %+.12f %+.12f" % (i, x[i], w[i])) 18 | 19 | >>> x, w = iad.quadrature.radau(n) 20 | >>> print(" i x weight") 21 | >>> for i,x in enumerate(xi): 22 | >>> print("%2d %+.12f %+.12f" % (i, x[i], w[i])) 23 | 24 | >>> x, w = iad.quadrature.lobatto(n) 25 | >>> print(" i x weight") 26 | >>> for i,x in enumerate(xi): 27 | >>> print("%2d %+.12f %+.12f" % (i, x[i], w[i])) 28 | 29 | """ 30 | 31 | import functools 32 | import scipy.special 33 | import scipy.optimize 34 | import numpy as np 35 | 36 | __all__ = ('gauss', 37 | 'radau', 38 | 'lobatto', 39 | ) 40 | 41 | 42 | def _gauss_func(n, x): 43 | """Zeroes of this function are the Gaussian quadrature points.""" 44 | return scipy.special.legendre(n)(x) 45 | 46 | 47 | def _radau_func(n, x): 48 | """Zeros of this function are the Radau quadrature points.""" 49 | return (scipy.special.eval_legendre(n - 1, x) + scipy.special.eval_legendre(n, x)) / (1 + x) 50 | 51 | 52 | def _lobatto_func(n, x): 53 | """Zeros of this function are the Lobatto quadrature points.""" 54 | return scipy.special.legendre(n - 1).deriv(1)(x) 55 | 56 | 57 | def gauss(n, a=-1, b=1): 58 | """Return abscissas and weights for Gaussian quadrature. 59 | 60 | The definite integral ranges from a to b. The default 61 | interval is -1 to 1. The quadrature approximation is 62 | just the sum of :math:`w_i f(x_i)`. Neither a nor b is included 63 | in the list of quadrature abscissas. 64 | 65 | The result should be exact when integrating any polynomial 66 | of degree 2n-1 or less. 67 | 68 | If -a=b, then abscissas will be symmetric about the origin 69 | 70 | Args: 71 | n: number of quadrature points 72 | a: lower limit of integral 73 | b: upper limit of integral 74 | Returns: 75 | x: array of abscissas of length n 76 | w: array of weights of length n 77 | """ 78 | x, w, _ = scipy.special.roots_legendre(n, mu=True) 79 | 80 | # scale for desired interval 81 | x *= 0.5 * (a - b) 82 | x += 0.5 * (a + b) 83 | w *= 0.5 * (b - a) 84 | 85 | return np.flip(x), np.flip(w) 86 | 87 | 88 | def radau(n, a=-1, b=1): 89 | """Return abscissas and weights for Radau quadrature. 90 | 91 | The definite integral ranges from a to b. The default 92 | interval is -1 to 1. The quadrature approximation is 93 | just the sum of :math:`w_i f(x_i)`. The upper endpoint b is include 94 | in the list of quadrature abscissas. 95 | 96 | The result should be exact when integrating any polynomial 97 | of degree 2n-2 or less. 98 | 99 | Args: 100 | n: number of quadrature points 101 | a: lower limit of integral 102 | b: upper limit of integral 103 | Returns: 104 | x: array of abscissas of length n 105 | w: array of weights of length n 106 | """ 107 | x = np.zeros(n) 108 | w = np.zeros(n) 109 | x[0] = -1 110 | 111 | # the roots of P_{n} bracket the roots of P_{n}': 112 | brackets = scipy.special.roots_legendre(n)[0] 113 | 114 | f = functools.partial(_radau_func, n) 115 | for i in range(n - 1): 116 | x[i + 1] = scipy.optimize.brentq(f, brackets[i], brackets[i + 1]) 117 | 118 | pp = scipy.special.legendre(n - 1).deriv(1) 119 | w[0] = 2 / n**2 120 | w[1:] = 1 / pp(x[1:])**2 / (1 - x[1:]) 121 | 122 | # scale for desired interval 123 | x *= 0.5 * (a - b) 124 | x += 0.5 * (b + a) 125 | w *= 0.5 * (b - a) 126 | 127 | return np.flip(x), np.flip(w) 128 | 129 | 130 | def lobatto(n, a=-1, b=1): 131 | """Return abscissas and weights for Lobatto quadrature. 132 | 133 | The definite integral ranges from a to b. The default 134 | interval is -1 to 1. The quadrature approximation is 135 | just the sum of w_i f(x_i). Both endpoints a and b are include 136 | in the list of quadrature abscissas. 137 | 138 | The result should be exact when integrating any polynomial 139 | of degree 2n-3 or less. 140 | 141 | If -a=b, then abscissas will be symmetric about the origin 142 | 143 | Args: 144 | n: number of quadrature points 145 | a: lower limit of integral 146 | b: upper limit of integral 147 | Returns: 148 | x: array of abscissas of length n 149 | w: array of weights of length n 150 | """ 151 | x = np.zeros(n) 152 | w = np.full(n, 2 / n / (n - 1)) 153 | x[0] = -1 154 | x[-1] = 1 155 | 156 | # The roots of P_{n-1} bracket the roots of P_{n-1}': 157 | brackets = scipy.special.roots_legendre(n - 1)[0] 158 | 159 | f = functools.partial(_lobatto_func, n) 160 | for i in range(n - 2): 161 | x[i + 1] = scipy.optimize.brentq(f, brackets[i], brackets[i + 1]) 162 | 163 | pp = scipy.special.legendre(n - 1)(x) 164 | w[1:-1] = w[1:-1] / pp[1:-1]**2 165 | 166 | # scale for desired interval 167 | x *= 0.5 * (a - b) 168 | x += 0.5 * (a + b) 169 | w *= 0.5 * (b - a) 170 | 171 | return np.flip(x), np.flip(w) 172 | -------------------------------------------------------------------------------- /iadpython/redistribution.py: -------------------------------------------------------------------------------- 1 | r"""Module for calculating the redistribution function. 2 | 3 | The single scattering phase function ..math:`p(\nu)` for a tissue determines the 4 | amount of light scattered at an angle nu=cos(theta) from the direction of 5 | incidence. The subtended angle nu is the dot product incident and exiting 6 | of the unit vectors. 7 | 8 | The redistribution function ..math:`h[i,j]` determines the fraction of light 9 | scattered from an incidence cone with angle `\nu_i` into a cone with angle 10 | ..math:`\nu_j`. The redistribution function is calculated by averaging the phase 11 | function over all possible azimuthal angles for fixed angles ..math:`\nu_i` and 12 | ..math:`nu_j`, 13 | 14 | Note that the angles ..math:`\nu_i` and ..math:`\nu_j` may also be negative (light 15 | travelling in the opposite direction). 16 | 17 | When the cosine of the angle of incidence or exitance is unity (..math:`\nu_i=1` or 18 | ..math:`\nu_j=1`), then the redistribution function is equivalent to the phase 19 | function ..math:`p(\nu_j)`. 20 | """ 21 | 22 | import scipy.special 23 | import numpy as np 24 | import numpy.polynomial.legendre 25 | 26 | __all__ = ('hg_elliptic', 27 | 'hg_legendre', 28 | ) 29 | 30 | 31 | def hg_legendre(sample): 32 | """Calculate the HG redistribution matrix using Legendre polynomials. 33 | 34 | This is a straightforward implementation of Wiscombe's delta-M 35 | method for calculating the redistribution function for a Henyey- 36 | Greenstein phase function. 37 | 38 | Probably should generate all the Legendre polynomials one 39 | time and then calculate. 40 | 41 | Reference: 42 | Wiscombe, "The Delta-M Method : Rapid Yet Accurate Radiative Flux 43 | Calculations for Strongly Asymmetric Phase Functions," 44 | J. Atmos. Sci., 34, 1978. 45 | """ 46 | if sample.nu is None: 47 | sample.update_quadrature() 48 | 49 | n = sample.quad_pts 50 | g = sample.g 51 | 52 | if g == 0: 53 | h = np.ones([n, n]) 54 | return h, h 55 | 56 | hp = np.ones([n, n]) 57 | hm = np.ones([n, n]) 58 | for k in range(1, n): 59 | c = np.append(np.zeros(k), [1]) 60 | chik = (2 * k + 1) * (g**k - g**n) / (1 - g**n) 61 | pk = numpy.polynomial.legendre.legval(sample.nu, c) 62 | for i in range(n): 63 | for j in range(i + 1): 64 | temp = chik * pk[i] * pk[j] 65 | hp[i, j] += temp 66 | hm[i, j] += (-1)**k * temp 67 | 68 | # fill symmetric entries 69 | for i in range(n): 70 | for j in range(i + 1, n): 71 | hp[i, j] = hp[j, i] 72 | hm[i, j] = hm[j, i] 73 | 74 | return hp, hm 75 | 76 | 77 | def hg_elliptic(sample): 78 | """Calculate redistribution function using elliptic integrals. 79 | 80 | This is the result of a direct integration of the Henyey- 81 | Greenstein phase function. 82 | 83 | It is not terribly useful because we cannot use the 84 | delta-M method to more accurate model highly anisotropic 85 | phase functions. 86 | """ 87 | if sample.nu is None: 88 | sample.update_quadrature() 89 | 90 | n = sample.quad_pts 91 | g = sample.g**n 92 | if g == 0: 93 | h = np.ones([n, n]) 94 | return h, h 95 | 96 | hp = np.zeros([n, n]) 97 | hm = np.zeros([n, n]) 98 | for i in range(n): 99 | for j in range(i + 1): 100 | ni = sample.nu[i] 101 | nj = sample.nu[j] 102 | gamma = 2 * g * np.sqrt(1 - ni**2) * np.sqrt(1 - nj**2) 103 | 104 | alpha = 1 + g**2 - 2 * g * ni * nj 105 | const = 2 / np.pi * (1 - g**2) / (alpha - gamma) / np.sqrt(alpha + gamma) 106 | arg = np.sqrt(2 * gamma / (alpha + gamma)) 107 | hp[i, j] = const * scipy.special.ellipe(arg) 108 | 109 | alpha = 1 + g**2 + 2 * g * ni * nj 110 | const = 2 / np.pi * (1 - g**2) / (alpha - gamma) / np.sqrt(alpha + gamma) 111 | arg = np.sqrt(2 * gamma / (alpha + gamma)) 112 | hm[i, j] = const * scipy.special.ellipe(arg) 113 | 114 | # fill symmetric entries 115 | for i in range(n): 116 | for j in range(i + 1, n): 117 | hp[i, j] = hp[j, i] 118 | hm[i, j] = hm[j, i] 119 | 120 | return hp, hm 121 | -------------------------------------------------------------------------------- /iadpython/rxt.py: -------------------------------------------------------------------------------- 1 | """Module for reading rxt files. 2 | 3 | This reads an input rxt file and saves the parameters into an 4 | Experiment object. 5 | 6 | Example:: 7 | 8 | >>> import numpy 9 | >>> import matplotlib.pyplot as plt 10 | >>> import iadpython 11 | >>> 12 | >>> filename = 'ink.rxt' 13 | >>> exp = iadpython.read_rxt(filename) 14 | >>> if exp.lambda0 is None: 15 | >>> plt.plot(exp.m_r) 16 | >>> else: 17 | >>> plt.plot(exp.lambda0, exp.m_r) 18 | >>> plt.ylabel("measured reflectance") 19 | >>> plt.title(filename) 20 | >>> plt.show() 21 | """ 22 | 23 | import os 24 | import re 25 | import numpy as np 26 | import iadpython 27 | 28 | __all__ = ('read_rxt', 'read_and_remove_notation') 29 | 30 | 31 | def read_and_remove_notation(filename): 32 | """Read file and remove all whitespace and comments.""" 33 | s = '' 34 | 35 | if not os.path.exists(filename): 36 | raise ValueError('input file "%s" must end in ".rxt"' % filename) 37 | 38 | with open(filename, encoding="utf-8") as f: 39 | for line in f: 40 | line = re.sub(r'\s*#.*', '', line) 41 | line = re.sub(r', ', ' ', line) 42 | s += line 43 | 44 | if len(re.findall('IAD1', s)) == 0: 45 | raise ValueError("Not an .rxt file. (Does not start with IAD1)") 46 | 47 | s = re.sub(r'IAD1', '', s) 48 | s = re.sub(r'\s+', ' ', s) 49 | s = s.rstrip() 50 | s = s.lstrip() 51 | return s 52 | 53 | 54 | def fill_in_data_fixed(exp, data_in_columns): 55 | """Read data the old way by fixed column position.""" 56 | col = 0 57 | if data_in_columns[0, 0] > 1: 58 | exp.lambda0 = data_in_columns[:, 0] 59 | col = 1 60 | exp.m_r = data_in_columns[:, col] 61 | if exp.num_measures >= 2: 62 | exp.m_t = data_in_columns[:, col + 1] 63 | if exp.num_measures >= 3: 64 | exp.m_u = data_in_columns[:, col + 2] 65 | if exp.num_measures >= 4: 66 | exp.r_sphere.r_wall = data_in_columns[:, col + 3] 67 | if exp.num_measures >= 5: 68 | exp.t_sphere.r_wall = data_in_columns[:, col + 4] 69 | if exp.num_measures >= 6: 70 | exp.r_sphere.r_std = data_in_columns[:, col + 5] 71 | if exp.num_measures >= 7: 72 | exp.t_sphere.r_std = data_in_columns[:, col + 6] 73 | if exp.num_measures > 7: 74 | raise ValueError('unimplemented') 75 | 76 | 77 | def fill_in_data_variable(exp, data_in_columns, column_letters_str): 78 | """Read data and interpret according to column_letters_str.""" 79 | for col, letter in enumerate(column_letters_str): 80 | if letter == 'a': 81 | exp.default_a = data_in_columns[:, col] 82 | elif letter == 'b': 83 | exp.default_b = data_in_columns[:, col] 84 | elif letter == 'B': 85 | exp.d_beam = data_in_columns[:, col] 86 | elif letter == 'c': 87 | exp.fraction_of_rc_in_mr = data_in_columns[:, col] 88 | elif letter == 'C': 89 | exp.fraction_of_tc_in_mt = data_in_columns[:, col] 90 | elif letter == 'd': 91 | exp.sample.d = data_in_columns[:, col] 92 | elif letter == 'D': 93 | exp.sample.d_above = data_in_columns[:, col] 94 | exp.sample.d_below = data_in_columns[:, col] 95 | elif letter == 'E': 96 | exp.sample.b_above = data_in_columns[:, col] 97 | exp.sample.b_below = data_in_columns[:, col] 98 | elif letter == 'e': 99 | exp.error = data_in_columns[:, col] 100 | elif letter == 'g': 101 | exp.default_g = data_in_columns[:, col] 102 | elif letter == 't': 103 | exp.m_t = data_in_columns[:, col] 104 | elif letter == 'L': 105 | exp.lambda0 = data_in_columns[:, col] 106 | elif letter == 'n': 107 | exp.n = data_in_columns[:, col] 108 | elif letter == 'N': 109 | exp.n_above = data_in_columns[:, col] 110 | exp.n_below = data_in_columns[:, col] 111 | elif letter == 'r': 112 | exp.m_r = data_in_columns[:, col] 113 | elif letter == 'R': 114 | exp.r_sphere.r_std = data_in_columns[:, col] 115 | elif letter == 'S': 116 | exp.num_spheres = data_in_columns[:, col] 117 | elif letter == 'T': 118 | exp.t_sphere.r_rstd = data_in_columns[:, col] 119 | elif letter == 'u': 120 | exp.m_u = data_in_columns[:, col] 121 | elif letter == 'w': 122 | exp.r_sphere.r_wall = data_in_columns[:, col] 123 | elif letter == 'W': 124 | exp.t_sphere.r_wall = data_in_columns[:, col] 125 | else: 126 | raise ValueError('unimplemented column type "%s"' % letter) 127 | 128 | 129 | def read_rxt(filename): 130 | """Read an IAD input file in .rxt format. 131 | 132 | Args: 133 | filename: .rxt filename 134 | 135 | Returns: 136 | Experiment object 137 | """ 138 | s = read_and_remove_notation(filename) 139 | x = s.split(' ') 140 | 141 | # Remove single-letter entries and save them 142 | column_letters = [item for item in x if len(item) == 1 and item.isalpha()] 143 | x = [item for item in x if not (len(item) == 1 and item.isalpha())] 144 | column_letters_str = ''.join(column_letters) 145 | 146 | x = np.array([float(item) for item in x]) 147 | 148 | sample = iadpython.Sample(a=None, b=None, g=None) 149 | sample.n = x[0] 150 | sample.n_above = x[1] 151 | sample.d = x[2] 152 | sample.d_above = x[3] 153 | 154 | # try and save people from themselves 155 | if sample.d_above == 0: 156 | sample.n_above = 1 157 | if sample.n_above == 1: 158 | sample.d_above = 0 159 | if sample.n_above == 0: 160 | sample.n_above = 1 161 | sample.d_above = 0 162 | sample.d_below = sample.d_above 163 | sample.n_below = sample.n_above 164 | exp = iadpython.Experiment(sample=sample) 165 | 166 | exp.d_beam = x[4] 167 | exp.rstd_r = x[5] 168 | exp.num_spheres = x[6] 169 | exp.method = 'substitution' 170 | 171 | if exp.num_spheres > 0: 172 | exp.r_sphere = iadpython.Sphere(x[7], x[8], x[9], x[10], 0, x[11]) 173 | 174 | if exp.num_spheres > 0: 175 | exp.t_sphere = iadpython.Sphere(x[12], x[13], x[14], x[15], 0, x[16]) 176 | 177 | # Read data 178 | if column_letters_str == '': 179 | # old style data format 180 | exp.num_measures = x[17] 181 | data = x[18:] 182 | columns = int(exp.num_measures) 183 | if data[0] > 1: 184 | columns += 1 185 | data_in_columns = data.reshape(-1, columns) 186 | fill_in_data_fixed(exp, data_in_columns) 187 | else: 188 | # new style variable header format 189 | exp.num_measures = 0 190 | if 'r' in column_letters_str: 191 | exp.num_measures += 1 192 | if 't' in column_letters_str: 193 | exp.num_measures += 1 194 | if 'u' in column_letters_str: 195 | exp.num_measures += 1 196 | data = x[17:] 197 | columns = len(column_letters_str) 198 | data_in_columns = data.reshape(-1, columns) 199 | fill_in_data_variable(exp, data_in_columns, column_letters_str) 200 | 201 | return exp 202 | -------------------------------------------------------------------------------- /iadpython/txt.py: -------------------------------------------------------------------------------- 1 | """Module for reading rxt files. 2 | 3 | This reads an output txt file and saves the parameters into an 4 | Experiment object. 5 | 6 | Example:: 7 | 8 | >>> import numpy 9 | >>> import matplotlib.pyplot as plt 10 | >>> import iadpython 11 | >>> 12 | >>> filename = 'ink.txt' 13 | >>> exp, data = iadpython.read_txt(filename) 14 | >>> plt.plot(data.lam, data.mr) 15 | >>> plt.plot(data.lam, data.rr) 16 | >>> plt.xlabel("Wavelength") 17 | >>> plt.ylabel("measured reflectance") 18 | >>> plt.title(filename) 19 | >>> plt.show() 20 | """ 21 | 22 | import os 23 | import re 24 | import numpy as np 25 | import iadpython 26 | 27 | __all__ = ('read_txt', 'IADResult') 28 | 29 | 30 | class IADResult(): 31 | """Container class results in an iad output file.""" 32 | def __init__(self): 33 | """Initialization.""" 34 | self.lam = np.array([0], dtype=float) 35 | self.mr = np.array([0], dtype=float) 36 | self.cr = np.array([0], dtype=float) 37 | self.mt = np.array([0], dtype=float) 38 | self.ct = np.array([0], dtype=float) 39 | self.mua = np.array([0], dtype=float) 40 | self.musp = np.array([0], dtype=float) 41 | self.g = np.array([0], dtype=float) 42 | self.success = np.array([0], dtype=bool) 43 | self.mus = np.array([0], dtype=float) 44 | 45 | 46 | def verify_magic(fp, magic): 47 | """Verify that the file's initial bytes match the string 'magic'.""" 48 | fp.seek(0) 49 | chunk = fp.read(len(magic)) 50 | fp.seek(0) 51 | return chunk == magic 52 | 53 | 54 | def get_number_from_line(fp): 55 | """Read the number on the next line after an = sign.""" 56 | s = fp.readline() 57 | # print("starting=%s" % s) 58 | if s[0] != '#': 59 | print("line in file should start with `#`") 60 | return 0 61 | 62 | s = re.sub(r'.*= *', '', s) 63 | s = re.sub(r' .*', '', s) 64 | 65 | # print("finish x=%10.5f" % float(s)) 66 | return float(s) 67 | 68 | 69 | def read_sphere(fp): 70 | """Read the information for a sphere.""" 71 | line = fp.readline() 72 | d_sphere = get_number_from_line(fp) 73 | d_sample = get_number_from_line(fp) 74 | sphere = iadpython.Sphere(d_sphere, d_sample) 75 | sphere.baffle = 'a baffle' in line 76 | sphere.third.d = get_number_from_line(fp) 77 | sphere.detector.d = get_number_from_line(fp) 78 | sphere.detector.uru = get_number_from_line(fp) / 100 79 | sphere.r_wall = get_number_from_line(fp) / 100 80 | sphere.r_std = get_number_from_line(fp) / 100 81 | return sphere 82 | 83 | 84 | def read_misc(fp, _exp): 85 | """Read info after sphere data but before data.""" 86 | for _ in range(14): 87 | fp.readline() 88 | 89 | 90 | def read_txt(filename): 91 | """Read an IAD output file in .rxt format.""" 92 | if not os.path.exists(filename): 93 | raise ValueError('input file "%s" must end in ".txt"' % filename) 94 | 95 | # verify that file is an output file 96 | with open(filename, encoding="utf-8") as fp: 97 | if not verify_magic(fp, '# Inverse Adding-Doubling'): 98 | raise ValueError('"%s" does not start with "# Inverse Adding-Doubling"' % filename) 99 | 100 | # create experiment object 101 | exp = iadpython.Experiment() 102 | 103 | # now read the header 104 | fp.readline() 105 | fp.readline() 106 | exp.d_beam = get_number_from_line(fp) 107 | exp.sample.d = get_number_from_line(fp) 108 | exp.sample.d_above = get_number_from_line(fp) 109 | exp.sample.d_below = get_number_from_line(fp) 110 | exp.sample.n = get_number_from_line(fp) 111 | exp.sample.n_above = get_number_from_line(fp) 112 | exp.sample.n_below = get_number_from_line(fp) 113 | fp.readline() 114 | exp.fraction_of_rc_in_mr = get_number_from_line(fp) / 100 115 | exp.fraction_of_tc_in_mt = get_number_from_line(fp) / 100 116 | fp.readline() 117 | exp.r_sphere = read_sphere(fp) 118 | fp.readline() 119 | exp.t_sphere = read_sphere(fp) 120 | read_misc(fp, exp) 121 | 122 | data = IADResult() 123 | position = fp.tell() 124 | 125 | result = np.loadtxt(fp, usecols=range(8), delimiter='\t') 126 | lam, mr, cr, mt, ct, mua, musp, g = result.T 127 | data.lam = np.atleast_1d(lam) 128 | data.mr = np.atleast_1d(mr) 129 | data.cr = np.atleast_1d(cr) 130 | data.mt = np.atleast_1d(mt) 131 | data.ct = np.atleast_1d(ct) 132 | data.mua = np.atleast_1d(mua) 133 | data.musp = np.atleast_1d(musp) 134 | data.g = np.atleast_1d(g) 135 | data.mus = data.musp / (1 - g) 136 | exp.m_r = data.mr 137 | exp.m_t = data.mt 138 | exp.lambda0 = data.lam 139 | 140 | fp.seek(position) 141 | converters = {8: lambda s: s.lstrip(b'#').strip()} 142 | status = np.loadtxt(fp, usecols=[8], dtype=str, converters=converters) 143 | status = np.atleast_1d(status) 144 | data.success = status == '*' 145 | 146 | return exp, data 147 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.ruff] 6 | line-length = 150 7 | exclude =['.git', '__pycache__', 'docs'] 8 | 9 | [tool.ruff.lint] 10 | select = ["D", "E", "F", "W"] 11 | ignore = ["D212", "F403"] 12 | pydocstyle.convention = "google" 13 | 14 | [tool.ruff.lint.per-file-ignores] 15 | # Ignore `E402` Module level import not at top of file 16 | "tests/test_iadcommand.py" = ["E402"] 17 | 18 | [tool.check-manifest] 19 | ignore = [ 20 | ".readthedocs.yaml", 21 | "docs/*", 22 | "Makefile", 23 | "release.txt", 24 | "requirements.txt", 25 | "requirements-dev.txt", 26 | "todo", 27 | ] 28 | 29 | [tool.pytest.ini_options] 30 | markers = ["notebooks"] 31 | 32 | [tool.pylint] 33 | disable = [ 34 | "invalid-name", 35 | "line-too-long", 36 | "too-many-locals", 37 | "too-many-arguments", 38 | "consider-using-f-string", 39 | "too-many-instance-attributes", 40 | "too-many-public-methods", 41 | "too-many-branches", 42 | "too-few-public-methods", 43 | ] 44 | -------------------------------------------------------------------------------- /release.txt: -------------------------------------------------------------------------------- 1 | A checklist for releasing a new version 2 | 3 | # sanity test the release 4 | make rcheck 5 | 6 | # update the version in setup.cfg and docs/conf.py 7 | git commit iadpython/__init__.py -m 'update version' 8 | git push 9 | 10 | # update CHANGELOG 11 | git shortlog v0.3.0..HEAD 12 | git commit CHANGELOG.rst -m 'update recent changes' 13 | git push 14 | 15 | # create release tag on github 16 | git tag v0.5.1 17 | git push origin v0.5.1 18 | 19 | # upload source to pypi 20 | python3 -m build 21 | python3 -m twine upload dist/* 22 | 23 | # update/check releases 24 | open https://github.com/scottprahl/iadpython 25 | open https://pypi.org/project/iadpython/ 26 | open https://iadpython.readthedocs.io 27 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | # 3 | # Test 4 | pytest 5 | # 6 | # Testing notebooks 7 | numpy 8 | matplotlib 9 | scipy 10 | nbconvert 11 | nbformat 12 | # 13 | # Lint 14 | rstcheck 15 | pylint 16 | pydocstyle 17 | # 18 | # Docs 19 | notebook 20 | # 21 | # Release 22 | check-manifest 23 | pyroma -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | scipy 4 | pytest 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description='Forward and inverse radiative transport using adding-doubling' 3 | 4 | keywords= 5 | absorption 6 | scattering 7 | reflection 8 | transmission 9 | optical properties 10 | radiative transport 11 | optical properties 12 | scattering coefficient 13 | scattering anisotropy 14 | 15 | classifiers= 16 | Development Status :: 3 - Alpha 17 | License :: OSI Approved :: MIT License 18 | Intended Audience :: Science/Research 19 | Programming Language :: Python 20 | Topic :: Scientific/Engineering :: Physics 21 | Programming Language :: Python :: 3 22 | Programming Language :: Python :: 3.7 23 | Programming Language :: Python :: 3.8 24 | Programming Language :: Python :: 3.9 25 | Programming Language :: Python :: 3.10 26 | Programming Language :: Python :: 3.11 27 | Programming Language :: Python :: 3.12 28 | 29 | include_package_data = True 30 | package_data = {'' : ['data/*.csv']} 31 | 32 | 33 | [options] 34 | packages = iadpython 35 | install_requires = 36 | numpy 37 | 38 | python_requires = >=3.7 39 | zip_safe = True 40 | 41 | [tool:pytest] 42 | norecursedirs = tests_iadc 43 | 44 | [flake8] 45 | ignore = W503, D212, D301, N802, N803, N806, E501 46 | per-file-ignores = 47 | __init__.py: F401, F403 48 | setup.py: D100 49 | tests/test_iadcommand.py: E402 50 | exclude = 51 | .git, 52 | __pycache__, 53 | docs, 54 | iadpython/.ipynb_checkpoints 55 | max-line-length = 99 56 | docstring-convention = google 57 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Script to setup iadpython module.""" 2 | import re 3 | import os.path 4 | from setuptools import setup 5 | 6 | project = 'iadpython' 7 | 8 | 9 | def get_init_property(prop): 10 | """Return property from __init__.py.""" 11 | here = os.path.abspath(os.path.dirname(__file__)) 12 | file_name = os.path.join(here, project, '__init__.py') 13 | regex = r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop) 14 | with open(file_name, 'r', encoding='utf-8') as file: 15 | result = re.search(regex, file.read()) 16 | return result.group(1) 17 | 18 | 19 | def get_contents(filename): 20 | """Return contents of filename relative to the location of this file.""" 21 | here = os.path.abspath(os.path.dirname(__file__)) 22 | fn = os.path.join(here, filename) 23 | with open(fn, 'r', encoding='utf-8') as f: 24 | contents = f.read() 25 | return contents 26 | 27 | 28 | setup(name=project, 29 | long_description=get_contents('README.rst'), 30 | long_description_content_type='text/x-rst', 31 | version=get_init_property('__version__'), 32 | author=get_init_property('__author__'), 33 | author_email=get_init_property('__email__'), 34 | license=get_init_property('__license__'), 35 | url=get_init_property('__url__') 36 | ) 37 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Configuration for pytest.""" 2 | import pytest 3 | 4 | 5 | def pytest_addoption(parser): 6 | """Add options to pytest.""" 7 | parser.addoption( 8 | "--runbinary", action="store_true", default=False, 9 | help="run iad lib tests" 10 | ) 11 | parser.addoption( 12 | "--notebooks", action="store_true", default=False, dest="notebooks", 13 | help="test notebooks by running them" 14 | ) 15 | parser.addoption( 16 | "--skip", action="store_true", default=False, 17 | help="run tests marked by @skip" 18 | ) 19 | 20 | 21 | def pytest_configure(config): 22 | """Configuration details.""" 23 | config.addinivalue_line("markers", "iadc: mark test as needing libiad") 24 | config.addinivalue_line("markers", "notebooks: mark test as needing notebooks") 25 | 26 | 27 | def pytest_collection_modifyitems(config, items): 28 | """Manage using iadc or testing notebooks.""" 29 | # Skip items marked with `iadc` by default 30 | if not config.getoption("--runbinary"): 31 | skip_binary = pytest.mark.skip(reason="need --runbinary option to run") 32 | for item in items: 33 | if "iadc" in item.keywords: 34 | item.add_marker(skip_binary) 35 | 36 | # Skip items marked with `notebooks` by default 37 | if not config.getoption("--notebooks"): 38 | skip_notebooks = pytest.mark.skip(reason="--notebooks option not used") 39 | for item in items: 40 | if "notebooks" in item.keywords: 41 | item.add_marker(skip_notebooks) 42 | -------------------------------------------------------------------------------- /tests/data/basic-A.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Basic test of two measurement functionality 4 | # These are accurate values for total reflectance transmittance 5 | # for a sample between glass sides for g=0 and various mus and mua 6 | 7 | 1.40 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 0.1 # [mm] Diameter of illumination beam 12 | 1.00 # Reflectivity of the reflectance calibration standard 13 | 14 | 0 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere (unused) 17 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 18 | 25.4 # [mm] Sample Port Diameter 19 | 12.7 # [mm] Entrance Port Diameter 20 | 1.00 # [mm] Detector Port Diameter 21 | 0.96 # Reflectivity of the sphere wall 22 | 23 | # Properties of sphere (unused) 24 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 25 | 25.4 # [mm] Sample Port Diameter 26 | 12.7 # [mm] Entrance Port Diameter 27 | 1.00 # [mm] Detector Port Diameter 28 | 0.96 # Reflectivity of the sphere wall 29 | 30 | 1 # Number of measurements, M_R, M_T 31 | 32 | #M_R #mua mus g 33 | 0.2992621379268361 #0.1 1.0 0.0 34 | 0.1722475379306846 #0.3 1.0 0.0 35 | 0.1308339701159747 #0.5 1.0 0.0 36 | 0.1096241197070925 #0.7 1.0 0.0 37 | 0.0966223181570366 #0.9 1.0 0.0 -------------------------------------------------------------------------------- /tests/data/basic-A.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad basic-A.rxt 3 | # Beam diameter = 0.1 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.4000 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector (ignored since no spheres used) 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 100.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector (ignored since no spheres used) 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # Just M_R was measured using the substitution (single-beam) method. 33 | # No sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 1 0.2993 0.2993 0.0000 0.0000 0.1008 1.0000 0.0000 * 46 | 2 0.1722 0.1722 0.0000 0.0000 0.3022 1.0000 0.0000 * 47 | 3 0.1308 0.1308 0.0000 0.0000 0.5036 1.0000 0.0000 * 48 | 4 0.1096 0.1096 0.0000 0.0000 0.7049 1.0000 0.0000 * 49 | 5 0.0966 0.0966 0.0000 0.0000 0.9062 1.0000 0.0000 * 50 | -------------------------------------------------------------------------------- /tests/data/basic-B.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Basic test of two measurement functionality 4 | # These are accurate values for total reflectance transmittance 5 | # for a sample between glass sides for g=0 and various mus and mua 6 | 7 | 1.40 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 0.1 # [mm] Diameter of illumination beam 12 | 1.00 # Reflectivity of the reflectance calibration standard 13 | 14 | 0 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere (unused) 17 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 18 | 25.4 # [mm] Sample Port Diameter 19 | 12.7 # [mm] Entrance Port Diameter 20 | 1.00 # [mm] Detector Port Diameter 21 | 0.96 # Reflectivity of the sphere wall 22 | 23 | # Properties of sphere (unused) 24 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 25 | 25.4 # [mm] Sample Port Diameter 26 | 12.7 # [mm] Entrance Port Diameter 27 | 1.00 # [mm] Detector Port Diameter 28 | 0.96 # Reflectivity of the sphere wall 29 | 30 | 2 # Number of measurements, M_R, M_T 31 | 32 | #M_R M_T #mus mua 33 | 0.5148573444505178 0.1959644571679601 #5 0.1 34 | 0.3701321358705934 0.0929664649724017 #5 0.3 35 | 0.2989763800085638 0.0527160912635514 #5 0.5 36 | 0.2552899905384752 0.0328462518016609 #5 0.7 37 | 0.2252300672926150 0.0217065281727672 #5 0.9 38 | 0.6248524802699547 0.1004121032354037 #10 0.1 39 | 0.4758274179187398 0.0350162179837752 #10 0.3 40 | 0.3990923511998981 0.0156151638867672 #10 0.5 41 | 0.3494510599841692 0.0079233635594288 #10 0.7 42 | 0.3137489625708088 0.0043656791062148 #10 0.9 43 | 0.6808399492043953 0.0607372578398821 #15 0.1 44 | 0.5367154510994456 0.0166095575040650 #15 0.3 45 | 0.4603973141034097 0.0061861181973436 #15 0.5 46 | 0.4095191467663043 0.0026977016460068 #15 0.7 47 | 0.3720173012161364 0.0013001215705761 #15 0.9 48 | 0.7162010972059861 0.0400597491902211 #20 0.1 49 | 0.5783489647316922 0.0088683569489287 #20 0.3 50 | 0.5037690166262928 0.0028359057692700 #20 0.5 51 | 0.4530217023633932 0.0010894765111262 #20 0.7 52 | 0.4149999553613176 0.0004697553575306 #20 0.9 53 | 0.7412266710066386 0.0279131918705861 #25 0.1 54 | 0.6094283922288846 0.0051087135687198 #25 0.3 55 | 0.5368738851657168 0.0014268811835740 #25 0.5 56 | 0.4867550342227706 0.0004901835041755 #25 0.7 57 | 0.4487541006177004 0.0001916681753960 #25 0.9 -------------------------------------------------------------------------------- /tests/data/basic-B.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad basic-B.rxt 3 | # Beam diameter = 0.1 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.4000 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector (ignored since no spheres used) 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 100.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector (ignored since no spheres used) 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R and M_T were measured using the substitution (single-beam) method. 33 | # No sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 1 0.5149 0.5148 0.1960 0.1960 0.1006 4.9836 0.0000 * 46 | 2 0.3701 0.3701 0.0930 0.0929 0.3016 4.9849 0.0000 * 47 | 3 0.2990 0.2989 0.0527 0.0527 0.5027 4.9854 0.0000 * 48 | 4 0.2553 0.2552 0.0328 0.0328 0.7037 4.9838 0.0000 * 49 | 5 0.2252 0.2252 0.0217 0.0217 0.9046 4.9842 0.0000 * 50 | 6 0.6249 0.6248 0.1004 0.1005 0.1005 9.9558 0.0000 * 51 | 7 0.4758 0.4759 0.0350 0.0350 0.3014 9.9610 0.0000 * 52 | 8 0.3991 0.3991 0.0156 0.0156 0.5027 9.9689 0.0000 * 53 | 9 0.3495 0.3495 0.0079 0.0079 0.7032 9.9690 0.0000 * 54 | 10 0.3137 0.3137 0.0044 0.0044 0.9038 9.9548 0.0000 * 55 | 11 0.6808 0.6809 0.0607 0.0607 0.1005 14.9382 0.0000 * 56 | 12 0.5367 0.5367 0.0166 0.0166 0.3013 14.9326 0.0000 * 57 | 13 0.4604 0.4604 0.0062 0.0062 0.5024 14.9422 0.0000 * 58 | 14 0.4095 0.4095 0.0027 0.0027 0.7054 14.9882 0.0000 * 59 | 15 0.3720 0.3720 0.0013 0.0013 0.9026 14.9125 0.0000 * 60 | 16 0.7162 0.7162 0.0401 0.0400 0.1005 19.9136 0.0000 * 61 | 17 0.5783 0.5784 0.0089 0.0089 0.3011 19.8936 0.0000 * 62 | 18 0.5038 0.5037 0.0028 0.0029 0.5016 19.8778 0.0000 * 63 | 19 0.4530 0.4530 0.0011 0.0010 0.7113 20.1441 0.0000 * 64 | 20 0.4150 0.4150 0.0005 0.0005 0.8995 19.8240 0.0000 * 65 | 21 0.7412 0.7411 0.0279 0.0279 0.1006 24.8759 0.0000 * 66 | 22 0.6094 0.6095 0.0051 0.0051 0.3012 24.8808 0.0000 * 67 | 23 0.5369 0.5368 0.0014 0.0014 0.5020 24.8681 0.0000 * 68 | 24 0.4868 0.4868 0.0005 0.0005 0.7093 25.1120 0.0000 * 69 | 25 0.4488 0.4488 0.0002 0.0002 0.8939 24.6199 0.0000 * 70 | -------------------------------------------------------------------------------- /tests/data/basic-C.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Basic test of two measurement functionality 4 | # These are accurate values for total reflectance transmittance 5 | # for a sample between glass sides for g=0 and various mus and mua 6 | 7 | 1.40 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 0.1 # [mm] Diameter of illumination beam 12 | 1.00 # Reflectivity of the reflectance calibration standard 13 | 14 | 0 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere (unused) 17 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 18 | 25.4 # [mm] Sample Port Diameter 19 | 12.7 # [mm] Entrance Port Diameter 20 | 1.00 # [mm] Detector Port Diameter 21 | 0.96 # Reflectivity of the sphere wall 22 | 23 | # Properties of sphere (unused) 24 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 25 | 25.4 # [mm] Sample Port Diameter 26 | 12.7 # [mm] Entrance Port Diameter 27 | 1.00 # [mm] Detector Port Diameter 28 | 0.96 # Reflectivity of the sphere wall 29 | 30 | 3 # Number of measurements, M_R, M_T 31 | 32 | #M_R M_T M_U #mus mua g 33 | 0.18744404560482633 0.5762005443502255 0.0056059263111615 # 5 0.1 0.9 34 | 0.11408695107696898 0.4247042155048462 0.0045897426957986 # 5 0.3 0.9 35 | 0.08471305576215057 0.3300887417740564 0.0037577621352314 # 5 0.5 0.9 36 | 0.06982294430167554 0.2612417396290802 0.0030765942439748 # 5 0.7 0.9 37 | 0.06135333777430316 0.2084620482128875 0.0025189012963516 # 5 0.9 0.9 38 | 0.27088016856897984 0.4509192971704515 0.0000377723382423 # 10 0.1 0.9 39 | 0.16333747798935647 0.2987243441470849 0.0000309253653455 # 10 0.3 0.9 40 | 0.11792911555677094 0.21658938611934533 0.0000253195394967 # 10 0.5 0.9 41 | 0.09428868597310426 0.16315981631593007 0.0000207298786979 # 10 0.7 0.9 42 | 0.08044503686810237 0.12537444039186588 0.0000169721832976 # 10 0.9 0.9 43 | 0.3237537120926814 0.3838856142761712 2.54504963154955e-7 # 15 0.1 0.9 44 | 0.20107528687650159 0.23478780431948043 2.08373676865758e-7 # 15 0.3 0.9 45 | 0.14701027202208009 0.16016218300262994 1.70601907236271e-7 # 15 0.5 0.9 46 | 0.11771880655085115 0.11501848868168339 1.39677002661786e-7 # 15 0.7 0.9 47 | 0.09985414400017813 0.08501666270897117 1.14357836306534e-7 # 15 0.9 0.9 48 | 0.3645806239639053 0.33789025764628244 1.71485298665413e-9 # 20 0.1 0.9 49 | 0.23310882350754394 0.19448119664465763 1.40400245034061e-9 # 20 0.3 0.9 50 | 0.17325128485522778 0.12628929941659306 1.14949962706843e-9 # 20 0.5 0.9 51 | 0.1397305504426868 0.08707259647454037 9.41130397771423e-10 # 20 0.7 0.9 52 | 0.1186171920562128 0.06218235955454871 7.70532150931932e-10 # 20 0.9 0.9 53 | 0.3987189090404213 0.30192686119454526 1.15544781609603e-11 # 25 0.1 0.9 54 | 0.26116346314962957 0.16526379969277508 9.46000213019849e-12 # 25 0.3 0.9 55 | 0.19697893309135678 0.10298113321790629 7.74519094516778e-12 # 25 0.5 0.9 56 | 0.16010187736822423 0.06857093492328084 6.34122291893490e-12 # 25 0.7 0.9 -------------------------------------------------------------------------------- /tests/data/basic-C.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad basic-C.rxt 3 | # Beam diameter = 0.1 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.4000 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector (ignored since no spheres used) 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 100.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector (ignored since no spheres used) 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R, M_T, and M_U were measured using the substitution (single-beam) method. 33 | # No sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 1 0.1874 0.1874 0.5762 0.5761 0.1008 0.5035 0.8993 * 46 | 2 0.1141 0.1141 0.4247 0.4247 0.3022 0.5019 0.8996 * 47 | 3 0.0847 0.0847 0.3301 0.3301 0.5045 0.4994 0.9000 * 48 | 4 0.0698 0.0698 0.2612 0.2612 0.7076 0.4967 0.9005 * 49 | 5 0.0614 0.0614 0.2085 0.2084 0.9105 0.4942 0.9009 * 50 | 6 0.2709 0.2709 0.4509 0.4508 0.1008 0.9997 0.9000 * 51 | 7 0.1633 0.1633 0.2987 0.2987 0.3023 0.9975 0.9002 * 52 | 8 0.1179 0.1179 0.2166 0.2166 0.5040 0.9953 0.9004 * 53 | 9 0.0943 0.0942 0.1632 0.1631 0.7065 0.9930 0.9006 * 54 | 10 0.0804 0.0805 0.1254 0.1254 0.9081 0.9910 0.9008 * 55 | 11 0.3238 0.3237 0.3839 0.3838 0.1007 1.4972 0.9002 * 56 | 12 0.2011 0.2010 0.2348 0.2348 0.3022 1.4946 0.9003 * 57 | 13 0.1470 0.1470 0.1602 0.1601 0.5038 1.4933 0.9004 * 58 | 14 0.1177 0.1177 0.1150 0.1151 0.7054 1.4902 0.9006 * 59 | 15 0.0999 0.0998 0.0850 0.0850 0.9078 1.4876 0.9008 * 60 | 16 0.3646 0.3646 0.3379 0.3379 0.1006 1.9943 0.9003 * 61 | 17 0.2331 0.2330 0.1945 0.1945 0.3021 1.9918 0.9004 * 62 | 18 0.1733 0.1732 0.1263 0.1262 0.5036 1.9904 0.9005 * 63 | 19 0.1397 0.1398 0.0871 0.0871 0.7046 1.9892 0.9005 * 64 | 20 0.1186 0.1187 0.0622 0.0622 0.9063 1.9872 0.9006 * 65 | 21 0.3987 0.3987 0.3019 0.3020 0.1006 2.4912 0.9003 * 66 | 22 0.2612 0.2612 0.1653 0.1652 0.3020 2.4905 0.9004 * 67 | 23 0.1970 0.1970 0.1030 0.1030 0.5032 2.4877 0.9005 * 68 | 24 0.1601 0.1601 0.0686 0.0685 0.7051 2.4868 0.9005 * 69 | -------------------------------------------------------------------------------- /tests/data/basic-D.rxt: -------------------------------------------------------------------------------- 1 | IAD1 2 | # 3 | # Tests using calculated values for M_R and M_T 4 | # by Scott Prahl 5 | # 6 | 1.4 # Index of refraction of sample 7 | 1.0 # Index of refraction of top slide 8 | 1.0 # [mm] Thickness of sample 9 | 1.0 # [mm] Thickness of slides 10 | 2.0 # [mm] Diameter of illumination beam 11 | 1.00 # Reflectance of calibration standard 12 | 13 | 0 # [mm] Number of spheres used during each measurement 14 | 15 | # Refection sphere properties (unused because n_spheres=0) 16 | 203.2 # [mm] Sphere Diameter 17 | 25.4 # [mm] Sample Port Diameter 18 | 12.7 # [mm] Entrance Port Diameter 19 | 12.7 # [mm] Detector Port Diameter 20 | 0.96 # Reflectivity of the sphere wall 21 | 22 | # Transmission sphere properties (unused because n_spheres=0) 23 | 203.2 # [mm] Sphere Diameter 24 | 25.4 # [mm] Sample Port Diameter 25 | 12.7 # [mm] Entrance Port Diameter 26 | 12.7 # [mm] Detector Port Diameter 27 | 0.96 # Reflectivity of the sphere wall 28 | 29 | 2 # [mm] Number of measurements 30 | 31 | # M_R M_T a b g 32 | 2.77865808457e-2 1.73065997660e-2 # 0.00 4.0000 0.00 33 | 4.20236699283e-2 1.91254597157e-2 # 0.19 4.0000 0.00 34 | 6.00960999727e-2 2.19067707658e-2 # 0.36 4.0000 0.00 35 | 8.34695920348e-2 2.63617299497e-2 # 0.51 4.0000 0.00 36 | 1.14361397922e-1 3.38563099504e-2 # 0.64 4.0000 0.00 37 | 1.56228601933e-1 4.70846891403e-2 # 0.75 4.0000 0.00 38 | 2.14577898383e-1 7.13708102703e-2 # 0.84 4.0000 0.00 39 | 2.97877311707e-1 1.16591498256e-1 # 0.91 4.0000 0.00 40 | 4.15349513292e-1 1.96420803666e-1 # 0.96 4.0000 0.00 41 | 5.54938077927e-1 3.06980103254e-1 # 0.99 4.0000 0.00 42 | 6.29535913467e-1 3.70464086533e-1 # 1.00 4.0000 0.00 43 | -------------------------------------------------------------------------------- /tests/data/basic-D.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad basic-D.rxt 3 | # Beam diameter = 2.0 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 0.000 mm 6 | # Bottom slide thickness = 0.000 mm 7 | # Sample index of refraction = 1.4000 mm 8 | # Top slide index of refraction = 1.0000 mm 9 | # Bottom slide index of refraction = 1.0000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector (ignored since no spheres used) 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 12.7 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 100.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector (ignored since no spheres used) 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 12.7 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R and M_T were measured using the substitution (single-beam) method. 33 | # No sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 1 0.0278 0.0278 0.0173 0.0174 3.9948 0.0000 0.0000 * 46 | 2 0.0420 0.0421 0.0191 0.0192 3.2221 0.7772 0.0000 * 47 | 3 0.0601 0.0601 0.0219 0.0219 2.5373 1.4641 0.0000 * 48 | 4 0.0835 0.0834 0.0264 0.0263 1.9385 2.0694 0.0000 * 49 | 5 0.1144 0.1144 0.0339 0.0339 1.4175 2.5890 0.0000 * 50 | 6 0.1562 0.1562 0.0471 0.0470 0.9835 3.0338 0.0000 * 51 | 7 0.2146 0.2146 0.0714 0.0713 0.6282 3.3940 0.0000 * 52 | 8 0.2979 0.2979 0.1166 0.1166 0.3528 3.6754 0.0000 * 53 | 9 0.4153 0.4153 0.1964 0.1965 0.1567 3.8777 0.0000 * 54 | 10 0.5549 0.5549 0.3070 0.3070 0.0391 4.0006 0.0000 * 55 | 11 0.6295 0.6295 0.3705 0.3705 0.0000 4.0415 0.0000 * 56 | -------------------------------------------------------------------------------- /tests/data/basic-E.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Basic test of two measurement functionality 4 | # These are accurate values for total reflectance transmittance 5 | # for a sample between glass sides for g=0 and various mus and mua 6 | 7 | 1.40 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 0.1 # [mm] Diameter of illumination beam 12 | 1.00 # Reflectivity of the reflectance calibration standard 13 | 14 | 0 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere (unused) 17 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 18 | 25.4 # [mm] Sample Port Diameter 19 | 12.7 # [mm] Entrance Port Diameter 20 | 1.00 # [mm] Detector Port Diameter 21 | 0.96 # Reflectivity of the sphere wall 22 | 23 | # Properties of sphere (unused) 24 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 25 | 25.4 # [mm] Sample Port Diameter 26 | 12.7 # [mm] Entrance Port Diameter 27 | 1.00 # [mm] Detector Port Diameter 28 | 0.96 # Reflectivity of the sphere wall 29 | 30 | 2 # Number of measurements, M_R, M_T 31 | 32 | #M_R M_T #mus mua g 33 | 0.18744404560482633 0.5762005443502255 # 5 0.1 0.9 34 | 0.11408695107696898 0.4247042155048462 # 5 0.3 0.9 35 | 0.08471305576215057 0.3300887417740564 # 5 0.5 0.9 36 | 0.06982294430167554 0.2612417396290802 # 5 0.7 0.9 37 | 0.06135333777430316 0.2084620482128875 # 5 0.9 0.9 38 | 0.27088016856897984 0.4509192971704515 # 10 0.1 0.9 39 | 0.16333747798935647 0.2987243441470849 # 10 0.3 0.9 40 | 0.11792911555677094 0.21658938611934533 # 10 0.5 0.9 41 | 0.09428868597310426 0.16315981631593007 # 10 0.7 0.9 42 | 0.08044503686810237 0.12537444039186588 # 10 0.9 0.9 43 | 0.3237537120926814 0.3838856142761712 # 15 0.1 0.9 44 | 0.20107528687650159 0.23478780431948043 # 15 0.3 0.9 45 | 0.14701027202208009 0.16016218300262994 # 15 0.5 0.9 46 | 0.11771880655085115 0.11501848868168339 # 15 0.7 0.9 47 | 0.09985414400017813 0.08501666270897117 # 15 0.9 0.9 48 | 0.3645806239639053 0.33789025764628244 # 20 0.1 0.9 49 | 0.23310882350754394 0.19448119664465763 # 20 0.3 0.9 50 | 0.17325128485522778 0.12628929941659306 # 20 0.5 0.9 51 | 0.1397305504426868 0.08707259647454037 # 20 0.7 0.9 52 | 0.1186171920562128 0.06218235955454871 # 20 0.9 0.9 53 | 0.3987189090404213 0.30192686119454526 # 25 0.1 0.9 54 | 0.26116346314962957 0.16526379969277508 # 25 0.3 0.9 55 | 0.19697893309135678 0.10298113321790629 # 25 0.5 0.9 56 | 0.16010187736822423 0.06857093492328084 # 25 0.7 0.9 -------------------------------------------------------------------------------- /tests/data/basic-E.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad basic-E.rxt 3 | # Beam diameter = 0.1 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.4000 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector (ignored since no spheres used) 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 100.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector (ignored since no spheres used) 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R and M_T were measured using the substitution (single-beam) method. 33 | # No sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 1 0.1874 0.1874 0.5762 0.5763 0.1035 0.6252 0.0000 * 46 | 2 0.1141 0.1141 0.4247 0.4247 0.3525 0.5756 0.0000 * 47 | 3 0.0847 0.0847 0.3301 0.3301 0.6243 0.5068 0.0000 * 48 | 4 0.0698 0.0698 0.2612 0.2612 0.8963 0.4421 0.0000 * 49 | 5 0.0614 0.0613 0.2085 0.2085 1.1582 0.3887 0.0000 * 50 | 6 0.2709 0.2709 0.4509 0.4510 0.1096 1.2017 0.0000 * 51 | 7 0.1633 0.1633 0.2987 0.2987 0.3670 1.1482 0.0000 * 52 | 8 0.1179 0.1179 0.2166 0.2166 0.6555 1.0732 0.0000 * 53 | 9 0.0943 0.0942 0.1632 0.1631 0.9543 0.9932 0.0000 * 54 | 10 0.0804 0.0804 0.1254 0.1253 1.2498 0.9205 0.0000 * 55 | 11 0.3238 0.3238 0.3839 0.3839 0.1100 1.6879 0.0000 * 56 | 12 0.2011 0.2010 0.2348 0.2348 0.3626 1.6511 0.0000 * 57 | 13 0.1470 0.1469 0.1602 0.1602 0.6452 1.5950 0.0000 * 58 | 14 0.1177 0.1177 0.1150 0.1150 0.9429 1.5307 0.0000 * 59 | 15 0.0999 0.0999 0.0850 0.0849 1.2449 1.4652 0.0000 * 60 | 16 0.3646 0.3645 0.3379 0.3379 0.1088 2.1504 0.0000 * 61 | 17 0.2331 0.2331 0.1945 0.1944 0.3531 2.1303 0.0000 * 62 | 18 0.1733 0.1732 0.1263 0.1263 0.6241 2.0924 0.0000 * 63 | 19 0.1397 0.1397 0.0871 0.0871 0.9119 2.0450 0.0000 * 64 | 20 0.1186 0.1186 0.0622 0.0621 1.2094 1.9932 0.0000 * 65 | 21 0.3987 0.3987 0.3019 0.3020 0.1074 2.6174 0.0000 * 66 | 22 0.2612 0.2611 0.1653 0.1653 0.3445 2.6043 0.0000 * 67 | 23 0.1970 0.1970 0.1030 0.1029 0.6050 2.5810 0.0000 * 68 | 24 0.1601 0.1601 0.0686 0.0686 0.8814 2.5464 0.0000 * 69 | -------------------------------------------------------------------------------- /tests/data/sample-A.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Input Example with a single set of sphere coefficients 4 | # The order of entries is important 5 | # Anything after a '#' is ignored, blank lines are also ignored 6 | 7 | 1.34 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 5.0 # [mm] Diameter of illumination beam 12 | 0.96 # Reflectivity of the reflectance calibration standard 13 | 14 | 0 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere used for reflection measurements 17 | # Unused but required 18 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 19 | 25.4 # [mm] Sample Port Diameter 20 | 12.7 # [mm] Entrance Port Diameter 21 | 1.00 # [mm] Detector Port Diameter 22 | 0.96 # Reflectivity of the sphere wall 23 | 24 | # Properties of sphere used for transmission measurements 25 | # Unused but required 26 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 27 | 25.4 # [mm] Sample Port Diameter 28 | 12.7 # [mm] Entrance Port Diameter 29 | 1.00 # [mm] Detector Port Diameter 30 | 0.96 # Reflectivity of the sphere wall 31 | 32 | 2 # Number of measurements, M_R, M_T 33 | 34 | 35 | #lambda M_R M_T 36 | 800 0.16830 0.24974 37 | 810 0.16271 0.26479 38 | 820 0.16289 0.25530 39 | 830 0.16853 0.25777 40 | 840 0.16681 0.26324 41 | 850 0.16848 0.26568 42 | 860 0.16604 0.26778 43 | 870 0.16499 0.24648 44 | 880 0.16195 0.24352 45 | 890 0.16478 0.23879 46 | 900 0.16201 0.23465 47 | 910 0.16515 0.23720 48 | 920 0.16502 0.24702 49 | 930 0.17153 0.25659 50 | 940 0.18651 0.26795 51 | 950 0.19330 0.29166 52 | 960 0.20565 0.30804 53 | 970 0.22184 0.33299 54 | 980 0.24276 0.36159 55 | 990 0.26358 0.39354 56 | 1000 0.28689 0.42759 57 | -------------------------------------------------------------------------------- /tests/data/sample-A.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad sample-A.rxt 3 | # Beam diameter = 5.0 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.3400 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector (ignored since no spheres used) 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 96.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector (ignored since no spheres used) 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R and M_T were measured using the substitution (single-beam) method. 33 | # No sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 800.0 0.1683 0.1683 0.2497 0.2497 0.4509 1.2995 0.0000 * 46 | 810.0 0.1627 0.1627 0.2648 0.2648 0.4445 1.2060 0.0000 * 47 | 820.0 0.1629 0.1628 0.2553 0.2553 0.4578 1.2405 0.0000 * 48 | 830.0 0.1685 0.1685 0.2578 0.2578 0.4387 1.2709 0.0000 * 49 | 840.0 0.1668 0.1668 0.2632 0.2632 0.4356 1.2396 0.0000 * 50 | 850.0 0.1685 0.1685 0.2657 0.2657 0.4277 1.2424 0.0000 * 51 | 860.0 0.1660 0.1660 0.2678 0.2678 0.4313 1.2184 0.0000 * 52 | 870.0 0.1650 0.1650 0.2465 0.2465 0.4647 1.2882 0.0000 * 53 | 880.0 0.1620 0.1620 0.2435 0.2435 0.4780 1.2775 0.0000 * 54 | 890.0 0.1648 0.1647 0.2388 0.2388 0.4774 1.3155 0.0000 * 55 | 900.0 0.1620 0.1620 0.2346 0.2346 0.4919 1.3117 0.0000 * 56 | 910.0 0.1651 0.1652 0.2372 0.2372 0.4786 1.3250 0.0000 * 57 | 920.0 0.1650 0.1650 0.2470 0.2469 0.4641 1.2866 0.0000 * 58 | 930.0 0.1715 0.1715 0.2566 0.2566 0.4328 1.2958 0.0000 * 59 | 940.0 0.1865 0.1865 0.2680 0.2679 0.3821 1.3532 0.0000 * 60 | 950.0 0.1933 0.1934 0.2917 0.2916 0.3400 1.3080 0.0000 * 61 | 960.0 0.2056 0.2056 0.3080 0.3080 0.3006 1.3212 0.0000 * 62 | 970.0 0.2218 0.2218 0.3330 0.3330 0.2525 1.3223 0.0000 * 63 | 980.0 0.2428 0.2427 0.3616 0.3616 0.2036 1.3301 0.0000 * 64 | 990.0 0.2636 0.2636 0.3935 0.3936 0.1605 1.3213 0.0000 * 65 | 1000.0 0.2869 0.2869 0.4276 0.4276 0.1215 1.3129 0.0000 * 66 | -------------------------------------------------------------------------------- /tests/data/sample-B.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Input Example with a single set of sphere coefficients 4 | # The order of entries is important 5 | # Anything after a '#' is ignored, blank lines are also ignored 6 | 7 | 1.34 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 5.0 # [mm] Diameter of illumination beam 12 | 0.96 # Reflectivity of the reflectance calibration standard 13 | 14 | 0 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere used for reflection measurements 17 | # Unused but required 18 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 19 | 25.4 # [mm] Sample Port Diameter 20 | 12.7 # [mm] Entrance Port Diameter 21 | 1.00 # [mm] Detector Port Diameter 22 | 0.96 # Reflectivity of the sphere wall 23 | 24 | # Properties of sphere used for transmission measurements 25 | # Unused but required 26 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 27 | 25.4 # [mm] Sample Port Diameter 28 | 12.7 # [mm] Entrance Port Diameter 29 | 1.00 # [mm] Detector Port Diameter 30 | 0.96 # Reflectivity of the sphere wall 31 | 32 | 1 # Number of measurements, M_R, M_T 33 | 34 | 35 | #lambda M_R 36 | 800 0.16830 37 | 810 0.16271 38 | 820 0.16289 39 | 830 0.16853 40 | 840 0.16681 41 | 850 0.16848 42 | 860 0.16604 43 | 870 0.16499 44 | 880 0.16195 45 | 890 0.16478 46 | 900 0.16201 47 | 910 0.16515 48 | 920 0.16502 49 | 930 0.17153 50 | 940 0.18651 51 | 950 0.19330 52 | 960 0.20565 53 | 970 0.22184 54 | 980 0.24276 55 | 990 0.26358 56 | 1000 0.28689 57 | -------------------------------------------------------------------------------- /tests/data/sample-B.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad sample-B.rxt 3 | # Beam diameter = 5.0 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.3400 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector (ignored since no spheres used) 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 96.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector (ignored since no spheres used) 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # Just M_R was measured using the substitution (single-beam) method. 33 | # No sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 800.0 0.1683 0.1683 0.0000 0.0000 0.3578 1.0000 0.0000 * 46 | 810.0 0.1627 0.1627 0.0000 0.0000 0.3810 1.0000 0.0000 * 47 | 820.0 0.1629 0.1629 0.0000 0.0000 0.3802 1.0000 0.0000 * 48 | 830.0 0.1685 0.1685 0.0000 0.0000 0.3568 1.0000 0.0000 * 49 | 840.0 0.1668 0.1668 0.0000 0.0000 0.3637 1.0000 0.0000 * 50 | 850.0 0.1685 0.1685 0.0000 0.0000 0.3570 1.0000 0.0000 * 51 | 860.0 0.1660 0.1660 0.0000 0.0000 0.3669 1.0000 0.0000 * 52 | 870.0 0.1650 0.1650 0.0000 0.0000 0.3712 1.0000 0.0000 * 53 | 880.0 0.1620 0.1620 0.0000 0.0000 0.3843 1.0000 0.0000 * 54 | 890.0 0.1648 0.1648 0.0000 0.0000 0.3721 1.0000 0.0000 * 55 | 900.0 0.1620 0.1620 0.0000 0.0000 0.3840 1.0000 0.0000 * 56 | 910.0 0.1651 0.1652 0.0000 0.0000 0.3705 1.0000 0.0000 * 57 | 920.0 0.1650 0.1650 0.0000 0.0000 0.3711 1.0000 0.0000 * 58 | 930.0 0.1715 0.1715 0.0000 0.0000 0.3453 1.0000 0.0000 * 59 | 940.0 0.1865 0.1865 0.0000 0.0000 0.2952 1.0000 0.0000 * 60 | 950.0 0.1933 0.1933 0.0000 0.0000 0.2759 1.0000 0.0000 * 61 | 960.0 0.2056 0.2056 0.0000 0.0000 0.2453 1.0000 0.0000 * 62 | 970.0 0.2218 0.2218 0.0000 0.0000 0.2119 1.0000 0.0000 * 63 | 980.0 0.2428 0.2428 0.0000 0.0000 0.1774 1.0000 0.0000 * 64 | 990.0 0.2636 0.2636 0.0000 0.0000 0.1501 1.0000 0.0000 * 65 | 1000.0 0.2869 0.2869 0.0000 0.0000 0.1256 1.0000 0.0000 * 66 | -------------------------------------------------------------------------------- /tests/data/sample-C.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Input Example with a single set of sphere coefficients 4 | # The order of entries is important 5 | # Anything after a '#' is ignored, blank lines are also ignored 6 | 7 | 1.34 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 5.0 # [mm] Diameter of illumination beam 12 | 0.96 # Reflectivity of the reflectance calibration standard 13 | 14 | 1 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere used for reflection measurements 17 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 18 | 25.4 # [mm] Sample Port Diameter 19 | 12.7 # [mm] Entrance Port Diameter 20 | 1.00 # [mm] Detector Port Diameter 21 | 0.96 # Reflectivity of the sphere wall 22 | 23 | # Properties of sphere used for transmission measurements 24 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 25 | 25.4 # [mm] Sample Port Diameter 26 | 12.7 # [mm] Entrance Port Diameter 27 | 1.00 # [mm] Detector Port Diameter 28 | 0.96 # Reflectivity of the sphere wall 29 | 30 | 2 # Number of measurements, M_R, M_T 31 | 32 | 33 | #lambda M_R M_T 34 | 800 0.16830 0.24974 35 | 810 0.16271 0.26479 36 | 820 0.16289 0.25530 37 | 830 0.16853 0.25777 38 | 840 0.16681 0.26324 39 | 850 0.16848 0.26568 40 | 860 0.16604 0.26778 41 | 870 0.16499 0.24648 42 | 880 0.16195 0.24352 43 | 890 0.16478 0.23879 44 | 900 0.16201 0.23465 45 | 910 0.16515 0.23720 46 | 920 0.16502 0.24702 47 | 930 0.17153 0.25659 48 | 940 0.18651 0.26795 49 | 950 0.19330 0.29166 50 | 960 0.20565 0.30804 51 | 970 0.22184 0.33299 52 | 980 0.24276 0.36159 53 | 990 0.26358 0.39354 54 | 1000 0.28689 0.42759 55 | -------------------------------------------------------------------------------- /tests/data/sample-C.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad sample-C.rxt 3 | # Beam diameter = 5.0 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.3400 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 96.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R and M_T were measured using the substitution (single-beam) method. 33 | # Single sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 800.0 0.1683 0.1683 0.2497 0.2497 0.4039 1.4336 0.0000 * 46 | 810.0 0.1627 0.1627 0.2648 0.2647 0.3983 1.3339 0.0000 * 47 | 820.0 0.1629 0.1629 0.2553 0.2553 0.4101 1.3713 0.0000 * 48 | 830.0 0.1685 0.1686 0.2578 0.2578 0.3926 1.4031 0.0000 * 49 | 840.0 0.1668 0.1668 0.2632 0.2632 0.3900 1.3694 0.0000 * 50 | 850.0 0.1685 0.1685 0.2657 0.2657 0.3827 1.3720 0.0000 * 51 | 860.0 0.1660 0.1660 0.2678 0.2678 0.3862 1.3460 0.0000 * 52 | 870.0 0.1650 0.1649 0.2465 0.2465 0.4168 1.4217 0.0000 * 53 | 880.0 0.1620 0.1620 0.2435 0.2435 0.4287 1.4112 0.0000 * 54 | 890.0 0.1648 0.1648 0.2388 0.2388 0.4277 1.4529 0.0000 * 55 | 900.0 0.1620 0.1621 0.2346 0.2346 0.4412 1.4489 0.0000 * 56 | 910.0 0.1651 0.1652 0.2372 0.2372 0.4291 1.4622 0.0000 * 57 | 920.0 0.1650 0.1650 0.2470 0.2471 0.4157 1.4200 0.0000 * 58 | 930.0 0.1715 0.1715 0.2566 0.2567 0.3870 1.4289 0.0000 * 59 | 940.0 0.1865 0.1865 0.2680 0.2679 0.3405 1.4892 0.0000 * 60 | 950.0 0.1933 0.1932 0.2917 0.2917 0.3022 1.4392 0.0000 * 61 | 960.0 0.2056 0.2057 0.3080 0.3080 0.2657 1.4547 0.0000 * 62 | 970.0 0.2218 0.2219 0.3330 0.3330 0.2216 1.4560 0.0000 * 63 | 980.0 0.2428 0.2428 0.3616 0.3616 0.1765 1.4663 0.0000 * 64 | 990.0 0.2636 0.2635 0.3935 0.3935 0.1369 1.4584 0.0000 * 65 | 1000.0 0.2869 0.2869 0.4276 0.4277 0.1006 1.4531 0.0000 * 66 | -------------------------------------------------------------------------------- /tests/data/sample-D.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Input Example with a single set of sphere coefficients 4 | # The order of entries is important 5 | # Anything after a '#' is ignored, blank lines are also ignored 6 | 7 | 1.34 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 5.0 # [mm] Beam diameter 12 | 0.96 # Reflectivity of the reflectance calibration standard 13 | 14 | 1 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere used for reflection measurements 17 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 18 | 25.4 # [mm] Sample Port Diameter 19 | 12.7 # [mm] Entrance Port Diameter 20 | 1.00 # [mm] Detector Port Diameter 21 | 0.96 # Reflectivity of the sphere wall 22 | 23 | # Properties of sphere used for transmission measurements 24 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 25 | 25.4 # [mm] Sample Port Diameter 26 | 12.7 # [mm] Entrance Port Diameter 27 | 1.00 # [mm] Detector Port Diameter 28 | 0.96 # Reflectivity of the sphere wall 29 | 30 | 5 # Number of measurements, M_R, M_T, M_U, r_w, r_std 31 | 32 | #lambda M_R M_T M_U r_w r_std 33 | 925.00 0.47248 0.47882 0.00000 0.97000 0.99100 34 | 926.00 0.47017 0.47966 0.00000 0.97000 0.99100 35 | 927.00 0.46893 0.47942 0.00000 0.97000 0.99100 36 | -------------------------------------------------------------------------------- /tests/data/sample-D.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad sample-D.rxt 3 | # Beam diameter = 5.0 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.3400 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 97.0 % 21 | # calibration standard = 96.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 99.1 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R, M_T, M_U, r_w, and t_w were measured using the substitution (single-beam) method. 33 | # Single sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 925.0 0.4725 0.4725 0.4788 0.4788 0.0199 2.5971 0.0000 * 46 | 926.0 0.4702 0.4701 0.4797 0.4797 0.0202 2.5742 0.0000 * 47 | 927.0 0.4689 0.4689 0.4794 0.4794 0.0207 2.5666 0.0000 * 48 | -------------------------------------------------------------------------------- /tests/data/sample-E.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | 1.34 # Index of refraction of the sample 4 | 1.50 # Index of refraction of the top and bottom slides 5 | 1.0 # [mm] Thickness of sample 6 | 1.0 # [mm] Thickness of slides 7 | 5.0 # [mm] Beam diameter 8 | 0.96 # Reflectivity of the reflectance calibration standard 9 | 10 | 2 # Number of spheres used during each measurement 11 | 12 | # Properties of sphere used for reflection measurements 13 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 14 | 25.4 # [mm] Sample Port Diameter 15 | 12.7 # [mm] Entrance Port Diameter 16 | 1.00 # [mm] Detector Port Diameter 17 | 0.96 # Reflectivity of the sphere wall 18 | 19 | # Properties of sphere used for transmission measurements 20 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 21 | 25.4 # [mm] Sample Port Diameter 22 | 12.7 # [mm] Entrance Port Diameter 23 | 1.00 # [mm] Detector Port Diameter 24 | 0.96 # Reflectivity of the sphere wall 25 | 26 | 2 # Two measurements, i.e., M_R & M_T 27 | 28 | #lambda M_R M_T 29 | 800 0.16830 0.24974 30 | 810 0.16271 0.26479 31 | 820 0.16289 0.25530 32 | -------------------------------------------------------------------------------- /tests/data/sample-E.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad sample-E.rxt 3 | # Beam diameter = 5.0 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.3400 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 96.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R and M_T were measured using the substitution (single-beam) method. 33 | # Double sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 800.0 0.1683 0.1684 0.2497 0.2497 0.4370 1.3756 0.0000 * 46 | 810.0 0.1627 0.1626 0.2648 0.2648 0.4326 1.2735 0.0000 * 47 | 820.0 0.1629 0.1629 0.2553 0.2553 0.4442 1.3120 0.0000 * 48 | -------------------------------------------------------------------------------- /tests/data/sample-F.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | 1.34 # Index of refraction of the sample 4 | 1.50 # Index of refraction of the top and bottom slides 5 | 1.0 # [mm] Thickness of sample 6 | 1.0 # [mm] Thickness of slides 7 | 5.0 # [mm] Beam diameter 8 | 0.96 # Reflectivity of the reflectance calibration standard 9 | 10 | 1 # Number of spheres used during each measurement 11 | 12 | # Properties of sphere used for reflection measurements 13 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 14 | 25.4 # [mm] Sample Port Diameter 15 | 12.7 # [mm] Entrance Port Diameter 16 | 1.00 # [mm] Detector Port Diameter 17 | 0.96 # Reflectivity of the sphere wall 18 | 19 | # Properties of sphere used for transmission measurements 20 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 21 | 25.4 # [mm] Sample Port Diameter 22 | 12.7 # [mm] Entrance Port Diameter 23 | 1.00 # [mm] Detector Port Diameter 24 | 0.96 # Reflectivity of the sphere wall 25 | 26 | 1 # One measurement i.e., M_R 27 | 28 | #lambda M_R 29 | 800 0.16830 30 | 810 0.16271 31 | 820 0.16289 32 | 830 0.16853 33 | 840 0.16681 34 | 850 0.16848 35 | 860 0.16604 36 | 870 0.16499 37 | 880 0.16195 38 | 890 0.16478 39 | 900 0.16201 40 | 910 0.16515 41 | 920 0.16502 42 | 930 0.17153 43 | 940 0.18651 44 | 950 0.19330 45 | 960 0.20565 46 | 970 0.22184 47 | 980 0.24276 48 | 990 0.26358 49 | 1000 0.28689 50 | -------------------------------------------------------------------------------- /tests/data/sample-F.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad sample-F.rxt 3 | # Beam diameter = 5.0 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.3400 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 96.0 % 21 | # calibration standard = 96.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 96.0 % 30 | # calibration standard = 100.0 % 31 | # 32 | # Just M_R was measured using the substitution (single-beam) method. 33 | # Single sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 800.0 0.1683 0.1683 0.0000 0.0000 0.2928 1.0000 0.0000 * 46 | 810.0 0.1627 0.1627 0.0000 0.0000 0.3117 1.0000 0.0000 * 47 | 820.0 0.1629 0.1629 0.0000 0.0000 0.3111 1.0000 0.0000 * 48 | 830.0 0.1685 0.1685 0.0000 0.0000 0.2921 1.0000 0.0000 * 49 | 840.0 0.1668 0.1668 0.0000 0.0000 0.2977 1.0000 0.0000 * 50 | 850.0 0.1685 0.1685 0.0000 0.0000 0.2922 1.0000 0.0000 * 51 | 860.0 0.1660 0.1660 0.0000 0.0000 0.3003 1.0000 0.0000 * 52 | 870.0 0.1650 0.1650 0.0000 0.0000 0.3038 1.0000 0.0000 * 53 | 880.0 0.1620 0.1620 0.0000 0.0000 0.3145 1.0000 0.0000 * 54 | 890.0 0.1648 0.1648 0.0000 0.0000 0.3045 1.0000 0.0000 * 55 | 900.0 0.1620 0.1620 0.0000 0.0000 0.3142 1.0000 0.0000 * 56 | 910.0 0.1651 0.1652 0.0000 0.0000 0.3033 1.0000 0.0000 * 57 | 920.0 0.1650 0.1650 0.0000 0.0000 0.3037 1.0000 0.0000 * 58 | 930.0 0.1715 0.1715 0.0000 0.0000 0.2827 1.0000 0.0000 * 59 | 940.0 0.1865 0.1865 0.0000 0.0000 0.2416 1.0000 0.0000 * 60 | 950.0 0.1933 0.1933 0.0000 0.0000 0.2258 1.0000 0.0000 * 61 | 960.0 0.2056 0.2056 0.0000 0.0000 0.2005 1.0000 0.0000 * 62 | 970.0 0.2218 0.2218 0.0000 0.0000 0.1729 1.0000 0.0000 * 63 | 980.0 0.2428 0.2428 0.0000 0.0000 0.1443 1.0000 0.0000 * 64 | 990.0 0.2636 0.2636 0.0000 0.0000 0.1217 1.0000 0.0000 * 65 | 1000.0 0.2869 0.2869 0.0000 0.0000 0.1013 1.0000 0.0000 * 66 | -------------------------------------------------------------------------------- /tests/data/sample-G.rxt: -------------------------------------------------------------------------------- 1 | IAD1 # Must be first four characters 2 | 3 | # Input Example with a two sets of sphere coefficients 4 | # The results from this file should be the same as for sample2.rt 5 | # Anything after a '#' is ignored, blank lines are also ignored 6 | 7 | 1.34 # Index of refraction of the sample 8 | 1.50 # Index of refraction of the top and bottom slides 9 | 1.0 # [mm] Thickness of sample 10 | 1.0 # [mm] Thickness of slides 11 | 5.0 # [mm] Beam diameter 12 | 0.96 # Reflectivity of the reflectance calibration standard 13 | 14 | 1 # Number of spheres used during each measurement 15 | 16 | # Properties of sphere used for reflection measurements 17 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 18 | 25.4 # [mm] Sample Port Diameter 19 | 12.7 # [mm] Entrance Port Diameter 20 | 1.00 # [mm] Detector Port Diameter 21 | 0.96 # Reflectivity of the sphere wall 22 | 23 | # Properties of sphere used for transmission measurements 24 | 203.2 # [mm] Sphere Diameter (8 in * 25.4 mm/in) 25 | 25.4 # [mm] Sample Port Diameter 26 | 12.7 # [mm] Entrance Port Diameter 27 | 1.00 # [mm] Detector Port Diameter 28 | 0.96 # Reflectivity of the sphere wall 29 | 30 | 5 # Number of measurements, M_R, M_T, M_U, r_w, r_std 31 | 32 | #lambda M_R M_T M_U r_w r_std 33 | 925.00 0.47248 0.47882 0.00000 0.97000 0.99100 34 | 926.00 0.47017 0.47966 0.00000 0.97000 0.99100 35 | 927.00 0.46893 0.47942 0.00000 0.97000 0.99100 36 | -------------------------------------------------------------------------------- /tests/data/sample-G.txt: -------------------------------------------------------------------------------- 1 | # Inverse Adding-Doubling 3-16-2 (24 Apr 2024) 2 | # iad sample-G.rxt 3 | # Beam diameter = 5.0 mm 4 | # Sample thickness = 1.000 mm 5 | # Top slide thickness = 1.000 mm 6 | # Bottom slide thickness = 1.000 mm 7 | # Sample index of refraction = 1.3400 mm 8 | # Top slide index of refraction = 1.5000 mm 9 | # Bottom slide index of refraction = 1.5000 mm 10 | # 11 | # Percentage unscattered refl. in M_R = 100.0 % 12 | # Percentage unscattered trans. in M_T = 100.0 % 13 | # 14 | # Reflection sphere has a baffle between sample and detector 15 | # sphere diameter = 203.2 mm 16 | # sample port diameter = 25.4 mm 17 | # entrance port diameter = 12.7 mm 18 | # detector port diameter = 1.0 mm 19 | # detector reflectance = 0.0 % 20 | # wall reflectance = 97.0 % 21 | # calibration standard = 96.0 % 22 | # 23 | # Transmission sphere has a baffle between sample and detector 24 | # sphere diameter = 203.2 mm 25 | # sample port diameter = 25.4 mm 26 | # third port diameter = 12.7 mm 27 | # detector port diameter = 1.0 mm 28 | # detector reflectance = 0.0 % 29 | # wall reflectance = 99.1 % 30 | # calibration standard = 100.0 % 31 | # 32 | # M_R, M_T, M_U, r_w, and t_w were measured using the substitution (single-beam) method. 33 | # Single sphere corrections were used and light was incident at 0 degrees from the normal. 34 | # The inverse routine adapted to the input data. 35 | # 36 | # 37 | # AD quadrature points = 8 38 | # AD tolerance for success = 0.00010 39 | # MC tolerance for mu_a and mu_s' = 0.010 % 40 | # Photons used to estimate lost light = 100000 41 | # 42 | # Measured M_R Measured M_T Estimated Estimated Estimated 43 | ##wave M_R fit M_T fit mu_a mu_s' g 44 | # [nm] [---] [---] [---] [---] 1/mm 1/mm [---] 45 | 925.0 0.4725 0.4725 0.4788 0.4788 0.0199 2.5971 0.0000 * 46 | 926.0 0.4702 0.4701 0.4797 0.4797 0.0202 2.5742 0.0000 * 47 | 927.0 0.4689 0.4689 0.4794 0.4794 0.0207 2.5666 0.0000 * 48 | -------------------------------------------------------------------------------- /tests/test_all_notebooks.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | This file is intended to be the target of a pytest run. 4 | 5 | It recursively finds all `.ipynb` files in the docs directory, ignoring 6 | directories that start with '.' and any files matching patterns found in the file 7 | `.testignore` 8 | 9 | Sample invocations of pytest which make the output nicely readable:: 10 | 11 | pytest --verbose --durations=5 test_all_notebooks.py 12 | 13 | If you install `pytest-xdist` you can run tests in parallel with:: 14 | 15 | pytest --verbose --durations=5 -n 4 test_all_notebooks.py 16 | 17 | Original version is licensed under GPL 3.0 so this modified one is as well. 18 | 19 | The original can be located at:: 20 | 21 | https://github.com/alchemyst/Dynamics-and-Control/blob/master/test_all_notebooks.py 22 | """ 23 | import os.path 24 | import pathlib 25 | import pytest 26 | import nbformat 27 | import nbconvert.preprocessors 28 | 29 | # Default search path is the current directory 30 | searchpath = pathlib.Path('.') 31 | 32 | # Read patterns from .testignore file 33 | ignores = [] 34 | if os.path.exists('.testignore'): 35 | with open('.testignore', encoding='utf-8') as file: 36 | ignores = [line.strip() for line in file if line.strip()] 37 | 38 | # Ignore hidden folders (startswith('.')) and files matching ignore patterns 39 | notebooks = [notebook for notebook in searchpath.glob('docs/*.ipynb') 40 | if not (any(parent.startswith('.') 41 | for parent in notebook.parent.parts) 42 | or any(notebook.match(pattern) 43 | for pattern in ignores))] 44 | 45 | notebooks.sort() 46 | ids = [str(n) for n in notebooks] 47 | 48 | 49 | @pytest.mark.parametrize("notebook", notebooks, ids=ids) 50 | def test_run_notebook(notebook): 51 | """Read and execute notebook. 52 | 53 | The method here is directly from the nbconvert docs 54 | 55 | There is no error handling as any errors will be caught by pytest 56 | """ 57 | with open(notebook, encoding='utf-8') as f: 58 | nb = nbformat.read(f, as_version=4) 59 | ep = nbconvert.preprocessors.ExecutePreprocessor(timeout=600) 60 | ep.preprocess(nb, {'metadata': {'path': notebook.parent}}) 61 | -------------------------------------------------------------------------------- /tests/test_boundary.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | # pylint: disable=too-many-statements 3 | # pylint: disable=protected-access 4 | """Tests for Boundary reflection.""" 5 | 6 | import unittest 7 | import numpy as np 8 | import iadpython 9 | 10 | 11 | class boundary(unittest.TestCase): 12 | """Boundary layer calculations.""" 13 | 14 | def test_01_boundary(self): 15 | """Initialization of boundary matrix without glass slides.""" 16 | s = iadpython.Sample(n=1.5, n_above=1.0, n_below=1.0) 17 | rr = np.array([0.11740, 0.43815, 0.02393, 0.00509]) 18 | tt = np.array([0.00000, 0.00000, 0.92455, 0.96000]) 19 | r01, r10, t01, t10 = iadpython.boundary_layer(s, top=True) 20 | np.testing.assert_allclose(t01, tt, atol=1e-5) 21 | np.testing.assert_allclose(r10, rr, atol=1e-5) 22 | np.testing.assert_allclose(t10, tt, atol=1e-5) 23 | np.testing.assert_allclose(r01, rr, atol=1e-5) 24 | 25 | def test_02_boundary(self): 26 | """Initialization of boundary matrix without glass slides.""" 27 | s = iadpython.Sample(n=1.5, n_above=1.0, n_below=1.0) 28 | rr = np.array([0.11740, 0.43815, 0.02393, 0.00509]) 29 | tt = np.array([0.00000, 0.00000, 0.92455, 0.96000]) 30 | r01, r10, t01, t10 = iadpython.boundary_layer(s, top=False) 31 | np.testing.assert_allclose(t01, tt, atol=1e-5) 32 | np.testing.assert_allclose(r10, rr, atol=1e-5) 33 | np.testing.assert_allclose(t10, tt, atol=1e-5) 34 | np.testing.assert_allclose(r01, rr, atol=1e-5) 35 | 36 | def test_04_boundary(self): 37 | """Initialization of boundary matrix without glass slides.""" 38 | s = iadpython.Sample(n=1.5, n_above=1.5, n_below=1.5) 39 | rr = np.array([0.11740, 0.43815, 0.02393, 0.00509]) 40 | tt = np.array([0.00000, 0.00000, 0.92455, 0.96000]) 41 | r01, r10, t01, t10 = iadpython.boundary_layer(s, top=True) 42 | np.testing.assert_allclose(t01, tt, atol=1e-5) 43 | np.testing.assert_allclose(r10, rr, atol=1e-5) 44 | np.testing.assert_allclose(t10, tt, atol=1e-5) 45 | np.testing.assert_allclose(r01, rr, atol=1e-5) 46 | r01, r10, t01, t10 = iadpython.boundary_layer(s, top=False) 47 | np.testing.assert_allclose(t01, tt, atol=1e-5) 48 | np.testing.assert_allclose(r10, rr, atol=1e-5) 49 | np.testing.assert_allclose(t10, tt, atol=1e-5) 50 | np.testing.assert_allclose(r01, rr, atol=1e-5) 51 | 52 | def test_05_boundary(self): 53 | """Initialization of boundary matrices with glass slides.""" 54 | s = iadpython.Sample(n=1.3, n_above=1.5, n_below=1.5) 55 | rr = np.array([0.08628, 0.32200, 0.03502, 0.00807]) 56 | tt = np.array([0.00000, 0.00000, 0.91484, 0.95530]) 57 | r01, r10, t01, t10 = iadpython.boundary_layer(s, top=True) 58 | np.testing.assert_allclose(r01, rr, atol=1e-5) 59 | np.testing.assert_allclose(t01, tt, atol=1e-5) 60 | np.testing.assert_allclose(r10, rr, atol=1e-5) 61 | np.testing.assert_allclose(t10, tt, atol=1e-5) 62 | r01, r10, t01, t10 = iadpython.boundary_layer(s, top=False) 63 | np.testing.assert_allclose(r01, rr, atol=1e-5) 64 | np.testing.assert_allclose(t01, tt, atol=1e-5) 65 | np.testing.assert_allclose(r10, rr, atol=1e-5) 66 | np.testing.assert_allclose(t10, tt, atol=1e-5) 67 | 68 | def test_06_boundary(self): 69 | """Initialization of boundary matrices with glass slides.""" 70 | s = iadpython.Sample(n=1.3, n_above=1.5, n_below=1.6) 71 | rr = np.array([0.08628, 0.32200, 0.03502, 0.00807]) 72 | tt = np.array([0.00000, 0.00000, 0.91484, 0.95530]) 73 | r01, r10, t01, t10 = iadpython.boundary_layer(s, top=True) 74 | np.testing.assert_allclose(r01, rr, atol=1e-5) 75 | np.testing.assert_allclose(t01, tt, atol=1e-5) 76 | np.testing.assert_allclose(r10, rr, atol=1e-5) 77 | np.testing.assert_allclose(t10, tt, atol=1e-5) 78 | r01, r10, t01, t10 = iadpython.boundary_layer(s, top=False) 79 | rr = np.array([0.08628, 0.32200, 0.04371, 0.01135]) 80 | tt = np.array([0.00000, 0.00000, 0.89370, 0.93715]) 81 | np.testing.assert_allclose(r01, rr, atol=1e-5) 82 | np.testing.assert_allclose(t01, tt, atol=1e-5) 83 | np.testing.assert_allclose(r10, rr, atol=1e-5) 84 | np.testing.assert_allclose(t10, tt, atol=1e-5) 85 | 86 | 87 | if __name__ == '__main__': 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /tests/test_double.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=protected-access 2 | 3 | """Tests for sphere object.""" 4 | 5 | import unittest 6 | import iadpython 7 | 8 | class TestDoubleSphere(unittest.TestCase): 9 | """Simple Double Sphere Tests.""" 10 | 11 | def setUp(self): 12 | """Setup for all these tests.""" 13 | r_sphere = iadpython.Sphere(100, 30, d_detector=10, r_wall=1) 14 | t_sphere = iadpython.Sphere(100, 30, d_detector=10, r_wall=1) 15 | self.double = iadpython.DoubleSphere(r_sphere, t_sphere) 16 | self.double.ur1 = 0 17 | self.double.uru = 0 18 | self.double.ut1 = 1 19 | self.double.utu = 1 20 | 21 | def test_init(self): 22 | """Make sure basics work.""" 23 | self.assertEqual(self.double.ur1, 0.0) 24 | self.assertEqual(self.double.ut1, 1.0) 25 | self.assertEqual(self.double.uru, 0.0) 26 | self.assertEqual(self.double.utu, 1.0) 27 | 28 | def test_str(self): 29 | """Test string describing the object.""" 30 | output = str(self.double) 31 | self.assertIn('Reflection Sphere', output) 32 | self.assertIn('Transmission Sphere', output) 33 | self.assertIn('ur1 = 0.000', output) 34 | self.assertIn('ut1 = 1.000', output) 35 | self.assertIn('uru = 0.000', output) 36 | self.assertIn('utu = 1.000', output) 37 | 38 | def test_no_sample(self): 39 | """Light passes unhindered between spheres.""" 40 | self.double.ur1 = 0 41 | self.double.ut1 = 1 42 | self.double.uru = self.double.ur1 43 | self.double.utu = self.double.ut1 44 | N = 1000 45 | r_total = 0 46 | t_total = 0 47 | # print("no sample") 48 | for _ in range(N): 49 | r_detected, t_detected = self.double.do_one_photon() 50 | r_total += r_detected 51 | t_total += t_detected 52 | # print(r_total, t_total) 53 | 54 | self.assertAlmostEqual(r_total/N, 0.5, places=1) 55 | self.assertAlmostEqual(t_total/N, 0.5, places=1) 56 | 57 | def test_no_sample_N(self): 58 | """Light passes unhindered between spheres.""" 59 | self.double.ur1 = 0 60 | self.double.ut1 = 1 61 | self.double.uru = self.double.ur1 62 | self.double.utu = self.double.ut1 63 | N = 1000 64 | r, _, t, _ = self.double.do_N_photons(N) 65 | self.assertAlmostEqual(r, 0.5, places=1) 66 | self.assertAlmostEqual(t, 0.5, places=1) 67 | 68 | def test_mirror_sample(self): 69 | """Light passes unhindered between spheres.""" 70 | self.double.ur1 = 1 71 | self.double.ut1 = 0 72 | self.double.uru = self.double.ur1 73 | self.double.utu = self.double.ut1 74 | 75 | N = 10 76 | r_total = 0 77 | t_total = 0 78 | for _ in range(N): 79 | r_detected, t_detected = self.double.do_one_photon() 80 | r_total += r_detected 81 | t_total += t_detected 82 | self.assertAlmostEqual(r_total/N, 1.0, places=5) 83 | self.assertAlmostEqual(t_total/N, 0.0, places=5) 84 | 85 | def test_mirror_sample_N(self): 86 | """Light passes unhindered between spheres.""" 87 | self.double.ur1 = 1 88 | self.double.ut1 = 0 89 | self.double.uru = self.double.ur1 90 | self.double.utu = self.double.ut1 91 | 92 | N = 10 93 | r, _, t, _ = self.double.do_N_photons(N) 94 | self.assertAlmostEqual(r, 1.0, places=5) 95 | self.assertAlmostEqual(t, 0.0, places=5) 96 | 97 | if __name__ == '__main__': 98 | unittest.main() 99 | -------------------------------------------------------------------------------- /tests/test_grid.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | """Tests for Inverse Adding Doubling.""" 4 | 5 | import unittest 6 | import numpy as np 7 | import iadpython 8 | 9 | 10 | class GridTest(unittest.TestCase): 11 | """Test grid construction.""" 12 | 13 | def test_grid_01(self): 14 | """Grid for search_ag.""" 15 | fixed_b = 4 16 | exp = iadpython.Experiment(r=0.1, t=0.5, default_b=fixed_b) 17 | exp.determine_search() 18 | grid = iadpython.Grid(N=5) 19 | grid.calc(exp, default=fixed_b) 20 | aa = [[0.000, 0.250, 0.500, 0.750, 1.000], 21 | [0.000, 0.250, 0.500, 0.750, 1.000], 22 | [0.000, 0.250, 0.500, 0.750, 1.000], 23 | [0.000, 0.250, 0.500, 0.750, 1.000], 24 | [0.000, 0.250, 0.500, 0.750, 1.000]] 25 | bb = [[4.000, 4.000, 4.000, 4.000, 4.000], 26 | [4.000, 4.000, 4.000, 4.000, 4.000], 27 | [4.000, 4.000, 4.000, 4.000, 4.000], 28 | [4.000, 4.000, 4.000, 4.000, 4.000], 29 | [4.000, 4.000, 4.000, 4.000, 4.000]] 30 | gg = [[-0.990, -0.990, -0.990, -0.990, -0.990], 31 | [-0.495, -0.495, -0.495, -0.495, -0.495], 32 | [0.000, 0.000, 0.000, 0.000, 0.000], 33 | [0.495, 0.495, 0.495, 0.495, 0.495], 34 | [0.990, 0.990, 0.990, 0.990, 0.990]] 35 | ur1 = [[0.000, 0.121, 0.258, 0.437, 0.788], 36 | [0.000, 0.085, 0.187, 0.335, 0.769], 37 | [0.000, 0.046, 0.115, 0.242, 0.691], 38 | [0.000, 0.016, 0.048, 0.129, 0.512], 39 | [0.000, 0.000, 0.001, 0.002, 0.008]] 40 | ut1 = [[0.018, 0.020, 0.025, 0.046, 0.212], 41 | [0.018, 0.020, 0.025, 0.041, 0.231], 42 | [0.018, 0.023, 0.032, 0.061, 0.309], 43 | [0.018, 0.030, 0.055, 0.124, 0.488], 44 | [0.018, 0.049, 0.133, 0.361, 0.992]] 45 | 46 | np.testing.assert_allclose(grid.a, aa, atol=1e-5) 47 | np.testing.assert_allclose(grid.b, bb, atol=1e-5) 48 | np.testing.assert_allclose(grid.g, gg, atol=1e-5) 49 | np.testing.assert_allclose(grid.ur1, ur1, atol=1e-2) 50 | np.testing.assert_allclose(grid.ut1, ut1, atol=1e-2) 51 | 52 | def test_grid_02(self): 53 | """Matched slab with search_bg.""" 54 | fixed_a = 0.5 55 | exp = iadpython.Experiment(r=0.1, t=0.5, default_a=fixed_a) 56 | exp.determine_search() 57 | grid = iadpython.Grid(N=5) 58 | grid.calc(exp, default=fixed_a) 59 | 60 | aa = [[0.5, 0.5, 0.5, 0.5, 0.5], 61 | [0.5, 0.5, 0.5, 0.5, 0.5], 62 | [0.5, 0.5, 0.5, 0.5, 0.5], 63 | [0.5, 0.5, 0.5, 0.5, 0.5], 64 | [0.5, 0.5, 0.5, 0.5, 0.5]] 65 | bb = [[0.000, 2.500, 5.000, 7.500, 10.000], 66 | [0.000, 2.500, 5.000, 7.500, 10.000], 67 | [0.000, 2.500, 5.000, 7.500, 10.000], 68 | [0.000, 2.500, 5.000, 7.500, 10.000], 69 | [0.000, 2.500, 5.000, 7.500, 10.000]] 70 | 71 | gg = [[-0.990, -0.990, -0.990, -0.990, -0.990], 72 | [-0.495, -0.495, -0.495, -0.495, -0.495], 73 | [0.000, 0.000, 0.000, 0.000, 0.000], 74 | [0.495, 0.495, 0.495, 0.495, 0.495], 75 | [0.990, 0.990, 0.990, 0.990, 0.990]] 76 | 77 | np.testing.assert_allclose(grid.a, aa, atol=1e-5) 78 | np.testing.assert_allclose(grid.b, bb, atol=1e-5) 79 | np.testing.assert_allclose(grid.g, gg, atol=1e-5) 80 | 81 | print(grid) 82 | 83 | def test_grid_03(self): 84 | """Matched slab with search_ab.""" 85 | fixed_g = 0.9 86 | exp = iadpython.Experiment(r=0.1, t=0.5, default_g=fixed_g) 87 | exp.determine_search() 88 | grid = iadpython.Grid(N=5) 89 | grid.calc(exp, default=fixed_g) 90 | 91 | aa = [[0.000, 0.250, 0.500, 0.750, 1.000], 92 | [0.000, 0.250, 0.500, 0.750, 1.000], 93 | [0.000, 0.250, 0.500, 0.750, 1.000], 94 | [0.000, 0.250, 0.500, 0.750, 1.000], 95 | [0.000, 0.250, 0.500, 0.750, 1.000]] 96 | bb = [[0.000, 0.000, 0.000, 0.000, 0.000], 97 | [2.500, 2.500, 2.500, 2.500, 2.500], 98 | [5.000, 5.000, 5.000, 5.000, 5.000], 99 | [7.500, 7.500, 7.500, 7.500, 7.500], 100 | [10.00, 10.00, 10.00, 10.00, 10.00]] 101 | gg = [[0.900, 0.900, 0.900, 0.900, 0.900], 102 | [0.900, 0.900, 0.900, 0.900, 0.900], 103 | [0.900, 0.900, 0.900, 0.900, 0.900], 104 | [0.900, 0.900, 0.900, 0.900, 0.900], 105 | [0.900, 0.900, 0.900, 0.900, 0.900]] 106 | 107 | np.testing.assert_allclose(grid.a, aa, atol=1e-5) 108 | np.testing.assert_allclose(grid.b, bb, atol=1e-5) 109 | np.testing.assert_allclose(grid.g, gg, atol=1e-5) 110 | 111 | def test_grid_04(self): 112 | """Verify that minimum values are returned.""" 113 | fixed_b = 4 114 | exp = iadpython.Experiment(r=0.1, t=0.5, default_b=fixed_b) 115 | exp.determine_search() 116 | grid = iadpython.Grid(N=21) 117 | grid.calc(exp, default=fixed_b) 118 | a, b, g = grid.min_abg(0.1, 0.5) 119 | 120 | self.assertAlmostEqual(a, 0.9, delta=1e-5) 121 | self.assertAlmostEqual(b, 4, delta=1e-5) 122 | self.assertAlmostEqual(g, 0.792, delta=1e-5) 123 | 124 | 125 | if __name__ == '__main__': 126 | unittest.main() 127 | -------------------------------------------------------------------------------- /tests/test_iadcommand.py: -------------------------------------------------------------------------------- 1 | """Tests for the command line wrapper.""" 2 | import sys 3 | import os 4 | import unittest 5 | import argparse 6 | from unittest.mock import patch 7 | 8 | # Calculate the path to the iadpython package so pytest works from any directory 9 | current_dir = os.path.dirname(os.path.abspath(__file__)) 10 | parent_dir = os.path.dirname(current_dir) 11 | iadpython_package_dir = os.path.join(parent_dir, 'iadpython') 12 | sys.path.append(iadpython_package_dir) 13 | 14 | # Now we can import iadcommand 15 | import iadcommand 16 | 17 | 18 | class TestCommandLineArgs(unittest.TestCase): 19 | """Tests for validator functions.""" 20 | 21 | def test_validator_01_valid(self): 22 | """Valid test that 01.""" 23 | self.assertEqual(iadcommand.validator_01("0.5"), 0.5) 24 | 25 | def test_validator_01_invalid(self): 26 | """Invalid test for 11.""" 27 | with self.assertRaises(argparse.ArgumentTypeError): 28 | iadcommand.validator_01("-1") 29 | 30 | def test_validator_11_valid(self): 31 | """Valid test that 11.""" 32 | self.assertEqual(iadcommand.validator_11("-0.5"), -0.5) 33 | 34 | def test_validator_11_invalid(self): 35 | """Invalid test for 11.""" 36 | with self.assertRaises(argparse.ArgumentTypeError): 37 | iadcommand.validator_11("2") 38 | 39 | def test_validator_positive_valid(self): 40 | """Valid test for positive.""" 41 | self.assertEqual(iadcommand.validator_positive("10"), 10.0) 42 | 43 | def test_validator_positive_invalid(self): 44 | """Invalid test for positive.""" 45 | with self.assertRaises(argparse.ArgumentTypeError): 46 | iadcommand.validator_positive("-5") 47 | 48 | 49 | class TestIadFile(unittest.TestCase): 50 | """Tests for rxt files.""" 51 | 52 | def test_valid_arguments(self): 53 | """Simple test.""" 54 | test_args = ['iadcommand.py', 'data/basic-A.rxt'] 55 | with self.assertRaises(SystemExit): 56 | with patch('sys.argv', test_args): 57 | iadcommand.main() 58 | 59 | 60 | class TestIadCommandForward(unittest.TestCase): 61 | """Test forward calculation scenarios.""" 62 | 63 | def test_only_albedo(self): 64 | """Test with only albedo provided.""" 65 | test_args = ['iadcommand.py', '-z', '-a', '0.5'] 66 | with self.assertRaises(SystemExit): 67 | with patch('sys.argv', test_args): 68 | iadcommand.main() 69 | 70 | def test_only_optical_thickness(self): 71 | """Test with only optical thickness provided.""" 72 | test_args = ['iadcommand.py', '-z', '-b', '1'] 73 | with self.assertRaises(SystemExit): 74 | with patch('sys.argv', test_args): 75 | iadcommand.main() 76 | 77 | def test_albedo_and_optical_thickness(self): 78 | """Test with albedo and optical thickness.""" 79 | test_args = ['iadcommand.py', '-z', '-a', '0.1', '-b', '1'] 80 | with patch('sys.argv', test_args): 81 | with self.assertRaises(SystemExit): # Expecting the program to exit 82 | iadcommand.main() 83 | 84 | def test_albedo_and_optical_thickness_and_g(self): 85 | """Test with albedo and optical thickness and g.""" 86 | test_args = ['iadcommand.py', '-z', '-a', '0.1', '-b', '1', '-g', '0.9'] 87 | with self.assertRaises(SystemExit): 88 | with patch('sys.argv', test_args): 89 | iadcommand.main() 90 | 91 | def test_slab_index(self): 92 | """Test with slab index.""" 93 | test_args = ['iadcommand.py', '-z', '-a', '0.1', '-n', '1.4'] 94 | with self.assertRaises(SystemExit): 95 | with patch('sys.argv', test_args): 96 | iadcommand.main() 97 | 98 | def test_slab_and_slide_index(self): 99 | """Test with slab and slide index.""" 100 | test_args = ['iadcommand.py', '-z', '-a', '0.1', '-n', '1.4', '-n', '1.5'] 101 | with self.assertRaises(SystemExit): 102 | with patch('sys.argv', test_args): 103 | iadcommand.main() 104 | 105 | def test_only_absorption_coefficient(self): 106 | """Test with only absorption coefficient provided.""" 107 | test_args = ['iadcommand.py', '-z', '--mua', '0.1'] 108 | with self.assertRaises(SystemExit): 109 | with patch('sys.argv', test_args): 110 | iadcommand.main() 111 | 112 | def test_only_scattering_coefficient(self): 113 | """Test with only scattering coefficient provided.""" 114 | test_args = ['iadcommand.py', '-z', '--mus', '10'] 115 | with self.assertRaises(SystemExit): 116 | with patch('sys.argv', test_args): 117 | iadcommand.main() 118 | 119 | def test_only_anisotropy(self): 120 | """Test with only anisotropy provided.""" 121 | test_args = ['iadcommand.py', '-z', '-g', '0.9'] 122 | with self.assertRaises(SystemExit): 123 | with patch('sys.argv', test_args): 124 | iadcommand.main() 125 | 126 | def test_scattering_and_absorption(self): 127 | """Test with scattering and absorption.""" 128 | test_args = ['iadcommand.py', '-z', '--mua', '0.1', '--mus', '10'] 129 | with self.assertRaises(SystemExit): 130 | with patch('sys.argv', test_args): 131 | iadcommand.main() 132 | 133 | def test_scattering_and_absorption_and_g(self): 134 | """Test with scattering and absorption and g.""" 135 | test_args = ['iadcommand.py', '-z', '--mua', '0.1', '--mus', '10', '-g', '0.9'] 136 | with self.assertRaises(SystemExit): 137 | with patch('sys.argv', test_args): 138 | iadcommand.main() 139 | 140 | def test_invalid_albedo(self): 141 | """Test with invalid albedo value.""" 142 | test_args = ['iadcommand.py', '-z', '-a', '-1'] 143 | with patch('sys.argv', test_args): 144 | with self.assertRaises(SystemExit): 145 | iadcommand.main() 146 | 147 | def test_invalid_optical_thickness(self): 148 | """Test example with invalid argument values.""" 149 | test_args = ['iadcommand.py', '-z', '-a', '0.5', '-b', '-1'] 150 | with patch('sys.argv', test_args): 151 | with self.assertRaises(SystemExit): # Expecting the program to exit 152 | iadcommand.main() 153 | 154 | def test_forward_calculation_missing_albedo(self): 155 | """Test example when albedo is not provided.""" 156 | test_args = ['iadcommand.py', '-z', '-b', '1', '-g', '0.9'] 157 | with patch('sys.argv', test_args): 158 | with self.assertRaises(SystemExit): # Expecting the program to exit 159 | iadcommand.main() 160 | 161 | def test_invalid_scattering(self): 162 | """Test example with invalid argument values.""" 163 | test_args = ['iadcommand.py', '-z', '--mus', '-0.5'] 164 | with patch('sys.argv', test_args): 165 | with self.assertRaises(SystemExit): # Expecting the program to exit 166 | iadcommand.main() 167 | 168 | def test_invalid_absorption(self): 169 | """Test example with invalid argument values.""" 170 | test_args = ['iadcommand.py', '-z', '--mua', '-0.5'] 171 | with patch('sys.argv', test_args): 172 | with self.assertRaises(SystemExit): # Expecting the program to exit 173 | iadcommand.main() 174 | 175 | def test_invalid_anisotropy(self): 176 | """Test example with invalid argument values.""" 177 | test_args = ['iadcommand.py', '-z', '-g', '1.1'] 178 | with patch('sys.argv', test_args): 179 | with self.assertRaises(SystemExit): # Expecting the program to exit 180 | iadcommand.main() 181 | 182 | 183 | class TestIadSingle(unittest.TestCase): 184 | """Single sphere tests.""" 185 | 186 | def test_inverse_missing(self): 187 | """Test with no arguments.""" 188 | test_args = ['iadcommand.py'] 189 | with patch('sys.argv', test_args): 190 | with self.assertRaises(SystemExit): 191 | iadcommand.main() 192 | 193 | def test_inverse_reflection(self): 194 | """Test with invalid albedo value.""" 195 | test_args = ['iadcommand.py', '-r', '0.5'] 196 | with patch('sys.argv', test_args): 197 | with self.assertRaises(SystemExit): 198 | iadcommand.main() 199 | 200 | def test_inverse_transmission(self): 201 | """Test with invalid albedo value.""" 202 | test_args = ['iadcommand.py', '-t', '0.5'] 203 | with patch('sys.argv', test_args): 204 | with self.assertRaises(SystemExit): 205 | iadcommand.main() 206 | 207 | def test_inverse_unscattered(self): 208 | """Test with invalid albedo value.""" 209 | test_args = ['iadcommand.py', '-u', '0.5'] 210 | with patch('sys.argv', test_args): 211 | with self.assertRaises(SystemExit): 212 | iadcommand.main() 213 | 214 | 215 | if __name__ == '__main__': 216 | unittest.main() 217 | -------------------------------------------------------------------------------- /tests/test_layer.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | """Tests for finite layer thicknesses.""" 4 | 5 | import unittest 6 | import numpy as np 7 | import iadpython 8 | 9 | 10 | class LayerTest(unittest.TestCase): 11 | """Starting layer calculations.""" 12 | 13 | def test_00_double(self): 14 | """Starting layer thickness calculation.""" 15 | s = iadpython.ad.Sample(a=0.5, b=1, g=0.0, n=1, quad_pts=4) 16 | b_min = iadpython.start.starting_thickness(s) 17 | np.testing.assert_approx_equal(b_min, 0.0625) 18 | 19 | def test_01_double(self): 20 | """Adding isotropic layers.""" 21 | s = iadpython.ad.Sample(a=0.5, b=1, g=0.0, n=1, quad_pts=4) 22 | rr, tt = iadpython.start.thinnest_layer(s) 23 | 24 | # b = 0.12500 25 | rr, tt = iadpython.add_layers_basic(s, rr, tt, rr, rr, tt, tt) 26 | R = np.array([[0.72851, 0.22898, 0.12610, 0.10068], 27 | [0.22898, 0.07678, 0.04267, 0.03414], 28 | [0.12610, 0.04267, 0.02374, 0.01900], 29 | [0.10068, 0.03414, 0.01900, 0.01521]]) 30 | 31 | T = np.array([[6.44003, 0.21795, 0.12290, 0.09867], 32 | [0.21795, 2.39264, 0.04243, 0.03399], 33 | [0.12290, 0.04243, 1.67063, 0.01896], 34 | [0.09867, 0.03399, 0.01896, 7.07487]]) 35 | 36 | np.testing.assert_allclose(R, rr, atol=1e-5) 37 | np.testing.assert_allclose(T, tt, atol=1e-5) 38 | 39 | # b = 0.25000 40 | rr, tt = iadpython.add_layers_basic(s, rr, tt, rr, rr, tt, tt) 41 | R = np.array([[0.78844, 0.28216, 0.15973, 0.12840], 42 | [0.28216, 0.12615, 0.07411, 0.06010], 43 | [0.15973, 0.07411, 0.04379, 0.03556], 44 | [0.12840, 0.06010, 0.03556, 0.02889]]) 45 | 46 | T = np.array([[1.64804, 0.22726, 0.14271, 0.11749], 47 | [0.22726, 1.82607, 0.07217, 0.05886], 48 | [0.14271, 0.07217, 1.44835, 0.03518], 49 | [0.11749, 0.05886, 0.03518, 6.25854]]) 50 | 51 | np.testing.assert_allclose(R, rr, atol=1e-5) 52 | np.testing.assert_allclose(T, tt, atol=1e-5) 53 | 54 | # b = 0.50000 55 | rr, tt = iadpython.add_layers_basic(s, rr, tt, rr, rr, tt, tt) 56 | R = np.array([[0.79808, 0.30346, 0.17574, 0.14215], 57 | [0.30346, 0.17598, 0.11217, 0.09291], 58 | [0.17574, 0.11217, 0.07299, 0.06076], 59 | [0.14215, 0.09291, 0.06076, 0.05064]]) 60 | 61 | T = np.array([[0.13566, 0.15475, 0.12350, 0.10763], 62 | [0.15475, 1.06976, 0.10102, 0.08554], 63 | [0.12350, 0.10102, 1.09191, 0.05817], 64 | [0.10763, 0.08554, 0.05817, 4.90038]]) 65 | 66 | np.testing.assert_allclose(R, rr, atol=1e-5) 67 | np.testing.assert_allclose(T, tt, atol=1e-5) 68 | 69 | # b = 1.00000 70 | rr, tt = iadpython.add_layers_basic(s, rr, tt, rr, rr, tt, tt) 71 | R = np.array([[0.80010, 0.31085, 0.18343, 0.14931], 72 | [0.31085, 0.20307, 0.14031, 0.11912], 73 | [0.18343, 0.14031, 0.10265, 0.08848], 74 | [0.14931, 0.11912, 0.08848, 0.07658]]) 75 | 76 | T = np.array([[0.01792, 0.06195, 0.07718, 0.07528], 77 | [0.06195, 0.37419, 0.09621, 0.08830], 78 | [0.07718, 0.09621, 0.62536, 0.07502], 79 | [0.07528, 0.08830, 0.07502, 3.00924]]) 80 | 81 | np.testing.assert_allclose(R, rr, atol=1e-5) 82 | np.testing.assert_allclose(T, tt, atol=1e-5) 83 | 84 | def test_02_double(self): 85 | """Isotropic slab with matched boundaries.""" 86 | s = iadpython.ad.Sample(a=0.5, b=1, g=0.0, n=1, quad_pts=4) 87 | rr, tt = iadpython.simple_layer_matrices(s) 88 | 89 | R = np.array([[0.80010, 0.31085, 0.18343, 0.14931], 90 | [0.31085, 0.20307, 0.14031, 0.11912], 91 | [0.18343, 0.14031, 0.10265, 0.08848], 92 | [0.14931, 0.11912, 0.08848, 0.07658]]) 93 | 94 | T = np.array([[0.01792, 0.06195, 0.07718, 0.07528], 95 | [0.06195, 0.37419, 0.09621, 0.08830], 96 | [0.07718, 0.09621, 0.62536, 0.07502], 97 | [0.07528, 0.08830, 0.07502, 3.00924]]) 98 | 99 | np.testing.assert_allclose(R, rr, atol=1e-5) 100 | np.testing.assert_allclose(T, tt, atol=1e-5) 101 | 102 | def test_03_double(self): 103 | """Anisotropic slab with matched boundaries.""" 104 | s = iadpython.ad.Sample(a=0.5, b=1, g=0.9, n=1, quad_pts=4) 105 | rr, tt = iadpython.simple_layer_matrices(s) 106 | 107 | R = np.array([[0.57675, 0.15818, 0.03296, -0.00369], 108 | [0.15818, 0.04710, 0.00836, 0.01059], 109 | [0.03296, 0.00836, 0.00434, 0.00708], 110 | [-0.00369, 0.01059, 0.00708, -0.00831]]) 111 | 112 | T = np.array([[0.01817, 0.06990, 0.03529, 0.00115], 113 | [0.06990, 0.71075, 0.06555, 0.02857], 114 | [0.03529, 0.06555, 0.91100, 0.10190], 115 | [0.00115, 0.02857, 0.10190, 4.24324]]) 116 | 117 | np.testing.assert_allclose(R, rr, atol=1e-5) 118 | np.testing.assert_allclose(T, tt, atol=1e-5) 119 | 120 | 121 | if __name__ == '__main__': 122 | unittest.main() 123 | -------------------------------------------------------------------------------- /tests/test_layers.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | """ 4 | Tests for multi-layer samples. 5 | 6 | These tests are incomplete. 7 | """ 8 | 9 | import unittest 10 | import numpy as np 11 | import iadpython 12 | 13 | 14 | class LayeredSample(unittest.TestCase): 15 | """Layered Sample calculations.""" 16 | 17 | def test_00_layers(self): 18 | """Two identical non-scattering layers without boundaries.""" 19 | s = iadpython.Sample(quad_pts=4) 20 | s.a = np.array([0.0, 0.0]) 21 | s.b = np.array([0.5, 0.5]) 22 | s.g = np.array([0.0, 0.0]) 23 | rr, tt = iadpython.simple_layer_matrices(s) 24 | 25 | s.a = 0.0 26 | s.b = 1.0 27 | s.g = 0.0 28 | R, T = iadpython.simple_layer_matrices(s) 29 | 30 | np.testing.assert_allclose(R, rr, atol=1e-5) 31 | np.testing.assert_allclose(T, tt, atol=1e-5) 32 | 33 | def test_01_layers(self): 34 | """Three identical non-scattering layers with boundaries.""" 35 | s = iadpython.Sample(n=1.4, n_above=1.5, n_below=1.5, quad_pts=16) 36 | s.a = np.array([0.0, 0.0, 0.0]) 37 | s.b = np.array([0.5, 0.2, 0.3]) 38 | s.g = np.array([0.9, 0.9, 0.9]) 39 | 40 | rr, _, tt, _ = s.rt_matrices() 41 | s.a = 0.0 42 | s.b = 1.0 43 | s.g = 0.0 44 | R, _, T, _ = s.rt_matrices() 45 | 46 | np.testing.assert_allclose(R, rr, atol=1e-4) 47 | np.testing.assert_allclose(T, tt, atol=1e-4) 48 | 49 | def test_02_layers(self): 50 | """Two identical isotropic scattering layers without boundaries.""" 51 | s = iadpython.Sample(quad_pts=4) 52 | s.a = np.array([0.5, 0.5]) 53 | s.b = np.array([0.5, 0.5]) 54 | s.g = np.array([0, 0]) 55 | 56 | rr, tt = iadpython.simple_layer_matrices(s) 57 | R = np.array([[0.80010, 0.31085, 0.18343, 0.14931], 58 | [0.31085, 0.20307, 0.14031, 0.11912], 59 | [0.18343, 0.14031, 0.10265, 0.08848], 60 | [0.14931, 0.11912, 0.08848, 0.07658]]) 61 | 62 | T = np.array([[0.01792, 0.06195, 0.07718, 0.07528], 63 | [0.06195, 0.37419, 0.09621, 0.08830], 64 | [0.07718, 0.09621, 0.62536, 0.07502], 65 | [0.07528, 0.08830, 0.07502, 3.00924]]) 66 | 67 | np.testing.assert_allclose(R, rr, atol=1e-5) 68 | np.testing.assert_allclose(T, tt, atol=1e-5) 69 | 70 | def test_03_layers(self): 71 | """Three identical isotropic scattering layers without boundaries.""" 72 | s = iadpython.Sample(quad_pts=4) 73 | s.a = np.array([0.5, 0.5, 0.5]) 74 | s.b = np.array([0.5, 0.2, 0.3]) 75 | s.g = np.array([0.0, 0.0, 0.0]) 76 | 77 | rr, tt = iadpython.simple_layer_matrices(s) 78 | R = np.array([[0.80010, 0.31085, 0.18343, 0.14931], 79 | [0.31085, 0.20307, 0.14031, 0.11912], 80 | [0.18343, 0.14031, 0.10265, 0.08848], 81 | [0.14931, 0.11912, 0.08848, 0.07658]]) 82 | 83 | T = np.array([[0.01792, 0.06195, 0.07718, 0.07528], 84 | [0.06195, 0.37419, 0.09621, 0.08830], 85 | [0.07718, 0.09621, 0.62536, 0.07502], 86 | [0.07528, 0.08830, 0.07502, 3.00924]]) 87 | 88 | np.testing.assert_allclose(R, rr, atol=1e-4) 89 | np.testing.assert_allclose(T, tt, atol=1e-4) 90 | 91 | def test_04_layers(self): 92 | """Three identical layers without boundaries.""" 93 | s = iadpython.Sample(quad_pts=16) 94 | s.a = np.array([0.5, 0.5, 0.5]) 95 | s.b = np.array([0.5, 0.2, 0.3]) 96 | s.g = np.array([0.9, 0.9, 0.9]) 97 | 98 | rr, tt = iadpython.simple_layer_matrices(s) 99 | s.a = 0.5 100 | s.b = 1 101 | s.g = 0.9 102 | R, T = iadpython.simple_layer_matrices(s) 103 | 104 | np.testing.assert_allclose(R, rr, atol=1e-5) 105 | np.testing.assert_allclose(T, tt, atol=1e-5) 106 | 107 | def test_05_layers(self): 108 | """Three identical layers with boundaries.""" 109 | s = iadpython.Sample(n=1.4, n_above=1.5, n_below=1.5, quad_pts=16) 110 | s.a = np.array([0.5, 0.5, 0.5]) 111 | s.b = np.array([0.5, 0.2, 0.3]) 112 | s.g = np.array([0.9, 0.9, 0.9]) 113 | 114 | rr, _, tt, _ = s.rt_matrices() 115 | s.a = 0.5 116 | s.b = 1 117 | s.g = 0.9 118 | R, _, T, _ = s.rt_matrices() 119 | 120 | np.testing.assert_allclose(R, rr, atol=6e-5) 121 | np.testing.assert_allclose(T, tt, atol=6e-5) 122 | 123 | 124 | if __name__ == '__main__': 125 | unittest.main() 126 | -------------------------------------------------------------------------------- /tests/test_nist.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | # pylint: disable=bad-whitespace 3 | 4 | """Tests for NIST reflectance data.""" 5 | 6 | import unittest 7 | import numpy as np 8 | import iadpython 9 | 10 | 11 | class TestNIST(unittest.TestCase): 12 | """NIST Reflectance data.""" 13 | 14 | def test_01_subj_refl(self): 15 | """Verify that subject 1 is read correctly.""" 16 | subject_number = 1 17 | λ, r1, r2, r3, rave = iadpython.nist.subject_reflectances(subject_number) 18 | np.testing.assert_allclose([λ[0]], [250.], atol=1e-5) 19 | np.testing.assert_allclose([λ[-1]], [2500.], atol=1e-5) 20 | np.testing.assert_allclose([r1[0]], [0.0611], atol=1e-5) 21 | np.testing.assert_allclose([r2[0]], [0.0563], atol=1e-5) 22 | np.testing.assert_allclose([r3[0]], [0.0546], atol=1e-5) 23 | np.testing.assert_allclose([rave[0]], [0.0573], atol=1e-5) 24 | np.testing.assert_allclose([r1[-1]], [0.0362], atol=1e-5) 25 | np.testing.assert_allclose([r2[-1]], [0.0311], atol=1e-5) 26 | np.testing.assert_allclose([r3[-1]], [0.0303], atol=1e-5) 27 | np.testing.assert_allclose([rave[-1]], [0.0325], atol=1e-5) 28 | 29 | def test_02_subj_refl(self): 30 | """Verify that subject 100 is read correctly.""" 31 | subject_number = 100 32 | λ, r1, r2, r3, rave = iadpython.nist.subject_reflectances(subject_number) 33 | np.testing.assert_allclose([λ[0]], [250.], atol=1e-5) 34 | np.testing.assert_allclose([λ[-1]], [2500.], atol=1e-5) 35 | np.testing.assert_allclose([r1[0]], [0.0565], atol=1e-5) 36 | np.testing.assert_allclose([r2[0]], [0.0543], atol=1e-5) 37 | np.testing.assert_allclose([r3[0]], [0.0533], atol=1e-5) 38 | np.testing.assert_allclose([rave[0]], [0.0547], atol=1e-5) 39 | np.testing.assert_allclose([r1[-1]], [0.0404], atol=1e-5) 40 | np.testing.assert_allclose([r2[-1]], [0.0433], atol=1e-5) 41 | np.testing.assert_allclose([r3[-1]], [0.0405], atol=1e-5) 42 | np.testing.assert_allclose([rave[-1]], [0.0414], atol=1e-5) 43 | 44 | def test_03_subj_refl(self): 45 | """Verify that subject 5 is read correctly.""" 46 | subject_number = 5 47 | λ, r1, r2, r3, rave = iadpython.nist.subject_reflectances(subject_number) 48 | np.testing.assert_allclose([λ[0]], [250.], atol=1e-5) 49 | np.testing.assert_allclose([λ[-1]], [2500.], atol=1e-5) 50 | np.testing.assert_allclose([r1[0]], [0.0580], atol=1e-5) 51 | np.testing.assert_allclose([r2[0]], [0.0565], atol=1e-5) 52 | np.testing.assert_allclose([r3[0]], [0.0573], atol=1e-5) 53 | np.testing.assert_allclose([rave[0]], [0.0573], atol=1e-5) 54 | np.testing.assert_allclose([r1[-1]], [0.0460], atol=1e-5) 55 | np.testing.assert_allclose([r2[-1]], [0.0440], atol=1e-5) 56 | np.testing.assert_allclose([r3[-1]], [0.0393], atol=1e-5) 57 | np.testing.assert_allclose([rave[-1]], [0.0431], atol=1e-5) 58 | 59 | def test_01_subj_ave_refl(self): 60 | """Verify that average reflectance of subject 1 read correctly.""" 61 | subject_number = 1 62 | λ, rave = iadpython.nist.subject_average_reflectance(subject_number) 63 | np.testing.assert_allclose([λ[0]], [250.], atol=1e-5) 64 | np.testing.assert_allclose([λ[-1]], [2500.], atol=1e-5) 65 | np.testing.assert_allclose([rave[0]], [0.0573], atol=1e-5) 66 | np.testing.assert_allclose([rave[-1]], [0.0325], atol=1e-5) 67 | 68 | def test_02_subj_ave_refl(self): 69 | """Verify that average reflectance of subject 100 read correctly.""" 70 | subject_number = 100 71 | λ, rave = iadpython.nist.subject_average_reflectance(subject_number) 72 | np.testing.assert_allclose([λ[0]], [250.], atol=1e-5) 73 | np.testing.assert_allclose([λ[-1]], [2500.], atol=1e-5) 74 | np.testing.assert_allclose([rave[0]], [0.0547], atol=1e-5) 75 | np.testing.assert_allclose([rave[-1]], [0.0414], atol=1e-5) 76 | 77 | def test_03_subj_ave_refl(self): 78 | """Verify that average reflectance of subject 100 read correctly.""" 79 | subject_number = 5 80 | λ, rave = iadpython.nist.subject_average_reflectance(subject_number) 81 | np.testing.assert_allclose([λ[0]], [250.], atol=1e-5) 82 | np.testing.assert_allclose([λ[-1]], [2500.], atol=1e-5) 83 | np.testing.assert_allclose([rave[0]], [0.0573], atol=1e-5) 84 | np.testing.assert_allclose([rave[-1]], [0.0431], atol=1e-5) 85 | 86 | def test_01_all_ave_refl(self): 87 | """Verify that all average spectra read correctly.""" 88 | λ, R = iadpython.nist.all_average_reflectances() 89 | np.testing.assert_allclose([λ[0]], [250.], atol=1e-5) 90 | np.testing.assert_allclose([λ[-1]], [2500.], atol=1e-5) 91 | entries, subjects = R.shape 92 | 93 | def test_02_all_ave_refl(self): 94 | """Verify entries for all average spectra read correctly.""" 95 | λ, R = iadpython.nist.all_average_reflectances() 96 | # subj 1 reflectances 97 | np.testing.assert_allclose([R[0, 0]], [0.0573], atol=1e-5) 98 | np.testing.assert_allclose([R[-1, 0]], [0.0325], atol=1e-5) 99 | 100 | # subj 100 reflectances 101 | np.testing.assert_allclose([R[0, 99]], [0.0547], atol=1e-5) 102 | np.testing.assert_allclose([R[-1, 99]], [0.0414], atol=1e-5) 103 | 104 | # subj 5 reflectances 105 | np.testing.assert_allclose([R[0, 4]], [0.0573], atol=1e-5) 106 | np.testing.assert_allclose([R[-1, 4]], [0.0431], atol=1e-5) 107 | 108 | 109 | if __name__ == '__main__': 110 | unittest.main() 111 | -------------------------------------------------------------------------------- /tests/test_port.py: -------------------------------------------------------------------------------- 1 | """Tests for the Port class.""" 2 | import unittest 3 | import numpy as np 4 | from iadpython import Sphere, Port 5 | 6 | 7 | class TestPort(unittest.TestCase): 8 | """Unit tests for the Port class.""" 9 | 10 | def setUp(self): 11 | """Set up test conditions for Port tests.""" 12 | d_sample = 25 # 20 mm sample port 13 | d_sphere = 200 # 200 mm diameter sphere 14 | R = d_sphere / 2 15 | d_port = 20 16 | uru_port = 0.5 17 | self.sphere = Sphere(d_sphere, d_sample) 18 | self.sphere.z = R 19 | self.port = Port(self.sphere, d_port, uru=uru_port, z=R) # Example port 20 | 21 | def test_cap_area(self): 22 | """Test the cap_area method.""" 23 | area = self.port.cap_area_exact() 24 | self.assertTrue(np.isclose(area, 314.94861522998946), "Cap area calculation is incorrect") 25 | 26 | def test_approx_relative_cap_area(self): 27 | """Test the approx_relative_cap_area method.""" 28 | approx_area = self.port.relative_cap_area() 29 | self.assertTrue(np.isclose(approx_area, 0.0025), "Approximate relative cap area calculation is incorrect") 30 | 31 | def test_calculate_sagitta(self): 32 | """Test the calculate_sagitta method.""" 33 | sagitta = self.port.calculate_sagitta() 34 | self.assertTrue(np.isclose(sagitta, 0.5012562893380021), "Sagitta calculation is incorrect") 35 | 36 | def test_max_center_chord(self): 37 | """Test the max_center_chord method.""" 38 | max_chord = self.port.max_center_chord() 39 | self.assertTrue(np.isclose(max_chord, 10.012555011963775), "Max center chord calculation is incorrect") 40 | 41 | def test_relative_cap_area(self): 42 | """Test the relative_cap_area method.""" 43 | rel_area = self.port.relative_cap_area_exact() 44 | self.assertTrue(np.isclose(rel_area, 0.002506281446690011), "Relative cap area calculation is incorrect") 45 | 46 | def test_hit(self): 47 | """Test the hit method.""" 48 | self.assertTrue(self.port.hit(), "Hit detection is incorrect") 49 | 50 | def test_uniform_01(self): 51 | """Test the generating points on the top of the sphere.""" 52 | for i in range(20): 53 | x, y, z = self.port.uniform() 54 | self.assertTrue(self.port.hit(), "Hit detection is incorrect") 55 | 56 | 57 | if __name__ == '__main__': 58 | unittest.main() 59 | -------------------------------------------------------------------------------- /tests/test_quadrature.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | """Tests for Quadrature.""" 4 | 5 | import unittest 6 | import numpy as np 7 | import iadpython.quadrature as quad 8 | 9 | 10 | class GaussTest(unittest.TestCase): 11 | """Tests for Gaussian-Legendre quadrature.""" 12 | 13 | def test_01_gaussian(self): 14 | """Gaussian quadrature with default endpoints.""" 15 | n = 8 16 | x, w = quad.gauss(n) 17 | xx = np.empty(n) 18 | ww = np.empty(n) 19 | xx[4] = -0.1834346424956498 20 | xx[3] = 0.1834346424956498 21 | xx[5] = -0.5255324099163290 22 | xx[2] = 0.5255324099163290 23 | xx[6] = -0.7966664774136267 24 | xx[1] = 0.7966664774136267 25 | xx[7] = -0.9602898564975363 26 | xx[0] = 0.9602898564975363 27 | 28 | ww[4] = 0.3626837833783620 29 | ww[3] = 0.3626837833783620 30 | ww[5] = 0.3137066458778873 31 | ww[2] = 0.3137066458778873 32 | ww[6] = 0.2223810344533745 33 | ww[1] = 0.2223810344533745 34 | ww[7] = 0.1012285362903763 35 | ww[0] = 0.1012285362903763 36 | xx = np.flip(xx) 37 | ww = np.flip(ww) 38 | np.testing.assert_allclose(x, xx) 39 | np.testing.assert_allclose(w, ww) 40 | 41 | def test_02_gaussian(self): 42 | """Gaussian quadrature with specific endpoints.""" 43 | n = 8 44 | a = -7 45 | b = 2 46 | x, w = quad.gauss(n, a, b) 47 | 48 | # integral of x^6 from 0 to 2 should be (x^7)/7 49 | quad_int = np.sum(x**6 * w) 50 | anal_int = (b)**7 / 7 - (a)**7 / 7 51 | np.testing.assert_approx_equal(quad_int, anal_int) 52 | 53 | def test_03_gaussian(self): 54 | """Gaussian quadrature with endpoint test.""" 55 | n = 5 56 | x, _ = quad.gauss(n) 57 | self.assertLess(-1, x[0]) 58 | self.assertLess(x[-1], 1) 59 | 60 | def test_04_gaussian(self): 61 | """Gaussian quadrature with endpoint test and specified endpoints.""" 62 | n = 5 63 | a = -7 64 | b = 2 65 | x, _ = quad.gauss(n, a, b) 66 | self.assertLess(a, x[0]) 67 | self.assertLess(x[-1], b) 68 | 69 | 70 | class RadauTest(unittest.TestCase): 71 | """Tests for Radau-Legendre quadrature.""" 72 | 73 | def test_01_radau(self): 74 | """Radau quadrature with default endpoints.""" 75 | x, w = quad.radau(8) 76 | xx = np.empty(8) 77 | ww = np.empty(8) 78 | xx[7] = -1 79 | xx[6] = -0.8874748789261557 80 | xx[5] = -0.6395186165262152 81 | xx[4] = -0.2947505657736607 82 | xx[3] = 0.0943072526611108 83 | xx[2] = 0.4684203544308211 84 | xx[1] = 0.7706418936781916 85 | xx[0] = 0.9550412271225750 86 | 87 | ww[7] = 2 / (8 * 8) 88 | ww[6] = 0.1853581548029793 89 | ww[5] = 0.3041306206467856 90 | ww[4] = 0.3765175453891186 91 | ww[3] = 0.3915721674524935 92 | ww[2] = 0.3470147956345014 93 | ww[1] = 0.2496479013298649 94 | ww[0] = 0.1145088147442572 95 | 96 | # the provided solutions must be adapted because the lower endpoint is assumed fixed 97 | xx *= -1 98 | np.testing.assert_allclose(x, xx) 99 | np.testing.assert_allclose(w, ww) 100 | 101 | def test_02_radau(self): 102 | """Radau quadrature with specific endpoints.""" 103 | n = 8 104 | a = -7 105 | b = 2 106 | x, w = quad.radau(n, a, b) 107 | 108 | # integral of x^6 from 0 to 2 should be (x^7)/7 109 | quad_int = np.sum(x**6 * w) 110 | anal_int = (b)**7 / 7 - (a)**7 / 7 111 | np.testing.assert_approx_equal(quad_int, anal_int) 112 | 113 | def test_03_radau(self): 114 | """Radau quadrature with endpoint test.""" 115 | n = 5 116 | x, _ = quad.radau(n) 117 | np.testing.assert_equal(x[-1], 1) 118 | self.assertLess(-1, x[0]) 119 | 120 | def test_04_radau(self): 121 | """Radau quadrature with endpoint test and specified endpoints.""" 122 | n = 5 123 | a = -7 124 | b = 2 125 | x, _ = quad.radau(n, a, b) 126 | np.testing.assert_equal(x[-1], b) 127 | self.assertLess(a, x[0]) 128 | 129 | 130 | class LobattoTest(unittest.TestCase): 131 | """Tests for Lobatto-Legendre quadrature.""" 132 | 133 | def test_01_lobatto(self): 134 | """Lobatto quadrature with default endpoints.""" 135 | x, w = quad.lobatto(8) 136 | xx = np.empty(8) 137 | ww = np.empty(8) 138 | xx[7] = -1 139 | xx[6] = -0.8717401485096066153375 140 | xx[5] = -0.5917001814331423021445 141 | xx[4] = -0.2092992179024788687687 142 | xx[3] = 0.2092992179024788687687 143 | xx[2] = 0.5917001814331423021445 144 | xx[1] = 0.8717401485096066153375 145 | xx[0] = 1 146 | 147 | ww[7] = 0.03571428571428571428571 148 | ww[6] = 0.210704227143506039383 149 | ww[5] = 0.3411226924835043647642 150 | ww[4] = 0.4124587946587038815671 151 | ww[3] = 0.412458794658703881567 152 | ww[2] = 0.341122692483504364764 153 | ww[1] = 0.210704227143506039383 154 | ww[0] = 0.0357142857142857142857 155 | 156 | xx = np.flip(xx) 157 | ww = np.flip(ww) 158 | np.testing.assert_allclose(x, xx) 159 | np.testing.assert_allclose(w, ww) 160 | 161 | def test_02_lobatto(self): 162 | """Lobatto quadrature with specific endpoints.""" 163 | n = 8 164 | a = -7 165 | b = 2 166 | x, w = quad.lobatto(n, a, b) 167 | 168 | # integral of x^6 from 0 to 2 should be (x^7)/7 169 | quad_int = np.sum(x**6 * w) 170 | anal_int = (b)**7 / 7 - (a)**7 / 7 171 | np.testing.assert_approx_equal(quad_int, anal_int) 172 | 173 | def test_03_lobatto(self): 174 | """Lobatto quadrature with endpoint test.""" 175 | n = 5 176 | x, _ = quad.lobatto(n) 177 | np.testing.assert_equal(x[-1], 1) 178 | np.testing.assert_equal(x[0], -1) 179 | 180 | def test_04_lobatto(self): 181 | """Lobatto quadrature with endpoint test and specified endpoints.""" 182 | n = 5 183 | a = -7 184 | b = 2 185 | x, _ = quad.lobatto(n, a, b) 186 | 187 | np.testing.assert_equal(x[-1], b) 188 | np.testing.assert_equal(x[0], a) 189 | 190 | 191 | if __name__ == '__main__': 192 | unittest.main() 193 | -------------------------------------------------------------------------------- /tests/test_redistribution.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | """Tests for Redistribution Function.""" 4 | 5 | import unittest 6 | import numpy as np 7 | import iadpython 8 | 9 | 10 | class redistribution(unittest.TestCase): 11 | """Redistribution function tests.""" 12 | 13 | def test_01(self): 14 | """Isotropic scattering.""" 15 | n = 4 16 | s = iadpython.ad.Sample(g=0.0, quad_pts=n) 17 | hp, hm = iadpython.redistribution.hg_legendre(s) 18 | 19 | np.testing.assert_allclose(hp, np.ones([n, n])) 20 | np.testing.assert_allclose(hm, np.ones([n, n])) 21 | 22 | def test_02(self): 23 | """Redistribution matrix for HG function with g=0.9.""" 24 | h = np.array( 25 | [[6.84908, 3.69902, 0.65844, -0.09856, 0.00000, -0.08633, 0.22946, 0.22803, -0.37395], 26 | [3.69902, 2.73731, 1.42038, 0.67022, 0.00000, 0.38894, 0.10074, 0.09250, 0.22803], 27 | [0.65844, 1.42038, 1.78555, 1.43478, 0.00000, 1.10818, 0.49081, 0.10074, 0.22946], 28 | [-0.09856, 0.67022, 1.43478, 1.57558, 0.00000, 1.49114, 1.10818, 0.38894, -0.08633], 29 | [0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000], 30 | [-0.08633, 0.38894, 1.10818, 1.49114, 0.00000, 1.57558, 1.43478, 0.67022, -0.09856], 31 | [0.22946, 0.10074, 0.49081, 1.10818, 0.00000, 1.43478, 1.78555, 1.42038, 0.65844], 32 | [0.22803, 0.09250, 0.10074, 0.38894, 0.00000, 0.67022, 1.42038, 2.73731, 3.69902], 33 | [-0.37395, 0.22803, 0.22946, -0.08633, 0.00000, -0.09856, 0.65844, 3.69902, 6.84908]]) 34 | 35 | n = 4 36 | s = iadpython.ad.Sample(g=0.9, quad_pts=n) 37 | s.update_quadrature() 38 | hp, hm = iadpython.redistribution.hg_legendre(s) 39 | 40 | hh = h[n + 1:, n + 1:] 41 | np.testing.assert_allclose(hp, hh, rtol=1e-4) 42 | 43 | hh = np.fliplr(h[n + 1:, 0:n]) 44 | np.testing.assert_allclose(hm, hh, rtol=1e-4) 45 | 46 | 47 | if __name__ == '__main__': 48 | unittest.main() 49 | -------------------------------------------------------------------------------- /tests/test_rxt.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | # pylint: disable=bad-whitespace 3 | 4 | """Tests for NIST reflectance data.""" 5 | 6 | import os 7 | import unittest 8 | import iadpython 9 | 10 | # Get the directory of the current test file 11 | test_dir = os.path.dirname(os.path.abspath(__file__)) 12 | 13 | # Path to the data directory relative to the test file 14 | data_dir = os.path.join(test_dir, 'data') 15 | 16 | 17 | class TestRXT(unittest.TestCase): 18 | """RXT files data.""" 19 | 20 | def test_rxt_01(self): 21 | """Verify m_r measurements read correctly.""" 22 | filename = os.path.join(data_dir, 'basic-A.rxt') 23 | exp = iadpython.read_rxt(filename) 24 | print(exp.lambda0) 25 | self.assertAlmostEqual(exp.m_r[0], 0.299262, delta=1e-5) 26 | self.assertAlmostEqual(exp.m_r[-1], 0.09662, delta=1e-5) 27 | self.assertIsNone(exp.m_t) 28 | self.assertIsNone(exp.m_u) 29 | self.assertIsNone(exp.lambda0) 30 | 31 | def test_rxt_02(self): 32 | """Verify m_r and m_t measurements read correctly.""" 33 | filename = os.path.join(data_dir, 'basic-B.rxt') 34 | exp = iadpython.read_rxt(filename) 35 | self.assertAlmostEqual(exp.m_r[0], 0.51485, delta=1e-5) 36 | self.assertAlmostEqual(exp.m_t[0], 0.19596, delta=1e-5) 37 | self.assertAlmostEqual(exp.m_r[-1], 0.44875, delta=1e-5) 38 | self.assertAlmostEqual(exp.m_t[-1], 0.00019, delta=1e-5) 39 | self.assertIsNone(exp.m_u) 40 | self.assertIsNone(exp.lambda0) 41 | 42 | def test_rxt_03(self): 43 | """Verify m_r, m_t, and m_u measurements read correctly.""" 44 | filename = os.path.join(data_dir, 'basic-C.rxt') 45 | exp = iadpython.read_rxt(filename) 46 | self.assertAlmostEqual(exp.m_r[0], 0.18744, delta=1e-5) 47 | self.assertAlmostEqual(exp.m_t[0], 0.57620, delta=1e-5) 48 | self.assertAlmostEqual(exp.m_u[0], 0.00560, delta=1e-5) 49 | self.assertAlmostEqual(exp.m_r[-1], 0.16010, delta=1e-5) 50 | self.assertAlmostEqual(exp.m_t[-1], 0.06857, delta=1e-5) 51 | self.assertAlmostEqual(exp.m_u[-1], 6.341e-12, delta=1e-14) 52 | self.assertIsNone(exp.lambda0) 53 | 54 | def test_rxt_04(self): 55 | """Verify lambda, m_r, m_t read correctly.""" 56 | filename = os.path.join(data_dir, 'sample-A.rxt') 57 | exp = iadpython.read_rxt(filename) 58 | self.assertAlmostEqual(exp.lambda0[0], 800) 59 | self.assertAlmostEqual(exp.m_r[0], 0.16830, delta=1e-5) 60 | self.assertAlmostEqual(exp.m_t[0], 0.24974, delta=1e-5) 61 | self.assertAlmostEqual(exp.lambda0[-1], 1000) 62 | self.assertAlmostEqual(exp.m_r[-1], 0.28689, delta=1e-5) 63 | self.assertAlmostEqual(exp.m_t[-1], 0.42759, delta=1e-5) 64 | self.assertIsNone(exp.m_u) 65 | 66 | def test_rxt_05(self): 67 | """Verify lambda, m_r read correctly.""" 68 | filename = os.path.join(data_dir, 'sample-B.rxt') 69 | exp = iadpython.read_rxt(filename) 70 | self.assertAlmostEqual(exp.lambda0[0], 800) 71 | self.assertAlmostEqual(exp.m_r[0], 0.16830, delta=1e-5) 72 | self.assertAlmostEqual(exp.lambda0[-1], 1000) 73 | self.assertAlmostEqual(exp.m_r[-1], 0.28689, delta=1e-5) 74 | self.assertIsNone(exp.m_t) 75 | self.assertIsNone(exp.m_u) 76 | 77 | 78 | if __name__ == '__main__': 79 | unittest.main() 80 | -------------------------------------------------------------------------------- /tests/test_sphere.py: -------------------------------------------------------------------------------- 1 | """Tests for sphere object.""" 2 | 3 | import unittest 4 | import numpy as np 5 | import iadpython 6 | 7 | module_path = iadpython.__file__ 8 | print("Module imported from:", module_path) 9 | 10 | 11 | class SimpleSphere(unittest.TestCase): 12 | """Creation and setting of sphere parameters.""" 13 | 14 | def test_01_object_creation(self): 15 | """Simple sphere creation.""" 16 | s = iadpython.Sphere(200, 20) 17 | np.testing.assert_allclose(s.d, 200, atol=1e-5) 18 | np.testing.assert_allclose(s.sample.d, 20, atol=1e-5) 19 | np.testing.assert_allclose(s.third.d, 0, atol=1e-5) 20 | np.testing.assert_allclose(s.detector.d, 0, atol=1e-5) 21 | 22 | def test_02_portsize(self): 23 | """Test setting values.""" 24 | s = iadpython.Sphere(200, 20) 25 | s.third.d = 10 26 | s.detector.d = 5 27 | s.sample.d = 18 28 | np.testing.assert_allclose(s.d, 200, atol=1e-5) 29 | np.testing.assert_allclose(s.sample.d, 18, atol=1e-5) 30 | np.testing.assert_allclose(s.third.d, 10, atol=1e-5) 31 | np.testing.assert_allclose(s.detector.d, 5, atol=1e-5) 32 | 33 | def test_03_cap(self): 34 | """Spherical cap calculations.""" 35 | R = 100 36 | r = 10 37 | s = iadpython.Sphere(2 * R, 2 * r) 38 | acap = s.sample.cap_area_exact() / (4 * np.pi * R**2) 39 | np.testing.assert_allclose(acap, s.sample.a, atol=1e-5) 40 | 41 | acap1 = np.pi * r**2 / (4 * np.pi * R**2) 42 | a_cap1 = s.sample.relative_cap_area() 43 | np.testing.assert_allclose(acap1, a_cap1, atol=1e-5) 44 | 45 | 46 | class SphereGain(unittest.TestCase): 47 | """Basic tests of gain relative to black sphere.""" 48 | 49 | def test_01_gain(self): 50 | """Gain calculations, r_wall=0.""" 51 | d_sphere = 200 52 | d_sample = 25 53 | s = iadpython.Sphere(d_sphere, d_sample, r_wall=0) 54 | g = s.gain(sample_uru=0) 55 | np.testing.assert_allclose(g, 1, atol=1e-5) 56 | 57 | def test_02_gain(self): 58 | """Gain calculations, r_wall=1.""" 59 | d_sphere = 200 60 | d_sample = 25 61 | s = iadpython.Sphere(d_sphere, d_sample, r_wall=1) 62 | g = s.gain(sample_uru=0) 63 | p = iadpython.Port(s, d_sample) 64 | Asphere = 4 * np.pi * (d_sphere / 2)**2 65 | gg = Asphere / p.cap_area() 66 | np.testing.assert_allclose(g, gg, atol=1e-4) 67 | 68 | def test_03_gain(self): 69 | """Gain calculations with third and detector, r_wall=0.""" 70 | d_sphere = 200 71 | d_sample = 25 72 | s = iadpython.Sphere(d_sphere, d_sample, d_third=5, d_detector=10, r_wall=0) 73 | g = s.gain(sample_uru=0) 74 | np.testing.assert_allclose(g, 1, atol=1e-5) 75 | 76 | def test_04_gain(self): 77 | """Gain calculations with third and detector, r_wall=1.""" 78 | d_sphere = 200 79 | d_sample = 25 80 | s = iadpython.Sphere(d_sphere, d_sample, d_third=5, d_detector=10, r_wall=1) 81 | g = s.gain(sample_uru=0) 82 | gg = 1 / (s.detector.a + s.third.a + s.sample.a) 83 | np.testing.assert_allclose(g, gg, atol=1e-5) 84 | 85 | def test_05_gain(self): 86 | """Array of r_wall values.""" 87 | r_wall = np.linspace(0, 1, 4) 88 | d_sphere = 200 89 | d_sample = 25 90 | s = iadpython.Sphere(d_sphere, d_sample, d_third=5, d_detector=10, r_wall=r_wall) 91 | g = s.gain(sample_uru=0) 92 | np.testing.assert_allclose(len(r_wall), len(g), atol=1e-5) 93 | 94 | # class DoubleSphere(unittest.TestCase): 95 | # """Creation and setting of a single sphere used parameters.""" 96 | # 97 | # def test_01_double_photon(self): 98 | # """One photon test for double sphere configuration.""" 99 | # s = iadpython.Sphere(50, 10, r_wall=1) 100 | # d = iadpython.DoubleSphere(s,s) 101 | # detected, transmitted, _ = d.do_one_photon() 102 | # np.testing.assert_allclose(detected, 0, atol=1e-5) 103 | # np.testing.assert_allclose(transmitted, 1, atol=1e-5) 104 | # 105 | # def test_02_double_photon(self): 106 | # """One photon test for double sphere configuration.""" 107 | # s = iadpython.Sphere(50, 10, r_wall=1) 108 | # d = iadpython.DoubleSphere(s,s) 109 | # detected, transmitted, _ = d.do_one_photon() 110 | # np.testing.assert_allclose(detected, 0, atol=1e-5) 111 | # np.testing.assert_allclose(transmitted, 1, atol=1e-5) 112 | # 113 | # def test_01_double_N_photon(self): 114 | # """Half the light should be detected.""" 115 | # N=10000 116 | # d_sample = 10 117 | # s = iadpython.Sphere(50, d_sample, r_wall=1, d_detector=d_sample) 118 | # d = iadpython.DoubleSphere(s,s) 119 | # ave, stderr = d.do_N_photons(N) 120 | # np.testing.assert_allclose(ave, 0.5, atol=0.03) 121 | 122 | 123 | if __name__ == '__main__': 124 | unittest.main() 125 | -------------------------------------------------------------------------------- /tests/test_start.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | """Tests for initial layer thickness Function.""" 4 | 5 | import unittest 6 | import numpy as np 7 | import iadpython 8 | 9 | 10 | class A_thin(unittest.TestCase): 11 | """Starting layer calculations.""" 12 | 13 | def test_01_thin(self): 14 | """Isotropic finite layer.""" 15 | s = iadpython.ad.Sample(a=1, b=1, g=0.0, n=1, quad_pts=4) 16 | b_min = iadpython.starting_thickness(s) 17 | np.testing.assert_approx_equal(b_min, 0.0625) 18 | 19 | def test_02_thin(self): 20 | """Isotropic thick layer.""" 21 | s = iadpython.ad.Sample(a=1, b=100, g=0.0, n=1, quad_pts=4) 22 | b_min = iadpython.starting_thickness(s) 23 | np.testing.assert_approx_equal(b_min, 0.048828125) 24 | 25 | def test_03_thin(self): 26 | """Isotropic semi-infinite layer.""" 27 | s = iadpython.ad.Sample(a=1, b=np.inf, g=0.0, n=1, quad_pts=4) 28 | b_min = iadpython.starting_thickness(s) 29 | np.testing.assert_approx_equal(b_min, 0.04429397) 30 | 31 | def test_04_thin(self): 32 | """Isotropic semi-infinite layer.""" 33 | s = iadpython.ad.Sample(a=1, b=0, g=0.0, n=1, quad_pts=4) 34 | b_min = iadpython.starting_thickness(s) 35 | np.testing.assert_approx_equal(b_min, 0.0) 36 | 37 | def test_05_thin(self): 38 | """Anisotropic finite layer.""" 39 | s = iadpython.ad.Sample(a=1, b=1, g=0.9, n=1, quad_pts=4) 40 | b_min = iadpython.starting_thickness(s) 41 | np.testing.assert_approx_equal(b_min, 0.08597500) 42 | 43 | def test_06_thin(self): 44 | """Anisotropic thick layer.""" 45 | s = iadpython.ad.Sample(a=1, b=100, g=0.9, n=1, quad_pts=4) 46 | b_min = iadpython.starting_thickness(s) 47 | np.testing.assert_approx_equal(b_min, 0.06716797) 48 | 49 | def test_07_thin(self): 50 | """Anisotropic semi-infinite layer.""" 51 | s = iadpython.ad.Sample(a=1, b=np.inf, g=0.9, n=1, quad_pts=4) 52 | b_min = iadpython.starting_thickness(s) 53 | np.testing.assert_approx_equal(b_min, 0.04429397) 54 | 55 | def test_08_thin(self): 56 | """Anisotropic zero-thickness layer.""" 57 | s = iadpython.ad.Sample(a=1, b=0, g=0.9, n=1, quad_pts=4) 58 | b_min = iadpython.starting_thickness(s) 59 | np.testing.assert_approx_equal(b_min, 0.0) 60 | 61 | 62 | class B_igi(unittest.TestCase): 63 | """IGI layer initializations.""" 64 | 65 | def test_01_igi(self): 66 | """IGI initialization with isotropic scattering.""" 67 | s = iadpython.ad.Sample(a=1, b=100, g=0.0, n=1, quad_pts=4) 68 | rr, tt = iadpython.start.igi(s) 69 | np.testing.assert_approx_equal(s.b_thinnest, 0.048828125) 70 | 71 | r = np.array([[1.55547, 0.33652, 0.17494, 0.13780], 72 | [0.33652, 0.07281, 0.03785, 0.02981], 73 | [0.17494, 0.03785, 0.01968, 0.01550], 74 | [0.13780, 0.02981, 0.01550, 0.01221]]) 75 | 76 | t = np.array([[13.04576, 0.33652, 0.17494, 0.13780], 77 | [0.33652, 2.84330, 0.03785, 0.02981], 78 | [0.17494, 0.03785, 1.83038, 0.01550], 79 | [0.13780, 0.02981, 0.01550, 7.62158]]) 80 | 81 | np.testing.assert_allclose(r, rr, atol=1e-5) 82 | np.testing.assert_allclose(t, tt, atol=1e-5) 83 | 84 | def test_02_igi(self): 85 | """IGI initialization with anisotropic scattering.""" 86 | s = iadpython.ad.Sample(a=1, b=100, g=0.9, quad_pts=4) 87 | rr, tt = iadpython.start.igi(s) 88 | np.testing.assert_approx_equal(s.b_thinnest, 0.06716797) 89 | 90 | r = np.array([[3.19060, 0.51300, 0.09360, -0.01636], 91 | [0.51300, 0.04916, 0.00524, 0.00941], 92 | [0.09360, 0.00524, 0.00250, 0.00486], 93 | [-0.01636, 0.00941, 0.00486, -0.00628]]) 94 | 95 | t = np.array([[9.56148, 0.66419, 0.16129, -0.01868], 96 | [0.66419, 2.80843, 0.07395, 0.02700], 97 | [0.16129, 0.07395, 1.83985, 0.07886], 98 | [-0.01868, 0.02700, 0.07886, 7.57767]]) 99 | 100 | np.testing.assert_allclose(r, rr, atol=1e-5) 101 | np.testing.assert_allclose(t, tt, atol=1e-5) 102 | 103 | 104 | class C_diamond(unittest.TestCase): 105 | """Diamond layer initializations.""" 106 | 107 | def test_0_diamond(self): 108 | """Diamond initialization with isotropic scattering.""" 109 | s = iadpython.ad.Sample(a=1, b=100, g=0.0, quad_pts=4) 110 | rr, tt = iadpython.start.diamond(s) 111 | 112 | r = np.array([[1.04004, 0.27087, 0.14472, 0.11473], 113 | [0.27087, 0.07055, 0.03769, 0.02988], 114 | [0.14472, 0.03769, 0.02014, 0.01596], 115 | [0.11473, 0.02988, 0.01596, 0.01266]]) 116 | 117 | t = np.array([[15.57900, 0.27087, 0.14472, 0.11473], 118 | [0.27087, 2.86214, 0.03769, 0.02988], 119 | [0.14472, 0.03769, 1.83444, 0.01596], 120 | [0.11473, 0.02988, 0.01596, 7.63134]]) 121 | 122 | np.testing.assert_allclose(r, rr, atol=1e-5) 123 | np.testing.assert_allclose(t, tt, atol=1e-5) 124 | 125 | def test_02_diamond(self): 126 | """Diamond initialization with anisotropic scattering.""" 127 | s = iadpython.ad.Sample(a=1, b=100, g=0.9, quad_pts=4) 128 | 129 | rr, tt = iadpython.start.diamond(s) 130 | 131 | r = np.array([[1.92637, 0.40140, 0.08092, -0.00869], 132 | [0.40140, 0.05438, 0.00773, 0.00888], 133 | [0.08092, 0.00773, 0.00306, 0.00473], 134 | [-0.00869, 0.00888, 0.00473, -0.00569]]) 135 | 136 | t = np.array([[13.55020, 0.50578, 0.13009, -0.00913], 137 | [0.50578, 2.83738, 0.07117, 0.02622], 138 | [0.13009, 0.07117, 1.84366, 0.07534], 139 | [-0.00913, 0.02622, 0.07534, 7.59016]]) 140 | 141 | np.testing.assert_allclose(r, rr, atol=1e-5) 142 | np.testing.assert_allclose(t, tt, atol=1e-5) 143 | 144 | def test_03_diamond(self): 145 | """Diamond initialization with isotropic scattering and n=1.5.""" 146 | s = iadpython.ad.Sample(a=1, b=100, g=0.0, n=1.5, quad_pts=4) 147 | 148 | rr, tt = iadpython.start.diamond(s) 149 | 150 | r = np.array([[0.65936, 0.21369, 0.15477, 0.12972], 151 | [0.21369, 0.06926, 0.05016, 0.04204], 152 | [0.15477, 0.05016, 0.03633, 0.03045], 153 | [0.12972, 0.04204, 0.03045, 0.02552]]) 154 | 155 | t = np.array([[5.14582, 0.21369, 0.15477, 0.12972], 156 | [0.21369, 2.00149, 0.05016, 0.04204], 157 | [0.15477, 0.05016, 2.83938, 0.03045], 158 | [0.12972, 0.04204, 0.03045, 7.14833]]) 159 | 160 | np.testing.assert_allclose(r, rr, atol=1e-5) 161 | np.testing.assert_allclose(t, tt, atol=1e-5) 162 | 163 | 164 | if __name__ == '__main__': 165 | unittest.main() 166 | -------------------------------------------------------------------------------- /tests/test_txt.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | """Tests for NIST reflectance data.""" 4 | 5 | import os 6 | import unittest 7 | import iadpython 8 | 9 | # Get the directory of the current test file 10 | test_dir = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | # Path to the data directory relative to the test file 13 | data_dir = os.path.join(test_dir, 'data') 14 | 15 | 16 | class TestTXT(unittest.TestCase): 17 | """TXT files data.""" 18 | 19 | def test_txt_01(self): 20 | """Verify m_r measurements read correctly.""" 21 | filename = os.path.join(data_dir, 'basic-A.txt') 22 | exp, data = iadpython.read_txt(filename) 23 | self.assertAlmostEqual(exp.m_r[0], 0.299262, delta=2e-4) 24 | self.assertAlmostEqual(exp.m_r[-1], 0.09662, delta=2e-4) 25 | self.assertAlmostEqual(exp.m_t[0], 0) 26 | self.assertIsNone(exp.m_u) 27 | self.assertAlmostEqual(data.mr[0], 0.299262, delta=2e-4) 28 | self.assertAlmostEqual(data.cr[0], 0.299262, delta=2e-4) 29 | self.assertAlmostEqual(data.mt[0], 0.000000, delta=2e-4) 30 | self.assertAlmostEqual(data.ct[0], 0.000000, delta=2e-4) 31 | self.assertAlmostEqual(data.mr[-1], 0.09662, delta=2e-4) 32 | self.assertAlmostEqual(data.cr[-1], 0.09662, delta=2e-4) 33 | self.assertAlmostEqual(data.mt[-1], 0.000000, delta=2e-4) 34 | self.assertAlmostEqual(data.ct[-1], 0.000000, delta=2e-4) 35 | 36 | def test_txt_02(self): 37 | """Verify m_r and m_t measurements read correctly.""" 38 | filename = os.path.join(data_dir, 'basic-B.txt') 39 | exp, data = iadpython.read_txt(filename) 40 | self.assertAlmostEqual(exp.m_r[0], 0.51485, delta=2e-4) 41 | self.assertAlmostEqual(exp.m_t[0], 0.19596, delta=2e-4) 42 | self.assertAlmostEqual(exp.m_r[-1], 0.44875, delta=2e-4) 43 | self.assertAlmostEqual(exp.m_t[-1], 0.00019, delta=2e-4) 44 | self.assertIsNone(exp.m_u) 45 | self.assertAlmostEqual(data.mr[0], 0.51485, delta=2e-4) 46 | self.assertAlmostEqual(data.cr[0], 0.51485, delta=2e-4) 47 | self.assertAlmostEqual(data.mt[0], 0.19596, delta=2e-4) 48 | self.assertAlmostEqual(data.ct[0], 0.19596, delta=2e-4) 49 | self.assertAlmostEqual(data.mr[-1], 0.44875, delta=2e-4) 50 | self.assertAlmostEqual(data.cr[-1], 0.44875, delta=2e-4) 51 | self.assertAlmostEqual(data.mt[-1], 0.00019, delta=2e-4) 52 | self.assertAlmostEqual(data.ct[-1], 0.00019, delta=2e-4) 53 | 54 | def test_txt_03(self): 55 | """Verify m_r, m_t, and m_u measurements read correctly.""" 56 | filename = os.path.join(data_dir, 'basic-C.txt') 57 | exp, data = iadpython.read_txt(filename) 58 | self.assertAlmostEqual(exp.m_r[0], 0.18744, delta=2e-4) 59 | self.assertAlmostEqual(exp.m_t[0], 0.57620, delta=2e-4) 60 | self.assertIsNone(exp.m_u) 61 | self.assertAlmostEqual(exp.m_r[-1], 0.16010, delta=2e-4) 62 | self.assertAlmostEqual(exp.m_t[-1], 0.06857, delta=2e-4) 63 | self.assertAlmostEqual(data.mr[0], 0.18744, delta=2e-4) 64 | self.assertAlmostEqual(data.cr[0], 0.18744, delta=2e-4) 65 | self.assertAlmostEqual(data.mt[0], 0.57620, delta=2e-4) 66 | self.assertAlmostEqual(data.ct[0], 0.57620, delta=2e-4) 67 | self.assertAlmostEqual(data.mr[-1], 0.16010, delta=2e-4) 68 | self.assertAlmostEqual(data.cr[-1], 0.16010, delta=2e-4) 69 | self.assertAlmostEqual(data.mt[-1], 0.06857, delta=2e-4) 70 | self.assertAlmostEqual(data.ct[-1], 0.06857, delta=2e-4) 71 | 72 | def test_txt_04(self): 73 | """Verify lambda, m_r, m_t read correctly.""" 74 | filename = os.path.join(data_dir, 'sample-A.txt') 75 | exp, data = iadpython.read_txt(filename) 76 | self.assertAlmostEqual(exp.lambda0[0], 800) 77 | self.assertAlmostEqual(exp.m_r[0], 0.16830, delta=2e-4) 78 | self.assertAlmostEqual(exp.m_t[0], 0.24974, delta=2e-4) 79 | self.assertAlmostEqual(exp.lambda0[-1], 1000) 80 | self.assertAlmostEqual(exp.m_r[-1], 0.28689, delta=2e-4) 81 | self.assertAlmostEqual(exp.m_t[-1], 0.42759, delta=2e-4) 82 | self.assertIsNone(exp.m_u) 83 | self.assertAlmostEqual(data.mr[0], 0.16830, delta=2e-4) 84 | self.assertAlmostEqual(data.cr[0], 0.16830, delta=2e-4) 85 | self.assertAlmostEqual(data.mt[0], 0.24974, delta=2e-4) 86 | self.assertAlmostEqual(data.ct[0], 0.24974, delta=2e-4) 87 | self.assertAlmostEqual(data.mr[-1], 0.28689, delta=2e-4) 88 | self.assertAlmostEqual(data.cr[-1], 0.28689, delta=2e-4) 89 | self.assertAlmostEqual(data.mt[-1], 0.42759, delta=2e-4) 90 | self.assertAlmostEqual(data.ct[-1], 0.42759, delta=2e-4) 91 | self.assertAlmostEqual(data.lam[-1], 1000) 92 | 93 | def test_txt_05(self): 94 | """Verify lambda, m_r read correctly.""" 95 | filename = os.path.join(data_dir, 'sample-B.txt') 96 | exp, data = iadpython.read_txt(filename) 97 | self.assertAlmostEqual(exp.lambda0[0], 800) 98 | self.assertAlmostEqual(exp.m_r[0], 0.16830, delta=2e-4) 99 | self.assertAlmostEqual(exp.lambda0[-1], 1000) 100 | self.assertAlmostEqual(exp.m_r[-1], 0.28689, delta=2e-4) 101 | self.assertAlmostEqual(exp.m_t[0], 0) 102 | self.assertIsNone(exp.m_u) 103 | self.assertAlmostEqual(data.lam[0], 800) 104 | self.assertAlmostEqual(data.mr[0], 0.16830, delta=2e-4) 105 | self.assertAlmostEqual(data.cr[0], 0.16830, delta=2e-4) 106 | self.assertAlmostEqual(data.mr[-1], 0.28689, delta=2e-4) 107 | self.assertAlmostEqual(data.cr[-1], 0.28689, delta=2e-4) 108 | self.assertAlmostEqual(data.lam[-1], 1000) 109 | self.assertAlmostEqual(data.mt[0], 0.000000, delta=2e-4) 110 | self.assertAlmostEqual(data.ct[0], 0.000000, delta=2e-4) 111 | self.assertAlmostEqual(data.mt[-1], 0.000000, delta=2e-4) 112 | self.assertAlmostEqual(data.ct[-1], 0.000000, delta=2e-4) 113 | 114 | 115 | if __name__ == '__main__': 116 | unittest.main() 117 | -------------------------------------------------------------------------------- /tests/test_ur1_uru.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | """Tests for total flux calculations.""" 4 | 5 | import unittest 6 | import numpy as np 7 | import iadpython 8 | 9 | 10 | class TestFinite(unittest.TestCase): 11 | """Finite layer in air.""" 12 | 13 | def test_01_sandwich(self): 14 | """Anisotropic finite layer calculation.""" 15 | s = iadpython.Sample(a=0.5, b=1, g=0.9, n=1.0, quad_pts=4) 16 | ur1, ut1, uru, utu = s.rt() 17 | ur1_true = 0.00585 18 | ut1_true = 0.59232 19 | uru_true = 0.01641 20 | utu_true = 0.42287 21 | np.testing.assert_allclose([ur1], [ur1_true], atol=1e-5) 22 | np.testing.assert_allclose([ut1], [ut1_true], atol=1e-5) 23 | np.testing.assert_allclose([uru], [uru_true], atol=1e-5) 24 | np.testing.assert_allclose([utu], [utu_true], atol=1e-5) 25 | 26 | def test_02_sandwich(self): 27 | """Anisotropic finite layer in air.""" 28 | s = iadpython.Sample(a=0.5, b=1, g=0.9, n=1.4, n_above=1.0, n_below=1.0) 29 | ur1, ut1, uru, utu = s.rt() 30 | ur1_true = 0.03859 31 | ut1_true = 0.54038 32 | uru_true = 0.06527 33 | utu_true = 0.45887 34 | np.testing.assert_allclose([ur1], [ur1_true], atol=1e-5) 35 | np.testing.assert_allclose([ut1], [ut1_true], atol=1e-5) 36 | np.testing.assert_allclose([utu], [utu_true], atol=1e-5) 37 | np.testing.assert_allclose([uru], [uru_true], atol=1e-5) 38 | 39 | def test_03_sandwich(self): 40 | """Anisotropic finite layer with slides.""" 41 | s = iadpython.Sample(a=0.5, b=1, g=0.9, n=1.4, n_above=1.5, n_below=1.5) 42 | ur1, ut1, uru, utu = s.rt() 43 | ur1_true = 0.05563 44 | ut1_true = 0.52571 45 | uru_true = 0.08472 46 | utu_true = 0.44368 47 | np.testing.assert_allclose([ur1], [ur1_true], atol=1e-5) 48 | np.testing.assert_allclose([uru], [uru_true], atol=1e-5) 49 | np.testing.assert_allclose([ut1], [ut1_true], atol=1e-5) 50 | np.testing.assert_allclose([utu], [utu_true], atol=1e-5) 51 | 52 | def test_04_semi_infinite(self): 53 | """Anisotropic infinite layer with slides.""" 54 | s = iadpython.Sample(a=0.5, b=np.inf, g=0.9, n=1.4, n_above=1.5) 55 | ur1, ut1, uru, utu = s.rt() 56 | ur1_true = 0.04255 57 | ut1_true = 0.00000 58 | uru_true = 0.07001 59 | utu_true = 0.00000 60 | np.testing.assert_allclose([ur1], [ur1_true], atol=1e-5) 61 | np.testing.assert_allclose([uru], [uru_true], atol=1e-5) 62 | np.testing.assert_allclose([ut1], [ut1_true], atol=1e-5) 63 | np.testing.assert_allclose([utu], [utu_true], atol=1e-5) 64 | 65 | def test_05_semi_infinite(self): 66 | """Anisotropic infinite layer with slides and arrays.""" 67 | s = iadpython.Sample(a=[0.0, 0.5], b=np.inf, g=0.9, n=1.4, n_above=1.5) 68 | ur1, ut1, uru, utu = s.rt() 69 | ur1_true = [0.04110, 0.04255] 70 | ut1_true = [0.00000, 0.00000] 71 | uru_true = [0.06735, 0.07001] 72 | utu_true = [0.00000, 0.00000] 73 | np.testing.assert_allclose(ur1, ur1_true, atol=1e-5) 74 | np.testing.assert_allclose(uru, uru_true, atol=1e-5) 75 | np.testing.assert_allclose(ut1, ut1_true, atol=1e-5) 76 | np.testing.assert_allclose(utu, utu_true, atol=1e-5) 77 | 78 | 79 | if __name__ == '__main__': 80 | unittest.main() 81 | -------------------------------------------------------------------------------- /tests_iadc/test_performance.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | # pylint: disable=too-many-locals 3 | # pylint: disable=consider-using-f-string 4 | 5 | """Compare speed of pure python implementation with c-library.""" 6 | 7 | import time 8 | import unittest 9 | import scipy.optimize 10 | import numpy as np 11 | import iadpython 12 | from iadpython import iadc 13 | 14 | 15 | class speed(unittest.TestCase): 16 | """Performance testing.""" 17 | 18 | def test_speed_01(self): 19 | """First Speed Test.""" 20 | n_slab = 1.5 # slab with boundary reflection 21 | n_slide = 1.0 # no glass slides above and below the sample 22 | b = 1 # this is pretty much infinite 23 | g = 0.9 # isotropic scattering is fine 24 | a = np.linspace(0, 1, 50) # albedo varies between 0 and 1 25 | 26 | start_time = time.perf_counter() 27 | _, _, _, _ = iadc.rt(n_slab, n_slide, a, b, g) 28 | deltaC = time.perf_counter() - start_time 29 | 30 | start_time = time.perf_counter() 31 | s = iadpython.Sample(a=a, b=b, g=g, n=n_slab, n_above=n_slide, 32 | n_below=n_slide, quad_pts=16) 33 | _, _, _, _ = s.rt() 34 | deltaP = time.perf_counter() - start_time 35 | print("# C python ratio") 36 | print("1 %7.2f %7.2f %5.0f%%" % (deltaC, deltaP, 100 * deltaP / deltaC)) 37 | 38 | def test_speed_02(self): 39 | """Second Speed Test.""" 40 | n_slab = 1.0 # ignore boundary reflection 41 | n_slide = 1.0 # no glass slides above and below the sample 42 | b = 10000 # this is pretty much infinite 43 | g = 0.0 # isotropic scattering is fine 44 | a = np.linspace(0, 1, 2000) # albedo varies between 0 and 1 45 | 46 | start_time = time.perf_counter() 47 | _, _, _, _ = iadc.rt(n_slab, n_slide, a, b, g) 48 | deltaC = time.perf_counter() - start_time 49 | 50 | start_time = time.perf_counter() 51 | s = iadpython.Sample(a=a, b=b, g=g, n=n_slab, n_above=n_slide, 52 | n_below=n_slide, quad_pts=16) 53 | _, _, _, _ = s.rt() 54 | deltaP = time.perf_counter() - start_time 55 | print("2 %7.2f %7.2f %5.0f%%" % (deltaC, deltaP, 100 * deltaP / deltaC)) 56 | 57 | def test_speed_03(self): 58 | """Third Speed Test.""" 59 | n_slab = 1.4 # sample has refractive index 60 | n_slide = 1.5 # glass slides above and below the sample 61 | g = 0.0 # isotropic scattering is fine 62 | a = 0.9 # scattering is 9X absorption 63 | b = np.linspace(0, 10, 4000) # opstart_timeal thickness 64 | 65 | start_time = time.perf_counter() 66 | _, _, _, _ = iadc.rt(n_slab, n_slide, a, b, g) 67 | deltaC = time.perf_counter() - start_time 68 | 69 | start_time = time.perf_counter() 70 | s = iadpython.Sample(a=a, b=b, g=g, n=n_slab, 71 | n_above=n_slide, n_below=n_slide, quad_pts=16) 72 | _, _, _, _ = s.rt() 73 | deltaP = time.perf_counter() - start_time 74 | print("3 %7.2f %7.2f %5.0f%%" % (deltaC, deltaP, 100 * deltaP / deltaC)) 75 | 76 | def test_speed_04(self): 77 | """Fourth Speed Test.""" 78 | N = 201 79 | n_slab = 1.0 # ignore boundary reflection 80 | n_slide = 1.0 # no glass slides above and below the sample 81 | g = 0.0 # isotropic scattering is fine 82 | a = np.linspace(0.01, 0.99, N) # avoid extremes because 83 | bmin = np.empty(N) 84 | b = 100000 85 | 86 | def f(bb): 87 | """C Function to find 99 percent.""" 88 | ur1c, _, _, _ = iadc.rt(n_slab, n_slide, aa, bb, g) 89 | return (ur1c - ur1_inf * 0.99)**2 90 | 91 | def ff(bb): 92 | """Python Function to find 99 percent.""" 93 | s.b = bb 94 | ur1p, _, _, _ = s.rt() 95 | return (ur1p - ur1_inf * 0.99)**2 96 | 97 | start_time = time.perf_counter() 98 | for i in range(N): 99 | aa = a[i] 100 | ur1_inf, _, _, _ = iadc.rt(n_slab, n_slide, aa, b, g) 101 | bmin[i] = scipy.optimize.brent(f) 102 | deltaC = time.perf_counter() - start_time 103 | 104 | start_time = time.perf_counter() 105 | s = iadpython.Sample(a=a, b=b, g=g, n=n_slab, 106 | n_above=n_slide, n_below=n_slide, quad_pts=16) 107 | 108 | for i in range(N): 109 | s.a = a[i] 110 | s.b = b 111 | ur1_inf, _, _, _ = s.rt() 112 | bmin[i] = scipy.optimize.brent(ff) 113 | deltaP = time.perf_counter() - start_time 114 | print("4 %7.2f %7.2f %5.0f%%" % (deltaC, deltaP, 100 * deltaP / deltaC)) 115 | 116 | def test_speed_05(self): 117 | """Fifth Speed Test.""" 118 | n_slab = 1.0 # ignore boundaries 119 | n_slide = 1.0 # no glass slides above and below the sample 120 | b = 1000 # relatively thin sample 121 | ap = np.linspace(0, 1, 800) # albedo varies between 0 and 1 122 | g = [0, 0.5, 0.95] 123 | ur1 = np.empty_like(g, dtype=list) 124 | 125 | start_time = time.perf_counter() 126 | for i in range(3): 127 | a = ap / (1 - g[i] + ap * g[i]) 128 | ur1[i], _, _, _ = iadc.rt(n_slab, n_slide, a, b, g[i]) 129 | deltaC = time.perf_counter() - start_time 130 | 131 | start_time = time.perf_counter() 132 | s = iadpython.Sample(b=b, n=n_slab, 133 | n_above=n_slide, n_below=n_slide, quad_pts=16) 134 | for i in range(3): 135 | s.a = ap / (1 - g[i] + ap * g[i]) 136 | s.g = g[i] 137 | ur1[i], _, _, _ = s.rt() 138 | deltaP = time.perf_counter() - start_time 139 | print("5 %7.2f %7.2f %5.0f%%" % (deltaC, deltaP, 100 * deltaP / deltaC)) 140 | 141 | 142 | if __name__ == '__main__': 143 | unittest.main() 144 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | functionality 2 | match command line functionality of iad 3 | 4 | match one sphere calculations 5 | 6 | add two sphere simulation notebook 7 | 8 | add support for two spheres 9 | implement ba inversion 10 | implement bs inversion 11 | improve Experiment __str__() 12 | improve Sphere __str__() 13 | include numba??? 14 | get oblique incidence working 15 | 16 | Documentation 17 | notebook for .rxt files 18 | notebook for no sphere inversion 19 | notebook for one sphere inversion 20 | notebook for two sphere inversion 21 | 22 | command-line applications 23 | ad 24 | iad 25 | 26 | --------------------------------------------------------------------------------