From 8b5503fdddd63b208a8db50f116ce1db9b7bc4c4 Mon Sep 17 00:00:00 2001 From: Long Yang Date: Sat, 10 Dec 2022 12:31:37 +0800 Subject: [PATCH 001/696] BLD: auto update the tag for git describe. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fad6ade3..a2d25955 100755 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def gitinfo(): from subprocess import Popen, PIPE kw = dict(stdout=PIPE, cwd=MYDIR, universal_newlines=True) - proc = Popen(['git', 'describe', '--match=v[[:digit:]]*'], **kw) + proc = Popen(['git', 'describe', '--tags', '--match=v[[:digit:]]*'], **kw) desc = proc.stdout.read() proc = Popen(['git', 'log', '-1', '--format=%H %ct %ci'], **kw) glog = proc.stdout.read() From 8c5007f01e29239f046e1c1015d2e07a26b9ac48 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 20 Jul 2023 10:48:10 +0200 Subject: [PATCH 002/696] initial commit of all the things for CI --- .codecov.yml | 34 ++++++++++++++++++++++++ .coveragerc | 9 +++++++ .github/workflows/main.yml | 54 ++++++++++++++++++++++++++++++++++++++ AUTHORS.txt | 2 ++ environment.yml | 6 +++++ requirements/docs.txt | 3 +++ requirements/run.txt | 0 requirements/test.txt | 5 ++++ run_tests.py | 17 ++++++++++++ 9 files changed, 130 insertions(+) create mode 100644 .codecov.yml create mode 100644 .github/workflows/main.yml create mode 100644 environment.yml create mode 100644 requirements/docs.txt create mode 100644 requirements/run.txt create mode 100644 requirements/test.txt create mode 100644 run_tests.py diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 00000000..03afe9c3 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,34 @@ +# codecov can find this file anywhere in the repo, so we don't need to clutter +# the root folder. +#comment: false + +codecov: + notify: + require_ci_to_pass: no + +coverage: + status: + patch: + default: + target: '70' + if_no_uploads: error + if_not_found: success + if_ci_failed: failure + project: + default: false + library: + target: auto + if_no_uploads: error + if_not_found: success + if_ci_failed: failure + paths: '!*/tests/.*' + + tests: + target: 97.9% + paths: '*/tests/.*' + if_not_found: success + +flags: + tests: + paths: + - tests/ diff --git a/.coveragerc b/.coveragerc index d0a0d16d..10895cee 100644 --- a/.coveragerc +++ b/.coveragerc @@ -14,9 +14,18 @@ exclude_lines = ^[ ]*@unittest.skip\b ^[ ]{4}unittest.main() if __name__ == .__main__.: +omit = + */python?.?/* + */site-packages/nose/* + # ignore _version.py and versioneer.py + .*version.* + *_version.py [run] +source = + src/diffpy/utils/ omit = ## exclude debug.py from codecov report */tests/debug.py + diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..d0fbe7cc --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,54 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + miniconda: + name: Miniconda ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-latest"] + steps: + - name: check out diffpy.utils + uses: actions/checkout@v3 + with: + repository: diffpy/diffpy.utils + path: . + + - name: initialize miniconda + # this uses a marketplace action that sets up miniconda in a way that makes + # it easier to use. I tried setting it up without this and it was a pain + uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: test + # environment.yml file is needed by this action. Because I don't want + # maintain this but rather maintain the requirements files it just has + # basic things in it like conda and pip + environment-file: ./environment.yml + python-version: 3 + auto-activate-base: false + + - name: install diffpy.utils requirements + shell: bash -l {0} + run: | + conda config --set always_yes yes --set changeps1 no + conda config --add channels conda-forge + conda activate test + conda install --file requirements/run.txt + conda install --file requirements/test.txt + pip install . + + - name: Validate diffpy.utils + shell: bash -l {0} + run: | + conda activate test + coverage run run_tests.py + coverage report -m + codecov + diff --git a/AUTHORS.txt b/AUTHORS.txt index 08950ab1..7d5c10b5 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1,4 +1,6 @@ Pavol Juhas +Andrew yang Timur Davis Christopher L. Farrow Simon J.L. Billinge + diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..42088bbc --- /dev/null +++ b/environment.yml @@ -0,0 +1,6 @@ +name: diffpy.utils +channels: + - conda-forge +dependencies: + - python=3 + - pip diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 00000000..92f4ff09 --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,3 @@ +sphinx +sphinx_rtd_theme +doctr diff --git a/requirements/run.txt b/requirements/run.txt new file mode 100644 index 00000000..e69de29b diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 00000000..6f9ccf84 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,5 @@ +flake8 +pytest +codecov +coverage +pytest-env diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 00000000..cdba6f0a --- /dev/null +++ b/run_tests.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +import sys +import pytest + +if __name__ == '__main__': + # show output results from every test function + args = ['-v'] + # show the message output for skipped and expected failure tests + if len(sys.argv) > 1: + args.extend(sys.argv[1:]) + print('pytest arguments: {}'.format(args)) + # # compute coverage stats for xpdAcq + # call pytest and exit with the return code from pytest so that + # travis will fail correctly if tests fail + exit_res = pytest.main(args) + sys.exit(exit_res) From a234e4c19374dae5964e126424168b3bb19df71d Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 20 Jul 2023 10:57:21 +0200 Subject: [PATCH 003/696] add nummpy to requirements --- requirements/run.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/run.txt b/requirements/run.txt index e69de29b..24ce15ab 100644 --- a/requirements/run.txt +++ b/requirements/run.txt @@ -0,0 +1 @@ +numpy From fddd232acce94f4185e7698798e4b16093070462 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 20 Jul 2023 12:44:36 +0200 Subject: [PATCH 004/696] changing authors and python versions --- run_tests.py | 3 +-- setup.py | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/run_tests.py b/run_tests.py index cdba6f0a..515bfc6b 100644 --- a/run_tests.py +++ b/run_tests.py @@ -11,7 +11,6 @@ args.extend(sys.argv[1:]) print('pytest arguments: {}'.format(args)) # # compute coverage stats for xpdAcq - # call pytest and exit with the return code from pytest so that - # travis will fail correctly if tests fail + # call pytest and exit with the return code from pytest exit_res = pytest.main(args) sys.exit(exit_res) diff --git a/setup.py b/setup.py index a2d25955..1c5660f1 100755 --- a/setup.py +++ b/setup.py @@ -90,8 +90,8 @@ def getversioncfg(): zip_safe = False, author = 'Simon J.L. Billinge group', author_email = 'sb2896@columbia.edu', - maintainer = 'Pavol Juhas', - maintainer_email = 'pavol.juhas@gmail.com', + maintainer = 'Simon J.L. Billinge group', + maintainer_email = 'sb2896@columbia.edu', description = "Shared utilities for diffpy packages.", long_description = long_description, long_description_content_type = 'text/x-rst', @@ -111,10 +111,9 @@ def getversioncfg(): 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Topic :: Scientific/Engineering :: Physics', ], ) From 7412036e604f1eedf8f1a3bf4f5d5030b1324533 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Sun, 23 Jul 2023 23:59:47 -0400 Subject: [PATCH 005/696] Changes for upstream --- python.ini | 3 +++ requirements/build.txt | 2 ++ src/diffpy/utils/tests/{testloaddata.py => test_loaddata.py} | 0 3 files changed, 5 insertions(+) create mode 100644 python.ini create mode 100644 requirements/build.txt rename src/diffpy/utils/tests/{testloaddata.py => test_loaddata.py} (100%) diff --git a/python.ini b/python.ini new file mode 100644 index 00000000..0ac83bbe --- /dev/null +++ b/python.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths= src/diffpy/utils/tests +python_files = test*.py \ No newline at end of file diff --git a/requirements/build.txt b/requirements/build.txt new file mode 100644 index 00000000..497aa6eb --- /dev/null +++ b/requirements/build.txt @@ -0,0 +1,2 @@ +python +setuptools \ No newline at end of file diff --git a/src/diffpy/utils/tests/testloaddata.py b/src/diffpy/utils/tests/test_loaddata.py similarity index 100% rename from src/diffpy/utils/tests/testloaddata.py rename to src/diffpy/utils/tests/test_loaddata.py From 0d0e645a0ea47e8127df4b5574e99da889ae4325 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 24 Jul 2023 00:19:31 -0400 Subject: [PATCH 006/696] Update to previous commit --- .github/workflows/main.yml | 1 + python.ini => pytest.ini | 0 src/diffpy/utils/tests/{testhelpers.py => t_helpers.py} | 0 src/diffpy/utils/tests/test_loaddata.py | 2 +- 4 files changed, 2 insertions(+), 1 deletion(-) rename python.ini => pytest.ini (100%) rename src/diffpy/utils/tests/{testhelpers.py => t_helpers.py} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d0fbe7cc..b33a66be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - CI pull_request: workflow_dispatch: diff --git a/python.ini b/pytest.ini similarity index 100% rename from python.ini rename to pytest.ini diff --git a/src/diffpy/utils/tests/testhelpers.py b/src/diffpy/utils/tests/t_helpers.py similarity index 100% rename from src/diffpy/utils/tests/testhelpers.py rename to src/diffpy/utils/tests/t_helpers.py diff --git a/src/diffpy/utils/tests/test_loaddata.py b/src/diffpy/utils/tests/test_loaddata.py index 1ab2252a..f8dc8a02 100644 --- a/src/diffpy/utils/tests/test_loaddata.py +++ b/src/diffpy/utils/tests/test_loaddata.py @@ -6,7 +6,7 @@ import unittest import numpy from diffpy.utils.parsers import loadData -from diffpy.utils.tests.testhelpers import datafile +from diffpy.utils.tests.t_helpers import datafile loaddata01 = datafile('loaddata01.txt') From 14eb39a6526f12f05ddbdef2dee90b6ef397d258 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 24 Jul 2023 22:37:46 -0400 Subject: [PATCH 007/696] Remove travis, pkg_resources --- .travis.yml | 115 ------------------ pytest.ini | 2 +- src/diffpy/utils/tests/__init__.py | 15 +-- src/diffpy/utils/tests/test_loaddata.py | 2 +- .../tests/{t_helpers.py => testhelpers.py} | 7 +- src/diffpy/utils/version.py | 27 ++-- 6 files changed, 29 insertions(+), 139 deletions(-) delete mode 100644 .travis.yml rename src/diffpy/utils/tests/{t_helpers.py => testhelpers.py} (81%) diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 19e799da..00000000 --- a/.travis.yml +++ /dev/null @@ -1,115 +0,0 @@ -language: generic - -os: - - linux - - osx - -env: - - MYUSEMC=true MYPYTHON_VERSION=2.7 - - MYUSEMC=true MYPYTHON_VERSION=3.7 - - MYUSEMC=true MYPYTHON_VERSION=3.8 - - MYUSEMC=true MYPYTHON_VERSION=3.9 - - MYUSEMC=true MYPYTHON_VERSION=3.10 - - MYUSEMC=false - -git: - depth: 999999 - -branches: - except: - - /^v[0-9]/ - -addons: - apt: - packages: - - python-numpy - - python-setuptools - - -before_install: - - MYNAME=diffpy.utils - - MYCOMMIT="$(git rev-parse HEAD)" - - umask 022 - - git fetch origin --tags - - MYPYTHON=python; MYPIP=pip - - NOSYS=true; NOAPT=true; NOBREW=true; NOMC=true - - if ${MYUSEMC}; then - NOMC=false; - elif [[ ${TRAVIS_OS_NAME} == linux ]]; then - NOAPT=false; NOSYS=false; - MYPIPFLAGS="--user"; - elif [[ ${TRAVIS_OS_NAME} == osx ]]; then - NOBREW=false; NOSYS=false; - MYPYTHON=python3; - MYPIP=pip3; - MYPIPFLAGS="--user"; - fi - - MYMCREPO=https://repo.anaconda.com/miniconda - - case ${TRAVIS_OS_NAME} in - linux) - MYMCBUNDLE=Miniconda3-latest-Linux-x86_64.sh ;; - osx) - MYMCBUNDLE=Miniconda3-latest-MacOSX-x86_64.sh ;; - *) - echo "Unsupported operating system." >&2; - exit 2 ;; - esac - - MYRUNDIR=${PWD}/build/rundir - - - mkdir -p ~/pkgs - - mkdir -p ${MYRUNDIR} - - cp .coveragerc ${MYRUNDIR}/ - - - $NOMC || pushd ~/pkgs - - $NOMC || wget --timestamping ${MYMCREPO}/${MYMCBUNDLE} - - $NOMC || test -x ~/mc/bin/conda || bash ${MYMCBUNDLE} -b -f -p ~/mc - - $NOMC || popd - - $NOMC || source ~/mc/bin/activate base - - $NOMC || conda update --yes conda - - $NOMC || conda install --yes conda-build conda-verify jinja2 - - $NOMC || conda create --name=testenv --yes python=${MYPYTHON_VERSION} coverage - - - $NOAPT || test "${TRAVIS_OS_NAME}" = "linux" || exit $? - - $NOAPT || PATH="$(echo "$PATH" | sed 's,:/opt/pyenv/[^:]*,,g')" - - $NOAPT || test "$(which python)" = "/usr/bin/python" || ( - which python; exit 1) - - - $NOBREW || test "${TRAVIS_OS_NAME}" = "osx" || exit $? - - $NOBREW || brew update - - $NOBREW || brew upgrade python - - $NOBREW || brew install gcc || brew link --overwrite gcc - - - $NOSYS || devutils/makesdist - - $NOSYS || MYTARBUNDLE="$(ls -t "${PWD}"/dist/*.tar.gz | head -1)" - - -install: - - $NOMC || conda build --python=${MYPYTHON_VERSION} conda-recipe - - $NOMC || conda render --python=${MYPYTHON_VERSION} --output conda-recipe | - sed 's,.*/,,; s/[.]tar[.]bz2$//; s/-/=/g' > /tmp/mypackage.txt - - $NOMC || source activate testenv - - $NOMC || conda install --yes --use-local --file=/tmp/mypackage.txt - - - $NOSYS || $MYPIP install $MYPIPFLAGS coverage - - $NOSYS || $MYPIP install $MYPIPFLAGS "${MYTARBUNDLE}" - - - cd ${MYRUNDIR} - - MYGIT_REV=$($MYPYTHON -c "import ${MYNAME}.version as v; print(v.__git_commit__)") - - if [[ "${MYCOMMIT}" != "${MYGIT_REV}" ]]; then - echo "Version mismatch ${MYCOMMIT} vs ${MYGIT_REV}."; - exit 1; - fi - - -before_script: - - $NOBREW || USER_BASE="$($MYPYTHON -c 'import site; print(site.USER_BASE)')" - - $NOBREW || PATH="${USER_BASE}/bin:${PATH}" - - -script: - - coverage run --source ${MYNAME} -m ${MYNAME}.tests.run - - -after_success: - - $MYPIP install $MYPIPFLAGS codecov - - codecov diff --git a/pytest.ini b/pytest.ini index 0ac83bbe..7ea926c5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths= src/diffpy/utils/tests -python_files = test*.py \ No newline at end of file +python_files = test_*.py \ No newline at end of file diff --git a/src/diffpy/utils/tests/__init__.py b/src/diffpy/utils/tests/__init__.py index 43d96aba..53a65fea 100644 --- a/src/diffpy/utils/tests/__init__.py +++ b/src/diffpy/utils/tests/__init__.py @@ -37,14 +37,15 @@ def testsuite(pattern=''): import re from os.path import dirname from itertools import chain - from pkg_resources import resource_filename + from importlib.resources import files, as_file loader = unittest.defaultTestLoader - thisdir = resource_filename(__name__, '') - depth = __name__.count('.') + 1 - topdir = thisdir - for i in range(depth): - topdir = dirname(topdir) - suite_all = loader.discover(thisdir, top_level_dir=topdir) + ref = files(__package__) + with as_file(ref) as thisdir: + depth = __name__.count('.') + 1 + topdir = thisdir + for i in range(depth): + topdir = dirname(topdir) + suite_all = loader.discover(str(thisdir), top_level_dir=topdir) # always filter the suite by pattern to test-cover the selection code. suite = unittest.TestSuite() rx = re.compile(pattern) diff --git a/src/diffpy/utils/tests/test_loaddata.py b/src/diffpy/utils/tests/test_loaddata.py index f8dc8a02..1ab2252a 100644 --- a/src/diffpy/utils/tests/test_loaddata.py +++ b/src/diffpy/utils/tests/test_loaddata.py @@ -6,7 +6,7 @@ import unittest import numpy from diffpy.utils.parsers import loadData -from diffpy.utils.tests.t_helpers import datafile +from diffpy.utils.tests.testhelpers import datafile loaddata01 = datafile('loaddata01.txt') diff --git a/src/diffpy/utils/tests/t_helpers.py b/src/diffpy/utils/tests/testhelpers.py similarity index 81% rename from src/diffpy/utils/tests/t_helpers.py rename to src/diffpy/utils/tests/testhelpers.py index 8abdaa7b..4941ad20 100644 --- a/src/diffpy/utils/tests/t_helpers.py +++ b/src/diffpy/utils/tests/testhelpers.py @@ -19,8 +19,9 @@ # helper functions def datafile(filename): - from pkg_resources import resource_filename - rv = resource_filename(__name__, "testdata/" + filename) - return rv + from importlib.resources import files, as_file + ref = files(__package__) / ("testdata/" + filename) + with as_file(ref) as rv: + return rv # End of file diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index f1e2126c..e0236cef 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -26,21 +26,24 @@ import os.path -from pkg_resources import resource_filename +from importlib.resources import files, as_file # obtain version information from the version.cfg file cp = dict(version='', date='', commit='', timestamp='0') -fcfg = resource_filename(__name__, 'version.cfg') -if not os.path.isfile(fcfg): # pragma: no cover - from warnings import warn - warn('Package metadata not found, execute "./setup.py egg_info".') - fcfg = os.devnull -with open(fcfg) as fp: - kwords = [[w.strip() for w in line.split(' = ', 1)] - for line in fp if line[:1].isalpha() and ' = ' in line] -assert all(w[0] in cp for w in kwords), "received unrecognized keyword" -cp.update(kwords) +if __package__ is not None: + ref = files(__package__) / 'version.cfg' + with as_file(ref) as fcfg: + if not os.path.isfile(fcfg): # pragma: no cover + from warnings import warn + warn('Package metadata not found, execute "./setup.py egg_info".') + fcfg = os.devnull + with open(fcfg) as fp: + kwords = [[w.strip() for w in line.split(' = ', 1)] + for line in fp if line[:1].isalpha() and ' = ' in line] + assert all(w[0] in cp for w in kwords), "received unrecognized keyword" + cp.update(kwords) + del kwords __version__ = cp['version'] __date__ = cp['date'] @@ -50,6 +53,6 @@ # TODO remove deprecated __gitsha__ in version 3.1. __gitsha__ = __git_commit__ -del cp, fcfg, fp, kwords +del cp # End of file From 5681d9d3f3ff8b329f39eaa596404ae93dc4f337 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 24 Jul 2023 22:44:09 -0400 Subject: [PATCH 008/696] Add header metadata parsing to loaddata --- src/diffpy/utils/parsers/loaddata.py | 48 +- src/diffpy/utils/tests/test_loaddata.py | 24 +- .../tests/testdata/loaddatawithheaders.txt | 10032 ++++++++++++++++ .../tests/{t_helpers.py => testhelpers.py} | 0 4 files changed, 10099 insertions(+), 5 deletions(-) create mode 100644 src/diffpy/utils/tests/testdata/loaddatawithheaders.txt rename src/diffpy/utils/tests/{t_helpers.py => testhelpers.py} (100%) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 70d01b82..7cab7b46 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -16,7 +16,7 @@ import numpy -def loadData(filename, minrows=10, **kwargs): +def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwargs): """Find and load data from a text file. The data reading starts at the first matrix block of at least minrows rows @@ -26,6 +26,9 @@ def loadData(filename, minrows=10, **kwargs): filename -- name of the file we want to load data from. minrows -- minimum number of rows in the first data block. All rows must have the same number of floating point values. + headers -- return also a dictionary of parameters specified in header + hdel -- delimiter for parsing header information + hignore -- ignore header rows beginning with any elements in the hignore list usecols -- zero-based index of columns to be loaded, by default use all detected columns. The reading skips data blocks that do not have the usecols-specified columns. @@ -39,6 +42,8 @@ def loadData(filename, minrows=10, **kwargs): See also numpy.loadtxt for more details. """ from numpy import array, loadtxt + # for storing header data + hdata = {} # determine the arguments delimiter = kwargs.get('delimiter') usecols = kwargs.get('usecols') @@ -72,8 +77,40 @@ def countcolumnsvalues(line): fpos = (0, 0) nrows = 0 for line in fid: + # decode line + dline = line.decode() + # find header information if requested + if headers: + hpair = dline.split(hdel) + flag = True + # ensure number of non-blank arguments is two + if len(hpair) != 2: + flag = False + else: + # ignore if an argument is blank + hpair[0] = hpair[0].strip() # name of data entry + hpair[1] = hpair[1].strip() # value of entry + if not hpair[0] or not hpair[1]: + flag = False + else: + # check if row has an ignore tag + if hignore is not None: + for tag in hignore: + taglen = len(tag) + if len(hpair[0]) >= taglen and hpair[0][:taglen] == tag: + flag = False + # add header data + if flag: + name = hpair[0] + value = hpair[1] + # can be stored as float if only one decimal + if hpair[1].replace(".", "", 1).isnumeric(): + value = float(hpair[1]) + # check if data value should be stored as float + hdata.update({name: value}) + # continue search for the start of datablock fpos = (fpos[1], fpos[1] + len(line)) - line = line.decode() + line = dline ncv = countcolumnsvalues(line) if ncv < mincv: start = None @@ -98,6 +135,11 @@ def countcolumnsvalues(line): # in case of trailing delimiters. kwargs.setdefault('usecols', list(range(ncvblock[0]))) rv = loadtxt(fid, **kwargs) + + # return headers if requested + if headers: + return hdata, rv + # otherwise do not return rv @@ -247,4 +289,4 @@ def isfloat(s): pass return False -# End of file +# End of file \ No newline at end of file diff --git a/src/diffpy/utils/tests/test_loaddata.py b/src/diffpy/utils/tests/test_loaddata.py index f8dc8a02..4b9ef939 100644 --- a/src/diffpy/utils/tests/test_loaddata.py +++ b/src/diffpy/utils/tests/test_loaddata.py @@ -6,9 +6,10 @@ import unittest import numpy from diffpy.utils.parsers import loadData -from diffpy.utils.tests.t_helpers import datafile +from diffpy.utils.tests.testhelpers import datafile loaddata01 = datafile('loaddata01.txt') +loaddatawithheaders = datafile('loaddatawithheaders.txt') ############################################################################## class TestLoadData(unittest.TestCase): @@ -44,9 +45,28 @@ def test_loadData_1column(self): self.assertFalse(numpy.array_equal(d1c, d)) return + + def test_loadData_headers(self): + """check loadData() with headers options enabled + """ + hignore = ["# ", "// ", "["] # ignore lines beginning with these strings + delimiter = ": " # what our data should be separated by + hdata, rv = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) + # only fourteen lines of data are formatted properly + assert len(hdata) == 14 + # check the following are floats + vfloats = ["wavelength", "qmaxinst", "qmin", "qmax", "bgscale"] + for name in vfloats: + assert isinstance(hdata.get(name), float) + # check the following are NOT floats + vnfloats = ["composition", "rmax", "rmin", "rstep", "rpoly"] + for name in vnfloats: + assert not isinstance(hdata.get(name), float) + + # End of class TestRoutines if __name__ == '__main__': unittest.main() -# End of file +# End of file \ No newline at end of file diff --git a/src/diffpy/utils/tests/testdata/loaddatawithheaders.txt b/src/diffpy/utils/tests/testdata/loaddatawithheaders.txt new file mode 100644 index 00000000..88e11525 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/loaddatawithheaders.txt @@ -0,0 +1,10032 @@ +[Data with : as Delimiter (14 in total)] +wavelength: 0.1 +dataformat: Qnm +inputfile: darkSub_rh20_C_01.chi +mode: xray +bgscale: 1.2998929285 +composition: 0.800.20 +outputtype: gr +qmaxinst: 25 +qmin: 0.1 +qmax: 25.0 +rmax: 100.0r +rmin: 0.0r +rstep: 0.01r +rpoly: 0.9r + +[Faulty Data Format] +blank: +no:space +equals = sign +::::::::::::::::::::::: +a:b:c +::::::::::::::::::::::: + +[Headers to Ignore] +# inputdir: ~/dbs/morph_data/darkSub +// savedir: ~/tmp + +#### start data +#S 1 +#L r($\AA$) G($\AA^{-2}$) +0 0 +0.01 -5.80176e-05 +0.02 -0.000112252 +0.03 -0.000159465 +0.04 -0.000196838 +0.05 -0.000222138 +0.06 -0.000233839 +0.07 -0.000231213 +0.08 -0.000214359 +0.09 -0.000183276 +0.1 -0.000140635 +0.11 -8.90557e-05 +0.12 -3.12665e-05 +0.13 2.98346e-05 +0.14 9.13917e-05 +0.15 0.000150783 +0.16 0.000205772 +0.17 0.00025415 +0.18 0.00029457 +0.19 0.000326276 +0.2 0.000348152 +0.21 0.000358503 +0.22 0.000354435 +0.23 0.000331122 +0.24 0.000280984 +0.25 0.000190825 +0.26 4.12237e-05 +0.27 -0.000187373 +0.28 -0.0005225 +0.29 -0.000997246 +0.3 -0.00164978 +0.31 -0.00252236 +0.32 -0.00365969 +0.33 -0.00511268 +0.34 -0.00695 +0.35 -0.00917954 +0.36 -0.0118233 +0.37 -0.0148868 +0.38 -0.018355 +0.39 -0.0221869 +0.4 -0.0263119 +0.41 -0.0306259 +0.42 -0.0349766 +0.43 -0.0391609 +0.44 -0.0429555 +0.45 -0.0461041 +0.46 -0.0483211 +0.47 -0.0492994 +0.48 -0.0487194 +0.49 -0.0462597 +0.5 -0.0414257 +0.51 -0.0340205 +0.52 -0.0238655 +0.53 -0.0107895 +0.54 0.00530389 +0.55 0.024424 +0.56 0.0464818 +0.57 0.0712817 +0.58 0.098626 +0.59 0.128003 +0.6 0.15876 +0.61 0.190252 +0.62 0.221758 +0.63 0.252494 +0.64 0.281643 +0.65 0.308377 +0.66 0.331752 +0.67 0.350809 +0.68 0.365113 +0.69 0.374132 +0.7 0.377481 +0.71 0.374938 +0.72 0.366463 +0.73 0.352205 +0.74 0.332442 +0.75 0.307428 +0.76 0.278356 +0.77 0.246109 +0.78 0.211681 +0.79 0.176143 +0.8 0.1406 +0.81 0.106151 +0.82 0.0738387 +0.83 0.0449691 +0.84 0.0201323 +0.85 -0.000104726 +0.86 -0.015363 +0.87 -0.025491 +0.88 -0.0305733 +0.89 -0.0309286 +0.9 -0.0270979 +0.91 -0.0196432 +0.92 -0.00974408 +0.93 0.00145192 +0.94 0.012822 +0.95 0.0232302 +0.96 0.0315806 +0.97 0.0368705 +0.98 0.0382398 +0.99 0.0347562 +1 0.0259576 +1.01 0.0119214 +1.02 -0.00723464 +1.03 -0.0311333 +1.04 -0.0591535 +1.05 -0.0904568 +1.06 -0.124025 +1.07 -0.158703 +1.08 -0.19311 +1.09 -0.225944 +1.1 -0.256029 +1.11 -0.282305 +1.12 -0.303889 +1.13 -0.320117 +1.14 -0.33058 +1.15 -0.335091 +1.16 -0.333417 +1.17 -0.326591 +1.18 -0.31539 +1.19 -0.300815 +1.2 -0.284045 +1.21 -0.266377 +1.22 -0.249165 +1.23 -0.233753 +1.24 -0.221773 +1.25 -0.21417 +1.26 -0.211768 +1.27 -0.215156 +1.28 -0.224624 +1.29 -0.240149 +1.3 -0.261388 +1.31 -0.28769 +1.32 -0.318331 +1.33 -0.351839 +1.34 -0.386725 +1.35 -0.421535 +1.36 -0.454794 +1.37 -0.485088 +1.38 -0.511133 +1.39 -0.531846 +1.4 -0.546082 +1.41 -0.553273 +1.42 -0.553633 +1.43 -0.547408 +1.44 -0.535184 +1.45 -0.51786 +1.46 -0.496605 +1.47 -0.472802 +1.48 -0.447996 +1.49 -0.423989 +1.5 -0.402396 +1.51 -0.384656 +1.52 -0.37201 +1.53 -0.365439 +1.54 -0.365608 +1.55 -0.372827 +1.56 -0.387082 +1.57 -0.408404 +1.58 -0.435291 +1.59 -0.466568 +1.6 -0.500821 +1.61 -0.536465 +1.62 -0.571829 +1.63 -0.60524 +1.64 -0.63512 +1.65 -0.659547 +1.66 -0.677665 +1.67 -0.688797 +1.68 -0.692577 +1.69 -0.689036 +1.7 -0.678609 +1.71 -0.662116 +1.72 -0.64072 +1.73 -0.615761 +1.74 -0.58922 +1.75 -0.563004 +1.76 -0.538914 +1.77 -0.518651 +1.78 -0.503727 +1.79 -0.495379 +1.8 -0.494495 +1.81 -0.501917 +1.82 -0.517719 +1.83 -0.540968 +1.84 -0.57074 +1.85 -0.605729 +1.86 -0.644316 +1.87 -0.68465 +1.88 -0.724745 +1.89 -0.762485 +1.9 -0.795529 +1.91 -0.822392 +1.92 -0.841714 +1.93 -0.85253 +1.94 -0.854334 +1.95 -0.847112 +1.96 -0.831357 +1.97 -0.808027 +1.98 -0.778177 +1.99 -0.744382 +2 -0.708782 +2.01 -0.67366 +2.02 -0.641318 +2.03 -0.613954 +2.04 -0.593531 +2.05 -0.581668 +2.06 -0.58038 +2.07 -0.58965 +2.08 -0.609235 +2.09 -0.638459 +2.1 -0.67607 +2.11 -0.720291 +2.12 -0.76891 +2.13 -0.819381 +2.14 -0.868731 +2.15 -0.913873 +2.16 -0.952227 +2.17 -0.981474 +2.18 -0.999749 +2.19 -1.00576 +2.2 -0.998875 +2.21 -0.979219 +2.22 -0.947251 +2.23 -0.904731 +2.24 -0.854809 +2.25 -0.800525 +2.26 -0.745335 +2.27 -0.692939 +2.28 -0.647081 +2.29 -0.611337 +2.3 -0.589225 +2.31 -0.58444 +2.32 -0.597487 +2.33 -0.628939 +2.34 -0.678251 +2.35 -0.743695 +2.36 -0.822341 +2.37 -0.910093 +2.38 -1.00179 +2.39 -1.09049 +2.4 -1.16899 +2.41 -1.22997 +2.42 -1.26606 +2.43 -1.27009 +2.44 -1.23544 +2.45 -1.15633 +2.46 -1.02815 +2.47 -0.843493 +2.48 -0.603955 +2.49 -0.311279 +2.5 0.0319351 +2.51 0.421176 +2.52 0.850088 +2.53 1.31061 +2.54 1.79319 +2.55 2.287 +2.56 2.77866 +2.57 3.25552 +2.58 3.70534 +2.59 4.11626 +2.6 4.47722 +2.61 4.77837 +2.62 5.01134 +2.63 5.16661 +2.64 5.23758 +2.65 5.22705 +2.66 5.13554 +2.67 4.96582 +2.68 4.72279 +2.69 4.41332 +2.7 4.04599 +2.71 3.63017 +2.72 3.17585 +2.73 2.69858 +2.74 2.21034 +2.75 1.72288 +2.76 1.24733 +2.77 0.793881 +2.78 0.371472 +2.79 -0.0124464 +2.8 -0.347506 +2.81 -0.633763 +2.82 -0.86961 +2.83 -1.05507 +2.84 -1.19182 +2.85 -1.28301 +2.86 -1.33304 +2.87 -1.34733 +2.88 -1.3303 +2.89 -1.29074 +2.9 -1.23598 +2.91 -1.17246 +2.92 -1.10613 +2.93 -1.04228 +2.94 -0.985342 +2.95 -0.938771 +2.96 -0.905772 +2.97 -0.887611 +2.98 -0.883846 +2.99 -0.893695 +3 -0.915602 +3.01 -0.947388 +3.02 -0.986431 +3.03 -1.02985 +3.04 -1.07463 +3.05 -1.11757 +3.06 -1.1561 +3.07 -1.18817 +3.08 -1.21225 +3.09 -1.22739 +3.1 -1.23325 +3.11 -1.2301 +3.12 -1.21865 +3.13 -1.1999 +3.14 -1.17624 +3.15 -1.14963 +3.16 -1.12208 +3.17 -1.09556 +3.18 -1.07184 +3.19 -1.0524 +3.2 -1.03829 +3.21 -1.03064 +3.22 -1.02888 +3.23 -1.03233 +3.24 -1.03979 +3.25 -1.04957 +3.26 -1.05959 +3.27 -1.0674 +3.28 -1.07038 +3.29 -1.06508 +3.3 -1.0489 +3.31 -1.01963 +3.32 -0.975204 +3.33 -0.913981 +3.34 -0.834882 +3.35 -0.737432 +3.36 -0.621813 +3.37 -0.488084 +3.38 -0.33814 +3.39 -0.175101 +3.4 -0.00184316 +3.41 0.178282 +3.42 0.361547 +3.43 0.543957 +3.44 0.721376 +3.45 0.889232 +3.46 1.04254 +3.47 1.17819 +3.48 1.29279 +3.49 1.38341 +3.5 1.44764 +3.51 1.48368 +3.52 1.49035 +3.53 1.46687 +3.54 1.41125 +3.55 1.32714 +3.56 1.21624 +3.57 1.08089 +3.58 0.923996 +3.59 0.74897 +3.6 0.5597 +3.61 0.360456 +3.62 0.155773 +3.63 -0.0486545 +3.64 -0.247808 +3.65 -0.43669 +3.66 -0.610365 +3.67 -0.76406 +3.68 -0.893258 +3.69 -0.993795 +3.7 -1.05953 +3.71 -1.08828 +3.72 -1.07866 +3.73 -1.02906 +3.74 -0.938667 +3.75 -0.807552 +3.76 -0.636697 +3.77 -0.428027 +3.78 -0.18293 +3.79 0.0942463 +3.8 0.396886 +3.81 0.719318 +3.82 1.05519 +3.83 1.39758 +3.84 1.73918 +3.85 2.07243 +3.86 2.38907 +3.87 2.6798 +3.88 2.93883 +3.89 3.15978 +3.9 3.33705 +3.91 3.46599 +3.92 3.54309 +3.93 3.56602 +3.94 3.53339 +3.95 3.44115 +3.96 3.29635 +3.97 3.10231 +3.98 2.86357 +3.99 2.58572 +4 2.27526 +4.01 1.9394 +4.02 1.58584 +4.03 1.22236 +4.04 0.858331 +4.05 0.501463 +4.06 0.158825 +4.07 -0.163251 +4.08 -0.459347 +4.09 -0.7251 +4.1 -0.957304 +4.11 -1.15162 +4.12 -1.30849 +4.13 -1.42984 +4.14 -1.51747 +4.15 -1.57416 +4.16 -1.60352 +4.17 -1.60977 +4.18 -1.59756 +4.19 -1.57141 +4.2 -1.53675 +4.21 -1.4989 +4.22 -1.46206 +4.23 -1.42985 +4.24 -1.4052 +4.25 -1.39025 +4.26 -1.38627 +4.27 -1.39395 +4.28 -1.41329 +4.29 -1.44205 +4.3 -1.4782 +4.31 -1.51913 +4.32 -1.56185 +4.33 -1.60307 +4.34 -1.63942 +4.35 -1.6675 +4.36 -1.68281 +4.37 -1.68342 +4.38 -1.66696 +4.39 -1.6316 +4.4 -1.57605 +4.41 -1.4996 +4.42 -1.40212 +4.43 -1.28402 +4.44 -1.14473 +4.45 -0.987091 +4.46 -0.81322 +4.47 -0.625179 +4.48 -0.425208 +4.49 -0.215648 +4.5 0.0011287 +4.51 0.22278 +4.52 0.44706 +4.53 0.6715 +4.54 0.894005 +4.55 1.11268 +4.56 1.32574 +4.57 1.53148 +4.58 1.72825 +4.59 1.91442 +4.6 2.08777 +4.61 2.24622 +4.62 2.38874 +4.63 2.51359 +4.64 2.61897 +4.65 2.7031 +4.66 2.76425 +4.67 2.80078 +4.68 2.81066 +4.69 2.79104 +4.7 2.74314 +4.71 2.66656 +4.72 2.56139 +4.73 2.42826 +4.74 2.26834 +4.75 2.08341 +4.76 1.87579 +4.77 1.64683 +4.78 1.40217 +4.79 1.14572 +4.8 0.881664 +4.81 0.614358 +4.82 0.3482 +4.83 0.0874882 +4.84 -0.16371 +4.85 -0.400398 +4.86 -0.619904 +4.87 -0.820017 +4.88 -0.998942 +4.89 -1.1556 +4.9 -1.28965 +4.91 -1.40145 +4.92 -1.49203 +4.93 -1.562 +4.94 -1.61412 +4.95 -1.65168 +4.96 -1.67736 +4.97 -1.69389 +4.98 -1.70387 +4.99 -1.70964 +5 -1.71317 +5.01 -1.71598 +5.02 -1.71903 +5.03 -1.72254 +5.04 -1.72611 +5.05 -1.72872 +5.06 -1.72889 +5.07 -1.72467 +5.08 -1.71385 +5.09 -1.69384 +5.1 -1.66126 +5.11 -1.61468 +5.12 -1.55223 +5.13 -1.47253 +5.14 -1.37476 +5.15 -1.25882 +5.16 -1.12532 +5.17 -0.975656 +5.18 -0.810916 +5.19 -0.635612 +5.2 -0.453411 +5.21 -0.268426 +5.22 -0.0851086 +5.23 0.0919138 +5.24 0.258012 +5.25 0.408731 +5.26 0.538225 +5.27 0.643633 +5.28 0.722643 +5.29 0.773267 +5.3 0.794376 +5.31 0.785731 +5.32 0.747981 +5.33 0.682632 +5.34 0.590747 +5.35 0.476036 +5.36 0.343192 +5.37 0.196179 +5.38 0.0391514 +5.39 -0.123712 +5.4 -0.288369 +5.41 -0.451061 +5.42 -0.608155 +5.43 -0.756321 +5.44 -0.893851 +5.45 -1.01932 +5.46 -1.13195 +5.47 -1.23159 +5.48 -1.31867 +5.49 -1.39409 +5.5 -1.45903 +5.51 -1.51465 +5.52 -1.56353 +5.53 -1.60745 +5.54 -1.64804 +5.55 -1.68675 +5.56 -1.72475 +5.57 -1.76286 +5.58 -1.80153 +5.59 -1.84089 +5.6 -1.88051 +5.61 -1.91976 +5.62 -1.95775 +5.63 -1.99346 +5.64 -2.0258 +5.65 -2.05374 +5.66 -2.07634 +5.67 -2.09243 +5.68 -2.10183 +5.69 -2.10466 +5.7 -2.10121 +5.71 -2.09212 +5.72 -2.07832 +5.73 -2.06099 +5.74 -2.04149 +5.75 -2.02127 +5.76 -2.00191 +5.77 -1.98464 +5.78 -1.9704 +5.79 -1.95972 +5.8 -1.95269 +5.81 -1.94886 +5.82 -1.94721 +5.83 -1.9461 +5.84 -1.94299 +5.85 -1.93508 +5.86 -1.91921 +5.87 -1.89193 +5.88 -1.84966 +5.89 -1.78883 +5.9 -1.70607 +5.91 -1.59803 +5.92 -1.45993 +5.93 -1.29233 +5.94 -1.09441 +5.95 -0.866254 +5.96 -0.608972 +5.97 -0.324666 +5.98 -0.0164496 +5.99 0.311614 +6 0.655411 +6.01 1.00747 +6.02 1.36129 +6.03 1.71014 +6.04 2.04714 +6.05 2.36544 +6.06 2.65845 +6.07 2.92002 +6.08 3.14175 +6.09 3.31995 +6.1 3.45216 +6.11 3.53582 +6.12 3.56949 +6.13 3.55281 +6.14 3.48653 +6.15 3.37242 +6.16 3.21123 +6.17 3.00704 +6.18 2.76655 +6.19 2.49477 +6.2 2.19709 +6.21 1.87916 +6.22 1.54673 +6.23 1.20553 +6.24 0.861223 +6.25 0.519844 +6.26 0.186507 +6.27 -0.134219 +6.28 -0.438172 +6.29 -0.721615 +6.3 -0.981221 +6.31 -1.21407 +6.32 -1.41736 +6.33 -1.58609 +6.34 -1.72111 +6.35 -1.82087 +6.36 -1.88408 +6.37 -1.90969 +6.38 -1.89691 +6.39 -1.8452 +6.4 -1.75429 +6.41 -1.62094 +6.42 -1.44852 +6.43 -1.23842 +6.44 -0.992022 +6.45 -0.711174 +6.46 -0.398258 +6.47 -0.0562008 +6.48 0.311524 +6.49 0.702007 +6.5 1.10943 +6.51 1.52802 +6.52 1.95232 +6.53 2.37659 +6.54 2.79492 +6.55 3.20136 +6.56 3.59002 +6.57 3.9539 +6.58 4.28676 +6.59 4.58515 +6.6 4.84487 +6.61 5.06248 +6.62 5.23525 +6.63 5.3613 +6.64 5.43959 +6.65 5.46879 +6.66 5.44739 +6.67 5.38054 +6.68 5.27056 +6.69 5.12041 +6.7 4.93353 +6.71 4.7138 +6.72 4.46539 +6.73 4.19254 +6.74 3.89832 +6.75 3.58931 +6.76 3.26971 +6.77 2.94351 +6.78 2.61442 +6.79 2.28587 +6.8 1.96092 +6.81 1.64233 +6.82 1.33342 +6.83 1.03591 +6.84 0.751465 +6.85 0.48168 +6.86 0.227992 +6.87 -0.0082921 +6.88 -0.225952 +6.89 -0.423823 +6.9 -0.599417 +6.91 -0.752033 +6.92 -0.881368 +6.93 -0.986398 +6.94 -1.06618 +6.95 -1.11987 +6.96 -1.1468 +6.97 -1.14651 +6.98 -1.11767 +6.99 -1.06006 +7 -0.975807 +7.01 -0.865942 +7.02 -0.731934 +7.03 -0.57573 +7.04 -0.399756 +7.05 -0.206904 +7.06 -0.000301267 +7.07 0.216414 +7.08 0.438113 +7.09 0.66043 +7.1 0.87885 +7.11 1.08881 +7.12 1.28579 +7.13 1.46546 +7.14 1.62369 +7.15 1.75405 +7.16 1.85555 +7.17 1.92565 +7.18 1.96243 +7.19 1.96467 +7.2 1.93186 +7.21 1.86423 +7.22 1.76273 +7.23 1.62667 +7.24 1.46062 +7.25 1.26824 +7.26 1.05308 +7.27 0.819067 +7.28 0.570451 +7.29 0.311653 +7.3 0.0471694 +7.31 -0.218361 +7.32 -0.480133 +7.33 -0.734173 +7.34 -0.976922 +7.35 -1.20529 +7.36 -1.41671 +7.37 -1.6092 +7.38 -1.78138 +7.39 -1.9317 +7.4 -2.05959 +7.41 -2.16691 +7.42 -2.25473 +7.43 -2.32461 +7.44 -2.37846 +7.45 -2.41846 +7.46 -2.44701 +7.47 -2.46645 +7.48 -2.47907 +7.49 -2.48784 +7.5 -2.49478 +7.51 -2.50162 +7.52 -2.50969 +7.53 -2.5199 +7.54 -2.53271 +7.55 -2.5481 +7.56 -2.5657 +7.57 -2.58426 +7.58 -2.60243 +7.59 -2.61852 +7.6 -2.63068 +7.61 -2.6369 +7.62 -2.63518 +7.63 -2.62359 +7.64 -2.59939 +7.65 -2.56162 +7.66 -2.50955 +7.67 -2.44262 +7.68 -2.36077 +7.69 -2.26444 +7.7 -2.15465 +7.71 -2.03288 +7.72 -1.90073 +7.73 -1.76108 +7.74 -1.61718 +7.75 -1.47208 +7.76 -1.32899 +7.77 -1.1911 +7.78 -1.06153 +7.79 -0.943176 +7.8 -0.839202 +7.81 -0.752468 +7.82 -0.683671 +7.83 -0.633856 +7.84 -0.603487 +7.85 -0.592441 +7.86 -0.600012 +7.87 -0.624951 +7.88 -0.665704 +7.89 -0.720755 +7.9 -0.786239 +7.91 -0.859346 +7.92 -0.937142 +7.93 -1.01666 +7.94 -1.095 +7.95 -1.16942 +7.96 -1.23741 +7.97 -1.29575 +7.98 -1.34322 +7.99 -1.37843 +8 -1.40034 +8.01 -1.40827 +8.02 -1.40191 +8.03 -1.38124 +8.04 -1.34653 +8.05 -1.29737 +8.06 -1.23514 +8.07 -1.16103 +8.08 -1.07596 +8.09 -0.980876 +8.1 -0.876756 +8.11 -0.764583 +8.12 -0.645347 +8.13 -0.519793 +8.14 -0.389132 +8.15 -0.254752 +8.16 -0.117862 +8.17 0.0202017 +8.18 0.157948 +8.19 0.29371 +8.2 0.425639 +8.21 0.551442 +8.22 0.668355 +8.23 0.774435 +8.24 0.867263 +8.25 0.944429 +8.26 1.00362 +8.27 1.0427 +8.28 1.05984 +8.29 1.05333 +8.3 1.02037 +8.31 0.962818 +8.32 0.881206 +8.33 0.776807 +8.34 0.651683 +8.35 0.508682 +8.36 0.351414 +8.37 0.184189 +8.38 0.0119661 +8.39 -0.158901 +8.4 -0.322578 +8.41 -0.473162 +8.42 -0.604833 +8.43 -0.712051 +8.44 -0.789765 +8.45 -0.833604 +8.46 -0.837316 +8.47 -0.799788 +8.48 -0.721094 +8.49 -0.601462 +8.5 -0.442426 +8.51 -0.246816 +8.52 -0.0187092 +8.53 0.236657 +8.54 0.513684 +8.55 0.804246 +8.56 1.09949 +8.57 1.39121 +8.58 1.67113 +8.59 1.93114 +8.6 2.16355 +8.61 2.36133 +8.62 2.51705 +8.63 2.62329 +8.64 2.67946 +8.65 2.68347 +8.66 2.63461 +8.67 2.53367 +8.68 2.38283 +8.69 2.18562 +8.7 1.94651 +8.71 1.66905 +8.72 1.36381 +8.73 1.03829 +8.74 0.700377 +8.75 0.358149 +8.76 0.0196144 +8.77 -0.307476 +8.78 -0.615834 +8.79 -0.89612 +8.8 -1.14392 +8.81 -1.35481 +8.82 -1.52505 +8.83 -1.65191 +8.84 -1.73374 +8.85 -1.76988 +8.86 -1.76068 +8.87 -1.70464 +8.88 -1.60563 +8.89 -1.46804 +8.9 -1.29543 +8.91 -1.09181 +8.92 -0.861573 +8.93 -0.609376 +8.94 -0.340027 +8.95 -0.0580883 +8.96 0.230996 +8.97 0.521842 +8.98 0.809829 +8.99 1.09053 +9 1.35975 +9.01 1.61356 +9.02 1.84831 +9.03 2.06003 +9.04 2.24419 +9.05 2.39993 +9.06 2.52507 +9.07 2.61786 +9.08 2.67698 +9.09 2.70157 +9.1 2.69126 +9.11 2.64602 +9.12 2.56362 +9.13 2.44863 +9.14 2.30292 +9.15 2.12891 +9.16 1.92952 +9.17 1.70816 +9.18 1.46869 +9.19 1.21533 +9.2 0.952242 +9.21 0.685311 +9.22 0.419587 +9.23 0.159992 +9.24 -0.088652 +9.25 -0.321726 +9.26 -0.534919 +9.27 -0.724331 +9.28 -0.884689 +9.29 -1.01364 +9.3 -1.11016 +9.31 -1.17295 +9.32 -1.20144 +9.33 -1.1958 +9.34 -1.15694 +9.35 -1.08648 +9.36 -0.9856 +9.37 -0.857254 +9.38 -0.706602 +9.39 -0.537601 +9.4 -0.354533 +9.41 -0.161909 +9.42 0.0356373 +9.43 0.23345 +9.44 0.426756 +9.45 0.610201 +9.46 0.780098 +9.47 0.932733 +9.48 1.06482 +9.49 1.17353 +9.5 1.25658 +9.51 1.31221 +9.52 1.3392 +9.53 1.33416 +9.54 1.29995 +9.55 1.23723 +9.56 1.14716 +9.57 1.03143 +9.58 0.892137 +9.59 0.731814 +9.6 0.55334 +9.61 0.358991 +9.62 0.153463 +9.63 -0.0593283 +9.64 -0.275643 +9.65 -0.491702 +9.66 -0.703752 +9.67 -0.908125 +9.68 -1.1013 +9.69 -1.27892 +9.7 -1.43788 +9.71 -1.57609 +9.72 -1.69126 +9.73 -1.78149 +9.74 -1.8453 +9.75 -1.8817 +9.76 -1.89015 +9.77 -1.86953 +9.78 -1.81981 +9.79 -1.74369 +9.8 -1.64262 +9.81 -1.51851 +9.82 -1.37367 +9.83 -1.21073 +9.84 -1.03267 +9.85 -0.842549 +9.86 -0.643681 +9.87 -0.44057 +9.88 -0.236885 +9.89 -0.0363007 +9.9 0.157568 +9.91 0.341225 +9.92 0.511359 +9.93 0.664899 +9.94 0.796987 +9.95 0.906892 +9.96 0.992747 +9.97 1.0531 +9.98 1.087 +9.99 1.09399 +10 1.07417 +10.01 1.02819 +10.02 0.955481 +10.03 0.859362 +10.04 0.742753 +10.05 0.60849 +10.06 0.45983 +10.07 0.300391 +10.08 0.134084 +10.09 -0.0349715 +10.1 -0.202207 +10.11 -0.362848 +10.12 -0.51284 +10.13 -0.648251 +10.14 -0.76547 +10.15 -0.861301 +10.16 -0.933058 +10.17 -0.978645 +10.18 -0.995624 +10.19 -0.982484 +10.2 -0.941167 +10.21 -0.872617 +10.22 -0.77851 +10.23 -0.661211 +10.24 -0.523713 +10.25 -0.36955 +10.26 -0.202575 +10.27 -0.0269905 +10.28 0.151706 +10.29 0.329011 +10.3 0.500562 +10.31 0.662271 +10.32 0.810463 +10.33 0.941984 +10.34 1.05431 +10.35 1.14357 +10.36 1.21057 +10.37 1.25533 +10.38 1.27851 +10.39 1.2815 +10.4 1.26635 +10.41 1.23563 +10.42 1.1924 +10.43 1.1396 +10.44 1.08153 +10.45 1.02193 +10.46 0.964101 +10.47 0.911048 +10.48 0.865324 +10.49 0.828936 +10.5 0.803261 +10.51 0.78958 +10.52 0.787698 +10.53 0.796188 +10.54 0.813491 +10.55 0.837461 +10.56 0.865457 +10.57 0.894454 +10.58 0.92118 +10.59 0.941951 +10.6 0.95271 +10.61 0.950736 +10.62 0.93323 +10.63 0.897835 +10.64 0.842725 +10.65 0.76667 +10.66 0.669085 +10.67 0.549796 +10.68 0.408101 +10.69 0.247308 +10.7 0.0693978 +10.71 -0.123206 +10.72 -0.327736 +10.73 -0.541184 +10.74 -0.760412 +10.75 -0.982258 +10.76 -1.20333 +10.77 -1.42054 +10.78 -1.63128 +10.79 -1.83327 +10.8 -2.02456 +10.81 -2.20357 +10.82 -2.36909 +10.83 -2.52022 +10.84 -2.65534 +10.85 -2.77471 +10.86 -2.87869 +10.87 -2.96735 +10.88 -3.04084 +10.89 -3.09935 +10.9 -3.14308 +10.91 -3.1722 +10.92 -3.18616 +10.93 -3.1851 +10.94 -3.16982 +10.95 -3.14048 +10.96 -3.09726 +10.97 -3.04048 +10.98 -2.9706 +10.99 -2.88828 +11 -2.79412 +11.01 -2.68902 +11.02 -2.57528 +11.03 -2.45465 +11.04 -2.32915 +11.05 -2.201 +11.06 -2.07253 +11.07 -1.94616 +11.08 -1.82433 +11.09 -1.71005 +11.1 -1.60486 +11.11 -1.51035 +11.12 -1.4277 +11.13 -1.35755 +11.14 -1.3 +11.15 -1.2545 +11.16 -1.21993 +11.17 -1.19516 +11.18 -1.17694 +11.19 -1.16235 +11.2 -1.14824 +11.21 -1.13127 +11.22 -1.10807 +11.23 -1.07537 +11.24 -1.03014 +11.25 -0.968691 +11.26 -0.889346 +11.27 -0.791434 +11.28 -0.674568 +11.29 -0.539197 +11.3 -0.386615 +11.31 -0.218938 +11.32 -0.039039 +11.33 0.149755 +11.34 0.342848 +11.35 0.53535 +11.36 0.722686 +11.37 0.900362 +11.38 1.06417 +11.39 1.21038 +11.4 1.33591 +11.41 1.43789 +11.42 1.51378 +11.43 1.56521 +11.44 1.59313 +11.45 1.59956 +11.46 1.58744 +11.47 1.56054 +11.48 1.52334 +11.49 1.4808 +11.5 1.43868 +11.51 1.40265 +11.52 1.37767 +11.53 1.36823 +11.54 1.37809 +11.55 1.41011 +11.56 1.46607 +11.57 1.5466 +11.58 1.65293 +11.59 1.78121 +11.6 1.92776 +11.61 2.08819 +11.62 2.25722 +11.63 2.42886 +11.64 2.59665 +11.65 2.75392 +11.66 2.89257 +11.67 3.00611 +11.68 3.08965 +11.69 3.13858 +11.7 3.14936 +11.71 3.11968 +11.72 3.04862 +11.73 2.93663 +11.74 2.78407 +11.75 2.59425 +11.76 2.37406 +11.77 2.12926 +11.78 1.86642 +11.79 1.5927 +11.8 1.31555 +11.81 1.04247 +11.82 0.781146 +11.83 0.540016 +11.84 0.323954 +11.85 0.137784 +11.86 -0.0148163 +11.87 -0.131438 +11.88 -0.210997 +11.89 -0.253745 +11.9 -0.261183 +11.91 -0.233371 +11.92 -0.177757 +11.93 -0.0992744 +11.94 -0.00351148 +11.95 0.103519 +11.96 0.215608 +11.97 0.326589 +11.98 0.430559 +11.99 0.520718 +12 0.592705 +12.01 0.643033 +12.02 0.66893 +12.03 0.668647 +12.04 0.641485 +12.05 0.587787 +12.06 0.508882 +12.07 0.405834 +12.08 0.282595 +12.09 0.143847 +12.1 -0.00616526 +12.11 -0.162925 +12.12 -0.321818 +12.13 -0.478301 +12.14 -0.628063 +12.15 -0.76662 +12.16 -0.889901 +12.17 -0.995918 +12.18 -1.08272 +12.19 -1.14909 +12.2 -1.19457 +12.21 -1.21947 +12.22 -1.22476 +12.23 -1.21182 +12.24 -1.18216 +12.25 -1.13995 +12.26 -1.08828 +12.27 -1.03043 +12.28 -0.969763 +12.29 -0.909586 +12.3 -0.853055 +12.31 -0.803048 +12.32 -0.763139 +12.33 -0.734596 +12.34 -0.718778 +12.35 -0.716467 +12.36 -0.727819 +12.37 -0.752362 +12.38 -0.788999 +12.39 -0.836039 +12.4 -0.891654 +12.41 -0.952335 +12.42 -1.0147 +12.43 -1.07528 +12.44 -1.13039 +12.45 -1.17633 +12.46 -1.20943 +12.47 -1.2262 +12.48 -1.2222 +12.49 -1.19464 +12.5 -1.14223 +12.51 -1.06345 +12.52 -0.957502 +12.53 -0.824311 +12.54 -0.664579 +12.55 -0.479789 +12.56 -0.271483 +12.57 -0.0425291 +12.58 0.201915 +12.59 0.457566 +12.6 0.719725 +12.61 0.983411 +12.62 1.2435 +12.63 1.49489 +12.64 1.73234 +12.65 1.94954 +12.66 2.14367 +12.67 2.31133 +12.68 2.44986 +12.69 2.5574 +12.7 2.63297 +12.71 2.67648 +12.72 2.68872 +12.73 2.66881 +12.74 2.62208 +12.75 2.55191 +12.76 2.46202 +12.77 2.35651 +12.78 2.23967 +12.79 2.11581 +12.8 1.98911 +12.81 1.86378 +12.82 1.74346 +12.83 1.63079 +12.84 1.52777 +12.85 1.43559 +12.86 1.35469 +12.87 1.28471 +12.88 1.22456 +12.89 1.17277 +12.9 1.12662 +12.91 1.08296 +12.92 1.03877 +12.93 0.991002 +12.94 0.936716 +12.95 0.873277 +12.96 0.7985 +12.97 0.710343 +12.98 0.607427 +12.99 0.490696 +13 0.36105 +13.01 0.220201 +13.02 0.0706272 +13.03 -0.0845119 +13.04 -0.241498 +13.05 -0.396116 +13.06 -0.543036 +13.07 -0.678121 +13.08 -0.797182 +13.09 -0.896428 +13.1 -0.972647 +13.11 -1.02336 +13.12 -1.04697 +13.13 -1.04284 +13.14 -1.00901 +13.15 -0.949431 +13.16 -0.866988 +13.17 -0.765237 +13.18 -0.64851 +13.19 -0.521748 +13.2 -0.390318 +13.21 -0.259798 +13.22 -0.136587 +13.23 -0.0262785 +13.24 0.066602 +13.25 0.13803 +13.26 0.184854 +13.27 0.20492 +13.28 0.197168 +13.29 0.161675 +13.3 0.0984713 +13.31 0.0103091 +13.32 -0.0980056 +13.33 -0.22214 +13.34 -0.357123 +13.35 -0.497549 +13.36 -0.637797 +13.37 -0.772257 +13.38 -0.895084 +13.39 -1.0002 +13.4 -1.08435 +13.41 -1.14428 +13.42 -1.17773 +13.43 -1.18345 +13.44 -1.16129 +13.45 -1.11213 +13.46 -1.03769 +13.47 -0.939345 +13.48 -0.823041 +13.49 -0.693086 +13.5 -0.554158 +13.51 -0.411111 +13.52 -0.268782 +13.53 -0.131791 +13.54 -0.00437177 +13.55 0.108376 +13.56 0.204265 +13.57 0.281391 +13.58 0.338506 +13.59 0.375189 +13.6 0.391811 +13.61 0.389465 +13.62 0.369874 +13.63 0.334458 +13.64 0.286659 +13.65 0.22979 +13.66 0.166715 +13.67 0.100178 +13.68 0.0326685 +13.69 -0.0336869 +13.7 -0.097213 +13.71 -0.156532 +13.72 -0.210976 +13.73 -0.260798 +13.74 -0.30652 +13.75 -0.349087 +13.76 -0.389776 +13.77 -0.430089 +13.78 -0.471642 +13.79 -0.516157 +13.8 -0.565459 +13.81 -0.620494 +13.82 -0.682078 +13.83 -0.750605 +13.84 -0.826004 +13.85 -0.907721 +13.86 -0.994728 +13.87 -1.08557 +13.88 -1.17835 +13.89 -1.27055 +13.9 -1.35978 +13.91 -1.44364 +13.92 -1.51973 +13.93 -1.58586 +13.94 -1.6401 +13.95 -1.68086 +13.96 -1.70577 +13.97 -1.71519 +13.98 -1.70934 +13.99 -1.68873 +14 -1.65437 +14.01 -1.60767 +14.02 -1.55043 +14.03 -1.48468 +14.04 -1.41241 +14.05 -1.33631 +14.06 -1.2588 +14.07 -1.1819 +14.08 -1.10739 +14.09 -1.0367 +14.1 -0.970912 +14.11 -0.910687 +14.12 -0.856541 +14.13 -0.808377 +14.14 -0.765349 +14.15 -0.726682 +14.16 -0.691431 +14.17 -0.658562 +14.18 -0.627054 +14.19 -0.595985 +14.2 -0.564599 +14.21 -0.532355 +14.22 -0.499195 +14.23 -0.465379 +14.24 -0.431518 +14.25 -0.398556 +14.26 -0.367722 +14.27 -0.340465 +14.28 -0.318385 +14.29 -0.3039 +14.3 -0.298005 +14.31 -0.302008 +14.32 -0.3169 +14.33 -0.343265 +14.34 -0.381203 +14.35 -0.430272 +14.36 -0.489467 +14.37 -0.557722 +14.38 -0.63211 +14.39 -0.709787 +14.4 -0.787701 +14.41 -0.862558 +14.42 -0.930959 +14.43 -0.989547 +14.44 -1.03516 +14.45 -1.06391 +14.46 -1.07361 +14.47 -1.06336 +14.48 -1.03236 +14.49 -0.980658 +14.5 -0.90911 +14.51 -0.819419 +14.52 -0.714074 +14.53 -0.595946 +14.54 -0.469196 +14.55 -0.338858 +14.56 -0.209489 +14.57 -0.0856915 +14.58 0.0280822 +14.59 0.127721 +14.6 0.209638 +14.61 0.270503 +14.62 0.306741 +14.63 0.319113 +14.64 0.307721 +14.65 0.273684 +14.66 0.219117 +14.67 0.147061 +14.68 0.0613688 +14.69 -0.0334261 +14.7 -0.132161 +14.71 -0.228635 +14.72 -0.317399 +14.73 -0.393168 +14.74 -0.451014 +14.75 -0.486553 +14.76 -0.496109 +14.77 -0.476844 +14.78 -0.424453 +14.79 -0.339792 +14.8 -0.223911 +14.81 -0.0781834 +14.82 0.0950933 +14.83 0.292799 +14.84 0.511119 +14.85 0.745696 +14.86 0.992156 +14.87 1.24478 +14.88 1.49817 +14.89 1.7476 +14.9 1.98865 +14.91 2.21738 +14.92 2.4304 +14.93 2.62496 +14.94 2.79818 +14.95 2.94801 +14.96 3.07514 +14.97 3.1796 +14.98 3.26193 +14.99 3.32309 +15 3.36439 +15.01 3.38734 +15.02 3.39338 +15.03 3.38332 +15.04 3.3601 +15.05 3.32511 +15.06 3.27957 +15.07 3.22442 +15.08 3.1604 +15.09 3.08798 +15.1 3.00742 +15.11 2.91809 +15.12 2.82065 +15.13 2.71509 +15.14 2.60134 +15.15 2.47943 +15.16 2.34952 +15.17 2.21194 +15.18 2.06721 +15.19 1.91564 +15.2 1.75862 +15.21 1.59745 +15.22 1.43343 +15.23 1.26794 +15.24 1.10239 +15.25 0.93818 +15.26 0.776609 +15.27 0.61909 +15.28 0.46665 +15.29 0.319774 +15.3 0.178859 +15.31 0.0440296 +15.32 -0.0848544 +15.33 -0.20817 +15.34 -0.326491 +15.35 -0.440421 +15.36 -0.550739 +15.37 -0.658509 +15.38 -0.764436 +15.39 -0.869069 +15.4 -0.972742 +15.41 -1.07552 +15.42 -1.17716 +15.43 -1.27707 +15.44 -1.37406 +15.45 -1.467 +15.46 -1.55441 +15.47 -1.63458 +15.48 -1.70571 +15.49 -1.76596 +15.5 -1.81359 +15.51 -1.84698 +15.52 -1.86331 +15.53 -1.86282 +15.54 -1.84516 +15.55 -1.81033 +15.56 -1.75883 +15.57 -1.69164 +15.58 -1.61026 +15.59 -1.5166 +15.6 -1.41246 +15.61 -1.30114 +15.62 -1.18571 +15.63 -1.06898 +15.64 -0.953697 +15.65 -0.842396 +15.66 -0.737294 +15.67 -0.640198 +15.68 -0.552918 +15.69 -0.476219 +15.7 -0.409664 +15.71 -0.352682 +15.72 -0.304143 +15.73 -0.262419 +15.74 -0.225461 +15.75 -0.19091 +15.76 -0.156129 +15.77 -0.118106 +15.78 -0.0744032 +15.79 -0.0228145 +15.8 0.038473 +15.81 0.110763 +15.82 0.194754 +15.83 0.290474 +15.84 0.397321 +15.85 0.514436 +15.86 0.638573 +15.87 0.76683 +15.88 0.895772 +15.89 1.02155 +15.9 1.14003 +15.91 1.24696 +15.92 1.33812 +15.93 1.40745 +15.94 1.45255 +15.95 1.47066 +15.96 1.45948 +15.97 1.41747 +15.98 1.34399 +15.99 1.23927 +16 1.10447 +16.01 0.939967 +16.02 0.750073 +16.03 0.539475 +16.04 0.312587 +16.05 0.074296 +16.06 -0.170197 +16.07 -0.415538 +16.08 -0.656387 +16.09 -0.886904 +16.1 -1.10161 +16.11 -1.29683 +16.12 -1.46899 +16.13 -1.61525 +16.14 -1.73354 +16.15 -1.82263 +16.16 -1.88211 +16.17 -1.91169 +16.18 -1.91148 +16.19 -1.88562 +16.2 -1.83674 +16.21 -1.76791 +16.22 -1.68256 +16.23 -1.58431 +16.24 -1.47687 +16.25 -1.36387 +16.26 -1.24893 +16.27 -1.1356 +16.28 -1.02656 +16.29 -0.92401 +16.3 -0.829614 +16.31 -0.744478 +16.32 -0.669134 +16.33 -0.603554 +16.34 -0.547863 +16.35 -0.500231 +16.36 -0.459003 +16.37 -0.422354 +16.38 -0.388256 +16.39 -0.354564 +16.4 -0.31913 +16.41 -0.279891 +16.42 -0.234508 +16.43 -0.181391 +16.44 -0.119584 +16.45 -0.0483416 +16.46 0.0326176 +16.47 0.123089 +16.48 0.222376 +16.49 0.329301 +16.5 0.442398 +16.51 0.559374 +16.52 0.677459 +16.53 0.793955 +16.54 0.906005 +16.55 1.01068 +16.56 1.1051 +16.57 1.18649 +16.58 1.2519 +16.59 1.29796 +16.6 1.32414 +16.61 1.32918 +16.62 1.31235 +16.63 1.27346 +16.64 1.21288 +16.65 1.13157 +16.66 1.03093 +16.67 0.911538 +16.68 0.777684 +16.69 0.632163 +16.7 0.478012 +16.71 0.318407 +16.72 0.156563 +16.73 -0.0043662 +16.74 -0.16139 +16.75 -0.311046 +16.76 -0.451171 +16.77 -0.580005 +16.78 -0.69613 +16.79 -0.798626 +16.8 -0.887081 +16.81 -0.961577 +16.82 -1.02266 +16.83 -1.07066 +16.84 -1.10736 +16.85 -1.13491 +16.86 -1.15517 +16.87 -1.17005 +16.88 -1.18148 +16.89 -1.19129 +16.9 -1.20114 +16.91 -1.21253 +16.92 -1.22671 +16.93 -1.24424 +16.94 -1.26541 +16.95 -1.29015 +16.96 -1.31799 +16.97 -1.34819 +16.98 -1.37971 +16.99 -1.41126 +17 -1.44122 +17.01 -1.46816 +17.02 -1.49066 +17.03 -1.50744 +17.04 -1.51737 +17.05 -1.51962 +17.06 -1.51361 +17.07 -1.49909 +17.08 -1.47542 +17.09 -1.44391 +17.1 -1.40544 +17.11 -1.3611 +17.12 -1.31225 +17.13 -1.26038 +17.14 -1.2071 +17.15 -1.15402 +17.16 -1.10295 +17.17 -1.05536 +17.18 -1.01239 +17.19 -0.97497 +17.2 -0.943735 +17.21 -0.918997 +17.22 -0.900743 +17.23 -0.888637 +17.24 -0.882301 +17.25 -0.880617 +17.26 -0.882207 +17.27 -0.885785 +17.28 -0.889991 +17.29 -0.893459 +17.3 -0.89488 +17.31 -0.893066 +17.32 -0.886813 +17.33 -0.875113 +17.34 -0.857737 +17.35 -0.834439 +17.36 -0.805229 +17.37 -0.770356 +17.38 -0.730277 +17.39 -0.685617 +17.4 -0.637074 +17.41 -0.585348 +17.42 -0.531549 +17.43 -0.476422 +17.44 -0.420589 +17.45 -0.364513 +17.46 -0.308457 +17.47 -0.252464 +17.48 -0.196344 +17.49 -0.139565 +17.5 -0.0814518 +17.51 -0.0211428 +17.52 0.0423619 +17.53 0.110151 +17.54 0.183346 +17.55 0.263047 +17.56 0.350271 +17.57 0.44657 +17.58 0.552276 +17.59 0.66759 +17.6 0.792667 +17.61 0.927382 +17.62 1.07133 +17.63 1.22383 +17.64 1.38395 +17.65 1.5508 +17.66 1.72279 +17.67 1.89812 +17.68 2.07518 +17.69 2.25234 +17.7 2.42798 +17.71 2.6005 +17.72 2.76842 +17.73 2.93013 +17.74 3.08401 +17.75 3.2294 +17.76 3.36536 +17.77 3.49111 +17.78 3.60598 +17.79 3.70936 +17.8 3.80074 +17.81 3.87949 +17.82 3.94414 +17.83 3.99531 +17.84 4.0325 +17.85 4.05517 +17.86 4.06275 +17.87 4.05466 +17.88 4.03028 +17.89 3.98901 +17.9 3.9287 +17.91 3.85026 +17.92 3.75352 +17.93 3.6383 +17.94 3.50464 +17.95 3.35282 +17.96 3.18337 +17.97 2.9971 +17.98 2.79412 +17.99 2.57667 +18 2.34693 +18.01 2.10688 +18.02 1.85872 +18.03 1.6048 +18.04 1.34761 +18.05 1.0897 +18.06 0.833844 +18.07 0.582866 +18.08 0.339024 +18.09 0.104513 +18.1 -0.118684 +18.11 -0.328831 +18.12 -0.524459 +18.13 -0.704386 +18.14 -0.867214 +18.15 -1.01163 +18.16 -1.1385 +18.17 -1.2478 +18.18 -1.33974 +18.19 -1.41469 +18.2 -1.4732 +18.21 -1.51593 +18.22 -1.5435 +18.23 -1.55573 +18.24 -1.55483 +18.25 -1.54172 +18.26 -1.51737 +18.27 -1.48274 +18.28 -1.43885 +18.29 -1.38673 +18.3 -1.32746 +18.31 -1.26174 +18.32 -1.19134 +18.33 -1.11758 +18.34 -1.04181 +18.35 -0.965414 +18.36 -0.889833 +18.37 -0.816542 +18.38 -0.747021 +18.39 -0.683167 +18.4 -0.626335 +18.41 -0.577615 +18.42 -0.538114 +18.43 -0.508737 +18.44 -0.490159 +18.45 -0.48278 +18.46 -0.486705 +18.47 -0.502211 +18.48 -0.528702 +18.49 -0.564673 +18.5 -0.60889 +18.51 -0.659849 +18.52 -0.715817 +18.53 -0.774882 +18.54 -0.835013 +18.55 -0.894041 +18.56 -0.94952 +18.57 -0.999587 +18.58 -1.04245 +18.59 -1.07654 +18.6 -1.10057 +18.61 -1.11357 +18.62 -1.11491 +18.63 -1.10427 +18.64 -1.08077 +18.65 -1.04607 +18.66 -1.00103 +18.67 -0.94679 +18.68 -0.884742 +18.69 -0.816444 +18.7 -0.743568 +18.71 -0.667844 +18.72 -0.591031 +18.73 -0.514989 +18.74 -0.441207 +18.75 -0.370974 +18.76 -0.305356 +18.77 -0.245171 +18.78 -0.190976 +18.79 -0.143063 +18.8 -0.101853 +18.81 -0.0669029 +18.82 -0.0375311 +18.83 -0.0131039 +18.84 0.00714168 +18.85 0.0240571 +18.86 0.0385395 +18.87 0.0514911 +18.88 0.0637887 +18.89 0.0763266 +18.9 0.0898138 +18.91 0.104807 +18.92 0.12173 +18.93 0.140863 +18.94 0.162347 +18.95 0.186188 +18.96 0.212312 +18.97 0.240559 +18.98 0.270421 +18.99 0.301489 +19 0.333301 +19.01 0.36537 +19.02 0.397188 +19.03 0.428253 +19.04 0.458069 +19.05 0.485964 +19.06 0.511636 +19.07 0.534691 +19.08 0.554772 +19.09 0.571563 +19.1 0.584787 +19.11 0.594203 +19.12 0.59961 +19.13 0.600496 +19.14 0.596985 +19.15 0.589066 +19.16 0.57669 +19.17 0.559846 +19.18 0.538559 +19.19 0.512892 +19.2 0.482944 +19.21 0.448612 +19.22 0.410175 +19.23 0.367987 +19.24 0.322263 +19.25 0.273226 +19.26 0.221101 +19.27 0.166103 +19.28 0.108426 +19.29 0.048143 +19.3 -0.0146507 +19.31 -0.0797347 +19.32 -0.14708 +19.33 -0.2167 +19.34 -0.288652 +19.35 -0.363018 +19.36 -0.4399 +19.37 -0.519451 +19.38 -0.601917 +19.39 -0.687114 +19.4 -0.774995 +19.41 -0.865417 +19.42 -0.95812 +19.43 -1.05271 +19.44 -1.14865 +19.45 -1.24525 +19.46 -1.34154 +19.47 -1.43644 +19.48 -1.52879 +19.49 -1.61732 +19.5 -1.7007 +19.51 -1.77756 +19.52 -1.84653 +19.53 -1.9063 +19.54 -1.95476 +19.55 -1.99136 +19.56 -2.01548 +19.57 -2.02653 +19.58 -2.0242 +19.59 -2.00844 +19.6 -1.97949 +19.61 -1.93792 +19.62 -1.88395 +19.63 -1.81902 +19.64 -1.74498 +19.65 -1.66336 +19.66 -1.5758 +19.67 -1.48403 +19.68 -1.38976 +19.69 -1.29465 +19.7 -1.20034 +19.71 -1.10842 +19.72 -1.01996 +19.73 -0.935854 +19.74 -0.856721 +19.75 -0.782917 +19.76 -0.714522 +19.77 -0.651366 +19.78 -0.593113 +19.79 -0.539364 +19.8 -0.488946 +19.81 -0.440983 +19.82 -0.394552 +19.83 -0.348732 +19.84 -0.302645 +19.85 -0.2555 +19.86 -0.206622 +19.87 -0.15523 +19.88 -0.101142 +19.89 -0.0441861 +19.9 0.0156693 +19.91 0.0783231 +19.92 0.143564 +19.93 0.211101 +19.94 0.280595 +19.95 0.351785 +19.96 0.424231 +19.97 0.497624 +19.98 0.571773 +19.99 0.646575 +20 0.722026 +20.01 0.798214 +20.02 0.875307 +20.03 0.953606 +20.04 1.03339 +20.05 1.11486 +20.06 1.19818 +20.07 1.28346 +20.08 1.37065 +20.09 1.45953 +20.1 1.54969 +20.11 1.64048 +20.12 1.73091 +20.13 1.81979 +20.14 1.90579 +20.15 1.98737 +20.16 2.06288 +20.17 2.13058 +20.18 2.18868 +20.19 2.23527 +20.2 2.26762 +20.21 2.28529 +20.22 2.28707 +20.23 2.27206 +20.24 2.2397 +20.25 2.18976 +20.26 2.12241 +20.27 2.03818 +20.28 1.93664 +20.29 1.8205 +20.3 1.69146 +20.31 1.55133 +20.32 1.40207 +20.33 1.24573 +20.34 1.08436 +20.35 0.919989 +20.36 0.754545 +20.37 0.589942 +20.38 0.427625 +20.39 0.268743 +20.4 0.114154 +20.41 -0.0355771 +20.42 -0.18017 +20.43 -0.319604 +20.44 -0.45385 +20.45 -0.58336 +20.46 -0.708879 +20.47 -0.830958 +20.48 -0.950122 +20.49 -1.06682 +20.5 -1.18134 +20.51 -1.29382 +20.52 -1.40408 +20.53 -1.51158 +20.54 -1.61582 +20.55 -1.71585 +20.56 -1.81053 +20.57 -1.89849 +20.58 -1.97822 +20.59 -2.04811 +20.6 -2.10638 +20.61 -2.15021 +20.62 -2.17913 +20.63 -2.19182 +20.64 -2.18716 +20.65 -2.16434 +20.66 -2.12285 +20.67 -2.06253 +20.68 -1.98358 +20.69 -1.88508 +20.7 -1.76948 +20.71 -1.63831 +20.72 -1.49319 +20.73 -1.336 +20.74 -1.16881 +20.75 -0.99383 +20.76 -0.813352 +20.77 -0.62964 +20.78 -0.445347 +20.79 -0.262742 +20.8 -0.0838682 +20.81 0.0894051 +20.82 0.255409 +20.83 0.412693 +20.84 0.560038 +20.85 0.695908 +20.86 0.819414 +20.87 0.930627 +20.88 1.02924 +20.89 1.11511 +20.9 1.18824 +20.91 1.24876 +20.92 1.29689 +20.93 1.3326 +20.94 1.35573 +20.95 1.36752 +20.96 1.36834 +20.97 1.35858 +20.98 1.3386 +20.99 1.30878 +21 1.26945 +21.01 1.22093 +21.02 1.16286 +21.03 1.09643 +21.04 1.02204 +21.05 0.940116 +21.06 0.85114 +21.07 0.755641 +21.08 0.654223 +21.09 0.547566 +21.1 0.436138 +21.11 0.321215 +21.12 0.203848 +21.13 0.0851316 +21.14 -0.033736 +21.15 -0.151456 +21.16 -0.266637 +21.17 -0.377803 +21.18 -0.482953 +21.19 -0.580463 +21.2 -0.66891 +21.21 -0.74668 +21.22 -0.812204 +21.23 -0.863988 +21.24 -0.900658 +21.25 -0.920997 +21.26 -0.9232 +21.27 -0.906225 +21.28 -0.870606 +21.29 -0.816252 +21.3 -0.743422 +21.31 -0.652751 +21.32 -0.545261 +21.33 -0.42236 +21.34 -0.285574 +21.35 -0.136731 +21.36 0.0206378 +21.37 0.183742 +21.38 0.349593 +21.39 0.515081 +21.4 0.677047 +21.41 0.832371 +21.42 0.978023 +21.43 1.10983 +21.44 1.22635 +21.45 1.32558 +21.46 1.40598 +21.47 1.46651 +21.48 1.50666 +21.49 1.52651 +21.5 1.52667 +21.51 1.50695 +21.52 1.47041 +21.53 1.41958 +21.54 1.35699 +21.55 1.28545 +21.56 1.20791 +21.57 1.12741 +21.58 1.04689 +21.59 0.969516 +21.6 0.898041 +21.61 0.834482 +21.62 0.780548 +21.63 0.737468 +21.64 0.705969 +21.65 0.686253 +21.66 0.678014 +21.67 0.680836 +21.68 0.693455 +21.69 0.713573 +21.7 0.739231 +21.71 0.768277 +21.72 0.798457 +21.73 0.82751 +21.74 0.853254 +21.75 0.873523 +21.76 0.885942 +21.77 0.889532 +21.78 0.883213 +21.79 0.866267 +21.8 0.838346 +21.81 0.799459 +21.82 0.749943 +21.83 0.690432 +21.84 0.621014 +21.85 0.543744 +21.86 0.459831 +21.87 0.370529 +21.88 0.277078 +21.89 0.180659 +21.9 0.0823491 +21.91 -0.0169133 +21.92 -0.11631 +21.93 -0.21518 +21.94 -0.313105 +21.95 -0.409812 +21.96 -0.505144 +21.97 -0.599027 +21.98 -0.691432 +21.99 -0.782338 +22 -0.871593 +22.01 -0.959073 +22.02 -1.0446 +22.03 -1.1278 +22.04 -1.20819 +22.05 -1.28515 +22.06 -1.35794 +22.07 -1.42572 +22.08 -1.48735 +22.09 -1.54163 +22.1 -1.58802 +22.11 -1.6257 +22.12 -1.65395 +22.13 -1.67218 +22.14 -1.68001 +22.15 -1.67723 +22.16 -1.66372 +22.17 -1.63907 +22.18 -1.60465 +22.19 -1.56115 +22.2 -1.50939 +22.21 -1.45031 +22.22 -1.38493 +22.23 -1.31427 +22.24 -1.23932 +22.25 -1.16077 +22.26 -1.07976 +22.27 -0.996917 +22.28 -0.912666 +22.29 -0.827255 +22.3 -0.740748 +22.31 -0.653045 +22.32 -0.563909 +22.33 -0.47285 +22.34 -0.379532 +22.35 -0.283638 +22.36 -0.184881 +22.37 -0.0831137 +22.38 0.0216299 +22.39 0.129084 +22.4 0.238719 +22.41 0.349733 +22.42 0.460824 +22.43 0.57048 +22.44 0.676995 +22.45 0.778459 +22.46 0.872809 +22.47 0.9579 +22.48 1.03158 +22.49 1.09125 +22.5 1.1343 +22.51 1.15999 +22.52 1.16703 +22.53 1.1545 +22.54 1.12192 +22.55 1.06926 +22.56 0.996974 +22.57 0.905757 +22.58 0.795923 +22.59 0.670916 +22.6 0.532946 +22.61 0.384492 +22.62 0.228228 +22.63 0.0669381 +22.64 -0.096566 +22.65 -0.259527 +22.66 -0.418805 +22.67 -0.572067 +22.68 -0.717231 +22.69 -0.852499 +22.7 -0.976401 +22.71 -1.0878 +22.72 -1.18591 +22.73 -1.27022 +22.74 -1.33958 +22.75 -1.39465 +22.76 -1.43611 +22.77 -1.4644 +22.78 -1.48004 +22.79 -1.48362 +22.8 -1.47572 +22.81 -1.45695 +22.82 -1.42737 +22.83 -1.38761 +22.84 -1.33863 +22.85 -1.28093 +22.86 -1.21498 +22.87 -1.14133 +22.88 -1.06058 +22.89 -0.973444 +22.9 -0.880654 +22.91 -0.783209 +22.92 -0.682734 +22.93 -0.580687 +22.94 -0.478714 +22.95 -0.378628 +22.96 -0.282383 +22.97 -0.192021 +22.98 -0.109722 +22.99 -0.0385251 +23 0.0203941 +23.01 0.0653233 +23.02 0.0948227 +23.03 0.107793 +23.04 0.103537 +23.05 0.0818045 +23.06 0.0428329 +23.07 -0.0139737 +23.08 -0.0859089 +23.09 -0.170965 +23.1 -0.266878 +23.11 -0.37101 +23.12 -0.480429 +23.13 -0.591994 +23.14 -0.702449 +23.15 -0.80801 +23.16 -0.905323 +23.17 -0.991605 +23.18 -1.06423 +23.19 -1.12094 +23.2 -1.15992 +23.21 -1.17986 +23.22 -1.17992 +23.23 -1.15887 +23.24 -1.11692 +23.25 -1.05588 +23.26 -0.976972 +23.27 -0.881733 +23.28 -0.771998 +23.29 -0.649804 +23.3 -0.517312 +23.31 -0.376574 +23.32 -0.229749 +23.33 -0.0794467 +23.34 0.0724356 +23.35 0.224195 +23.36 0.374349 +23.37 0.521639 +23.38 0.665033 +23.39 0.80367 +23.4 0.936395 +23.41 1.06318 +23.42 1.18363 +23.43 1.29743 +23.44 1.40423 +23.45 1.5037 +23.46 1.59541 +23.47 1.67888 +23.48 1.75273 +23.49 1.81684 +23.5 1.87059 +23.51 1.91319 +23.52 1.94387 +23.53 1.96189 +23.54 1.96659 +23.55 1.95747 +23.56 1.93332 +23.57 1.89454 +23.58 1.84188 +23.59 1.77596 +23.6 1.69771 +23.61 1.6084 +23.62 1.50961 +23.63 1.40321 +23.64 1.29121 +23.65 1.17618 +23.66 1.06087 +23.67 0.94777 +23.68 0.839322 +23.69 0.737836 +23.7 0.64542 +23.71 0.563903 +23.72 0.495091 +23.73 0.440844 +23.74 0.400778 +23.75 0.374993 +23.76 0.363135 +23.77 0.36441 +23.78 0.377617 +23.79 0.401196 +23.8 0.433307 +23.81 0.472226 +23.82 0.514914 +23.83 0.559065 +23.84 0.602409 +23.85 0.642797 +23.86 0.678269 +23.87 0.707125 +23.88 0.727972 +23.89 0.738984 +23.9 0.740075 +23.91 0.731224 +23.92 0.712635 +23.93 0.684874 +23.94 0.648832 +23.95 0.605685 +23.96 0.556835 +23.97 0.503715 +23.98 0.448311 +23.99 0.392494 +24 0.337953 +24.01 0.286284 +24.02 0.238941 +24.03 0.197195 +24.04 0.162098 +24.05 0.134789 +24.06 0.116005 +24.07 0.105555 +24.08 0.10344 +24.09 0.109437 +24.1 0.123122 +24.11 0.143901 +24.12 0.171045 +24.13 0.20382 +24.14 0.241504 +24.15 0.282763 +24.16 0.3267 +24.17 0.372468 +24.18 0.419288 +24.19 0.466471 +24.2 0.513429 +24.21 0.559684 +24.22 0.604736 +24.23 0.648424 +24.24 0.690603 +24.25 0.731209 +24.26 0.770239 +24.27 0.807737 +24.28 0.843769 +24.29 0.878404 +24.3 0.911596 +24.31 0.943437 +24.32 0.973908 +24.33 1.0029 +24.34 1.03022 +24.35 1.0556 +24.36 1.07868 +24.37 1.09902 +24.38 1.11587 +24.39 1.12861 +24.4 1.13671 +24.41 1.13946 +24.42 1.13611 +24.43 1.12593 +24.44 1.10819 +24.45 1.0822 +24.46 1.047 +24.47 1.00175 +24.48 0.946613 +24.49 0.881311 +24.5 0.805733 +24.51 0.719939 +24.52 0.62417 +24.53 0.518862 +24.54 0.404517 +24.55 0.281564 +24.56 0.151795 +24.57 0.0164731 +24.58 -0.122974 +24.59 -0.26497 +24.6 -0.407823 +24.61 -0.549748 +24.62 -0.688906 +24.63 -0.822831 +24.64 -0.949964 +24.65 -1.06857 +24.66 -1.17702 +24.67 -1.27383 +24.68 -1.35769 +24.69 -1.42755 +24.7 -1.48257 +24.71 -1.52103 +24.72 -1.54346 +24.73 -1.55031 +24.74 -1.54197 +24.75 -1.51911 +24.76 -1.48266 +24.77 -1.43382 +24.78 -1.37397 +24.79 -1.30429 +24.8 -1.22685 +24.81 -1.14391 +24.82 -1.05737 +24.83 -0.96915 +24.84 -0.881151 +24.85 -0.795192 +24.86 -0.712998 +24.87 -0.636393 +24.88 -0.567175 +24.89 -0.506204 +24.9 -0.454506 +24.91 -0.41291 +24.92 -0.38205 +24.93 -0.362364 +24.94 -0.354104 +24.95 -0.357502 +24.96 -0.373263 +24.97 -0.400153 +24.98 -0.437785 +24.99 -0.485662 +25 -0.543191 +25.01 -0.609697 +25.02 -0.684427 +25.03 -0.766564 +25.04 -0.855741 +25.05 -0.95038 +25.06 -1.04943 +25.07 -1.15184 +25.08 -1.25648 +25.09 -1.36218 +25.1 -1.46772 +25.11 -1.57182 +25.12 -1.67285 +25.13 -1.76942 +25.14 -1.86021 +25.15 -1.94383 +25.16 -2.0189 +25.17 -2.08407 +25.18 -2.13807 +25.19 -2.17971 +25.2 -2.20723 +25.21 -2.21968 +25.22 -2.21704 +25.23 -2.19887 +25.24 -2.16498 +25.25 -2.11545 +25.26 -2.05063 +25.27 -1.97115 +25.28 -1.87754 +25.29 -1.77069 +25.3 -1.65303 +25.31 -1.52627 +25.32 -1.39232 +25.33 -1.25322 +25.34 -1.11114 +25.35 -0.968277 +25.36 -0.826858 +25.37 -0.689519 +25.38 -0.558095 +25.39 -0.434403 +25.4 -0.320038 +25.41 -0.216334 +25.42 -0.124331 +25.43 -0.04475 +25.44 0.0220194 +25.45 0.0748234 +25.46 0.115024 +25.47 0.14343 +25.48 0.161004 +25.49 0.168962 +25.5 0.16873 +25.51 0.161898 +25.52 0.150162 +25.53 0.135188 +25.54 0.119033 +25.55 0.103463 +25.56 0.0900255 +25.57 0.0801032 +25.58 0.0748666 +25.59 0.0752434 +25.6 0.0818918 +25.61 0.095503 +25.62 0.11615 +25.63 0.143213 +25.64 0.176137 +25.65 0.214113 +25.66 0.256112 +25.67 0.300915 +25.68 0.347163 +25.69 0.393351 +25.7 0.437724 +25.71 0.478797 +25.72 0.515131 +25.73 0.545394 +25.74 0.568407 +25.75 0.583179 +25.76 0.588945 +25.77 0.585119 +25.78 0.570629 +25.79 0.54645 +25.8 0.512967 +25.81 0.470841 +25.82 0.421001 +25.83 0.364622 +25.84 0.303101 +25.85 0.238024 +25.86 0.171141 +25.87 0.104628 +25.88 0.0404524 +25.89 -0.0194218 +25.9 -0.073054 +25.91 -0.118571 +25.92 -0.154206 +25.93 -0.178343 +25.94 -0.188636 +25.95 -0.184158 +25.96 -0.164443 +25.97 -0.128859 +25.98 -0.0770626 +25.99 -0.00901332 +26 0.0750231 +26.01 0.174474 +26.02 0.289054 +26.03 0.417549 +26.04 0.557689 +26.05 0.707739 +26.06 0.865755 +26.07 1.02962 +26.08 1.19705 +26.09 1.3657 +26.1 1.53303 +26.11 1.69614 +26.12 1.85273 +26.13 2.00047 +26.14 2.13714 +26.15 2.26068 +26.16 2.36923 +26.17 2.46115 +26.18 2.53498 +26.19 2.58775 +26.2 2.62045 +26.21 2.63258 +26.22 2.62399 +26.23 2.5948 +26.24 2.5454 +26.25 2.47648 +26.26 2.38895 +26.27 2.28264 +26.28 2.16019 +26.29 2.02341 +26.3 1.87398 +26.31 1.71365 +26.32 1.54424 +26.33 1.3676 +26.34 1.1856 +26.35 0.999962 +26.36 0.812794 +26.37 0.625939 +26.38 0.441052 +26.39 0.259702 +26.4 0.0833689 +26.41 -0.0865607 +26.42 -0.248794 +26.43 -0.401723 +26.44 -0.543946 +26.45 -0.67499 +26.46 -0.793954 +26.47 -0.900029 +26.48 -0.992498 +26.49 -1.07075 +26.5 -1.13427 +26.51 -1.18234 +26.52 -1.21382 +26.53 -1.22978 +26.54 -1.23028 +26.55 -1.21556 +26.56 -1.18602 +26.57 -1.14227 +26.58 -1.0851 +26.59 -1.01551 +26.6 -0.933739 +26.61 -0.842414 +26.62 -0.743147 +26.63 -0.637702 +26.64 -0.527974 +26.65 -0.415953 +26.66 -0.303691 +26.67 -0.193264 +26.68 -0.087183 +26.69 0.0126083 +26.7 0.104357 +26.71 0.186395 +26.72 0.257264 +26.73 0.31575 +26.74 0.360913 +26.75 0.392108 +26.76 0.40818 +26.77 0.409406 +26.78 0.396745 +26.79 0.370876 +26.8 0.332758 +26.81 0.283599 +26.82 0.22482 +26.83 0.158016 +26.84 0.0847437 +26.85 0.00692858 +26.86 -0.0732311 +26.87 -0.153934 +26.88 -0.233454 +26.89 -0.310177 +26.9 -0.382642 +26.91 -0.449564 +26.92 -0.509733 +26.93 -0.561735 +26.94 -0.605535 +26.95 -0.640775 +26.96 -0.667312 +26.97 -0.685212 +26.98 -0.694724 +26.99 -0.696267 +27 -0.690399 +27.01 -0.677231 +27.02 -0.658219 +27.03 -0.634222 +27.04 -0.60611 +27.05 -0.574761 +27.06 -0.54104 +27.07 -0.505787 +27.08 -0.469796 +27.09 -0.433861 +27.1 -0.39872 +27.11 -0.364961 +27.12 -0.333102 +27.13 -0.303583 +27.14 -0.276767 +27.15 -0.252944 +27.16 -0.232326 +27.17 -0.215236 +27.18 -0.201707 +27.19 -0.191573 +27.2 -0.184752 +27.21 -0.181098 +27.22 -0.180412 +27.23 -0.182441 +27.24 -0.18689 +27.25 -0.193486 +27.26 -0.201882 +27.27 -0.211577 +27.28 -0.222207 +27.29 -0.233425 +27.3 -0.244917 +27.31 -0.256416 +27.32 -0.267719 +27.33 -0.27869 +27.34 -0.289263 +27.35 -0.299535 +27.36 -0.309673 +27.37 -0.319935 +27.38 -0.330664 +27.39 -0.342281 +27.4 -0.355263 +27.41 -0.370124 +27.42 -0.387657 +27.43 -0.408201 +27.44 -0.432183 +27.45 -0.459956 +27.46 -0.491753 +27.47 -0.52766 +27.48 -0.567589 +27.49 -0.611259 +27.5 -0.658365 +27.51 -0.707951 +27.52 -0.758969 +27.53 -0.810259 +27.54 -0.860486 +27.55 -0.908165 +27.56 -0.951702 +27.57 -0.989432 +27.58 -1.0192 +27.59 -1.03916 +27.6 -1.04813 +27.61 -1.0447 +27.62 -1.02764 +27.63 -0.995978 +27.64 -0.948976 +27.65 -0.886207 +27.66 -0.807069 +27.67 -0.711249 +27.68 -0.600409 +27.69 -0.475441 +27.7 -0.337515 +27.71 -0.18805 +27.72 -0.0286718 +27.73 0.138825 +27.74 0.312564 +27.75 0.490664 +27.76 0.670588 +27.77 0.850336 +27.78 1.02797 +27.79 1.20165 +27.8 1.36964 +27.81 1.53037 +27.82 1.6824 +27.83 1.82347 +27.84 1.95315 +27.85 2.0706 +27.86 2.175 +27.87 2.26567 +27.88 2.34201 +27.89 2.40352 +27.9 2.44979 +27.91 2.4794 +27.92 2.49264 +27.93 2.48978 +27.94 2.47072 +27.95 2.43542 +27.96 2.38397 +27.97 2.31656 +27.98 2.23352 +27.99 2.13467 +28 2.02076 +28.01 1.89347 +28.02 1.75391 +28.03 1.60341 +28.04 1.44349 +28.05 1.27589 +28.06 1.10247 +28.07 0.925203 +28.08 0.74639 +28.09 0.56844 +28.1 0.393507 +28.11 0.223679 +28.12 0.0609198 +28.13 -0.0929797 +28.14 -0.236453 +28.15 -0.368111 +28.16 -0.485823 +28.17 -0.59009 +28.18 -0.680675 +28.19 -0.757686 +28.2 -0.821565 +28.21 -0.873062 +28.22 -0.913204 +28.23 -0.943248 +28.24 -0.96403 +28.25 -0.977889 +28.26 -0.986564 +28.27 -0.991666 +28.28 -0.994735 +28.29 -0.997188 +28.3 -1.00028 +28.31 -1.00504 +28.32 -1.0125 +28.33 -1.02318 +28.34 -1.03724 +28.35 -1.05472 +28.36 -1.07541 +28.37 -1.09893 +28.38 -1.12474 +28.39 -1.15217 +28.4 -1.1805 +28.41 -1.20884 +28.42 -1.23638 +28.43 -1.26239 +28.44 -1.28623 +28.45 -1.30732 +28.46 -1.32519 +28.47 -1.33947 +28.48 -1.34977 +28.49 -1.35569 +28.5 -1.35739 +28.51 -1.35475 +28.52 -1.34771 +28.53 -1.33612 +28.54 -1.31985 +28.55 -1.29866 +28.56 -1.27223 +28.57 -1.23964 +28.58 -1.2009 +28.59 -1.15547 +28.6 -1.10279 +28.61 -1.04227 +28.62 -0.973361 +28.63 -0.895603 +28.64 -0.808641 +28.65 -0.711529 +28.66 -0.604947 +28.67 -0.489397 +28.68 -0.365515 +28.69 -0.234246 +28.7 -0.096852 +28.71 0.0450941 +28.72 0.189724 +28.73 0.334794 +28.74 0.477636 +28.75 0.615705 +28.76 0.746388 +28.77 0.867075 +28.78 0.975234 +28.79 1.06849 +28.8 1.14471 +28.81 1.20122 +28.82 1.23611 +28.83 1.24954 +28.84 1.24106 +28.85 1.21072 +28.86 1.15904 +28.87 1.08704 +28.88 0.996201 +28.89 0.888092 +28.9 0.764406 +28.91 0.629014 +28.92 0.484691 +28.93 0.334325 +28.94 0.18083 +28.95 0.027069 +28.96 -0.124218 +28.97 -0.270484 +28.98 -0.408528 +28.99 -0.536967 +29 -0.654226 +29.01 -0.759081 +29.02 -0.850669 +29.03 -0.928475 +29.04 -0.992311 +29.05 -1.04228 +29.06 -1.07775 +29.07 -1.10017 +29.08 -1.11058 +29.09 -1.10985 +29.1 -1.09891 +29.11 -1.07872 +29.12 -1.05017 +29.13 -1.01413 +29.14 -0.971013 +29.15 -0.92169 +29.16 -0.866962 +29.17 -0.807261 +29.18 -0.742922 +29.19 -0.674196 +29.2 -0.601267 +29.21 -0.524273 +29.22 -0.443186 +29.23 -0.358053 +29.24 -0.269254 +29.25 -0.176982 +29.26 -0.081485 +29.27 0.0169175 +29.28 0.11783 +29.29 0.220779 +29.3 0.325227 +29.31 0.430535 +29.32 0.535893 +29.33 0.640557 +29.34 0.743748 +29.35 0.844667 +29.36 0.942502 +29.37 1.03645 +29.38 1.12571 +29.39 1.20893 +29.4 1.28583 +29.41 1.35571 +29.42 1.41794 +29.43 1.4719 +29.44 1.51704 +29.45 1.55285 +29.46 1.57886 +29.47 1.59388 +29.48 1.59807 +29.49 1.59138 +29.5 1.57363 +29.51 1.54471 +29.52 1.50459 +29.53 1.45336 +29.54 1.39122 +29.55 1.31793 +29.56 1.23415 +29.57 1.14095 +29.58 1.03911 +29.59 0.929552 +29.6 0.813323 +29.61 0.691614 +29.62 0.565732 +29.63 0.437044 +29.64 0.307192 +29.65 0.177891 +29.66 0.0507166 +29.67 -0.0727667 +29.68 -0.191033 +29.69 -0.302626 +29.7 -0.406187 +29.71 -0.500336 +29.72 -0.583164 +29.73 -0.65459 +29.74 -0.713922 +29.75 -0.760662 +29.76 -0.794508 +29.77 -0.815354 +29.78 -0.823279 +29.79 -0.818543 +29.8 -0.8005 +29.81 -0.770843 +29.82 -0.730377 +29.83 -0.679941 +29.84 -0.620458 +29.85 -0.552919 +29.86 -0.478368 +29.87 -0.397884 +29.88 -0.312304 +29.89 -0.223075 +29.9 -0.131431 +29.91 -0.0384687 +29.92 0.0547324 +29.93 0.147118 +29.94 0.237667 +29.95 0.325395 +29.96 0.409126 +29.97 0.487891 +29.98 0.561075 +29.99 0.627966 +30 0.687946 +30.01 0.740509 +30.02 0.785269 +30.03 0.821978 +30.04 0.850287 +30.05 0.869989 +30.06 0.881896 +30.07 0.886436 +30.08 0.884203 +30.09 0.87594 +30.1 0.86253 +30.11 0.844973 +30.12 0.82434 +30.13 0.801762 +30.14 0.778668 +30.15 0.756214 +30.16 0.735497 +30.17 0.717516 +30.18 0.703135 +30.19 0.69305 +30.2 0.687764 +30.21 0.688023 +30.22 0.69345 +30.23 0.703767 +30.24 0.718518 +30.25 0.737037 +30.26 0.75847 +30.27 0.78181 +30.28 0.805931 +30.29 0.829516 +30.3 0.851218 +30.31 0.86986 +30.32 0.884326 +30.33 0.893624 +30.34 0.896923 +30.35 0.893585 +30.36 0.883188 +30.37 0.865197 +30.38 0.839677 +30.39 0.807323 +30.4 0.768673 +30.41 0.724449 +30.42 0.675526 +30.43 0.622884 +30.44 0.567574 +30.45 0.510653 +30.46 0.453255 +30.47 0.39643 +30.48 0.341006 +30.49 0.287656 +30.5 0.236872 +30.51 0.18895 +30.52 0.143979 +30.53 0.101864 +30.54 0.0624563 +30.55 0.0249784 +30.56 -0.0112315 +30.57 -0.0469409 +30.58 -0.0829843 +30.59 -0.120222 +30.6 -0.1595 +30.61 -0.201606 +30.62 -0.247588 +30.63 -0.297789 +30.64 -0.352536 +30.65 -0.412029 +30.66 -0.476276 +30.67 -0.545096 +30.68 -0.618122 +30.69 -0.694811 +30.7 -0.774605 +30.71 -0.85647 +30.72 -0.939359 +30.73 -1.02226 +30.74 -1.10412 +30.75 -1.1839 +30.76 -1.26057 +30.77 -1.3332 +30.78 -1.40063 +30.79 -1.46195 +30.8 -1.5168 +30.81 -1.56469 +30.82 -1.60521 +30.83 -1.63814 +30.84 -1.66333 +30.85 -1.68078 +30.86 -1.69039 +30.87 -1.69201 +30.88 -1.68649 +30.89 -1.67416 +30.9 -1.65543 +30.91 -1.63073 +30.92 -1.60055 +30.93 -1.56539 +30.94 -1.52577 +30.95 -1.48196 +30.96 -1.43495 +30.97 -1.38534 +30.98 -1.33377 +30.99 -1.28089 +31 -1.22737 +31.01 -1.1739 +31.02 -1.12119 +31.03 -1.07013 +31.04 -1.02138 +31.05 -0.975598 +31.06 -0.933438 +31.07 -0.89549 +31.08 -0.862279 +31.09 -0.834241 +31.1 -0.811709 +31.11 -0.795252 +31.12 -0.784786 +31.13 -0.779992 +31.14 -0.780599 +31.15 -0.786174 +31.16 -0.796127 +31.17 -0.809711 +31.18 -0.826038 +31.19 -0.844118 +31.2 -0.862685 +31.21 -0.880436 +31.22 -0.896116 +31.23 -0.908457 +31.24 -0.916216 +31.25 -0.91821 +31.26 -0.913346 +31.27 -0.900477 +31.28 -0.878241 +31.29 -0.846679 +31.3 -0.805434 +31.31 -0.754385 +31.32 -0.693654 +31.33 -0.623621 +31.34 -0.544917 +31.35 -0.45842 +31.36 -0.364737 +31.37 -0.265998 +31.38 -0.16378 +31.39 -0.0598178 +31.4 0.0440344 +31.41 0.14584 +31.42 0.243626 +31.43 0.335425 +31.44 0.418564 +31.45 0.491544 +31.46 0.552917 +31.47 0.601287 +31.48 0.635513 +31.49 0.654743 +31.5 0.65845 +31.51 0.646459 +31.52 0.618092 +31.53 0.574332 +31.54 0.516838 +31.55 0.447018 +31.56 0.366629 +31.57 0.277738 +31.58 0.182688 +31.59 0.0840445 +31.6 -0.0153808 +31.61 -0.112353 +31.62 -0.203985 +31.63 -0.287482 +31.64 -0.360188 +31.65 -0.419655 +31.66 -0.463719 +31.67 -0.490557 +31.68 -0.498402 +31.69 -0.484999 +31.7 -0.451494 +31.71 -0.398028 +31.72 -0.325253 +31.73 -0.234321 +31.74 -0.126862 +31.75 -0.00493809 +31.76 0.129002 +31.77 0.272785 +31.78 0.422259 +31.79 0.574201 +31.8 0.725362 +31.81 0.872526 +31.82 1.0126 +31.83 1.1427 +31.84 1.26021 +31.85 1.36162 +31.86 1.44573 +31.87 1.51157 +31.88 1.55831 +31.89 1.58563 +31.9 1.59363 +31.91 1.5829 +31.92 1.55443 +31.93 1.50879 +31.94 1.44807 +31.95 1.37496 +31.96 1.29156 +31.97 1.20009 +31.98 1.10276 +31.99 1.00175 +32 0.899104 +32.01 0.79679 +32.02 0.696723 +32.03 0.600252 +32.04 0.508497 +32.05 0.422309 +32.06 0.342276 +32.07 0.268741 +32.08 0.201823 +32.09 0.141543 +32.1 0.0880776 +32.11 0.0405231 +32.12 -0.0015366 +32.13 -0.0385364 +32.14 -0.0708929 +32.15 -0.0989713 +32.16 -0.123059 +32.17 -0.143344 +32.18 -0.159561 +32.19 -0.171969 +32.2 -0.180415 +32.21 -0.184624 +32.22 -0.18424 +32.23 -0.178848 +32.24 -0.167998 +32.25 -0.151236 +32.26 -0.127646 +32.27 -0.0971079 +32.28 -0.0595098 +32.29 -0.0146803 +32.3 0.0374267 +32.31 0.0967177 +32.32 0.162951 +32.33 0.235732 +32.34 0.314787 +32.35 0.399282 +32.36 0.488083 +32.37 0.580209 +32.38 0.674578 +32.39 0.770023 +32.4 0.865311 +32.41 0.959166 +32.42 1.05016 +32.43 1.13667 +32.44 1.21763 +32.45 1.29177 +32.46 1.35791 +32.47 1.41492 +32.48 1.46175 +32.49 1.49747 +32.5 1.52111 +32.51 1.53097 +32.52 1.52756 +32.53 1.51054 +32.54 1.47972 +32.55 1.43508 +32.56 1.37683 +32.57 1.30534 +32.58 1.22121 +32.59 1.1243 +32.6 1.01668 +32.61 0.89972 +32.62 0.774884 +32.63 0.643798 +32.64 0.508239 +32.65 0.370107 +32.66 0.231393 +32.67 0.0943828 +32.68 -0.0387013 +32.69 -0.165908 +32.7 -0.285326 +32.71 -0.395185 +32.72 -0.493897 +32.73 -0.580096 +32.74 -0.65268 +32.75 -0.710116 +32.76 -0.751864 +32.77 -0.778565 +32.78 -0.790473 +32.79 -0.788175 +32.8 -0.772573 +32.81 -0.74486 +32.82 -0.706489 +32.83 -0.658935 +32.84 -0.603971 +32.85 -0.54419 +32.86 -0.481624 +32.87 -0.418298 +32.88 -0.356179 +32.89 -0.297121 +32.9 -0.242822 +32.91 -0.19484 +32.92 -0.155165 +32.93 -0.124156 +32.94 -0.102477 +32.95 -0.0905156 +32.96 -0.0883795 +32.97 -0.0959124 +32.98 -0.112712 +32.99 -0.138157 +33 -0.172021 +33.01 -0.21266 +33.02 -0.258872 +33.03 -0.309484 +33.04 -0.363294 +33.05 -0.419095 +33.06 -0.475717 +33.07 -0.532048 +33.08 -0.586905 +33.09 -0.639307 +33.1 -0.688503 +33.11 -0.733815 +33.12 -0.774682 +33.13 -0.810657 +33.14 -0.841405 +33.15 -0.866688 +33.16 -0.886109 +33.17 -0.899556 +33.18 -0.90735 +33.19 -0.90957 +33.2 -0.906342 +33.21 -0.897837 +33.22 -0.884266 +33.23 -0.865872 +33.24 -0.84283 +33.25 -0.815287 +33.26 -0.78393 +33.27 -0.749155 +33.28 -0.711395 +33.29 -0.671124 +33.3 -0.628856 +33.31 -0.585142 +33.32 -0.540567 +33.33 -0.495788 +33.34 -0.451518 +33.35 -0.408404 +33.36 -0.367094 +33.37 -0.328218 +33.38 -0.292378 +33.39 -0.260132 +33.4 -0.231979 +33.41 -0.20875 +33.42 -0.190509 +33.43 -0.177411 +33.44 -0.16958 +33.45 -0.16703 +33.46 -0.169666 +33.47 -0.177279 +33.48 -0.189554 +33.49 -0.206302 +33.5 -0.226828 +33.51 -0.250362 +33.52 -0.276218 +33.53 -0.303654 +33.54 -0.331882 +33.55 -0.360086 +33.56 -0.387436 +33.57 -0.412992 +33.58 -0.435795 +33.59 -0.455155 +33.6 -0.470324 +33.61 -0.480599 +33.62 -0.485338 +33.63 -0.483963 +33.64 -0.475976 +33.65 -0.460801 +33.66 -0.43768 +33.67 -0.406951 +33.68 -0.368514 +33.69 -0.322379 +33.7 -0.268673 +33.71 -0.207641 +33.72 -0.13965 +33.73 -0.065186 +33.74 0.0156221 +33.75 0.101389 +33.76 0.19123 +33.77 0.284152 +33.78 0.379063 +33.79 0.474785 +33.8 0.570072 +33.81 0.663625 +33.82 0.753771 +33.83 0.839226 +33.84 0.91875 +33.85 0.991092 +33.86 1.05508 +33.87 1.10966 +33.88 1.15388 +33.89 1.18698 +33.9 1.20763 +33.91 1.21563 +33.92 1.21128 +33.93 1.19462 +33.94 1.16593 +33.95 1.12567 +33.96 1.07451 +33.97 1.01333 +33.98 0.94283 +33.99 0.864193 +34 0.779254 +34.01 0.689407 +34.02 0.596096 +34.03 0.50078 +34.04 0.404908 +34.05 0.309879 +34.06 0.217084 +34.07 0.12808 +34.08 0.0437421 +34.09 -0.0350138 +34.1 -0.107437 +34.11 -0.172954 +34.12 -0.231169 +34.13 -0.281874 +34.14 -0.325036 +34.15 -0.36012 +34.16 -0.388174 +34.17 -0.4097 +34.18 -0.42531 +34.19 -0.43572 +34.2 -0.441728 +34.21 -0.444191 +34.22 -0.444003 +34.23 -0.442001 +34.24 -0.439252 +34.25 -0.436627 +34.26 -0.434891 +34.27 -0.434721 +34.28 -0.436687 +34.29 -0.441234 +34.3 -0.44867 +34.31 -0.459325 +34.32 -0.473146 +34.33 -0.489812 +34.34 -0.50898 +34.35 -0.530157 +34.36 -0.55271 +34.37 -0.575884 +34.38 -0.598816 +34.39 -0.620483 +34.4 -0.639689 +34.41 -0.655479 +34.42 -0.666824 +34.43 -0.672732 +34.44 -0.67228 +34.45 -0.66465 +34.46 -0.649154 +34.47 -0.625131 +34.48 -0.591615 +34.49 -0.549216 +34.5 -0.49805 +34.51 -0.438457 +34.52 -0.371001 +34.53 -0.296455 +34.54 -0.215798 +34.55 -0.130184 +34.56 -0.0407227 +34.57 0.0506994 +34.58 0.142531 +34.59 0.233204 +34.6 0.321161 +34.61 0.404891 +34.62 0.482973 +34.63 0.554117 +34.64 0.616545 +34.65 0.669628 +34.66 0.712922 +34.67 0.746003 +34.68 0.768698 +34.69 0.781073 +34.7 0.783433 +34.71 0.776303 +34.72 0.759996 +34.73 0.735589 +34.74 0.704533 +34.75 0.667969 +34.76 0.627085 +34.77 0.583079 +34.78 0.537122 +34.79 0.49032 +34.8 0.443725 +34.81 0.398357 +34.82 0.354917 +34.83 0.31397 +34.84 0.275924 +34.85 0.241031 +34.86 0.209399 +34.87 0.181004 +34.88 0.155744 +34.89 0.133584 +34.9 0.113965 +34.91 0.0965675 +34.92 0.0810744 +34.93 0.0671932 +34.94 0.054679 +34.95 0.0433514 +34.96 0.0331068 +34.97 0.0240262 +34.98 0.0161094 +34.99 0.00950926 +35 0.0044533 +35.01 0.00121679 +35.02 0.000101883 +35.03 0.00141368 +35.04 0.00543462 +35.05 0.0126122 +35.06 0.0229928 +35.07 0.0365248 +35.08 0.0531229 +35.09 0.0725675 +35.1 0.0944981 +35.11 0.118412 +35.12 0.143668 +35.13 0.169486 +35.14 0.194866 +35.15 0.218814 +35.16 0.240318 +35.17 0.258354 +35.18 0.271926 +35.19 0.2801 +35.2 0.282042 +35.21 0.276841 +35.22 0.263616 +35.23 0.242568 +35.24 0.213626 +35.25 0.176948 +35.26 0.132929 +35.27 0.0821898 +35.28 0.0255743 +35.29 -0.0359038 +35.3 -0.101184 +35.31 -0.168463 +35.32 -0.23626 +35.33 -0.303049 +35.34 -0.367293 +35.35 -0.427498 +35.36 -0.482251 +35.37 -0.530264 +35.38 -0.569655 +35.39 -0.600023 +35.4 -0.620817 +35.41 -0.631632 +35.42 -0.63232 +35.43 -0.622989 +35.44 -0.603999 +35.45 -0.575945 +35.46 -0.539166 +35.47 -0.495071 +35.48 -0.445109 +35.49 -0.390569 +35.5 -0.332805 +35.51 -0.273191 +35.52 -0.213082 +35.53 -0.153771 +35.54 -0.0965894 +35.55 -0.0427356 +35.56 0.00705537 +35.57 0.052128 +35.58 0.0920349 +35.59 0.126545 +35.6 0.155644 +35.61 0.17953 +35.62 0.198493 +35.63 0.21299 +35.64 0.22413 +35.65 0.232792 +35.66 0.239936 +35.67 0.246569 +35.68 0.253707 +35.69 0.262339 +35.7 0.273404 +35.71 0.288087 +35.72 0.306808 +35.73 0.330071 +35.74 0.358195 +35.75 0.391308 +35.76 0.429331 +35.77 0.471986 +35.78 0.518789 +35.79 0.569282 +35.8 0.622278 +35.81 0.676685 +35.82 0.731343 +35.83 0.785001 +35.84 0.83634 +35.85 0.884018 +35.86 0.926695 +35.87 0.962599 +35.88 0.990534 +35.89 1.00964 +35.9 1.01894 +35.91 1.01765 +35.92 1.00515 +35.93 0.981002 +35.94 0.944994 +35.95 0.896627 +35.96 0.835991 +35.97 0.764215 +35.98 0.681957 +35.99 0.590061 +36 0.489541 +36.01 0.381558 +36.02 0.267401 +36.03 0.148394 +36.04 0.0260015 +36.05 -0.0978952 +36.06 -0.221741 +36.07 -0.343985 +36.08 -0.463103 +36.09 -0.577626 +36.1 -0.68615 +36.11 -0.787353 +36.12 -0.879112 +36.13 -0.961102 +36.14 -1.0324 +36.15 -1.09224 +36.16 -1.13999 +36.17 -1.17518 +36.18 -1.19747 +36.19 -1.20671 +36.2 -1.20184 +36.21 -1.18381 +36.22 -1.15317 +36.23 -1.11034 +36.24 -1.05586 +36.25 -0.990406 +36.26 -0.914729 +36.27 -0.829687 +36.28 -0.735788 +36.29 -0.63434 +36.3 -0.526759 +36.31 -0.414183 +36.32 -0.29779 +36.33 -0.17879 +36.34 -0.0584108 +36.35 0.0621072 +36.36 0.181431 +36.37 0.298129 +36.38 0.411072 +36.39 0.519086 +36.4 0.621042 +36.41 0.715864 +36.42 0.802541 +36.43 0.880138 +36.44 0.947612 +36.45 1.00349 +36.46 1.04796 +36.47 1.08055 +36.48 1.10089 +36.49 1.10879 +36.5 1.10417 +36.51 1.08714 +36.52 1.05794 +36.53 1.01593 +36.54 0.962838 +36.55 0.899456 +36.56 0.826686 +36.57 0.745561 +36.58 0.65723 +36.59 0.562942 +36.6 0.464025 +36.61 0.361722 +36.62 0.257765 +36.63 0.153633 +36.64 0.0507034 +36.65 -0.0497078 +36.66 -0.14637 +36.67 -0.238166 +36.68 -0.324113 +36.69 -0.402974 +36.7 -0.474125 +36.71 -0.537429 +36.72 -0.592654 +36.73 -0.639759 +36.74 -0.678895 +36.75 -0.710391 +36.76 -0.734742 +36.77 -0.752387 +36.78 -0.764038 +36.79 -0.770976 +36.8 -0.774138 +36.81 -0.774488 +36.82 -0.772989 +36.83 -0.770569 +36.84 -0.768091 +36.85 -0.766354 +36.86 -0.766172 +36.87 -0.767972 +36.88 -0.772102 +36.89 -0.778756 +36.9 -0.787969 +36.91 -0.799624 +36.92 -0.813457 +36.93 -0.829075 +36.94 -0.846033 +36.95 -0.863556 +36.96 -0.880961 +36.97 -0.897537 +36.98 -0.912567 +36.99 -0.925355 +37 -0.935249 +37.01 -0.941666 +37.02 -0.943796 +37.03 -0.941412 +37.04 -0.934374 +37.05 -0.922568 +37.06 -0.906007 +37.07 -0.88483 +37.08 -0.859293 +37.09 -0.829761 +37.1 -0.796541 +37.11 -0.760267 +37.12 -0.721707 +37.13 -0.681497 +37.14 -0.64029 +37.15 -0.598728 +37.16 -0.55743 +37.17 -0.516971 +37.18 -0.477928 +37.19 -0.440857 +37.2 -0.406016 +37.21 -0.373672 +37.22 -0.344006 +37.23 -0.317116 +37.24 -0.293018 +37.25 -0.271653 +37.26 -0.252925 +37.27 -0.236808 +37.28 -0.222841 +37.29 -0.210753 +37.3 -0.200252 +37.31 -0.191037 +37.32 -0.1828 +37.33 -0.175246 +37.34 -0.168088 +37.35 -0.161053 +37.36 -0.15387 +37.37 -0.146327 +37.38 -0.13824 +37.39 -0.129448 +37.4 -0.119817 +37.41 -0.109229 +37.42 -0.0975852 +37.43 -0.0847142 +37.44 -0.0705768 +37.45 -0.0551344 +37.46 -0.0383165 +37.47 -0.0200507 +37.48 -0.000261436 +37.49 0.0211312 +37.5 0.0442111 +37.51 0.0691564 +37.52 0.0960669 +37.53 0.124939 +37.54 0.155849 +37.55 0.18886 +37.56 0.224016 +37.57 0.261337 +37.58 0.300809 +37.59 0.342438 +37.6 0.38619 +37.61 0.431758 +37.62 0.47892 +37.63 0.527392 +37.64 0.576824 +37.65 0.626795 +37.66 0.676821 +37.67 0.726339 +37.68 0.774578 +37.69 0.820909 +37.7 0.864613 +37.71 0.904934 +37.72 0.941105 +37.73 0.972353 +37.74 0.997927 +37.75 1.01711 +37.76 1.02858 +37.77 1.03223 +37.78 1.0277 +37.79 1.01464 +37.8 0.992844 +37.81 0.962257 +37.82 0.922987 +37.83 0.875309 +37.84 0.819191 +37.85 0.755611 +37.86 0.685644 +37.87 0.610256 +37.88 0.530547 +37.89 0.447721 +37.9 0.363069 +37.91 0.277934 +37.92 0.193788 +37.93 0.112175 +37.94 0.0343345 +37.95 -0.0385229 +37.96 -0.105301 +37.97 -0.165047 +37.98 -0.21697 +37.99 -0.260464 +38 -0.294888 +38.01 -0.319601 +38.02 -0.335286 +38.03 -0.342172 +38.04 -0.340663 +38.05 -0.331318 +38.06 -0.314829 +38.07 -0.291988 +38.08 -0.263639 +38.09 -0.230374 +38.1 -0.193594 +38.11 -0.154172 +38.12 -0.112915 +38.13 -0.0705466 +38.14 -0.0276824 +38.15 0.0151814 +38.16 0.0576766 +38.17 0.0995043 +38.18 0.140599 +38.19 0.180995 +38.2 0.220808 +38.21 0.260223 +38.22 0.299469 +38.23 0.338795 +38.24 0.378436 +38.25 0.418632 +38.26 0.459505 +38.27 0.501058 +38.28 0.543204 +38.29 0.585734 +38.3 0.628311 +38.31 0.670475 +38.32 0.711642 +38.33 0.751017 +38.34 0.787676 +38.35 0.820862 +38.36 0.849686 +38.37 0.873255 +38.38 0.890708 +38.39 0.901243 +38.4 0.904147 +38.41 0.898631 +38.42 0.883734 +38.43 0.859831 +38.44 0.826835 +38.45 0.784841 +38.46 0.734119 +38.47 0.675114 +38.48 0.608429 +38.49 0.534805 +38.5 0.454637 +38.51 0.369573 +38.52 0.280673 +38.53 0.189039 +38.54 0.0957865 +38.55 0.00202374 +38.56 -0.0911741 +38.57 -0.182782 +38.58 -0.271565 +38.59 -0.356722 +38.6 -0.437525 +38.61 -0.513281 +38.62 -0.583386 +38.63 -0.647314 +38.64 -0.704613 +38.65 -0.754895 +38.66 -0.797375 +38.67 -0.831886 +38.68 -0.858472 +38.69 -0.876921 +38.7 -0.887041 +38.71 -0.88867 +38.72 -0.881669 +38.73 -0.865928 +38.74 -0.841039 +38.75 -0.806807 +38.76 -0.763818 +38.77 -0.712213 +38.78 -0.652229 +38.79 -0.584212 +38.8 -0.508629 +38.81 -0.426077 +38.82 -0.337191 +38.83 -0.242633 +38.84 -0.14393 +38.85 -0.0422564 +38.86 0.0611005 +38.87 0.164756 +38.88 0.267252 +38.89 0.367081 +38.9 0.462719 +38.91 0.551967 +38.92 0.633796 +38.93 0.706869 +38.94 0.769961 +38.95 0.822008 +38.96 0.86213 +38.97 0.889658 +38.98 0.90415 +38.99 0.904406 +39 0.891238 +39.01 0.865299 +39.02 0.827195 +39.03 0.777762 +39.04 0.718044 +39.05 0.649274 +39.06 0.572839 +39.07 0.490005 +39.08 0.402669 +39.09 0.312745 +39.1 0.221899 +39.11 0.131767 +39.12 0.0439145 +39.13 -0.0401915 +39.14 -0.119215 +39.15 -0.191716 +39.16 -0.256398 +39.17 -0.312905 +39.18 -0.360685 +39.19 -0.399402 +39.2 -0.42893 +39.21 -0.44935 +39.22 -0.460941 +39.23 -0.464046 +39.24 -0.458854 +39.25 -0.446854 +39.26 -0.428961 +39.27 -0.406182 +39.28 -0.379582 +39.29 -0.350265 +39.3 -0.319343 +39.31 -0.287909 +39.32 -0.257162 +39.33 -0.228117 +39.34 -0.201654 +39.35 -0.178556 +39.36 -0.159488 +39.37 -0.144991 +39.38 -0.135472 +39.39 -0.131204 +39.4 -0.132719 +39.41 -0.13975 +39.42 -0.152016 +39.43 -0.169259 +39.44 -0.191123 +39.45 -0.217161 +39.46 -0.246857 +39.47 -0.279637 +39.48 -0.31498 +39.49 -0.352143 +39.5 -0.390367 +39.51 -0.429007 +39.52 -0.467439 +39.53 -0.505071 +39.54 -0.541347 +39.55 -0.575762 +39.56 -0.607769 +39.57 -0.636838 +39.58 -0.662794 +39.59 -0.685364 +39.6 -0.704329 +39.61 -0.719523 +39.62 -0.730827 +39.63 -0.738168 +39.64 -0.741465 +39.65 -0.74043 +39.66 -0.735451 +39.67 -0.726602 +39.68 -0.713981 +39.69 -0.697715 +39.7 -0.677949 +39.71 -0.654853 +39.72 -0.628621 +39.73 -0.599224 +39.74 -0.567159 +39.75 -0.532712 +39.76 -0.496161 +39.77 -0.457804 +39.78 -0.417953 +39.79 -0.376932 +39.8 -0.33507 +39.81 -0.292689 +39.82 -0.250173 +39.83 -0.207851 +39.84 -0.166021 +39.85 -0.124953 +39.86 -0.0848926 +39.87 -0.0460454 +39.88 -0.00858016 +39.89 0.0272977 +39.9 0.0614984 +39.91 0.0940623 +39.92 0.124993 +39.93 0.154324 +39.94 0.182116 +39.95 0.208445 +39.96 0.233394 +39.97 0.257007 +39.98 0.27931 +39.99 0.300427 +40 0.320365 +40.01 0.339087 +40.02 0.356515 +40.03 0.372523 +40.04 0.386941 +40.05 0.399539 +40.06 0.409878 +40.07 0.417848 +40.08 0.423167 +40.09 0.425556 +40.1 0.424764 +40.11 0.420574 +40.12 0.412828 +40.13 0.401437 +40.14 0.38609 +40.15 0.367179 +40.16 0.344964 +40.17 0.319775 +40.18 0.292042 +40.19 0.262295 +40.2 0.231149 +40.21 0.199287 +40.22 0.167498 +40.23 0.136631 +40.24 0.107438 +40.25 0.0806426 +40.26 0.0569045 +40.27 0.0367957 +40.28 0.0207749 +40.29 0.00916698 +40.3 0.00235726 +40.31 0.000366179 +40.32 0.00275741 +40.33 0.00917006 +40.34 0.0190878 +40.35 0.0318546 +40.36 0.0466964 +40.37 0.0627479 +40.38 0.0790648 +40.39 0.0945556 +40.4 0.108279 +40.41 0.119361 +40.42 0.12702 +40.43 0.130601 +40.44 0.1296 +40.45 0.123688 +40.46 0.112705 +40.47 0.0963078 +40.48 0.0752834 +40.49 0.0501443 +40.5 0.0215813 +40.51 -0.00955857 +40.52 -0.042301 +40.53 -0.0755776 +40.54 -0.108264 +40.55 -0.138979 +40.56 -0.166629 +40.57 -0.19021 +40.58 -0.208808 +40.59 -0.22165 +40.6 -0.228139 +40.61 -0.227872 +40.62 -0.220655 +40.63 -0.206092 +40.64 -0.184671 +40.65 -0.157131 +40.66 -0.124155 +40.67 -0.0865869 +40.68 -0.0454062 +40.69 -0.00169367 +40.7 0.0434047 +40.71 0.0886719 +40.72 0.132788 +40.73 0.17465 +40.74 0.213248 +40.75 0.247692 +40.76 0.277233 +40.77 0.301285 +40.78 0.319436 +40.79 0.33132 +40.8 0.336572 +40.81 0.33583 +40.82 0.329424 +40.83 0.31783 +40.84 0.301647 +40.85 0.281574 +40.86 0.258383 +40.87 0.232895 +40.88 0.205899 +40.89 0.178439 +40.9 0.151317 +40.91 0.125284 +40.92 0.101026 +40.93 0.0791442 +40.94 0.0601513 +40.95 0.0444651 +40.96 0.0327134 +40.97 0.0249057 +40.98 0.0210883 +40.99 0.0212923 +41 0.0254757 +41.01 0.0335338 +41.02 0.0453089 +41.03 0.060601 +41.04 0.0793565 +41.05 0.101216 +41.06 0.125776 +41.07 0.152749 +41.08 0.18184 +41.09 0.21275 +41.1 0.245174 +41.11 0.2788 +41.12 0.313331 +41.13 0.348404 +41.14 0.383622 +41.15 0.418619 +41.16 0.453012 +41.17 0.486396 +41.18 0.518345 +41.19 0.548417 +41.2 0.5761 +41.21 0.600707 +41.22 0.621959 +41.23 0.639396 +41.24 0.652577 +41.25 0.661089 +41.26 0.664563 +41.27 0.66268 +41.28 0.65519 +41.29 0.641344 +41.3 0.621611 +41.31 0.596019 +41.32 0.564689 +41.33 0.527841 +41.34 0.485798 +41.35 0.438977 +41.36 0.387886 +41.37 0.332887 +41.38 0.27492 +41.39 0.214791 +41.4 0.153265 +41.41 0.0911256 +41.42 0.029158 +41.43 -0.0318707 +41.44 -0.0912282 +41.45 -0.148067 +41.46 -0.201742 +41.47 -0.251845 +41.48 -0.297959 +41.49 -0.339769 +41.5 -0.377061 +41.51 -0.409728 +41.52 -0.437764 +41.53 -0.461099 +41.54 -0.479823 +41.55 -0.494479 +41.56 -0.505387 +41.57 -0.512902 +41.58 -0.517404 +41.59 -0.519278 +41.6 -0.518898 +41.61 -0.516587 +41.62 -0.512575 +41.63 -0.507258 +41.64 -0.500818 +41.65 -0.493372 +41.66 -0.48497 +41.67 -0.475599 +41.68 -0.465185 +41.69 -0.453606 +41.7 -0.440557 +41.71 -0.42595 +41.72 -0.409592 +41.73 -0.391293 +41.74 -0.370889 +41.75 -0.348255 +41.76 -0.323321 +41.77 -0.296082 +41.78 -0.266455 +41.79 -0.234729 +41.8 -0.201212 +41.81 -0.166243 +41.82 -0.130234 +41.83 -0.0936646 +41.84 -0.0570719 +41.85 -0.0210328 +41.86 0.013757 +41.87 0.0466216 +41.88 0.0770292 +41.89 0.104429 +41.9 0.128327 +41.91 0.148299 +41.92 0.164008 +41.93 0.175212 +41.94 0.181625 +41.95 0.183079 +41.96 0.17999 +41.97 0.172579 +41.98 0.161165 +41.99 0.146157 +42 0.128041 +42.01 0.107366 +42.02 0.0847112 +42.03 0.0606674 +42.04 0.0360283 +42.05 0.0114167 +42.06 -0.0125715 +42.07 -0.0353811 +42.08 -0.0565096 +42.09 -0.0755171 +42.1 -0.0920335 +42.11 -0.105493 +42.12 -0.115899 +42.13 -0.12315 +42.14 -0.127198 +42.15 -0.128061 +42.16 -0.125824 +42.17 -0.120627 +42.18 -0.112663 +42.19 -0.102008 +42.2 -0.0890768 +42.21 -0.0742485 +42.22 -0.0578574 +42.23 -0.0402569 +42.24 -0.021816 +42.25 -0.00291702 +42.26 0.0160478 +42.27 0.0346405 +42.28 0.0524001 +42.29 0.068935 +42.3 0.083835 +42.31 0.0966934 +42.32 0.107112 +42.33 0.114707 +42.34 0.119115 +42.35 0.119889 +42.36 0.116547 +42.37 0.109094 +42.38 0.0973446 +42.39 0.0811792 +42.4 0.0605502 +42.41 0.0354891 +42.42 0.00611208 +42.43 -0.0274118 +42.44 -0.0650583 +42.45 -0.106067 +42.46 -0.149961 +42.47 -0.196192 +42.48 -0.24415 +42.49 -0.293175 +42.5 -0.342576 +42.51 -0.391641 +42.52 -0.439507 +42.53 -0.485495 +42.54 -0.528967 +42.55 -0.569319 +42.56 -0.606014 +42.57 -0.63859 +42.58 -0.66667 +42.59 -0.689965 +42.6 -0.707939 +42.61 -0.720659 +42.62 -0.728282 +42.63 -0.730882 +42.64 -0.7286 +42.65 -0.721633 +42.66 -0.710221 +42.67 -0.694632 +42.68 -0.674988 +42.69 -0.651596 +42.7 -0.624938 +42.71 -0.595281 +42.72 -0.56287 +42.73 -0.527924 +42.74 -0.49064 +42.75 -0.451191 +42.76 -0.409683 +42.77 -0.366192 +42.78 -0.321002 +42.79 -0.274264 +42.8 -0.226147 +42.81 -0.176848 +42.82 -0.126598 +42.83 -0.0756692 +42.84 -0.0243787 +42.85 0.0268677 +42.86 0.0776219 +42.87 0.127425 +42.88 0.175785 +42.89 0.22219 +42.9 0.266118 +42.91 0.30705 +42.92 0.344487 +42.93 0.377598 +42.94 0.40621 +42.95 0.430043 +42.96 0.448839 +42.97 0.462423 +42.98 0.470715 +42.99 0.473728 +43 0.471569 +43.01 0.464144 +43.02 0.45192 +43.03 0.435448 +43.04 0.415162 +43.05 0.391545 +43.06 0.365113 +43.07 0.336395 +43.08 0.305926 +43.09 0.274197 +43.1 0.24176 +43.11 0.209133 +43.12 0.176732 +43.13 0.144924 +43.14 0.114028 +43.15 0.0843172 +43.16 0.0560196 +43.17 0.0293647 +43.18 0.00461737 +43.19 -0.0182186 +43.2 -0.039032 +43.21 -0.0577203 +43.22 -0.0741839 +43.23 -0.088321 +43.24 -0.100026 +43.25 -0.109181 +43.26 -0.115411 +43.27 -0.118848 +43.28 -0.119385 +43.29 -0.116929 +43.3 -0.11141 +43.31 -0.102791 +43.32 -0.0910768 +43.33 -0.076318 +43.34 -0.0584019 +43.35 -0.0377059 +43.36 -0.01453 +43.37 0.0108161 +43.38 0.0379638 +43.39 0.0664925 +43.4 0.0959386 +43.41 0.125808 +43.42 0.155551 +43.43 0.184596 +43.44 0.212455 +43.45 0.238671 +43.46 0.262834 +43.47 0.2846 +43.48 0.303696 +43.49 0.319933 +43.5 0.333092 +43.51 0.343111 +43.52 0.350259 +43.53 0.354703 +43.54 0.356683 +43.55 0.356487 +43.56 0.354447 +43.57 0.350912 +43.58 0.346221 +43.59 0.340699 +43.6 0.33473 +43.61 0.328561 +43.62 0.322374 +43.63 0.316285 +43.64 0.31033 +43.65 0.304469 +43.66 0.29859 +43.67 0.292467 +43.68 0.285854 +43.69 0.27846 +43.7 0.269971 +43.71 0.260068 +43.72 0.248442 +43.73 0.234817 +43.74 0.218964 +43.75 0.200526 +43.76 0.179549 +43.77 0.1561 +43.78 0.130288 +43.79 0.10231 +43.8 0.0724558 +43.81 0.0410947 +43.82 0.00866714 +43.83 -0.0243293 +43.84 -0.0572763 +43.85 -0.0895886 +43.86 -0.120703 +43.87 -0.150077 +43.88 -0.177205 +43.89 -0.201637 +43.9 -0.222989 +43.91 -0.240829 +43.92 -0.254836 +43.93 -0.265123 +43.94 -0.271686 +43.95 -0.274611 +43.96 -0.274069 +43.97 -0.270308 +43.98 -0.263639 +43.99 -0.25439 +44 -0.242866 +44.01 -0.229696 +44.02 -0.215311 +44.03 -0.20013 +44.04 -0.184551 +44.05 -0.168941 +44.06 -0.153625 +44.07 -0.138881 +44.08 -0.125023 +44.09 -0.112157 +44.1 -0.100383 +44.11 -0.0897512 +44.12 -0.0802627 +44.13 -0.0718744 +44.14 -0.0645035 +44.15 -0.0580328 +44.16 -0.0523597 +44.17 -0.0472584 +44.18 -0.0425213 +44.19 -0.0379477 +44.2 -0.0333293 +44.21 -0.0284539 +44.22 -0.0231087 +44.23 -0.0170828 +44.24 -0.0101128 +44.25 -0.0019839 +44.26 0.00744699 +44.27 0.0183489 +44.28 0.030872 +44.29 0.0451438 +44.3 0.0612636 +44.31 0.0792984 +44.32 0.0993403 +44.33 0.12143 +44.34 0.145364 +44.35 0.171015 +44.36 0.198196 +44.37 0.226663 +44.38 0.256113 +44.39 0.286185 +44.4 0.316461 +44.41 0.346408 +44.42 0.375502 +44.43 0.403206 +44.44 0.428969 +44.45 0.452237 +44.46 0.472471 +44.47 0.489162 +44.48 0.501845 +44.49 0.509686 +44.5 0.512705 +44.51 0.510715 +44.52 0.503594 +44.53 0.491327 +44.54 0.474009 +44.55 0.451845 +44.56 0.425148 +44.57 0.394083 +44.58 0.359401 +44.59 0.321822 +44.6 0.282015 +44.61 0.240695 +44.62 0.1986 +44.63 0.156479 +44.64 0.115068 +44.65 0.075175 +44.66 0.0375333 +44.67 0.00264402 +44.68 -0.0290007 +44.69 -0.0570003 +44.7 -0.0810535 +44.71 -0.100963 +44.72 -0.116636 +44.73 -0.127962 +44.74 -0.134915 +44.75 -0.138008 +44.76 -0.137545 +44.77 -0.133896 +44.78 -0.127491 +44.79 -0.1188 +44.8 -0.108323 +44.81 -0.09657 +44.82 -0.0840526 +44.83 -0.0713798 +44.84 -0.0590319 +44.85 -0.0474558 +44.86 -0.0370573 +44.87 -0.0281943 +44.88 -0.0211722 +44.89 -0.0162404 +44.9 -0.0138005 +44.91 -0.0138146 +44.92 -0.0163186 +44.93 -0.021318 +44.94 -0.0287658 +44.95 -0.0385659 +44.96 -0.050577 +44.97 -0.0646178 +44.98 -0.0805757 +44.99 -0.0981083 +45 -0.116885 +45.01 -0.136613 +45.02 -0.156992 +45.03 -0.177714 +45.04 -0.198473 +45.05 -0.218972 +45.06 -0.238886 +45.07 -0.25791 +45.08 -0.275835 +45.09 -0.292448 +45.1 -0.307566 +45.11 -0.32104 +45.12 -0.332752 +45.13 -0.34262 +45.14 -0.350541 +45.15 -0.356408 +45.16 -0.36037 +45.17 -0.36246 +45.18 -0.362728 +45.19 -0.361236 +45.2 -0.358049 +45.21 -0.353232 +45.22 -0.346836 +45.23 -0.338781 +45.24 -0.329237 +45.25 -0.318214 +45.26 -0.305698 +45.27 -0.29166 +45.28 -0.276048 +45.29 -0.2588 +45.3 -0.239841 +45.31 -0.21894 +45.32 -0.196147 +45.33 -0.171435 +45.34 -0.144777 +45.35 -0.116183 +45.36 -0.085703 +45.37 -0.0534394 +45.38 -0.0195506 +45.39 0.0158147 +45.4 0.052291 +45.41 0.0894701 +45.42 0.126933 +45.43 0.164207 +45.44 0.200777 +45.45 0.236092 +45.46 0.269583 +45.47 0.300543 +45.48 0.328301 +45.49 0.352459 +45.5 0.372527 +45.51 0.388077 +45.52 0.398753 +45.53 0.404282 +45.54 0.404484 +45.55 0.399156 +45.56 0.38805 +45.57 0.371721 +45.58 0.350412 +45.59 0.324454 +45.6 0.294259 +45.61 0.260306 +45.62 0.223126 +45.63 0.183282 +45.64 0.141235 +45.65 0.0978222 +45.66 0.0536275 +45.67 0.0092057 +45.68 -0.0349266 +45.69 -0.0783015 +45.7 -0.120506 +45.71 -0.161188 +45.72 -0.199892 +45.73 -0.236489 +45.74 -0.270852 +45.75 -0.302871 +45.76 -0.332481 +45.77 -0.359649 +45.78 -0.384368 +45.79 -0.406647 +45.8 -0.426355 +45.81 -0.443562 +45.82 -0.458362 +45.83 -0.470744 +45.84 -0.480682 +45.85 -0.48813 +45.86 -0.493031 +45.87 -0.495312 +45.88 -0.494785 +45.89 -0.491313 +45.9 -0.484984 +45.91 -0.47575 +45.92 -0.463586 +45.93 -0.448503 +45.94 -0.430551 +45.95 -0.409825 +45.96 -0.386427 +45.97 -0.360439 +45.98 -0.332329 +45.99 -0.302411 +46 -0.271044 +46.01 -0.238625 +46.02 -0.205581 +46.03 -0.172362 +46.04 -0.139429 +46.05 -0.107356 +46.06 -0.0765547 +46.07 -0.0474421 +46.08 -0.0204014 +46.09 0.00422964 +46.1 0.0261659 +46.11 0.0451819 +46.12 0.0611164 +46.13 0.0736295 +46.14 0.0828826 +46.15 0.0890029 +46.16 0.0921164 +46.17 0.0924109 +46.18 0.0901313 +46.19 0.0855732 +46.2 0.0790762 +46.21 0.0709519 +46.22 0.0616833 +46.23 0.051762 +46.24 0.0416275 +46.25 0.0317215 +46.26 0.0224803 +46.27 0.0143269 +46.28 0.00766406 +46.29 0.00294811 +46.3 0.000612569 +46.31 0.000828904 +46.32 0.00383858 +46.33 0.00982957 +46.34 0.0189315 +46.35 0.0312114 +46.36 0.0466701 +46.37 0.0652873 +46.38 0.087096 +46.39 0.111616 +46.4 0.138549 +46.41 0.167532 +46.42 0.198136 +46.43 0.229878 +46.44 0.262225 +46.45 0.294604 +46.46 0.326302 +46.47 0.356697 +46.48 0.385175 +46.49 0.411132 +46.5 0.433996 +46.51 0.453241 +46.52 0.468397 +46.53 0.479072 +46.54 0.484586 +46.55 0.484954 +46.56 0.480206 +46.57 0.470365 +46.58 0.455562 +46.59 0.436038 +46.6 0.412133 +46.61 0.384288 +46.62 0.352877 +46.63 0.318608 +46.64 0.282297 +46.65 0.244641 +46.66 0.206357 +46.67 0.168162 +46.68 0.130754 +46.69 0.0947952 +46.7 0.0609867 +46.71 0.0300163 +46.72 0.002197 +46.73 -0.0221047 +46.74 -0.0426132 +46.75 -0.0591461 +46.76 -0.0716128 +46.77 -0.0800114 +46.78 -0.0843733 +46.79 -0.0846007 +46.8 -0.0812657 +46.81 -0.0746532 +46.82 -0.0650875 +46.83 -0.0529205 +46.84 -0.0385212 +46.85 -0.0222647 +46.86 -0.00452418 +46.87 0.0144068 +46.88 0.0340596 +46.89 0.054103 +46.9 0.0742354 +46.91 0.094177 +46.92 0.113669 +46.93 0.132469 +46.94 0.150353 +46.95 0.167012 +46.96 0.182266 +46.97 0.19595 +46.98 0.207868 +46.99 0.217824 +47 0.225629 +47.01 0.231096 +47.02 0.234046 +47.03 0.234172 +47.04 0.231314 +47.05 0.225488 +47.06 0.216611 +47.07 0.204644 +47.08 0.189596 +47.09 0.171531 +47.1 0.150571 +47.11 0.126833 +47.12 0.100512 +47.13 0.0721244 +47.14 0.0420638 +47.15 0.0107799 +47.16 -0.0212304 +47.17 -0.0534328 +47.18 -0.0852673 +47.19 -0.116147 +47.2 -0.145323 +47.21 -0.172343 +47.22 -0.196693 +47.23 -0.217914 +47.24 -0.235608 +47.25 -0.249451 +47.26 -0.259203 +47.27 -0.264715 +47.28 -0.265561 +47.29 -0.262131 +47.3 -0.25463 +47.31 -0.243304 +47.32 -0.228483 +47.33 -0.210565 +47.34 -0.190009 +47.35 -0.16732 +47.36 -0.142965 +47.37 -0.117617 +47.38 -0.0918747 +47.39 -0.0662721 +47.4 -0.041313 +47.41 -0.0174584 +47.42 0.0048826 +47.43 0.0253619 +47.44 0.0435871 +47.45 0.059318 +47.46 0.0725439 +47.47 0.0832122 +47.48 0.0913374 +47.49 0.0969953 +47.5 0.100317 +47.51 0.10148 +47.52 0.100658 +47.53 0.0980514 +47.54 0.0940678 +47.55 0.0889869 +47.56 0.0830891 +47.57 0.0766483 +47.58 0.069925 +47.59 0.0631609 +47.6 0.0565767 +47.61 0.0504124 +47.62 0.0448043 +47.63 0.0398785 +47.64 0.0357314 +47.65 0.0324313 +47.66 0.0300195 +47.67 0.0285127 +47.68 0.027906 +47.69 0.0282454 +47.7 0.0294233 +47.71 0.0313716 +47.72 0.0340243 +47.73 0.0373085 +47.74 0.041146 +47.75 0.0454555 +47.76 0.0501538 +47.77 0.0551712 +47.78 0.0604056 +47.79 0.0657647 +47.8 0.0711677 +47.81 0.076535 +47.82 0.0817885 +47.83 0.0868512 +47.84 0.091647 +47.85 0.0960824 +47.86 0.100068 +47.87 0.103549 +47.88 0.10645 +47.89 0.108697 +47.9 0.110216 +47.91 0.110933 +47.92 0.110773 +47.93 0.109642 +47.94 0.107409 +47.95 0.104078 +47.96 0.0995874 +47.97 0.0938801 +47.98 0.0869057 +47.99 0.0786225 +48 0.0689987 +48.01 0.058011 +48.02 0.0455287 +48.03 0.0316952 +48.04 0.0165424 +48.05 0.00012127 +48.06 -0.017497 +48.07 -0.0362199 +48.08 -0.0559337 +48.09 -0.0765032 +48.1 -0.097815 +48.11 -0.11963 +48.12 -0.141742 +48.13 -0.163941 +48.14 -0.18601 +48.15 -0.207724 +48.16 -0.22886 +48.17 -0.249195 +48.18 -0.268445 +48.19 -0.286414 +48.2 -0.30297 +48.21 -0.317967 +48.22 -0.331289 +48.23 -0.342856 +48.24 -0.352621 +48.25 -0.360581 +48.26 -0.366699 +48.27 -0.371033 +48.28 -0.373804 +48.29 -0.375166 +48.3 -0.375303 +48.31 -0.374422 +48.32 -0.37275 +48.33 -0.370529 +48.34 -0.368001 +48.35 -0.365432 +48.36 -0.363073 +48.37 -0.361135 +48.38 -0.359804 +48.39 -0.359237 +48.4 -0.359553 +48.41 -0.360832 +48.42 -0.363113 +48.43 -0.366483 +48.44 -0.370787 +48.45 -0.375923 +48.46 -0.381754 +48.47 -0.388105 +48.48 -0.394775 +48.49 -0.401536 +48.5 -0.408145 +48.51 -0.414296 +48.52 -0.419725 +48.53 -0.424189 +48.54 -0.427443 +48.55 -0.429263 +48.56 -0.429444 +48.57 -0.42781 +48.58 -0.424215 +48.59 -0.418423 +48.6 -0.410383 +48.61 -0.400148 +48.62 -0.387719 +48.63 -0.373133 +48.64 -0.356456 +48.65 -0.337788 +48.66 -0.317253 +48.67 -0.294941 +48.68 -0.271006 +48.69 -0.245738 +48.7 -0.219334 +48.71 -0.192001 +48.72 -0.163945 +48.73 -0.135375 +48.74 -0.106499 +48.75 -0.077518 +48.76 -0.0486506 +48.77 -0.0200834 +48.78 0.00801506 +48.79 0.0354894 +48.8 0.062198 +48.81 0.088014 +48.82 0.112825 +48.83 0.136534 +48.84 0.158939 +48.85 0.180076 +48.86 0.199895 +48.87 0.218357 +48.88 0.23543 +48.89 0.251095 +48.9 0.265339 +48.91 0.278156 +48.92 0.289441 +48.93 0.299268 +48.94 0.307679 +48.95 0.314681 +48.96 0.320284 +48.97 0.324494 +48.98 0.327323 +48.99 0.328778 +49 0.328799 +49.01 0.327414 +49.02 0.324703 +49.03 0.320693 +49.04 0.31542 +49.05 0.308933 +49.06 0.301296 +49.07 0.292592 +49.08 0.282894 +49.09 0.27232 +49.1 0.26109 +49.11 0.249384 +49.12 0.237412 +49.13 0.225406 +49.14 0.213622 +49.15 0.202336 +49.16 0.191855 +49.17 0.18259 +49.18 0.174774 +49.19 0.168707 +49.2 0.164672 +49.21 0.162933 +49.22 0.163719 +49.23 0.167222 +49.24 0.173584 +49.25 0.183163 +49.26 0.195729 +49.27 0.211203 +49.28 0.229454 +49.29 0.250283 +49.3 0.273418 +49.31 0.29852 +49.32 0.325187 +49.33 0.353003 +49.34 0.381355 +49.35 0.409668 +49.36 0.437375 +49.37 0.4639 +49.38 0.488676 +49.39 0.511154 +49.4 0.530818 +49.41 0.547014 +49.42 0.559293 +49.43 0.56749 +49.44 0.571358 +49.45 0.570732 +49.46 0.565535 +49.47 0.555776 +49.48 0.54155 +49.49 0.522912 +49.5 0.499997 +49.51 0.473426 +49.52 0.443586 +49.53 0.410903 +49.54 0.375833 +49.55 0.338852 +49.56 0.300437 +49.57 0.261054 +49.58 0.22116 +49.59 0.181247 +49.6 0.141689 +49.61 0.102816 +49.62 0.0649061 +49.63 0.0281877 +49.64 -0.00716466 +49.65 -0.0410266 +49.66 -0.0731808 +49.67 -0.103716 +49.68 -0.132658 +49.69 -0.16004 +49.7 -0.185919 +49.71 -0.210357 +49.72 -0.233423 +49.73 -0.255182 +49.74 -0.275604 +49.75 -0.294772 +49.76 -0.312737 +49.77 -0.329484 +49.78 -0.344974 +49.79 -0.359146 +49.8 -0.371913 +49.81 -0.383169 +49.82 -0.392709 +49.83 -0.400375 +49.84 -0.406113 +49.85 -0.409787 +49.86 -0.411268 +49.87 -0.410443 +49.88 -0.407217 +49.89 -0.40152 +49.9 -0.393243 +49.91 -0.382263 +49.92 -0.368791 +49.93 -0.35289 +49.94 -0.334657 +49.95 -0.314221 +49.96 -0.291739 +49.97 -0.267395 +49.98 -0.241385 +49.99 -0.213834 +50 -0.185131 +50.01 -0.155519 +50.02 -0.125246 +50.03 -0.0945576 +50.04 -0.0636921 +50.05 -0.0328788 +50.06 -0.00233424 +50.07 0.0276845 +50.08 0.0570107 +50.09 0.0854872 +50.1 0.112968 +50.11 0.139324 +50.12 0.164442 +50.13 0.188224 +50.14 0.210585 +50.15 0.231353 +50.16 0.250503 +50.17 0.268033 +50.18 0.283896 +50.19 0.29805 +50.2 0.310458 +50.21 0.321085 +50.22 0.3299 +50.23 0.336793 +50.24 0.341716 +50.25 0.34475 +50.26 0.345879 +50.27 0.345093 +50.28 0.342389 +50.29 0.33777 +50.3 0.33125 +50.31 0.322807 +50.32 0.312383 +50.33 0.300163 +50.34 0.286203 +50.35 0.27057 +50.36 0.253341 +50.37 0.234602 +50.38 0.214451 +50.39 0.192989 +50.4 0.170233 +50.41 0.146426 +50.42 0.121695 +50.43 0.096172 +50.44 0.0699924 +50.45 0.0432941 +50.46 0.0162174 +50.47 -0.0110966 +50.48 -0.0385017 +50.49 -0.0658355 +50.5 -0.0929541 +50.51 -0.119715 +50.52 -0.145978 +50.53 -0.171601 +50.54 -0.196443 +50.55 -0.220366 +50.56 -0.243161 +50.57 -0.264699 +50.58 -0.284889 +50.59 -0.303603 +50.6 -0.320719 +50.61 -0.336118 +50.62 -0.349694 +50.63 -0.361346 +50.64 -0.370905 +50.65 -0.378252 +50.66 -0.383451 +50.67 -0.386467 +50.68 -0.387285 +50.69 -0.385912 +50.7 -0.382379 +50.71 -0.37674 +50.72 -0.36904 +50.73 -0.359289 +50.74 -0.347783 +50.75 -0.334685 +50.76 -0.320182 +50.77 -0.304483 +50.78 -0.287813 +50.79 -0.270413 +50.8 -0.252538 +50.81 -0.234449 +50.82 -0.216451 +50.83 -0.198801 +50.84 -0.181746 +50.85 -0.165516 +50.86 -0.150324 +50.87 -0.136355 +50.88 -0.123766 +50.89 -0.1128 +50.9 -0.103458 +50.91 -0.0957413 +50.92 -0.0896394 +50.93 -0.085099 +50.94 -0.0820234 +50.95 -0.0802743 +50.96 -0.0796749 +50.97 -0.0800517 +50.98 -0.0811123 +50.99 -0.0825464 +51 -0.0840641 +51.01 -0.0853659 +51.02 -0.0861502 +51.03 -0.0861212 +51.04 -0.0849967 +51.05 -0.0824568 +51.06 -0.0782075 +51.07 -0.0721472 +51.08 -0.0641326 +51.09 -0.054068 +51.1 -0.0419091 +51.11 -0.0276652 +51.12 -0.0113998 +51.13 0.00679858 +51.14 0.026848 +51.15 0.0483663 +51.16 0.0710816 +51.17 0.0946858 +51.18 0.118841 +51.19 0.143189 +51.2 0.167356 +51.21 0.190967 +51.22 0.213528 +51.23 0.234737 +51.24 0.254269 +51.25 0.271824 +51.26 0.287141 +51.27 0.300003 +51.28 0.310239 +51.29 0.31773 +51.3 0.322197 +51.31 0.323775 +51.32 0.322578 +51.33 0.318703 +51.34 0.312288 +51.35 0.303513 +51.36 0.292589 +51.37 0.279755 +51.38 0.265196 +51.39 0.249234 +51.4 0.232218 +51.41 0.214424 +51.42 0.196117 +51.43 0.177548 +51.44 0.15895 +51.45 0.140532 +51.46 0.122496 +51.47 0.105027 +51.48 0.0882203 +51.49 0.0721642 +51.5 0.0569204 +51.51 0.0425235 +51.52 0.0289836 +51.53 0.0162891 +51.54 0.00441979 +51.55 -0.00662219 +51.56 -0.0169661 +51.57 -0.026681 +51.58 -0.0358414 +51.59 -0.0445244 +51.6 -0.0528074 +51.61 -0.060766 +51.62 -0.0684727 +51.63 -0.0759845 +51.64 -0.0833837 +51.65 -0.0907263 +51.66 -0.0980607 +51.67 -0.105428 +51.68 -0.112864 +51.69 -0.120394 +51.7 -0.12804 +51.71 -0.135824 +51.72 -0.143742 +51.73 -0.151786 +51.74 -0.159939 +51.75 -0.168174 +51.76 -0.176454 +51.77 -0.184728 +51.78 -0.192933 +51.79 -0.200982 +51.8 -0.208772 +51.81 -0.216202 +51.82 -0.223155 +51.83 -0.2295 +51.84 -0.235098 +51.85 -0.239802 +51.86 -0.243462 +51.87 -0.245885 +51.88 -0.246855 +51.89 -0.246318 +51.9 -0.244149 +51.91 -0.240239 +51.92 -0.234504 +51.93 -0.226883 +51.94 -0.217347 +51.95 -0.205878 +51.96 -0.192376 +51.97 -0.177091 +51.98 -0.16014 +51.99 -0.141677 +52 -0.121888 +52.01 -0.100991 +52.02 -0.0792283 +52.03 -0.056864 +52.04 -0.0341759 +52.05 -0.0115055 +52.06 0.0108519 +52.07 0.0326098 +52.08 0.0534934 +52.09 0.0732463 +52.1 0.0916359 +52.11 0.108459 +52.12 0.123417 +52.13 0.136433 +52.14 0.147472 +52.15 0.156489 +52.16 0.163474 +52.17 0.168458 +52.18 0.171506 +52.19 0.172712 +52.2 0.172127 +52.21 0.169915 +52.22 0.16633 +52.23 0.161552 +52.24 0.155768 +52.25 0.149167 +52.26 0.141937 +52.27 0.134257 +52.28 0.126297 +52.29 0.11823 +52.3 0.110216 +52.31 0.102386 +52.32 0.0948552 +52.33 0.0877245 +52.34 0.0810827 +52.35 0.0750064 +52.36 0.0695669 +52.37 0.0648848 +52.38 0.0609502 +52.39 0.0578054 +52.4 0.0554874 +52.41 0.0540285 +52.42 0.0534555 +52.43 0.0537894 +52.44 0.0550443 +52.45 0.0573039 +52.46 0.0604947 +52.47 0.0645838 +52.48 0.069535 +52.49 0.075295 +52.5 0.0817911 +52.51 0.08893 +52.52 0.0965969 +52.53 0.10467 +52.54 0.112957 +52.55 0.121263 +52.56 0.129387 +52.57 0.137117 +52.58 0.144228 +52.59 0.150491 +52.6 0.155679 +52.61 0.159502 +52.62 0.161711 +52.63 0.16219 +52.64 0.160782 +52.65 0.157357 +52.66 0.151824 +52.67 0.144133 +52.68 0.134277 +52.69 0.122249 +52.7 0.108047 +52.71 0.0919749 +52.72 0.0742224 +52.73 0.0550211 +52.74 0.03464 +52.75 0.0133797 +52.76 -0.00843372 +52.77 -0.0304551 +52.78 -0.0522801 +52.79 -0.0735379 +52.8 -0.0938795 +52.81 -0.112972 +52.82 -0.130506 +52.83 -0.146204 +52.84 -0.159823 +52.85 -0.171164 +52.86 -0.179871 +52.87 -0.185997 +52.88 -0.189544 +52.89 -0.19053 +52.9 -0.189021 +52.91 -0.185135 +52.92 -0.179033 +52.93 -0.170921 +52.94 -0.160948 +52.95 -0.149462 +52.96 -0.136827 +52.97 -0.123354 +52.98 -0.109359 +52.99 -0.09516 +53 -0.0810631 +53.01 -0.0673593 +53.02 -0.054354 +53.03 -0.0423278 +53.04 -0.0314373 +53.05 -0.0218402 +53.06 -0.0136524 +53.07 -0.00694623 +53.08 -0.00174977 +53.09 0.00195249 +53.1 0.0041909 +53.11 0.00500105 +53.12 0.00463418 +53.13 0.00326079 +53.14 0.00107566 +53.15 -0.0017081 +53.16 -0.00486541 +53.17 -0.00816578 +53.18 -0.0113798 +53.19 -0.0142353 +53.2 -0.0165409 +53.21 -0.0181139 +53.22 -0.0187968 +53.23 -0.0184614 +53.24 -0.0170119 +53.25 -0.0143866 +53.26 -0.0105591 +53.27 -0.00544825 +53.28 0.000824694 +53.29 0.00815411 +53.3 0.0164303 +53.31 0.0255193 +53.32 0.0352677 +53.33 0.0455067 +53.34 0.0560581 +53.35 0.0667352 +53.36 0.077331 +53.37 0.0876656 +53.38 0.097574 +53.39 0.106905 +53.4 0.115527 +53.41 0.123324 +53.42 0.130204 +53.43 0.136057 +53.44 0.140809 +53.45 0.144497 +53.46 0.147118 +53.47 0.14869 +53.48 0.149245 +53.49 0.148828 +53.5 0.147497 +53.51 0.145306 +53.52 0.142285 +53.53 0.13858 +53.54 0.134273 +53.55 0.129444 +53.56 0.124173 +53.57 0.11854 +53.58 0.112623 +53.59 0.106497 +53.6 0.100231 +53.61 0.0939135 +53.62 0.087615 +53.63 0.081402 +53.64 0.0753391 +53.65 0.0694879 +53.66 0.0639062 +53.67 0.0586471 +53.68 0.0537884 +53.69 0.0493535 +53.7 0.0453613 +53.71 0.0418283 +53.72 0.0387573 +53.73 0.0361367 +53.74 0.0339384 +53.75 0.0321176 +53.76 0.0306242 +53.77 0.0293624 +53.78 0.0282188 +53.79 0.0270787 +53.8 0.0258157 +53.81 0.0242947 +53.82 0.0223757 +53.83 0.019918 +53.84 0.0167576 +53.85 0.0127294 +53.86 0.00776904 +53.87 0.00178791 +53.88 -0.00527784 +53.89 -0.0134637 +53.9 -0.0227737 +53.91 -0.0331788 +53.92 -0.0446281 +53.93 -0.0570757 +53.94 -0.0702976 +53.95 -0.0841266 +53.96 -0.0983704 +53.97 -0.112816 +53.98 -0.127238 +53.99 -0.1414 +54 -0.155067 +54.01 -0.167923 +54.02 -0.179797 +54.03 -0.190504 +54.04 -0.19988 +54.05 -0.207793 +54.06 -0.214149 +54.07 -0.21889 +54.08 -0.222001 +54.09 -0.223396 +54.1 -0.223222 +54.11 -0.221631 +54.12 -0.218764 +54.13 -0.214786 +54.14 -0.209887 +54.15 -0.204271 +54.16 -0.198149 +54.17 -0.191734 +54.18 -0.185261 +54.19 -0.178942 +54.2 -0.172954 +54.21 -0.167457 +54.22 -0.162584 +54.23 -0.15844 +54.24 -0.155101 +54.25 -0.152643 +54.26 -0.151114 +54.27 -0.150439 +54.28 -0.150576 +54.29 -0.151461 +54.3 -0.153011 +54.31 -0.155125 +54.32 -0.15769 +54.33 -0.160586 +54.34 -0.163687 +54.35 -0.166835 +54.36 -0.169896 +54.37 -0.172743 +54.38 -0.175249 +54.39 -0.177294 +54.4 -0.178765 +54.41 -0.179554 +54.42 -0.179485 +54.43 -0.17852 +54.44 -0.176588 +54.45 -0.173616 +54.46 -0.169542 +54.47 -0.164315 +54.48 -0.157895 +54.49 -0.150255 +54.5 -0.141298 +54.51 -0.131075 +54.52 -0.119645 +54.53 -0.107052 +54.54 -0.0933551 +54.55 -0.0786356 +54.56 -0.0629952 +54.57 -0.0465561 +54.58 -0.0294367 +54.59 -0.0118196 +54.6 0.00608249 +54.61 0.0240762 +54.62 0.0419581 +54.63 0.0595191 +54.64 0.0765482 +54.65 0.0928373 +54.66 0.108155 +54.67 0.122243 +54.68 0.135003 +54.69 0.146289 +54.7 0.15598 +54.71 0.163985 +54.72 0.170237 +54.73 0.174706 +54.74 0.177375 +54.75 0.178131 +54.76 0.177198 +54.77 0.174663 +54.78 0.170636 +54.79 0.165243 +54.8 0.158624 +54.81 0.150929 +54.82 0.142312 +54.83 0.132876 +54.84 0.122838 +54.85 0.112352 +54.86 0.101546 +54.87 0.0905376 +54.88 0.0794299 +54.89 0.0683098 +54.9 0.0572495 +54.91 0.0463167 +54.92 0.0355554 +54.93 0.0249917 +54.94 0.0146471 +54.95 0.00453702 +54.96 -0.00532706 +54.97 -0.0149349 +54.98 -0.0242756 +54.99 -0.0333218 +55 -0.0420497 +55.01 -0.0504495 +55.02 -0.05849 +55.03 -0.0661342 +55.04 -0.0733395 +55.05 -0.0800585 +55.06 -0.0862406 +55.07 -0.0918174 +55.08 -0.0966979 +55.09 -0.100875 +55.1 -0.104302 +55.11 -0.106935 +55.12 -0.108737 +55.13 -0.109678 +55.14 -0.109735 +55.15 -0.108889 +55.16 -0.107049 +55.17 -0.104299 +55.18 -0.100646 +55.19 -0.0960963 +55.2 -0.0906633 +55.21 -0.08436 +55.22 -0.0772002 +55.23 -0.0691968 +55.24 -0.0602937 +55.25 -0.0505545 +55.26 -0.0399982 +55.27 -0.0286283 +55.28 -0.0164463 +55.29 -0.00345323 +55.3 0.0103489 +55.31 0.0249558 +55.32 0.0404061 +55.33 0.0566615 +55.34 0.0736656 +55.35 0.0913797 +55.36 0.109752 +55.37 0.128714 +55.38 0.148183 +55.39 0.168058 +55.4 0.188225 +55.41 0.208532 +55.42 0.228806 +55.43 0.248876 +55.44 0.268557 +55.45 0.287657 +55.46 0.305972 +55.47 0.323299 +55.48 0.339403 +55.49 0.353983 +55.5 0.366945 +55.51 0.37811 +55.52 0.387321 +55.53 0.394437 +55.54 0.399346 +55.55 0.40196 +55.56 0.40222 +55.57 0.39987 +55.58 0.395157 +55.59 0.388129 +55.6 0.37887 +55.61 0.367497 +55.62 0.354156 +55.63 0.339026 +55.64 0.322311 +55.65 0.304156 +55.66 0.284916 +55.67 0.264885 +55.68 0.244345 +55.69 0.223578 +55.7 0.202872 +55.71 0.182506 +55.72 0.162753 +55.73 0.143933 +55.74 0.126294 +55.75 0.110007 +55.76 0.0952497 +55.77 0.0821661 +55.78 0.070866 +55.79 0.0614214 +55.8 0.0538654 +55.81 0.0482606 +55.82 0.0445919 +55.83 0.0426531 +55.84 0.0423156 +55.85 0.0434182 +55.86 0.0457705 +55.87 0.0491576 +55.88 0.0533444 +55.89 0.0580862 +55.9 0.0631139 +55.91 0.0681248 +55.92 0.0728615 +55.93 0.0770762 +55.94 0.0805364 +55.95 0.0830297 +55.96 0.0843682 +55.97 0.0843922 +55.98 0.0828269 +55.99 0.079709 +56 0.0749917 +56.01 0.0686595 +56.02 0.0607322 +56.03 0.0512634 +56.04 0.0403389 +56.05 0.0280734 +56.06 0.0145303 +56.07 -4.48129e-05 +56.08 -0.0154397 +56.09 -0.0314565 +56.1 -0.0478896 +56.11 -0.0645294 +56.12 -0.0811671 +56.13 -0.0975988 +56.14 -0.113597 +56.15 -0.128962 +56.16 -0.143551 +56.17 -0.157218 +56.18 -0.169842 +56.19 -0.181321 +56.2 -0.191579 +56.21 -0.200564 +56.22 -0.208203 +56.23 -0.214455 +56.24 -0.219427 +56.25 -0.223164 +56.26 -0.225731 +56.27 -0.227208 +56.28 -0.227691 +56.29 -0.22729 +56.3 -0.226111 +56.31 -0.224247 +56.32 -0.221886 +56.33 -0.219153 +56.34 -0.21617 +56.35 -0.213049 +56.36 -0.209893 +56.37 -0.206795 +56.38 -0.203831 +56.39 -0.201082 +56.4 -0.198576 +56.41 -0.196333 +56.42 -0.194353 +56.43 -0.192621 +56.44 -0.191105 +56.45 -0.189757 +56.46 -0.188517 +56.47 -0.187311 +56.48 -0.186049 +56.49 -0.184649 +56.5 -0.183028 +56.51 -0.181107 +56.52 -0.178814 +56.53 -0.176086 +56.54 -0.172876 +56.55 -0.169119 +56.56 -0.164802 +56.57 -0.159952 +56.58 -0.154593 +56.59 -0.148763 +56.6 -0.142519 +56.61 -0.135933 +56.62 -0.129086 +56.63 -0.122069 +56.64 -0.114991 +56.65 -0.107962 +56.66 -0.101082 +56.67 -0.0944426 +56.68 -0.0881303 +56.69 -0.0822179 +56.7 -0.0767653 +56.71 -0.0718225 +56.72 -0.0674593 +56.73 -0.0636383 +56.74 -0.0603508 +56.75 -0.057573 +56.76 -0.0552686 +56.77 -0.0533904 +56.78 -0.051884 +56.79 -0.0506899 +56.8 -0.0497639 +56.81 -0.049022 +56.82 -0.0484081 +56.83 -0.0478757 +56.84 -0.0473872 +56.85 -0.0469155 +56.86 -0.0464439 +56.87 -0.0459664 +56.88 -0.0454869 +56.89 -0.0450196 +56.9 -0.0445828 +56.91 -0.0441978 +56.92 -0.0438853 +56.93 -0.0436628 +56.94 -0.0435419 +56.95 -0.0435249 +56.96 -0.0436065 +56.97 -0.0437605 +56.98 -0.043943 +56.99 -0.0441004 +57 -0.044166 +57.01 -0.0440608 +57.02 -0.0436966 +57.03 -0.0429787 +57.04 -0.0417943 +57.05 -0.0400136 +57.06 -0.0375817 +57.07 -0.0344225 +57.08 -0.0304762 +57.09 -0.0257028 +57.1 -0.0200858 +57.11 -0.0136348 +57.12 -0.00638167 +57.13 0.00165878 +57.14 0.0103143 +57.15 0.019458 +57.16 0.0289396 +57.17 0.0385893 +57.18 0.0482221 +57.19 0.0576439 +57.2 0.0666564 +57.21 0.0749962 +57.22 0.082505 +57.23 0.0890235 +57.24 0.0944061 +57.25 0.0985344 +57.26 0.101321 +57.27 0.102712 +57.28 0.102691 +57.29 0.10119 +57.3 0.098322 +57.31 0.0942318 +57.32 0.0890502 +57.33 0.0829356 +57.34 0.0760688 +57.35 0.0686483 +57.36 0.0608837 +57.37 0.0529944 +57.38 0.045222 +57.39 0.0377758 +57.4 0.0308455 +57.41 0.0246011 +57.42 0.0191888 +57.43 0.0147272 +57.44 0.0113049 +57.45 0.00900595 +57.46 0.00790831 +57.47 0.00791449 +57.48 0.00897733 +57.49 0.011021 +57.5 0.0139439 +57.51 0.0176221 +57.52 0.0219137 +57.53 0.0266638 +57.54 0.0317166 +57.55 0.036862 +57.56 0.0419322 +57.57 0.0467656 +57.58 0.0512109 +57.59 0.0551318 +57.6 0.0584097 +57.61 0.0609475 +57.62 0.0626006 +57.63 0.0633744 +57.64 0.0632676 +57.65 0.0622933 +57.66 0.0604921 +57.67 0.0579312 +57.68 0.0547033 +57.69 0.0509243 +57.7 0.0467153 +57.71 0.0422618 +57.72 0.0377499 +57.73 0.03336 +57.74 0.0292777 +57.75 0.0256887 +57.76 0.0227743 +57.77 0.0207068 +57.78 0.0196925 +57.79 0.0199044 +57.8 0.0213964 +57.81 0.0242536 +57.82 0.0285311 +57.83 0.0342519 +57.84 0.0414049 +57.85 0.0499442 +57.86 0.0598143 +57.87 0.0709504 +57.88 0.083099 +57.89 0.0960769 +57.9 0.109676 +57.91 0.123667 +57.92 0.137806 +57.93 0.151834 +57.94 0.165492 +57.95 0.17843 +57.96 0.190439 +57.97 0.201279 +57.98 0.210729 +57.99 0.218591 +58 0.224697 +58.01 0.228905 +58.02 0.231108 +58.03 0.231069 +58.04 0.228873 +58.05 0.224569 +58.06 0.218195 +58.07 0.209824 +58.08 0.199557 +58.09 0.187519 +58.1 0.173861 +58.11 0.158676 +58.12 0.142198 +58.13 0.124678 +58.14 0.106315 +58.15 0.0873107 +58.16 0.0678645 +58.17 0.0481703 +58.18 0.0284149 +58.19 0.00878597 +58.2 -0.010526 +58.21 -0.0293805 +58.22 -0.0476424 +58.23 -0.065191 +58.24 -0.0819202 +58.25 -0.0977381 +58.26 -0.112567 +58.27 -0.126324 +58.28 -0.138884 +58.29 -0.150299 +58.3 -0.160545 +58.31 -0.169612 +58.32 -0.1775 +58.33 -0.184219 +58.34 -0.189793 +58.35 -0.194251 +58.36 -0.197538 +58.37 -0.199809 +58.38 -0.201127 +58.39 -0.201558 +58.4 -0.201176 +58.41 -0.200058 +58.42 -0.198286 +58.43 -0.195942 +58.44 -0.193078 +58.45 -0.189809 +58.46 -0.18622 +58.47 -0.182382 +58.48 -0.178356 +58.49 -0.174196 +58.5 -0.169942 +58.51 -0.165627 +58.52 -0.161266 +58.53 -0.156866 +58.54 -0.15242 +58.55 -0.147911 +58.56 -0.143309 +58.57 -0.138578 +58.58 -0.133677 +58.59 -0.128558 +58.6 -0.123166 +58.61 -0.117438 +58.62 -0.111352 +58.63 -0.104875 +58.64 -0.0979808 +58.65 -0.0906562 +58.66 -0.0828996 +58.67 -0.0747226 +58.68 -0.0661457 +58.69 -0.0571897 +58.7 -0.0479431 +58.71 -0.0384737 +58.72 -0.0288598 +58.73 -0.0191881 +58.74 -0.00955127 +58.75 -4.56095e-05 +58.76 0.00923171 +58.77 0.0181471 +58.78 0.026631 +58.79 0.0346038 +58.8 0.0419937 +58.81 0.0487407 +58.82 0.0547979 +58.83 0.0601327 +58.84 0.064727 +58.85 0.0685252 +58.86 0.0715763 +58.87 0.0739278 +58.88 0.0756201 +58.89 0.076704 +58.9 0.077239 +58.91 0.0772911 +58.92 0.0769306 +58.93 0.0762164 +58.94 0.075232 +58.95 0.0740623 +58.96 0.0727723 +58.97 0.0714206 +58.98 0.0700588 +58.99 0.0687295 +59 0.0674662 +59.01 0.0662958 +59.02 0.0652365 +59.03 0.0642841 +59.04 0.0634316 +59.05 0.0626641 +59.06 0.0619596 +59.07 0.0612903 +59.08 0.060624 +59.09 0.0599246 +59.1 0.0591463 +59.11 0.0582561 +59.12 0.0572197 +59.13 0.0560061 +59.14 0.0545889 +59.15 0.0529478 +59.16 0.0510691 +59.17 0.0489467 +59.18 0.0465625 +59.19 0.043949 +59.2 0.0411301 +59.21 0.0381361 +59.22 0.0350055 +59.23 0.0317843 +59.24 0.0285253 +59.25 0.025287 +59.26 0.0221426 +59.27 0.0191627 +59.28 0.016412 +59.29 0.0139579 +59.3 0.0118654 +59.31 0.0101956 +59.32 0.00900439 +59.33 0.0083405 +59.34 0.00827327 +59.35 0.00883673 +59.36 0.0100193 +59.37 0.0118261 +59.38 0.0142495 +59.39 0.0172693 +59.4 0.0208523 +59.41 0.0249527 +59.42 0.0295237 +59.43 0.0345031 +59.44 0.0397778 +59.45 0.0452576 +59.46 0.0508453 +59.47 0.0564382 +59.48 0.0619301 +59.49 0.0672127 +59.5 0.0721746 +59.51 0.0766643 +59.52 0.080611 +59.53 0.0839198 +59.54 0.0865035 +59.55 0.0882839 +59.56 0.0891934 +59.57 0.0891763 +59.58 0.0881892 +59.59 0.0861153 +59.6 0.0830126 +59.61 0.0788952 +59.62 0.0737792 +59.63 0.0676948 +59.64 0.0606857 +59.65 0.0528089 +59.66 0.0441336 +59.67 0.0346996 +59.68 0.0246328 +59.69 0.0140575 +59.7 0.0030819 +59.71 -0.00818067 +59.72 -0.0196135 +59.73 -0.0310982 +59.74 -0.0425168 +59.75 -0.0537398 +59.76 -0.0646391 +59.77 -0.075121 +59.78 -0.0850889 +59.79 -0.0944562 +59.8 -0.103147 +59.81 -0.111096 +59.82 -0.118253 +59.83 -0.124558 +59.84 -0.129944 +59.85 -0.134463 +59.86 -0.138121 +59.87 -0.140935 +59.88 -0.142931 +59.89 -0.144147 +59.9 -0.144629 +59.91 -0.144425 +59.92 -0.14354 +59.93 -0.142097 +59.94 -0.140154 +59.95 -0.137766 +59.96 -0.134988 +59.97 -0.131868 +59.98 -0.128448 +59.99 -0.124762 +60 -0.120822 +60.01 -0.116663 +60.02 -0.1123 +60.03 -0.107739 +60.04 -0.102978 +60.05 -0.0980114 +60.06 -0.0928318 +60.07 -0.0874298 +60.08 -0.0817825 +60.09 -0.0758896 +60.1 -0.0697593 +60.11 -0.0633975 +60.12 -0.056818 +60.13 -0.0500432 +60.14 -0.043105 +60.15 -0.0360454 +60.16 -0.0289159 +60.17 -0.021787 +60.18 -0.0147339 +60.19 -0.00783584 +60.2 -0.00117722 +60.21 0.00515397 +60.22 0.0110685 +60.23 0.016478 +60.24 0.0212836 +60.25 0.0253616 +60.26 0.0286904 +60.27 0.0312135 +60.28 0.0328878 +60.29 0.0336852 +60.3 0.0335942 +60.31 0.0326206 +60.32 0.0307873 +60.33 0.0280644 +60.34 0.0245986 +60.35 0.0204713 +60.36 0.015779 +60.37 0.0106322 +60.38 0.00515321 +60.39 -0.000526363 +60.4 -0.00626819 +60.41 -0.0119122 +60.42 -0.0173062 +60.43 -0.0223122 +60.44 -0.0267967 +60.45 -0.0306353 +60.46 -0.0337156 +60.47 -0.0359399 +60.48 -0.0372272 +60.49 -0.0374545 +60.5 -0.0365978 +60.51 -0.0346828 +60.52 -0.0317181 +60.53 -0.0277353 +60.54 -0.0227884 +60.55 -0.0169528 +60.56 -0.0103244 +60.57 -0.00299672 +60.58 0.00489347 +60.59 0.0131601 +60.6 0.0216468 +60.61 0.0301911 +60.62 0.0386278 +60.63 0.0467923 +60.64 0.0545243 +60.65 0.0616585 +60.66 0.0679947 +60.67 0.0734591 +60.68 0.0779465 +60.69 0.0813719 +60.7 0.083672 +60.71 0.0848061 +60.72 0.0847571 +60.73 0.0835316 +60.74 0.081054 +60.75 0.0774912 +60.76 0.0729223 +60.77 0.0674417 +60.78 0.0611594 +60.79 0.0541977 +60.8 0.0466889 +60.81 0.0387718 +60.82 0.0305798 +60.83 0.0222846 +60.84 0.0140315 +60.85 0.00595471 +60.86 -0.00181943 +60.87 -0.00917422 +60.88 -0.0160041 +60.89 -0.022216 +60.9 -0.0276875 +60.91 -0.0323543 +60.92 -0.0361973 +60.93 -0.0391803 +60.94 -0.04128 +60.95 -0.0424856 +60.96 -0.0427976 +60.97 -0.0422271 +60.98 -0.0407663 +60.99 -0.038427 +61 -0.0353039 +61.01 -0.0314453 +61.02 -0.0269064 +61.03 -0.0217493 +61.04 -0.0160421 +61.05 -0.00985901 +61.06 -0.00327519 +61.07 0.00363619 +61.08 0.0107527 +61.09 0.01798 +61.1 0.0252206 +61.11 0.032375 +61.12 0.0393426 +61.13 0.0460234 +61.14 0.0523194 +61.15 0.0580837 +61.16 0.0632625 +61.17 0.0677792 +61.18 0.0715641 +61.19 0.0745591 +61.2 0.0767188 +61.21 0.0780127 +61.22 0.0784254 +61.23 0.0778955 +61.24 0.0764856 +61.25 0.0742561 +61.26 0.0712602 +61.27 0.0675654 +61.28 0.063252 +61.29 0.0584114 +61.3 0.0531437 +61.31 0.0475443 +61.32 0.0417398 +61.33 0.0358547 +61.34 0.0299974 +61.35 0.0242713 +61.36 0.0187726 +61.37 0.0135888 +61.38 0.00879665 +61.39 0.00447779 +61.4 0.000707572 +61.41 -0.00250755 +61.42 -0.00514242 +61.43 -0.0071834 +61.44 -0.00862729 +61.45 -0.00948006 +61.46 -0.00975532 +61.47 -0.00946666 +61.48 -0.00860085 +61.49 -0.00723316 +61.5 -0.00539176 +61.51 -0.00310458 +61.52 -0.00039834 +61.53 0.00270215 +61.54 0.0061743 +61.55 0.00999784 +61.56 0.0141828 +61.57 0.0186844 +61.58 0.023484 +61.59 0.0285658 +61.6 0.0339127 +61.61 0.0395055 +61.62 0.0453218 +61.63 0.0513355 +61.64 0.057525 +61.65 0.0638436 +61.66 0.0702445 +61.67 0.0766811 +61.68 0.083102 +61.69 0.0894519 +61.7 0.0956725 +61.71 0.101703 +61.72 0.107467 +61.73 0.112894 +61.74 0.117938 +61.75 0.122541 +61.76 0.126651 +61.77 0.130218 +61.78 0.1332 +61.79 0.13556 +61.8 0.137246 +61.81 0.138204 +61.82 0.138463 +61.83 0.138013 +61.84 0.136848 +61.85 0.13497 +61.86 0.132383 +61.87 0.129098 +61.88 0.125124 +61.89 0.120428 +61.9 0.115093 +61.91 0.109146 +61.92 0.102617 +61.93 0.095539 +61.94 0.0879494 +61.95 0.0798888 +61.96 0.0714015 +61.97 0.0625078 +61.98 0.0532934 +61.99 0.0438196 +62 0.0341464 +62.01 0.0243368 +62.02 0.0144561 +62.03 0.00457107 +62.04 -0.00525125 +62.05 -0.0149314 +62.06 -0.024402 +62.07 -0.0336067 +62.08 -0.0424895 +62.09 -0.0509999 +62.1 -0.0590942 +62.11 -0.0667363 +62.12 -0.0738982 +62.13 -0.0805369 +62.14 -0.0866371 +62.15 -0.0922239 +62.16 -0.0973031 +62.17 -0.101887 +62.18 -0.105993 +62.19 -0.109641 +62.2 -0.112854 +62.21 -0.115646 +62.22 -0.118023 +62.23 -0.120032 +62.24 -0.121687 +62.25 -0.122998 +62.26 -0.123966 +62.27 -0.124588 +62.28 -0.124853 +62.29 -0.124743 +62.3 -0.124199 +62.31 -0.12323 +62.32 -0.121805 +62.33 -0.119896 +62.34 -0.117474 +62.35 -0.114514 +62.36 -0.110996 +62.37 -0.106906 +62.38 -0.102194 +62.39 -0.0969059 +62.4 -0.0910695 +62.41 -0.0847165 +62.42 -0.0778905 +62.43 -0.0706473 +62.44 -0.0630538 +62.45 -0.0551879 +62.46 -0.0471311 +62.47 -0.0389954 +62.48 -0.0308891 +62.49 -0.0229176 +62.5 -0.0151881 +62.51 -0.0078071 +62.52 -0.000878758 +62.53 0.00549712 +62.54 0.0111959 +62.55 0.0161112 +62.56 0.0202051 +62.57 0.0234132 +62.58 0.0256821 +62.59 0.0269704 +62.6 0.0272488 +62.61 0.0265005 +62.62 0.0246997 +62.63 0.0218017 +62.64 0.01791 +62.65 0.0130616 +62.66 0.00730618 +62.67 0.00070508 +62.68 -0.00666863 +62.69 -0.0147309 +62.7 -0.0233878 +62.71 -0.0325707 +62.72 -0.0421091 +62.73 -0.051881 +62.74 -0.061759 +62.75 -0.0716112 +62.76 -0.081304 +62.77 -0.0907036 +62.78 -0.0996783 +62.79 -0.108048 +62.8 -0.115716 +62.81 -0.122585 +62.82 -0.128561 +62.83 -0.133565 +62.84 -0.137535 +62.85 -0.140427 +62.86 -0.142217 +62.87 -0.142838 +62.88 -0.142333 +62.89 -0.14079 +62.9 -0.138267 +62.91 -0.134845 +62.92 -0.130615 +62.93 -0.125682 +62.94 -0.120159 +62.95 -0.114152 +62.96 -0.107787 +62.97 -0.101209 +62.98 -0.0945292 +62.99 -0.0878474 +63 -0.0812528 +63.01 -0.0748194 +63.02 -0.0686054 +63.03 -0.0626571 +63.04 -0.0570162 +63.05 -0.0516646 +63.06 -0.0465902 +63.07 -0.0417676 +63.08 -0.0371608 +63.09 -0.0327258 +63.1 -0.0284142 +63.11 -0.0241761 +63.12 -0.0199618 +63.13 -0.015724 +63.14 -0.0114301 +63.15 -0.00705856 +63.16 -0.00260039 +63.17 0.00193975 +63.18 0.00654314 +63.19 0.0111773 +63.2 0.0157916 +63.21 0.020323 +63.22 0.0247048 +63.23 0.0288644 +63.24 0.0327264 +63.25 0.0362163 +63.26 0.0392635 +63.27 0.0418041 +63.28 0.0437508 +63.29 0.0450666 +63.3 0.0457525 +63.31 0.0458022 +63.32 0.0452264 +63.33 0.0440534 +63.34 0.0423284 +63.35 0.0401125 +63.36 0.0374701 +63.37 0.0344925 +63.38 0.0313052 +63.39 0.0280151 +63.4 0.0247331 +63.41 0.0215711 +63.42 0.018639 +63.43 0.0160427 +63.44 0.0138892 +63.45 0.0123124 +63.46 0.0113516 +63.47 0.0110703 +63.48 0.011519 +63.49 0.0127337 +63.5 0.0147357 +63.51 0.0175321 +63.52 0.0211152 +63.53 0.0255312 +63.54 0.0306729 +63.55 0.0364855 +63.56 0.0429065 +63.57 0.0498626 +63.58 0.0572704 +63.59 0.0650378 +63.6 0.0730649 +63.61 0.0812476 +63.62 0.0894541 +63.63 0.0975637 +63.64 0.105457 +63.65 0.113016 +63.66 0.12012 +63.67 0.126655 +63.68 0.132512 +63.69 0.137541 +63.7 0.141645 +63.71 0.144782 +63.72 0.146889 +63.73 0.147919 +63.74 0.147842 +63.75 0.146648 +63.76 0.144347 +63.77 0.140937 +63.78 0.136442 +63.79 0.131008 +63.8 0.124727 +63.81 0.117709 +63.82 0.110077 +63.83 0.101962 +63.84 0.0935074 +63.85 0.0848561 +63.86 0.0761645 +63.87 0.0675877 +63.88 0.0592575 +63.89 0.0512946 +63.9 0.0438054 +63.91 0.0368795 +63.92 0.030588 +63.93 0.0249819 +63.94 0.0201573 +63.95 0.0160641 +63.96 0.0126827 +63.97 0.00998221 +63.98 0.00791604 +63.99 0.00642489 +64 0.00543949 +64.01 0.00488366 +64.02 0.00469593 +64.03 0.00477309 +64.04 0.00502826 +64.05 0.00538998 +64.06 0.00579454 +64.07 0.00618754 +64.08 0.00652502 +64.09 0.00677402 +64.1 0.00690654 +64.11 0.00691087 +64.12 0.00679265 +64.13 0.00655971 +64.14 0.00622591 +64.15 0.00580926 +64.16 0.00533001 +64.17 0.00480879 +64.18 0.00426469 +64.19 0.00371522 +64.2 0.00317279 +64.21 0.00264322 +64.22 0.00212674 +64.23 0.00161796 +64.24 0.00110619 +64.25 0.000576124 +64.26 8.23979e-06 +64.27 -0.000626223 +64.28 -0.00134662 +64.29 -0.00217556 +64.3 -0.00313401 +64.31 -0.00424017 +64.32 -0.00550845 +64.33 -0.00694879 +64.34 -0.00856619 +64.35 -0.0103752 +64.36 -0.0123562 +64.37 -0.0144961 +64.38 -0.0167798 +64.39 -0.0191893 +64.4 -0.0217044 +64.41 -0.0243037 +64.42 -0.0269654 +64.43 -0.0296702 +64.44 -0.0323953 +64.45 -0.0351229 +64.46 -0.0378392 +64.47 -0.0405331 +64.48 -0.0431965 +64.49 -0.0458238 +64.5 -0.0484112 +64.51 -0.0509547 +64.52 -0.0534526 +64.53 -0.0559054 +64.54 -0.0583109 +64.55 -0.0606651 +64.56 -0.0629616 +64.57 -0.065192 +64.58 -0.0673449 +64.59 -0.0694035 +64.6 -0.0713441 +64.61 -0.0731548 +64.62 -0.0748155 +64.63 -0.0763061 +64.64 -0.0776066 +64.65 -0.0786989 +64.66 -0.0795669 +64.67 -0.0801965 +64.68 -0.0805567 +64.69 -0.0806661 +64.7 -0.0805259 +64.71 -0.0801416 +64.72 -0.0795234 +64.73 -0.078685 +64.74 -0.0776432 +64.75 -0.076417 +64.76 -0.0750144 +64.77 -0.0734679 +64.78 -0.0717979 +64.79 -0.0700194 +64.8 -0.0681438 +64.81 -0.0661776 +64.82 -0.0641224 +64.83 -0.0619742 +64.84 -0.0597159 +64.85 -0.0573333 +64.86 -0.0548097 +64.87 -0.0521214 +64.88 -0.0492423 +64.89 -0.0461457 +64.9 -0.0428053 +64.91 -0.0391972 +64.92 -0.0352882 +64.93 -0.0310581 +64.94 -0.0265161 +64.95 -0.0216635 +64.96 -0.0165096 +64.97 -0.011073 +64.98 -0.00538136 +64.99 0.00052911 +65 0.00661641 +65.01 0.0128287 +65.02 0.0190939 +65.03 0.0253484 +65.04 0.0315259 +65.05 0.0375588 +65.06 0.0433802 +65.07 0.0489255 +65.08 0.0541331 +65.09 0.0589052 +65.1 0.0632279 +65.11 0.067061 +65.12 0.070373 +65.13 0.0731405 +65.14 0.0753494 +65.15 0.0769936 +65.16 0.0780759 +65.17 0.078564 +65.18 0.0785118 +65.19 0.0779546 +65.2 0.0769226 +65.21 0.0754506 +65.22 0.0735766 +65.23 0.071341 +65.24 0.0687864 +65.25 0.0659421 +65.26 0.0628626 +65.27 0.0596013 +65.28 0.0562001 +65.29 0.0526994 +65.3 0.0491375 +65.31 0.0455506 +65.32 0.0419719 +65.33 0.0384346 +65.34 0.0349699 +65.35 0.0315977 +65.36 0.0283366 +65.37 0.0252008 +65.38 0.0222004 +65.39 0.0193407 +65.4 0.0166227 +65.41 0.0140452 +65.42 0.0116067 +65.43 0.00928359 +65.44 0.00705937 +65.45 0.00491476 +65.46 0.00282828 +65.47 0.000777032 +65.48 -0.00126237 +65.49 -0.00331284 +65.5 -0.00540084 +65.51 -0.00754332 +65.52 -0.0097549 +65.53 -0.0120459 +65.54 -0.0144216 +65.55 -0.0168818 +65.56 -0.0194205 +65.57 -0.0220258 +65.58 -0.0246823 +65.59 -0.0273617 +65.6 -0.0300347 +65.61 -0.0326697 +65.62 -0.0352327 +65.63 -0.0376887 +65.64 -0.0400023 +65.65 -0.0421398 +65.66 -0.0440564 +65.67 -0.0457244 +65.68 -0.0471306 +65.69 -0.0482587 +65.7 -0.0490981 +65.71 -0.0496446 +65.72 -0.0499001 +65.73 -0.0498726 +65.74 -0.0495665 +65.75 -0.0489956 +65.76 -0.048199 +65.77 -0.0472015 +65.78 -0.046029 +65.79 -0.0447074 +65.8 -0.0432615 +65.81 -0.0417142 +65.82 -0.0400841 +65.83 -0.038384 +65.84 -0.0366314 +65.85 -0.0348326 +65.86 -0.0329894 +65.87 -0.0311 +65.88 -0.0291587 +65.89 -0.0271572 +65.9 -0.025085 +65.91 -0.0229223 +65.92 -0.020664 +65.93 -0.0183008 +65.94 -0.0158253 +65.95 -0.0132331 +65.96 -0.0105236 +65.97 -0.00770073 +65.98 -0.00477315 +65.99 -0.00174903 +66 0.0013463 +66.01 0.00448671 +66.02 0.00764434 +66.03 0.010788 +66.04 0.0138841 +66.05 0.0168969 +66.06 0.01979 +66.07 0.0225167 +66.08 0.0250379 +66.09 0.0273271 +66.1 0.0293528 +66.11 0.0310864 +66.12 0.0325025 +66.13 0.0335794 +66.14 0.0342992 +66.15 0.0346354 +66.16 0.0345645 +66.17 0.0341057 +66.18 0.0332574 +66.19 0.032022 +66.2 0.0304054 +66.21 0.0284171 +66.22 0.0260703 +66.23 0.0233777 +66.24 0.0203383 +66.25 0.0170062 +66.26 0.0134096 +66.27 0.00958072 +66.28 0.00555517 +66.29 0.00137214 +66.3 -0.00292591 +66.31 -0.00729359 +66.32 -0.0116804 +66.33 -0.0160299 +66.34 -0.0202907 +66.35 -0.0244113 +66.36 -0.0283407 +66.37 -0.0320299 +66.38 -0.0354324 +66.39 -0.0385055 +66.4 -0.0411835 +66.41 -0.0434464 +66.42 -0.0452806 +66.43 -0.0466686 +66.44 -0.0476006 +66.45 -0.0480741 +66.46 -0.0480938 +66.47 -0.0476714 +66.48 -0.046805 +66.49 -0.0455217 +66.5 -0.0438702 +66.51 -0.0418816 +66.52 -0.0395885 +66.53 -0.0370233 +66.54 -0.0342178 +66.55 -0.0312012 +66.56 -0.0279949 +66.57 -0.0246165 +66.58 -0.0210948 +66.59 -0.0174411 +66.6 -0.0136621 +66.61 -0.00975998 +66.62 -0.00573311 +66.63 -0.00157705 +66.64 0.00271571 +66.65 0.00716409 +66.66 0.0117616 +66.67 0.0165115 +66.68 0.0214132 +66.69 0.0264615 +66.7 0.0316454 +66.71 0.0369472 +66.72 0.0423426 +66.73 0.047802 +66.74 0.053278 +66.75 0.0587237 +66.76 0.064088 +66.77 0.0693156 +66.78 0.0743481 +66.79 0.0791265 +66.8 0.0835914 +66.81 0.0876578 +66.82 0.0912794 +66.83 0.0944211 +66.84 0.0970423 +66.85 0.0991102 +66.86 0.1006 +66.87 0.101497 +66.88 0.101793 +66.89 0.101464 +66.9 0.10052 +66.91 0.0990111 +66.92 0.0969647 +66.93 0.094413 +66.94 0.0913928 +66.95 0.0879439 +66.96 0.0841083 +66.97 0.0799214 +66.98 0.0754165 +66.99 0.0706597 +67 0.0656904 +67.01 0.0605462 +67.02 0.0552621 +67.03 0.0498706 +67.04 0.0444016 +67.05 0.0388833 +67.06 0.0333422 +67.07 0.0278072 +67.08 0.0223022 +67.09 0.0168509 +67.1 0.0114769 +67.11 0.00620431 +67.12 0.00105758 +67.13 -0.00393829 +67.14 -0.00874113 +67.15 -0.0133351 +67.16 -0.017698 +67.17 -0.0218056 +67.18 -0.0256356 +67.19 -0.0291678 +67.2 -0.0323847 +67.21 -0.0352726 +67.22 -0.0377998 +67.23 -0.0399705 +67.24 -0.0417971 +67.25 -0.043285 +67.26 -0.0444444 +67.27 -0.0452901 +67.28 -0.0458412 +67.29 -0.0461201 +67.3 -0.046143 +67.31 -0.0459383 +67.32 -0.0455484 +67.33 -0.0450022 +67.34 -0.0443278 +67.35 -0.0435515 +67.36 -0.0426976 +67.37 -0.0417869 +67.38 -0.0408364 +67.39 -0.0398594 +67.4 -0.0388674 +67.41 -0.0378649 +67.42 -0.0368528 +67.43 -0.0358283 +67.44 -0.0347856 +67.45 -0.0337165 +67.46 -0.0326106 +67.47 -0.0314507 +67.48 -0.0302283 +67.49 -0.0289312 +67.5 -0.0275479 +67.51 -0.0260678 +67.52 -0.0244821 +67.53 -0.0227834 +67.54 -0.0209664 +67.55 -0.0190181 +67.56 -0.0169447 +67.57 -0.0147498 +67.58 -0.0124363 +67.59 -0.0100088 +67.6 -0.00747376 +67.61 -0.00483867 +67.62 -0.00211246 +67.63 0.000698904 +67.64 0.00358093 +67.65 0.00651789 +67.66 0.00949588 +67.67 0.0124997 +67.68 0.0155127 +67.69 0.0185167 +67.7 0.0214917 +67.71 0.0244132 +67.72 0.0272529 +67.73 0.0299881 +67.74 0.0325914 +67.75 0.0350337 +67.76 0.0372849 +67.77 0.0393138 +67.78 0.0410892 +67.79 0.0425743 +67.8 0.0437169 +67.81 0.0445124 +67.82 0.044936 +67.83 0.0449661 +67.84 0.044586 +67.85 0.0437835 +67.86 0.0425524 +67.87 0.0408927 +67.88 0.0387712 +67.89 0.0362441 +67.9 0.0333342 +67.91 0.0300706 +67.92 0.026489 +67.93 0.0226314 +67.94 0.0185453 +67.95 0.0142831 +67.96 0.00989651 +67.97 0.00545785 +67.98 0.00103088 +67.99 -0.00332269 +68 -0.00754196 +68.01 -0.011568 +68.02 -0.0153451 +68.03 -0.0188217 +68.04 -0.0219297 +68.05 -0.0246301 +68.06 -0.0269064 +68.07 -0.0287351 +68.08 -0.0301014 +68.09 -0.0309987 +68.1 -0.0314293 +68.11 -0.0314044 +68.12 -0.0309291 +68.13 -0.030025 +68.14 -0.0287562 +68.15 -0.027167 +68.16 -0.0253073 +68.17 -0.0232322 +68.18 -0.0210005 +68.19 -0.0186739 +68.2 -0.0163161 +68.21 -0.0139992 +68.22 -0.0117866 +68.23 -0.0097374 +68.24 -0.00790655 +68.25 -0.006344 +68.26 -0.00509355 +68.27 -0.00419196 +68.28 -0.0036682 +68.29 -0.00358113 +68.3 -0.00390662 +68.31 -0.00464343 +68.32 -0.00578327 +68.33 -0.00730928 +68.34 -0.00919646 +68.35 -0.0114125 +68.36 -0.0139183 +68.37 -0.016684 +68.38 -0.0196432 +68.39 -0.0227374 +68.4 -0.0259129 +68.41 -0.0291157 +68.42 -0.0322927 +68.43 -0.0353935 +68.44 -0.0383708 +68.45 -0.0411707 +68.46 -0.0437526 +68.47 -0.0460937 +68.48 -0.0481694 +68.49 -0.0499614 +68.5 -0.0514576 +68.51 -0.052652 +68.52 -0.0535437 +68.53 -0.0541275 +68.54 -0.0544037 +68.55 -0.054404 +68.56 -0.054143 +68.57 -0.0536366 +68.58 -0.0529012 +68.59 -0.0519531 +68.6 -0.0508075 +68.61 -0.0494763 +68.62 -0.0479596 +68.63 -0.0462824 +68.64 -0.0444515 +68.65 -0.0424718 +68.66 -0.0403462 +68.67 -0.0380759 +68.68 -0.0356613 +68.69 -0.0331021 +68.7 -0.0303851 +68.71 -0.0275231 +68.72 -0.0245197 +68.73 -0.0213795 +68.74 -0.0181096 +68.75 -0.0147202 +68.76 -0.0112249 +68.77 -0.00764066 +68.78 -0.00398493 +68.79 -0.000287568 +68.8 0.00342184 +68.81 0.00711295 +68.82 0.0107534 +68.83 0.0143092 +68.84 0.0177459 +68.85 0.0210287 +68.86 0.0241128 +68.87 0.0269618 +68.88 0.0295557 +68.89 0.031868 +68.9 0.033876 +68.91 0.0355611 +68.92 0.0369094 +68.93 0.0379118 +68.94 0.0385538 +68.95 0.0388235 +68.96 0.038752 +68.97 0.0383511 +68.98 0.0376369 +68.99 0.0366296 +69 0.0353527 +69.01 0.0338329 +69.02 0.0320972 +69.03 0.0301647 +69.04 0.0280859 +69.05 0.0258925 +69.06 0.0236163 +69.07 0.0212876 +69.08 0.0189357 +69.09 0.0165876 +69.1 0.0142681 +69.11 0.0120051 +69.12 0.00981525 +69.13 0.00771293 +69.14 0.00571004 +69.15 0.00381521 +69.16 0.002034 +69.17 0.000369005 +69.18 -0.00117985 +69.19 -0.00260777 +69.2 -0.00392396 +69.21 -0.00513836 +69.22 -0.00625839 +69.23 -0.00729211 +69.24 -0.00824769 +69.25 -0.00913309 +69.26 -0.00995561 +69.27 -0.0107192 +69.28 -0.011429 +69.29 -0.0120911 +69.3 -0.0127069 +69.31 -0.0132762 +69.32 -0.013797 +69.33 -0.0142659 +69.34 -0.014678 +69.35 -0.0150255 +69.36 -0.0152977 +69.37 -0.0154921 +69.38 -0.0156016 +69.39 -0.0156198 +69.4 -0.0155411 +69.41 -0.0153615 +69.42 -0.0150783 +69.43 -0.0146906 +69.44 -0.0141916 +69.45 -0.0135956 +69.46 -0.0129096 +69.47 -0.0121433 +69.48 -0.0113082 +69.49 -0.0104183 +69.5 -0.00948878 +69.51 -0.00853656 +69.52 -0.00758039 +69.53 -0.00664045 +69.54 -0.00573469 +69.55 -0.00488031 +69.56 -0.00409332 +69.57 -0.00338806 +69.58 -0.00277683 +69.59 -0.00226952 +69.6 -0.00188066 +69.61 -0.0016115 +69.62 -0.00145874 +69.63 -0.00141997 +69.64 -0.00148988 +69.65 -0.00166033 +69.66 -0.00192069 +69.67 -0.00225804 +69.68 -0.00265948 +69.69 -0.00310692 +69.7 -0.00357935 +69.71 -0.00405846 +69.72 -0.00452574 +69.73 -0.00496292 +69.74 -0.00535229 +69.75 -0.00567712 +69.76 -0.00591971 +69.77 -0.0060597 +69.78 -0.00609147 +69.79 -0.00600507 +69.8 -0.00579258 +69.81 -0.00544814 +69.82 -0.00496794 +69.83 -0.00435017 +69.84 -0.00359465 +69.85 -0.00269104 +69.86 -0.00165639 +69.87 -0.000496054 +69.88 0.000783439 +69.89 0.00217461 +69.9 0.00366924 +69.91 0.00525865 +69.92 0.00693392 +69.93 0.00869153 +69.94 0.0105171 +69.95 0.0124011 +69.96 0.0143358 +69.97 0.0163141 +69.98 0.0183297 +69.99 0.0203771 +70 0.0224512 +70.01 0.0245488 +70.02 0.0266649 +70.03 0.0287948 +70.04 0.0309346 +70.05 0.0330798 +70.06 0.0352252 +70.07 0.0373641 +70.08 0.0394888 +70.09 0.041588 +70.1 0.0436475 +70.11 0.0456539 +70.12 0.0475901 +70.13 0.0494365 +70.14 0.0511709 +70.15 0.0527693 +70.16 0.0542055 +70.17 0.055448 +70.18 0.0564525 +70.19 0.0572062 +70.2 0.0576818 +70.21 0.0578542 +70.22 0.0577004 +70.23 0.0572017 +70.24 0.0563432 +70.25 0.0551159 +70.26 0.0534806 +70.27 0.0514779 +70.28 0.0491202 +70.29 0.0464268 +70.3 0.0434241 +70.31 0.0401455 +70.32 0.0366307 +70.33 0.0329251 +70.34 0.0290721 +70.35 0.0251392 +70.36 0.0211857 +70.37 0.0172686 +70.38 0.0134439 +70.39 0.00976534 +70.4 0.00628302 +70.41 0.00304186 +70.42 9.82377e-05 +70.43 -0.00251784 +70.44 -0.00479685 +70.45 -0.00672614 +70.46 -0.00830192 +70.47 -0.00952916 +70.48 -0.0104213 +70.49 -0.0109994 +70.5 -0.0112826 +70.51 -0.0113033 +70.52 -0.0111182 +70.53 -0.0107696 +70.54 -0.0103014 +70.55 -0.00975724 +70.56 -0.00917947 +70.57 -0.00860776 +70.58 -0.00807918 +70.59 -0.0076324 +70.6 -0.00728759 +70.61 -0.0070627 +70.62 -0.00696934 +70.63 -0.00701286 +70.64 -0.00719259 +70.65 -0.00750239 +70.66 -0.0079313 +70.67 -0.00847249 +70.68 -0.00909685 +70.69 -0.00978379 +70.7 -0.0105126 +70.71 -0.0112628 +70.72 -0.0120155 +70.73 -0.0127537 +70.74 -0.0134637 +70.75 -0.0141315 +70.76 -0.0147522 +70.77 -0.015325 +70.78 -0.015852 +70.79 -0.0163391 +70.8 -0.0167955 +70.81 -0.0172328 +70.82 -0.017665 +70.83 -0.0181089 +70.84 -0.0185824 +70.85 -0.0191003 +70.86 -0.0196771 +70.87 -0.0203252 +70.88 -0.0210547 +70.89 -0.0218726 +70.9 -0.0227826 +70.91 -0.0237876 +70.92 -0.0248855 +70.93 -0.026062 +70.94 -0.0273049 +70.95 -0.0285986 +70.96 -0.0299247 +70.97 -0.0312621 +70.98 -0.0325878 +70.99 -0.0338766 +71 -0.0350959 +71.01 -0.0362236 +71.02 -0.0372348 +71.03 -0.0381056 +71.04 -0.0388137 +71.05 -0.0393389 +71.06 -0.0396635 +71.07 -0.0397726 +71.08 -0.0396334 +71.09 -0.039256 +71.1 -0.0386385 +71.11 -0.0377806 +71.12 -0.0366858 +71.13 -0.0353609 +71.14 -0.0338168 +71.15 -0.0320671 +71.16 -0.0301173 +71.17 -0.0279979 +71.18 -0.0257372 +71.19 -0.0233605 +71.2 -0.020895 +71.21 -0.018369 +71.22 -0.0158119 +71.23 -0.0132538 +71.24 -0.0107273 +71.25 -0.0082656 +71.26 -0.0058953 +71.27 -0.00364298 +71.28 -0.00153327 +71.29 0.000411555 +71.3 0.00217193 +71.31 0.00373129 +71.32 0.00506968 +71.33 0.00616777 +71.34 0.00703484 +71.35 0.00766893 +71.36 0.00807152 +71.37 0.00824745 +71.38 0.00820464 +71.39 0.00795389 +71.4 0.00750686 +71.41 0.00686666 +71.42 0.00606803 +71.43 0.00512995 +71.44 0.0040723 +71.45 0.00291547 +71.46 0.00168005 +71.47 0.000386428 +71.48 -0.00094541 +71.49 -0.00229621 +71.5 -0.00364459 +71.51 -0.00497324 +71.52 -0.0062662 +71.53 -0.00750867 +71.54 -0.0086871 +71.55 -0.00978915 +71.56 -0.0108037 +71.57 -0.0117137 +71.58 -0.0125131 +71.59 -0.0131977 +71.6 -0.0137613 +71.61 -0.0141993 +71.62 -0.0145081 +71.63 -0.0146856 +71.64 -0.014731 +71.65 -0.0146395 +71.66 -0.0144133 +71.67 -0.0140642 +71.68 -0.0135986 +71.69 -0.013025 +71.7 -0.0123536 +71.71 -0.0115963 +71.72 -0.0107665 +71.73 -0.00987796 +71.74 -0.00894645 +71.75 -0.00799246 +71.76 -0.00703262 +71.77 -0.00608314 +71.78 -0.00515936 +71.79 -0.00427512 +71.8 -0.00344232 +71.81 -0.00267075 +71.82 -0.00197292 +71.83 -0.00134527 +71.84 -0.000786244 +71.85 -0.000290378 +71.86 0.000151741 +71.87 0.000553374 +71.88 0.000931395 +71.89 0.00130591 +71.9 0.00170348 +71.91 0.00215026 +71.92 0.00267231 +71.93 0.0032962 +71.94 0.00404781 +71.95 0.00495135 +71.96 0.00602844 +71.97 0.00729721 +71.98 0.00878505 +71.99 0.0104949 +72 0.0124211 +72.01 0.0145589 +72.02 0.0168974 +72.03 0.0194187 +72.04 0.0220991 +72.05 0.0249087 +72.06 0.0278144 +72.07 0.0307714 +72.08 0.0337302 +72.09 0.0366441 +72.1 0.0394646 +72.11 0.0421434 +72.12 0.044633 +72.13 0.0468879 +72.14 0.048859 +72.15 0.0504882 +72.16 0.0517653 +72.17 0.0526649 +72.18 0.0531681 +72.19 0.0532626 +72.2 0.0529431 +72.21 0.0522108 +72.22 0.0510724 +72.23 0.0495074 +72.24 0.0475745 +72.25 0.0452984 +72.26 0.0427075 +72.27 0.0398328 +72.28 0.0367073 +72.29 0.0333648 +72.3 0.0298396 +72.31 0.0261561 +72.32 0.0223593 +72.33 0.0184823 +72.34 0.0145541 +72.35 0.0106014 +72.36 0.00664893 +72.37 0.00271971 +72.38 -0.00116492 +72.39 -0.00497979 +72.4 -0.00870482 +72.41 -0.0123237 +72.42 -0.0158176 +72.43 -0.0191669 +72.44 -0.0223514 +72.45 -0.0253499 +72.46 -0.0281399 +72.47 -0.0306878 +72.48 -0.0329617 +72.49 -0.0349506 +72.5 -0.0366301 +72.51 -0.0379765 +72.52 -0.0389679 +72.53 -0.0395848 +72.54 -0.0398109 +72.55 -0.0396261 +72.56 -0.0389988 +72.57 -0.0379605 +72.58 -0.0365175 +72.59 -0.0346823 +72.6 -0.0324747 +72.61 -0.0299211 +72.62 -0.0270543 +72.63 -0.0239132 +72.64 -0.0205246 +72.65 -0.0169639 +72.66 -0.0132839 +72.67 -0.00953912 +72.68 -0.00578444 +72.69 -0.00207395 +72.7 0.00154045 +72.71 0.00501033 +72.72 0.00827433 +72.73 0.011302 +72.74 0.0140673 +72.75 0.0165475 +72.76 0.018727 +72.77 0.020598 +72.78 0.0221597 +72.79 0.0234184 +72.8 0.0243707 +72.81 0.0250417 +72.82 0.0254659 +72.83 0.025669 +72.84 0.0256784 +72.85 0.025522 +72.86 0.0252275 +72.87 0.0248208 +72.88 0.0243237 +72.89 0.0237574 +72.9 0.0231441 +72.91 0.0224967 +72.92 0.0218249 +72.93 0.0211345 +72.94 0.0204285 +72.95 0.0197072 +72.96 0.0189687 +72.97 0.0182085 +72.98 0.0174245 +72.99 0.0166136 +73 0.0157739 +73.01 0.0149046 +73.02 0.0140072 +73.03 0.0130852 +73.04 0.0121442 +73.05 0.0111922 +73.06 0.010241 +73.07 0.00930242 +73.08 0.0083891 +73.09 0.00751391 +73.1 0.00668945 +73.11 0.00592729 +73.12 0.00523739 +73.13 0.00463389 +73.14 0.00411812 +73.15 0.00368968 +73.16 0.00334703 +73.17 0.0030856 +73.18 0.00289801 +73.19 0.00277443 +73.2 0.00270307 +73.21 0.00267212 +73.22 0.00266569 +73.23 0.00266858 +73.24 0.00266828 +73.25 0.00265395 +73.26 0.00261698 +73.27 0.00255148 +73.28 0.00245454 +73.29 0.00232547 +73.3 0.00216745 +73.31 0.00198926 +73.32 0.00179995 +73.33 0.00161027 +73.34 0.00143194 +73.35 0.0012769 +73.36 0.00115643 +73.37 0.00108107 +73.38 0.00106276 +73.39 0.00110156 +73.4 0.00119795 +73.41 0.00134861 +73.42 0.0015463 +73.43 0.00177996 +73.44 0.00203503 +73.45 0.00229398 +73.46 0.00253358 +73.47 0.00273189 +73.48 0.00286734 +73.49 0.00291903 +73.5 0.00286789 +73.51 0.00269765 +73.52 0.00239571 +73.53 0.00195385 +73.54 0.0013589 +73.55 0.000620067 +73.56 -0.000249984 +73.57 -0.00123741 +73.58 -0.00232367 +73.59 -0.00348622 +73.6 -0.00469933 +73.61 -0.00593512 +73.62 -0.00716287 +73.63 -0.00835018 +73.64 -0.0094706 +73.65 -0.0105 +73.66 -0.0114179 +73.67 -0.0122084 +73.68 -0.0128609 +73.69 -0.01337 +73.7 -0.0137322 +73.71 -0.0139498 +73.72 -0.0140451 +73.73 -0.0140351 +73.74 -0.0139406 +73.75 -0.0137849 +73.76 -0.0135933 +73.77 -0.0133913 +73.78 -0.0132044 +73.79 -0.0130615 +73.8 -0.0129809 +73.81 -0.0129792 +73.82 -0.0130688 +73.83 -0.0132575 +73.84 -0.0135486 +73.85 -0.0139404 +73.86 -0.0144272 +73.87 -0.0150052 +73.88 -0.0156532 +73.89 -0.0163539 +73.9 -0.0170897 +73.91 -0.017842 +73.92 -0.0185926 +73.93 -0.0193242 +73.94 -0.0200212 +73.95 -0.0206664 +73.96 -0.02125 +73.97 -0.0217666 +73.98 -0.0222124 +73.99 -0.0225864 +74 -0.0228903 +74.01 -0.0231281 +74.02 -0.0233055 +74.03 -0.0234272 +74.04 -0.0235013 +74.05 -0.023538 +74.06 -0.0235434 +74.07 -0.0235227 +74.08 -0.0234793 +74.09 -0.0234146 +74.1 -0.0233276 +74.11 -0.0232146 +74.12 -0.0230683 +74.13 -0.0228831 +74.14 -0.0226504 +74.15 -0.0223606 +74.16 -0.0220036 +74.17 -0.0215695 +74.18 -0.0210494 +74.19 -0.0204345 +74.2 -0.01971 +74.21 -0.0188811 +74.22 -0.017947 +74.23 -0.0169096 +74.24 -0.0157738 +74.25 -0.0145471 +74.26 -0.0132397 +74.27 -0.0118642 +74.28 -0.0104322 +74.29 -0.00896556 +74.3 -0.00748272 +74.31 -0.00600202 +74.32 -0.00454186 +74.33 -0.00312019 +74.34 -0.00175405 +74.35 -0.000459126 +74.36 0.000744412 +74.37 0.00184618 +74.38 0.00284023 +74.39 0.00372025 +74.4 0.00448263 +74.41 0.00512652 +74.42 0.00565382 +74.43 0.00606912 +74.44 0.00637541 +74.45 0.00658271 +74.46 0.00670882 +74.47 0.00676771 +74.48 0.00677483 +74.49 0.00674674 +74.5 0.00670076 +74.51 0.00665449 +74.52 0.00662622 +74.53 0.00663634 +74.54 0.00669872 +74.55 0.00682701 +74.56 0.00703258 +74.57 0.00732417 +74.58 0.00770741 +74.59 0.00818448 +74.6 0.00875411 +74.61 0.00941792 +74.62 0.0101568 +74.63 0.0109564 +74.64 0.0117988 +74.65 0.0126628 +74.66 0.0135242 +74.67 0.0143566 +74.68 0.0151325 +74.69 0.0158141 +74.7 0.0163763 +74.71 0.0167942 +74.72 0.017044 +74.73 0.0171051 +74.74 0.0169614 +74.75 0.0166015 +74.76 0.0160197 +74.77 0.0152031 +74.78 0.0141646 +74.79 0.0129264 +74.8 0.0115081 +74.81 0.00993425 +74.82 0.00823415 +74.83 0.00644054 +74.84 0.00458869 +74.85 0.00271594 +74.86 0.000863899 +74.87 -0.000931201 +74.88 -0.00263662 +74.89 -0.00422319 +74.9 -0.00566618 +74.91 -0.00694599 +74.92 -0.0080486 +74.93 -0.00896207 +74.94 -0.00967348 +74.95 -0.0102017 +74.96 -0.0105561 +74.97 -0.0107497 +74.98 -0.010799 +74.99 -0.0107225 +75 -0.0105396 +75.01 -0.0102697 +75.02 -0.00992594 +75.03 -0.00953196 +75.04 -0.00910023 +75.05 -0.00863961 +75.06 -0.00815506 +75.07 -0.0076476 +75.08 -0.00711449 +75.09 -0.0065496 +75.1 -0.00594013 +75.11 -0.00527635 +75.12 -0.00454738 +75.13 -0.0037418 +75.14 -0.00284961 +75.15 -0.00186318 +75.16 -0.000778157 +75.17 0.000405794 +75.18 0.00168933 +75.19 0.00305999 +75.2 0.00449874 +75.21 0.00598583 +75.22 0.00749741 +75.23 0.00900637 +75.24 0.0104832 +75.25 0.0118973 +75.26 0.0132133 +75.27 0.0143953 +75.28 0.0154233 +75.29 0.0162748 +75.3 0.0169327 +75.31 0.017385 +75.32 0.0176262 +75.33 0.017657 +75.34 0.0174817 +75.35 0.0171032 +75.36 0.0165579 +75.37 0.0158702 +75.38 0.0150682 +75.39 0.0141816 +75.4 0.0132417 +75.41 0.012279 +75.42 0.0113226 +75.43 0.0104043 +75.44 0.00954488 +75.45 0.00876129 +75.46 0.00806547 +75.47 0.007464 +75.48 0.00695817 +75.49 0.00654426 +75.5 0.00621404 +75.51 0.00595969 +75.52 0.00576081 +75.53 0.00559865 +75.54 0.00545553 +75.55 0.00531427 +75.56 0.00515908 +75.57 0.00497644 +75.58 0.00475575 +75.59 0.00448729 +75.6 0.00416824 +75.61 0.00380203 +75.62 0.00339365 +75.63 0.00295131 +75.64 0.0024859 +75.65 0.00201026 +75.66 0.00153839 +75.67 0.00108575 +75.68 0.000668715 +75.69 0.000298106 +75.7 -1.61434e-05 +75.71 -0.000266817 +75.72 -0.000449809 +75.73 -0.000564343 +75.74 -0.00061302 +75.75 -0.000601011 +75.76 -0.000534681 +75.77 -0.000430872 +75.78 -0.000303247 +75.79 -0.000166646 +75.8 -3.64214e-05 +75.81 7.2216e-05 +75.82 0.000144815 +75.83 0.000168229 +75.84 0.000124698 +75.85 9.5859e-06 +75.86 -0.000183106 +75.87 -0.00045744 +75.88 -0.000815198 +75.89 -0.00125606 +75.9 -0.00177788 +75.91 -0.002377 +75.92 -0.00305336 +75.93 -0.00379755 +75.94 -0.00460155 +75.95 -0.00545921 +75.96 -0.0063645 +75.97 -0.00731161 +75.98 -0.00829495 +75.99 -0.0093091 +76 -0.0103497 +76.01 -0.0114101 +76.02 -0.0124831 +76.03 -0.0135615 +76.04 -0.0146371 +76.05 -0.0157005 +76.06 -0.0167405 +76.07 -0.0177447 +76.08 -0.0186969 +76.09 -0.0195773 +76.1 -0.0203726 +76.11 -0.0210653 +76.12 -0.021638 +76.13 -0.0220736 +76.14 -0.0223562 +76.15 -0.0224714 +76.16 -0.0224056 +76.17 -0.0221344 +76.18 -0.0216688 +76.19 -0.0210081 +76.2 -0.020155 +76.21 -0.0191165 +76.22 -0.0179035 +76.23 -0.0165302 +76.24 -0.0150139 +76.25 -0.0133656 +76.26 -0.0116186 +76.27 -0.00979686 +76.28 -0.0079232 +76.29 -0.00601964 +76.3 -0.00410677 +76.31 -0.00220315 +76.32 -0.000324805 +76.33 0.0015118 +76.34 0.00329727 +76.35 0.00502645 +76.36 0.00669572 +76.37 0.00830389 +76.38 0.00985172 +76.39 0.0113413 +76.4 0.0127756 +76.41 0.014155 +76.42 0.0154822 +76.43 0.0167606 +76.44 0.0179898 +76.45 0.0191672 +76.46 0.0202884 +76.47 0.021347 +76.48 0.0223349 +76.49 0.0232401 +76.5 0.0240471 +76.51 0.0247512 +76.52 0.0253428 +76.53 0.0258133 +76.54 0.0261565 +76.55 0.0263684 +76.56 0.0264485 +76.57 0.0263988 +76.58 0.0262161 +76.59 0.025923 +76.6 0.0255332 +76.61 0.0250632 +76.62 0.0245315 +76.63 0.0239577 +76.64 0.0233616 +76.65 0.0227624 +76.66 0.0221799 +76.67 0.0216288 +76.68 0.0211191 +76.69 0.0206569 +76.7 0.0202437 +76.71 0.0198761 +76.72 0.0195457 +76.73 0.0192399 +76.74 0.0189414 +76.75 0.0186275 +76.76 0.0182759 +76.77 0.0178636 +76.78 0.0173683 +76.79 0.0167691 +76.8 0.0160481 +76.81 0.0151912 +76.82 0.0141826 +76.83 0.0130169 +76.84 0.0117054 +76.85 0.0102572 +76.86 0.00868706 +76.87 0.00701479 +76.88 0.00526457 +76.89 0.00346389 +76.9 0.00164267 +76.91 -0.0001646 +76.92 -0.00192695 +76.93 -0.00361672 +76.94 -0.0052095 +76.95 -0.00668516 +76.96 -0.00802866 +76.97 -0.00923056 +76.98 -0.0102869 +76.99 -0.0111877 +77 -0.0119559 +77.01 -0.0126053 +77.02 -0.0131537 +77.03 -0.0136215 +77.04 -0.0140307 +77.05 -0.0144036 +77.06 -0.0147614 +77.07 -0.015125 +77.08 -0.0155108 +77.09 -0.0159294 +77.1 -0.0163865 +77.11 -0.0168828 +77.12 -0.0174135 +77.13 -0.0179693 +77.14 -0.0185363 +77.15 -0.0190955 +77.16 -0.0196244 +77.17 -0.0201016 +77.18 -0.0205056 +77.19 -0.0208164 +77.2 -0.0210161 +77.21 -0.0210905 +77.22 -0.0210298 +77.23 -0.0208234 +77.24 -0.0204702 +77.25 -0.0199859 +77.26 -0.0193829 +77.27 -0.0186788 +77.28 -0.017895 +77.29 -0.0170561 +77.3 -0.0161887 +77.31 -0.0153211 +77.32 -0.0144849 +77.33 -0.0137054 +77.34 -0.0130049 +77.35 -0.0124025 +77.36 -0.0119127 +77.37 -0.0115451 +77.38 -0.0113043 +77.39 -0.0111899 +77.4 -0.0112072 +77.41 -0.0113332 +77.42 -0.0115535 +77.43 -0.011851 +77.44 -0.0122066 +77.45 -0.0126003 +77.46 -0.0130122 +77.47 -0.013423 +77.48 -0.0138124 +77.49 -0.0141649 +77.5 -0.014469 +77.51 -0.0147155 +77.52 -0.0148983 +77.53 -0.0150143 +77.54 -0.0150629 +77.55 -0.0150456 +77.56 -0.0149624 +77.57 -0.0148193 +77.58 -0.0146238 +77.59 -0.0143806 +77.6 -0.0140936 +77.61 -0.0137659 +77.62 -0.0133991 +77.63 -0.0129932 +77.64 -0.0125453 +77.65 -0.0120512 +77.66 -0.0115093 +77.67 -0.0109147 +77.68 -0.0102625 +77.69 -0.00954805 +77.7 -0.00876718 +77.71 -0.00791703 +77.72 -0.00699498 +77.73 -0.00599719 +77.74 -0.00493227 +77.75 -0.00380513 +77.76 -0.00262263 +77.77 -0.00139336 +77.78 -0.000127355 +77.79 0.00116434 +77.8 0.00247006 +77.81 0.00377718 +77.82 0.00507324 +77.83 0.00634788 +77.84 0.007592 +77.85 0.00879797 +77.86 0.00995984 +77.87 0.0110733 +77.88 0.0121356 +77.89 0.0131416 +77.9 0.0140937 +77.91 0.0149938 +77.92 0.0158425 +77.93 0.0166405 +77.94 0.0173874 +77.95 0.0180822 +77.96 0.0187224 +77.97 0.0193008 +77.98 0.0198118 +77.99 0.0202512 +78 0.0206113 +78.01 0.0208835 +78.02 0.0210596 +78.03 0.0211317 +78.04 0.0210931 +78.05 0.0209349 +78.06 0.0206509 +78.07 0.0202497 +78.08 0.0197357 +78.09 0.0191168 +78.1 0.0184044 +78.11 0.0176133 +78.12 0.0167611 +78.13 0.0158677 +78.14 0.0149559 +78.15 0.0140517 +78.16 0.0131771 +78.17 0.012353 +78.18 0.011598 +78.19 0.0109272 +78.2 0.0103517 +78.21 0.00987812 +78.22 0.00951675 +78.23 0.00925375 +78.24 0.00907808 +78.25 0.00897463 +78.26 0.00892416 +78.27 0.00890416 +78.28 0.00888983 +78.29 0.00885527 +78.3 0.00876957 +78.31 0.00860803 +78.32 0.00835085 +78.33 0.00798084 +78.34 0.00748559 +78.35 0.00685812 +78.36 0.00609727 +78.37 0.00520789 +78.38 0.00419548 +78.39 0.00307933 +78.4 0.00188674 +78.41 0.000643055 +78.42 -0.000623898 +78.43 -0.00188506 +78.44 -0.00311135 +78.45 -0.0042749 +78.46 -0.00534679 +78.47 -0.00629866 +78.48 -0.00711987 +78.49 -0.00779849 +78.5 -0.00832773 +78.51 -0.00870595 +78.52 -0.0089364 +78.53 -0.00902675 +78.54 -0.00898718 +78.55 -0.00882475 +78.56 -0.00856695 +78.57 -0.00823072 +78.58 -0.00783256 +78.59 -0.00738765 +78.6 -0.00690904 +78.61 -0.00640704 +78.62 -0.00588884 +78.63 -0.00535734 +78.64 -0.00481371 +78.65 -0.00425521 +78.66 -0.00367673 +78.67 -0.00307166 +78.68 -0.00243272 +78.69 -0.00175285 +78.7 -0.00102615 +78.71 -0.000245187 +78.72 0.000588883 +78.73 0.00147138 +78.74 0.00239534 +78.75 0.00334984 +78.76 0.00432019 +78.77 0.00528823 +78.78 0.00623304 +78.79 0.00712826 +78.8 0.00794649 +78.81 0.00866647 +78.82 0.00926595 +78.83 0.00972526 +78.84 0.0100283 +78.85 0.0101635 +78.86 0.0101244 +78.87 0.00990555 +78.88 0.00950559 +78.89 0.00894891 +78.9 0.0082526 +78.91 0.00743825 +78.92 0.00653102 +78.93 0.00555858 +78.94 0.00454992 +78.95 0.00353426 +78.96 0.00254385 +78.97 0.00160372 +78.98 0.000735015 +78.99 -4.55054e-05 +79 -0.000726059 +79.01 -0.00130011 +79.02 -0.0017664 +79.03 -0.00212867 +79.04 -0.00238807 +79.05 -0.00256503 +79.06 -0.0026761 +79.07 -0.002738 +79.08 -0.00276775 +79.09 -0.00278158 +79.1 -0.00279381 +79.11 -0.00281601 +79.12 -0.0028576 +79.13 -0.00292176 +79.14 -0.00300635 +79.15 -0.00310555 +79.16 -0.00320955 +79.17 -0.00330508 +79.18 -0.00337621 +79.19 -0.00340527 +79.2 -0.00337035 +79.21 -0.00325059 +79.22 -0.00303347 +79.23 -0.0027059 +79.24 -0.00225849 +79.25 -0.0016862 +79.26 -0.000988876 +79.27 -0.000171403 +79.28 0.000758498 +79.29 0.00178864 +79.3 0.00289099 +79.31 0.00404219 +79.32 0.00521642 +79.33 0.00638645 +79.34 0.00752466 +79.35 0.00860411 +79.36 0.00959914 +79.37 0.0104754 +79.38 0.0112228 +79.39 0.0118268 +79.4 0.012277 +79.41 0.0125677 +79.42 0.0126968 +79.43 0.0126666 +79.44 0.0124823 +79.45 0.0121409 +79.46 0.0116632 +79.47 0.0110628 +79.48 0.0103516 +79.49 0.00954084 +79.5 0.00864103 +79.51 0.00766133 +79.52 0.00660937 +79.53 0.00548741 +79.54 0.00430196 +79.55 0.0030585 +79.56 0.00175941 +79.57 0.000406886 +79.58 -0.000996449 +79.59 -0.00244699 +79.6 -0.0039396 +79.61 -0.00546812 +79.62 -0.0070219 +79.63 -0.00858627 +79.64 -0.0101459 +79.65 -0.0116832 +79.66 -0.0131785 +79.67 -0.0146109 +79.68 -0.0159587 +79.69 -0.0171978 +79.7 -0.0182984 +79.71 -0.019251 +79.72 -0.0200408 +79.73 -0.0206567 +79.74 -0.0210919 +79.75 -0.0213446 +79.76 -0.0214179 +79.77 -0.0213198 +79.78 -0.0210493 +79.79 -0.0206411 +79.8 -0.0201166 +79.81 -0.0194993 +79.82 -0.0188144 +79.83 -0.0180872 +79.84 -0.017342 +79.85 -0.0166013 +79.86 -0.0158876 +79.87 -0.0152161 +79.88 -0.0145962 +79.89 -0.014033 +79.9 -0.0135267 +79.91 -0.0130727 +79.92 -0.0126618 +79.93 -0.0122812 +79.94 -0.0119153 +79.95 -0.011544 +79.96 -0.0111485 +79.97 -0.0107116 +79.98 -0.0102174 +79.99 -0.00965331 +80 -0.00900995 +80.01 -0.00828242 +80.02 -0.00746744 +80.03 -0.0065689 +80.04 -0.00560225 +80.05 -0.00458202 +80.06 -0.00352627 +80.07 -0.00245571 +80.08 -0.00139271 +80.09 -0.000360215 +80.1 0.000618081 +80.11 0.00151476 +80.12 0.00231708 +80.13 0.00301146 +80.14 0.00358865 +80.15 0.0040441 +80.16 0.00437807 +80.17 0.00459555 +80.18 0.00470583 +80.19 0.00471453 +80.2 0.00464797 +80.21 0.00452468 +80.22 0.00436356 +80.23 0.00418309 +80.24 0.00400032 +80.25 0.00382996 +80.26 0.00368354 +80.27 0.00357149 +80.28 0.00349554 +80.29 0.00345326 +80.3 0.00343889 +80.31 0.00344299 +80.32 0.00345305 +80.33 0.00345429 +80.34 0.00343056 +80.35 0.00336218 +80.36 0.00323193 +80.37 0.00302806 +80.38 0.00273965 +80.39 0.00235926 +80.4 0.00188342 +80.41 0.0013129 +80.42 0.000652859 +80.43 -8.96578e-05 +80.44 -0.000901484 +80.45 -0.00176058 +80.46 -0.0026477 +80.47 -0.00354222 +80.48 -0.00442315 +80.49 -0.00527001 +80.5 -0.0060638 +80.51 -0.00678659 +80.52 -0.00741759 +80.53 -0.0079534 +80.54 -0.0083883 +80.55 -0.00872038 +80.56 -0.0089513 +80.57 -0.00908594 +80.58 -0.00913188 +80.59 -0.00909868 +80.6 -0.00899191 +80.61 -0.00882956 +80.62 -0.00862265 +80.63 -0.0083805 +80.64 -0.00811053 +80.65 -0.00781785 +80.66 -0.00750498 +80.67 -0.00717178 +80.68 -0.00681361 +80.69 -0.00642587 +80.7 -0.00600234 +80.71 -0.00553533 +80.72 -0.00501707 +80.73 -0.00444047 +80.74 -0.00379993 +80.75 -0.00309208 +80.76 -0.00231303 +80.77 -0.0014667 +80.78 -0.000563505 +80.79 0.00038566 +80.8 0.0013665 +80.81 0.00236171 +80.82 0.00335167 +80.83 0.00431516 +80.84 0.00522822 +80.85 0.00606456 +80.86 0.00680833 +80.87 0.00744305 +80.88 0.00795599 +80.89 0.00833892 +80.9 0.00858875 +80.91 0.00870781 +80.92 0.00870288 +80.93 0.00858076 +80.94 0.00837142 +80.95 0.00809824 +80.96 0.00778765 +80.97 0.00746804 +80.98 0.00716843 +80.99 0.00691726 +81 0.00674103 +81.01 0.00667346 +81.02 0.00672677 +81.03 0.00691243 +81.04 0.00723687 +81.05 0.00770013 +81.06 0.0082958 +81.07 0.00901121 +81.08 0.00982796 +81.09 0.010726 +81.1 0.0116715 +81.11 0.0126313 +81.12 0.0135735 +81.13 0.0144661 +81.14 0.0152786 +81.15 0.0159829 +81.16 0.0165547 +81.17 0.0169663 +81.18 0.0172018 +81.19 0.0172614 +81.2 0.0171439 +81.21 0.0168534 +81.22 0.0163994 +81.23 0.0157961 +81.24 0.0150617 +81.25 0.0142148 +81.26 0.0132774 +81.27 0.0122808 +81.28 0.0112484 +81.29 0.0102021 +81.3 0.00916176 +81.31 0.00814418 +81.32 0.00716302 +81.33 0.00622871 +81.34 0.00535237 +81.35 0.00453178 +81.36 0.00376604 +81.37 0.00305172 +81.38 0.00238347 +81.39 0.00175475 +81.4 0.00115849 +81.41 0.000587848 +81.42 3.80748e-05 +81.43 -0.000496964 +81.44 -0.00101974 +81.45 -0.00153027 +81.46 -0.00202626 +81.47 -0.00250317 +81.48 -0.00295442 +81.49 -0.00337168 +81.5 -0.00374186 +81.51 -0.0040548 +81.52 -0.00430157 +81.53 -0.0044724 +81.54 -0.00455869 +81.55 -0.00455356 +81.56 -0.00445224 +81.57 -0.00425247 +81.58 -0.00395042 +81.59 -0.00354919 +81.6 -0.00306055 +81.61 -0.00249354 +81.62 -0.00185935 +81.63 -0.00117096 +81.64 -0.000442702 +81.65 0.000310268 +81.66 0.00107228 +81.67 0.00182601 +81.68 0.00255645 +81.69 0.0032502 +81.7 0.00389529 +81.71 0.00448131 +81.72 0.00499961 +81.73 0.00544331 +81.74 0.00580688 +81.75 0.00607899 +81.76 0.00626536 +81.77 0.00636523 +81.78 0.00637877 +81.79 0.0063068 +81.8 0.0061506 +81.81 0.00591178 +81.82 0.00559208 +81.83 0.0051871 +81.84 0.0047042 +81.85 0.00414685 +81.86 0.00351751 +81.87 0.00281908 +81.88 0.00205505 +81.89 0.00122967 +81.9 0.000348096 +81.91 -0.000586035 +81.92 -0.00156305 +81.93 -0.00257232 +81.94 -0.00360384 +81.95 -0.00464661 +81.96 -0.00568877 +81.97 -0.00671787 +81.98 -0.00772115 +81.99 -0.00868377 +82 -0.00959113 +82.01 -0.0104343 +82.02 -0.0112031 +82.03 -0.0118892 +82.04 -0.012486 +82.05 -0.0129888 +82.06 -0.0133952 +82.07 -0.0137032 +82.08 -0.0139097 +82.09 -0.0140269 +82.1 -0.0140609 +82.11 -0.0140196 +82.12 -0.0139118 +82.13 -0.013747 +82.14 -0.0135352 +82.15 -0.0132861 +82.16 -0.0130072 +82.17 -0.0127102 +82.18 -0.0124021 +82.19 -0.0120885 +82.2 -0.0117735 +82.21 -0.0114594 +82.22 -0.0111473 +82.23 -0.0108363 +82.24 -0.0105243 +82.25 -0.0102079 +82.26 -0.00988286 +82.27 -0.0095448 +82.28 -0.00918896 +82.29 -0.00881076 +82.3 -0.00840596 +82.31 -0.00797095 +82.32 -0.00750086 +82.33 -0.00699432 +82.34 -0.00645201 +82.35 -0.00587437 +82.36 -0.00526278 +82.37 -0.0046196 +82.38 -0.00394795 +82.39 -0.00325164 +82.4 -0.00253443 +82.41 -0.00180125 +82.42 -0.00105821 +82.43 -0.000310246 +82.44 0.000437769 +82.45 0.00118113 +82.46 0.00191533 +82.47 0.00263613 +82.48 0.00333915 +82.49 0.00401887 +82.5 0.00467321 +82.51 0.00529879 +82.52 0.00589234 +82.53 0.00645068 +82.54 0.00697071 +82.55 0.00744943 +82.56 0.00788398 +82.57 0.00826695 +82.58 0.00860012 +82.59 0.00888164 +82.6 0.00911005 +82.61 0.00928447 +82.62 0.00940475 +82.63 0.00947153 +82.64 0.00948633 +82.65 0.00944811 +82.66 0.00936325 +82.67 0.00923725 +82.68 0.00907525 +82.69 0.00888295 +82.7 0.00866648 +82.71 0.00843218 +82.72 0.00818636 +82.73 0.00793504 +82.74 0.00768437 +82.75 0.00743902 +82.76 0.00720243 +82.77 0.0069768 +82.78 0.00676302 +82.79 0.00656058 +82.8 0.00636757 +82.81 0.0061808 +82.82 0.00599524 +82.83 0.00580487 +82.84 0.00560339 +82.85 0.0053841 +82.86 0.00514026 +82.87 0.00486544 +82.88 0.00455382 +82.89 0.00419996 +82.9 0.00379693 +82.91 0.00334669 +82.92 0.00284944 +82.93 0.00230728 +82.94 0.00172427 +82.95 0.0011064 +82.96 0.000461514 +82.97 -0.000200897 +82.98 -0.000869367 +82.99 -0.00153004 +83 -0.00216984 +83.01 -0.00277539 +83.02 -0.00333335 +83.03 -0.00383082 +83.04 -0.00425571 +83.05 -0.00459712 +83.06 -0.00483842 +83.07 -0.00497571 +83.08 -0.00500642 +83.09 -0.00492774 +83.1 -0.00473922 +83.11 -0.0044428 +83.12 -0.00404279 +83.13 -0.00354578 +83.14 -0.00295658 +83.15 -0.00228802 +83.16 -0.00155702 +83.17 -0.000777838 +83.18 3.41855e-05 +83.19 0.000862963 +83.2 0.00169202 +83.21 0.00250485 +83.22 0.00328379 +83.23 0.00400994 +83.24 0.00467158 +83.25 0.00525612 +83.26 0.00575277 +83.27 0.00615275 +83.28 0.00644955 +83.29 0.00663904 +83.3 0.00671845 +83.31 0.00668081 +83.32 0.00653966 +83.33 0.00630136 +83.34 0.00597443 +83.35 0.00556928 +83.36 0.00509785 +83.37 0.0045733 +83.38 0.0040095 +83.39 0.00341964 +83.4 0.00282086 +83.41 0.00222645 +83.42 0.00164824 +83.43 0.00109644 +83.44 0.000579292 +83.45 0.000102809 +83.46 -0.000329395 +83.47 -0.000713164 +83.48 -0.00105205 +83.49 -0.00135149 +83.5 -0.00161752 +83.51 -0.00185775 +83.52 -0.00208087 +83.53 -0.0022962 +83.54 -0.00251307 +83.55 -0.00274116 +83.56 -0.00298902 +83.57 -0.00326168 +83.58 -0.00356254 +83.59 -0.00389253 +83.6 -0.00424983 +83.61 -0.00462985 +83.62 -0.00502525 +83.63 -0.00542598 +83.64 -0.0058181 +83.65 -0.00618728 +83.66 -0.00651856 +83.67 -0.00679671 +83.68 -0.007007 +83.69 -0.00713594 +83.7 -0.00717201 +83.71 -0.00710547 +83.72 -0.00692147 +83.73 -0.00662822 +83.74 -0.00622854 +83.75 -0.00572935 +83.76 -0.00514145 +83.77 -0.00447934 +83.78 -0.00376066 +83.79 -0.00300561 +83.8 -0.00223698 +83.81 -0.00148091 +83.82 -0.000760335 +83.83 -9.68431e-05 +83.84 0.000490062 +83.85 0.000983784 +83.86 0.00137134 +83.87 0.00164393 +83.88 0.00178954 +83.89 0.00181322 +83.9 0.00172515 +83.91 0.0015359 +83.92 0.00125983 +83.93 0.000914461 +83.94 0.000519606 +83.95 9.64792e-05 +83.96 -0.000332615 +83.97 -0.000744016 +83.98 -0.00111867 +83.99 -0.00144045 +84 -0.00169663 +84.01 -0.00187852 +84.02 -0.00198178 +84.03 -0.00200659 +84.04 -0.00195599 +84.05 -0.00183706 +84.06 -0.00166842 +84.07 -0.00146613 +84.08 -0.0012482 +84.09 -0.00103362 +84.1 -0.000841436 +84.11 -0.000689683 +84.12 -0.000594812 +84.13 -0.000577467 +84.14 -0.000641051 +84.15 -0.000790392 +84.16 -0.00102591 +84.17 -0.00134352 +84.18 -0.00173483 +84.19 -0.00218742 +84.2 -0.00268545 +84.21 -0.0032108 +84.22 -0.00373901 +84.23 -0.00424811 +84.24 -0.00471677 +84.25 -0.00512483 +84.26 -0.0054542 +84.27 -0.00568961 +84.28 -0.00581927 +84.29 -0.00582808 +84.3 -0.00571552 +84.31 -0.00548748 +84.32 -0.00514984 +84.33 -0.00471227 +84.34 -0.0041877 +84.35 -0.00359172 +84.36 -0.00294181 +84.37 -0.0022559 +84.38 -0.00155492 +84.39 -0.00085876 +84.4 -0.000183967 +84.41 0.00045501 +84.42 0.00104632 +84.43 0.00158103 +84.44 0.00205337 +84.45 0.00245935 +84.46 0.00279615 +84.47 0.00307258 +84.48 0.00329467 +84.49 0.00347019 +84.5 0.00360807 +84.51 0.0037178 +84.52 0.00380888 +84.53 0.00389021 +84.54 0.0039701 +84.55 0.00405533 +84.56 0.00415008 +84.57 0.00425656 +84.58 0.004375 +84.59 0.00450372 +84.6 0.00463935 +84.61 0.00477711 +84.62 0.00491031 +84.63 0.00503196 +84.64 0.00513537 +84.65 0.0052141 +84.66 0.00526234 +84.67 0.00527529 +84.68 0.0052494 +84.69 0.00518259 +84.7 0.00507192 +84.71 0.00491952 +84.72 0.00472989 +84.73 0.00450714 +84.74 0.00425652 +84.75 0.00398406 +84.76 0.00369636 +84.77 0.00340028 +84.78 0.00310286 +84.79 0.00281152 +84.8 0.00253221 +84.81 0.00227013 +84.82 0.00202966 +84.83 0.00181435 +84.84 0.00162685 +84.85 0.00146898 +84.86 0.0013424 +84.87 0.00124944 +84.88 0.00118741 +84.89 0.00115569 +84.9 0.00115336 +84.91 0.00117929 +84.92 0.00123219 +84.93 0.00131063 +84.94 0.00141304 +84.95 0.00153962 +84.96 0.00168611 +84.97 0.00185013 +84.98 0.00202897 +84.99 0.00221948 +85 0.00241808 +85.01 0.00262068 +85.02 0.0028228 +85.03 0.00301877 +85.04 0.00320321 +85.05 0.00337108 +85.06 0.00351736 +85.07 0.00363745 +85.08 0.00372733 +85.09 0.00378388 +85.1 0.00380501 +85.11 0.00378796 +85.12 0.00373397 +85.13 0.00364696 +85.14 0.00353067 +85.15 0.00339016 +85.16 0.0032316 +85.17 0.00306202 +85.18 0.00288896 +85.19 0.00272054 +85.2 0.00256535 +85.21 0.00242956 +85.22 0.00231847 +85.23 0.00223581 +85.24 0.00218345 +85.25 0.00216122 +85.26 0.00216676 +85.27 0.00219578 +85.28 0.00224195 +85.29 0.00229466 +85.3 0.00234385 +85.31 0.0023786 +85.32 0.0023877 +85.33 0.00236022 +85.34 0.0022861 +85.35 0.00215679 +85.36 0.00195964 +85.37 0.00169603 +85.38 0.00136578 +85.39 0.000971413 +85.4 0.00051856 +85.41 1.5795e-05 +85.42 -0.000525628 +85.43 -0.00109221 +85.44 -0.0016683 +85.45 -0.00223543 +85.46 -0.00277699 +85.47 -0.00327731 +85.48 -0.00372219 +85.49 -0.0040996 +85.5 -0.00440033 +85.51 -0.00461844 +85.52 -0.00474731 +85.53 -0.00478963 +85.54 -0.00475547 +85.55 -0.00465445 +85.56 -0.00449917 +85.57 -0.00430468 +85.58 -0.00408765 +85.59 -0.00386562 +85.6 -0.00365704 +85.61 -0.00348124 +85.62 -0.00335135 +85.63 -0.00327871 +85.64 -0.00327124 +85.65 -0.003333 +85.66 -0.00346391 +85.67 -0.0036597 +85.68 -0.00391253 +85.69 -0.00421221 +85.7 -0.0045382 +85.71 -0.00487262 +85.72 -0.0051963 +85.73 -0.00548976 +85.74 -0.00573423 +85.75 -0.00591259 +85.76 -0.00601027 +85.77 -0.00600728 +85.78 -0.00590387 +85.79 -0.00569931 +85.8 -0.00539673 +85.81 -0.00500395 +85.82 -0.00453314 +85.83 -0.00400039 +85.84 -0.00342502 +85.85 -0.00282893 +85.86 -0.00223873 +85.87 -0.00167839 +85.88 -0.00117043 +85.89 -0.000735405 +85.9 -0.000391067 +85.91 -0.000151542 +85.92 -2.67428e-05 +85.93 -2.77887e-05 +85.94 -0.000154674 +85.95 -0.000395943 +85.96 -0.000741672 +85.97 -0.00117799 +85.98 -0.00168776 +85.99 -0.00225136 +86 -0.00284757 +86.01 -0.00345421 +86.02 -0.0040469 +86.03 -0.00460525 +86.04 -0.00511163 +86.05 -0.00555131 +86.06 -0.00591297 +86.07 -0.006189 +86.08 -0.00637565 +86.09 -0.00647231 +86.1 -0.00647626 +86.11 -0.00640308 +86.12 -0.00626281 +86.13 -0.00606735 +86.14 -0.00582979 +86.15 -0.00556368 +86.16 -0.00528235 +86.17 -0.00499826 +86.18 -0.00472398 +86.19 -0.00446857 +86.2 -0.00423837 +86.21 -0.00403742 +86.22 -0.00386738 +86.23 -0.00372764 +86.24 -0.00361556 +86.25 -0.00352678 +86.26 -0.00345634 +86.27 -0.00339629 +86.28 -0.00333908 +86.29 -0.00327786 +86.3 -0.00320625 +86.31 -0.00311873 +86.32 -0.0030108 +86.33 -0.00287918 +86.34 -0.00272063 +86.35 -0.00253418 +86.36 -0.00232172 +86.37 -0.00208464 +86.38 -0.00182497 +86.39 -0.00154509 +86.4 -0.00124747 +86.41 -0.000934497 +86.42 -0.000607901 +86.43 -0.000268786 +86.44 8.10369e-05 +86.45 0.00044127 +86.46 0.000812094 +86.47 0.00119403 +86.48 0.00158774 +86.49 0.00199381 +86.5 0.00241256 +86.51 0.00284464 +86.52 0.00328752 +86.53 0.00373885 +86.54 0.00419511 +86.55 0.00465166 +86.56 0.00510273 +86.57 0.00554159 +86.58 0.00596074 +86.59 0.00634926 +86.6 0.00670011 +86.61 0.007006 +86.62 0.00725995 +86.63 0.00745608 +86.64 0.00758997 +86.65 0.00765896 +86.66 0.00766231 +86.67 0.00759757 +86.68 0.00747046 +86.69 0.00728904 +86.7 0.0070607 +86.71 0.00679412 +86.72 0.0064989 +86.73 0.00618504 +86.74 0.00586248 +86.75 0.00554095 +86.76 0.00522975 +86.77 0.00493513 +86.78 0.00466155 +86.79 0.00441134 +86.8 0.00418462 +86.81 0.00397933 +86.82 0.00379139 +86.83 0.00361505 +86.84 0.00344249 +86.85 0.00326475 +86.86 0.00307351 +86.87 0.00286089 +86.88 0.00261994 +86.89 0.00234523 +86.9 0.00203327 +86.91 0.0016828 +86.92 0.00129198 +86.93 0.000869092 +86.94 0.000421208 +86.95 -4.23466e-05 +86.96 -0.000510359 +86.97 -0.000970218 +86.98 -0.0014085 +86.99 -0.0018116 +87 -0.00216165 +87.01 -0.00244867 +87.02 -0.00266399 +87.03 -0.00280042 +87.04 -0.00285364 +87.05 -0.0028224 +87.06 -0.00270863 +87.07 -0.00251743 +87.08 -0.00225337 +87.09 -0.00193 +87.1 -0.00156307 +87.11 -0.00116695 +87.12 -0.000756632 +87.13 -0.000347072 +87.14 4.754e-05 +87.15 0.000414391 +87.16 0.000740775 +87.17 0.00101649 +87.18 0.00123882 +87.19 0.00140552 +87.2 0.00151717 +87.21 0.00157695 +87.22 0.00159037 +87.23 0.00156472 +87.24 0.00150827 +87.25 0.00143008 +87.26 0.00134176 +87.27 0.00125194 +87.28 0.00116805 +87.29 0.00109585 +87.3 0.00103916 +87.31 0.000999687 +87.32 0.000976971 +87.33 0.000969482 +87.34 0.00097103 +87.35 0.000975736 +87.36 0.000977033 +87.37 0.000968221 +87.38 0.000943068 +87.39 0.0008964 +87.4 0.000824639 +87.41 0.000724303 +87.42 0.000598162 +87.43 0.000450872 +87.44 0.000289061 +87.45 0.00012173 +87.46 -4.01008e-05 +87.47 -0.000183887 +87.48 -0.000296187 +87.49 -0.000360129 +87.5 -0.000361795 +87.51 -0.000292009 +87.52 -0.000141923 +87.53 9.43489e-05 +87.54 0.000419215 +87.55 0.000831254 +87.56 0.00132505 +87.57 0.00189328 +87.58 0.00252243 +87.59 0.00319095 +87.6 0.00387929 +87.61 0.00456626 +87.62 0.00522995 +87.63 0.00584871 +87.64 0.00640212 +87.65 0.00687053 +87.66 0.00723085 +87.67 0.00747999 +87.68 0.00761218 +87.69 0.00762641 +87.7 0.00752649 +87.71 0.00732089 +87.72 0.00702229 +87.73 0.00664703 +87.74 0.00621086 +87.75 0.00574178 +87.76 0.00526189 +87.77 0.00479239 +87.78 0.00435288 +87.79 0.00396031 +87.8 0.00362817 +87.81 0.00336584 +87.82 0.00318342 +87.83 0.00307659 +87.84 0.00303736 +87.85 0.00305524 +87.86 0.00311587 +87.87 0.00320179 +87.88 0.00329334 +87.89 0.00336972 +87.9 0.00340712 +87.91 0.00338368 +87.92 0.00328422 +87.93 0.00309508 +87.94 0.00280671 +87.95 0.00241415 +87.96 0.00191737 +87.97 0.00132139 +87.98 0.00063371 +87.99 -0.000131714 +88 -0.000950125 +88.01 -0.0017998 +88.02 -0.00265721 +88.03 -0.0034981 +88.04 -0.00429855 +88.05 -0.00503602 +88.06 -0.00568919 +88.07 -0.00623224 +88.08 -0.00666066 +88.09 -0.00696697 +88.1 -0.00714841 +88.11 -0.00720687 +88.12 -0.00714871 +88.13 -0.00698429 +88.14 -0.00672738 +88.15 -0.00638924 +88.16 -0.005996 +88.17 -0.0055674 +88.18 -0.0051223 +88.19 -0.00467841 +88.2 -0.00425162 +88.21 -0.00385531 +88.22 -0.00349994 +88.23 -0.00319636 +88.24 -0.00294651 +88.25 -0.00274892 +88.26 -0.00260068 +88.27 -0.00249645 +88.28 -0.00242902 +88.29 -0.00238992 +88.3 -0.0023701 +88.31 -0.00236064 +88.32 -0.00235229 +88.33 -0.00233791 +88.34 -0.00231257 +88.35 -0.00227337 +88.36 -0.00221954 +88.37 -0.00215243 +88.38 -0.00207537 +88.39 -0.00199336 +88.4 -0.00191355 +88.41 -0.00184336 +88.42 -0.00179003 +88.43 -0.00176039 +88.44 -0.00176037 +88.45 -0.00179451 +88.46 -0.00186566 +88.47 -0.00197503 +88.48 -0.00212409 +88.49 -0.0023057 +88.5 -0.00251429 +88.51 -0.00274268 +88.52 -0.00298257 +88.53 -0.00322487 +88.54 -0.00346034 +88.55 -0.00367999 +88.56 -0.003873 +88.57 -0.00403386 +88.58 -0.00415807 +88.59 -0.0042428 +88.6 -0.00428731 +88.61 -0.00429298 +88.62 -0.00426313 +88.63 -0.00420286 +88.64 -0.00411752 +88.65 -0.00401625 +88.66 -0.00390763 +88.67 -0.0037993 +88.68 -0.00369813 +88.69 -0.0036098 +88.7 -0.00353837 +88.71 -0.00348597 +88.72 -0.0034534 +88.73 -0.00343805 +88.74 -0.00343415 +88.75 -0.0034351 +88.76 -0.00343282 +88.77 -0.00341816 +88.78 -0.00338145 +88.79 -0.00331302 +88.8 -0.00320259 +88.81 -0.00303921 +88.82 -0.0028202 +88.83 -0.00254197 +88.84 -0.00220341 +88.85 -0.00180603 +88.86 -0.00135399 +88.87 -0.000853969 +88.88 -0.000314799 +88.89 0.00025388 +88.9 0.000836022 +88.91 0.00141835 +88.92 0.00198747 +88.93 0.0025305 +88.94 0.00303566 +88.95 0.00349278 +88.96 0.00389377 +88.97 0.0042276 +88.98 0.00449557 +88.99 0.00469901 +89 0.00484091 +89.01 0.00492669 +89.02 0.00496384 +89.03 0.0049615 +89.04 0.00492993 +89.05 0.00487952 +89.06 0.00482284 +89.07 0.00477058 +89.08 0.00473161 +89.09 0.00471324 +89.1 0.00472082 +89.11 0.00475763 +89.12 0.00482471 +89.13 0.00492202 +89.14 0.00504614 +89.15 0.00519017 +89.16 0.00534752 +89.17 0.00551071 +89.18 0.00567183 +89.19 0.00582296 +89.2 0.00595667 +89.21 0.00606571 +89.22 0.00614261 +89.23 0.00618567 +89.24 0.00619284 +89.25 0.0061637 +89.26 0.00609937 +89.27 0.00600228 +89.28 0.00587602 +89.29 0.00572493 +89.3 0.00555243 +89.31 0.00536572 +89.32 0.00516936 +89.33 0.00496722 +89.34 0.00476232 +89.35 0.00455654 +89.36 0.00435065 +89.37 0.00414422 +89.38 0.00393535 +89.39 0.00372138 +89.4 0.00349912 +89.41 0.00326504 +89.42 0.00301565 +89.43 0.00274786 +89.44 0.0024593 +89.45 0.00214861 +89.46 0.00181443 +89.47 0.0014589 +89.48 0.00108605 +89.49 0.000700478 +89.5 0.000308107 +89.51 -8.40685e-05 +89.52 -0.00046828 +89.53 -0.000836353 +89.54 -0.00117886 +89.55 -0.00148653 +89.56 -0.00175429 +89.57 -0.00197687 +89.58 -0.00215069 +89.59 -0.00227405 +89.6 -0.00234729 +89.61 -0.00237284 +89.62 -0.00235442 +89.63 -0.00229687 +89.64 -0.00221166 +89.65 -0.00210788 +89.66 -0.00199527 +89.67 -0.00188361 +89.68 -0.00178232 +89.69 -0.0016999 +89.7 -0.00164355 +89.71 -0.00162209 +89.72 -0.00163588 +89.73 -0.00168557 +89.74 -0.00176958 +89.75 -0.00188415 +89.76 -0.00202352 +89.77 -0.00218028 +89.78 -0.00234571 +89.79 -0.00250949 +89.8 -0.00266088 +89.81 -0.00279054 +89.82 -0.00289004 +89.83 -0.00295236 +89.84 -0.00297229 +89.85 -0.00294678 +89.86 -0.00287513 +89.87 -0.00275687 +89.88 -0.00259762 +89.89 -0.00240617 +89.9 -0.00219135 +89.91 -0.00196346 +89.92 -0.00173379 +89.93 -0.00151408 +89.94 -0.00131596 +89.95 -0.00115191 +89.96 -0.00103379 +89.97 -0.000966997 +89.98 -0.000956826 +89.99 -0.00100609 +90 -0.00111493 +90.01 -0.00128079 +90.02 -0.00149848 +90.03 -0.00176087 +90.04 -0.00205927 +90.05 -0.00237829 +90.06 -0.00270494 +90.07 -0.00302567 +90.08 -0.00332685 +90.09 -0.00359536 +90.1 -0.00381908 +90.11 -0.00398735 +90.12 -0.00408481 +90.13 -0.00411032 +90.14 -0.00406062 +90.15 -0.0039346 +90.16 -0.0037337 +90.17 -0.00346172 +90.18 -0.00312469 +90.19 -0.00273059 +90.2 -0.00228639 +90.21 -0.0018065 +90.22 -0.00130396 +90.23 -0.000791022 +90.24 -0.000279913 +90.25 0.000217511 +90.26 0.000690082 +90.27 0.0011276 +90.28 0.00151832 +90.29 0.00185414 +90.3 0.00213136 +90.31 0.00234581 +90.32 0.00249491 +90.33 0.00257768 +90.34 0.00259467 +90.35 0.00254787 +90.36 0.00243887 +90.37 0.00227109 +90.38 0.00205444 +90.39 0.00179586 +90.4 0.00150304 +90.41 0.00118435 +90.42 0.000848579 +90.43 0.000504837 +90.44 0.000162437 +90.45 -0.00016786 +90.46 -0.000477672 +90.47 -0.000759005 +90.48 -0.00100472 +90.49 -0.00120875 +90.5 -0.00136634 +90.51 -0.00147419 +90.52 -0.00153067 +90.53 -0.00153149 +90.54 -0.00148328 +90.55 -0.00139071 +90.56 -0.00125978 +90.57 -0.00109807 +90.58 -0.000914517 +90.59 -0.000719128 +90.6 -0.000522592 +90.61 -0.00033732 +90.62 -0.000174786 +90.63 -4.4525e-05 +90.64 4.48894e-05 +90.65 8.66037e-05 +90.66 7.58779e-05 +90.67 1.04043e-05 +90.68 -0.000109464 +90.69 -0.000282803 +90.7 -0.000502535 +90.71 -0.000757008 +90.72 -0.00103477 +90.73 -0.0013225 +90.74 -0.0016055 +90.75 -0.00186838 +90.76 -0.00209563 +90.77 -0.00227051 +90.78 -0.00237557 +90.79 -0.00240354 +90.8 -0.00234628 +90.81 -0.00219893 +90.82 -0.00196019 +90.83 -0.00163256 +90.84 -0.00122237 +90.85 -0.000739078 +90.86 -0.000192908 +90.87 0.000393035 +90.88 0.000999944 +90.89 0.00160776 +90.9 0.00219601 +90.91 0.00274473 +90.92 0.00323529 +90.93 0.00365128 +90.94 0.00397082 +90.95 0.00419037 +90.96 0.0043063 +90.97 0.00431832 +90.98 0.0042306 +90.99 0.00405162 +91 0.00379377 +91.01 0.00347285 +91.02 0.00310576 +91.03 0.0027168 +91.04 0.00232854 +91.05 0.00196186 +91.06 0.00163631 +91.07 0.0013692 +91.08 0.00117481 +91.09 0.00106368 +91.1 0.00104637 +91.11 0.00112493 +91.12 0.00129079 +91.13 0.00153574 +91.14 0.00184759 +91.15 0.00221078 +91.16 0.00260716 +91.17 0.00301678 +91.18 0.00341819 +91.19 0.00378729 +91.2 0.00410597 +91.21 0.00435765 +91.22 0.00452888 +91.23 0.00460989 +91.24 0.00459505 +91.25 0.00448311 +91.26 0.00427678 +91.27 0.00397692 +91.28 0.00360415 +91.29 0.00317348 +91.3 0.00270215 +91.31 0.00220882 +91.32 0.00171266 +91.33 0.00123235 +91.34 0.000785283 +91.35 0.000391758 +91.36 6.15155e-05 +91.37 -0.000198096 +91.38 -0.000382778 +91.39 -0.000492149 +91.4 -0.000529652 +91.41 -0.000502277 +91.42 -0.000420112 +91.43 -0.000294029 +91.44 -0.000141392 +91.45 2.09487e-05 +91.46 0.00017767 +91.47 0.000314349 +91.48 0.000418212 +91.49 0.000478779 +91.5 0.000488384 +91.51 0.000440137 +91.52 0.000332478 +91.53 0.000171571 +91.54 -3.64079e-05 +91.55 -0.000282545 +91.56 -0.000555705 +91.57 -0.000843161 +91.58 -0.00113129 +91.59 -0.00140571 +91.6 -0.00165047 +91.61 -0.00185522 +91.62 -0.00201054 +91.63 -0.00210949 +91.64 -0.002148 +91.65 -0.00212501 +91.66 -0.00204248 +91.67 -0.00190518 +91.68 -0.00171706 +91.69 -0.00149332 +91.7 -0.00124546 +91.71 -0.000985786 +91.72 -0.000726792 +91.73 -0.000480475 +91.74 -0.000257732 +91.75 -6.78184e-05 +91.76 7.85383e-05 +91.77 0.000178993 +91.78 0.000232808 +91.79 0.000241121 +91.8 0.000207551 +91.81 0.000137918 +91.82 3.98555e-05 +91.83 -7.76763e-05 +91.84 -0.000204901 +91.85 -0.00033052 +91.86 -0.000444861 +91.87 -0.000539664 +91.88 -0.000608218 +91.89 -0.00064573 +91.9 -0.000649582 +91.91 -0.00061946 +91.92 -0.00055628 +91.93 -0.000464434 +91.94 -0.000352272 +91.95 -0.000227718 +91.96 -9.96582e-05 +91.97 2.25725e-05 +91.98 0.000129734 +91.99 0.000213212 +92 0.000264816 +92.01 0.000275939 +92.02 0.000245645 +92.03 0.000172637 +92.04 5.79049e-05 +92.05 -9.53142e-05 +92.06 -0.00028169 +92.07 -0.000494072 +92.08 -0.000723847 +92.09 -0.000961155 +92.1 -0.00119427 +92.11 -0.00141314 +92.12 -0.00160839 +92.13 -0.00177177 +92.14 -0.00189654 +92.15 -0.00197785 +92.16 -0.0020129 +92.17 -0.00199764 +92.18 -0.00193673 +92.19 -0.00183532 +92.2 -0.00169931 +92.21 -0.00153603 +92.22 -0.00135384 +92.23 -0.00116169 +92.24 -0.00096863 +92.25 -0.000784275 +92.26 -0.000617279 +92.27 -0.000473693 +92.28 -0.000358412 +92.29 -0.000274615 +92.3 -0.000223672 +92.31 -0.000205165 +92.32 -0.000216993 +92.33 -0.000256355 +92.34 -0.000318359 +92.35 -0.000395603 +92.36 -0.000481955 +92.37 -0.000571308 +92.38 -0.00065797 +92.39 -0.000736997 +92.4 -0.000804493 +92.41 -0.000857581 +92.42 -0.000894018 +92.43 -0.000915563 +92.44 -0.000924138 +92.45 -0.000922874 +92.46 -0.000915872 +92.47 -0.000907912 +92.48 -0.000904109 +92.49 -0.000909557 +92.5 -0.000930631 +92.51 -0.000970229 +92.52 -0.0010309 +92.53 -0.00111392 +92.54 -0.00121909 +92.55 -0.00134464 +92.56 -0.0014873 +92.57 -0.00164237 +92.58 -0.00180394 +92.59 -0.00196401 +92.6 -0.00211493 +92.61 -0.00224907 +92.62 -0.00235905 +92.63 -0.00243815 +92.64 -0.00248066 +92.65 -0.00248214 +92.66 -0.00243734 +92.67 -0.00234552 +92.68 -0.00220971 +92.69 -0.00203268 +92.7 -0.00181879 +92.71 -0.00157379 +92.72 -0.00130448 +92.73 -0.00101845 +92.74 -0.000723593 +92.75 -0.000428697 +92.76 -0.000141562 +92.77 0.00013139 +92.78 0.000384912 +92.79 0.000615167 +92.8 0.000819873 +92.81 0.00099837 +92.82 0.00115131 +92.83 0.00127983 +92.84 0.00139002 +92.85 0.00148675 +92.86 0.00157549 +92.87 0.0016619 +92.88 0.00175148 +92.89 0.00184919 +92.9 0.00195911 +92.91 0.00208569 +92.92 0.0022291 +92.93 0.00238883 +92.94 0.00256293 +92.95 0.00274793 +92.96 0.00293897 +92.97 0.00313009 +92.98 0.00331444 +92.99 0.00348321 +93 0.00362876 +93.01 0.00374453 +93.02 0.00382433 +93.03 0.00386308 +93.04 0.00385714 +93.05 0.00380453 +93.06 0.00370504 +93.07 0.00355826 +93.08 0.00336838 +93.09 0.00314305 +93.1 0.00288916 +93.11 0.00261463 +93.12 0.00232808 +93.13 0.00203845 +93.14 0.00175455 +93.15 0.00148531 +93.16 0.00123969 +93.17 0.00102245 +93.18 0.000837815 +93.19 0.000688288 +93.2 0.00057456 +93.21 0.000495535 +93.22 0.000448431 +93.23 0.000429167 +93.24 0.000433363 +93.25 0.000452082 +93.26 0.000478374 +93.27 0.000505283 +93.28 0.000526234 +93.29 0.000535406 +93.3 0.000528058 +93.31 0.000500791 +93.32 0.000449844 +93.33 0.000376901 +93.34 0.000284061 +93.35 0.000174724 +93.36 5.36582e-05 +93.37 -7.32513e-05 +93.38 -0.000199321 +93.39 -0.000317429 +93.4 -0.000418924 +93.41 -0.000496812 +93.42 -0.000545713 +93.43 -0.000560879 +93.44 -0.000538967 +93.45 -0.000478233 +93.46 -0.000378654 +93.47 -0.000241955 +93.48 -7.02407e-05 +93.49 0.000130857 +93.5 0.000353047 +93.51 0.000588811 +93.52 0.000830033 +93.53 0.00106837 +93.54 0.00129564 +93.55 0.00150419 +93.56 0.00168645 +93.57 0.00183489 +93.58 0.00194766 +93.59 0.00202257 +93.6 0.00205914 +93.61 0.00205853 +93.62 0.00202347 +93.63 0.00195803 +93.64 0.00186734 +93.65 0.00175648 +93.66 0.00163431 +93.67 0.00150751 +93.68 0.00138248 +93.69 0.00126497 +93.7 0.00115988 +93.71 0.00107092 +93.72 0.00100054 +93.73 0.000951381 +93.74 0.000921446 +93.75 0.000908392 +93.76 0.000908981 +93.77 0.000918995 +93.78 0.0009335 +93.79 0.000947133 +93.8 0.000954416 +93.81 0.000949027 +93.82 0.000926066 +93.83 0.0008822 +93.84 0.000814727 +93.85 0.000722162 +93.86 0.000604327 +93.87 0.00046238 +93.88 0.000298768 +93.89 0.000116554 +93.9 -7.90852e-05 +93.91 -0.000281607 +93.92 -0.000484977 +93.93 -0.000682988 +93.94 -0.000869547 +93.95 -0.00103895 +93.96 -0.00118614 +93.97 -0.00130625 +93.98 -0.00139432 +93.99 -0.00145065 +94 -0.00147474 +94.01 -0.00146723 +94.02 -0.00142988 +94.03 -0.00136542 +94.04 -0.00127738 +94.05 -0.00116983 +94.06 -0.00104628 +94.07 -0.000913122 +94.08 -0.000774745 +94.09 -0.000635185 +94.1 -0.000497923 +94.11 -0.00036576 +94.12 -0.000240731 +94.13 -0.000124071 +94.14 -1.69102e-05 +94.15 8.17048e-05 +94.16 0.000172953 +94.17 0.000258286 +94.18 0.000339417 +94.19 0.000418138 +94.2 0.000496141 +94.21 0.000574845 +94.22 0.000655384 +94.23 0.00073816 +94.24 0.000822806 +94.25 0.000908386 +94.26 0.00099335 +94.27 0.00107561 +94.28 0.00115266 +94.29 0.00122169 +94.3 0.00127928 +94.31 0.001322 +94.32 0.00134808 +94.33 0.00135558 +94.34 0.0013432 +94.35 0.00131044 +94.36 0.00125764 +94.37 0.00118604 +94.38 0.00109749 +94.39 0.000994465 +94.4 0.000882 +94.41 0.000764295 +94.42 0.000645816 +94.43 0.000531065 +94.44 0.000424336 +94.45 0.000329488 +94.46 0.000249718 +94.47 0.000189187 +94.48 0.00014748 +94.49 0.000124517 +94.5 0.000119082 +94.51 0.000128828 +94.52 0.00015037 +94.53 0.000179433 +94.54 0.00021105 +94.55 0.000239146 +94.56 0.000257724 +94.57 0.000261463 +94.58 0.00024546 +94.59 0.000205574 +94.6 0.00013867 +94.61 4.28188e-05 +94.62 -8.25535e-05 +94.63 -0.000238062 +94.64 -0.000420476 +94.65 -0.000624571 +94.66 -0.000845087 +94.67 -0.00107575 +94.68 -0.00130951 +94.69 -0.00153888 +94.7 -0.00175622 +94.71 -0.00195315 +94.72 -0.00212136 +94.73 -0.00225638 +94.74 -0.00235366 +94.75 -0.00241013 +94.76 -0.00242427 +94.77 -0.00239627 +94.78 -0.00232798 +94.79 -0.00222242 +94.8 -0.00208316 +94.81 -0.00191975 +94.82 -0.00173974 +94.83 -0.00155131 +94.84 -0.00136297 +94.85 -0.00118321 +94.86 -0.00102012 +94.87 -0.000881121 +94.88 -0.000775958 +94.89 -0.000707286 +94.9 -0.000678326 +94.91 -0.000690859 +94.92 -0.000745036 +94.93 -0.000839409 +94.94 -0.000971012 +94.95 -0.00113551 +94.96 -0.0013289 +94.97 -0.00154261 +94.98 -0.0017687 +94.99 -0.0019996 +95 -0.00222767 +95.01 -0.00244547 +95.02 -0.002646 +95.03 -0.00282294 +95.04 -0.00296897 +95.05 -0.00307931 +95.06 -0.00315201 +95.07 -0.00318485 +95.08 -0.00317666 +95.09 -0.00312725 +95.1 -0.00303737 +95.11 -0.00290856 +95.12 -0.00274196 +95.13 -0.00253972 +95.14 -0.00230757 +95.15 -0.0020492 +95.16 -0.00176854 +95.17 -0.00146963 +95.18 -0.00115661 +95.19 -0.000833636 +95.2 -0.000504842 +95.21 -0.00017466 +95.22 0.000152418 +95.23 0.000472409 +95.24 0.000781428 +95.25 0.00107572 +95.26 0.00135169 +95.27 0.00160601 +95.28 0.00183561 +95.29 0.00203519 +95.3 0.00220474 +95.31 0.00234305 +95.32 0.00244943 +95.33 0.002524 +95.34 0.00256772 +95.35 0.00258251 +95.36 0.0025712 +95.37 0.00253628 +95.38 0.0024839 +95.39 0.00242013 +95.4 0.00235093 +95.41 0.00228257 +95.42 0.0022214 +95.43 0.00217355 +95.44 0.00214466 +95.45 0.00214101 +95.46 0.00216677 +95.47 0.00222297 +95.48 0.00231033 +95.49 0.00242791 +95.5 0.00257307 +95.51 0.00274147 +95.52 0.0029272 +95.53 0.00312293 +95.54 0.0033191 +95.55 0.00350558 +95.56 0.00367252 +95.57 0.00381011 +95.58 0.00390907 +95.59 0.00396108 +95.6 0.00395918 +95.61 0.00389762 +95.62 0.00376823 +95.63 0.00357614 +95.64 0.00332361 +95.65 0.0030154 +95.66 0.00265862 +95.67 0.00226255 +95.68 0.0018383 +95.69 0.00139841 +95.7 0.000957389 +95.71 0.000530501 +95.72 0.000131248 +95.73 -0.000227826 +95.74 -0.00053568 +95.75 -0.000783275 +95.76 -0.000964003 +95.77 -0.00107401 +95.78 -0.00110778 +95.79 -0.00107072 +95.8 -0.000970913 +95.81 -0.000816953 +95.82 -0.000619792 +95.83 -0.00039229 +95.84 -0.000148664 +95.85 9.61408e-05 +95.86 0.000325767 +95.87 0.000523929 +95.88 0.000678738 +95.89 0.000779512 +95.9 0.000818097 +95.91 0.000789249 +95.92 0.00069089 +95.93 0.000524231 +95.94 0.000292283 +95.95 1.2145e-06 +95.96 -0.000333552 +95.97 -0.000698972 +95.98 -0.00108044 +95.99 -0.00146243 +96 -0.00182921 +96.01 -0.00216556 +96.02 -0.00245713 +96.03 -0.00268584 +96.04 -0.00284692 +96.05 -0.00293426 +96.06 -0.00294499 +96.07 -0.00287965 +96.08 -0.00274214 +96.09 -0.0025396 +96.1 -0.00228203 +96.11 -0.00197959 +96.12 -0.00165117 +96.13 -0.00131317 +96.14 -0.000981717 +96.15 -0.000672589 +96.16 -0.000400473 +96.17 -0.000178323 +96.18 -1.67832e-05 +96.19 7.16531e-05 +96.2 8.41738e-05 +96.21 2.26564e-05 +96.22 -0.000110038 +96.23 -0.000307792 +96.24 -0.000561516 +96.25 -0.000859545 +96.26 -0.00118817 +96.27 -0.00153215 +96.28 -0.00187348 +96.29 -0.00219547 +96.3 -0.002483 +96.31 -0.00272238 +96.32 -0.0029019 +96.33 -0.00301237 +96.34 -0.00304746 +96.35 -0.00300215 +96.36 -0.00287264 +96.37 -0.00266881 +96.38 -0.00239749 +96.39 -0.00206813 +96.4 -0.00169238 +96.41 -0.00128352 +96.42 -0.000855933 +96.43 -0.000424483 +96.44 -6.26899e-06 +96.45 0.000384931 +96.46 0.000736921 +96.47 0.00103943 +96.48 0.00128449 +96.49 0.0014667 +96.5 0.00158339 +96.51 0.00163465 +96.52 0.00161867 +96.53 0.00154556 +96.54 0.00142438 +96.55 0.00126484 +96.56 0.00107792 +96.57 0.000875361 +96.58 0.000669201 +96.59 0.000471222 +96.6 0.000294262 +96.61 0.000148783 +96.62 4.19209e-05 +96.63 -2.03239e-05 +96.64 -3.40659e-05 +96.65 2.36191e-06 +96.66 8.83949e-05 +96.67 0.000221315 +96.68 0.000397811 +96.69 0.000611068 +96.7 0.00085045 +96.71 0.00110674 +96.72 0.0013701 +96.73 0.00163049 +96.74 0.00187805 +96.75 0.00210348 +96.76 0.00229766 +96.77 0.00245055 +96.78 0.00255933 +96.79 0.00262038 +96.8 0.00263186 +96.81 0.00259375 +96.82 0.0025078 +96.83 0.00237746 +96.84 0.00220766 +96.85 0.00200216 +96.86 0.0017722 +96.87 0.001526 +96.88 0.00127211 +96.89 0.00101918 +96.9 0.000775544 +96.91 0.000549036 +96.92 0.000346651 +96.93 0.000177021 +96.94 4.34339e-05 +96.95 -5.17999e-05 +96.96 -0.000107218 +96.97 -0.000122876 +96.98 -0.000100317 +96.99 -4.2486e-05 +97 4.63946e-05 +97.01 0.000162093 +97.02 0.000297132 +97.03 0.000443503 +97.04 0.000593922 +97.05 0.000741062 +97.06 0.000877826 +97.07 0.000997601 +97.08 0.0010945 +97.09 0.00116242 +97.1 0.00119641 +97.11 0.00119595 +97.12 0.00116012 +97.13 0.00108937 +97.14 0.00098548 +97.15 0.000851484 +97.16 0.000691551 +97.17 0.000510582 +97.18 0.000314195 +97.19 0.000110535 +97.2 -9.36729e-05 +97.21 -0.000291747 +97.22 -0.000477302 +97.23 -0.000644485 +97.24 -0.000788185 +97.25 -0.000904216 +97.26 -0.000986384 +97.27 -0.00103554 +97.28 -0.00105135 +97.29 -0.00103457 +97.3 -0.000987067 +97.31 -0.00091174 +97.32 -0.000812351 +97.33 -0.000693355 +97.34 -0.000559 +97.35 -0.000415762 +97.36 -0.000269182 +97.37 -0.000124209 +97.38 1.46525e-05 +97.39 0.000143511 +97.4 0.000259229 +97.41 0.000359518 +97.42 0.000442078 +97.43 0.000506658 +97.44 0.000554642 +97.45 0.000587436 +97.46 0.000607108 +97.47 0.000616235 +97.48 0.000617739 +97.49 0.000614707 +97.5 0.000610263 +97.51 0.00060764 +97.52 0.000609328 +97.53 0.000617238 +97.54 0.000632675 +97.55 0.000656282 +97.56 0.000688018 +97.57 0.000727176 +97.58 0.000772486 +97.59 0.000822163 +97.6 0.00087336 +97.61 0.000923482 +97.62 0.000969835 +97.63 0.00100977 +97.64 0.00104082 +97.65 0.00106085 +97.66 0.00106812 +97.67 0.0010601 +97.68 0.00103733 +97.69 0.000999972 +97.7 0.000948669 +97.71 0.000884574 +97.72 0.000809298 +97.73 0.000724814 +97.74 0.000633358 +97.75 0.000537167 +97.76 0.000439119 +97.77 0.000341656 +97.78 0.000246914 +97.79 0.000156775 +97.8 7.2812e-05 +97.81 -3.73839e-06 +97.82 -7.19807e-05 +97.83 -0.000130888 +97.84 -0.000180199 +97.85 -0.000220353 +97.86 -0.000251528 +97.87 -0.000273995 +97.88 -0.000288066 +97.89 -0.000294031 +97.9 -0.000292123 +97.91 -0.000282259 +97.92 -0.000264188 +97.93 -0.00023839 +97.94 -0.000204779 +97.95 -0.000163278 +97.96 -0.000113886 +97.97 -5.67453e-05 +97.98 7.78764e-06 +97.99 7.91301e-05 +98 0.000156627 +98.01 0.000238253 +98.02 0.000322177 +98.03 0.000406202 +98.04 0.0004878 +98.05 0.00056419 +98.06 0.000632433 +98.07 0.000689549 +98.08 0.000731201 +98.09 0.000755635 +98.1 0.000760872 +98.11 0.000745338 +98.12 0.000708135 +98.13 0.000649135 +98.14 0.000569033 +98.15 0.000469372 +98.16 0.000351587 +98.17 0.000219884 +98.18 7.87164e-05 +98.19 -6.75154e-05 +98.2 -0.00021412 +98.21 -0.000356318 +98.22 -0.000489448 +98.23 -0.000609188 +98.24 -0.000710802 +98.25 -0.000790848 +98.26 -0.00084828 +98.27 -0.00088215 +98.28 -0.000892565 +98.29 -0.000880683 +98.3 -0.000848657 +98.31 -0.000799531 +98.32 -0.000736859 +98.33 -0.000665115 +98.34 -0.000589911 +98.35 -0.000515972 +98.36 -0.000447811 +98.37 -0.000389497 +98.38 -0.000344451 +98.39 -0.000315282 +98.4 -0.000303768 +98.41 -0.000312079 +98.42 -0.000337952 +98.43 -0.000379786 +98.44 -0.000435115 +98.45 -0.000500767 +98.46 -0.000573054 +98.47 -0.000647994 +98.48 -0.000721545 +98.49 -0.000789104 +98.5 -0.000847337 +98.51 -0.000893635 +98.52 -0.000926102 +98.53 -0.000943727 +98.54 -0.000946429 +98.55 -0.000935038 +98.56 -0.000911218 +98.57 -0.000876898 +98.58 -0.000835676 +98.59 -0.00079116 +98.6 -0.000746647 +98.61 -0.000705224 +98.62 -0.000669533 +98.63 -0.000641577 +98.64 -0.00062255 +98.65 -0.000613092 +98.66 -0.000612375 +98.67 -0.000618 +98.68 -0.000627214 +98.69 -0.000636557 +98.7 -0.000642074 +98.71 -0.000639562 +98.72 -0.000624845 +98.73 -0.000593614 +98.74 -0.000541343 +98.75 -0.000467144 +98.76 -0.000370026 +98.77 -0.000250286 +98.78 -0.000109587 +98.79 4.90384e-05 +98.8 0.000221268 +98.81 0.000401652 +98.82 0.000583314 +98.83 0.000758535 +98.84 0.00092013 +98.85 0.00106115 +98.86 0.00117528 +98.87 0.00125717 +98.88 0.00130283 +98.89 0.00130985 +98.9 0.00127456 +98.91 0.00120119 +98.92 0.00109412 +98.93 0.000958814 +98.94 0.000802306 +98.95 0.000632879 +98.96 0.000459676 +98.97 0.000292256 +98.98 0.000141589 +98.99 1.71776e-05 +99 -7.40731e-05 +99.01 -0.000126246 +99.02 -0.000135379 +99.03 -9.97234e-05 +99.04 -1.98956e-05 +99.05 0.000101096 +99.06 0.000259063 +99.07 0.000445955 +99.08 0.000650244 +99.09 0.000861259 +99.1 0.00106768 +99.11 0.00125811 +99.12 0.00142167 +99.13 0.00154859 +99.14 0.00162968 +99.15 0.00165552 +99.16 0.00162675 +99.17 0.00154314 +99.18 0.00140731 +99.19 0.00122456 +99.2 0.00100272 +99.21 0.000751796 +99.22 0.000483467 +99.23 0.000211404 +99.24 -4.95782e-05 +99.25 -0.000286527 +99.26 -0.000487519 +99.27 -0.00064228 +99.28 -0.000742724 +99.29 -0.000783395 +99.3 -0.00076178 +99.31 -0.000674007 +99.32 -0.00052814 +99.33 -0.000332822 +99.34 -9.79744e-05 +99.35 0.000164407 +99.36 0.000440831 +99.37 0.000716983 +99.38 0.000978428 +99.39 0.00120898 +99.4 0.0013953 +99.41 0.00152867 +99.42 0.00160172 +99.43 0.00161011 +99.44 0.00155272 +99.45 0.0014317 +99.46 0.00125241 +99.47 0.0010217 +99.48 0.000750978 +99.49 0.000456359 +99.5 0.000152084 +99.51 -0.000147194 +99.52 -0.000427157 +99.53 -0.000674539 +99.54 -0.000877797 +99.55 -0.00102669 +99.56 -0.00111048 +99.57 -0.00113099 +99.58 -0.00108885 +99.59 -0.000987848 +99.6 -0.00083473 +99.61 -0.000638819 +99.62 -0.00041151 +99.63 -0.000165658 +99.64 8.42736e-05 +99.65 0.000323108 +99.66 0.000538128 +99.67 0.000718158 +99.68 0.000854125 +99.69 0.000939519 +99.7 0.000970717 +99.71 0.000947154 +99.72 0.000867884 +99.73 0.000741916 +99.74 0.000579076 +99.75 0.000389952 +99.76 0.000186557 +99.77 -1.83176e-05 +99.78 -0.000211789 +99.79 -0.000381575 +99.8 -0.000514267 +99.81 -0.000600134 +99.82 -0.000634542 +99.83 -0.000613993 +99.84 -0.000537898 +99.85 -0.000408579 +99.86 -0.000231129 +99.87 -1.31223e-05 +99.88 0.00023644 +99.89 0.000505518 +99.9 0.000779984 +99.91 0.00104751 +99.92 0.00129631 +99.93 0.00151573 +99.94 0.00169684 +99.95 0.00183281 +99.96 0.0019186 +99.97 0.00194891 +99.98 0.00192961 +99.99 0.00186482 +100 0.0017609 \ No newline at end of file diff --git a/src/diffpy/utils/tests/t_helpers.py b/src/diffpy/utils/tests/testhelpers.py similarity index 100% rename from src/diffpy/utils/tests/t_helpers.py rename to src/diffpy/utils/tests/testhelpers.py From 97a4f7096457c823ef71d16451ac0f207a2fe7b1 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 24 Jul 2023 22:50:22 -0400 Subject: [PATCH 009/696] Use existing isfloat() function --- src/diffpy/utils/parsers/loaddata.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 7cab7b46..0e6ff88d 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -103,10 +103,9 @@ def countcolumnsvalues(line): if flag: name = hpair[0] value = hpair[1] - # can be stored as float if only one decimal - if hpair[1].replace(".", "", 1).isnumeric(): - value = float(hpair[1]) # check if data value should be stored as float + if isfloat(hpair[1]): + value = float(hpair[1]) hdata.update({name: value}) # continue search for the start of datablock fpos = (fpos[1], fpos[1] + len(line)) From 580f633ae4f4ceb802eb339a8da2b4ca2236e41c Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 25 Jul 2023 07:13:32 +0200 Subject: [PATCH 010/696] added GH actions badge --- README.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 005617b5..fc1502ca 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,5 @@ -.. image:: https://travis-ci.org/diffpy/diffpy.utils.svg?branch=master - :target: https://travis-ci.org/diffpy/diffpy.utils - -.. image:: https://codecov.io/gh/diffpy/diffpy.utils/branch/master/graph/badge.svg - :target: https://codecov.io/gh/diffpy/diffpy.utils - +.. image:: https://github.com/diffpy/diffpy.utils/actions/workflows/main.yml/badge.svg + :target: https://github.com/diffpy/diffpy.utils/actions/workflows/main.yml diffpy.utils ======================================================================== From 4dff673332e14017c784ce77a9027582a6192594 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 25 Jul 2023 07:53:14 +0200 Subject: [PATCH 011/696] put the codecov badge back --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index fc1502ca..00b73b0d 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,10 @@ .. image:: https://github.com/diffpy/diffpy.utils/actions/workflows/main.yml/badge.svg :target: https://github.com/diffpy/diffpy.utils/actions/workflows/main.yml +.. image:: https://codecov.io/gh/diffpy/diffpy.utils/branch/main/graph/badge.svg + :target: https://codecov.io/gh/diffpy/diffpy.utils + + diffpy.utils ======================================================================== From cf4dbdc12b5a3ab667f1b0c1c3a5191e5a260040 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Tue, 25 Jul 2023 08:44:19 -0400 Subject: [PATCH 012/696] Remove wx from tests --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index 10895cee..e199c1ed 100644 --- a/.coveragerc +++ b/.coveragerc @@ -20,6 +20,8 @@ omit = # ignore _version.py and versioneer.py .*version.* *_version.py + # ignore wx directory + */wx/* [run] From b3f543534392ae8c8bbe208b7d6bf79b6ea70085 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Thu, 27 Jul 2023 00:43:27 -0400 Subject: [PATCH 013/696] Allow loading PDF into markup file, tests included --- src/diffpy/utils/parsers/__init__.py | 1 + src/diffpy/utils/parsers/loadmetafile.py | 164 ++++++++++++++++++ src/diffpy/utils/tests/test_loadmetafile.py | 48 +++++ src/diffpy/utils/tests/testdata/dbload/e1.gr | 9 + src/diffpy/utils/tests/testdata/dbload/e2.gr | 9 + src/diffpy/utils/tests/testdata/dbload/e3.gr | 9 + src/diffpy/utils/tests/testdata/loadmu.txt | 25 +++ .../utils/tests/testdata/strumining.json | 17 ++ src/diffpy/utils/tests/testdata/targetdb.json | 26 +++ src/diffpy/utils/tests/testdata/targetmu.json | 21 +++ 10 files changed, 329 insertions(+) create mode 100644 src/diffpy/utils/parsers/loadmetafile.py create mode 100644 src/diffpy/utils/tests/test_loadmetafile.py create mode 100644 src/diffpy/utils/tests/testdata/dbload/e1.gr create mode 100644 src/diffpy/utils/tests/testdata/dbload/e2.gr create mode 100644 src/diffpy/utils/tests/testdata/dbload/e3.gr create mode 100644 src/diffpy/utils/tests/testdata/loadmu.txt create mode 100644 src/diffpy/utils/tests/testdata/strumining.json create mode 100644 src/diffpy/utils/tests/testdata/targetdb.json create mode 100644 src/diffpy/utils/tests/testdata/targetmu.json diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index 39e581a6..217d63cc 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -17,6 +17,7 @@ """ from .loaddata import loadData +from .loadmetafile import load_PDF_into_db, markup_PDF, apply_schema from .resample import resample # silence the pyflakes syntax checker diff --git a/src/diffpy/utils/parsers/loadmetafile.py b/src/diffpy/utils/parsers/loadmetafile.py new file mode 100644 index 00000000..dc48ff74 --- /dev/null +++ b/src/diffpy/utils/parsers/loadmetafile.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.utils by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## + +import pathlib +import json + +from diffpy.utils.parsers import loadData + +# FIXME: add support for yaml, xml +supported_formats = ['.json'] + + +def load_PDF_into_db(dbname, pdfname, hddata: dict, rv: list, oneline=True, show_path=True): + """Load PDF header and base data into a database file. + + Requires hdata and rv generated from loadData. + + dbname -- name of the database file to load into. + pdfname -- name of the PDF file. + hddata -- Dictionary of PDF metadata generated by loadData. + rv -- List of PDF (r, gr) pairs generated by loadData. + oneline -- store r and gr arrays in a single line for compactness (default True). + show_path -- include a PDF_path element in the database entry (default True). + """ + # new file or update + existing = False + if pathlib.Path.is_file(dbname): + existing = True + + # collect entry + with open(pdfname, 'r') as grfile: + data = {} + + # add path + grpath = grfile.name + if show_path: + data.update({'PDF_path': grpath}) + + # add r, gr, and header metadata + if oneline: + data.update({'r': str(list(rv[:, 0])), 'gr': str(list(rv[:, 1]))}) + else: + data.update({'r': list(rv[:, 0]), 'gr': list(rv[:, 1])}) + data.update(hddata) + + # parse name using pathlib and generate json entry + name = pathlib.Path(grpath).name + entry = {name: data} + + # check if supported type + extension = pathlib.Path(dbname).suffix + if extension not in supported_formats: + raise Exception(f"Format of {dbname} is not supported.") + + # json + if extension == '.json': + # dump if non-existing + if not existing: + with open(dbname, 'w') as jsonfile: + jsonfile.write(json.dumps(entry, indent=2)) + + # update if existing + else: + with open(dbname, 'r') as json_read: + pdfs = json.load(json_read) + pdfs.update(entry) + with open(dbname, 'w') as json_write: + # dump to string first for formatting + json.dump(pdfs, json_write, indent=2) + + +def markup_PDF(muname, hddata: dict, rv: list): + # FIXME: for REST API, remove if better ways to implement + """Put PDF file information in a markup language file. + + mumane -- name of markup file to put data into. + hddata -- Dictionary of metadata. + rv -- List of (r, gr) pairs. + """ + + # gather data + data = {} + data.update({'r': str(list(rv[:, 0])), 'gr': str(list(rv[:, 1]))}) + data.update(hddata) + extension = pathlib.Path(muname).suffix + if extension not in supported_formats: + raise Exception(f"Format of {muname} is not supported.") + + # dumps into file, automatically overwrites + if extension == '.json': + with open(muname, 'w') as json_write: + json.dump(data, json_write, indent=2) + + +def apply_schema(filename, schemaname, multiple_entries=False): + """ Reformat a file so relevant entries match the same order as a schema file. + Other entries are put at the end in the same order. + + filename -- name of file to apply the schema to. + schemaname -- name of schema to apply. + multiple_entries -- True if database file (i.e. those generated by load_PDF_into_db). + False if data from a single file (i.e. those generated by markup_PDF). + """ + + # ensure proper extension + file_ext = pathlib.Path(filename).suffix + schema_ext = pathlib.Path(schemaname).suffix + if file_ext != schema_ext: + return Exception("Schema type does not match file type.") + if file_ext not in supported_formats: + return Exception(f"Format of {filename} is not supported.") + + # json + if file_ext == ".json": + with open(schemaname, 'r') as jsonschema: + schema = json.load(jsonschema) + schema_order = [] + for dp in schema: + schema_order.append(dp) + + # database file + if multiple_entries: + # reformat each entry in a collection + with open(filename, 'r') as json_read: + data_dict = json.load(json_read) + reformatted_dict = {} # new dictionary for entire json + for entry in data_dict.keys(): + # reformat each entry + entry_dict = data_dict.get(entry) + reformatted_entry = {} # new dictionary for a particular entry + for dp in schema_order: + if dp in entry_dict: + reformatted_entry.update({dp: entry_dict.get(dp)}) + entry_dict.pop(dp) + reformatted_entry.update(entry_dict) + reformatted_dict.update({entry: reformatted_entry}) + with open(filename, 'w') as json_write: + json.dump(reformatted_dict, json_write, indent=2) + + # single file + else: + with open(filename, 'r') as json_read: + data_dict = json.load(json_read) + reformatted_dict = {} + # reformat + for dp in schema_order: + if dp in data_dict: + reformatted_dict.update({dp: data_dict.get(dp)}) + data_dict.pop(dp) + reformatted_dict.update(data_dict) + with open(filename, 'w') as json_write: + json.dump(reformatted_dict, json_write, indent=2) diff --git a/src/diffpy/utils/tests/test_loadmetafile.py b/src/diffpy/utils/tests/test_loadmetafile.py new file mode 100644 index 00000000..9860ee82 --- /dev/null +++ b/src/diffpy/utils/tests/test_loadmetafile.py @@ -0,0 +1,48 @@ +from diffpy.utils.parsers import load_PDF_into_db, markup_PDF, apply_schema +from diffpy.utils.parsers import loadData +from diffpy.utils.tests.testhelpers import datafile + +generatedjson = datafile('tljson.json') +targetjson = datafile('targetdb.json') + +schemaname = datafile('strumining.json') +muload = datafile('loadmu.txt') +generatedmu = datafile('tmujson.json') +targetmu = datafile('targetmu.json') + + +def test_load_gr(): + import os + import filecmp + + # generate json and apply schema + tddbload_list = os.listdir(os.getcwd() + "/testdata/dbload") + tddbload_list.sort() + print(tddbload_list) + for headerfile in tddbload_list: + headerfile = os.getcwd() + "/testdata/dbload/" + headerfile + hdata, rv = loadData(headerfile, headers=True) + load_PDF_into_db(generatedjson, headerfile, hdata, rv, show_path=False) + apply_schema(generatedjson, schemaname, multiple_entries=True) + + # compare to target + assert filecmp.cmp(generatedjson, targetjson) + + # cleanup + os.remove(generatedjson) + + +def test_markup_gr(): + import os + import filecmp + + # put into json and apply schema + hdata, rv = loadData(muload, headers=True) + markup_PDF(generatedmu, hdata, rv) + apply_schema(generatedmu, schemaname) + + # check against target + assert filecmp.cmp(generatedmu, targetmu) + + # cleanup + os.remove(generatedmu) diff --git a/src/diffpy/utils/tests/testdata/dbload/e1.gr b/src/diffpy/utils/tests/testdata/dbload/e1.gr new file mode 100644 index 00000000..ca0ae586 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/dbload/e1.gr @@ -0,0 +1,9 @@ +rmin = 0 +rmax = 10 +qmin = 0 +qmax = 10 + +0 0 +1 0 +2 0 +3 0 \ No newline at end of file diff --git a/src/diffpy/utils/tests/testdata/dbload/e2.gr b/src/diffpy/utils/tests/testdata/dbload/e2.gr new file mode 100644 index 00000000..eb2fca29 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/dbload/e2.gr @@ -0,0 +1,9 @@ +rmin = 1 +rmax = 11 +qmin = 1 +qmax = 11 + +0 1 +1 2 +2 3 +3 4 \ No newline at end of file diff --git a/src/diffpy/utils/tests/testdata/dbload/e3.gr b/src/diffpy/utils/tests/testdata/dbload/e3.gr new file mode 100644 index 00000000..839eaf79 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/dbload/e3.gr @@ -0,0 +1,9 @@ +rmin = 2 +rmax = 12 +qmin = 2 +qmax = 12 + +0 0 +1 5 +2 4 +3 3 \ No newline at end of file diff --git a/src/diffpy/utils/tests/testdata/loadmu.txt b/src/diffpy/utils/tests/testdata/loadmu.txt new file mode 100644 index 00000000..326c9492 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/loadmu.txt @@ -0,0 +1,25 @@ +rmin = 0 +rmed = 5 +rmax = 10 +max_num = 10 +min_num = 1 +refinepos_option = N +mpi_option = Y +db_option = LOCAL +pos = 3.5 +spd = 0.1 +qmin = 0 +qmax = 10 +qstep = invalid +qbroad = 0.2 +qdamp = 0.0001 +stype = Neutron +stru_str = baddata + +0 0 +1 0.00001 +2 0.00003 +3 0.00005 +4 0.00001 +5 -0.00003 +6 -0.00006 \ No newline at end of file diff --git a/src/diffpy/utils/tests/testdata/strumining.json b/src/diffpy/utils/tests/testdata/strumining.json new file mode 100644 index 00000000..1466ea51 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/strumining.json @@ -0,0 +1,17 @@ +{ + "r": [0, 1, 2, 3, 4], + "gr": [0.1, 0.2, 0.3, 0.4, 0.5], + "stru_str": "example", + "stype": "N", + "qdamp": 0.01, + "qbroad": 0.02, + "qmax": 10, + "qmin": 0, + "spd": 0.1, + "db_option": "MPD", + "mpi_option": "Y", + "refinepos_option": "Y", + "max_num": 10, + "rmax": 10, + "rmin": 0 +} \ No newline at end of file diff --git a/src/diffpy/utils/tests/testdata/targetdb.json b/src/diffpy/utils/tests/testdata/targetdb.json new file mode 100644 index 00000000..6c0d6d0c --- /dev/null +++ b/src/diffpy/utils/tests/testdata/targetdb.json @@ -0,0 +1,26 @@ +{ + "e1.gr": { + "r": "[0.0, 1.0, 2.0, 3.0]", + "gr": "[0.0, 0.0, 0.0, 0.0]", + "qmax": 10.0, + "qmin": 0.0, + "rmax": 10.0, + "rmin": 0.0 + }, + "e2.gr": { + "r": "[0.0, 1.0, 2.0, 3.0]", + "gr": "[1.0, 2.0, 3.0, 4.0]", + "qmax": 11.0, + "qmin": 1.0, + "rmax": 11.0, + "rmin": 1.0 + }, + "e3.gr": { + "r": "[0.0, 1.0, 2.0, 3.0]", + "gr": "[0.0, 5.0, 4.0, 3.0]", + "qmax": 12.0, + "qmin": 2.0, + "rmax": 12.0, + "rmin": 2.0 + } +} \ No newline at end of file diff --git a/src/diffpy/utils/tests/testdata/targetmu.json b/src/diffpy/utils/tests/testdata/targetmu.json new file mode 100644 index 00000000..80512311 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/targetmu.json @@ -0,0 +1,21 @@ +{ + "r": "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]", + "gr": "[0.0, 1e-05, 3e-05, 5e-05, 1e-05, -3e-05, -6e-05]", + "stru_str": "baddata", + "stype": "Neutron", + "qdamp": 0.0001, + "qbroad": 0.2, + "qmax": 10.0, + "qmin": 0.0, + "spd": 0.1, + "db_option": "LOCAL", + "mpi_option": "Y", + "refinepos_option": "N", + "max_num": 10.0, + "rmax": 10.0, + "rmin": 0.0, + "rmed": 5.0, + "min_num": 1.0, + "pos": 3.5, + "qstep": "invalid" +} \ No newline at end of file From a2171a0e38456ce0a76481c9952614db00c4f54c Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Thu, 27 Jul 2023 01:49:48 -0400 Subject: [PATCH 014/696] Fix paths for test --- src/diffpy/utils/tests/test_loadmetafile.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/tests/test_loadmetafile.py b/src/diffpy/utils/tests/test_loadmetafile.py index 9860ee82..9aad578c 100644 --- a/src/diffpy/utils/tests/test_loadmetafile.py +++ b/src/diffpy/utils/tests/test_loadmetafile.py @@ -2,6 +2,11 @@ from diffpy.utils.parsers import loadData from diffpy.utils.tests.testhelpers import datafile +import os +import filecmp + +tests_dir = os.path.dirname(os.path.abspath(locals().get('__file__', 'file.py'))) + generatedjson = datafile('tljson.json') targetjson = datafile('targetdb.json') @@ -12,15 +17,12 @@ def test_load_gr(): - import os - import filecmp - # generate json and apply schema - tddbload_list = os.listdir(os.getcwd() + "/testdata/dbload") + tddbload_list = os.listdir(os.path.join(tests_dir, "testdata", "dbload")) tddbload_list.sort() print(tddbload_list) for headerfile in tddbload_list: - headerfile = os.getcwd() + "/testdata/dbload/" + headerfile + headerfile = os.path.join(tests_dir, "testdata", "dbload", headerfile) hdata, rv = loadData(headerfile, headers=True) load_PDF_into_db(generatedjson, headerfile, hdata, rv, show_path=False) apply_schema(generatedjson, schemaname, multiple_entries=True) @@ -33,9 +35,6 @@ def test_load_gr(): def test_markup_gr(): - import os - import filecmp - # put into json and apply schema hdata, rv = loadData(muload, headers=True) markup_PDF(generatedmu, hdata, rv) From 0a6d9e9ef9037a7bb5ea55ee675f54e315657983 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 31 Jul 2023 12:40:07 -0400 Subject: [PATCH 015/696] Single-value return with updated test --- src/diffpy/utils/parsers/loaddata.py | 13 +++++++------ src/diffpy/utils/tests/test_loaddata.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 0e6ff88d..b7a6ddc4 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -26,7 +26,8 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar filename -- name of the file we want to load data from. minrows -- minimum number of rows in the first data block. All rows must have the same number of floating point values. - headers -- return also a dictionary of parameters specified in header + headers -- when true (default False), the function instead returns + a dictionary of parameters specified in header. hdel -- delimiter for parsing header information hignore -- ignore header rows beginning with any elements in the hignore list usecols -- zero-based index of columns to be loaded, by default use @@ -124,6 +125,11 @@ def countcolumnsvalues(line): # block was found here! if nrows >= minrows: break + + # Return header data if requested + if headers: + return hdata # Return, so do not proceed to reading datablock + # Return an empty array when no data found. # loadtxt would otherwise raise an exception on loading from EOF. if start is None: @@ -134,11 +140,6 @@ def countcolumnsvalues(line): # in case of trailing delimiters. kwargs.setdefault('usecols', list(range(ncvblock[0]))) rv = loadtxt(fid, **kwargs) - - # return headers if requested - if headers: - return hdata, rv - # otherwise do not return rv diff --git a/src/diffpy/utils/tests/test_loaddata.py b/src/diffpy/utils/tests/test_loaddata.py index 4b9ef939..a3e20b68 100644 --- a/src/diffpy/utils/tests/test_loaddata.py +++ b/src/diffpy/utils/tests/test_loaddata.py @@ -51,7 +51,7 @@ def test_loadData_headers(self): """ hignore = ["# ", "// ", "["] # ignore lines beginning with these strings delimiter = ": " # what our data should be separated by - hdata, rv = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) + hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) # only fourteen lines of data are formatted properly assert len(hdata) == 14 # check the following are floats From 160e6d93535f04ddf56cec4ed281f0e3f7df8b51 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 31 Jul 2023 12:43:55 -0400 Subject: [PATCH 016/696] Update docstring --- src/diffpy/utils/parsers/loaddata.py | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index b7a6ddc4..f60f10e1 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -23,21 +23,25 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar and constant number of columns. This seems to work for most of the datafiles including those generated by PDFGetX2. - filename -- name of the file we want to load data from. - minrows -- minimum number of rows in the first data block. - All rows must have the same number of floating point values. - headers -- when true (default False), the function instead returns - a dictionary of parameters specified in header. - hdel -- delimiter for parsing header information - hignore -- ignore header rows beginning with any elements in the hignore list - usecols -- zero-based index of columns to be loaded, by default use - all detected columns. The reading skips data blocks that - do not have the usecols-specified columns. - unpack -- return data as a sequence of columns that allows tuple - unpacking such as x, y = loadData(FILENAME, unpack=True). - Note transposing the loaded array as loadData(FILENAME).T - has the same effect. - kwargs -- keyword arguments that are passed to numpy.loadtxt + filename -- name of the file we want to load data from. + minrows -- minimum number of rows in the first data block. + All rows must have the same number of floating point values. + headers -- when true (default False), the function instead returns + a dictionary of parameters specified in header. + hdel -- (only used when headers enabled) delimiter for parsing header + information. + hignore -- (only used when headers enabled) ignore header rows beginning + with any elements in the hignore list + kwargs -- keyword arguments that are passed to numpy.loadtxt including + the following arguments below. + delimiter -- delimiter for the data in the block. + usecols -- zero-based index of columns to be loaded, by default use + all detected columns. The reading skips data blocks that + do not have the usecols-specified columns. + unpack -- return data as a sequence of columns that allows tuple + unpacking such as x, y = loadData(FILENAME, unpack=True). + Note transposing the loaded array as loadData(FILENAME).T + has the same effect. Return a numpy array of the data. See also numpy.loadtxt for more details. From 4a79b5305ea0f1ff1fe0ff3aa99afabbfd7e35b5 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 31 Jul 2023 15:44:32 -0400 Subject: [PATCH 017/696] Small edit docstring --- src/diffpy/utils/parsers/loaddata.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index f60f10e1..52a7bee1 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -27,7 +27,7 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar minrows -- minimum number of rows in the first data block. All rows must have the same number of floating point values. headers -- when true (default False), the function instead returns - a dictionary of parameters specified in header. + a dictionary of values specified in header. hdel -- (only used when headers enabled) delimiter for parsing header information. hignore -- (only used when headers enabled) ignore header rows beginning @@ -43,8 +43,9 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar Note transposing the loaded array as loadData(FILENAME).T has the same effect. - Return a numpy array of the data. - See also numpy.loadtxt for more details. + Return a numpy array of the data. (See also numpy.loadtxt for more details.) + If headers enabled, instead returns a dictionary of values read from header + information. """ from numpy import array, loadtxt # for storing header data From bc31ed0b069df4322e13e1ba52ebd973552d094f Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Tue, 1 Aug 2023 15:09:39 -0400 Subject: [PATCH 018/696] Examples in docstring, changelog --- CHANGELOG.md | 22 +++++++++++++++----- src/diffpy/utils/parsers/loaddata.py | 31 +++++++++++++++++----------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52dccd84..3d3abd12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,32 @@ # Release notes -## Version 3.1.0 – 2022-12-09 +## Version 3.2.0 – 2023-8-** ### Added -- Compatibility with Python 3.10, 3.9, 3.8 +- CI Coverage. +- New tests for loadData function. ### Changed -### Deprecated +- loadData function now toggleable. Can return either (a) data read from data blocks or (b) header +information stored above the data block. +- Exclude wx from tests. ### Removed -- Remove the support for Python 3.5, 3.6. +- Remove use of pkg_resources (deprecated). +- No longer use Travis. -### Fixed +## Version 3.1.0 – 2022-12-09 + +### Added + +- Compatibility with Python 3.10, 3.9, 3.8 + +### Removed + +- Remove the support for Python 3.5, 3.6. ## Version 3.0.0 -- 2019-03-12 diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 52a7bee1..ecf9583b 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -19,22 +19,30 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwargs): """Find and load data from a text file. - The data reading starts at the first matrix block of at least minrows rows - and constant number of columns. This seems to work for most of the - datafiles including those generated by PDFGetX2. + The data block is identified as the first matrix block of at least minrows rows + and constant number of columns. This seems to work for most of the datafiles including + those generated by PDFGetX2. filename -- name of the file we want to load data from. minrows -- minimum number of rows in the first data block. All rows must have the same number of floating point values. - headers -- when true (default False), the function instead returns - a dictionary of values specified in header. + headers -- when False (defualt), the function returns a numpy array of the + data in the data block. When True, the function instead returns a + dictionary of parameters and their corresponding values parsed from + header (information prior the data block). See hdel and hignore for + options to help with parsing header information. hdel -- (only used when headers enabled) delimiter for parsing header - information. + information (default '='). e.g. using default hdel, the line + 'parameter = p_value' is put into the dictionary as + {parameter: p_value}. hignore -- (only used when headers enabled) ignore header rows beginning - with any elements in the hignore list + with any elements in the hignore list. e.g. hignore=['# ', '['] + means the following lines are skipped: '# qmax=10', '[defaults]'. kwargs -- keyword arguments that are passed to numpy.loadtxt including - the following arguments below. - delimiter -- delimiter for the data in the block. + the following arguments below. (See also numpy.loadtxt for more + details.) + delimiter -- delimiter for the data in the block (default None). e.g. for + comma-separated values in the datablock, set delimiter to ','. usecols -- zero-based index of columns to be loaded, by default use all detected columns. The reading skips data blocks that do not have the usecols-specified columns. @@ -43,9 +51,8 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar Note transposing the loaded array as loadData(FILENAME).T has the same effect. - Return a numpy array of the data. (See also numpy.loadtxt for more details.) - If headers enabled, instead returns a dictionary of values read from header - information. + Return a numpy array of the data. If headers enabled, instead returns a + dictionary of parameters read from the header. """ from numpy import array, loadtxt # for storing header data From 699e60478e6fe10f33604266bd63108923995430 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Tue, 1 Aug 2023 15:12:17 -0400 Subject: [PATCH 019/696] Fix to previous commit --- src/diffpy/utils/parsers/loaddata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index ecf9583b..859d18d2 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -41,10 +41,10 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar kwargs -- keyword arguments that are passed to numpy.loadtxt including the following arguments below. (See also numpy.loadtxt for more details.) - delimiter -- delimiter for the data in the block (default None). e.g. for - comma-separated values in the datablock, set delimiter to ','. + delimiter -- delimiter for the data in the block (default use whitespace). + For comma-separated data blocks, set delimiter to ','. usecols -- zero-based index of columns to be loaded, by default use - all detected columns. The reading skips data blocks that + all detected columns. The reading skips data blocks that do not have the usecols-specified columns. unpack -- return data as a sequence of columns that allows tuple unpacking such as x, y = loadData(FILENAME, unpack=True). From ac4d2ff74fc1bcba22519de976ce62dcb3da60d5 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 7 Aug 2023 09:27:16 -0400 Subject: [PATCH 020/696] Edit functionality, use tmp_path fixture for tests --- conda-recipe/run_test.py | 4 + src/diffpy/utils/parsers/__init__.py | 2 +- src/diffpy/utils/parsers/loadmetafile.py | 90 ++++++++++++++----- src/diffpy/utils/tests/test_loadmetafile.py | 42 +++++---- src/diffpy/utils/tests/testdata/targetdb.json | 12 +-- src/diffpy/utils/tests/testdata/targetmu.json | 4 +- 6 files changed, 109 insertions(+), 45 deletions(-) diff --git a/conda-recipe/run_test.py b/conda-recipe/run_test.py index 039c5a2c..a4edf44d 100644 --- a/conda-recipe/run_test.py +++ b/conda-recipe/run_test.py @@ -1,4 +1,8 @@ #!/usr/bin/env python +import sys +import pathlib +sys.path.append((pathlib.Path.cwd().parent.absolute() / "src").as_posix()) + import diffpy.utils.tests assert diffpy.utils.tests.test().wasSuccessful() diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index 217d63cc..5aeeb7af 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -17,7 +17,7 @@ """ from .loaddata import loadData -from .loadmetafile import load_PDF_into_db, markup_PDF, apply_schema +from .loadmetafile import load_PDF_into_db, markup_PDF, apply_schema_to_file, markup_oneline from .resample import resample # silence the pyflakes syntax checker diff --git a/src/diffpy/utils/parsers/loadmetafile.py b/src/diffpy/utils/parsers/loadmetafile.py index dc48ff74..a50555f2 100644 --- a/src/diffpy/utils/parsers/loadmetafile.py +++ b/src/diffpy/utils/parsers/loadmetafile.py @@ -16,23 +16,22 @@ import pathlib import json -from diffpy.utils.parsers import loadData - # FIXME: add support for yaml, xml supported_formats = ['.json'] -def load_PDF_into_db(dbname, pdfname, hddata: dict, rv: list, oneline=True, show_path=True): - """Load PDF header and base data into a database file. +def load_PDF_into_db(dbname, pdfname, hddata: dict, rv: list, show_path=True): + """Load an entry consisting of PDF header and base data into a database file. Requires hdata and rv generated from loadData. - dbname -- name of the database file to load into. - pdfname -- name of the PDF file. - hddata -- Dictionary of PDF metadata generated by loadData. - rv -- List of PDF (r, gr) pairs generated by loadData. - oneline -- store r and gr arrays in a single line for compactness (default True). - show_path -- include a PDF_path element in the database entry (default True). + dbname -- name of the database file to load an entry into. + pdfname -- name of the PDF file. + hddata -- Dictionary of PDF metadata generated by loadData. + rv -- List of PDF (r, gr) pairs generated by loadData. + show_path -- include a PDF_path element in the database entry (default True). + + Returns the dictionary loaded from/into the updated database file. """ # new file or update existing = False @@ -49,10 +48,7 @@ def load_PDF_into_db(dbname, pdfname, hddata: dict, rv: list, oneline=True, show data.update({'PDF_path': grpath}) # add r, gr, and header metadata - if oneline: - data.update({'r': str(list(rv[:, 0])), 'gr': str(list(rv[:, 1]))}) - else: - data.update({'r': list(rv[:, 0]), 'gr': list(rv[:, 1])}) + data.update({'r': list(rv[:, 0]), 'gr': list(rv[:, 1])}) data.update(hddata) # parse name using pathlib and generate json entry @@ -69,7 +65,8 @@ def load_PDF_into_db(dbname, pdfname, hddata: dict, rv: list, oneline=True, show # dump if non-existing if not existing: with open(dbname, 'w') as jsonfile: - jsonfile.write(json.dumps(entry, indent=2)) + pdfs = entry # for return + json.dump(pdfs, jsonfile, indent=2) # update if existing else: @@ -80,20 +77,30 @@ def load_PDF_into_db(dbname, pdfname, hddata: dict, rv: list, oneline=True, show # dump to string first for formatting json.dump(pdfs, json_write, indent=2) + return pdfs -def markup_PDF(muname, hddata: dict, rv: list): - # FIXME: for REST API, remove if better ways to implement - """Put PDF file information in a markup language file. - mumane -- name of markup file to put data into. +def markup_PDF(hddata: dict, rv: list, muname=None): + # FIXME: may be better suited for REST API package, not diffpy.utils + """Put PDF file information into a dictionary. + hddata -- Dictionary of metadata. rv -- List of (r, gr) pairs. + muname -- file to save into (default None, no saving occurs). + + Returns the dictionary loaded from/into markup file. """ # gather data data = {} - data.update({'r': str(list(rv[:, 0])), 'gr': str(list(rv[:, 1]))}) + data.update({'r': list(rv[:, 0]), 'gr': list(rv[:, 1])}) data.update(hddata) + + # return directly + if muname is None: + return data + + # save to disk when enabled extension = pathlib.Path(muname).suffix if extension not in supported_formats: raise Exception(f"Format of {muname} is not supported.") @@ -103,8 +110,45 @@ def markup_PDF(muname, hddata: dict, rv: list): with open(muname, 'w') as json_write: json.dump(data, json_write, indent=2) + return data + + +def markup_oneline(filename): + """Reformat lists in markup languages to take up only one line. -def apply_schema(filename, schemaname, multiple_entries=False): + Works well when only lists are surrounded by square brackets and no other data is comma and newline separated. + + filename -- name of markup file to reformat. + """ + + # check file type + extension = pathlib.Path(filename).suffix + if extension not in supported_formats: + raise Exception(f"Format of {filename} is not supported.") + + if extension == '.json': + # cannot easily do regex substitution since lists are of floats + with open(filename, 'r+') as json_file: + lines = json_file.readlines() + json_file.seek(0) + json_file.truncate() + + s_flag = False + for line in lines: + if "\"r\": [" in line or "\"gr\": [" in line: + s_flag = True + updated_line = line[:-1] + elif "]," in line: + s_flag = False + updated_line = f"{updated_line[:-1]}{line.strip()}\n" + json_file.write(updated_line) + elif s_flag: + updated_line += f"{line[:-1].strip()} " + else: + json_file.write(line) + + +def apply_schema_to_file(filename, schemaname, multiple_entries=False): """ Reformat a file so relevant entries match the same order as a schema file. Other entries are put at the end in the same order. @@ -112,6 +156,8 @@ def apply_schema(filename, schemaname, multiple_entries=False): schemaname -- name of schema to apply. multiple_entries -- True if database file (i.e. those generated by load_PDF_into_db). False if data from a single file (i.e. those generated by markup_PDF). + + Returns the dictionary loaded from/into the reformatted file. """ # ensure proper extension @@ -162,3 +208,5 @@ def apply_schema(filename, schemaname, multiple_entries=False): reformatted_dict.update(data_dict) with open(filename, 'w') as json_write: json.dump(reformatted_dict, json_write, indent=2) + + return reformatted_dict diff --git a/src/diffpy/utils/tests/test_loadmetafile.py b/src/diffpy/utils/tests/test_loadmetafile.py index 9aad578c..09eb1b25 100644 --- a/src/diffpy/utils/tests/test_loadmetafile.py +++ b/src/diffpy/utils/tests/test_loadmetafile.py @@ -1,4 +1,4 @@ -from diffpy.utils.parsers import load_PDF_into_db, markup_PDF, apply_schema +from diffpy.utils.parsers import load_PDF_into_db, markup_PDF, apply_schema_to_file, markup_oneline from diffpy.utils.parsers import loadData from diffpy.utils.tests.testhelpers import datafile @@ -7,40 +7,52 @@ tests_dir = os.path.dirname(os.path.abspath(locals().get('__file__', 'file.py'))) -generatedjson = datafile('tljson.json') targetjson = datafile('targetdb.json') schemaname = datafile('strumining.json') muload = datafile('loadmu.txt') -generatedmu = datafile('tmujson.json') targetmu = datafile('targetmu.json') -def test_load_gr(): +def test_load_gr(tmp_path): # generate json and apply schema + generatedjson = tmp_path / "generated_db.json" tddbload_list = os.listdir(os.path.join(tests_dir, "testdata", "dbload")) tddbload_list.sort() - print(tddbload_list) for headerfile in tddbload_list: headerfile = os.path.join(tests_dir, "testdata", "dbload", headerfile) - hdata, rv = loadData(headerfile, headers=True) - load_PDF_into_db(generatedjson, headerfile, hdata, rv, show_path=False) - apply_schema(generatedjson, schemaname, multiple_entries=True) + hdata = loadData(headerfile, headers=True) + rv = loadData(headerfile) + db_data = load_PDF_into_db(generatedjson, headerfile, hdata, rv, show_path=False) + apply_schema_to_file(generatedjson, schemaname, multiple_entries=True) + markup_oneline(generatedjson) # compare to target + # first compare if base data is same + import json + with open(targetjson, 'r') as target: + target_db_data = json.load(target) + assert target_db_data == db_data + # then compare file structure/organization assert filecmp.cmp(generatedjson, targetjson) - # cleanup - os.remove(generatedjson) - -def test_markup_gr(): +def test_markup_gr(tmp_path): # put into json and apply schema - hdata, rv = loadData(muload, headers=True) - markup_PDF(generatedmu, hdata, rv) - apply_schema(generatedmu, schemaname) + generatedmu = tmp_path / "generated_markup.json" + hdata = loadData(muload, headers=True) + rv = loadData(muload) + data = markup_PDF(hdata, rv, generatedmu) + apply_schema_to_file(generatedmu, schemaname) + markup_oneline(generatedmu) # check against target + # first compare data is same + import json + with open(targetmu, 'r') as target: + target_data = json.load(target) + assert target_data == data + # then compare structure assert filecmp.cmp(generatedmu, targetmu) # cleanup diff --git a/src/diffpy/utils/tests/testdata/targetdb.json b/src/diffpy/utils/tests/testdata/targetdb.json index 6c0d6d0c..4d6ec119 100644 --- a/src/diffpy/utils/tests/testdata/targetdb.json +++ b/src/diffpy/utils/tests/testdata/targetdb.json @@ -1,23 +1,23 @@ { "e1.gr": { - "r": "[0.0, 1.0, 2.0, 3.0]", - "gr": "[0.0, 0.0, 0.0, 0.0]", + "r": [0.0, 1.0, 2.0, 3.0], + "gr": [0.0, 0.0, 0.0, 0.0], "qmax": 10.0, "qmin": 0.0, "rmax": 10.0, "rmin": 0.0 }, "e2.gr": { - "r": "[0.0, 1.0, 2.0, 3.0]", - "gr": "[1.0, 2.0, 3.0, 4.0]", + "r": [0.0, 1.0, 2.0, 3.0], + "gr": [1.0, 2.0, 3.0, 4.0], "qmax": 11.0, "qmin": 1.0, "rmax": 11.0, "rmin": 1.0 }, "e3.gr": { - "r": "[0.0, 1.0, 2.0, 3.0]", - "gr": "[0.0, 5.0, 4.0, 3.0]", + "r": [0.0, 1.0, 2.0, 3.0], + "gr": [0.0, 5.0, 4.0, 3.0], "qmax": 12.0, "qmin": 2.0, "rmax": 12.0, diff --git a/src/diffpy/utils/tests/testdata/targetmu.json b/src/diffpy/utils/tests/testdata/targetmu.json index 80512311..967d3b99 100644 --- a/src/diffpy/utils/tests/testdata/targetmu.json +++ b/src/diffpy/utils/tests/testdata/targetmu.json @@ -1,6 +1,6 @@ { - "r": "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]", - "gr": "[0.0, 1e-05, 3e-05, 5e-05, 1e-05, -3e-05, -6e-05]", + "r": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], + "gr": [0.0, 1e-05, 3e-05, 5e-05, 1e-05, -3e-05, -6e-05], "stru_str": "baddata", "stype": "Neutron", "qdamp": 0.0001, From 8af9b7bf818ef9c992916ecadfacd0d3f6179509 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 7 Aug 2023 11:45:39 -0400 Subject: [PATCH 021/696] Remove os cleanup, add read from db file --- src/diffpy/utils/parsers/__init__.py | 2 +- src/diffpy/utils/parsers/loadmetafile.py | 21 +++++++++++++++++++++ src/diffpy/utils/tests/test_loadmetafile.py | 16 +++++----------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index 5aeeb7af..97aec171 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -17,7 +17,7 @@ """ from .loaddata import loadData -from .loadmetafile import load_PDF_into_db, markup_PDF, apply_schema_to_file, markup_oneline +from .loadmetafile import load_PDF_into_db, load_from_db, markup_PDF, apply_schema_to_file, markup_oneline from .resample import resample # silence the pyflakes syntax checker diff --git a/src/diffpy/utils/parsers/loadmetafile.py b/src/diffpy/utils/parsers/loadmetafile.py index a50555f2..645eab73 100644 --- a/src/diffpy/utils/parsers/loadmetafile.py +++ b/src/diffpy/utils/parsers/loadmetafile.py @@ -80,6 +80,27 @@ def load_PDF_into_db(dbname, pdfname, hddata: dict, rv: list, show_path=True): return pdfs +def load_from_db(filename): + """Load a dictionary from a database file. + + filename -- database file to load from. + + Returns a dictionary of database information. + """ + + # check if supported type + extension = pathlib.Path(filename).suffix + if extension not in supported_formats: + raise Exception(f"Format of {filename} is not supported.") + + # json + if extension == '.json': + with open(filename, 'r') as json_file: + j_dict = json.load(json_file) + + return j_dict + + def markup_PDF(hddata: dict, rv: list, muname=None): # FIXME: may be better suited for REST API package, not diffpy.utils """Put PDF file information into a dictionary. diff --git a/src/diffpy/utils/tests/test_loadmetafile.py b/src/diffpy/utils/tests/test_loadmetafile.py index 09eb1b25..f91bfaeb 100644 --- a/src/diffpy/utils/tests/test_loadmetafile.py +++ b/src/diffpy/utils/tests/test_loadmetafile.py @@ -1,4 +1,4 @@ -from diffpy.utils.parsers import load_PDF_into_db, markup_PDF, apply_schema_to_file, markup_oneline +from diffpy.utils.parsers import load_PDF_into_db, load_from_db, markup_PDF, apply_schema_to_file, markup_oneline from diffpy.utils.parsers import loadData from diffpy.utils.tests.testhelpers import datafile @@ -30,9 +30,8 @@ def test_load_gr(tmp_path): # compare to target # first compare if base data is same import json - with open(targetjson, 'r') as target: - target_db_data = json.load(target) - assert target_db_data == db_data + target_db_data = load_from_db(targetjson) + assert target_db_data == db_data # then compare file structure/organization assert filecmp.cmp(generatedjson, targetjson) @@ -48,12 +47,7 @@ def test_markup_gr(tmp_path): # check against target # first compare data is same - import json - with open(targetmu, 'r') as target: - target_data = json.load(target) - assert target_data == data + target_data = load_from_db(targetmu) + assert target_data == data # then compare structure assert filecmp.cmp(generatedmu, targetmu) - - # cleanup - os.remove(generatedmu) From 4e5c3f11e0196715e134c1316ca5dd692a068438 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Tue, 8 Aug 2023 23:31:21 -0400 Subject: [PATCH 022/696] Make loadmetadata more robust --- CHANGELOG.md | 3 +- src/diffpy/utils/parsers/__init__.py | 2 +- src/diffpy/utils/parsers/loaddata.py | 10 +- src/diffpy/utils/parsers/loadmetafile.py | 163 +++++++++--------- src/diffpy/utils/tests/test_loadmetafile.py | 33 ++-- .../{targetdb.json => generated_db.json} | 0 .../utils/tests/testdata/targetjson.json | 26 +++ 7 files changed, 134 insertions(+), 103 deletions(-) rename src/diffpy/utils/tests/testdata/{targetdb.json => generated_db.json} (100%) create mode 100644 src/diffpy/utils/tests/testdata/targetjson.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d3abd12..a8965748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Release notes -## Version 3.2.0 – 2023-8-** +## Version 3.2.0 – 2023-08-** ### Added @@ -11,7 +11,6 @@ - loadData function now toggleable. Can return either (a) data read from data blocks or (b) header information stored above the data block. -- Exclude wx from tests. ### Removed diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index 97aec171..11333b6a 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -17,7 +17,7 @@ """ from .loaddata import loadData -from .loadmetafile import load_PDF_into_db, load_from_db, markup_PDF, apply_schema_to_file, markup_oneline +from .loadmetafile import serialize_data, deserialize_data, apply_schema_to_file, serial_oneline from .resample import resample # silence the pyflakes syntax checker diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 859d18d2..54825ac1 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -51,8 +51,8 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar Note transposing the loaded array as loadData(FILENAME).T has the same effect. - Return a numpy array of the data. If headers enabled, instead returns a - dictionary of parameters read from the header. + Return a numpy array of the data (data_block). If headers enabled, instead returns a + dictionary of parameters read from the header (hddata). """ from numpy import array, loadtxt # for storing header data @@ -145,14 +145,14 @@ def countcolumnsvalues(line): # Return an empty array when no data found. # loadtxt would otherwise raise an exception on loading from EOF. if start is None: - rv = array([], dtype=float) + data_block = array([], dtype=float) else: fid.seek(start) # always use usecols argument so that loadtxt does not crash # in case of trailing delimiters. kwargs.setdefault('usecols', list(range(ncvblock[0]))) - rv = loadtxt(fid, **kwargs) - return rv + data_block = loadtxt(fid, **kwargs) + return data_block class TextDataLoader(object): diff --git a/src/diffpy/utils/parsers/loadmetafile.py b/src/diffpy/utils/parsers/loadmetafile.py index 645eab73..b645203e 100644 --- a/src/diffpy/utils/parsers/loadmetafile.py +++ b/src/diffpy/utils/parsers/loadmetafile.py @@ -20,68 +20,110 @@ supported_formats = ['.json'] -def load_PDF_into_db(dbname, pdfname, hddata: dict, rv: list, show_path=True): - """Load an entry consisting of PDF header and base data into a database file. - - Requires hdata and rv generated from loadData. - - dbname -- name of the database file to load an entry into. - pdfname -- name of the PDF file. - hddata -- Dictionary of PDF metadata generated by loadData. - rv -- List of PDF (r, gr) pairs generated by loadData. - show_path -- include a PDF_path element in the database entry (default True). +def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_colnames=None, serial_file=None): + """Serialize file data into a dictionary. Can also save dictionary into a serial language file. + Dictionary is formatted as {filename: data}. + + Requires hdata and data_table generated from loadData. + + filename -- name of the file whose data is being serialized. + hdata -- Dictionary of PDF metadata generated by loadData. + data_table -- List storing parsed by loadData. + dt_colnames -- List containing names of each column in data_table. Every name in + data_table_cols will be put into the Dictionary as a key with a value + of that column in data_table (stored as a List). Put None for + columns without names. If dt_cols has less non-None entries + than columns in data_table, the pair {'data table': data_table} will be put + in the dictionary. (Default None: only entry {'data table': data_table} + will be added to dictionary.) + show_path -- include a path element in the database entry (default True). + If 'path' is not included in hddata, extract path from filename. + serial_file -- serial language file to dump dictionary into. Returns the dictionary loaded from/into the updated database file. """ - # new file or update - existing = False - if pathlib.Path.is_file(dbname): - existing = True - - # collect entry - with open(pdfname, 'r') as grfile: - data = {} - - # add path - grpath = grfile.name - if show_path: - data.update({'PDF_path': grpath}) - # add r, gr, and header metadata - data.update({'r': list(rv[:, 0]), 'gr': list(rv[:, 1])}) - data.update(hddata) - - # parse name using pathlib and generate json entry - name = pathlib.Path(grpath).name - entry = {name: data} + # compile data_table and hddata together + data = {} + # handle getting name of file for variety of filename types + with open(filename, 'r') as file_path: + abs_path = pathlib.Path(file_path.name).resolve() + # add path to start of data if requested + if show_path and 'path' not in hdata.keys(): + data.update({'path': abs_path.name}) + # title the entry with name of file (taken from end of path) + title = abs_path.name + + # first add named columns in dt_cols + num_columns = [len(row) for row in data_table] + max_columns = max(num_columns) + num_col_names = len(dt_colnames) + if max_columns < num_col_names: # assume numpy.loadtxt gives non-irregular array + raise Exception("More entries in dt_colnames than columns in data_table.") + named_columns = 0 + for idx in range(num_col_names): + colname = dt_colnames[idx] + if colname is not None: + data.update({colname: list(data_table[:, idx])}) + named_columns += 1 + + # second add data in hddata dict + data.update(hdata) + + # finally add data_table as an entry named 'data table' if not all columns were parsed + if named_columns < max_columns: + if 'data table' not in data.keys(): + data.update({'data table': data_table}) + else: # if 'data table' is already a key, keep adding primes to the end + dt_name = 'data table' + while dt_name in data.keys(): + dt_name += " prime" + data.update({dt_name: data_table}) + + # parse name using pathlib and generate dictionary entry + entry = {title: data} + + # no save + if serial_file is None: + return entry + + # saving/updating file # check if supported type - extension = pathlib.Path(dbname).suffix + extension = pathlib.Path(serial_file).suffix if extension not in supported_formats: - raise Exception(f"Format of {dbname} is not supported.") + raise Exception(f"Format of {serial_file} is not supported.") + + # new file or update + existing = False + try: + open(serial_file) + existing = True + except FileNotFoundError: + pass # json if extension == '.json': # dump if non-existing if not existing: - with open(dbname, 'w') as jsonfile: - pdfs = entry # for return - json.dump(pdfs, jsonfile, indent=2) + with open(serial_file, 'w') as jsonfile: + file_data = entry # for return + json.dump(file_data, jsonfile, indent=2) # update if existing else: - with open(dbname, 'r') as json_read: - pdfs = json.load(json_read) - pdfs.update(entry) - with open(dbname, 'w') as json_write: + with open(serial_file, 'r') as json_read: + file_data = json.load(json_read) + file_data.update(entry) + with open(serial_file, 'w') as json_write: # dump to string first for formatting - json.dump(pdfs, json_write, indent=2) + json.dump(file_data, json_write, indent=2) - return pdfs + return file_data -def load_from_db(filename): - """Load a dictionary from a database file. +def deserialize_data(filename): + """Load a dictionary from a serial file. filename -- database file to load from. @@ -101,40 +143,7 @@ def load_from_db(filename): return j_dict -def markup_PDF(hddata: dict, rv: list, muname=None): - # FIXME: may be better suited for REST API package, not diffpy.utils - """Put PDF file information into a dictionary. - - hddata -- Dictionary of metadata. - rv -- List of (r, gr) pairs. - muname -- file to save into (default None, no saving occurs). - - Returns the dictionary loaded from/into markup file. - """ - - # gather data - data = {} - data.update({'r': list(rv[:, 0]), 'gr': list(rv[:, 1])}) - data.update(hddata) - - # return directly - if muname is None: - return data - - # save to disk when enabled - extension = pathlib.Path(muname).suffix - if extension not in supported_formats: - raise Exception(f"Format of {muname} is not supported.") - - # dumps into file, automatically overwrites - if extension == '.json': - with open(muname, 'w') as json_write: - json.dump(data, json_write, indent=2) - - return data - - -def markup_oneline(filename): +def serial_oneline(filename): """Reformat lists in markup languages to take up only one line. Works well when only lists are surrounded by square brackets and no other data is comma and newline separated. diff --git a/src/diffpy/utils/tests/test_loadmetafile.py b/src/diffpy/utils/tests/test_loadmetafile.py index f91bfaeb..3e3e5d11 100644 --- a/src/diffpy/utils/tests/test_loadmetafile.py +++ b/src/diffpy/utils/tests/test_loadmetafile.py @@ -1,4 +1,4 @@ -from diffpy.utils.parsers import load_PDF_into_db, load_from_db, markup_PDF, apply_schema_to_file, markup_oneline +from diffpy.utils.parsers import serialize_data, deserialize_data, apply_schema_to_file, serial_oneline from diffpy.utils.parsers import loadData from diffpy.utils.tests.testhelpers import datafile @@ -7,47 +7,44 @@ tests_dir = os.path.dirname(os.path.abspath(locals().get('__file__', 'file.py'))) -targetjson = datafile('targetdb.json') - +targetjson = datafile('targetjson.json') schemaname = datafile('strumining.json') + muload = datafile('loadmu.txt') targetmu = datafile('targetmu.json') def test_load_gr(tmp_path): # generate json and apply schema - generatedjson = tmp_path / "generated_db.json" + generatedjson = tmp_path / "generated_serialization.json" tddbload_list = os.listdir(os.path.join(tests_dir, "testdata", "dbload")) tddbload_list.sort() for headerfile in tddbload_list: headerfile = os.path.join(tests_dir, "testdata", "dbload", headerfile) hdata = loadData(headerfile, headers=True) - rv = loadData(headerfile) - db_data = load_PDF_into_db(generatedjson, headerfile, hdata, rv, show_path=False) + data_table = loadData(headerfile) + db_data = serialize_data(headerfile, hdata, data_table, dt_colnames=['r', 'gr'], + show_path=False, serial_file=generatedjson) apply_schema_to_file(generatedjson, schemaname, multiple_entries=True) - markup_oneline(generatedjson) + serial_oneline(generatedjson) # compare to target # first compare if base data is same import json - target_db_data = load_from_db(targetjson) + target_db_data = deserialize_data(targetjson) assert target_db_data == db_data # then compare file structure/organization assert filecmp.cmp(generatedjson, targetjson) +# FIXME: tests for REST API, remove after merge def test_markup_gr(tmp_path): # put into json and apply schema generatedmu = tmp_path / "generated_markup.json" hdata = loadData(muload, headers=True) - rv = loadData(muload) - data = markup_PDF(hdata, rv, generatedmu) - apply_schema_to_file(generatedmu, schemaname) - markup_oneline(generatedmu) - - # check against target - # first compare data is same - target_data = load_from_db(targetmu) + data_table = loadData(muload) + data = serialize_data(muload, hdata, data_table, dt_colnames=['r', 'gr'], show_path=False).get('loadmu.txt') + + # compare data is same + target_data = deserialize_data(targetmu) assert target_data == data - # then compare structure - assert filecmp.cmp(generatedmu, targetmu) diff --git a/src/diffpy/utils/tests/testdata/targetdb.json b/src/diffpy/utils/tests/testdata/generated_db.json similarity index 100% rename from src/diffpy/utils/tests/testdata/targetdb.json rename to src/diffpy/utils/tests/testdata/generated_db.json diff --git a/src/diffpy/utils/tests/testdata/targetjson.json b/src/diffpy/utils/tests/testdata/targetjson.json new file mode 100644 index 00000000..4d6ec119 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/targetjson.json @@ -0,0 +1,26 @@ +{ + "e1.gr": { + "r": [0.0, 1.0, 2.0, 3.0], + "gr": [0.0, 0.0, 0.0, 0.0], + "qmax": 10.0, + "qmin": 0.0, + "rmax": 10.0, + "rmin": 0.0 + }, + "e2.gr": { + "r": [0.0, 1.0, 2.0, 3.0], + "gr": [1.0, 2.0, 3.0, 4.0], + "qmax": 11.0, + "qmin": 1.0, + "rmax": 11.0, + "rmin": 1.0 + }, + "e3.gr": { + "r": [0.0, 1.0, 2.0, 3.0], + "gr": [0.0, 5.0, 4.0, 3.0], + "qmax": 12.0, + "qmin": 2.0, + "rmax": 12.0, + "rmin": 2.0 + } +} \ No newline at end of file From f577dd1c23cabba37b597a920a010ec5166423e0 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Tue, 8 Aug 2023 23:41:12 -0400 Subject: [PATCH 023/696] Move item in changelog --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8965748..028c0d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,6 @@ - CI Coverage. - New tests for loadData function. - -### Changed - - loadData function now toggleable. Can return either (a) data read from data blocks or (b) header information stored above the data block. From 7f3618efbc75fce865bc1119989282f865ace620 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Tue, 8 Aug 2023 23:45:07 -0400 Subject: [PATCH 024/696] Missed comment --- src/diffpy/utils/parsers/loaddata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 54825ac1..9c3f83bc 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -21,7 +21,7 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar The data block is identified as the first matrix block of at least minrows rows and constant number of columns. This seems to work for most of the datafiles including - those generated by PDFGetX2. + those generated by diffpy programs. filename -- name of the file we want to load data from. minrows -- minimum number of rows in the first data block. From 6116cf33f3e4e5cc05aaf51f778f18da9a1d9036 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Fri, 11 Aug 2023 09:44:28 -0400 Subject: [PATCH 025/696] Refactoring and improve coverage --- src/diffpy/utils/parsers/__init__.py | 3 +- src/diffpy/utils/parsers/custom_exceptions.py | 45 ++++ src/diffpy/utils/parsers/loadmetafile.py | 242 ------------------ src/diffpy/utils/parsers/serialization.py | 151 +++++++++++ src/diffpy/utils/tests/test_loadmetafile.py | 50 ---- src/diffpy/utils/tests/test_serialization.py | 91 +++++++ .../testdata/{loadmu.txt => loadfile.txt} | 1 + src/diffpy/utils/tests/testdata/targetmu.json | 21 -- src/diffpy/utils/tests/testdata/wrong.type | 1 + 9 files changed, 291 insertions(+), 314 deletions(-) create mode 100644 src/diffpy/utils/parsers/custom_exceptions.py delete mode 100644 src/diffpy/utils/parsers/loadmetafile.py create mode 100644 src/diffpy/utils/parsers/serialization.py delete mode 100644 src/diffpy/utils/tests/test_loadmetafile.py create mode 100644 src/diffpy/utils/tests/test_serialization.py rename src/diffpy/utils/tests/testdata/{loadmu.txt => loadfile.txt} (92%) delete mode 100644 src/diffpy/utils/tests/testdata/targetmu.json create mode 100644 src/diffpy/utils/tests/testdata/wrong.type diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index 11333b6a..1965df9f 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -17,10 +17,11 @@ """ from .loaddata import loadData -from .loadmetafile import serialize_data, deserialize_data, apply_schema_to_file, serial_oneline +from .serialization import serialize_data, deserialize_data from .resample import resample # silence the pyflakes syntax checker assert loadData or resample or True +assert serialize_data or deserialize_data or True # End of file diff --git a/src/diffpy/utils/parsers/custom_exceptions.py b/src/diffpy/utils/parsers/custom_exceptions.py new file mode 100644 index 00000000..347edd43 --- /dev/null +++ b/src/diffpy/utils/parsers/custom_exceptions.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.utils by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## + +class UnsupportedTypeError(Exception): + """For file types not supported by our parsers. + + supported_types -- List of supported types. + file -- file triggering the error. + message -- for writing a custom message. + """ + + def __init__(self, file, supported_types=None, message=None): + if message is None: + self.message = f"The file {file} is not supported." + if supported_types is not None: + self.message += " Supported file types include: " + for t in supported_types: + self.message += t + ", " + self.message = self.message[:-2] + "." + super().__init__(self.message) + + +class ImproperSizeError(Exception): + """When the size of an object does not match expectations. + + bad_object -- Object with improper size. + message -- for writing a custom message. + """ + + def __init__(self, bad_object, message=None): + if message is None: + self.message = f"The size of {bad_object} is different than expected." + super().__init__(self.message) diff --git a/src/diffpy/utils/parsers/loadmetafile.py b/src/diffpy/utils/parsers/loadmetafile.py deleted file mode 100644 index b645203e..00000000 --- a/src/diffpy/utils/parsers/loadmetafile.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.utils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## - -import pathlib -import json - -# FIXME: add support for yaml, xml -supported_formats = ['.json'] - - -def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_colnames=None, serial_file=None): - """Serialize file data into a dictionary. Can also save dictionary into a serial language file. - Dictionary is formatted as {filename: data}. - - Requires hdata and data_table generated from loadData. - - filename -- name of the file whose data is being serialized. - hdata -- Dictionary of PDF metadata generated by loadData. - data_table -- List storing parsed by loadData. - dt_colnames -- List containing names of each column in data_table. Every name in - data_table_cols will be put into the Dictionary as a key with a value - of that column in data_table (stored as a List). Put None for - columns without names. If dt_cols has less non-None entries - than columns in data_table, the pair {'data table': data_table} will be put - in the dictionary. (Default None: only entry {'data table': data_table} - will be added to dictionary.) - show_path -- include a path element in the database entry (default True). - If 'path' is not included in hddata, extract path from filename. - serial_file -- serial language file to dump dictionary into. - - Returns the dictionary loaded from/into the updated database file. - """ - - # compile data_table and hddata together - data = {} - - # handle getting name of file for variety of filename types - with open(filename, 'r') as file_path: - abs_path = pathlib.Path(file_path.name).resolve() - # add path to start of data if requested - if show_path and 'path' not in hdata.keys(): - data.update({'path': abs_path.name}) - # title the entry with name of file (taken from end of path) - title = abs_path.name - - # first add named columns in dt_cols - num_columns = [len(row) for row in data_table] - max_columns = max(num_columns) - num_col_names = len(dt_colnames) - if max_columns < num_col_names: # assume numpy.loadtxt gives non-irregular array - raise Exception("More entries in dt_colnames than columns in data_table.") - named_columns = 0 - for idx in range(num_col_names): - colname = dt_colnames[idx] - if colname is not None: - data.update({colname: list(data_table[:, idx])}) - named_columns += 1 - - # second add data in hddata dict - data.update(hdata) - - # finally add data_table as an entry named 'data table' if not all columns were parsed - if named_columns < max_columns: - if 'data table' not in data.keys(): - data.update({'data table': data_table}) - else: # if 'data table' is already a key, keep adding primes to the end - dt_name = 'data table' - while dt_name in data.keys(): - dt_name += " prime" - data.update({dt_name: data_table}) - - # parse name using pathlib and generate dictionary entry - entry = {title: data} - - # no save - if serial_file is None: - return entry - - # saving/updating file - # check if supported type - extension = pathlib.Path(serial_file).suffix - if extension not in supported_formats: - raise Exception(f"Format of {serial_file} is not supported.") - - # new file or update - existing = False - try: - open(serial_file) - existing = True - except FileNotFoundError: - pass - - # json - if extension == '.json': - # dump if non-existing - if not existing: - with open(serial_file, 'w') as jsonfile: - file_data = entry # for return - json.dump(file_data, jsonfile, indent=2) - - # update if existing - else: - with open(serial_file, 'r') as json_read: - file_data = json.load(json_read) - file_data.update(entry) - with open(serial_file, 'w') as json_write: - # dump to string first for formatting - json.dump(file_data, json_write, indent=2) - - return file_data - - -def deserialize_data(filename): - """Load a dictionary from a serial file. - - filename -- database file to load from. - - Returns a dictionary of database information. - """ - - # check if supported type - extension = pathlib.Path(filename).suffix - if extension not in supported_formats: - raise Exception(f"Format of {filename} is not supported.") - - # json - if extension == '.json': - with open(filename, 'r') as json_file: - j_dict = json.load(json_file) - - return j_dict - - -def serial_oneline(filename): - """Reformat lists in markup languages to take up only one line. - - Works well when only lists are surrounded by square brackets and no other data is comma and newline separated. - - filename -- name of markup file to reformat. - """ - - # check file type - extension = pathlib.Path(filename).suffix - if extension not in supported_formats: - raise Exception(f"Format of {filename} is not supported.") - - if extension == '.json': - # cannot easily do regex substitution since lists are of floats - with open(filename, 'r+') as json_file: - lines = json_file.readlines() - json_file.seek(0) - json_file.truncate() - - s_flag = False - for line in lines: - if "\"r\": [" in line or "\"gr\": [" in line: - s_flag = True - updated_line = line[:-1] - elif "]," in line: - s_flag = False - updated_line = f"{updated_line[:-1]}{line.strip()}\n" - json_file.write(updated_line) - elif s_flag: - updated_line += f"{line[:-1].strip()} " - else: - json_file.write(line) - - -def apply_schema_to_file(filename, schemaname, multiple_entries=False): - """ Reformat a file so relevant entries match the same order as a schema file. - Other entries are put at the end in the same order. - - filename -- name of file to apply the schema to. - schemaname -- name of schema to apply. - multiple_entries -- True if database file (i.e. those generated by load_PDF_into_db). - False if data from a single file (i.e. those generated by markup_PDF). - - Returns the dictionary loaded from/into the reformatted file. - """ - - # ensure proper extension - file_ext = pathlib.Path(filename).suffix - schema_ext = pathlib.Path(schemaname).suffix - if file_ext != schema_ext: - return Exception("Schema type does not match file type.") - if file_ext not in supported_formats: - return Exception(f"Format of {filename} is not supported.") - - # json - if file_ext == ".json": - with open(schemaname, 'r') as jsonschema: - schema = json.load(jsonschema) - schema_order = [] - for dp in schema: - schema_order.append(dp) - - # database file - if multiple_entries: - # reformat each entry in a collection - with open(filename, 'r') as json_read: - data_dict = json.load(json_read) - reformatted_dict = {} # new dictionary for entire json - for entry in data_dict.keys(): - # reformat each entry - entry_dict = data_dict.get(entry) - reformatted_entry = {} # new dictionary for a particular entry - for dp in schema_order: - if dp in entry_dict: - reformatted_entry.update({dp: entry_dict.get(dp)}) - entry_dict.pop(dp) - reformatted_entry.update(entry_dict) - reformatted_dict.update({entry: reformatted_entry}) - with open(filename, 'w') as json_write: - json.dump(reformatted_dict, json_write, indent=2) - - # single file - else: - with open(filename, 'r') as json_read: - data_dict = json.load(json_read) - reformatted_dict = {} - # reformat - for dp in schema_order: - if dp in data_dict: - reformatted_dict.update({dp: data_dict.get(dp)}) - data_dict.pop(dp) - reformatted_dict.update(data_dict) - with open(filename, 'w') as json_write: - json.dump(reformatted_dict, json_write, indent=2) - - return reformatted_dict diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py new file mode 100644 index 00000000..edf7dc6a --- /dev/null +++ b/src/diffpy/utils/parsers/serialization.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.utils by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## + +import pathlib +import json + +from .custom_exceptions import UnsupportedTypeError, ImproperSizeError + +# FIXME: add support for yaml, xml +supported_formats = ['.json'] + + +def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_colnames=None, serial_file=None): + """Serialize file data into a dictionary. Can also save dictionary into a serial language file. + Dictionary is formatted as {filename: data}. + + Requires hdata and data_table generated from loadData. + + filename -- name of the file whose data is being serialized. + hdata -- Dictionary of PDF metadata generated by loadData. + data_table -- List storing parsed by loadData. + dt_colnames -- List containing names of each column in data_table. Every name in + data_table_cols will be put into the Dictionary as a key with a value + of that column in data_table (stored as a List). Put None for + columns without names. If dt_cols has less non-None entries + than columns in data_table, the pair {'data table': data_table} will be put + in the dictionary. (Default None: only entry {'data table': data_table} + will be added to dictionary.) + show_path -- include a path element in the database entry (default True). + If 'path' is not included in hddata, extract path from filename. + serial_file -- serial language file to dump dictionary into. + + Returns the dictionary loaded from/into the updated database file. + """ + + # compile data_table and hddata together + data = {} + + # handle getting name of file for variety of filename types + abs_path = pathlib.Path(filename).resolve() + # add path to start of data if requested + if show_path and 'path' not in hdata.keys(): + data.update({'path': abs_path.as_posix()}) + # title the entry with name of file (taken from end of path) + title = abs_path.name + + # first add named columns in dt_cols + named_columns = 0 # initial value + max_columns = 1 # higher than named_columns to trigger 'data table' entry + if dt_colnames is not None: + num_columns = [len(row) for row in data_table] + max_columns = max(num_columns) + num_col_names = len(dt_colnames) + if max_columns < num_col_names: # assume numpy.loadtxt gives non-irregular array + raise ImproperSizeError("More entries in dt_colnames than columns in data_table.") + named_columns = 0 + for idx in range(num_col_names): + colname = dt_colnames[idx] + if colname is not None: + data.update({colname: list(data_table[:, idx])}) + named_columns += 1 + + # second add data in hddata dict + data.update(hdata) + + # finally add data_table as an entry named 'data table' if not all columns were parsed + if named_columns < max_columns: + if 'data table' not in data.keys(): + data.update({'data table': data_table}) + else: # if 'data table' is already a key, keep adding primes to the end + dt_name = 'data table' + while dt_name in data.keys(): + dt_name += " prime" + data.update({dt_name: data_table}) + + # parse name using pathlib and generate dictionary entry + entry = {title: data} + + # no save + if serial_file is None: + return entry + + # saving/updating file + # check if supported type + sf = pathlib.Path(serial_file) + sf_name = sf.name + extension = sf.suffix + if extension not in supported_formats: + raise UnsupportedTypeError(sf_name, supported_formats) + + # new file or update + existing = False + try: + open(serial_file) + existing = True + except FileNotFoundError: + pass + + # json + if extension == '.json': + # dump if non-existing + if not existing: + with open(serial_file, 'w') as jsonfile: + file_data = entry # for return + json.dump(file_data, jsonfile, indent=2) + + # update if existing + else: + with open(serial_file, 'r') as json_read: + file_data = json.load(json_read) + file_data.update(entry) + with open(serial_file, 'w') as json_write: + # dump to string first for formatting + json.dump(file_data, json_write, indent=2) + + return file_data + + +def deserialize_data(filename): + """Load a dictionary from a serial file. + + filename -- database file to load from. + + Returns a dictionary of database information. + """ + + # check if supported type + f = pathlib.Path(filename) + f_name = f.name + extension = f.suffix + if extension not in supported_formats: + raise UnsupportedTypeError(f_name, supported_formats) + + # json + if extension == '.json': + with open(filename, 'r') as json_file: + j_dict = json.load(json_file) + + return j_dict diff --git a/src/diffpy/utils/tests/test_loadmetafile.py b/src/diffpy/utils/tests/test_loadmetafile.py deleted file mode 100644 index 3e3e5d11..00000000 --- a/src/diffpy/utils/tests/test_loadmetafile.py +++ /dev/null @@ -1,50 +0,0 @@ -from diffpy.utils.parsers import serialize_data, deserialize_data, apply_schema_to_file, serial_oneline -from diffpy.utils.parsers import loadData -from diffpy.utils.tests.testhelpers import datafile - -import os -import filecmp - -tests_dir = os.path.dirname(os.path.abspath(locals().get('__file__', 'file.py'))) - -targetjson = datafile('targetjson.json') -schemaname = datafile('strumining.json') - -muload = datafile('loadmu.txt') -targetmu = datafile('targetmu.json') - - -def test_load_gr(tmp_path): - # generate json and apply schema - generatedjson = tmp_path / "generated_serialization.json" - tddbload_list = os.listdir(os.path.join(tests_dir, "testdata", "dbload")) - tddbload_list.sort() - for headerfile in tddbload_list: - headerfile = os.path.join(tests_dir, "testdata", "dbload", headerfile) - hdata = loadData(headerfile, headers=True) - data_table = loadData(headerfile) - db_data = serialize_data(headerfile, hdata, data_table, dt_colnames=['r', 'gr'], - show_path=False, serial_file=generatedjson) - apply_schema_to_file(generatedjson, schemaname, multiple_entries=True) - serial_oneline(generatedjson) - - # compare to target - # first compare if base data is same - import json - target_db_data = deserialize_data(targetjson) - assert target_db_data == db_data - # then compare file structure/organization - assert filecmp.cmp(generatedjson, targetjson) - - -# FIXME: tests for REST API, remove after merge -def test_markup_gr(tmp_path): - # put into json and apply schema - generatedmu = tmp_path / "generated_markup.json" - hdata = loadData(muload, headers=True) - data_table = loadData(muload) - data = serialize_data(muload, hdata, data_table, dt_colnames=['r', 'gr'], show_path=False).get('loadmu.txt') - - # compare data is same - target_data = deserialize_data(targetmu) - assert target_data == data diff --git a/src/diffpy/utils/tests/test_serialization.py b/src/diffpy/utils/tests/test_serialization.py new file mode 100644 index 00000000..5a613a02 --- /dev/null +++ b/src/diffpy/utils/tests/test_serialization.py @@ -0,0 +1,91 @@ +from diffpy.utils.parsers import serialize_data, deserialize_data +from diffpy.utils.parsers import loadData +from diffpy.utils.tests.testhelpers import datafile + +from diffpy.utils.parsers.custom_exceptions import UnsupportedTypeError, ImproperSizeError + +import os +import pytest +import numpy + +tests_dir = os.path.dirname(os.path.abspath(locals().get('__file__', 'file.py'))) + +targetjson = datafile('targetjson.json') +schemaname = datafile('strumining.json') +wrongtype = datafile('wrong.type') +loadfile = datafile('loadfile.txt') +nodt = datafile('loaddatawithheaders.txt') + + +def test_load_multiple(tmp_path): + # generate json and apply schema + generatedjson = tmp_path / "generated_serialization.json" + tlm_list = os.listdir(os.path.join(tests_dir, "testdata", "dbload")) + tlm_list.sort() + generated_data = None + for hfname in tlm_list: + # gather data using loadData + headerfile = os.path.normpath(os.path.join(tests_dir, "testdata", "dbload", hfname)) + hdata = loadData(headerfile, headers=True) + data_table = loadData(headerfile) + + # check path extraction + generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=['r', 'gr'], show_path=True) + assert headerfile == os.path.normpath(generated_data[hfname].pop('path')) + + # rerun without path information and save to file + generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=['r', 'gr'], + show_path=False, serial_file=generatedjson) + + # compare to target + target_data = deserialize_data(targetjson) + assert target_data == generated_data + # ensure file saved properly + assert target_data == deserialize_data(generatedjson) + + +def test_exceptions(): + hdata = loadData(loadfile, headers=True) + data_table = loadData(loadfile) + + # improper file types + with pytest.raises(UnsupportedTypeError): + serialize_data(loadfile, hdata, data_table, serial_file=wrongtype) + with pytest.raises(UnsupportedTypeError): + deserialize_data(wrongtype) + + # various dt_colnames inputs + with pytest.raises(ImproperSizeError): + serialize_data(loadfile, hdata, data_table, dt_colnames=["one", "two", "three is too many"]) + # check proper output + normal = serialize_data(loadfile, hdata, data_table, dt_colnames=['r', 'gr']) + data_name = list(normal.keys())[0] + r_list = normal[data_name]['r'] + gr_list = normal[data_name]['gr'] + # three equivalent ways to denote no column names + missing_parameter = serialize_data(loadfile, hdata, data_table, show_path=False) + empty_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[]) + none_entry_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None]) + # check equivalence + assert missing_parameter == empty_parameter + assert missing_parameter == none_entry_parameter + print(data_table) + print(missing_parameter[data_name]['data table prime']) + assert numpy.allclose(missing_parameter[data_name]['data table prime'], data_table) + # extract a single column + r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=['r']) + gr_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, 'gr']) + incorrect_r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, 'r']) + # check proper columns extracted + assert numpy.allclose(gr_extract[data_name]['gr'], incorrect_r_extract[data_name]['r']) + assert 'r' not in gr_extract[data_name] + assert 'gr' not in r_extract[data_name] and 'gr' not in incorrect_r_extract[data_name] + # check correct values extracted + assert numpy.allclose(r_extract[data_name]['r'], r_list) + assert numpy.allclose(gr_extract[data_name]['gr'], gr_list) + # no datatable + nodt_hdata = loadData(nodt, headers=True) + nodt_dt = loadData(nodt) + no_dt = serialize_data(nodt, nodt_hdata, nodt_dt, show_path=False) + nodt_data_name = list(no_dt.keys())[0] + assert numpy.allclose(no_dt[nodt_data_name]['data table'], nodt_dt) diff --git a/src/diffpy/utils/tests/testdata/loadmu.txt b/src/diffpy/utils/tests/testdata/loadfile.txt similarity index 92% rename from src/diffpy/utils/tests/testdata/loadmu.txt rename to src/diffpy/utils/tests/testdata/loadfile.txt index 326c9492..c5babc89 100644 --- a/src/diffpy/utils/tests/testdata/loadmu.txt +++ b/src/diffpy/utils/tests/testdata/loadfile.txt @@ -15,6 +15,7 @@ qbroad = 0.2 qdamp = 0.0001 stype = Neutron stru_str = baddata +data table = no_table 0 0 1 0.00001 diff --git a/src/diffpy/utils/tests/testdata/targetmu.json b/src/diffpy/utils/tests/testdata/targetmu.json deleted file mode 100644 index 967d3b99..00000000 --- a/src/diffpy/utils/tests/testdata/targetmu.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "r": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], - "gr": [0.0, 1e-05, 3e-05, 5e-05, 1e-05, -3e-05, -6e-05], - "stru_str": "baddata", - "stype": "Neutron", - "qdamp": 0.0001, - "qbroad": 0.2, - "qmax": 10.0, - "qmin": 0.0, - "spd": 0.1, - "db_option": "LOCAL", - "mpi_option": "Y", - "refinepos_option": "N", - "max_num": 10.0, - "rmax": 10.0, - "rmin": 0.0, - "rmed": 5.0, - "min_num": 1.0, - "pos": 3.5, - "qstep": "invalid" -} \ No newline at end of file diff --git a/src/diffpy/utils/tests/testdata/wrong.type b/src/diffpy/utils/tests/testdata/wrong.type new file mode 100644 index 00000000..81750b96 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/wrong.type @@ -0,0 +1 @@ +{ \ No newline at end of file From 5c868d15d040d6dd5fd13b2da0729d39e1eef041 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Fri, 15 Sep 2023 17:20:17 -0400 Subject: [PATCH 026/696] Add rever.xsh, news --- news/loaddata_headers.rst | 9 +++++++++ news/setup_rever.rst | 5 +++++ news/support-py2-and-py3.rst | 9 +++++++++ rever.xsh | 14 ++++++++++++++ 4 files changed, 37 insertions(+) create mode 100644 news/loaddata_headers.rst create mode 100644 news/setup_rever.rst create mode 100644 news/support-py2-and-py3.rst create mode 100644 rever.xsh diff --git a/news/loaddata_headers.rst b/news/loaddata_headers.rst new file mode 100644 index 00000000..c9dfe8a3 --- /dev/null +++ b/news/loaddata_headers.rst @@ -0,0 +1,9 @@ +**Added** +- CI Coverage. +- New tests for loadData function. +- loadData function now toggleable. Can return either (a) data read from data blocks or (b) header +information stored above the data block. + +**Removed** +- Remove use of pkg_resources (deprecated). +- No longer use Travis. \ No newline at end of file diff --git a/news/setup_rever.rst b/news/setup_rever.rst new file mode 100644 index 00000000..0829442e --- /dev/null +++ b/news/setup_rever.rst @@ -0,0 +1,5 @@ +***Added*** +- Compatibility with Python 3.10, 3.9, 3.8 + +***Removed*** +- Remove the support for Python 3.5, 3.6. \ No newline at end of file diff --git a/news/support-py2-and-py3.rst b/news/support-py2-and-py3.rst new file mode 100644 index 00000000..0e26197a --- /dev/null +++ b/news/support-py2-and-py3.rst @@ -0,0 +1,9 @@ +***Added*** +- Compatibility with Python 3.7, 3.6, 3.5 in addition to 2.7. + +***Changed*** +- Switch to platform-independent "noarch" Anaconda package. + +***Deprecated*** +- Variable `__gitsha__` in the `version` module which was renamed + to `__git_commit__`. \ No newline at end of file diff --git a/rever.xsh b/rever.xsh new file mode 100644 index 00000000..24341cde --- /dev/null +++ b/rever.xsh @@ -0,0 +1,14 @@ +$PROJECT = 'diffpy.utils' +$ACTIVITIES = [ + 'tag', # Creates a tag for the new version number + 'push_tag', # Pushes the tag up to the $TAG_REMOTE + 'pypi', # Sends the package to pypi + 'ghrelease' # Creates a Github release entry for the new tag + ] +$PUSH_TAG_REMOTE = 'git@github.com:diffpy/diffpy.utils.git' # Repo to push tags to +$GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge +$GITHUB_REPO = 'diffpy.utils' # Github repo for Github releases and conda-forge +$GHRELEASE_PREPEND = """See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. + +The release is also available at [PyPI](https://pypi.org/project/diffpy.pdffit2/) and [Conda](https://anaconda.org/conda-forge/diffpy.pdffit2). +""" # release message From 96098a0e9bcd4d07c0c9277a8b2587a3484072c4 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Fri, 15 Sep 2023 17:22:22 -0400 Subject: [PATCH 027/696] Fix rever --- rever.xsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rever.xsh b/rever.xsh index 24341cde..e46aa2b1 100644 --- a/rever.xsh +++ b/rever.xsh @@ -10,5 +10,5 @@ $GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge $GITHUB_REPO = 'diffpy.utils' # Github repo for Github releases and conda-forge $GHRELEASE_PREPEND = """See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. -The release is also available at [PyPI](https://pypi.org/project/diffpy.pdffit2/) and [Conda](https://anaconda.org/conda-forge/diffpy.pdffit2). +The release is also available at [PyPI](https://pypi.org/project/diffpy.utils/) and [Conda](https://anaconda.org/conda-forge/diffpy.utils). """ # release message From 92b630ae2d6f70996cb5f9253069b2431410839b Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 25 Sep 2023 16:41:58 -0400 Subject: [PATCH 028/696] Tested with 3.11 and 3.12.0rc3 --- CHANGELOG.md | 1 + news/loaddata_headers.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 028c0d21..1fcd64ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- Compatability with Python 3.12, 3.11 - CI Coverage. - New tests for loadData function. - loadData function now toggleable. Can return either (a) data read from data blocks or (b) header diff --git a/news/loaddata_headers.rst b/news/loaddata_headers.rst index c9dfe8a3..b784ea4d 100644 --- a/news/loaddata_headers.rst +++ b/news/loaddata_headers.rst @@ -1,4 +1,5 @@ **Added** +- Compatability with Python 3.12, 3.11 - CI Coverage. - New tests for loadData function. - loadData function now toggleable. Can return either (a) data read from data blocks or (b) header From eef5164e74fe2627d0375a95bf9f7bf5be2418f6 Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Mon, 25 Sep 2023 17:06:38 -0400 Subject: [PATCH 029/696] Update version compatability list --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1c5660f1..e31540b0 100755 --- a/setup.py +++ b/setup.py @@ -111,9 +111,9 @@ def getversioncfg(): 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Scientific/Engineering :: Physics', ], ) From e03d374b5bcff86d9581545e12905dd594314998 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 28 Sep 2023 16:42:56 -0400 Subject: [PATCH 030/696] Smooth out rever release --- news/TEMPLATE.rst | 23 +++++++++++++++++++++++ rever.xsh | 5 ++++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 news/TEMPLATE.rst diff --git a/news/TEMPLATE.rst b/news/TEMPLATE.rst new file mode 100644 index 00000000..5ada21a8 --- /dev/null +++ b/news/TEMPLATE.rst @@ -0,0 +1,23 @@ +**Added:** None + +* + +**Changed:** None + +* + +**Deprecated:** None + +* + +**Removed:** None + +* + +**Fixed:** None + +* + +**Security:** None + +* diff --git a/rever.xsh b/rever.xsh index e46aa2b1..92f28023 100644 --- a/rever.xsh +++ b/rever.xsh @@ -2,13 +2,16 @@ $PROJECT = 'diffpy.utils' $ACTIVITIES = [ 'tag', # Creates a tag for the new version number 'push_tag', # Pushes the tag up to the $TAG_REMOTE + 'ghrelease', # Creates a Github release entry for the new tag 'pypi', # Sends the package to pypi - 'ghrelease' # Creates a Github release entry for the new tag + 'conda_forge' ] $PUSH_TAG_REMOTE = 'git@github.com:diffpy/diffpy.utils.git' # Repo to push tags to $GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge $GITHUB_REPO = 'diffpy.utils' # Github repo for Github releases and conda-forge $GHRELEASE_PREPEND = """See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. +$CHANGELOG_FILENAME = 'CHANGELOG.rst' +$CHANGELOG_IGNORE = ['TEMPLATE.rst'] The release is also available at [PyPI](https://pypi.org/project/diffpy.utils/) and [Conda](https://anaconda.org/conda-forge/diffpy.utils). """ # release message From c61bac69b16b83fa7f88021d42ddf97ca3b1e3d6 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 2 Oct 2023 16:55:59 -0400 Subject: [PATCH 031/696] fixes for release --- conda-recipe/meta.yaml | 3 +++ rever.xsh | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 4d383218..28b17f98 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -17,6 +17,9 @@ build: # number: 0 requirements: + host: + - pip + build: - python >=3.5|2.7.* - setuptools diff --git a/rever.xsh b/rever.xsh index 92f28023..ec92a4a0 100644 --- a/rever.xsh +++ b/rever.xsh @@ -9,9 +9,9 @@ $ACTIVITIES = [ $PUSH_TAG_REMOTE = 'git@github.com:diffpy/diffpy.utils.git' # Repo to push tags to $GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge $GITHUB_REPO = 'diffpy.utils' # Github repo for Github releases and conda-forge -$GHRELEASE_PREPEND = """See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. $CHANGELOG_FILENAME = 'CHANGELOG.rst' $CHANGELOG_IGNORE = ['TEMPLATE.rst'] +$GHRELEASE_PREPEND = """See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. The release is also available at [PyPI](https://pypi.org/project/diffpy.utils/) and [Conda](https://anaconda.org/conda-forge/diffpy.utils). """ # release message diff --git a/setup.py b/setup.py index e31540b0..21625b43 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # Use this version when git data are not available, like in git zip archive. # Update when tagging a new release. -FALLBACK_VERSION = '3.1.0' +FALLBACK_VERSION = '3.2.0' # versioncfgfile holds version data for git commit hash and date. # It must reside in the same directory as version.py. From 309ee6de65186a97e64a03784cd138cf6849059a Mon Sep 17 00:00:00 2001 From: = Date: Mon, 2 Oct 2023 17:02:16 -0400 Subject: [PATCH 032/696] Add pytest to requirements --- conda-recipe/meta.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 28b17f98..003ac9f5 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -29,6 +29,12 @@ requirements: - setuptools - numpy >=1.3 + test: + - python >=3.5|2.7.* + - setuptools + - numpy >=1.3 + - pytest + test: # Python imports imports: From cabb941ae037ae87adc9a9430993ad0a6dd305d0 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 2 Oct 2023 17:27:24 -0400 Subject: [PATCH 033/696] Update fallback version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 21625b43..8deaa6a1 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # Use this version when git data are not available, like in git zip archive. # Update when tagging a new release. -FALLBACK_VERSION = '3.2.0' +FALLBACK_VERSION = '3.2.1' # versioncfgfile holds version data for git commit hash and date. # It must reside in the same directory as version.py. From 08295f9bdf13b9330d9d7b81067854fa9a7cadc7 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 2 Oct 2023 18:01:48 -0400 Subject: [PATCH 034/696] Remove v tagging --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8deaa6a1..305316e4 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def getversioncfg(): cp0.read(gitarchivecfgfile) if len(cp0.get('DEFAULT', 'commit')) > 20: g = cp0.defaults() - mx = re.search(r'\btag: v(\d[^,]*)', g.pop('refnames')) + mx = re.search(r'\btag: (\d[^,]*)', g.pop('refnames')) if mx: g['version'] = mx.group(1) # then try to obtain version data from git. @@ -75,6 +75,7 @@ def getversioncfg(): return cp versiondata = getversioncfg() +print(versiondata.get('DEFAULT', 'version')) with open(os.path.join(MYDIR, 'README.rst')) as fp: long_description = fp.read() From dbb2320554694cdf1317b19707a8bab64801049e Mon Sep 17 00:00:00 2001 From: = Date: Mon, 2 Oct 2023 18:03:12 -0400 Subject: [PATCH 035/696] Remove print --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 305316e4..573f4f75 100755 --- a/setup.py +++ b/setup.py @@ -75,7 +75,6 @@ def getversioncfg(): return cp versiondata = getversioncfg() -print(versiondata.get('DEFAULT', 'version')) with open(os.path.join(MYDIR, 'README.rst')) as fp: long_description = fp.read() From 82f95784152c31e4d74ba4505764cccb4d970938 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 2 Oct 2023 18:09:39 -0400 Subject: [PATCH 036/696] Manual add version --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 573f4f75..2fc6d313 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # Use this version when git data are not available, like in git zip archive. # Update when tagging a new release. -FALLBACK_VERSION = '3.2.1' +FALLBACK_VERSION = '3.2.0' # versioncfgfile holds version data for git commit hash and date. # It must reside in the same directory as version.py. @@ -82,7 +82,7 @@ def getversioncfg(): # define distribution setup_args = dict( name = "diffpy.utils", - version = versiondata.get('DEFAULT', 'version'), + version = '3.2.3', packages = find_packages(os.path.join(MYDIR, 'src')), package_dir = {'' : 'src'}, test_suite = 'diffpy.utils.tests', From d7f20c853056ade997b109d3e77a91de5c4ac3f3 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 3 Oct 2023 17:06:29 -0400 Subject: [PATCH 037/696] Rebuild documentation base --- .github/workflows/docs.yml | 38 +++++++++++++++++++ .../source/api/diffpy.utils.parsers.rst | 8 ++++ doc/manual/source/conf.py | 4 +- requirements/docs.txt | 1 + rever.xsh | 1 - setup.py | 10 ++--- src/diffpy/utils/wx/gridutils.py | 2 - 7 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..4f104437 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +name: Build Documentation + +on: + release: + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + steps: + - uses: actions/checkout@v3 + + - uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: build + auto-update-conda: true + + - name: install requirements + run: >- + conda install -n build -c conda-forge + --file requirements/build.txt + --file requirements/run.txt + --file requirements/docs.txt + --quiet --yes + + - name: install the package + run: python -m pip install . --no-deps + + - name: build documents + run: make -C doc/manual html + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/build/html diff --git a/doc/manual/source/api/diffpy.utils.parsers.rst b/doc/manual/source/api/diffpy.utils.parsers.rst index e98ab742..502394d5 100644 --- a/doc/manual/source/api/diffpy.utils.parsers.rst +++ b/doc/manual/source/api/diffpy.utils.parsers.rst @@ -14,6 +14,14 @@ diffpy.utils.parsers.loaddata module :undoc-members: :show-inheritance: +diffpy.utils.parsers.serialization module +----------------------------------------- + +.. automodule:: diffpy.utils.parsers.serialization + :members: + :undoc-members: + :show-inheritance: + diffpy.utils.parsers.resample module ------------------------------------ diff --git a/doc/manual/source/conf.py b/doc/manual/source/conf.py index cb948769..107c500e 100644 --- a/doc/manual/source/conf.py +++ b/doc/manual/source/conf.py @@ -22,6 +22,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('../../..')) +sys.path.insert(0, os.path.abspath('../../../src')) # abbreviations ab_authors = u'Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge group' @@ -117,14 +118,13 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_py3doc_enhanced_theme' +html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { - 'collapsiblesidebar' : 'true', 'navigation_with_keys' : 'true', } diff --git a/requirements/docs.txt b/requirements/docs.txt index 92f4ff09..ab17b1c8 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,3 +1,4 @@ sphinx sphinx_rtd_theme doctr +m2r diff --git a/rever.xsh b/rever.xsh index ec92a4a0..ce010977 100644 --- a/rever.xsh +++ b/rever.xsh @@ -4,7 +4,6 @@ $ACTIVITIES = [ 'push_tag', # Pushes the tag up to the $TAG_REMOTE 'ghrelease', # Creates a Github release entry for the new tag 'pypi', # Sends the package to pypi - 'conda_forge' ] $PUSH_TAG_REMOTE = 'git@github.com:diffpy/diffpy.utils.git' # Repo to push tags to $GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge diff --git a/setup.py b/setup.py index 2fc6d313..6c797b5c 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # Use this version when git data are not available, like in git zip archive. # Update when tagging a new release. -FALLBACK_VERSION = '3.2.0' +FALLBACK_VERSION = '3.2.3' # versioncfgfile holds version data for git commit hash and date. # It must reside in the same directory as version.py. @@ -27,12 +27,12 @@ def gitinfo(): from subprocess import Popen, PIPE kw = dict(stdout=PIPE, cwd=MYDIR, universal_newlines=True) - proc = Popen(['git', 'describe', '--tags', '--match=v[[:digit:]]*'], **kw) + proc = Popen(['git', 'describe', '--tags', '--match=[v,V,[:digit:]]*'], **kw) desc = proc.stdout.read() proc = Popen(['git', 'log', '-1', '--format=%H %ct %ci'], **kw) glog = proc.stdout.read() rv = {} - rv['version'] = '.post'.join(desc.strip().split('-')[:2]).lstrip('v') + rv['version'] = '.post'.join(desc.strip().split('-')[:2]).lstrip('v').lstrip('V') rv['commit'], rv['timestamp'], rv['date'] = glog.strip().split(None, 2) return rv @@ -49,7 +49,7 @@ def getversioncfg(): cp0.read(gitarchivecfgfile) if len(cp0.get('DEFAULT', 'commit')) > 20: g = cp0.defaults() - mx = re.search(r'\btag: (\d[^,]*)', g.pop('refnames')) + mx = re.search(r'\btag: [vV]?(\d[^,]*)', g.pop('refnames')) if mx: g['version'] = mx.group(1) # then try to obtain version data from git. @@ -82,7 +82,7 @@ def getversioncfg(): # define distribution setup_args = dict( name = "diffpy.utils", - version = '3.2.3', + version = versiondata.get('DEFAULT', 'version'), packages = find_packages(os.path.join(MYDIR, 'src')), package_dir = {'' : 'src'}, test_suite = 'diffpy.utils.tests', diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index 95d6bfc8..38b41012 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -16,8 +16,6 @@ """Common functions for manipulating wx.grid.Grid. """ -import wx - def getSelectionRows(grid): """Indices of the rows that have any cell selected. From 426d4bd6d63caca78bb554880835543cf4611696 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 3 Oct 2023 17:35:25 -0400 Subject: [PATCH 038/696] Fix path --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4f104437..9602b64f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -35,4 +35,4 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/build/html + publish_dir: ./doc/manual/build/html From 15af30ec6f61682d5776de04fa24f81c3136aecb Mon Sep 17 00:00:00 2001 From: = Date: Wed, 4 Oct 2023 09:41:23 -0400 Subject: [PATCH 039/696] Setup rever changelog activity --- CHANGELOG.md | 8 +++++++- news/TEMPLATE.rst | 12 ++++++------ news/docs_patch.rst | 23 ++++++++++++++++++++++ news/loaddata_headers.rst | 38 ++++++++++++++++++++++++++---------- news/setup_rever.rst | 26 ++++++++++++++++++++---- news/support-py2-and-py3.rst | 28 +++++++++++++++++++------- rever.xsh | 6 ++++-- 7 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 news/docs_patch.rst diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fcd64ce..5fd349e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Release notes -## Version 3.2.0 – 2023-08-** +## Version 3.2.4 – 2023-10--** + +### Added + +- New documentation build + +## Version 3.2.3 – 2023-10-03 ### Added diff --git a/news/TEMPLATE.rst b/news/TEMPLATE.rst index 5ada21a8..790d30b1 100644 --- a/news/TEMPLATE.rst +++ b/news/TEMPLATE.rst @@ -1,23 +1,23 @@ -**Added:** None +**Added:** * -**Changed:** None +**Changed:** * -**Deprecated:** None +**Deprecated:** * -**Removed:** None +**Removed:** * -**Fixed:** None +**Fixed:** * -**Security:** None +**Security:** * diff --git a/news/docs_patch.rst b/news/docs_patch.rst new file mode 100644 index 00000000..42751fbd --- /dev/null +++ b/news/docs_patch.rst @@ -0,0 +1,23 @@ +**Added:** + +* New documentation build. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/news/loaddata_headers.rst b/news/loaddata_headers.rst index b784ea4d..c275c20b 100644 --- a/news/loaddata_headers.rst +++ b/news/loaddata_headers.rst @@ -1,10 +1,28 @@ -**Added** -- Compatability with Python 3.12, 3.11 -- CI Coverage. -- New tests for loadData function. -- loadData function now toggleable. Can return either (a) data read from data blocks or (b) header -information stored above the data block. - -**Removed** -- Remove use of pkg_resources (deprecated). -- No longer use Travis. \ No newline at end of file +**Added:** + +* Compatability with Python 3.12.0rc3, 3.11. +* CI Coverage. +* New tests for loadData function. +* loadData function now toggleable. Can return either (a) data read from data blocks or (b) header information stored + above the data block. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Remove use of pkg_resources (deprecated). +* No longer use Travis. + +**Fixed:** + +* + +**Security:** + +* diff --git a/news/setup_rever.rst b/news/setup_rever.rst index 0829442e..a46b163d 100644 --- a/news/setup_rever.rst +++ b/news/setup_rever.rst @@ -1,5 +1,23 @@ -***Added*** -- Compatibility with Python 3.10, 3.9, 3.8 +**Added:** -***Removed*** -- Remove the support for Python 3.5, 3.6. \ No newline at end of file +* Compatibility with Python 3.10, 3.9, 3.8. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Remove the support for Python 3.5, 3.6. + +**Fixed:** + +* + +**Security:** + +* diff --git a/news/support-py2-and-py3.rst b/news/support-py2-and-py3.rst index 0e26197a..f711a6eb 100644 --- a/news/support-py2-and-py3.rst +++ b/news/support-py2-and-py3.rst @@ -1,9 +1,23 @@ -***Added*** -- Compatibility with Python 3.7, 3.6, 3.5 in addition to 2.7. +**Added:** -***Changed*** -- Switch to platform-independent "noarch" Anaconda package. +* Compatibility with Python 3.7, 3.6, 3.5 in addition to 2.7. -***Deprecated*** -- Variable `__gitsha__` in the `version` module which was renamed - to `__git_commit__`. \ No newline at end of file +**Changed:** + +* Switch to platform-independent "noarch" Anaconda package. + +**Deprecated:** + +* Variable `__gitsha__` in the `version` module which was renamed to `__git_commit__`. + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/rever.xsh b/rever.xsh index ce010977..edea2f7e 100644 --- a/rever.xsh +++ b/rever.xsh @@ -1,15 +1,17 @@ $PROJECT = 'diffpy.utils' $ACTIVITIES = [ + 'changelog', # Create a CHANGELOG.rst using news directory 'tag', # Creates a tag for the new version number 'push_tag', # Pushes the tag up to the $TAG_REMOTE 'ghrelease', # Creates a Github release entry for the new tag 'pypi', # Sends the package to pypi ] +$CHANGELOG_FILENAME = 'CHANGELOG.rst' +$CHANGELOG_TEMPLATE = 'TEMPLATE.rst' +$CHANGELOG_IGNORE = ['TEMPLATE.rst'] $PUSH_TAG_REMOTE = 'git@github.com:diffpy/diffpy.utils.git' # Repo to push tags to $GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge $GITHUB_REPO = 'diffpy.utils' # Github repo for Github releases and conda-forge -$CHANGELOG_FILENAME = 'CHANGELOG.rst' -$CHANGELOG_IGNORE = ['TEMPLATE.rst'] $GHRELEASE_PREPEND = """See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. The release is also available at [PyPI](https://pypi.org/project/diffpy.utils/) and [Conda](https://anaconda.org/conda-forge/diffpy.utils). From 284495aae12cdd21dff65b877afab3f3018aa61f Mon Sep 17 00:00:00 2001 From: = Date: Wed, 4 Oct 2023 09:52:31 -0400 Subject: [PATCH 040/696] Update test requirements --- conda-recipe/meta.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 003ac9f5..750beada 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -36,6 +36,10 @@ requirements: - pytest test: + # Put any additional test requirements here + requires: + - pytest + # Python imports imports: - diffpy @@ -50,10 +54,6 @@ test: # You can also put a file called run_test.py in the recipe that will be run # at test time. - requires: - # Put any additional test requirements here. For example - # - nose - about: home: https://github.com/diffpy/diffpy.utils summary: Shared utilities for diffpy packages. From c10162240eba485b0dce452878bea370adc452af Mon Sep 17 00:00:00 2001 From: = Date: Mon, 9 Oct 2023 10:44:07 -0400 Subject: [PATCH 041/696] numpy format --- src/diffpy/utils/__init__.py | 6 +- src/diffpy/utils/parsers/custom_exceptions.py | 19 +++- src/diffpy/utils/parsers/loaddata.py | 102 +++++++++++------- src/diffpy/utils/parsers/resample.py | 60 +++++++---- src/diffpy/utils/parsers/serialization.py | 58 ++++++---- src/diffpy/utils/wx/__init__.py | 2 - src/diffpy/utils/wx/gridutils.py | 39 ++++--- 7 files changed, 182 insertions(+), 104 deletions(-) diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index dc19f85a..3abeab37 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -13,11 +13,7 @@ # ############################################################################## -"""Smalled shared functions for use by other diffpy packages. - -Subpackages: - -parsers -- various utilities related to data parsing and manipulation. +"""Smaller shared functions for use by other diffpy packages. """ # package version diff --git a/src/diffpy/utils/parsers/custom_exceptions.py b/src/diffpy/utils/parsers/custom_exceptions.py index 347edd43..16b77f59 100644 --- a/src/diffpy/utils/parsers/custom_exceptions.py +++ b/src/diffpy/utils/parsers/custom_exceptions.py @@ -16,9 +16,14 @@ class UnsupportedTypeError(Exception): """For file types not supported by our parsers. - supported_types -- List of supported types. - file -- file triggering the error. - message -- for writing a custom message. + Parameters + ---------- + file + Name of file triggering the error. + supported_types: list + Supported file types. + message: str + Overwrites default message. """ def __init__(self, file, supported_types=None, message=None): @@ -35,8 +40,12 @@ def __init__(self, file, supported_types=None, message=None): class ImproperSizeError(Exception): """When the size of an object does not match expectations. - bad_object -- Object with improper size. - message -- for writing a custom message. + Parameters + ---------- + bad_object + Object with improper size. + message: str + Overwrites default message. """ def __init__(self, bad_object, message=None): diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 9c3f83bc..41e2ed7f 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -19,40 +19,49 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwargs): """Find and load data from a text file. - The data block is identified as the first matrix block of at least minrows rows - and constant number of columns. This seems to work for most of the datafiles including - those generated by diffpy programs. + The data block is identified as the first matrix block of at least minrows rows and constant number of columns. + This seems to work for most of the datafiles including those generated by diffpy programs. - filename -- name of the file we want to load data from. - minrows -- minimum number of rows in the first data block. - All rows must have the same number of floating point values. - headers -- when False (defualt), the function returns a numpy array of the - data in the data block. When True, the function instead returns a - dictionary of parameters and their corresponding values parsed from - header (information prior the data block). See hdel and hignore for - options to help with parsing header information. - hdel -- (only used when headers enabled) delimiter for parsing header - information (default '='). e.g. using default hdel, the line - 'parameter = p_value' is put into the dictionary as - {parameter: p_value}. - hignore -- (only used when headers enabled) ignore header rows beginning - with any elements in the hignore list. e.g. hignore=['# ', '['] - means the following lines are skipped: '# qmax=10', '[defaults]'. - kwargs -- keyword arguments that are passed to numpy.loadtxt including - the following arguments below. (See also numpy.loadtxt for more - details.) - delimiter -- delimiter for the data in the block (default use whitespace). - For comma-separated data blocks, set delimiter to ','. - usecols -- zero-based index of columns to be loaded, by default use - all detected columns. The reading skips data blocks that - do not have the usecols-specified columns. - unpack -- return data as a sequence of columns that allows tuple - unpacking such as x, y = loadData(FILENAME, unpack=True). - Note transposing the loaded array as loadData(FILENAME).T - has the same effect. + Parameters + ---------- + filename + Name of the file we want to load data from. + minrows: int + Minimum number of rows in the first data block. All rows must have the same number of floating point values. + headers: bool + when False (defualt), the function returns a numpy array of the data in the data block. + When True, the function instead returns a dictionary of parameters and their corresponding values parsed from + header (information prior the data block). See hdel and hignore for options to help with parsing header + information. + hdel: str + (Only used when headers enabled.) Delimiter for parsing header information (default '='). e.g. using + default hdel, the line 'parameter = p_value' is put into the dictionary as {parameter: p_value}. + hignore: list + (Only used when headers enabled.) Ignore header rows beginning with any elements in hignore. + e.g. hignore=['# ', '['] causes the following lines to be skipped: '# qmax=10', '[defaults]'. + kwargs: + Keyword arguments that are passed to numpy.loadtxt including the following arguments below. (See + numpy.loadtxt for more details.) Only pass kwargs used by numpy.loadtxt. - Return a numpy array of the data (data_block). If headers enabled, instead returns a - dictionary of parameters read from the header (hddata). + Useful kwargs + ============= + delimiter: str + Delimiter for the data in the block (default use whitespace). For comma-separated data blocks, + set delimiter to ','. + usecols: + Zero-based index of columns to be loaded, by default use all detected columns. The reading skips + data blocks that do not have the usecols-specified columns. + unpack: bool + Return data as a sequence of columns that allows tuple unpacking such as x, y = + loadData(FILENAME, unpack=True). Note transposing the loaded array as loadData(FILENAME).T has the same + effect. + + Returns + ------- + data_block: ndarray + A numpy array containing the found data block. (This is not returned if headers is enabled.) + hdata: dict + If headers are enabled, return a dictionary of parameters read from the header. """ from numpy import array, loadtxt # for storing header data @@ -156,18 +165,24 @@ def countcolumnsvalues(line): class TextDataLoader(object): - '''Smart loading of a text data with possibly multiple datasets. - ''' + """Smart loading of a text data with possibly multiple datasets. - minrows = 10 - usecols = None - skiprows = None + Parameters + ---------- + minrows: int + Minimum number of rows in the first data block. (Default 10.) + usecols: tuple + Which columns in our dataset to use. Ignores all other columns. If None (default), use all columns. + skiprows + Rows in dataset to skip. (Currently not functional.) + """ - def __init__(self, minrows=None, usecols=None, skiprows=None): + def __init__(self, minrows=10, usecols=None, skiprows=None): if minrows is not None: self.minrows = minrows if usecols is not None: self.usecols = tuple(usecols) + # FIXME: implement usage in _findDataBlocks if skiprows is not None: self.skiprows = skiprows # data items @@ -194,12 +209,23 @@ def _resetvars(self): def read(self, filename): + """Open a file and run readfp. + + Use if file is not already open for read byte. + """ with open(filename, 'rb') as fp: self.readfp(fp) return def readfp(self, fp, append=False): + """Get file details. + + File details include: + * File name. + * All data blocks findable by loadData. + * Headers (if present) for each data block. (Generally the headers contain column name information). + """ self._reset() # try to read lines from fp first self._lines = fp.readlines() diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index 5e23ce70..4553e227 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -17,17 +17,34 @@ import numpy + # NOTE - this should be faster than resample below and conforms more closely to # numpy.interp. I'm keeping resample for legacy reasons. -def wsinterp(x, xp, fp, left = None, right = None): +def wsinterp(x, xp, fp, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation. - This uses the Whittaker-Shannon interpolation formula to interpolate the - value of fp (array), which is defined over xp (array), at x (array or - float). - - Returns the interpolated array with dimensions of x. - + This uses the Whittaker-Shannon interpolation formula to interpolate the value of fp (array), which is defined over + xp (array), at x (array or float). + + Paramaters + ---------- + x: ndarray + Desired range for interpolation. + xp: ndarray + Defined range for fp. + fp: ndarray + Function to be interpolated. + left: float + If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, set fp for x < xp[0] + to fp evaluated at xp[-1]. + right: float + If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for + x > xp[-1] to fp evaluated at xp[-1]. + + Returns + ------- + ndarray: + Returns the interpolated array with dimensions of x. """ scalar = numpy.isscalar(x) if scalar: @@ -50,23 +67,30 @@ def wsinterp(x, xp, fp, left = None, right = None): fp_at_x[x > xp[-1]] = right # Return a float if we got a float - if scalar: return float(fp_at_x) + if scalar: + return float(fp_at_x) return fp_at_x + def resample(r, s, dr): """Resample a PDF on a new grid. - This uses the Whittaker-Shannon interpolation formula to put s1 on a new - grid if dr is less than the sampling interval of r1, or linear - interpolation if dr is greater than the sampling interval of r1. - - r -- The r-grid used for s1 - s -- The signal to be resampled - dr -- The new sampling interval - - Returns resampled (r, s) - + This uses the Whittaker-Shannon interpolation formula to put s1 on a new grid if dr is less than the sampling + interval of r1, or linear interpolation if dr is greater than the sampling interval of r1. + + Parameters + ---------- + r + The r-grid used for s1. + s + The signal to be resampled. + dr + The new sampling interval. + + Returns + ------- + Returns resampled (r, s). """ dr0 = r[1] - r[0] diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index edf7dc6a..bb980bfc 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -23,26 +23,34 @@ def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_colnames=None, serial_file=None): - """Serialize file data into a dictionary. Can also save dictionary into a serial language file. - Dictionary is formatted as {filename: data}. - - Requires hdata and data_table generated from loadData. - - filename -- name of the file whose data is being serialized. - hdata -- Dictionary of PDF metadata generated by loadData. - data_table -- List storing parsed by loadData. - dt_colnames -- List containing names of each column in data_table. Every name in - data_table_cols will be put into the Dictionary as a key with a value - of that column in data_table (stored as a List). Put None for - columns without names. If dt_cols has less non-None entries - than columns in data_table, the pair {'data table': data_table} will be put - in the dictionary. (Default None: only entry {'data table': data_table} - will be added to dictionary.) - show_path -- include a path element in the database entry (default True). - If 'path' is not included in hddata, extract path from filename. - serial_file -- serial language file to dump dictionary into. - - Returns the dictionary loaded from/into the updated database file. + """Serialize file data into a dictionary. Can also save dictionary into a serial language file. Dictionary is + formatted as {filename: data}. + + Requires hdata and data_table (can be generated by loadData). + + Parameters + ---------- + filename + Name of the file whose data is being serialized. + hdata: dict + File metadata (generally related to data table). + data_table: list + Data table. + dt_colnames: list + Names of each column in data_table. Every name in data_table_cols will be put into the Dictionary as a key with + a value of that column in data_table (stored as a List). Put None for columns without names. If dt_cols has less + non-None entries than columns in data_table, the pair {'data table': data_table} will be put in the dictionary. + (Default None: only entry {'data table': data_table} will be added to dictionary.) + show_path: bool + include a path element in the database entry (default True). If 'path' is not included in hddata, extract path + from filename. + serial_file + Serial language file to dump dictionary into. If None (defualt), no dumping will occur. + + Returns + ------- + dict: + Returns the dictionary loaded from/into the updated database file. """ # compile data_table and hddata together @@ -131,9 +139,15 @@ def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_c def deserialize_data(filename): """Load a dictionary from a serial file. - filename -- database file to load from. + Parameters + ---------- + filename + Serial file to load from. - Returns a dictionary of database information. + Returns + ------- + dict + A dictionary read from a serial file. """ # check if supported type diff --git a/src/diffpy/utils/wx/__init__.py b/src/diffpy/utils/wx/__init__.py index 30a96019..c2d0617b 100644 --- a/src/diffpy/utils/wx/__init__.py +++ b/src/diffpy/utils/wx/__init__.py @@ -14,8 +14,6 @@ ############################################################################## """Utilities related wx Python GUIs. - -gridutils -- selection management in wx.grid.Grid """ # End of file diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index 38b41012..55cab38a 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -55,8 +55,7 @@ def getSelectionColumns(grid): def getSelectedCells(grid): """Get list of (row, col) pairs of all selected cells. - Unlike grid.GetSelectedCells this returns them all no matter - how they were selected. + Unlike grid.GetSelectedCells this returns them all no matter how they were selected. """ rows = grid.GetNumberRows() cols = grid.GetNumberCols() @@ -82,13 +81,20 @@ def limitSelectionToRows(grid, indices): '''Limit selection to the specified row indices. No action for empty indices. - grid -- instance of wx.grid.Grid - indices -- list of row indices to be selected, must be sorted and unique. + Parameters + ---------- + grid + instance of wx.grid.Grid + indices: list + Row indices to be selected, must be sorted and unique. + Returns + ------- No return value. ''' import bisect - if not indices: return + if not indices: + return rowblocks = _indicesToBlocks(indices) cindices = getSelectionColumns(grid) or [grid.GetGridCursorCol()] colblocks = _indicesToBlocks(cindices) @@ -134,16 +140,21 @@ def quickResizeColumns(grid, indices): # Local Helpers -------------------------------------------------------------- def _indicesToBlocks(indices): - '''Convert a list of integer indices to a list of (start, stop) tuples. - The (start, stop) tuple defines a continuous block, where the stop index - is included in the block. - - indices -- list of integer indices, must be unique and sorted. - - Return a list of (start, stop) tuples. - ''' + """Convert a list of integer indices to a list of (start, stop) tuples. + The (start, stop) tuple defines a continuous block, where the stop index is included in the block. + + Parameters + ---------- + indices: list + Integer indices, must be unique and sorted. + + Returns + ------- + list: + A list of (start, stop) tuples. + """ rngs = [] - i0 = -100 + i0 = indices[0] - 2 # Ensure i0 + 1 < i for first step for i in indices: if i > i0 + 1: rngs.append([i, i]) From 259d3fdc4f45c80a57536be523d7b728b149fada Mon Sep 17 00:00:00 2001 From: = Date: Tue, 10 Oct 2023 13:44:04 -0400 Subject: [PATCH 042/696] Add wsinterp test for coverage --- src/diffpy/utils/parsers/resample.py | 12 ++++++---- src/diffpy/utils/tests/test_resample.py | 30 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 src/diffpy/utils/tests/test_resample.py diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index 4553e227..41f89b32 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -43,8 +43,10 @@ def wsinterp(x, xp, fp, left=None, right=None): Returns ------- + float: + If input x is a scalar (not an array), return the interpolated value at x. ndarray: - Returns the interpolated array with dimensions of x. + If input x is an array, return the interpolated array with dimensions of x. """ scalar = numpy.isscalar(x) if scalar: @@ -58,12 +60,14 @@ def wsinterp(x, xp, fp, left=None, right=None): # shape = (nx, nxp), m(v) data spans axis 1 m = fp * numpy.sinc(v) # Sum over m(v) (axis 1) - fp_at_x = numpy.sum(m, axis = 1) + fp_at_x = numpy.sum(m, axis=1) # Enforce left and right - if left is None: left = fp[0] + if left is None: + left = fp[0] fp_at_x[x < xp[0]] = left - if right is None: right = fp[-1] + if right is None: + right = fp[-1] fp_at_x[x > xp[-1]] = right # Return a float if we got a float diff --git a/src/diffpy/utils/tests/test_resample.py b/src/diffpy/utils/tests/test_resample.py new file mode 100644 index 00000000..5f0a1645 --- /dev/null +++ b/src/diffpy/utils/tests/test_resample.py @@ -0,0 +1,30 @@ +import pytest +import numpy as np + +from diffpy.utils.parsers.resample import wsinterp + + +def test_wsinterp(): + import random + # Check known points are unchanged by interpolation + # FIXME: if another SW interp function exists, run comparisons for interpolated points + + # Sampling rate + ssr = 44100**-1 # Standard sampling rate for human-hearable frequencies + t = ssr + n = 5 # Do 6 samples + xp = np.array([i*t for i in range(-n, n+1, 1)]) + x = np.array([i*t for i in range(-n-1, n+2, 1)]) + + # Interpolate a few random datasets + trials = 10 + for trial in range(trials): + fp = np.array([random.random() * ssr for i in range(-n, n+1, 1)]) + fp_at_x = wsinterp(x, xp, fp) + assert np.allclose(fp_at_x[1:-1], fp) + for i in range(len(x)): + assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp)) + + +if __name__ == '__main__': + test_wsinterp() From 2d68f9afab91795b924397f9acf3fdcffd8e3835 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 24 Oct 2023 15:35:06 -0400 Subject: [PATCH 043/696] Fix warnings, add warnings --- news/docs_patch.rst | 4 +++- rever.xsh | 2 +- src/diffpy/utils/parsers/loaddata.py | 2 +- src/diffpy/utils/parsers/resample.py | 6 ++--- src/diffpy/utils/parsers/serialization.py | 24 ++++++++++--------- src/diffpy/utils/tests/test_resample.py | 2 +- src/diffpy/utils/tests/test_serialization.py | 14 ++++++++--- .../utils/tests/testdata/generatewarnings.txt | 11 +++++++++ src/diffpy/utils/tests/testdata/loadfile.txt | 1 - 9 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 src/diffpy/utils/tests/testdata/generatewarnings.txt diff --git a/news/docs_patch.rst b/news/docs_patch.rst index 42751fbd..c7353fbe 100644 --- a/news/docs_patch.rst +++ b/news/docs_patch.rst @@ -1,10 +1,12 @@ **Added:** * New documentation build. +* Quickstart tutorial for parsers. **Changed:** -* +* Theme changed from `sphinx_py3doc_enhanced_theme` to `sphinx_rtd_theme`. +* User now warned when data_table data overwrites hdata (header) data. **Deprecated:** diff --git a/rever.xsh b/rever.xsh index edea2f7e..25ddb256 100644 --- a/rever.xsh +++ b/rever.xsh @@ -1,8 +1,8 @@ $PROJECT = 'diffpy.utils' $ACTIVITIES = [ - 'changelog', # Create a CHANGELOG.rst using news directory 'tag', # Creates a tag for the new version number 'push_tag', # Pushes the tag up to the $TAG_REMOTE + 'changelog', # Create a CHANGELOG.rst using news directory 'ghrelease', # Creates a Github release entry for the new tag 'pypi', # Sends the package to pypi ] diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 41e2ed7f..26f1e716 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -88,7 +88,7 @@ def countcolumnsvalues(line): if usecols is not None: nv = len([float(words[i]) for i in usecols]) else: - nv = len([float(w) for w in words]) + nv = len([ float(w) for w in words]) except (IndexError, ValueError): nc = nv = 0 return nc, nv diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index 41f89b32..e41219f4 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -26,7 +26,7 @@ def wsinterp(x, xp, fp, left=None, right=None): This uses the Whittaker-Shannon interpolation formula to interpolate the value of fp (array), which is defined over xp (array), at x (array or float). - Paramaters + Parameters ---------- x: ndarray Desired range for interpolation. @@ -72,7 +72,7 @@ def wsinterp(x, xp, fp, left=None, right=None): # Return a float if we got a float if scalar: - return float(fp_at_x) + return float(fp_at_x[0]) return fp_at_x @@ -97,7 +97,7 @@ def resample(r, s, dr): Returns resampled (r, s). """ - dr0 = r[1] - r[0] + dr0 = r[1] - r[0] # Constant timestep if dr0 < dr: rnew = numpy.arange(r[0], r[-1]+0.5*dr, dr) diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index bb980bfc..c4eb9ddd 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -17,6 +17,7 @@ import json from .custom_exceptions import UnsupportedTypeError, ImproperSizeError +import warnings # FIXME: add support for yaml, xml supported_formats = ['.json'] @@ -64,7 +65,11 @@ def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_c # title the entry with name of file (taken from end of path) title = abs_path.name - # first add named columns in dt_cols + # first add data in hddata dict + data.update(hdata) + + # second add named columns in dt_cols + # performed second to prioritize overwriting hdata entries with data_table column entries named_columns = 0 # initial value max_columns = 1 # higher than named_columns to trigger 'data table' entry if dt_colnames is not None: @@ -77,21 +82,18 @@ def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_c for idx in range(num_col_names): colname = dt_colnames[idx] if colname is not None: + if colname in hdata.keys(): + warnings.warn(f'Entry \'{colname}\' in hdata has been overwritten by a data_table entry.', + RuntimeWarning) data.update({colname: list(data_table[:, idx])}) named_columns += 1 - # second add data in hddata dict - data.update(hdata) - # finally add data_table as an entry named 'data table' if not all columns were parsed if named_columns < max_columns: - if 'data table' not in data.keys(): - data.update({'data table': data_table}) - else: # if 'data table' is already a key, keep adding primes to the end - dt_name = 'data table' - while dt_name in data.keys(): - dt_name += " prime" - data.update({dt_name: data_table}) + if 'data table' in data.keys(): + warnings.warn('Entry \'data table\' in hdata has been overwritten by data_table.', + RuntimeWarning) + data.update({'data table': data_table}) # parse name using pathlib and generate dictionary entry entry = {title: data} diff --git a/src/diffpy/utils/tests/test_resample.py b/src/diffpy/utils/tests/test_resample.py index 5f0a1645..e3395f44 100644 --- a/src/diffpy/utils/tests/test_resample.py +++ b/src/diffpy/utils/tests/test_resample.py @@ -12,7 +12,7 @@ def test_wsinterp(): # Sampling rate ssr = 44100**-1 # Standard sampling rate for human-hearable frequencies t = ssr - n = 5 # Do 6 samples + n = 5 xp = np.array([i*t for i in range(-n, n+1, 1)]) x = np.array([i*t for i in range(-n-1, n+2, 1)]) diff --git a/src/diffpy/utils/tests/test_serialization.py b/src/diffpy/utils/tests/test_serialization.py index 5a613a02..5ce3b798 100644 --- a/src/diffpy/utils/tests/test_serialization.py +++ b/src/diffpy/utils/tests/test_serialization.py @@ -14,6 +14,7 @@ schemaname = datafile('strumining.json') wrongtype = datafile('wrong.type') loadfile = datafile('loadfile.txt') +warningfile = datafile('generatewarnings.txt') nodt = datafile('loaddatawithheaders.txt') @@ -69,9 +70,7 @@ def test_exceptions(): # check equivalence assert missing_parameter == empty_parameter assert missing_parameter == none_entry_parameter - print(data_table) - print(missing_parameter[data_name]['data table prime']) - assert numpy.allclose(missing_parameter[data_name]['data table prime'], data_table) + assert numpy.allclose(missing_parameter[data_name]['data table'], data_table) # extract a single column r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=['r']) gr_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, 'gr']) @@ -89,3 +88,12 @@ def test_exceptions(): no_dt = serialize_data(nodt, nodt_hdata, nodt_dt, show_path=False) nodt_data_name = list(no_dt.keys())[0] assert numpy.allclose(no_dt[nodt_data_name]['data table'], nodt_dt) + + # ensure user is warned when columns are overwritten + hdata = loadData(warningfile, headers=True) + data_table = loadData(warningfile) + with pytest.warns(RuntimeWarning) as record: + data = serialize_data(warningfile, hdata, data_table, show_path=False, dt_colnames=['c1', 'c2', 'c3']) + assert len(record) == 4 + for msg in record: + assert 'overwritten' in msg.message.args[0] diff --git a/src/diffpy/utils/tests/testdata/generatewarnings.txt b/src/diffpy/utils/tests/testdata/generatewarnings.txt new file mode 100644 index 00000000..7f65a9b9 --- /dev/null +++ b/src/diffpy/utils/tests/testdata/generatewarnings.txt @@ -0,0 +1,11 @@ +data table = generate data table +c1 = time +c2 = position +c3 = velocity + +# label: [c1] [c2] [c3] +0.0 0.0 0.0 1.0 +1.0 0.5 1.0 1.0 +2.0 2.0 2.0 1.0 +3.0 4.5 3.0 1.0 +4.0 8.0 4.0 1.0 diff --git a/src/diffpy/utils/tests/testdata/loadfile.txt b/src/diffpy/utils/tests/testdata/loadfile.txt index c5babc89..326c9492 100644 --- a/src/diffpy/utils/tests/testdata/loadfile.txt +++ b/src/diffpy/utils/tests/testdata/loadfile.txt @@ -15,7 +15,6 @@ qbroad = 0.2 qdamp = 0.0001 stype = Neutron stru_str = baddata -data table = no_table 0 0 1 0.00001 From 9875d5eb6ddda3b2d692b084b150264c8a37f6e4 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 30 Oct 2023 19:20:19 -0400 Subject: [PATCH 044/696] Finish doc writeup, fix jupyter incompatibility --- CHANGELOG.rst | 4 + .../source/api/diffpy.utils.parsers.rst | 4 + .../examples/exampledata/parserdata.zip | Bin 0 -> 1034 bytes doc/manual/source/examples/examples.rst | 10 ++ doc/manual/source/examples/parsersexample.rst | 104 ++++++++++++++++++ doc/manual/source/index.rst | 16 ++- doc/manual/source/license.rst | 2 + doc/manual/source/release.rst | 4 +- src/diffpy/utils/parsers/loaddata.py | 9 +- src/diffpy/utils/parsers/serialization.py | 39 +++++-- src/diffpy/utils/tests/test_serialization.py | 2 +- 11 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 CHANGELOG.rst create mode 100644 doc/manual/source/examples/exampledata/parserdata.zip create mode 100644 doc/manual/source/examples/examples.rst create mode 100644 doc/manual/source/examples/parsersexample.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..1484206d --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,4 @@ +Release notes +############# + +empty until rever changelog action \ No newline at end of file diff --git a/doc/manual/source/api/diffpy.utils.parsers.rst b/doc/manual/source/api/diffpy.utils.parsers.rst index 502394d5..82c70ed7 100644 --- a/doc/manual/source/api/diffpy.utils.parsers.rst +++ b/doc/manual/source/api/diffpy.utils.parsers.rst @@ -1,3 +1,5 @@ +.. _Parsers Documentation: + diffpy.utils.parsers package ============================ @@ -6,6 +8,8 @@ diffpy.utils.parsers package :undoc-members: :show-inheritance: +For a sample data extraction workflow, see :ref:`parsers example`. + diffpy.utils.parsers.loaddata module ------------------------------------ diff --git a/doc/manual/source/examples/exampledata/parserdata.zip b/doc/manual/source/examples/exampledata/parserdata.zip new file mode 100644 index 0000000000000000000000000000000000000000..c06cefa1dc297c3f31e86cf9945e6902b80f7605 GIT binary patch literal 1034 zcmWIWW@Zs#U|`^2sA-G{@3``(>m4HlgBS|~gD6n6q_iZzC^Io9C9xz?A4KStRFt$% zj?KPpAky}p_aA@LH#0|ft6x%Y6AauWVE_)in^Su8-`_sFQ)X=4d0%eT zt@-Tr{$lJ0{lvW4w{32v+4C;Xe3@+D`(|41bAQL02$$P){(djud8}CWW!CzIxot01 zC;T(~J^y8*U8zpnLybxp#b?MJl9;}Yaq0CrGFI&|9zt{4 z4yzoJKgUV@1mgk+ZJwoFmuYz?~D*!Z;7 zAY);eBcEr+${tUb(kzGFuSB+b9P(H?kt2Mrlw&IQ!V10T0y4r?-M;-S zwb)pfGTV4}!j0m{FzyjnAF4l*lBn6 zt_9nVnC;Z%6<@n=Z*#D+_PGOdem>A#9=k3`r+&IfvgN$fH5u(iLEc}(&ldnsHPIPfi!Wx# zbjTV>=L=e~r&(vDcm6%_M$k!xZ)Om(fi)lh+Qrpg~JY*xV#Rf` and take a look at ``data.txt``. + Our goal will be to extract and serialize the data table as well as the parameters listed in the header of this file. + +2) To get the data table, we will use the ``loadData`` function. The default behavior of this + function is to find and extract a data table from a file.:: + + from diffpy.utils.parsers import loadData + data_table = loadData('') + + While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is + due to the comments ``$ Phase Transition Near This Temperature Range`` and ``--> Note Significant Jump in Rw <--`` + embedded within the dataset. To fix this, try using the ``comments`` parameter. :: + + data_table = loadData('', comments=['$', '-->']) + + This parameter tells ``loadData`` that any lines beginning with ``$`` and ``-->`` are just comments and + more entries in our data table may follow. + + Here are a few other parameters to test out: + + * ``delimiter=','``: Look for a comma-separated data table. Useful for csv file types. + However, since ``data.txt`` is whitespace separated, running :: + + loadData('', comments=['$', '-->'], delimiter=',') + + returns an empty list. + * ``minrows=50``: Only look for data tables with at least 50 rows. Since our data table has much less than that many + rows, running :: + + loadData('', comments=['$', '-->'], minrows=50) + + returns an empty list. + * ``usecols=[0, 3]``: Only return the 0th and 3rd columns (zero-indexed) of the data table. For ``data.txt``, this + corresponds to the temperature and rw columns. :: + + loadData('', comments=['$', '-->'], usecols=[0, 3]) + +3) Next, to get the header information, we can again use ``loadData``, + but this time with the ``headers`` parameter enabled. :: + + hdata = loadData('', comments=['$', '-->'], headers=True) + +4) Rather than working with separate ``data_table`` and ``hdata`` objects, it may be easier to combine them into a single +dictionary. We can do so using the ``serialize_data`` function. :: + + from diffpy.utils.parsers import serialize_data + file_data = serialize_data('', hdata, data_table, dt_colnames=data_table_column_names) + data_dict = file_data['data.txt'] + + Now we can extract specific data table columns from the dictionary. :: + + data_table_temperature_column = data_dict['temperature'] + data_table_rw_column = data_dict['rw'] + +5) When we are done working with the data, we can store it on disc for later use. This can also be done using the + ``serialize_data`` function with an additional ``serial_file`` parameter.:: + + parsed_file_data = serialize_data('', hdata, data_table, serial_file='') + + The returned value, ``parsed_file_data``, is the dictionary we just added to ``serialfile.json``. + To extract the data from the serial file, we use ``deserialize_data''. :: + + from diffpy.utils.parsers import deserialize_data + parsed_file_data = deserialize_data('') + +6) Finally, ``serialize_data`` allows us to store data from multiple text file in a single serial file. For one last bit + of practice, we will extract and add the data from ``moredata.txt`` into the same ``serialdata.json`` file.:: + + data_table = loadData('') + hdata = loadData('', headers=True) + serialize_data('', hdata, data_table, serial_file='') + + The serial file ``serialfile.json`` should now contain two entries: ``data.txt`` and ``moredata.txt``. + The data from each file can be accessed using :: + + serial_data = deserialize_data('') + data_txt_data = serial_data['data.txt'] # Access data.txt data + moredata_txt_data = serial_data['moredata.txt'] # Access moredata.txt data + +For more information, check out the :ref:`documentation` of the ``parsers`` module. diff --git a/doc/manual/source/index.rst b/doc/manual/source/index.rst index 258b1ed6..f150e77d 100644 --- a/doc/manual/source/index.rst +++ b/doc/manual/source/index.rst @@ -7,11 +7,16 @@ diffpy.utils - general purpose shared utilities for the diffpy libraries. | Software version |release|. | Last updated |today|. -The diffpy.utils package provides functions for extracting array data from -variously formatted text files and wx GUI utilities used by the PDFgui -program. The package also includes interpolation function based on the -Whittaker-Shannon formula that can be used to resample a PDF or other profile -function over a new grid. +The diffpy.utils package provides general functions for extracting data from variously formatted text files as well as +some PDF-specific functionality. These include wx GUI utilities used by the PDFgui program and an interpolation function +based on the Whittaker-Shannon formula for upsampling/downsampling a bandlimited PDF or other profile function. + +======== +Examples +======== +Illustrations of when and how one would use various diffpy.utils functions. + +* :ref:`File Data Extraction` ======= Authors @@ -40,6 +45,7 @@ Table of contents license release + Examples Package API ====================================== diff --git a/doc/manual/source/license.rst b/doc/manual/source/license.rst index 7d78e94f..0719ab2c 100644 --- a/doc/manual/source/license.rst +++ b/doc/manual/source/license.rst @@ -1,3 +1,5 @@ +:tocdepth: 2 + .. index:: license License diff --git a/doc/manual/source/release.rst b/doc/manual/source/release.rst index 7ec4f81d..a7afad17 100644 --- a/doc/manual/source/release.rst +++ b/doc/manual/source/release.rst @@ -1,3 +1,5 @@ +:tocdepth: 2 + .. index:: release notes -.. mdinclude:: ../../../CHANGELOG.md +.. include:: ../../../CHANGELOG.rst diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 26f1e716..0bb261bc 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -45,16 +45,19 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar Useful kwargs ============= + comments: str, sequence of str + The characters or list of characters used to indicate the start of a comment (default '#'). + Comment lines are ignored. delimiter: str Delimiter for the data in the block (default use whitespace). For comma-separated data blocks, set delimiter to ','. - usecols: - Zero-based index of columns to be loaded, by default use all detected columns. The reading skips - data blocks that do not have the usecols-specified columns. unpack: bool Return data as a sequence of columns that allows tuple unpacking such as x, y = loadData(FILENAME, unpack=True). Note transposing the loaded array as loadData(FILENAME).T has the same effect. + usecols: + Zero-based index of columns to be loaded, by default use all detected columns. The reading skips + data blocks that do not have the usecols-specified columns. Returns ------- diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index c4eb9ddd..55dc4d9c 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -15,6 +15,7 @@ import pathlib import json +import numpy from .custom_exceptions import UnsupportedTypeError, ImproperSizeError import warnings @@ -23,7 +24,7 @@ supported_formats = ['.json'] -def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_colnames=None, serial_file=None): +def serialize_data(filename, hdata: dict, data_table, dt_colnames=None, show_path=True, serial_file=None): """Serialize file data into a dictionary. Can also save dictionary into a serial language file. Dictionary is formatted as {filename: data}. @@ -35,7 +36,7 @@ def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_c Name of the file whose data is being serialized. hdata: dict File metadata (generally related to data table). - data_table: list + data_table: list or ndarray Data table. dt_colnames: list Names of each column in data_table. Every name in data_table_cols will be put into the Dictionary as a key with @@ -120,11 +121,18 @@ def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_c # json if extension == '.json': + # cannot serialize numpy arrays + class NumpyEncoder(json.JSONEncoder): + def default(self, data_obj): + if type(data_obj) is numpy.ndarray: + return data_obj.tolist() + return json.JSONEncoder.default(self, data_obj) + # dump if non-existing if not existing: with open(serial_file, 'w') as jsonfile: file_data = entry # for return - json.dump(file_data, jsonfile, indent=2) + json.dump(file_data, jsonfile, indent=2, cls=NumpyEncoder) # update if existing else: @@ -133,12 +141,12 @@ def serialize_data(filename, hdata: dict, data_table: list, show_path=True, dt_c file_data.update(entry) with open(serial_file, 'w') as json_write: # dump to string first for formatting - json.dump(file_data, json_write, indent=2) + json.dump(file_data, json_write, indent=2, cls=NumpyEncoder) return file_data -def deserialize_data(filename): +def deserialize_data(filename, filetype=None): """Load a dictionary from a serial file. Parameters @@ -146,6 +154,9 @@ def deserialize_data(filename): filename Serial file to load from. + filetype + For specifying extension type (i.e. '.json'). + Returns ------- dict @@ -155,13 +166,23 @@ def deserialize_data(filename): # check if supported type f = pathlib.Path(filename) f_name = f.name - extension = f.suffix - if extension not in supported_formats: - raise UnsupportedTypeError(f_name, supported_formats) + + if filetype is None: + extension = f.suffix + if extension not in supported_formats: + raise UnsupportedTypeError(f_name, supported_formats) + else: + extension = filetype + + return_dict = {} # json if extension == '.json': with open(filename, 'r') as json_file: j_dict = json.load(json_file) + return_dict = j_dict + + if len(return_dict) == 0: + warnings.warn(f'Loaded dictionary is empty. Possibly due to improper file type.', RuntimeWarning) - return j_dict + return return_dict diff --git a/src/diffpy/utils/tests/test_serialization.py b/src/diffpy/utils/tests/test_serialization.py index 5ce3b798..1d7223aa 100644 --- a/src/diffpy/utils/tests/test_serialization.py +++ b/src/diffpy/utils/tests/test_serialization.py @@ -42,7 +42,7 @@ def test_load_multiple(tmp_path): target_data = deserialize_data(targetjson) assert target_data == generated_data # ensure file saved properly - assert target_data == deserialize_data(generatedjson) + assert target_data == deserialize_data(generatedjson, filetype='.json') def test_exceptions(): From 47d53b8dac9b67302c947936b0240c3ccc1e03d6 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 3 Nov 2023 22:40:52 -0400 Subject: [PATCH 045/696] Add wsinterp in __init__ --- src/diffpy/utils/parsers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index 1965df9f..fb0c471b 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -18,7 +18,7 @@ from .loaddata import loadData from .serialization import serialize_data, deserialize_data -from .resample import resample +from .resample import wsinterp, resample # silence the pyflakes syntax checker assert loadData or resample or True From b5d2c6d422643836eb0ebfd7e81fda02e7293bd1 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 8 Nov 2023 04:45:24 -0500 Subject: [PATCH 046/696] Add tutorial for resampling function --- .../examples/exampledata/parserdata.zip | Bin 1034 -> 42784 bytes doc/manual/source/examples/examples.rst | 1 + .../source/examples/resampleexample.rst | 78 ++++++++++++++++++ doc/manual/source/index.rst | 3 +- 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 doc/manual/source/examples/resampleexample.rst diff --git a/doc/manual/source/examples/exampledata/parserdata.zip b/doc/manual/source/examples/exampledata/parserdata.zip index c06cefa1dc297c3f31e86cf9945e6902b80f7605..6659e54852cb41c9262390f5784012605351bc0d 100644 GIT binary patch delta 42330 zcmV)RK(oJ!2%yda9#BgI1QY-O00;nld01D7)clI>0RR9N1ONaC00027D**x%hger8 z_sN_R0RRBK0RR9D00029OadSQe****000080MH3&R}>Y-F@_HS0P!IJ00{s908VLR zYh`RMXL3zh%Z_D75!|1zxYBq53%9EBQI7=+1cD7hAeKC&_INzB@JtUqJzxv*??gn_ zwXiMuMAtc0Sy`Epk#*nPe)z+$fAjgv)9bt2ubw}C_w@eD%j4_Q^T*qpf9uZ}`Rn!X z$A7*1{@us-uYbS&;?^Ge@^O9p`1<(W^UH_FSN!ddA3t1AA3uG0{qE`ecfh}YdU*Ty zr|X-?xBqzm^8DrFx99KIhv#qk{~ul+f4shV|M~5s{@fqh_4fJ0r{~X4T9*@C&tG0K z;p>l|_{RH}>z^MUe|Y-%f0@f!;)8#Fc={-FQbYGYztr<+dOok<@$>7uPxaidmru{% z|0uAp>ka<7eSUp>dA)H@*PB1x`t{~_x0j!N^_O4%@~fZUZok#j-~RKp{r9V%U+vZ| z_~7<6rfS_*=TForS5i)jDV11Toj1*C+>f z#K)-h=SEl=kbIxMkr|`b&mJQIwEpfm*A0~PMdrfexUB~_26z!>!qHaJ!a9{$T$=<^0xg61~+y>;^Ju#{r`!M?{2cV!9x5&TV28 zk|6epRE7Qha3I`TnjXch&;*a@!b{m@Y2u!tBDhSN2yApY<0 zF2cN9g(Si*2a(bD*f>f-w+hN|=K`jvoLD^w7?;Cl3Meo$`_LRR#KAg7#JUhZw`<1D z+=ama4(DWU)&UYCjNA>}+Fb`@aG~BRBl_MUe-xE8+z1&F*5>H-;$RSEdeoo6z7#R6x3T$RP;E zDL{t|ivpz@pf@^A9tseAw7TYG>ZT{;M;{d*3+h5hx$CaPQyhW0;9@doW4swZnnyQa zIZ$Drl`_*ta5qyaV}0yJz}kaYcY(9Fe@QE6Ii!KVG|8&4&7nE=IeQe?ULkCqxwK>n z@@0aa`k_(C6lch+vV=Uk1!>8^;z__7=6f}PQ7;B1L|=!^(@{D5l27_JQgbJjvbsBS zgLyn4VX*X;Aqf4&mG=qO0M&IuuZn=w6(pf;oCi0)6|dn7tU@HGFaFtX1NmKBC%MD?)3iO?(fguuwTpz{sqwa1x+L`d&j*h(yMg zR4Z1+ItY3qB@Gy{Y2_np;FGvaD?1gW9bQFdL8e$&uzKIFAqHy?C`baHOR5lAk*X(3 zgobFw-iESjT~?s@qAU>cSP+jpe>J;|@U}RKiWyQDE8Q72Senf4>TW`|*7pRmn8{7f#p;_e>ZpsHXgAV zqjC1A3gg?jF>)%WHOkKc;qmQ>oHa11yg-CZf^_m9~i9NWH6M*^FSlTw&VL zrD#+f(otw=%qYr=4Y^_Ce<*+n4kJ>KK+U7Z**J8DI;QWA5{4pZg2y7BEa4C_Leq63 zQfteV8d{V{tJkEPBM|lgbl;>_vf@>y6@zu&gOyE!&C_s1A9EEF!R!reAdE}LO5qK(`WIq$`@=sl9pIwQ2B^`2q{2eyT> zzB2mR>xgjB9fUw!(r1Cyh!ic*Sxqa*mVrc|Z`2)-Sv^WagHRTMRo&(%aasyEot7M$ z9q8!vna&5je_wFABVI@OA>cugagwt!=2Bi^ z55T>bfSpt>8q*ehAz3};77XZ2vf53w%e;T_!?rBHp?7sg&|TRDi&)SoF?UAA41yJb zc2q3%j@uwC`Dkcd1?!xkU5zMog5V2{spE1L3NNy4e;5NRX;dl&)BrZ{Q3f@3p*7Pk zR#NTox+UshL6|qoBDRz$_C70YpfOQceHO_iVnc`qf%e$tco8p}noWP*dAIRq!i#qk zeQ>=gb1)-G&F2wsfueVLT?_PJS{&Ym#z7JDRoxfbV(z#u20AOKAJQEIoT<+gI2}!! z(D8V(f35OWfdhZjl%m}Rve28Nm_1pFT&jU1e6C_NP^5xLr__B>?)p)*K?Wj{Exf5h zgW;uUQD;-r$SF`oLr9i1-3s}+Du~WPOjwD{W0+So&4~a)-HLN@Y7^ZKk*pa|kj^(x zN~Y5xuE&my#&dZF+>Fu@e&S|E&>2VTLYlCPk8`IwAhk>{i?>jT zJveJMAz+ZuyLG{hYND}9Z<0oh9wmb!h~XpU5p!U?h>{R!w~*cul~XwX!8gkyLtrlr z)=l>Ub!|dfG*GobmT@O!^v68*B4nI0f8>=i{t$iF)hKmri8di}2vyy5o@@9xbTDqPcW`N>doAc|t1;?>nk zMsafof7GBVDe7tyQl9am3D%1c`f?uBq2@-`X~JbwU>+XTxo^P1qB#uWGRk@LEPTQn z1h)w5?9gL_DC1nU(Sf2ce+bJexF2ki z=a;sGAi-&5KkNIOiDR4{Ota-=$qN)rB_sc*`28?4`688LA$Dnmb@+#DtcG>Amv zGK|4eOI2LK(7?w?&(PQ9ELJENF2mSYFhqE*e~hBu^WE1BwS6=Ksbvr< zx+Z%BK_#M;pbS`IdRJ~2l zcR7;gwSPXvep%YCpMRh5|M-Vr{{BDx;m3dc-QWKo|MZtX|LdRrq5l5kkAMCL{r|uH<#+$( z$AA2%zyAI2_;dfI{rLMo|M6e`{MSFwuNwIA=YRb*8~pXZ{EOcBr@#F8&wu>gzy0A) ze^qy5f8_`N`Hz426X+}+eSiLofA7ZcdL@7U_1EA3i+}eYfBBa`|MPzVct3vpP5$%q zufP88FTeiOn11}t|MIi{_?!RX=U@J-zy06-{lEX)fA{m}f6uS~{r~>E@&Ekozx&aC zwjXNc=ilXjTbu8^we^=#e|bA%AN?i#mmd2Le_omOhkb7dUIo84$J8IT|1esM#4~@M z?~QYp;9KyG{&?ZlAJ)l!@(+bqA8jtZ&_6`aUZ)GK{vkTfdxyWoPyb=W+fRR({=?~e zUEWyyq4)h_2;PR*+CzBtWB9JMXyE?zIOZ9={|sjXR|6gOcwdkn{pUxA&SO9CJH`Pv ze}aAN)kVlr_}xBx*C@xT5xl;x5E%O#Vj`A1(J$Z-iaf-{CTal-z7-W;)LFpb_C<2{}m zy`1O#SFq7r=j_en`<>z-YWFgh|TLpZ}d29h&8_{ z7?XvznCN~_V;wHm;hed$wG0-^@XRCzpjKgTw;ev!8tnP5fDIp;jg_#q{Ki-jfBI@` z@r`|sI@nry(UbZj_SU+KT!SyatILMWe-#%@%Vj_2lsC5Hoi&R|XpR1Q*6%%`UM_1Wr?g)U z^0I_-SboR?8a;_cx2&IB-G}Kmw6iHae5{^vaoXL&Aq3@ah`_bzh=()U;Pxd+le{v@$g$0I3 zeziGz@&6w0Y+hb|d1s%!cz<8-PChLv-8FdEWXg3&L#9izm|RiaJb#D-TtNn3KMfF+ za^6-Byo=WhT-m8w>Yx-?tG$cO{~b%`G@;Z%DY=~=f>G|Rv4l|vqj_?0JO!m(k)5Z* zkY=%qy#pne1gwWiGW9aTe+~6DHJCB0o9>s5lD4?WV!l4dTfxi5sV5qOj8ZbwTxh-c zdG639$m32v*oCfHd7QU_Oe0~$tM%@QDFn^KlfgIW8y}I@1}q}&34P7K=k7+|?0e=y z682`^D^KsGZfQ3sOt5&O=mdgOF8vL{X52egQbHi#?xd0ce=yqTUPfb{y|bOp zt6-Fj>1r@$*rzws)BHNQLmra}F29;2rUS@pijFoc6Sbsh>jkh}Oa z_${NfjMALqNnL!J!R$6@_!?}imIX?CT(7fT&t4aib~l(OzlgMr(#*q3y>8=_htJ?; zkmz*;l*a|{nj!1=HBn+eDE|e67ThP74sHz^%@ZG z7B@{g{22Uvl7t4FThQx3JnIsZ1%;pGVJm0?U2(Pn*A}-n{^?I)_K9W(9MXL22ZqgKfcwQHuGh$mYVSZnA^-ZgCo!ptb9ov5Dy zcnS&Q)J>cuD>#@qxt#POg^3e$<4zzCxKi}hbQ-EB>x(Zw%^a0M6VMYja> zd8$uWe-uboe3DNN7Hcx%Y>PvBh!1BkD;lqllBez#_Q`g~FX%7=hA27wJAR5UNk1Cmw>pwjLJ%OCRPt1) zf5hWlP2uK{(~|`*)`^*I(jSD(Yg47l`A*Q>f96ilh`d46W>0ogmFU&lrMcBi_ z9J#(Un#01B0Lz=K%;=r%evYBdN<7z4DyxFnBGFIbR2?CLnmtkcOMAn{=_H9F(?P;DW^`17bzYBl&s?A&iZa$e${w$skizj}uvLqQu>lAKMm3GYsotd2e=0~gq-c4;7CS@=v^xZ$tIK+#<}pNsII{+cAtL46 z%ZlD*8p%c>h=tpca=c$=nj)nsgG^KIud{gB(=;S23j74g2O+k_faFN(h{ctXRo927 zc-&^hX15Xwc+p!rMNZ!IM%oiXnce29XCs;2ZtMY3?Dm8=i`nft&7FhFf0daoAs(dw z+tj()ZBBV;2xhle2U--jmA=MTV3MQL*vxEG{00Dz6tAI?nQh8{EEF@_*PnX^j}++H z-UcEiECJw=@;n1Iw|#RnP;=WnAJoc_OrNcF3c1ve`aypm)o=pG9tOXc{Yn!d}fNsrX~nHv@&=G9?A5RBh8Q7 zK6);08_Aawuy~j<*b`8~Ys#*Av6oUNGpRsp(|JT6b!gjiKY13a4=l^GN#zlw?Vz)s zKF&{{!#uf?<$7N<`6Pj)F09uijpSC_8g?>UagyAI0>)2KR;+f4f7Q<1<@>4AWVK5> zckum6AZKg|yCFI0?bPlCoRpxXajVbdvng8LfHKKucTzsrKmroLWbPH3AvrA-ktJ+G za$3^z0q|NfA3m89+Gb-4LV{ZIlqGDEzwSIi2MB}^=qYsS_f5=FFbEoZmf(iol9aly zshdny6L1*J6an(ke-2S2*{M$TarJ$2Q;E8=I!-ZDX@rYzl9%d`I9JG+m8J}{=%;Ll z5>94W4rE7wA~uSo#sW}^o{k=YrCB_>TRxTMV~ERaBjp%jyjYtjx0bqJtj!cICvSR> zq~=1K-Xpng1SBrf+Aj0H0rCU@=s-_Ym$g8umh7+-hAK^Kf3gb1PNira)}gqmG^_4s zv?yg$IEt4_kqT%mR+`#NDvrWxgUHBN)gLdW0RPnF0g(}_iwI{J8dLN-;mX2F*yc71f8_Zh4mQgykW1aWwamIX35}=N-nFRcUuRy57qZ&SUoe2bA+cnNok~w= zlV$XD=E>65)#lULn_E5r(Ck`=eot$5N?jJ9HJeMO)1J6St|u186W2&p!mT}?NEG1f zC&_e)jjE|!@JmcoeW*h*2=#uX*elbg1ozxQG)tZy7F%6ytKF5XBYE_yyS*@u* zUfyK2f99I-n=IGVQq=c22Be8B#8#=x5u)w@lPRxdLT(?^{6cIUQoxQ-4)2>9YSb3K zP%OZtX(9yqm^;g@_?Syl3$Xkg@|Z4UR-H2syuz~3mC11Z@@;_8G`OiT^I}VKTv=K^%dFkI4eH>?aeu1REP%`+5?~R~cMWZ+I*_8(edRbc3rT7q+ei z(^S4$K<1}AIkX5{{4{kzA#>`830k@4&pec39~%z=)f$%yW)HzDhNZY7^4f8>pG@~{v9CNs}yAY*bSyykgVH7S$}(Nt3) zk$)kY4m?pj^C6S{@tqHu6@F%-OcTYTX|C~HujV=_x6B{=|1$(E0i`vx{AQPA>NT=U zGVQsthalTlk5uek9BbuJ?sTuf4K&C zu~u#C&AYZduShm6u&k@8@~WRQuSyHK0&UaGMajcc=x^G!c?dOUmrY=%w4v{8lnFVe z_h%D0+8VoT0x6JpUbN&MGA!)ku8pvYUEDSIs#{^6y=Ko z&0eKfYjVt9S1v-mX!bf^>Z5|Ze|lpvr)IAqL1e>SE$GmqSIn$NAc}T0&ult6lNmO9 zO>N@{OH(?}(Wwy>d+qWY3$#HXm&RmB@K?Frjj{G1jYdUk|o^da{$PUde`5x5?dm`7GIT}D-{C)Qmj%m0h=RPC=WU| z?U5|huLQ{L*n@Soo&*t)f02jabsOy2nG*o!c3|b|GO^0G+9zAqGHQx}`p`mZZgyHV zPwlz5h2<2~L1kXSerhIqs~WzJ#(7h2qDjh8O`=3W|p=!@$z7DrtrwT0KVo6rXP zQ^5g4?ld)Te*>yQ!-TfYr`d>{vj>G=7I4{J4hp|a6aiUxncEfdb5Qs@ffzJu38=|s zc77?t=3)8Rbb7osY&p%jS^CPB)0NT^FCqiw*}@_yN7Z`Fy9Lx#_nXFE@c?V|Ml-O} zJ#Yz~iU-ia06DLv;#?(qNIX}QHni0z0bL`RJ4=mte=b?m>E-HZ&3z?{>=8$m=oAe8 zVxN2XVUIYfY~IPCL{T*UCxwgNF}0sAA^f26zrKUfTjS4-`hW(Dz8mRBlv{4ArqMr) z{tBPsnJ`JYOHQ3rF#tWaFVJ)ZS1jbeQxRNA#HH~$fa~VO1bv$mQ=92Sye;tjhW8?r~(0xf6-o!N%AzRNCe-$*f zldt%zA{u235u`OT<*XOk=pZS7)d=qY)avD@y!QUL6e5xv3Ji;ha`=ck=CC8>5zwh< ztr^uW1lp-0@visNOqE{rv8bAI1;Z*)HAO4UvG-^nSJaqRZ;s4a7eQ4#K(3Zcpq4Dv ze>%1=aaydCrrxJr0C~u&_vvM`QVYZSiv<<^Zj8hy%P8pq09A%Kieqn{G-NB83CAQhZ?o9H5nC8VX0b z@ZgrL#48sjoZDUowfhCrDk4h|(<f^75eg2?I`ejV zb!Zl`#XR-adPAn(TdX+c$;Ygi6>GU{wQN=_4G#zkb<7OcoOmXqyJhD6wI;}iX`9xq;l~?zt)sj?1|+{ z)ERkXKXF`1n{rbL%M19VKI4MFtwTz>x=a##CWIhrp8-+mICNAgO zD?7kU3~9$aU$HK)B-lXqdL_YLnS)3gFVBBv^U2`%sILe_-VTmRa>7 z!Acs_f`3S`a%Scn2s(mtjb5H5CNleN1}+g+&cKvp5(4?my`(4;fa_U?!s8 zl+F6VUq$2zDq^qlHf}>3s1J8wR20H2=mN!L4#ixBw0!^X7e;pzF@VnKx)GaDC07Bn zFin-k;3l7?tkBi>geVH`e-tq%Vtp!On`g%gfI@U1yj3!DXgFrA?}@R$Vy)G_S*slG zYQxd5iccK+&f%}bG^@W#RFJp~NN&J*vamkK1}K`6b}ZIs9|Ev<9x3@xd{t(k$Lhs~ zp~MpX)`E>C&-8Qj)!K&@v?W+4?_gZmTji}$pV(V(4qr2Yz4b;le}GBEt9r}coTnjc zdduFdXmlZ?mxc~!{g#I$u|N9cXvgfQ=$vP>K*+lw02fJ^0+dU=iO2Kgdk4dmr??)p!78) zyH|FB(HVp70x9_)e_piVtPOWy!`bfaE$gB(q6@HLAlVC#*w~8`>Xl_+u{~`UkR?~m zzcTtNo$HldK+Y1Dg&p?Faj-C zFXHlBiL<$F7Z{hpb*7|E%VxDw<(I#P*XqP*AY!SuEm`Rp1=(h~Cgg(^h=1`Y%r_w& zuQ%73(lsPfX!k9-@dUyBb6p3J7V};N?l-YqS^uT83et9x0fFHScIS|nT9Eci9t4lx zhqlz{1pF(gf01X~Z3dqa$u7U2noQpBM}yOyaA$25Hd&F6nAUmat`fBH9z72sq%_Mf z>1R0X*OFOFpe5|RkKc8=&mNv60x>3;hJuik@c;3$&98aNPb1+3gdFnO(6urUy5c(p zT3Tu|jER#F&c@jxm`OXAkS)Qt;NwZIL+ zNdwT`{5S>qB9q14zFFf8NIjol=pf)@fxat3E$uOSD}x0}oH0MmzBExXnB5XsI0qu?f(kZ;SLKX$}aW z&D-i|(WmTwLGDQMl7E4*NgkbwL;{e7=(giEmHb-{#gU0`{LySe-tT`BF^BmH*DXcwxjk$U?VLxVy?m+frKF=?q2Pq|KF={eWX^=+P<=_12pc*A9DOg_fD&Lc0 zh4lwkPHx((INli3iQ(5MVGHoMe+ongE(?X6qnAIRS)}IM-5kPVe63+I$(|PD zfTQn6rnW%BSr<+Lfwr>c(ZEv#u&pRO0UUKhTDc&dFWLy8I0gZac_XKjnq*fGei8!B0vT%YJ>?WBNf}m5|k)-k;%0TNps{YB;e3d1#CN3&KxM0@%U|{Z0m6f)9{5~>fN;sS%!Mu z#3~DN3c|Ex6LKy>_hTD!@;y_of5LB5%b*`yky9jkZn+HFZr3|-4)dGx$;mq?&_rDA zLN!a@Jta+gaZ=e!S<>_8tg;;X^yYT_m_|^~nZPCK9keQfERU4q=ci85Q|+@4iMhBS zhfz5-iVeh(H~00lU{wyKTtlDd93fdy0i8V^W?tBp*T-V9E9Z_|p26pke{8ul0ZHZV zvzgG3Ajn+*o}>px#|ekt1n!QJnqiBmuN^zGV|l5EVh`!W*cUPLOJoKpQ2VG;@Jr)%ny@ zwCof;7FcxD?4AdZpJIH-e|Cc*bX0yxRUp;T`Qs{hULZ>U>`EO<5Y2w7HUSW7zv35o zx2z#gv6=!={QFh-t3V?)WmXorjPh`|_UYvj`~0#MyeChjKnKbt69gSC@Pmx@5 zOqo4!vDQ^iZ?GE7*!Nlrxeh3u#cO zO5dM_;6%lrlaWqBzvl2{5a1g_4iP~TTO__S$$0sW42wW=)7|6VaPuWY-}Hu`?~x@Z zNaEUk@_Y%x(|6^KP6$`wQ2c}dhgvHXT@dcRGmJ9+zAGUFNF!LvF$Tip7u__$=a&ao z12KF3it#GCL#^YGA#23Rr_>h_nfCU)RY zr+bx8u8-eRgHt?%R5m~>HOynK*os_2gF&6LGLPdObj&Oje{&>|8WeG!_Z~LrbB9;e zE#bhAf=N(lX&*L0D?{BcGI=LFT^OPF^laG(-O<5^tGS_4jtgn#5 zlOLGwPx`?8HpP=Zpv-T&ek5aVxga{JYl=^n8KslDrikO|?enA%+~Ym?qz|OtVEi9O z@P5l(PY(ZS^`!i_A48+sGW*$y^hE8?ysxU&%(`HxK4G zQn$%m(=I{mC38(&OEp@$GS|Ap+yDeAB7jD)PcK(WAa$*i!}x#@+n$W#J)B7K^2DG1 z5Syd)LqEmy_MQUOde~wz*F%&JTTGH^^$jub*{J3ae=U_K@CF5g+w)ibKwjZ;>QaF8}Zl~6J9;O*|j%v}h z)7lia!t^+=E!j{95hu3YIqIpsac>wc2eI`O5!_E%zfgai*)~Yv^|!Xi^$1$Vsz&Td zcVe6Ne|mWROpe#w#Di`o$7{~E+nszWb%+6!75gL2@vDvs7)j_> z21}TR3xC7RL)|aqa8Ni6FZ1bIVf;2Ohea53e<$0Gr@cYZjniR1VAe zZ!B~`>nABX48xz@vL=K|JUz9(&X6d@v-Yb6b{y?I{4UmmKyD`U4Ue+7{gnei#t-Op ze|6N((@cnqurRjy5#0KXVjdUnbg+el*>zuv66;}hopP&JEbKZr!UYl_!I{qg2xZtmIfE_f3ZL81(jp$;Sc6*2vR%`!k*Su?uhvNvA|dSiIJe6|6;z*Ik9iepIQN zp4x9xWtMn-;$jK;65zT#=KV{6Q*6NofArRO!IAle#chhg%(8pWjyYqN;XAdbXOOLb zWZ9b~Sl>ydm}$W}2|Ve=g0;>eKVG*5}T6Xf|bb=t4ksv3YeceF>hpEzzJ1KHv zvsjzSkrPYir`B<_?=`LC(G<4Or%O3BvxR9nS^?v9&DMx(gb9^f6Km>ptegxmd`706 z1SbH?KT4SCl`6Lt-wQr(_38t1QRQ9owv(LNdz!{LI>+P;FqFwUzt_ zF6LNy=xs=CvvzW@QIEZ_QYjnsUvhPi?#*GjdMdKnuuuLJ91PO@+^u;Me{9uGcDcRg zotB)c_x9X6vLj`f=egkZC65EmIksadE{M-7x-MTV<*=~cGmBlzsJxi6 zlCM*`HU~K+mRyv*S@QK!eKD}F@;hU+Mtdc-B&*_e7vOoLBYe!ySaRC+U@FzFA1G~a zWh%SRV5$DgbamcxA5`3#f0el0ZK$1p58W1oV!eE$Udt8PoktO z%09mmJg0fFy97@j7{f@p2+z82c(XYIZ_0JR;(BGiQS=!wK8iT4d$qvKRTV+3-cjR2 z6=SXJ2`mT_0uSKDI_Tp*RyR%422v# zy@|*~4&EeT%il>O8I3|l%E8l_r%aQ+oXlMH)t0lFQ?goqQM|Gf&)>}!6wvN+el`SNR zgW|QmDiSJY2*`?-f6i~)BFWMHCFE_wfclq_cLN#!OUZl5!a$Q_GbiZMjfL%Y5RO&} z+gk4i!(jJ3Ti8xwZ-dKC;kjL3Jl>HHzppPKuU*eZMdZyQe^!O$9c1t2z!By17_#Yh zZ7()Q<;l+w!XDFxlOMTTcQ^N z#VQ-OqP7hVfJPSPIPkfO)pN0PIY}SHfKfe(L8eJMekSzT7XM2#{FD_ z%@3cYyXO7DGpgRslE9g2lbE%YFTpR_ftbOU;@6s%&o0BZ+yd%!mtp&j z6h|+x6(yyn`Z)*5mH%)7`{Y&N*ar5=+dF2oFV`=X6$nzvekl_jckgV!ltL76)xUH< zWj-1mf9LxtN8im`u}I{D8XJAvmZBW(aQj3rDSF)?wv<$7mYc&R*C;B|N&AzvLRs)# zX@5j&cg98V$qLk>}0`?egO?84-8`o}aT)1ht03jsM?VTXQ3ZLwo2BZp5+_?7Obq*3P2 z9!iC``Aw$^lf>V5Rjt@5SwM&BD!>hU`mXxKDG&9=Fzlo3a=I}Q<)Tb^+VrrCQlan9 zf6uLy&V9UeE6zkLnC>i(i~E@{7RZx##`G*v8z{G(`5wSU}nEL5^s^*tKgN4tQ#^^`cgc z0c%g;zMUxejypJ7$O<*2gQM9VnD9vzf0UQYT^}s}4c9M;f@td|%0Vu9%iH@{d)nLE z*~OH5GnNjfZd=i>eV3FyRE8AX?2=4s4^zf2$(L)xtf*?SwQF=OXz$id5DVJ(+j@=- zOcmHn&%uEy-~=!GpbI$omp7OU%Scz|U?E+(*n5~0R5CU(nf=Qh?9^-BU!kDee~h6y z_PNar;492V5X3Hy9i8g9yxH)~pR$&yWTCJ<<&0FNGx48OJUiLN6YoF8v$bnRa|6q& zCkFH5q8AeO>UDcbQ~UADm~jTOBiV#9K9l9zl}@MtsiV3+oqR%p3!#^ULUatJz4hae z?4>}B4oIrW2xXV{V?G_KlIf>ZfA!8aB!%vnPpcfm1Se;|q-r|d@~cyUL*-P(;$%$P zht4jPb9UomIA!(DO$uep=AAqT+s-MQ_fts#Ia+RUa`5{285)V^Ve`7ON=Ar7k z1ax)KNixk~><&7a_J~*dVavB^nwpQDgY!7%By_R@5ng$X65KpbO(O(5SD^|3tSTv4 zg9}y_g{AvN7{gYbf_`jPnU;6SLzJ=VbkOpdpc;<-*r@XEoBj@?ib6pSvxik>DFYJJ zu&Sv1V?D*Ha`FiD)-Vs(e-Z>gv+!gI+f!fFi#ORZ{gh4RUOUFlFC{n~V`4E}^8$%1 zhD%rmlL|HI$4yxdKT|m-{Yz53WUuruOYsHPk}pm1J*5ivQ*wBVIiZf_a2alRYGHIK zkFTE+#9P#+vm~DKzPGQf-r26Zwbk22)cTjNctfGw{YzMsT12;1e^TRIPrHLn)-5F9 zR;ts%r*^A8{4eK=^nT&lb*OiDxaj$;CwZ*-Q*Xaqg>>UUldBw2!_1(3j&AGtP*~5a z)^R4OC{f+{R1M>qhxVg&bBd0#dcG?35+@C__Y_ZH#BD`ssz<_{5^SP^5ydK<#+YZ_ zpmV}a#&G1$u{~UNe+dwGw8`AvlZXW+USNy1Lw9u1^4fx;K=p3WSw)XHZ&Dd`Xj*zF zmru26lyQye=vuS{myWMp2yu)1E^Pq zQ`(f4EVvO$5KS-kQe3O3VKoDhUCk3V^&>-@jP<8KO+4a%6uU-<<6+-i*CAVBA< zczlRKzQyAe@f@IMdu3{q5A-Umu&KBMutgiC2hd!F6)s(s;qNgHmv!}4SYb|7_va*+ zPX`}evv?I_e_n13iEAstuwQfdNyU?X;qb{R1ERp;pQF031@=BnPweI?N*zW|iMc#8 zg6<|NjKbo82pkA(pVu`yHs0my3P`z(kDHVXT3+)@4Wv8~y_l9{P~Ed&2+biU0*tBI zc^uM4=A#s1u4K5b+SwId1BC9N3tRl5@9j9mFI@}gf8Ey!e0h)AsOmZPYh(GEcGcf9 zmS0uhptD)`D@& z2`nJyvHzbY4BL)U&TY)MA_iJ`81F|rnD(r(be>`(@891~opSgLiN=@lAGuI70d{)d{ zH#+!Jn7J& zfBh9Z4;AI3zq)258N{Z!x@TnQawA|ns|MM~{e597axeLNJHzBZ2L!v&bsd1WkU@}p z#msZ@A5VjAOe?16tjy;+R83oFu`8Q6AU##=6?4>S-jReU{F`>+DyfQ{0CbG_Be?aI zxIW1$?a$&mWrE!k>T`mPVOHHF_`x$fe{a6M=2>8G%kw<|Q$Vc0ofk6vl|#!5pz!e^ zSnk>U@>D61S;YL3!Y|^&v-#zumU9>v=9ew608bfKP+hn`3+-c3!LmOK?YWLqNueX# zUV4yd-@ly8+@j4iv+ZR$?EaP5o`?DK;CIPob9bJaur^{1?d{Rl8FR8WtGJN!gv9w8s zdSd-KSf$V5mBJ$>BLT3YqIvPb)>B-#^?wkyz8p{f%+@t-`iHBB!h=~G)EZIc3LN9_ ziw-}tnU0p)r;F0}_uV{_lB(y|p3mrB_O_viMgTahh%EDtSMCpd0$_=2lc3!lOZF5&<&crcB5>HG^=P-MJ9S0% z3N(#Wj~kFh{zy<2XaVLbXiuQkz#$b$AOcKEPRHFcRRhaRg7zgOBd1}r_2h)IoCvlQ zuknL z2$3AmxggBKU3rHIX*m5U*-QYs!BN12Ap9GvP+4~350i2^%;;+bk$fr&K@e={s0bk- z6V$JCbAdWflnmFv4RiUDGo5eIT(@`z5F(~h5BYSPH=bJ06eAgjJKh9x;>rj`w=PH0 zQ@Q|Q>XCF+`|!7f@|~!Lk$-GuP68)#F>wVa;Fv$z990l}dOUc2j z{LAo(;YDe)OQ#~gKn^-de12+U`t9CJHm0v82ed%*096qX1PeG6@_!XrI?Oa$_J;Ot zZdeufpG?yNZJtt$G+@|V-b^v`?oI`Bf#%)vV*u`A<=%4U-oDGG74uekBEM^k{#Fes zm+GJ{_UOgCm0)`T%_mh?2GG|1E`OB3vhLGKFXpYQPu-Ng#DjZwdNJ>&!a{|I#9}EwdXYvkXjLTEEM|2?Z$)Ou9fUpLeV$jvSAcMOUZpaQZl`Cv5_m?p(=+$piPt;o3?I_sekYMLj_m1zoyUVT|J1o2Vv%JlsAR zMse-EnpVmvuDvbpjpEwtspg76Fj~6r0uYQ;)z3{CqploDfj&occbvfP14*TI#+QpJ zcfbVn*|yNJw+zW%#8Q&lq**wwQqjImd9jQ2=`o17gvm7j-JN{^2snq z&;7eSEjFg4Y~x|!{cK#LGKRgMRP+SW`7+k=G>GZx0%l%B5T7U@!*aN;XQV~T;k$4{ z4p&IOA^fnIhDN1k?(1brW~L&rz>>_&6Mq~Y!*#0?V~>7w>UoR7!p|Be6D(X#INI>d zfw_tO)ueq@P!I$nuWh#iBI{I}+?UX~|8JOur~F?KH+*#2qlZ#4b$&uCDp(~C9fpX? zdMFsf^-oejar3Ti9Wu3!;Q}aap?8lmkmy2478#uTrcQ9&4o90R>H1}iyGzz7fqw{0 z$wob|7{3*IULZA4WhF6))#eg~KSo(ePN+uj7&en6HJ+yzGf7Gr!%O=*(eGTNOi@-< zPBIf9fqu)Q#kW9r!M=wx=%pxl5s^WE<_#|nGU$&A^&3Pmc>^I}*b07C>)c?uRaBQ^ z3|qm^CE#uhTfvX6Hg-pjJJzJBkAFO!Q86m4#(&p-Mse~S3UB+65KVzNVA@@cfx4<|p5e{!`hX!5cSO~7 zZEt(}T-Y`Fl^1{`$y2j2y=Mjca6$2is(qF0jRPa9_SGE(u#TwOSF;eQ(pS2|6Pi@% zE8U9Zw~J=A*2*frj$HpE$|{~Z_;lwL&9Vr}8}UUXPF*(#%$zEf%YS1*MAsd-!RzcJ z3A4Ox3yT81cz3(#2DM^F*utWI1L9+0*}=8JVFSz2u7U;_ShSpvTQYAw$D9BQ3vsUg z))7eCbK!!D-EYF_yRX2F;`CO6qd2|X$v{Ld;XD{&PM;_11apEDqj>@_gb&Jjj<6wI zm)!a@4&li)&Cc2!zJFAUjZpQ_Jn>MAHk1%(aKk4|P>!X+7gh&%a~G}X_hf1k?Fi`Wo0Xscg)}F z7cMt#V~S38ziW$=3YP&+%lFF88ebx3ov}{jl*sYu)ps4F))VgMx@ajEYtJf>GPtT} zOczKAY6p#31%Faj50k?MQsmvPKpo~U#0_9QVI4GGEL7i9jKF%y3+NF z&f*$=k@~IHy3Z5R9lM;n7yq%4d$^)}S`JS!(M$k2 zd_8Ml^9w@3V?1Wb;VG3*TDlIjIMd7L+Ibzd5PxCRij^>m*9Oz(oqQc<(VC|WK0TBw z&r_JBqRMCpKAlXfHm}1Ao32l-KUk(pN_)lMd<`RU8jOi7rd zXnr>(YG)X>J|2DAJTsIo6kQui~-`*!%0+^t*~>-^5Mpdlk!0g+fuzV||G~ zEq?{&3vBW2P=R%r5@cUfV&m>E2{j8@KBLzV#!ZxZsXkQD%|-?nEg zTfZ-%NMlvPPs2%28JvpU^P6O~k`gkOt$*KfRTXJ0Tfe8629SJn^C^U3EGOPC?n@hs zYO^Xk43hXO^Qbl9aae|r;$`diz%RXQ{O(ByMnJLIH&tbIELX6~o=tC8uv!?}mW|&z z6CNwZ@2>d&u^e*JD3UOittBcM*ZmNgai0&wE0+x8(JI!GCVh7JvHf4E*Ih7jnSWXN zEiSaOs6FGog1x$r_7Cv;O-KM4uWwbkr@6YgR##;3xn=DqYCs&z+HcDE4a@?6r%$72 zVVg@(+&t*z&c;f!O4!crG7GL46w3pvLJU=T@ci+t1I9-BhsxU}LB2U!hH1exu1^aCU88dH(&q<3G;zRN1qc zD!e!8{j{iO)?rgQWfsONL;2F3W>BgO<*Ao?_t;P#$pO6Bt8$^-GEl~b@_(^3^S};W zQ!RJ*25wRYy#EZw+Fq3oDNnLvQCPo)l$;hVBHOp7!0WYh%8|Uc) zEMTLj;?4fv2F~1{CbfBf=M!=1y??; z0=3k%8M{p9$wk90m8+WQmG4%^;LhmrUF*oP^4;pFo9zASDZXUEV z$_a~q?z`Htkc+S|r zeDk7Y|FO&VqI(q%uiN%=Qd&IFuM+s1-4?rSFGttgg05{Zr)-J)v%RSBu-o(DeOMGW zyvQJE!r1U~Lstuqy7=+RflO1bJ)Y{md$QP$JJxT1Tz7Z{T5wmLRD-75qLR+obta@P z)J*3xzl^KG9b?y-kbiukRrAJeD&G6iq<4Xz6BV!GJZasC% z^9pPOOrs?1_)=7DLY>F9^+hzS zSCOY@?b_|zvyrYVCv=h#KXw_|o8o-Owt+p5V{cc>^;8@i+kbNX`=-tE|MhYqCp1}_ z7Xvw=oml-*IiX`>i~Vv!%`8X>P1evDJ0-@$THkv z_reY`LgjbzTvIG-1fIj~Vp%D?ntBw=>Reqnwu@z{(i(VC+EA7RKcm8bpm~!9D!-k| z$_%nVk(WGs0)LY02bfSSOPSQnB-OG~*z_uSmG4b&E|$ejZDYH}cxuko2=zO5trd!l zR~Qo6pK{Wg!#!UgqRq_=;wB>TQ*Kw)G9Jg3Rh0I1fV4!WKXG9d#Z_G(d(o94H7eUv z)o3K7Rkr83+=f2BY)>f|9grof2!IesP_J$$;$d0OReu^uX)EKDn4V|l_2;&pPgOhx zWgE{GlC(kEwsR!_FT-`9?C<+iJh(BeV&B2$K3|3Ls|=-aKEJ0b7micx`zR0s&}N@d z&e=G{zEw+ve(d;sS2@??yH-+6!5}sbrOUc0$MqcL&5Yl!mfSc2o$ZN}bUekfmDs!79xOX& zP_<~rDVD7m6A#2JyQyY@8z5z~E6|z#S4Bm04u4nrCt*4e$wjpuHvx|?{ZlpogD8Tj zo-|Njj&cVJ&+S=yiT6BUbmk5Z3ZmFC++S zk$<5(3=)gfV|uR;)^cxPqCXWqo|?&V!;Cihg)zn^qw51ZqdgAW+|%pQMwxz}mpJ@m zPJ(pms>+l<5o7AA%8-%4Pno=Ht)ecg6#gC%Mw#9mK2`TsHo5k~AKTd>&!jn^1@)v2 z8r7Hjnd@kKom(&-&vRxuJ(bIDj$k=mR)1qYex*gs=7GHOUfIrez5-g0E78Vxg{9n# zUQHI$w;~Q1%q#N;SFVij8cQuVspIu9kH949EipayR-BsmH!=O_4ykcVOjkKw&GjuY zeGhKX9=GIl-QA`Myq`IJaP9B7Wt-Wtkecpin^`IrHttuGMWLiLZ(%ca2A!w3*nj>} z-3-7o9PKwjw!#xr9zQg0p0DdkAD!1F?5QtJkgaMo1Lw^xmfwixANQ+{IA5VsN`&$T z#x03`tK{^!#qwP%X53==a&PhJZlVRfY&dh zkKQlVCD@3!%8oZApZmqS9M0EUEPtP>jN=x^SN4~`1gDfu7f4i{tzNTej^C+iH*Rr! z6&dx*c8MI^;@^#zvZD$r?POg~k!AF!f~BBQLb@)3_H$2RHs$qYmam+>U=Xuxe}VDf z{_LYLyt88?{;|v~uV^K0pMX$Szs_bqBiPS3-->3>} zffgY{!`uJbE;>CJT%*fc?zL-FIeFlLYjpPTnz^P(_X;8Hb8r*EsI<=%uj)-KwC{NK zNc+^44UEPIVyl_O+^$8uSZ4S|Q_Ps|!%;6ODi|F{y-dedRPN`IAAcW+iQf(&sMOCL z)02r`bhj`w#xJVcD_V{GR+)^%u2MhO2-xzM`q>&=^nF1u5b4DWZ$=(GMVz zrXy6rWmN9xOfD%Q{(Xu3TV<+8<$lIJO3fP1{ftYfO{IQHi4rTQ)K4A;VE9YKSXbR=C~wX! z+h_G>yHt$tecM-(l?T}QEPvON>RO3?sKoYoRV#6DL*IB+EHMuD^H;?ZC)aupXMDrw1oV!?Jijk(a(>+td_Doq_o6nn2hITysi)AT0yUYRCYipyR z=~h3mkd<9-(P0QZa5xH=6|}db}dOyqVIYeI*6kE#I&97MYAQQb8ovK0)mwJEC5^G(>*zY5Cev)p$Z~0 ziTEo)9D1tB3sgm?bK48LezV%dEji?AN=SXHW(|FF$3)kT1TrSdKoSHh5%n42u*od| zh=x6dS9jF~1qcfwQn@KlL6B8Yv2j6_<)K)k1b;bUUwdv8HZI#Jy~D**tyRvxKr>8^ zEP<83EWKAC3^TdRb-QTl3iX3FzHEMo`2xZ*m5$?%eB5;zsS>nBfI>BbSUR2jGl34S z#7-~Jj^sWSKPi}XtzzpYg$?EXa(AU*`zRC3<<@Q7NRF`F{PjLiopOP8azAP|C+uCL zvVXKZwj7=lseom{HoY8bjSIH1eCd`hU|4}}^Dwwdxb*&qIWr5c1dh=YA z$8_!HZB*5^WLDv8`%Hp}0fDBLY=g|RJ%6V1IeN?3W13p9E^QHPm3=-Fy9$`^Zy@%V z$^&x@n?=3XOoUy2zw4)V^Yyijn~e{tqNpIYU?M$^ic9i7<`-qTcwZh{I-07a37lTB zvQ!8}uDVTyYy`6S4;gs^5k9wK(FHk7klP*vBEh6&+GvTrs^Dah!1|ILYkKh_oPRe; zaSK9SnX6uPQHa_l*W6)v&$}jzuF3*}JIvvYYT=wR4uaqgGDCnXQ1W3f2AH^RHS@}G zOu7^iKXttLyxv&8)=f4g0ffmnt}+#9S(poU1v=|WM3X=wt93!k1%Jdim718gQ@41o2&Vx=C~cP{FGvWfe0cXmg!3S7 zN+23$=kmVwndx^Y&!r z6XerHnSp{3$fb{hwdFa@ElGb2hZ9U~~v(MxJW9uG|uAhir-@3(Ra0No0<}iso3ygmc7s>ZJ|jD&qXf zttilT9`4u`Xb1LQ1={w6s(&ZYwkHZG3%nNbWW*K-A72Y*-Ob0B>W~5T0w3?1ldt6& ztJ>v5W_y7a$8n|zv^`9>0Reta_33ZHNwvJ(&-Hn6y%MYXN^b^z1~f0>s;rePy+JrL z@w4{vXg?<2m$zMALviv1@%N>`4CIksTwM2pOuui&JmeDg2PA~()_)D=(U&VrkfnQ6 z)*%%ghy|uHq2S`o(59GWL1>5L=z>pyL_*2J3LefbnsQSM(u!AVJt$ySA)!%NyREpFKmCF{c*{ygM1!r33TsiHzB8HP08}q`hL!gM5SN z6?2|SKsAewo}+Kj(tqyi=ZW&2oT}f)nBd+iahaz*i&1yV0UP}bYDs=*ENm?wajW4^ zJLn#2#WCioaot>PzSE6Y8OwDWZa(*lnJl(ZwsL8}5+X*~%C8eNVwA1?$!r?HwuMfL zc*gi*TFzkijW4Dpw_EdbM;s)Jx^B-xiwXgD;OFlT3t`w;E`J}5(Xp}o<#cR6u6PK= z_W&EcHdL-J#%C=*1j)ttJfw@WZj^)c4qg8SY)h*Gv;5rpdg_V??CLflg$Dqec3L^~ zR%H{VYWlH7Dph=%z~yMrSq5wYR>kbW-eE3FrdhD9>)>QhaQikVbQ}>eD$FG%9c75c zs4$nQ;4HqkyMNfl#$!~Ni;66o7?om_C%c=k(zrU${oK}$d74LSrk{*p)^jrpSFMaM zS2P!)BEREH7HwQy55DI5s@M^p9!?ci(C~B2F)F~{JU!TBC`jnH5h18t8kH@oozEh! z2zAxA@Tc3yS@Xm?|6h3tjD!LJGqrEiZQ7WYJBFu-M}IeY4-averXcrBBpjwGG=Cs6 zM0j=LE|m&4=@fVAOQxu9aAG~7$6%81va(*GZE8!;;VUpXPXybUP}DuxZ`GR$t4 zmHnj`iDQ(zbhYkDvAb0EUb=B^(c)&m7r};(6jFd$9Si9f;UF)V$Y#N9OaN3h8?IhiRF#)1vzS88n&);r&6k8eANI{?-5Ga z9+L}S#cN`eJtoB)Q?;t*F3PvYDA#F9VQ7oPu76Wql3+61cd8gJZv*>IQ&Q^QC3c>w zEWWX;uy*-I-CP|9{dg8D#_4`wKE#-fVN;Zv9;W|Lp&>5)Qm2EmzaLOR- zEq~^0yo~QVl_4$0uxPKWRn2f%w4Vx?1!EUSm-0@x$o5HpB>_cU2#Wn<0-_)$8(i+E ztRqTDj!G|8DVgxg@QO~o&2Vz*YS7H~8mZR@9EQWUmqN*+QcIIJs}O5cYH5mX&!3W$ z`?dnXqcTgw!T(X2r79k3^oM0{A1Q=18n%?5 zT3CI`)CDyNB5ZXZK}n+so81*(7tj>5yDA+TN6hXj0%)Ydf+4<|2%FuX&#f?@4=1!l zn9YYS4}&tW`H)|hex{u!ZF%ucJ554Pya@AolK~+0QQ~Ii=gp`sS zwkZXt*L@~)@im)Qj_2dGU=hC`oaYfG;^!K}f)OR+$K_tGh)=}7%EkvS-nCKtqD1_Z z!)tR}#7~~CvWXF2XnUR}%imif(|?7y5UK(SrgxT$=&C?Gjk}0$GT~0!b&c~oUh{P; z=)C3)w!Mc>`zYhPs*uvJjqg*1l>jS9;A_f30<#vq9}Y!dMwInEo{FhQRDtx|-6n`D zbtwzTPpv(M!f_(1QdhUg5>=^7td7YsqtEep9hlMI-KKe86OTm@uTuh^NiT_)PY64n}Aqubd8mG zt zrwuhJ**(8VBF^KOoS&hFWj`_0q=waa1SePK{ouP{Uu9EVYt+S{snjXaC}wf;ltqT0 z!Zp__qQT0!2(W>kWq64~s#+Seg!tq_7z^ho#4Aq+#H=%ZNGoQAz=(AKdBdGvpV~4qO&$lpD8H45G`m;#)Q*& z1sF%01!Ay~>)d_e34D}xjEM}dTD_BDScXrEbw5N{NLAi6D}ST=Y2L@jXF8`$rJ*Xq z*ZU5ed+u@7Cy7c!6~{HXF}fqMFhY#(^80q4+Tc8tU>=o+s{7Qv*$8<|>9V_Fhdj#G z2gZavrf%QNG#v8y)-|Cp-bnd$w-|4_3StTW zz)eQh&XpUh3Lo*Mq$cB-O2~;XC$-647ybiSra(%xU@Xh-k_;3S7t7LnQ_yC(NtW7H zEYSiM36m}AemG!g*shZ7Vp)Lis_kYw&jVt1NuGvYmNbf3daA?)( zs8gl60b#JGvz03!HCGQN+sa2(>|ix)Z^6XBvn;*q#_mo>2%hID^(I!y?@7-Ibu7P+ zSD#vbU*u}WDz1N3eHYLw#fOv~u8aG4W0k`-`)Pj4$*eMM{#HnBveCnri>m(Cg-*!t zXDi1a(0@)~6+r|<*Pjc7gzPam$A4mItfRysxErKPDoMZvqlfDjNWwTZ7d7!Pd+<;XiMtCIk~EY0E-cET;e5>)uG(Jxy=I*lKj$!p%wm%fZtUB2ffQe1wAx02bsT<6Bc zV!J|>ybvt5=iF^iy~Xw@|KFF98jIqI@nxh&DuRolmS=ro{VLl2R3ad;EU&PvPA7l2VB_ zr~jg*>U05`cwKHsU=vRP=6r5Be#+nux_=ezt}6@tzE!f+s~hOQGAF%N?OD7Sx3*kG zyYF+&FwFS#l*5r}*+dn?1$>nVR9|fZ+k#Ia(fD#wlkVC|zon$Ez-0Z~1*C~s!M2d! zKC3lri}zs9|1KcCx*6NBgAQ&(GO7zmFLg5k4bMk_&36H*E|C=r{w^R*)mAsQ#D8}I zX>t%m{ayBZZj2hh+mzvst=MJ2SG3ae^Uo5eRJqt?z@NUTJ9b@dmwSAJUR{)-+Z+vB z*S?(?iCqSKRYm|?+wZ%X)|aKK5-_bZc+37u*V>H66~bw)pi^k<$jkHheB$xeHJJZ#Ur1; z@m+&<_;=kZ7j;dkN}NuE2o;ArJY#bf^q{OnY@Y?GT;)H0id-hMn%5hrL4TeNPbD2< z`#4a&m@t`r!Ch(v?+``vTTZIt9cznJS+MLBZ}l$eKX=T-7Pd?NZ%q}L76WD>B<037 zURdy(+_H@?w&h1+SYz9UpS*cuXV^C4x2w1}CIS_ptFE3OBiO276N9h@pd>**wFb~? z(OCmHvoMGL|Jwsv1v3(-6)J?W$rYicTKz&X1s_2`NXn>s01l_Y~(C2B2 zjRA#yBn(1|fPEySoah;-BSE$tH*Xh$YO+C!y7}&&ZX(;HAC$+4U4KVHN{1>k9=nc& z)Y0&{?m7~-D*5|;yZ%(z33PoVsGN*>iH`*2K*-}GL3haebvq`b<4D`zi7<4T?T<<~ zm$I95SVr^1)<-EX>&-3{OUreaExT)z8FbpxPg>U`D2bTNdVgIbD>NE)Y(1X<~| zr%oLSx{0sa_vtAmg@5<0{l>sR@}<3Y-F(99RWnj0x{SW7(OZGwg) zFm<^hWOW4QlDiTRAvFbtV3ZsMDr(vdI&ffilh9#1^Qcx=oPUa=SIG&QvO|v|M{z2S zes1K#QyPJ8TIW-11i7J~Uk0gX-dT5>s!Y@Nw?ayFOg&MY1geVcdFCY8a{53=PJ%5f zGQ(BFvKbyjP)Y@bfFR>jWG)~g_=#c4a1`XBr{X1Xd=|*ZZ6dpHdl-FA0%bB)L!3=f z>(pLbU6lezGk+?N3AU?ZPjFRNn(NXnHZ-(s!3!nVN2R%Lu?nc-^%*VY~eImUBm zdTYY+$Go*)%<^|547w6smC220aY_kDBuEmCQ(Az60zD75gpQ?rpE#cuFp?86jQdGz z55&u9n@5J1Zw1jOOAK1S#Zm`O=HIS5y=$A%R0;Pzws&2Xa95xh=-+aDjN_I0Y*@uD za;k=VyMNV~Yq(1U0CIqua(e)6h1Hd3fOavg<+<}U)^Y;)Wo(|Q*-VJwYph$%q;Ij# zrKnZzC!IjjFU$4mp3?4$+&V{$HRgBrMT+;7^70<$A7{>Ltl=o?nS`o`v1G{R0%rN zr$g_o{kCVf0_~Rr&c1}#6e2MjehIHHC1MYts?vy!(|kqR4`Nbo8U5S2ZoJliH!&*E zektuZZrS{m`O3@QNw4|SN|<`J1Ip%qhkvK1LhbkP3a?Q6Kxe-dq1mpkj*9z*+6RFO z_p45(I5M!RSbN>^W!P%3gdB!cI{D&I`z}D;WctM|Q;@RXC#cvIG*g{_v}Rk7-0;Wm z*K{824fl)E%+>z@^z|B527?pbS>S~78M^uJM?5&kbrjoCu+|5LN!Yk%_g zCEoO!|L;`2-0-m_&L*&1#V6aiUWG0`xpysbB>vn;x0)it$%XU7Nw8Wl^y-`PCNYPV z1)JxHd4mJ(Zv>4x&|Ww50WCu&qXX@^$UVNxPfz!*ccA?$cJ^h#dbfbs<5r^hvS4>y zFDfkkPl2S+5<`D#o5MrKV|#U78-L@d+V8l5DhoFEfC}P7Wx=MnwY=xbUE9Lz@%hXq z?aHE8GpW8jSe5u3fXFuE(9V|!D{bFTZSceo@!J)s2R`v#f%;UVBfc9@lV@~yW7B!S zlfNujXDT@fb|{I$5MV6qSMRgtla^CSu<~x!L$t~}gUqq1OA@qg{e!r#@W zNB|l}b-d{EVnAyc5|Rzusw4f!uiL7lTWT(_w_hWc=)3(? zwWB&1*6pV<#`$$KQiW}3LFxEzKTW<)XwoZQFV(#p_UpR2 zk;=Ag`>`{M!as=2ymHtIf9-S1{XIa-Ii9HOp6U5&fT z!q=g$jeb$Lp{k&Pk+8*oU$0X~WcG%|@l{NwEO3lqSp(8zK(^$ixPL}G%l0Y{Zv-vd zzdgK8EZZwonWns(FXH+u*?u|(@0D!dNTZHt*?w;s$N^!azI!>IW&0&-E<{AOr|>^h zifliXgzA>rmWow0h-`DYs4bp(drD!QmTb-m%l35zYOd3>+)>@BCwHD#gU$S@T2oO! zUfKC|KZ18-U8>i0Q-2w-mzOI~#{2C2<84n{+4*Iy<9%-p$-0om5`Iojk*Os7)O5|C zEaIzP+ycKUKA9qw=~!`@O_q4zGEH2DX8~U~+jyOnNhfteFR?P|EEOadT!9xOycpi+ zB0*?+U$oA-Ma?fP^Ws2$Wzx|tm2S<4_I=N6!4@6e)#;sGm47E=gg&iDcJIac#1zaYK@Ch z1(#wL^*WdhU6tMiS2%;xT?JWd>x35xHrb}i?EpuS@m@dooOZ>~3$$lxr7(PeJ*9Wo zW9uPW(T2;wuYZ&l;7@W@aB9M3hMTD*0$LEueb^bw7ojgl7qD$%|8G%Ob}g|9Ln}%S ziv+l!^xr~s7tQ+{d29?A(0V10hU!9Ef*vY@aPZ5fw+WR1wCr>x zu4w9ISpW+Noz+O)LbO3Edc$!D6QvP%b!yjR>#j)j1jB+uUxubyt^`CEco$&nywgc<}DgCw8YEbmjK{Qh+uiK3*2 z%q#4~u@wqQ#6VKE{(zArNxRs$>SK{4Q+V_#fMm-hOKM${DI{cT{mV|p)rKTbKoY^p zKiU89ga%8VPzUhT5|F%)6p_P?&unuj{c1>ogzBSr*;j{(72$fL+U#0lWA@+ zN}}dYn-25YNCf#vQwB%KNeQK(Wn4*4#gtx98}0gq{H`A}-Gh|d^1!=LBzIx51Su$z zu(9z)N{>^**9ekaY#UJoB)!<+y9LR`mVeA&2a>wViDy#E$|Fmgj`P7f)r+XCAOQkz z=4+9LllRi%vr9-_e637A!3xhYZSvWSH;-wP&t!Jwm=4q*2TrG$Hu-!|EMnT?^B6fD z2IE2vNRC$iT{;9pSX=~~J>IG?{baOR%ns8pHpDI|Odr-9_FrN8kZ9GCjIdv*|9=Y8 zN7ZY74~6Nc7}|80{z2vulI+t(gnvv$=|ASoh$)+XjcuBgWh`JQi|Hu++J0IE^6xGH zut;*6U?y2OwARM!~3fXCYocrhT~!$(VGvWNhr#ryCSDfcUo51#w3Bc-+L*SqC^2X`K(XA(QjzYzoC` z^)Yli76eCjZGIg*UZ!Jx){^))tp+R{y?ouZ>-1_{N(Wda{%Pm64ggm#X@9>ikrFGW zbpUvnFXzKn{$M)1n9>0za%J#wJ9Zxc2E5-#f3eM_x%bgu^S8B58RiK=0U5k`fd^R3 z3Cu_sri)r@K&ggtx})?uZ&by4mZYx3L$RK_>fN-@88xm*E6zu;FVQ$%6f86?IUA)f7HP@ZAAeJTMFVx;B%5iR zE^xfM+t%0}gS5=kjFTlf{WqC)!*n0GGlwoE;LW7u(lFio(cBsh)4h)n%V?M`>~Bl$ zNeS5amZYr&44^`yVY+n$BZWg4rhE4Ep~ zg$H9<4SQ01B>Ge;W6PaSdSlx+29lV(v?DNb?1tb7FY*A5}LE*L{V} zg&kC$v^u;sR381ModGZtF8hi+9O|z!VActvu{s52=zpkH3e1mgbjMCG8CMvI3k4hB zxa+s{mr8QiIE}5{A#@xr$(s`UXf7%;fflC zt^f78hJV|Xc!4zbY(vM<4H(R0`>mD z>}#b>w{JdK!n;2PcqOB&zMjF79@mKh8sG%6MJD9zZug`WoZ4NHQI#^gJ8nk;5c1n`lVI8j`9r%yPfG}nk zW`E@5z(%IJfX9J*OXA40psrC$#{xg>dNpth0zy*e0% z_N9zIh#w-Qb1YKBtXA1SKhJygakF&-;T!@FY1;fQG1H zGV5Rfb!VO;+sBf!3RPiPFG|m_xMYMG0YY+8 z^V7)QnoKN~M)o4=$#3}iT9Zv`?I5O~Z*&Fl#~Lfq$llrs8n{>i;*CF!=I04UP)$UO z{Y*t1VNO8!ksg=nzc|6x|8ktzCKDabOl+Yqt#@K7aTaM^}HC>W(L~`^8)4sXLp_B9_4W0XfllUjTRq->&cS~R(>jqpfIr!=Dutu)4_~Xq z$&8JPaLP?&IOR8X(#@%3OF1`7P|{|lIskLu)xVqxWJ}S%lr~V!yZ+_j_#O@0DSe=% zcB<;B;~ysmq!#0QOMjk1++!Hs3jqf&HMn;n&UBNcQS*~MDBI}&V(MRN;czJ(l3Y$7 zP|#bvQS+>P%S1Ya z;lITFaF*#8Ksd1P6?4Bd9vJ>xvNCPThX2;#OkS1t3zQflifq(chpN;THT31=7Z^xD zL~?1}q?VwPw13r}9wOPNWPC^%1UzAw;{a&-fu~;$Uk&!t>b3WGBASjgrwZtEYAdc# zJ7BXpeBKH{olKOjx<+jYD5z}^U8A=6KY$g{HEQ7oJA6aeBZPiLTGASF4XHi3>5|C{ zqifWb^g;c*Yt-(N{U&L%fk^f5SeVSAITst)b%U0VGk^JPmTZrP1lPhKi*878kweY7 z`LF-BsQZ9*j79`goznkyX?_T$4I_d}<3XTcG$I%~T&ugi1RFTgXh<+5f4xp#0;USp z^H$F$J;@CTF8S(c^mY@hL8Dnig0a1Aon=rQ!MpBp-$jBh9^BnM1ShaK!5tQNhsE99 zo#5^k+}%CFodgYmAea9=_rp22PEWnlQ(gV5?s>mW*E`Si=~8YYV(P=|Gj zPt4Ws9r`gXno+}gCbsTH8PH>joI(J{`;pXn0^P}=WJbmOZwEj8y;#>nK~tqy2Zm!x zR2tR(_(aMcZh2UhWOfc5IZRiqkHX(B{yp5es=w*{QtzgA)JarIfrQYZs_YgUF+l;P zD%5!QQd1?^)pprDYKQq(=P8DSBu%y_S78c0mbgZ7U*J!68gj^gFY+e75!#ssi`@e)?(V2L78;DN!4&M6d*iO-7V{Heu=M>w=I zh~;QRVbNHUTy^d&C^TT}ECv1J%weFsOTawj)}K_3c%K&Hfy9OPd9|JeJHXp+wLTX{ zE%Hx8TR5$hFg&N!joT5?*zri6O`&qEPsulud`16 z5mBkTHPSR!{Q~s9*ApqK`wZIk*vgyZHDVF9%0JB&@VaNqAN_gkvxi_b!oqJSS9emD z&c`?}Bw_Lm;?*9>3&!`;UVN7EGLcEGs1e92r}zO$jsB#KL7V03&F3xKoFk$uE@yrK zMRakp9C|Z#b4~n%DN!KIg4Ntr0m3kusMoRBQGC4Vsx#E?P~fk$)|cn<3eqyHPdX|} z;i@R9iahu1q4XgmCtr!EEUYOvqFzshgkbOV1rybS%`u^^%J?`re$)_30f`yUAsYF( z+hXp7zLN>%Z_h87iR0Af9o^hnWKJkc?_PC%LTsVOHX3{2{hk$SFt<9iQLOZIN3m-U zvqdWLMig4hgIY3=Ek~N*EF{+vf`hj{NaF-akxP1)JYW{5ak6Y^8eEMME0`fB#)pdQ zn&iGRc(`qLfIq69!PeyKRl^gsB_g7bTmEeE z-pTCoHMPM~duf6TT#Sv^4PJ|X7jJ+37_@QF@(Kmt%q~vJ`q3XE_64`EkR1`7w%wl{ zm-W~w2R#H_p4thhP%+eI4k55-w>e~MW=68sejWNemuYox oL=YoQ?5dCLdci;sa zob?^2G{KC;o-1OZHJ~bT69aB!J3hWSE}S{Oq(`D5SU}1GTOIPK*@QyUrlVwmO|M4e z8wvgT8P%~o`-46w0VfIRl|EM+D|48u`@9~%Pfw#B4*}wCklph}$&R~j8~u8&!QJLB zq?G*)J$=Y-Q}gA)@#G0_+)9}O18WL_D)AY8l$I6Ov#J6X8eyB7!V-k14ZmRl2#dkr z%t+n)@nim*P!od5+*%NqLV+Lya*-o7h9p!-w}J-PS5DI2IN8(TwMS$XGf}Q(2p*@w z0@oZJ=30hku`jf1&Mi!`goGY}rD-*c;x@-=3@T}SavMbCbWZR1T2>!bX1-s#t@PWJ zIOXnC#8TIBbz@kceTZ~@EKKSg^D27;`*(!m^i!L2zUJZA-%Nh7m_<)ts-f9a{snz@ zmcUF=IQzsomxbdZzAUa(ViK32m6Jk?21ZUul8HX9e?t>stWkB)7{f*zagHmM^zFf_ z`LrcSyY^meIus)Y)MEVn(Y%(d?3t*P0Aqf^kpiw5(x_0=jxr~5>3ONMe*aW)dTxd%Abx-t9S1&q-*q=kpfo z4|HrE7I%bXcEcIv-%Px?v)> zAg@jbb#EzRX_=UKqHLoJ4GjgvlUuLjh;3b~IJNB~GpHs81;uRzn%Xa4(A8-;lcK{j z-jOncy&KDvTD)4y(ez;mf5pilG_JG9%Ks-aRQ#oh|AmM!9e=)jsk!ca=KC z+;W3YiD^znX0q>MQ~VBEz9*iA+yTU)j{=8X;)ND*vfrckXmoUL^sB#o=<*jkphO?c zC?@1cOEsRld(ca|yQ-FI=`x<;X;{0BH6_E5_vb%SRa;Jh;u5Q8Qp(|>6x7KiF=C;c z1ea_y0jJIMX9@66Dde0AW>9TOBr^$uqX1^Lw=TlljHu?7!JIP7mI~WU4rqxhyC&b+ zgW0wf|A^rRRG55^ASfE2TJ}ZWDePAoqX=Cf$bTi66_KSKhRqTy(A?cUpCX63!E`Sp z#jC@|@u}Z{t^^}!bQhRDL{GCPgmF;)P|Ba27}TL{xl43Ut!r+CEvFN*Gl$Pilie7s z*y#x4n8ocZGoBjDD;02r5UDfZT;{eB{e27OfL|D90N;+a^ly?jMa7_!=#N%&`oGrA zIB;%5U2}gX$$~X92)KiX&&$bOOAT>7mt`W;w(?h?RS0ESyAogLJ&yv}!}^JKq{CIK zVVRsr;o5*c)kAJ$RI?9UX%INxu@T&f5DLD;o5})K4ml!*2y$R>mSy9U4B--n6Z2>1 z%R+qIE*hpYv!PUpX(^l_YcQ91+0!?58l$U+X-<&x#utF~k+0*x%q+?DVrAlogaN;rXzqjb3Gr%7vtIz^sHG^owzB)}Wf<>onyDeojT}edX^`kC zadi|Z)($Cz;$BIJd1daX?HW>yug={}B-1le1)Y_%L%9vg9!lqFCi0>$dtD9lC<_0`JgE>PDvw@`w<%tpui-yHSh_jK8aU5&mMpor9@H?u)Kddb$ zJKQlb-o7Jo*F+eCCh2$}*Mk%EJTRTYs5^coBn9Kf8wczsm3+#Q-(Trf$38CYHdO7w_{k^f!h-%#jn~xk9cUyh zls65rB;}M~ma4h(wGBvd@PCg&hM$8?SB@4cu=l=kazb9)3Ab_zM?i?b;DCgC(3T}b*cOfAkh2?8y1AF z$!`sRQl;x7+y1Z=Ca8l>5-I2?K^pq(wl=>)7I3Co%zKxTR=$3TW7e-e;MN}vYN|uk zz-|;o)P8{_C^_&)!QMSdy)$VnA(fqQESDxKGI!U*ctrGjmJ*HCv$tm+5X^*%D~w9} zBlFB+0w--8$w)l^7*SExRD8||Av+mcMP3zPyW1W0Rc<$ux)5OlXBWYcuTa=Uc`K3( zqH8*yaUYj2peYU9qHLf6vE&N;gbQ)`<(T-%*)0on`yZDAO%W}hBn4jF;M~P^KuS(U zOSfrD=U0d4o9li?=!h%;OAe?Vu9XlgAy(R89C`$~?T@(V3kO>%xaE(oUAU-l|Kt=B z+oxXBit!>R%ZEsE-#{vr)`q{k>RXfi?cZ*Eo)-+MVuWPMdi}4^j{lbSepd_EF6*WJ z!%2*;e?}$@v#xDrQtH-Ahv|hPYjbn67{rMyNO+PYM$bJ3>C|VfXlQx&qAtWnoG6TP zS=zYPm?JFl^zDx|BQwe5-4@|QtQK_~a+CvyuXjUVDO4=zLw*bpS@yy_()LTsA7&x4b)VM3M^jZgEB#TFm7J6mA^pj_gNd6uSYJ&*7+I{x4UGTIrdsA{}I?BE+JNhmH!!?G4t`_HBoiu zr6Z(26yNA15M{BdvX_DXePfOsh`Np!h0T3;*GmW+BJfhzq`0+0&H%#YI4Kaaj_ zDr=A|j3Rgh=?_%Uks;!yYOU?UCh4n<0>Fsz|82CP6oF!4B*ex@>l^sdjS3?wcv?yp z)8W9JcvWFxM)~n_cedV!-f|HW14JAigDwY4$LvK-0?j$sonCk5;fo09V#v@k8p`6x zz3QW9Z2>B@G%2N5FPsdN9rXr%ix+tf&6K!TcVM$rQ}EED%%C}lScH9Nk_g{xhmvx< z7(Wy54%&ftbqDJ)uyRmc&Jv%|meU(hh8*lQBjzx?bah#?HfH-JtB0wAgIh4T#KliA z;h@H-nI=|a!`;8OGL(HH*DPrt69wr}; zIUG;R)?AH1uEVPd8pmkUIl;mesgu$g<%%00fyxhpLMZt(y$2+UH#ehWU@mN{T_N7} zZ5F1bbOoE?cc;fq;v_6=(QNqXJhR;G!8mE1^OK)xZ;M2lmrLn9q*{NFHNa>I;Qt-qk9 zju>MWw2CEhX8Dbc%jvW#smJ3@e@BGtW<6DQxwgW@A<4E3)5#Y#0Y?Qvo4XMWOCb3i z8FmY3Sy@xLiGcAi_J+IOPA#;l9jkAh;oxZM>0dNB)QQjB+9S6I_BMEpj3rFeRJrtr z4C%79Ua$0B7rkpb?HP+wNyv@Xl}%AKp)R?&U4KHf6w~Ecd2d;LI?e3Vhm<;%IDyc= z!m6Q3DpGJK!LFrdDQO4Lh6B}cS%tyD^_HY)|1D(JG+}TPq*4HJ#ur}v#D_GK!;G%y6fV|5Bow~AJ9vB)kwS_Ubl*vEkKeB$R6u6IytfX>2Q)8k< zb9-CbYDi#2&yiS%H&mU}(HPw(H4F7YKA<*G=a_jQlcr&Q_Nt!Me6dp2S* z8`BkCEp&X_>y14LDhpgk*?M$s5^M=R zCYCIsnD-;ebH4Ud#L3-6LpEs^Yh?~C#*HZa&`;Tf^5LK=C+5_2`g%)~A8ZtJOX*i^ zu}~AugF#;+c$N31nI^Ry7TK2sX~816k&+b11Nn&v2WoMX|vRB*ur+_nM3lK~a0RV03n! z^>(^P)^m^>l-l36$}Lu5mqlkaBl7AlCNAR1sA>t2_G=`}fv1?NNPAPduKpSb8C?%^ z;|)<^Zlz@HS4E}Pl|>2>BQDSVNoGB#xK~~Hh?6WX5nlGEMYpZYa^2fQbbGob9!tz! z%bW=7X1MkaDTI^A1ly(I$TFc zU{AEOafT2D>G0yq-cxuNZM9a60jCM@18T$?Ubrycu|kR6*odVhrgVrGuXr{huo-7# zcv`Wn+lU451Y`7`E=&m*1@Nxuu1VcZpi`$h#KLHX*;06GGn*O# zG)25ZBf`E8QFA$j@E)8^@2=?wGOk^8$EyFknv2R$L@RuR1n{!sfim6DvJB*O%ZzB2 zpJAKd4OgL?&8sUou4rPOy=(wJF2$H=>y#~gG!BZS_%;mi z+@9JAL}OH9(g|#{+1#W)*df9fNKId|B{QkO9ppcLOh~{q72lPyG6s!_B!t>n%SB56^0+LV&9(>=jZBG&Tr+=ReNToOng)+uuSdm9g{H9Vrq7kh!J`t6JPPd!^+E<9tUMaIN<6By(y+@ zPoPh-4zX>w> z!DYy?cy#et|30}_rFR!rcYrDqxQFQj=ci96W?%w@S&SHPKi?;&6k?1>&WkeL_-=^$ z`cj0}+rtb7;F8`xKg`at9CZkR^YXOZ^Hn{pq;vndQNWJujBp-{U!_S~&!`m9*=^ zBd36^1qIu+L9a3)<|oRjgyye$qz>_<4k3I4`~uvY-**1s*Z6--y*K~xj#rCq8~2lC z%M;kz`WwY`8)-f_gQo<)`*35p#Qj#Bu6;z(JJuqw6|Yiws{M6me&T+_=4@S8GrYJV z6FH<~yCQ0j1)Hw_2JJv#E714sPbQkAEAiW*0el3q-5w*v@8j9g@ozPQ)>nD22U#pN zMNuVZSm6QGaA-FctAQgoC76v&5(L2^FX;qR5s9Y`w?VnV0hDY8^%Zrk^K+y<{FpFl z>-PneEtFu+E(-1&2abd@ZZLfR@P#xykJ0V|fLD-8V(9=8>V|9SdnSgk)sUY|Y)q|zj8sz>atc)!6O8!G(GH`o}&lc~1d zN{+C3<7j_@TtS7xPCaKlX_VOQdL;%__x-W9G zJtypnONQ7(wR?Gkw>Mta9~P18$<4N@|Gi2KDp2WXWo_ddd@GqE=k7jeNWMtluQs~> zNs_f+O|N@KlsDIc;)rsM&OcYFB!io)td&q3Pw4tNH?FDExv3+Jo84O>zb{Y|(|D(Z zW&fgVlx8nSE_KqaM8QlOW66>7bb}+@;9);P@2~x5=tMo5ncz&^;Y3%;thZ+*%gQ=0B~Befi))H3&QT=?34RA>l>jXl6<}oQe`CF#G64Cfjlm z5f2aZ43+ZP>=PS;wELk=m)@@jc510m#bV3go*oM|lc=O>6+>DOc@ej-)pS)AI+xSh zMO|OFF0z!~9F#XP>6OAihHrF85%x`@*R-&Qy155JE+UlAE0)v7&%UnHd{aTXj4rhN zJ5NbZ75oXr5$AHgPh?+;SmD`%_<5zHtjDHz44V1U_U``HVzJ4_Nq?62C%S->xXY>+ zg=-`!Z6`Lv`~@myBPMUmV0QvZl`|giU`Sd2;?(X1ATLWr;_}rsUXKEC1m(z+15qT$ z&YPvOLb*doIAU+4+*2VDYR9|XCxVMR=p_0CS+(%SRND&frH~;J&ZNoGZ?NBsE!JDU zK`D+5*f)7cE}XH8pj{8LL}v3{iJO9aaRLm1dVeTGcChDs$W{*+r+w+kc`-{YV(ovEF?<% zi2yVf8NkGpOhaNDB)m{pZc~W%H?%(CjG;6<*}dCjZ+#Xe9f)TzTXd_yF6(}*=f^N& zvsNTQGKia;i5Sc&E??SIN*+z(J>6RZeHG{%{Mw;;O}|LP0Rq%i|o^LUD&-v^}m5+TM9R!O7RsdOME*NQdJJgG(Js%2j$KJ z{2JcEFdj4&Go%?|a#b$Bt!XbfC0a;ZX@R%$QeXU$2giP=dv_ThxOg{^o`w(m5%{pd zpl}j1_r&%56Afxzly#P>=1zF8&R4(LzR3~??-aM)|0{+bPDvmUeSe$~#(>7WYR&SZ z4JM`K|04Bknz8CS>Ez5H(WItGRJC%R;^g!y+dY8!;ji<8rk*YaRr~?jT2;ZQ~ z{IZftas~zVN&|zKfY_y%>eo{nOjh zh7oJktG}Bcn`sK&z@t22_^UO|FW+yZwypu-L)|Q&KE^mzna2y)aOf`9W8rP4k9fVg z*p$eOsJD$vJSLCE4bMY+r1y`t-gmzeKfdfDq%HWpLHpf2dV z`D03ajj+dTNcS;Ps_@uNpW3S_p9qBlMXl|8(w{jqYZfDyA_)??8FMRpKLHlkS#8jF6t~>GY>TquJEjW#;WWZxB=wW5SS68eeQ5E7_ zlb_Sysr#rSCP965xMoKJuXNyK)f(3T7C?6RmE;w^!44<@hFkm!qE^&dFw6#_B1}J+ zvKd)lq_0mZ!9t%5n$k<}Mjo?zC^7;ixqLFm>qd0$F;~e_ain0eND*g;S&GE4;D`9`FRn_o~u5Z+Vi3G=Y|IG#u5p5}Fjek=aX+M=2zD5WwQfVIT zl9<0AMnENZ1BZWa^0I4hFIRfpd!}%Ll7YlbO-dYNIevc0`iYE`h zmAe=D1?%j`U7p@eXe>iYZ-TlWhnJl_^w^jBrlB70dRM8P*DsE=rG@_9B`iP6qXyAM zebe?ZzsI2?buDT&k(RqY4BL~;T3&tvY>N`9cgVFu(lM|^gY%ckhz^G3l&&Dz58@1s z&;Gzn<%PplcaAX+HM_M3U!$;H1Ua9;3Nna|qtw0I&w zK<_vDH9Bd9oa_v83}P2dqBOWPLgzAX_(?1KhxAN%VM>y`=4 z#r$`nWuuF)d&32FH?Mjj}- zPvdAv{fF01U;CBh$ezM-%{)RoshG6Kp?@U9b#?oHm35@zVN~D)k!{ul`%0{pWghNN z&3pYAT3>a`X3awjxbC*)B{tvS!3yvQxG?`|W0Bfg%)rQOr1$6l1m=6Vm^(v^Z7m?S z=B(B(4)#6(Gys|(SOFFe7v?_@3XHFpwpPT_bJQ*Hmn1?5d$0oHfAR-|_`. Then, load the data table from ``Nickel.gr`` + and ``NiTarget.gr``. These datasets are based on data from `Atomic Pair Distribution Function Analysis: A Primer + `_. + :: + + from diffpy.utils.parsers import loadData + nickel_datatable = loadData('') + nitarget_datatable = loadData('') + + Each data table has two columns: first is the grid and second is the function value. + To extract the columns, we can utilize the serialize function ... :: + + from diffpy.utils.parsers import serialize_data + nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) + nickel_grid = nickel_data['Nickel.gr']['grid'] + nickel_func = nickel_data['Nickel.gr']['func'] + target_data = serialize_data('NiTarget.gr', {}, nitarget_datatable, dt_colnames=['grid', 'function']) + target_grid = nickel_data['Nickel.gr']['grid'] + target_func = nickel_data['Nickel.gr']['func'] + + ... or you can use any other column extracting method you prefer. + +2) If we plot the two on top of each other :: + + import matplotlib.pyplot as plt + plt.plot(target_grid, target_func, linewidth=3) + plt.plot(nickel_grid, nickel_func, linewidth=1) + + they look pretty similar, but to truly see the difference, we should plot the difference between the two. + We may want to run something like ... :: + + import numpy as np + difference = np.subtract(target_func, nickel_func) + + ... but this will only produce the right result if the ``target_func`` and ``nickel_func`` are on the same grid. + Checking the lengths of ``target_grid`` and ``nickel_grid`` shows that these grids are clearly distinct. + +3) However, we can resample the two functions to be on the same grid. Since both functions have grids spanning + ``[0, 60]``, let us define a new grid ... :: + + grid = np.linspace(0, 60, 6001) + + ... and use the diffpy.utils ``wsinterp`` function to resample on this grid.:: + + from diffpy.utils.parsers import wsinterp + nickel_resample = wsinterp(grid, nickel_grid, nickel_func) + target_resample = wsinterp(grid, target_grid, target_func) + + We can now plot the difference to see that these two functions are in fact equal.: + + plt.plot(grid, target_resample - nickel_resample) + + This is the desired result as the data in ``Nickel.gr`` is every tenth data point in ``NiTarget.gr``. + This also shows us that ``wsinterp`` can help us reconstruct a function from incomplete data. + +4) In order for our function reconstruction to be perfect, we require that (a) the function is a Fourier transform of a + band-limited dataset and (b) the original grid has enough equally-spaced datapoints based on the Nyquist sampling + theorem. + + * If our function :math:`F(r)` is of the form :math:`F(r) = \int_0^{qmax} f(q)e^{-iqr}dq` where :math:`qmax` is + the bandlimit, then for a grid spanning :math:`r \in [rmin, rmax]`, the Nyquist sampling theorem tells us we + require at least :math:`qmax * (rmin - rmax) / \pi` equally-spaced datapoints. + + In the case of our dataset, our band-limit is ``qmax=25.0`` and our function spans :math:`r \in (0.0, 60.0)`. + Thus, our original grid requires :math:`25.0 * 60.0 / \pi < 478`. Since our grid has :math:`601` datapoints, our + reconstruction was perfect as shown from the comparison between ``Nickel.gr`` and ``NiTarget.gr``. diff --git a/doc/manual/source/index.rst b/doc/manual/source/index.rst index f150e77d..ffa605cc 100644 --- a/doc/manual/source/index.rst +++ b/doc/manual/source/index.rst @@ -9,7 +9,7 @@ diffpy.utils - general purpose shared utilities for the diffpy libraries. The diffpy.utils package provides general functions for extracting data from variously formatted text files as well as some PDF-specific functionality. These include wx GUI utilities used by the PDFgui program and an interpolation function -based on the Whittaker-Shannon formula for upsampling/downsampling a bandlimited PDF or other profile function. +based on the Whittaker-Shannon formula for resampling a bandlimited PDF or other profile function. ======== Examples @@ -17,6 +17,7 @@ Examples Illustrations of when and how one would use various diffpy.utils functions. * :ref:`File Data Extraction` +* :ref:`Resampling & Data Reconstruction` ======= Authors From ef0d9cd8ae4091d5022101d4c43055d8e0de1d77 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 8 Nov 2023 14:36:00 -0500 Subject: [PATCH 047/696] Update news (minor) --- news/docs_patch.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/news/docs_patch.rst b/news/docs_patch.rst index c7353fbe..efede54b 100644 --- a/news/docs_patch.rst +++ b/news/docs_patch.rst @@ -1,7 +1,8 @@ **Added:** * New documentation build. -* Quickstart tutorial for parsers. +* Added examples for file parsers and resampling. +* Tested for Jupyter Notebook compatibility. **Changed:** From c896acf940d70e216db4e0d404b61d97b1a5b98d Mon Sep 17 00:00:00 2001 From: = Date: Wed, 8 Nov 2023 15:44:29 -0500 Subject: [PATCH 048/696] Setup changelog --- CHANGELOG.rst | 7 ++++--- rever.xsh | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1484206d..f34f35db 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,4 +1,5 @@ -Release notes -############# +======================= +diffpy.utils Change Log +======================= -empty until rever changelog action \ No newline at end of file +.. current developments diff --git a/rever.xsh b/rever.xsh index 25ddb256..edea2f7e 100644 --- a/rever.xsh +++ b/rever.xsh @@ -1,8 +1,8 @@ $PROJECT = 'diffpy.utils' $ACTIVITIES = [ + 'changelog', # Create a CHANGELOG.rst using news directory 'tag', # Creates a tag for the new version number 'push_tag', # Pushes the tag up to the $TAG_REMOTE - 'changelog', # Create a CHANGELOG.rst using news directory 'ghrelease', # Creates a Github release entry for the new tag 'pypi', # Sends the package to pypi ] From 4920fafb441540453bbb9254396fcda82c2d265c Mon Sep 17 00:00:00 2001 From: = Date: Wed, 8 Nov 2023 18:42:40 -0500 Subject: [PATCH 049/696] Setup for 3.2.4 release --- CHANGELOG.md | 49 ------------------------------------ CHANGELOG.rst | 48 +++++++++++++++++++++++++++++++++++ news/loaddata_headers.rst | 28 --------------------- news/setup_rever.rst | 23 ----------------- news/support-py2-and-py3.rst | 23 ----------------- setup.py | 2 +- 6 files changed, 49 insertions(+), 124 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 news/loaddata_headers.rst delete mode 100644 news/setup_rever.rst delete mode 100644 news/support-py2-and-py3.rst diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 5fd349e2..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,49 +0,0 @@ -# Release notes - -## Version 3.2.4 – 2023-10--** - -### Added - -- New documentation build - -## Version 3.2.3 – 2023-10-03 - -### Added - -- Compatability with Python 3.12, 3.11 -- CI Coverage. -- New tests for loadData function. -- loadData function now toggleable. Can return either (a) data read from data blocks or (b) header -information stored above the data block. - -### Removed - -- Remove use of pkg_resources (deprecated). -- No longer use Travis. - -## Version 3.1.0 – 2022-12-09 - -### Added - -- Compatibility with Python 3.10, 3.9, 3.8 - -### Removed - -- Remove the support for Python 3.5, 3.6. - -## Version 3.0.0 -- 2019-03-12 - -Differences from version 1.2.2. - -### Added - -- Compatibility with Python 3.7, 3.6, 3.5 in addition to 2.7. - -### Changed - -- Switch to platform-independent "noarch" Anaconda package. - -### Deprecated - -- Variable `__gitsha__` in the `version` module which was renamed - to `__git_commit__`. diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f34f35db..8cef12e5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,3 +3,51 @@ diffpy.utils Change Log ======================= .. current developments + +v3.2.3 +==================== + +**Added:** + +* Compatability with Python 3.12.0rc3, 3.11. +* CI Coverage. +* New tests for loadData function. +* loadData function now toggleable. Can return either (a) data read from data blocks or (b) header information stored + above the data block. + +**Removed:** + +* Remove use of pkg_resources (deprecated). +* No longer use Travis. + + + +v3.1.0 +==================== + +**Added:** + +* Compatibility with Python 3.10, 3.9, 3.8. + +**Removed:** + +* Remove the support for Python 3.5, 3.6. + + + +v3.0.0 +==================== + +**Added:** + +* Compatibility with Python 3.7, 3.6, 3.5 in addition to 2.7. + +**Changed:** + +* Switch to platform-independent "noarch" Anaconda package. + +**Deprecated:** + +* Variable `__gitsha__` in the `version` module which was renamed to `__git_commit__`. + + diff --git a/news/loaddata_headers.rst b/news/loaddata_headers.rst deleted file mode 100644 index c275c20b..00000000 --- a/news/loaddata_headers.rst +++ /dev/null @@ -1,28 +0,0 @@ -**Added:** - -* Compatability with Python 3.12.0rc3, 3.11. -* CI Coverage. -* New tests for loadData function. -* loadData function now toggleable. Can return either (a) data read from data blocks or (b) header information stored - above the data block. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* Remove use of pkg_resources (deprecated). -* No longer use Travis. - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/setup_rever.rst b/news/setup_rever.rst deleted file mode 100644 index a46b163d..00000000 --- a/news/setup_rever.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Compatibility with Python 3.10, 3.9, 3.8. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* Remove the support for Python 3.5, 3.6. - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/support-py2-and-py3.rst b/news/support-py2-and-py3.rst deleted file mode 100644 index f711a6eb..00000000 --- a/news/support-py2-and-py3.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Compatibility with Python 3.7, 3.6, 3.5 in addition to 2.7. - -**Changed:** - -* Switch to platform-independent "noarch" Anaconda package. - -**Deprecated:** - -* Variable `__gitsha__` in the `version` module which was renamed to `__git_commit__`. - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/setup.py b/setup.py index 6c797b5c..18d71f8d 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # Use this version when git data are not available, like in git zip archive. # Update when tagging a new release. -FALLBACK_VERSION = '3.2.3' +FALLBACK_VERSION = '3.2.4' # versioncfgfile holds version data for git commit hash and date. # It must reside in the same directory as version.py. From 98aa43c52d09aeeff7188f0596107409fcacd2f4 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 8 Nov 2023 19:12:13 -0500 Subject: [PATCH 050/696] Fix reference --- rever.xsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rever.xsh b/rever.xsh index edea2f7e..f1a87228 100644 --- a/rever.xsh +++ b/rever.xsh @@ -12,7 +12,7 @@ $CHANGELOG_IGNORE = ['TEMPLATE.rst'] $PUSH_TAG_REMOTE = 'git@github.com:diffpy/diffpy.utils.git' # Repo to push tags to $GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge $GITHUB_REPO = 'diffpy.utils' # Github repo for Github releases and conda-forge -$GHRELEASE_PREPEND = """See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. +$GHRELEASE_PREPEND = """See [CHANGELOG.rst](CHANGELOG.rst) for detailed release notes. The release is also available at [PyPI](https://pypi.org/project/diffpy.utils/) and [Conda](https://anaconda.org/conda-forge/diffpy.utils). """ # release message From 1502c67412c5a2de71afdf897ff51a263c2cf905 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 8 Nov 2023 19:21:14 -0500 Subject: [PATCH 051/696] Fix docs sidebar --- CHANGELOG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8cef12e5..27d7f48b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,6 @@ -======================= -diffpy.utils Change Log -======================= +============= +Release Notes +============= .. current developments From c2cb2aa1084be36b1cb9eeeeb8a34437d48df673 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 13 Nov 2023 21:42:57 +0000 Subject: [PATCH 052/696] adding rever directory to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 435c0626..313f3b88 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ nosetests.xml # version information setup.cfg /src/diffpy/*/version.cfg + +# Rever +rever/ From c178dfa09ad67e9f03c9b0b124f3d562d88bfcdc Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 13 Nov 2023 21:45:06 +0000 Subject: [PATCH 053/696] Updated CHANGELOG for 3.2.4 --- CHANGELOG.rst | 16 ++++++++++++++++ news/docs_patch.rst | 26 -------------------------- 2 files changed, 16 insertions(+), 26 deletions(-) delete mode 100644 news/docs_patch.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 27d7f48b..93412f9c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,22 @@ Release Notes .. current developments +v3.2.4 +==================== + +**Added:** + +* New documentation build. +* Added examples for file parsers and resampling. +* Tested for Jupyter Notebook compatibility. + +**Changed:** + +* Theme changed from `sphinx_py3doc_enhanced_theme` to `sphinx_rtd_theme`. +* User now warned when data_table data overwrites hdata (header) data. + + + v3.2.3 ==================== diff --git a/news/docs_patch.rst b/news/docs_patch.rst deleted file mode 100644 index efede54b..00000000 --- a/news/docs_patch.rst +++ /dev/null @@ -1,26 +0,0 @@ -**Added:** - -* New documentation build. -* Added examples for file parsers and resampling. -* Tested for Jupyter Notebook compatibility. - -**Changed:** - -* Theme changed from `sphinx_py3doc_enhanced_theme` to `sphinx_rtd_theme`. -* User now warned when data_table data overwrites hdata (header) data. - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From 7fcce6d16f754d126af9371ee412d92af9e4c9d0 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 14 Nov 2023 13:31:12 -0500 Subject: [PATCH 054/696] Change install sources --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 00b73b0d..c8510224 100644 --- a/README.rst +++ b/README.rst @@ -48,13 +48,13 @@ INSTALLATION The preferred method is to use Anaconda Python and install from the "diffpy" channel of Anaconda packages :: - conda config --add channels diffpy + conda config --add channels conda-forge conda install diffpy.utils -Another option is to use ``easy_install`` to download and install the +Another option is to use ``pip`` to download and install the latest release from `Python Package Index `_ :: - easy_install diffpy.utils + pip install diffpy.utils If you prefer to install from sources, obtain the source archive and run :: From 4083e7d26f031c0e4a05fade978358f6baa3b257 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 16 Nov 2023 17:41:42 -0500 Subject: [PATCH 055/696] Update to Miniconda --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index c8510224..99e40a8f 100644 --- a/README.rst +++ b/README.rst @@ -33,7 +33,7 @@ The functions in diffpy.utils.wx module require * ``wxPython`` - GUI toolkit for the Python language -We recommend to use `Anaconda Python `_ +We recommend to use `Miniconda Python `_ as it allows to install the software dependencies together with diffpy.utils. For other Python distributions it is necessary to install the required software separately. As an example, on Ubuntu Linux the @@ -45,8 +45,8 @@ required software can be installed with :: INSTALLATION ------------------------------------------------------------------------ -The preferred method is to use Anaconda Python and install from the -"diffpy" channel of Anaconda packages :: +The preferred method is to use Miniconda (or, less preferred, Anaconda) Python +and install from the "conda-forge" channel of Conda packages :: conda config --add channels conda-forge conda install diffpy.utils From 45dc183a4010f7acaf73694037196da2c46e8c72 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 16 Nov 2023 18:19:58 -0500 Subject: [PATCH 056/696] Intuitive installation instructions --- README.rst | 46 +++++++++++-------- .../source/examples/resampleexample.rst | 10 ++-- doc/manual/source/index.rst | 2 +- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/README.rst b/README.rst index 99e40a8f..8288ad6b 100644 --- a/README.rst +++ b/README.rst @@ -33,40 +33,46 @@ The functions in diffpy.utils.wx module require * ``wxPython`` - GUI toolkit for the Python language -We recommend to use `Miniconda Python `_ -as it allows to install the software dependencies together with -diffpy.utils. For other Python distributions it is necessary to install -the required software separately. As an example, on Ubuntu Linux the -required software can be installed with :: - - sudo apt-get install python-setuptools python-numpy - INSTALLATION ------------------------------------------------------------------------ -The preferred method is to use Miniconda (or, less preferred, Anaconda) Python -and install from the "conda-forge" channel of Conda packages :: +The preferred method is to use `Miniconda Python +`_ +and install from the "conda-forge" channel of Conda packages. + +To add "conda-forge" to the conda channels, run the following in a terminal. :: conda config --add channels conda-forge - conda install diffpy.utils -Another option is to use ``pip`` to download and install the -latest release from `Python Package Index `_ :: +We want to install our packages in a suitable conda environment. +The following creates and activates a new environment named ``utils`` :: + + conda create -n utils python=3 + conda activate utils +Then, to fully install ``diffpy.utils`` in our active environment, run :: + + conda install diffpy.utils + +Another option is to use ``pip`` to download and install the latest release from +`Python Package Index `_. +To install using ``pip`` into your ``utils`` environment, we will also have to install dependencies :: + + pip install numpy pip install diffpy.utils -If you prefer to install from sources, obtain the source archive and -run :: +If you prefer to install from sources, obtain the source archive and run :: python setup.py install -You may need to use ``sudo`` with system Python as it attempts to -install to standard system directories. If sudo is not available, check -the usage info from ``python setup.py install --help`` for options to -install to user-writable locations. The installation integrity can be -verified by changing to the HOME directory and running :: +You may need to use ``sudo`` with system Python as it attempts to install to standard system directories. +If sudo is not available, check the usage info from ``python setup.py install --help`` for options to +install to user-writable locations. + +To check the installation integrity, install pytest. If the following code passes all checks, the install worked! :: + pip install pytest python -m diffpy.utils.tests.run diff --git a/doc/manual/source/examples/resampleexample.rst b/doc/manual/source/examples/resampleexample.rst index 1279740d..7b91c249 100644 --- a/doc/manual/source/examples/resampleexample.rst +++ b/doc/manual/source/examples/resampleexample.rst @@ -58,16 +58,18 @@ given enough datapoints. nickel_resample = wsinterp(grid, nickel_grid, nickel_func) target_resample = wsinterp(grid, target_grid, target_func) - We can now plot the difference to see that these two functions are in fact equal.: + We can now plot the difference to see that these two functions are quite similar.: + plt.plot(grid, target_resample) + plt.plot(grid, nickel_resample) plt.plot(grid, target_resample - nickel_resample) This is the desired result as the data in ``Nickel.gr`` is every tenth data point in ``NiTarget.gr``. This also shows us that ``wsinterp`` can help us reconstruct a function from incomplete data. -4) In order for our function reconstruction to be perfect, we require that (a) the function is a Fourier transform of a - band-limited dataset and (b) the original grid has enough equally-spaced datapoints based on the Nyquist sampling - theorem. +4) In order for our function reconstruction to be perfect up to a truncation error, we require that (a) the function is + a Fourier transform of a band-limited dataset and (b) the original grid has enough equally-spaced datapoints based on + the Nyquist sampling theorem. * If our function :math:`F(r)` is of the form :math:`F(r) = \int_0^{qmax} f(q)e^{-iqr}dq` where :math:`qmax` is the bandlimit, then for a grid spanning :math:`r \in [rmin, rmax]`, the Nyquist sampling theorem tells us we diff --git a/doc/manual/source/index.rst b/doc/manual/source/index.rst index ffa605cc..ccab47a2 100644 --- a/doc/manual/source/index.rst +++ b/doc/manual/source/index.rst @@ -34,7 +34,7 @@ https://github.com/diffpy/diffpy.utils/graphs/contributors. Installation ====================================== -See the `README `_ +See the `README `_ file included with the distribution. ====================================== From f3eca3cf5f5aa6771fdb21cc3b8e02d89a9fd7ec Mon Sep 17 00:00:00 2001 From: = Date: Thu, 16 Nov 2023 18:29:41 -0500 Subject: [PATCH 057/696] wxPython note --- README.rst | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index 8288ad6b..fcfcb319 100644 --- a/README.rst +++ b/README.rst @@ -19,21 +19,6 @@ function over a new grid. For more information about the diffpy.utils library, see the users manual at http://diffpy.github.io/diffpy.utils. - -REQUIREMENTS ------------------------------------------------------------------------- - -The diffpy.utils package requires Python 3.7 or later or 2.7 and -the following software: - -* ``setuptools`` - tools for installing Python packages -* ``NumPy`` - library for scientific computing with Python - -The functions in diffpy.utils.wx module require - -* ``wxPython`` - GUI toolkit for the Python language - - INSTALLATION ------------------------------------------------------------------------ @@ -62,7 +47,13 @@ To install using ``pip`` into your ``utils`` environment, we will also have to i pip install numpy pip install diffpy.utils -If you prefer to install from sources, obtain the source archive and run :: +For those planning to use functions in the ``diffpy.utils.wx`` module, you will also need to install ``wxPython''. +Both of the following lines will install this package. :: + + conda install wxPython + pip install wxPython + +If you prefer to install from sources, after installing the dependencies, obtain the source archive and run :: python setup.py install @@ -70,7 +61,7 @@ You may need to use ``sudo`` with system Python as it attempts to install to sta If sudo is not available, check the usage info from ``python setup.py install --help`` for options to install to user-writable locations. -To check the installation integrity, install pytest. If the following code passes all checks, the install worked! :: +To check the installation integrity if the following passes all checks, you are good! :: pip install pytest python -m diffpy.utils.tests.run From 94daed9c005dd8b5b7aea9a29cd7ebb11b18c0f3 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 16 Nov 2023 18:36:35 -0500 Subject: [PATCH 058/696] Rebuild documentation --- .github/workflows/docs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9602b64f..5d865623 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,6 +1,9 @@ name: Build Documentation on: + push: + branches: + - main release: jobs: From ade26134c53986226248c6c24a11b33bf569b793 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 16 Nov 2023 18:46:34 -0500 Subject: [PATCH 059/696] Final edits --- README.rst | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index fcfcb319..da70c652 100644 --- a/README.rst +++ b/README.rst @@ -31,10 +31,10 @@ To add "conda-forge" to the conda channels, run the following in a terminal. :: conda config --add channels conda-forge We want to install our packages in a suitable conda environment. -The following creates and activates a new environment named ``utils`` :: +The following creates and activates a new environment named ``diffpy-utils`` :: - conda create -n utils python=3 - conda activate utils + conda create -n diffpy-utils python=3 + conda activate diffpy-utils Then, to fully install ``diffpy.utils`` in our active environment, run :: @@ -42,7 +42,7 @@ Then, to fully install ``diffpy.utils`` in our active environment, run :: Another option is to use ``pip`` to download and install the latest release from `Python Package Index `_. -To install using ``pip`` into your ``utils`` environment, we will also have to install dependencies :: +To install using ``pip`` into your ``diffpy-utils`` environment, we will also have to install dependencies :: pip install numpy pip install diffpy.utils @@ -53,15 +53,13 @@ Both of the following lines will install this package. :: conda install wxPython pip install wxPython -If you prefer to install from sources, after installing the dependencies, obtain the source archive and run :: +If you prefer to install from sources, after installing the dependencies, obtain the source archive from +`GitHub`_. Once installed, ``cd`` into your ``diffpy.utils`` directory +and run the following :: - python setup.py install + pip install -e . -You may need to use ``sudo`` with system Python as it attempts to install to standard system directories. -If sudo is not available, check the usage info from ``python setup.py install --help`` for options to -install to user-writable locations. - -To check the installation integrity if the following passes all checks, you are good! :: +To check the installation integrity, if the following passes all checks, you are good! :: pip install pytest python -m diffpy.utils.tests.run From 0a79d8717f2f8c73746d2af83275f34ac1c63d20 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 19 Jan 2024 20:20:31 -0500 Subject: [PATCH 060/696] Add wx import --- news/add_wx_import.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/wx/gridutils.py | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 news/add_wx_import.rst diff --git a/news/add_wx_import.rst b/news/add_wx_import.rst new file mode 100644 index 00000000..6cf3dbef --- /dev/null +++ b/news/add_wx_import.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Added a wx import to fix module not found error. + +**Security:** + +* diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index 55cab38a..4289196d 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -16,6 +16,8 @@ """Common functions for manipulating wx.grid.Grid. """ +import wx + def getSelectionRows(grid): """Indices of the rows that have any cell selected. From 01399975ad59905ea335258d27eb5576cab8cccf Mon Sep 17 00:00:00 2001 From: = Date: Fri, 19 Jan 2024 21:01:44 -0500 Subject: [PATCH 061/696] Fallback --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 18d71f8d..08e2146b 100755 --- a/setup.py +++ b/setup.py @@ -59,6 +59,9 @@ def getversioncfg(): g = gitinfo() except OSError: pass + if len(g['version']) == 0: + g['version'] = FALLBACK_VERSION + # finally, check and update the active version file cp = RawConfigParser() cp.read(versioncfgfile) From e32ba1d15772c9f3d9e5f61f76970e9b08788bba Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 20 Jan 2024 02:38:18 +0000 Subject: [PATCH 062/696] Updated CHANGELOG for 3.2.5 --- CHANGELOG.rst | 9 +++++++++ news/add_wx_import.rst | 23 ----------------------- 2 files changed, 9 insertions(+), 23 deletions(-) delete mode 100644 news/add_wx_import.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 93412f9c..73d00b79 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,15 @@ Release Notes .. current developments +v3.2.5 +==================== + +**Fixed:** + +* Added a wx import to fix module not found error. + + + v3.2.4 ==================== diff --git a/news/add_wx_import.rst b/news/add_wx_import.rst deleted file mode 100644 index 6cf3dbef..00000000 --- a/news/add_wx_import.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Added a wx import to fix module not found error. - -**Security:** - -* From c51fd20dd63a2b00c2b281a723bd09010ab602de Mon Sep 17 00:00:00 2001 From: = Date: Fri, 19 Jan 2024 21:47:07 -0500 Subject: [PATCH 063/696] Add space --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index da70c652..ab2c3bab 100644 --- a/README.rst +++ b/README.rst @@ -54,7 +54,7 @@ Both of the following lines will install this package. :: pip install wxPython If you prefer to install from sources, after installing the dependencies, obtain the source archive from -`GitHub`_. Once installed, ``cd`` into your ``diffpy.utils`` directory +`GitHub `_. Once installed, ``cd`` into your ``diffpy.utils`` directory and run the following :: pip install -e . From 1d8067fe9fd45f02fa21a70ebaa45d943560f552 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 20 Jan 2024 02:50:40 +0000 Subject: [PATCH 064/696] Updated CHANGELOG for 3.2.6 --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 73d00b79..d6b7f8aa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,11 @@ Release Notes .. current developments +v3.2.6 +==================== + + + v3.2.5 ==================== From 89d59a84ecc192c2d37617d8f585523c8639089d Mon Sep 17 00:00:00 2001 From: = Date: Fri, 19 Jan 2024 22:05:11 -0500 Subject: [PATCH 065/696] Fix emph --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ab2c3bab..95612b5c 100644 --- a/README.rst +++ b/README.rst @@ -47,7 +47,7 @@ To install using ``pip`` into your ``diffpy-utils`` environment, we will also ha pip install numpy pip install diffpy.utils -For those planning to use functions in the ``diffpy.utils.wx`` module, you will also need to install ``wxPython''. +For those planning to use functions in the ``diffpy.utils.wx`` module, you will also need to install ``wxPython``. Both of the following lines will install this package. :: conda install wxPython From 45e562b77b9ca90270b5d348a3b75588193da915 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 20 Jan 2024 03:07:19 +0000 Subject: [PATCH 066/696] Updated CHANGELOG for 3.2.7 --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d6b7f8aa..bc754ff7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,11 @@ Release Notes .. current developments +v3.2.7 +==================== + + + v3.2.6 ==================== From b1ee249e61388107afffab827e72681e9f1a1345 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 12 Feb 2024 16:03:53 -0500 Subject: [PATCH 067/696] initial commit of diffraction objects --- .../scattering_objects/diffraction_objects.py | 403 ++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 src/diffpy/utils/scattering_objects/diffraction_objects.py diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py new file mode 100644 index 00000000..bbdb119a --- /dev/null +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -0,0 +1,403 @@ +import uuid +from copy import deepcopy + +import numpy as np +from numpy import array + +QQUANTITIES = ["q"] +ANGLEQUANTITIES = ["angle","tth", "twotheta", "2theta"] +DQUANTITIES = ["d", "dspace"] +XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES +XUNITS = ["degrees","radians","rad","deg","inv_angs","inv_nm","nm-1","A-1"] + +class Diffraction_object(): + def __init__(self, name='', wavelength=None): + self.name = name + self.wavelength = wavelength + self.scat_quantities = {} + self.on_q = [np.empty(1), np.empty(1)] + self.on_tth = [np.empty(1), np.empty(1)] + self.on_d = [np.empty(1), np.empty(1)] + self._all_arrays = [self.on_q, self.on_tth] + + def __add__(self, other): + summed = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + summed.on_tth[1] = self.on_tth[1] + other + summed.on_q[1] = self.on_q[1] + other + elif not isinstance(other, Diffraction_object): + raise TypeError(f"I only know how to sum two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(f"objects are not on the same x-grid. You may add them using the self.add method and" + f"specifying how to handle the mismatch.") + else: + summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] + summed.on_q[1] = self.on_q[1] + other.on_q[1] + return summed + def __radd__(self, other): + summed = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + summed.on_tth[1] = self.on_tth[1] + other + summed.on_q[1] = self.on_q[1] + other + elif not isinstance(other, Diffraction_object): + raise TypeError(f"I only know how to sum two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(f"objects are not on the same x-grid. You may add them using the self.add method and" + f"specifying how to handle the mismatch.") + else: + summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] + summed.on_q[1] = self.on_q[1] + other.on_q[1] + return summed + + def __sub__(self, other): + subtracted = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + subtracted.on_tth[1] = self.on_tth[1] - other + subtracted.on_q[1] = self.on_q[1] - other + elif not isinstance(other, Diffraction_object): + raise TypeError(f"I only know how to subtract two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError( + f"objects are not on the same x-grid. You may subtract them using the self.add method and" + f"specifying how to handle the mismatch.") + else: + subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] + subtracted.on_q[1] = self.on_q[1] - other.on_q[1] + return subtracted + + def __rsub__(self, other): + subtracted = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + subtracted.on_tth[1] = other - self.on_tth[1] + subtracted.on_q[1] = other - self.on_q[1] + elif not isinstance(other, Diffraction_object): + raise TypeError(f"I only know how to subtract two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError( + f"objects are not on the same x-grid. You may subtract them using the self.add method and" + f"specifying how to handle the mismatch.") + else: + subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] + subtracted.on_q[1] = other.on_q[1] - self.on_q[1] + return subtracted + + def __mul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif not isinstance(other, Diffraction_object): + raise TypeError(f"I only know how to multiply two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError( + f"objects are not on the same x-grid. You may multiply them using the self.add method and" + f"specifying how to handle the mismatch.") + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + + def __rmul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError( + f"objects are not on the same x-grid. You may multiply them using the self.add method and" + f"specifying how to handle the mismatch.") + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + + def __truediv__(self, other): + divided = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + divided.on_tth[1] = other / self.on_tth[1] + divided.on_q[1] = other / self.on_q[1] + elif not isinstance(other, Diffraction_object): + raise TypeError(f"I only know how to multiply two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError( + f"objects are not on the same x-grid. You may multiply them using the self.add method and" + f"specifying how to handle the mismatch.") + else: + divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] + divided.on_q[1] = self.on_q[1] / other.on_q[1] + return divided + + + def __rtruediv__(self, other): + divided = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + divided.on_tth[1] = other / self.on_tth[1] + divided.on_q[1] = other / self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError( + f"Diffraction objects are not on the same x-grid. You may multiply them using the self.add method and" + f"specifying how to handle the mismatch.") + else: + divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] + divided.on_q[1] = other.on_q[1] / self.on_q[1] + return divided + + + def set_angles_from_list(self, angles_list): + self.angles = angles_list + self.n_steps = len(angles_list) - 1. + self.begin_angle = self.angles[0] + self.end_angle = self.angles[-1] + + def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): + ''' + create an array of linear spaced Q-values + + Parameters + ---------- + begin_q float + the beginning angle + end_q float + the ending angle + step_size float + the size of the step between points. Only specify step_size or n_steps, not both + n_steps integer + the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both + + Returns + ------- + Sets self.qs + self.qs array of floats + the q values in the independent array + + ''' + self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) + + + def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): + ''' + create an array of linear spaced angle-values + + Parameters + ---------- + begin_angle float + the beginning angle + end_angle float + the ending angle + step_size float + the size of the step between points. Only specify step_size or n_steps, not both + n_steps integer + the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both + + Returns + ------- + Sets self.angles + self.angles array of floats + the q values in the independent array + + ''' + self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) + + + def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): + if step_size is not None and n_steps is not None: + print( + "WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be " + "reset.") + array = np.linspace(begin, end, n_steps) + elif step_size is not None: + array = np.arange(begin, end, step_size) + elif n_steps is not None: + array = np.linspace(begin, end, n_steps) + return array + + def get_angle_index(self, angle): + count = 0 + for i, target in enumerate(self.angles): + if angle == target: + return i + else: + count += 1 + if count >= len(self.angles): + raise IndexError(f"WARNING: no angle {angle} found in angles list") + + def insert_scattering_quantity(self, xarray, yarray, xtype, metadata={}): + f""" + insert a new scattering quantity into the scattering object + + Parameters + ---------- + xarray array-like of floats + the independent variable array + yarray array-like of floats + the dependent variable array + xtype string + the type of quantity for the independent variable from {*XQUANTITIES,} + metadata: dict + the metadata in the form of a dictionary of user-supplied key:value pairs + + Returns + ------- + + """ + self.input_xtype = xtype + self.metadata = metadata + if xtype.lower() in QQUANTITIES: + self.on_q = [np.array(xarray),np.array(yarray)] + elif xtype.lower() in ANGLEQUANTITIES: + self.on_tth = [np.array(xarray),np.array(yarray)] + elif xtype.lower() in DQUANTITIES: + self.on_tth = [np.array(xarray),np.array(yarray)] + self.set_all_arrays() + def q_to_tth(self): + r""" + Helper function to convert q to two-theta. + + By definition the relationship is: + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) + + Parameters + ---------- + q : array + An array of :math:`q` values + + wavelength : float + Wavelength of the incoming x-rays + + Function adapted from scikit-beam. Thanks to those developers + + Returns + ------- + two_theta : array + An array of :math:`2\theta` values in radians + """ + q = self.on_q[0] + q = np.asarray(q) + wavelength = float(self.wavelength) + pre_factor = wavelength / (4 * np.pi) + return np.rad2deg(2 * np.arcsin(q * pre_factor)) + + def tth_to_q(self): + r""" + Helper function to convert two-theta to q + + By definition the relationship is + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} + + + + Parameters + ---------- + two_theta : array + An array of :math:`2\theta` values in units of degrees + + wavelength : float + Wavelength of the incoming x-rays + + Function adapted from scikit-beam. Thanks to those developers. + + Returns + ------- + q : array + An array of :math:`q` values in the inverse of the units + of ``wavelength`` + """ + two_theta = np.asarray(np.deg2rad(self.on_tth[0])) + wavelength = float(self.wavelength) + pre_factor = (4 * np.pi) / wavelength + return pre_factor * np.sin(two_theta / 2) + + def set_all_arrays(self): + master_array, xtype = self._get_original_array() + if xtype == "q": + self.on_tth[0] = self.q_to_tth() + self.on_tth[1] = master_array[1] + if xtype == "tth": + self.on_q[0] = self.tth_to_q() + self.on_q[1] = master_array[1] + self.tthmin = self.on_tth[0][0] + self.tthmax = self.on_tth[0][-1] + self.qmin = self.on_q[0][0] + self.qmax = self.on_q[0][-1] + + + def _get_original_array(self): + if self.input_xtype in QQUANTITIES: + return self.on_q, "q" + elif self.input_xtype in ANGLEQUANTITIES: + return self.on_tth, "tth" + elif self.input_xtype in DQUANTITIES: + return self.on_d, "d" + + def scale_to(self, target_diff_object, xtype=None, xvalue=None): + f""" + returns a new diffraction object which is the current object but recaled in y to the target + + Parameters + ---------- + target_diff_object: Diffraction_object + the diffractoin object you want to scale the current one on to + xtype: string, optional. Default is Q + the xtype, from {XQUANTITIES}, that you will specify a point from to scale to + xvalue: float. Default is the midpoint of the array + the y-value in the target at this x-value will be used as the factor to scale to. + The entire array is scaled be the factor that places on on top of the other at that point. + xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling. + + Returns + ------- + the rescaled Diffraction_object as a new object + + """ + scaled = deepcopy(self) + if xtype is None: + xtype = "q" + + data = self.on_xtype(xtype) + target = target_diff_object.on_xtype(xtype) + if xvalue is None: + xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2. + + xindex = (np.abs(data[0] - xvalue)).argmin() + ytarget = target[1][xindex] + yself = data[1][xindex] + scaled.on_tth[1] = data[1]*ytarget/yself + scaled.on_q[1] = data[1]*ytarget/yself + return scaled + + def on_xtype(self, xtype): + ''' + return a 2D np array with x in the first column and y in the second for x of type type + Parameters + ---------- + xtype + + Returns + ------- + + ''' + if xtype.lower() in ANGLEQUANTITIES: + return self.on_tth + elif xtype.lower() in QQUANTITIES: + return self.on_q + elif xtype.lower() in DQUANTITIES: + return self.on_d + pass From 800f789e834baf56067b04ee81fc5d83c97b9b8b Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 26 Feb 2024 15:35:55 -0500 Subject: [PATCH 068/696] small edits --- .../utils/scattering_objects/__init__.py | 19 +++++++++++++++++++ .../scattering_objects/diffraction_objects.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/diffpy/utils/scattering_objects/__init__.py diff --git a/src/diffpy/utils/scattering_objects/__init__.py b/src/diffpy/utils/scattering_objects/__init__.py new file mode 100644 index 00000000..08069c20 --- /dev/null +++ b/src/diffpy/utils/scattering_objects/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.utils by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Simon Billinge +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## + +"""Scattering objects +""" + +# End of file diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index bbdb119a..3cf1a808 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -26,7 +26,7 @@ def __add__(self, other): summed.on_tth[1] = self.on_tth[1] + other summed.on_q[1] = self.on_q[1] + other elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to sum two Scattering_object objects") + raise TypeError(f"I only know how to sum two Diffraction_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError(f"objects are not on the same x-grid. You may add them using the self.add method and" f"specifying how to handle the mismatch.") From 40c8f8e2ff995faea6225e6a4e5c2801dd84bbce Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 26 Feb 2024 23:41:03 -0500 Subject: [PATCH 069/696] replace setup.py wth pyproject.toml. version is no longer found dynamically, though we could try and change that --- pyproject.toml | 43 +++++++++++++++++ setup.py | 127 ------------------------------------------------- 2 files changed, 43 insertions(+), 127 deletions(-) create mode 100644 pyproject.toml delete mode 100755 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..27290713 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,43 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "diffpy.utils" +version = "3.2.7" +authors = [ + { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, +] +maintainers = [ + { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, +] +description = "Shared utilities for diffpy packages." +keywords = ["text data parsers", "wx grid", "diffraction objects"] +readme = "README.rst" +requires-python = ">=3.10" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Topic :: Scientific/Engineering :: Physics', +] + +[project.urls] +Homepage = "https://github.com/diffpy/diffpy.utils/" +Issues = "https://github.com/diffpy/diffpy.utils/issues" + +[tool.setuptools.packages.find] +where = ["src"] # list of folders that contain the packages (["."] by default) +include = ["diffpy*"] # package names should match these glob patterns (["*"] by default) +exclude = ["diffpy.utils.tests*"] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) diff --git a/setup.py b/setup.py deleted file mode 100755 index 08e2146b..00000000 --- a/setup.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python - -"""utils - small shared utilities for other diffpy packages - -Packages: diffpy.utils -""" - -import os -import re -import sys -from setuptools import setup, find_packages - -# Use this version when git data are not available, like in git zip archive. -# Update when tagging a new release. -FALLBACK_VERSION = '3.2.4' - -# versioncfgfile holds version data for git commit hash and date. -# It must reside in the same directory as version.py. -MYDIR = os.path.dirname(os.path.abspath(__file__)) -versioncfgfile = os.path.join(MYDIR, 'src/diffpy/utils/version.cfg') -gitarchivecfgfile = os.path.join(MYDIR, '.gitarchive.cfg') - -# determine if we run with Python 3. -PY3 = (sys.version_info[0] == 3) - - -def gitinfo(): - from subprocess import Popen, PIPE - kw = dict(stdout=PIPE, cwd=MYDIR, universal_newlines=True) - proc = Popen(['git', 'describe', '--tags', '--match=[v,V,[:digit:]]*'], **kw) - desc = proc.stdout.read() - proc = Popen(['git', 'log', '-1', '--format=%H %ct %ci'], **kw) - glog = proc.stdout.read() - rv = {} - rv['version'] = '.post'.join(desc.strip().split('-')[:2]).lstrip('v').lstrip('V') - rv['commit'], rv['timestamp'], rv['date'] = glog.strip().split(None, 2) - return rv - - -def getversioncfg(): - if PY3: - from configparser import RawConfigParser - else: - from ConfigParser import RawConfigParser - vd0 = dict(version=FALLBACK_VERSION, commit='', date='', timestamp=0) - # first fetch data from gitarchivecfgfile, ignore if it is unexpanded - g = vd0.copy() - cp0 = RawConfigParser(vd0) - cp0.read(gitarchivecfgfile) - if len(cp0.get('DEFAULT', 'commit')) > 20: - g = cp0.defaults() - mx = re.search(r'\btag: [vV]?(\d[^,]*)', g.pop('refnames')) - if mx: - g['version'] = mx.group(1) - # then try to obtain version data from git. - gitdir = os.path.join(MYDIR, '.git') - if os.path.exists(gitdir) or 'GIT_DIR' in os.environ: - try: - g = gitinfo() - except OSError: - pass - if len(g['version']) == 0: - g['version'] = FALLBACK_VERSION - - # finally, check and update the active version file - cp = RawConfigParser() - cp.read(versioncfgfile) - d = cp.defaults() - rewrite = not d or (g['commit'] and ( - g['version'] != d.get('version') or g['commit'] != d.get('commit'))) - if rewrite: - cp.set('DEFAULT', 'version', g['version']) - cp.set('DEFAULT', 'commit', g['commit']) - cp.set('DEFAULT', 'date', g['date']) - cp.set('DEFAULT', 'timestamp', g['timestamp']) - with open(versioncfgfile, 'w') as fp: - cp.write(fp) - return cp - -versiondata = getversioncfg() - -with open(os.path.join(MYDIR, 'README.rst')) as fp: - long_description = fp.read() - -# define distribution -setup_args = dict( - name = "diffpy.utils", - version = versiondata.get('DEFAULT', 'version'), - packages = find_packages(os.path.join(MYDIR, 'src')), - package_dir = {'' : 'src'}, - test_suite = 'diffpy.utils.tests', - include_package_data = True, - zip_safe = False, - author = 'Simon J.L. Billinge group', - author_email = 'sb2896@columbia.edu', - maintainer = 'Simon J.L. Billinge group', - maintainer_email = 'sb2896@columbia.edu', - description = "Shared utilities for diffpy packages.", - long_description = long_description, - long_description_content_type = 'text/x-rst', - license = 'BSD-style license', - url = "https://github.com/diffpy/diffpy.utils/", - keywords = "text data parsers wx grid", - classifiers = [ - # List of possible values at - # http://pypi.python.org/pypi?:action=list_classifiers - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: Unix', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Topic :: Scientific/Engineering :: Physics', - ], -) - -if __name__ == '__main__': - setup(**setup_args) - -# End of file From bd0e40b65b2a3aad5b28685bfd1ec3ae860f2a84 Mon Sep 17 00:00:00 2001 From: Sparky <59151395+Sparks29032@users.noreply.github.com> Date: Tue, 27 Feb 2024 09:02:45 -0500 Subject: [PATCH 070/696] setuptools base version >=62.* Can use pyproject.toml to install in editable mode for this version of setuptools (https://github.com/pypa/setuptools/issues/3019#issuecomment-1264613828) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 27290713..e9ca1c30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61.0"] +requires = ["setuptools>=62.0"] build-backend = "setuptools.build_meta" [project] From f8c4896ecc64f12cf7c5fae413baa964f91fa7da Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 27 Feb 2024 14:26:36 -0500 Subject: [PATCH 071/696] adding support for p3.8 and 3.9 for the time being. --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 27290713..5f86fd16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ description = "Shared utilities for diffpy packages." keywords = ["text data parsers", "wx grid", "diffraction objects"] readme = "README.rst" -requires-python = ">=3.10" +requires-python = ">=3.8" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -26,6 +26,8 @@ classifiers = [ 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', From abe049b25c3ae038595fc133ac679dd1aa1dfd83 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Mon, 18 Mar 2024 00:45:45 -0400 Subject: [PATCH 072/696] Tag-based dynamic versioning --- README.rst | 9 ++++++--- pyproject.toml | 13 +++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 95612b5c..b7b1ec35 100644 --- a/README.rst +++ b/README.rst @@ -57,7 +57,7 @@ If you prefer to install from sources, after installing the dependencies, obtain `GitHub `_. Once installed, ``cd`` into your ``diffpy.utils`` directory and run the following :: - pip install -e . + pip install . To check the installation integrity, if the following passes all checks, you are good! :: @@ -75,9 +75,12 @@ https://github.com/diffpy/diffpy.utils. Feel free to fork the project and contribute. To install diffpy.utils in a development mode, with its sources being directly used by Python -rather than copied to a package directory, use :: +rather than copied to a package directory, use the following in the root +directory :: + + pip install -e . - python setup.py develop --user +Note that this is only supported for `setuptools` version 62.0 and above. CONTACTS diff --git a/pyproject.toml b/pyproject.toml index 5b6c85af..53b7d419 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools>=62.0"] +requires = ["setuptools>=62.0", "setuptools-git-versioning<2"] build-backend = "setuptools.build_meta" [project] name = "diffpy.utils" -version = "3.2.7" +dynamic=['version'] authors = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] @@ -14,7 +14,7 @@ maintainers = [ description = "Shared utilities for diffpy packages." keywords = ["text data parsers", "wx grid", "diffraction objects"] readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -26,7 +26,6 @@ classifiers = [ 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -38,6 +37,12 @@ classifiers = [ Homepage = "https://github.com/diffpy/diffpy.utils/" Issues = "https://github.com/diffpy/diffpy.utils/issues" +[tool.setuptools-git-versioning] +enabled = true +template = "{tag}" +dev_template = "{tag}.post{ccount}" +dirty_template = "{tag}.post{ccount}" + [tool.setuptools.packages.find] where = ["src"] # list of folders that contain the packages (["."] by default) include = ["diffpy*"] # package names should match these glob patterns (["*"] by default) From 02440f4569cd5268b35835e6308c9b9a15b6e161 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 18 Mar 2024 01:38:01 -0400 Subject: [PATCH 073/696] Update documentation build --- .github/workflows/docs.yml | 2 ++ .github/workflows/main.yml | 1 + doc/manual/source/conf.py | 7 +++---- src/diffpy/utils/version.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5d865623..9e4962e8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,6 +14,8 @@ jobs: shell: bash -l {0} steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - uses: conda-incubator/setup-miniconda@v2 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b33a66be..ce422cb4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,6 +21,7 @@ jobs: with: repository: diffpy/diffpy.utils path: . + fetch-depth: 0 # avoid shallow clone with no tags - name: initialize miniconda # this uses a marketplace action that sets up miniconda in a way that makes diff --git a/doc/manual/source/conf.py b/doc/manual/source/conf.py index 107c500e..245da4d4 100644 --- a/doc/manual/source/conf.py +++ b/doc/manual/source/conf.py @@ -63,8 +63,8 @@ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -from setup import versiondata -fullversion = versiondata.get('DEFAULT', 'version') +from importlib.metadata import version +fullversion = version(project) # The short X.Y version. version = ''.join(fullversion.split('.post')[:1]) # The full version, including alpha/beta/rc tags. @@ -77,8 +77,7 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' -today_seconds = versiondata.getint('DEFAULT', 'timestamp') -today = time.strftime('%B %d, %Y', time.localtime(today_seconds)) +today = time.strftime('%B %d, %Y', time.localtime()) year = today.split()[-1] # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index e0236cef..c1618828 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -36,7 +36,7 @@ with as_file(ref) as fcfg: if not os.path.isfile(fcfg): # pragma: no cover from warnings import warn - warn('Package metadata not found, execute "./setup.py egg_info".') + warn('Package metadata not found.') fcfg = os.devnull with open(fcfg) as fp: kwords = [[w.strip() for w in line.split(' = ', 1)] From acfc00d32a72156f760cafcb8e016ea62572eed3 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 29 Mar 2024 15:08:32 -0400 Subject: [PATCH 074/696] dump method for diffraction_objects with test --- .../scattering_objects/diffraction_objects.py | 20 ++++++++++++++- .../utils/tests/test_diffraction_objects.py | 25 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/diffpy/utils/tests/test_diffraction_objects.py diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 3cf1a808..b02c5225 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -14,7 +14,7 @@ class Diffraction_object(): def __init__(self, name='', wavelength=None): self.name = name self.wavelength = wavelength - self.scat_quantities = {} + self.scat_quantity = "" self.on_q = [np.empty(1), np.empty(1)] self.on_tth = [np.empty(1), np.empty(1)] self.on_d = [np.empty(1), np.empty(1)] @@ -401,3 +401,21 @@ def on_xtype(self, xtype): elif xtype.lower() in DQUANTITIES: return self.on_d pass + + def dump(self, filepath, xtype=None): + if xtype is None: + xtype = " q" + if xtype == "q": + data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) + elif xtype == "tth": + data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) + else: + print(f"WARNING: cannot handle the xtype '{xtype}'") + + with open(filepath, 'w') as f: + f.write(f"[Diffraction_object]\nname = {self.name}\nwavelength = {self.wavelength}\n" + f"scat_quantity = {self.scat_quantity}\n") + for key, value in self.metadata.items(): + f.write(f"{key} = {value}\n") + f.write("\n#### start data\n") + np.savetxt(f, data_to_save, delimiter=" ") diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py new file mode 100644 index 00000000..333718e7 --- /dev/null +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -0,0 +1,25 @@ +import numpy as np +from pathlib import Path +from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object + +def test_dump(tmp_path): + x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) + directory = Path(tmp_path) + file = directory / "testfile" + test = Diffraction_object() + test.wavelength = 1.54 + test.name = "test" + test.scat_quantity = "x-ray" + test.insert_scattering_quantity(x, y, "q", metadata={"thing1": 1, "thing2": "thing2"}) + test.dump(file, "q") + with open(file, "r") as f: + actual = f.read() + expected = ("[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" + "thing2 = thing2\n\n#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" + "1.000000000000000000e+00 1.000000000000000000e+00\n2.000000000000000000e+00 2.000000000000000000e+00\n" + "3.000000000000000000e+00 3.000000000000000000e+00\n4.000000000000000000e+00 4.000000000000000000e+00\n" + "5.000000000000000000e+00 5.000000000000000000e+00\n" + "6.000000000000000000e+00 6.000000000000000000e+00\n7.000000000000000000e+00 7.000000000000000000e+00\n" + "8.000000000000000000e+00 8.000000000000000000e+00\n9.000000000000000000e+00 9.000000000000000000e+00\n" + "1.000000000000000000e+01 1.000000000000000000e+01\n") + assert actual == expected From 8ff049d8a8826ad529e43c04b9dd05fe8c2d9929 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 29 Mar 2024 15:10:42 -0400 Subject: [PATCH 075/696] pyproject.toml version number --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 53b7d419..87f8b86a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,8 +40,8 @@ Issues = "https://github.com/diffpy/diffpy.utils/issues" [tool.setuptools-git-versioning] enabled = true template = "{tag}" -dev_template = "{tag}.post{ccount}" -dirty_template = "{tag}.post{ccount}" +dev_template = "{tag}" +dirty_template = "{tag}" [tool.setuptools.packages.find] where = ["src"] # list of folders that contain the packages (["."] by default) From b029999bae15e4b9e3ef3d38c8b131d3de25a0ea Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 29 Mar 2024 15:12:08 -0400 Subject: [PATCH 076/696] news --- news/dump.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/dump.rst diff --git a/news/dump.rst b/news/dump.rst new file mode 100644 index 00000000..48e83182 --- /dev/null +++ b/news/dump.rst @@ -0,0 +1,23 @@ +**Added:** + +* dump method to diffraction_object + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 30baec1b748df0c6f422871c51e8127ab92aa94d Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 29 Mar 2024 16:01:26 -0400 Subject: [PATCH 077/696] update changelog --- CHANGELOG.rst | 12 ++++++++++-- news/dump.rst | 23 ----------------------- 2 files changed, 10 insertions(+), 25 deletions(-) delete mode 100644 news/dump.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bc754ff7..0509544a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,16 @@ Release Notes .. current developments +v3.3.0 +==================== + +**Added:** + +* Diffraction_objects for easier manipulations of diffraction objects +* dump method to Diffraction_object + + + v3.2.7 ==================== @@ -84,5 +94,3 @@ v3.0.0 **Deprecated:** * Variable `__gitsha__` in the `version` module which was renamed to `__git_commit__`. - - diff --git a/news/dump.rst b/news/dump.rst deleted file mode 100644 index 48e83182..00000000 --- a/news/dump.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* dump method to diffraction_object - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From 98d6e695971bee68712090a5cf6ff222f27ee813 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 30 Mar 2024 14:29:21 -0400 Subject: [PATCH 078/696] dont inadvertently overwrite attributes on insert_scattering_quantity --- .../utils/scattering_objects/diffraction_objects.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index b02c5225..4b8793d7 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -19,6 +19,7 @@ def __init__(self, name='', wavelength=None): self.on_tth = [np.empty(1), np.empty(1)] self.on_d = [np.empty(1), np.empty(1)] self._all_arrays = [self.on_q, self.on_tth] + self.metadata = {} def __add__(self, other): summed = deepcopy(self) @@ -221,7 +222,8 @@ def get_angle_index(self, angle): if count >= len(self.angles): raise IndexError(f"WARNING: no angle {angle} found in angles list") - def insert_scattering_quantity(self, xarray, yarray, xtype, metadata={}): + def insert_scattering_quantity(self, xarray, yarray, xtype, metadata={}, + scat_quantity=None, name=None, wavelength=None): f""" insert a new scattering quantity into the scattering object @@ -241,7 +243,12 @@ def insert_scattering_quantity(self, xarray, yarray, xtype, metadata={}): """ self.input_xtype = xtype - self.metadata = metadata + # empty attributes have been defined in the __init__ method so only + # set the attributes that are not empty to avoid emptying them by mistake + if metadata: self.metadata = metadata + if scat_quantity is not None: self.scat_quantity = scat_quantity + if name is not None: self.name = name + if wavelength is not None: self.wavelength = wavelength if xtype.lower() in QQUANTITIES: self.on_q = [np.array(xarray),np.array(yarray)] elif xtype.lower() in ANGLEQUANTITIES: @@ -284,7 +291,7 @@ def q_to_tth(self): q = np.asarray(q) wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) - return np.rad2deg(2 * np.arcsin(q * pre_factor)) + return np.rad2deg(2. * np.arcsin(q * pre_factor)) def tth_to_q(self): r""" From 11852f85e3c90420b02a4a7209a7a9759361b5bc Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 30 Mar 2024 14:33:03 -0400 Subject: [PATCH 079/696] news --- news/tweaks.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/tweaks.rst diff --git a/news/tweaks.rst b/news/tweaks.rst new file mode 100644 index 00000000..d35e97a7 --- /dev/null +++ b/news/tweaks.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* fixed inadvertent overwrite of attributes on self.insert_scattering_quantity() + +**Security:** + +* From bbc94e8dcefa3a611e2b0db7ca469d258984511e Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 10:13:21 -0400 Subject: [PATCH 080/696] added eq functions for diffraction objects --- .../utils/scattering_objects/diffraction_objects.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 4b8793d7..23d8ffa5 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -21,6 +21,17 @@ def __init__(self, name='', wavelength=None): self._all_arrays = [self.on_q, self.on_tth] self.metadata = {} + def __eq__(self, other): + if not isinstance(other, Diffraction_object): + return NotImplemented + return (self.name == other.name and + self.wavelength == other.wavelength and + self.scat_quantity == other.scat_quantity and + np.array_equal(self.on_q, other.on_q) and + np.array_equal(self.on_tth, other.on_tth) and + np.array_equal(self.on_d, other.on_d) and + self.metadata == other.metadata) + def __add__(self, other): summed = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): From 31e8dd39e0408b84c965561c86514a1a9c54b642 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 12:29:40 -0400 Subject: [PATCH 081/696] added functions for getting username and email --- src/diffpy/utils/scattering_objects/tools.py | 43 +++++++++ .../utils/scattering_objects/user_config.py | 34 +++++++ src/diffpy/utils/tests/conftest.py | 17 ++++ src/diffpy/utils/tests/test_tools.py | 96 +++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 src/diffpy/utils/scattering_objects/tools.py create mode 100644 src/diffpy/utils/scattering_objects/user_config.py create mode 100644 src/diffpy/utils/tests/conftest.py create mode 100644 src/diffpy/utils/tests/test_tools.py diff --git a/src/diffpy/utils/scattering_objects/tools.py b/src/diffpy/utils/scattering_objects/tools.py new file mode 100644 index 00000000..c296c90c --- /dev/null +++ b/src/diffpy/utils/scattering_objects/tools.py @@ -0,0 +1,43 @@ +from pathlib import Path + +from diffpy.utils.scattering_objects.user_config import prompt_user_info, read_conf_file, write_conf_file + +def get_user_info(): + """ + Get username and email. + + Prompt the user to enter username and email. + If not provided, read from the config file (first from cwd, and then from home directory). + If neither are available, raise a ValueError. + Save provided values to the config file if a config file doesn't exist. + + Parameters + ---------- + args argparse.Namespace + the arguments from the parser + + Returns + ------- + two strings on username and email + + """ + input_username, input_email = prompt_user_info() + conf_username, conf_email = read_conf_file() + + no_username = not input_username and not conf_username + no_email = not input_email and not conf_email + if no_username and no_email: + raise ValueError("Please rerun the program and provide a username and email.") + if no_username: + raise ValueError("Please rerun the program and provide a username.") + if no_email: + raise ValueError("Please rerun the program and provide an email.") + + username = input_username or conf_username + email = input_email or conf_email + if "@" not in email: + raise ValueError("Please rerun the program and provide a valid email.") + + if not conf_username and not conf_email: + write_conf_file(username, email) + return username, email diff --git a/src/diffpy/utils/scattering_objects/user_config.py b/src/diffpy/utils/scattering_objects/user_config.py new file mode 100644 index 00000000..7c820361 --- /dev/null +++ b/src/diffpy/utils/scattering_objects/user_config.py @@ -0,0 +1,34 @@ +import json +from pathlib import Path + +CONFIG_FILE = "diffpyconfig.json" +CWD_CONFIG_PATH = Path.cwd() / CONFIG_FILE +HOME_CONFIG_PATH = Path.home() / CONFIG_FILE + + +def prompt_user_info(): + username = input("Please enter your username (or press Enter to skip if you already entered before): ") + email = input("Please enter your email (or press Enter to skip if you already entered before): ") + return username, email + + +def find_conf_file(): + if CWD_CONFIG_PATH.exists() and CWD_CONFIG_PATH.is_file(): + return CWD_CONFIG_PATH + elif HOME_CONFIG_PATH.exists() and HOME_CONFIG_PATH.is_file(): + return HOME_CONFIG_PATH + return None + + +def read_conf_file(): + conf_file = find_conf_file() + if conf_file: + with open(conf_file, "r") as f: + config = json.load(f) + return config.get("username"), config.get("email") + return None, None + + +def write_conf_file(username, email): + with open(HOME_CONFIG_PATH, "w") as f: + json.dump({"username": username, "email": email}, f) diff --git a/src/diffpy/utils/tests/conftest.py b/src/diffpy/utils/tests/conftest.py new file mode 100644 index 00000000..6467a277 --- /dev/null +++ b/src/diffpy/utils/tests/conftest.py @@ -0,0 +1,17 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.fixture +def user_filesystem(tmp_path): + base_dir = Path(tmp_path) + conf_dir = base_dir / "conf_dir" + conf_dir.mkdir(parents=True, exist_ok=True) + + user_config_data = {"username": "good_username", "email": "good@email.com"} + with open(conf_dir / "diffpyconfig.json", "w") as f: + json.dump(user_config_data, f) + + yield tmp_path diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py new file mode 100644 index 00000000..63bc5311 --- /dev/null +++ b/src/diffpy/utils/tests/test_tools.py @@ -0,0 +1,96 @@ +import json +import os +import re +from pathlib import Path + +import pytest + +from diffpy.utils.scattering_objects.tools import get_user_info + +params_user_info_without_conf_file = [ + (["new_username", "new@email.com"], ["new_username", "new@email.com", "new_username", "new@email.com"]), +] + + +@pytest.mark.parametrize("inputs, expected", params_user_info_without_conf_file) +def test_load_user_info_without_conf_file(monkeypatch, inputs, expected, user_filesystem): + expected_username, expected_email, expected_conf_username, expected_conf_email = expected + + os.chdir(user_filesystem) + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", user_filesystem / "diffpyconfig.json") + + actual_username, actual_email = get_user_info() + assert actual_username == expected_username + assert actual_email == expected_email + with open(user_filesystem / "diffpyconfig.json", "r") as f: + config_data = json.load(f) + assert config_data == {"username": expected_conf_username, "email": expected_conf_email} + + +params_user_info_with_conf_file = [ + (["", ""], ["good_username", "good@email.com", "good_username", "good@email.com"]), + (["new_username", ""], ["new_username", "good@email.com", "good_username", "good@email.com"]), + (["", "new@email.com"], ["good_username", "new@email.com", "good_username", "good@email.com"]), + (["new_username", "new@email.com"], ["new_username", "new@email.com", "good_username", "good@email.com"]), +] + + +@pytest.mark.parametrize("inputs, expected", params_user_info_with_conf_file) +def test_load_user_info_with_conf_file(monkeypatch, inputs, expected, user_filesystem): + expected_username, expected_email, expected_conf_username, expected_conf_email = expected + user_config_data = {"username": "good_username", "email": "good@email.com"} + with open(user_filesystem / "diffpyconfig.json", "w") as f: + json.dump(user_config_data, f) + + # test it works when config file is in current directory + # check username and email are correctly loaded and config file is not modified + os.chdir(user_filesystem / "conf_dir") + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", user_filesystem / "diffpyconfig.json") + + actual_username, actual_email = get_user_info() + assert actual_username == expected_username + assert actual_email == expected_email + with open(Path.cwd() / "diffpyconfig.json", "r") as f: + config_data = json.load(f) + assert config_data == {"username": expected_conf_username, "email": expected_conf_email} + + # test it works when config file is in home directory and not in current directory new_dir + # check username and email are correctly loaded and config file is not modified + new_dir = user_filesystem / "new_dir" + new_dir.mkdir(parents=True, exist_ok=True) + os.chdir(new_dir) + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + + actual_username, actual_email = get_user_info() + assert actual_username == expected_username + assert actual_email == expected_email + with open(user_filesystem / "diffpyconfig.json", "r") as f: + config_data = json.load(f) + assert config_data == {"username": expected_conf_username, "email": expected_conf_email} + + +params_user_info_bad = [ + # No valid username/email in config file (or no config file), + # and user didn't enter username/email the first time they were asked + (["", ""], "Please rerun the program and provide a username and email."), + (["", "good@email.com"], "Please rerun the program and provide a username."), + (["good_username", ""], "Please rerun the program and provide an email."), + # User entered an invalid email + (["good_username", "bad_email"], "Please rerun the program and provide a valid email."), +] + + +@pytest.mark.parametrize("inputs, msg", params_user_info_bad) +def test_load_user_info_bad(monkeypatch, inputs, msg, user_filesystem): + os.chdir(user_filesystem) + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", user_filesystem / "diffpyconfig.json") + + with pytest.raises(ValueError, match=msg[0]): + actual_username, actual_email = get_user_info() From 213cc7dd23729262888969a472799756664ede4f Mon Sep 17 00:00:00 2001 From: = Date: Wed, 22 May 2024 13:47:55 -0400 Subject: [PATCH 082/696] Deploy on push to main only --- .github/workflows/docs.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9e4962e8..7ab97e0a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,6 +6,10 @@ on: - main release: + pull_request: + branches: + - main + jobs: test: runs-on: ubuntu-latest @@ -37,6 +41,7 @@ jobs: run: make -C doc/manual html - name: Deploy + if: ${github.event_name == 'push' && github.ref == 'refs/heads/main'} uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From 783b3d14682c9ec0a85e57bac5da778db7a5ce7c Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 13:49:22 -0400 Subject: [PATCH 083/696] added tests --- .../utils/tests/test_diffraction_objects.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 333718e7..7b85adfb 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -1,6 +1,46 @@ import numpy as np from pathlib import Path from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object +import pytest + + +params = [ + (["", None, "", [], {}], ["", None, "", [], {}], True), # empty + (["test", 0.71, "", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], + ["test", 0.71, "", [[1,2], [3,4]], {"thing2": "thing2", "thing1": 1}], + True), # Compare same attributes + (["test1", 0.71, "", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], + ["test2", 0.71, "", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], + False), # Different names + (["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + ["test", 1.54, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + False), # Different wavelengths + (["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + ["test", 0.71, "x-ray", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + False), # Different scat_quantity + (["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + ["test", 0.71, "", [[1, 3], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + False), # Different on_q, on_tth, on_d + (["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 0, "thing2": "thing2"}], + ["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + False), # Different metadata +] + +@pytest.mark.parametrize("inputs1, inputs2, expected", params) +def test_diffraction_objects_equality(inputs1, inputs2, expected): + diffraction_object1 = Diffraction_object(name=inputs1[0], wavelength=inputs1[1]) + diffraction_object2 = Diffraction_object(name=inputs2[0], wavelength=inputs2[1]) + diffraction_object1.scat_quantity = inputs1[2] + diffraction_object2.scat_quantity = inputs2[2] + diffraction_object1.on_q = inputs1[3] + diffraction_object2.on_q = inputs2[3] + diffraction_object1.on_tth = inputs1[3] + diffraction_object2.on_tth = inputs2[3] + diffraction_object1.on_d = inputs1[3] + diffraction_object2.on_d = inputs2[3] + diffraction_object1.metadata = inputs1[4] + diffraction_object2.metadata = inputs2[4] + assert (diffraction_object1 == diffraction_object2) == expected def test_dump(tmp_path): x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) From 9eb8288ea9f933a635e6544b9ce6af03eebfae66 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 22 May 2024 13:50:18 -0400 Subject: [PATCH 084/696] Include release --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7ab97e0a..13bb6945 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,7 +41,7 @@ jobs: run: make -C doc/manual html - name: Deploy - if: ${github.event_name == 'push' && github.ref == 'refs/heads/main'} + if: ${(github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'release'} uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From 650a1e910a30a382ae63ac52eef2eb293aa87840 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 22 May 2024 13:57:05 -0400 Subject: [PATCH 085/696] Typo --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 13bb6945..60a19a53 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,7 +41,7 @@ jobs: run: make -C doc/manual html - name: Deploy - if: ${(github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'release'} + if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'release' }} uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From ced32557d302c32ab52d9cda0e41b51c1c8e688e Mon Sep 17 00:00:00 2001 From: Sparky <59151395+Sparks29032@users.noreply.github.com> Date: Wed, 22 May 2024 14:14:29 -0400 Subject: [PATCH 086/696] Update docs.yml --- .github/workflows/docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 60a19a53..b15bbed9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,6 +6,7 @@ on: - main release: + # Documentation built but not deployed for pull requests pull_request: branches: - main @@ -41,6 +42,7 @@ jobs: run: make -C doc/manual html - name: Deploy + # Documentation deployed only on pushes to main and releases if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'release' }} uses: peaceiris/actions-gh-pages@v3 with: From 8608ea0500d7474c1013555e9bc2a1082a755db9 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 14:45:48 -0400 Subject: [PATCH 087/696] updated eq function by iterating over the attributes --- .../scattering_objects/diffraction_objects.py | 21 ++++++++++++------- .../utils/tests/test_diffraction_objects.py | 4 ++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 23d8ffa5..0923baaf 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -24,13 +24,20 @@ def __init__(self, name='', wavelength=None): def __eq__(self, other): if not isinstance(other, Diffraction_object): return NotImplemented - return (self.name == other.name and - self.wavelength == other.wavelength and - self.scat_quantity == other.scat_quantity and - np.array_equal(self.on_q, other.on_q) and - np.array_equal(self.on_tth, other.on_tth) and - np.array_equal(self.on_d, other.on_d) and - self.metadata == other.metadata) + for key, value in self.__dict__.items(): + if key.startswith("_"): + continue + other_value = getattr(other, key) + if isinstance(value, np.ndarray): + if not np.array_equal(value, other_value): + return False + elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): + if not all(np.array_equal(i, j) for i, j in zip(value, other_value)): + return False + else: + if value != other_value: + return False + return True def __add__(self, other): summed = deepcopy(self) diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 7b85adfb..4c070b8b 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -6,8 +6,8 @@ params = [ (["", None, "", [], {}], ["", None, "", [], {}], True), # empty - (["test", 0.71, "", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], - ["test", 0.71, "", [[1,2], [3,4]], {"thing2": "thing2", "thing1": 1}], + (["test", 0.71, "x-ray", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], + ["test", 0.71, "x-ray", [[1,2], [3,4]], {"thing2": "thing2", "thing1": 1}], True), # Compare same attributes (["test1", 0.71, "", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], ["test2", 0.71, "", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], From 67a2a6b1826248da4ac0a4a6a83af57eee3d76dc Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 14:55:51 -0400 Subject: [PATCH 088/696] removed comparing arrays in eq function --- src/diffpy/utils/scattering_objects/diffraction_objects.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 0923baaf..78c97fd4 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -28,10 +28,7 @@ def __eq__(self, other): if key.startswith("_"): continue other_value = getattr(other, key) - if isinstance(value, np.ndarray): - if not np.array_equal(value, other_value): - return False - elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): + if isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): if not all(np.array_equal(i, j) for i, j in zip(value, other_value)): return False else: From 00de84d0fc82c2b407d9234d3c7e458dafb19540 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 15:20:46 -0400 Subject: [PATCH 089/696] added tolerance for floats --- .../utils/scattering_objects/diffraction_objects.py | 10 +++++++--- src/diffpy/utils/tests/test_diffraction_objects.py | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 78c97fd4..7f8ea2a4 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -20,7 +20,7 @@ def __init__(self, name='', wavelength=None): self.on_d = [np.empty(1), np.empty(1)] self._all_arrays = [self.on_q, self.on_tth] self.metadata = {} - +# list of array, dict def __eq__(self, other): if not isinstance(other, Diffraction_object): return NotImplemented @@ -28,8 +28,12 @@ def __eq__(self, other): if key.startswith("_"): continue other_value = getattr(other, key) - if isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): - if not all(np.array_equal(i, j) for i, j in zip(value, other_value)): + if isinstance(value, float): + if not (value is None and other_value is None): + if not np.isclose(value, other_value, rtol=1e-5): + return False + elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): + if not (np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): return False else: if value != other_value: diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 4c070b8b..925025fb 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -41,6 +41,7 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): diffraction_object1.metadata = inputs1[4] diffraction_object2.metadata = inputs2[4] assert (diffraction_object1 == diffraction_object2) == expected + assert Diffraction_object() == Diffraction_object() def test_dump(tmp_path): x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) From dca7985270c658c8f33bdc8afe54e3710b6dd576 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 16:50:30 -0400 Subject: [PATCH 090/696] added more tests for tolerance --- .../scattering_objects/diffraction_objects.py | 23 +++++++++------ .../utils/tests/test_diffraction_objects.py | 28 ++++++++----------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 7f8ea2a4..56e1ab6e 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -20,20 +20,27 @@ def __init__(self, name='', wavelength=None): self.on_d = [np.empty(1), np.empty(1)] self._all_arrays = [self.on_q, self.on_tth] self.metadata = {} -# list of array, dict + def __eq__(self, other): if not isinstance(other, Diffraction_object): return NotImplemented - for key, value in self.__dict__.items(): - if key.startswith("_"): - continue + + attributes_to_test = {"name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"} + self_public_attributes = {key for key, _ in self.__dict__.items() if not key.startswith("_")} + other_public_attributes = {key for key, _ in self.__dict__.items() if not key.startswith("_")} + if (self_public_attributes != other_public_attributes or + attributes_to_test != self_public_attributes): + return False + + for key in attributes_to_test: + value = getattr(self, key) other_value = getattr(other, key) if isinstance(value, float): - if not (value is None and other_value is None): - if not np.isclose(value, other_value, rtol=1e-5): - return False + if (not (value is None and other_value is None) and + (value is None) or (other_value is None) or not np.isclose(value, other_value, rtol=1e-5)): + return False elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): - if not (np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): + if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): return False else: if value != other_value: diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 925025fb..9ca790e4 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -5,24 +5,19 @@ params = [ - (["", None, "", [], {}], ["", None, "", [], {}], True), # empty - (["test", 0.71, "x-ray", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], - ["test", 0.71, "x-ray", [[1,2], [3,4]], {"thing2": "thing2", "thing1": 1}], + (["", None, "", [np.empty(1), np.empty(1)], {}], ["", None, "", [np.empty(1), np.empty(1)], {}], True), # default + (["test", 0.71, "x-ray", [np.array([1,2]), np.array([3,4])], {"thing1": 1, "thing2": "thing2"}], + ["test", 0.71000001, "x-ray", [np.array([1.0000001,2]), np.array([3,4])], {"thing2": "thing2", "thing1": 1}], True), # Compare same attributes - (["test1", 0.71, "", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], - ["test2", 0.71, "", [[1,2], [3,4]], {"thing1": 1, "thing2": "thing2"}], - False), # Different names - (["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], - ["test", 1.54, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], - False), # Different wavelengths - (["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], - ["test", 0.71, "x-ray", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], - False), # Different scat_quantity - (["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], - ["test", 0.71, "", [[1, 3], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + (["test1", None, "", [], {}], ["test2", None, "", [], {}], False), # Different names + (["", 0.71, "", [], {}], ["", 1.54, "", [], {}], False), # Different wavelengths + (["", 0.71, "", [], {}], ["", None, "", [], {}], False), # Different wavelengths + (["", None, "", [], {}], ["", None, "x-ray", [], {}], False), # Different scat_quantity + (["", None, "", [np.array([1,2]), np.array([3,4])], {}], + ["", None, "", [np.array([1.01,2]), np.array([3,4])], {}], False), # Different on_q, on_tth, on_d - (["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 0, "thing2": "thing2"}], - ["test", 0.71, "", [[1, 2], [3, 4]], {"thing1": 1, "thing2": "thing2"}], + (["", None, "", [], {"thing1": 0, "thing2": "thing2"}], + ["", None, "", [], {"thing1": 1, "thing2": "thing2"}], False), # Different metadata ] @@ -41,7 +36,6 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): diffraction_object1.metadata = inputs1[4] diffraction_object2.metadata = inputs2[4] assert (diffraction_object1 == diffraction_object2) == expected - assert Diffraction_object() == Diffraction_object() def test_dump(tmp_path): x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) From 86e3e053f635bd18567f441965d7179019b499bc Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 16:51:56 -0400 Subject: [PATCH 091/696] small adjustments --- src/diffpy/utils/tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 9ca790e4..9e1d300d 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -10,7 +10,7 @@ ["test", 0.71000001, "x-ray", [np.array([1.0000001,2]), np.array([3,4])], {"thing2": "thing2", "thing1": 1}], True), # Compare same attributes (["test1", None, "", [], {}], ["test2", None, "", [], {}], False), # Different names - (["", 0.71, "", [], {}], ["", 1.54, "", [], {}], False), # Different wavelengths + (["", 0.71, "", [], {}], ["", 0.711, "", [], {}], False), # Different wavelengths (["", 0.71, "", [], {}], ["", None, "", [], {}], False), # Different wavelengths (["", None, "", [], {}], ["", None, "x-ray", [], {}], False), # Different scat_quantity (["", None, "", [np.array([1,2]), np.array([3,4])], {}], From a596f902b0f987754b5ecf33d3c474bbbb780e80 Mon Sep 17 00:00:00 2001 From: Sparky <59151395+Sparks29032@users.noreply.github.com> Date: Wed, 22 May 2024 18:10:28 -0400 Subject: [PATCH 092/696] Revert docs.yml --- .github/workflows/docs.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b15bbed9..9e4962e8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,11 +6,6 @@ on: - main release: - # Documentation built but not deployed for pull requests - pull_request: - branches: - - main - jobs: test: runs-on: ubuntu-latest @@ -42,8 +37,6 @@ jobs: run: make -C doc/manual html - name: Deploy - # Documentation deployed only on pushes to main and releases - if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'release' }} uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From 9f36d8b9300ef0b6dd0a6647b64183082fa5ef4a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 22:17:35 -0400 Subject: [PATCH 093/696] added tests for attribute list --- .../scattering_objects/diffraction_objects.py | 6 +- .../utils/tests/test_diffraction_objects.py | 102 +++++++++++++----- 2 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 56e1ab6e..82b5aa4b 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -15,9 +15,9 @@ def __init__(self, name='', wavelength=None): self.name = name self.wavelength = wavelength self.scat_quantity = "" - self.on_q = [np.empty(1), np.empty(1)] - self.on_tth = [np.empty(1), np.empty(1)] - self.on_d = [np.empty(1), np.empty(1)] + self.on_q = [np.empty(0), np.empty(0)] + self.on_tth = [np.empty(0), np.empty(0)] + self.on_d = [np.empty(0), np.empty(0)] self._all_arrays = [self.on_q, self.on_tth] self.metadata = {} diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 9e1d300d..e2d1f4c8 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -1,41 +1,85 @@ -import numpy as np from pathlib import Path -from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object + +import numpy as np import pytest +from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object params = [ - (["", None, "", [np.empty(1), np.empty(1)], {}], ["", None, "", [np.empty(1), np.empty(1)], {}], True), # default - (["test", 0.71, "x-ray", [np.array([1,2]), np.array([3,4])], {"thing1": 1, "thing2": "thing2"}], - ["test", 0.71000001, "x-ray", [np.array([1.0000001,2]), np.array([3,4])], {"thing2": "thing2", "thing1": 1}], - True), # Compare same attributes - (["test1", None, "", [], {}], ["test2", None, "", [], {}], False), # Different names - (["", 0.71, "", [], {}], ["", 0.711, "", [], {}], False), # Different wavelengths - (["", 0.71, "", [], {}], ["", None, "", [], {}], False), # Different wavelengths - (["", None, "", [], {}], ["", None, "x-ray", [], {}], False), # Different scat_quantity - (["", None, "", [np.array([1,2]), np.array([3,4])], {}], - ["", None, "", [np.array([1.01,2]), np.array([3,4])], {}], - False), # Different on_q, on_tth, on_d - (["", None, "", [], {"thing1": 0, "thing2": "thing2"}], - ["", None, "", [], {"thing1": 1, "thing2": "thing2"}], - False), # Different metadata + ( # Default + ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + True, + ), + ( # Compare same attributes + ["test", 0.71, "x-ray", [np.array([1, 2]), np.array([3, 4])], [np.array([1, 2]), np.array([3, 4])], + [np.array([1, 2]), np.array([3, 4])], {"thing1": 1, "thing2": "thing2"}], + ["test", 0.71, "x-ray", [np.array([1, 2]), np.array([3, 4])], [np.array([1, 2]), np.array([3, 4])], + [np.array([1, 2]), np.array([3, 4])], {"thing1": 1, "thing2": "thing2"}], + True, + ), + ( # Different names + ["test1", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + ["test2", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + False, + ), + ( # Different wavelengths + ["", 0.71, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + ["", 0.711, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + False, + ), + ( # Different wavelengths + ["", 0.71, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + False, + ), + ( # Different scat_quantity + ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + ["", None, "x-ray", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + False, + ), + ( # Different on_q + ["", None, "", [np.array([1, 2]), np.array([3, 4])], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + ["", None, "", [np.array([1.01, 2]), np.array([3, 4])], [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], {}], + False, + ), + ( # Different on_tth + ["", None, "", [np.empty(0), np.empty(0)], [np.array([1, 2]), np.array([3, 4])], + [np.empty(0), np.empty(0)], {}], + ["", None, "", [np.empty(0), np.empty(0)], [np.array([1.01, 2]), np.array([3, 4])], + [np.empty(0), np.empty(0)], {}], + False, + ), + ( # Different on_d + ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], + [np.array([1, 2]), np.array([3, 4])], {}], + ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], + [np.array([1.01, 2]), np.array([3, 4])], {}], + False, + ), + ( # Different metadata + ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], + {"thing1": 0, "thing2": "thing2"}], + ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], + {"thing1": 1, "thing2": "thing2"}], + False, + ), ] @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = Diffraction_object(name=inputs1[0], wavelength=inputs1[1]) - diffraction_object2 = Diffraction_object(name=inputs2[0], wavelength=inputs2[1]) - diffraction_object1.scat_quantity = inputs1[2] - diffraction_object2.scat_quantity = inputs2[2] - diffraction_object1.on_q = inputs1[3] - diffraction_object2.on_q = inputs2[3] - diffraction_object1.on_tth = inputs1[3] - diffraction_object2.on_tth = inputs2[3] - diffraction_object1.on_d = inputs1[3] - diffraction_object2.on_d = inputs2[3] - diffraction_object1.metadata = inputs1[4] - diffraction_object2.metadata = inputs2[4] - assert (diffraction_object1 == diffraction_object2) == expected + diffraction_object1 = Diffraction_object() + diffraction_object2 = Diffraction_object() + attributes_to_test = ["name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"] + diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] + diffraction_object2_attributes = [key for key in diffraction_object2.__dict__ if not key.startswith("_")] + for i, attribute in enumerate(attributes_to_test): + setattr(diffraction_object1, attribute, inputs1[i]) + setattr(diffraction_object2, attribute, inputs2[i]) + assert (diffraction_object1 == diffraction_object2) == expected + assert sorted(attributes_to_test) == sorted(diffraction_object1_attributes) + assert sorted(diffraction_object1_attributes) == sorted(diffraction_object2_attributes) def test_dump(tmp_path): x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) From 5f5ecb2b415ae820072a15fc793a1be5e9e8cb28 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 22 May 2024 22:33:21 -0400 Subject: [PATCH 094/696] made attributes_to_test a global parameter --- .../utils/scattering_objects/diffraction_objects.py | 12 +++--------- src/diffpy/utils/tests/test_diffraction_objects.py | 7 +++---- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 82b5aa4b..209e8021 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -9,6 +9,8 @@ DQUANTITIES = ["d", "dspace"] XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees","radians","rad","deg","inv_angs","inv_nm","nm-1","A-1"] +ATTRIBUTES_TO_TEST = ["name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"] + class Diffraction_object(): def __init__(self, name='', wavelength=None): @@ -24,15 +26,7 @@ def __init__(self, name='', wavelength=None): def __eq__(self, other): if not isinstance(other, Diffraction_object): return NotImplemented - - attributes_to_test = {"name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"} - self_public_attributes = {key for key, _ in self.__dict__.items() if not key.startswith("_")} - other_public_attributes = {key for key, _ in self.__dict__.items() if not key.startswith("_")} - if (self_public_attributes != other_public_attributes or - attributes_to_test != self_public_attributes): - return False - - for key in attributes_to_test: + for key in ATTRIBUTES_TO_TEST: value = getattr(self, key) other_value = getattr(other, key) if isinstance(value, float): diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index e2d1f4c8..479de2ca 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object +from diffpy.utils.scattering_objects.diffraction_objects import ATTRIBUTES_TO_TEST, Diffraction_object params = [ ( # Default @@ -71,14 +71,13 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): diffraction_object1 = Diffraction_object() diffraction_object2 = Diffraction_object() - attributes_to_test = ["name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"] diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] diffraction_object2_attributes = [key for key in diffraction_object2.__dict__ if not key.startswith("_")] - for i, attribute in enumerate(attributes_to_test): + for i, attribute in enumerate(ATTRIBUTES_TO_TEST): setattr(diffraction_object1, attribute, inputs1[i]) setattr(diffraction_object2, attribute, inputs2[i]) assert (diffraction_object1 == diffraction_object2) == expected - assert sorted(attributes_to_test) == sorted(diffraction_object1_attributes) + assert sorted(ATTRIBUTES_TO_TEST) == sorted(diffraction_object1_attributes) assert sorted(diffraction_object1_attributes) == sorted(diffraction_object2_attributes) def test_dump(tmp_path): From 80f6e6b697eca8aea6e0e7d78801dbd5766f5c9c Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 23 May 2024 10:08:37 -0400 Subject: [PATCH 095/696] moved attribute list within test function and adjusted tests to check tolerance --- .../scattering_objects/diffraction_objects.py | 6 +++--- .../utils/tests/test_diffraction_objects.py | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 209e8021..30e52fc2 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -9,7 +9,6 @@ DQUANTITIES = ["d", "dspace"] XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees","radians","rad","deg","inv_angs","inv_nm","nm-1","A-1"] -ATTRIBUTES_TO_TEST = ["name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"] class Diffraction_object(): @@ -26,8 +25,9 @@ def __init__(self, name='', wavelength=None): def __eq__(self, other): if not isinstance(other, Diffraction_object): return NotImplemented - for key in ATTRIBUTES_TO_TEST: - value = getattr(self, key) + for key, value in self.__dict__.items(): + if key.startswith("_"): + continue other_value = getattr(other, key) if isinstance(value, float): if (not (value is None and other_value is None) and diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 479de2ca..57a9e837 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from diffpy.utils.scattering_objects.diffraction_objects import ATTRIBUTES_TO_TEST, Diffraction_object +from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object params = [ ( # Default @@ -14,8 +14,9 @@ ( # Compare same attributes ["test", 0.71, "x-ray", [np.array([1, 2]), np.array([3, 4])], [np.array([1, 2]), np.array([3, 4])], [np.array([1, 2]), np.array([3, 4])], {"thing1": 1, "thing2": "thing2"}], - ["test", 0.71, "x-ray", [np.array([1, 2]), np.array([3, 4])], [np.array([1, 2]), np.array([3, 4])], - [np.array([1, 2]), np.array([3, 4])], {"thing1": 1, "thing2": "thing2"}], + ["test", 0.7100001, "x-ray", [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], + [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], + [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], {"thing1": 1, "thing2": "thing2"}], True, ), ( # Different names @@ -39,7 +40,8 @@ False, ), ( # Different on_q - ["", None, "", [np.array([1, 2]), np.array([3, 4])], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + ["", None, "", [np.array([1, 2]), np.array([3, 4])], [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], {}], ["", None, "", [np.array([1.01, 2]), np.array([3, 4])], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], False, @@ -71,13 +73,14 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): diffraction_object1 = Diffraction_object() diffraction_object2 = Diffraction_object() + attributes_to_test = ["name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"] diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] diffraction_object2_attributes = [key for key in diffraction_object2.__dict__ if not key.startswith("_")] - for i, attribute in enumerate(ATTRIBUTES_TO_TEST): + for i, attribute in enumerate(attributes_to_test): setattr(diffraction_object1, attribute, inputs1[i]) setattr(diffraction_object2, attribute, inputs2[i]) assert (diffraction_object1 == diffraction_object2) == expected - assert sorted(ATTRIBUTES_TO_TEST) == sorted(diffraction_object1_attributes) + assert sorted(attributes_to_test) == sorted(diffraction_object1_attributes) assert sorted(diffraction_object1_attributes) == sorted(diffraction_object2_attributes) def test_dump(tmp_path): From 6bd0fa2b6f874126ffdf82363bd0c2a5f2ba91d6 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 23 May 2024 11:00:12 -0400 Subject: [PATCH 096/696] added comparison of DO attribute lists in eq function --- .../utils/scattering_objects/diffraction_objects.py | 9 ++++++--- src/diffpy/utils/tests/test_diffraction_objects.py | 2 -- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 30e52fc2..5031d6d6 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -25,9 +25,12 @@ def __init__(self, name='', wavelength=None): def __eq__(self, other): if not isinstance(other, Diffraction_object): return NotImplemented - for key, value in self.__dict__.items(): - if key.startswith("_"): - continue + self_attributes = [key for key in self.__dict__ if not key.startswith("_")] + other_attributes = [key for key in other.__dict__ if not key.startswith("_")] + if not sorted(self_attributes) == sorted(other_attributes): + return False + for key in self_attributes: + value = getattr(self, key) other_value = getattr(other, key) if isinstance(value, float): if (not (value is None and other_value is None) and diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 57a9e837..294417b7 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -75,13 +75,11 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): diffraction_object2 = Diffraction_object() attributes_to_test = ["name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"] diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] - diffraction_object2_attributes = [key for key in diffraction_object2.__dict__ if not key.startswith("_")] for i, attribute in enumerate(attributes_to_test): setattr(diffraction_object1, attribute, inputs1[i]) setattr(diffraction_object2, attribute, inputs2[i]) assert (diffraction_object1 == diffraction_object2) == expected assert sorted(attributes_to_test) == sorted(diffraction_object1_attributes) - assert sorted(diffraction_object1_attributes) == sorted(diffraction_object2_attributes) def test_dump(tmp_path): x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) From 43660e705802e7a94efc76ad8a56509cc199e769 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 23 May 2024 12:12:04 -0400 Subject: [PATCH 097/696] changed attribute list to generate dynamically from the dict --- src/diffpy/utils/tests/test_diffraction_objects.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 294417b7..bb450982 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -73,13 +73,11 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): diffraction_object1 = Diffraction_object() diffraction_object2 = Diffraction_object() - attributes_to_test = ["name", "wavelength", "scat_quantity", "on_q", "on_tth", "on_d", "metadata"] diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] - for i, attribute in enumerate(attributes_to_test): + for i, attribute in enumerate(diffraction_object1_attributes): setattr(diffraction_object1, attribute, inputs1[i]) setattr(diffraction_object2, attribute, inputs2[i]) assert (diffraction_object1 == diffraction_object2) == expected - assert sorted(attributes_to_test) == sorted(diffraction_object1_attributes) def test_dump(tmp_path): x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) From 196924bff54d81d3d9ce06e5acdaf90c9d76f543 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 23 May 2024 14:38:22 -0400 Subject: [PATCH 098/696] added helper test function --- src/diffpy/utils/tests/conftest.py | 17 +++- src/diffpy/utils/tests/test_tools.py | 134 ++++++++++++--------------- 2 files changed, 72 insertions(+), 79 deletions(-) diff --git a/src/diffpy/utils/tests/conftest.py b/src/diffpy/utils/tests/conftest.py index 6467a277..c5045169 100644 --- a/src/diffpy/utils/tests/conftest.py +++ b/src/diffpy/utils/tests/conftest.py @@ -7,11 +7,18 @@ @pytest.fixture def user_filesystem(tmp_path): base_dir = Path(tmp_path) - conf_dir = base_dir / "conf_dir" - conf_dir.mkdir(parents=True, exist_ok=True) + home_dir = base_dir / "home_dir" + home_dir.mkdir(parents=True, exist_ok=True) + cwd_dir = base_dir / "cwd_dir" + cwd_dir.mkdir(parents=True, exist_ok=True) + new_dir = base_dir / "new_dir" + new_dir.mkdir(parents=True, exist_ok=True) - user_config_data = {"username": "good_username", "email": "good@email.com"} - with open(conf_dir / "diffpyconfig.json", "w") as f: - json.dump(user_config_data, f) + home_config_data = {"username": "home_username", "email": "home@email.com"} + cwd_config_data = {"username": "cwd_username", "email": "cwd@email.com"} + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump(home_config_data, f) + with open(cwd_dir / "diffpyconfig.json", "w") as f: + json.dump(cwd_config_data, f) yield tmp_path diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 63bc5311..9e620c57 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -7,90 +7,76 @@ from diffpy.utils.scattering_objects.tools import get_user_info -params_user_info_without_conf_file = [ - (["new_username", "new@email.com"], ["new_username", "new@email.com", "new_username", "new@email.com"]), -] - - -@pytest.mark.parametrize("inputs, expected", params_user_info_without_conf_file) -def test_load_user_info_without_conf_file(monkeypatch, inputs, expected, user_filesystem): - expected_username, expected_email, expected_conf_username, expected_conf_email = expected - - os.chdir(user_filesystem) +# Helper test function +def _test_user_info(monkeypatch, cwd, inputs, expected, user_filesystem, no_conf_file=False, expect_error=False, error_msg=None): + # Determine paths + cwd_path = user_filesystem / cwd + home_dir = user_filesystem / "home_dir" + cwd_config_path = cwd_path / "diffpyconfig.json" + home_config_path = home_dir / "diffpyconfig.json" + + # Set monkeypatch attributes input_user = iter(inputs) monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", user_filesystem / "diffpyconfig.json") - - actual_username, actual_email = get_user_info() - assert actual_username == expected_username - assert actual_email == expected_email - with open(user_filesystem / "diffpyconfig.json", "r") as f: - config_data = json.load(f) - assert config_data == {"username": expected_conf_username, "email": expected_conf_email} + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_config_path) + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", home_config_path) + + # Handle no configuration file + if no_conf_file: + os.remove(home_dir / "diffpyconfig.json") + # Change directory to specified cwd + os.chdir(user_filesystem / cwd) + + # Perform the test + if expect_error: + with pytest.raises(ValueError, match=error_msg): + get_user_info() + else: + actual_username, actual_email = get_user_info() + expected_username, expected_email, expected_conf_folder, expected_conf_username, expected_conf_email = expected + assert actual_username == expected_username + assert actual_email == expected_email + expected_conf_path = (user_filesystem / expected_conf_folder) / "diffpyconfig.json" + with open(expected_conf_path, "r") as f: + config_data = json.load(f) + assert config_data == {"username": expected_conf_username, "email": expected_conf_email} params_user_info_with_conf_file = [ - (["", ""], ["good_username", "good@email.com", "good_username", "good@email.com"]), - (["new_username", ""], ["new_username", "good@email.com", "good_username", "good@email.com"]), - (["", "new@email.com"], ["good_username", "new@email.com", "good_username", "good@email.com"]), - (["new_username", "new@email.com"], ["new_username", "new@email.com", "good_username", "good@email.com"]), + # No cwd config file, only home config file + ("new_dir", ["", ""], ["home_username", "home@email.com", "home_dir", "home_username", "home@email.com"]), + ("new_dir", ["input_username", ""], ["input_username", "home@email.com", "home_dir", "home_username", "home@email.com"]), + ("new_dir", ["", "input@email.com"], ["home_username", "input@email.com", "home_dir", "home_username", "home@email.com"]), + ("new_dir", ["input_username", "input@email.com"], + ["input_username", "input@email.com", "home_dir", "home_username", "home@email.com"]), + + # There exists valid cwd config file + ("cwd_dir", ["", ""], ["cwd_username", "cwd@email.com", "cwd_dir", "cwd_username", "cwd@email.com"]), + ("cwd_dir", ["input_username", ""], ["input_username", "cwd@email.com", "cwd_dir", "cwd_username", "cwd@email.com"]), + ("cwd_dir", ["", "input@email.com"], ["cwd_username", "input@email.com", "cwd_dir", "cwd_username", "cwd@email.com"]), + ("cwd_dir", ["input_username", "input@email.com"], + ["input_username", "input@email.com", "cwd_dir", "cwd_username", "cwd@email.com"]), ] - -@pytest.mark.parametrize("inputs, expected", params_user_info_with_conf_file) -def test_load_user_info_with_conf_file(monkeypatch, inputs, expected, user_filesystem): - expected_username, expected_email, expected_conf_username, expected_conf_email = expected - user_config_data = {"username": "good_username", "email": "good@email.com"} - with open(user_filesystem / "diffpyconfig.json", "w") as f: - json.dump(user_config_data, f) - - # test it works when config file is in current directory - # check username and email are correctly loaded and config file is not modified - os.chdir(user_filesystem / "conf_dir") - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", user_filesystem / "diffpyconfig.json") - - actual_username, actual_email = get_user_info() - assert actual_username == expected_username - assert actual_email == expected_email - with open(Path.cwd() / "diffpyconfig.json", "r") as f: - config_data = json.load(f) - assert config_data == {"username": expected_conf_username, "email": expected_conf_email} - - # test it works when config file is in home directory and not in current directory new_dir - # check username and email are correctly loaded and config file is not modified - new_dir = user_filesystem / "new_dir" - new_dir.mkdir(parents=True, exist_ok=True) - os.chdir(new_dir) - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - - actual_username, actual_email = get_user_info() - assert actual_username == expected_username - assert actual_email == expected_email - with open(user_filesystem / "diffpyconfig.json", "r") as f: - config_data = json.load(f) - assert config_data == {"username": expected_conf_username, "email": expected_conf_email} - +params_user_info_without_conf_file = [ + ("new_dir", ["input_username", "input@email.com"], ["input_username", "input@email.com", "home_dir", "input_username", "input@email.com"]), +] params_user_info_bad = [ - # No valid username/email in config file (or no config file), - # and user didn't enter username/email the first time they were asked - (["", ""], "Please rerun the program and provide a username and email."), - (["", "good@email.com"], "Please rerun the program and provide a username."), - (["good_username", ""], "Please rerun the program and provide an email."), - # User entered an invalid email - (["good_username", "bad_email"], "Please rerun the program and provide a valid email."), + ("new_dir", ["", ""], "Please rerun the program and provide a username and email."), + ("new_dir", ["", "input@email.com"], "Please rerun the program and provide a username."), + ("new_dir", ["input_username", ""], "Please rerun the program and provide an email."), + ("new_dir", ["input_username", "bad_email"], "Please rerun the program and provide a valid email."), ] +@pytest.mark.parametrize("cwd, inputs, expected", params_user_info_with_conf_file) +def test_load_user_info_with_conf_file(monkeypatch, cwd, inputs, expected, user_filesystem): + _test_user_info(monkeypatch, cwd, inputs, expected, user_filesystem) -@pytest.mark.parametrize("inputs, msg", params_user_info_bad) -def test_load_user_info_bad(monkeypatch, inputs, msg, user_filesystem): - os.chdir(user_filesystem) - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", user_filesystem / "diffpyconfig.json") +@pytest.mark.parametrize("cwd, inputs, expected", params_user_info_without_conf_file) +def test_load_user_info_without_conf_file(monkeypatch, cwd, inputs, expected, user_filesystem): + _test_user_info(monkeypatch, cwd, inputs, expected, user_filesystem, no_conf_file=True) - with pytest.raises(ValueError, match=msg[0]): - actual_username, actual_email = get_user_info() +@pytest.mark.parametrize("cwd, inputs, msg", params_user_info_bad) +def test_load_user_info_bad(monkeypatch, cwd, inputs, msg, user_filesystem): + _test_user_info(monkeypatch, cwd, inputs, None, user_filesystem, no_conf_file=True, expect_error=True, error_msg=msg) From c3ebf94e166b7bb2879bef1235bd5033683d5bf1 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 23 May 2024 17:58:42 -0400 Subject: [PATCH 099/696] reformatted tests and added error messages for invalid conf files --- .../utils/scattering_objects/user_config.py | 2 + src/diffpy/utils/tests/conftest.py | 5 - src/diffpy/utils/tests/test_tools.py | 144 +++++++++++------- 3 files changed, 88 insertions(+), 63 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/user_config.py b/src/diffpy/utils/scattering_objects/user_config.py index 7c820361..e1c4df10 100644 --- a/src/diffpy/utils/scattering_objects/user_config.py +++ b/src/diffpy/utils/scattering_objects/user_config.py @@ -25,6 +25,8 @@ def read_conf_file(): if conf_file: with open(conf_file, "r") as f: config = json.load(f) + if not config.get("username") or not config.get("email"): + raise ValueError("Please provide a configuration file with username and email.") return config.get("username"), config.get("email") return None, None diff --git a/src/diffpy/utils/tests/conftest.py b/src/diffpy/utils/tests/conftest.py index c5045169..e3b63139 100644 --- a/src/diffpy/utils/tests/conftest.py +++ b/src/diffpy/utils/tests/conftest.py @@ -11,14 +11,9 @@ def user_filesystem(tmp_path): home_dir.mkdir(parents=True, exist_ok=True) cwd_dir = base_dir / "cwd_dir" cwd_dir.mkdir(parents=True, exist_ok=True) - new_dir = base_dir / "new_dir" - new_dir.mkdir(parents=True, exist_ok=True) home_config_data = {"username": "home_username", "email": "home@email.com"} - cwd_config_data = {"username": "cwd_username", "email": "cwd@email.com"} with open(home_dir / "diffpyconfig.json", "w") as f: json.dump(home_config_data, f) - with open(cwd_dir / "diffpyconfig.json", "w") as f: - json.dump(cwd_config_data, f) yield tmp_path diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 9e620c57..dbadbf3e 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -7,76 +7,104 @@ from diffpy.utils.scattering_objects.tools import get_user_info -# Helper test function -def _test_user_info(monkeypatch, cwd, inputs, expected, user_filesystem, no_conf_file=False, expect_error=False, error_msg=None): - # Determine paths - cwd_path = user_filesystem / cwd + +params_user_info_with_conf_file = [ + (["", ""], ["home_username", "home@email.com", "cwd_username", "cwd@email.com"]), + (["input_username", ""], ["input_username", "home@email.com", "input_username", "cwd@email.com"]), + (["", "input@email.com"], ["home_username", "input@email.com", "cwd_username", "input@email.com"]), + (["input_username", "input@email.com"], ["input_username", "input@email.com", "input_username", "input@email.com"]), +] +@pytest.mark.parametrize("inputs, expected", params_user_info_with_conf_file) +def test_load_user_info_with_conf_file(monkeypatch, inputs, expected, user_filesystem): + expected_username_home, expected_email_home, expected_username_cwd, expected_email_cwd = expected home_dir = user_filesystem / "home_dir" - cwd_config_path = cwd_path / "diffpyconfig.json" - home_config_path = home_dir / "diffpyconfig.json" + cwd_dir = user_filesystem / "cwd_dir" - # Set monkeypatch attributes + # conf file is in home directory, not in cwd input_user = iter(inputs) monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_config_path) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", home_config_path) - - # Handle no configuration file - if no_conf_file: - os.remove(home_dir / "diffpyconfig.json") - # Change directory to specified cwd - os.chdir(user_filesystem / cwd) - - # Perform the test - if expect_error: - with pytest.raises(ValueError, match=error_msg): - get_user_info() - else: - actual_username, actual_email = get_user_info() - expected_username, expected_email, expected_conf_folder, expected_conf_username, expected_conf_email = expected - assert actual_username == expected_username - assert actual_email == expected_email - - expected_conf_path = (user_filesystem / expected_conf_folder) / "diffpyconfig.json" - with open(expected_conf_path, "r") as f: - config_data = json.load(f) - assert config_data == {"username": expected_conf_username, "email": expected_conf_email} + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_dir / "diffpyconfig.json") + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", home_dir / "diffpyconfig.json") + actual_username, actual_email = get_user_info() + assert actual_username == expected_username_home + assert actual_email == expected_email_home -params_user_info_with_conf_file = [ - # No cwd config file, only home config file - ("new_dir", ["", ""], ["home_username", "home@email.com", "home_dir", "home_username", "home@email.com"]), - ("new_dir", ["input_username", ""], ["input_username", "home@email.com", "home_dir", "home_username", "home@email.com"]), - ("new_dir", ["", "input@email.com"], ["home_username", "input@email.com", "home_dir", "home_username", "home@email.com"]), - ("new_dir", ["input_username", "input@email.com"], - ["input_username", "input@email.com", "home_dir", "home_username", "home@email.com"]), + # conf file is in cwd + cwd_username, cwd_email = "cwd_username", "cwd@email.com" + with open(cwd_dir / "diffpyconfig.json", "w") as f: + json.dump({"username": cwd_username, "email": cwd_email}, f) + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + actual_username, actual_email = get_user_info() + assert actual_username == expected_username_cwd + assert actual_email == expected_email_cwd - # There exists valid cwd config file - ("cwd_dir", ["", ""], ["cwd_username", "cwd@email.com", "cwd_dir", "cwd_username", "cwd@email.com"]), - ("cwd_dir", ["input_username", ""], ["input_username", "cwd@email.com", "cwd_dir", "cwd_username", "cwd@email.com"]), - ("cwd_dir", ["", "input@email.com"], ["cwd_username", "input@email.com", "cwd_dir", "cwd_username", "cwd@email.com"]), - ("cwd_dir", ["input_username", "input@email.com"], - ["input_username", "input@email.com", "cwd_dir", "cwd_username", "cwd@email.com"]), -] params_user_info_without_conf_file = [ - ("new_dir", ["input_username", "input@email.com"], ["input_username", "input@email.com", "home_dir", "input_username", "input@email.com"]), + (["new_username", "new@email.com"], ["new_username", "new@email.com", "new_username", "new@email.com"]), ] +@pytest.mark.parametrize("inputs, expected", params_user_info_without_conf_file) +def test_load_user_info_without_conf_file(monkeypatch, inputs, expected, user_filesystem): + expected_username, expected_email, expected_conf_username, expected_conf_email = expected + home_dir = user_filesystem / "home_dir" + cwd_dir = user_filesystem / "cwd_dir" + os.remove(home_dir / "diffpyconfig.json") + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_dir / "diffpyconfig.json") + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", user_filesystem / "diffpyconfig.json") + + actual_username, actual_email = get_user_info() + assert actual_username == expected_username + assert actual_email == expected_email + with open(user_filesystem / "diffpyconfig.json", "r") as f: + config_data = json.load(f) + assert config_data == {"username": expected_conf_username, "email": expected_conf_email} + params_user_info_bad = [ - ("new_dir", ["", ""], "Please rerun the program and provide a username and email."), - ("new_dir", ["", "input@email.com"], "Please rerun the program and provide a username."), - ("new_dir", ["input_username", ""], "Please rerun the program and provide an email."), - ("new_dir", ["input_username", "bad_email"], "Please rerun the program and provide a valid email."), + # Invalid inputs + (["input_username", "bad_email"], "Please rerun the program and provide a valid email."), + # Skipped inputs when there is no valid conf file + (["", ""], "Please rerun the program and provide a username and email."), + (["", "input@email.com"], "Please rerun the program and provide a username."), + (["input_username", ""], "Please rerun the program and provide an email."), ] +@pytest.mark.parametrize("inputs, msg", params_user_info_bad) +def test_load_user_info_bad(monkeypatch, inputs, msg, user_filesystem): + home_dir = user_filesystem / "home_dir" + cwd_dir = user_filesystem / "cwd_dir" + os.remove(home_dir / "diffpyconfig.json") + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_dir / "diffpyconfig.json") + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", home_dir / "diffpyconfig.json") + with pytest.raises(ValueError, match=msg[0]): + get_user_info() -@pytest.mark.parametrize("cwd, inputs, expected", params_user_info_with_conf_file) -def test_load_user_info_with_conf_file(monkeypatch, cwd, inputs, expected, user_filesystem): - _test_user_info(monkeypatch, cwd, inputs, expected, user_filesystem) -@pytest.mark.parametrize("cwd, inputs, expected", params_user_info_without_conf_file) -def test_load_user_info_without_conf_file(monkeypatch, cwd, inputs, expected, user_filesystem): - _test_user_info(monkeypatch, cwd, inputs, expected, user_filesystem, no_conf_file=True) +params_user_info_invalid_conf_files = [ + # Invalid conf files + # Test first invalid home conf file, then invalid cwd conf file + (["input_username", "input@email.com"], "Please provide a configuration file with username and email."), +] +@pytest.mark.parametrize("inputs, msg", params_user_info_invalid_conf_files) +def test_load_user_info_invalid_conf_files(monkeypatch, inputs, msg, user_filesystem): + home_dir = user_filesystem / "home_dir" + cwd_dir = user_filesystem / "cwd_dir" + os.remove(home_dir / "diffpyconfig.json") + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump({}, f) + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_dir / "diffpyconfig.json") + monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", home_dir / "diffpyconfig.json") + with pytest.raises(ValueError, match=msg[0]): + get_user_info() -@pytest.mark.parametrize("cwd, inputs, msg", params_user_info_bad) -def test_load_user_info_bad(monkeypatch, cwd, inputs, msg, user_filesystem): - _test_user_info(monkeypatch, cwd, inputs, None, user_filesystem, no_conf_file=True, expect_error=True, error_msg=msg) + with open(cwd_dir / "diffpyconfig.json", "w") as f: + json.dump({}, f) + input_user = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _: next(input_user)) + with pytest.raises(ValueError, match=msg[0]): + get_user_info() From 339596224b40e72ad6104b2349091d0917c9d28a Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 24 May 2024 08:17:30 -0400 Subject: [PATCH 100/696] global config file present passing tests --- requirements/test.txt | 1 + src/diffpy/utils/scattering_objects/tools.py | 43 ---------- .../utils/scattering_objects/user_config.py | 5 +- src/diffpy/utils/tests/test_tools.py | 78 ++++++++++++------- src/diffpy/utils/tools.py | 73 +++++++++++++++++ 5 files changed, 123 insertions(+), 77 deletions(-) delete mode 100644 src/diffpy/utils/scattering_objects/tools.py create mode 100644 src/diffpy/utils/tools.py diff --git a/requirements/test.txt b/requirements/test.txt index 6f9ccf84..5d6faa33 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,3 +1,4 @@ +argparse flake8 pytest codecov diff --git a/src/diffpy/utils/scattering_objects/tools.py b/src/diffpy/utils/scattering_objects/tools.py deleted file mode 100644 index c296c90c..00000000 --- a/src/diffpy/utils/scattering_objects/tools.py +++ /dev/null @@ -1,43 +0,0 @@ -from pathlib import Path - -from diffpy.utils.scattering_objects.user_config import prompt_user_info, read_conf_file, write_conf_file - -def get_user_info(): - """ - Get username and email. - - Prompt the user to enter username and email. - If not provided, read from the config file (first from cwd, and then from home directory). - If neither are available, raise a ValueError. - Save provided values to the config file if a config file doesn't exist. - - Parameters - ---------- - args argparse.Namespace - the arguments from the parser - - Returns - ------- - two strings on username and email - - """ - input_username, input_email = prompt_user_info() - conf_username, conf_email = read_conf_file() - - no_username = not input_username and not conf_username - no_email = not input_email and not conf_email - if no_username and no_email: - raise ValueError("Please rerun the program and provide a username and email.") - if no_username: - raise ValueError("Please rerun the program and provide a username.") - if no_email: - raise ValueError("Please rerun the program and provide an email.") - - username = input_username or conf_username - email = input_email or conf_email - if "@" not in email: - raise ValueError("Please rerun the program and provide a valid email.") - - if not conf_username and not conf_email: - write_conf_file(username, email) - return username, email diff --git a/src/diffpy/utils/scattering_objects/user_config.py b/src/diffpy/utils/scattering_objects/user_config.py index e1c4df10..0db84a1f 100644 --- a/src/diffpy/utils/scattering_objects/user_config.py +++ b/src/diffpy/utils/scattering_objects/user_config.py @@ -6,10 +6,7 @@ HOME_CONFIG_PATH = Path.home() / CONFIG_FILE -def prompt_user_info(): - username = input("Please enter your username (or press Enter to skip if you already entered before): ") - email = input("Please enter your email (or press Enter to skip if you already entered before): ") - return username, email + def find_conf_file(): diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index dbadbf3e..ea5833ac 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -1,43 +1,61 @@ +import argparse import json import os -import re from pathlib import Path import pytest -from diffpy.utils.scattering_objects.tools import get_user_info +from diffpy.utils.tools import get_user_info -params_user_info_with_conf_file = [ - (["", ""], ["home_username", "home@email.com", "cwd_username", "cwd@email.com"]), - (["input_username", ""], ["input_username", "home@email.com", "input_username", "cwd@email.com"]), - (["", "input@email.com"], ["home_username", "input@email.com", "cwd_username", "input@email.com"]), - (["input_username", "input@email.com"], ["input_username", "input@email.com", "input_username", "input@email.com"]), +params_user_info_with_home_conf_file = [ + (["", ""], ["home_username", "home@email.com"]), + (["cli_username", ""], ["cli_username", "home@email.com"]), + (["", "cli@email.com"], ["home_username", "cli@email.com"]), + ([None, None], ["home_username", "home@email.com"]), + (["cli_username", None], ["cli_username", "home@email.com"]), + ([None, "cli@email.com"], ["home_username", "cli@email.com"]), + (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), ] -@pytest.mark.parametrize("inputs, expected", params_user_info_with_conf_file) -def test_load_user_info_with_conf_file(monkeypatch, inputs, expected, user_filesystem): - expected_username_home, expected_email_home, expected_username_cwd, expected_email_cwd = expected - home_dir = user_filesystem / "home_dir" - cwd_dir = user_filesystem / "cwd_dir" - - # conf file is in home directory, not in cwd - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_dir / "diffpyconfig.json") - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", home_dir / "diffpyconfig.json") - actual_username, actual_email = get_user_info() - assert actual_username == expected_username_home - assert actual_email == expected_email_home +params_user_info_with_cwd_conf_file = [ + (["", ""], ["cwd_username", "cwd@email.com"]), + (["cli_username", ""], ["cli_username", "cwd@email.com"]), + (["", "cli@email.com"], ["cwd_username", "cli@email.com"]), + (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), +] +params_user_info_with_no_home_conf_file = [ + ([None, None], ["input_username", "input@email.com", "input_username", "input@email.com"]), + (["cli_username", None], ["input_username", "input@email.com"]), + ([None, "cli@email.com"], ["input_username", "input@email.com"]), + (["", ""], ["input_username", "input@email.com", "input_username", "input@email.com"]), + (["cli_username", ""], ["input_username", "input@email.com"]), + (["", "cli@email.com"], ["input_username", "input@email.com"]), + (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), +] +@pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) +def test_load_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): + cwd = Path(user_filesystem) + home_dir = cwd / "home_dir" + monkeypatch.setattr("pathlib.Path.home", lambda _: home_dir) + os.chdir(cwd) - # conf file is in cwd - cwd_username, cwd_email = "cwd_username", "cwd@email.com" - with open(cwd_dir / "diffpyconfig.json", "w") as f: - json.dump({"username": cwd_username, "email": cwd_email}, f) - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - actual_username, actual_email = get_user_info() - assert actual_username == expected_username_cwd - assert actual_email == expected_email_cwd + # allow inputs from a cli contained as username and email in an argparse Namespace + args = {"username": inputs[0], "email": inputs[1]} + expected_username, expected_email = expected + config = get_user_info(args) + assert config.get("username") == expected_username + assert config.get("email") == expected_email +# +# def sthgelse(): +# # conf file is in cwd +# cwd_username, cwd_email = "cwd_username", "cwd@email.com" +# with open(cwd_dir / "diffpyconfig.json", "w") as f: +# json.dump({"username": cwd_username, "email": cwd_email}, f) +# input_user = iter(inputs) +# monkeypatch.setattr("builtins.input", lambda _: next(input_user)) +# actual_username, actual_email = get_user_info() +# assert actual_username == expected_username_cwd +# assert actual_email == expected_email_cwd params_user_info_without_conf_file = [ diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py new file mode 100644 index 00000000..7fb99a98 --- /dev/null +++ b/src/diffpy/utils/tools.py @@ -0,0 +1,73 @@ +import json +from copy import copy +from pathlib import Path + +def clean_dict(obj): + obj = obj if obj is not None else {} + for key, value in copy(obj).items(): + if not value: + del obj[key] + return obj + +def load_config(file_path): + config_file = Path(file_path).resolve() + if config_file.is_file(): + with open(config_file, "r") as f: + config = json.load(f) + return config + else: + return None + +def _sorted_merge(*dicts): + merged = {} + for d in dicts: + merged.update(d) + return merged + +def get_user_info(args=None): + """ + Get username and email. + + Prompt the user to enter username and email. + If not provided, read from the config file (first from cwd, and then from home directory). + If neither are available, raise a ValueError. + Save provided values to the config file if a config file doesn't exist. + + Parameters + ---------- + args argparse.Namespace + the arguments from the parser + + Returns + ------- + two strings on username and email + + """ + global_config = load_config(Path().home() / "diffpyconfig.json" ) + local_config = load_config(Path().cwd() / "diffpyconfig.json" ) + + config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) + + return config + # # trigger create global config flow only we don't find any config file + # if global_config is None and local_config is None: + # _create_global_config(args) + # conf_username, conf_email = read_conf_file() + # + # no_username = not input_username and not conf_username + # no_email = not input_email and not conf_email + # if no_username and no_email: + # raise ValueError("Please rerun the program and provide a username and email.") + # if no_username: + # raise ValueError("Please rerun the program and provide a username.") + # if no_email: + # raise ValueError("Please rerun the program and provide an email.") + # + # username = input_username or conf_username + # email = input_email or conf_email + # if "@" not in email: + # raise ValueError("Please rerun the program and provide a valid email.") + # + # if not conf_username and not conf_email: + # write_conf_file(username, email) + # return username, email From 4530a2a59e001a478f1721410e9f390519c58dfa Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 24 May 2024 08:33:45 -0400 Subject: [PATCH 101/696] local config file present passing tests --- src/diffpy/utils/tests/test_tools.py | 48 +++++++++++++++++++++------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index ea5833ac..432823bd 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -17,34 +17,60 @@ ([None, "cli@email.com"], ["home_username", "cli@email.com"]), (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), ] -params_user_info_with_cwd_conf_file = [ +params_user_info_with_local_conf_file = [ (["", ""], ["cwd_username", "cwd@email.com"]), (["cli_username", ""], ["cli_username", "cwd@email.com"]), (["", "cli@email.com"], ["cwd_username", "cli@email.com"]), + ([None, None], ["cwd_username", "cwd@email.com"]), + (["cli_username", None], ["cli_username", "cwd@email.com"]), + ([None, "cli@email.com"], ["cwd_username", "cli@email.com"]), (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), ] params_user_info_with_no_home_conf_file = [ ([None, None], ["input_username", "input@email.com", "input_username", "input@email.com"]), - (["cli_username", None], ["input_username", "input@email.com"]), - ([None, "cli@email.com"], ["input_username", "input@email.com"]), - (["", ""], ["input_username", "input@email.com", "input_username", "input@email.com"]), - (["cli_username", ""], ["input_username", "input@email.com"]), - (["", "cli@email.com"], ["input_username", "input@email.com"]), - (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), + (["cli_username", None], ["input_username", "input@email.com", "cli_username", "input@email.com"]), + ([None, "cli@email.com"], ["input_username", "input@email.com", "input_username", "cli@email.com"]), + ([None, None], ["input_username", "input@email.com", "input_username", "input@email.com"]), + (["cli_username", None], ["input_username", "input@email.com", "cli_username", "input@email.com"]), + ([None, "cli@email.com"], ["input_username", "input@email.com", "input_username", "cli@email.com"]), + (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com", "cli_username", "cli@email.com"]), ] -@pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) -def test_load_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): +def _setup_dirs(monkeypatch,user_filesystem): cwd = Path(user_filesystem) home_dir = cwd / "home_dir" monkeypatch.setattr("pathlib.Path.home", lambda _: home_dir) os.chdir(cwd) - - # allow inputs from a cli contained as username and email in an argparse Namespace + return home_dir +def _run_tests(inputs, expected): args = {"username": inputs[0], "email": inputs[1]} expected_username, expected_email = expected config = get_user_info(args) assert config.get("username") == expected_username assert config.get("email") == expected_email + +@pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) +def test_load_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): + _setup_dirs(monkeypatch, user_filesystem) + _run_tests(inputs, expected) + +@pytest.mark.parametrize("inputs, expected", params_user_info_with_local_conf_file) +def test_load_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): + home_dir = _setup_dirs(monkeypatch, user_filesystem) + local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} + with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: + json.dump(local_config_data, f) + _run_tests(inputs, expected) + os.remove(Path().home() / "diffpyconfig.json") + _run_tests(inputs, expected) + +@pytest.mark.parametrize("inputs, expected", params_user_info_with_no_home_conf_file) +def test_load_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): + home_dir = _setup_dirs(monkeypatch, user_filesystem) + local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} + with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: + json.dump(local_config_data, f) + _run_tests(inputs, expected) + # # def sthgelse(): # # conf file is in cwd From cacac4ddb3537e9a3cdb54879885c4f44be1dc22 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 24 May 2024 13:32:07 -0400 Subject: [PATCH 102/696] empty flow working and no config working --- src/diffpy/utils/tests/test_tools.py | 148 ++++++++------------------- src/diffpy/utils/tools.py | 47 ++++----- 2 files changed, 64 insertions(+), 131 deletions(-) diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 432823bd..830b9ca4 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -7,6 +7,19 @@ from diffpy.utils.tools import get_user_info +def _setup_dirs(monkeypatch,user_filesystem): + cwd = Path(user_filesystem) + home_dir = cwd / "home_dir" + monkeypatch.setattr("pathlib.Path.home", lambda _: home_dir) + os.chdir(cwd) + return home_dir +def _run_tests(inputs, expected): + args = {"username": inputs[0], "email": inputs[1]} + expected_username, expected_email = expected + config = get_user_info(args) + assert config.get("username") == expected_username + assert config.get("email") == expected_email + params_user_info_with_home_conf_file = [ (["", ""], ["home_username", "home@email.com"]), @@ -27,26 +40,18 @@ (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), ] params_user_info_with_no_home_conf_file = [ - ([None, None], ["input_username", "input@email.com", "input_username", "input@email.com"]), - (["cli_username", None], ["input_username", "input@email.com", "cli_username", "input@email.com"]), - ([None, "cli@email.com"], ["input_username", "input@email.com", "input_username", "cli@email.com"]), - ([None, None], ["input_username", "input@email.com", "input_username", "input@email.com"]), - (["cli_username", None], ["input_username", "input@email.com", "cli_username", "input@email.com"]), - ([None, "cli@email.com"], ["input_username", "input@email.com", "input_username", "cli@email.com"]), - (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com", "cli_username", "cli@email.com"]), + ([None, None], ["input_username", "input@email.com"], ["input_username", "input@email.com"]), + (["cli_username", None], ["", "input@email.com"], ["cli_username", "input@email.com"]), + ([None, "cli@email.com"], ["input_username", ""], ["input_username", "cli@email.com"]), + (["", ""], ["input_username", "input@email.com"], ["input_username", "input@email.com"]), + (["cli_username", ""], ["", "input@email.com"], ["cli_username", "input@email.com"]), + (["", "cli@email.com"], ["input_username", ""], ["input_username", "cli@email.com"]), + (["cli_busername", "cli@email.com"], ["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), ] -def _setup_dirs(monkeypatch,user_filesystem): - cwd = Path(user_filesystem) - home_dir = cwd / "home_dir" - monkeypatch.setattr("pathlib.Path.home", lambda _: home_dir) - os.chdir(cwd) - return home_dir -def _run_tests(inputs, expected): - args = {"username": inputs[0], "email": inputs[1]} - expected_username, expected_email = expected - config = get_user_info(args) - assert config.get("username") == expected_username - assert config.get("email") == expected_email +params_user_info_no_conf_file_no_inputs = [ + ([None, None], ["", ""], ["", ""]), +] + @pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) def test_load_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): @@ -63,92 +68,23 @@ def test_load_user_info_with_local_conf_file(monkeypatch, inputs, expected, user os.remove(Path().home() / "diffpyconfig.json") _run_tests(inputs, expected) -@pytest.mark.parametrize("inputs, expected", params_user_info_with_no_home_conf_file) -def test_load_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): - home_dir = _setup_dirs(monkeypatch, user_filesystem) - local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} - with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: - json.dump(local_config_data, f) - _run_tests(inputs, expected) - -# -# def sthgelse(): -# # conf file is in cwd -# cwd_username, cwd_email = "cwd_username", "cwd@email.com" -# with open(cwd_dir / "diffpyconfig.json", "w") as f: -# json.dump({"username": cwd_username, "email": cwd_email}, f) -# input_user = iter(inputs) -# monkeypatch.setattr("builtins.input", lambda _: next(input_user)) -# actual_username, actual_email = get_user_info() -# assert actual_username == expected_username_cwd -# assert actual_email == expected_email_cwd - - -params_user_info_without_conf_file = [ - (["new_username", "new@email.com"], ["new_username", "new@email.com", "new_username", "new@email.com"]), -] -@pytest.mark.parametrize("inputs, expected", params_user_info_without_conf_file) -def test_load_user_info_without_conf_file(monkeypatch, inputs, expected, user_filesystem): - expected_username, expected_email, expected_conf_username, expected_conf_email = expected - home_dir = user_filesystem / "home_dir" - cwd_dir = user_filesystem / "cwd_dir" - os.remove(home_dir / "diffpyconfig.json") - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_dir / "diffpyconfig.json") - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", user_filesystem / "diffpyconfig.json") - - actual_username, actual_email = get_user_info() - assert actual_username == expected_username - assert actual_email == expected_email - with open(user_filesystem / "diffpyconfig.json", "r") as f: - config_data = json.load(f) - assert config_data == {"username": expected_conf_username, "email": expected_conf_email} - - -params_user_info_bad = [ - # Invalid inputs - (["input_username", "bad_email"], "Please rerun the program and provide a valid email."), - # Skipped inputs when there is no valid conf file - (["", ""], "Please rerun the program and provide a username and email."), - (["", "input@email.com"], "Please rerun the program and provide a username."), - (["input_username", ""], "Please rerun the program and provide an email."), -] -@pytest.mark.parametrize("inputs, msg", params_user_info_bad) -def test_load_user_info_bad(monkeypatch, inputs, msg, user_filesystem): - home_dir = user_filesystem / "home_dir" - cwd_dir = user_filesystem / "cwd_dir" - os.remove(home_dir / "diffpyconfig.json") - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_dir / "diffpyconfig.json") - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", home_dir / "diffpyconfig.json") - with pytest.raises(ValueError, match=msg[0]): - get_user_info() - +@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) +def test_load_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): + _setup_dirs(monkeypatch, user_filesystem) + os.remove(Path().home() / "diffpyconfig.json") + inp_iter = iter(inputsb) + monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) + _run_tests(inputsa, expected) + confile = Path().home() / "diffpyconfig.json" + assert confile.is_file() -params_user_info_invalid_conf_files = [ - # Invalid conf files - # Test first invalid home conf file, then invalid cwd conf file - (["input_username", "input@email.com"], "Please provide a configuration file with username and email."), -] -@pytest.mark.parametrize("inputs, msg", params_user_info_invalid_conf_files) -def test_load_user_info_invalid_conf_files(monkeypatch, inputs, msg, user_filesystem): - home_dir = user_filesystem / "home_dir" - cwd_dir = user_filesystem / "cwd_dir" - os.remove(home_dir / "diffpyconfig.json") - with open(home_dir / "diffpyconfig.json", "w") as f: - json.dump({}, f) - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.CWD_CONFIG_PATH", cwd_dir / "diffpyconfig.json") - monkeypatch.setattr("diffpy.utils.scattering_objects.user_config.HOME_CONFIG_PATH", home_dir / "diffpyconfig.json") - with pytest.raises(ValueError, match=msg[0]): - get_user_info() - with open(cwd_dir / "diffpyconfig.json", "w") as f: - json.dump({}, f) - input_user = iter(inputs) - monkeypatch.setattr("builtins.input", lambda _: next(input_user)) - with pytest.raises(ValueError, match=msg[0]): - get_user_info() +@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) +def test_load_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): + _setup_dirs(monkeypatch, user_filesystem) + os.remove(Path().home() / "diffpyconfig.json") + inp_iter = iter(inputsb) + monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) + _run_tests(inputsa, expected) + confile = Path().home() / "diffpyconfig.json" + assert confile.exists() is False diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 7fb99a98..167ce161 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,4 +1,5 @@ import json +import os from copy import copy from pathlib import Path @@ -9,6 +10,9 @@ def clean_dict(obj): del obj[key] return obj +def stringify(obj): + return obj if obj is not None else "" + def load_config(file_path): config_file = Path(file_path).resolve() if config_file.is_file(): @@ -24,6 +28,16 @@ def _sorted_merge(*dicts): merged.update(d) return merged +def _create_global_config(args): + username = input(f"Please enter the name of the user to put in the diffpy global config file " + f"[{args.get("username", "")}]: ").strip() or args.get("username", "") + email = input(f"Please enter the email of the user to put in the diffpy global config file " + f"[{args.get("email", "")}]: ").strip() or args.get("email", "") + return_bool = False if username is None or email is None else True + with open(Path().home() / "diffpyconfig.json", "w") as f: + f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) + return return_bool + def get_user_info(args=None): """ Get username and email. @@ -43,31 +57,14 @@ def get_user_info(args=None): two strings on username and email """ - global_config = load_config(Path().home() / "diffpyconfig.json" ) - local_config = load_config(Path().cwd() / "diffpyconfig.json" ) - + global_config = load_config(Path().home() / "diffpyconfig.json") + local_config = load_config(Path().cwd() / "diffpyconfig.json") + if global_config is None and local_config is None: + config_bool = _create_global_config(args) + global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) + if config_bool is False: + os.remove(Path().home() / "diffpyconfig.json") + config = {"username": "", "email": ""} return config - # # trigger create global config flow only we don't find any config file - # if global_config is None and local_config is None: - # _create_global_config(args) - # conf_username, conf_email = read_conf_file() - # - # no_username = not input_username and not conf_username - # no_email = not input_email and not conf_email - # if no_username and no_email: - # raise ValueError("Please rerun the program and provide a username and email.") - # if no_username: - # raise ValueError("Please rerun the program and provide a username.") - # if no_email: - # raise ValueError("Please rerun the program and provide an email.") - # - # username = input_username or conf_username - # email = input_email or conf_email - # if "@" not in email: - # raise ValueError("Please rerun the program and provide a valid email.") - # - # if not conf_username and not conf_email: - # write_conf_file(username, email) - # return username, email From 5617af0ca251a02307c40edaba3f2360a844b1d5 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 24 May 2024 13:42:42 -0400 Subject: [PATCH 103/696] news --- news/users.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 news/users.rst diff --git a/news/users.rst b/news/users.rst new file mode 100644 index 00000000..09b12e8f --- /dev/null +++ b/news/users.rst @@ -0,0 +1,24 @@ +**Added:** + +* utility for handling the capture of username and email for diffpy applications +* __eq__ method into Diffraction_object so we can equation two instances of a diffraction object + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 87d88101849c153f2165d242254c443e152f4b34 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 24 May 2024 14:14:13 -0400 Subject: [PATCH 104/696] remove argparse from requirements and fix config_bool not found bug --- requirements/test.txt | 1 - src/diffpy/utils/tests/test_tools.py | 2 +- src/diffpy/utils/tools.py | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/test.txt b/requirements/test.txt index 5d6faa33..6f9ccf84 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,4 +1,3 @@ -argparse flake8 pytest codecov diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 830b9ca4..dea8ae1a 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -46,7 +46,7 @@ def _run_tests(inputs, expected): (["", ""], ["input_username", "input@email.com"], ["input_username", "input@email.com"]), (["cli_username", ""], ["", "input@email.com"], ["cli_username", "input@email.com"]), (["", "cli@email.com"], ["input_username", ""], ["input_username", "cli@email.com"]), - (["cli_busername", "cli@email.com"], ["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), + (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), ] params_user_info_no_conf_file_no_inputs = [ ([None, None], ["", ""], ["", ""]), diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 167ce161..344055bb 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -57,6 +57,7 @@ def get_user_info(args=None): two strings on username and email """ + config_bool = True global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: From 97a3cfe9d4f44eda9273daf7d9f51226b32f7c59 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 24 May 2024 18:52:28 -0400 Subject: [PATCH 105/696] added docstrings --- src/diffpy/utils/tests/test_tools.py | 10 ++--- src/diffpy/utils/tools.py | 56 +++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index dea8ae1a..7c82feb2 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -46,7 +46,7 @@ def _run_tests(inputs, expected): (["", ""], ["input_username", "input@email.com"], ["input_username", "input@email.com"]), (["cli_username", ""], ["", "input@email.com"], ["cli_username", "input@email.com"]), (["", "cli@email.com"], ["input_username", ""], ["input_username", "cli@email.com"]), - (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), + (["cli_username", "cli@email.com"], ["input_username", "input@email.com"], ["cli_username", "cli@email.com"]), ] params_user_info_no_conf_file_no_inputs = [ ([None, None], ["", ""], ["", ""]), @@ -54,12 +54,12 @@ def _run_tests(inputs, expected): @pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) -def test_load_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): +def test_get_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): _setup_dirs(monkeypatch, user_filesystem) _run_tests(inputs, expected) @pytest.mark.parametrize("inputs, expected", params_user_info_with_local_conf_file) -def test_load_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): +def test_get_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): home_dir = _setup_dirs(monkeypatch, user_filesystem) local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: @@ -69,7 +69,7 @@ def test_load_user_info_with_local_conf_file(monkeypatch, inputs, expected, user _run_tests(inputs, expected) @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) -def test_load_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): +def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): _setup_dirs(monkeypatch, user_filesystem) os.remove(Path().home() / "diffpyconfig.json") inp_iter = iter(inputsb) @@ -80,7 +80,7 @@ def test_load_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, ex @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) -def test_load_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): +def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): _setup_dirs(monkeypatch, user_filesystem) os.remove(Path().home() / "diffpyconfig.json") inp_iter = iter(inputsb) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 344055bb..654f9b21 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -4,6 +4,19 @@ from pathlib import Path def clean_dict(obj): + """ + remove keys from the dictionary where the corresponding value is None + + Parameters + ---------- + obj: dict + the dictionary to clean. If None, initialize as an empty dictionary + + Returns + ------- + the cleaned dictionary with keys removed where the value is None + + """ obj = obj if obj is not None else {} for key, value in copy(obj).items(): if not value: @@ -11,9 +24,34 @@ def clean_dict(obj): return obj def stringify(obj): + """ + convert None to an empty string + + Parameters + ---------- + obj: str + the object to convert. If None, return an empty string + + Returns + ------- + the converted string if obj is not None, otherwise an empty string + """ return obj if obj is not None else "" def load_config(file_path): + """ + load configuration from a json file + + Parameters + ---------- + file_path: Path + the path to the configuration file + + Returns + ------- + the configuration dictionary or None if file does not exist + + """ config_file = Path(file_path).resolve() if config_file.is_file(): with open(config_file, "r") as f: @@ -30,9 +68,9 @@ def _sorted_merge(*dicts): def _create_global_config(args): username = input(f"Please enter the name of the user to put in the diffpy global config file " - f"[{args.get("username", "")}]: ").strip() or args.get("username", "") + f"[{args.get('username', '')}]: ").strip() or args.get("username", "") email = input(f"Please enter the email of the user to put in the diffpy global config file " - f"[{args.get("email", "")}]: ").strip() or args.get("email", "") + f"[{args.get('email', '')}]: ").strip() or args.get("email", "") return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) @@ -40,21 +78,21 @@ def _create_global_config(args): def get_user_info(args=None): """ - Get username and email. + get username and email configuration - Prompt the user to enter username and email. - If not provided, read from the config file (first from cwd, and then from home directory). - If neither are available, raise a ValueError. - Save provided values to the config file if a config file doesn't exist. + First attempts to load config file from global and local paths. + If neither exists, creates a global config file. + It prioritizes values from args, then local, then global. + Removes invalid global config file if creation is needed, replacing it with empty username and email. Parameters ---------- args argparse.Namespace - the arguments from the parser + the arguments from the parser, default is None Returns ------- - two strings on username and email + the dictionary containing username and email with corresponding values """ config_bool = True From a282afa85720e179eecc0e4290c6126ce380d294 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 28 May 2024 15:25:15 -0400 Subject: [PATCH 106/696] initial commit to add docs for tools --- doc/manual/source/examples/toolsexample.rst | 51 ++++++++++++++++++++ doc/manual/source/utilities/toolsutility.rst | 16 ++++++ 2 files changed, 67 insertions(+) create mode 100644 doc/manual/source/examples/toolsexample.rst create mode 100644 doc/manual/source/utilities/toolsutility.rst diff --git a/doc/manual/source/examples/toolsexample.rst b/doc/manual/source/examples/toolsexample.rst new file mode 100644 index 00000000..fbb5a9c4 --- /dev/null +++ b/doc/manual/source/examples/toolsexample.rst @@ -0,0 +1,51 @@ +.. _Tools Example: + +:tocdepth: 2 + +Tools Example +############# + +This example will demonstrate how diffpy.utils allows us to load and manage username and email information. +Using the tools module, we can efficiently handle username and email configurations stored in ``diffpyconfig.json``. + +1) To begin, we may create a configuration file ``diffpyconfig.json`` and add username and email to it. :: + + with ("diffpyconfig.json", "w") as f: + json.dump({"username": "example_username", "email": "example@email.com"}, f) + +2) To read the configuration file, we can use the ``load_config`` function, + which reads a json file and returns its content as a dictionary. :: + + from pathlib import Path + from diffpy.utils.tools import load_config + global_config = load_config(Path().home() / "diffpyconfig.json") + local_config = load_config(Path().cwd() / "diffpyconfig.json") + + If the configuration file does not exist, this function will return ``None``. + +3) To clean the configuration dictionary by removing keys with ``None`` values, + we can use the ``clean_dict`` function. :: + + from diffpy.utils.tools import clean_dict + cleaned_global_config = clean_dict(global_config) + cleaned_local_config = clean_dict(local_config) + +4) We have the function ``get_user_info`` that integrates all functionalities mentioned above, + neatly returning a dictionary containing the username and email. :: + + from diffpy.utils.tools import get_user_info + user_info = get_user_info() + + This function first searches for a configuration file in the current working directory and then the home directory. + If no configuration files exist, it prompts for user input and creates a configuration file in the home directory. + You can also override existing values by passing a dictionary to the function. :: + + new_args = {"username": "new_username", "email": "new@example.com"} + new_user_info = get_user_info(new_args) + + Here, the function returns a dictionary containing the new args. + Parameters provided to the function override any existing values, + and values from the current directory file override those in the home directory file. + + +By using this function, we ensure that user information is correctly loaded, merged, and saved. diff --git a/doc/manual/source/utilities/toolsutility.rst b/doc/manual/source/utilities/toolsutility.rst new file mode 100644 index 00000000..c031bd8f --- /dev/null +++ b/doc/manual/source/utilities/toolsutility.rst @@ -0,0 +1,16 @@ +.. _Tools Utility: + +:tocdepth: 2 + +Tools Utility +############# + +The ``diffpy.utils.tools`` module is designed for users to manage and track their username and email information. +This module helps by storing this information in a ``diffpyconfig.json`` file generated by the function. + +Users can use the module to simplify the process of loading, merging, and saving information consistently and easily. +Additionally, it saves the effort of re-entering information, and allows overriding current information by +passing parameters. + +Users can apply the function ``get_user_info`` for this purpose. +Other helper functions, such as ``load_config``, ``clean_dict``, are also available. From f07ef306cd3fe9a07e952b8a203b5f335d5e60cd Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 28 May 2024 22:25:14 -0400 Subject: [PATCH 107/696] black --- src/diffpy/__init__.py | 1 + src/diffpy/utils/parsers/custom_exceptions.py | 1 + src/diffpy/utils/parsers/loaddata.py | 63 +++-- src/diffpy/utils/parsers/resample.py | 25 +- src/diffpy/utils/parsers/serialization.py | 56 ++-- .../scattering_objects/diffraction_objects.py | 208 +++++++++----- .../utils/scattering_objects/user_config.py | 7 +- src/diffpy/utils/tests/__init__.py | 19 +- src/diffpy/utils/tests/debug.py | 5 +- src/diffpy/utils/tests/run.py | 5 +- .../utils/tests/test_diffraction_objects.py | 261 ++++++++++++++---- src/diffpy/utils/tests/test_loaddata.py | 30 +- src/diffpy/utils/tests/test_resample.py | 9 +- src/diffpy/utils/tests/test_serialization.py | 99 ++++--- src/diffpy/utils/tests/test_tools.py | 73 ++++- src/diffpy/utils/tests/testhelpers.py | 3 + src/diffpy/utils/tools.py | 26 +- src/diffpy/utils/version.py | 26 +- src/diffpy/utils/wx/gridutils.py | 29 +- 19 files changed, 656 insertions(+), 290 deletions(-) diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 53bafe1a..f37a7759 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -20,6 +20,7 @@ from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) diff --git a/src/diffpy/utils/parsers/custom_exceptions.py b/src/diffpy/utils/parsers/custom_exceptions.py index 16b77f59..ffb6aca6 100644 --- a/src/diffpy/utils/parsers/custom_exceptions.py +++ b/src/diffpy/utils/parsers/custom_exceptions.py @@ -13,6 +13,7 @@ # ############################################################################## + class UnsupportedTypeError(Exception): """For file types not supported by our parsers. diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 0bb261bc..f659db7c 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -16,7 +16,7 @@ import numpy -def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwargs): +def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs): """Find and load data from a text file. The data block is identified as the first matrix block of at least minrows rows and constant number of columns. @@ -67,11 +67,12 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar If headers are enabled, return a dictionary of parameters read from the header. """ from numpy import array, loadtxt + # for storing header data hdata = {} # determine the arguments - delimiter = kwargs.get('delimiter') - usecols = kwargs.get('usecols') + delimiter = kwargs.get("delimiter") + usecols = kwargs.get("usecols") # required at least one column of floating point values mincv = (1, 1) # but if usecols is specified, require sufficient number of columns @@ -79,6 +80,7 @@ def loadData(filename, minrows=10, headers=False, hdel='=', hignore=None, **kwar if usecols is not None: hiidx = max(-min(usecols), max(usecols) + 1) mincv = (hiidx, len(set(usecols))) + # Check if a line consists of floats only and return their count # Return zero if some strings cannot be converted. def countcolumnsvalues(line): @@ -91,12 +93,13 @@ def countcolumnsvalues(line): if usecols is not None: nv = len([float(words[i]) for i in usecols]) else: - nv = len([ float(w) for w in words]) + nv = len([float(w) for w in words]) except (IndexError, ValueError): nc = nv = 0 return nc, nv + # make sure fid gets cleaned up - with open(filename, 'rb') as fid: + with open(filename, "rb") as fid: # search for the start of datablock start = ncvblock = None fpos = (0, 0) @@ -162,7 +165,7 @@ def countcolumnsvalues(line): fid.seek(start) # always use usecols argument so that loadtxt does not crash # in case of trailing delimiters. - kwargs.setdefault('usecols', list(range(ncvblock[0]))) + kwargs.setdefault("usecols", list(range(ncvblock[0]))) data_block = loadtxt(fid, **kwargs) return data_block @@ -192,17 +195,15 @@ def __init__(self, minrows=10, usecols=None, skiprows=None): self._reset() return - def _reset(self): - self.filename = '' + self.filename = "" self.headers = [] self.datasets = [] self._resetvars() return - def _resetvars(self): - self._filename = '' + self._filename = "" self._lines = None self._splitlines = None self._words = None @@ -210,17 +211,15 @@ def _resetvars(self): self._wordrecs = None return - def read(self, filename): """Open a file and run readfp. Use if file is not already open for read byte. """ - with open(filename, 'rb') as fp: + with open(filename, "rb") as fp: self.readfp(fp) return - def readfp(self, fp, append=False): """Get file details. @@ -233,13 +232,12 @@ def readfp(self, fp, append=False): # try to read lines from fp first self._lines = fp.readlines() # and if good, assign filename - self.filename = getattr(fp, 'name', '') - self._words = ''.join(self._lines).split() + self.filename = getattr(fp, "name", "") + self._words = "".join(self._lines).split() self._splitlines = [line.split() for line in self._lines] self._findDataBlocks() return - def _findDataBlocks(self): mincols = 1 if self.usecols is not None and len(self.usecols): @@ -249,8 +247,10 @@ def _findDataBlocks(self): nwords = len(self._words) # idx - line index, nw0, nw1 - index of the first and last word, # nf - number of words, ok - has data - self._linerecs = numpy.recarray((nlines,), dtype=[('idx', int), - ('nw0', int), ('nw1', int), ('nf', int), ('ok', bool)]) + self._linerecs = numpy.recarray( + (nlines,), + dtype=[("idx", int), ("nw0", int), ("nw1", int), ("nf", int), ("ok", bool)], + ) lr = self._linerecs lr.idx = numpy.arange(nlines) lr.nf = [len(sl) for sl in self._splitlines] @@ -258,8 +258,16 @@ def _findDataBlocks(self): lr.nw0 = lr.nw1 - lr.nf lr.ok = True # word records - lw = self._wordrecs = numpy.recarray((nwords,), dtype=[('idx', int), - ('line', int), ('col', int), ('ok', bool), ('value', float)]) + lw = self._wordrecs = numpy.recarray( + (nwords,), + dtype=[ + ("idx", int), + ("line", int), + ("col", int), + ("ok", bool), + ("value", float), + ], + ) lw.idx = numpy.arange(nwords) n1 = numpy.zeros(nwords, dtype=bool) n1[lr.nw1[:-1]] = True @@ -290,17 +298,17 @@ def _findDataBlocks(self): end = numpy.nonzero(oke | blocke)[0] rowcounts = end - beg assert not numpy.any(rowcounts < 0) - goodrows = (rowcounts >= self.minrows) + goodrows = rowcounts >= self.minrows begend = numpy.transpose([beg, end - 1])[goodrows] hbeg = 0 for dbeg, dend in begend: bb1 = lr1[dbeg] ee1 = lr1[dend] hend = bb1.idx - header = ''.join(self._lines[hbeg:hend]) + header = "".join(self._lines[hbeg:hend]) hbeg = ee1.idx + 1 if self.usecols is None: - data = numpy.reshape(lw.value[bb1.nw0:ee1.nw1], (-1, bb1.nf)) + data = numpy.reshape(lw.value[bb1.nw0 : ee1.nw1], (-1, bb1.nf)) else: tdata = numpy.empty((len(self.usecols), dend - dbeg), dtype=float) for j, trow in zip(self.usecols, tdata): @@ -311,7 +319,7 @@ def _findDataBlocks(self): self.datasets.append(data) # finish reading to a last header and empty dataset if hbeg < len(self._lines): - header = ''.join(self._lines[hbeg:]) + header = "".join(self._lines[hbeg:]) data = numpy.empty(0, dtype=float) self.headers.append(header) self.datasets.append(data) @@ -320,9 +328,9 @@ def _findDataBlocks(self): # End of class TextDataLoader + def isfloat(s): - '''True if s is convertible to float. - ''' + """True if s is convertible to float.""" try: float(s) return True @@ -330,4 +338,5 @@ def isfloat(s): pass return False -# End of file \ No newline at end of file + +# End of file diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index e41219f4..e67c448d 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -100,30 +100,30 @@ def resample(r, s, dr): dr0 = r[1] - r[0] # Constant timestep if dr0 < dr: - rnew = numpy.arange(r[0], r[-1]+0.5*dr, dr) + rnew = numpy.arange(r[0], r[-1] + 0.5 * dr, dr) snew = numpy.interp(rnew, r, s) return rnew, snew elif dr0 > dr: # Tried to pad the end of s to dampen, but nothing works. - #m = (s[-1] - s[-2]) / dr0 - #b = (s[-2] * r[-1] - s[-1] * r[-2]) / dr0 - #rpad = r[-1] + numpy.arange(1, len(s))*dr0 - #spad = rpad * m + b - #spad = numpy.concatenate([s,spad]) - #rnew = numpy.arange(0, rpad[-1], dr) - #snew = numpy.zeros_like(rnew) + # m = (s[-1] - s[-2]) / dr0 + # b = (s[-2] * r[-1] - s[-1] * r[-2]) / dr0 + # rpad = r[-1] + numpy.arange(1, len(s))*dr0 + # spad = rpad * m + b + # spad = numpy.concatenate([s,spad]) + # rnew = numpy.arange(0, rpad[-1], dr) + # snew = numpy.zeros_like(rnew) ## Accomodate for the fact that r[0] might not be 0 - #u = (rnew-r[0]) / dr0 - #for n in range(len(spad)): + # u = (rnew-r[0]) / dr0 + # for n in range(len(spad)): # snew += spad[n] * numpy.sinc(u - n) - #sel = numpy.logical_and(rnew >= r[0], rnew <= r[-1]) + # sel = numpy.logical_and(rnew >= r[0], rnew <= r[-1]) rnew = numpy.arange(0, r[-1], dr) snew = numpy.zeros_like(rnew) - u = (rnew-r[0]) / dr0 + u = (rnew - r[0]) / dr0 for n in range(len(s)): snew += s[n] * numpy.sinc(u - n) sel = numpy.logical_and(rnew >= r[0], rnew <= r[-1]) @@ -132,4 +132,5 @@ def resample(r, s, dr): # If we got here, then no resampling is required return r.copy(), s.copy() + # End of file diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 55dc4d9c..ebd85c63 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -21,10 +21,17 @@ import warnings # FIXME: add support for yaml, xml -supported_formats = ['.json'] +supported_formats = [".json"] -def serialize_data(filename, hdata: dict, data_table, dt_colnames=None, show_path=True, serial_file=None): +def serialize_data( + filename, + hdata: dict, + data_table, + dt_colnames=None, + show_path=True, + serial_file=None, +): """Serialize file data into a dictionary. Can also save dictionary into a serial language file. Dictionary is formatted as {filename: data}. @@ -61,8 +68,8 @@ def serialize_data(filename, hdata: dict, data_table, dt_colnames=None, show_pat # handle getting name of file for variety of filename types abs_path = pathlib.Path(filename).resolve() # add path to start of data if requested - if show_path and 'path' not in hdata.keys(): - data.update({'path': abs_path.as_posix()}) + if show_path and "path" not in hdata.keys(): + data.update({"path": abs_path.as_posix()}) # title the entry with name of file (taken from end of path) title = abs_path.name @@ -77,24 +84,32 @@ def serialize_data(filename, hdata: dict, data_table, dt_colnames=None, show_pat num_columns = [len(row) for row in data_table] max_columns = max(num_columns) num_col_names = len(dt_colnames) - if max_columns < num_col_names: # assume numpy.loadtxt gives non-irregular array - raise ImproperSizeError("More entries in dt_colnames than columns in data_table.") + if ( + max_columns < num_col_names + ): # assume numpy.loadtxt gives non-irregular array + raise ImproperSizeError( + "More entries in dt_colnames than columns in data_table." + ) named_columns = 0 for idx in range(num_col_names): colname = dt_colnames[idx] if colname is not None: if colname in hdata.keys(): - warnings.warn(f'Entry \'{colname}\' in hdata has been overwritten by a data_table entry.', - RuntimeWarning) + warnings.warn( + f"Entry '{colname}' in hdata has been overwritten by a data_table entry.", + RuntimeWarning, + ) data.update({colname: list(data_table[:, idx])}) named_columns += 1 # finally add data_table as an entry named 'data table' if not all columns were parsed if named_columns < max_columns: - if 'data table' in data.keys(): - warnings.warn('Entry \'data table\' in hdata has been overwritten by data_table.', - RuntimeWarning) - data.update({'data table': data_table}) + if "data table" in data.keys(): + warnings.warn( + "Entry 'data table' in hdata has been overwritten by data_table.", + RuntimeWarning, + ) + data.update({"data table": data_table}) # parse name using pathlib and generate dictionary entry entry = {title: data} @@ -120,7 +135,7 @@ def serialize_data(filename, hdata: dict, data_table, dt_colnames=None, show_pat pass # json - if extension == '.json': + if extension == ".json": # cannot serialize numpy arrays class NumpyEncoder(json.JSONEncoder): def default(self, data_obj): @@ -130,16 +145,16 @@ def default(self, data_obj): # dump if non-existing if not existing: - with open(serial_file, 'w') as jsonfile: + with open(serial_file, "w") as jsonfile: file_data = entry # for return json.dump(file_data, jsonfile, indent=2, cls=NumpyEncoder) # update if existing else: - with open(serial_file, 'r') as json_read: + with open(serial_file, "r") as json_read: file_data = json.load(json_read) file_data.update(entry) - with open(serial_file, 'w') as json_write: + with open(serial_file, "w") as json_write: # dump to string first for formatting json.dump(file_data, json_write, indent=2, cls=NumpyEncoder) @@ -177,12 +192,15 @@ def deserialize_data(filename, filetype=None): return_dict = {} # json - if extension == '.json': - with open(filename, 'r') as json_file: + if extension == ".json": + with open(filename, "r") as json_file: j_dict = json.load(json_file) return_dict = j_dict if len(return_dict) == 0: - warnings.warn(f'Loaded dictionary is empty. Possibly due to improper file type.', RuntimeWarning) + warnings.warn( + f"Loaded dictionary is empty. Possibly due to improper file type.", + RuntimeWarning, + ) return return_dict diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 5031d6d6..6d85013f 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -5,14 +5,14 @@ from numpy import array QQUANTITIES = ["q"] -ANGLEQUANTITIES = ["angle","tth", "twotheta", "2theta"] +ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] DQUANTITIES = ["d", "dspace"] XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES -XUNITS = ["degrees","radians","rad","deg","inv_angs","inv_nm","nm-1","A-1"] +XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -class Diffraction_object(): - def __init__(self, name='', wavelength=None): +class Diffraction_object: + def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength self.scat_quantity = "" @@ -33,11 +33,19 @@ def __eq__(self, other): value = getattr(self, key) other_value = getattr(other, key) if isinstance(value, float): - if (not (value is None and other_value is None) and - (value is None) or (other_value is None) or not np.isclose(value, other_value, rtol=1e-5)): + if ( + not (value is None and other_value is None) + and (value is None) + or (other_value is None) + or not np.isclose(value, other_value, rtol=1e-5) + ): return False - elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): - if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): + elif isinstance(value, list) and all( + isinstance(i, np.ndarray) for i in value + ): + if not all( + np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value) + ): return False else: if value != other_value: @@ -46,28 +54,41 @@ def __eq__(self, other): def __add__(self, other): summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + if ( + isinstance(other, int) + or isinstance(other, float) + or isinstance(other, np.ndarray) + ): summed.on_tth[1] = self.on_tth[1] + other summed.on_q[1] = self.on_q[1] + other elif not isinstance(other, Diffraction_object): raise TypeError(f"I only know how to sum two Diffraction_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(f"objects are not on the same x-grid. You may add them using the self.add method and" - f"specifying how to handle the mismatch.") + raise RuntimeError( + f"objects are not on the same x-grid. You may add them using the self.add method and" + f"specifying how to handle the mismatch." + ) else: summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] summed.on_q[1] = self.on_q[1] + other.on_q[1] return summed + def __radd__(self, other): summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + if ( + isinstance(other, int) + or isinstance(other, float) + or isinstance(other, np.ndarray) + ): summed.on_tth[1] = self.on_tth[1] + other summed.on_q[1] = self.on_q[1] + other elif not isinstance(other, Diffraction_object): raise TypeError(f"I only know how to sum two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(f"objects are not on the same x-grid. You may add them using the self.add method and" - f"specifying how to handle the mismatch.") + raise RuntimeError( + f"objects are not on the same x-grid. You may add them using the self.add method and" + f"specifying how to handle the mismatch." + ) else: summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] summed.on_q[1] = self.on_q[1] + other.on_q[1] @@ -75,15 +96,22 @@ def __radd__(self, other): def __sub__(self, other): subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + if ( + isinstance(other, int) + or isinstance(other, float) + or isinstance(other, np.ndarray) + ): subtracted.on_tth[1] = self.on_tth[1] - other subtracted.on_q[1] = self.on_q[1] - other elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to subtract two Scattering_object objects") + raise TypeError( + f"I only know how to subtract two Scattering_object objects" + ) elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may subtract them using the self.add method and" - f"specifying how to handle the mismatch.") + f"specifying how to handle the mismatch." + ) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] subtracted.on_q[1] = self.on_q[1] - other.on_q[1] @@ -91,31 +119,45 @@ def __sub__(self, other): def __rsub__(self, other): subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + if ( + isinstance(other, int) + or isinstance(other, float) + or isinstance(other, np.ndarray) + ): subtracted.on_tth[1] = other - self.on_tth[1] subtracted.on_q[1] = other - self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to subtract two Scattering_object objects") + raise TypeError( + f"I only know how to subtract two Scattering_object objects" + ) elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may subtract them using the self.add method and" - f"specifying how to handle the mismatch.") + f"specifying how to handle the mismatch." + ) else: - subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] + subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] subtracted.on_q[1] = other.on_q[1] - self.on_q[1] return subtracted def __mul__(self, other): multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + if ( + isinstance(other, int) + or isinstance(other, float) + or isinstance(other, np.ndarray) + ): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to multiply two Scattering_object objects") + raise TypeError( + f"I only know how to multiply two Scattering_object objects" + ) elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may multiply them using the self.add method and" - f"specifying how to handle the mismatch.") + f"specifying how to handle the mismatch." + ) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -123,13 +165,18 @@ def __mul__(self, other): def __rmul__(self, other): multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + if ( + isinstance(other, int) + or isinstance(other, float) + or isinstance(other, np.ndarray) + ): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may multiply them using the self.add method and" - f"specifying how to handle the mismatch.") + f"specifying how to handle the mismatch." + ) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -137,44 +184,54 @@ def __rmul__(self, other): def __truediv__(self, other): divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + if ( + isinstance(other, int) + or isinstance(other, float) + or isinstance(other, np.ndarray) + ): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to multiply two Scattering_object objects") + raise TypeError( + f"I only know how to multiply two Scattering_object objects" + ) elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may multiply them using the self.add method and" - f"specifying how to handle the mismatch.") + f"specifying how to handle the mismatch." + ) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] divided.on_q[1] = self.on_q[1] / other.on_q[1] return divided - def __rtruediv__(self, other): divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + if ( + isinstance(other, int) + or isinstance(other, float) + or isinstance(other, np.ndarray) + ): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"Diffraction objects are not on the same x-grid. You may multiply them using the self.add method and" - f"specifying how to handle the mismatch.") + f"specifying how to handle the mismatch." + ) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] divided.on_q[1] = other.on_q[1] / self.on_q[1] return divided - def set_angles_from_list(self, angles_list): - self.angles = angles_list - self.n_steps = len(angles_list) - 1. - self.begin_angle = self.angles[0] - self.end_angle = self.angles[-1] + self.angles = angles_list + self.n_steps = len(angles_list) - 1.0 + self.begin_angle = self.angles[0] + self.end_angle = self.angles[-1] def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): - ''' + """ create an array of linear spaced Q-values Parameters @@ -194,12 +251,15 @@ def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): self.qs array of floats the q values in the independent array - ''' - self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) - + """ + self.qs = self._set_array_from_range( + begin_q, end_q, step_size=step_size, n_steps=n_steps + ) - def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): - ''' + def set_angles_from_range( + self, begin_angle, end_angle, step_size=None, n_steps=None + ): + """ create an array of linear spaced angle-values Parameters @@ -219,15 +279,17 @@ def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps= self.angles array of floats the q values in the independent array - ''' - self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) - + """ + self.angles = self._set_array_from_range( + begin_angle, end_angle, step_size=step_size, n_steps=n_steps + ) def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): if step_size is not None and n_steps is not None: print( "WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be " - "reset.") + "reset." + ) array = np.linspace(begin, end, n_steps) elif step_size is not None: array = np.arange(begin, end, step_size) @@ -245,8 +307,16 @@ def get_angle_index(self, angle): if count >= len(self.angles): raise IndexError(f"WARNING: no angle {angle} found in angles list") - def insert_scattering_quantity(self, xarray, yarray, xtype, metadata={}, - scat_quantity=None, name=None, wavelength=None): + def insert_scattering_quantity( + self, + xarray, + yarray, + xtype, + metadata={}, + scat_quantity=None, + name=None, + wavelength=None, + ): f""" insert a new scattering quantity into the scattering object @@ -268,17 +338,22 @@ def insert_scattering_quantity(self, xarray, yarray, xtype, metadata={}, self.input_xtype = xtype # empty attributes have been defined in the __init__ method so only # set the attributes that are not empty to avoid emptying them by mistake - if metadata: self.metadata = metadata - if scat_quantity is not None: self.scat_quantity = scat_quantity - if name is not None: self.name = name - if wavelength is not None: self.wavelength = wavelength + if metadata: + self.metadata = metadata + if scat_quantity is not None: + self.scat_quantity = scat_quantity + if name is not None: + self.name = name + if wavelength is not None: + self.wavelength = wavelength if xtype.lower() in QQUANTITIES: - self.on_q = [np.array(xarray),np.array(yarray)] + self.on_q = [np.array(xarray), np.array(yarray)] elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = [np.array(xarray),np.array(yarray)] + self.on_tth = [np.array(xarray), np.array(yarray)] elif xtype.lower() in DQUANTITIES: - self.on_tth = [np.array(xarray),np.array(yarray)] + self.on_tth = [np.array(xarray), np.array(yarray)] self.set_all_arrays() + def q_to_tth(self): r""" Helper function to convert q to two-theta. @@ -314,7 +389,7 @@ def q_to_tth(self): q = np.asarray(q) wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) - return np.rad2deg(2. * np.arcsin(q * pre_factor)) + return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) def tth_to_q(self): r""" @@ -368,7 +443,6 @@ def set_all_arrays(self): self.qmin = self.on_q[0][0] self.qmax = self.on_q[0][-1] - def _get_original_array(self): if self.input_xtype in QQUANTITIES: return self.on_q, "q" @@ -404,17 +478,17 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): data = self.on_xtype(xtype) target = target_diff_object.on_xtype(xtype) if xvalue is None: - xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2. + xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 xindex = (np.abs(data[0] - xvalue)).argmin() ytarget = target[1][xindex] yself = data[1][xindex] - scaled.on_tth[1] = data[1]*ytarget/yself - scaled.on_q[1] = data[1]*ytarget/yself + scaled.on_tth[1] = data[1] * ytarget / yself + scaled.on_q[1] = data[1] * ytarget / yself return scaled def on_xtype(self, xtype): - ''' + """ return a 2D np array with x in the first column and y in the second for x of type type Parameters ---------- @@ -423,7 +497,7 @@ def on_xtype(self, xtype): Returns ------- - ''' + """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth elif xtype.lower() in QQUANTITIES: @@ -442,9 +516,11 @@ def dump(self, filepath, xtype=None): else: print(f"WARNING: cannot handle the xtype '{xtype}'") - with open(filepath, 'w') as f: - f.write(f"[Diffraction_object]\nname = {self.name}\nwavelength = {self.wavelength}\n" - f"scat_quantity = {self.scat_quantity}\n") + with open(filepath, "w") as f: + f.write( + f"[Diffraction_object]\nname = {self.name}\nwavelength = {self.wavelength}\n" + f"scat_quantity = {self.scat_quantity}\n" + ) for key, value in self.metadata.items(): f.write(f"{key} = {value}\n") f.write("\n#### start data\n") diff --git a/src/diffpy/utils/scattering_objects/user_config.py b/src/diffpy/utils/scattering_objects/user_config.py index 0db84a1f..06b83943 100644 --- a/src/diffpy/utils/scattering_objects/user_config.py +++ b/src/diffpy/utils/scattering_objects/user_config.py @@ -6,9 +6,6 @@ HOME_CONFIG_PATH = Path.home() / CONFIG_FILE - - - def find_conf_file(): if CWD_CONFIG_PATH.exists() and CWD_CONFIG_PATH.is_file(): return CWD_CONFIG_PATH @@ -23,7 +20,9 @@ def read_conf_file(): with open(conf_file, "r") as f: config = json.load(f) if not config.get("username") or not config.get("email"): - raise ValueError("Please provide a configuration file with username and email.") + raise ValueError( + "Please provide a configuration file with username and email." + ) return config.get("username"), config.get("email") return None, None diff --git a/src/diffpy/utils/tests/__init__.py b/src/diffpy/utils/tests/__init__.py index 53a65fea..ccf8dcee 100644 --- a/src/diffpy/utils/tests/__init__.py +++ b/src/diffpy/utils/tests/__init__.py @@ -19,8 +19,8 @@ import unittest -def testsuite(pattern=''): - '''Create a unit tests suite for diffpy.utils package. +def testsuite(pattern=""): + """Create a unit tests suite for diffpy.utils package. Parameters ---------- @@ -33,15 +33,16 @@ def testsuite(pattern=''): ------- suite : `unittest.TestSuite` The TestSuite object containing the matching tests. - ''' + """ import re from os.path import dirname from itertools import chain from importlib.resources import files, as_file + loader = unittest.defaultTestLoader ref = files(__package__) with as_file(ref) as thisdir: - depth = __name__.count('.') + 1 + depth = __name__.count(".") + 1 topdir = thisdir for i in range(depth): topdir = dirname(topdir) @@ -51,12 +52,12 @@ def testsuite(pattern=''): rx = re.compile(pattern) tsuites = list(chain.from_iterable(suite_all)) tsok = all(isinstance(ts, unittest.TestSuite) for ts in tsuites) - if not tsok: # pragma: no cover + if not tsok: # pragma: no cover return suite_all tcases = chain.from_iterable(tsuites) for tc in tcases: - tcwords = tc.id().split('.') - shortname = '.'.join(tcwords[-3:]) + tcwords = tc.id().split(".") + shortname = ".".join(tcwords[-3:]) if rx.search(shortname): suite.addTest(tc) # verify all tests are found for an empty pattern. @@ -65,12 +66,12 @@ def testsuite(pattern=''): def test(): - '''Execute all unit tests for the diffpy.utils package. + """Execute all unit tests for the diffpy.utils package. Returns ------- result : `unittest.TestResult` - ''' + """ suite = testsuite() runner = unittest.TextTestRunner() result = runner.run(suite) diff --git a/src/diffpy/utils/tests/debug.py b/src/diffpy/utils/tests/debug.py index fc8595b2..96d7c8dc 100644 --- a/src/diffpy/utils/tests/debug.py +++ b/src/diffpy/utils/tests/debug.py @@ -22,10 +22,11 @@ """ -if __name__ == '__main__': +if __name__ == "__main__": import sys from diffpy.utils.tests import testsuite - pattern = sys.argv[1] if len(sys.argv) > 1 else '' + + pattern = sys.argv[1] if len(sys.argv) > 1 else "" suite = testsuite(pattern) suite.debug() diff --git a/src/diffpy/utils/tests/run.py b/src/diffpy/utils/tests/run.py index 37e04e85..cc824e7a 100644 --- a/src/diffpy/utils/tests/run.py +++ b/src/diffpy/utils/tests/run.py @@ -18,15 +18,18 @@ python -m diffpy.utils.tests.run """ -if __name__ == '__main__': +if __name__ == "__main__": import sys + # show warnings by default if not sys.warnoptions: import os, warnings + warnings.simplefilter("default") # also affect subprocesses os.environ["PYTHONWARNINGS"] = "default" from diffpy.utils.tests import test + # produce zero exit code for a successful test sys.exit(not test().wasSuccessful()) diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index bb450982..424565ea 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -7,96 +7,253 @@ params = [ ( # Default - ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], - ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], True, ), ( # Compare same attributes - ["test", 0.71, "x-ray", [np.array([1, 2]), np.array([3, 4])], [np.array([1, 2]), np.array([3, 4])], - [np.array([1, 2]), np.array([3, 4])], {"thing1": 1, "thing2": "thing2"}], - ["test", 0.7100001, "x-ray", [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], - [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], - [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], {"thing1": 1, "thing2": "thing2"}], + [ + "test", + 0.71, + "x-ray", + [np.array([1, 2]), np.array([3, 4])], + [np.array([1, 2]), np.array([3, 4])], + [np.array([1, 2]), np.array([3, 4])], + {"thing1": 1, "thing2": "thing2"}, + ], + [ + "test", + 0.7100001, + "x-ray", + [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], + [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], + [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], + {"thing1": 1, "thing2": "thing2"}, + ], True, ), ( # Different names - ["test1", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], - ["test2", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + [ + "test1", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], + [ + "test2", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], False, ), ( # Different wavelengths - ["", 0.71, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], - ["", 0.711, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + [ + "", + 0.71, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], + [ + "", + 0.711, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], False, ), ( # Different wavelengths - ["", 0.71, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], - ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + [ + "", + 0.71, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], False, ), ( # Different scat_quantity - ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], - ["", None, "x-ray", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], {}], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], + [ + "", + None, + "x-ray", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], False, ), ( # Different on_q - ["", None, "", [np.array([1, 2]), np.array([3, 4])], [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], {}], - ["", None, "", [np.array([1.01, 2]), np.array([3, 4])], [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], {}], + [ + "", + None, + "", + [np.array([1, 2]), np.array([3, 4])], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], + [ + "", + None, + "", + [np.array([1.01, 2]), np.array([3, 4])], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {}, + ], False, ), ( # Different on_tth - ["", None, "", [np.empty(0), np.empty(0)], [np.array([1, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], {}], - ["", None, "", [np.empty(0), np.empty(0)], [np.array([1.01, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], {}], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.array([1, 2]), np.array([3, 4])], + [np.empty(0), np.empty(0)], + {}, + ], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.array([1.01, 2]), np.array([3, 4])], + [np.empty(0), np.empty(0)], + {}, + ], False, ), ( # Different on_d - ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], - [np.array([1, 2]), np.array([3, 4])], {}], - ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], - [np.array([1.01, 2]), np.array([3, 4])], {}], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.array([1, 2]), np.array([3, 4])], + {}, + ], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.array([1.01, 2]), np.array([3, 4])], + {}, + ], False, ), ( # Different metadata - ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], - {"thing1": 0, "thing2": "thing2"}], - ["", None, "", [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], [np.empty(0), np.empty(0)], - {"thing1": 1, "thing2": "thing2"}], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {"thing1": 0, "thing2": "thing2"}, + ], + [ + "", + None, + "", + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + [np.empty(0), np.empty(0)], + {"thing1": 1, "thing2": "thing2"}, + ], False, ), ] + @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): diffraction_object1 = Diffraction_object() diffraction_object2 = Diffraction_object() - diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] + diffraction_object1_attributes = [ + key for key in diffraction_object1.__dict__ if not key.startswith("_") + ] for i, attribute in enumerate(diffraction_object1_attributes): setattr(diffraction_object1, attribute, inputs1[i]) setattr(diffraction_object2, attribute, inputs2[i]) assert (diffraction_object1 == diffraction_object2) == expected + def test_dump(tmp_path): - x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) - directory = Path(tmp_path) - file = directory / "testfile" - test = Diffraction_object() - test.wavelength = 1.54 - test.name = "test" - test.scat_quantity = "x-ray" - test.insert_scattering_quantity(x, y, "q", metadata={"thing1": 1, "thing2": "thing2"}) - test.dump(file, "q") - with open(file, "r") as f: - actual = f.read() - expected = ("[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" - "thing2 = thing2\n\n#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" - "1.000000000000000000e+00 1.000000000000000000e+00\n2.000000000000000000e+00 2.000000000000000000e+00\n" - "3.000000000000000000e+00 3.000000000000000000e+00\n4.000000000000000000e+00 4.000000000000000000e+00\n" - "5.000000000000000000e+00 5.000000000000000000e+00\n" - "6.000000000000000000e+00 6.000000000000000000e+00\n7.000000000000000000e+00 7.000000000000000000e+00\n" - "8.000000000000000000e+00 8.000000000000000000e+00\n9.000000000000000000e+00 9.000000000000000000e+00\n" - "1.000000000000000000e+01 1.000000000000000000e+01\n") - assert actual == expected + x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) + directory = Path(tmp_path) + file = directory / "testfile" + test = Diffraction_object() + test.wavelength = 1.54 + test.name = "test" + test.scat_quantity = "x-ray" + test.insert_scattering_quantity( + x, y, "q", metadata={"thing1": 1, "thing2": "thing2"} + ) + test.dump(file, "q") + with open(file, "r") as f: + actual = f.read() + expected = ( + "[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" + "thing2 = thing2\n\n#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" + "1.000000000000000000e+00 1.000000000000000000e+00\n2.000000000000000000e+00 2.000000000000000000e+00\n" + "3.000000000000000000e+00 3.000000000000000000e+00\n4.000000000000000000e+00 4.000000000000000000e+00\n" + "5.000000000000000000e+00 5.000000000000000000e+00\n" + "6.000000000000000000e+00 6.000000000000000000e+00\n7.000000000000000000e+00 7.000000000000000000e+00\n" + "8.000000000000000000e+00 8.000000000000000000e+00\n9.000000000000000000e+00 9.000000000000000000e+00\n" + "1.000000000000000000e+01 1.000000000000000000e+01\n" + ) + assert actual == expected diff --git a/src/diffpy/utils/tests/test_loaddata.py b/src/diffpy/utils/tests/test_loaddata.py index a3e20b68..dc1ace0a 100644 --- a/src/diffpy/utils/tests/test_loaddata.py +++ b/src/diffpy/utils/tests/test_loaddata.py @@ -8,17 +8,17 @@ from diffpy.utils.parsers import loadData from diffpy.utils.tests.testhelpers import datafile -loaddata01 = datafile('loaddata01.txt') -loaddatawithheaders = datafile('loaddatawithheaders.txt') +loaddata01 = datafile("loaddata01.txt") +loaddatawithheaders = datafile("loaddatawithheaders.txt") + ############################################################################## class TestLoadData(unittest.TestCase): def test_loadData_default(self): - """check loadData() with default options - """ + """check loadData() with default options""" d2c = numpy.array([[3, 31], [4, 32], [5, 33]]) - self.assertRaises(IOError, loadData, 'doesnotexist') + self.assertRaises(IOError, loadData, "doesnotexist") # the default minrows=10 makes it read from the third line d = loadData(loaddata01) self.assertTrue(numpy.array_equal(d2c, d)) @@ -27,15 +27,13 @@ def test_loadData_default(self): self.assertTrue(numpy.array_equal(d2c, d)) # check the effect of usecols effect d = loadData(loaddata01, usecols=(0,)) - self.assertTrue(numpy.array_equal(d2c[:,0], d)) + self.assertTrue(numpy.array_equal(d2c[:, 0], d)) d = loadData(loaddata01, usecols=(1,)) - self.assertTrue(numpy.array_equal(d2c[:,1], d)) + self.assertTrue(numpy.array_equal(d2c[:, 1], d)) return - def test_loadData_1column(self): - """check loading of one-column data. - """ + """check loading of one-column data.""" d1c = numpy.arange(1, 6) d = loadData(loaddata01, usecols=[0], minrows=1) self.assertTrue(numpy.array_equal(d1c, d)) @@ -45,13 +43,13 @@ def test_loadData_1column(self): self.assertFalse(numpy.array_equal(d1c, d)) return - def test_loadData_headers(self): - """check loadData() with headers options enabled - """ + """check loadData() with headers options enabled""" hignore = ["# ", "// ", "["] # ignore lines beginning with these strings delimiter = ": " # what our data should be separated by - hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) + hdata = loadData( + loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore + ) # only fourteen lines of data are formatted properly assert len(hdata) == 14 # check the following are floats @@ -66,7 +64,7 @@ def test_loadData_headers(self): # End of class TestRoutines -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() -# End of file \ No newline at end of file +# End of file diff --git a/src/diffpy/utils/tests/test_resample.py b/src/diffpy/utils/tests/test_resample.py index e3395f44..9ea265f6 100644 --- a/src/diffpy/utils/tests/test_resample.py +++ b/src/diffpy/utils/tests/test_resample.py @@ -6,6 +6,7 @@ def test_wsinterp(): import random + # Check known points are unchanged by interpolation # FIXME: if another SW interp function exists, run comparisons for interpolated points @@ -13,18 +14,18 @@ def test_wsinterp(): ssr = 44100**-1 # Standard sampling rate for human-hearable frequencies t = ssr n = 5 - xp = np.array([i*t for i in range(-n, n+1, 1)]) - x = np.array([i*t for i in range(-n-1, n+2, 1)]) + xp = np.array([i * t for i in range(-n, n + 1, 1)]) + x = np.array([i * t for i in range(-n - 1, n + 2, 1)]) # Interpolate a few random datasets trials = 10 for trial in range(trials): - fp = np.array([random.random() * ssr for i in range(-n, n+1, 1)]) + fp = np.array([random.random() * ssr for i in range(-n, n + 1, 1)]) fp_at_x = wsinterp(x, xp, fp) assert np.allclose(fp_at_x[1:-1], fp) for i in range(len(x)): assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp)) -if __name__ == '__main__': +if __name__ == "__main__": test_wsinterp() diff --git a/src/diffpy/utils/tests/test_serialization.py b/src/diffpy/utils/tests/test_serialization.py index 1d7223aa..832c75de 100644 --- a/src/diffpy/utils/tests/test_serialization.py +++ b/src/diffpy/utils/tests/test_serialization.py @@ -2,20 +2,23 @@ from diffpy.utils.parsers import loadData from diffpy.utils.tests.testhelpers import datafile -from diffpy.utils.parsers.custom_exceptions import UnsupportedTypeError, ImproperSizeError +from diffpy.utils.parsers.custom_exceptions import ( + UnsupportedTypeError, + ImproperSizeError, +) import os import pytest import numpy -tests_dir = os.path.dirname(os.path.abspath(locals().get('__file__', 'file.py'))) +tests_dir = os.path.dirname(os.path.abspath(locals().get("__file__", "file.py"))) -targetjson = datafile('targetjson.json') -schemaname = datafile('strumining.json') -wrongtype = datafile('wrong.type') -loadfile = datafile('loadfile.txt') -warningfile = datafile('generatewarnings.txt') -nodt = datafile('loaddatawithheaders.txt') +targetjson = datafile("targetjson.json") +schemaname = datafile("strumining.json") +wrongtype = datafile("wrong.type") +loadfile = datafile("loadfile.txt") +warningfile = datafile("generatewarnings.txt") +nodt = datafile("loaddatawithheaders.txt") def test_load_multiple(tmp_path): @@ -26,23 +29,33 @@ def test_load_multiple(tmp_path): generated_data = None for hfname in tlm_list: # gather data using loadData - headerfile = os.path.normpath(os.path.join(tests_dir, "testdata", "dbload", hfname)) + headerfile = os.path.normpath( + os.path.join(tests_dir, "testdata", "dbload", hfname) + ) hdata = loadData(headerfile, headers=True) data_table = loadData(headerfile) # check path extraction - generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=['r', 'gr'], show_path=True) - assert headerfile == os.path.normpath(generated_data[hfname].pop('path')) + generated_data = serialize_data( + headerfile, hdata, data_table, dt_colnames=["r", "gr"], show_path=True + ) + assert headerfile == os.path.normpath(generated_data[hfname].pop("path")) # rerun without path information and save to file - generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=['r', 'gr'], - show_path=False, serial_file=generatedjson) + generated_data = serialize_data( + headerfile, + hdata, + data_table, + dt_colnames=["r", "gr"], + show_path=False, + serial_file=generatedjson, + ) # compare to target target_data = deserialize_data(targetjson) assert target_data == generated_data # ensure file saved properly - assert target_data == deserialize_data(generatedjson, filetype='.json') + assert target_data == deserialize_data(generatedjson, filetype=".json") def test_exceptions(): @@ -57,43 +70,65 @@ def test_exceptions(): # various dt_colnames inputs with pytest.raises(ImproperSizeError): - serialize_data(loadfile, hdata, data_table, dt_colnames=["one", "two", "three is too many"]) + serialize_data( + loadfile, hdata, data_table, dt_colnames=["one", "two", "three is too many"] + ) # check proper output - normal = serialize_data(loadfile, hdata, data_table, dt_colnames=['r', 'gr']) + normal = serialize_data(loadfile, hdata, data_table, dt_colnames=["r", "gr"]) data_name = list(normal.keys())[0] - r_list = normal[data_name]['r'] - gr_list = normal[data_name]['gr'] + r_list = normal[data_name]["r"] + gr_list = normal[data_name]["gr"] # three equivalent ways to denote no column names missing_parameter = serialize_data(loadfile, hdata, data_table, show_path=False) - empty_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[]) - none_entry_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None]) + empty_parameter = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[] + ) + none_entry_parameter = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None] + ) # check equivalence assert missing_parameter == empty_parameter assert missing_parameter == none_entry_parameter - assert numpy.allclose(missing_parameter[data_name]['data table'], data_table) + assert numpy.allclose(missing_parameter[data_name]["data table"], data_table) # extract a single column - r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=['r']) - gr_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, 'gr']) - incorrect_r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, 'r']) + r_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=["r"] + ) + gr_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "gr"] + ) + incorrect_r_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "r"] + ) # check proper columns extracted - assert numpy.allclose(gr_extract[data_name]['gr'], incorrect_r_extract[data_name]['r']) - assert 'r' not in gr_extract[data_name] - assert 'gr' not in r_extract[data_name] and 'gr' not in incorrect_r_extract[data_name] + assert numpy.allclose( + gr_extract[data_name]["gr"], incorrect_r_extract[data_name]["r"] + ) + assert "r" not in gr_extract[data_name] + assert ( + "gr" not in r_extract[data_name] and "gr" not in incorrect_r_extract[data_name] + ) # check correct values extracted - assert numpy.allclose(r_extract[data_name]['r'], r_list) - assert numpy.allclose(gr_extract[data_name]['gr'], gr_list) + assert numpy.allclose(r_extract[data_name]["r"], r_list) + assert numpy.allclose(gr_extract[data_name]["gr"], gr_list) # no datatable nodt_hdata = loadData(nodt, headers=True) nodt_dt = loadData(nodt) no_dt = serialize_data(nodt, nodt_hdata, nodt_dt, show_path=False) nodt_data_name = list(no_dt.keys())[0] - assert numpy.allclose(no_dt[nodt_data_name]['data table'], nodt_dt) + assert numpy.allclose(no_dt[nodt_data_name]["data table"], nodt_dt) # ensure user is warned when columns are overwritten hdata = loadData(warningfile, headers=True) data_table = loadData(warningfile) with pytest.warns(RuntimeWarning) as record: - data = serialize_data(warningfile, hdata, data_table, show_path=False, dt_colnames=['c1', 'c2', 'c3']) + data = serialize_data( + warningfile, + hdata, + data_table, + show_path=False, + dt_colnames=["c1", "c2", "c3"], + ) assert len(record) == 4 for msg in record: - assert 'overwritten' in msg.message.args[0] + assert "overwritten" in msg.message.args[0] diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 7c82feb2..eb73a34b 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -7,12 +7,15 @@ from diffpy.utils.tools import get_user_info -def _setup_dirs(monkeypatch,user_filesystem): + +def _setup_dirs(monkeypatch, user_filesystem): cwd = Path(user_filesystem) home_dir = cwd / "home_dir" monkeypatch.setattr("pathlib.Path.home", lambda _: home_dir) os.chdir(cwd) return home_dir + + def _run_tests(inputs, expected): args = {"username": inputs[0], "email": inputs[1]} expected_username, expected_email = expected @@ -40,13 +43,41 @@ def _run_tests(inputs, expected): (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), ] params_user_info_with_no_home_conf_file = [ - ([None, None], ["input_username", "input@email.com"], ["input_username", "input@email.com"]), - (["cli_username", None], ["", "input@email.com"], ["cli_username", "input@email.com"]), - ([None, "cli@email.com"], ["input_username", ""], ["input_username", "cli@email.com"]), - (["", ""], ["input_username", "input@email.com"], ["input_username", "input@email.com"]), - (["cli_username", ""], ["", "input@email.com"], ["cli_username", "input@email.com"]), - (["", "cli@email.com"], ["input_username", ""], ["input_username", "cli@email.com"]), - (["cli_username", "cli@email.com"], ["input_username", "input@email.com"], ["cli_username", "cli@email.com"]), + ( + [None, None], + ["input_username", "input@email.com"], + ["input_username", "input@email.com"], + ), + ( + ["cli_username", None], + ["", "input@email.com"], + ["cli_username", "input@email.com"], + ), + ( + [None, "cli@email.com"], + ["input_username", ""], + ["input_username", "cli@email.com"], + ), + ( + ["", ""], + ["input_username", "input@email.com"], + ["input_username", "input@email.com"], + ), + ( + ["cli_username", ""], + ["", "input@email.com"], + ["cli_username", "input@email.com"], + ), + ( + ["", "cli@email.com"], + ["input_username", ""], + ["input_username", "cli@email.com"], + ), + ( + ["cli_username", "cli@email.com"], + ["input_username", "input@email.com"], + ["cli_username", "cli@email.com"], + ), ] params_user_info_no_conf_file_no_inputs = [ ([None, None], ["", ""], ["", ""]), @@ -54,12 +85,17 @@ def _run_tests(inputs, expected): @pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) -def test_get_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): +def test_get_user_info_with_home_conf_file( + monkeypatch, inputs, expected, user_filesystem +): _setup_dirs(monkeypatch, user_filesystem) _run_tests(inputs, expected) + @pytest.mark.parametrize("inputs, expected", params_user_info_with_local_conf_file) -def test_get_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): +def test_get_user_info_with_local_conf_file( + monkeypatch, inputs, expected, user_filesystem +): home_dir = _setup_dirs(monkeypatch, user_filesystem) local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: @@ -68,8 +104,13 @@ def test_get_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_ os.remove(Path().home() / "diffpyconfig.json") _run_tests(inputs, expected) -@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) -def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): + +@pytest.mark.parametrize( + "inputsa, inputsb, expected", params_user_info_with_no_home_conf_file +) +def test_get_user_info_with_no_home_conf_file( + monkeypatch, inputsa, inputsb, expected, user_filesystem +): _setup_dirs(monkeypatch, user_filesystem) os.remove(Path().home() / "diffpyconfig.json") inp_iter = iter(inputsb) @@ -79,8 +120,12 @@ def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, exp assert confile.is_file() -@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) -def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): +@pytest.mark.parametrize( + "inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs +) +def test_get_user_info_no_conf_file_no_inputs( + monkeypatch, inputsa, inputsb, expected, user_filesystem +): _setup_dirs(monkeypatch, user_filesystem) os.remove(Path().home() / "diffpyconfig.json") inp_iter = iter(inputsb) diff --git a/src/diffpy/utils/tests/testhelpers.py b/src/diffpy/utils/tests/testhelpers.py index 4941ad20..064b54b2 100644 --- a/src/diffpy/utils/tests/testhelpers.py +++ b/src/diffpy/utils/tests/testhelpers.py @@ -18,10 +18,13 @@ # helper functions + def datafile(filename): from importlib.resources import files, as_file + ref = files(__package__) / ("testdata/" + filename) with as_file(ref) as rv: return rv + # End of file diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 654f9b21..6be9433a 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -3,6 +3,7 @@ from copy import copy from pathlib import Path + def clean_dict(obj): """ remove keys from the dictionary where the corresponding value is None @@ -23,6 +24,7 @@ def clean_dict(obj): del obj[key] return obj + def stringify(obj): """ convert None to an empty string @@ -38,6 +40,7 @@ def stringify(obj): """ return obj if obj is not None else "" + def load_config(file_path): """ load configuration from a json file @@ -60,22 +63,31 @@ def load_config(file_path): else: return None + def _sorted_merge(*dicts): merged = {} for d in dicts: merged.update(d) return merged + def _create_global_config(args): - username = input(f"Please enter the name of the user to put in the diffpy global config file " - f"[{args.get('username', '')}]: ").strip() or args.get("username", "") - email = input(f"Please enter the email of the user to put in the diffpy global config file " - f"[{args.get('email', '')}]: ").strip() or args.get("email", "") + username = input( + f"Please enter the name of the user to put in the diffpy global config file " + f"[{args.get('username', '')}]: " + ).strip() or args.get("username", "") + email = input( + f"Please enter the email of the user to put in the diffpy global config file " + f"[{args.get('email', '')}]: " + ).strip() or args.get("email", "") return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: - f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) + f.write( + json.dumps({"username": stringify(username), "email": stringify(email)}) + ) return return_bool + def get_user_info(args=None): """ get username and email configuration @@ -101,7 +113,9 @@ def get_user_info(args=None): if global_config is None and local_config is None: config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") - config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) + config = _sorted_merge( + clean_dict(global_config), clean_dict(local_config), clean_dict(args) + ) if config_bool is False: os.remove(Path().home() / "diffpyconfig.json") config = {"username": "", "email": ""} diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index c1618828..aae752b5 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -22,7 +22,7 @@ Use `__git_commit__` instead. """ -__all__ = ['__date__', '__git_commit__', '__timestamp__', '__version__'] +__all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] import os.path @@ -30,25 +30,29 @@ # obtain version information from the version.cfg file -cp = dict(version='', date='', commit='', timestamp='0') +cp = dict(version="", date="", commit="", timestamp="0") if __package__ is not None: - ref = files(__package__) / 'version.cfg' + ref = files(__package__) / "version.cfg" with as_file(ref) as fcfg: - if not os.path.isfile(fcfg): # pragma: no cover + if not os.path.isfile(fcfg): # pragma: no cover from warnings import warn - warn('Package metadata not found.') + + warn("Package metadata not found.") fcfg = os.devnull with open(fcfg) as fp: - kwords = [[w.strip() for w in line.split(' = ', 1)] - for line in fp if line[:1].isalpha() and ' = ' in line] + kwords = [ + [w.strip() for w in line.split(" = ", 1)] + for line in fp + if line[:1].isalpha() and " = " in line + ] assert all(w[0] in cp for w in kwords), "received unrecognized keyword" cp.update(kwords) del kwords -__version__ = cp['version'] -__date__ = cp['date'] -__git_commit__ = cp['commit'] -__timestamp__ = int(cp['timestamp']) +__version__ = cp["version"] +__date__ = cp["date"] +__git_commit__ = cp["commit"] +__timestamp__ = int(cp["timestamp"]) # TODO remove deprecated __gitsha__ in version 3.1. __gitsha__ = __git_commit__ diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index 4289196d..1a25bfb3 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -20,8 +20,7 @@ def getSelectionRows(grid): - """Indices of the rows that have any cell selected. - """ + """Indices of the rows that have any cell selected.""" rows = grid.GetNumberRows() rset = set() if grid.GetSelectedCols(): @@ -29,8 +28,7 @@ def getSelectionRows(grid): rset.update(grid.GetSelectedRows()) for r, c in grid.GetSelectedCells(): rset.add(r) - blocks = zip(grid.GetSelectionBlockTopLeft(), - grid.GetSelectionBlockBottomRight()) + blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) for tl, br in blocks: rset.update(range(tl[0], br[0] + 1)) rv = sorted(rset) @@ -38,8 +36,7 @@ def getSelectionRows(grid): def getSelectionColumns(grid): - """Indices of columns that have any cell selected. - """ + """Indices of columns that have any cell selected.""" cols = grid.GetNumberCols() cset = set() if grid.GetSelectedRows(): @@ -47,8 +44,7 @@ def getSelectionColumns(grid): cset.update(grid.GetSelectedCols()) for r, c in grid.GetSelectedCells(): cset.add(c) - blocks = zip(grid.GetSelectionBlockTopLeft(), - grid.GetSelectionBlockBottomRight()) + blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) for tl, br in blocks: cset.update(range(tl[1], br[1] + 1)) rv = sorted(cset) @@ -68,8 +64,7 @@ def getSelectedCells(grid): rcset.update(zip(cols * [r], allcols)) for c in grid.GetSelectedCols(): rcset.update(zip(allrows, rows * [c])) - blocks = zip(grid.GetSelectionBlockTopLeft(), - grid.GetSelectionBlockBottomRight()) + blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) for tl, br in blocks: brows = range(tl[0], br[0] + 1) bcols = range(tl[1], br[1] + 1) @@ -80,7 +75,7 @@ def getSelectedCells(grid): def limitSelectionToRows(grid, indices): - '''Limit selection to the specified row indices. + """Limit selection to the specified row indices. No action for empty indices. Parameters @@ -93,8 +88,9 @@ def limitSelectionToRows(grid, indices): Returns ------- No return value. - ''' + """ import bisect + if not indices: return rowblocks = _indicesToBlocks(indices) @@ -124,23 +120,25 @@ def quickResizeColumns(grid, indices): # Get the columns and maximum text width in each one dc = wx.ScreenDC() maxSize = {} - for (i, j) in indices: + for i, j in indices: if j not in maxSize: renderer = grid.GetCellRenderer(i, j) attr = grid.GetOrCreateCellAttr(i, j) size = renderer.GetBestSize(grid, attr, dc, i, j).width - size += 10 # Need a small buffer + size += 10 # Need a small buffer maxSize[j] = size grid.BeginBatch() - for (j, size) in maxSize.items(): + for j, size in maxSize.items(): if size > grid.GetColSize(j): grid.SetColSize(j, size) grid.EndBatch() return + # Local Helpers -------------------------------------------------------------- + def _indicesToBlocks(indices): """Convert a list of integer indices to a list of (start, stop) tuples. The (start, stop) tuple defines a continuous block, where the stop index is included in the block. @@ -166,4 +164,5 @@ def _indicesToBlocks(indices): rv = [tuple(ij) for ij in rngs] return rv + # End of file From 7961c6ccecb1c96632ecbf0e1aa5579fc1fa6525 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 28 May 2024 22:25:56 -0400 Subject: [PATCH 108/696] black on tests --- conda-recipe/run_test.py | 2 + doc/manual/source/conf.py | 159 +++++++++++++++++++------------------- run_tests.py | 6 +- 3 files changed, 86 insertions(+), 81 deletions(-) diff --git a/conda-recipe/run_test.py b/conda-recipe/run_test.py index a4edf44d..5aa7a28b 100644 --- a/conda-recipe/run_test.py +++ b/conda-recipe/run_test.py @@ -2,7 +2,9 @@ import sys import pathlib + sys.path.append((pathlib.Path.cwd().parent.absolute() / "src").as_posix()) import diffpy.utils.tests + assert diffpy.utils.tests.test().wasSuccessful() diff --git a/doc/manual/source/conf.py b/doc/manual/source/conf.py index 245da4d4..0e52d5a4 100644 --- a/doc/manual/source/conf.py +++ b/doc/manual/source/conf.py @@ -20,94 +20,97 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../../..')) -sys.path.insert(0, os.path.abspath('../../../src')) +# sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath("../../..")) +sys.path.insert(0, os.path.abspath("../../../src")) # abbreviations -ab_authors = u'Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge group' +ab_authors = ( + "Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge group" +) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', - 'm2r', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "m2r", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'diffpy.utils' -copyright = '%Y, Brookhaven National Laboratory' +project = "diffpy.utils" +copyright = "%Y, Brookhaven National Laboratory" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. from importlib.metadata import version + fullversion = version(project) # The short X.Y version. -version = ''.join(fullversion.split('.post')[:1]) +version = "".join(fullversion.split(".post")[:1]) # The full version, including alpha/beta/rc tags. release = fullversion # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' -today = time.strftime('%B %d, %Y', time.localtime()) +# today = '' +today = time.strftime("%B %d, %Y", time.localtime()) year = today.split()[-1] # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # substitute YEAR in the copyright string -copyright = copyright.replace('%Y', year) +copyright = copyright.replace("%Y", year) # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['build'] +exclude_patterns = ["build"] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['diffpy.utils'] +modindex_common_prefix = ["diffpy.utils"] # Display all warnings for missing links. nitpicky = True @@ -117,143 +120,137 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { - 'navigation_with_keys' : 'true', + "navigation_with_keys": "true", } # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] +# html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'diffpyutilsdoc' +htmlhelp_basename = "diffpyutilsdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'diffpy.utils.tex', 'diffpy.utils Documentation', - ab_authors, 'manual'), + ("index", "diffpy.utils.tex", "diffpy.utils Documentation", ab_authors, "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'diffpy.utils', 'diffpy.utils Documentation', - ab_authors, 1) -] +man_pages = [("index", "diffpy.utils", "diffpy.utils Documentation", ab_authors, 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -262,22 +259,28 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'diffpy.utils', 'diffpy.utils Documentation', - ab_authors, 'diffpy.utils', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "diffpy.utils", + "diffpy.utils Documentation", + ab_authors, + "diffpy.utils", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. diff --git a/run_tests.py b/run_tests.py index 515bfc6b..24441edf 100644 --- a/run_tests.py +++ b/run_tests.py @@ -3,13 +3,13 @@ import sys import pytest -if __name__ == '__main__': +if __name__ == "__main__": # show output results from every test function - args = ['-v'] + args = ["-v"] # show the message output for skipped and expected failure tests if len(sys.argv) > 1: args.extend(sys.argv[1:]) - print('pytest arguments: {}'.format(args)) + print("pytest arguments: {}".format(args)) # # compute coverage stats for xpdAcq # call pytest and exit with the return code from pytest exit_res = pytest.main(args) From a04e744df11484fa583287c949fa625b37290fa0 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 28 May 2024 22:29:12 -0400 Subject: [PATCH 109/696] pyproject.toml black config --- pyproject.toml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 87f8b86a..5d54b46f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,3 +48,26 @@ where = ["src"] # list of folders that contain the packages (["."] by default) include = ["diffpy*"] # package names should match these glob patterns (["*"] by default) exclude = ["diffpy.utils.tests*"] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) + +[tool.black] +line-length = 115 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | \.rst + | \.txt + | _build + | buck-out + | build + | dist + + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data +)/ +''' From 7c7cceba81bc583f7dd58d69ed2c885bcb048cf0 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 29 May 2024 16:05:54 -0400 Subject: [PATCH 110/696] updated tools example and utility --- doc/manual/source/examples/toolsexample.rst | 54 +++++++++----------- doc/manual/source/utilities/toolsutility.rst | 8 ++- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/doc/manual/source/examples/toolsexample.rst b/doc/manual/source/examples/toolsexample.rst index fbb5a9c4..b59d8843 100644 --- a/doc/manual/source/examples/toolsexample.rst +++ b/doc/manual/source/examples/toolsexample.rst @@ -6,46 +6,42 @@ Tools Example ############# This example will demonstrate how diffpy.utils allows us to load and manage username and email information. -Using the tools module, we can efficiently handle username and email configurations stored in ``diffpyconfig.json``. +Using the tools module, we can efficiently get them in terms of a dictionary. -1) To begin, we may create a configuration file ``diffpyconfig.json`` and add username and email to it. :: +1) We have the function ``get_user_info`` that neatly returns a dictionary containing the username and email. + You can use this function without arguments. :: - with ("diffpyconfig.json", "w") as f: - json.dump({"username": "example_username", "email": "example@email.com"}, f) + from diffpy.utils.tools import get_user_info + user_info = get_user_info() -2) To read the configuration file, we can use the ``load_config`` function, - which reads a json file and returns its content as a dictionary. :: + This function will first attempt to load configuration files + from both the current working directory and the home directory. + If no configuration files exist, it prompts for user input and creates a configuration file in the home directory. + It prioritizes prompted user inputs, then current working directory, and finally home directory. + If no configuration files or inputs are found, this function creates a configuration in the home directory + with empty values for username and email stored as a dictionary. - from pathlib import Path - from diffpy.utils.tools import load_config - global_config = load_config(Path().home() / "diffpyconfig.json") - local_config = load_config(Path().cwd() / "diffpyconfig.json") +2) You can also override existing values by passing a dictionary to the function. :: - If the configuration file does not exist, this function will return ``None``. + new_args = {"username": "new_username", "email": "new@example.com"} + new_user_info = get_user_info(new_args) -3) To clean the configuration dictionary by removing keys with ``None`` values, - we can use the ``clean_dict`` function. :: + Here, the function returns a dictionary containing the new arguments. + If no configuration files exist, it prompts for inputs again. The arguments passed here also override input values. + The updated arguments will not be saved in files. - from diffpy.utils.tools import clean_dict - cleaned_global_config = clean_dict(global_config) - cleaned_local_config = clean_dict(local_config) + You can update only the username or email individually, for example :: -4) We have the function ``get_user_info`` that integrates all functionalities mentioned above, - neatly returning a dictionary containing the username and email. :: + new_username = {"username": new_username} + new_user_info = get_user_info(new_username) - from diffpy.utils.tools import get_user_info - user_info = get_user_info() - - This function first searches for a configuration file in the current working directory and then the home directory. - If no configuration files exist, it prompts for user input and creates a configuration file in the home directory. - You can also override existing values by passing a dictionary to the function. :: + This updates username to "new_username" while fetching the email from inputs or the configuration files. + Similarly, you can update only the email. :: - new_args = {"username": "new_username", "email": "new@example.com"} - new_user_info = get_user_info(new_args) + new_email = {"email": new@email.com} + new_user_info = get_user_info(new_email) - Here, the function returns a dictionary containing the new args. - Parameters provided to the function override any existing values, - and values from the current directory file override those in the home directory file. + This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. By using this function, we ensure that user information is correctly loaded, merged, and saved. diff --git a/doc/manual/source/utilities/toolsutility.rst b/doc/manual/source/utilities/toolsutility.rst index c031bd8f..b4f0b21c 100644 --- a/doc/manual/source/utilities/toolsutility.rst +++ b/doc/manual/source/utilities/toolsutility.rst @@ -5,12 +5,10 @@ Tools Utility ############# -The ``diffpy.utils.tools`` module is designed for users to manage and track their username and email information. -This module helps by storing this information in a ``diffpyconfig.json`` file generated by the function. +The ``diffpy.utils.tools`` module contains tool functions for use with diffpy apps. +One of these functions, ``get_user_info``, is designed for managing and tracking username and email information. +This function helps storing this data in a ``diffpyconfig.json`` file. Users can use the module to simplify the process of loading, merging, and saving information consistently and easily. Additionally, it saves the effort of re-entering information, and allows overriding current information by passing parameters. - -Users can apply the function ``get_user_info`` for this purpose. -Other helper functions, such as ``load_config``, ``clean_dict``, are also available. From 802f0571beaceb317ae9e1db2905ee557b86315a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 31 May 2024 10:59:56 -0400 Subject: [PATCH 111/696] add flake8 file --- .flake8 | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..92c9bb29 --- /dev/null +++ b/.flake8 @@ -0,0 +1,12 @@ +[flake8] +exclude = + .git, + __pycache__, + build, + dist, + versioneer.py, + labpdfproc/_version.py, + doc/manual/source/conf.py +max-line-length = 115 +# Ignore some style 'errors' produced while formatting by 'black' +ignore = W503 From 0be93105fd0d4c1f42edfa9a8a2a2c388228d2fa Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 31 May 2024 11:03:01 -0400 Subject: [PATCH 112/696] add .isort.cfg file --- .isort.cfg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .isort.cfg diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 00000000..e0926f42 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,4 @@ +[settings] +line_length = 115 +multi_line_output = 3 +include_trailing_comma = True From 3e59412c86c55f9d3c8f0c046ce1c92165936226 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 31 May 2024 11:03:22 -0400 Subject: [PATCH 113/696] add pre-commit yaml file --- .pre-commit-config.yaml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..3a8f4de5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +default_language_version: + python: python3 +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + exclude: '\.(rst|txt)$' + - repo: https://github.com/ambv/black + rev: 23.3.0 + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/kynan/nbstripout + rev: 0.6.1 + hooks: + - id: nbstripout + - repo: local + hooks: + - id: prevent-commit-to-main + name: Prevent Commit to Main Branch + entry: ./prevent_commit_to_main.sh + language: script + stages: [commit] From aacf348601826f3843410fbf5ac6594e59f6ca35 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 31 May 2024 11:03:53 -0400 Subject: [PATCH 114/696] add prevent_commit_to_main.sh file --- prevent_commit_to_main.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100755 prevent_commit_to_main.sh diff --git a/prevent_commit_to_main.sh b/prevent_commit_to_main.sh new file mode 100755 index 00000000..5237b16a --- /dev/null +++ b/prevent_commit_to_main.sh @@ -0,0 +1,9 @@ +#!/bin/sh +##!"C:\\Program Files\\Git\\usr\\bin\\bash.exe" + +branch="$(git rev-parse --abbrev-ref HEAD)" + +if [ "$branch" = "main" ]; then +echo "You are on the main branch. Committing to the main branch is not allowed." +exit 1 +fi From ea8d6ab0256bc11411ee787e29395cd18b0098c0 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 31 May 2024 14:41:15 -0400 Subject: [PATCH 115/696] edits made by running pre-commit --- .coveragerc | 1 - .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 1 - AUTHORS.txt | 1 - conda-recipe/run_test.py | 2 +- devutils/makesdist | 38 ++++---- doc/manual/source/conf.py | 6 +- pytest.ini | 2 +- requirements/build.txt | 2 +- run_tests.py | 1 + src/diffpy/utils/parsers/__init__.py | 4 +- src/diffpy/utils/parsers/resample.py | 1 - src/diffpy/utils/parsers/serialization.py | 15 ++-- .../scattering_objects/diffraction_objects.py | 88 ++++--------------- .../utils/scattering_objects/user_config.py | 4 +- src/diffpy/utils/tests/__init__.py | 4 +- src/diffpy/utils/tests/debug.py | 1 + src/diffpy/utils/tests/run.py | 3 +- .../utils/tests/test_diffraction_objects.py | 8 +- src/diffpy/utils/tests/test_loaddata.py | 7 +- src/diffpy/utils/tests/test_resample.py | 3 +- src/diffpy/utils/tests/test_serialization.py | 56 ++++-------- src/diffpy/utils/tests/test_tools.py | 24 ++--- src/diffpy/utils/tests/testdata/dbload/e1.gr | 2 +- src/diffpy/utils/tests/testdata/dbload/e2.gr | 2 +- src/diffpy/utils/tests/testdata/dbload/e3.gr | 2 +- .../utils/tests/testdata/generated_db.json | 2 +- .../tests/testdata/loaddatawithheaders.txt | 2 +- src/diffpy/utils/tests/testdata/loadfile.txt | 2 +- .../utils/tests/testdata/strumining.json | 2 +- .../utils/tests/testdata/targetjson.json | 2 +- src/diffpy/utils/tests/testdata/wrong.type | 2 +- src/diffpy/utils/tests/testhelpers.py | 2 +- src/diffpy/utils/tools.py | 8 +- src/diffpy/utils/version.py | 8 +- 35 files changed, 104 insertions(+), 206 deletions(-) diff --git a/.coveragerc b/.coveragerc index e199c1ed..f6252ad3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -30,4 +30,3 @@ source = omit = ## exclude debug.py from codecov report */tests/debug.py - diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9e4962e8..03dffa5e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: - name: install requirements run: >- - conda install -n build -c conda-forge + conda install -n build -c conda-forge --file requirements/build.txt --file requirements/run.txt --file requirements/docs.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce422cb4..c39ace43 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,4 +53,3 @@ jobs: coverage run run_tests.py coverage report -m codecov - diff --git a/AUTHORS.txt b/AUTHORS.txt index 7d5c10b5..92b450db 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -3,4 +3,3 @@ Andrew yang Timur Davis Christopher L. Farrow Simon J.L. Billinge - diff --git a/conda-recipe/run_test.py b/conda-recipe/run_test.py index 5aa7a28b..51a59789 100644 --- a/conda-recipe/run_test.py +++ b/conda-recipe/run_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -import sys import pathlib +import sys sys.path.append((pathlib.Path.cwd().parent.absolute() / "src").as_posix()) diff --git a/devutils/makesdist b/devutils/makesdist index 6aaae616..e77257ea 100755 --- a/devutils/makesdist +++ b/devutils/makesdist @@ -1,51 +1,57 @@ #!/usr/bin/env python -'''Create source distribution tar.gz archive, where each file belongs +"""Create source distribution tar.gz archive, where each file belongs to a root user and modification time is set to the git commit time. -''' +""" -import sys +import glob +import gzip import os import subprocess -import glob +import sys import tarfile -import gzip BASEDIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) sys.path.insert(0, BASEDIR) -from setup import versiondata, FALLBACK_VERSION -timestamp = versiondata.getint('DEFAULT', 'timestamp') +from setup import FALLBACK_VERSION, versiondata -vfb = versiondata.get('DEFAULT', 'version').split('.post')[0] + '.post0' +timestamp = versiondata.getint("DEFAULT", "timestamp") + +vfb = versiondata.get("DEFAULT", "version").split(".post")[0] + ".post0" emsg = "Invalid FALLBACK_VERSION. Expected %r got %r." assert vfb == FALLBACK_VERSION, emsg % (vfb, FALLBACK_VERSION) + def inform(s): sys.stdout.write(s) sys.stdout.flush() return + inform('Run "setup.py sdist --formats=tar" ') -cmd_sdist = [sys.executable] + 'setup.py sdist --formats=tar'.split() -ec = subprocess.call(cmd_sdist, cwd=BASEDIR, stdout=open(os.devnull, 'w')) -if ec: sys.exit(ec) +cmd_sdist = [sys.executable] + "setup.py sdist --formats=tar".split() +ec = subprocess.call(cmd_sdist, cwd=BASEDIR, stdout=open(os.devnull, "w")) +if ec: + sys.exit(ec) inform("[done]\n") -tarname = max(glob.glob(BASEDIR + '/dist/*.tar'), key=os.path.getmtime) +tarname = max(glob.glob(BASEDIR + "/dist/*.tar"), key=os.path.getmtime) tfin = tarfile.open(tarname) -fpout = gzip.GzipFile(tarname + '.gz', 'w', mtime=0) -tfout = tarfile.open(fileobj=fpout, mode='w') +fpout = gzip.GzipFile(tarname + ".gz", "w", mtime=0) +tfout = tarfile.open(fileobj=fpout, mode="w") + def fixtarinfo(tinfo): tinfo.uid = tinfo.gid = 0 - tinfo.uname = tinfo.gname = 'root' + tinfo.uname = tinfo.gname = "root" tinfo.mtime = timestamp tinfo.mode &= ~0o022 return tinfo -inform('Filter %s --> %s.gz ' % (2 * (os.path.basename(tarname),))) + +inform("Filter %s --> %s.gz " % (2 * (os.path.basename(tarname),))) for ti in tfin: tfout.addfile(fixtarinfo(ti), tfin.extractfile(ti)) diff --git a/doc/manual/source/conf.py b/doc/manual/source/conf.py index 0e52d5a4..77749dcf 100644 --- a/doc/manual/source/conf.py +++ b/doc/manual/source/conf.py @@ -13,8 +13,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os +import sys import time # If extensions (or modules to document with autodoc) are in another directory, @@ -25,9 +25,7 @@ sys.path.insert(0, os.path.abspath("../../../src")) # abbreviations -ab_authors = ( - "Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge group" -) +ab_authors = "Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge group" # -- General configuration ------------------------------------------------ diff --git a/pytest.ini b/pytest.ini index 7ea926c5..21473ae6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths= src/diffpy/utils/tests -python_files = test_*.py \ No newline at end of file +python_files = test_*.py diff --git a/requirements/build.txt b/requirements/build.txt index 497aa6eb..f72d870d 100644 --- a/requirements/build.txt +++ b/requirements/build.txt @@ -1,2 +1,2 @@ python -setuptools \ No newline at end of file +setuptools diff --git a/run_tests.py b/run_tests.py index 24441edf..9ab1aabb 100644 --- a/run_tests.py +++ b/run_tests.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys + import pytest if __name__ == "__main__": diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index fb0c471b..f8d57c3b 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -17,8 +17,8 @@ """ from .loaddata import loadData -from .serialization import serialize_data, deserialize_data -from .resample import wsinterp, resample +from .resample import resample, wsinterp +from .serialization import deserialize_data, serialize_data # silence the pyflakes syntax checker assert loadData or resample or True diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index e67c448d..b68dbec8 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -105,7 +105,6 @@ def resample(r, s, dr): return rnew, snew elif dr0 > dr: - # Tried to pad the end of s to dampen, but nothing works. # m = (s[-1] - s[-2]) / dr0 # b = (s[-2] * r[-1] - s[-1] * r[-2]) / dr0 diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index ebd85c63..ab857b0a 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -13,12 +13,13 @@ # ############################################################################## -import pathlib import json +import pathlib +import warnings + import numpy -from .custom_exceptions import UnsupportedTypeError, ImproperSizeError -import warnings +from .custom_exceptions import ImproperSizeError, UnsupportedTypeError # FIXME: add support for yaml, xml supported_formats = [".json"] @@ -84,12 +85,8 @@ def serialize_data( num_columns = [len(row) for row in data_table] max_columns = max(num_columns) num_col_names = len(dt_colnames) - if ( - max_columns < num_col_names - ): # assume numpy.loadtxt gives non-irregular array - raise ImproperSizeError( - "More entries in dt_colnames than columns in data_table." - ) + if max_columns < num_col_names: # assume numpy.loadtxt gives non-irregular array + raise ImproperSizeError("More entries in dt_colnames than columns in data_table.") named_columns = 0 for idx in range(num_col_names): colname = dt_colnames[idx] diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 6d85013f..ef6eed9a 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -40,12 +40,8 @@ def __eq__(self, other): or not np.isclose(value, other_value, rtol=1e-5) ): return False - elif isinstance(value, list) and all( - isinstance(i, np.ndarray) for i in value - ): - if not all( - np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value) - ): + elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): + if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): return False else: if value != other_value: @@ -54,11 +50,7 @@ def __eq__(self, other): def __add__(self, other): summed = deepcopy(self) - if ( - isinstance(other, int) - or isinstance(other, float) - or isinstance(other, np.ndarray) - ): + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): summed.on_tth[1] = self.on_tth[1] + other summed.on_q[1] = self.on_q[1] + other elif not isinstance(other, Diffraction_object): @@ -75,11 +67,7 @@ def __add__(self, other): def __radd__(self, other): summed = deepcopy(self) - if ( - isinstance(other, int) - or isinstance(other, float) - or isinstance(other, np.ndarray) - ): + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): summed.on_tth[1] = self.on_tth[1] + other summed.on_q[1] = self.on_q[1] + other elif not isinstance(other, Diffraction_object): @@ -96,17 +84,11 @@ def __radd__(self, other): def __sub__(self, other): subtracted = deepcopy(self) - if ( - isinstance(other, int) - or isinstance(other, float) - or isinstance(other, np.ndarray) - ): + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): subtracted.on_tth[1] = self.on_tth[1] - other subtracted.on_q[1] = self.on_q[1] - other elif not isinstance(other, Diffraction_object): - raise TypeError( - f"I only know how to subtract two Scattering_object objects" - ) + raise TypeError(f"I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may subtract them using the self.add method and" @@ -119,17 +101,11 @@ def __sub__(self, other): def __rsub__(self, other): subtracted = deepcopy(self) - if ( - isinstance(other, int) - or isinstance(other, float) - or isinstance(other, np.ndarray) - ): + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): subtracted.on_tth[1] = other - self.on_tth[1] subtracted.on_q[1] = other - self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError( - f"I only know how to subtract two Scattering_object objects" - ) + raise TypeError(f"I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may subtract them using the self.add method and" @@ -142,17 +118,11 @@ def __rsub__(self, other): def __mul__(self, other): multiplied = deepcopy(self) - if ( - isinstance(other, int) - or isinstance(other, float) - or isinstance(other, np.ndarray) - ): + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError( - f"I only know how to multiply two Scattering_object objects" - ) + raise TypeError(f"I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may multiply them using the self.add method and" @@ -165,11 +135,7 @@ def __mul__(self, other): def __rmul__(self, other): multiplied = deepcopy(self) - if ( - isinstance(other, int) - or isinstance(other, float) - or isinstance(other, np.ndarray) - ): + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): @@ -184,17 +150,11 @@ def __rmul__(self, other): def __truediv__(self, other): divided = deepcopy(self) - if ( - isinstance(other, int) - or isinstance(other, float) - or isinstance(other, np.ndarray) - ): + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError( - f"I only know how to multiply two Scattering_object objects" - ) + raise TypeError(f"I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( f"objects are not on the same x-grid. You may multiply them using the self.add method and" @@ -207,11 +167,7 @@ def __truediv__(self, other): def __rtruediv__(self, other): divided = deepcopy(self) - if ( - isinstance(other, int) - or isinstance(other, float) - or isinstance(other, np.ndarray) - ): + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): @@ -252,13 +208,9 @@ def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): the q values in the independent array """ - self.qs = self._set_array_from_range( - begin_q, end_q, step_size=step_size, n_steps=n_steps - ) + self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) - def set_angles_from_range( - self, begin_angle, end_angle, step_size=None, n_steps=None - ): + def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): """ create an array of linear spaced angle-values @@ -280,9 +232,7 @@ def set_angles_from_range( the q values in the independent array """ - self.angles = self._set_array_from_range( - begin_angle, end_angle, step_size=step_size, n_steps=n_steps - ) + self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): if step_size is not None and n_steps is not None: @@ -319,7 +269,7 @@ def insert_scattering_quantity( ): f""" insert a new scattering quantity into the scattering object - + Parameters ---------- xarray array-like of floats @@ -330,7 +280,7 @@ def insert_scattering_quantity( the type of quantity for the independent variable from {*XQUANTITIES,} metadata: dict the metadata in the form of a dictionary of user-supplied key:value pairs - + Returns ------- diff --git a/src/diffpy/utils/scattering_objects/user_config.py b/src/diffpy/utils/scattering_objects/user_config.py index 06b83943..ca2cc004 100644 --- a/src/diffpy/utils/scattering_objects/user_config.py +++ b/src/diffpy/utils/scattering_objects/user_config.py @@ -20,9 +20,7 @@ def read_conf_file(): with open(conf_file, "r") as f: config = json.load(f) if not config.get("username") or not config.get("email"): - raise ValueError( - "Please provide a configuration file with username and email." - ) + raise ValueError("Please provide a configuration file with username and email.") return config.get("username"), config.get("email") return None, None diff --git a/src/diffpy/utils/tests/__init__.py b/src/diffpy/utils/tests/__init__.py index ccf8dcee..42a123f3 100644 --- a/src/diffpy/utils/tests/__init__.py +++ b/src/diffpy/utils/tests/__init__.py @@ -35,9 +35,9 @@ def testsuite(pattern=""): The TestSuite object containing the matching tests. """ import re - from os.path import dirname + from importlib.resources import as_file, files from itertools import chain - from importlib.resources import files, as_file + from os.path import dirname loader = unittest.defaultTestLoader ref = files(__package__) diff --git a/src/diffpy/utils/tests/debug.py b/src/diffpy/utils/tests/debug.py index 96d7c8dc..fed20421 100644 --- a/src/diffpy/utils/tests/debug.py +++ b/src/diffpy/utils/tests/debug.py @@ -24,6 +24,7 @@ if __name__ == "__main__": import sys + from diffpy.utils.tests import testsuite pattern = sys.argv[1] if len(sys.argv) > 1 else "" diff --git a/src/diffpy/utils/tests/run.py b/src/diffpy/utils/tests/run.py index cc824e7a..241caf11 100644 --- a/src/diffpy/utils/tests/run.py +++ b/src/diffpy/utils/tests/run.py @@ -23,7 +23,8 @@ # show warnings by default if not sys.warnoptions: - import os, warnings + import os + import warnings warnings.simplefilter("default") # also affect subprocesses diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 424565ea..f8e4de2b 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -223,9 +223,7 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): diffraction_object1 = Diffraction_object() diffraction_object2 = Diffraction_object() - diffraction_object1_attributes = [ - key for key in diffraction_object1.__dict__ if not key.startswith("_") - ] + diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] for i, attribute in enumerate(diffraction_object1_attributes): setattr(diffraction_object1, attribute, inputs1[i]) setattr(diffraction_object2, attribute, inputs2[i]) @@ -240,9 +238,7 @@ def test_dump(tmp_path): test.wavelength = 1.54 test.name = "test" test.scat_quantity = "x-ray" - test.insert_scattering_quantity( - x, y, "q", metadata={"thing1": 1, "thing2": "thing2"} - ) + test.insert_scattering_quantity(x, y, "q", metadata={"thing1": 1, "thing2": "thing2"}) test.dump(file, "q") with open(file, "r") as f: actual = f.read() diff --git a/src/diffpy/utils/tests/test_loaddata.py b/src/diffpy/utils/tests/test_loaddata.py index dc1ace0a..9399e71d 100644 --- a/src/diffpy/utils/tests/test_loaddata.py +++ b/src/diffpy/utils/tests/test_loaddata.py @@ -4,7 +4,9 @@ """ import unittest + import numpy + from diffpy.utils.parsers import loadData from diffpy.utils.tests.testhelpers import datafile @@ -14,7 +16,6 @@ ############################################################################## class TestLoadData(unittest.TestCase): - def test_loadData_default(self): """check loadData() with default options""" d2c = numpy.array([[3, 31], [4, 32], [5, 33]]) @@ -47,9 +48,7 @@ def test_loadData_headers(self): """check loadData() with headers options enabled""" hignore = ["# ", "// ", "["] # ignore lines beginning with these strings delimiter = ": " # what our data should be separated by - hdata = loadData( - loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore - ) + hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) # only fourteen lines of data are formatted properly assert len(hdata) == 14 # check the following are floats diff --git a/src/diffpy/utils/tests/test_resample.py b/src/diffpy/utils/tests/test_resample.py index 9ea265f6..fa9c7e70 100644 --- a/src/diffpy/utils/tests/test_resample.py +++ b/src/diffpy/utils/tests/test_resample.py @@ -1,5 +1,5 @@ -import pytest import numpy as np +import pytest from diffpy.utils.parsers.resample import wsinterp @@ -9,7 +9,6 @@ def test_wsinterp(): # Check known points are unchanged by interpolation # FIXME: if another SW interp function exists, run comparisons for interpolated points - # Sampling rate ssr = 44100**-1 # Standard sampling rate for human-hearable frequencies t = ssr diff --git a/src/diffpy/utils/tests/test_serialization.py b/src/diffpy/utils/tests/test_serialization.py index 832c75de..850ce46b 100644 --- a/src/diffpy/utils/tests/test_serialization.py +++ b/src/diffpy/utils/tests/test_serialization.py @@ -1,15 +1,11 @@ -from diffpy.utils.parsers import serialize_data, deserialize_data -from diffpy.utils.parsers import loadData -from diffpy.utils.tests.testhelpers import datafile - -from diffpy.utils.parsers.custom_exceptions import ( - UnsupportedTypeError, - ImproperSizeError, -) - import os -import pytest + import numpy +import pytest + +from diffpy.utils.parsers import deserialize_data, loadData, serialize_data +from diffpy.utils.parsers.custom_exceptions import ImproperSizeError, UnsupportedTypeError +from diffpy.utils.tests.testhelpers import datafile tests_dir = os.path.dirname(os.path.abspath(locals().get("__file__", "file.py"))) @@ -29,16 +25,12 @@ def test_load_multiple(tmp_path): generated_data = None for hfname in tlm_list: # gather data using loadData - headerfile = os.path.normpath( - os.path.join(tests_dir, "testdata", "dbload", hfname) - ) + headerfile = os.path.normpath(os.path.join(tests_dir, "testdata", "dbload", hfname)) hdata = loadData(headerfile, headers=True) data_table = loadData(headerfile) # check path extraction - generated_data = serialize_data( - headerfile, hdata, data_table, dt_colnames=["r", "gr"], show_path=True - ) + generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=["r", "gr"], show_path=True) assert headerfile == os.path.normpath(generated_data[hfname].pop("path")) # rerun without path information and save to file @@ -70,9 +62,7 @@ def test_exceptions(): # various dt_colnames inputs with pytest.raises(ImproperSizeError): - serialize_data( - loadfile, hdata, data_table, dt_colnames=["one", "two", "three is too many"] - ) + serialize_data(loadfile, hdata, data_table, dt_colnames=["one", "two", "three is too many"]) # check proper output normal = serialize_data(loadfile, hdata, data_table, dt_colnames=["r", "gr"]) data_name = list(normal.keys())[0] @@ -80,34 +70,20 @@ def test_exceptions(): gr_list = normal[data_name]["gr"] # three equivalent ways to denote no column names missing_parameter = serialize_data(loadfile, hdata, data_table, show_path=False) - empty_parameter = serialize_data( - loadfile, hdata, data_table, show_path=False, dt_colnames=[] - ) - none_entry_parameter = serialize_data( - loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None] - ) + empty_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[]) + none_entry_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None]) # check equivalence assert missing_parameter == empty_parameter assert missing_parameter == none_entry_parameter assert numpy.allclose(missing_parameter[data_name]["data table"], data_table) # extract a single column - r_extract = serialize_data( - loadfile, hdata, data_table, show_path=False, dt_colnames=["r"] - ) - gr_extract = serialize_data( - loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "gr"] - ) - incorrect_r_extract = serialize_data( - loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "r"] - ) + r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=["r"]) + gr_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "gr"]) + incorrect_r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "r"]) # check proper columns extracted - assert numpy.allclose( - gr_extract[data_name]["gr"], incorrect_r_extract[data_name]["r"] - ) + assert numpy.allclose(gr_extract[data_name]["gr"], incorrect_r_extract[data_name]["r"]) assert "r" not in gr_extract[data_name] - assert ( - "gr" not in r_extract[data_name] and "gr" not in incorrect_r_extract[data_name] - ) + assert "gr" not in r_extract[data_name] and "gr" not in incorrect_r_extract[data_name] # check correct values extracted assert numpy.allclose(r_extract[data_name]["r"], r_list) assert numpy.allclose(gr_extract[data_name]["gr"], gr_list) diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index eb73a34b..2a105f0f 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -85,17 +85,13 @@ def _run_tests(inputs, expected): @pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) -def test_get_user_info_with_home_conf_file( - monkeypatch, inputs, expected, user_filesystem -): +def test_get_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): _setup_dirs(monkeypatch, user_filesystem) _run_tests(inputs, expected) @pytest.mark.parametrize("inputs, expected", params_user_info_with_local_conf_file) -def test_get_user_info_with_local_conf_file( - monkeypatch, inputs, expected, user_filesystem -): +def test_get_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): home_dir = _setup_dirs(monkeypatch, user_filesystem) local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: @@ -105,12 +101,8 @@ def test_get_user_info_with_local_conf_file( _run_tests(inputs, expected) -@pytest.mark.parametrize( - "inputsa, inputsb, expected", params_user_info_with_no_home_conf_file -) -def test_get_user_info_with_no_home_conf_file( - monkeypatch, inputsa, inputsb, expected, user_filesystem -): +@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) +def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): _setup_dirs(monkeypatch, user_filesystem) os.remove(Path().home() / "diffpyconfig.json") inp_iter = iter(inputsb) @@ -120,12 +112,8 @@ def test_get_user_info_with_no_home_conf_file( assert confile.is_file() -@pytest.mark.parametrize( - "inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs -) -def test_get_user_info_no_conf_file_no_inputs( - monkeypatch, inputsa, inputsb, expected, user_filesystem -): +@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) +def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): _setup_dirs(monkeypatch, user_filesystem) os.remove(Path().home() / "diffpyconfig.json") inp_iter = iter(inputsb) diff --git a/src/diffpy/utils/tests/testdata/dbload/e1.gr b/src/diffpy/utils/tests/testdata/dbload/e1.gr index ca0ae586..f5d83a51 100644 --- a/src/diffpy/utils/tests/testdata/dbload/e1.gr +++ b/src/diffpy/utils/tests/testdata/dbload/e1.gr @@ -6,4 +6,4 @@ qmax = 10 0 0 1 0 2 0 -3 0 \ No newline at end of file +3 0 diff --git a/src/diffpy/utils/tests/testdata/dbload/e2.gr b/src/diffpy/utils/tests/testdata/dbload/e2.gr index eb2fca29..b5b4f356 100644 --- a/src/diffpy/utils/tests/testdata/dbload/e2.gr +++ b/src/diffpy/utils/tests/testdata/dbload/e2.gr @@ -6,4 +6,4 @@ qmax = 11 0 1 1 2 2 3 -3 4 \ No newline at end of file +3 4 diff --git a/src/diffpy/utils/tests/testdata/dbload/e3.gr b/src/diffpy/utils/tests/testdata/dbload/e3.gr index 839eaf79..bbd16b27 100644 --- a/src/diffpy/utils/tests/testdata/dbload/e3.gr +++ b/src/diffpy/utils/tests/testdata/dbload/e3.gr @@ -6,4 +6,4 @@ qmax = 12 0 0 1 5 2 4 -3 3 \ No newline at end of file +3 3 diff --git a/src/diffpy/utils/tests/testdata/generated_db.json b/src/diffpy/utils/tests/testdata/generated_db.json index 4d6ec119..18803b7e 100644 --- a/src/diffpy/utils/tests/testdata/generated_db.json +++ b/src/diffpy/utils/tests/testdata/generated_db.json @@ -23,4 +23,4 @@ "rmax": 12.0, "rmin": 2.0 } -} \ No newline at end of file +} diff --git a/src/diffpy/utils/tests/testdata/loaddatawithheaders.txt b/src/diffpy/utils/tests/testdata/loaddatawithheaders.txt index 88e11525..10bf71d3 100644 --- a/src/diffpy/utils/tests/testdata/loaddatawithheaders.txt +++ b/src/diffpy/utils/tests/testdata/loaddatawithheaders.txt @@ -10029,4 +10029,4 @@ a:b:c 99.97 0.00194891 99.98 0.00192961 99.99 0.00186482 -100 0.0017609 \ No newline at end of file +100 0.0017609 diff --git a/src/diffpy/utils/tests/testdata/loadfile.txt b/src/diffpy/utils/tests/testdata/loadfile.txt index 326c9492..6d83ac2a 100644 --- a/src/diffpy/utils/tests/testdata/loadfile.txt +++ b/src/diffpy/utils/tests/testdata/loadfile.txt @@ -22,4 +22,4 @@ stru_str = baddata 3 0.00005 4 0.00001 5 -0.00003 -6 -0.00006 \ No newline at end of file +6 -0.00006 diff --git a/src/diffpy/utils/tests/testdata/strumining.json b/src/diffpy/utils/tests/testdata/strumining.json index 1466ea51..b1c57d59 100644 --- a/src/diffpy/utils/tests/testdata/strumining.json +++ b/src/diffpy/utils/tests/testdata/strumining.json @@ -14,4 +14,4 @@ "max_num": 10, "rmax": 10, "rmin": 0 -} \ No newline at end of file +} diff --git a/src/diffpy/utils/tests/testdata/targetjson.json b/src/diffpy/utils/tests/testdata/targetjson.json index 4d6ec119..18803b7e 100644 --- a/src/diffpy/utils/tests/testdata/targetjson.json +++ b/src/diffpy/utils/tests/testdata/targetjson.json @@ -23,4 +23,4 @@ "rmax": 12.0, "rmin": 2.0 } -} \ No newline at end of file +} diff --git a/src/diffpy/utils/tests/testdata/wrong.type b/src/diffpy/utils/tests/testdata/wrong.type index 81750b96..98232c64 100644 --- a/src/diffpy/utils/tests/testdata/wrong.type +++ b/src/diffpy/utils/tests/testdata/wrong.type @@ -1 +1 @@ -{ \ No newline at end of file +{ diff --git a/src/diffpy/utils/tests/testhelpers.py b/src/diffpy/utils/tests/testhelpers.py index 064b54b2..aaa2a519 100644 --- a/src/diffpy/utils/tests/testhelpers.py +++ b/src/diffpy/utils/tests/testhelpers.py @@ -20,7 +20,7 @@ def datafile(filename): - from importlib.resources import files, as_file + from importlib.resources import as_file, files ref = files(__package__) / ("testdata/" + filename) with as_file(ref) as rv: diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 6be9433a..2b093037 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -82,9 +82,7 @@ def _create_global_config(args): ).strip() or args.get("email", "") return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: - f.write( - json.dumps({"username": stringify(username), "email": stringify(email)}) - ) + f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) return return_bool @@ -113,9 +111,7 @@ def get_user_info(args=None): if global_config is None and local_config is None: config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") - config = _sorted_merge( - clean_dict(global_config), clean_dict(local_config), clean_dict(args) - ) + config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) if config_bool is False: os.remove(Path().home() / "diffpyconfig.json") config = {"username": "", "email": ""} diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index aae752b5..7e9d1130 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -25,9 +25,7 @@ __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] import os.path - -from importlib.resources import files, as_file - +from importlib.resources import as_file, files # obtain version information from the version.cfg file cp = dict(version="", date="", commit="", timestamp="0") @@ -41,9 +39,7 @@ fcfg = os.devnull with open(fcfg) as fp: kwords = [ - [w.strip() for w in line.split(" = ", 1)] - for line in fp - if line[:1].isalpha() and " = " in line + [w.strip() for w in line.split(" = ", 1)] for line in fp if line[:1].isalpha() and " = " in line ] assert all(w[0] in cp for w in kwords), "received unrecognized keyword" cp.update(kwords) From 307ca13236ab0e69d01fbb8d6022863e9e2f3ce6 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 10:45:39 -0400 Subject: [PATCH 116/696] addition of ci into pre-commit --- .pre-commit-config.yaml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a8f4de5..70b929e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,14 @@ default_language_version: python: python3 +ci: + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: 'pre-commit-autoupdate' + autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 @@ -25,10 +34,10 @@ repos: rev: 0.6.1 hooks: - id: nbstripout - - repo: local + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 hooks: - - id: prevent-commit-to-main + - id: no-commit-to-branch name: Prevent Commit to Main Branch - entry: ./prevent_commit_to_main.sh - language: script - stages: [commit] + args: ["--branch", "main"] + stages: [pre-commit] From f7839908af21b1aee05bb6ec6e8ecb5d3fb300be Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 13:37:06 -0400 Subject: [PATCH 117/696] move modules to top of file --- conda-recipe/run_test.py | 3 ++- devutils/makesdist | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/conda-recipe/run_test.py b/conda-recipe/run_test.py index 51a59789..946a0960 100644 --- a/conda-recipe/run_test.py +++ b/conda-recipe/run_test.py @@ -3,8 +3,9 @@ import pathlib import sys +import diffpy.utils.tests + sys.path.append((pathlib.Path.cwd().parent.absolute() / "src").as_posix()) -import diffpy.utils.tests assert diffpy.utils.tests.test().wasSuccessful() diff --git a/devutils/makesdist b/devutils/makesdist index e77257ea..8b0a2179 100755 --- a/devutils/makesdist +++ b/devutils/makesdist @@ -11,10 +11,11 @@ import subprocess import sys import tarfile +from setup import FALLBACK_VERSION, versiondata + BASEDIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) sys.path.insert(0, BASEDIR) -from setup import FALLBACK_VERSION, versiondata timestamp = versiondata.getint("DEFAULT", "timestamp") From 5fec341cd29fdae552e36d90aefa738046e654a0 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 13:38:15 -0400 Subject: [PATCH 118/696] remove unused import --- src/diffpy/utils/parsers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index f8d57c3b..a2104181 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -17,7 +17,7 @@ """ from .loaddata import loadData -from .resample import resample, wsinterp +from .resample import resample from .serialization import deserialize_data, serialize_data # silence the pyflakes syntax checker From d16b6802980b1b406021ee970d414cdd22762f43 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 13:40:29 -0400 Subject: [PATCH 119/696] fix line length --- src/diffpy/utils/parsers/loaddata.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index f659db7c..d2df3048 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -27,10 +27,12 @@ def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwar filename Name of the file we want to load data from. minrows: int - Minimum number of rows in the first data block. All rows must have the same number of floating point values. + Minimum number of rows in the first data block. All rows must have the same number of floating + point values. headers: bool when False (defualt), the function returns a numpy array of the data in the data block. - When True, the function instead returns a dictionary of parameters and their corresponding values parsed from + When True, the function instead returns a dictionary of parameters and their corresponding + values parsed from header (information prior the data block). See hdel and hignore for options to help with parsing header information. hdel: str From bcc7d9a14fa8abc3bd9065fc957b61e8edc0f1c8 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 13:50:19 -0400 Subject: [PATCH 120/696] remove bare by adding --- src/diffpy/utils/parsers/loaddata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index d2df3048..0e25fab0 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -280,7 +280,7 @@ def _findDataBlocks(self): for i, w in enumerate(self._words): try: values[i] = float(w) - except: + except ValueError: lw.ok[i] = False # prune lines that have a non-float values: lw.values = values @@ -336,7 +336,7 @@ def isfloat(s): try: float(s) return True - except: + except ValueError: pass return False From 8f660e453b2753544b7d4699049ee31871c39c93 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 13:52:44 -0400 Subject: [PATCH 121/696] fix line length --- src/diffpy/utils/parsers/resample.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index b68dbec8..95e96601 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -23,7 +23,8 @@ def wsinterp(x, xp, fp, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation. - This uses the Whittaker-Shannon interpolation formula to interpolate the value of fp (array), which is defined over + This uses the Whittaker-Shannon interpolation formula to interpolate the value of fp (array), + which is defined over xp (array), at x (array or float). Parameters @@ -35,7 +36,8 @@ def wsinterp(x, xp, fp, left=None, right=None): fp: ndarray Function to be interpolated. left: float - If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, set fp for x < xp[0] + If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, + set fp for x < xp[0] to fp evaluated at xp[-1]. right: float If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for From a4ff03554e49843164182e3bbd0d02b43ab4e07f Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 13:53:07 -0400 Subject: [PATCH 122/696] remove extra # --- src/diffpy/utils/parsers/resample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index 95e96601..032c19b0 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -115,7 +115,7 @@ def resample(r, s, dr): # spad = numpy.concatenate([s,spad]) # rnew = numpy.arange(0, rpad[-1], dr) # snew = numpy.zeros_like(rnew) - ## Accomodate for the fact that r[0] might not be 0 + # Accomodate for the fact that r[0] might not be 0 # u = (rnew-r[0]) / dr0 # for n in range(len(spad)): # snew += spad[n] * numpy.sinc(u - n) From 0207384987a6887402ab5fa3b68c2be8a8550b41 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 14:52:27 -0400 Subject: [PATCH 123/696] remove prevent_commit_to_main.sh --- prevent_commit_to_main.sh | 9 --------- 1 file changed, 9 deletions(-) delete mode 100755 prevent_commit_to_main.sh diff --git a/prevent_commit_to_main.sh b/prevent_commit_to_main.sh deleted file mode 100755 index 5237b16a..00000000 --- a/prevent_commit_to_main.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -##!"C:\\Program Files\\Git\\usr\\bin\\bash.exe" - -branch="$(git rev-parse --abbrev-ref HEAD)" - -if [ "$branch" = "main" ]; then -echo "You are on the main branch. Committing to the main branch is not allowed." -exit 1 -fi From c1dfe3f23a95a9de8315b2391cd1464efe994f84 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 15:03:33 -0400 Subject: [PATCH 124/696] fix line length --- src/diffpy/utils/parsers/serialization.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index ab857b0a..e85b588f 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -47,13 +47,14 @@ def serialize_data( data_table: list or ndarray Data table. dt_colnames: list - Names of each column in data_table. Every name in data_table_cols will be put into the Dictionary as a key with - a value of that column in data_table (stored as a List). Put None for columns without names. If dt_cols has less - non-None entries than columns in data_table, the pair {'data table': data_table} will be put in the dictionary. + Names of each column in data_table. Every name in data_table_cols will be put into the Dictionary + as a key with a value of that column in data_table (stored as a List). Put None for columns + without names. If dt_cols has less non-None entries than columns in data_table, + the pair {'data table': data_table} will be put in the dictionary. (Default None: only entry {'data table': data_table} will be added to dictionary.) show_path: bool - include a path element in the database entry (default True). If 'path' is not included in hddata, extract path - from filename. + include a path element in the database entry (default True). If 'path' is not included in hddata, + extract path from filename. serial_file Serial language file to dump dictionary into. If None (defualt), no dumping will occur. From 8e92998560e78148305db9f6b262295e945bbdb2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 15:04:44 -0400 Subject: [PATCH 125/696] change f-string to string --- src/diffpy/utils/parsers/serialization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index e85b588f..a0bec92c 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -197,7 +197,7 @@ def deserialize_data(filename, filetype=None): if len(return_dict) == 0: warnings.warn( - f"Loaded dictionary is empty. Possibly due to improper file type.", + "Loaded dictionary is empty. Possibly due to improper file type.", RuntimeWarning, ) From 8c24b3f815001758549b69de1f95fbc47993290a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 15:25:42 -0400 Subject: [PATCH 126/696] remove unused variable from testing --- src/diffpy/utils/tests/test_serialization.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/diffpy/utils/tests/test_serialization.py b/src/diffpy/utils/tests/test_serialization.py index 850ce46b..7194cba5 100644 --- a/src/diffpy/utils/tests/test_serialization.py +++ b/src/diffpy/utils/tests/test_serialization.py @@ -93,18 +93,3 @@ def test_exceptions(): no_dt = serialize_data(nodt, nodt_hdata, nodt_dt, show_path=False) nodt_data_name = list(no_dt.keys())[0] assert numpy.allclose(no_dt[nodt_data_name]["data table"], nodt_dt) - - # ensure user is warned when columns are overwritten - hdata = loadData(warningfile, headers=True) - data_table = loadData(warningfile) - with pytest.warns(RuntimeWarning) as record: - data = serialize_data( - warningfile, - hdata, - data_table, - show_path=False, - dt_colnames=["c1", "c2", "c3"], - ) - assert len(record) == 4 - for msg in record: - assert "overwritten" in msg.message.args[0] From 6b169937326888226f419edebf4d8e66d2ecec52 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 15:26:37 -0400 Subject: [PATCH 127/696] remove unused import --- src/diffpy/utils/tests/test_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 2a105f0f..9236e65b 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -1,4 +1,3 @@ -import argparse import json import os from pathlib import Path From d2f529058eefada0d057285c636c79970f3dd062 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 15:28:24 -0400 Subject: [PATCH 128/696] remove unused variable from testing --- src/diffpy/utils/tests/test_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 9236e65b..cc8ce74f 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -91,7 +91,6 @@ def test_get_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_f @pytest.mark.parametrize("inputs, expected", params_user_info_with_local_conf_file) def test_get_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): - home_dir = _setup_dirs(monkeypatch, user_filesystem) local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: json.dump(local_config_data, f) From 74947ade9125f4f946700add7c428705fefbfc24 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:58:07 +0000 Subject: [PATCH 129/696] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.6.0) - https://github.com/ambv/black → https://github.com/psf/black - [github.com/psf/black: 23.3.0 → 24.4.2](https://github.com/psf/black/compare/23.3.0...24.4.2) - [github.com/pycqa/flake8: 6.0.0 → 7.0.0](https://github.com/pycqa/flake8/compare/6.0.0...7.0.0) - [github.com/pycqa/isort: 5.12.0 → 5.13.2](https://github.com/pycqa/isort/compare/5.12.0...5.13.2) - [github.com/kynan/nbstripout: 0.6.1 → 0.7.1](https://github.com/kynan/nbstripout/compare/0.6.1...0.7.1) --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a8f4de5..9bea36b7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,27 +2,27 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace exclude: '\.(rst|txt)$' - - repo: https://github.com/ambv/black - rev: 23.3.0 + - repo: https://github.com/psf/black + rev: 24.4.2 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 7.0.0 hooks: - id: flake8 - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/kynan/nbstripout - rev: 0.6.1 + rev: 0.7.1 hooks: - id: nbstripout - repo: local From b30b2559249d3bd028a33b903593f047d4f5bd8d Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 3 Jun 2024 18:24:06 -0400 Subject: [PATCH 130/696] adding code back in w/o defining as variable --- src/diffpy/utils/tests/test_serialization.py | 15 +++++++++++++++ src/diffpy/utils/tests/test_tools.py | 1 + 2 files changed, 16 insertions(+) diff --git a/src/diffpy/utils/tests/test_serialization.py b/src/diffpy/utils/tests/test_serialization.py index 7194cba5..bbe6be24 100644 --- a/src/diffpy/utils/tests/test_serialization.py +++ b/src/diffpy/utils/tests/test_serialization.py @@ -93,3 +93,18 @@ def test_exceptions(): no_dt = serialize_data(nodt, nodt_hdata, nodt_dt, show_path=False) nodt_data_name = list(no_dt.keys())[0] assert numpy.allclose(no_dt[nodt_data_name]["data table"], nodt_dt) + + # ensure user is warned when columns are overwritten + hdata = loadData(warningfile, headers=True) + data_table = loadData(warningfile) + with pytest.warns(RuntimeWarning) as record: + serialize_data( + warningfile, + hdata, + data_table, + show_path=False, + dt_colnames=["c1", "c2", "c3"], + ) + assert len(record) == 4 + for msg in record: + assert "overwritten" in msg.message.args[0] diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index cc8ce74f..4f637480 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -91,6 +91,7 @@ def test_get_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_f @pytest.mark.parametrize("inputs, expected", params_user_info_with_local_conf_file) def test_get_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): + _setup_dirs(monkeypatch, user_filesystem) local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: json.dump(local_config_data, f) From c13f05ddbb834bb9bd926bc1fc9f33427bcdce19 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 4 Jun 2024 12:52:26 -0400 Subject: [PATCH 131/696] remove unused imports --- src/diffpy/utils/scattering_objects/diffraction_objects.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index ef6eed9a..7b4b11a3 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -1,8 +1,6 @@ -import uuid from copy import deepcopy import numpy as np -from numpy import array QQUANTITIES = ["q"] ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] From 06444b396482c12f38972af7e53f0e7da583f284 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 4 Jun 2024 12:53:53 -0400 Subject: [PATCH 132/696] fix line length --- src/diffpy/utils/scattering_objects/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 7b4b11a3..84e441aa 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -170,8 +170,8 @@ def __rtruediv__(self, other): divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"Diffraction objects are not on the same x-grid. You may multiply them using the self.add method and" - f"specifying how to handle the mismatch." + f"Diffraction objects are not on the same x-grid. You may multiply them using the self.add" + f"method and specifying how to handle the mismatch." ) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] From b774a6c363669faea113df55722ae719f4b5e171 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 4 Jun 2024 12:56:17 -0400 Subject: [PATCH 133/696] replace f-string with string --- .../scattering_objects/diffraction_objects.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 84e441aa..284ce853 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -52,11 +52,11 @@ def __add__(self, other): summed.on_tth[1] = self.on_tth[1] + other summed.on_q[1] = self.on_q[1] + other elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to sum two Diffraction_object objects") + raise TypeError("I only know how to sum two Diffraction_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"objects are not on the same x-grid. You may add them using the self.add method and" - f"specifying how to handle the mismatch." + "objects are not on the same x-grid. You may add them using the self.add method and" + "specifying how to handle the mismatch." ) else: summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] @@ -69,11 +69,11 @@ def __radd__(self, other): summed.on_tth[1] = self.on_tth[1] + other summed.on_q[1] = self.on_q[1] + other elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to sum two Scattering_object objects") + raise TypeError("I only know how to sum two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"objects are not on the same x-grid. You may add them using the self.add method and" - f"specifying how to handle the mismatch." + "objects are not on the same x-grid. You may add them using the self.add method and" + "specifying how to handle the mismatch." ) else: summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] @@ -86,11 +86,11 @@ def __sub__(self, other): subtracted.on_tth[1] = self.on_tth[1] - other subtracted.on_q[1] = self.on_q[1] - other elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to subtract two Scattering_object objects") + raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"objects are not on the same x-grid. You may subtract them using the self.add method and" - f"specifying how to handle the mismatch." + "objects are not on the same x-grid. You may subtract them using the self.add method and" + "specifying how to handle the mismatch." ) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] @@ -103,11 +103,11 @@ def __rsub__(self, other): subtracted.on_tth[1] = other - self.on_tth[1] subtracted.on_q[1] = other - self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to subtract two Scattering_object objects") + raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"objects are not on the same x-grid. You may subtract them using the self.add method and" - f"specifying how to handle the mismatch." + "objects are not on the same x-grid. You may subtract them using the self.add method and" + "specifying how to handle the mismatch." ) else: subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] @@ -120,11 +120,11 @@ def __mul__(self, other): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to multiply two Scattering_object objects") + raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"objects are not on the same x-grid. You may multiply them using the self.add method and" - f"specifying how to handle the mismatch." + "objects are not on the same x-grid. You may multiply them using the self.add method and" + "specifying how to handle the mismatch." ) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] @@ -138,8 +138,8 @@ def __rmul__(self, other): multiplied.on_q[1] = other * self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"objects are not on the same x-grid. You may multiply them using the self.add method and" - f"specifying how to handle the mismatch." + "objects are not on the same x-grid. You may multiply them using the self.add method and" + "specifying how to handle the mismatch." ) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] @@ -152,11 +152,11 @@ def __truediv__(self, other): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif not isinstance(other, Diffraction_object): - raise TypeError(f"I only know how to multiply two Scattering_object objects") + raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"objects are not on the same x-grid. You may multiply them using the self.add method and" - f"specifying how to handle the mismatch." + "objects are not on the same x-grid. You may multiply them using the self.add method and" + "specifying how to handle the mismatch." ) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] @@ -170,8 +170,8 @@ def __rtruediv__(self, other): divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): raise RuntimeError( - f"Diffraction objects are not on the same x-grid. You may multiply them using the self.add" - f"method and specifying how to handle the mismatch." + "Diffraction objects are not on the same x-grid. You may multiply them using the self.add" + "method and specifying how to handle the mismatch." ) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] From 746d5e6d65c59c88c6c5fd4bbcc99f16b7bdac9c Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 4 Jun 2024 13:00:06 -0400 Subject: [PATCH 134/696] add space after , --- src/diffpy/utils/scattering_objects/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 284ce853..d93e36ed 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -275,7 +275,7 @@ def insert_scattering_quantity( yarray array-like of floats the dependent variable array xtype string - the type of quantity for the independent variable from {*XQUANTITIES,} + the type of quantity for the independent variable from {*XQUANTITIES, } metadata: dict the metadata in the form of a dictionary of user-supplied key:value pairs From cefec154b57a9a4d60a5a474eaafff7d092ed44a Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 6 Jun 2024 03:25:22 -0400 Subject: [PATCH 135/696] tool added to update metadata with package info --- .../utils/tests/test_diffraction_objects.py | 3 +- src/diffpy/utils/tests/test_tools.py | 17 ++++++++++- src/diffpy/utils/tools.py | 29 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index f8e4de2b..6c2afd01 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -244,7 +244,8 @@ def test_dump(tmp_path): actual = f.read() expected = ( "[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" - "thing2 = thing2\n\n#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" + "thing2 = thing2\npackage_info = [('package1', '1.2.3'), ('diffpy.utils', '3.3.0')]\n" + "creation_time = 2024-05-30 12:30:01\n\n#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" "1.000000000000000000e+00 1.000000000000000000e+00\n2.000000000000000000e+00 2.000000000000000000e+00\n" "3.000000000000000000e+00 3.000000000000000000e+00\n4.000000000000000000e+00 4.000000000000000000e+00\n" "5.000000000000000000e+00 5.000000000000000000e+00\n" diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 4f637480..72d928b9 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -1,10 +1,11 @@ +import importlib.metadata import json import os from pathlib import Path import pytest -from diffpy.utils.tools import get_user_info +from diffpy.utils.tools import get_user_info, get_package_info def _setup_dirs(monkeypatch, user_filesystem): @@ -120,3 +121,17 @@ def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, exp _run_tests(inputsa, expected) confile = Path().home() / "diffpyconfig.json" assert confile.exists() is False + +params_package_info = [ + (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), + (["package1", None], {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), + (["package1", {"thing1": 1}], {"thing1": 1, "package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), + (["package1", {"package_info": {"package1": "1.1.0", "package2": "3.4.5"}}], + {"package_info": {"package1": "1.2.3", "package2": "3.4.5", "diffpy.utils": "3.3.0"}}), +] +@pytest.mark.parametrize("inputs, expected", params_package_info) +def test_get_package_info(monkeypatch, inputs, expected): + monkeypatch.setattr(importlib.metadata, "version", + lambda package_name: "3.3.0" if package_name == "diffpy.utils" else "1.2.3") + actual_metadata = get_package_info(inputs[0], metadata=inputs[1]) + assert actual_metadata == expected diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 2b093037..36047edc 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,3 +1,4 @@ +import importlib import json import os from copy import copy @@ -117,3 +118,31 @@ def get_user_info(args=None): config = {"username": "", "email": ""} return config + +def get_package_info(package_names, metadata=None): + """ + fetches package version and updates it into (given) metadata + + package info stored in metadata as {'package_info': {'package_name': 'version_number'}} + + ---------- + package_name : str or list + the name of the package(s) to retrieve the version number for + metadata : dict + the dictionary to store the package info. If not provided, a new dictionary will be created + + Returns + ------- + the updated metadata dict with package info inserted + + """ + if metadata is None: + metadata = {} + if isinstance(package_names, str): + package_names = [package_names] + package_names.append("diffpy.utils") + pkg_info = metadata.get("package_info", {}) + for package in package_names: + pkg_info.update({package: importlib.metadata.version(package)}) + metadata["package_info"] = pkg_info + return metadata From e1eb74179197337cef41eb4c244a0817d4020dcb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 07:26:43 +0000 Subject: [PATCH 136/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tests/test_tools.py | 16 +++++++++++----- src/diffpy/utils/tools.py | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/tests/test_tools.py b/src/diffpy/utils/tests/test_tools.py index 72d928b9..0a42332f 100644 --- a/src/diffpy/utils/tests/test_tools.py +++ b/src/diffpy/utils/tests/test_tools.py @@ -5,7 +5,7 @@ import pytest -from diffpy.utils.tools import get_user_info, get_package_info +from diffpy.utils.tools import get_package_info, get_user_info def _setup_dirs(monkeypatch, user_filesystem): @@ -122,16 +122,22 @@ def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, exp confile = Path().home() / "diffpyconfig.json" assert confile.exists() is False + params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), (["package1", None], {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), (["package1", {"thing1": 1}], {"thing1": 1, "package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), - (["package1", {"package_info": {"package1": "1.1.0", "package2": "3.4.5"}}], - {"package_info": {"package1": "1.2.3", "package2": "3.4.5", "diffpy.utils": "3.3.0"}}), + ( + ["package1", {"package_info": {"package1": "1.1.0", "package2": "3.4.5"}}], + {"package_info": {"package1": "1.2.3", "package2": "3.4.5", "diffpy.utils": "3.3.0"}}, + ), ] + + @pytest.mark.parametrize("inputs, expected", params_package_info) def test_get_package_info(monkeypatch, inputs, expected): - monkeypatch.setattr(importlib.metadata, "version", - lambda package_name: "3.3.0" if package_name == "diffpy.utils" else "1.2.3") + monkeypatch.setattr( + importlib.metadata, "version", lambda package_name: "3.3.0" if package_name == "diffpy.utils" else "1.2.3" + ) actual_metadata = get_package_info(inputs[0], metadata=inputs[1]) assert actual_metadata == expected diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 36047edc..9687211f 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -119,6 +119,7 @@ def get_user_info(args=None): return config + def get_package_info(package_names, metadata=None): """ fetches package version and updates it into (given) metadata From dc2c5e9ffb4635f62623b6b16e72aab1f3cd6090 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 6 Jun 2024 04:26:56 -0400 Subject: [PATCH 137/696] dump now adds creation time and diffpy utils version number to output file --- requirements/test.txt | 2 ++ .../utils/scattering_objects/diffraction_objects.py | 7 ++++++- src/diffpy/utils/tests/test_diffraction_objects.py | 12 +++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/requirements/test.txt b/requirements/test.txt index 6f9ccf84..72644779 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -3,3 +3,5 @@ pytest codecov coverage pytest-env +pytest-mock +freezegun diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index d93e36ed..81e6e678 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -1,7 +1,10 @@ from copy import deepcopy - +import datetime +import importlib.metadata import numpy as np +from diffpy.utils.tools import get_package_info + QQUANTITIES = ["q"] ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] DQUANTITIES = ["d", "dspace"] @@ -463,6 +466,8 @@ def dump(self, filepath, xtype=None): data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) else: print(f"WARNING: cannot handle the xtype '{xtype}'") + self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) + self.metadata["creation_time"] = datetime.datetime.now() with open(filepath, "w") as f: f.write( diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 6c2afd01..72d1be06 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -1,3 +1,4 @@ +from freezegun import freeze_time from pathlib import Path import numpy as np @@ -230,7 +231,7 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (diffraction_object1 == diffraction_object2) == expected -def test_dump(tmp_path): +def test_dump(tmp_path, mocker): x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) directory = Path(tmp_path) file = directory / "testfile" @@ -238,14 +239,15 @@ def test_dump(tmp_path): test.wavelength = 1.54 test.name = "test" test.scat_quantity = "x-ray" - test.insert_scattering_quantity(x, y, "q", metadata={"thing1": 1, "thing2": "thing2"}) - test.dump(file, "q") + test.insert_scattering_quantity(x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {'package2': '3.4.5'}}) + with mocker.patch('importlib.metadata.version', return_value='3.3.0'), freeze_time("2012-01-14"): + test.dump(file, "q") with open(file, "r") as f: actual = f.read() expected = ( "[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" - "thing2 = thing2\npackage_info = [('package1', '1.2.3'), ('diffpy.utils', '3.3.0')]\n" - "creation_time = 2024-05-30 12:30:01\n\n#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" + "thing2 = thing2\npackage_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" + "creation_time = 2012-01-14 00:00:00\n\n#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" "1.000000000000000000e+00 1.000000000000000000e+00\n2.000000000000000000e+00 2.000000000000000000e+00\n" "3.000000000000000000e+00 3.000000000000000000e+00\n4.000000000000000000e+00 4.000000000000000000e+00\n" "5.000000000000000000e+00 5.000000000000000000e+00\n" From 04a049cc9bd02f1cd0a32d581cf5b49b1935013f Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 6 Jun 2024 04:28:55 -0400 Subject: [PATCH 138/696] news --- news/dump_with_info.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/dump_with_info.rst diff --git a/news/dump_with_info.rst b/news/dump_with_info.rst new file mode 100644 index 00000000..862d1bbb --- /dev/null +++ b/news/dump_with_info.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* diffraction_object.dump now adds creation time and diffpy.utils version number to the output file + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From e690538b2f34c843d4211ab66e060d3018712cdc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:34:31 +0000 Subject: [PATCH 139/696] [pre-commit.ci] auto fixes from pre-commit hooks --- .../utils/scattering_objects/diffraction_objects.py | 3 ++- src/diffpy/utils/tests/test_diffraction_objects.py | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 81e6e678..75fec8fd 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -1,6 +1,7 @@ -from copy import deepcopy import datetime import importlib.metadata +from copy import deepcopy + import numpy as np from diffpy.utils.tools import get_package_info diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 72d1be06..7802b1d3 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -1,8 +1,8 @@ -from freezegun import freeze_time from pathlib import Path import numpy as np import pytest +from freezegun import freeze_time from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object @@ -239,8 +239,10 @@ def test_dump(tmp_path, mocker): test.wavelength = 1.54 test.name = "test" test.scat_quantity = "x-ray" - test.insert_scattering_quantity(x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {'package2': '3.4.5'}}) - with mocker.patch('importlib.metadata.version', return_value='3.3.0'), freeze_time("2012-01-14"): + test.insert_scattering_quantity( + x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} + ) + with mocker.patch("importlib.metadata.version", return_value="3.3.0"), freeze_time("2012-01-14"): test.dump(file, "q") with open(file, "r") as f: actual = f.read() From 2ad5e2ac0daeb06bf241431111e9d24926e61156 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 6 Jun 2024 04:38:18 -0400 Subject: [PATCH 140/696] remove unused import --- src/diffpy/utils/scattering_objects/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 81e6e678..e809e018 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -1,6 +1,6 @@ -from copy import deepcopy import datetime -import importlib.metadata +from copy import deepcopy + import numpy as np from diffpy.utils.tools import get_package_info From 8a861c01376f2164ac9f08239cbf960129a997f8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 6 Jun 2024 13:06:07 -0400 Subject: [PATCH 141/696] added package docs and corrected import in tools --- doc/manual/source/examples/toolsexample.rst | 31 ++++++++++++++++++-- doc/manual/source/utilities/toolsutility.rst | 14 ++++++--- src/diffpy/utils/tools.py | 2 +- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/doc/manual/source/examples/toolsexample.rst b/doc/manual/source/examples/toolsexample.rst index b59d8843..51d2ab44 100644 --- a/doc/manual/source/examples/toolsexample.rst +++ b/doc/manual/source/examples/toolsexample.rst @@ -5,7 +5,7 @@ Tools Example ############# -This example will demonstrate how diffpy.utils allows us to load and manage username and email information. +This example will demonstrate how diffpy.utils allows us to load and manage user and package information. Using the tools module, we can efficiently get them in terms of a dictionary. 1) We have the function ``get_user_info`` that neatly returns a dictionary containing the username and email. @@ -43,5 +43,32 @@ Using the tools module, we can efficiently get them in terms of a dictionary. This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. +3) We also have the function ``get_package_info``, which inserts or updates package names and versions + in the given metadata dictionary under the key "package_info". + It stores the package information as {"package_info": {"package_name": "version_number"}}. + This function can be used as follows. :: -By using this function, we ensure that user information is correctly loaded, merged, and saved. + from diffpy.utils.tools import get_user_info + package_metadata = get_package_info("diffpy.utils") + + You can also specify a specific metadata dictionary to store the information. :: + + existing_dict = {"key": "value"} + existing_dict.update(get_package_info("diffpy.utils", metadata=existing_dict)) + + In this case, the function inserts the package info into ``existing_dict``. + + If you want to specify package other than "diffpy.utils", + "diffpy.utils" is automatically included in the package info. :: + + existing_dict.update(get_package_info("new_package", metadata=existing_dict)) + + The package info will then contain information for both "diffpy.utils" and "new_package". + +4) We can also use ``get_package_info`` with diffraction objects to update the package information. :: + + from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object + example = Diffraction_object() + example.metadata.update(get_package_info("diffpy.utils", metadata=example.metadata)) + +By using this module, we ensure that user and package information is correctly loaded, merged, and saved. diff --git a/doc/manual/source/utilities/toolsutility.rst b/doc/manual/source/utilities/toolsutility.rst index b4f0b21c..819d0840 100644 --- a/doc/manual/source/utilities/toolsutility.rst +++ b/doc/manual/source/utilities/toolsutility.rst @@ -5,10 +5,16 @@ Tools Utility ############# -The ``diffpy.utils.tools`` module contains tool functions for use with diffpy apps. -One of these functions, ``get_user_info``, is designed for managing and tracking username and email information. -This function helps storing this data in a ``diffpyconfig.json`` file. +The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. -Users can use the module to simplify the process of loading, merging, and saving information consistently and easily. +1) ``get_uder_info``: This function is designed for managing and tracking username and email information. +It helps to store this data in a ``diffpyconfig.json`` file. + +Users can use this function to simplify the process of loading, merging, and saving information consistently and easily. Additionally, it saves the effort of re-entering information, and allows overriding current information by passing parameters. + +2) ``get_package_info``: This function loads package name and version information into a dictionary. +It stores the package information under the key "package_info" in the format {"package_name": "version_number"}. + +Users can use this function to track and manage dependencies, ensuring the correct packages and versions were used. diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 9687211f..195fdfed 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,4 +1,4 @@ -import importlib +import importlib.metadata import json import os from copy import copy From 00e283c83d6f83641e05a7295ca024241ad38af8 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 7 Jun 2024 04:06:40 -0400 Subject: [PATCH 142/696] small edits to text by simon --- doc/manual/source/utilities/toolsutility.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/manual/source/utilities/toolsutility.rst b/doc/manual/source/utilities/toolsutility.rst index 819d0840..66c1d148 100644 --- a/doc/manual/source/utilities/toolsutility.rst +++ b/doc/manual/source/utilities/toolsutility.rst @@ -8,13 +8,16 @@ Tools Utility The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. 1) ``get_uder_info``: This function is designed for managing and tracking username and email information. -It helps to store this data in a ``diffpyconfig.json`` file. -Users can use this function to simplify the process of loading, merging, and saving information consistently and easily. +Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. Additionally, it saves the effort of re-entering information, and allows overriding current information by passing parameters. 2) ``get_package_info``: This function loads package name and version information into a dictionary. -It stores the package information under the key "package_info" in the format {"package_name": "version_number"}. +It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, +resulting in an entry in the passed metadata dictionary that looks like +`{"package_info": {"package1": "version_number1", "package2": "version_number2"} if the function is called more than +once. -Users can use this function to track and manage dependencies, ensuring the correct packages and versions were used. +Users can use these functions to track and manage versions of packages that can later be stored, for example, in an output +file header. From 10f110bbe3f2a3781dbbaa6807552880e56315f9 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 7 Jun 2024 04:19:35 -0400 Subject: [PATCH 143/696] more tweaks by simon --- doc/manual/source/examples/toolsexample.rst | 49 +++++++-------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/doc/manual/source/examples/toolsexample.rst b/doc/manual/source/examples/toolsexample.rst index 51d2ab44..1a2aeba0 100644 --- a/doc/manual/source/examples/toolsexample.rst +++ b/doc/manual/source/examples/toolsexample.rst @@ -5,7 +5,7 @@ Tools Example ############# -This example will demonstrate how diffpy.utils allows us to load and manage user and package information. +This example will demonstrate how diffpy.utils allows us to conveniently load and manage user and package information. Using the tools module, we can efficiently get them in terms of a dictionary. 1) We have the function ``get_user_info`` that neatly returns a dictionary containing the username and email. @@ -14,23 +14,22 @@ Using the tools module, we can efficiently get them in terms of a dictionary. from diffpy.utils.tools import get_user_info user_info = get_user_info() - This function will first attempt to load configuration files - from both the current working directory and the home directory. - If no configuration files exist, it prompts for user input and creates a configuration file in the home directory. - It prioritizes prompted user inputs, then current working directory, and finally home directory. - If no configuration files or inputs are found, this function creates a configuration in the home directory - with empty values for username and email stored as a dictionary. + This function will first attempt to load the information from configuration files looking first in + the current working directory and then in the user's home directory. + If no configuration files exist, it prompts for user input and creates a configuration file in the home directory + so that the next time the program is run it will no longer have to prompt the user. + It can be passed user information which overrides looking in config files, and so could be passed + information that is entered through a gui or command line interface to override default information at runtime. + It prioritizes prompted user inputs, then current working directory config file, and finally home directory config file. -2) You can also override existing values by passing a dictionary to the function. :: + The function returns a dictionary containing the username and email information. + +2) You can also override existing values by passing a dictionary to the function with the keys `"username"` and `"email"` :: new_args = {"username": "new_username", "email": "new@example.com"} new_user_info = get_user_info(new_args) - Here, the function returns a dictionary containing the new arguments. - If no configuration files exist, it prompts for inputs again. The arguments passed here also override input values. - The updated arguments will not be saved in files. - - You can update only the username or email individually, for example :: +3) You can update only the username or email individually, for example :: new_username = {"username": new_username} new_user_info = get_user_info(new_username) @@ -49,26 +48,12 @@ Using the tools module, we can efficiently get them in terms of a dictionary. This function can be used as follows. :: from diffpy.utils.tools import get_user_info - package_metadata = get_package_info("diffpy.utils") + package_metadata = get_package_info("my_package") - You can also specify a specific metadata dictionary to store the information. :: + You can also specify an existing dictionary to be updated with the information. :: existing_dict = {"key": "value"} - existing_dict.update(get_package_info("diffpy.utils", metadata=existing_dict)) - - In this case, the function inserts the package info into ``existing_dict``. - - If you want to specify package other than "diffpy.utils", - "diffpy.utils" is automatically included in the package info. :: - - existing_dict.update(get_package_info("new_package", metadata=existing_dict)) - - The package info will then contain information for both "diffpy.utils" and "new_package". - -4) We can also use ``get_package_info`` with diffraction objects to update the package information. :: - - from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object - example = Diffraction_object() - example.metadata.update(get_package_info("diffpy.utils", metadata=example.metadata)) + updated_dict = get_package_info("my_package", metadata=existing_dict)) -By using this module, we ensure that user and package information is correctly loaded, merged, and saved. + note that `"diffpy.utils"` is automatically included in the package info since the `get_user_info` function is + part of diffpy.utils. From 0ba93826ee56377b149a87e8cf4ce8e36d995092 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Fri, 7 Jun 2024 16:02:01 -0400 Subject: [PATCH 144/696] Typo + format fix --- doc/manual/source/utilities/toolsutility.rst | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/manual/source/utilities/toolsutility.rst b/doc/manual/source/utilities/toolsutility.rst index 66c1d148..0121b210 100644 --- a/doc/manual/source/utilities/toolsutility.rst +++ b/doc/manual/source/utilities/toolsutility.rst @@ -7,17 +7,17 @@ Tools Utility The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. -1) ``get_uder_info``: This function is designed for managing and tracking username and email information. +1. ``get_user_info``: This function is designed for managing and tracking username and email information. -Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. -Additionally, it saves the effort of re-entering information, and allows overriding current information by -passing parameters. + Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. + Additionally, it saves the effort of re-entering information, and allows overriding current information by + passing parameters. -2) ``get_package_info``: This function loads package name and version information into a dictionary. -It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, -resulting in an entry in the passed metadata dictionary that looks like -`{"package_info": {"package1": "version_number1", "package2": "version_number2"} if the function is called more than -once. +2. ``get_package_info``: This function loads package name and version information into a dictionary. + It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, + resulting in an entry in the passed metadata dictionary that looks like + ``{"package_info": {"package1": "version_number1", "package2": "version_number2"}`` if the function is called more than + once. -Users can use these functions to track and manage versions of packages that can later be stored, for example, in an output -file header. + Users can use these functions to track and manage versions of packages that can later be stored, for example, in an output + file header. From 7c48a59be86912ea3a30e41a8505dabca2098071 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Fri, 7 Jun 2024 16:09:40 -0400 Subject: [PATCH 145/696] Rewrite version.py --- src/diffpy/utils/version.py | 45 +++++-------------------------------- 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index 7e9d1130..0d6621a2 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -13,46 +13,13 @@ # ############################################################################## -""" -Definition of __version__, __date__, __timestamp__, __git_commit__. +"""Definition of __version__.""" -Notes ------ -Variable `__gitsha__` is deprecated as of version 3.0. -Use `__git_commit__` instead. -""" +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] -__all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] - -import os.path -from importlib.resources import as_file, files - -# obtain version information from the version.cfg file -cp = dict(version="", date="", commit="", timestamp="0") -if __package__ is not None: - ref = files(__package__) / "version.cfg" - with as_file(ref) as fcfg: - if not os.path.isfile(fcfg): # pragma: no cover - from warnings import warn - - warn("Package metadata not found.") - fcfg = os.devnull - with open(fcfg) as fp: - kwords = [ - [w.strip() for w in line.split(" = ", 1)] for line in fp if line[:1].isalpha() and " = " in line - ] - assert all(w[0] in cp for w in kwords), "received unrecognized keyword" - cp.update(kwords) - del kwords - -__version__ = cp["version"] -__date__ = cp["date"] -__git_commit__ = cp["commit"] -__timestamp__ = int(cp["timestamp"]) - -# TODO remove deprecated __gitsha__ in version 3.1. -__gitsha__ = __git_commit__ - -del cp +# obtain version information +from importlib.metadata import version +__version__ = version("diffpy.utils") # End of file From a7045eeb425b9f0a9ceb6e4d1469784e1d000d89 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 20:13:03 +0000 Subject: [PATCH 146/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/version.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index 0d6621a2..b09f0b9f 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -20,6 +20,7 @@ # obtain version information from importlib.metadata import version + __version__ = version("diffpy.utils") # End of file From 6dd028f913bae90794db10a7310962a70eed771c Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Sat, 8 Jun 2024 23:49:40 -0400 Subject: [PATCH 147/696] pep8 formatting --- .flake8 | 5 +++-- .../utils/tests/test_diffraction_objects.py | 15 ++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.flake8 b/.flake8 index 92c9bb29..cd3b0976 100644 --- a/.flake8 +++ b/.flake8 @@ -8,5 +8,6 @@ exclude = labpdfproc/_version.py, doc/manual/source/conf.py max-line-length = 115 -# Ignore some style 'errors' produced while formatting by 'black' -ignore = W503 +# Ignore some style 'errors' produced while formatting by 'black' (see link below) +# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings +ignore = E203, E701 diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/src/diffpy/utils/tests/test_diffraction_objects.py index 7802b1d3..fa613a53 100644 --- a/src/diffpy/utils/tests/test_diffraction_objects.py +++ b/src/diffpy/utils/tests/test_diffraction_objects.py @@ -249,12 +249,17 @@ def test_dump(tmp_path, mocker): expected = ( "[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" "thing2 = thing2\npackage_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" - "creation_time = 2012-01-14 00:00:00\n\n#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" - "1.000000000000000000e+00 1.000000000000000000e+00\n2.000000000000000000e+00 2.000000000000000000e+00\n" - "3.000000000000000000e+00 3.000000000000000000e+00\n4.000000000000000000e+00 4.000000000000000000e+00\n" + "creation_time = 2012-01-14 00:00:00\n\n" + "#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" + "1.000000000000000000e+00 1.000000000000000000e+00\n" + "2.000000000000000000e+00 2.000000000000000000e+00\n" + "3.000000000000000000e+00 3.000000000000000000e+00\n" + "4.000000000000000000e+00 4.000000000000000000e+00\n" "5.000000000000000000e+00 5.000000000000000000e+00\n" - "6.000000000000000000e+00 6.000000000000000000e+00\n7.000000000000000000e+00 7.000000000000000000e+00\n" - "8.000000000000000000e+00 8.000000000000000000e+00\n9.000000000000000000e+00 9.000000000000000000e+00\n" + "6.000000000000000000e+00 6.000000000000000000e+00\n" + "7.000000000000000000e+00 7.000000000000000000e+00\n" + "8.000000000000000000e+00 8.000000000000000000e+00\n" + "9.000000000000000000e+00 9.000000000000000000e+00\n" "1.000000000000000000e+01 1.000000000000000000e+01\n" ) assert actual == expected From b29fc5dc30dfa6e96c259edd10777531d51428ef Mon Sep 17 00:00:00 2001 From: Sparky <59151395+Sparks29032@users.noreply.github.com> Date: Sun, 9 Jun 2024 00:01:25 -0400 Subject: [PATCH 148/696] Cosmetic change --- doc/manual/source/utilities/toolsutility.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/source/utilities/toolsutility.rst b/doc/manual/source/utilities/toolsutility.rst index 0121b210..b7bfe441 100644 --- a/doc/manual/source/utilities/toolsutility.rst +++ b/doc/manual/source/utilities/toolsutility.rst @@ -7,13 +7,13 @@ Tools Utility The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. -1. ``get_user_info``: This function is designed for managing and tracking username and email information. +1) ``get_user_info``: This function is designed for managing and tracking username and email information. Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. Additionally, it saves the effort of re-entering information, and allows overriding current information by passing parameters. -2. ``get_package_info``: This function loads package name and version information into a dictionary. +2) ``get_package_info``: This function loads package name and version information into a dictionary. It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, resulting in an entry in the passed metadata dictionary that looks like ``{"package_info": {"package1": "version_number1", "package2": "version_number2"}`` if the function is called more than From 6e42b7c56e09bc74dbf453afaa57b73b4ae9c7bd Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Mon, 10 Jun 2024 21:20:59 -0400 Subject: [PATCH 149/696] extend rather than ignore --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index cd3b0976..0b351c04 100644 --- a/.flake8 +++ b/.flake8 @@ -10,4 +10,4 @@ exclude = max-line-length = 115 # Ignore some style 'errors' produced while formatting by 'black' (see link below) # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings -ignore = E203, E701 +extend-ignore = E203 From 3d94e42709a31eba41e0a7407ba5cf3b20a24aad Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Mon, 10 Jun 2024 21:21:17 -0400 Subject: [PATCH 150/696] Removed as outdated --- conda-recipe/build.sh | 8 ----- conda-recipe/meta.yaml | 64 ---------------------------------------- conda-recipe/run_test.py | 11 ------- 3 files changed, 83 deletions(-) delete mode 100644 conda-recipe/build.sh delete mode 100644 conda-recipe/meta.yaml delete mode 100644 conda-recipe/run_test.py diff --git a/conda-recipe/build.sh b/conda-recipe/build.sh deleted file mode 100644 index b7920393..00000000 --- a/conda-recipe/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -$PYTHON setup.py install - -# Add more build steps here, if they are necessary. - -# See http://docs.continuum.io/conda/build.html -# for a list of environment variables that are set during the build process. diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml deleted file mode 100644 index 750beada..00000000 --- a/conda-recipe/meta.yaml +++ /dev/null @@ -1,64 +0,0 @@ -{% set setupdata = load_setup_py_data() %} - -package: - name: diffpy.utils - version: {{ setupdata['version'] }} - -source: - # git_url: https://github.com/diffpy/diffpy.utils - git_url: .. - -build: - preserve_egg_dir: True - noarch: python - - # If this is a new build for the same version, increment the build - # number. If you do not include this key, it defaults to 0. - # number: 0 - -requirements: - host: - - pip - - build: - - python >=3.5|2.7.* - - setuptools - - run: - - python >=3.5|2.7.* - - setuptools - - numpy >=1.3 - - test: - - python >=3.5|2.7.* - - setuptools - - numpy >=1.3 - - pytest - -test: - # Put any additional test requirements here - requires: - - pytest - - # Python imports - imports: - - diffpy - - diffpy.utils - - diffpy.utils.parsers - - diffpy.utils.tests - - commands: - # You can put test commands to be run here. Use this to test that the - # entry points work. - - # You can also put a file called run_test.py in the recipe that will be run - # at test time. - -about: - home: https://github.com/diffpy/diffpy.utils - summary: Shared utilities for diffpy packages. - license: Modified BSD License - license_file: LICENSE.txt - -# See http://docs.continuum.io/conda/build.html -# for more information about meta.yaml. diff --git a/conda-recipe/run_test.py b/conda-recipe/run_test.py deleted file mode 100644 index 946a0960..00000000 --- a/conda-recipe/run_test.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import pathlib -import sys - -import diffpy.utils.tests - -sys.path.append((pathlib.Path.cwd().parent.absolute() / "src").as_posix()) - - -assert diffpy.utils.tests.test().wasSuccessful() From 1ff119c6c5b35beb0021caddc121fc7cfe8aa4c7 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 10 Jun 2024 22:00:17 -0400 Subject: [PATCH 151/696] added whitespace to trigger a build --- src/diffpy/utils/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index b09f0b9f..2566d69f 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -15,8 +15,8 @@ """Definition of __version__.""" -# We do not use the other three variables, but can be added back if needed. -# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] # obtain version information from importlib.metadata import version From a8186fab7ed20cb9f3c44d9e8e542237da4dc818 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Thu, 13 Jun 2024 15:10:20 -0400 Subject: [PATCH 152/696] Refactor documentation, fix warnings --- doc/{manual => }/Makefile | 0 .../source/api/diffpy.utils.parsers.rst | 0 doc/{manual => }/source/api/diffpy.utils.rst | 0 doc/{manual => }/source/api/diffpy.utils.wx.rst | 0 doc/{manual => }/source/conf.py | 14 +++++++------- .../source/examples/exampledata/parserdata.zip | Bin doc/{manual => }/source/examples/examples.rst | 0 .../source/examples/parsersexample.rst | 4 ++-- .../source/examples/resampleexample.rst | 0 doc/{manual => }/source/examples/toolsexample.rst | 0 doc/{manual => }/source/index.rst | 0 doc/{manual => }/source/license.rst | 0 doc/{manual => }/source/release.rst | 2 +- .../source/utilities/toolsutility.rst | 0 src/diffpy/utils/parsers/loaddata.py | 9 ++++----- src/diffpy/utils/parsers/resample.py | 6 ++---- 16 files changed, 16 insertions(+), 19 deletions(-) rename doc/{manual => }/Makefile (100%) rename doc/{manual => }/source/api/diffpy.utils.parsers.rst (100%) rename doc/{manual => }/source/api/diffpy.utils.rst (100%) rename doc/{manual => }/source/api/diffpy.utils.wx.rst (100%) rename doc/{manual => }/source/conf.py (97%) rename doc/{manual => }/source/examples/exampledata/parserdata.zip (100%) rename doc/{manual => }/source/examples/examples.rst (100%) rename doc/{manual => }/source/examples/parsersexample.rst (98%) rename doc/{manual => }/source/examples/resampleexample.rst (100%) rename doc/{manual => }/source/examples/toolsexample.rst (100%) rename doc/{manual => }/source/index.rst (100%) rename doc/{manual => }/source/license.rst (100%) rename doc/{manual => }/source/release.rst (52%) rename doc/{manual => }/source/utilities/toolsutility.rst (100%) diff --git a/doc/manual/Makefile b/doc/Makefile similarity index 100% rename from doc/manual/Makefile rename to doc/Makefile diff --git a/doc/manual/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst similarity index 100% rename from doc/manual/source/api/diffpy.utils.parsers.rst rename to doc/source/api/diffpy.utils.parsers.rst diff --git a/doc/manual/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst similarity index 100% rename from doc/manual/source/api/diffpy.utils.rst rename to doc/source/api/diffpy.utils.rst diff --git a/doc/manual/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst similarity index 100% rename from doc/manual/source/api/diffpy.utils.wx.rst rename to doc/source/api/diffpy.utils.wx.rst diff --git a/doc/manual/source/conf.py b/doc/source/conf.py similarity index 97% rename from doc/manual/source/conf.py rename to doc/source/conf.py index 77749dcf..e85a33da 100644 --- a/doc/manual/source/conf.py +++ b/doc/source/conf.py @@ -16,13 +16,14 @@ import os import sys import time +from importlib.metadata import version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath("../../..")) -sys.path.insert(0, os.path.abspath("../../../src")) +sys.path.insert(0, os.path.abspath("../..")) +sys.path.insert(0, os.path.abspath("../../src")) # abbreviations ab_authors = "Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge group" @@ -63,7 +64,6 @@ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -from importlib.metadata import version fullversion = version(project) # The short X.Y version. @@ -206,16 +206,16 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', + # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', + # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. - #'preamble': '', + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). +# author, documentclass [howto, manual, or own class]). latex_documents = [ ("index", "diffpy.utils.tex", "diffpy.utils Documentation", ab_authors, "manual"), ] diff --git a/doc/manual/source/examples/exampledata/parserdata.zip b/doc/source/examples/exampledata/parserdata.zip similarity index 100% rename from doc/manual/source/examples/exampledata/parserdata.zip rename to doc/source/examples/exampledata/parserdata.zip diff --git a/doc/manual/source/examples/examples.rst b/doc/source/examples/examples.rst similarity index 100% rename from doc/manual/source/examples/examples.rst rename to doc/source/examples/examples.rst diff --git a/doc/manual/source/examples/parsersexample.rst b/doc/source/examples/parsersexample.rst similarity index 98% rename from doc/manual/source/examples/parsersexample.rst rename to doc/source/examples/parsersexample.rst index b78b462e..04235bd8 100644 --- a/doc/manual/source/examples/parsersexample.rst +++ b/doc/source/examples/parsersexample.rst @@ -51,7 +51,7 @@ Using the parsers module, we can load file data into simple and easy-to-work-wit hdata = loadData('', comments=['$', '-->'], headers=True) 4) Rather than working with separate ``data_table`` and ``hdata`` objects, it may be easier to combine them into a single -dictionary. We can do so using the ``serialize_data`` function. :: + dictionary. We can do so using the ``serialize_data`` function. :: from diffpy.utils.parsers import serialize_data file_data = serialize_data('', hdata, data_table, serial_file='') The returned value, ``parsed_file_data``, is the dictionary we just added to ``serialfile.json``. - To extract the data from the serial file, we use ``deserialize_data''. :: + To extract the data from the serial file, we use ``deserialize_data``. :: from diffpy.utils.parsers import deserialize_data parsed_file_data = deserialize_data('') diff --git a/doc/manual/source/examples/resampleexample.rst b/doc/source/examples/resampleexample.rst similarity index 100% rename from doc/manual/source/examples/resampleexample.rst rename to doc/source/examples/resampleexample.rst diff --git a/doc/manual/source/examples/toolsexample.rst b/doc/source/examples/toolsexample.rst similarity index 100% rename from doc/manual/source/examples/toolsexample.rst rename to doc/source/examples/toolsexample.rst diff --git a/doc/manual/source/index.rst b/doc/source/index.rst similarity index 100% rename from doc/manual/source/index.rst rename to doc/source/index.rst diff --git a/doc/manual/source/license.rst b/doc/source/license.rst similarity index 100% rename from doc/manual/source/license.rst rename to doc/source/license.rst diff --git a/doc/manual/source/release.rst b/doc/source/release.rst similarity index 52% rename from doc/manual/source/release.rst rename to doc/source/release.rst index a7afad17..6ebf6dd6 100644 --- a/doc/manual/source/release.rst +++ b/doc/source/release.rst @@ -2,4 +2,4 @@ .. index:: release notes -.. include:: ../../../CHANGELOG.rst +.. include:: ../../CHANGELOG.rst diff --git a/doc/manual/source/utilities/toolsutility.rst b/doc/source/utilities/toolsutility.rst similarity index 100% rename from doc/manual/source/utilities/toolsutility.rst rename to doc/source/utilities/toolsutility.rst diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 0e25fab0..4bb0d792 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -28,13 +28,12 @@ def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwar Name of the file we want to load data from. minrows: int Minimum number of rows in the first data block. All rows must have the same number of floating - point values. + point values. headers: bool - when False (defualt), the function returns a numpy array of the data in the data block. + when False (defualt), the function returns a numpy array of the data in the data block. When True, the function instead returns a dictionary of parameters and their corresponding - values parsed from - header (information prior the data block). See hdel and hignore for options to help with parsing header - information. + values parsed from header (information prior the data block). See hdel and hignore for options + to help with parsing header information. hdel: str (Only used when headers enabled.) Delimiter for parsing header information (default '='). e.g. using default hdel, the line 'parameter = p_value' is put into the dictionary as {parameter: p_value}. diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index 032c19b0..f81fc2a2 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -24,8 +24,7 @@ def wsinterp(x, xp, fp, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation. This uses the Whittaker-Shannon interpolation formula to interpolate the value of fp (array), - which is defined over - xp (array), at x (array or float). + which is defined over xp (array), at x (array or float). Parameters ---------- @@ -37,8 +36,7 @@ def wsinterp(x, xp, fp, left=None, right=None): Function to be interpolated. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, - set fp for x < xp[0] - to fp evaluated at xp[-1]. + set fp for x < xp[0] to fp evaluated at xp[-1]. right: float If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for x > xp[-1] to fp evaluated at xp[-1]. From dcd8d4498509d3cc7285fd0ab3d8b3f435b3c99a Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Thu, 13 Jun 2024 16:24:41 -0400 Subject: [PATCH 153/696] Add utilities to toc, reformat toc --- doc/source/examples/examples.rst | 3 ++- doc/source/examples/parsersexample.rst | 2 +- doc/source/examples/resampleexample.rst | 2 +- doc/source/examples/toolsexample.rst | 14 +++++++------- doc/source/index.rst | 6 +++++- doc/source/utilities/toolsutility.rst | 6 +++--- doc/source/utilities/utilities.rst | 13 +++++++++++++ 7 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 doc/source/utilities/utilities.rst diff --git a/doc/source/examples/examples.rst b/doc/source/examples/examples.rst index d3d7ab3d..fefe843a 100644 --- a/doc/source/examples/examples.rst +++ b/doc/source/examples/examples.rst @@ -1,6 +1,6 @@ .. _Examples: -:tocdepth: 2 +:tocdepth: -1 Examples ######## @@ -9,3 +9,4 @@ Landing page for diffpy.utils examples. .. toctree:: parsersexample resampleexample + toolsexample diff --git a/doc/source/examples/parsersexample.rst b/doc/source/examples/parsersexample.rst index 04235bd8..90605741 100644 --- a/doc/source/examples/parsersexample.rst +++ b/doc/source/examples/parsersexample.rst @@ -1,6 +1,6 @@ .. _Parsers Example: -:tocdepth: 2 +:tocdepth: -1 Parsers Example ############### diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resampleexample.rst index 7b91c249..efa3c474 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resampleexample.rst @@ -1,6 +1,6 @@ .. _Resample Example: -:tocdepth: 2 +:tocdepth: -1 Resampling Example ################## diff --git a/doc/source/examples/toolsexample.rst b/doc/source/examples/toolsexample.rst index 1a2aeba0..d4d00cce 100644 --- a/doc/source/examples/toolsexample.rst +++ b/doc/source/examples/toolsexample.rst @@ -1,6 +1,6 @@ .. _Tools Example: -:tocdepth: 2 +:tocdepth: -1 Tools Example ############# @@ -24,7 +24,7 @@ Using the tools module, we can efficiently get them in terms of a dictionary. The function returns a dictionary containing the username and email information. -2) You can also override existing values by passing a dictionary to the function with the keys `"username"` and `"email"` :: +2) You can also override existing values by passing a dictionary to the function with the keys ``"username"`` and ``"email"`` :: new_args = {"username": "new_username", "email": "new@example.com"} new_user_info = get_user_info(new_args) @@ -37,12 +37,12 @@ Using the tools module, we can efficiently get them in terms of a dictionary. This updates username to "new_username" while fetching the email from inputs or the configuration files. Similarly, you can update only the email. :: - new_email = {"email": new@email.com} - new_user_info = get_user_info(new_email) + new_email = {"email": new@email.com} + new_user_info = get_user_info(new_email) This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. -3) We also have the function ``get_package_info``, which inserts or updates package names and versions +4) We also have the function ``get_package_info``, which inserts or updates package names and versions in the given metadata dictionary under the key "package_info". It stores the package information as {"package_info": {"package_name": "version_number"}}. This function can be used as follows. :: @@ -55,5 +55,5 @@ Using the tools module, we can efficiently get them in terms of a dictionary. existing_dict = {"key": "value"} updated_dict = get_package_info("my_package", metadata=existing_dict)) - note that `"diffpy.utils"` is automatically included in the package info since the `get_user_info` function is - part of diffpy.utils. + note that `"diffpy.utils"` is automatically included in the package info since the `get_user_info` function is + part of diffpy.utils. diff --git a/doc/source/index.rst b/doc/source/index.rst index ccab47a2..0cd373a6 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -11,13 +11,16 @@ The diffpy.utils package provides general functions for extracting data from var some PDF-specific functionality. These include wx GUI utilities used by the PDFgui program and an interpolation function based on the Whittaker-Shannon formula for resampling a bandlimited PDF or other profile function. +Click :ref:`here` for a full list of utilities offered by diffpy.utils. + ======== Examples ======== Illustrations of when and how one would use various diffpy.utils functions. * :ref:`File Data Extraction` -* :ref:`Resampling & Data Reconstruction` +* :ref:`Resampling and Data Reconstruction` +* :ref:`Load and Manage User and Package Information` ======= Authors @@ -46,6 +49,7 @@ Table of contents license release + Utilities Examples Package API diff --git a/doc/source/utilities/toolsutility.rst b/doc/source/utilities/toolsutility.rst index b7bfe441..6feab459 100644 --- a/doc/source/utilities/toolsutility.rst +++ b/doc/source/utilities/toolsutility.rst @@ -1,9 +1,7 @@ .. _Tools Utility: -:tocdepth: 2 - Tools Utility -############# +============= The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. @@ -21,3 +19,5 @@ The ``diffpy.utils.tools`` module provides tool functions for use with diffpy ap Users can use these functions to track and manage versions of packages that can later be stored, for example, in an output file header. + +For a more in-depth tutorial for how to use these tools, click :ref:`here `. diff --git a/doc/source/utilities/utilities.rst b/doc/source/utilities/utilities.rst new file mode 100644 index 00000000..34e8a242 --- /dev/null +++ b/doc/source/utilities/utilities.rst @@ -0,0 +1,13 @@ +.. _Utilities: + +:tocdepth: -1 + +Utilities +######### +Overview of utilities offered by ``diffpy.utils``. +Check the :ref:`examples` provided for how to use these. + +.. contents:: + :depth: 2 + +.. include:: toolsutility.rst From e61bc813c490e0c39225e99d4e3534ac6844d18b Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Thu, 13 Jun 2024 16:47:46 -0400 Subject: [PATCH 154/696] Fix docstrings --- doc/source/api/diffpy.utils.rst | 6 ++-- doc/source/utilities/parsersutility.rst | 14 +++++++++ doc/source/utilities/toolsutility.rst | 4 +-- doc/source/utilities/utilities.rst | 1 + src/diffpy/utils/tools.py | 39 ++++++++++++++----------- 5 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 doc/source/utilities/parsersutility.rst diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 266c595a..1ca13b1a 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -19,10 +19,10 @@ Subpackages Submodules ---------- -diffpy.utils.version module ---------------------------- +diffpy.utils.tools module +------------------------- -.. automodule:: diffpy.utils.version +.. automodule:: diffpy.utils.tools :members: :undoc-members: :show-inheritance: diff --git a/doc/source/utilities/parsersutility.rst b/doc/source/utilities/parsersutility.rst new file mode 100644 index 00000000..0e92fd4f --- /dev/null +++ b/doc/source/utilities/parsersutility.rst @@ -0,0 +1,14 @@ +.. _Parsers Utility: + +Parsers Utility +=============== + +The ``diffpy.utils.parsers`` module allows users to easily and robustly load file data into a Python project. + +1) `loadData()`: + +2) `serialize_data()`: + +3) `deserialize_data()`: + +For a more in-depth tutorial for how to use these parser utilities, click :ref:`here `. diff --git a/doc/source/utilities/toolsutility.rst b/doc/source/utilities/toolsutility.rst index 6feab459..e1dd073f 100644 --- a/doc/source/utilities/toolsutility.rst +++ b/doc/source/utilities/toolsutility.rst @@ -5,13 +5,13 @@ Tools Utility The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. -1) ``get_user_info``: This function is designed for managing and tracking username and email information. +1) ``get_user_info()``: This function is designed for managing and tracking username and email information. Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. Additionally, it saves the effort of re-entering information, and allows overriding current information by passing parameters. -2) ``get_package_info``: This function loads package name and version information into a dictionary. +2) ``get_package_info()``: This function loads package name and version information into a dictionary. It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, resulting in an entry in the passed metadata dictionary that looks like ``{"package_info": {"package1": "version_number1", "package2": "version_number2"}`` if the function is called more than diff --git a/doc/source/utilities/utilities.rst b/doc/source/utilities/utilities.rst index 34e8a242..02e5d4d7 100644 --- a/doc/source/utilities/utilities.rst +++ b/doc/source/utilities/utilities.rst @@ -10,4 +10,5 @@ Check the :ref:`examples` provided for how to use these. .. contents:: :depth: 2 +.. include:: parsersutility.rst .. include:: toolsutility.rst diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 195fdfed..1dfb8284 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -7,16 +7,17 @@ def clean_dict(obj): """ - remove keys from the dictionary where the corresponding value is None + Remove keys from the dictionary where the corresponding value is None. Parameters ---------- obj: dict - the dictionary to clean. If None, initialize as an empty dictionary + The dictionary to clean. If None, initialize as an empty dictionary. Returns ------- - the cleaned dictionary with keys removed where the value is None + dict: + The cleaned dictionary with keys removed where the value is None. """ obj = obj if obj is not None else {} @@ -28,32 +29,34 @@ def clean_dict(obj): def stringify(obj): """ - convert None to an empty string + Convert None to an empty string. Parameters ---------- obj: str - the object to convert. If None, return an empty string + The object to convert. If None, return an empty string. Returns ------- - the converted string if obj is not None, otherwise an empty string + str or None: + The converted string if obj is not None, otherwise an empty string. """ return obj if obj is not None else "" def load_config(file_path): """ - load configuration from a json file + Load configuration from a .json file. Parameters ---------- file_path: Path - the path to the configuration file + The path to the configuration file. Returns ------- - the configuration dictionary or None if file does not exist + dict: + The configuration dictionary or None if file does not exist. """ config_file = Path(file_path).resolve() @@ -89,7 +92,7 @@ def _create_global_config(args): def get_user_info(args=None): """ - get username and email configuration + Get username and email configuration. First attempts to load config file from global and local paths. If neither exists, creates a global config file. @@ -99,11 +102,12 @@ def get_user_info(args=None): Parameters ---------- args argparse.Namespace - the arguments from the parser, default is None + The arguments from the parser, default is None. Returns ------- - the dictionary containing username and email with corresponding values + dict or None: + The dictionary containing username and email with corresponding values. """ config_bool = True @@ -122,19 +126,20 @@ def get_user_info(args=None): def get_package_info(package_names, metadata=None): """ - fetches package version and updates it into (given) metadata + Fetches package version and updates it into (given) metadata. - package info stored in metadata as {'package_info': {'package_name': 'version_number'}} + Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. ---------- package_name : str or list - the name of the package(s) to retrieve the version number for + The name of the package(s) to retrieve the version number for. metadata : dict - the dictionary to store the package info. If not provided, a new dictionary will be created + The dictionary to store the package info. If not provided, a new dictionary will be created. Returns ------- - the updated metadata dict with package info inserted + dict: + The updated metadata dict with package info inserted. """ if metadata is None: From 6723abd6c772aab926908781ef8fe584ae517553 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Thu, 13 Jun 2024 16:55:58 -0400 Subject: [PATCH 155/696] API toctree --- doc/source/api/diffpy.utils.rst | 2 +- doc/source/api/diffpy.utils.wx.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 1ca13b1a..f9a9c099 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -1,4 +1,4 @@ -:tocdepth: 2 +:tocdepth: -1 diffpy.utils package ==================== diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index bee1efcc..fb747f58 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -1,3 +1,5 @@ +.. _wx Python Utilities Documentation + diffpy.utils.wx package ======================= From f70acabd31804c994772ed55abcc8c745a221156 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Thu, 13 Jun 2024 17:17:02 -0400 Subject: [PATCH 156/696] Finish utilities page --- doc/source/utilities/parsersutility.rst | 15 ++++++++++++--- doc/source/utilities/resampleutility.rst | 9 +++++++++ doc/source/utilities/toolsutility.rst | 22 +++++++++++----------- doc/source/utilities/utilities.rst | 1 + 4 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 doc/source/utilities/resampleutility.rst diff --git a/doc/source/utilities/parsersutility.rst b/doc/source/utilities/parsersutility.rst index 0e92fd4f..f653c3d7 100644 --- a/doc/source/utilities/parsersutility.rst +++ b/doc/source/utilities/parsersutility.rst @@ -5,10 +5,19 @@ Parsers Utility The ``diffpy.utils.parsers`` module allows users to easily and robustly load file data into a Python project. -1) `loadData()`: +- ``loadData()``: Find and load a data table/block from a text file. This seems to work for most datafiles + including those generated by diffpy programs. Running only ``numpy.loadtxt`` will result in errors + for most these files as there is often excess data or parameters stored above the data block. + Users can instead choose to load all the parameters of the form `` = `` into a dictionary + with this function by setting the ``header`` parameter to ``True``. -2) `serialize_data()`: + The program identifies data blocks as the first matrix block with a constant number of columns. + A user can tune the minimum number of rows this matrix block must have. -3) `deserialize_data()`: +- ``deserialize_data()``: Load data from a serial file format into a Python dictionary. Currently, the only supported + serial format is ``.json``. + +- ``serialize_data()``: Serialize the data generated by ``loadData()`` into a serial file format. Currently, the only + supported serial format is ``.json``. For a more in-depth tutorial for how to use these parser utilities, click :ref:`here `. diff --git a/doc/source/utilities/resampleutility.rst b/doc/source/utilities/resampleutility.rst new file mode 100644 index 00000000..184cf22d --- /dev/null +++ b/doc/source/utilities/resampleutility.rst @@ -0,0 +1,9 @@ +.. _Resample Utility: + +Resample Utility +================ + +- ``wsinterp()``: Allows users easily reample a PDF onto another grid. + This makes use of the Whittaker-Shannon interpolation formula. + To see the theory behind how this interpolation works and how to use + it in practice, click :ref:`here `. diff --git a/doc/source/utilities/toolsutility.rst b/doc/source/utilities/toolsutility.rst index e1dd073f..973056e5 100644 --- a/doc/source/utilities/toolsutility.rst +++ b/doc/source/utilities/toolsutility.rst @@ -5,19 +5,19 @@ Tools Utility The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. -1) ``get_user_info()``: This function is designed for managing and tracking username and email information. +- ``get_user_info()``: This function is designed for managing and tracking username and email information. - Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. - Additionally, it saves the effort of re-entering information, and allows overriding current information by - passing parameters. + Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. + Additionally, it saves the effort of re-entering information, and allows overriding current information by + passing parameters. -2) ``get_package_info()``: This function loads package name and version information into a dictionary. - It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, - resulting in an entry in the passed metadata dictionary that looks like - ``{"package_info": {"package1": "version_number1", "package2": "version_number2"}`` if the function is called more than - once. +- ``get_package_info()``: This function loads package name and version information into a dictionary. + It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, + resulting in an entry in the passed metadata dictionary that looks like + ``{"package_info": {"package1": "version_number1", "package2": "version_number2"}`` if the function is called more than + once. - Users can use these functions to track and manage versions of packages that can later be stored, for example, in an output - file header. + Users can use these functions to track and manage versions of packages that can later be stored, for example, in an output + file header. For a more in-depth tutorial for how to use these tools, click :ref:`here `. diff --git a/doc/source/utilities/utilities.rst b/doc/source/utilities/utilities.rst index 02e5d4d7..5ec89656 100644 --- a/doc/source/utilities/utilities.rst +++ b/doc/source/utilities/utilities.rst @@ -11,4 +11,5 @@ Check the :ref:`examples` provided for how to use these. :depth: 2 .. include:: parsersutility.rst +.. include:: resampleutility.rst .. include:: toolsutility.rst From 08bd25d67f87856d5ee841e4b22bbd7730c087c1 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Thu, 13 Jun 2024 17:27:42 -0400 Subject: [PATCH 157/696] Change toc depth --- doc/source/license.rst | 2 +- doc/source/release.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/license.rst b/doc/source/license.rst index 0719ab2c..38f27eec 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -1,4 +1,4 @@ -:tocdepth: 2 +:tocdepth: -1 .. index:: license diff --git a/doc/source/release.rst b/doc/source/release.rst index 6ebf6dd6..27cd0cc9 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -1,4 +1,4 @@ -:tocdepth: 2 +:tocdepth: -1 .. index:: release notes From 3a69e6e4dac0bfcf65b62d806a10ef0711b1e47b Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 13 Jun 2024 21:46:13 +0000 Subject: [PATCH 158/696] Update CHANGELOG.rst --- CHANGELOG.rst | 18 ++++++++++++++++++ news/dump_with_info.rst | 23 ----------------------- news/tweaks.rst | 23 ----------------------- news/users.rst | 24 ------------------------ 4 files changed, 18 insertions(+), 70 deletions(-) delete mode 100644 news/dump_with_info.rst delete mode 100644 news/tweaks.rst delete mode 100644 news/users.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0509544a..a0750dae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,24 @@ Release Notes .. current developments +3.4.0 +===== + +**Added:** + +* utility for handling the capture of username and email for diffpy applications +* __eq__ method into Diffraction_object so we can equation two instances of a diffraction object + +**Changed:** + +* diffraction_object.dump now adds creation time and diffpy.utils version number to the output file + +**Fixed:** + +* fixed inadvertent overwrite of attributes on self.insert_scattering_quantity() + + + v3.3.0 ==================== diff --git a/news/dump_with_info.rst b/news/dump_with_info.rst deleted file mode 100644 index 862d1bbb..00000000 --- a/news/dump_with_info.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* diffraction_object.dump now adds creation time and diffpy.utils version number to the output file - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/tweaks.rst b/news/tweaks.rst deleted file mode 100644 index d35e97a7..00000000 --- a/news/tweaks.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* fixed inadvertent overwrite of attributes on self.insert_scattering_quantity() - -**Security:** - -* diff --git a/news/users.rst b/news/users.rst deleted file mode 100644 index 09b12e8f..00000000 --- a/news/users.rst +++ /dev/null @@ -1,24 +0,0 @@ -**Added:** - -* utility for handling the capture of username and email for diffpy applications -* __eq__ method into Diffraction_object so we can equation two instances of a diffraction object - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From 4feaf62218682ba0c2b36fc7cbbce2d66eb5a8c7 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Thu, 13 Jun 2024 17:50:08 -0400 Subject: [PATCH 159/696] Refactor doc workflow --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 03dffa5e..a4661f5c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,10 +34,10 @@ jobs: run: python -m pip install . --no-deps - name: build documents - run: make -C doc/manual html + run: make -C doc html - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/manual/build/html + publish_dir: ./doc/build/html From be351635f3eb34be5bba5b582be505dafef9462c Mon Sep 17 00:00:00 2001 From: Sparky <59151395+Sparks29032@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:49:29 -0400 Subject: [PATCH 160/696] Update .flake8 --- .flake8 | 1 - 1 file changed, 1 deletion(-) diff --git a/.flake8 b/.flake8 index 0b351c04..49384350 100644 --- a/.flake8 +++ b/.flake8 @@ -5,7 +5,6 @@ exclude = build, dist, versioneer.py, - labpdfproc/_version.py, doc/manual/source/conf.py max-line-length = 115 # Ignore some style 'errors' produced while formatting by 'black' (see link below) From 2b0aeec8728f89013523cbe35b6c8db5adc075be Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 4 Jul 2024 04:13:26 -0400 Subject: [PATCH 161/696] simplifying error message handling in diffraction objects. add .idea to gitigbore --- .gitignore | 3 ++ .../scattering_objects/diffraction_objects.py | 45 ++++++------------- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 313f3b88..f49476b4 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ setup.cfg # Rever rever/ + +# PyCharm +.idea/ diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index e809e018..bd5b16db 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -11,6 +11,11 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] +x_grid_emsg = ( + "objects are not on the same x-grid. You may add them using the self.add method " + "and specifying how to handle the mismatch." +) + class Diffraction_object: def __init__(self, name="", wavelength=None): @@ -57,10 +62,7 @@ def __add__(self, other): elif not isinstance(other, Diffraction_object): raise TypeError("I only know how to sum two Diffraction_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError( - "objects are not on the same x-grid. You may add them using the self.add method and" - "specifying how to handle the mismatch." - ) + raise RuntimeError(x_grid_emsg) else: summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] summed.on_q[1] = self.on_q[1] + other.on_q[1] @@ -74,10 +76,7 @@ def __radd__(self, other): elif not isinstance(other, Diffraction_object): raise TypeError("I only know how to sum two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError( - "objects are not on the same x-grid. You may add them using the self.add method and" - "specifying how to handle the mismatch." - ) + raise RuntimeError(x_grid_emsg) else: summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] summed.on_q[1] = self.on_q[1] + other.on_q[1] @@ -91,10 +90,7 @@ def __sub__(self, other): elif not isinstance(other, Diffraction_object): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError( - "objects are not on the same x-grid. You may subtract them using the self.add method and" - "specifying how to handle the mismatch." - ) + raise RuntimeError(x_grid_emsg) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] subtracted.on_q[1] = self.on_q[1] - other.on_q[1] @@ -108,10 +104,7 @@ def __rsub__(self, other): elif not isinstance(other, Diffraction_object): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError( - "objects are not on the same x-grid. You may subtract them using the self.add method and" - "specifying how to handle the mismatch." - ) + raise RuntimeError(x_grid_emsg) else: subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] subtracted.on_q[1] = other.on_q[1] - self.on_q[1] @@ -125,10 +118,7 @@ def __mul__(self, other): elif not isinstance(other, Diffraction_object): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError( - "objects are not on the same x-grid. You may multiply them using the self.add method and" - "specifying how to handle the mismatch." - ) + raise RuntimeError(x_grid_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -140,10 +130,7 @@ def __rmul__(self, other): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError( - "objects are not on the same x-grid. You may multiply them using the self.add method and" - "specifying how to handle the mismatch." - ) + raise RuntimeError(x_grid_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -157,10 +144,7 @@ def __truediv__(self, other): elif not isinstance(other, Diffraction_object): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError( - "objects are not on the same x-grid. You may multiply them using the self.add method and" - "specifying how to handle the mismatch." - ) + raise RuntimeError(x_grid_emsg) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] divided.on_q[1] = self.on_q[1] / other.on_q[1] @@ -172,10 +156,7 @@ def __rtruediv__(self, other): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError( - "Diffraction objects are not on the same x-grid. You may multiply them using the self.add" - "method and specifying how to handle the mismatch." - ) + raise RuntimeError(x_grid_emsg) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] divided.on_q[1] = other.on_q[1] / self.on_q[1] From 37b32dbabdf7d6b1aa932d5ea703390e42dfecf1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Sep 2024 13:02:48 -0400 Subject: [PATCH 162/696] Move tests dir to top level --- {src/diffpy/utils/tests => tests}/__init__.py | 0 {src/diffpy/utils/tests => tests}/conftest.py | 0 {src/diffpy/utils/tests => tests}/debug.py | 0 {src/diffpy/utils/tests => tests}/run.py | 0 {src/diffpy/utils/tests => tests}/test_diffraction_objects.py | 0 {src/diffpy/utils/tests => tests}/test_loaddata.py | 0 {src/diffpy/utils/tests => tests}/test_resample.py | 0 {src/diffpy/utils/tests => tests}/test_serialization.py | 0 {src/diffpy/utils/tests => tests}/test_tools.py | 0 {src/diffpy/utils/tests => tests}/testdata/dbload/e1.gr | 0 {src/diffpy/utils/tests => tests}/testdata/dbload/e2.gr | 0 {src/diffpy/utils/tests => tests}/testdata/dbload/e3.gr | 0 {src/diffpy/utils/tests => tests}/testdata/generated_db.json | 0 {src/diffpy/utils/tests => tests}/testdata/generatewarnings.txt | 0 {src/diffpy/utils/tests => tests}/testdata/loaddata01.txt | 0 .../diffpy/utils/tests => tests}/testdata/loaddatawithheaders.txt | 0 {src/diffpy/utils/tests => tests}/testdata/loadfile.txt | 0 {src/diffpy/utils/tests => tests}/testdata/strumining.json | 0 {src/diffpy/utils/tests => tests}/testdata/targetjson.json | 0 {src/diffpy/utils/tests => tests}/testdata/wrong.type | 0 {src/diffpy/utils/tests => tests}/testhelpers.py | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename {src/diffpy/utils/tests => tests}/__init__.py (100%) rename {src/diffpy/utils/tests => tests}/conftest.py (100%) rename {src/diffpy/utils/tests => tests}/debug.py (100%) rename {src/diffpy/utils/tests => tests}/run.py (100%) rename {src/diffpy/utils/tests => tests}/test_diffraction_objects.py (100%) rename {src/diffpy/utils/tests => tests}/test_loaddata.py (100%) rename {src/diffpy/utils/tests => tests}/test_resample.py (100%) rename {src/diffpy/utils/tests => tests}/test_serialization.py (100%) rename {src/diffpy/utils/tests => tests}/test_tools.py (100%) rename {src/diffpy/utils/tests => tests}/testdata/dbload/e1.gr (100%) rename {src/diffpy/utils/tests => tests}/testdata/dbload/e2.gr (100%) rename {src/diffpy/utils/tests => tests}/testdata/dbload/e3.gr (100%) rename {src/diffpy/utils/tests => tests}/testdata/generated_db.json (100%) rename {src/diffpy/utils/tests => tests}/testdata/generatewarnings.txt (100%) rename {src/diffpy/utils/tests => tests}/testdata/loaddata01.txt (100%) rename {src/diffpy/utils/tests => tests}/testdata/loaddatawithheaders.txt (100%) rename {src/diffpy/utils/tests => tests}/testdata/loadfile.txt (100%) rename {src/diffpy/utils/tests => tests}/testdata/strumining.json (100%) rename {src/diffpy/utils/tests => tests}/testdata/targetjson.json (100%) rename {src/diffpy/utils/tests => tests}/testdata/wrong.type (100%) rename {src/diffpy/utils/tests => tests}/testhelpers.py (100%) diff --git a/src/diffpy/utils/tests/__init__.py b/tests/__init__.py similarity index 100% rename from src/diffpy/utils/tests/__init__.py rename to tests/__init__.py diff --git a/src/diffpy/utils/tests/conftest.py b/tests/conftest.py similarity index 100% rename from src/diffpy/utils/tests/conftest.py rename to tests/conftest.py diff --git a/src/diffpy/utils/tests/debug.py b/tests/debug.py similarity index 100% rename from src/diffpy/utils/tests/debug.py rename to tests/debug.py diff --git a/src/diffpy/utils/tests/run.py b/tests/run.py similarity index 100% rename from src/diffpy/utils/tests/run.py rename to tests/run.py diff --git a/src/diffpy/utils/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py similarity index 100% rename from src/diffpy/utils/tests/test_diffraction_objects.py rename to tests/test_diffraction_objects.py diff --git a/src/diffpy/utils/tests/test_loaddata.py b/tests/test_loaddata.py similarity index 100% rename from src/diffpy/utils/tests/test_loaddata.py rename to tests/test_loaddata.py diff --git a/src/diffpy/utils/tests/test_resample.py b/tests/test_resample.py similarity index 100% rename from src/diffpy/utils/tests/test_resample.py rename to tests/test_resample.py diff --git a/src/diffpy/utils/tests/test_serialization.py b/tests/test_serialization.py similarity index 100% rename from src/diffpy/utils/tests/test_serialization.py rename to tests/test_serialization.py diff --git a/src/diffpy/utils/tests/test_tools.py b/tests/test_tools.py similarity index 100% rename from src/diffpy/utils/tests/test_tools.py rename to tests/test_tools.py diff --git a/src/diffpy/utils/tests/testdata/dbload/e1.gr b/tests/testdata/dbload/e1.gr similarity index 100% rename from src/diffpy/utils/tests/testdata/dbload/e1.gr rename to tests/testdata/dbload/e1.gr diff --git a/src/diffpy/utils/tests/testdata/dbload/e2.gr b/tests/testdata/dbload/e2.gr similarity index 100% rename from src/diffpy/utils/tests/testdata/dbload/e2.gr rename to tests/testdata/dbload/e2.gr diff --git a/src/diffpy/utils/tests/testdata/dbload/e3.gr b/tests/testdata/dbload/e3.gr similarity index 100% rename from src/diffpy/utils/tests/testdata/dbload/e3.gr rename to tests/testdata/dbload/e3.gr diff --git a/src/diffpy/utils/tests/testdata/generated_db.json b/tests/testdata/generated_db.json similarity index 100% rename from src/diffpy/utils/tests/testdata/generated_db.json rename to tests/testdata/generated_db.json diff --git a/src/diffpy/utils/tests/testdata/generatewarnings.txt b/tests/testdata/generatewarnings.txt similarity index 100% rename from src/diffpy/utils/tests/testdata/generatewarnings.txt rename to tests/testdata/generatewarnings.txt diff --git a/src/diffpy/utils/tests/testdata/loaddata01.txt b/tests/testdata/loaddata01.txt similarity index 100% rename from src/diffpy/utils/tests/testdata/loaddata01.txt rename to tests/testdata/loaddata01.txt diff --git a/src/diffpy/utils/tests/testdata/loaddatawithheaders.txt b/tests/testdata/loaddatawithheaders.txt similarity index 100% rename from src/diffpy/utils/tests/testdata/loaddatawithheaders.txt rename to tests/testdata/loaddatawithheaders.txt diff --git a/src/diffpy/utils/tests/testdata/loadfile.txt b/tests/testdata/loadfile.txt similarity index 100% rename from src/diffpy/utils/tests/testdata/loadfile.txt rename to tests/testdata/loadfile.txt diff --git a/src/diffpy/utils/tests/testdata/strumining.json b/tests/testdata/strumining.json similarity index 100% rename from src/diffpy/utils/tests/testdata/strumining.json rename to tests/testdata/strumining.json diff --git a/src/diffpy/utils/tests/testdata/targetjson.json b/tests/testdata/targetjson.json similarity index 100% rename from src/diffpy/utils/tests/testdata/targetjson.json rename to tests/testdata/targetjson.json diff --git a/src/diffpy/utils/tests/testdata/wrong.type b/tests/testdata/wrong.type similarity index 100% rename from src/diffpy/utils/tests/testdata/wrong.type rename to tests/testdata/wrong.type diff --git a/src/diffpy/utils/tests/testhelpers.py b/tests/testhelpers.py similarity index 100% rename from src/diffpy/utils/tests/testhelpers.py rename to tests/testhelpers.py From fa20e3d7eabc3d3ca1b9f4d786e3d8663ded2d01 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Sep 2024 13:27:00 -0400 Subject: [PATCH 163/696] Implement test helper as fixture --- tests/conftest.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index e3b63139..dd493352 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import json +from importlib.resources import as_file, files from pathlib import Path import pytest @@ -17,3 +18,20 @@ def user_filesystem(tmp_path): json.dump(home_config_data, f) yield tmp_path + + +def get_datafile(filename): + """Helper function to retrieve the file path for test data.""" + ref = files(__package__) / f"testdata/{filename}" + with as_file(ref) as rv: + return rv + + +@pytest.fixture +def datafile(): + """Fixture to dynamically load any test file.""" + + def _load(filename): + return get_datafile(filename) + + return _load \ No newline at end of file From a36af71eb77f046c8bd692a6ec79f9b5439db94f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Sep 2024 13:27:23 -0400 Subject: [PATCH 164/696] Remove unittest run files --- tests/debug.py | 35 ----------------------------------- tests/run.py | 37 ------------------------------------- 2 files changed, 72 deletions(-) delete mode 100644 tests/debug.py delete mode 100644 tests/run.py diff --git a/tests/debug.py b/tests/debug.py deleted file mode 100644 index fed20421..00000000 --- a/tests/debug.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.utils Complex Modeling Initiative -# (c) 2016 Brookhaven Science Associates, -# Brookhaven National Laboratory. -# All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -""" -Convenience module for debugging the unit tests using - -python -m diffpy.utils.tests.debug - -Exceptions raised by failed tests or other errors are not caught. -""" - - -if __name__ == "__main__": - import sys - - from diffpy.utils.tests import testsuite - - pattern = sys.argv[1] if len(sys.argv) > 1 else "" - suite = testsuite(pattern) - suite.debug() - - -# End of file diff --git a/tests/run.py b/tests/run.py deleted file mode 100644 index 241caf11..00000000 --- a/tests/run.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.utils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2012 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## - -"""Convenience module for executing all unit tests with - -python -m diffpy.utils.tests.run -""" - -if __name__ == "__main__": - import sys - - # show warnings by default - if not sys.warnoptions: - import os - import warnings - - warnings.simplefilter("default") - # also affect subprocesses - os.environ["PYTHONWARNINGS"] = "default" - from diffpy.utils.tests import test - - # produce zero exit code for a successful test - sys.exit(not test().wasSuccessful()) - -# End of file From ccce7f2d153e5b779578d7105492e57e63f211be Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Sep 2024 14:48:58 -0400 Subject: [PATCH 165/696] Remove content of pytest.ini --- pytest.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index 21473ae6..eea2c180 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1 @@ [pytest] -testpaths= src/diffpy/utils/tests -python_files = test_*.py From cc5305dcc1327c89ee459a35edfda7ec27dbd97c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Sep 2024 14:49:15 -0400 Subject: [PATCH 166/696] Run init files for unittest --- tests/__init__.py | 81 ----------------------------------------------- 1 file changed, 81 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 42a123f3..e69de29b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,81 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.utils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2011 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## - -"""Unit tests for diffpy.utils. -""" - -import unittest - - -def testsuite(pattern=""): - """Create a unit tests suite for diffpy.utils package. - - Parameters - ---------- - pattern : str, optional - Regular expression pattern for selecting test cases. - Select all tests when empty. Ignore the pattern when - any of unit test modules fails to import. - - Returns - ------- - suite : `unittest.TestSuite` - The TestSuite object containing the matching tests. - """ - import re - from importlib.resources import as_file, files - from itertools import chain - from os.path import dirname - - loader = unittest.defaultTestLoader - ref = files(__package__) - with as_file(ref) as thisdir: - depth = __name__.count(".") + 1 - topdir = thisdir - for i in range(depth): - topdir = dirname(topdir) - suite_all = loader.discover(str(thisdir), top_level_dir=topdir) - # always filter the suite by pattern to test-cover the selection code. - suite = unittest.TestSuite() - rx = re.compile(pattern) - tsuites = list(chain.from_iterable(suite_all)) - tsok = all(isinstance(ts, unittest.TestSuite) for ts in tsuites) - if not tsok: # pragma: no cover - return suite_all - tcases = chain.from_iterable(tsuites) - for tc in tcases: - tcwords = tc.id().split(".") - shortname = ".".join(tcwords[-3:]) - if rx.search(shortname): - suite.addTest(tc) - # verify all tests are found for an empty pattern. - assert pattern or suite_all.countTestCases() == suite.countTestCases() - return suite - - -def test(): - """Execute all unit tests for the diffpy.utils package. - - Returns - ------- - result : `unittest.TestResult` - """ - suite = testsuite() - runner = unittest.TextTestRunner() - result = runner.run(suite) - return result - - -# End of file From 2d5e5716cfea89a066323d13c4d1c3a100bb6d72 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Sep 2024 14:49:26 -0400 Subject: [PATCH 167/696] Use conftest within unittesT --- tests/test_loaddata.py | 12 ++++++++---- tests/test_serialization.py | 23 +++++++++++------------ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index 9399e71d..a7221d0e 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -4,20 +4,22 @@ """ import unittest +import pytest import numpy from diffpy.utils.parsers import loadData -from diffpy.utils.tests.testhelpers import datafile - -loaddata01 = datafile("loaddata01.txt") -loaddatawithheaders = datafile("loaddatawithheaders.txt") ############################################################################## class TestLoadData(unittest.TestCase): + @pytest.fixture(autouse=True) + def prepare_fixture(self, datafile): + self.datafile = datafile + def test_loadData_default(self): """check loadData() with default options""" + loaddata01 = self.datafile("loaddata01.txt") d2c = numpy.array([[3, 31], [4, 32], [5, 33]]) self.assertRaises(IOError, loadData, "doesnotexist") # the default minrows=10 makes it read from the third line @@ -35,6 +37,7 @@ def test_loadData_default(self): def test_loadData_1column(self): """check loading of one-column data.""" + loaddata01 = self.datafile("loaddata01.txt") d1c = numpy.arange(1, 6) d = loadData(loaddata01, usecols=[0], minrows=1) self.assertTrue(numpy.array_equal(d1c, d)) @@ -46,6 +49,7 @@ def test_loadData_1column(self): def test_loadData_headers(self): """check loadData() with headers options enabled""" + loaddatawithheaders = self.datafile("loaddatawithheaders.txt") hignore = ["# ", "// ", "["] # ignore lines beginning with these strings delimiter = ": " # what our data should be separated by hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) diff --git a/tests/test_serialization.py b/tests/test_serialization.py index bbe6be24..986892e7 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -5,21 +5,15 @@ from diffpy.utils.parsers import deserialize_data, loadData, serialize_data from diffpy.utils.parsers.custom_exceptions import ImproperSizeError, UnsupportedTypeError -from diffpy.utils.tests.testhelpers import datafile tests_dir = os.path.dirname(os.path.abspath(locals().get("__file__", "file.py"))) -targetjson = datafile("targetjson.json") -schemaname = datafile("strumining.json") -wrongtype = datafile("wrong.type") -loadfile = datafile("loadfile.txt") -warningfile = datafile("generatewarnings.txt") -nodt = datafile("loaddatawithheaders.txt") - -def test_load_multiple(tmp_path): - # generate json and apply schema +def test_load_multiple(tmp_path, datafile): + # Load test data + targetjson = datafile("targetjson.json") generatedjson = tmp_path / "generated_serialization.json" + tlm_list = os.listdir(os.path.join(tests_dir, "testdata", "dbload")) tlm_list.sort() generated_data = None @@ -50,7 +44,12 @@ def test_load_multiple(tmp_path): assert target_data == deserialize_data(generatedjson, filetype=".json") -def test_exceptions(): +def test_exceptions(datafile): + # Load test data + wrongtype = datafile("wrong.type") + loadfile = datafile("loadfile.txt") + warningfile = datafile("generatewarnings.txt") + nodt = datafile("loaddatawithheaders.txt") hdata = loadData(loadfile, headers=True) data_table = loadData(loadfile) @@ -107,4 +106,4 @@ def test_exceptions(): ) assert len(record) == 4 for msg in record: - assert "overwritten" in msg.message.args[0] + assert "overwritten" in msg.message.args[0] \ No newline at end of file From 19a7da822a7d92a9c9f7eba7413fc15fbe054843 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 18:52:14 +0000 Subject: [PATCH 168/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/conftest.py | 2 +- tests/test_loaddata.py | 4 ++-- tests/test_serialization.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index dd493352..09551da5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,4 +34,4 @@ def datafile(): def _load(filename): return get_datafile(filename) - return _load \ No newline at end of file + return _load diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index a7221d0e..2ca2fa07 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -4,9 +4,9 @@ """ import unittest -import pytest import numpy +import pytest from diffpy.utils.parsers import loadData @@ -16,7 +16,7 @@ class TestLoadData(unittest.TestCase): @pytest.fixture(autouse=True) def prepare_fixture(self, datafile): self.datafile = datafile - + def test_loadData_default(self): """check loadData() with default options""" loaddata01 = self.datafile("loaddata01.txt") diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 986892e7..51921e01 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -106,4 +106,4 @@ def test_exceptions(datafile): ) assert len(record) == 4 for msg in record: - assert "overwritten" in msg.message.args[0] \ No newline at end of file + assert "overwritten" in msg.message.args[0] From e0aaad1862648bc26e44ed644c65d2bfef2b6951 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 8 Sep 2024 11:03:53 -0400 Subject: [PATCH 169/696] Include latest CI files from cookiecutter --- .github/workflows/codecov.yml | 53 ++++++++++++++++++++++++++++++++ .github/workflows/docs.yml | 49 ++++++++++++++++-------------- .github/workflows/main.yml | 57 ++++++++++++++--------------------- 3 files changed, 102 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/codecov.yml diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 00000000..67c555eb --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,53 @@ +name: Gather coverage report and upload to codecov + +on: + push: + branches: + - main + release: + types: + - prereleased + - published + workflow_dispatch: + +defaults: + run: + shell: bash -l {0} + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - name: Check out diffpy.utils + uses: actions/checkout@v4 + + - name: Initialize miniconda + uses: conda-incubator/setup-miniconda@v3 + with: + activate-environment: test + auto-update-conda: true + environment-file: environment.yml + auto-activate-base: false + + - name: Conda config + run: >- + conda config --set always_yes yes + --set changeps1 no + + - name: Install diffpy.utils and requirements + run: | + conda install --file requirements/run.txt + conda install --file requirements/test.txt + python -m pip install -r requirements/pip.txt + python -m pip install . --no-deps + + - name: Validate diffpy.utils + run: | + coverage run -m pytest -vv -s + coverage report -m + codecov + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a4661f5c..2dfe9640 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,43 +1,48 @@ -name: Build Documentation +name: Build and Deploy Documentation on: - push: - branches: - - main release: + types: + - published + workflow_dispatch: + +defaults: + run: + shell: bash -l {0} jobs: - test: + docs: runs-on: ubuntu-latest - defaults: - run: - shell: bash -l {0} steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 + - name: Check out diffpy.utils + uses: actions/checkout@v4 - - uses: conda-incubator/setup-miniconda@v2 + - name: Initialize miniconda + uses: conda-incubator/setup-miniconda@v3 with: activate-environment: build auto-update-conda: true + environment-file: environment.yml + auto-activate-base: false - - name: install requirements + - name: Conda config run: >- - conda install -n build -c conda-forge - --file requirements/build.txt - --file requirements/run.txt - --file requirements/docs.txt - --quiet --yes + conda config --set always_yes yes + --set changeps1 no - - name: install the package - run: python -m pip install . --no-deps + - name: Install diffpy.utils and build requirements + run: | + conda install --file requirements/build.txt + conda install --file requirements/run.txt + conda install --file requirements/docs.txt + python -m pip install -r requirements/pip.txt + python -m pip install . --no-deps - name: build documents run: make -C doc html - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/build/html + publish_dir: ./doc/build/html \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c39ace43..eeffc764 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,55 +1,42 @@ -name: CI +name: Test on: push: branches: - main - - CI pull_request: workflow_dispatch: +defaults: + run: + shell: bash -l {0} + jobs: - miniconda: - name: Miniconda ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: ["ubuntu-latest"] + validate: + runs-on: ubuntu-latest steps: - - name: check out diffpy.utils - uses: actions/checkout@v3 - with: - repository: diffpy/diffpy.utils - path: . - fetch-depth: 0 # avoid shallow clone with no tags + - name: Check out diffpy.utils + uses: actions/checkout@v4 - - name: initialize miniconda - # this uses a marketplace action that sets up miniconda in a way that makes - # it easier to use. I tried setting it up without this and it was a pain - uses: conda-incubator/setup-miniconda@v2 + - name: Initialize miniconda + uses: conda-incubator/setup-miniconda@v3 with: activate-environment: test - # environment.yml file is needed by this action. Because I don't want - # maintain this but rather maintain the requirements files it just has - # basic things in it like conda and pip - environment-file: ./environment.yml - python-version: 3 + auto-update-conda: true + environment-file: environment.yml auto-activate-base: false - - name: install diffpy.utils requirements - shell: bash -l {0} + - name: Conda config + run: >- + conda config --set always_yes yes + --set changeps1 no + + - name: Install diffpy.utils and requirements run: | - conda config --set always_yes yes --set changeps1 no - conda config --add channels conda-forge - conda activate test conda install --file requirements/run.txt conda install --file requirements/test.txt - pip install . + python -m pip install -r requirements/pip.txt + python -m pip install . --no-deps - name: Validate diffpy.utils - shell: bash -l {0} - run: | - conda activate test - coverage run run_tests.py - coverage report -m - codecov + run: python -m pytest \ No newline at end of file From 567429eceb3d3b19fb2f37766e154372f73826e9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 8 Sep 2024 11:07:45 -0400 Subject: [PATCH 170/696] Include empty pip.txt file to satisfy GH CI --- requirements/pip.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 requirements/pip.txt diff --git a/requirements/pip.txt b/requirements/pip.txt new file mode 100644 index 00000000..e69de29b From 163f509f15337cecf57bf847541ca75f400930fb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 8 Sep 2024 11:10:52 -0400 Subject: [PATCH 171/696] Remove gitarchive, gitattributes --- .gitarchive.cfg | 5 ---- .gitattributes | 8 ------ devutils/makesdist | 61 ---------------------------------------------- 3 files changed, 74 deletions(-) delete mode 100644 .gitarchive.cfg delete mode 100644 .gitattributes delete mode 100755 devutils/makesdist diff --git a/.gitarchive.cfg b/.gitarchive.cfg deleted file mode 100644 index 95e1448c..00000000 --- a/.gitarchive.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[DEFAULT] -commit = $Format:%H$ -date = $Format:%ci$ -timestamp = $Format:%ct$ -refnames = $Format:%D$ diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 13fc0b54..00000000 --- a/.gitattributes +++ /dev/null @@ -1,8 +0,0 @@ -/.gitattributes export-ignore -/.gitignore export-ignore -/.travis.yml export-ignore -/conda-recipe/ export-ignore -/devutils export-ignore -/doc export-ignore -.gitarchive.cfg export-subst -*.bat text eol=crlf diff --git a/devutils/makesdist b/devutils/makesdist deleted file mode 100755 index 8b0a2179..00000000 --- a/devutils/makesdist +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python - -"""Create source distribution tar.gz archive, where each file belongs -to a root user and modification time is set to the git commit time. -""" - -import glob -import gzip -import os -import subprocess -import sys -import tarfile - -from setup import FALLBACK_VERSION, versiondata - -BASEDIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -sys.path.insert(0, BASEDIR) - - -timestamp = versiondata.getint("DEFAULT", "timestamp") - -vfb = versiondata.get("DEFAULT", "version").split(".post")[0] + ".post0" -emsg = "Invalid FALLBACK_VERSION. Expected %r got %r." -assert vfb == FALLBACK_VERSION, emsg % (vfb, FALLBACK_VERSION) - - -def inform(s): - sys.stdout.write(s) - sys.stdout.flush() - return - - -inform('Run "setup.py sdist --formats=tar" ') -cmd_sdist = [sys.executable] + "setup.py sdist --formats=tar".split() -ec = subprocess.call(cmd_sdist, cwd=BASEDIR, stdout=open(os.devnull, "w")) -if ec: - sys.exit(ec) -inform("[done]\n") - -tarname = max(glob.glob(BASEDIR + "/dist/*.tar"), key=os.path.getmtime) - -tfin = tarfile.open(tarname) -fpout = gzip.GzipFile(tarname + ".gz", "w", mtime=0) -tfout = tarfile.open(fileobj=fpout, mode="w") - - -def fixtarinfo(tinfo): - tinfo.uid = tinfo.gid = 0 - tinfo.uname = tinfo.gname = "root" - tinfo.mtime = timestamp - tinfo.mode &= ~0o022 - return tinfo - - -inform("Filter %s --> %s.gz " % (2 * (os.path.basename(tarname),))) -for ti in tfin: - tfout.addfile(fixtarinfo(ti), tfin.extractfile(ti)) - -tfin.close() -os.remove(tarname) -inform("[done]\n") From 3882fdf085f5dbb618838883850217756c37d963 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 8 Sep 2024 11:11:06 -0400 Subject: [PATCH 172/696] Remove rever, run_tests --- rever.xsh | 18 ------------------ run_tests.py | 17 ----------------- 2 files changed, 35 deletions(-) delete mode 100644 rever.xsh delete mode 100644 run_tests.py diff --git a/rever.xsh b/rever.xsh deleted file mode 100644 index f1a87228..00000000 --- a/rever.xsh +++ /dev/null @@ -1,18 +0,0 @@ -$PROJECT = 'diffpy.utils' -$ACTIVITIES = [ - 'changelog', # Create a CHANGELOG.rst using news directory - 'tag', # Creates a tag for the new version number - 'push_tag', # Pushes the tag up to the $TAG_REMOTE - 'ghrelease', # Creates a Github release entry for the new tag - 'pypi', # Sends the package to pypi - ] -$CHANGELOG_FILENAME = 'CHANGELOG.rst' -$CHANGELOG_TEMPLATE = 'TEMPLATE.rst' -$CHANGELOG_IGNORE = ['TEMPLATE.rst'] -$PUSH_TAG_REMOTE = 'git@github.com:diffpy/diffpy.utils.git' # Repo to push tags to -$GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge -$GITHUB_REPO = 'diffpy.utils' # Github repo for Github releases and conda-forge -$GHRELEASE_PREPEND = """See [CHANGELOG.rst](CHANGELOG.rst) for detailed release notes. - -The release is also available at [PyPI](https://pypi.org/project/diffpy.utils/) and [Conda](https://anaconda.org/conda-forge/diffpy.utils). -""" # release message diff --git a/run_tests.py b/run_tests.py deleted file mode 100644 index 9ab1aabb..00000000 --- a/run_tests.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python - -import sys - -import pytest - -if __name__ == "__main__": - # show output results from every test function - args = ["-v"] - # show the message output for skipped and expected failure tests - if len(sys.argv) > 1: - args.extend(sys.argv[1:]) - print("pytest arguments: {}".format(args)) - # # compute coverage stats for xpdAcq - # call pytest and exit with the return code from pytest - exit_res = pytest.main(args) - sys.exit(exit_res) From e176a37231367440b26406ca91964f7226b745fe Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 8 Sep 2024 11:12:24 -0400 Subject: [PATCH 173/696] Apply pre-commit --- .github/workflows/codecov.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 2 +- MANIFEST.in | 16 ++++------------ pyproject.toml | 6 +++--- 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 67c555eb..e8bae0c8 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -50,4 +50,4 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2dfe9640..7b3ac4ce 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -45,4 +45,4 @@ jobs: uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/build/html \ No newline at end of file + publish_dir: ./doc/build/html diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eeffc764..fb0cb88b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,4 +39,4 @@ jobs: python -m pip install . --no-deps - name: Validate diffpy.utils - run: python -m pytest \ No newline at end of file + run: python -m pytest diff --git a/MANIFEST.in b/MANIFEST.in index 1e928c83..56f4a490 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,15 +1,7 @@ recursive-include src * +recursive-include tests * include AUTHORS.txt LICENSE*.txt README.rst -recursive-exclude src *.py[co] -global-exclude .gitattributes .gitignore .gitarchive.cfg -global-exclude .DS_Store -# Avoid user content in setup.cfg to make distribution reproducible. -exclude setup.cfg - -# Exclude git-tracked files spuriously added by setuptools_scm -exclude .coveragerc -exclude .travis* -prune conda-recipe -prune devutils -prune doc +# Exclude all bytecode files and __pycache__ directories +recursive-exclude src *.pyc *.pyo __pycache__/ +recursive-exclude tests *.pyc *.pyo __pycache__/ diff --git a/pyproject.toml b/pyproject.toml index 5d54b46f..5d8d0e57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ requires = ["setuptools>=62.0", "setuptools-git-versioning<2"] build-backend = "setuptools.build_meta" [project] -name = "diffpy.utils" -dynamic=['version'] +name = "diffpy.utilstestlee" +version = "3.2.0rc10" authors = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] @@ -46,7 +46,7 @@ dirty_template = "{tag}" [tool.setuptools.packages.find] where = ["src"] # list of folders that contain the packages (["."] by default) include = ["diffpy*"] # package names should match these glob patterns (["*"] by default) -exclude = ["diffpy.utils.tests*"] # exclude packages matching these glob patterns (empty by default) +exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) [tool.black] From b1966f106b02fb2dd35dd82b9779628ba2393109 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 8 Sep 2024 11:19:30 -0400 Subject: [PATCH 174/696] Use unmodified MANIFEST, pyproject files --- MANIFEST.in | 16 ++++++++++++---- pyproject.toml | 8 ++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 56f4a490..1e928c83 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,15 @@ recursive-include src * -recursive-include tests * include AUTHORS.txt LICENSE*.txt README.rst +recursive-exclude src *.py[co] +global-exclude .gitattributes .gitignore .gitarchive.cfg +global-exclude .DS_Store -# Exclude all bytecode files and __pycache__ directories -recursive-exclude src *.pyc *.pyo __pycache__/ -recursive-exclude tests *.pyc *.pyo __pycache__/ +# Avoid user content in setup.cfg to make distribution reproducible. +exclude setup.cfg + +# Exclude git-tracked files spuriously added by setuptools_scm +exclude .coveragerc +exclude .travis* +prune conda-recipe +prune devutils +prune doc diff --git a/pyproject.toml b/pyproject.toml index 5d8d0e57..73a50ceb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ requires = ["setuptools>=62.0", "setuptools-git-versioning<2"] build-backend = "setuptools.build_meta" [project] -name = "diffpy.utilstestlee" -version = "3.2.0rc10" +name = "diffpy.utils" +dynamic=['version'] authors = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] @@ -46,7 +46,7 @@ dirty_template = "{tag}" [tool.setuptools.packages.find] where = ["src"] # list of folders that contain the packages (["."] by default) include = ["diffpy*"] # package names should match these glob patterns (["*"] by default) -exclude = [] # exclude packages matching these glob patterns (empty by default) +exclude = ["diffpy.utils.tests*"] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) [tool.black] @@ -70,4 +70,4 @@ exclude = ''' | blib2to3 | tests/data )/ -''' +''' \ No newline at end of file From f5904fd4d2a312e0b51f944376ea4d0fb50702d6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:19:38 +0000 Subject: [PATCH 175/696] [pre-commit.ci] auto fixes from pre-commit hooks --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 73a50ceb..5d54b46f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,4 +70,4 @@ exclude = ''' | blib2to3 | tests/data )/ -''' \ No newline at end of file +''' From 8db3d9db3f4d7273a2646adc735c2386280e4910 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 8 Sep 2024 11:20:23 -0400 Subject: [PATCH 176/696] Remove unused testhelper.py --- pyproject.toml | 2 +- tests/testhelpers.py | 30 ------------------------------ 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 tests/testhelpers.py diff --git a/pyproject.toml b/pyproject.toml index 73a50ceb..5d54b46f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,4 +70,4 @@ exclude = ''' | blib2to3 | tests/data )/ -''' \ No newline at end of file +''' diff --git a/tests/testhelpers.py b/tests/testhelpers.py deleted file mode 100644 index aaa2a519..00000000 --- a/tests/testhelpers.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.utils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2011 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## - -"""Helper routines for running other unit tests. -""" - -# helper functions - - -def datafile(filename): - from importlib.resources import as_file, files - - ref = files(__package__) / ("testdata/" + filename) - with as_file(ref) as rv: - return rv - - -# End of file From bf2637245141ad0996bbe3c28a27c6c602adb5fc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Sep 2024 06:53:54 -0400 Subject: [PATCH 177/696] Trigger CI From 17b1c8b535d5582d929c91c50d6a116e444161a8 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 13:04:20 -0400 Subject: [PATCH 178/696] new workflow files --- .github/workflows/check-news-item.yml | 12 ++++++++++++ .../matrix-and-codecov-on-merge-to-main.yml | 19 +++++++++++++++++++ .github/workflows/publish-docs-on-release.yml | 14 ++++++++++++++ .github/workflows/tests-on-pr.yml | 16 ++++++++++++++++ .pre-commit-config.yaml | 5 ++++- 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/check-news-item.yml create mode 100644 .github/workflows/matrix-and-codecov-on-merge-to-main.yml create mode 100644 .github/workflows/publish-docs-on-release.yml create mode 100644 .github/workflows/tests-on-pr.yml diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml new file mode 100644 index 00000000..be549960 --- /dev/null +++ b/.github/workflows/check-news-item.yml @@ -0,0 +1,12 @@ +name: Check for News + +on: + pull_request_target: + branches: + - main + +jobs: + check-news-item: + uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 + with: + project: diffpy.utils diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml new file mode 100644 index 00000000..091d9f50 --- /dev/null +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -0,0 +1,19 @@ +name: Codecov and matrix + +on: + push: + branches: + - main + release: + types: + - prereleased + - published + workflow_dispatch: + +jobs: + coverage-and-matrix: + uses: Billingegroup/release-scripts/.github/workflows/_coverage-and-matrix.yml@v0 + with: + project: diffpy.utils + c_extension: false + headless: false diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml new file mode 100644 index 00000000..56667dfa --- /dev/null +++ b/.github/workflows/publish-docs-on-release.yml @@ -0,0 +1,14 @@ +name: Build and Deploy Docs + +on: + release: + types: + - published + workflow_dispatch: + +jobs: + publish-docs-on-release: + uses: Billingegroup/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 + with: + project: diffpy.utils + c_extension: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml new file mode 100644 index 00000000..dd0a102e --- /dev/null +++ b/.github/workflows/tests-on-pr.yml @@ -0,0 +1,16 @@ +name: Tests on PR + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + tests-on-pr: + uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 + with: + project: diffpy.utils + c_extension: false + headless: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4588061..3070e199 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,10 @@ repos: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - exclude: '\.(rst|txt)$' + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-added-large-files - repo: https://github.com/psf/black rev: 24.4.2 hooks: From 834736a70ec83a25f46be6b2f60afe046554a535 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 13:08:24 -0400 Subject: [PATCH 179/696] adding authors, code of conduct etc. --- AUTHORS.rst | 10 ++++ CODE_OF_CONDUCT.rst | 133 ++++++++++++++++++++++++++++++++++++++++++++ LICENSE.rst | 30 ++++++++++ doc/make.bat | 36 ++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 AUTHORS.rst create mode 100644 CODE_OF_CONDUCT.rst create mode 100644 LICENSE.rst create mode 100644 doc/make.bat diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 00000000..cdb35e24 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,10 @@ +Authors +======= + +Billinge Group and community contibutors. + +Contributors +------------ + +For a list of contributors, visit +https://github.com/diffpy/diffpy.utils/graphs/contributors diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 00000000..ff9c3561 --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,133 @@ +===================================== + Contributor Covenant Code of Conduct +===================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sb2896@columbia.edu. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +**************** + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +************* + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +****************** + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +****************** + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant `_. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder `_. + +For answers to common questions about this code of conduct, see the `FAQ `_. `Translations are available `_ diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 00000000..95a04ac9 --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,30 @@ +BSD 3-Clause License + +Copyright (c) 2024, The Trustees of Columbia University +in the City of New York. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 00000000..2be83069 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=PackagingScientificPython + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd From 581d1a83a6c3dc1bb35920c5480049548ea56003 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 13:12:50 -0400 Subject: [PATCH 180/696] adding _static --- doc/source/_static/.placeholder | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/source/_static/.placeholder diff --git a/doc/source/_static/.placeholder b/doc/source/_static/.placeholder new file mode 100644 index 00000000..e69de29b From d9afddf7ec2c0e4d421447a82fa5394b78c9cc79 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 13:20:51 -0400 Subject: [PATCH 181/696] removing deleted files from git --- .github/workflows/codecov.yml | 53 ------------- .github/workflows/docs.yml | 48 ------------ .github/workflows/main.yml | 42 ----------- AUTHORS.txt | 5 -- LICENSE.txt | 137 ---------------------------------- LICENSE_DANSE.txt | 41 ---------- pytest.ini | 1 - 7 files changed, 327 deletions(-) delete mode 100644 .github/workflows/codecov.yml delete mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/main.yml delete mode 100644 AUTHORS.txt delete mode 100644 LICENSE.txt delete mode 100644 LICENSE_DANSE.txt delete mode 100644 pytest.ini diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml deleted file mode 100644 index e8bae0c8..00000000 --- a/.github/workflows/codecov.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Gather coverage report and upload to codecov - -on: - push: - branches: - - main - release: - types: - - prereleased - - published - workflow_dispatch: - -defaults: - run: - shell: bash -l {0} - -jobs: - coverage: - runs-on: ubuntu-latest - steps: - - name: Check out diffpy.utils - uses: actions/checkout@v4 - - - name: Initialize miniconda - uses: conda-incubator/setup-miniconda@v3 - with: - activate-environment: test - auto-update-conda: true - environment-file: environment.yml - auto-activate-base: false - - - name: Conda config - run: >- - conda config --set always_yes yes - --set changeps1 no - - - name: Install diffpy.utils and requirements - run: | - conda install --file requirements/run.txt - conda install --file requirements/test.txt - python -m pip install -r requirements/pip.txt - python -m pip install . --no-deps - - - name: Validate diffpy.utils - run: | - coverage run -m pytest -vv -s - coverage report -m - codecov - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 7b3ac4ce..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Build and Deploy Documentation - -on: - release: - types: - - published - workflow_dispatch: - -defaults: - run: - shell: bash -l {0} - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - name: Check out diffpy.utils - uses: actions/checkout@v4 - - - name: Initialize miniconda - uses: conda-incubator/setup-miniconda@v3 - with: - activate-environment: build - auto-update-conda: true - environment-file: environment.yml - auto-activate-base: false - - - name: Conda config - run: >- - conda config --set always_yes yes - --set changeps1 no - - - name: Install diffpy.utils and build requirements - run: | - conda install --file requirements/build.txt - conda install --file requirements/run.txt - conda install --file requirements/docs.txt - python -m pip install -r requirements/pip.txt - python -m pip install . --no-deps - - - name: build documents - run: make -C doc html - - - name: Deploy - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/build/html diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index fb0cb88b..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Test - -on: - push: - branches: - - main - pull_request: - workflow_dispatch: - -defaults: - run: - shell: bash -l {0} - -jobs: - validate: - runs-on: ubuntu-latest - steps: - - name: Check out diffpy.utils - uses: actions/checkout@v4 - - - name: Initialize miniconda - uses: conda-incubator/setup-miniconda@v3 - with: - activate-environment: test - auto-update-conda: true - environment-file: environment.yml - auto-activate-base: false - - - name: Conda config - run: >- - conda config --set always_yes yes - --set changeps1 no - - - name: Install diffpy.utils and requirements - run: | - conda install --file requirements/run.txt - conda install --file requirements/test.txt - python -m pip install -r requirements/pip.txt - python -m pip install . --no-deps - - - name: Validate diffpy.utils - run: python -m pytest diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 92b450db..00000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,5 +0,0 @@ -Pavol Juhas -Andrew yang -Timur Davis -Christopher L. Farrow -Simon J.L. Billinge diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index f6d92af7..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,137 +0,0 @@ -OPEN SOURCE LICENSE AGREEMENT -============================= - -Copyright (c) 2009-2011, University of Tennessee -Copyright (c) 1989, 1991 Free Software Foundation, Inc. -Copyright (c) 2006, The Regents of the University of California through - Lawrence Berkeley National Laboratory -Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") -Copyright (c) 2006-2007, Board of Trustees of Michigan State University -Copyright (c) 2008-2012, The Trustees of Columbia University in the City - of New York - -Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National - Laboratory - - -The "DiffPy-CMI" is distributed subject to the following license conditions: - - -SOFTWARE LICENSE AGREEMENT - - Software: DiffPy-CMI - - -(1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either -source code, or binary form and accompanying documentation). - -Part of the software was derived from the DANSE, ObjCryst++ (with permission), -PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of -which the original Copyrights are contained in each individual file. - -Each licensee is addressed as "you" or "Licensee." - - -(2) The copyright holders shown above and their third-party Licensors hereby -grant licensee a royalty-free nonexclusive license, subject to the limitations -stated herein and U.S. Government license rights. - - -(3) You may modify and make a copy or copies of the software for use within -your organization, if you meet the following conditions: - - (a) Copies in source code must include the copyright notice and this - software license agreement. - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy. - - -(4) You may modify a copy or copies of the Software or any portion of it, thus -forming a work based on the Software, and distribute copies of such work -outside your organization, if you meet all of the following conditions: - - (a) Copies in source code must include the copyright notice and this - Software License Agreement; - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy; - - (c) Modified copies and works based on the Software must carry prominent - notices stating that you changed specified portions of the Software. - - (d) Neither the name of Brookhaven Science Associates or Brookhaven - National Laboratory nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - written permission. - - -(5) Portions of the Software resulted from work developed under a U.S. -Government contract and are subject to the following license: -The Government is granted for itself and others acting on its behalf a -paid-up, nonexclusive, irrevocable worldwide license in this computer software -to reproduce, prepare derivative works, and perform publicly and display -publicly. - - -(6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT -WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY -LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND -THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING -BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL -LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF -THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE -PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION -UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. - - -(7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR -THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF -ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, -CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING -BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, -WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING -NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS -BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. - - -Brookhaven National Laboratory Notice -===================================== - -Acknowledgment of sponsorship ------------------------------ - -This software was produced by the Brookhaven National Laboratory, under -Contract DE-AC02-98CH10886 with the Department of Energy. - - -Government disclaimer of liability ----------------------------------- - -Neither the United States nor the United States Department of Energy, nor -any of their employees, makes any warranty, express or implied, or assumes -any legal liability or responsibility for the accuracy, completeness, or -usefulness of any data, apparatus, product, or process disclosed, or -represents that its use would not infringe privately owned rights. - - -Brookhaven disclaimer of liability ----------------------------------- - -Brookhaven National Laboratory makes no representations or warranties, -express or implied, nor assumes any liability for the use of this software. - - -Maintenance of notice ---------------------- - -In the interest of clarity regarding the origin and status of this -software, Brookhaven National Laboratory requests that any recipient of it -maintain this notice affixed to any distribution by the recipient that -contains a copy or derivative of this software. - - -END OF LICENSE diff --git a/LICENSE_DANSE.txt b/LICENSE_DANSE.txt deleted file mode 100644 index b7e1c702..00000000 --- a/LICENSE_DANSE.txt +++ /dev/null @@ -1,41 +0,0 @@ -This program is part of the DiffPy and DANSE open-source projects -and is available subject to the conditions and terms laid out below. - -Copyright 2006-2007, Board of Trustees of Michigan State University, -Copyright 2008-2012, The Trustees of Columbia University in the -city of New York. (Copyright holder indicated in each source file). - -For more information please visit the project web-page: - http://www.diffpy.org/ -or email Prof. Simon Billinge at sb2896@columbia.edu - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS". COPYRIGHT HOLDER -EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES AND CONDITIONS, EITHER -EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY, TITLE, FITNESS, ADEQUACY OR SUITABILITY -FOR A PARTICULAR PURPOSE, AND ANY WARRANTIES OF FREEDOM FROM -INFRINGEMENT OF ANY DOMESTIC OR FOREIGN PATENT, COPYRIGHTS, TRADE -SECRETS OR OTHER PROPRIETARY RIGHTS OF ANY PARTY. IN NO EVENT SHALL -COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE OR RELATING TO THIS AGREEMENT, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index eea2c180..00000000 --- a/pytest.ini +++ /dev/null @@ -1 +0,0 @@ -[pytest] From 8a29941e9783f85b450e713b3aed904f47dd6e13 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:40:48 +0000 Subject: [PATCH 182/696] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/examples/toolsexample.rst | 2 +- doc/source/utilities/toolsutility.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/examples/toolsexample.rst b/doc/source/examples/toolsexample.rst index d4d00cce..b3f615fd 100644 --- a/doc/source/examples/toolsexample.rst +++ b/doc/source/examples/toolsexample.rst @@ -56,4 +56,4 @@ Using the tools module, we can efficiently get them in terms of a dictionary. updated_dict = get_package_info("my_package", metadata=existing_dict)) note that `"diffpy.utils"` is automatically included in the package info since the `get_user_info` function is - part of diffpy.utils. + part of diffpy.utils. diff --git a/doc/source/utilities/toolsutility.rst b/doc/source/utilities/toolsutility.rst index 973056e5..01685a93 100644 --- a/doc/source/utilities/toolsutility.rst +++ b/doc/source/utilities/toolsutility.rst @@ -17,7 +17,7 @@ The ``diffpy.utils.tools`` module provides tool functions for use with diffpy ap ``{"package_info": {"package1": "version_number1", "package2": "version_number2"}`` if the function is called more than once. - Users can use these functions to track and manage versions of packages that can later be stored, for example, in an output - file header. + Users can use these functions to track and manage versions of packages that can later be stored, for example, in an output + file header. For a more in-depth tutorial for how to use these tools, click :ref:`here `. From 153f8437be8c927ea0a447bb8e704e639712fd60 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 14:28:39 -0400 Subject: [PATCH 183/696] finishing the recut --- .codecov.yml | 2 +- .coveragerc | 29 ++------- .flake8 | 5 +- .gitignore | 88 +++++++++++++++++++++------ MANIFEST.in | 23 ++++--- README.rst | 113 ++++++++++++++++++++++------------- doc/Makefile | 9 +-- doc/source/conf.py | 22 ++++--- doc/source/index.rst | 26 ++++---- pyproject.toml | 20 ++++--- src/diffpy/__init__.py | 11 ++-- src/diffpy/utils/__init__.py | 17 +++--- src/diffpy/utils/version.py | 14 ++--- tests/__init__.py | 0 14 files changed, 225 insertions(+), 154 deletions(-) delete mode 100644 tests/__init__.py diff --git a/.codecov.yml b/.codecov.yml index 03afe9c3..04dd6510 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -20,7 +20,7 @@ coverage: target: auto if_no_uploads: error if_not_found: success - if_ci_failed: failure + if_ci_failed: error paths: '!*/tests/.*' tests: diff --git a/.coveragerc b/.coveragerc index f6252ad3..0ed6eac5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,32 +1,13 @@ -# Configuration of the coverage.py tool for reporting test coverage. - +[run] +source = + diffpy.utils [report] -# RE patterns for lines to be excluded from consideration. -exclude_lines = - ## Have to re-enable the standard pragma - pragma: no cover - ## Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - ^[ ]*assert False - - ## Don't complain if non-runnable code isn't run: - ^[ ]*@unittest.skip\b - ^[ ]{4}unittest.main() - if __name__ == .__main__.: omit = */python?.?/* */site-packages/nose/* # ignore _version.py and versioneer.py .*version.* *_version.py - # ignore wx directory - */wx/* - -[run] -source = - src/diffpy/utils/ -omit = - ## exclude debug.py from codecov report - */tests/debug.py +exclude_lines = + if __name__ == '__main__': diff --git a/.flake8 b/.flake8 index 49384350..2d2cb168 100644 --- a/.flake8 +++ b/.flake8 @@ -4,9 +4,8 @@ exclude = __pycache__, build, dist, - versioneer.py, - doc/manual/source/conf.py + doc/source/conf.py max-line-length = 115 -# Ignore some style 'errors' produced while formatting by 'black' (see link below) +# Ignore some style 'errors' produced while formatting by 'black' # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings extend-ignore = E203 diff --git a/.gitignore b/.gitignore index f49476b4..a25212ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,99 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ *.py[cod] +*$py.class # C extensions *.so -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -temp -develop-eggs +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +venv/ +*.egg-info/ .installed.cfg -lib -lib64 -tags +*.egg +bin/ +temp/ +tags/ errors.err +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + # Installer logs pip-log.txt +pip-delete-this-directory.txt MANIFEST # Unit test / coverage reports +htmlcov/ +.tox/ .coverage -.tox +.coverage.* +.cache nosetests.xml +coverage.xml +*,cover +.hypothesis/ # Translations *.mo +*.pot # Mr Developer .mr.developer.cfg .project .pydevproject +# Django stuff: +*.log + +# Sphinx documentation +docs/build/ +docs/source/generated/ + +# pytest +.pytest_cache/ + +# PyBuilder +target/ + +# Editor files +# mac +.DS_Store +*~ + +# vim +*.swp +*.swo + +# pycharm +.idea/ + +# VSCode +.vscode/ + +# Ipython Notebook +.ipynb_checkpoints + # version information setup.cfg /src/diffpy/*/version.cfg # Rever rever/ - -# PyCharm -.idea/ diff --git a/MANIFEST.in b/MANIFEST.in index 1e928c83..f1a78eec 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,15 +1,12 @@ -recursive-include src * -include AUTHORS.txt LICENSE*.txt README.rst -recursive-exclude src *.py[co] -global-exclude .gitattributes .gitignore .gitarchive.cfg -global-exclude .DS_Store +graft src +graft tests +graft requirements -# Avoid user content in setup.cfg to make distribution reproducible. -exclude setup.cfg +include AUTHORS.rst LICENSE*.rst README.rst -# Exclude git-tracked files spuriously added by setuptools_scm -exclude .coveragerc -exclude .travis* -prune conda-recipe -prune devutils -prune doc +# Exclude all bytecode files and __pycache__ directories +global-exclude *.py[cod] # Exclude all .pyc, .pyo, and .pyd files. +global-exclude .DS_Store # Exclude Mac filesystem artifacts. +global-exclude __pycache__ # Exclude Python cache directories. +global-exclude .git* # Exclude git files and directories. +global-exclude .idea # Exclude PyCharm project settings. diff --git a/README.rst b/README.rst index b7b1ec35..1eb23adb 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,39 @@ -.. image:: https://github.com/diffpy/diffpy.utils/actions/workflows/main.yml/badge.svg - :target: https://github.com/diffpy/diffpy.utils/actions/workflows/main.yml +|Icon| |title|_ +=============== -.. image:: https://codecov.io/gh/diffpy/diffpy.utils/branch/main/graph/badge.svg - :target: https://codecov.io/gh/diffpy/diffpy.utils +.. |title| replace:: diffpy.utils +.. _title: https://diffpy.github.io/diffpy.utils +.. |Icon| image:: https://avatars.githubusercontent.com/diffpy + :target: https://diffpy.github.io/diffpy.utils + :height: 100px + +|PyPi| |Forge| |PythonVersion| |PR| + +|CI| |Codecov| |Black| |Tracking| + +.. |Black| image:: https://img.shields.io/badge/code_style-black-black + :target: https://github.com/psf/black + +.. |CI| image:: https://github.com/diffpy/diffpy.utils/actions/workflows/codecov-on-merge-to-main.yml/badge.svg + :target: https://github.com/diffpy/diffpy.utils/actions/workflows/codecov-on-merge-to-main.yml + +.. |Codecov| image:: https://codecov.io/gh/diffpy/diffpy.utils/branch/main/graph/badge.svg + :target: https://codecov.io/gh/diffpy/diffpy.utils + +.. |Forge| image:: https://img.shields.io/conda/vn/conda-forge/diffpy.utils + :target: https://anaconda.org/conda-forge/diffpy.utils + +.. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + +.. |PyPi| image:: https://img.shields.io/pypi/v/diffpy.utils + :target: https://pypi.org/project/diffpy.utils/ + +.. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.utils + :target: https://pypi.org/project/diffpy.utils/ + +.. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue + :target: https://github.com/diffpy/diffpy.utils/issues diffpy.utils ======================================================================== @@ -16,11 +46,15 @@ program. The package also includes an interpolation function based on the Whittaker-Shannon formula that can be used to resample a PDF or other profile function over a new grid. -For more information about the diffpy.utils library, see the users manual at -http://diffpy.github.io/diffpy.utils. +Citation +-------- + +If you use diffpy.utils in a scientific publication, we would like you to cite this package as + + diffpy.utils Package, https://github.com/diffpy/diffpy.utils -INSTALLATION ------------------------------------------------------------------------- +Installation +------------ The preferred method is to use `Miniconda Python `_ @@ -28,66 +62,65 @@ and install from the "conda-forge" channel of Conda packages. To add "conda-forge" to the conda channels, run the following in a terminal. :: - conda config --add channels conda-forge + conda config --add channels conda-forge We want to install our packages in a suitable conda environment. -The following creates and activates a new environment named ``diffpy-utils`` :: +The following creates and activates a new environment named ``diffpy.utils_env`` :: - conda create -n diffpy-utils python=3 - conda activate diffpy-utils + conda create -n diffpy.utils_env python=3 + conda activate diffpy.utils_env Then, to fully install ``diffpy.utils`` in our active environment, run :: - conda install diffpy.utils + conda install diffpy.utils Another option is to use ``pip`` to download and install the latest release from `Python Package Index `_. -To install using ``pip`` into your ``diffpy-utils`` environment, we will also have to install dependencies :: +To install using ``pip`` into your ``diffpy.utils_env`` environment, we will also have to install dependencies :: - pip install numpy - pip install diffpy.utils + pip install -r https://raw.githubusercontent.com/diffpy/diffpy.utils/main/requirements/run.txt -For those planning to use functions in the ``diffpy.utils.wx`` module, you will also need to install ``wxPython``. -Both of the following lines will install this package. :: +and then install the package :: - conda install wxPython - pip install wxPython + pip install diffpy.utils If you prefer to install from sources, after installing the dependencies, obtain the source archive from `GitHub `_. Once installed, ``cd`` into your ``diffpy.utils`` directory and run the following :: - pip install . + pip install . -To check the installation integrity, if the following passes all checks, you are good! :: +Support and Contribute +---------------------- - pip install pytest - python -m diffpy.utils.tests.run +`Diffpy user group `_ is the discussion forum for general questions and discussions about the use of diffpy.utils. Please join the diffpy.utils users community by joining the Google group. The diffpy.utils project welcomes your expertise and enthusiasm! +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. -DEVELOPMENT ------------------------------------------------------------------------- - -diffpy.utils is an open-source software developed as a part of the -DiffPy-CMI complex modeling initiative at the Brookhaven National -Laboratory. The diffpy.utils sources are hosted at -https://github.com/diffpy/diffpy.utils. - -Feel free to fork the project and contribute. To install diffpy.utils +Feel free to fork the project and contribute. To install diffpy.utils in a development mode, with its sources being directly used by Python rather than copied to a package directory, use the following in the root directory :: - pip install -e . + pip install -e . + +To ensure code quality and to prevent accidental commits into the default branch, please set up the use of our pre-commit +hooks. + +1. Install pre-commit in your working environment by running ``conda install pre-commit``. -Note that this is only supported for `setuptools` version 62.0 and above. +2. Initialize pre-commit (one time only) ``pre-commit install``. +Thereafter your code will be linted by black and isort and checked against flake8 before you can commit. +If it fails by black or isort, just rerun and it should pass (black and isort will modify the files so should +pass after they are modified). If the flake8 test fails please see the error messages and fix them manually before +trying to commit again. -CONTACTS ------------------------------------------------------------------------- +Improvements and fixes are always appreciated. -For more information on diffpy.utils please visit the project web-page +Before contribuing, please read our `Code of Conduct `_. -http://www.diffpy.org/ +Contact +------- -or email Prof. Simon Billinge at sb2896@columbia.edu. +For more information on diffpy.utils please visit the project `web-page `_ or email Prof. Simon Billinge at sb2896@columbia.edu. diff --git a/doc/Makefile b/doc/Makefile index 00a6ed10..623232f0 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -6,6 +6,7 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build +BASENAME = $(subst .,,$(subst $() $(),,diffpy.utils)) # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) @@ -85,17 +86,17 @@ qthelp: @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/diffpyutils.qhcp" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/$(BASENAME).qhcp" @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/diffpyutils.qhc" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(BASENAME).qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/diffpyutils" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/diffpyutils" + @echo "# mkdir -p $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/$(BASENAME)" @echo "# devhelp" epub: diff --git a/doc/source/conf.py b/doc/source/conf.py index e85a33da..721f940e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -13,20 +13,20 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import os import sys import time from importlib.metadata import version +from pathlib import Path # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath("../..")) -sys.path.insert(0, os.path.abspath("../../src")) +# documentation root, use Path().resolve() to make it absolute, like shown here. +# sys.path.insert(0, str(Path(".").resolve())) +sys.path.insert(0, str(Path("../..").resolve())) +sys.path.insert(0, str(Path("../../src").resolve())) # abbreviations -ab_authors = "Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge group" +ab_authors = "Pavol Juhás, Christopher L. Farrow, Billinge Group members and community contributors" # -- General configuration ------------------------------------------------ @@ -38,8 +38,11 @@ # ones. extensions = [ "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.todo", "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_rtd_theme", "m2r", ] @@ -59,7 +62,7 @@ # General information about the project. project = "diffpy.utils" -copyright = "%Y, Brookhaven National Laboratory" +copyright = "%Y, The Trustees of Columbia University in the City of New York" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -199,7 +202,8 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = "diffpyutilsdoc" +basename = "diffpy.utils".replace(" ", "").replace(".", "") +htmlhelp_basename = basename + "doc" # -- Options for LaTeX output --------------------------------------------- diff --git a/doc/source/index.rst b/doc/source/index.rst index 0cd373a6..f9f9fe80 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,8 +1,10 @@ -#################################################### -diffpy.utils documentation -#################################################### +####### +|title| +####### -diffpy.utils - general purpose shared utilities for the diffpy libraries. +.. |title| replace:: diffpy.utils documentation + +diffpy.utils - Shared utilities for diffpy packages. | Software version |release|. | Last updated |today|. @@ -28,22 +30,22 @@ Authors diffpy.utils is developed by members of the Billinge Group at Columbia University and at Brookhaven National Laboratory including -Pavol Juhás, Timur Davis, Christopher L. Farrow, Simon J.L. Billinge. +Pavol Juhás, Christopher L. Farrow, the Billinge Group +and its community contributors. For a detailed list of contributors see https://github.com/diffpy/diffpy.utils/graphs/contributors. -====================================== +============ Installation -====================================== +============ See the `README `_ file included with the distribution. -====================================== +================= Table of contents -====================================== - +================= .. toctree:: :titlesonly: @@ -53,9 +55,9 @@ Table of contents Examples Package API -====================================== +======= Indices -====================================== +======= * :ref:`genindex` * :ref:`search` diff --git a/pyproject.toml b/pyproject.toml index 5d54b46f..4337830d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,17 +4,17 @@ build-backend = "setuptools.build_meta" [project] name = "diffpy.utils" -dynamic=['version'] +dynamic=['version', 'dependencies'] authors = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] maintainers = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] -description = "Shared utilities for diffpy packages." -keywords = ["text data parsers", "wx grid", "diffraction objects"] +description = "Shared utilities for diffpy packages" +keywords = ['"text data parsers" "wx grid" "diffraction objects"'] readme = "README.rst" -requires-python = ">=3.9" +requires-python = ">=3.10" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -25,17 +25,16 @@ classifiers = [ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Topic :: Scientific/Engineering :: Physics', + 'Topic :: Scientific/Engineering :: Chemistry', ] [project.urls] Homepage = "https://github.com/diffpy/diffpy.utils/" -Issues = "https://github.com/diffpy/diffpy.utils/issues" +Issues = "https://github.com/diffpy/diffpy.utils/issues/" [tool.setuptools-git-versioning] enabled = true @@ -45,10 +44,13 @@ dirty_template = "{tag}" [tool.setuptools.packages.find] where = ["src"] # list of folders that contain the packages (["."] by default) -include = ["diffpy*"] # package names should match these glob patterns (["*"] by default) -exclude = ["diffpy.utils.tests*"] # exclude packages matching these glob patterns (empty by default) +include = ["*"] # package names should match these glob patterns (["*"] by default) +exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) +[tool.setuptools.dynamic] +dependencies = {file = ["requirements/run.txt"]} + [tool.black] line-length = 115 include = '\.pyi?$' diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index f37a7759..11a4204c 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -5,11 +5,15 @@ # Simon J. L. Billinge # (c) 2010 The Trustees of Columbia University # in the City of New York. All rights reserved. +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Pavol Juhas +# File coded by: Billinge Group members and community contributors. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.utils/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## @@ -23,5 +27,4 @@ __path__ = extend_path(__path__, __name__) - # End of file diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index 3abeab37..41a4fa54 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -1,20 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.utils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Chris Farrow, Pavol Juhas +# File coded by: Billinge Group members and community contributors. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.utils/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## -"""Smaller shared functions for use by other diffpy packages. -""" +"""Shared utilities for diffpy packages""" # package version from diffpy.utils.version import __version__ diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index 2566d69f..e42fed78 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -1,15 +1,15 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.utils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2011 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Pavol Juhas +# File coded by: Billinge Group members and community contributors. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.utils/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 From af47408596ae6977509245a866a954a4c613e266 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 14:38:59 -0400 Subject: [PATCH 184/696] force a build with readme tweak --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1eb23adb..ed6f27f6 100644 --- a/README.rst +++ b/README.rst @@ -35,7 +35,7 @@ .. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue :target: https://github.com/diffpy/diffpy.utils/issues -diffpy.utils +diffpy.utils Package ======================================================================== General purpose shared utilities for the diffpy libraries. From b8d458bbfde87f162dac8eaa7bd491677cdd99f9 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 14:46:50 -0400 Subject: [PATCH 185/696] fix conftest.py and news --- news/recut.rst | 23 +++++++++++++++++++++++ tests/conftest.py | 10 +--------- 2 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 news/recut.rst diff --git a/news/recut.rst b/news/recut.rst new file mode 100644 index 00000000..c8c0a36a --- /dev/null +++ b/news/recut.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Updated package structure to new group standard + +**Security:** + +* diff --git a/tests/conftest.py b/tests/conftest.py index 09551da5..5471acc1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ import json -from importlib.resources import as_file, files from pathlib import Path import pytest @@ -20,18 +19,11 @@ def user_filesystem(tmp_path): yield tmp_path -def get_datafile(filename): - """Helper function to retrieve the file path for test data.""" - ref = files(__package__) / f"testdata/{filename}" - with as_file(ref) as rv: - return rv - - @pytest.fixture def datafile(): """Fixture to dynamically load any test file.""" def _load(filename): - return get_datafile(filename) + return "tests/testdata/" + filename return _load From 6131f9fc915a1b47d2868eed436861df57c86574 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 14:58:58 -0400 Subject: [PATCH 186/696] fix matrix-workflow --- .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 091d9f50..b1102cce 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -12,7 +12,7 @@ on: jobs: coverage-and-matrix: - uses: Billingegroup/release-scripts/.github/workflows/_coverage-and-matrix.yml@v0 + uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.utils c_extension: false From 34261a1e8eae1b0e99ebd464c94fa47f34a4a766 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 16 Sep 2024 15:32:36 -0400 Subject: [PATCH 187/696] Include api doc --- doc/source/api/diffpy.utils.parsers.rst | 28 ++++++++++++------ doc/source/api/diffpy.utils.rst | 5 +++- .../api/diffpy.utils.scattering_objects.rst | 29 +++++++++++++++++++ doc/source/api/diffpy.utils.wx.rst | 8 +++-- 4 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 doc/source/api/diffpy.utils.scattering_objects.rst diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index 82c70ed7..223accd7 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -1,4 +1,4 @@ -.. _Parsers Documentation: +:tocdepth: -1 diffpy.utils.parsers package ============================ @@ -8,28 +8,38 @@ diffpy.utils.parsers package :undoc-members: :show-inheritance: -For a sample data extraction workflow, see :ref:`parsers example`. +Submodules +---------- + +diffpy.utils.parsers.resample module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.parsers.resample + :members: + :undoc-members: + :show-inheritance: diffpy.utils.parsers.loaddata module ------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.parsers.loaddata :members: :undoc-members: :show-inheritance: -diffpy.utils.parsers.serialization module ------------------------------------------ +diffpy.utils.parsers.custom_exceptions module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.parsers.serialization +.. automodule:: diffpy.utils.parsers.custom_exceptions :members: :undoc-members: :show-inheritance: -diffpy.utils.parsers.resample module ------------------------------------- +diffpy.utils.parsers.serialization module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.parsers.resample +.. automodule:: diffpy.utils.parsers.serialization :members: :undoc-members: :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index f9a9c099..3ce300f4 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -12,17 +12,20 @@ Subpackages ----------- .. toctree:: + :titlesonly: diffpy.utils.parsers diffpy.utils.wx + diffpy.utils.scattering_objects Submodules ---------- diffpy.utils.tools module -------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.tools :members: :undoc-members: :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.scattering_objects.rst b/doc/source/api/diffpy.utils.scattering_objects.rst new file mode 100644 index 00000000..928eca4a --- /dev/null +++ b/doc/source/api/diffpy.utils.scattering_objects.rst @@ -0,0 +1,29 @@ +:tocdepth: -1 + +diffpy.utils.scattering_objects package +======================================= + +.. automodule:: diffpy.utils.scattering_objects + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +diffpy.utils.scattering_objects.user_config module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.scattering_objects.user_config + :members: + :undoc-members: + :show-inheritance: + +diffpy.utils.scattering_objects.diffraction_objects module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.scattering_objects.diffraction_objects + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index fb747f58..60f60599 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -1,4 +1,4 @@ -.. _wx Python Utilities Documentation +:tocdepth: -1 diffpy.utils.wx package ======================= @@ -8,10 +8,14 @@ diffpy.utils.wx package :undoc-members: :show-inheritance: +Submodules +---------- + diffpy.utils.wx.gridutils module --------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.wx.gridutils :members: :undoc-members: :show-inheritance: + From c115f18c827b35ef9526fbeb5f8cd0bfed122869 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 19:33:38 +0000 Subject: [PATCH 188/696] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/api/diffpy.utils.parsers.rst | 1 - doc/source/api/diffpy.utils.rst | 1 - doc/source/api/diffpy.utils.scattering_objects.rst | 1 - doc/source/api/diffpy.utils.wx.rst | 1 - 4 files changed, 4 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index 223accd7..0709cf3a 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -42,4 +42,3 @@ diffpy.utils.parsers.serialization module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 3ce300f4..cf69f596 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -28,4 +28,3 @@ diffpy.utils.tools module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.scattering_objects.rst b/doc/source/api/diffpy.utils.scattering_objects.rst index 928eca4a..fe511bf5 100644 --- a/doc/source/api/diffpy.utils.scattering_objects.rst +++ b/doc/source/api/diffpy.utils.scattering_objects.rst @@ -26,4 +26,3 @@ diffpy.utils.scattering_objects.diffraction_objects module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 60f60599..76c89035 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,4 +18,3 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: - From 49c873e37bae53af92aa864d424e8676b6fec644 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 16:03:54 -0400 Subject: [PATCH 189/696] fix badge name, take 1 --- .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 2 +- README.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index b1102cce..4db7a40e 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -11,7 +11,7 @@ on: workflow_dispatch: jobs: - coverage-and-matrix: + CI: uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.utils diff --git a/README.rst b/README.rst index ed6f27f6..38916e22 100644 --- a/README.rst +++ b/README.rst @@ -15,8 +15,8 @@ .. |Black| image:: https://img.shields.io/badge/code_style-black-black :target: https://github.com/psf/black -.. |CI| image:: https://github.com/diffpy/diffpy.utils/actions/workflows/codecov-on-merge-to-main.yml/badge.svg - :target: https://github.com/diffpy/diffpy.utils/actions/workflows/codecov-on-merge-to-main.yml +.. |CI| image:: https://github.com/diffpy/diffpy.utils/actions/workflows/matrix-and-codecov-on-merge-to-main.yml/badge.svg + :target: https://github.com/diffpy/diffpy.utils/actions/workflows/matrix-and-codecov-on-merge-to-main.yml .. |Codecov| image:: https://codecov.io/gh/diffpy/diffpy.utils/branch/main/graph/badge.svg :target: https://codecov.io/gh/diffpy/diffpy.utils From 655e43e6bdc609dbceb715d04a4fa3864bd15697 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 16 Sep 2024 16:08:08 -0400 Subject: [PATCH 190/696] Change name to CI --- .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 4db7a40e..eeffb3e9 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -1,4 +1,4 @@ -name: Codecov and matrix +name: CI on: push: From a995a612bfb974a93fb6a9cab836d9ae4d7aa4d8 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Sep 2024 20:33:07 +0000 Subject: [PATCH 191/696] Update CHANGELOG.rst --- CHANGELOG.rst | 9 +++++++++ news/recut.rst | 23 ----------------------- 2 files changed, 9 insertions(+), 23 deletions(-) delete mode 100644 news/recut.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a0750dae..d82ff15f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,15 @@ Release Notes .. current developments +3.4.1 +===== + +**Fixed:** + +* Updated package structure to new group standard + + + 3.4.0 ===== diff --git a/news/recut.rst b/news/recut.rst deleted file mode 100644 index c8c0a36a..00000000 --- a/news/recut.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Updated package structure to new group standard - -**Security:** - -* From 5a5aa0cb295718b35340d5a5a622b6e13584e1c9 Mon Sep 17 00:00:00 2001 From: Tieqiong Zhang Date: Mon, 16 Sep 2024 22:05:16 -0400 Subject: [PATCH 192/696] add secrets inherit --- .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index eeffb3e9..08ccaca2 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -17,3 +17,4 @@ jobs: project: diffpy.utils c_extension: false headless: false + secrets: inherit From 0c5f9c56063e06ca0d91ca3d33abeaef4bc6ad12 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 09:15:02 -0400 Subject: [PATCH 193/696] Add pytest-cov test test.txt dependencies --- requirements/test.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/test.txt b/requirements/test.txt index 72644779..9966e09d 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -4,4 +4,5 @@ codecov coverage pytest-env pytest-mock +pytest-cov freezegun From 95dc0f54c79a85e5ca5e5f68a5e53401ab580300 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 18:32:23 -0400 Subject: [PATCH 194/696] Update and rename LICENSE.rst to LICENSE.txt --- LICENSE.rst | 30 ------------ LICENSE.txt | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 30 deletions(-) delete mode 100644 LICENSE.rst create mode 100644 LICENSE.txt diff --git a/LICENSE.rst b/LICENSE.rst deleted file mode 100644 index 95a04ac9..00000000 --- a/LICENSE.rst +++ /dev/null @@ -1,30 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2024, The Trustees of Columbia University -in the City of New York. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..f6d92af7 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,137 @@ +OPEN SOURCE LICENSE AGREEMENT +============================= + +Copyright (c) 2009-2011, University of Tennessee +Copyright (c) 1989, 1991 Free Software Foundation, Inc. +Copyright (c) 2006, The Regents of the University of California through + Lawrence Berkeley National Laboratory +Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") +Copyright (c) 2006-2007, Board of Trustees of Michigan State University +Copyright (c) 2008-2012, The Trustees of Columbia University in the City + of New York + +Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National + Laboratory + + +The "DiffPy-CMI" is distributed subject to the following license conditions: + + +SOFTWARE LICENSE AGREEMENT + + Software: DiffPy-CMI + + +(1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either +source code, or binary form and accompanying documentation). + +Part of the software was derived from the DANSE, ObjCryst++ (with permission), +PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of +which the original Copyrights are contained in each individual file. + +Each licensee is addressed as "you" or "Licensee." + + +(2) The copyright holders shown above and their third-party Licensors hereby +grant licensee a royalty-free nonexclusive license, subject to the limitations +stated herein and U.S. Government license rights. + + +(3) You may modify and make a copy or copies of the software for use within +your organization, if you meet the following conditions: + + (a) Copies in source code must include the copyright notice and this + software license agreement. + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy. + + +(4) You may modify a copy or copies of the Software or any portion of it, thus +forming a work based on the Software, and distribute copies of such work +outside your organization, if you meet all of the following conditions: + + (a) Copies in source code must include the copyright notice and this + Software License Agreement; + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy; + + (c) Modified copies and works based on the Software must carry prominent + notices stating that you changed specified portions of the Software. + + (d) Neither the name of Brookhaven Science Associates or Brookhaven + National Laboratory nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + written permission. + + +(5) Portions of the Software resulted from work developed under a U.S. +Government contract and are subject to the following license: +The Government is granted for itself and others acting on its behalf a +paid-up, nonexclusive, irrevocable worldwide license in this computer software +to reproduce, prepare derivative works, and perform publicly and display +publicly. + + +(6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT +WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY +LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND +THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL +LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF +THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE +PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION +UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. + + +(7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR +THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF +ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING +BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, +WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING +NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS +BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. + + +Brookhaven National Laboratory Notice +===================================== + +Acknowledgment of sponsorship +----------------------------- + +This software was produced by the Brookhaven National Laboratory, under +Contract DE-AC02-98CH10886 with the Department of Energy. + + +Government disclaimer of liability +---------------------------------- + +Neither the United States nor the United States Department of Energy, nor +any of their employees, makes any warranty, express or implied, or assumes +any legal liability or responsibility for the accuracy, completeness, or +usefulness of any data, apparatus, product, or process disclosed, or +represents that its use would not infringe privately owned rights. + + +Brookhaven disclaimer of liability +---------------------------------- + +Brookhaven National Laboratory makes no representations or warranties, +express or implied, nor assumes any liability for the use of this software. + + +Maintenance of notice +--------------------- + +In the interest of clarity regarding the origin and status of this +software, Brookhaven National Laboratory requests that any recipient of it +maintain this notice affixed to any distribution by the recipient that +contains a copy or derivative of this software. + + +END OF LICENSE From 4c30cc9378ab02f90cc79277c5a4c949103e0b2a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 18:33:15 -0400 Subject: [PATCH 195/696] Create LICENSE_DANSE.txt --- LICENSE_DANSE.txt | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 LICENSE_DANSE.txt diff --git a/LICENSE_DANSE.txt b/LICENSE_DANSE.txt new file mode 100644 index 00000000..b7e1c702 --- /dev/null +++ b/LICENSE_DANSE.txt @@ -0,0 +1,41 @@ +This program is part of the DiffPy and DANSE open-source projects +and is available subject to the conditions and terms laid out below. + +Copyright 2006-2007, Board of Trustees of Michigan State University, +Copyright 2008-2012, The Trustees of Columbia University in the +city of New York. (Copyright holder indicated in each source file). + +For more information please visit the project web-page: + http://www.diffpy.org/ +or email Prof. Simon Billinge at sb2896@columbia.edu + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS". COPYRIGHT HOLDER +EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES AND CONDITIONS, EITHER +EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY, TITLE, FITNESS, ADEQUACY OR SUITABILITY +FOR A PARTICULAR PURPOSE, AND ANY WARRANTIES OF FREEDOM FROM +INFRINGEMENT OF ANY DOMESTIC OR FOREIGN PATENT, COPYRIGHTS, TRADE +SECRETS OR OTHER PROPRIETARY RIGHTS OF ANY PARTY. IN NO EVENT SHALL +COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE OR RELATING TO THIS AGREEMENT, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. From 2003404c2b37835dd8eb268cec9d14b63b0686ed Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 18:40:49 -0400 Subject: [PATCH 196/696] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4337830d..c6fa2c33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', + 'License :: Other/Proprietary License', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', From d7ea35d260cb91232e12958c0995a6fa891b8169 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 18:46:07 -0400 Subject: [PATCH 197/696] Create cookie.rst news for 3.4.2 --- news/cookie.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/cookie.rst diff --git a/news/cookie.rst b/news/cookie.rst new file mode 100644 index 00000000..c8c0a36a --- /dev/null +++ b/news/cookie.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Updated package structure to new group standard + +**Security:** + +* From 053a22d828c0c673b83cce912b304c0725543ad8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 18:54:33 -0400 Subject: [PATCH 198/696] Remove CHANGELOG 3.4.1 content --- CHANGELOG.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d82ff15f..481d6038 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,14 +4,6 @@ Release Notes .. current developments -3.4.1 -===== - -**Fixed:** - -* Updated package structure to new group standard - - 3.4.0 ===== From a20ee875260410c1ff9414da7ec63c2287d67c3a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 18:57:24 -0400 Subject: [PATCH 199/696] Delete LICENSE.txt --- LICENSE.txt | 137 ---------------------------------------------------- 1 file changed, 137 deletions(-) delete mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index f6d92af7..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,137 +0,0 @@ -OPEN SOURCE LICENSE AGREEMENT -============================= - -Copyright (c) 2009-2011, University of Tennessee -Copyright (c) 1989, 1991 Free Software Foundation, Inc. -Copyright (c) 2006, The Regents of the University of California through - Lawrence Berkeley National Laboratory -Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") -Copyright (c) 2006-2007, Board of Trustees of Michigan State University -Copyright (c) 2008-2012, The Trustees of Columbia University in the City - of New York - -Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National - Laboratory - - -The "DiffPy-CMI" is distributed subject to the following license conditions: - - -SOFTWARE LICENSE AGREEMENT - - Software: DiffPy-CMI - - -(1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either -source code, or binary form and accompanying documentation). - -Part of the software was derived from the DANSE, ObjCryst++ (with permission), -PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of -which the original Copyrights are contained in each individual file. - -Each licensee is addressed as "you" or "Licensee." - - -(2) The copyright holders shown above and their third-party Licensors hereby -grant licensee a royalty-free nonexclusive license, subject to the limitations -stated herein and U.S. Government license rights. - - -(3) You may modify and make a copy or copies of the software for use within -your organization, if you meet the following conditions: - - (a) Copies in source code must include the copyright notice and this - software license agreement. - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy. - - -(4) You may modify a copy or copies of the Software or any portion of it, thus -forming a work based on the Software, and distribute copies of such work -outside your organization, if you meet all of the following conditions: - - (a) Copies in source code must include the copyright notice and this - Software License Agreement; - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy; - - (c) Modified copies and works based on the Software must carry prominent - notices stating that you changed specified portions of the Software. - - (d) Neither the name of Brookhaven Science Associates or Brookhaven - National Laboratory nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - written permission. - - -(5) Portions of the Software resulted from work developed under a U.S. -Government contract and are subject to the following license: -The Government is granted for itself and others acting on its behalf a -paid-up, nonexclusive, irrevocable worldwide license in this computer software -to reproduce, prepare derivative works, and perform publicly and display -publicly. - - -(6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT -WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY -LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND -THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING -BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL -LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF -THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE -PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION -UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. - - -(7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR -THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF -ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, -CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING -BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, -WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING -NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS -BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. - - -Brookhaven National Laboratory Notice -===================================== - -Acknowledgment of sponsorship ------------------------------ - -This software was produced by the Brookhaven National Laboratory, under -Contract DE-AC02-98CH10886 with the Department of Energy. - - -Government disclaimer of liability ----------------------------------- - -Neither the United States nor the United States Department of Energy, nor -any of their employees, makes any warranty, express or implied, or assumes -any legal liability or responsibility for the accuracy, completeness, or -usefulness of any data, apparatus, product, or process disclosed, or -represents that its use would not infringe privately owned rights. - - -Brookhaven disclaimer of liability ----------------------------------- - -Brookhaven National Laboratory makes no representations or warranties, -express or implied, nor assumes any liability for the use of this software. - - -Maintenance of notice ---------------------- - -In the interest of clarity regarding the origin and status of this -software, Brookhaven National Laboratory requests that any recipient of it -maintain this notice affixed to any distribution by the recipient that -contains a copy or derivative of this software. - - -END OF LICENSE From 291a1bfea0d6af49c4dd666694fcaf27ae53e20c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 19:12:08 -0400 Subject: [PATCH 200/696] Add LICENSE.rst --- LICENSE.rst | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 LICENSE.rst diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 00000000..ee734192 --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,141 @@ +OPEN SOURCE LICENSE AGREEMENT +============================= + +Copyright (c) 1989, 1991 Free Software Foundation, Inc. + +Copyright (c) 2006, The Regents of the University of California through Lawrence Berkeley National Laboratory + +Copyright (c) 2006-2007, Board of Trustees of Michigan State University + +Copyright (c) 2008-2012, The Trustees of Columbia University in the City of New York + +Copyright (c) 2009-2011, University of Tennessee + +Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") + +Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National Laboratory + +Copyright (c) 2019-2024, The Trustees of Columbia University in the City of New York. +All rights reserved. + +The "DiffPy-CMI" is distributed subject to the following license conditions: + +.. code-block:: text + + SOFTWARE LICENSE AGREEMENT + + Software: DiffPy-CMI + + + (1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either + source code, or binary form and accompanying documentation). + + Part of the software was derived from the DANSE, ObjCryst++ (with permission), + PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of + which the original Copyrights are contained in each individual file. + + Each licensee is addressed as "you" or "Licensee." + + + (2) The copyright holders shown above and their third-party Licensors hereby + grant licensee a royalty-free nonexclusive license, subject to the limitations + stated herein and U.S. Government license rights. + + + (3) You may modify and make a copy or copies of the software for use within + your organization, if you meet the following conditions: + + (a) Copies in source code must include the copyright notice and this + software license agreement. + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy. + + + (4) You may modify a copy or copies of the Software or any portion of it, thus + forming a work based on the Software, and distribute copies of such work + outside your organization, if you meet all of the following conditions: + + (a) Copies in source code must include the copyright notice and this + Software License Agreement; + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy; + + (c) Modified copies and works based on the Software must carry prominent + notices stating that you changed specified portions of the Software. + + (d) Neither the name of Brookhaven Science Associates or Brookhaven + National Laboratory nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + written permission. + + + (5) Portions of the Software resulted from work developed under a U.S. + Government contract and are subject to the following license: + The Government is granted for itself and others acting on its behalf a + paid-up, nonexclusive, irrevocable worldwide license in this computer software + to reproduce, prepare derivative works, and perform publicly and display + publicly. + + + (6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT + WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY + LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND + THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL + LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF + THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE + PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION + UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. + + + (7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR + THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF + ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, + CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING + BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, + WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING + NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS + BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. + + +Brookhaven National Laboratory Notice +===================================== + +Acknowledgment of sponsorship +----------------------------- + +This software was produced by the Brookhaven National Laboratory, under +Contract DE-AC02-98CH10886 with the Department of Energy. + + +Government disclaimer of liability +---------------------------------- + +Neither the United States nor the United States Department of Energy, nor +any of their employees, makes any warranty, express or implied, or assumes +any legal liability or responsibility for the accuracy, completeness, or +usefulness of any data, apparatus, product, or process disclosed, or +represents that its use would not infringe privately owned rights. + + +Brookhaven disclaimer of liability +---------------------------------- + +Brookhaven National Laboratory makes no representations or warranties, +express or implied, nor assumes any liability for the use of this software. + + +Maintenance of notice +--------------------- + +In the interest of clarity regarding the origin and status of this +software, Brookhaven National Laboratory requests that any recipient of it +maintain this notice affixed to any distribution by the recipient that +contains a copy or derivative of this software. + +END OF LICENSE From 1195065cc52ec12e434aa834f62f20bddb809b3a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:12:15 +0000 Subject: [PATCH 201/696] [pre-commit.ci] auto fixes from pre-commit hooks --- LICENSE.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.rst b/LICENSE.rst index ee734192..d042ca2c 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -15,7 +15,7 @@ Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National Laboratory -Copyright (c) 2019-2024, The Trustees of Columbia University in the City of New York. +Copyright (c) 2019-2024, The Trustees of Columbia University in the City of New York. All rights reserved. The "DiffPy-CMI" is distributed subject to the following license conditions: From 05a73a00f3412157d6009deadcfd8efb4295de07 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 19:18:32 -0400 Subject: [PATCH 202/696] Update pyproject.toml to free to use but with restrictions --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c6fa2c33..c1e0f861 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', - 'License :: Other/Proprietary License', + 'License :: Free To Use But Restricted', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', From c0ffe30876f8702faa1acdeff0457013224df89c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 20:26:44 -0400 Subject: [PATCH 203/696] Empty build.txt file which is used for C++/C --- requirements/build.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements/build.txt b/requirements/build.txt index f72d870d..e69de29b 100644 --- a/requirements/build.txt +++ b/requirements/build.txt @@ -1,2 +0,0 @@ -python -setuptools From 3e8dd2348b73887f6604befdeecd9c46ca1ee20b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 20:32:27 -0400 Subject: [PATCH 204/696] Fix arb value to fix warning --- tests/test_diffraction_objects.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index fa613a53..a410f313 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -236,18 +236,20 @@ def test_dump(tmp_path, mocker): directory = Path(tmp_path) file = directory / "testfile" test = Diffraction_object() - test.wavelength = 1.54 + test.wavelength = 1.01 test.name = "test" test.scat_quantity = "x-ray" test.insert_scattering_quantity( x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) + + with mocker.patch("importlib.metadata.version", return_value="3.3.0"), freeze_time("2012-01-14"): test.dump(file, "q") with open(file, "r") as f: actual = f.read() expected = ( - "[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" + "[Diffraction_object]\nname = test\nwavelength = 1.01\nscat_quantity = x-ray\nthing1 = 1\n" "thing2 = thing2\npackage_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" "creation_time = 2012-01-14 00:00:00\n\n" "#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" @@ -262,4 +264,5 @@ def test_dump(tmp_path, mocker): "9.000000000000000000e+00 9.000000000000000000e+00\n" "1.000000000000000000e+01 1.000000000000000000e+01\n" ) + assert actual == expected From b62fddb46ce88d11814a78e9bb75a022ca8ece52 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 20:42:18 -0400 Subject: [PATCH 205/696] Remove patch warning --- tests/test_diffraction_objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a410f313..21b6c66e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -243,9 +243,10 @@ def test_dump(tmp_path, mocker): x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - - with mocker.patch("importlib.metadata.version", return_value="3.3.0"), freeze_time("2012-01-14"): + mocker.patch("importlib.metadata.version", return_value="3.3.0") + with freeze_time("2012-01-14"): test.dump(file, "q") + with open(file, "r") as f: actual = f.read() expected = ( From 6e2ed38c9f8e3e760fbaf5957dfddbd3027e95ec Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 20:43:51 -0400 Subject: [PATCH 206/696] Add news --- news/warning.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/warning.rst diff --git a/news/warning.rst b/news/warning.rst new file mode 100644 index 00000000..45768802 --- /dev/null +++ b/news/warning.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Two Pytest warnings due to numpy and pytest mocker in test_dump function + +**Security:** + +* From 9908bea2fbdda5f123af8a1085e2f75e60287b3f Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 17 Sep 2024 21:29:23 -0400 Subject: [PATCH 207/696] adding doc link to readme --- README.rst | 5 +++++ news/doclink.rst | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 news/doclink.rst diff --git a/README.rst b/README.rst index 38916e22..a55090af 100644 --- a/README.rst +++ b/README.rst @@ -53,6 +53,11 @@ If you use diffpy.utils in a scientific publication, we would like you to cite t diffpy.utils Package, https://github.com/diffpy/diffpy.utils +Documentation +------------- + +Documentation may be found at, https://diffy.github.io/diffpy.utils + Installation ------------ diff --git a/news/doclink.rst b/news/doclink.rst new file mode 100644 index 00000000..cfd86dc6 --- /dev/null +++ b/news/doclink.rst @@ -0,0 +1,23 @@ +**Added:** + +* link docs in the README + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 7bd6f8ddb94f6e7517e68810e13c464efa492af5 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 17 Sep 2024 21:33:59 -0400 Subject: [PATCH 208/696] fix typo in url --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a55090af..99ef5d5c 100644 --- a/README.rst +++ b/README.rst @@ -56,7 +56,7 @@ If you use diffpy.utils in a scientific publication, we would like you to cite t Documentation ------------- -Documentation may be found at, https://diffy.github.io/diffpy.utils +Documentation may be found at, https://diffpy.github.io/diffpy.utils Installation ------------ From 6696c5c20c7981322267129c3c6bc132ad9bcf56 Mon Sep 17 00:00:00 2001 From: Tieqiong Zhang Date: Tue, 17 Sep 2024 21:43:26 -0400 Subject: [PATCH 209/696] explicitly pass codecov secrets --- .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 08ccaca2..2df748dc 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -17,4 +17,5 @@ jobs: project: diffpy.utils c_extension: false headless: false - secrets: inherit + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 12935f84317ec0fed6f4a95d7e226ac2172cabe2 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 17 Sep 2024 22:00:19 -0400 Subject: [PATCH 210/696] trim trailing whitespace --- .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 2df748dc..99a3c623 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -17,5 +17,5 @@ jobs: project: diffpy.utils c_extension: false headless: false - secrets: + secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From bc1a7821d2b10abbcd8147293e0301a93a9cc9b9 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 17 Sep 2024 22:12:37 -0400 Subject: [PATCH 211/696] remove mention of installing dependencies from readme --- README.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 99ef5d5c..629dec94 100644 --- a/README.rst +++ b/README.rst @@ -81,11 +81,7 @@ Then, to fully install ``diffpy.utils`` in our active environment, run :: Another option is to use ``pip`` to download and install the latest release from `Python Package Index `_. -To install using ``pip`` into your ``diffpy.utils_env`` environment, we will also have to install dependencies :: - - pip install -r https://raw.githubusercontent.com/diffpy/diffpy.utils/main/requirements/run.txt - -and then install the package :: +To install using ``pip`` into your ``diffpy.utils_env`` environment type :: pip install diffpy.utils From eb131ec098ff0a80bef3abb9120374741168405e Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 17 Sep 2024 22:15:22 -0400 Subject: [PATCH 212/696] news --- news/install.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/install.rst diff --git a/news/install.rst b/news/install.rst new file mode 100644 index 00000000..816dd79d --- /dev/null +++ b/news/install.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* removed need to install requirements separately when pip installing. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 20751c8b2cf945833f64855172dd39438890d8d3 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 18 Sep 2024 02:23:11 +0000 Subject: [PATCH 213/696] Update CHANGELOG.rst --- CHANGELOG.rst | 17 +++++++++++++++++ news/cookie.rst | 23 ----------------------- news/doclink.rst | 23 ----------------------- news/install.rst | 23 ----------------------- 4 files changed, 17 insertions(+), 69 deletions(-) delete mode 100644 news/cookie.rst delete mode 100644 news/doclink.rst delete mode 100644 news/install.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 481d6038..b57c7e79 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,23 @@ Release Notes .. current developments +3.4.2 +===== + +**Added:** + +* link docs in the README + +**Changed:** + +* removed need to install requirements separately when pip installing. + +**Fixed:** + +* Updated package structure to new group standard + + + 3.4.0 ===== diff --git a/news/cookie.rst b/news/cookie.rst deleted file mode 100644 index c8c0a36a..00000000 --- a/news/cookie.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Updated package structure to new group standard - -**Security:** - -* diff --git a/news/doclink.rst b/news/doclink.rst deleted file mode 100644 index cfd86dc6..00000000 --- a/news/doclink.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* link docs in the README - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/install.rst b/news/install.rst deleted file mode 100644 index 816dd79d..00000000 --- a/news/install.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* removed need to install requirements separately when pip installing. - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From 5f2d56ce91ccd1ea963130a96bd4f3dca4e5e1ed Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 17 Sep 2024 22:35:55 -0400 Subject: [PATCH 214/696] added mention of DO's in README --- README.rst | 7 ++++--- news/sos.rst | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 news/sos.rst diff --git a/README.rst b/README.rst index 629dec94..8045f840 100644 --- a/README.rst +++ b/README.rst @@ -41,10 +41,11 @@ diffpy.utils Package General purpose shared utilities for the diffpy libraries. The diffpy.utils package provides functions for extracting array data from -variously formatted text files and wx GUI utilities used by the PDFgui -program. The package also includes an interpolation function based on the +variously formatted text files, an interpolation function based on the Whittaker-Shannon formula that can be used to resample a PDF or other profile -function over a new grid. +function over a new grid, `Diffraction_objects` for conveniently manipulating +diffraction data, and some wx GUI utilities used by the PDFgui +program. Citation -------- diff --git a/news/sos.rst b/news/sos.rst new file mode 100644 index 00000000..b5a35246 --- /dev/null +++ b/news/sos.rst @@ -0,0 +1,23 @@ +**Added:** + +* Diffraction_objects mentioned in the README + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From b6d1ffe3231a2eb474e4e6be8a35893a4a93b2c1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 22:48:23 -0400 Subject: [PATCH 215/696] Fix contributors typo --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index cdb35e24..d115a8aa 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,7 +1,7 @@ Authors ======= -Billinge Group and community contibutors. +Billinge Group and community contributors. Contributors ------------ From de82a7705e207a16e90639abe75e21adf172676d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 19 Sep 2024 07:16:27 -0400 Subject: [PATCH 216/696] Clean up tests for catching error --- tests/test_diffraction_objects.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8958c17f..aa99326a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -241,21 +241,28 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" - # Capture warnings due to invalid arcsin input value due to the wavelenght being 1.54 + # Setup the testing environment to capture warnings triggered by an invalid arcsin input with warnings.catch_warnings(record=True) as captured_warnings: + # Configure the warnings system to capture all warnings warnings.simplefilter("always") + + # Perform the method call that is expected to trigger the RuntimeWarning due to the specified wavelength test.insert_scattering_quantity( x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - # Assert that a RuntimeWarning was raised + # Verify that at least one RuntimeWarning is raised due to the arcsin domain error from wavelength 1.54 assert any( isinstance(w.message, RuntimeWarning) for w in captured_warnings - ), "Expected a RuntimeWarning due to invalid arcsin input value from the wavelenght set to 1.54" - # Expect only one warning message produced - assert len(captured_warnings) == 1 + ), "Expected a RuntimeWarning due to invalid arcsin input value from the wavelength set to 1.54" + + # Ensure that exactly one warning was captured to confirm that no additional unexpected warnings are raised + assert len(captured_warnings) == 1, "Expected exactly one warning to be captured" + + # Patch the version lookup to control the external dependency in the test environment mocker.patch("importlib.metadata.version", return_value="3.3.0") + with freeze_time("2012-01-14"): test.dump(file, "q") From d65aa57402cb4114c896e50d835b3497cc875756 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:16:37 +0000 Subject: [PATCH 217/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aa99326a..7a32b3bf 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -262,7 +262,6 @@ def test_dump(tmp_path, mocker): # Patch the version lookup to control the external dependency in the test environment mocker.patch("importlib.metadata.version", return_value="3.3.0") - with freeze_time("2012-01-14"): test.dump(file, "q") From b9b05a505f06f00ee12638cc4b5fb6ae974ef371 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 19 Sep 2024 07:18:51 -0400 Subject: [PATCH 218/696] Shorten comments for test_diffraction_objects --- tests/test_diffraction_objects.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 7a32b3bf..9e3abe3e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -241,7 +241,6 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" - # Setup the testing environment to capture warnings triggered by an invalid arcsin input with warnings.catch_warnings(record=True) as captured_warnings: # Configure the warnings system to capture all warnings warnings.simplefilter("always") @@ -251,15 +250,14 @@ def test_dump(tmp_path, mocker): x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - # Verify that at least one RuntimeWarning is raised due to the arcsin domain error from wavelength 1.54 + # Verify that at least one RuntimeWarning is raised due to the arcsin error from wavelength 1.54 assert any( isinstance(w.message, RuntimeWarning) for w in captured_warnings ), "Expected a RuntimeWarning due to invalid arcsin input value from the wavelength set to 1.54" - # Ensure that exactly one warning was captured to confirm that no additional unexpected warnings are raised + # Ensure that exactly one warning was captured assert len(captured_warnings) == 1, "Expected exactly one warning to be captured" - # Patch the version lookup to control the external dependency in the test environment mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): From 8038e9dcd2a9678c1e0208243a90416869198646 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Sep 2024 13:14:04 -0400 Subject: [PATCH 219/696] Update setuptools-git-versioning >= 2.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c1e0f861..655b5047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=62.0", "setuptools-git-versioning<2"] +requires = ["setuptools>=62.0", "setuptools-git-versioning>=2.0"] build-backend = "setuptools.build_meta" [project] From 20d54ab94edebb5f644e3332b0135302d7bf12ee Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Sep 2024 13:18:14 -0400 Subject: [PATCH 220/696] Create git-version.rst --- news/git-version.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/git-version.rst diff --git a/news/git-version.rst b/news/git-version.rst new file mode 100644 index 00000000..7b4e85bf --- /dev/null +++ b/news/git-version.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* setuptools-git-versioning from <2.0 to >= 2.0 in pyproject.toml + +**Security:** + +* From 53480fa1d826844ae00f692bb62e34385f2a5f99 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Sep 2024 21:08:18 -0400 Subject: [PATCH 221/696] Add citation to readme --- README.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 8045f840..777f01bf 100644 --- a/README.rst +++ b/README.rst @@ -50,9 +50,16 @@ program. Citation -------- -If you use diffpy.utils in a scientific publication, we would like you to cite this package as - - diffpy.utils Package, https://github.com/diffpy/diffpy.utils +If you use this program for a scientific research that leads +to publication, we ask that you acknowledge use of the program +by citing the following paper in your publication: + + P. Juhás, C. L. Farrow, X. Yang, K. R. Knox and S. J. L. Billinge, + `Complex modeling: a strategy and software program for combining + multiple information sources to solve ill posed structure and + nanostructure inverse problems + `__, + *Acta Crystallogr. A* **71**, 562-568 (2015). Documentation ------------- From ccad4656dbbbf9307c81e0f6b4c1d209f6a958cf Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 4 Oct 2024 11:29:25 -0400 Subject: [PATCH 222/696] Add pip packages under pip.txt --- news/pip.rst | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- requirements/{run.txt => conda.txt} | 0 requirements/pip.txt | 1 + 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 news/pip.rst rename requirements/{run.txt => conda.txt} (100%) diff --git a/news/pip.rst b/news/pip.rst new file mode 100644 index 00000000..d10e3b6d --- /dev/null +++ b/news/pip.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Add pip dependencies under pip.txt and conda dependencies under conda.txt + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 655b5047..421f8886 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) [tool.setuptools.dynamic] -dependencies = {file = ["requirements/run.txt"]} +dependencies = {file = ["requirements/pip.txt"]} [tool.black] line-length = 115 diff --git a/requirements/run.txt b/requirements/conda.txt similarity index 100% rename from requirements/run.txt rename to requirements/conda.txt diff --git a/requirements/pip.txt b/requirements/pip.txt index e69de29b..24ce15ab 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -0,0 +1 @@ +numpy From 3aec5a3f1baf9e0d218f6d6970b5fc45b466325d Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:34:45 -0400 Subject: [PATCH 223/696] Update README --- README.rst | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 777f01bf..f0476d94 100644 --- a/README.rst +++ b/README.rst @@ -80,16 +80,18 @@ To add "conda-forge" to the conda channels, run the following in a terminal. :: We want to install our packages in a suitable conda environment. The following creates and activates a new environment named ``diffpy.utils_env`` :: - conda create -n diffpy.utils_env python=3 + conda create -n diffpy.utils_env diffpy.utils conda activate diffpy.utils_env -Then, to fully install ``diffpy.utils`` in our active environment, run :: +To confirm that the installation was successful, type :: - conda install diffpy.utils + python -c "import diffpy.utils; print(diffpy.utils.__version__)" -Another option is to use ``pip`` to download and install the latest release from +The output should print the latest version displayed on the badges above. + +If the above does not work, you can use ``pip`` to download and install the latest release from `Python Package Index `_. -To install using ``pip`` into your ``diffpy.utils_env`` environment type :: +To install using ``pip`` into your ``diffpy.utils_env`` environment, type :: pip install diffpy.utils @@ -99,12 +101,17 @@ and run the following :: pip install . +Getting Started +--------------- + +You may consult our `online documentation `_ for tutorials and API references. + Support and Contribute ---------------------- `Diffpy user group `_ is the discussion forum for general questions and discussions about the use of diffpy.utils. Please join the diffpy.utils users community by joining the Google group. The diffpy.utils project welcomes your expertise and enthusiasm! -If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. Feel free to fork the project and contribute. To install diffpy.utils in a development mode, with its sources being directly used by Python From 2fdf24d4a2742f1e1c744057879feaf3d2b69ab5 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:36:54 -0400 Subject: [PATCH 224/696] Add .github/ISSUE_TEMPLATE --- .github/ISSUE_TEMPLATE/bug_feature.md | 16 +++++++++++++++ .github/ISSUE_TEMPLATE/release_checklist.md | 22 +++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_feature.md create mode 100644 .github/ISSUE_TEMPLATE/release_checklist.md diff --git a/.github/ISSUE_TEMPLATE/bug_feature.md b/.github/ISSUE_TEMPLATE/bug_feature.md new file mode 100644 index 00000000..b3454deb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_feature.md @@ -0,0 +1,16 @@ +--- +name: Bug Report or Feature Request +about: Report a bug or suggest a new feature! +title: "" +labels: "" +assignees: "" +--- + +### Problem + + + +### Proposed solution diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md new file mode 100644 index 00000000..a87a44a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -0,0 +1,22 @@ +--- +name: Release +about: Checklist and communication channel for PyPI and GitHub release +title: "Ready for PyPI/GitHub release" +labels: "release" +assignees: "" +--- + +### Release checklist for GitHub contributors + +- [ ] All PRs/issues attached to the release are merged. +- [ ] All the badges on the README are passing. +- [ ] License information is verified as correct. If you are unsure, please comment below. +- [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are + missing), tutorials, and other human written text is up-to-date with any changes in the code. +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated and + tested +- [ ] Successfully run any tutorial examples or do functional testing in some other way. +- [ ] Grammar and writing quality have been checked (no typos). + +Please mention @sbillinge when you are ready for release. Include any additional comments necessary, such as +version information and details about the pre-release. From b2bad3606dc53c548c49f362b8319703fd180701 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:38:04 -0400 Subject: [PATCH 225/696] Add build-wheel-release-upload.yml --- .github/workflows/build-wheel-release-upload.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/build-wheel-release-upload.yml diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml new file mode 100644 index 00000000..9ea21782 --- /dev/null +++ b/.github/workflows/build-wheel-release-upload.yml @@ -0,0 +1,16 @@ +name: Release (GitHub/PyPI) + +on: + workflow_dispatch: + push: + tags: + - '*' # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + +jobs: + release: + uses: Billingegroup/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 + with: + project: diffpy.utils + secrets: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} From d04a029e30d67fccd8ead4aa6996bb83584a490e Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:39:10 -0400 Subject: [PATCH 226/696] Add example_package.rst in api --- .../api/diffpy.utils.example_package.rst | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 doc/source/api/diffpy.utils.example_package.rst diff --git a/doc/source/api/diffpy.utils.example_package.rst b/doc/source/api/diffpy.utils.example_package.rst new file mode 100644 index 00000000..40388ba1 --- /dev/null +++ b/doc/source/api/diffpy.utils.example_package.rst @@ -0,0 +1,31 @@ +.. _example_package documentation: + +|title| +======= + +.. |title| replace:: diffpy.utils.example_package package + +.. automodule:: diffpy.utils.example_package + :members: + :undoc-members: + :show-inheritance: + +|foo| +----- + +.. |foo| replace:: diffpy.utils.example_package.foo module + +.. automodule:: diffpy.utils.example_package.foo + :members: + :undoc-members: + :show-inheritance: + +|bar| +----- + +.. |bar| replace:: diffpy.utils.example_package.bar module + +.. automodule:: diffpy.utils.example_package.foo + :members: + :undoc-members: + :show-inheritance: From 27b57378f90ebdadc9761dab9de516a8053cf27c Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:39:35 -0400 Subject: [PATCH 227/696] Add README for requirements --- requirements/README.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 requirements/README.txt diff --git a/requirements/README.txt b/requirements/README.txt new file mode 100644 index 00000000..3740a138 --- /dev/null +++ b/requirements/README.txt @@ -0,0 +1,10 @@ +# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! +# +# This directory is where you should place your project dependencies. +# build.txt should list all Conda packages required for building the package in GitHub CI, including those specified in the build section of meta.yaml (conda-recipe). +# conda.txt should list all Conda packages required (including optional) for running the package in GitHub CI, as well as those specified in the run section of meta.yaml (conda-recipe). +# pip.txt should list all PyPI packages (including optional) required to install the package via `pip install `. +# test.txt should list all Conda/PyPI packages required for the testing suite to ensure all tests pass. +# docs.txt should list all Conda/PyPI packages required for building the package documentation page. +# +# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! From c241c9c33997ef5bd0bd4a1b19ea3222dc462cc7 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:39:54 -0400 Subject: [PATCH 228/696] Add test_version.py --- tests/test_version.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/test_version.py diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 00000000..421a96e4 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,10 @@ +"""Unit tests for __version__.py +""" + +import diffpy.utils + + +def test_package_version(): + """Ensure the package version is defined and not set to the initial placeholder.""" + assert hasattr(diffpy.utils, "__version__") + assert diffpy.utils.__version__ != "0.0.0" From 54170fef7d62730ae70a29629e43499001544c67 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:41:33 -0400 Subject: [PATCH 229/696] Add news for recut --- news/recut.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/recut.rst diff --git a/news/recut.rst b/news/recut.rst new file mode 100644 index 00000000..70e17b5d --- /dev/null +++ b/news/recut.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Recut to group's package standard, fix installation, add GitHub release workflow + +**Security:** + +* From 3b67d83a618f13a2112f24d8432da6144bb61a25 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:43:39 -0400 Subject: [PATCH 230/696] Added newline to README --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f0476d94..5a2875d8 100644 --- a/README.rst +++ b/README.rst @@ -111,7 +111,7 @@ Support and Contribute `Diffpy user group `_ is the discussion forum for general questions and discussions about the use of diffpy.utils. Please join the diffpy.utils users community by joining the Google group. The diffpy.utils project welcomes your expertise and enthusiasm! -If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. Feel free to fork the project and contribute. To install diffpy.utils in a development mode, with its sources being directly used by Python @@ -140,3 +140,4 @@ Contact ------- For more information on diffpy.utils please visit the project `web-page `_ or email Prof. Simon Billinge at sb2896@columbia.edu. + From a9fd906c1a0d0d3100aa6320f5d7eda76f3aee87 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 13:03:51 -0400 Subject: [PATCH 231/696] deleted README.txt --- requirements/README.txt | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 requirements/README.txt diff --git a/requirements/README.txt b/requirements/README.txt deleted file mode 100644 index 3740a138..00000000 --- a/requirements/README.txt +++ /dev/null @@ -1,10 +0,0 @@ -# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! -# -# This directory is where you should place your project dependencies. -# build.txt should list all Conda packages required for building the package in GitHub CI, including those specified in the build section of meta.yaml (conda-recipe). -# conda.txt should list all Conda packages required (including optional) for running the package in GitHub CI, as well as those specified in the run section of meta.yaml (conda-recipe). -# pip.txt should list all PyPI packages (including optional) required to install the package via `pip install `. -# test.txt should list all Conda/PyPI packages required for the testing suite to ensure all tests pass. -# docs.txt should list all Conda/PyPI packages required for building the package documentation page. -# -# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! From 86813a5634cc1be90720cf19f947efd39eac7674 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 13:04:33 -0400 Subject: [PATCH 232/696] deleted new line --- README.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/README.rst b/README.rst index 5a2875d8..98e34649 100644 --- a/README.rst +++ b/README.rst @@ -140,4 +140,3 @@ Contact ------- For more information on diffpy.utils please visit the project `web-page `_ or email Prof. Simon Billinge at sb2896@columbia.edu. - From ce8cdef672cb4e673657e7d6f1efceef05b63b57 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 13:17:46 -0400 Subject: [PATCH 233/696] deleted example_package.rst in api --- .../api/diffpy.utils.example_package.rst | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 doc/source/api/diffpy.utils.example_package.rst diff --git a/doc/source/api/diffpy.utils.example_package.rst b/doc/source/api/diffpy.utils.example_package.rst deleted file mode 100644 index 40388ba1..00000000 --- a/doc/source/api/diffpy.utils.example_package.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. _example_package documentation: - -|title| -======= - -.. |title| replace:: diffpy.utils.example_package package - -.. automodule:: diffpy.utils.example_package - :members: - :undoc-members: - :show-inheritance: - -|foo| ------ - -.. |foo| replace:: diffpy.utils.example_package.foo module - -.. automodule:: diffpy.utils.example_package.foo - :members: - :undoc-members: - :show-inheritance: - -|bar| ------ - -.. |bar| replace:: diffpy.utils.example_package.bar module - -.. automodule:: diffpy.utils.example_package.foo - :members: - :undoc-members: - :show-inheritance: From 31f3c03b25e01341fc351deede00f368b6de3276 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 25 Oct 2024 10:59:14 -0400 Subject: [PATCH 234/696] Setup requirements --- requirements/{run.txt => conda.txt} | 0 requirements/pip.txt | 1 + 2 files changed, 1 insertion(+) rename requirements/{run.txt => conda.txt} (100%) diff --git a/requirements/run.txt b/requirements/conda.txt similarity index 100% rename from requirements/run.txt rename to requirements/conda.txt diff --git a/requirements/pip.txt b/requirements/pip.txt index e69de29b..24ce15ab 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -0,0 +1 @@ +numpy From e69255afb24f01902f547bf3c38ef0bcb2b2a3cc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 25 Oct 2024 10:59:39 -0400 Subject: [PATCH 235/696] Set valid q range for test dump function --- tests/test_diffraction_objects.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9e3abe3e..8798930b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -233,31 +233,17 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_dump(tmp_path, mocker): - x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) + x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" test = Diffraction_object() test.wavelength = 1.54 test.name = "test" test.scat_quantity = "x-ray" - - with warnings.catch_warnings(record=True) as captured_warnings: - # Configure the warnings system to capture all warnings - warnings.simplefilter("always") - - # Perform the method call that is expected to trigger the RuntimeWarning due to the specified wavelength - test.insert_scattering_quantity( + test.insert_scattering_quantity( x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} - ) - - # Verify that at least one RuntimeWarning is raised due to the arcsin error from wavelength 1.54 - assert any( - isinstance(w.message, RuntimeWarning) for w in captured_warnings - ), "Expected a RuntimeWarning due to invalid arcsin input value from the wavelength set to 1.54" - - # Ensure that exactly one warning was captured - assert len(captured_warnings) == 1, "Expected exactly one warning to be captured" - + ) + mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): @@ -275,11 +261,6 @@ def test_dump(tmp_path, mocker): "3.000000000000000000e+00 3.000000000000000000e+00\n" "4.000000000000000000e+00 4.000000000000000000e+00\n" "5.000000000000000000e+00 5.000000000000000000e+00\n" - "6.000000000000000000e+00 6.000000000000000000e+00\n" - "7.000000000000000000e+00 7.000000000000000000e+00\n" - "8.000000000000000000e+00 8.000000000000000000e+00\n" - "9.000000000000000000e+00 9.000000000000000000e+00\n" - "1.000000000000000000e+01 1.000000000000000000e+01\n" ) assert actual == expected From 7b12bafda78914961df782289ec92d689f3f17f0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:01:46 +0000 Subject: [PATCH 236/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8798930b..5658b9b1 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -241,9 +241,9 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" test.insert_scattering_quantity( - x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} + x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - + mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): From 0cb34e28641b44ae21826fa79a6e09faccac3e05 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 25 Oct 2024 11:06:36 -0400 Subject: [PATCH 237/696] Pre-commit --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8798930b..b5581384 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -1,4 +1,3 @@ -import warnings from pathlib import Path import numpy as np From 859755e6b57c75a7b96bc3f750795fc7230e94d6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 25 Oct 2024 11:07:08 -0400 Subject: [PATCH 238/696] Apply pre-commit --- tests/test_diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index b5581384..a155b95e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -240,9 +240,9 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" test.insert_scattering_quantity( - x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} + x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - + mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): From f17b979076b0769713b3601c1bd1e0139c3a8b17 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 25 Oct 2024 15:38:47 -0400 Subject: [PATCH 239/696] update pyproject.toml for python 3.13 --- news/python313.rst | 23 +++++++++++++++++++++++ pyproject.toml | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 news/python313.rst diff --git a/news/python313.rst b/news/python313.rst new file mode 100644 index 00000000..4b34bb41 --- /dev/null +++ b/news/python313.rst @@ -0,0 +1,23 @@ +**Added:** + +* Support for Python 3.13 + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Support for Python 3.10 + +**Fixed:** + +* + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 421f8886..41fe385a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ description = "Shared utilities for diffpy packages" keywords = ['"text data parsers" "wx grid" "diffraction objects"'] readme = "README.rst" -requires-python = ">=3.10" +requires-python = ">=3.11, <3.14" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -25,9 +25,9 @@ classifiers = [ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', - 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Scientific/Engineering :: Chemistry', ] From 4834db1ae0e6c9af00276db66fa4fd27aa2ee95d Mon Sep 17 00:00:00 2001 From: sbillinge Date: Sat, 26 Oct 2024 20:56:55 +0000 Subject: [PATCH 240/696] update changelog --- CHANGELOG.rst | 15 +++++++++++++++ news/git-version.rst | 23 ----------------------- news/pip.rst | 23 ----------------------- news/recut.rst | 23 ----------------------- news/sos.rst | 23 ----------------------- news/warning.rst | 23 ----------------------- 6 files changed, 15 insertions(+), 115 deletions(-) delete mode 100644 news/git-version.rst delete mode 100644 news/pip.rst delete mode 100644 news/recut.rst delete mode 100644 news/sos.rst delete mode 100644 news/warning.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b57c7e79..d574883f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,21 @@ Release Notes .. current developments +3.4.3 +===== + +**Added:** + +* Diffraction_objects mentioned in the README + +**Fixed:** + +* Recut to group's package standard, fix installation, add GitHub release workflow +* setuptools-git-versioning from <2.0 to >= 2.0 in pyproject.toml +* Two Pytest warnings due to numpy and pytest mocker in test_dump function +* Add pip dependencies under pip.txt and conda dependencies under conda.txt + + 3.4.2 ===== diff --git a/news/git-version.rst b/news/git-version.rst deleted file mode 100644 index 7b4e85bf..00000000 --- a/news/git-version.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* setuptools-git-versioning from <2.0 to >= 2.0 in pyproject.toml - -**Security:** - -* diff --git a/news/pip.rst b/news/pip.rst deleted file mode 100644 index d10e3b6d..00000000 --- a/news/pip.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Add pip dependencies under pip.txt and conda dependencies under conda.txt - -**Security:** - -* diff --git a/news/recut.rst b/news/recut.rst deleted file mode 100644 index 70e17b5d..00000000 --- a/news/recut.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Recut to group's package standard, fix installation, add GitHub release workflow - -**Security:** - -* diff --git a/news/sos.rst b/news/sos.rst deleted file mode 100644 index b5a35246..00000000 --- a/news/sos.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Diffraction_objects mentioned in the README - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/warning.rst b/news/warning.rst deleted file mode 100644 index 45768802..00000000 --- a/news/warning.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Two Pytest warnings due to numpy and pytest mocker in test_dump function - -**Security:** - -* From ebbb644a9d9bb4cccd2fdadbb4c7a56fe2dca972 Mon Sep 17 00:00:00 2001 From: sbillinge Date: Sun, 27 Oct 2024 00:32:12 +0000 Subject: [PATCH 241/696] update changelog --- CHANGELOG.rst | 12 ++++++++++++ news/python313.rst | 23 ----------------------- 2 files changed, 12 insertions(+), 23 deletions(-) delete mode 100644 news/python313.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d574883f..63320901 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,18 @@ Release Notes .. current developments +3.5.0 +===== + +**Added:** + +* Support for Python 3.13 + +**Removed:** + +* Support for Python 3.10 + + 3.4.3 ===== diff --git a/news/python313.rst b/news/python313.rst deleted file mode 100644 index 4b34bb41..00000000 --- a/news/python313.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Support for Python 3.13 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* Support for Python 3.10 - -**Fixed:** - -* - -**Security:** - -* From f3cd7b4c4ca8963f698d20cd601b1b334f6328c6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 26 Oct 2024 22:33:27 -0400 Subject: [PATCH 242/696] Add commas in keywords in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 41fe385a..a43118ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ maintainers = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] description = "Shared utilities for diffpy packages" -keywords = ['"text data parsers" "wx grid" "diffraction objects"'] +keywords = ["text data parsers", "wx grid", "diffraction objects"] readme = "README.rst" requires-python = ">=3.11, <3.14" classifiers = [ From 611d219f3402c0b0b26c51fca0a6b91bd32d6018 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 4 Nov 2024 18:15:01 -0500 Subject: [PATCH 243/696] add xtype=d in dump function --- src/diffpy/utils/scattering_objects/diffraction_objects.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index bd5b16db..94d2831a 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -445,6 +445,8 @@ def dump(self, filepath, xtype=None): data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) elif xtype == "tth": data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) + elif xtype == "d": + data_to_save = np.column_stack((self.on_d[0], self.on_d[1])) else: print(f"WARNING: cannot handle the xtype '{xtype}'") self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) From 7112fac0c7804badf4ad41733cabec0960f063ff Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 5 Nov 2024 16:31:25 -0500 Subject: [PATCH 244/696] Add warning to missing global config file --- news/get-user-fix.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/tools.py | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 news/get-user-fix.rst diff --git a/news/get-user-fix.rst b/news/get-user-fix.rst new file mode 100644 index 00000000..5b2fd3df --- /dev/null +++ b/news/get-user-fix.rst @@ -0,0 +1,23 @@ +**Added:** + +* Warning message for missing global config file + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 1dfb8284..dc908a27 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -3,6 +3,7 @@ import os from copy import copy from pathlib import Path +import warnings def clean_dict(obj): @@ -114,6 +115,7 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: + warnings.warn("No global config file, please follow prompts below. For more information, refer to www.diffpy.org/diffpy.utils/") config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) From d24acb6305e6c9247618fc20b9633162b1096e79 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 5 Nov 2024 16:39:23 -0500 Subject: [PATCH 245/696] fix line too long --- src/diffpy/utils/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index dc908a27..7574d093 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -115,7 +115,7 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: - warnings.warn("No global config file, please follow prompts below. For more information, refer to www.diffpy.org/diffpy.utils/") + warnings.warn("No global config file, please follow prompts below.") config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) From 0a59a0be30f1b5acb811dca376d88c18de97ae82 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 5 Nov 2024 17:03:12 -0500 Subject: [PATCH 246/696] better wording for capture user info --- news/capture-user.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/tools.py | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 news/capture-user.rst diff --git a/news/capture-user.rst b/news/capture-user.rst new file mode 100644 index 00000000..b789d46e --- /dev/null +++ b/news/capture-user.rst @@ -0,0 +1,23 @@ +**Added:** + +* Better wording on the capture user info functionality + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 7574d093..aab4a64f 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -115,7 +115,9 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: - warnings.warn("No global config file, please follow prompts below.") + warnings.warn("No global config file, please follow prompts below. " + "The global config file is very important in crediting your work in the future. " + "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html") config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) From 2379a62ca863778361395c8779ff16a190b055fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:05:56 +0000 Subject: [PATCH 247/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index aab4a64f..2f165f87 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,9 +1,9 @@ import importlib.metadata import json import os +import warnings from copy import copy from pathlib import Path -import warnings def clean_dict(obj): @@ -115,9 +115,11 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: - warnings.warn("No global config file, please follow prompts below. " - "The global config file is very important in crediting your work in the future. " - "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html") + warnings.warn( + "No global config file, please follow prompts below. " + "The global config file is very important in crediting your work in the future. " + "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" + ) config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) From e5a6c381bce9b0ea4760834654bbc0434cfec7ce Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 10:26:57 -0500 Subject: [PATCH 248/696] configure codespell at pyproject.toml and pre-commit --- .codespell/ignore_lines.txt | 2 ++ .codespell/ignore_words.txt | 11 +++++++++++ .pre-commit-config.yaml | 6 ++++++ CHANGELOG.rst | 2 +- README.rst | 2 +- news/codespell.rst | 23 +++++++++++++++++++++++ pyproject.toml | 5 +++++ src/diffpy/utils/parsers/loaddata.py | 2 +- src/diffpy/utils/parsers/resample.py | 2 +- src/diffpy/utils/parsers/serialization.py | 2 +- 10 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 .codespell/ignore_lines.txt create mode 100644 .codespell/ignore_words.txt create mode 100644 news/codespell.rst diff --git a/.codespell/ignore_lines.txt b/.codespell/ignore_lines.txt new file mode 100644 index 00000000..07fa7c8c --- /dev/null +++ b/.codespell/ignore_lines.txt @@ -0,0 +1,2 @@ +;; Please include filenames and explanations for each ignored line. +;; See https://docs.openverse.org/meta/codespell.html for docs. diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt new file mode 100644 index 00000000..9757d7c0 --- /dev/null +++ b/.codespell/ignore_words.txt @@ -0,0 +1,11 @@ +;; Please include explanations for each ignored word (lowercase). +;; See https://docs.openverse.org/meta/codespell.html for docs. + +;; abbreviation for "materials" often used in a journal title +mater + +;; alternative use of socioeconomic +socio-economic + +;; Frobenius norm used in np.linalg.norm +fro diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3070e199..aee43d0e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,3 +44,9 @@ repos: name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + additional_dependencies: + - tomli diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b57c7e79..ceed2511 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -90,7 +90,7 @@ v3.2.3 **Added:** -* Compatability with Python 3.12.0rc3, 3.11. +* Compatibility with Python 3.12.0rc3, 3.11. * CI Coverage. * New tests for loadData function. * loadData function now toggleable. Can return either (a) data read from data blocks or (b) header information stored diff --git a/README.rst b/README.rst index 98e34649..48967571 100644 --- a/README.rst +++ b/README.rst @@ -134,7 +134,7 @@ trying to commit again. Improvements and fixes are always appreciated. -Before contribuing, please read our `Code of Conduct `_. +Before contributing, please read our `Code of Conduct `_. Contact ------- diff --git a/news/codespell.rst b/news/codespell.rst new file mode 100644 index 00000000..8c5ba6d2 --- /dev/null +++ b/news/codespell.rst @@ -0,0 +1,23 @@ +**Added:** + +* Spelling check via Codespell in pre-commit + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 421f8886..53e14e82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,11 @@ namespaces = false # to disable scanning PEP 420 namespaces (true by default) [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} +[tool.codespell] +exclude-file = ".codespell/ignore_lines.txt" +ignore-words = ".codespell/ignore_words.txt" +skip = "*.cif,*.dat" + [tool.black] line-length = 115 include = '\.pyi?$' diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 4bb0d792..18375d90 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -30,7 +30,7 @@ def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwar Minimum number of rows in the first data block. All rows must have the same number of floating point values. headers: bool - when False (defualt), the function returns a numpy array of the data in the data block. + when False (default), the function returns a numpy array of the data in the data block. When True, the function instead returns a dictionary of parameters and their corresponding values parsed from header (information prior the data block). See hdel and hignore for options to help with parsing header information. diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index f81fc2a2..e88c4f0b 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -113,7 +113,7 @@ def resample(r, s, dr): # spad = numpy.concatenate([s,spad]) # rnew = numpy.arange(0, rpad[-1], dr) # snew = numpy.zeros_like(rnew) - # Accomodate for the fact that r[0] might not be 0 + # Accommodate for the fact that r[0] might not be 0 # u = (rnew-r[0]) / dr0 # for n in range(len(spad)): # snew += spad[n] * numpy.sinc(u - n) diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index a0bec92c..46d4b8ff 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -56,7 +56,7 @@ def serialize_data( include a path element in the database entry (default True). If 'path' is not included in hddata, extract path from filename. serial_file - Serial language file to dump dictionary into. If None (defualt), no dumping will occur. + Serial language file to dump dictionary into. If None (default), no dumping will occur. Returns ------- From 4bb5ecf7b6362594de8f099fbef3cc651aed7980 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 17:06:04 -0500 Subject: [PATCH 249/696] delete unnecessary get-user-fix.rst --- news/get-user-fix.rst | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 news/get-user-fix.rst diff --git a/news/get-user-fix.rst b/news/get-user-fix.rst deleted file mode 100644 index 5b2fd3df..00000000 --- a/news/get-user-fix.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Warning message for missing global config file - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From 2732b498d33c5799cb4565a3aa2f99740f363537 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 17:41:11 -0500 Subject: [PATCH 250/696] edited get user prompt --- src/diffpy/utils/tools.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 2f165f87..812e320b 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,7 +1,6 @@ import importlib.metadata import json import os -import warnings from copy import copy from pathlib import Path @@ -78,11 +77,11 @@ def _sorted_merge(*dicts): def _create_global_config(args): username = input( - f"Please enter the name of the user to put in the diffpy global config file " + f"Please enter the name of the user would want future work to be credited to " f"[{args.get('username', '')}]: " ).strip() or args.get("username", "") email = input( - f"Please enter the email of the user to put in the diffpy global config file " + f"Please enter the email of the user " f"[{args.get('email', '')}]: " ).strip() or args.get("email", "") return_bool = False if username is None or email is None else True @@ -115,9 +114,8 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: - warnings.warn( + print( "No global config file, please follow prompts below. " - "The global config file is very important in crediting your work in the future. " "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" ) config_bool = _create_global_config(args) From 7e25316974d3426339b7520ddd8ed45c575d5f5b Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 6 Nov 2024 17:56:41 -0500 Subject: [PATCH 251/696] add news --- news/dump.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/dump.rst diff --git a/news/dump.rst b/news/dump.rst new file mode 100644 index 00000000..7f99d586 --- /dev/null +++ b/news/dump.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality in dump to allow writing data on dspace + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From e36726e83e3135f76507236bebf7b76f90f7db72 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 18:33:26 -0500 Subject: [PATCH 252/696] edited message and prompt to capture user info --- src/diffpy/utils/tools.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 812e320b..1e804212 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -77,11 +77,11 @@ def _sorted_merge(*dicts): def _create_global_config(args): username = input( - f"Please enter the name of the user would want future work to be credited to " + f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " ).strip() or args.get("username", "") email = input( - f"Please enter the email of the user " + f"Please enter the your email " f"[{args.get('email', '')}]: " ).strip() or args.get("email", "") return_bool = False if username is None or email is None else True @@ -115,7 +115,11 @@ def get_user_info(args=None): local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: print( - "No global config file, please follow prompts below. " + "No global configuration file was found containing information about the user to associate with the data. " + "By following the prompts below you can add your name and email to this file on the current computer and " + "your name will be automatically associated with subsequent diffpy data by default. " + "This is not recommended on a shared or public computer. " + "You will only have to do that once. " "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" ) config_bool = _create_global_config(args) From c367349d85bb39510d7987dba3abb47d660be10f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:33:43 +0000 Subject: [PATCH 253/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 1e804212..461d3f71 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -77,13 +77,9 @@ def _sorted_merge(*dicts): def _create_global_config(args): username = input( - f"Please enter the name you would want future work to be credited to " - f"[{args.get('username', '')}]: " + f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " ).strip() or args.get("username", "") - email = input( - f"Please enter the your email " - f"[{args.get('email', '')}]: " - ).strip() or args.get("email", "") + email = input(f"Please enter the your email " f"[{args.get('email', '')}]: ").strip() or args.get("email", "") return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) From 5bfc9dd539e21dd9d8a892fd7192646ce4360f67 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 18:49:44 -0500 Subject: [PATCH 254/696] fixed pre-commit issues --- src/diffpy/utils/tools.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 461d3f71..82cca8c9 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -111,11 +111,12 @@ def get_user_info(args=None): local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: print( - "No global configuration file was found containing information about the user to associate with the data. " - "By following the prompts below you can add your name and email to this file on the current computer and " - "your name will be automatically associated with subsequent diffpy data by default. " + "No global configuration file was found containing " + "information about the user to associate with the data.\n" + "By following the prompts below you can add your name and email to this file on the current" + " computer and your name will be automatically associated with subsequent diffpy data by default.\n" "This is not recommended on a shared or public computer. " - "You will only have to do that once. " + "You will only have to do that once.\n" "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" ) config_bool = _create_global_config(args) From f204ebdc10f8c1e7a50bb16f0281d4a55948828d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 6 Nov 2024 19:13:34 -0500 Subject: [PATCH 255/696] use latest .codecov.yml, remove coveragerc, add codecov secret to test on pr workflow --- .codecov.yml | 42 ++++++++----------------------- .coveragerc | 13 ---------- .github/workflows/tests-on-pr.yml | 2 ++ 3 files changed, 13 insertions(+), 44 deletions(-) delete mode 100644 .coveragerc diff --git a/.codecov.yml b/.codecov.yml index 04dd6510..7e61a29f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,34 +1,14 @@ -# codecov can find this file anywhere in the repo, so we don't need to clutter -# the root folder. -#comment: false - -codecov: - notify: - require_ci_to_pass: no - coverage: status: - patch: + project: # more options at https://docs.codecov.com/docs/commit-status default: - target: '70' - if_no_uploads: error - if_not_found: success - if_ci_failed: failure - project: - default: false - library: - target: auto - if_no_uploads: error - if_not_found: success - if_ci_failed: error - paths: '!*/tests/.*' - - tests: - target: 97.9% - paths: '*/tests/.*' - if_not_found: success - -flags: - tests: - paths: - - tests/ + target: auto # use the coverage from the base commit, fail if coverage is lower + threshold: 0% # allow the coverage to drop by + +comment: + layout: " diff, flags, files" + behavior: default + require_changes: false + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] + hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] \ No newline at end of file diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 0ed6eac5..00000000 --- a/.coveragerc +++ /dev/null @@ -1,13 +0,0 @@ -[run] -source = - diffpy.utils -[report] -omit = - */python?.?/* - */site-packages/nose/* - # ignore _version.py and versioneer.py - .*version.* - *_version.py - -exclude_lines = - if __name__ == '__main__': diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index dd0a102e..baac1aeb 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -14,3 +14,5 @@ jobs: project: diffpy.utils c_extension: false headless: false + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From aa2ce946de4e543cab7f0497f88fffb7d40586c4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 6 Nov 2024 19:14:18 -0500 Subject: [PATCH 256/696] Apply precommit --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 7e61a29f..5a94096e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -11,4 +11,4 @@ comment: require_changes: false require_base: false # [true :: must have a base report to post] require_head: false # [true :: must have a head report to post] - hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] \ No newline at end of file + hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] From c79ebb4b3817e1997e1f71e4d4c47982ad89de60 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 22:45:36 -0500 Subject: [PATCH 257/696] modify issue checklist --- .github/ISSUE_TEMPLATE/release_checklist.md | 14 ++++++++++---- .github/workflows/build-wheel-release-upload.yml | 2 +- .github/workflows/check-news-item.yml | 2 +- .../matrix-and-codecov-on-merge-to-main.yml | 2 +- .github/workflows/publish-docs-on-release.yml | 14 -------------- .github/workflows/tests-on-pr.yml | 2 +- .pre-commit-config.yaml | 6 +++--- 7 files changed, 17 insertions(+), 25 deletions(-) delete mode 100644 .github/workflows/publish-docs-on-release.yml diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index a87a44a8..b96c782d 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -13,10 +13,16 @@ assignees: "" - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated and - tested -- [ ] Successfully run any tutorial examples or do functional testing in some other way. +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) updated. +- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version - [ ] Grammar and writing quality have been checked (no typos). Please mention @sbillinge when you are ready for release. Include any additional comments necessary, such as -version information and details about the pre-release. +version information and details about the pre-release here: + +### Post-release checklist + +Before closing this issue, please complete the following: + +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. +- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 9ea21782..0c9f1357 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -1,4 +1,4 @@ -name: Release (GitHub/PyPI) +name: Release (GitHub/PyPI) and Deploy Docs on: workflow_dispatch: diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index be549960..54a86e99 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -6,7 +6,7 @@ on: - main jobs: - check-news-item: + build: uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 99a3c623..bdf97643 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -11,7 +11,7 @@ on: workflow_dispatch: jobs: - CI: + coverage: uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.utils diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml deleted file mode 100644 index 56667dfa..00000000 --- a/.github/workflows/publish-docs-on-release.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Build and Deploy Docs - -on: - release: - types: - - published - workflow_dispatch: - -jobs: - publish-docs-on-release: - uses: Billingegroup/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 - with: - project: diffpy.utils - c_extension: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index baac1aeb..7371ec89 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: jobs: - tests-on-pr: + validate: uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.utils diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aee43d0e..9cf0556f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,6 +47,6 @@ repos: - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - - id: codespell - additional_dependencies: - - tomli + - id: codespell + additional_dependencies: + - tomli From 579a293f3d121598e5b43c0f540ac522ffdc40cc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 22:54:44 -0500 Subject: [PATCH 258/696] Use "check-for-new" job name --- .github/workflows/check-news-item.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index 54a86e99..8faed9ea 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -6,7 +6,7 @@ on: - main jobs: - build: + check-for-news: uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils From 9908ee1090ee44bb4e9b6f4844b933b3af776e92 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 22:55:03 -0500 Subject: [PATCH 259/696] use matrix-coverage job name --- .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index bdf97643..8543c786 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -11,7 +11,7 @@ on: workflow_dispatch: jobs: - coverage: + matrix-coverage: uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.utils From cd3f078afc71b3014a592b2b37caa13c546e835a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 23:01:55 -0500 Subject: [PATCH 260/696] use `tests-on-pr` job name --- .github/workflows/tests-on-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index 7371ec89..baac1aeb 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: jobs: - validate: + tests-on-pr: uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.utils From 6eba041ecdeec4c4e1d0b52b74f6b7d32f256179 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 23:03:31 -0500 Subject: [PATCH 261/696] Rename to check-news-items --- .github/workflows/check-news-item.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index 8faed9ea..2e18c710 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -6,7 +6,7 @@ on: - main jobs: - check-for-news: + check-news-items: uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils From 07072468b0660afeabd262e82eabea98b954fa83 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 23:06:06 -0500 Subject: [PATCH 262/696] rename `check-news-items` to `check-news-item` --- .github/workflows/check-news-item.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index 2e18c710..be549960 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -6,7 +6,7 @@ on: - main jobs: - check-news-items: + check-news-item: uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils From 1c7eae6f2800baddcb0c39d85bae68d754922e1e Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 8 Nov 2024 21:25:17 -0500 Subject: [PATCH 263/696] remove relative path imports --- src/diffpy/utils/parsers/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index a2104181..cd95dd63 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -16,12 +16,4 @@ """Various utilities related to data parsing and manipulation. """ -from .loaddata import loadData -from .resample import resample -from .serialization import deserialize_data, serialize_data - -# silence the pyflakes syntax checker -assert loadData or resample or True -assert serialize_data or deserialize_data or True - # End of file From 165ffb8f0d2fbee0c9d27fc0c38bfbc33c523d73 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 8 Nov 2024 21:25:41 -0500 Subject: [PATCH 264/696] moved resample out of parsers --- src/diffpy/utils/{parsers/resample.py => resampler.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/diffpy/utils/{parsers/resample.py => resampler.py} (100%) diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/resampler.py similarity index 100% rename from src/diffpy/utils/parsers/resample.py rename to src/diffpy/utils/resampler.py From 11a340141c4f3977d6317e4febe88228833eb9cf Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 8 Nov 2024 21:26:06 -0500 Subject: [PATCH 265/696] edited tests based on fixed file and function names --- tests/test_loaddata.py | 2 +- tests/test_resample.py | 2 +- tests/test_serialization.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index 2ca2fa07..3d775c74 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -8,7 +8,7 @@ import numpy import pytest -from diffpy.utils.parsers import loadData +from diffpy.utils.parsers.loaddata import loadData ############################################################################## diff --git a/tests/test_resample.py b/tests/test_resample.py index fa9c7e70..deddcca2 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from diffpy.utils.parsers.resample import wsinterp +from diffpy.utils.resampler import wsinterp def test_wsinterp(): diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 51921e01..fc3696bf 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -3,8 +3,9 @@ import numpy import pytest -from diffpy.utils.parsers import deserialize_data, loadData, serialize_data from diffpy.utils.parsers.custom_exceptions import ImproperSizeError, UnsupportedTypeError +from diffpy.utils.parsers.loaddata import loadData +from diffpy.utils.parsers.serialization import deserialize_data, serialize_data tests_dir = os.path.dirname(os.path.abspath(locals().get("__file__", "file.py"))) From ccecf1074d3b95076706ae604d21f0e9737e6a3c Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 8 Nov 2024 21:26:28 -0500 Subject: [PATCH 266/696] add news item --- news/resampler-relocation.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/resampler-relocation.rst diff --git a/news/resampler-relocation.rst b/news/resampler-relocation.rst new file mode 100644 index 00000000..220fc2f5 --- /dev/null +++ b/news/resampler-relocation.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Moved resampler out of parsers, new path is diffpy.utils.resampler + +**Deprecated:** + +* + +**Removed:** + +* Relative imports in parser's __init__.py + +**Fixed:** + +* + +**Security:** + +* From f0a39043270233289e92031934e970f5142a3d4d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 9 Nov 2024 16:10:53 -0500 Subject: [PATCH 267/696] Add conda-forge release checklist to GitHub Issue template --- .github/ISSUE_TEMPLATE/release_checklist.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index b96c782d..0f560278 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -6,23 +6,30 @@ labels: "release" assignees: "" --- -### Release checklist for GitHub contributors +### PyPI/GitHub release checklist: - [ ] All PRs/issues attached to the release are merged. - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) updated. -- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version -- [ ] Grammar and writing quality have been checked (no typos). +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. +- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. +- [ ] Grammar and writing quality are checked (no typos). -Please mention @sbillinge when you are ready for release. Include any additional comments necessary, such as +Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: +### conda-forge release checklist: + + + +- [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. +- [ ] All relevant issues in the feedstock are addressed in the release PR. + ### Post-release checklist -Before closing this issue, please complete the following: + -- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. - [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. From c486d35ce7c6393eafc40dc082b75fc684c1a363 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 11 Nov 2024 11:53:52 -0500 Subject: [PATCH 268/696] Update release_checklist.md - use fourigui 0.2.0 version --- .github/ISSUE_TEMPLATE/release_checklist.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 0f560278..33410e8c 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -6,26 +6,35 @@ labels: "release" assignees: "" --- -### PyPI/GitHub release checklist: +### PyPI/GitHub rc-release preparation checklist: - [ ] All PRs/issues attached to the release are merged. - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). -Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as -version information and details about the pre-release here: +Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: -### conda-forge release checklist: +### PyPI/GitHub full-release preparation checklist: +- [ ] Create a new conda environment and install the rc from pypi (`pip install =??`) +- [ ] License information at Pypi is verified as correct. +- [ ] Docs deployed successfully to `.github.io` +- [ ] Successfully run all tests, tutorial examples or do functional testing + +Please let @sbillinge know that all checks are done and package is ready for full release. + +### conda-forge release preparation checklist: +- [ ] Ensure that the full release has appeared on Pypi successfully - [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. -- [ ] All relevant issues in the feedstock are addressed in the release PR. +- [ ] Close any open issues on the feedstock. Reach out to @bobleesj if you have questions +- [ ] let @sbillinge and @bobleesj when this is ready ### Post-release checklist From 1831b797f9a5cb1edab47b5aaff1898ba537aa02 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:54:46 +0000 Subject: [PATCH 269/696] [pre-commit.ci] auto fixes from pre-commit hooks --- .github/ISSUE_TEMPLATE/release_checklist.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 33410e8c..ba1d40ce 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -13,7 +13,7 @@ assignees: "" - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). @@ -21,7 +21,7 @@ Please mention @sbillinge here when you are ready for PyPI/GitHub release. Inclu ### PyPI/GitHub full-release preparation checklist: - [ ] Create a new conda environment and install the rc from pypi (`pip install =??`) -- [ ] License information at Pypi is verified as correct. +- [ ] License information at Pypi is verified as correct. - [ ] Docs deployed successfully to `.github.io` - [ ] Successfully run all tests, tutorial examples or do functional testing From b86ce5e525f6553f823514316a4d5fc0520969e6 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Mon, 11 Nov 2024 17:01:29 -0500 Subject: [PATCH 270/696] Documentation update --- doc/source/api/diffpy.utils.parsers.rst | 13 +++---------- doc/source/api/diffpy.utils.rst | 9 +++++++++ doc/source/api/diffpy.utils.scattering_objects.rst | 13 +++++++------ doc/source/api/diffpy.utils.wx.rst | 1 + doc/source/examples/parsersexample.rst | 6 +++--- doc/source/examples/resampleexample.rst | 6 +++--- doc/source/utilities/parsersutility.rst | 6 +++--- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index 0709cf3a..bf3d6cc6 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -11,10 +11,10 @@ diffpy.utils.parsers package Submodules ---------- -diffpy.utils.parsers.resample module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.parsers.serialization module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.parsers.resample +.. automodule:: diffpy.utils.parsers.serialization :members: :undoc-members: :show-inheritance: @@ -35,10 +35,3 @@ diffpy.utils.parsers.custom_exceptions module :undoc-members: :show-inheritance: -diffpy.utils.parsers.serialization module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.parsers.serialization - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index cf69f596..bd4cda5b 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -28,3 +28,12 @@ diffpy.utils.tools module :members: :undoc-members: :show-inheritance: + +diffpy.utils.resampler module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.resampler + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.scattering_objects.rst b/doc/source/api/diffpy.utils.scattering_objects.rst index fe511bf5..b7c24bf1 100644 --- a/doc/source/api/diffpy.utils.scattering_objects.rst +++ b/doc/source/api/diffpy.utils.scattering_objects.rst @@ -11,18 +11,19 @@ diffpy.utils.scattering_objects package Submodules ---------- -diffpy.utils.scattering_objects.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.scattering_objects.diffraction_objects module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.scattering_objects.user_config +.. automodule:: diffpy.utils.scattering_objects.diffraction_objects :members: :undoc-members: :show-inheritance: -diffpy.utils.scattering_objects.diffraction_objects module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.scattering_objects.user_config module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.scattering_objects.diffraction_objects +.. automodule:: diffpy.utils.scattering_objects.user_config :members: :undoc-members: :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 76c89035..60f60599 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,3 +18,4 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: + diff --git a/doc/source/examples/parsersexample.rst b/doc/source/examples/parsersexample.rst index 90605741..b709de60 100644 --- a/doc/source/examples/parsersexample.rst +++ b/doc/source/examples/parsersexample.rst @@ -14,7 +14,7 @@ Using the parsers module, we can load file data into simple and easy-to-work-wit 2) To get the data table, we will use the ``loadData`` function. The default behavior of this function is to find and extract a data table from a file.:: - from diffpy.utils.parsers import loadData + from diffpy.utils.parsers.loaddata import loadData data_table = loadData('') While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is @@ -53,7 +53,7 @@ Using the parsers module, we can load file data into simple and easy-to-work-wit 4) Rather than working with separate ``data_table`` and ``hdata`` objects, it may be easier to combine them into a single dictionary. We can do so using the ``serialize_data`` function. :: - from diffpy.utils.parsers import serialize_data + from diffpy.utils.parsers.loaddata import serialize_data file_data = serialize_data('') 6) Finally, ``serialize_data`` allows us to store data from multiple text file in a single serial file. For one last bit diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resampleexample.rst index efa3c474..f7ba5e18 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resampleexample.rst @@ -15,14 +15,14 @@ given enough datapoints. `_. :: - from diffpy.utils.parsers import loadData + from diffpy.utils.parsers.loaddata import loadData nickel_datatable = loadData('') nitarget_datatable = loadData('') Each data table has two columns: first is the grid and second is the function value. To extract the columns, we can utilize the serialize function ... :: - from diffpy.utils.parsers import serialize_data + from diffpy.utils.parsers.serialization import serialize_data nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) nickel_grid = nickel_data['Nickel.gr']['grid'] nickel_func = nickel_data['Nickel.gr']['func'] @@ -54,7 +54,7 @@ given enough datapoints. ... and use the diffpy.utils ``wsinterp`` function to resample on this grid.:: - from diffpy.utils.parsers import wsinterp + from diffpy.utils.resampler import wsinterp nickel_resample = wsinterp(grid, nickel_grid, nickel_func) target_resample = wsinterp(grid, target_grid, target_func) diff --git a/doc/source/utilities/parsersutility.rst b/doc/source/utilities/parsersutility.rst index f653c3d7..ffaf768e 100644 --- a/doc/source/utilities/parsersutility.rst +++ b/doc/source/utilities/parsersutility.rst @@ -5,7 +5,7 @@ Parsers Utility The ``diffpy.utils.parsers`` module allows users to easily and robustly load file data into a Python project. -- ``loadData()``: Find and load a data table/block from a text file. This seems to work for most datafiles +- ``loaddata.loadData()``: Find and load a data table/block from a text file. This seems to work for most datafiles including those generated by diffpy programs. Running only ``numpy.loadtxt`` will result in errors for most these files as there is often excess data or parameters stored above the data block. Users can instead choose to load all the parameters of the form `` = `` into a dictionary @@ -14,10 +14,10 @@ The ``diffpy.utils.parsers`` module allows users to easily and robustly load fil The program identifies data blocks as the first matrix block with a constant number of columns. A user can tune the minimum number of rows this matrix block must have. -- ``deserialize_data()``: Load data from a serial file format into a Python dictionary. Currently, the only supported +- ``serialization.deserialize_data()``: Load data from a serial file format into a Python dictionary. Currently, the only supported serial format is ``.json``. -- ``serialize_data()``: Serialize the data generated by ``loadData()`` into a serial file format. Currently, the only +- ``serialization.serialize_data()``: Serialize the data generated by ``loadData()`` into a serial file format. Currently, the only supported serial format is ``.json``. For a more in-depth tutorial for how to use these parser utilities, click :ref:`here `. From dac72cb6a0dad627ccf43e4ecc56e293cc44106d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:02:24 +0000 Subject: [PATCH 271/696] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/api/diffpy.utils.parsers.rst | 1 - doc/source/api/diffpy.utils.rst | 1 - doc/source/api/diffpy.utils.scattering_objects.rst | 1 - doc/source/api/diffpy.utils.wx.rst | 1 - 4 files changed, 4 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index bf3d6cc6..3456f24e 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -34,4 +34,3 @@ diffpy.utils.parsers.custom_exceptions module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index bd4cda5b..b873c333 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -36,4 +36,3 @@ diffpy.utils.resampler module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.scattering_objects.rst b/doc/source/api/diffpy.utils.scattering_objects.rst index b7c24bf1..4607ad98 100644 --- a/doc/source/api/diffpy.utils.scattering_objects.rst +++ b/doc/source/api/diffpy.utils.scattering_objects.rst @@ -26,4 +26,3 @@ diffpy.utils.scattering_objects.user_config module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 60f60599..76c89035 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,4 +18,3 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: - From 8cd36296793162f311e361681343a26f4f1cc31c Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Mon, 11 Nov 2024 17:03:16 -0500 Subject: [PATCH 272/696] news --- news/doc-update.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/doc-update.rst diff --git a/news/doc-update.rst b/news/doc-update.rst new file mode 100644 index 00000000..1993a616 --- /dev/null +++ b/news/doc-update.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Paths in our documentation reflect changes made in code. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From ada9157a2264592343c8ef97d19cd8c4c99a488a Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 11:02:46 -0500 Subject: [PATCH 273/696] restructure tests dir to match src --- tests/{ => diffpy/utils/parsers}/test_loaddata.py | 0 tests/{ => diffpy/utils/parsers}/test_serialization.py | 0 .../utils/scattering_objects}/test_diffraction_objects.py | 0 tests/{ => diffpy/utils}/test_resample.py | 0 tests/{ => diffpy/utils}/test_tools.py | 0 tests/{ => diffpy/utils}/test_version.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/{ => diffpy/utils/parsers}/test_loaddata.py (100%) rename tests/{ => diffpy/utils/parsers}/test_serialization.py (100%) rename tests/{ => diffpy/utils/scattering_objects}/test_diffraction_objects.py (100%) rename tests/{ => diffpy/utils}/test_resample.py (100%) rename tests/{ => diffpy/utils}/test_tools.py (100%) rename tests/{ => diffpy/utils}/test_version.py (100%) diff --git a/tests/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py similarity index 100% rename from tests/test_loaddata.py rename to tests/diffpy/utils/parsers/test_loaddata.py diff --git a/tests/test_serialization.py b/tests/diffpy/utils/parsers/test_serialization.py similarity index 100% rename from tests/test_serialization.py rename to tests/diffpy/utils/parsers/test_serialization.py diff --git a/tests/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py similarity index 100% rename from tests/test_diffraction_objects.py rename to tests/diffpy/utils/scattering_objects/test_diffraction_objects.py diff --git a/tests/test_resample.py b/tests/diffpy/utils/test_resample.py similarity index 100% rename from tests/test_resample.py rename to tests/diffpy/utils/test_resample.py diff --git a/tests/test_tools.py b/tests/diffpy/utils/test_tools.py similarity index 100% rename from tests/test_tools.py rename to tests/diffpy/utils/test_tools.py diff --git a/tests/test_version.py b/tests/diffpy/utils/test_version.py similarity index 100% rename from tests/test_version.py rename to tests/diffpy/utils/test_version.py From b7f9ff24ba3c64f7d7358031d060f8da018b0d3d Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 11:25:20 -0500 Subject: [PATCH 274/696] fixed file paths in test files for the changed tests structure --- tests/conftest.py | 3 ++- tests/diffpy/utils/parsers/test_serialization.py | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5471acc1..b1783f57 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,8 +22,9 @@ def user_filesystem(tmp_path): @pytest.fixture def datafile(): """Fixture to dynamically load any test file.""" + base_path = Path(__file__).parent / "testdata" # Adjusted base path def _load(filename): - return "tests/testdata/" + filename + return str(base_path / filename) return _load diff --git a/tests/diffpy/utils/parsers/test_serialization.py b/tests/diffpy/utils/parsers/test_serialization.py index fc3696bf..97f01c9b 100644 --- a/tests/diffpy/utils/parsers/test_serialization.py +++ b/tests/diffpy/utils/parsers/test_serialization.py @@ -7,20 +7,19 @@ from diffpy.utils.parsers.loaddata import loadData from diffpy.utils.parsers.serialization import deserialize_data, serialize_data -tests_dir = os.path.dirname(os.path.abspath(locals().get("__file__", "file.py"))) - def test_load_multiple(tmp_path, datafile): # Load test data targetjson = datafile("targetjson.json") generatedjson = tmp_path / "generated_serialization.json" - tlm_list = os.listdir(os.path.join(tests_dir, "testdata", "dbload")) - tlm_list.sort() + dbload_dir = os.path.dirname(datafile("dbload/e1.gr")) + tlm_list = sorted(os.listdir(dbload_dir)) + generated_data = None for hfname in tlm_list: # gather data using loadData - headerfile = os.path.normpath(os.path.join(tests_dir, "testdata", "dbload", hfname)) + headerfile = os.path.join(dbload_dir, hfname) hdata = loadData(headerfile, headers=True) data_table = loadData(headerfile) From 116f5c5f9db17fe8f9becde18dbcd87e3ab77875 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 11:27:01 -0500 Subject: [PATCH 275/696] add news item --- news/tests-restruct.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/tests-restruct.rst diff --git a/news/tests-restruct.rst b/news/tests-restruct.rst new file mode 100644 index 00000000..4b28c56f --- /dev/null +++ b/news/tests-restruct.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* \tests directory tree to match \src + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* file paths of the test files according to new \tests directory tree + +**Security:** + +* From 9e292035f0f445a4c8803c62c023a79dacf99de3 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 21:25:42 -0500 Subject: [PATCH 276/696] used pathlib Path --- tests/conftest.py | 2 +- tests/diffpy/utils/parsers/test_serialization.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b1783f57..09de40f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,6 +25,6 @@ def datafile(): base_path = Path(__file__).parent / "testdata" # Adjusted base path def _load(filename): - return str(base_path / filename) + return base_path / filename return _load diff --git a/tests/diffpy/utils/parsers/test_serialization.py b/tests/diffpy/utils/parsers/test_serialization.py index 97f01c9b..cc205c43 100644 --- a/tests/diffpy/utils/parsers/test_serialization.py +++ b/tests/diffpy/utils/parsers/test_serialization.py @@ -1,4 +1,5 @@ import os +from pathlib import Path import numpy import pytest @@ -13,19 +14,18 @@ def test_load_multiple(tmp_path, datafile): targetjson = datafile("targetjson.json") generatedjson = tmp_path / "generated_serialization.json" - dbload_dir = os.path.dirname(datafile("dbload/e1.gr")) - tlm_list = sorted(os.listdir(dbload_dir)) + dbload_dir = datafile("dbload") + tlm_list = sorted(dbload_dir.glob("*.gr")) generated_data = None - for hfname in tlm_list: + for headerfile in tlm_list: # gather data using loadData - headerfile = os.path.join(dbload_dir, hfname) hdata = loadData(headerfile, headers=True) data_table = loadData(headerfile) # check path extraction generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=["r", "gr"], show_path=True) - assert headerfile == os.path.normpath(generated_data[hfname].pop("path")) + assert headerfile == Path(generated_data[headerfile.name].pop("path")) # rerun without path information and save to file generated_data = serialize_data( From 68841b97bd121139720de70cbc84eb7138387a77 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 21:27:49 -0500 Subject: [PATCH 277/696] fixed pre-commit error --- tests/diffpy/utils/parsers/test_serialization.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/diffpy/utils/parsers/test_serialization.py b/tests/diffpy/utils/parsers/test_serialization.py index cc205c43..0ba397ad 100644 --- a/tests/diffpy/utils/parsers/test_serialization.py +++ b/tests/diffpy/utils/parsers/test_serialization.py @@ -1,4 +1,3 @@ -import os from pathlib import Path import numpy From 2e15a776ecec3ece9fe83849690adc1317f84ff8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 14 Nov 2024 11:10:07 -0500 Subject: [PATCH 278/696] Modify tests for DiffractionObject --- .../scattering_objects/test_diffraction_objects.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index a155b95e..b2225db8 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -4,7 +4,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object +from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject params = [ ( # Default @@ -222,8 +222,8 @@ @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = Diffraction_object() - diffraction_object2 = Diffraction_object() + diffraction_object1 = DiffractionObject() + diffraction_object2 = DiffractionObject() diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] for i, attribute in enumerate(diffraction_object1_attributes): setattr(diffraction_object1, attribute, inputs1[i]) @@ -235,7 +235,7 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - test = Diffraction_object() + test = DiffractionObject() test.wavelength = 1.54 test.name = "test" test.scat_quantity = "x-ray" @@ -251,7 +251,7 @@ def test_dump(tmp_path, mocker): with open(file, "r") as f: actual = f.read() expected = ( - "[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" + "[DiffractionObject]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" "thing2 = thing2\npackage_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" "creation_time = 2012-01-14 00:00:00\n\n" "#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" From 931dc7bd01fad5507bc54b8d91d0f2218fd045a5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 14 Nov 2024 11:10:23 -0500 Subject: [PATCH 279/696] Make DiffractionObject as a copy of diffraciton_object --- .../scattering_objects/diffraction_objects.py | 481 +++++++++++++++++- 1 file changed, 480 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 94d2831a..97b61fe2 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -1,6 +1,6 @@ import datetime from copy import deepcopy - +import warnings import numpy as np from diffpy.utils.tools import get_package_info @@ -18,6 +18,20 @@ class Diffraction_object: + """A class to represent and manipulate data associated with diffraction experiments. + + .. deprecated:: 3.5.1 + `Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0. It is replaced by + `DiffractionObject` to follow the class naming convention. + """ + + warnings.warn( + "Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0, It is replaced by " + "DiffractionObject` to follow the class naming convention.", + DeprecationWarning, + stacklevel=2, + ) + def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength @@ -461,3 +475,468 @@ def dump(self, filepath, xtype=None): f.write(f"{key} = {value}\n") f.write("\n#### start data\n") np.savetxt(f, data_to_save, delimiter=" ") + + +import datetime +from copy import deepcopy + +import numpy as np + +from diffpy.utils.tools import get_package_info + +QQUANTITIES = ["q"] +ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] +DQUANTITIES = ["d", "dspace"] +XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES +XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] + +x_grid_emsg = ( + "objects are not on the same x-grid. You may add them using the self.add method " + "and specifying how to handle the mismatch." +) + + +class DiffractionObject: + def __init__(self, name="", wavelength=None): + self.name = name + self.wavelength = wavelength + self.scat_quantity = "" + self.on_q = [np.empty(0), np.empty(0)] + self.on_tth = [np.empty(0), np.empty(0)] + self.on_d = [np.empty(0), np.empty(0)] + self._all_arrays = [self.on_q, self.on_tth] + self.metadata = {} + + def __eq__(self, other): + if not isinstance(other, DiffractionObject): + return NotImplemented + self_attributes = [key for key in self.__dict__ if not key.startswith("_")] + other_attributes = [key for key in other.__dict__ if not key.startswith("_")] + if not sorted(self_attributes) == sorted(other_attributes): + return False + for key in self_attributes: + value = getattr(self, key) + other_value = getattr(other, key) + if isinstance(value, float): + if ( + not (value is None and other_value is None) + and (value is None) + or (other_value is None) + or not np.isclose(value, other_value, rtol=1e-5) + ): + return False + elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): + if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): + return False + else: + if value != other_value: + return False + return True + + def __add__(self, other): + summed = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + summed.on_tth[1] = self.on_tth[1] + other + summed.on_q[1] = self.on_q[1] + other + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to sum two DiffractionObject objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] + summed.on_q[1] = self.on_q[1] + other.on_q[1] + return summed + + def __radd__(self, other): + summed = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + summed.on_tth[1] = self.on_tth[1] + other + summed.on_q[1] = self.on_q[1] + other + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to sum two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] + summed.on_q[1] = self.on_q[1] + other.on_q[1] + return summed + + def __sub__(self, other): + subtracted = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + subtracted.on_tth[1] = self.on_tth[1] - other + subtracted.on_q[1] = self.on_q[1] - other + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to subtract two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] + subtracted.on_q[1] = self.on_q[1] - other.on_q[1] + return subtracted + + def __rsub__(self, other): + subtracted = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + subtracted.on_tth[1] = other - self.on_tth[1] + subtracted.on_q[1] = other - self.on_q[1] + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to subtract two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] + subtracted.on_q[1] = other.on_q[1] - self.on_q[1] + return subtracted + + def __mul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to multiply two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + + def __rmul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + + def __truediv__(self, other): + divided = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + divided.on_tth[1] = other / self.on_tth[1] + divided.on_q[1] = other / self.on_q[1] + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to multiply two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] + divided.on_q[1] = self.on_q[1] / other.on_q[1] + return divided + + def __rtruediv__(self, other): + divided = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + divided.on_tth[1] = other / self.on_tth[1] + divided.on_q[1] = other / self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] + divided.on_q[1] = other.on_q[1] / self.on_q[1] + return divided + + def set_angles_from_list(self, angles_list): + self.angles = angles_list + self.n_steps = len(angles_list) - 1.0 + self.begin_angle = self.angles[0] + self.end_angle = self.angles[-1] + + def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): + """ + create an array of linear spaced Q-values + + Parameters + ---------- + begin_q float + the beginning angle + end_q float + the ending angle + step_size float + the size of the step between points. Only specify step_size or n_steps, not both + n_steps integer + the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both + + Returns + ------- + Sets self.qs + self.qs array of floats + the q values in the independent array + + """ + self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) + + def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): + """ + create an array of linear spaced angle-values + + Parameters + ---------- + begin_angle float + the beginning angle + end_angle float + the ending angle + step_size float + the size of the step between points. Only specify step_size or n_steps, not both + n_steps integer + the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both + + Returns + ------- + Sets self.angles + self.angles array of floats + the q values in the independent array + + """ + self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) + + def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): + if step_size is not None and n_steps is not None: + print( + "WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be " + "reset." + ) + array = np.linspace(begin, end, n_steps) + elif step_size is not None: + array = np.arange(begin, end, step_size) + elif n_steps is not None: + array = np.linspace(begin, end, n_steps) + return array + + def get_angle_index(self, angle): + count = 0 + for i, target in enumerate(self.angles): + if angle == target: + return i + else: + count += 1 + if count >= len(self.angles): + raise IndexError(f"WARNING: no angle {angle} found in angles list") + + def insert_scattering_quantity( + self, + xarray, + yarray, + xtype, + metadata={}, + scat_quantity=None, + name=None, + wavelength=None, + ): + f""" + insert a new scattering quantity into the scattering object + + Parameters + ---------- + xarray array-like of floats + the independent variable array + yarray array-like of floats + the dependent variable array + xtype string + the type of quantity for the independent variable from {*XQUANTITIES, } + metadata: dict + the metadata in the form of a dictionary of user-supplied key:value pairs + + Returns + ------- + + """ + self.input_xtype = xtype + # empty attributes have been defined in the __init__ method so only + # set the attributes that are not empty to avoid emptying them by mistake + if metadata: + self.metadata = metadata + if scat_quantity is not None: + self.scat_quantity = scat_quantity + if name is not None: + self.name = name + if wavelength is not None: + self.wavelength = wavelength + if xtype.lower() in QQUANTITIES: + self.on_q = [np.array(xarray), np.array(yarray)] + elif xtype.lower() in ANGLEQUANTITIES: + self.on_tth = [np.array(xarray), np.array(yarray)] + elif xtype.lower() in DQUANTITIES: + self.on_tth = [np.array(xarray), np.array(yarray)] + self.set_all_arrays() + + def q_to_tth(self): + r""" + Helper function to convert q to two-theta. + + By definition the relationship is: + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) + + Parameters + ---------- + q : array + An array of :math:`q` values + + wavelength : float + Wavelength of the incoming x-rays + + Function adapted from scikit-beam. Thanks to those developers + + Returns + ------- + two_theta : array + An array of :math:`2\theta` values in radians + """ + q = self.on_q[0] + q = np.asarray(q) + wavelength = float(self.wavelength) + pre_factor = wavelength / (4 * np.pi) + return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) + + def tth_to_q(self): + r""" + Helper function to convert two-theta to q + + By definition the relationship is + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} + + + + Parameters + ---------- + two_theta : array + An array of :math:`2\theta` values in units of degrees + + wavelength : float + Wavelength of the incoming x-rays + + Function adapted from scikit-beam. Thanks to those developers. + + Returns + ------- + q : array + An array of :math:`q` values in the inverse of the units + of ``wavelength`` + """ + two_theta = np.asarray(np.deg2rad(self.on_tth[0])) + wavelength = float(self.wavelength) + pre_factor = (4 * np.pi) / wavelength + return pre_factor * np.sin(two_theta / 2) + + def set_all_arrays(self): + master_array, xtype = self._get_original_array() + if xtype == "q": + self.on_tth[0] = self.q_to_tth() + self.on_tth[1] = master_array[1] + if xtype == "tth": + self.on_q[0] = self.tth_to_q() + self.on_q[1] = master_array[1] + self.tthmin = self.on_tth[0][0] + self.tthmax = self.on_tth[0][-1] + self.qmin = self.on_q[0][0] + self.qmax = self.on_q[0][-1] + + def _get_original_array(self): + if self.input_xtype in QQUANTITIES: + return self.on_q, "q" + elif self.input_xtype in ANGLEQUANTITIES: + return self.on_tth, "tth" + elif self.input_xtype in DQUANTITIES: + return self.on_d, "d" + + def scale_to(self, target_diff_object, xtype=None, xvalue=None): + f""" + returns a new diffraction object which is the current object but recaled in y to the target + + Parameters + ---------- + target_diff_object: DiffractionObject + the diffractoin object you want to scale the current one on to + xtype: string, optional. Default is Q + the xtype, from {XQUANTITIES}, that you will specify a point from to scale to + xvalue: float. Default is the midpoint of the array + the y-value in the target at this x-value will be used as the factor to scale to. + The entire array is scaled be the factor that places on on top of the other at that point. + xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling. + + Returns + ------- + the rescaled DiffractionObject as a new object + + """ + scaled = deepcopy(self) + if xtype is None: + xtype = "q" + + data = self.on_xtype(xtype) + target = target_diff_object.on_xtype(xtype) + if xvalue is None: + xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 + + xindex = (np.abs(data[0] - xvalue)).argmin() + ytarget = target[1][xindex] + yself = data[1][xindex] + scaled.on_tth[1] = data[1] * ytarget / yself + scaled.on_q[1] = data[1] * ytarget / yself + return scaled + + def on_xtype(self, xtype): + """ + return a 2D np array with x in the first column and y in the second for x of type type + Parameters + ---------- + xtype + + Returns + ------- + + """ + if xtype.lower() in ANGLEQUANTITIES: + return self.on_tth + elif xtype.lower() in QQUANTITIES: + return self.on_q + elif xtype.lower() in DQUANTITIES: + return self.on_d + pass + + def dump(self, filepath, xtype=None): + if xtype is None: + xtype = " q" + if xtype == "q": + data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) + elif xtype == "tth": + data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) + elif xtype == "d": + data_to_save = np.column_stack((self.on_d[0], self.on_d[1])) + else: + print(f"WARNING: cannot handle the xtype '{xtype}'") + self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) + self.metadata["creation_time"] = datetime.datetime.now() + + with open(filepath, "w") as f: + f.write( + f"[DiffractionObject]\nname = {self.name}\nwavelength = {self.wavelength}\n" + f"scat_quantity = {self.scat_quantity}\n" + ) + for key, value in self.metadata.items(): + f.write(f"{key} = {value}\n") + f.write("\n#### start data\n") + np.savetxt(f, data_to_save, delimiter=" ") From 21520964ef89658ece0a898d51a1df38c9b85840 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 14 Nov 2024 11:13:24 -0500 Subject: [PATCH 280/696] Remove redundant imports for new DiffractionObject --- .../scattering_objects/diffraction_objects.py | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 97b61fe2..926ed8b2 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -1,6 +1,7 @@ import datetime -from copy import deepcopy import warnings +from copy import deepcopy + import numpy as np from diffpy.utils.tools import get_package_info @@ -24,14 +25,14 @@ class Diffraction_object: `Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0. It is replaced by `DiffractionObject` to follow the class naming convention. """ - + warnings.warn( "Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0, It is replaced by " "DiffractionObject` to follow the class naming convention.", DeprecationWarning, stacklevel=2, ) - + def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength @@ -477,25 +478,6 @@ def dump(self, filepath, xtype=None): np.savetxt(f, data_to_save, delimiter=" ") -import datetime -from copy import deepcopy - -import numpy as np - -from diffpy.utils.tools import get_package_info - -QQUANTITIES = ["q"] -ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] -DQUANTITIES = ["d", "dspace"] -XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES -XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] - -x_grid_emsg = ( - "objects are not on the same x-grid. You may add them using the self.add method " - "and specifying how to handle the mismatch." -) - - class DiffractionObject: def __init__(self, name="", wavelength=None): self.name = name From e3ac7a9f283f07b8afc675cf7bf4e9be75dfba02 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 14 Nov 2024 11:16:15 -0500 Subject: [PATCH 281/696] Add news --- news/deprecate.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/deprecate.rst diff --git a/news/deprecate.rst b/news/deprecate.rst new file mode 100644 index 00000000..31dd10e5 --- /dev/null +++ b/news/deprecate.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* Diffraction_object class, renamed to DiffractionObject + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 7841ff298b14246e92fb9ed528691d9c4c4fc184 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 14 Nov 2024 23:08:06 -0500 Subject: [PATCH 282/696] fix docstring and add error messages --- .../scattering_objects/diffraction_objects.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 926ed8b2..17a687f5 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -763,25 +763,29 @@ def q_to_tth(self): 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) + Function adapted from scikit-beam. Thanks to those developers + Parameters ---------- q : array - An array of :math:`q` values + The array of :math:`q` values wavelength : float Wavelength of the incoming x-rays - Function adapted from scikit-beam. Thanks to those developers - Returns ------- two_theta : array - An array of :math:`2\theta` values in radians + The array of :math:`2\theta` values in radians """ q = self.on_q[0] q = np.asarray(q) wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) + if np.any(np.abs(q * pre_factor) > 1): + raise ValueError( + "Invalid input for arcsin: some values exceed the range [-1, 1]. Check wavelength or q values." + ) return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) def tth_to_q(self): @@ -800,25 +804,25 @@ def tth_to_q(self): q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} - + Function adapted from scikit-beam. Thanks to those developers. Parameters ---------- two_theta : array - An array of :math:`2\theta` values in units of degrees + The array of :math:`2\theta` values in units of degrees wavelength : float Wavelength of the incoming x-rays - Function adapted from scikit-beam. Thanks to those developers. - Returns ------- q : array - An array of :math:`q` values in the inverse of the units + The array of :math:`q` values in the inverse of the units of ``wavelength`` """ two_theta = np.asarray(np.deg2rad(self.on_tth[0])) + if np.any(two_theta > np.pi): + raise ValueError("Two theta exceeds 180 degrees.") wavelength = float(self.wavelength) pre_factor = (4 * np.pi) / wavelength return pre_factor * np.sin(two_theta / 2) From 1b0fa19e59f65f45aa1e034e81b4124877127fea Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 14 Nov 2024 23:11:08 -0500 Subject: [PATCH 283/696] add tests --- .../test_diffraction_objects.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index b2225db8..57e6babf 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -231,6 +231,42 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (diffraction_object1 == diffraction_object2) == expected +def test_q_to_tth(): + # valid q values including edge cases when q=0 or 1 + # expected tth values are 2*arcsin(q) + actual = DiffractionObject(wavelength=4 * np.pi) + setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) + actual_tth = actual.q_to_tth() + expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180] + assert np.allclose(actual_tth, expected_tth) + + +def test_q_to_tth_bad(): + # invalid q values when arcsin value is not in the range of [-1, 1] + actual = DiffractionObject(wavelength=4 * np.pi) + setattr(actual, "on_q", [[0.6, 0.8, 1, 1.2], [1, 2, 3, 4]]) + with pytest.raises(ValueError): + actual.q_to_tth() + + +def test_tth_to_q(): + # valid tth values including edge cases when tth=0 or 180 degree + # expected q vales are sin15, sin30, sin45, sin60, sin90 + actual = DiffractionObject(wavelength=4 * np.pi) + setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) + actual_q = actual.tth_to_q() + expected_q = [0, 0.258819, 0.5, 0.707107, 0.866025, 1] + assert np.allclose(actual_q, expected_q) + + +def test_tth_to_q_bad(): + # invalid tth value of > 180 degree + actual = DiffractionObject(wavelength=4 * np.pi) + setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]]) + with pytest.raises(ValueError): + actual.tth_to_q() + + def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) From 1c4d1fc9ef4d2b9d2ab0d2372aafdf2b3947cd17 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 15 Nov 2024 22:37:23 -0500 Subject: [PATCH 284/696] Use pytest for test_loaddata --- tests/diffpy/utils/parsers/test_loaddata.py | 128 ++++++++++---------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py index 3d775c74..45874fab 100644 --- a/tests/diffpy/utils/parsers/test_loaddata.py +++ b/tests/diffpy/utils/parsers/test_loaddata.py @@ -3,71 +3,73 @@ """Unit tests for diffpy.utils.parsers.loaddata """ -import unittest - -import numpy +import numpy as np import pytest from diffpy.utils.parsers.loaddata import loadData -############################################################################## -class TestLoadData(unittest.TestCase): - @pytest.fixture(autouse=True) - def prepare_fixture(self, datafile): - self.datafile = datafile - - def test_loadData_default(self): - """check loadData() with default options""" - loaddata01 = self.datafile("loaddata01.txt") - d2c = numpy.array([[3, 31], [4, 32], [5, 33]]) - self.assertRaises(IOError, loadData, "doesnotexist") - # the default minrows=10 makes it read from the third line - d = loadData(loaddata01) - self.assertTrue(numpy.array_equal(d2c, d)) - # the usecols=(0, 1) would make it read from the third line - d = loadData(loaddata01, minrows=1, usecols=(0, 1)) - self.assertTrue(numpy.array_equal(d2c, d)) - # check the effect of usecols effect - d = loadData(loaddata01, usecols=(0,)) - self.assertTrue(numpy.array_equal(d2c[:, 0], d)) - d = loadData(loaddata01, usecols=(1,)) - self.assertTrue(numpy.array_equal(d2c[:, 1], d)) - return - - def test_loadData_1column(self): - """check loading of one-column data.""" - loaddata01 = self.datafile("loaddata01.txt") - d1c = numpy.arange(1, 6) - d = loadData(loaddata01, usecols=[0], minrows=1) - self.assertTrue(numpy.array_equal(d1c, d)) - d = loadData(loaddata01, usecols=[0], minrows=2) - self.assertTrue(numpy.array_equal(d1c, d)) - d = loadData(loaddata01, usecols=[0], minrows=3) - self.assertFalse(numpy.array_equal(d1c, d)) - return - - def test_loadData_headers(self): - """check loadData() with headers options enabled""" - loaddatawithheaders = self.datafile("loaddatawithheaders.txt") - hignore = ["# ", "// ", "["] # ignore lines beginning with these strings - delimiter = ": " # what our data should be separated by - hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) - # only fourteen lines of data are formatted properly - assert len(hdata) == 14 - # check the following are floats - vfloats = ["wavelength", "qmaxinst", "qmin", "qmax", "bgscale"] - for name in vfloats: - assert isinstance(hdata.get(name), float) - # check the following are NOT floats - vnfloats = ["composition", "rmax", "rmin", "rstep", "rpoly"] - for name in vnfloats: - assert not isinstance(hdata.get(name), float) - - -# End of class TestRoutines - -if __name__ == "__main__": - unittest.main() - -# End of file +def test_loadData_default(datafile): + """check loadData() with default options""" + loaddata01 = datafile("loaddata01.txt") + d2c = np.array([[3, 31], [4, 32], [5, 33]]) + + with pytest.raises(IOError): + loadData("doesnotexist") + + # The default minrows=10 makes it read from the third line + d = loadData(loaddata01) + assert np.array_equal(d2c, d) + + # The usecols=(0, 1) would make it read from the third line + d = loadData(loaddata01, minrows=1, usecols=(0, 1)) + assert np.array_equal(d2c, d) + + # Check the effect of usecols effect + d = loadData(loaddata01, usecols=(0,)) + assert np.array_equal(d2c[:, 0], d) + + d = loadData(loaddata01, usecols=(1,)) + assert np.array_equal(d2c[:, 1], d) + + +def test_loadData_1column(datafile): + """check loading of one-column data.""" + loaddata01 = datafile("loaddata01.txt") + d1c = np.arange(1, 6) + + # Assertions using pytest's assert + d = loadData(loaddata01, usecols=[0], minrows=1) + assert np.array_equal(d1c, d) + + d = loadData(loaddata01, usecols=[0], minrows=2) + assert np.array_equal(d1c, d) + + d = loadData(loaddata01, usecols=[0], minrows=3) + assert not np.array_equal(d1c, d) + + return + + +def test_loadData_headers(datafile): + """check loadData() with headers options enabled""" + loaddatawithheaders = datafile("loaddatawithheaders.txt") + hignore = ["# ", "// ", "["] # ignore lines beginning with these strings + delimiter = ": " # what our data should be separated by + + # Load data with headers + hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) + + # Assertions using pytest + # Only fourteen lines of data are formatted properly + assert len(hdata) == 14 + + # Check the following are floats + vfloats = ["wavelength", "qmaxinst", "qmin", "qmax", "bgscale"] + for name in vfloats: + assert isinstance(hdata.get(name), float) + + # Check the following are NOT floats + vnfloats = ["composition", "rmax", "rmin", "rstep", "rpoly"] + for name in vnfloats: + assert not isinstance(hdata.get(name), float) From bf88396cf1a9f703c066dccafb1026003f886d2d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 15 Nov 2024 22:40:13 -0500 Subject: [PATCH 285/696] Add news --- news/test-refactor.rst | 23 +++++++++++++++++++++ tests/diffpy/utils/parsers/test_loaddata.py | 3 --- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 news/test-refactor.rst diff --git a/news/test-refactor.rst b/news/test-refactor.rst new file mode 100644 index 00000000..095df231 --- /dev/null +++ b/news/test-refactor.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Unittest to Pytest migration for test_loaddata.py + +**Security:** + +* diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py index 45874fab..8ee80bf5 100644 --- a/tests/diffpy/utils/parsers/test_loaddata.py +++ b/tests/diffpy/utils/parsers/test_loaddata.py @@ -48,8 +48,6 @@ def test_loadData_1column(datafile): d = loadData(loaddata01, usecols=[0], minrows=3) assert not np.array_equal(d1c, d) - return - def test_loadData_headers(datafile): """check loadData() with headers options enabled""" @@ -60,7 +58,6 @@ def test_loadData_headers(datafile): # Load data with headers hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) - # Assertions using pytest # Only fourteen lines of data are formatted properly assert len(hdata) == 14 From 0f4c5400869c4daf3aad6e26fff57300ec69b427 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 16 Nov 2024 23:11:00 -0500 Subject: [PATCH 286/696] Use actual data to test headers and test error IO msg --- tests/diffpy/utils/parsers/test_loaddata.py | 34 ++++++++++++--------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py index 8ee80bf5..a747be8a 100644 --- a/tests/diffpy/utils/parsers/test_loaddata.py +++ b/tests/diffpy/utils/parsers/test_loaddata.py @@ -14,8 +14,9 @@ def test_loadData_default(datafile): loaddata01 = datafile("loaddata01.txt") d2c = np.array([[3, 31], [4, 32], [5, 33]]) - with pytest.raises(IOError): + with pytest.raises(IOError) as err: loadData("doesnotexist") + assert "No such file or directory" in str(err.value) # The default minrows=10 makes it read from the third line d = loadData(loaddata01) @@ -51,22 +52,27 @@ def test_loadData_1column(datafile): def test_loadData_headers(datafile): """check loadData() with headers options enabled""" + expected = { + "wavelength": 0.1, + "dataformat": "Qnm", + "inputfile": "darkSub_rh20_C_01.chi", + "mode": "xray", + "bgscale": 1.2998929285, + "composition": "0.800.20", + "outputtype": "gr", + "qmaxinst": 25.0, + "qmin": 0.1, + "qmax": 25.0, + "rmax": "100.0r", + "rmin": "0.0r", + "rstep": "0.01r", + "rpoly": "0.9r", + } + loaddatawithheaders = datafile("loaddatawithheaders.txt") hignore = ["# ", "// ", "["] # ignore lines beginning with these strings delimiter = ": " # what our data should be separated by # Load data with headers hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) - - # Only fourteen lines of data are formatted properly - assert len(hdata) == 14 - - # Check the following are floats - vfloats = ["wavelength", "qmaxinst", "qmin", "qmax", "bgscale"] - for name in vfloats: - assert isinstance(hdata.get(name), float) - - # Check the following are NOT floats - vnfloats = ["composition", "rmax", "rmin", "rstep", "rpoly"] - for name in vnfloats: - assert not isinstance(hdata.get(name), float) + assert hdata == expected From 4b055189e8927793943ad10266086208ae37cafd Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 17 Nov 2024 11:37:31 -0500 Subject: [PATCH 287/696] Add custom IOError msg for file not found in loaddata --- src/diffpy/utils/parsers/loaddata.py | 6 ++++++ tests/diffpy/utils/parsers/test_loaddata.py | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 18375d90..870e6016 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -13,6 +13,8 @@ # ############################################################################## +import os + import numpy @@ -99,6 +101,10 @@ def countcolumnsvalues(line): nc = nv = 0 return nc, nv + # Check if file exists before trying to open + if not os.path.exists(filename): + raise IOError(f"File {filename} cannot be found. Please rerun the program specifying a valid filename.") + # make sure fid gets cleaned up with open(filename, "rb") as fid: # search for the start of datablock diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py index a747be8a..a7e273e7 100644 --- a/tests/diffpy/utils/parsers/test_loaddata.py +++ b/tests/diffpy/utils/parsers/test_loaddata.py @@ -15,8 +15,11 @@ def test_loadData_default(datafile): d2c = np.array([[3, 31], [4, 32], [5, 33]]) with pytest.raises(IOError) as err: - loadData("doesnotexist") - assert "No such file or directory" in str(err.value) + loadData("doesnotexist.txt") + assert ( + str(err.value) + == "File doesnotexist.txt cannot be found. Please rerun the program specifying a valid filename." + ) # The default minrows=10 makes it read from the third line d = loadData(loaddata01) From 4e85b0a215d1dd344689616d3a6d1a4d73c923f4 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 20 Nov 2024 12:00:54 -0500 Subject: [PATCH 288/696] edit error message --- .../utils/scattering_objects/diffraction_objects.py | 9 +++++---- .../utils/scattering_objects/test_diffraction_objects.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 17a687f5..0ff9f0ce 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -783,9 +783,7 @@ def q_to_tth(self): wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) if np.any(np.abs(q * pre_factor) > 1): - raise ValueError( - "Invalid input for arcsin: some values exceed the range [-1, 1]. Check wavelength or q values." - ) + raise ValueError("Please check if you entered an incorrect wavelength or q value.") return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) def tth_to_q(self): @@ -822,7 +820,10 @@ def tth_to_q(self): """ two_theta = np.asarray(np.deg2rad(self.on_tth[0])) if np.any(two_theta > np.pi): - raise ValueError("Two theta exceeds 180 degrees.") + raise ValueError( + "Two theta exceeds 180 degrees. Please check if invalid values were entered " + "or if degrees were incorrectly specified as radians." + ) wavelength = float(self.wavelength) pre_factor = (4 * np.pi) / wavelength return pre_factor * np.sin(two_theta / 2) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 57e6babf..4caa61ae 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -232,7 +232,7 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_q_to_tth(): - # valid q values including edge cases when q=0 or 1 + # valid q values including edge cases when q=0 or tth=180 after converting # expected tth values are 2*arcsin(q) actual = DiffractionObject(wavelength=4 * np.pi) setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) @@ -242,7 +242,7 @@ def test_q_to_tth(): def test_q_to_tth_bad(): - # invalid q values when arcsin value is not in the range of [-1, 1] + # invalid wavelength or q values when arcsin value is not in the range of [-1, 1] actual = DiffractionObject(wavelength=4 * np.pi) setattr(actual, "on_q", [[0.6, 0.8, 1, 1.2], [1, 2, 3, 4]]) with pytest.raises(ValueError): From a33482b240dd276c508700936c5c07a31e2eb5ed Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 21 Nov 2024 18:19:43 -0500 Subject: [PATCH 289/696] add more tests --- .../test_diffraction_objects.py | 79 ++++++++++++++++--- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 4caa61ae..36aace8f 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -232,8 +232,8 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_q_to_tth(): - # valid q values including edge cases when q=0 or tth=180 after converting - # expected tth values are 2*arcsin(q) + # Valid q values that should result in 0-180 tth values after conversion + # expected tth values are 2*arcsin(q) in degrees actual = DiffractionObject(wavelength=4 * np.pi) setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) actual_tth = actual.q_to_tth() @@ -241,16 +241,43 @@ def test_q_to_tth(): assert np.allclose(actual_tth, expected_tth) -def test_q_to_tth_bad(): - # invalid wavelength or q values when arcsin value is not in the range of [-1, 1] - actual = DiffractionObject(wavelength=4 * np.pi) - setattr(actual, "on_q", [[0.6, 0.8, 1, 1.2], [1, 2, 3, 4]]) +params_q_to_tth_bad = [ + # UC1: user did not specify wavelength + ( + [None, [0, 0.2, 0.4, 0.6, 0.8, 1]], + "Wavelength is not specified. Please provide a valid wavelength, " + "e.g., DiffractionObject(wavelength=0.71).", + ), + # UC2: user specified invalid q values that result in tth > 180 degrees + ( + [4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2]], + "Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", + ), + # UC3: user specified a wrong wavelength that result in tth > 180 degrees + ( + [100, [0, 0.2, 0.4, 0.6, 0.8, 1]], + "Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", + ), + # UC4: user specified an empty q array + ([4 * np.pi, []], "Q array is empty. Please provide valid q values."), + # UC5: user specified a non-numeric value in q array + ( + [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"]], + "Invalid value found in q array. Please ensure all values are numeric.", + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) +def test_q_to_tth_bad(inputs, expected): + actual = DiffractionObject(wavelength=inputs[0]) + setattr(actual, "on_q", [inputs[1], [1, 2, 3, 4, 5, 6]]) with pytest.raises(ValueError): actual.q_to_tth() def test_tth_to_q(): - # valid tth values including edge cases when tth=0 or 180 degree + # Valid tth values between 0-180 degrees # expected q vales are sin15, sin30, sin45, sin60, sin90 actual = DiffractionObject(wavelength=4 * np.pi) setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) @@ -259,11 +286,39 @@ def test_tth_to_q(): assert np.allclose(actual_q, expected_q) -def test_tth_to_q_bad(): - # invalid tth value of > 180 degree - actual = DiffractionObject(wavelength=4 * np.pi) - setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]]) - with pytest.raises(ValueError): +params_tth_to_q_bad = [ + # UC1: user did not specify wavelength + ( + [None, [0, 30, 60, 90, 120, 180]], + "Wavelength is not specified. Please provide a valid wavelength, " + "e.g., DiffractionObject(wavelength=0.71).", + ), + # UC2: user specified an invalid tth value of > 180 degrees + ( + [4 * np.pi, [0, 30, 60, 90, 120, 181]], + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + # UC3: user did not specify wavelength and specified invalid tth values + ( + [None, [0, 30, 60, 90, 120, 181]], + "Wavelength is not specified. Please provide a valid wavelength, " + "e.g., DiffractionObject(wavelength=0.71).", + ), + # UC4: user specified an empty two theta array + ([4 * np.pi, []], "Two theta array is empty. Please provide valid two theta values."), + # UC5: user specified a non-numeric value in two theta array + ( + [4 * np.pi, [0, 30, 60, 90, 120, "invalid"]], + "Invalid value found in two theta array. Please ensure all values are numeric.", + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) +def test_tth_to_q_bad(inputs, expected): + actual = DiffractionObject(wavelength=inputs[0]) + setattr(actual, "on_tth", [inputs[1], [1, 2, 3, 4, 5, 6]]) + with pytest.raises(ValueError, match=expected): actual.tth_to_q() From 3efde28d9e40ef68a97796e569471e646df24827 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 25 Nov 2024 09:54:09 -0500 Subject: [PATCH 290/696] add warning msg for wavelength, edit error msg --- .../test_diffraction_objects.py | 183 +++++++++++++----- 1 file changed, 134 insertions(+), 49 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 36aace8f..07674a5c 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -231,39 +231,95 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (diffraction_object1 == diffraction_object2) == expected -def test_q_to_tth(): - # Valid q values that should result in 0-180 tth values after conversion +def _test_valid_diffraction_objects(actual_diffraction_object, function, expected_array): + """Checks the behavior of the DiffractionObject: + when there is no wavelength, we expect the correct warning message and output, + otherwise, we only check the output matches the expected array.""" + if actual_diffraction_object.wavelength is None: + with pytest.warns(UserWarning) as warn_record: + getattr(actual_diffraction_object, function)() + assert str(warn_record[0].message) == ( + "INFO: no wavelength has been specified. You can continue " + "to use the DiffractionObject but some of its powerful features " + "will not be available. To specify a wavelength, you can use " + "DiffractionObject(wavelength=0.71)." + ) + actual_array = getattr(actual_diffraction_object, function)() + return np.allclose(actual_array, expected_array) + + +params_q_to_tth = [ + # UC1: User specified empty q values (without wavelength) + ( + [None, [], []], + [[]], + ), + # UC2: User specified empty q values (with wavelength) + ( + [4 * np.pi, [], []], + [[]], + ), + # UC3: User specified valid q values (without wavelength) # expected tth values are 2*arcsin(q) in degrees - actual = DiffractionObject(wavelength=4 * np.pi) - setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) - actual_tth = actual.q_to_tth() - expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180] - assert np.allclose(actual_tth, expected_tth) + ( + [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], + [[]], + ), + # UC4: User specified valid q values (with wavelength) + # expected tth values are 2*arcsin(q) in degrees + ( + [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], + [[0, 23.07392, 47.15636, 73.73980, 106.26020, 180]], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_tth) +def test_q_to_tth(inputs, expected): + actual = DiffractionObject(wavelength=inputs[0]) + actual.on_q = [inputs[1], inputs[2]] + expected_tth = expected[0] + assert _test_valid_diffraction_objects(actual, "q_to_tth", expected_tth) params_q_to_tth_bad = [ - # UC1: user did not specify wavelength + # UC1: user specified invalid q values that result in tth > 180 degrees ( - [None, [0, 0.2, 0.4, 0.6, 0.8, 1]], - "Wavelength is not specified. Please provide a valid wavelength, " - "e.g., DiffractionObject(wavelength=0.71).", + [4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2], [1, 2, 3, 4, 5, 6]], + [ + ValueError, + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject.", + ], ), - # UC2: user specified invalid q values that result in tth > 180 degrees + # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2]], - "Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", + [100, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], + [ + ValueError, + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject.", + ], ), - # UC3: user specified a wrong wavelength that result in tth > 180 degrees + # UC3: user specified a q array that does not match the length of intensity array (without wavelength) ( - [100, [0, 0.2, 0.4, 0.6, 0.8, 1]], - "Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", + [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], + [IndexError, "Please ensure q array and intensity array are the same length."], ), - # UC4: user specified an empty q array - ([4 * np.pi, []], "Q array is empty. Please provide valid q values."), - # UC5: user specified a non-numeric value in q array + # UC4: user specified a q array that does not match the length of intensity array (with wavelength) ( - [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"]], - "Invalid value found in q array. Please ensure all values are numeric.", + [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], + [IndexError, "Please ensure q array and intensity array are the same length."], + ), + # UC5: user specified a non-numeric value in q array (without wavelength) + ( + [None, [0, 0.2, 0.4, 0.6, 0.8, "invalid"], [1, 2, 3, 4, 5, 6]], + [TypeError, "Invalid value found in q array. Please ensure all values are numeric."], + ), + # UC5: user specified a non-numeric value in q array (with wavelength) + ( + [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"], [1, 2, 3, 4, 5, 6]], + [TypeError, "Invalid value found in q array. Please ensure all values are numeric."], ), ] @@ -271,45 +327,74 @@ def test_q_to_tth(): @pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) def test_q_to_tth_bad(inputs, expected): actual = DiffractionObject(wavelength=inputs[0]) - setattr(actual, "on_q", [inputs[1], [1, 2, 3, 4, 5, 6]]) - with pytest.raises(ValueError): + actual.on_q = [inputs[1], inputs[2]] + with pytest.raises(expected[0], match=expected[1]): actual.q_to_tth() -def test_tth_to_q(): - # Valid tth values between 0-180 degrees +params_tth_to_q = [ + # UC1: User specified empty tth values (without wavelength) + ( + [None, [], []], + [[]], + ), + # UC2: User specified empty tth values (with wavelength) + ( + [4 * np.pi, [], []], + [[]], + ), + # UC3: User specified valid tth values between 0-180 degrees (without wavelength) + ( + [None, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], + [[]], + ), + # UC4: User specified valid tth values between 0-180 degrees (with wavelength) # expected q vales are sin15, sin30, sin45, sin60, sin90 - actual = DiffractionObject(wavelength=4 * np.pi) - setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) - actual_q = actual.tth_to_q() - expected_q = [0, 0.258819, 0.5, 0.707107, 0.866025, 1] - assert np.allclose(actual_q, expected_q) + ( + [4 * np.pi, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], + [[0, 0.258819, 0.5, 0.707107, 0.866025, 1]], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_q) +def test_tth_to_q(inputs, expected): + actual = DiffractionObject(wavelength=inputs[0]) + actual.on_tth = [inputs[1], inputs[2]] + expected_q = expected[0] + assert _test_valid_diffraction_objects(actual, "tth_to_q", expected_q) params_tth_to_q_bad = [ - # UC1: user did not specify wavelength + # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + [None, [0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), + # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + [4 * np.pi, [0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), + # UC3: user specified a two theta array that does not match the length of intensity array (without wavelength) ( - [None, [0, 30, 60, 90, 120, 180]], - "Wavelength is not specified. Please provide a valid wavelength, " - "e.g., DiffractionObject(wavelength=0.71).", + [None, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], + [IndexError, "Please ensure two theta array and intensity array are the same length."], ), - # UC2: user specified an invalid tth value of > 180 degrees + # UC4: user specified a two theta array that does not match the length of intensity array (with wavelength) ( - [4 * np.pi, [0, 30, 60, 90, 120, 181]], - "Two theta exceeds 180 degrees. Please check the input values for errors.", + [4 * np.pi, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], + [IndexError, "Please ensure two theta array and intensity array are the same length."], ), - # UC3: user did not specify wavelength and specified invalid tth values + # UC5: user specified a non-numeric value in two theta array (without wavelength) ( - [None, [0, 30, 60, 90, 120, 181]], - "Wavelength is not specified. Please provide a valid wavelength, " - "e.g., DiffractionObject(wavelength=0.71).", + [None, [0, 30, 60, 90, 120, "invalid"], [1, 2, 3, 4, 5, 6]], + [TypeError, "Invalid value found in two theta array. Please ensure all values are numeric."], ), - # UC4: user specified an empty two theta array - ([4 * np.pi, []], "Two theta array is empty. Please provide valid two theta values."), - # UC5: user specified a non-numeric value in two theta array + # UC6: user specified a non-numeric value in two theta array (with wavelength) ( - [4 * np.pi, [0, 30, 60, 90, 120, "invalid"]], - "Invalid value found in two theta array. Please ensure all values are numeric.", + [4 * np.pi, [0, 30, 60, 90, 120, "invalid"], [1, 2, 3, 4, 5, 6]], + [TypeError, "Invalid value found in two theta array. Please ensure all values are numeric."], ), ] @@ -317,8 +402,8 @@ def test_tth_to_q(): @pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) def test_tth_to_q_bad(inputs, expected): actual = DiffractionObject(wavelength=inputs[0]) - setattr(actual, "on_tth", [inputs[1], [1, 2, 3, 4, 5, 6]]) - with pytest.raises(ValueError, match=expected): + actual.on_tth = [inputs[1], inputs[2]] + with pytest.raises(expected[0], match=expected[1]): actual.tth_to_q() From a53461f5865818a0d0db6dfd838759436e734f25 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 25 Nov 2024 14:36:27 -0500 Subject: [PATCH 291/696] edit warning msg for wavelength, change index error to runtime error --- .../test_diffraction_objects.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 07674a5c..59d7cafb 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -241,8 +241,10 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte assert str(warn_record[0].message) == ( "INFO: no wavelength has been specified. You can continue " "to use the DiffractionObject but some of its powerful features " - "will not be available. To specify a wavelength, you can use " - "DiffractionObject(wavelength=0.71)." + "will not be available. To specify a wavelength, set " + "diffraction_object.wavelength = [number], " + "where diffraction_object is the variable name of you Diffraction Object, " + "and number is the wavelength in angstroms." ) actual_array = getattr(actual_diffraction_object, function)() return np.allclose(actual_array, expected_array) @@ -289,7 +291,7 @@ def test_q_to_tth(inputs, expected): [ ValueError, "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject.", + "Please check these values and re-instantiate the DiffractionObject with correct values.", ], ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees @@ -304,12 +306,12 @@ def test_q_to_tth(inputs, expected): # UC3: user specified a q array that does not match the length of intensity array (without wavelength) ( [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [IndexError, "Please ensure q array and intensity array are the same length."], + [RuntimeError, "Please ensure q array and intensity array are the same length."], ), # UC4: user specified a q array that does not match the length of intensity array (with wavelength) ( [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [IndexError, "Please ensure q array and intensity array are the same length."], + [RuntimeError, "Please ensure q array and intensity array are the same length."], ), # UC5: user specified a non-numeric value in q array (without wavelength) ( @@ -379,12 +381,12 @@ def test_tth_to_q(inputs, expected): # UC3: user specified a two theta array that does not match the length of intensity array (without wavelength) ( [None, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [IndexError, "Please ensure two theta array and intensity array are the same length."], + [RuntimeError, "Please ensure two theta array and intensity array are the same length."], ), # UC4: user specified a two theta array that does not match the length of intensity array (with wavelength) ( [4 * np.pi, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [IndexError, "Please ensure two theta array and intensity array are the same length."], + [RuntimeError, "Please ensure two theta array and intensity array are the same length."], ), # UC5: user specified a non-numeric value in two theta array (without wavelength) ( From 2a5a819e55b8c14d3fc7e327d4088fbe65aea14f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 25 Nov 2024 15:15:10 -0500 Subject: [PATCH 292/696] add functionality for handling error messages --- .../scattering_objects/diffraction_objects.py | 40 ++++++++++++-- .../test_diffraction_objects.py | 52 +++++-------------- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 0ff9f0ce..2017b926 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -17,6 +17,23 @@ "and specifying how to handle the mismatch." ) +wavelength_warning_emsg = ( + "INFO: no wavelength has been specified. You can continue " + "to use the DiffractionObject but some of its powerful features " + "will not be available. To specify a wavelength, set " + "diffraction_object.wavelength = [number], " + "where diffraction_object is the variable name of you Diffraction Object, " + "and number is the wavelength in angstroms." +) + +length_mismatch_emsg = "Please ensure {array_name} array and intensity array are of the same length." +non_numeric_value_emsg = "Invalid value found in {array_name} array. Please ensure all values are numeric." +invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." +invalid_q_or_wavelength_emsg = ( + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values." +) + class Diffraction_object: """A class to represent and manipulate data associated with diffraction experiments. @@ -778,12 +795,20 @@ def q_to_tth(self): two_theta : array The array of :math:`2\theta` values in radians """ + for i, value in enumerate(self.on_q[0]): + if not isinstance(value, (int, float)): + raise TypeError(non_numeric_value_emsg.format(array_name="q")) + if len(self.on_q[0]) != len(self.on_q[1]): + raise RuntimeError(length_mismatch_emsg.format(array_name="q")) + if self.wavelength is None: + warnings.warn(wavelength_warning_emsg, UserWarning) + return np.empty(0) q = self.on_q[0] q = np.asarray(q) wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) if np.any(np.abs(q * pre_factor) > 1): - raise ValueError("Please check if you entered an incorrect wavelength or q value.") + raise ValueError(invalid_q_or_wavelength_emsg) return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) def tth_to_q(self): @@ -818,12 +843,17 @@ def tth_to_q(self): The array of :math:`q` values in the inverse of the units of ``wavelength`` """ + for i, value in enumerate(self.on_tth[0]): + if not isinstance(value, (int, float)): + raise TypeError(non_numeric_value_emsg.format(array_name="two theta")) + if len(self.on_tth[0]) != len(self.on_tth[1]): + raise RuntimeError(length_mismatch_emsg.format(array_name="two theta")) two_theta = np.asarray(np.deg2rad(self.on_tth[0])) if np.any(two_theta > np.pi): - raise ValueError( - "Two theta exceeds 180 degrees. Please check if invalid values were entered " - "or if degrees were incorrectly specified as radians." - ) + raise ValueError(invalid_tth_emsg) + if self.wavelength is None: + warnings.warn(wavelength_warning_emsg, UserWarning) + return np.empty(0) wavelength = float(self.wavelength) pre_factor = (4 * np.pi) / wavelength return pre_factor * np.sin(two_theta / 2) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 59d7cafb..7fd1ef96 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -4,7 +4,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject +from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject, wavelength_warning_emsg params = [ ( # Default @@ -238,35 +238,18 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte if actual_diffraction_object.wavelength is None: with pytest.warns(UserWarning) as warn_record: getattr(actual_diffraction_object, function)() - assert str(warn_record[0].message) == ( - "INFO: no wavelength has been specified. You can continue " - "to use the DiffractionObject but some of its powerful features " - "will not be available. To specify a wavelength, set " - "diffraction_object.wavelength = [number], " - "where diffraction_object is the variable name of you Diffraction Object, " - "and number is the wavelength in angstroms." - ) + assert str(warn_record[0].message) == wavelength_warning_emsg actual_array = getattr(actual_diffraction_object, function)() return np.allclose(actual_array, expected_array) params_q_to_tth = [ # UC1: User specified empty q values (without wavelength) - ( - [None, [], []], - [[]], - ), + ([None, [], []], [[]]), # UC2: User specified empty q values (with wavelength) - ( - [4 * np.pi, [], []], - [[]], - ), + ([4 * np.pi, [], []], [[]]), # UC3: User specified valid q values (without wavelength) - # expected tth values are 2*arcsin(q) in degrees - ( - [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], - [[]], - ), + ([None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], [[]]), # UC4: User specified valid q values (with wavelength) # expected tth values are 2*arcsin(q) in degrees ( @@ -300,18 +283,18 @@ def test_q_to_tth(inputs, expected): [ ValueError, "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject.", + "Please check these values and re-instantiate the DiffractionObject with correct values.", ], ), # UC3: user specified a q array that does not match the length of intensity array (without wavelength) ( [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [RuntimeError, "Please ensure q array and intensity array are the same length."], + [RuntimeError, "Please ensure q array and intensity array are of the same length."], ), # UC4: user specified a q array that does not match the length of intensity array (with wavelength) ( [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [RuntimeError, "Please ensure q array and intensity array are the same length."], + [RuntimeError, "Please ensure q array and intensity array are of the same length."], ), # UC5: user specified a non-numeric value in q array (without wavelength) ( @@ -336,15 +319,9 @@ def test_q_to_tth_bad(inputs, expected): params_tth_to_q = [ # UC1: User specified empty tth values (without wavelength) - ( - [None, [], []], - [[]], - ), + ([None, [], []], [[]]), # UC2: User specified empty tth values (with wavelength) - ( - [4 * np.pi, [], []], - [[]], - ), + ([4 * np.pi, [], []], [[]]), # UC3: User specified valid tth values between 0-180 degrees (without wavelength) ( [None, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], @@ -352,10 +329,7 @@ def test_q_to_tth_bad(inputs, expected): ), # UC4: User specified valid tth values between 0-180 degrees (with wavelength) # expected q vales are sin15, sin30, sin45, sin60, sin90 - ( - [4 * np.pi, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], - [[0, 0.258819, 0.5, 0.707107, 0.866025, 1]], - ), + ([4 * np.pi, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], [[0, 0.258819, 0.5, 0.707107, 0.866025, 1]]), ] @@ -381,12 +355,12 @@ def test_tth_to_q(inputs, expected): # UC3: user specified a two theta array that does not match the length of intensity array (without wavelength) ( [None, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [RuntimeError, "Please ensure two theta array and intensity array are the same length."], + [RuntimeError, "Please ensure two theta array and intensity array are of the same length."], ), # UC4: user specified a two theta array that does not match the length of intensity array (with wavelength) ( [4 * np.pi, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [RuntimeError, "Please ensure two theta array and intensity array are the same length."], + [RuntimeError, "Please ensure two theta array and intensity array are of the same length."], ), # UC5: user specified a non-numeric value in two theta array (without wavelength) ( From 64d8693747357a889d922f1c5451717ff2967148 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 25 Nov 2024 20:38:48 -0500 Subject: [PATCH 293/696] remove docstring from private test functions --- .../utils/scattering_objects/test_diffraction_objects.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 7fd1ef96..3bdc22d4 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -232,9 +232,6 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def _test_valid_diffraction_objects(actual_diffraction_object, function, expected_array): - """Checks the behavior of the DiffractionObject: - when there is no wavelength, we expect the correct warning message and output, - otherwise, we only check the output matches the expected array.""" if actual_diffraction_object.wavelength is None: with pytest.warns(UserWarning) as warn_record: getattr(actual_diffraction_object, function)() From 728ff36ba12dbda4e12415d695144c33ce5ac587 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 26 Nov 2024 08:20:02 -0500 Subject: [PATCH 294/696] add news --- news/tth-q.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/tth-q.rst diff --git a/news/tth-q.rst b/news/tth-q.rst new file mode 100644 index 00000000..c512bf9b --- /dev/null +++ b/news/tth-q.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality to raise useful warning and error messages during angular conversion between two theta and q + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 490486c25d692cd3de07ab9f44ae3ac7b3caad56 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 26 Nov 2024 13:21:42 -0500 Subject: [PATCH 295/696] add example and utility --- .../examples/diffractionobjectsexample.rst | 41 +++++++++++++++++++ .../utilities/diffractionobjectsutility.rst | 16 ++++++++ 2 files changed, 57 insertions(+) create mode 100644 doc/source/examples/diffractionobjectsexample.rst create mode 100644 doc/source/utilities/diffractionobjectsutility.rst diff --git a/doc/source/examples/diffractionobjectsexample.rst b/doc/source/examples/diffractionobjectsexample.rst new file mode 100644 index 00000000..f9def3f4 --- /dev/null +++ b/doc/source/examples/diffractionobjectsexample.rst @@ -0,0 +1,41 @@ +.. _Diffraction Objects Example: + +:tocdepth: -1 + +Diffraction Objects Example +########################### + +This example will demonstrate how to use the ``DiffractionObject`` class in the +``diffpy.utils.scattering_objects.diffraction_objects`` module to process and analyze diffraction data. + +1) We have the function ``q_to_tth`` to convert q to two theta values in degrees, and ``tth_to_q`` to do the reverse. + You can use these functions with a pre-defined ``DiffractionObject``. :: + + # convert q to tth + from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject + test = DiffractionObject(wavelength=1.54) + test.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] + test.q_to_tth() + + This function will convert your provided q array and return a two theta array in degrees. + To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. + + Similarly, use the function ``tth_to_q`` to convert two theta values in degrees to q values. :: + + # convert tth to q + from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject + test = DiffractionObject(wavelength=1.54) + test.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] + test.tth_to_q() + + To load the converted array, you can either call ``test.tth_to_q()`` or ``test.on_tth[0]``. + +2) You can use these functions without specifying a wavelength. However, if so, the function will return an empty array, + so we strongly encourage you to specify a wavelength when using these functions. :: + + from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject + test = DiffractionObject() + test.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] + test.q_to_tth() + + In this case, the function will return an empty array on two theta. diff --git a/doc/source/utilities/diffractionobjectsutility.rst b/doc/source/utilities/diffractionobjectsutility.rst new file mode 100644 index 00000000..da9ddac6 --- /dev/null +++ b/doc/source/utilities/diffractionobjectsutility.rst @@ -0,0 +1,16 @@ +.. _Diffraction Objects Utility: + +Diffraction Objects Utility +=========================== + +The ``diffpy.utils.scattering_objects.diffraction_objects`` module provides functions +for managing and analyzing diffraction data, including angle-space conversions +and interactions between diffraction data. + +- ``q_to_tth()``: Converts an array of q values to their corresponding two theta values, based on specified wavelength. +- ``tth_to_q()``: Converts an array of two theta values to their corresponding q values, based on specified wavelength. + + These functions help developers standardize diffraction data and update the arrays + in the associated ``DiffractionObject``, enabling easier analysis and further processing. + +For a more in-depth tutorial for how to use these functions, click :ref:`here `. From d481253f09ea0ca698f2175f6fb6d9ac4f3e8714 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 26 Nov 2024 18:39:06 -0500 Subject: [PATCH 296/696] edits on docs --- .../examples/diffractionobjectsexample.rst | 39 ++++++++----------- .../utilities/diffractionobjectsutility.rst | 7 +--- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/doc/source/examples/diffractionobjectsexample.rst b/doc/source/examples/diffractionobjectsexample.rst index f9def3f4..f3114ab0 100644 --- a/doc/source/examples/diffractionobjectsexample.rst +++ b/doc/source/examples/diffractionobjectsexample.rst @@ -8,34 +8,27 @@ Diffraction Objects Example This example will demonstrate how to use the ``DiffractionObject`` class in the ``diffpy.utils.scattering_objects.diffraction_objects`` module to process and analyze diffraction data. -1) We have the function ``q_to_tth`` to convert q to two theta values in degrees, and ``tth_to_q`` to do the reverse. - You can use these functions with a pre-defined ``DiffractionObject``. :: +1) Assuming we have created a ``DiffractionObject`` called my_diffraction_pattern from a measured diffraction pattern, + and we have specified the wavelength (see Section ??, to be added), + we can use the ``q_to_tth`` and ``tth_to_q`` functions to convert between q and two-theta. :: - # convert q to tth - from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject - test = DiffractionObject(wavelength=1.54) - test.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] - test.q_to_tth() + # Example: convert q to tth + my_diffraction_pattern.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] + my_diffraction_pattern.q_to_tth() This function will convert your provided q array and return a two theta array in degrees. - To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. - - Similarly, use the function ``tth_to_q`` to convert two theta values in degrees to q values. :: + To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. :: - # convert tth to q + # Example: convert tth to q from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject - test = DiffractionObject(wavelength=1.54) - test.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] - test.tth_to_q() + my_diffraction_pattern.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] + my_diffraction_pattern.tth_to_q() - To load the converted array, you can either call ``test.tth_to_q()`` or ``test.on_tth[0]``. + Similarly, to load the converted array, you can either call ``test.tth_to_q()`` or ``test.on_tth[0]``. -2) You can use these functions without specifying a wavelength. However, if so, the function will return an empty array, - so we strongly encourage you to specify a wavelength when using these functions. :: - - from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject - test = DiffractionObject() - test.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] - test.q_to_tth() +2) Both functions require a wavelength to perform conversions. Without a wavelength, they will return empty arrays. + Therefore, we strongly encourage you to specify a wavelength when using these functions. :: - In this case, the function will return an empty array on two theta. + # Example: without wavelength specified + my_diffraction_pattern.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] + my_diffraction_pattern.q_to_tth() # returns an empty array diff --git a/doc/source/utilities/diffractionobjectsutility.rst b/doc/source/utilities/diffractionobjectsutility.rst index da9ddac6..d388e856 100644 --- a/doc/source/utilities/diffractionobjectsutility.rst +++ b/doc/source/utilities/diffractionobjectsutility.rst @@ -7,10 +7,7 @@ The ``diffpy.utils.scattering_objects.diffraction_objects`` module provides func for managing and analyzing diffraction data, including angle-space conversions and interactions between diffraction data. -- ``q_to_tth()``: Converts an array of q values to their corresponding two theta values, based on specified wavelength. -- ``tth_to_q()``: Converts an array of two theta values to their corresponding q values, based on specified wavelength. - - These functions help developers standardize diffraction data and update the arrays - in the associated ``DiffractionObject``, enabling easier analysis and further processing. +These functions help developers standardize diffraction data and update the arrays +in the associated ``DiffractionObject``, enabling easier analysis and further processing. For a more in-depth tutorial for how to use these functions, click :ref:`here `. From 114cb65046dea151d9e49394250f79a28d12b2a7 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 1 Dec 2024 08:44:59 -0500 Subject: [PATCH 297/696] refactor the transforms out of DiffractionObjects to make them more wiedely available in general --- news/private_f.rst | 24 ++ .../scattering_objects/diffraction_objects.py | 230 ++---------------- src/diffpy/utils/transforms.py | 122 ++++++++++ .../test_diffraction_objects.py | 151 +----------- tests/test_transforms.py | 99 ++++++++ 5 files changed, 271 insertions(+), 355 deletions(-) create mode 100644 news/private_f.rst create mode 100644 src/diffpy/utils/transforms.py create mode 100644 tests/test_transforms.py diff --git a/news/private_f.rst b/news/private_f.rst new file mode 100644 index 00000000..b67df7b7 --- /dev/null +++ b/news/private_f.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside + DiffractionObject + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 2017b926..4351c537 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -5,6 +5,7 @@ import numpy as np from diffpy.utils.tools import get_package_info +from diffpy.utils.transforms import q_to_tth, tth_to_q QQUANTITIES = ["q"] ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] @@ -17,23 +18,6 @@ "and specifying how to handle the mismatch." ) -wavelength_warning_emsg = ( - "INFO: no wavelength has been specified. You can continue " - "to use the DiffractionObject but some of its powerful features " - "will not be available. To specify a wavelength, set " - "diffraction_object.wavelength = [number], " - "where diffraction_object is the variable name of you Diffraction Object, " - "and number is the wavelength in angstroms." -) - -length_mismatch_emsg = "Please ensure {array_name} array and intensity array are of the same length." -non_numeric_value_emsg = "Invalid value found in {array_name} array. Please ensure all values are numeric." -invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." -invalid_q_or_wavelength_emsg = ( - "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values." -) - class Diffraction_object: """A class to represent and manipulate data associated with diffraction experiments. @@ -54,9 +38,9 @@ def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength self.scat_quantity = "" - self.on_q = [np.empty(0), np.empty(0)] - self.on_tth = [np.empty(0), np.empty(0)] - self.on_d = [np.empty(0), np.empty(0)] + self.on_q = np.array([np.empty(0), np.empty(0)]) + self.on_tth = np.array([np.empty(0), np.empty(0)]) + self.on_d = np.array([np.empty(0), np.empty(0)]) self._all_arrays = [self.on_q, self.on_tth] self.metadata = {} @@ -311,97 +295,19 @@ def insert_scattering_quantity( if wavelength is not None: self.wavelength = wavelength if xtype.lower() in QQUANTITIES: - self.on_q = [np.array(xarray), np.array(yarray)] + self.on_q = np.array([xarray, yarray]) elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] + self.on_tth = np.array([xarray, yarray]) elif xtype.lower() in DQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] + self.on_tth = np.array([xarray, yarray]) self.set_all_arrays() - def q_to_tth(self): - r""" - Helper function to convert q to two-theta. - - By definition the relationship is: - - .. math:: - - \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} - - thus - - .. math:: - - 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) - - Parameters - ---------- - q : array - An array of :math:`q` values - - wavelength : float - Wavelength of the incoming x-rays - - Function adapted from scikit-beam. Thanks to those developers - - Returns - ------- - two_theta : array - An array of :math:`2\theta` values in radians - """ - q = self.on_q[0] - q = np.asarray(q) - wavelength = float(self.wavelength) - pre_factor = wavelength / (4 * np.pi) - return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) - - def tth_to_q(self): - r""" - Helper function to convert two-theta to q - - By definition the relationship is - - .. math:: - - \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} - - thus - - .. math:: - - q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} - - - - Parameters - ---------- - two_theta : array - An array of :math:`2\theta` values in units of degrees - - wavelength : float - Wavelength of the incoming x-rays - - Function adapted from scikit-beam. Thanks to those developers. - - Returns - ------- - q : array - An array of :math:`q` values in the inverse of the units - of ``wavelength`` - """ - two_theta = np.asarray(np.deg2rad(self.on_tth[0])) - wavelength = float(self.wavelength) - pre_factor = (4 * np.pi) / wavelength - return pre_factor * np.sin(two_theta / 2) - def set_all_arrays(self): master_array, xtype = self._get_original_array() if xtype == "q": - self.on_tth[0] = self.q_to_tth() - self.on_tth[1] = master_array[1] - if xtype == "tth": - self.on_q[0] = self.tth_to_q() - self.on_q[1] = master_array[1] + self.on_tth = q_to_tth(self.on_q, self.wavelength) + elif xtype == "tth": + self.on_q = tth_to_q(self.on_tth, self.wavelength) self.tthmin = self.on_tth[0][0] self.tthmax = self.on_tth[0][-1] self.qmin = self.on_q[0][0] @@ -500,9 +406,9 @@ def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength self.scat_quantity = "" - self.on_q = [np.empty(0), np.empty(0)] - self.on_tth = [np.empty(0), np.empty(0)] - self.on_d = [np.empty(0), np.empty(0)] + self.on_q = np.empty((2, 0), dtype=np.float64) + self.on_tth = np.empty((2, 0), dtype=np.float64) + self.on_d = np.empty((2, 0), dtype=np.float64) self._all_arrays = [self.on_q, self.on_tth] self.metadata = {} @@ -757,115 +663,19 @@ def insert_scattering_quantity( if wavelength is not None: self.wavelength = wavelength if xtype.lower() in QQUANTITIES: - self.on_q = [np.array(xarray), np.array(yarray)] + self.on_q = np.array([xarray, yarray]) elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] - elif xtype.lower() in DQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] + self.on_tth = np.array([xarray, yarray]) + elif xtype.lower() in DQUANTITIES: # Fixme when d is implemented. This here as a placeholder + self.on_tth = np.array([xarray, yarray]) self.set_all_arrays() - def q_to_tth(self): - r""" - Helper function to convert q to two-theta. - - By definition the relationship is: - - .. math:: - - \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} - - thus - - .. math:: - - 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) - - Function adapted from scikit-beam. Thanks to those developers - - Parameters - ---------- - q : array - The array of :math:`q` values - - wavelength : float - Wavelength of the incoming x-rays - - Returns - ------- - two_theta : array - The array of :math:`2\theta` values in radians - """ - for i, value in enumerate(self.on_q[0]): - if not isinstance(value, (int, float)): - raise TypeError(non_numeric_value_emsg.format(array_name="q")) - if len(self.on_q[0]) != len(self.on_q[1]): - raise RuntimeError(length_mismatch_emsg.format(array_name="q")) - if self.wavelength is None: - warnings.warn(wavelength_warning_emsg, UserWarning) - return np.empty(0) - q = self.on_q[0] - q = np.asarray(q) - wavelength = float(self.wavelength) - pre_factor = wavelength / (4 * np.pi) - if np.any(np.abs(q * pre_factor) > 1): - raise ValueError(invalid_q_or_wavelength_emsg) - return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) - - def tth_to_q(self): - r""" - Helper function to convert two-theta to q - - By definition the relationship is - - .. math:: - - \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} - - thus - - .. math:: - - q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} - - Function adapted from scikit-beam. Thanks to those developers. - - Parameters - ---------- - two_theta : array - The array of :math:`2\theta` values in units of degrees - - wavelength : float - Wavelength of the incoming x-rays - - Returns - ------- - q : array - The array of :math:`q` values in the inverse of the units - of ``wavelength`` - """ - for i, value in enumerate(self.on_tth[0]): - if not isinstance(value, (int, float)): - raise TypeError(non_numeric_value_emsg.format(array_name="two theta")) - if len(self.on_tth[0]) != len(self.on_tth[1]): - raise RuntimeError(length_mismatch_emsg.format(array_name="two theta")) - two_theta = np.asarray(np.deg2rad(self.on_tth[0])) - if np.any(two_theta > np.pi): - raise ValueError(invalid_tth_emsg) - if self.wavelength is None: - warnings.warn(wavelength_warning_emsg, UserWarning) - return np.empty(0) - wavelength = float(self.wavelength) - pre_factor = (4 * np.pi) / wavelength - return pre_factor * np.sin(two_theta / 2) - def set_all_arrays(self): master_array, xtype = self._get_original_array() if xtype == "q": - self.on_tth[0] = self.q_to_tth() - self.on_tth[1] = master_array[1] - if xtype == "tth": - self.on_q[0] = self.tth_to_q() - self.on_q[1] = master_array[1] + self.on_tth = q_to_tth(self.on_q, self.wavelength) + elif xtype == "tth": + self.on_q = tth_to_q(self.on_tth, self.wavelength) self.tthmin = self.on_tth[0][0] self.tthmax = self.on_tth[0][-1] self.qmin = self.on_q[0][0] diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py new file mode 100644 index 00000000..08be3786 --- /dev/null +++ b/src/diffpy/utils/transforms.py @@ -0,0 +1,122 @@ +import warnings +from copy import copy + +import numpy as np + +wavelength_warning_emsg = ( + "INFO: no wavelength has been specified. You can continue " + "to use the DiffractionObject but some of its powerful features " + "will not be available. To specify a wavelength, set " + "diffraction_object.wavelength = [number], " + "where diffraction_object is the variable name of you Diffraction Object, " + "and number is the wavelength in angstroms." +) +invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." +invalid_q_or_wavelength_emsg = ( + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values." +) + + +def _validate_inputs(on_q, wavelength): + if wavelength is None: + warnings.warn(wavelength_warning_emsg, UserWarning) + return np.empty(0) + pre_factor = wavelength / (4 * np.pi) + if np.any(np.abs(on_q[0] * pre_factor) > 1.0): + raise ValueError(invalid_q_or_wavelength_emsg) + + +def q_to_tth(on_q, wavelength): + r""" + Helper function to convert q to two-theta. + + If wavelength is missing, returns x-values that are integer indexes + + By definition the relationship is: + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) + + Parameters + ---------- + on_q : 2D array + The array of :math:`q` values and :math: 'i' intensity values, np.array([[qs], [is]]). + This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_q + The units of q must be reciprocal of the units of wavelength. + + wavelength : float + Wavelength of the incoming x-rays/neutrons/electrons + + Returns + ------- + on_tth : 2D array + The array of :math:`2\theta` values in degrees and :math: 'i' intensity values unchanged, + np.array([[tths], [is]]). + This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth + """ + _validate_inputs(on_q, wavelength) + on_q.astype(np.float64) + on_tth = copy(on_q) # initialize output array of same shape + if wavelength is not None: + on_tth[0] = np.rad2deg(2.0 * np.arcsin(on_q[0] * wavelength / (4 * np.pi))) + else: # return intensities vs. an x-array that is just the index + for i, _ in enumerate(on_q[0]): + on_tth[0][i] = i + return on_tth + + +def tth_to_q(on_tth, wavelength): + r""" + Helper function to convert two-theta to q on independent variable axis. + + If wavelength is missing, returns independent variable axis as integer indexes. + + By definition the relationship is: + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} + + Parameters + ---------- + on_tth : 2D array + The array of :math:`2\theta` values and :math: 'i' intensity values, np.array([[tths], [is]]). + This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_tth + The units of tth are expected in degrees. + + wavelength : float + Wavelength of the incoming x-rays/neutrons/electrons + + Returns + ------- + on_q : 2D array + The array of :math:`q` values and :math: 'i' intensity values unchanged, + np.array([[qs], [is]]). + The units for the q-values are the inverse of the units of the provided wavelength. + This is the correct format for loading into diffpy.utils.DiffractionOject.on_q + """ + on_tth.astype(np.float64) + if np.any(np.deg2rad(on_tth[0]) > np.pi): + raise ValueError(invalid_tth_emsg) + on_q = copy(on_tth) + if wavelength is not None: + pre_factor = (4.0 * np.pi) / wavelength + on_q[0] = pre_factor * np.sin(np.deg2rad(on_tth[0] / 2)) + else: # return intensities vs. an x-array that is just the index + for i, _ in enumerate(on_q[0]): + on_q[0][i] = i + return on_q diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 3bdc22d4..ada14ff1 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -4,7 +4,8 @@ import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject, wavelength_warning_emsg +from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject +from diffpy.utils.transforms import wavelength_warning_emsg params = [ ( # Default @@ -240,146 +241,6 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte return np.allclose(actual_array, expected_array) -params_q_to_tth = [ - # UC1: User specified empty q values (without wavelength) - ([None, [], []], [[]]), - # UC2: User specified empty q values (with wavelength) - ([4 * np.pi, [], []], [[]]), - # UC3: User specified valid q values (without wavelength) - ([None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], [[]]), - # UC4: User specified valid q values (with wavelength) - # expected tth values are 2*arcsin(q) in degrees - ( - [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], - [[0, 23.07392, 47.15636, 73.73980, 106.26020, 180]], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_q_to_tth) -def test_q_to_tth(inputs, expected): - actual = DiffractionObject(wavelength=inputs[0]) - actual.on_q = [inputs[1], inputs[2]] - expected_tth = expected[0] - assert _test_valid_diffraction_objects(actual, "q_to_tth", expected_tth) - - -params_q_to_tth_bad = [ - # UC1: user specified invalid q values that result in tth > 180 degrees - ( - [4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2], [1, 2, 3, 4, 5, 6]], - [ - ValueError, - "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], - ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees - ( - [100, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], - [ - ValueError, - "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], - ), - # UC3: user specified a q array that does not match the length of intensity array (without wavelength) - ( - [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [RuntimeError, "Please ensure q array and intensity array are of the same length."], - ), - # UC4: user specified a q array that does not match the length of intensity array (with wavelength) - ( - [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [RuntimeError, "Please ensure q array and intensity array are of the same length."], - ), - # UC5: user specified a non-numeric value in q array (without wavelength) - ( - [None, [0, 0.2, 0.4, 0.6, 0.8, "invalid"], [1, 2, 3, 4, 5, 6]], - [TypeError, "Invalid value found in q array. Please ensure all values are numeric."], - ), - # UC5: user specified a non-numeric value in q array (with wavelength) - ( - [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"], [1, 2, 3, 4, 5, 6]], - [TypeError, "Invalid value found in q array. Please ensure all values are numeric."], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) -def test_q_to_tth_bad(inputs, expected): - actual = DiffractionObject(wavelength=inputs[0]) - actual.on_q = [inputs[1], inputs[2]] - with pytest.raises(expected[0], match=expected[1]): - actual.q_to_tth() - - -params_tth_to_q = [ - # UC1: User specified empty tth values (without wavelength) - ([None, [], []], [[]]), - # UC2: User specified empty tth values (with wavelength) - ([4 * np.pi, [], []], [[]]), - # UC3: User specified valid tth values between 0-180 degrees (without wavelength) - ( - [None, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], - [[]], - ), - # UC4: User specified valid tth values between 0-180 degrees (with wavelength) - # expected q vales are sin15, sin30, sin45, sin60, sin90 - ([4 * np.pi, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], [[0, 0.258819, 0.5, 0.707107, 0.866025, 1]]), -] - - -@pytest.mark.parametrize("inputs, expected", params_tth_to_q) -def test_tth_to_q(inputs, expected): - actual = DiffractionObject(wavelength=inputs[0]) - actual.on_tth = [inputs[1], inputs[2]] - expected_q = expected[0] - assert _test_valid_diffraction_objects(actual, "tth_to_q", expected_q) - - -params_tth_to_q_bad = [ - # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) - ( - [None, [0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], - ), - # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) - ( - [4 * np.pi, [0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], - ), - # UC3: user specified a two theta array that does not match the length of intensity array (without wavelength) - ( - [None, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [RuntimeError, "Please ensure two theta array and intensity array are of the same length."], - ), - # UC4: user specified a two theta array that does not match the length of intensity array (with wavelength) - ( - [4 * np.pi, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [RuntimeError, "Please ensure two theta array and intensity array are of the same length."], - ), - # UC5: user specified a non-numeric value in two theta array (without wavelength) - ( - [None, [0, 30, 60, 90, 120, "invalid"], [1, 2, 3, 4, 5, 6]], - [TypeError, "Invalid value found in two theta array. Please ensure all values are numeric."], - ), - # UC6: user specified a non-numeric value in two theta array (with wavelength) - ( - [4 * np.pi, [0, 30, 60, 90, 120, "invalid"], [1, 2, 3, 4, 5, 6]], - [TypeError, "Invalid value found in two theta array. Please ensure all values are numeric."], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) -def test_tth_to_q_bad(inputs, expected): - actual = DiffractionObject(wavelength=inputs[0]) - actual.on_tth = [inputs[1], inputs[2]] - with pytest.raises(expected[0], match=expected[1]): - actual.tth_to_q() - - def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) @@ -389,14 +250,14 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" test.insert_scattering_quantity( - x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} + np.array(x), + np.array(y), + "q", + metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, ) - mocker.patch("importlib.metadata.version", return_value="3.3.0") - with freeze_time("2012-01-14"): test.dump(file, "q") - with open(file, "r") as f: actual = f.read() expected = ( diff --git a/tests/test_transforms.py b/tests/test_transforms.py new file mode 100644 index 00000000..d5c5ccfa --- /dev/null +++ b/tests/test_transforms.py @@ -0,0 +1,99 @@ +import numpy as np +import pytest + +from diffpy.utils.transforms import q_to_tth, tth_to_q + +params_q_to_tth = [ + # UC1: Empty q values, no wavelength, return empty arrays + ([None, np.empty((2, 0))], np.empty((2, 0))), + # UC2: Empty q values, wavelength specified, return empty arrays + ([4 * np.pi, np.empty((2, 0))], np.empty((2, 0))), + # UC3: User specified valid q values, no wavelength, return empty arrays + ( + [None, np.array([[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]])], + np.array([[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]), + ), + # UC4: User specified valid q values (with wavelength) + # expected tth values are 2*arcsin(q) in degrees + ([4 * np.pi, np.array([[0, 1 / np.sqrt(2), 1.0], [1, 2, 3]])], np.array([[0, 90.0, 180.0], [1, 2, 3]])), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_tth) +def test_q_to_tth(inputs, expected): + actual = q_to_tth(inputs[1], inputs[0]) + assert np.allclose(expected[0], actual[0]) + assert np.allclose(expected[1], actual[1]) + + +params_q_to_tth_bad = [ + # UC1: user specified invalid q values that result in tth > 180 degrees + ( + [4 * np.pi, np.array([[0.2, 0.4, 0.6, 0.8, 1, 1.2], [1, 2, 3, 4, 5, 6]])], + [ + ValueError, + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", + ], + ), + # UC2: user specified a wrong wavelength that result in tth > 180 degrees + ( + [100, np.array([[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]])], + [ + ValueError, + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", + ], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) +def test_q_to_tth_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + q_to_tth(inputs[1], inputs[0]) + + +params_tth_to_q = [ + # UC0: User specified empty tth values (without wavelength) + ([None, np.array([[], []])], np.array([[], []])), + # UC1: User specified empty tth values (with wavelength) + ([4 * np.pi, np.array([[], []])], np.array([[], []])), + # UC2: User specified valid tth values between 0-180 degrees (without wavelength) + ( + [None, np.array([[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]])], + np.array([[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]), + ), + # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + # expected q vales are sin15, sin30, sin45, sin60, sin90 + ( + [4 * np.pi, np.array([[0, 30.0, 60.0, 90.0, 120.0, 180.0], [1, 2, 3, 4, 5, 6]])], + np.array([[0, 0.258819, 0.5, 0.707107, 0.866025, 1], [1, 2, 3, 4, 5, 6]]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_q) +def test_tth_to_q(inputs, expected): + actual = tth_to_q(inputs[1], inputs[0]) + assert np.allclose(actual, expected) + + +params_tth_to_q_bad = [ + # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + [None, np.array([[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]])], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), + # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + [4 * np.pi, np.array([[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]])], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) +def test_tth_to_q_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + tth_to_q(inputs[1], inputs[0]) From e55e44d44929620747eeef1941b6702482c4ad07 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 2 Dec 2024 14:25:18 -0500 Subject: [PATCH 298/696] public functions now operate on 1D x-arrays only for ease of user use --- src/diffpy/utils/transforms.py | 49 +++++++++++++++++----------------- tests/test_transforms.py | 33 +++++++++++------------ 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 08be3786..773aa607 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -18,16 +18,16 @@ ) -def _validate_inputs(on_q, wavelength): +def _validate_inputs(q, wavelength): if wavelength is None: warnings.warn(wavelength_warning_emsg, UserWarning) return np.empty(0) pre_factor = wavelength / (4 * np.pi) - if np.any(np.abs(on_q[0] * pre_factor) > 1.0): + if np.any(np.abs(q * pre_factor) > 1.0): raise ValueError(invalid_q_or_wavelength_emsg) -def q_to_tth(on_q, wavelength): +def q_to_tth(q, wavelength): r""" Helper function to convert q to two-theta. @@ -47,9 +47,8 @@ def q_to_tth(on_q, wavelength): Parameters ---------- - on_q : 2D array - The array of :math:`q` values and :math: 'i' intensity values, np.array([[qs], [is]]). - This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_q + q : 1D array + The array of :math:`q` values numpy.array([qs]). The units of q must be reciprocal of the units of wavelength. wavelength : float @@ -57,24 +56,24 @@ def q_to_tth(on_q, wavelength): Returns ------- - on_tth : 2D array - The array of :math:`2\theta` values in degrees and :math: 'i' intensity values unchanged, - np.array([[tths], [is]]). + tth : 1D array + The array of :math:`2\theta` values in degrees numpy.array([tths]). This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth """ - _validate_inputs(on_q, wavelength) - on_q.astype(np.float64) - on_tth = copy(on_q) # initialize output array of same shape + _validate_inputs(q, wavelength) + q.astype(np.float64) + tth = copy(q) # initialize output array of same shape if wavelength is not None: - on_tth[0] = np.rad2deg(2.0 * np.arcsin(on_q[0] * wavelength / (4 * np.pi))) + tth = np.rad2deg(2.0 * np.arcsin(q * wavelength / (4 * np.pi))) else: # return intensities vs. an x-array that is just the index - for i, _ in enumerate(on_q[0]): - on_tth[0][i] = i - return on_tth + for i, _ in enumerate(q): + tth[i] = i + return tth -def tth_to_q(on_tth, wavelength): +def tth_to_q(tth, wavelength): r""" + Helper function to convert two-theta to q on independent variable axis. If wavelength is missing, returns independent variable axis as integer indexes. @@ -93,7 +92,7 @@ def tth_to_q(on_tth, wavelength): Parameters ---------- - on_tth : 2D array + tth : 2D array The array of :math:`2\theta` values and :math: 'i' intensity values, np.array([[tths], [is]]). This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_tth The units of tth are expected in degrees. @@ -109,14 +108,14 @@ def tth_to_q(on_tth, wavelength): The units for the q-values are the inverse of the units of the provided wavelength. This is the correct format for loading into diffpy.utils.DiffractionOject.on_q """ - on_tth.astype(np.float64) - if np.any(np.deg2rad(on_tth[0]) > np.pi): + tth.astype(np.float64) + if np.any(np.deg2rad(tth) > np.pi): raise ValueError(invalid_tth_emsg) - on_q = copy(on_tth) + q = copy(tth) if wavelength is not None: pre_factor = (4.0 * np.pi) / wavelength - on_q[0] = pre_factor * np.sin(np.deg2rad(on_tth[0] / 2)) + q = pre_factor * np.sin(np.deg2rad(tth / 2)) else: # return intensities vs. an x-array that is just the index - for i, _ in enumerate(on_q[0]): - on_q[0][i] = i - return on_q + for i, _ in enumerate(q): + q[i] = i + return q diff --git a/tests/test_transforms.py b/tests/test_transforms.py index d5c5ccfa..7e3eae3f 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,31 +5,30 @@ params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays - ([None, np.empty((2, 0))], np.empty((2, 0))), + ([None, np.empty((1,))], np.empty((1,))), # UC2: Empty q values, wavelength specified, return empty arrays - ([4 * np.pi, np.empty((2, 0))], np.empty((2, 0))), + ([4 * np.pi, np.empty((1,))], np.empty((1,))), # UC3: User specified valid q values, no wavelength, return empty arrays ( - [None, np.array([[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]])], - np.array([[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]), + [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + np.array([0, 1, 2, 3, 4, 5]), ), # UC4: User specified valid q values (with wavelength) # expected tth values are 2*arcsin(q) in degrees - ([4 * np.pi, np.array([[0, 1 / np.sqrt(2), 1.0], [1, 2, 3]])], np.array([[0, 90.0, 180.0], [1, 2, 3]])), + ([4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0])], np.array([0, 90.0, 180.0])), ] @pytest.mark.parametrize("inputs, expected", params_q_to_tth) def test_q_to_tth(inputs, expected): actual = q_to_tth(inputs[1], inputs[0]) - assert np.allclose(expected[0], actual[0]) - assert np.allclose(expected[1], actual[1]) + assert np.allclose(expected, actual) params_q_to_tth_bad = [ # UC1: user specified invalid q values that result in tth > 180 degrees ( - [4 * np.pi, np.array([[0.2, 0.4, 0.6, 0.8, 1, 1.2], [1, 2, 3, 4, 5, 6]])], + [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], [ ValueError, "The supplied q-array and wavelength will result in an impossible two-theta. " @@ -38,7 +37,7 @@ def test_q_to_tth(inputs, expected): ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [100, np.array([[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]])], + [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], [ ValueError, "The supplied q-array and wavelength will result in an impossible two-theta. " @@ -56,19 +55,19 @@ def test_q_to_tth_bad(inputs, expected): params_tth_to_q = [ # UC0: User specified empty tth values (without wavelength) - ([None, np.array([[], []])], np.array([[], []])), + ([None, np.array([])], np.array([])), # UC1: User specified empty tth values (with wavelength) - ([4 * np.pi, np.array([[], []])], np.array([[], []])), + ([4 * np.pi, np.array([])], np.array([])), # UC2: User specified valid tth values between 0-180 degrees (without wavelength) ( - [None, np.array([[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]])], - np.array([[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]), + [None, np.array([0, 30, 60, 90, 120, 180])], + np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) # expected q vales are sin15, sin30, sin45, sin60, sin90 ( - [4 * np.pi, np.array([[0, 30.0, 60.0, 90.0, 120.0, 180.0], [1, 2, 3, 4, 5, 6]])], - np.array([[0, 0.258819, 0.5, 0.707107, 0.866025, 1], [1, 2, 3, 4, 5, 6]]), + [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], + np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), ), ] @@ -82,12 +81,12 @@ def test_tth_to_q(inputs, expected): params_tth_to_q_bad = [ # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) ( - [None, np.array([[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]])], + [None, np.array([0, 30, 60, 90, 120, 181])], [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], ), # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) ( - [4 * np.pi, np.array([[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]])], + [4 * np.pi, np.array([0, 30, 60, 90, 120, 181])], [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], ), ] From d624faab8d7f16d5d87fdab83a094bdc445ce9f7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 2 Dec 2024 15:25:20 -0500 Subject: [PATCH 299/696] Remove layer for scattering objects --- .../diffraction_objects.py | 0 .../utils/scattering_objects/__init__.py | 19 ------------------- .../{scattering_objects => }/user_config.py | 0 .../test_diffraction_objects.py | 0 4 files changed, 19 deletions(-) rename src/diffpy/utils/{scattering_objects => }/diffraction_objects.py (100%) delete mode 100644 src/diffpy/utils/scattering_objects/__init__.py rename src/diffpy/utils/{scattering_objects => }/user_config.py (100%) rename tests/diffpy/utils/{scattering_objects => }/test_diffraction_objects.py (100%) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py similarity index 100% rename from src/diffpy/utils/scattering_objects/diffraction_objects.py rename to src/diffpy/utils/diffraction_objects.py diff --git a/src/diffpy/utils/scattering_objects/__init__.py b/src/diffpy/utils/scattering_objects/__init__.py deleted file mode 100644 index 08069c20..00000000 --- a/src/diffpy/utils/scattering_objects/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.utils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Simon Billinge -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## - -"""Scattering objects -""" - -# End of file diff --git a/src/diffpy/utils/scattering_objects/user_config.py b/src/diffpy/utils/user_config.py similarity index 100% rename from src/diffpy/utils/scattering_objects/user_config.py rename to src/diffpy/utils/user_config.py diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/test_diffraction_objects.py similarity index 100% rename from tests/diffpy/utils/scattering_objects/test_diffraction_objects.py rename to tests/diffpy/utils/test_diffraction_objects.py From 4132f1407c22e6ad1105a284e8ab06a46fcd07ff Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 2 Dec 2024 15:27:22 -0500 Subject: [PATCH 300/696] Add news for removing scattering objects layer --- news/scattering_obt.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scattering_obt.rst diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst new file mode 100644 index 00000000..0090b88e --- /dev/null +++ b/news/scattering_obt.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* scattering_objects layer in importing diffraction_objects + +**Fixed:** + +* + +**Security:** + +* From 1442c7c7ae1df2c71a6b6b0e91d7c97bd5a9cf2d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 2 Dec 2024 15:32:51 -0500 Subject: [PATCH 301/696] Update docs without scttering objects layer --- doc/source/api/diffpy.utils.rst | 17 +++++++++++ .../api/diffpy.utils.scattering_objects.rst | 28 ------------------- .../examples/diffractionobjectsexample.rst | 4 +-- .../utilities/diffractionobjectsutility.rst | 2 +- .../diffpy/utils/test_diffraction_objects.py | 2 +- 5 files changed, 21 insertions(+), 32 deletions(-) delete mode 100644 doc/source/api/diffpy.utils.scattering_objects.rst diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index b873c333..868f546b 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -36,3 +36,20 @@ diffpy.utils.resampler module :members: :undoc-members: :show-inheritance: + + +diffpy.utils.user_config module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.user_config + :members: + :undoc-members: + :show-inheritance: + +diffpy.utils.diffraction_objects module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.diffraction_objects + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/doc/source/api/diffpy.utils.scattering_objects.rst b/doc/source/api/diffpy.utils.scattering_objects.rst deleted file mode 100644 index 4607ad98..00000000 --- a/doc/source/api/diffpy.utils.scattering_objects.rst +++ /dev/null @@ -1,28 +0,0 @@ -:tocdepth: -1 - -diffpy.utils.scattering_objects package -======================================= - -.. automodule:: diffpy.utils.scattering_objects - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -diffpy.utils.scattering_objects.diffraction_objects module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.scattering_objects.diffraction_objects - :members: - :undoc-members: - :show-inheritance: - -diffpy.utils.scattering_objects.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.scattering_objects.user_config - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/examples/diffractionobjectsexample.rst b/doc/source/examples/diffractionobjectsexample.rst index f3114ab0..48409432 100644 --- a/doc/source/examples/diffractionobjectsexample.rst +++ b/doc/source/examples/diffractionobjectsexample.rst @@ -6,7 +6,7 @@ Diffraction Objects Example ########################### This example will demonstrate how to use the ``DiffractionObject`` class in the -``diffpy.utils.scattering_objects.diffraction_objects`` module to process and analyze diffraction data. +``diffpy.utils.diffraction_objects`` module to process and analyze diffraction data. 1) Assuming we have created a ``DiffractionObject`` called my_diffraction_pattern from a measured diffraction pattern, and we have specified the wavelength (see Section ??, to be added), @@ -20,7 +20,7 @@ This example will demonstrate how to use the ``DiffractionObject`` class in the To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. :: # Example: convert tth to q - from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject + from diffpy.utils.diffraction_objects import DiffractionObject my_diffraction_pattern.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] my_diffraction_pattern.tth_to_q() diff --git a/doc/source/utilities/diffractionobjectsutility.rst b/doc/source/utilities/diffractionobjectsutility.rst index d388e856..94cb1308 100644 --- a/doc/source/utilities/diffractionobjectsutility.rst +++ b/doc/source/utilities/diffractionobjectsutility.rst @@ -3,7 +3,7 @@ Diffraction Objects Utility =========================== -The ``diffpy.utils.scattering_objects.diffraction_objects`` module provides functions +The ``diffpy.utils.diffraction_objects`` module provides functions for managing and analyzing diffraction data, including angle-space conversions and interactions between diffraction data. diff --git a/tests/diffpy/utils/test_diffraction_objects.py b/tests/diffpy/utils/test_diffraction_objects.py index ada14ff1..1bc5220e 100644 --- a/tests/diffpy/utils/test_diffraction_objects.py +++ b/tests/diffpy/utils/test_diffraction_objects.py @@ -4,7 +4,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject +from diffpy.utils.diffraction_objects import DiffractionObject from diffpy.utils.transforms import wavelength_warning_emsg params = [ From 16937e74f0b2b90ae33a54520fc9fb5ecf4f82d2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:33:44 +0000 Subject: [PATCH 302/696] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/api/diffpy.utils.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 868f546b..2513e821 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -52,4 +52,4 @@ diffpy.utils.diffraction_objects module .. automodule:: diffpy.utils.diffraction_objects :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: From 5a32de45f573b423066222af49555eda26e8efb5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 2 Dec 2024 18:13:17 -0500 Subject: [PATCH 303/696] initial commit, raise value error if xtype is invalid --- .../scattering_objects/diffraction_objects.py | 3 +- .../test_diffraction_objects.py | 51 +++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 4351c537..a4909af0 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -742,7 +742,8 @@ def on_xtype(self, xtype): return self.on_q elif xtype.lower() in DQUANTITIES: return self.on_d - pass + else: + raise ValueError(f"Unknown xtype: {xtype}. Allowed xtypes are {*XQUANTITIES, }.") def dump(self, filepath, xtype=None): if xtype is None: diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index ada14ff1..570404fe 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -1,11 +1,17 @@ +import re from pathlib import Path import numpy as np import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject -from diffpy.utils.transforms import wavelength_warning_emsg +from diffpy.utils.scattering_objects.diffraction_objects import ( + ANGLEQUANTITIES, + DQUANTITIES, + QQUANTITIES, + XQUANTITIES, + DiffractionObject, +) params = [ ( # Default @@ -232,13 +238,40 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (diffraction_object1 == diffraction_object2) == expected -def _test_valid_diffraction_objects(actual_diffraction_object, function, expected_array): - if actual_diffraction_object.wavelength is None: - with pytest.warns(UserWarning) as warn_record: - getattr(actual_diffraction_object, function)() - assert str(warn_record[0].message) == wavelength_warning_emsg - actual_array = getattr(actual_diffraction_object, function)() - return np.allclose(actual_array, expected_array) +params_on_xtype = [ + ( + [ + np.array([1, 2, 3, 4, 5, 6]), # intensity array + np.array([0, 30, 60, 90, 120, 180]), # tth array + np.array([1, 2, 3, 4, 5, 6]), # q array + np.array([10, 20, 30, 40, 50, 60]), # d array + ], + [ + np.array([[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]), # expected on_tth + np.array([[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]]), # expected on_q + np.array([[10, 20, 30, 40, 50, 60], [1, 2, 3, 4, 5, 6]]), # expected on_d + ], + ) +] + + +@pytest.mark.parametrize("inputs, expected", params_on_xtype) +def test_on_xtype(inputs, expected): + test = DiffractionObject() + test.on_tth = np.array([inputs[1], inputs[0]]) + test.on_q = np.array([inputs[2], inputs[0]]) + test.on_d = np.array([inputs[3], inputs[0]]) + for xtype_list, expected_value in zip([ANGLEQUANTITIES, QQUANTITIES, DQUANTITIES], expected): + for xtype in xtype_list: + assert np.allclose(test.on_xtype(xtype), expected_value) + + +def test_on_xtype_bad(): + test = DiffractionObject() + with pytest.raises( + ValueError, match=re.escape(f"Unknown xtype: invalid. Allowed xtypes are {*XQUANTITIES, }.") + ): + test.on_xtype("invalid") def test_dump(tmp_path, mocker): From b0cb3d8a3cb256dc97bddcaeacb0c2e0a164e447 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 2 Dec 2024 21:36:07 -0500 Subject: [PATCH 304/696] move tests to top level directory --- tests/{diffpy/utils => }/test_diffraction_objects.py | 0 tests/{diffpy/utils/parsers => }/test_loaddata.py | 0 tests/{diffpy/utils => }/test_resample.py | 0 tests/{diffpy/utils/parsers => }/test_serialization.py | 0 tests/{diffpy/utils => }/test_tools.py | 0 tests/{diffpy/utils => }/test_version.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/{diffpy/utils => }/test_diffraction_objects.py (100%) rename tests/{diffpy/utils/parsers => }/test_loaddata.py (100%) rename tests/{diffpy/utils => }/test_resample.py (100%) rename tests/{diffpy/utils/parsers => }/test_serialization.py (100%) rename tests/{diffpy/utils => }/test_tools.py (100%) rename tests/{diffpy/utils => }/test_version.py (100%) diff --git a/tests/diffpy/utils/test_diffraction_objects.py b/tests/test_diffraction_objects.py similarity index 100% rename from tests/diffpy/utils/test_diffraction_objects.py rename to tests/test_diffraction_objects.py diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/test_loaddata.py similarity index 100% rename from tests/diffpy/utils/parsers/test_loaddata.py rename to tests/test_loaddata.py diff --git a/tests/diffpy/utils/test_resample.py b/tests/test_resample.py similarity index 100% rename from tests/diffpy/utils/test_resample.py rename to tests/test_resample.py diff --git a/tests/diffpy/utils/parsers/test_serialization.py b/tests/test_serialization.py similarity index 100% rename from tests/diffpy/utils/parsers/test_serialization.py rename to tests/test_serialization.py diff --git a/tests/diffpy/utils/test_tools.py b/tests/test_tools.py similarity index 100% rename from tests/diffpy/utils/test_tools.py rename to tests/test_tools.py diff --git a/tests/diffpy/utils/test_version.py b/tests/test_version.py similarity index 100% rename from tests/diffpy/utils/test_version.py rename to tests/test_version.py From e4b78fdd58e1dd03c0aa4af037cac9614216e8a1 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 3 Dec 2024 13:51:34 -0500 Subject: [PATCH 305/696] initial commit --- .../scattering_objects/diffraction_objects.py | 20 ++++++++++++++----- .../test_diffraction_objects.py | 18 +++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 4351c537..c881a5b5 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -614,14 +614,24 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): return array def get_angle_index(self, angle): - count = 0 + """ + returns the index of a given angle in the angles list + + Parameters + ---------- + angle float + the angle to search for + + Returns + ------- + the index of the angle in the angles list + """ + if not hasattr(self, "angles"): + self.angles = np.array([]) for i, target in enumerate(self.angles): if angle == target: return i - else: - count += 1 - if count >= len(self.angles): - raise IndexError(f"WARNING: no angle {angle} found in angles list") + raise IndexError(f"WARNING: no angle {angle} found in angles list.") def insert_scattering_quantity( self, diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index ada14ff1..9fe4ef26 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -241,6 +241,24 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte return np.allclose(actual_array, expected_array) +def test_get_angle_index(): + test = DiffractionObject() + test.angles = np.array([10, 20, 30, 40, 50, 60]) + actual_angle_index = test.get_angle_index(angle=10) + assert actual_angle_index == 0 + + +def test_get_angle_index_bad(): + test = DiffractionObject() + # empty angles list + with pytest.raises(IndexError, match="WARNING: no angle 11 found in angles list."): + test.get_angle_index(angle=11) + # pre-defined angles list + test.angles = np.array([10, 20, 30, 40, 50, 60]) + with pytest.raises(IndexError, match="WARNING: no angle 11 found in angles list."): + test.get_angle_index(angle=11) + + def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) From bf5207878b186e52e11c67259a9ff2fe3795860f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 3 Dec 2024 14:28:27 -0500 Subject: [PATCH 306/696] change test intensity array to avoid duplication with x-arrays --- tests/test_diffraction_objects.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 3271023b..a7e4ea04 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -241,15 +241,15 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): params_on_xtype = [ ( [ - np.array([1, 2, 3, 4, 5, 6]), # intensity array + np.array([100, 200, 300, 400, 500, 600]), # intensity array np.array([0, 30, 60, 90, 120, 180]), # tth array np.array([1, 2, 3, 4, 5, 6]), # q array np.array([10, 20, 30, 40, 50, 60]), # d array ], [ - np.array([[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]), # expected on_tth - np.array([[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]]), # expected on_q - np.array([[10, 20, 30, 40, 50, 60], [1, 2, 3, 4, 5, 6]]), # expected on_d + np.array([[0, 30, 60, 90, 120, 180], [100, 200, 300, 400, 500, 600]]), # expected on_tth + np.array([[1, 2, 3, 4, 5, 6], [100, 200, 300, 400, 500, 600]]), # expected on_q + np.array([[10, 20, 30, 40, 50, 60], [100, 200, 300, 400, 500, 600]]), # expected on_d ], ) ] From 90be271f0061a3ee00813d2c61dc224bc47aee42 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 3 Dec 2024 23:14:10 -0500 Subject: [PATCH 307/696] DiffractionObject can now be instantiated directlyand basic data structure is a 2D array with everything in it --- news/constructor.rst | 25 ++ src/diffpy/utils/diffraction_objects.py | 475 +++--------------------- src/diffpy/utils/transforms.py | 22 +- tests/test_diffraction_objects.py | 466 +++++++++++++---------- tests/test_transforms.py | 4 +- 5 files changed, 361 insertions(+), 631 deletions(-) create mode 100644 news/constructor.rst diff --git a/news/constructor.rst b/news/constructor.rst new file mode 100644 index 00000000..9738c7a8 --- /dev/null +++ b/news/constructor.rst @@ -0,0 +1,25 @@ +**Added:** + +* + +**Changed:** + +* arrays and attributes now can be inserted when a DiffractionObject is instantiated +* data are now stored as a (len(x),4) numpy array with intensity in column 0, the q, then tth, then d +* `DiffractionObject.on_q`, on_tth and on_d are now methods and called as DiffractionObject.on_q() etc.` + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 4351c537..e628beed 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -1,11 +1,10 @@ import datetime -import warnings from copy import deepcopy import numpy as np from diffpy.utils.tools import get_package_info -from diffpy.utils.transforms import q_to_tth, tth_to_q +from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q QQUANTITIES = ["q"] ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] @@ -19,398 +18,17 @@ ) -class Diffraction_object: - """A class to represent and manipulate data associated with diffraction experiments. - - .. deprecated:: 3.5.1 - `Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0. It is replaced by - `DiffractionObject` to follow the class naming convention. - """ - - warnings.warn( - "Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0, It is replaced by " - "DiffractionObject` to follow the class naming convention.", - DeprecationWarning, - stacklevel=2, - ) - - def __init__(self, name="", wavelength=None): - self.name = name - self.wavelength = wavelength - self.scat_quantity = "" - self.on_q = np.array([np.empty(0), np.empty(0)]) - self.on_tth = np.array([np.empty(0), np.empty(0)]) - self.on_d = np.array([np.empty(0), np.empty(0)]) - self._all_arrays = [self.on_q, self.on_tth] - self.metadata = {} - - def __eq__(self, other): - if not isinstance(other, Diffraction_object): - return NotImplemented - self_attributes = [key for key in self.__dict__ if not key.startswith("_")] - other_attributes = [key for key in other.__dict__ if not key.startswith("_")] - if not sorted(self_attributes) == sorted(other_attributes): - return False - for key in self_attributes: - value = getattr(self, key) - other_value = getattr(other, key) - if isinstance(value, float): - if ( - not (value is None and other_value is None) - and (value is None) - or (other_value is None) - or not np.isclose(value, other_value, rtol=1e-5) - ): - return False - elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): - if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): - return False - else: - if value != other_value: - return False - return True - - def __add__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to sum two Diffraction_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed - - def __radd__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to sum two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed - - def __sub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = self.on_tth[1] - other - subtracted.on_q[1] = self.on_q[1] - other - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to subtract two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] - subtracted.on_q[1] = self.on_q[1] - other.on_q[1] - return subtracted - - def __rsub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = other - self.on_tth[1] - subtracted.on_q[1] = other - self.on_q[1] - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to subtract two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] - subtracted.on_q[1] = other.on_q[1] - self.on_q[1] - return subtracted - - def __mul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to multiply two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied - - def __rmul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied - - def __truediv__(self, other): - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to multiply two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] - divided.on_q[1] = self.on_q[1] / other.on_q[1] - return divided - - def __rtruediv__(self, other): - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] - divided.on_q[1] = other.on_q[1] / self.on_q[1] - return divided - - def set_angles_from_list(self, angles_list): - self.angles = angles_list - self.n_steps = len(angles_list) - 1.0 - self.begin_angle = self.angles[0] - self.end_angle = self.angles[-1] - - def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): - """ - create an array of linear spaced Q-values - - Parameters - ---------- - begin_q float - the beginning angle - end_q float - the ending angle - step_size float - the size of the step between points. Only specify step_size or n_steps, not both - n_steps integer - the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both - - Returns - ------- - Sets self.qs - self.qs array of floats - the q values in the independent array - - """ - self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) - - def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): - """ - create an array of linear spaced angle-values - - Parameters - ---------- - begin_angle float - the beginning angle - end_angle float - the ending angle - step_size float - the size of the step between points. Only specify step_size or n_steps, not both - n_steps integer - the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both - - Returns - ------- - Sets self.angles - self.angles array of floats - the q values in the independent array - - """ - self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) - - def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): - if step_size is not None and n_steps is not None: - print( - "WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be " - "reset." - ) - array = np.linspace(begin, end, n_steps) - elif step_size is not None: - array = np.arange(begin, end, step_size) - elif n_steps is not None: - array = np.linspace(begin, end, n_steps) - return array - - def get_angle_index(self, angle): - count = 0 - for i, target in enumerate(self.angles): - if angle == target: - return i - else: - count += 1 - if count >= len(self.angles): - raise IndexError(f"WARNING: no angle {angle} found in angles list") - - def insert_scattering_quantity( - self, - xarray, - yarray, - xtype, - metadata={}, - scat_quantity=None, - name=None, - wavelength=None, - ): - f""" - insert a new scattering quantity into the scattering object - - Parameters - ---------- - xarray array-like of floats - the independent variable array - yarray array-like of floats - the dependent variable array - xtype string - the type of quantity for the independent variable from {*XQUANTITIES, } - metadata: dict - the metadata in the form of a dictionary of user-supplied key:value pairs - - Returns - ------- - - """ - self.input_xtype = xtype - # empty attributes have been defined in the __init__ method so only - # set the attributes that are not empty to avoid emptying them by mistake - if metadata: - self.metadata = metadata - if scat_quantity is not None: - self.scat_quantity = scat_quantity - if name is not None: - self.name = name - if wavelength is not None: - self.wavelength = wavelength - if xtype.lower() in QQUANTITIES: - self.on_q = np.array([xarray, yarray]) - elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = np.array([xarray, yarray]) - elif xtype.lower() in DQUANTITIES: - self.on_tth = np.array([xarray, yarray]) - self.set_all_arrays() - - def set_all_arrays(self): - master_array, xtype = self._get_original_array() - if xtype == "q": - self.on_tth = q_to_tth(self.on_q, self.wavelength) - elif xtype == "tth": - self.on_q = tth_to_q(self.on_tth, self.wavelength) - self.tthmin = self.on_tth[0][0] - self.tthmax = self.on_tth[0][-1] - self.qmin = self.on_q[0][0] - self.qmax = self.on_q[0][-1] - - def _get_original_array(self): - if self.input_xtype in QQUANTITIES: - return self.on_q, "q" - elif self.input_xtype in ANGLEQUANTITIES: - return self.on_tth, "tth" - elif self.input_xtype in DQUANTITIES: - return self.on_d, "d" - - def scale_to(self, target_diff_object, xtype=None, xvalue=None): - f""" - returns a new diffraction object which is the current object but recaled in y to the target - - Parameters - ---------- - target_diff_object: Diffraction_object - the diffractoin object you want to scale the current one on to - xtype: string, optional. Default is Q - the xtype, from {XQUANTITIES}, that you will specify a point from to scale to - xvalue: float. Default is the midpoint of the array - the y-value in the target at this x-value will be used as the factor to scale to. - The entire array is scaled be the factor that places on on top of the other at that point. - xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling. - - Returns - ------- - the rescaled Diffraction_object as a new object - - """ - scaled = deepcopy(self) - if xtype is None: - xtype = "q" - - data = self.on_xtype(xtype) - target = target_diff_object.on_xtype(xtype) - if xvalue is None: - xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 - - xindex = (np.abs(data[0] - xvalue)).argmin() - ytarget = target[1][xindex] - yself = data[1][xindex] - scaled.on_tth[1] = data[1] * ytarget / yself - scaled.on_q[1] = data[1] * ytarget / yself - return scaled - - def on_xtype(self, xtype): - """ - return a 2D np array with x in the first column and y in the second for x of type type - Parameters - ---------- - xtype - - Returns - ------- - - """ - if xtype.lower() in ANGLEQUANTITIES: - return self.on_tth - elif xtype.lower() in QQUANTITIES: - return self.on_q - elif xtype.lower() in DQUANTITIES: - return self.on_d - pass - - def dump(self, filepath, xtype=None): - if xtype is None: - xtype = " q" - if xtype == "q": - data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) - elif xtype == "tth": - data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) - elif xtype == "d": - data_to_save = np.column_stack((self.on_d[0], self.on_d[1])) - else: - print(f"WARNING: cannot handle the xtype '{xtype}'") - self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) - self.metadata["creation_time"] = datetime.datetime.now() - - with open(filepath, "w") as f: - f.write( - f"[Diffraction_object]\nname = {self.name}\nwavelength = {self.wavelength}\n" - f"scat_quantity = {self.scat_quantity}\n" - ) - for key, value in self.metadata.items(): - f.write(f"{key} = {value}\n") - f.write("\n#### start data\n") - np.savetxt(f, data_to_save, delimiter=" ") - - class DiffractionObject: - def __init__(self, name="", wavelength=None): - self.name = name - self.wavelength = wavelength - self.scat_quantity = "" - self.on_q = np.empty((2, 0), dtype=np.float64) - self.on_tth = np.empty((2, 0), dtype=np.float64) - self.on_d = np.empty((2, 0), dtype=np.float64) - self._all_arrays = [self.on_q, self.on_tth] - self.metadata = {} + def __init__( + self, name="", wavelength=None, scat_quantity="", metadata={}, xarray=None, yarray=None, xtype="" + ): + if xarray is None: + xarray = np.empty(0) + if yarray is None: + yarray = np.empty(0) + self.insert_scattering_quantity( + xarray, yarray, xtype, metadata=metadata, scat_quantity=scat_quantity, name=name, wavelength=wavelength + ) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -629,7 +247,7 @@ def insert_scattering_quantity( yarray, xtype, metadata={}, - scat_quantity=None, + scat_quantity="", name=None, wavelength=None, ): @@ -652,42 +270,47 @@ def insert_scattering_quantity( """ self.input_xtype = xtype - # empty attributes have been defined in the __init__ method so only - # set the attributes that are not empty to avoid emptying them by mistake - if metadata: - self.metadata = metadata - if scat_quantity is not None: - self.scat_quantity = scat_quantity - if name is not None: - self.name = name - if wavelength is not None: - self.wavelength = wavelength + self.metadata = metadata + self.scat_quantity = scat_quantity + self.name = name + self.wavelength = wavelength + self.all_arrays = np.empty(shape=(len(yarray), 4)) + self.all_arrays[:, 0] = yarray if xtype.lower() in QQUANTITIES: - self.on_q = np.array([xarray, yarray]) + self.all_arrays[:, 1] = xarray + self.all_arrays[:, 2] = q_to_tth(xarray, wavelength) + self.all_arrays[:, 3] = q_to_d(xarray) elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = np.array([xarray, yarray]) - elif xtype.lower() in DQUANTITIES: # Fixme when d is implemented. This here as a placeholder - self.on_tth = np.array([xarray, yarray]) - self.set_all_arrays() - - def set_all_arrays(self): - master_array, xtype = self._get_original_array() - if xtype == "q": - self.on_tth = q_to_tth(self.on_q, self.wavelength) - elif xtype == "tth": - self.on_q = tth_to_q(self.on_tth, self.wavelength) - self.tthmin = self.on_tth[0][0] - self.tthmax = self.on_tth[0][-1] - self.qmin = self.on_q[0][0] - self.qmax = self.on_q[0][-1] + self.all_arrays[:, 2] = xarray + self.all_arrays[:, 1] = tth_to_q(xarray, wavelength) + self.all_arrays[:, 3] = tth_to_d(xarray, wavelength) + elif xtype.lower() in DQUANTITIES: + self.all_arrays[:, 3] = xarray + self.all_arrays[:, 1] = d_to_q(xarray) + self.all_arrays[:, 2] = d_to_tth(xarray, wavelength) + self.qmin = np.nanmin(self.all_arrays[:, 1], initial=np.inf) + self.qmax = np.nanmax(self.all_arrays[:, 1], initial=0.0) + self.tthmin = np.nanmin(self.all_arrays[:, 2], initial=np.inf) + self.tthmax = np.nanmax(self.all_arrays[:, 2], initial=0.0) + self.dmin = np.nanmin(self.all_arrays[:, 3], initial=np.inf) + self.dmax = np.nanmax(self.all_arrays[:, 3], initial=0.0) def _get_original_array(self): if self.input_xtype in QQUANTITIES: - return self.on_q, "q" + return self.on_q(), "q" elif self.input_xtype in ANGLEQUANTITIES: - return self.on_tth, "tth" + return self.on_tth(), "tth" elif self.input_xtype in DQUANTITIES: - return self.on_d, "d" + return self.on_d(), "d" + + def on_q(self): + return [self.all_arrays[:, 1], self.all_arrays[:, 0]] + + def on_tth(self): + return [self.all_arrays[:, 2], self.all_arrays[:, 0]] + + def on_d(self): + return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, xtype=None, xvalue=None): f""" @@ -748,11 +371,11 @@ def dump(self, filepath, xtype=None): if xtype is None: xtype = " q" if xtype == "q": - data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) + data_to_save = np.column_stack((self.on_q()[0], self.on_q()[1])) elif xtype == "tth": - data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) + data_to_save = np.column_stack((self.on_tth()[0], self.on_tth()[1])) elif xtype == "d": - data_to_save = np.column_stack((self.on_d[0], self.on_d[1])) + data_to_save = np.column_stack((self.on_d()[0], self.on_d()[1])) else: print(f"WARNING: cannot handle the xtype '{xtype}'") self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 773aa607..71e50599 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -61,7 +61,7 @@ def q_to_tth(q, wavelength): This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth """ _validate_inputs(q, wavelength) - q.astype(np.float64) + q.astype(float) tth = copy(q) # initialize output array of same shape if wavelength is not None: tth = np.rad2deg(2.0 * np.arcsin(q * wavelength / (4 * np.pi))) @@ -108,7 +108,7 @@ def tth_to_q(tth, wavelength): The units for the q-values are the inverse of the units of the provided wavelength. This is the correct format for loading into diffpy.utils.DiffractionOject.on_q """ - tth.astype(np.float64) + tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): raise ValueError(invalid_tth_emsg) q = copy(tth) @@ -119,3 +119,21 @@ def tth_to_q(tth, wavelength): for i, _ in enumerate(q): q[i] = i return q + + +def q_to_d(qarray): + return 2.0 * np.pi / copy(qarray) + + +def tth_to_d(ttharray, wavelength): + qarray = tth_to_q(ttharray, wavelength) + return 2.0 * np.pi / copy(qarray) + + +def d_to_q(darray): + return 2.0 * np.pi / copy(darray) + + +def d_to_tth(darray, wavelength): + qarray = d_to_q(darray) + return q_to_tth(qarray, wavelength) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 1bc5220e..d04f5284 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -7,215 +7,179 @@ from diffpy.utils.diffraction_objects import DiffractionObject from diffpy.utils.transforms import wavelength_warning_emsg + +def compare_dicts(dict1, dict2): + assert dict1.keys() == dict2.keys(), "Keys mismatch" + for key in dict1: + val1, val2 = dict1[key], dict2[key] + if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): + assert np.allclose(val1, val2), f"Arrays for key '{key}' differ" + elif isinstance(val1, np.float64) and isinstance(val2, np.float64): + assert np.isclose(val1, val2), f"Float64 values for key '{key}' differ" + else: + assert val1 == val2, f"Values for key '{key}' differ: {val1} != {val2}" + + +def dicts_equal(dict1, dict2): + equal = True + if not dict1.keys() == dict2.keys(): + equal = False + for key in dict1: + val1, val2 = dict1[key], dict2[key] + if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): + if not np.allclose(val1, val2): + equal = False + elif isinstance(val1, np.float64) and isinstance(val2, np.float64): + if not np.isclose(val1, val2): + equal = False + else: + print(key, val1, val2) + if not val1 == val2: + equal = False + return equal + + params = [ ( # Default - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + {}, + {}, True, ), ( # Compare same attributes - [ - "test", - 0.71, - "x-ray", - [np.array([1, 2]), np.array([3, 4])], - [np.array([1, 2]), np.array([3, 4])], - [np.array([1, 2]), np.array([3, 4])], - {"thing1": 1, "thing2": "thing2"}, - ], - [ - "test", - 0.7100001, - "x-ray", - [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], - [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], - [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], - {"thing1": 1, "thing2": "thing2"}, - ], + { + "name": "same", + "scat_quantity": "x-ray", + "wavelength": 0.71, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1}, + }, + { + "name": "same", + "scat_quantity": "x-ray", + "wavelength": 0.71, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1}, + }, True, ), ( # Different names - [ - "test1", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "test2", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + { + "name": "something", + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "name": "something else", + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different wavelengths - [ - "", - 0.71, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - 0.711, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + { + "scat_quantity": "", + "wavelength": 0.71, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different wavelengths - [ - "", - 0.71, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + { + "scat_quantity": "", + "wavelength": 0.71, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "scat_quantity": "", + "wavelength": 0.711, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different scat_quantity - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "x-ray", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + { + "scat_quantity": "x-ray", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "scat_quantity": "neutron", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different on_q - [ - "", - None, - "", - [np.array([1, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "", - [np.array([1.01, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - False, - ), - ( # Different on_tth - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.array([1, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.array([1.01, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], - {}, - ], - False, - ), - ( # Different on_d - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.array([1, 2]), np.array([3, 4])], - {}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.array([1.01, 2]), np.array([3, 4])], - {}, - ], + { + "scat_quantity": "", + "wavelength": None, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {}, + }, + { + "scat_quantity": "", + "wavelength": None, + "xtype": "q", + "xarray": np.array([3.0, 4.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different metadata - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {"thing1": 0, "thing2": "thing2"}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {"thing1": 1, "thing2": "thing2"}, - ], + { + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 0, "thing2": "thing2"}, + }, + { + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ] @@ -223,13 +187,13 @@ @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = DiffractionObject() - diffraction_object2 = DiffractionObject() - diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] - for i, attribute in enumerate(diffraction_object1_attributes): - setattr(diffraction_object1, attribute, inputs1[i]) - setattr(diffraction_object2, attribute, inputs2[i]) - assert (diffraction_object1 == diffraction_object2) == expected + diffraction_object1 = DiffractionObject(inputs1) + diffraction_object2 = DiffractionObject(inputs2) + # diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] + # for i, attribute in enumerate(diffraction_object1_attributes): + # setattr(diffraction_object1, attribute, inputs1[i]) + # setattr(diffraction_object2, attribute, inputs2[i]) + assert dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__) == expected def _test_valid_diffraction_objects(actual_diffraction_object, function, expected_array): @@ -245,14 +209,13 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - test = DiffractionObject() - test.wavelength = 1.54 - test.name = "test" - test.scat_quantity = "x-ray" - test.insert_scattering_quantity( - np.array(x), - np.array(y), - "q", + test = DiffractionObject( + wavelength=1.54, + name="test", + scat_quantity="x-ray", + xarray=np.array(x), + yarray=np.array(y), + xtype="q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, ) mocker.patch("importlib.metadata.version", return_value="3.3.0") @@ -273,3 +236,104 @@ def test_dump(tmp_path, mocker): ) assert actual == expected + + +tc_params = [ + ( + {}, + { + "all_arrays": np.empty(shape=(0, 4)), # instantiate empty + "metadata": {}, + "input_xtype": "", + "name": "", + "scat_quantity": "", + "qmin": np.float64(np.inf), + "qmax": np.float64(0.0), + "tthmin": np.float64(np.inf), + "tthmax": np.float64(0.0), + "dmin": np.float64(np.inf), + "dmax": np.float64(0.0), + "wavelength": None, + }, + ), + ( # instantiate just non-array attributes + {"name": "test", "scat_quantity": "x-ray", "metadata": {"thing": "1", "another": "2"}}, + { + "all_arrays": np.empty(shape=(0, 4)), + "metadata": {"thing": "1", "another": "2"}, + "input_xtype": "", + "name": "test", + "scat_quantity": "x-ray", + "qmin": np.float64(np.inf), + "qmax": np.float64(0.0), + "tthmin": np.float64(np.inf), + "tthmax": np.float64(0.0), + "dmin": np.float64(np.inf), + "dmax": np.float64(0.0), + "wavelength": None, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), +] + + +@pytest.mark.parametrize("inputs, expected", tc_params) +def test_constructor(inputs, expected): + actualdo = DiffractionObject(**inputs) + compare_dicts(actualdo.__dict__, expected) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 7e3eae3f..e8b15492 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,9 +5,9 @@ params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays - ([None, np.empty((1,))], np.empty((1,))), + ([None, np.empty((0))], np.empty((0))), # UC2: Empty q values, wavelength specified, return empty arrays - ([4 * np.pi, np.empty((1,))], np.empty((1,))), + ([4 * np.pi, np.empty((0))], np.empty(0)), # UC3: User specified valid q values, no wavelength, return empty arrays ( [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], From 828b2a69e696dc357061f9610ddb9a145a188f0a Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 4 Dec 2024 08:18:33 -0500 Subject: [PATCH 308/696] now passing tests --- src/diffpy/utils/diffraction_objects.py | 98 ++++++++++++++----------- tests/test_diffraction_objects.py | 16 ++-- 2 files changed, 65 insertions(+), 49 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index e628beed..1daa62d0 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -1,4 +1,5 @@ import datetime +import warnings from copy import deepcopy import numpy as np @@ -18,17 +19,31 @@ ) +def _xtype_wmsg(xtype): + return ( + f"WARNING: I don't know how to handle the xtype, '{xtype}'. Please rerun specifying and " + f"xtype from {*XQUANTITIES, }" + ) + + class DiffractionObject: def __init__( - self, name="", wavelength=None, scat_quantity="", metadata={}, xarray=None, yarray=None, xtype="" + self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype="" ): + if name is None: + name = "" + self.name = name + if metadata is None: + metadata = {} + self.metadata = metadata + self.scat_quantity = scat_quantity + self.wavelength = wavelength + if xarray is None: xarray = np.empty(0) if yarray is None: yarray = np.empty(0) - self.insert_scattering_quantity( - xarray, yarray, xtype, metadata=metadata, scat_quantity=scat_quantity, name=name, wavelength=wavelength - ) + self.insert_scattering_quantity(xarray, yarray, xtype) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -241,15 +256,32 @@ def get_angle_index(self, angle): if count >= len(self.angles): raise IndexError(f"WARNING: no angle {angle} found in angles list") + def _set_xarrays(self, xarray, xtype): + self.all_arrays = np.empty(shape=(len(xarray), 4)) + if xtype.lower() in QQUANTITIES: + self.all_arrays[:, 1] = xarray + self.all_arrays[:, 2] = q_to_tth(xarray, self.wavelength) + self.all_arrays[:, 3] = q_to_d(xarray) + elif xtype.lower() in ANGLEQUANTITIES: + self.all_arrays[:, 2] = xarray + self.all_arrays[:, 1] = tth_to_q(xarray, self.wavelength) + self.all_arrays[:, 3] = tth_to_d(xarray, self.wavelength) + elif xtype.lower() in DQUANTITIES: + self.all_arrays[:, 3] = xarray + self.all_arrays[:, 1] = d_to_q(xarray) + self.all_arrays[:, 2] = d_to_tth(xarray, self.wavelength) + self.qmin = np.nanmin(self.all_arrays[:, 1], initial=np.inf) + self.qmax = np.nanmax(self.all_arrays[:, 1], initial=0.0) + self.tthmin = np.nanmin(self.all_arrays[:, 2], initial=np.inf) + self.tthmax = np.nanmax(self.all_arrays[:, 2], initial=0.0) + self.dmin = np.nanmin(self.all_arrays[:, 3], initial=np.inf) + self.dmax = np.nanmax(self.all_arrays[:, 3], initial=0.0) + def insert_scattering_quantity( self, xarray, yarray, xtype, - metadata={}, - scat_quantity="", - name=None, - wavelength=None, ): f""" insert a new scattering quantity into the scattering object @@ -262,38 +294,14 @@ def insert_scattering_quantity( the dependent variable array xtype string the type of quantity for the independent variable from {*XQUANTITIES, } - metadata: dict - the metadata in the form of a dictionary of user-supplied key:value pairs Returns ------- """ - self.input_xtype = xtype - self.metadata = metadata - self.scat_quantity = scat_quantity - self.name = name - self.wavelength = wavelength - self.all_arrays = np.empty(shape=(len(yarray), 4)) + self._set_xarrays(xarray, xtype) self.all_arrays[:, 0] = yarray - if xtype.lower() in QQUANTITIES: - self.all_arrays[:, 1] = xarray - self.all_arrays[:, 2] = q_to_tth(xarray, wavelength) - self.all_arrays[:, 3] = q_to_d(xarray) - elif xtype.lower() in ANGLEQUANTITIES: - self.all_arrays[:, 2] = xarray - self.all_arrays[:, 1] = tth_to_q(xarray, wavelength) - self.all_arrays[:, 3] = tth_to_d(xarray, wavelength) - elif xtype.lower() in DQUANTITIES: - self.all_arrays[:, 3] = xarray - self.all_arrays[:, 1] = d_to_q(xarray) - self.all_arrays[:, 2] = d_to_tth(xarray, wavelength) - self.qmin = np.nanmin(self.all_arrays[:, 1], initial=np.inf) - self.qmax = np.nanmax(self.all_arrays[:, 1], initial=0.0) - self.tthmin = np.nanmin(self.all_arrays[:, 2], initial=np.inf) - self.tthmax = np.nanmax(self.all_arrays[:, 2], initial=0.0) - self.dmin = np.nanmin(self.all_arrays[:, 3], initial=np.inf) - self.dmax = np.nanmax(self.all_arrays[:, 3], initial=0.0) + self.input_xtype = xtype def _get_original_array(self): if self.input_xtype in QQUANTITIES: @@ -319,7 +327,7 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): Parameters ---------- target_diff_object: DiffractionObject - the diffractoin object you want to scale the current one on to + the diffraction object you want to scale the current one on to xtype: string, optional. Default is Q the xtype, from {XQUANTITIES}, that you will specify a point from to scale to xvalue: float. Default is the midpoint of the array @@ -351,6 +359,7 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): def on_xtype(self, xtype): """ return a 2D np array with x in the first column and y in the second for x of type type + Parameters ---------- xtype @@ -360,24 +369,25 @@ def on_xtype(self, xtype): """ if xtype.lower() in ANGLEQUANTITIES: - return self.on_tth + return self.on_tth() elif xtype.lower() in QQUANTITIES: - return self.on_q + return self.on_q() elif xtype.lower() in DQUANTITIES: - return self.on_d - pass + return self.on_d() + else: + warnings.warn(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): if xtype is None: - xtype = " q" - if xtype == "q": + xtype = "q" + if xtype in QQUANTITIES: data_to_save = np.column_stack((self.on_q()[0], self.on_q()[1])) - elif xtype == "tth": + elif xtype in ANGLEQUANTITIES: data_to_save = np.column_stack((self.on_tth()[0], self.on_tth()[1])) - elif xtype == "d": + elif xtype in DQUANTITIES: data_to_save = np.column_stack((self.on_d()[0], self.on_d()[1])) else: - print(f"WARNING: cannot handle the xtype '{xtype}'") + warnings.warn(_xtype_wmsg(xtype)) self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) self.metadata["creation_time"] = datetime.datetime.now() diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index d04f5284..9db6932e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -22,6 +22,9 @@ def compare_dicts(dict1, dict2): def dicts_equal(dict1, dict2): equal = True + print("") + print(dict1) + print(dict2) if not dict1.keys() == dict2.keys(): equal = False for key in dict1: @@ -29,11 +32,13 @@ def dicts_equal(dict1, dict2): if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): if not np.allclose(val1, val2): equal = False + elif isinstance(val1, list) and isinstance(val2, list): + if not val1.all() == val2.all(): + equal = False elif isinstance(val1, np.float64) and isinstance(val2, np.float64): if not np.isclose(val1, val2): equal = False else: - print(key, val1, val2) if not val1 == val2: equal = False return equal @@ -187,12 +192,13 @@ def dicts_equal(dict1, dict2): @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = DiffractionObject(inputs1) - diffraction_object2 = DiffractionObject(inputs2) + diffraction_object1 = DiffractionObject(**inputs1) + diffraction_object2 = DiffractionObject(**inputs2) # diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] # for i, attribute in enumerate(diffraction_object1_attributes): # setattr(diffraction_object1, attribute, inputs1[i]) # setattr(diffraction_object2, attribute, inputs2[i]) + print(dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__), expected) assert dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__) == expected @@ -246,7 +252,7 @@ def test_dump(tmp_path, mocker): "metadata": {}, "input_xtype": "", "name": "", - "scat_quantity": "", + "scat_quantity": None, "qmin": np.float64(np.inf), "qmax": np.float64(0.0), "tthmin": np.float64(np.inf), @@ -291,7 +297,7 @@ def test_dump(tmp_path, mocker): "metadata": {}, "input_xtype": "tth", "name": "", - "scat_quantity": "", + "scat_quantity": None, "qmin": np.float64(0.0), "qmax": np.float64(1.0), "tthmin": np.float64(0.0), From daa9c3f1b311c5dfdd0216c39ff094d00d5be445 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 4 Dec 2024 08:40:04 -0500 Subject: [PATCH 309/696] small tweaks and typo fixes --- news/constructor.rst | 2 +- src/diffpy/utils/diffraction_objects.py | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/news/constructor.rst b/news/constructor.rst index 9738c7a8..39ffa77f 100644 --- a/news/constructor.rst +++ b/news/constructor.rst @@ -6,7 +6,7 @@ * arrays and attributes now can be inserted when a DiffractionObject is instantiated * data are now stored as a (len(x),4) numpy array with intensity in column 0, the q, then tth, then d -* `DiffractionObject.on_q`, on_tth and on_d are now methods and called as DiffractionObject.on_q() etc.` +* `DiffractionObject.on_q`, `...on_tth` and `...on_d` are now methods and called as `DiffractionObject.on_q()` etc.` **Deprecated:** diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1daa62d0..54374fa7 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -21,14 +21,14 @@ def _xtype_wmsg(xtype): return ( - f"WARNING: I don't know how to handle the xtype, '{xtype}'. Please rerun specifying and " + f"WARNING: I don't know how to handle the xtype, '{xtype}'. Please rerun specifying an " f"xtype from {*XQUANTITIES, }" ) class DiffractionObject: def __init__( - self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype="" + self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype=None ): if name is None: name = "" @@ -36,6 +36,8 @@ def __init__( if metadata is None: metadata = {} self.metadata = metadata + if xtype is None: + xtype = "" self.scat_quantity = scat_quantity self.wavelength = wavelength @@ -282,6 +284,10 @@ def insert_scattering_quantity( xarray, yarray, xtype, + metadata={}, + scat_quantity=None, + name=None, + wavelength=None, ): f""" insert a new scattering quantity into the scattering object @@ -294,14 +300,27 @@ def insert_scattering_quantity( the dependent variable array xtype string the type of quantity for the independent variable from {*XQUANTITIES, } + metadata, scat_quantity, name and wavelength are optional. They have the same + meaning as in the constructor. Values will only be overwritten if non-empty values are passed. Returns ------- + Nothing. Updates the object in place. """ self._set_xarrays(xarray, xtype) self.all_arrays[:, 0] = yarray self.input_xtype = xtype + # only update these optional values if non-empty quantities are passed to avoid overwriting + # valid data inadvertently + if metadata: + self.metadata = metadata + if scat_quantity is not None: + self.scat_quantity = scat_quantity + if name is not None: + self.name = name + if wavelength is not None: + self.wavelength = wavelength def _get_original_array(self): if self.input_xtype in QQUANTITIES: From 8342f2ac2d56cae13dac3f155fa69eb55a425587 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 4 Dec 2024 09:53:01 -0500 Subject: [PATCH 310/696] Use private _all_arrays to set --- src/diffpy/utils/diffraction_objects.py | 45 +++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 54374fa7..66b840e5 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -181,6 +181,17 @@ def __rtruediv__(self, other): divided.on_q[1] = other.on_q[1] / self.on_q[1] return divided + @property + def all_arrays(self): + return self._all_arrays + + @all_arrays.setter + def all_arrays(self, value): + raise AttributeError( + "Direct modification of 'all_arrays' is not allowed." + "Please use 'insert_scattering_quantity' to modify it." + ) + def set_angles_from_list(self, angles_list): self.angles = angles_list self.n_steps = len(angles_list) - 1.0 @@ -259,25 +270,25 @@ def get_angle_index(self, angle): raise IndexError(f"WARNING: no angle {angle} found in angles list") def _set_xarrays(self, xarray, xtype): - self.all_arrays = np.empty(shape=(len(xarray), 4)) + self._all_arrays = np.empty(shape=(len(xarray), 4)) if xtype.lower() in QQUANTITIES: - self.all_arrays[:, 1] = xarray - self.all_arrays[:, 2] = q_to_tth(xarray, self.wavelength) - self.all_arrays[:, 3] = q_to_d(xarray) + self._all_arrays[:, 1] = xarray + self._all_arrays[:, 2] = q_to_tth(xarray, self.wavelength) + self._all_arrays[:, 3] = q_to_d(xarray) elif xtype.lower() in ANGLEQUANTITIES: - self.all_arrays[:, 2] = xarray - self.all_arrays[:, 1] = tth_to_q(xarray, self.wavelength) - self.all_arrays[:, 3] = tth_to_d(xarray, self.wavelength) + self._all_arrays[:, 2] = xarray + self._all_arrays[:, 1] = tth_to_q(xarray, self.wavelength) + self._all_arrays[:, 3] = tth_to_d(xarray, self.wavelength) elif xtype.lower() in DQUANTITIES: - self.all_arrays[:, 3] = xarray - self.all_arrays[:, 1] = d_to_q(xarray) - self.all_arrays[:, 2] = d_to_tth(xarray, self.wavelength) - self.qmin = np.nanmin(self.all_arrays[:, 1], initial=np.inf) - self.qmax = np.nanmax(self.all_arrays[:, 1], initial=0.0) - self.tthmin = np.nanmin(self.all_arrays[:, 2], initial=np.inf) - self.tthmax = np.nanmax(self.all_arrays[:, 2], initial=0.0) - self.dmin = np.nanmin(self.all_arrays[:, 3], initial=np.inf) - self.dmax = np.nanmax(self.all_arrays[:, 3], initial=0.0) + self._all_arrays[:, 3] = xarray + self._all_arrays[:, 1] = d_to_q(xarray) + self._all_arrays[:, 2] = d_to_tth(xarray, self.wavelength) + self.qmin = np.nanmin(self._all_arrays[:, 1], initial=np.inf) + self.qmax = np.nanmax(self._all_arrays[:, 1], initial=0.0) + self.tthmin = np.nanmin(self._all_arrays[:, 2], initial=np.inf) + self.tthmax = np.nanmax(self._all_arrays[:, 2], initial=0.0) + self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf) + self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0) def insert_scattering_quantity( self, @@ -309,7 +320,7 @@ def insert_scattering_quantity( """ self._set_xarrays(xarray, xtype) - self.all_arrays[:, 0] = yarray + self._all_arrays[:, 0] = yarray self.input_xtype = xtype # only update these optional values if non-empty quantities are passed to avoid overwriting # valid data inadvertently From 6278a8ebfdd195821e06d8960e65b3c16202ffee Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 4 Dec 2024 09:53:24 -0500 Subject: [PATCH 311/696] Update tests with actual object info --- tests/test_diffraction_objects.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9db6932e..4651273e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -341,5 +341,27 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize("inputs, expected", tc_params) def test_constructor(inputs, expected): - actualdo = DiffractionObject(**inputs) - compare_dicts(actualdo.__dict__, expected) + actual_do = DiffractionObject(**inputs) + actual_dict = { + "all_arrays": actual_do.all_arrays, + "metadata": actual_do.metadata, + "input_xtype": actual_do.input_xtype, + "name": actual_do.name, + "scat_quantity": actual_do.scat_quantity, + "qmin": actual_do.qmin, + "qmax": actual_do.qmax, + "tthmin": actual_do.tthmin, + "tthmax": actual_do.tthmax, + "dmin": actual_do.dmin, + "dmax": actual_do.dmax, + "wavelength": actual_do.wavelength, + } + compare_dicts(actual_dict, expected) + + +def test_all_array_setter(): + actual_do = DiffractionObject() + + # Attempt to directly modify the property + with pytest.raises(AttributeError, match="Direct modification of 'all_arrays' is not allowed."): + actual_do.all_arrays = np.empty((4, 4)) From 9772e1ce034f2ed51aed218540237edacd056f1e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 4 Dec 2024 09:53:33 -0500 Subject: [PATCH 312/696] Add news --- news/setter-property.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/setter-property.rst diff --git a/news/setter-property.rst b/news/setter-property.rst new file mode 100644 index 00000000..8b2ddc97 --- /dev/null +++ b/news/setter-property.rst @@ -0,0 +1,23 @@ +**Added:** + +* prevent direct modification of `all_arrays` using `@property` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 5257ee227b26acb2137f219fbf3ccdbdd28b8103 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 4 Dec 2024 16:27:43 -0500 Subject: [PATCH 313/696] add more bad tests --- src/diffpy/utils/diffraction_objects.py | 22 ++++++----- tests/test_diffraction_objects.py | 49 ++++++++++++++++++------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index e9b15696..88c4452e 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -248,25 +248,27 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): array = np.linspace(begin, end, n_steps) return array - def get_angle_index(self, angle): + def get_array_index(self, xtype, value): """ - returns the index of a given angle in the angles list + returns the index of a given value in the array associated with the specified xtype Parameters ---------- - angle float - the angle to search for + xtype str + the xtype used to access the array + value float + the target value to search for Returns ------- - the index of the angle in the angles list + the index of the value in the array """ - if not hasattr(self, "angles"): - self.angles = np.array([]) - for i, target in enumerate(self.angles): - if angle == target: + if self.on_xtype(xtype) is None: + raise ValueError(_xtype_wmsg(xtype)) + for i, target in enumerate(self.on_xtype(xtype)[0]): + if value == target: return i - raise IndexError(f"WARNING: no angle {angle} found in angles list.") + raise IndexError(f"WARNING: no matching value {value} found in the {xtype} array.") def _set_xarrays(self, xarray, xtype): self.all_arrays = np.empty(shape=(len(xarray), 4)) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c1ee6f15..045c7cf2 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -1,10 +1,11 @@ +import re from pathlib import Path import numpy as np import pytest from freezegun import freeze_time -from diffpy.utils.diffraction_objects import DiffractionObject +from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject from diffpy.utils.transforms import wavelength_warning_emsg @@ -212,21 +213,41 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte def test_get_angle_index(): - test = DiffractionObject() - test.angles = np.array([10, 20, 30, 40, 50, 60]) - actual_angle_index = test.get_angle_index(angle=10) - assert actual_angle_index == 0 + test = DiffractionObject( + wavelength=0.71, xarray=np.array([30, 60, 90]), yarray=np.array([1, 2, 3]), xtype="tth" + ) + actual_index = test.get_array_index(xtype="tth", value=30) + assert actual_index == 0 + + +params_index_bad = [ + # UC1: empty array + ( + [0.71, np.array([]), np.array([]), "tth", "tth", 10], + [IndexError, "WARNING: no matching value 10 found in the tth array."], + ), + # UC2: invalid xtype + ( + [None, np.array([]), np.array([]), "tth", "invalid", 10], + [ + ValueError, + f"WARNING: I don't know how to handle the xtype, 'invalid'. " + f"Please rerun specifying an xtype from {*XQUANTITIES, }", + ], + ), + # UC3: pre-defined array with non-matching value + ( + [0.71, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "q", 30], + [IndexError, "WARNING: no matching value 30 found in the q array."], + ), +] -def test_get_angle_index_bad(): - test = DiffractionObject() - # empty angles list - with pytest.raises(IndexError, match="WARNING: no angle 11 found in angles list."): - test.get_angle_index(angle=11) - # pre-defined angles list - test.angles = np.array([10, 20, 30, 40, 50, 60]) - with pytest.raises(IndexError, match="WARNING: no angle 11 found in angles list."): - test.get_angle_index(angle=11) +@pytest.mark.parametrize("inputs, expected", params_index_bad) +def test_get_angle_index_bad(inputs, expected): + test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) + with pytest.raises(expected[0], match=re.escape(expected[1])): + test.get_array_index(xtype=inputs[4], value=inputs[5]) def test_dump(tmp_path, mocker): From 1f9f142f3e7f00e0da46d84fd4c8b8d442ba9900 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 4 Dec 2024 16:44:37 -0500 Subject: [PATCH 314/696] edit tests --- src/diffpy/utils/diffraction_objects.py | 2 +- tests/test_diffraction_objects.py | 45 ++++++------------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 54374fa7..c213b5df 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -394,7 +394,7 @@ def on_xtype(self, xtype): elif xtype.lower() in DQUANTITIES: return self.on_d() else: - warnings.warn(_xtype_wmsg(xtype)) + raise ValueError(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): if xtype is None: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 09506fec..beecab56 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -5,13 +5,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.diffraction_objects import ( - ANGLEQUANTITIES, - DQUANTITIES, - QQUANTITIES, - XQUANTITIES, - DiffractionObject, -) +from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject def compare_dicts(dict1, dict2): @@ -208,38 +202,21 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__) == expected -params_on_xtype = [ - ( - [ - np.array([100, 200, 300, 400, 500, 600]), # intensity array - np.array([0, 30, 60, 90, 120, 180]), # tth array - np.array([1, 2, 3, 4, 5, 6]), # q array - np.array([10, 20, 30, 40, 50, 60]), # d array - ], - [ - np.array([[0, 30, 60, 90, 120, 180], [100, 200, 300, 400, 500, 600]]), # expected on_tth - np.array([[1, 2, 3, 4, 5, 6], [100, 200, 300, 400, 500, 600]]), # expected on_q - np.array([[10, 20, 30, 40, 50, 60], [100, 200, 300, 400, 500, 600]]), # expected on_d - ], - ) -] - - -@pytest.mark.parametrize("inputs, expected", params_on_xtype) -def test_on_xtype(inputs, expected): - test = DiffractionObject() - test.on_tth = np.array([inputs[1], inputs[0]]) - test.on_q = np.array([inputs[2], inputs[0]]) - test.on_d = np.array([inputs[3], inputs[0]]) - for xtype_list, expected_value in zip([ANGLEQUANTITIES, QQUANTITIES, DQUANTITIES], expected): - for xtype in xtype_list: - assert np.allclose(test.on_xtype(xtype), expected_value) +def test_on_xtype(): + test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + assert np.allclose(test.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("q"), [np.array([0.51763809, 1]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("d"), [np.array([12.13818192, 6.28318531]), np.array([1, 2])]) def test_on_xtype_bad(): test = DiffractionObject() with pytest.raises( - ValueError, match=re.escape(f"Unknown xtype: invalid. Allowed xtypes are {*XQUANTITIES, }.") + ValueError, + match=re.escape( + f"WARNING: I don't know how to handle the xtype, 'invalid'. Please rerun specifying an " + f"xtype from {*XQUANTITIES, }" + ), ): test.on_xtype("invalid") From 11a15ee8f939b7df8dcdb18112c4624a985e1516 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 4 Dec 2024 19:15:17 -0500 Subject: [PATCH 315/696] add docstring and news --- news/xtype.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 7 ++++--- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 news/xtype.rst diff --git a/news/xtype.rst b/news/xtype.rst new file mode 100644 index 00000000..24a78758 --- /dev/null +++ b/news/xtype.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality to return the 2D array based on the specified xtype + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index c213b5df..b1ed5a78 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -376,16 +376,17 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): return scaled def on_xtype(self, xtype): - """ + f""" return a 2D np array with x in the first column and y in the second for x of type type Parameters ---------- - xtype + xtype str + the type of quantity for the independent variable from {*XQUANTITIES, } Returns ------- - + a 2D np array with x and y data """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() From 56e72788bedea4f90ddcc8985fcc9c3b34e848a8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 5 Dec 2024 10:55:27 -0500 Subject: [PATCH 316/696] add more tests --- src/diffpy/utils/diffraction_objects.py | 34 ++++++++++---- tests/test_diffraction_objects.py | 61 +++++++++++++++++-------- 2 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 88c4452e..3a0f88bc 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -248,9 +248,9 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): array = np.linspace(begin, end, n_steps) return array - def get_array_index(self, xtype, value): + def get_array_index(self, value, xtype=None): """ - returns the index of a given value in the array associated with the specified xtype + returns the index of the closest value in the array associated with the specified xtype Parameters ---------- @@ -263,12 +263,30 @@ def get_array_index(self, xtype, value): ------- the index of the value in the array """ - if self.on_xtype(xtype) is None: - raise ValueError(_xtype_wmsg(xtype)) - for i, target in enumerate(self.on_xtype(xtype)[0]): - if value == target: - return i - raise IndexError(f"WARNING: no matching value {value} found in the {xtype} array.") + + if xtype is None: + xtype = self.input_xtype + if self.on_xtype(xtype) is None or len(self.on_xtype(xtype)[0]) == 0: + raise ValueError( + f"The '{xtype}' array is empty. " "Please ensure it is initialized and the correct xtype is used." + ) + array = self.on_xtype(xtype)[0] + i = (np.abs(array - value)).argmin() + nearest_value = np.abs(array[i] - value) + distance = min(np.abs(value - array.min()), np.abs(value - array.max())) + threshold = 0.5 * (array.max() - array.min()) + + if nearest_value != 0 and (array.min() <= value <= array.max() or distance <= threshold): + warnings.warn( + f"WARNING: The value {value} is not an exact match of the '{xtype}' array. " + f"Returning the index of the closest value." + ) + elif distance > threshold: + raise IndexError( + f"The value {value} is too far from any value in the '{xtype}' array. " + f"Please check if you have specified the correct xtype. " + ) + return i def _set_xarrays(self, xarray, xtype): self.all_arrays = np.empty(shape=(len(xarray), 4)) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 045c7cf2..1bf3a459 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -5,7 +5,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject +from diffpy.utils.diffraction_objects import DiffractionObject from diffpy.utils.transforms import wavelength_warning_emsg @@ -212,42 +212,65 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte return np.allclose(actual_array, expected_array) -def test_get_angle_index(): - test = DiffractionObject( - wavelength=0.71, xarray=np.array([30, 60, 90]), yarray=np.array([1, 2, 3]), xtype="tth" - ) - actual_index = test.get_array_index(xtype="tth", value=30) - assert actual_index == 0 +params_index = [ + # UC1: exact match + ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), + # UC2: target value lies in the array, returns the (first) closest index + ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45], [0]), + ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25], [0]), + # UC3: target value out of the range but within reasonable distance, returns the closest index + ([4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1], [0]), + ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63], [1]), +] + + +@pytest.mark.parametrize("inputs, expected", params_index) +def test_get_array_index(inputs, expected): + test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) + actual = test.get_array_index(value=inputs[5], xtype=inputs[4]) + assert actual == expected[0] params_index_bad = [ - # UC1: empty array + # UC0: empty array ( - [0.71, np.array([]), np.array([]), "tth", "tth", 10], - [IndexError, "WARNING: no matching value 10 found in the tth array."], + [2 * np.pi, np.array([]), np.array([]), "tth", "tth", 30], + [ValueError, "The 'tth' array is empty. Please ensure it is initialized and the correct xtype is used."], ), - # UC2: invalid xtype + # UC1: empty array (because of invalid xtype) ( - [None, np.array([]), np.array([]), "tth", "invalid", 10], + [2 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "invalid", 30], [ ValueError, - f"WARNING: I don't know how to handle the xtype, 'invalid'. " - f"Please rerun specifying an xtype from {*XQUANTITIES, }", + "The 'invalid' array is empty. Please ensure it is initialized and the correct xtype is used.", ], ), - # UC3: pre-defined array with non-matching value + # UC3: value is too far from any element in the array ( - [0.71, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "q", 30], - [IndexError, "WARNING: no matching value 30 found in the q array."], + [2 * np.pi, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "tth", 140], + [ + IndexError, + "The value 140 is too far from any value in the 'tth' array. " + "Please check if you have specified the correct xtype.", + ], + ), + # UC4: value is too far from any element in the array (because of wrong xtype) + ( + [2 * np.pi, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "q", 30], + [ + IndexError, + "The value 30 is too far from any value in the 'q' array. " + "Please check if you have specified the correct xtype.", + ], ), ] @pytest.mark.parametrize("inputs, expected", params_index_bad) -def test_get_angle_index_bad(inputs, expected): +def test_get_array_index_bad(inputs, expected): test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) with pytest.raises(expected[0], match=re.escape(expected[1])): - test.get_array_index(xtype=inputs[4], value=inputs[5]) + test.get_array_index(value=inputs[5], xtype=inputs[4]) def test_dump(tmp_path, mocker): From 57465e2563090b88807f46762b43ca9deaf53b7a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 5 Dec 2024 11:08:12 -0500 Subject: [PATCH 317/696] edit tests --- tests/test_diffraction_objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index beecab56..95848111 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -205,8 +205,9 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_on_xtype(): test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") assert np.allclose(test.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("q"), [np.array([0.51763809, 1]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("d"), [np.array([12.13818192, 6.28318531]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) def test_on_xtype_bad(): From 3ba3038f6531a4c0951a193f5f1cf0548085afcc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 13:38:39 -0500 Subject: [PATCH 318/696] Use deepdiff to compare do.__dict__ --- news/scattering_obt.rst | 2 -- requirements/test.txt | 1 + tests/test_diffraction_objects.py | 44 ++++++++++++++++++------------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst index 0090b88e..e5177fde 100644 --- a/news/scattering_obt.rst +++ b/news/scattering_obt.rst @@ -19,5 +19,3 @@ * **Security:** - -* diff --git a/requirements/test.txt b/requirements/test.txt index 9966e09d..a392bd23 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -6,3 +6,4 @@ pytest-env pytest-mock pytest-cov freezegun +DeepDiff diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 4651273e..c80f15b8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -2,6 +2,7 @@ import numpy as np import pytest +from deepdiff import DeepDiff from freezegun import freeze_time from diffpy.utils.diffraction_objects import DiffractionObject @@ -248,7 +249,7 @@ def test_dump(tmp_path, mocker): ( {}, { - "all_arrays": np.empty(shape=(0, 4)), # instantiate empty + "_all_arrays": np.empty(shape=(0, 4)), # instantiate empty "metadata": {}, "input_xtype": "", "name": "", @@ -265,7 +266,7 @@ def test_dump(tmp_path, mocker): ( # instantiate just non-array attributes {"name": "test", "scat_quantity": "x-ray", "metadata": {"thing": "1", "another": "2"}}, { - "all_arrays": np.empty(shape=(0, 4)), + "_all_arrays": np.empty(shape=(0, 4)), "metadata": {"thing": "1", "another": "2"}, "input_xtype": "", "name": "test", @@ -287,7 +288,7 @@ def test_dump(tmp_path, mocker): "wavelength": 4.0 * np.pi, }, { - "all_arrays": np.array( + "_all_arrays": np.array( [ [1.0, 0.0, 0.0, np.float64(np.inf)], [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], @@ -316,7 +317,7 @@ def test_dump(tmp_path, mocker): "scat_quantity": "x-ray", }, { - "all_arrays": np.array( + "_all_arrays": np.array( [ [1.0, 0.0, 0.0, np.float64(np.inf)], [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], @@ -342,21 +343,26 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize("inputs, expected", tc_params) def test_constructor(inputs, expected): actual_do = DiffractionObject(**inputs) - actual_dict = { - "all_arrays": actual_do.all_arrays, - "metadata": actual_do.metadata, - "input_xtype": actual_do.input_xtype, - "name": actual_do.name, - "scat_quantity": actual_do.scat_quantity, - "qmin": actual_do.qmin, - "qmax": actual_do.qmax, - "tthmin": actual_do.tthmin, - "tthmax": actual_do.tthmax, - "dmin": actual_do.dmin, - "dmax": actual_do.dmax, - "wavelength": actual_do.wavelength, - } - compare_dicts(actual_dict, expected) + diff = DeepDiff(actual_do.__dict__, expected, ignore_order=True, significant_digits=4) + # Ensure there is no difference + assert diff == {} + + +def test_all_array_getter(): + actual_do = DiffractionObject( + xarray=np.array([0.0, 90.0, 180.0]), + yarray=np.array([1.0, 2.0, 3.0]), + xtype="tth", + wavelength=4.0 * np.pi, + ) + expected_all_arrays = np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ) + assert np.array_equal(actual_do.all_arrays, expected_all_arrays) def test_all_array_setter(): From 71fcb6b366320a7a0f91e9c5c3848f5f8d94c40d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 13:41:12 -0500 Subject: [PATCH 319/696] Re-design error msg on how to modify all_arrays --- src/diffpy/utils/diffraction_objects.py | 4 ++-- tests/test_diffraction_objects.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 66b840e5..82ad9cf4 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -188,8 +188,8 @@ def all_arrays(self): @all_arrays.setter def all_arrays(self, value): raise AttributeError( - "Direct modification of 'all_arrays' is not allowed." - "Please use 'insert_scattering_quantity' to modify it." + "Direct modification of attribute 'all_arrays' is not allowed." + "Please use 'insert_scattering_quantity' to modify `all_arrays`." ) def set_angles_from_list(self, angles_list): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c80f15b8..a484de39 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -369,5 +369,9 @@ def test_all_array_setter(): actual_do = DiffractionObject() # Attempt to directly modify the property - with pytest.raises(AttributeError, match="Direct modification of 'all_arrays' is not allowed."): + with pytest.raises( + AttributeError, + match="Direct modification of attribute 'all_arrays' is not allowed." + "Please use 'insert_scattering_quantity' to modify `all_arrays`.", + ): actual_do.all_arrays = np.empty((4, 4)) From 03eaa877afffa54a16d5dac86d3d2c63be661312 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 5 Dec 2024 14:20:14 -0500 Subject: [PATCH 320/696] initial commit, add docstring and tests --- src/diffpy/utils/transforms.py | 57 ++++++++++++++++++++------ tests/test_transforms.py | 74 +++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 14 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 71e50599..f13632f8 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,6 +16,9 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) +invalid_input_emsg = ( + "Input values have resulted in an infinite output. Please ensure there are no zeros in the input." +) def _validate_inputs(q, wavelength): @@ -58,7 +61,7 @@ def q_to_tth(q, wavelength): ------- tth : 1D array The array of :math:`2\theta` values in degrees numpy.array([tths]). - This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth + This is the correct format for loading into diffpy.utils.DiffractionObject.on_tth. """ _validate_inputs(q, wavelength) q.astype(float) @@ -92,9 +95,8 @@ def tth_to_q(tth, wavelength): Parameters ---------- - tth : 2D array - The array of :math:`2\theta` values and :math: 'i' intensity values, np.array([[tths], [is]]). - This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_tth + tth : 1D array + The array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. wavelength : float @@ -102,11 +104,10 @@ def tth_to_q(tth, wavelength): Returns ------- - on_q : 2D array - The array of :math:`q` values and :math: 'i' intensity values unchanged, - np.array([[qs], [is]]). + q : 1D array + The array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. - This is the correct format for loading into diffpy.utils.DiffractionOject.on_q + This is the correct format for loading into diffpy.utils.DiffractionObject.on_q. """ tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): @@ -121,8 +122,24 @@ def tth_to_q(tth, wavelength): return q -def q_to_d(qarray): - return 2.0 * np.pi / copy(qarray) +def q_to_d(q): + r""" + Helper function to convert q to d on independent variable axis, using :math:`d = \frac{2 \pi}{q}`. + + Parameters + ---------- + q : 1D array + The array of :math:`q` values np.array([qs]). + The units of q must be reciprocal of the units of wavelength. + + Returns + ------- + d : 1D array + The array of :math:`d` values np.array([ds]). + """ + if 0 in q: + raise ValueError(invalid_input_emsg) + return 2.0 * np.pi / copy(q)[::-1] def tth_to_d(ttharray, wavelength): @@ -130,8 +147,24 @@ def tth_to_d(ttharray, wavelength): return 2.0 * np.pi / copy(qarray) -def d_to_q(darray): - return 2.0 * np.pi / copy(darray) +def d_to_q(d): + r""" + Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. + + Parameters + ---------- + d : 1D array + The array of :math:`d` values np.array([ds]). + + Returns + ------- + q : 1D array + The array of :math:`q` values np.array([qs]). + The units of q must be reciprocal of the units of wavelength. + """ + if 0 in d: + raise ValueError(invalid_input_emsg) + return 2.0 * np.pi / copy(d)[::-1] def d_to_tth(darray, wavelength): diff --git a/tests/test_transforms.py b/tests/test_transforms.py index e8b15492..db35d1ef 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from diffpy.utils.transforms import q_to_tth, tth_to_q +from diffpy.utils.transforms import d_to_q, q_to_d, q_to_tth, tth_to_q params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays @@ -64,7 +64,7 @@ def test_q_to_tth_bad(inputs, expected): np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) - # expected q vales are sin15, sin30, sin45, sin60, sin90 + # expected q values are sin15, sin30, sin45, sin60, sin90 ( [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), @@ -96,3 +96,73 @@ def test_tth_to_q(inputs, expected): def test_tth_to_q_bad(inputs, expected): with pytest.raises(expected[0], match=expected[1]): tth_to_q(inputs[1], inputs[0]) + + +params_q_to_d = [ + # UC1: User specified empty q values + ([np.array([])], np.array([])), + # UC2: User specified valid q values + ( + [np.array([np.pi / 6, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([0.4, 0.5, 0.66667, 1, 2, 12]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_d) +def test_q_to_d(inputs, expected): + actual = q_to_d(inputs[0]) + assert np.allclose(actual, expected) + + +params_q_to_d_bad = [ + # UC1: user specified an invalid q value that results in an infinite d value + ( + [np.array([0, 1, 2, 3, 4])], + [ + ValueError, + "Input values have resulted in an infinite output. Please ensure there are no zeros in the input.", + ], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_d_bad) +def test_q_to_d_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + q_to_d(inputs[0]) + + +params_d_to_q = [ + # UC1: User specified empty d values + ([np.array([])], np.array([])), + # UC2: User specified valid d values + ( + [np.array([np.pi / 6, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([0.4, 0.5, 0.66667, 1, 2, 12]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_q) +def test_d_to_q(inputs, expected): + actual = d_to_q(inputs[0]) + assert np.allclose(actual, expected) + + +params_d_to_q_bad = [ + # UC1: user specified an invalid d value that results in an infinite q value + ( + [np.array([0, 1, 2, 3, 4])], + [ + ValueError, + "Input values have resulted in an infinite output. Please ensure there are no zeros in the input.", + ], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_q_bad) +def test_d_to_q_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + d_to_q(inputs[0]) From f635f4d8b9c89709ca2728327aa1e5b3e77bae21 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 16:26:51 -0500 Subject: [PATCH 321/696] Add sig fig to 13 and remove comment not needed --- tests/test_diffraction_objects.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a484de39..f0c6c2be 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -343,8 +343,7 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize("inputs, expected", tc_params) def test_constructor(inputs, expected): actual_do = DiffractionObject(**inputs) - diff = DeepDiff(actual_do.__dict__, expected, ignore_order=True, significant_digits=4) - # Ensure there is no difference + diff = DeepDiff(actual_do.__dict__, expected, ignore_order=True, significant_digits=13) assert diff == {} From b7fa23e8c850f52421890b5394a21c3f1dd01a08 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 16:30:45 -0500 Subject: [PATCH 322/696] Use all close for comparing expected all_array value --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index f0c6c2be..b7d8eae3 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -361,7 +361,7 @@ def test_all_array_getter(): [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], ] ) - assert np.array_equal(actual_do.all_arrays, expected_all_arrays) + assert np.allclose(actual_do.all_arrays, expected_all_arrays) def test_all_array_setter(): From ba889ece9f9633e9a0d7f9fed108ab9427e5eb84 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 16:33:49 -0500 Subject: [PATCH 323/696] Reset scattering_obj news --- news/scattering_obt.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst index e5177fde..e1f0cf53 100644 --- a/news/scattering_obt.rst +++ b/news/scattering_obt.rst @@ -19,3 +19,5 @@ * **Security:** + +* From d20d2bcd97600831f991fea80d7b3a3bb59926bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:33:58 +0000 Subject: [PATCH 324/696] [pre-commit.ci] auto fixes from pre-commit hooks --- news/scattering_obt.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst index e1f0cf53..0090b88e 100644 --- a/news/scattering_obt.rst +++ b/news/scattering_obt.rst @@ -20,4 +20,4 @@ **Security:** -* +* From 76d89755d006ec785872b63baf7790a58155e0f0 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 10:49:33 -0500 Subject: [PATCH 325/696] change value error to warning msg --- src/diffpy/utils/transforms.py | 14 ++++------- tests/test_transforms.py | 44 ++++------------------------------ 2 files changed, 9 insertions(+), 49 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index f13632f8..b4a74bf7 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,9 +16,7 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -invalid_input_emsg = ( - "Input values have resulted in an infinite output. Please ensure there are no zeros in the input." -) +inf_output_msg = "WARNING: The largest output is infinite and cannot be plotted." def _validate_inputs(q, wavelength): @@ -61,7 +59,6 @@ def q_to_tth(q, wavelength): ------- tth : 1D array The array of :math:`2\theta` values in degrees numpy.array([tths]). - This is the correct format for loading into diffpy.utils.DiffractionObject.on_tth. """ _validate_inputs(q, wavelength) q.astype(float) @@ -107,7 +104,6 @@ def tth_to_q(tth, wavelength): q : 1D array The array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. - This is the correct format for loading into diffpy.utils.DiffractionObject.on_q. """ tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): @@ -138,8 +134,8 @@ def q_to_d(q): The array of :math:`d` values np.array([ds]). """ if 0 in q: - raise ValueError(invalid_input_emsg) - return 2.0 * np.pi / copy(q)[::-1] + warnings.warn(inf_output_msg) + return 2.0 * np.pi / copy(q) def tth_to_d(ttharray, wavelength): @@ -163,8 +159,8 @@ def d_to_q(d): The units of q must be reciprocal of the units of wavelength. """ if 0 in d: - raise ValueError(invalid_input_emsg) - return 2.0 * np.pi / copy(d)[::-1] + warnings.warn(inf_output_msg) + return 2.0 * np.pi / copy(d) def d_to_tth(darray, wavelength): diff --git a/tests/test_transforms.py b/tests/test_transforms.py index db35d1ef..6c2e4c6e 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -103,8 +103,8 @@ def test_tth_to_q_bad(inputs, expected): ([np.array([])], np.array([])), # UC2: User specified valid q values ( - [np.array([np.pi / 6, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], - np.array([0.4, 0.5, 0.66667, 1, 2, 12]), + [np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0])], + np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), ), ] @@ -115,31 +115,13 @@ def test_q_to_d(inputs, expected): assert np.allclose(actual, expected) -params_q_to_d_bad = [ - # UC1: user specified an invalid q value that results in an infinite d value - ( - [np.array([0, 1, 2, 3, 4])], - [ - ValueError, - "Input values have resulted in an infinite output. Please ensure there are no zeros in the input.", - ], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_q_to_d_bad) -def test_q_to_d_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - q_to_d(inputs[0]) - - params_d_to_q = [ # UC1: User specified empty d values ([np.array([])], np.array([])), # UC2: User specified valid d values ( - [np.array([np.pi / 6, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], - np.array([0.4, 0.5, 0.66667, 1, 2, 12]), + [np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), ), ] @@ -148,21 +130,3 @@ def test_q_to_d_bad(inputs, expected): def test_d_to_q(inputs, expected): actual = d_to_q(inputs[0]) assert np.allclose(actual, expected) - - -params_d_to_q_bad = [ - # UC1: user specified an invalid d value that results in an infinite q value - ( - [np.array([0, 1, 2, 3, 4])], - [ - ValueError, - "Input values have resulted in an infinite output. Please ensure there are no zeros in the input.", - ], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_d_to_q_bad) -def test_d_to_q_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - d_to_q(inputs[0]) From 1199a4853e06a43e149f1e5a5607ee6d958832bd Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 14:16:47 -0500 Subject: [PATCH 326/696] initial commit --- src/diffpy/utils/transforms.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 71e50599..742de2a3 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,6 +16,7 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) +inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted." def _validate_inputs(q, wavelength): @@ -125,8 +126,30 @@ def q_to_d(qarray): return 2.0 * np.pi / copy(qarray) -def tth_to_d(ttharray, wavelength): - qarray = tth_to_q(ttharray, wavelength) +def tth_to_d(tth, wavelength): + r""" + Helper function to convert two-theta to d on independent variable axis. + + Uses the formula .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. + + Parameters + ---------- + tth : 1D array + The array of :math:`2\theta` values np.array([tths]). + The units of tth are expected in degrees. + + wavelength : float + Wavelength of the incoming x-rays/neutrons/electrons + + Returns + ------- + d : 1D array + The array of :math:`d` values np.array([ds]). + """ + tth = np.deg2rad(tth) + if 0 in tth: + warnings.warn(inf_output_wmsg) + qarray = tth_to_q(tth, wavelength) return 2.0 * np.pi / copy(qarray) From ccea574ba34ae69179c38ade63026d22bfc7321a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 14:20:28 -0500 Subject: [PATCH 327/696] better name for the warning msg --- src/diffpy/utils/transforms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index b4a74bf7..bc6416ea 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,7 +16,7 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_msg = "WARNING: The largest output is infinite and cannot be plotted." +inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted." def _validate_inputs(q, wavelength): @@ -134,7 +134,7 @@ def q_to_d(q): The array of :math:`d` values np.array([ds]). """ if 0 in q: - warnings.warn(inf_output_msg) + warnings.warn(inf_output_wmsg) return 2.0 * np.pi / copy(q) @@ -159,7 +159,7 @@ def d_to_q(d): The units of q must be reciprocal of the units of wavelength. """ if 0 in d: - warnings.warn(inf_output_msg) + warnings.warn(inf_output_wmsg) return 2.0 * np.pi / copy(d) From 6f29b6ec2fba895a8124443cdff0580d6227add5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:06:10 -0500 Subject: [PATCH 328/696] initial commit --- src/diffpy/utils/transforms.py | 50 ++++++++++++---- tests/test_transforms.py | 102 ++++++++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 14 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 742de2a3..d329f607 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -12,8 +12,8 @@ "and number is the wavelength in angstroms." ) invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." -invalid_q_or_wavelength_emsg = ( - "The supplied q-array and wavelength will result in an impossible two-theta. " +invalid_q_or_d_or_wavelength_emsg = ( + "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted." @@ -25,7 +25,7 @@ def _validate_inputs(q, wavelength): return np.empty(0) pre_factor = wavelength / (4 * np.pi) if np.any(np.abs(q * pre_factor) > 1.0): - raise ValueError(invalid_q_or_wavelength_emsg) + raise ValueError(invalid_q_or_d_or_wavelength_emsg) def q_to_tth(q, wavelength): @@ -130,7 +130,9 @@ def tth_to_d(tth, wavelength): r""" Helper function to convert two-theta to d on independent variable axis. - Uses the formula .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. + The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. + + Here we convert tth to q first, then to d. Parameters ---------- @@ -146,17 +148,43 @@ def tth_to_d(tth, wavelength): d : 1D array The array of :math:`d` values np.array([ds]). """ - tth = np.deg2rad(tth) - if 0 in tth: + q = tth_to_q(tth, wavelength) + d = copy(tth) + if wavelength is None: + warnings.warn(wavelength_warning_emsg, UserWarning) + for i, _ in enumerate(tth): + d[i] = i + return d + if 0 in q: warnings.warn(inf_output_wmsg) - qarray = tth_to_q(tth, wavelength) - return 2.0 * np.pi / copy(qarray) + return 2.0 * np.pi / copy(q) def d_to_q(darray): return 2.0 * np.pi / copy(darray) -def d_to_tth(darray, wavelength): - qarray = d_to_q(darray) - return q_to_tth(qarray, wavelength) +def d_to_tth(d, wavelength): + r""" + Helper function to convert d to two-theta on independent variable axis. + + The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). + + Here we convert d to q first, then to tth. + + Parameters + ---------- + d : 1D array + The array of :math:`d` values np.array([ds]). + + wavelength : float + Wavelength of the incoming x-rays/neutrons/electrons + + Returns + ------- + tth : 1D array + The array of :math:`2\theta` values np.array([tths]). + The units of tth are expected in degrees. + """ + q = d_to_q(d) + return q_to_tth(q, wavelength) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index e8b15492..7141ba02 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from diffpy.utils.transforms import q_to_tth, tth_to_q +from diffpy.utils.transforms import d_to_tth, q_to_tth, tth_to_d, tth_to_q params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays @@ -31,7 +31,7 @@ def test_q_to_tth(inputs, expected): [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], [ ValueError, - "The supplied q-array and wavelength will result in an impossible two-theta. " + "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values.", ], ), @@ -40,7 +40,7 @@ def test_q_to_tth(inputs, expected): [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], [ ValueError, - "The supplied q-array and wavelength will result in an impossible two-theta. " + "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values.", ], ), @@ -96,3 +96,99 @@ def test_tth_to_q(inputs, expected): def test_tth_to_q_bad(inputs, expected): with pytest.raises(expected[0], match=expected[1]): tth_to_q(inputs[1], inputs[0]) + + +params_tth_to_d = [ + # UC0: User specified empty tth values (without wavelength) + ([None, np.array([])], np.array([])), + # UC1: User specified empty tth values (with wavelength) + ([4 * np.pi, np.array([])], np.array([])), + # UC2: User specified valid tth values between 0-180 degrees (without wavelength) + ( + [None, np.array([0, 30, 60, 90, 120, 180])], + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + ( + [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], + np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_d) +def test_tth_to_d(inputs, expected): + actual = tth_to_d(inputs[1], inputs[0]) + assert np.allclose(actual, expected) + + +params_tth_to_d_bad = [ + # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + [None, np.array([0, 30, 60, 90, 120, 181])], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), + # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + [4 * np.pi, np.array([0, 30, 60, 90, 120, 181])], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_d_bad) +def test_tth_to_d_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + tth_to_d(inputs[1], inputs[0]) + + +params_d_to_tth = [ + # UC1: Empty d values, no wavelength, return empty arrays + ([None, np.empty((0))], np.empty((0))), + # UC2: Empty d values, wavelength specified, return empty arrays + ([4 * np.pi, np.empty((0))], np.empty(0)), + # UC3: User specified valid d values, no wavelength, return empty arrays + ( + [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC4: User specified valid d values (with wavelength) + ( + [4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi])], + np.array([60.0, 90.0, 120.0]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_tth) +def test_d_to_tth(inputs, expected): + actual = d_to_tth(inputs[1], inputs[0]) + assert np.allclose(expected, actual) + + +params_d_to_tth_bad = [ + # UC1: user specified invalid d values that result in tth > 180 degrees + ( + [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], + [ + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", + ], + ), + # UC2: user specified a wrong wavelength that result in tth > 180 degrees + ( + [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + [ + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", + ], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_tth_bad) +def test_d_to_tth_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + d_to_tth(inputs[1], inputs[0]) From 5c3fa5c1d9680059f27bc42e9c500294461bcabf Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:10:24 -0500 Subject: [PATCH 329/696] edit test cases --- tests/test_transforms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 7141ba02..34652775 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -149,7 +149,7 @@ def test_tth_to_d_bad(inputs, expected): ([4 * np.pi, np.empty((0))], np.empty(0)), # UC3: User specified valid d values, no wavelength, return empty arrays ( - [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + [None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])], np.array([0, 1, 2, 3, 4, 5]), ), # UC4: User specified valid d values (with wavelength) @@ -169,7 +169,7 @@ def test_d_to_tth(inputs, expected): params_d_to_tth_bad = [ # UC1: user specified invalid d values that result in tth > 180 degrees ( - [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], + [4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2])], [ ValueError, "The supplied input array and wavelength will result in an impossible two-theta. " @@ -178,7 +178,7 @@ def test_d_to_tth(inputs, expected): ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + [100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])], [ ValueError, "The supplied input array and wavelength will result in an impossible two-theta. " From 25db4e7bc16eca717a5904793ad0b0e6991feaf8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:11:23 -0500 Subject: [PATCH 330/696] add news --- news/d-tth.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/d-tth.rst diff --git a/news/d-tth.rst b/news/d-tth.rst new file mode 100644 index 00000000..2bfc091f --- /dev/null +++ b/news/d-tth.rst @@ -0,0 +1,23 @@ +**Added:** + +* functions to convert between d and tth + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 79399a20d42b92ab40061896670f322250db74c9 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:14:53 -0500 Subject: [PATCH 331/696] add warning message about wavelength in tth_to_q --- src/diffpy/utils/transforms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index bc6416ea..92689284 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -113,6 +113,7 @@ def tth_to_q(tth, wavelength): pre_factor = (4.0 * np.pi) / wavelength q = pre_factor * np.sin(np.deg2rad(tth / 2)) else: # return intensities vs. an x-array that is just the index + warnings.warn(wavelength_warning_emsg, UserWarning) for i, _ in enumerate(q): q[i] = i return q From 664df765d7bea7c5288ee357974177868b803a6b Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:15:26 -0500 Subject: [PATCH 332/696] add news --- news/d-q.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/d-q.rst diff --git a/news/d-q.rst b/news/d-q.rst new file mode 100644 index 00000000..92105f07 --- /dev/null +++ b/news/d-q.rst @@ -0,0 +1,23 @@ +**Added:** + +* functions to convert between d and q + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 2e1853af96d0dd2b13d5bd3e89df12afade2fd0f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:35:33 -0500 Subject: [PATCH 333/696] edit docstring --- src/diffpy/utils/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index b1ed5a78..d8e86f58 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -377,7 +377,7 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): def on_xtype(self, xtype): f""" - return a 2D np array with x in the first column and y in the second for x of type type + return a list of two 1D np array with x and y data, raise an error if the specified xtype is invalid Parameters ---------- @@ -386,7 +386,7 @@ def on_xtype(self, xtype): Returns ------- - a 2D np array with x and y data + a list of two 1D np array with x and y data """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() From 3dac06a06cdae0f58d75ad6f936979dd25a82403 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:42:48 -0500 Subject: [PATCH 334/696] simplify tests --- news/array_index.rst | 23 +++++++++++++ src/diffpy/utils/diffraction_objects.py | 20 ++--------- tests/test_diffraction_objects.py | 46 +++---------------------- 3 files changed, 30 insertions(+), 59 deletions(-) create mode 100644 news/array_index.rst diff --git a/news/array_index.rst b/news/array_index.rst new file mode 100644 index 00000000..6f687373 --- /dev/null +++ b/news/array_index.rst @@ -0,0 +1,23 @@ +**Added:** + +* function to return the index of the closest value to the specified value in an array. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 3a0f88bc..48fffa7f 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -266,26 +266,10 @@ def get_array_index(self, value, xtype=None): if xtype is None: xtype = self.input_xtype - if self.on_xtype(xtype) is None or len(self.on_xtype(xtype)[0]) == 0: - raise ValueError( - f"The '{xtype}' array is empty. " "Please ensure it is initialized and the correct xtype is used." - ) array = self.on_xtype(xtype)[0] + if len(array) == 0: + raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") i = (np.abs(array - value)).argmin() - nearest_value = np.abs(array[i] - value) - distance = min(np.abs(value - array.min()), np.abs(value - array.max())) - threshold = 0.5 * (array.max() - array.min()) - - if nearest_value != 0 and (array.min() <= value <= array.max() or distance <= threshold): - warnings.warn( - f"WARNING: The value {value} is not an exact match of the '{xtype}' array. " - f"Returning the index of the closest value." - ) - elif distance > threshold: - raise IndexError( - f"The value {value} is too far from any value in the '{xtype}' array. " - f"Please check if you have specified the correct xtype. " - ) return i def _set_xarrays(self, xarray, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 1bf3a459..6fa81d3e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -218,7 +218,7 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte # UC2: target value lies in the array, returns the (first) closest index ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45], [0]), ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25], [0]), - # UC3: target value out of the range but within reasonable distance, returns the closest index + # UC3: target value out of the range, returns the closest index ([4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1], [0]), ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63], [1]), ] @@ -231,46 +231,10 @@ def test_get_array_index(inputs, expected): assert actual == expected[0] -params_index_bad = [ - # UC0: empty array - ( - [2 * np.pi, np.array([]), np.array([]), "tth", "tth", 30], - [ValueError, "The 'tth' array is empty. Please ensure it is initialized and the correct xtype is used."], - ), - # UC1: empty array (because of invalid xtype) - ( - [2 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "invalid", 30], - [ - ValueError, - "The 'invalid' array is empty. Please ensure it is initialized and the correct xtype is used.", - ], - ), - # UC3: value is too far from any element in the array - ( - [2 * np.pi, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "tth", 140], - [ - IndexError, - "The value 140 is too far from any value in the 'tth' array. " - "Please check if you have specified the correct xtype.", - ], - ), - # UC4: value is too far from any element in the array (because of wrong xtype) - ( - [2 * np.pi, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "q", 30], - [ - IndexError, - "The value 30 is too far from any value in the 'q' array. " - "Please check if you have specified the correct xtype.", - ], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_index_bad) -def test_get_array_index_bad(inputs, expected): - test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) - with pytest.raises(expected[0], match=re.escape(expected[1])): - test.get_array_index(value=inputs[5], xtype=inputs[4]) +def test_get_array_index_bad(): + test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") + with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")): + test.get_array_index(value=30) def test_dump(tmp_path, mocker): From 6295aa5aa83df68357b961d2c70f12f49602d4c8 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 6 Dec 2024 19:49:10 -0500 Subject: [PATCH 335/696] small tweak to infinite d message --- src/diffpy/utils/transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 92689284..a762529a 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,7 +16,7 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted." +inf_output_wmsg = "INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): @@ -135,7 +135,7 @@ def q_to_d(q): The array of :math:`d` values np.array([ds]). """ if 0 in q: - warnings.warn(inf_output_wmsg) + print(inf_output_wmsg) return 2.0 * np.pi / copy(q) From 39f1c542a854fcf2e462a7190f833efc0ff87d12 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 00:49:19 +0000 Subject: [PATCH 336/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/transforms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index a762529a..0d02fe54 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,7 +16,9 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_wmsg = "INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted." +inf_output_wmsg = ( + "INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted." +) def _validate_inputs(q, wavelength): From 7eb0c0d237392553b79530de63b778b8bce19b3d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 20:11:28 -0500 Subject: [PATCH 337/696] remove warning in error msg --- src/diffpy/utils/diffraction_objects.py | 2 +- tests/test_diffraction_objects.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index d8e86f58..703baf03 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -21,7 +21,7 @@ def _xtype_wmsg(xtype): return ( - f"WARNING: I don't know how to handle the xtype, '{xtype}'. Please rerun specifying an " + f"I don't know how to handle the xtype, '{xtype}'. Please rerun specifying an " f"xtype from {*XQUANTITIES, }" ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 95848111..2371838f 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -215,7 +215,7 @@ def test_on_xtype_bad(): with pytest.raises( ValueError, match=re.escape( - f"WARNING: I don't know how to handle the xtype, 'invalid'. Please rerun specifying an " + f"I don't know how to handle the xtype, 'invalid'. Please rerun specifying an " f"xtype from {*XQUANTITIES, }" ), ): From 3588f571b9accc239f8c43e4fec84428d751e8e3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 16:24:10 -0500 Subject: [PATCH 338/696] Add New release checklist BG standard: python -m build and twine check dist/* --- .github/ISSUE_TEMPLATE/release_checklist.md | 24 +++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index ba1d40ce..11df804d 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -12,29 +12,31 @@ assignees: "" - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are - missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. + missing), tutorials, and other human-written text is up-to-date with any changes in the code. +- [ ] Installation instructions in the README, documentation, and the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). +- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: -### PyPI/GitHub full-release preparation checklist: -- [ ] Create a new conda environment and install the rc from pypi (`pip install =??`) -- [ ] License information at Pypi is verified as correct. -- [ ] Docs deployed successfully to `.github.io` -- [ ] Successfully run all tests, tutorial examples or do functional testing +### PyPI/GitHub full-release preparation checklist: -Please let @sbillinge know that all checks are done and package is ready for full release. +- [ ] Create a new conda environment and install the rc from PyPI (`pip install ==??`) +- [ ] License information on PyPI is correct. +- [ ] Docs are deployed successfully to `https://www.diffpy.org/`. +- [ ] Successfully run all tests, tutorial examples or do functional testing. + +Please let @sbillinge know that all checks are done and the package is ready for full release. ### conda-forge release preparation checklist: -- [ ] Ensure that the full release has appeared on Pypi successfully +- [ ] Ensure that the full release has appeared on PyPI successfully. - [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. -- [ ] Close any open issues on the feedstock. Reach out to @bobleesj if you have questions -- [ ] let @sbillinge and @bobleesj when this is ready +- [ ] Close any open issues on the feedstock. Reach out to @bobleesj if you have questions. +- [ ] Tag @sbillinge and @bobleesj for conda-forge release. ### Post-release checklist From 6c5d79b9d2161732cba5262afb70484b07927634 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 16:44:41 -0500 Subject: [PATCH 339/696] Add copy method for instance --- src/diffpy/utils/diffraction_objects.py | 11 +++++++++++ tests/test_diffraction_objects.py | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6ce965d0..888f83c9 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -445,3 +445,14 @@ def dump(self, filepath, xtype=None): f.write(f"{key} = {value}\n") f.write("\n#### start data\n") np.savetxt(f, data_to_save, delimiter=" ") + + def copy(self): + """ + Create a deep copy of the DiffractionObject instance. + + Returns + ------- + DiffractionObject + A new instance of DiffractionObject, which is a deep copy of the current instance. + """ + return deepcopy(self) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aeba1522..b65521cd 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -410,3 +410,15 @@ def test_all_array_setter(): "Please use 'insert_scattering_quantity' to modify `all_arrays`.", ): actual_do.all_arrays = np.empty((4, 4)) + + +def test_copy_object(): + do = DiffractionObject( + name="test", + wavelength=4.0 * np.pi, + xarray=np.array([0.0, 90.0, 180.0]), + yarray=np.array([1.0, 2.0, 3.0]), + xtype="tth", + ) + copy_of_DO = do.copy() + assert do == copy_of_DO From 005a19168e3662a8c0cd4681b20834b72c58710a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 16:44:46 -0500 Subject: [PATCH 340/696] Add news --- news/deepcopy.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/deepcopy.rst diff --git a/news/deepcopy.rst b/news/deepcopy.rst new file mode 100644 index 00000000..578d360c --- /dev/null +++ b/news/deepcopy.rst @@ -0,0 +1,23 @@ +**Added:** + +* copy() method for DiffractionObject instance + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 33ce9b3393e687282bf7e6652d4228e3a880257c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 16:58:45 -0500 Subject: [PATCH 341/696] Add one more test for different memory pointed by copied obj --- tests/test_diffraction_objects.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index b65521cd..dcb99ac6 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -422,3 +422,5 @@ def test_copy_object(): ) copy_of_DO = do.copy() assert do == copy_of_DO + assert id(do) != id(copy_of_DO) + From edfc415d107e468e3f4dcb1c70aff4c1b663595a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:58:52 +0000 Subject: [PATCH 342/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index dcb99ac6..81f6ce8c 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -423,4 +423,3 @@ def test_copy_object(): copy_of_DO = do.copy() assert do == copy_of_DO assert id(do) != id(copy_of_DO) - From 876c15ee41c164e91949fbc0f75530cd7bce3d88 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 17:00:38 -0500 Subject: [PATCH 343/696] enhance variable name --- tests/test_diffraction_objects.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index dcb99ac6..dcdd4f59 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -420,7 +420,6 @@ def test_copy_object(): yarray=np.array([1.0, 2.0, 3.0]), xtype="tth", ) - copy_of_DO = do.copy() - assert do == copy_of_DO - assert id(do) != id(copy_of_DO) - + do_copy = do.copy() + assert do == do_copy + assert id(do) != id(do_copy) From a6ddd5b29340a7f72930488246ada44d605fce24 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Sat, 7 Dec 2024 17:56:29 -0500 Subject: [PATCH 344/696] more info on global config for users --- news/config-UX.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/tools.py | 4 ++++ 2 files changed, 27 insertions(+) create mode 100644 news/config-UX.rst diff --git a/news/config-UX.rst b/news/config-UX.rst new file mode 100644 index 00000000..05f66a10 --- /dev/null +++ b/news/config-UX.rst @@ -0,0 +1,23 @@ +**Added:** + +* additional information to users to relieve frustration in finding how to update global config + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 82cca8c9..728d8601 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -83,6 +83,10 @@ def _create_global_config(args): return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) + print( + f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" + f"Or you can update the config file by passing new values to get_user_info(), " + f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html") return return_bool From 15d14f520597a195910aec5ea09c6ebd7109348a Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Sat, 7 Dec 2024 17:58:19 -0500 Subject: [PATCH 345/696] pre-commit fix --- src/diffpy/utils/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 728d8601..7e5e858f 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -86,7 +86,8 @@ def _create_global_config(args): print( f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" f"Or you can update the config file by passing new values to get_user_info(), " - f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html") + f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html" + ) return return_bool From ee190b9b6c271937f823ed1821a265f6dbcfa4d1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 20:41:19 -0500 Subject: [PATCH 346/696] valid xtype and use getter/setting for xtype --- src/diffpy/utils/diffraction_objects.py | 48 +++++++++++++++++++------ 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6ce965d0..6316f2a2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -26,6 +26,13 @@ def _xtype_wmsg(xtype): ) +def _setter_wmsg(attribute): + return ( + f"Direct modification of attribute '{attribute}' is not allowed." + f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", + ) + + class DiffractionObject: def __init__( self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype=None @@ -45,6 +52,7 @@ def __init__( xarray = np.empty(0) if yarray is None: yarray = np.empty(0) + self.insert_scattering_quantity(xarray, yarray, xtype) def __eq__(self, other): @@ -186,11 +194,16 @@ def all_arrays(self): return self._all_arrays @all_arrays.setter - def all_arrays(self, value): - raise AttributeError( - "Direct modification of attribute 'all_arrays' is not allowed." - "Please use 'insert_scattering_quantity' to modify `all_arrays`." - ) + def all_arrays(self, _): + raise AttributeError(_setter_wmsg("all_arrays")) + + @property + def xtype(self): + return self._xtype + + @xtype.setter + def xtype(self, _): + raise AttributeError(_setter_wmsg("xtype")) def set_angles_from_list(self, angles_list): self.angles = angles_list @@ -261,7 +274,7 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): def get_array_index(self, value, xtype=None): """ - returns the index of the closest value in the array associated with the specified xtype + Return the index of the closest value in the array associated with the specified xtype. Parameters ---------- @@ -276,7 +289,7 @@ def get_array_index(self, value, xtype=None): """ if xtype is None: - xtype = self.input_xtype + xtype = self._xtype array = self.on_xtype(xtype)[0] if len(array) == 0: raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") @@ -335,7 +348,7 @@ def insert_scattering_quantity( """ self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray - self.input_xtype = xtype + self._xtype = xtype # only update these optional values if non-empty quantities are passed to avoid overwriting # valid data inadvertently if metadata: @@ -347,12 +360,25 @@ def insert_scattering_quantity( if wavelength is not None: self.wavelength = wavelength + # Check xarray and yarray have the same length + if len(xarray) != len(yarray): + raise ValueError( + "`xarray` and `yarray` must have the same length. " + "Please re-initialize `DiffractionObject` or re-run the method `insert_scattering_quantity` " + "with `xarray` and `yarray` of identical length." + ) + + # Check xtype is valid. An empty string is the default value. + if xtype != "": + if xtype not in XQUANTITIES: + raise ValueError(_xtype_wmsg(xtype)) + def _get_original_array(self): - if self.input_xtype in QQUANTITIES: + if self._xtype in QQUANTITIES: return self.on_q(), "q" - elif self.input_xtype in ANGLEQUANTITIES: + elif self._xtype in ANGLEQUANTITIES: return self.on_tth(), "tth" - elif self.input_xtype in DQUANTITIES: + elif self._xtype in DQUANTITIES: return self.on_d(), "d" def on_q(self): From c119b1f5fc6702ab95fc4fe4b60706e4fb201ab5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 20:41:26 -0500 Subject: [PATCH 347/696] Add tests for getter and setter --- tests/test_diffraction_objects.py | 34 +++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aeba1522..141a429a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -211,16 +211,15 @@ def test_on_xtype(): assert np.allclose(test.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) -def test_on_xtype_bad(): - test = DiffractionObject() +def test_init_invalid_xtype(): with pytest.raises( ValueError, match=re.escape( - f"I don't know how to handle the xtype, 'invalid'. Please rerun specifying an " + f"I don't know how to handle the xtype, 'invalid_type'. Please rerun specifying an " f"xtype from {*XQUANTITIES, }" ), ): - test.on_xtype("invalid") + DiffractionObject(xtype="invalid_type") params_index = [ @@ -287,7 +286,7 @@ def test_dump(tmp_path, mocker): { "_all_arrays": np.empty(shape=(0, 4)), # instantiate empty "metadata": {}, - "input_xtype": "", + "_xtype": "", "name": "", "scat_quantity": None, "qmin": np.float64(np.inf), @@ -304,7 +303,7 @@ def test_dump(tmp_path, mocker): { "_all_arrays": np.empty(shape=(0, 4)), "metadata": {"thing": "1", "another": "2"}, - "input_xtype": "", + "_xtype": "", "name": "test", "scat_quantity": "x-ray", "qmin": np.float64(np.inf), @@ -332,7 +331,7 @@ def test_dump(tmp_path, mocker): ] ), "metadata": {}, - "input_xtype": "tth", + "_xtype": "tth", "name": "", "scat_quantity": None, "qmin": np.float64(0.0), @@ -361,7 +360,7 @@ def test_dump(tmp_path, mocker): ] ), "metadata": {}, - "input_xtype": "d", + "_xtype": "d", "name": "", "scat_quantity": "x-ray", "qmin": np.float64(0.0), @@ -407,6 +406,23 @@ def test_all_array_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed." - "Please use 'insert_scattering_quantity' to modify `all_arrays`.", + "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) + + +def test_xtype_getter(): + do = DiffractionObject(xtype="tth") + assert do.xtype == "tth" + + +def test_xtype_setter(): + do = DiffractionObject(xtype="tth") + + # Attempt to directly modify the property + with pytest.raises( + AttributeError, + match="Direct modification of attribute 'xtype' is not allowed." + "Please use 'insert_scattering_quantity' to modify 'xtype'.", + ): + do.xtype = "q" From 8e993a3db6a9e1555096266ed614ec1fab880598 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 20:41:33 -0500 Subject: [PATCH 348/696] Add news --- news/scattering-obj-valid.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scattering-obj-valid.rst diff --git a/news/scattering-obj-valid.rst b/news/scattering-obj-valid.rst new file mode 100644 index 00000000..adf4dfa9 --- /dev/null +++ b/news/scattering-obj-valid.rst @@ -0,0 +1,23 @@ +**Added:** + +* validate xtype belongs to XQUANTITIES during DiffractionObject init + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 74a740ac8876fbb46c011f3587e30cd2ed111e35 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Sun, 8 Dec 2024 14:25:14 -0500 Subject: [PATCH 349/696] fixed news item --- news/config-UX.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/news/config-UX.rst b/news/config-UX.rst index 05f66a10..af826723 100644 --- a/news/config-UX.rst +++ b/news/config-UX.rst @@ -1,6 +1,6 @@ **Added:** -* additional information to users to relieve frustration in finding how to update global config +* **Changed:** @@ -16,7 +16,7 @@ **Fixed:** -* +* additional information to users to relieve frustration in finding how to update global config **Security:** From d65dce1374f0754d8d19df6d0985cf1814011aea Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 8 Dec 2024 16:03:00 -0500 Subject: [PATCH 350/696] initial commit, tests need discussion --- src/diffpy/utils/diffraction_objects.py | 7 +- tests/test_diffraction_objects.py | 93 +++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6ce965d0..39f6d15c 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -366,7 +366,7 @@ def on_d(self): def scale_to(self, target_diff_object, xtype=None, xvalue=None): f""" - returns a new diffraction object which is the current object but recaled in y to the target + returns a new diffraction object which is the current object but rescaled in y to the target Parameters ---------- @@ -390,14 +390,15 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): data = self.on_xtype(xtype) target = target_diff_object.on_xtype(xtype) + if len(data[0]) == 0 or len(target[0]) == 0 or len(data[0]) != len(target[0]): + raise ValueError("I cannot scale two diffraction objects with empty or different lengths.") if xvalue is None: xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 xindex = (np.abs(data[0] - xvalue)).argmin() ytarget = target[1][xindex] yself = data[1][xindex] - scaled.on_tth[1] = data[1] * ytarget / yself - scaled.on_q[1] = data[1] * ytarget / yself + scaled._all_arrays[:, 0] = data[1] * ytarget / yself return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aeba1522..e386c7cc 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -223,6 +223,99 @@ def test_on_xtype_bad(): test.on_xtype("invalid") +params_scale_to = [ + # UC1: xvalue exact match + ( + [ + np.array([10, 15, 25, 30, 60, 140]), + np.array([10, 20, 25, 30, 60, 100]), + "tth", + 2 * np.pi, + np.array([10, 15, 25, 30, 60, 140]), + np.array([2, 3, 4, 5, 6, 7]), + "tth", + 2 * np.pi, + "tth", + 60, + ], + [np.array([1, 2, 2.5, 3, 6, 10])], + ), + # UC2: xvalue approximate match + ( + [ + np.array([0.11, 0.24, 0.31, 0.4]), + np.array([10, 20, 40, 60]), + "q", + 2 * np.pi, + np.array([0.11, 0.24, 0.31, 0.4]), + np.array([1, 3, 4, 5]), + "q", + 2 * np.pi, + "q", + 0.1, + ], + [np.array([1, 2, 4, 6])], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_scale_to) +def test_scale_to(inputs, expected): + orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + target_diff_object = DiffractionObject( + xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + ) + scaled_diff_object = orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + # Check the intensity data is same as expected + assert np.allclose(scaled_diff_object.on_xtype(inputs[8])[1], expected[0]) + + +params_scale_to_bad = [ + # UC1: at least one of the y-arrays is empty + ( + [ + np.array([]), + np.array([]), + "tth", + 2 * np.pi, + np.array([11, 14, 16, 20, 25, 30]), + np.array([2, 3, 4, 5, 6, 7]), + "tth", + 2 * np.pi, + "tth", + 60, + ] + ), + # UC2: diffraction objects with different array lengths + ( + [ + np.array([0.11, 0.24, 0.31, 0.4, 0.5]), + np.array([10, 20, 40, 50, 60]), + "q", + 2 * np.pi, + np.array([0.1, 0.15, 0.3, 0.4]), + np.array([1, 3, 4, 5]), + "q", + 2 * np.pi, + "q", + 0.1, + ] + ), +] + + +@pytest.mark.parametrize("inputs", params_scale_to_bad) +def test_scale_to_bad(inputs): + orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + target_diff_object = DiffractionObject( + xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + ) + with pytest.raises( + ValueError, match="I cannot scale two diffraction objects with empty or different lengths." + ): + orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + + params_index = [ # UC1: exact match ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), From 8431fe262ab8fcd1b75cc4d0ac01c829b79b237f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:35:06 +0000 Subject: [PATCH 351/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 666f5dbe..82add998 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -427,6 +427,7 @@ def test_xtype_setter(): ): do.xtype = "q" + def test_copy_object(): do = DiffractionObject( name="test", @@ -438,4 +439,3 @@ def test_copy_object(): do_copy = do.copy() assert do == do_copy assert id(do) != id(do_copy) - From b30cc7e1f6905295a7143d12ad04beea6d070bb1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 11:44:09 -0500 Subject: [PATCH 352/696] maintain input_xtype, while make it non-settable --- src/diffpy/utils/diffraction_objects.py | 14 +++++++------- tests/test_diffraction_objects.py | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 9d4bc323..049bf3ce 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -198,12 +198,12 @@ def all_arrays(self, _): raise AttributeError(_setter_wmsg("all_arrays")) @property - def xtype(self): - return self._xtype + def input_xtype(self): + return self._input_xtype - @xtype.setter - def xtype(self, _): - raise AttributeError(_setter_wmsg("xtype")) + @input_xtype.setter + def input_xtype(self, _): + raise AttributeError(_setter_wmsg("input_xtype")) def set_angles_from_list(self, angles_list): self.angles = angles_list @@ -289,7 +289,7 @@ def get_array_index(self, value, xtype=None): """ if xtype is None: - xtype = self._xtype + xtype = self._input_xtype array = self.on_xtype(xtype)[0] if len(array) == 0: raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") @@ -348,7 +348,7 @@ def insert_scattering_quantity( """ self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray - self._xtype = xtype + self._input_xtype = xtype # only update these optional values if non-empty quantities are passed to avoid overwriting # valid data inadvertently if metadata: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 82add998..0384d65d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -286,7 +286,7 @@ def test_dump(tmp_path, mocker): { "_all_arrays": np.empty(shape=(0, 4)), # instantiate empty "metadata": {}, - "_xtype": "", + "_input_xtype": "", "name": "", "scat_quantity": None, "qmin": np.float64(np.inf), @@ -303,7 +303,7 @@ def test_dump(tmp_path, mocker): { "_all_arrays": np.empty(shape=(0, 4)), "metadata": {"thing": "1", "another": "2"}, - "_xtype": "", + "_input_xtype": "", "name": "test", "scat_quantity": "x-ray", "qmin": np.float64(np.inf), @@ -331,7 +331,7 @@ def test_dump(tmp_path, mocker): ] ), "metadata": {}, - "_xtype": "tth", + "_input_xtype": "tth", "name": "", "scat_quantity": None, "qmin": np.float64(0.0), @@ -360,7 +360,7 @@ def test_dump(tmp_path, mocker): ] ), "metadata": {}, - "_xtype": "d", + "_input_xtype": "d", "name": "", "scat_quantity": "x-ray", "qmin": np.float64(0.0), @@ -411,21 +411,21 @@ def test_all_array_setter(): actual_do.all_arrays = np.empty((4, 4)) -def test_xtype_getter(): +def test_input_xtype_getter(): do = DiffractionObject(xtype="tth") - assert do.xtype == "tth" + assert do.input_xtype == "tth" -def test_xtype_setter(): +def test_input_xtype_setter(): do = DiffractionObject(xtype="tth") # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'xtype' is not allowed." - "Please use 'insert_scattering_quantity' to modify 'xtype'.", + match="Direct modification of attribute 'input_xtype' is not allowed." + "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", ): - do.xtype = "q" + do.input_xtype = "q" def test_copy_object(): From 2a7428cffa122e95188a9b4ec0a95f952ec81f29 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 11:53:29 -0500 Subject: [PATCH 353/696] Use _input_xtype within method --- src/diffpy/utils/diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 049bf3ce..2fedf18d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -374,11 +374,11 @@ def insert_scattering_quantity( raise ValueError(_xtype_wmsg(xtype)) def _get_original_array(self): - if self._xtype in QQUANTITIES: + if self._input_xtype in QQUANTITIES: return self.on_q(), "q" - elif self._xtype in ANGLEQUANTITIES: + elif self._input_xtype in ANGLEQUANTITIES: return self.on_tth(), "tth" - elif self._xtype in DQUANTITIES: + elif self._input_xtype in DQUANTITIES: return self.on_d(), "d" def on_q(self): From c580d82432f7a86b2ae18a093acec57d225daa03 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 11:57:33 -0500 Subject: [PATCH 354/696] Add test for mismatch for y and xarray lenght --- src/diffpy/utils/diffraction_objects.py | 17 +++++++++-------- tests/test_diffraction_objects.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2fedf18d..3b47dfb2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -346,6 +346,15 @@ def insert_scattering_quantity( Nothing. Updates the object in place. """ + + # Check xarray and yarray have the same length + if len(xarray) != len(yarray): + raise ValueError( + "'xarray' and 'yarray' must have the same length. " + "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "with 'xarray' and 'yarray' of identical length." + ) + self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray self._input_xtype = xtype @@ -360,14 +369,6 @@ def insert_scattering_quantity( if wavelength is not None: self.wavelength = wavelength - # Check xarray and yarray have the same length - if len(xarray) != len(yarray): - raise ValueError( - "`xarray` and `yarray` must have the same length. " - "Please re-initialize `DiffractionObject` or re-run the method `insert_scattering_quantity` " - "with `xarray` and `yarray` of identical length." - ) - # Check xtype is valid. An empty string is the default value. if xtype != "": if xtype not in XQUANTITIES: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0384d65d..88ac38d7 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -411,6 +411,16 @@ def test_all_array_setter(): actual_do.all_arrays = np.empty((4, 4)) +def test_xarray_yarray_length_mismatch(): + with pytest.raises( + ValueError, + match="'xarray' and 'yarray' must have the same length. " + "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "with 'xarray' and 'yarray' of identical length", + ): + DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) + + def test_input_xtype_getter(): do = DiffractionObject(xtype="tth") assert do.input_xtype == "tth" From 85290a956ad7cc337ab07d18c38dd2e445c0211f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 12:01:24 -0500 Subject: [PATCH 355/696] Remove f in the beginning of docstring --- src/diffpy/utils/diffraction_objects.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 3b47dfb2..5bfb9bd8 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -392,8 +392,8 @@ def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, xtype=None, xvalue=None): - f""" - returns a new diffraction object which is the current object but recaled in y to the target + """ + Return a new diffraction object which is the current object but recaled in y to the target Parameters ---------- @@ -428,8 +428,8 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): return scaled def on_xtype(self, xtype): - f""" - return a list of two 1D np array with x and y data, raise an error if the specified xtype is invalid + """ + Return a list of two 1D np array with x and y data, raise an error if the specified xtype is invalid Parameters ---------- From 9bf532926f6bd1f2d04a1762f581314a76933921 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 12:04:52 -0500 Subject: [PATCH 356/696] Add empty space to _xtype_wmsg --- src/diffpy/utils/diffraction_objects.py | 6 +++--- tests/test_diffraction_objects.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 5bfb9bd8..ab39e53b 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -21,14 +21,14 @@ def _xtype_wmsg(xtype): return ( - f"I don't know how to handle the xtype, '{xtype}'. Please rerun specifying an " - f"xtype from {*XQUANTITIES, }" + f"I don't know how to handle the xtype, '{xtype}'. " + f"Please rerun specifying an xtype from {*XQUANTITIES, }" ) def _setter_wmsg(attribute): return ( - f"Direct modification of attribute '{attribute}' is not allowed." + f"Direct modification of attribute '{attribute}' is not allowed. " f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 88ac38d7..d7b9b32c 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -215,8 +215,8 @@ def test_init_invalid_xtype(): with pytest.raises( ValueError, match=re.escape( - f"I don't know how to handle the xtype, 'invalid_type'. Please rerun specifying an " - f"xtype from {*XQUANTITIES, }" + f"I don't know how to handle the xtype, 'invalid_type'. " + f"Please rerun specifying an xtype from {*XQUANTITIES, }" ), ): DiffractionObject(xtype="invalid_type") @@ -405,7 +405,7 @@ def test_all_array_setter(): # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'all_arrays' is not allowed." + match="Direct modification of attribute 'all_arrays' is not allowed. " "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) @@ -432,7 +432,7 @@ def test_input_xtype_setter(): # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'input_xtype' is not allowed." + match="Direct modification of attribute 'input_xtype' is not allowed. " "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", ): do.input_xtype = "q" From 5d07b9a2ba015dd52c8ce96afc4d72606835a373 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 9 Dec 2024 17:11:53 -0500 Subject: [PATCH 357/696] add news and more tests --- news/scaleto.rst | 23 +++++ src/diffpy/utils/diffraction_objects.py | 44 +++++---- tests/test_diffraction_objects.py | 122 +++++++++++++++--------- 3 files changed, 122 insertions(+), 67 deletions(-) create mode 100644 news/scaleto.rst diff --git a/news/scaleto.rst b/news/scaleto.rst new file mode 100644 index 00000000..6f6b0635 --- /dev/null +++ b/news/scaleto.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality to rescale diffraction objects, placing one on top of another at a specified point + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 39f6d15c..0fd77746 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -364,41 +364,43 @@ def on_tth(self): def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] - def scale_to(self, target_diff_object, xtype=None, xvalue=None): - f""" + def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): + """ returns a new diffraction object which is the current object but rescaled in y to the target + The y-value in the target at the closest specified x-value will be used as the factor to scale to. + The entire array is scaled by this factor so that one object places on top of the other at that point. + If multiple values of `q`, `tth`, or `d` are provided, the priority is `q` > `tth` > `d`. + If none are provided, the midpoint of the current object's `q`-array will be used. + Parameters ---------- target_diff_object: DiffractionObject - the diffraction object you want to scale the current one on to - xtype: string, optional. Default is Q - the xtype, from {XQUANTITIES}, that you will specify a point from to scale to - xvalue: float. Default is the midpoint of the array - the y-value in the target at this x-value will be used as the factor to scale to. - The entire array is scaled be the factor that places on on top of the other at that point. - xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling. + the diffraction object you want to scale the current one onto + + q, tth, d : float, optional, default is the midpoint of the current object's `q`-array + the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects + + offset : float, optional, default is 0 + an offset to add to the scaled y-values Returns ------- the rescaled DiffractionObject as a new object - """ scaled = deepcopy(self) - if xtype is None: - xtype = "q" + xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" + data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) + if len(data[0]) == 0 or len(target[0]) == 0: + raise ValueError("I cannot scale diffraction objects with empty arrays.") - data = self.on_xtype(xtype) - target = target_diff_object.on_xtype(xtype) - if len(data[0]) == 0 or len(target[0]) == 0 or len(data[0]) != len(target[0]): - raise ValueError("I cannot scale two diffraction objects with empty or different lengths.") + xvalue = q if xtype == "q" else tth if xtype == "tth" else d if xvalue is None: - xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 + xvalue = (data[0][0] + data[0][-1]) / 2.0 - xindex = (np.abs(data[0] - xvalue)).argmin() - ytarget = target[1][xindex] - yself = data[1][xindex] - scaled._all_arrays[:, 0] = data[1] * ytarget / yself + x_data, x_target = (np.abs(data[0] - xvalue)).argmin(), (np.abs(target[0] - xvalue)).argmin() + y_data, y_target = data[1][x_data], target[1][x_target] + scaled._all_arrays[:, 0] = data[1] * y_target / y_data + offset return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e386c7cc..df8f14d3 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -224,96 +224,126 @@ def test_on_xtype_bad(): params_scale_to = [ - # UC1: xvalue exact match + # UC1: same x-array and y-array, check offset ( [ np.array([10, 15, 25, 30, 60, 140]), - np.array([10, 20, 25, 30, 60, 100]), + np.array([2, 3, 4, 5, 6, 7]), "tth", 2 * np.pi, np.array([10, 15, 25, 30, 60, 140]), np.array([2, 3, 4, 5, 6, 7]), "tth", 2 * np.pi, + None, + 60, + None, + 2.1, + ], + ["tth", np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])], + ), + # UC2: same length x-arrays with exact x-value match + ( + [ + np.array([10, 15, 25, 30, 60, 140]), + np.array([10, 20, 25, 30, 60, 100]), + "tth", + 2 * np.pi, + np.array([10, 20, 25, 30, 60, 140]), + np.array([2, 3, 4, 5, 6, 7]), "tth", + 2 * np.pi, + None, 60, + None, + 0, ], - [np.array([1, 2, 2.5, 3, 6, 10])], + ["tth", np.array([1, 2, 2.5, 3, 6, 10])], ), - # UC2: xvalue approximate match + # UC3: same length x-arrays with approximate x-value match ( [ - np.array([0.11, 0.24, 0.31, 0.4]), + np.array([0.12, 0.24, 0.31, 0.4]), np.array([10, 20, 40, 60]), "q", 2 * np.pi, - np.array([0.11, 0.24, 0.31, 0.4]), + np.array([0.14, 0.24, 0.31, 0.4]), np.array([1, 3, 4, 5]), "q", 2 * np.pi, - "q", 0.1, + None, + None, + 0, ], - [np.array([1, 2, 4, 6])], + ["q", np.array([1, 2, 4, 6])], ), -] - - -@pytest.mark.parametrize("inputs, expected", params_scale_to) -def test_scale_to(inputs, expected): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) - target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] - ) - scaled_diff_object = orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) - # Check the intensity data is same as expected - assert np.allclose(scaled_diff_object.on_xtype(inputs[8])[1], expected[0]) - - -params_scale_to_bad = [ - # UC1: at least one of the y-arrays is empty + # UC4: different x-array lengths with approximate x-value match ( [ - np.array([]), - np.array([]), + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), "tth", 2 * np.pi, - np.array([11, 14, 16, 20, 25, 30]), - np.array([2, 3, 4, 5, 6, 7]), + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), "tth", 2 * np.pi, - "tth", + None, 60, - ] + None, + 0, + ], + # scaling factor is calculated at index = 5 for self and index = 6 for target + ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), - # UC2: diffraction objects with different array lengths + # UC5: user specified multiple x-values, prioritize q > tth > d ( [ - np.array([0.11, 0.24, 0.31, 0.4, 0.5]), - np.array([10, 20, 40, 50, 60]), - "q", + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), + "tth", 2 * np.pi, - np.array([0.1, 0.15, 0.3, 0.4]), - np.array([1, 3, 4, 5]), - "q", + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "tth", 2 * np.pi, - "q", - 0.1, - ] + None, + 60, + 10, + 0, + ], + ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), ] -@pytest.mark.parametrize("inputs", params_scale_to_bad) -def test_scale_to_bad(inputs): +@pytest.mark.parametrize("inputs, expected", params_scale_to) +def test_scale_to(inputs, expected): orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) target_diff_object = DiffractionObject( xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] ) - with pytest.raises( - ValueError, match="I cannot scale two diffraction objects with empty or different lengths." - ): - orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + scaled_diff_object = orig_diff_object.scale_to( + target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11] + ) + # Check the intensity data is same as expected + assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) + + +def test_scale_to_bad(): + # UC1: at least one of the y-arrays is empty + orig_diff_object = DiffractionObject( + xarray=np.array([]), yarray=np.array([]), xtype="tth", wavelength=2 * np.pi + ) + target_diff_object = DiffractionObject( + xarray=np.array([11, 14, 16, 20, 25, 30]), + yarray=np.array([2, 3, 4, 5, 6, 7]), + xtype="tth", + wavelength=2 * np.pi, + ) + with pytest.raises(ValueError, match="I cannot scale diffraction objects with empty arrays."): + orig_diff_object.scale_to(target_diff_object, tth=20) params_index = [ From 3c5a426929fc8eb935375befa4841badfbd0202a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 10 Dec 2024 11:40:27 -0500 Subject: [PATCH 358/696] remove error msg, change copy method --- src/diffpy/utils/diffraction_objects.py | 4 +--- tests/test_diffraction_objects.py | 15 --------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1e7ee05d..e1b45cd1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -388,11 +388,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): ------- the rescaled DiffractionObject as a new object """ - scaled = deepcopy(self) + scaled = self.copy() xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) - if len(data[0]) == 0 or len(target[0]) == 0: - raise ValueError("I cannot scale diffraction objects with empty arrays.") xvalue = q if xtype == "q" else tth if xtype == "tth" else d if xvalue is None: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e066c025..2903bb49 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -331,21 +331,6 @@ def test_scale_to(inputs, expected): assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) -def test_scale_to_bad(): - # UC1: at least one of the y-arrays is empty - orig_diff_object = DiffractionObject( - xarray=np.array([]), yarray=np.array([]), xtype="tth", wavelength=2 * np.pi - ) - target_diff_object = DiffractionObject( - xarray=np.array([11, 14, 16, 20, 25, 30]), - yarray=np.array([2, 3, 4, 5, 6, 7]), - xtype="tth", - wavelength=2 * np.pi, - ) - with pytest.raises(ValueError, match="I cannot scale diffraction objects with empty arrays."): - orig_diff_object.scale_to(target_diff_object, tth=20) - - params_index = [ # UC1: exact match ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), From 73b2f46df52bf51a26dbbde4719605cfddc62acc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 11 Dec 2024 21:56:38 -0500 Subject: [PATCH 359/696] Remove unused test util func, fix test for comparing two objects --- tests/test_diffraction_objects.py | 48 ++----------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index dcdd4f59..a8678fd8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -8,43 +8,6 @@ from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject - -def compare_dicts(dict1, dict2): - assert dict1.keys() == dict2.keys(), "Keys mismatch" - for key in dict1: - val1, val2 = dict1[key], dict2[key] - if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): - assert np.allclose(val1, val2), f"Arrays for key '{key}' differ" - elif isinstance(val1, np.float64) and isinstance(val2, np.float64): - assert np.isclose(val1, val2), f"Float64 values for key '{key}' differ" - else: - assert val1 == val2, f"Values for key '{key}' differ: {val1} != {val2}" - - -def dicts_equal(dict1, dict2): - equal = True - print("") - print(dict1) - print(dict2) - if not dict1.keys() == dict2.keys(): - equal = False - for key in dict1: - val1, val2 = dict1[key], dict2[key] - if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): - if not np.allclose(val1, val2): - equal = False - elif isinstance(val1, list) and isinstance(val2, list): - if not val1.all() == val2.all(): - equal = False - elif isinstance(val1, np.float64) and isinstance(val2, np.float64): - if not np.isclose(val1, val2): - equal = False - else: - if not val1 == val2: - equal = False - return equal - - params = [ ( # Default {}, @@ -193,14 +156,9 @@ def dicts_equal(dict1, dict2): @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = DiffractionObject(**inputs1) - diffraction_object2 = DiffractionObject(**inputs2) - # diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] - # for i, attribute in enumerate(diffraction_object1_attributes): - # setattr(diffraction_object1, attribute, inputs1[i]) - # setattr(diffraction_object2, attribute, inputs2[i]) - print(dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__), expected) - assert dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__) == expected + do_1 = DiffractionObject(**inputs1) + do_2 = DiffractionObject(**inputs2) + assert (do_1 == do_2) == expected def test_on_xtype(): From 919c60bab6c0c2d7b77503db6edbebd423230fe5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 12:48:11 -0500 Subject: [PATCH 360/696] ename to in init --- news/mv-input-scattering-quan.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 news/mv-input-scattering-quan.rst diff --git a/news/mv-input-scattering-quan.rst b/news/mv-input-scattering-quan.rst new file mode 100644 index 00000000..25e8aac9 --- /dev/null +++ b/news/mv-input-scattering-quan.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Rename `input_scattering_quantity` to `input_data` in `DiffractionObject` init + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ab39e53b..fe853d02 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -53,7 +53,7 @@ def __init__( if yarray is None: yarray = np.empty(0) - self.insert_scattering_quantity(xarray, yarray, xtype) + self.input_data(xarray, yarray, xtype) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -317,7 +317,7 @@ def _set_xarrays(self, xarray, xtype): self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf) self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0) - def insert_scattering_quantity( + def input_data( self, xarray, yarray, From f311988add3866b2e18c2a2185fde6d2a31f7811 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 12:56:26 -0500 Subject: [PATCH 361/696] Fix insert_scattering_quantity to input data in all other files globally --- src/diffpy/utils/diffraction_objects.py | 4 ++-- tests/test_diffraction_objects.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index fe853d02..59aac37b 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -29,7 +29,7 @@ def _xtype_wmsg(xtype): def _setter_wmsg(attribute): return ( f"Direct modification of attribute '{attribute}' is not allowed. " - f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", + f"Please use 'input_data' to modify '{attribute}'.", ) @@ -351,7 +351,7 @@ def input_data( if len(xarray) != len(yarray): raise ValueError( "'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index f93190ae..328bb512 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -364,7 +364,7 @@ def test_all_array_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed. " - "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", + "Please use 'input_data' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) @@ -373,7 +373,7 @@ def test_xarray_yarray_length_mismatch(): with pytest.raises( ValueError, match="'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length", ): DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) @@ -391,7 +391,7 @@ def test_input_xtype_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'input_xtype' is not allowed. " - "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", + "Please use 'input_data' to modify 'input_xtype'.", ): do.input_xtype = "q" From b97db395aefd2df297e20f60f5816f2264e93bc4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 13:28:57 -0500 Subject: [PATCH 362/696] Add uuid property to DiffractionObject --- src/diffpy/utils/diffraction_objects.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ab39e53b..abfd44b0 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -1,4 +1,5 @@ import datetime +import uuid import warnings from copy import deepcopy @@ -29,7 +30,7 @@ def _xtype_wmsg(xtype): def _setter_wmsg(attribute): return ( f"Direct modification of attribute '{attribute}' is not allowed. " - f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", + f"Please use 'input_data' to modify '{attribute}'.", ) @@ -53,7 +54,8 @@ def __init__( if yarray is None: yarray = np.empty(0) - self.insert_scattering_quantity(xarray, yarray, xtype) + self._id = uuid.uuid4() + self.input_data(xarray, yarray, xtype) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -205,6 +207,14 @@ def input_xtype(self): def input_xtype(self, _): raise AttributeError(_setter_wmsg("input_xtype")) + @property + def id(self): + return self._id + + @id.setter + def id(self, _): + raise AttributeError(_setter_wmsg("id")) + def set_angles_from_list(self, angles_list): self.angles = angles_list self.n_steps = len(angles_list) - 1.0 @@ -317,7 +327,7 @@ def _set_xarrays(self, xarray, xtype): self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf) self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0) - def insert_scattering_quantity( + def input_data( self, xarray, yarray, @@ -351,7 +361,7 @@ def insert_scattering_quantity( if len(xarray) != len(yarray): raise ValueError( "'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length." ) From 5d87cdcaef215d783cbc05042f6c717e134f17c1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 13:29:12 -0500 Subject: [PATCH 363/696] Modify tests functions to test gettable id property --- tests/test_diffraction_objects.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index f93190ae..ef758b9d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -1,5 +1,7 @@ import re +import uuid from pathlib import Path +from uuid import UUID import numpy as np import pytest @@ -335,8 +337,8 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize("inputs, expected", tc_params) def test_constructor(inputs, expected): - actual_do = DiffractionObject(**inputs) - diff = DeepDiff(actual_do.__dict__, expected, ignore_order=True, significant_digits=13) + actual = DiffractionObject(**inputs).__dict__ + diff = DeepDiff(actual, expected, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") assert diff == {} @@ -364,16 +366,33 @@ def test_all_array_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed. " - "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", + "Please use 'input_data' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) +def test_id_getter(): + do = DiffractionObject() + assert hasattr(do, "id") + assert isinstance(do.id, UUID) + assert len(str(do.id)) == 36 + + +def test_id_setter(): + do = DiffractionObject() + # Attempt to directly modify the property + with pytest.raises( + AttributeError, + match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", + ): + do.id = uuid.uuid4() + + def test_xarray_yarray_length_mismatch(): with pytest.raises( ValueError, match="'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length", ): DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) @@ -391,7 +410,7 @@ def test_input_xtype_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'input_xtype' is not allowed. " - "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", + "Please use 'input_data' to modify 'input_xtype'.", ): do.input_xtype = "q" From cd535869827ddc77441597ad3fbc91b854e572b0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 13:29:17 -0500 Subject: [PATCH 364/696] Add news --- news/uuid.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/uuid.rst diff --git a/news/uuid.rst b/news/uuid.rst new file mode 100644 index 00000000..474793f4 --- /dev/null +++ b/news/uuid.rst @@ -0,0 +1,23 @@ +**Added:** + +* Gettable `id` property to `DiffractionObject` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 6169620dce28ab3f599b16c371ff29fe26a4133e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 13:31:36 -0500 Subject: [PATCH 365/696] Use insert_scattering_quantity for now before PR merged --- src/diffpy/utils/diffraction_objects.py | 8 ++++---- tests/test_diffraction_objects.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index abfd44b0..0ec305bc 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -30,7 +30,7 @@ def _xtype_wmsg(xtype): def _setter_wmsg(attribute): return ( f"Direct modification of attribute '{attribute}' is not allowed. " - f"Please use 'input_data' to modify '{attribute}'.", + f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", ) @@ -55,7 +55,7 @@ def __init__( yarray = np.empty(0) self._id = uuid.uuid4() - self.input_data(xarray, yarray, xtype) + self.insert_scattering_quantity(xarray, yarray, xtype) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -327,7 +327,7 @@ def _set_xarrays(self, xarray, xtype): self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf) self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0) - def input_data( + def insert_scattering_quantity( self, xarray, yarray, @@ -361,7 +361,7 @@ def input_data( if len(xarray) != len(yarray): raise ValueError( "'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " + "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " "with 'xarray' and 'yarray' of identical length." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ef758b9d..b54d7a02 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -366,7 +366,7 @@ def test_all_array_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed. " - "Please use 'input_data' to modify 'all_arrays'.", + "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) @@ -383,7 +383,7 @@ def test_id_setter(): # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", + match="Direct modification of attribute 'id' is not allowed. Please use 'insert_scattering_quantity' to modify 'id'.", ): do.id = uuid.uuid4() @@ -392,7 +392,7 @@ def test_xarray_yarray_length_mismatch(): with pytest.raises( ValueError, match="'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " + "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " "with 'xarray' and 'yarray' of identical length", ): DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) @@ -410,7 +410,7 @@ def test_input_xtype_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'input_xtype' is not allowed. " - "Please use 'input_data' to modify 'input_xtype'.", + "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", ): do.input_xtype = "q" From 0dd95e898b3b7b964117ac1c6d900287216eefe7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 14:35:34 -0500 Subject: [PATCH 366/696] Use input_data across all tests file instead of input_scattering_data --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c01c203b..3a7ae366 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -378,12 +378,12 @@ def test_id_getter(): assert len(str(do.id)) == 36 -def test_id_setter(): +def test_id_setter_error(): do = DiffractionObject() # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. Please use 'insert_scattering_quantity' to modify 'id'.", + match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", ): do.id = uuid.uuid4() @@ -403,7 +403,7 @@ def test_input_xtype_getter(): assert do.input_xtype == "tth" -def test_input_xtype_setter(): +def test_input_xtype_setter_error(): do = DiffractionObject(xtype="tth") # Attempt to directly modify the property From 79161f177ec2463be321fd6a47fe28ca63f27328 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 12 Dec 2024 15:04:49 -0500 Subject: [PATCH 367/696] remove expected parameters in bad tests --- tests/test_diffraction_objects.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 68dd0c01..043da5c0 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -290,27 +290,24 @@ def test_scale_to(inputs, expected): params_scale_to_bad = [ ( - [ - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - 10, - 0, - ], - ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), + "tth", + 2 * np.pi, + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "tth", + 2 * np.pi, + None, + 60, + 10, + 0, ), ] -@pytest.mark.parametrize("inputs, expected", params_scale_to_bad) -def test_scale_to_bad(inputs, expected): +@pytest.mark.parametrize("inputs", params_scale_to_bad) +def test_scale_to_bad(inputs): orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) target_diff_object = DiffractionObject( xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] From 1b7aa14903814ba6df2c235f240c4724966db308 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 12 Dec 2024 15:07:40 -0500 Subject: [PATCH 368/696] edit docstring --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 936bb25c..a8f0bbaa 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -397,7 +397,7 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, the priority is `q` > `tth` > `d`. + If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. If none are provided, the midpoint of the current object's `q`-array will be used. Parameters From 4bd441fa39fc8fee9090097858ea729321de812a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 17:13:49 -0500 Subject: [PATCH 369/696] Add mocker for testing id property --- tests/test_diffraction_objects.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 3a7ae366..5b366846 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -378,12 +378,18 @@ def test_id_getter(): assert len(str(do.id)) == 36 +def test_id_getter_with_mock(mocker): + mocker.patch.object(DiffractionObject, "id", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87")) + do = DiffractionObject() + assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + + def test_id_setter_error(): do = DiffractionObject() - # Attempt to directly modify the property + with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", + match="Direct modification of attribute 'id' is not allowed. " "Please use 'input_data' to modify 'id'.", ): do.id = uuid.uuid4() From 3a52b2f82893a307a32dbc28638eb43fbf6b35a7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 17:24:39 -0500 Subject: [PATCH 370/696] Fix spacing in error msg --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 5b366846..50080550 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -389,7 +389,7 @@ def test_id_setter_error(): with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. " "Please use 'input_data' to modify 'id'.", + match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", ): do.id = uuid.uuid4() From 6e51eb79e66a78c7dbafe4797174ffe19ad7be75 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 17:30:57 -0500 Subject: [PATCH 371/696] Re-run CI for codecov fail From 53585aeb13a12be4a4b8f3319626d8ed691d36be Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 18:22:37 -0500 Subject: [PATCH 372/696] Add doc build yml file --- .github/workflows/publish-docs-on-release.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/publish-docs-on-release.yml diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml new file mode 100644 index 00000000..b09c688a --- /dev/null +++ b/.github/workflows/publish-docs-on-release.yml @@ -0,0 +1,16 @@ +name: Deploy Documentation on Release + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + docs: + permissions: + contents: write + uses: Billingegroup/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 + with: + project: diffpy.utils + c_extension: false + headless: false From 3ec44dcdd5e2b6d6258fe4eb420875f9c5965930 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 23:26:11 -0500 Subject: [PATCH 373/696] Rename Diffraction_objects to DiffractionObject in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 48967571..520d4029 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,7 @@ General purpose shared utilities for the diffpy libraries. The diffpy.utils package provides functions for extracting array data from variously formatted text files, an interpolation function based on the Whittaker-Shannon formula that can be used to resample a PDF or other profile -function over a new grid, `Diffraction_objects` for conveniently manipulating +function over a new grid, `DiffractionObject` for conveniently manipulating diffraction data, and some wx GUI utilities used by the PDFgui program. From 4fa614ff905a9f805fd812764ad3b502dbaebe6e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 23:27:25 -0500 Subject: [PATCH 374/696] Import numpy as np, remove end of line, improve docstring --- src/diffpy/utils/__init__.py | 2 - src/diffpy/utils/parsers/__init__.py | 2 - src/diffpy/utils/parsers/loaddata.py | 3 -- src/diffpy/utils/resampler.py | 61 +++++++++++++--------------- src/diffpy/utils/version.py | 2 - src/diffpy/utils/wx/__init__.py | 2 - src/diffpy/utils/wx/gridutils.py | 3 -- tests/test_resample.py | 26 ++++++++---- 8 files changed, 45 insertions(+), 56 deletions(-) diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index 41a4fa54..c9909a8a 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -20,5 +20,3 @@ # silence the pyflakes syntax checker assert __version__ or True - -# End of file diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index cd95dd63..bab9943c 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -15,5 +15,3 @@ """Various utilities related to data parsing and manipulation. """ - -# End of file diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 870e6016..39c4b163 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -344,6 +344,3 @@ def isfloat(s): except ValueError: pass return False - - -# End of file diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index e88c4f0b..3959afdf 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -15,52 +15,50 @@ """Various utilities related to data parsing and manipulation.""" -import numpy +import numpy as np -# NOTE - this should be faster than resample below and conforms more closely to -# numpy.interp. I'm keeping resample for legacy reasons. def wsinterp(x, xp, fp, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation. - This uses the Whittaker-Shannon interpolation formula to interpolate the value of fp (array), - which is defined over xp (array), at x (array or float). + Reconstruct a continuous signal from discrete data points by utilizing sinc functions + as interpolation kernels. This function interpolates the values of fp (array), + which are defined over xp (array), at new points x (array or float). Parameters ---------- x: ndarray - Desired range for interpolation. + The x values at which interpolation is computed. xp: ndarray - Defined range for fp. + The array of known x values. fp: ndarray - Function to be interpolated. + The array of y values associated xp. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, set fp for x < xp[0] to fp evaluated at xp[-1]. right: float If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for - x > xp[-1] to fp evaluated at xp[-1]. + x > xp[-1] to fp evaluated at xp[-1] Returns ------- - float: - If input x is a scalar (not an array), return the interpolated value at x. - ndarray: - If input x is an array, return the interpolated array with dimensions of x. + ndarray or float + Interpolated values at points x. Returns a single float if x is a scalar, + otherwise returns a numpy.ndarray. """ - scalar = numpy.isscalar(x) + scalar = np.isscalar(x) if scalar: - x = numpy.array(x) + x = np.array(x) x.resize(1) # shape = (nxp, nx), nxp copies of x data span axis 1 - u = numpy.resize(x, (len(xp), len(x))) + u = np.resize(x, (len(xp), len(x))) # Must take transpose of u for proper broadcasting with xp. # shape = (nx, nxp), v(xp) data spans axis 1 v = (xp - u.T) / (xp[1] - xp[0]) # shape = (nx, nxp), m(v) data spans axis 1 - m = fp * numpy.sinc(v) + m = fp * np.sinc(v) # Sum over m(v) (axis 1) - fp_at_x = numpy.sum(m, axis=1) + fp_at_x = np.sum(m, axis=1) # Enforce left and right if left is None: @@ -100,36 +98,33 @@ def resample(r, s, dr): dr0 = r[1] - r[0] # Constant timestep if dr0 < dr: - rnew = numpy.arange(r[0], r[-1] + 0.5 * dr, dr) - snew = numpy.interp(rnew, r, s) + rnew = np.arange(r[0], r[-1] + 0.5 * dr, dr) + snew = np.interp(rnew, r, s) return rnew, snew elif dr0 > dr: # Tried to pad the end of s to dampen, but nothing works. # m = (s[-1] - s[-2]) / dr0 # b = (s[-2] * r[-1] - s[-1] * r[-2]) / dr0 - # rpad = r[-1] + numpy.arange(1, len(s))*dr0 + # rpad = r[-1] + np.arange(1, len(s))*dr0 # spad = rpad * m + b - # spad = numpy.concatenate([s,spad]) - # rnew = numpy.arange(0, rpad[-1], dr) - # snew = numpy.zeros_like(rnew) + # spad = np.concatenate([s,spad]) + # rnew = np.arange(0, rpad[-1], dr) + # snew = np.zeros_like(rnew) # Accommodate for the fact that r[0] might not be 0 # u = (rnew-r[0]) / dr0 # for n in range(len(spad)): - # snew += spad[n] * numpy.sinc(u - n) + # snew += spad[n] * np.sinc(u - n) - # sel = numpy.logical_and(rnew >= r[0], rnew <= r[-1]) + # sel = np.logical_and(rnew >= r[0], rnew <= r[-1]) - rnew = numpy.arange(0, r[-1], dr) - snew = numpy.zeros_like(rnew) + rnew = np.arange(0, r[-1], dr) + snew = np.zeros_like(rnew) u = (rnew - r[0]) / dr0 for n in range(len(s)): - snew += s[n] * numpy.sinc(u - n) - sel = numpy.logical_and(rnew >= r[0], rnew <= r[-1]) + snew += s[n] * np.sinc(u - n) + sel = np.logical_and(rnew >= r[0], rnew <= r[-1]) return rnew[sel], snew[sel] # If we got here, then no resampling is required return r.copy(), s.copy() - - -# End of file diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index e42fed78..0bc397e4 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -22,5 +22,3 @@ from importlib.metadata import version __version__ = version("diffpy.utils") - -# End of file diff --git a/src/diffpy/utils/wx/__init__.py b/src/diffpy/utils/wx/__init__.py index c2d0617b..3f7417ef 100644 --- a/src/diffpy/utils/wx/__init__.py +++ b/src/diffpy/utils/wx/__init__.py @@ -15,5 +15,3 @@ """Utilities related wx Python GUIs. """ - -# End of file diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index 1a25bfb3..c459ac30 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -163,6 +163,3 @@ def _indicesToBlocks(indices): i0 = i rv = [tuple(ij) for ij in rngs] return rv - - -# End of file diff --git a/tests/test_resample.py b/tests/test_resample.py index deddcca2..137f5dd9 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -1,3 +1,5 @@ +import random + import numpy as np import pytest @@ -5,22 +7,28 @@ def test_wsinterp(): - import random - - # Check known points are unchanged by interpolation # FIXME: if another SW interp function exists, run comparisons for interpolated points + # Sampling rate ssr = 44100**-1 # Standard sampling rate for human-hearable frequencies - t = ssr + + # Creating a symmetric set of sample points around zero. n = 5 - xp = np.array([i * t for i in range(-n, n + 1, 1)]) - x = np.array([i * t for i in range(-n - 1, n + 2, 1)]) + xp = np.array([i * ssr for i in range(-n, n + 1, 1)]) + x = np.array([i * ssr for i in range(-n - 1, n + 2, 1)]) + assert len(xp) == 11 and len(x) == 13 - # Interpolate a few random datasets + # Generating fp values across 10 trial runs trials = 10 - for trial in range(trials): - fp = np.array([random.random() * ssr for i in range(-n, n + 1, 1)]) + + for _ in range(trials): + # Create random function values (fp) at the points defined in xp above + fp = np.array([random.random() * ssr for _ in range(-n, n + 1, 1)]) + + # Interpolate the values at new x points fp_at_x = wsinterp(x, xp, fp) + + # Check that the known points are unchanged by interpolation assert np.allclose(fp_at_x[1:-1], fp) for i in range(len(x)): assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp)) From f737a13fb038d1fc76aef3d064fac5029718718e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 23:29:27 -0500 Subject: [PATCH 375/696] Add a period to docstring --- src/diffpy/utils/resampler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 3959afdf..7245b57e 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -38,7 +38,7 @@ def wsinterp(x, xp, fp, left=None, right=None): set fp for x < xp[0] to fp evaluated at xp[-1]. right: float If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for - x > xp[-1] to fp evaluated at xp[-1] + x > xp[-1] to fp evaluated at xp[-1]. Returns ------- From 83a0c30e23c549d3443d7de22eb0095a759346d6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 10:23:48 -0500 Subject: [PATCH 376/696] Add citation to wsint, remove empty line, add 'the' in docstrings --- src/diffpy/utils/resampler.py | 5 +++-- tests/test_resample.py | 8 +------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 7245b57e..3c761d1b 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -24,6 +24,7 @@ def wsinterp(x, xp, fp, left=None, right=None): Reconstruct a continuous signal from discrete data points by utilizing sinc functions as interpolation kernels. This function interpolates the values of fp (array), which are defined over xp (array), at new points x (array or float). + The implementation is based on E. T. Whittaker's 1915 paper (https://doi.org/10.1017/S0370164600017806). Parameters ---------- @@ -32,7 +33,7 @@ def wsinterp(x, xp, fp, left=None, right=None): xp: ndarray The array of known x values. fp: ndarray - The array of y values associated xp. + The array of y values associated with xp. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, set fp for x < xp[0] to fp evaluated at xp[-1]. @@ -43,7 +44,7 @@ def wsinterp(x, xp, fp, left=None, right=None): Returns ------- ndarray or float - Interpolated values at points x. Returns a single float if x is a scalar, + The interpolated values at points x. Returns a single float if x is a scalar, otherwise returns a numpy.ndarray. """ scalar = np.isscalar(x) diff --git a/tests/test_resample.py b/tests/test_resample.py index 137f5dd9..c4e25ac3 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -18,21 +18,15 @@ def test_wsinterp(): x = np.array([i * ssr for i in range(-n - 1, n + 2, 1)]) assert len(xp) == 11 and len(x) == 13 - # Generating fp values across 10 trial runs + # Generating a new set of fp values across 10 trial runs trials = 10 for _ in range(trials): # Create random function values (fp) at the points defined in xp above fp = np.array([random.random() * ssr for _ in range(-n, n + 1, 1)]) - - # Interpolate the values at new x points fp_at_x = wsinterp(x, xp, fp) # Check that the known points are unchanged by interpolation assert np.allclose(fp_at_x[1:-1], fp) for i in range(len(x)): assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp)) - - -if __name__ == "__main__": - test_wsinterp() From a2f244dc90d30cfb28efe3a3493a990fbca93194 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 10:25:04 -0500 Subject: [PATCH 377/696] Reword from generating to generate in comment --- src/diffpy/utils/resampler.py | 3 ++- tests/test_resample.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 3c761d1b..5f6062d2 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -24,7 +24,8 @@ def wsinterp(x, xp, fp, left=None, right=None): Reconstruct a continuous signal from discrete data points by utilizing sinc functions as interpolation kernels. This function interpolates the values of fp (array), which are defined over xp (array), at new points x (array or float). - The implementation is based on E. T. Whittaker's 1915 paper (https://doi.org/10.1017/S0370164600017806). + The implementation is based on E. T. Whittaker's 1915 paper + (https://doi.org/10.1017/S0370164600017806). Parameters ---------- diff --git a/tests/test_resample.py b/tests/test_resample.py index c4e25ac3..784932bb 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -18,7 +18,7 @@ def test_wsinterp(): x = np.array([i * ssr for i in range(-n - 1, n + 2, 1)]) assert len(xp) == 11 and len(x) == 13 - # Generating a new set of fp values across 10 trial runs + # Generate a new set of fp values across 10 trial runs trials = 10 for _ in range(trials): From 91594ed5273f877fd0ae8a7f01a819c5ad7f2a6c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 10:27:56 -0500 Subject: [PATCH 378/696] Rerun CI From 781e3581cfa9a55d90cd2d6722b1dd5a265c3513 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 12:22:45 -0500 Subject: [PATCH 379/696] Capture no wavelenght warning for test_q_to_tth --- tests/test_transforms.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index f1c940b7..5f86294b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,24 +5,30 @@ params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays - ([None, np.empty((0))], np.empty((0))), - # UC2: Empty q values, wavelength specified, return empty arrays - ([4 * np.pi, np.empty((0))], np.empty(0)), + (None, np.empty((0)), np.empty((0))), + # # UC2: Empty q values, wavelength specified, return empty arrays + (4 * np.pi, np.empty((0)), np.empty(0)), # UC3: User specified valid q values, no wavelength, return empty arrays ( - [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + None, + np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), # UC4: User specified valid q values (with wavelength) # expected tth values are 2*arcsin(q) in degrees - ([4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0])], np.array([0, 90.0, 180.0])), + (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ] +@pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) +def test_q_to_tth(wavelength, q, expected_tth): + + if wavelength is None: + with pytest.warns(UserWarning, match="INFO: no wavelength has been specified"): + actual_tth = q_to_tth(q, wavelength) + else: + actual_tth = q_to_tth(q, wavelength) -@pytest.mark.parametrize("inputs, expected", params_q_to_tth) -def test_q_to_tth(inputs, expected): - actual = q_to_tth(inputs[1], inputs[0]) - assert np.allclose(expected, actual) + assert np.allclose(expected_tth, actual_tth) params_q_to_tth_bad = [ From 7ac21a37708566ae668555cecc31e1da8e1af31d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:26:22 +0000 Subject: [PATCH 380/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 5f86294b..43391e3b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -19,10 +19,11 @@ (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ] + @pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) def test_q_to_tth(wavelength, q, expected_tth): - - if wavelength is None: + + if wavelength is None: with pytest.warns(UserWarning, match="INFO: no wavelength has been specified"): actual_tth = q_to_tth(q, wavelength) else: From 792ce171b1bce508df171f1966cef3b728211714 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 12:26:35 -0500 Subject: [PATCH 381/696] Apply precommit --- tests/test_transforms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 5f86294b..43391e3b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -19,10 +19,11 @@ (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ] + @pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) def test_q_to_tth(wavelength, q, expected_tth): - - if wavelength is None: + + if wavelength is None: with pytest.warns(UserWarning, match="INFO: no wavelength has been specified"): actual_tth = q_to_tth(q, wavelength) else: From c22a95bc9a03231fb812c29bbf2ffded06d17355 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 13 Dec 2024 12:26:50 -0500 Subject: [PATCH 382/696] remove default value in the function --- src/diffpy/utils/diffraction_objects.py | 19 +++++++------ tests/test_diffraction_objects.py | 36 ++++++++++++------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a8f0bbaa..0813ef9b 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -397,15 +397,14 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. - If none are provided, the midpoint of the current object's `q`-array will be used. + If multiple values of `q`, `tth`, or `d` are provided, or none are provided, an error will be raised. Parameters ---------- target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, default is the midpoint of the current object's `q`-array + q, tth, d : float, optional, must specify exactly one of them the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects offset : float, optional, default is 0 @@ -417,19 +416,19 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): """ scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) - if count > 1: - raise ValueError("You can only specify one of 'q', 'tth', or 'd'. Please rerun specifying only one.") + if count != 1: + raise ValueError( + "You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." + ) xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) xvalue = q if xtype == "q" else tth if xtype == "tth" else d - if xvalue is None: - xvalue = (data[0][0] + data[0][-1]) / 2.0 - x_data, x_target = (np.abs(data[0] - xvalue)).argmin(), (np.abs(target[0] - xvalue)).argmin() - y_data, y_target = data[1][x_data], target[1][x_target] - scaled._all_arrays[:, 0] = data[1] * y_target / y_data + offset + xindex_data = (np.abs(data[0] - xvalue)).argmin() + xindex_target = (np.abs(target[0] - xvalue)).argmin() + scaled._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 043da5c0..bd8c6c6e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -254,24 +254,6 @@ def test_init_invalid_xtype(): # scaling factor is calculated at index = 5 for self and index = 6 for target ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), - # UC5: user did not specify anything, use the midpoint of the current object's q-array - ( - [ - np.array([0.1, 0.2, 0.3]), - np.array([1, 2, 3]), - "q", - 2 * np.pi, - np.array([0.05, 0.1, 0.2, 0.3]), - np.array([5, 10, 20, 30]), - "q", - 2 * np.pi, - None, - None, - None, - 0, - ], - ["q", np.array([10, 20, 30])], - ), ] @@ -289,6 +271,22 @@ def test_scale_to(inputs, expected): params_scale_to_bad = [ + # UC1: user did not specify anything + ( + np.array([0.1, 0.2, 0.3]), + np.array([1, 2, 3]), + "q", + 2 * np.pi, + np.array([0.05, 0.1, 0.2, 0.3]), + np.array([5, 10, 20, 30]), + "q", + 2 * np.pi, + None, + None, + None, + 0, + ), + # UC2: user specified more than one of q, tth, and d ( np.array([10, 25, 30.1, 40.2, 61, 120, 140]), np.array([10, 20, 30, 40, 50, 60, 100]), @@ -313,7 +311,7 @@ def test_scale_to_bad(inputs): xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] ) with pytest.raises( - ValueError, match="You can only specify one of 'q', 'tth', or 'd'. Please rerun specifying only one." + ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): orig_diff_object.scale_to(target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11]) From 8c891db7d11d94fe79852cca7d40742299ee64e5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 23:29:37 -0500 Subject: [PATCH 383/696] use variables within parametrize for test_diffraction_objects --- tests/test_diffraction_objects.py | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 50080550..5a5d9805 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -164,11 +164,11 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_on_xtype(): - test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") - assert np.allclose(test.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) + do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + assert np.allclose(do.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) + assert np.allclose(do.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])]) + assert np.allclose(do.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])]) + assert np.allclose(do.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) def test_init_invalid_xtype(): @@ -184,34 +184,34 @@ def test_init_invalid_xtype(): params_index = [ # UC1: exact match - ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), + (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), # UC2: target value lies in the array, returns the (first) closest index - ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45], [0]), - ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25], [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), # UC3: target value out of the range, returns the closest index - ([4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1], [0]), - ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63], [1]), + (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), ] -@pytest.mark.parametrize("inputs, expected", params_index) -def test_get_array_index(inputs, expected): - test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) - actual = test.get_array_index(value=inputs[5], xtype=inputs[4]) - assert actual == expected[0] +@pytest.mark.parametrize("wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", params_index) +def test_get_array_index(wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index): + do = DiffractionObject(wavelength=wavelength, xarray=xarray, yarray=yarray, xtype=xtype_1) + actual_index = do.get_array_index(value=value, xtype=xtype_2) + assert actual_index == expected_index def test_get_array_index_bad(): - test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") + do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")): - test.get_array_index(value=30) + do.get_array_index(value=30) def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - test = DiffractionObject( + do = DiffractionObject( wavelength=1.54, name="test", scat_quantity="x-ray", @@ -222,7 +222,7 @@ def test_dump(tmp_path, mocker): ) mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): - test.dump(file, "q") + do.dump(file, "q") with open(file, "r") as f: actual = f.read() expected = ( @@ -360,7 +360,7 @@ def test_all_array_getter(): def test_all_array_setter(): - actual_do = DiffractionObject() + do = DiffractionObject() # Attempt to directly modify the property with pytest.raises( @@ -368,7 +368,7 @@ def test_all_array_setter(): match="Direct modification of attribute 'all_arrays' is not allowed. " "Please use 'input_data' to modify 'all_arrays'.", ): - actual_do.all_arrays = np.empty((4, 4)) + do.all_arrays = np.empty((4, 4)) def test_id_getter(): From cce5716329249677e3c93234d5017f8b744a09a4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 23:29:57 -0500 Subject: [PATCH 384/696] Remove INFO and improve how to set wavelenght warning msg --- src/diffpy/utils/transforms.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index c150b2a4..77df975e 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -4,12 +4,10 @@ import numpy as np wavelength_warning_emsg = ( - "INFO: no wavelength has been specified. You can continue " - "to use the DiffractionObject but some of its powerful features " - "will not be available. To specify a wavelength, set " - "diffraction_object.wavelength = [number], " - "where diffraction_object is the variable name of you Diffraction Object, " - "and number is the wavelength in angstroms." + "No wavelength has been specified. You can continue to use the DiffractionObject, but " + "some of its powerful features will not be available. " + "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "you may set do.wavelength = 1.54 with the unit in angstroms." ) invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." invalid_q_or_d_or_wavelength_emsg = ( From 6bb822f574b7189687eb46d4a3a1ce0c62a87281 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 23:30:30 -0500 Subject: [PATCH 385/696] Use variable names within parametrize for transforms --- tests/test_transforms.py | 61 +++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 43391e3b..453844c2 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,3 +1,5 @@ +import re + import numpy as np import pytest @@ -6,7 +8,7 @@ params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), - # # UC2: Empty q values, wavelength specified, return empty arrays + # UC2: Empty q values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), # UC3: User specified valid q values, no wavelength, return empty arrays ( @@ -23,8 +25,15 @@ @pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) def test_q_to_tth(wavelength, q, expected_tth): + wavelength_warning_emsg = ( + "No wavelength has been specified. You can continue to use the DiffractionObject, but " + "some of its powerful features will not be available. " + "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "you may set do.wavelength = 1.54 with the unit in angstroms." + ) if wavelength is None: - with pytest.warns(UserWarning, match="INFO: no wavelength has been specified"): + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_emsg)): + actual_tth = q_to_tth(q, wavelength) actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) @@ -35,54 +44,54 @@ def test_q_to_tth(wavelength, q, expected_tth): params_q_to_tth_bad = [ # UC1: user specified invalid q values that result in tth > 180 degrees ( - [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], - [ - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], + 4 * np.pi, + np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], - [ - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], + 100, + np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", ), ] -@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) -def test_q_to_tth_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - q_to_tth(inputs[1], inputs[0]) +@pytest.mark.parametrize("q, wavelength, expected_error_type, expected_error_msg", params_q_to_tth_bad) +def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): + with pytest.raises(expected_error_type, match=expected_error_msg): + q_to_tth(wavelength, q) params_tth_to_q = [ # UC0: User specified empty tth values (without wavelength) - ([None, np.array([])], np.array([])), + (None, np.array([]), np.array([])), # UC1: User specified empty tth values (with wavelength) - ([4 * np.pi, np.array([])], np.array([])), + (4 * np.pi, np.array([]), np.array([])), # UC2: User specified valid tth values between 0-180 degrees (without wavelength) ( - [None, np.array([0, 30, 60, 90, 120, 180])], + None, + np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) # expected q values are sin15, sin30, sin45, sin60, sin90 ( - [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], + 4 * np.pi, + np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), ), ] -@pytest.mark.parametrize("inputs, expected", params_tth_to_q) -def test_tth_to_q(inputs, expected): - actual = tth_to_q(inputs[1], inputs[0]) - assert np.allclose(actual, expected) +@pytest.mark.parametrize("wavelength, tth, expected_q", params_tth_to_q) +def test_tth_to_q(wavelength, tth, expected_q): + actual_q = tth_to_q(tth, wavelength) + assert np.allclose(actual_q, expected_q) params_tth_to_q_bad = [ From d176b6b34b0a504beb6aa73390ad1db89c43b05f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 23:35:29 -0500 Subject: [PATCH 386/696] Remove another duplicate line for actual_tth = q_to_tth(q, wavelength) --- tests/test_transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 453844c2..19096784 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -34,7 +34,6 @@ def test_q_to_tth(wavelength, q, expected_tth): if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_emsg)): actual_tth = q_to_tth(q, wavelength) - actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) From 92a2c4c50442f91f0950ecf74f5ec3d7a88c1826 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:09:35 -0500 Subject: [PATCH 387/696] Add do_minimal setup to conftest --- tests/conftest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 09de40f6..894d6b31 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,11 @@ import json from pathlib import Path +import numpy as np import pytest +from diffpy.utils.diffraction_objects import DiffractionObject + @pytest.fixture def user_filesystem(tmp_path): @@ -28,3 +31,9 @@ def _load(filename): return base_path / filename return _load + + +@pytest.fixture +def do_minimal(): + # Create an instance of DiffractionObject with minimal setup + return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="tth", wavelength=1.54) From 56541e5b6ed7e48774cf739947ccab063ca20f4e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:10:35 -0500 Subject: [PATCH 388/696] Refactor and remove input_data func --- src/diffpy/utils/diffraction_objects.py | 100 ++++++------------------ 1 file changed, 26 insertions(+), 74 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index abfd44b0..23afb790 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -36,26 +36,36 @@ def _setter_wmsg(attribute): class DiffractionObject: def __init__( - self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype=None + self, + xarray, + yarray, + xtype, + wavelength, + scat_quantity="", + name="", + metadata={}, ): - if name is None: - name = "" - self.name = name - if metadata is None: - metadata = {} - self.metadata = metadata - if xtype is None: - xtype = "" + # Check xtype is valid. An empty string is the default value. + if xtype not in XQUANTITIES: + raise ValueError(_xtype_wmsg(xtype)) + + # Check xarray and yarray have the same length + if len(xarray) != len(yarray): + raise ValueError( + "'xarray' and 'yarray' must have the same length. " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " + "with 'xarray' and 'yarray' of identical length." + ) + self.scat_quantity = scat_quantity self.wavelength = wavelength + self.metadata = metadata + self.name = name - if xarray is None: - xarray = np.empty(0) - if yarray is None: - yarray = np.empty(0) - + self._input_xtype = xtype self._id = uuid.uuid4() - self.input_data(xarray, yarray, xtype) + self._set_xarrays(xarray, xtype) + self._all_arrays[:, 0] = yarray def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -298,8 +308,7 @@ def get_array_index(self, value, xtype=None): the index of the value in the array """ - if xtype is None: - xtype = self._input_xtype + xtype = self._input_xtype array = self.on_xtype(xtype)[0] if len(array) == 0: raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") @@ -327,63 +336,6 @@ def _set_xarrays(self, xarray, xtype): self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf) self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0) - def input_data( - self, - xarray, - yarray, - xtype, - metadata={}, - scat_quantity=None, - name=None, - wavelength=None, - ): - f""" - insert a new scattering quantity into the scattering object - - Parameters - ---------- - xarray array-like of floats - the independent variable array - yarray array-like of floats - the dependent variable array - xtype string - the type of quantity for the independent variable from {*XQUANTITIES, } - metadata, scat_quantity, name and wavelength are optional. They have the same - meaning as in the constructor. Values will only be overwritten if non-empty values are passed. - - Returns - ------- - Nothing. Updates the object in place. - - """ - - # Check xarray and yarray have the same length - if len(xarray) != len(yarray): - raise ValueError( - "'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " - "with 'xarray' and 'yarray' of identical length." - ) - - self._set_xarrays(xarray, xtype) - self._all_arrays[:, 0] = yarray - self._input_xtype = xtype - # only update these optional values if non-empty quantities are passed to avoid overwriting - # valid data inadvertently - if metadata: - self.metadata = metadata - if scat_quantity is not None: - self.scat_quantity = scat_quantity - if name is not None: - self.name = name - if wavelength is not None: - self.wavelength = wavelength - - # Check xtype is valid. An empty string is the default value. - if xtype != "": - if xtype not in XQUANTITIES: - raise ValueError(_xtype_wmsg(xtype)) - def _get_original_array(self): if self._input_xtype in QQUANTITIES: return self.on_q(), "q" From 1144d1f9c989d16ce1f2cd1ef4e7ff6642461d44 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:11:00 -0500 Subject: [PATCH 389/696] Re-organize test with required parameters of xarray, yarray, wavelength, and xtype --- tests/test_diffraction_objects.py | 116 +++++++++++------------------- 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 50080550..089dfc2a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -11,11 +11,6 @@ from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject params = [ - ( # Default - {}, - {}, - True, - ), ( # Compare same attributes { "name": "same", @@ -41,8 +36,8 @@ { "name": "something", "scat_quantity": "", - "wavelength": None, - "xtype": "", + "wavelength": 0.71, + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -50,8 +45,8 @@ { "name": "something else", "scat_quantity": "", - "wavelength": None, - "xtype": "", + "wavelength": 0.71, + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -62,15 +57,15 @@ { "scat_quantity": "", "wavelength": 0.71, - "xtype": "", + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, }, { "scat_quantity": "", - "wavelength": None, - "xtype": "", + "wavelength": 0.42, + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -81,7 +76,7 @@ { "scat_quantity": "", "wavelength": 0.71, - "xtype": "", + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -89,7 +84,7 @@ { "scat_quantity": "", "wavelength": 0.711, - "xtype": "", + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -99,16 +94,16 @@ ( # Different scat_quantity { "scat_quantity": "x-ray", - "wavelength": None, - "xtype": "", + "xtype": "tth", + "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, }, { "scat_quantity": "neutron", - "wavelength": None, - "xtype": "", + "xtype": "tth", + "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -118,16 +113,16 @@ ( # Different on_q { "scat_quantity": "", - "wavelength": None, "xtype": "q", + "wavelength": 0.71, "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), "metadata": {}, }, { "scat_quantity": "", - "wavelength": None, "xtype": "q", + "wavelength": 0.71, "xarray": np.array([3.0, 4.0]), "yarray": np.array([100.0, 200.0]), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -137,16 +132,16 @@ ( # Different metadata { "scat_quantity": "", - "wavelength": None, - "xtype": "", + "xtype": "q", + "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 0, "thing2": "thing2"}, }, { "scat_quantity": "", - "wavelength": None, - "xtype": "", + "xtype": "q", + "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -171,7 +166,7 @@ def test_on_xtype(): assert np.allclose(test.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) -def test_init_invalid_xtype(): +def test_init_invalid_xtype(do_minimal): with pytest.raises( ValueError, match=re.escape( @@ -179,7 +174,7 @@ def test_init_invalid_xtype(): f"Please rerun specifying an xtype from {*XQUANTITIES, }" ), ): - DiffractionObject(xtype="invalid_type") + return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="invalid_type", wavelength=1.54) params_index = [ @@ -241,40 +236,6 @@ def test_dump(tmp_path, mocker): tc_params = [ - ( - {}, - { - "_all_arrays": np.empty(shape=(0, 4)), # instantiate empty - "metadata": {}, - "_input_xtype": "", - "name": "", - "scat_quantity": None, - "qmin": np.float64(np.inf), - "qmax": np.float64(0.0), - "tthmin": np.float64(np.inf), - "tthmax": np.float64(0.0), - "dmin": np.float64(np.inf), - "dmax": np.float64(0.0), - "wavelength": None, - }, - ), - ( # instantiate just non-array attributes - {"name": "test", "scat_quantity": "x-ray", "metadata": {"thing": "1", "another": "2"}}, - { - "_all_arrays": np.empty(shape=(0, 4)), - "metadata": {"thing": "1", "another": "2"}, - "_input_xtype": "", - "name": "test", - "scat_quantity": "x-ray", - "qmin": np.float64(np.inf), - "qmax": np.float64(0.0), - "tthmin": np.float64(np.inf), - "tthmax": np.float64(0.0), - "dmin": np.float64(np.inf), - "dmax": np.float64(0.0), - "wavelength": None, - }, - ), ( # instantiate just array attributes { "xarray": np.array([0.0, 90.0, 180.0]), @@ -293,7 +254,7 @@ def test_dump(tmp_path, mocker): "metadata": {}, "_input_xtype": "tth", "name": "", - "scat_quantity": None, + "scat_quantity": "", "qmin": np.float64(0.0), "qmax": np.float64(1.0), "tthmin": np.float64(0.0), @@ -339,6 +300,9 @@ def test_dump(tmp_path, mocker): def test_constructor(inputs, expected): actual = DiffractionObject(**inputs).__dict__ diff = DeepDiff(actual, expected, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") + print("Print diff") + print(diff) + # {'dictionary_item_added': ["root['name']", "root['scat_quantity']"]} assert diff == {} @@ -359,8 +323,8 @@ def test_all_array_getter(): assert np.allclose(actual_do.all_arrays, expected_all_arrays) -def test_all_array_setter(): - actual_do = DiffractionObject() +def test_all_array_setter(do_minimal): + actual_do = do_minimal # Attempt to directly modify the property with pytest.raises( @@ -371,21 +335,21 @@ def test_all_array_setter(): actual_do.all_arrays = np.empty((4, 4)) -def test_id_getter(): - do = DiffractionObject() +def test_id_getter(do_minimal): + do = do_minimal assert hasattr(do, "id") assert isinstance(do.id, UUID) assert len(str(do.id)) == 36 -def test_id_getter_with_mock(mocker): +def test_id_getter_with_mock(mocker, do_minimal): mocker.patch.object(DiffractionObject, "id", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87")) - do = DiffractionObject() + do = do_minimal assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") -def test_id_setter_error(): - do = DiffractionObject() +def test_id_setter_error(do_minimal): + do = do_minimal with pytest.raises( AttributeError, @@ -401,18 +365,18 @@ def test_xarray_yarray_length_mismatch(): "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length", ): - DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) + DiffractionObject( + xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0]), xtype="tth", wavelength=1.54 + ) -def test_input_xtype_getter(): - do = DiffractionObject(xtype="tth") +def test_input_xtype_getter(do_minimal): + do = do_minimal assert do.input_xtype == "tth" -def test_input_xtype_setter_error(): - do = DiffractionObject(xtype="tth") - - # Attempt to directly modify the property +def test_input_xtype_setter_error(do_minimal): + do = do_minimal with pytest.raises( AttributeError, match="Direct modification of attribute 'input_xtype' is not allowed. " From e7b4dc2b53f0007d7f36715ba2d57fcedf37254b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:15:09 -0500 Subject: [PATCH 390/696] Add input public data back to init func --- src/diffpy/utils/diffraction_objects.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 23afb790..2eddc8a6 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -45,6 +45,12 @@ def __init__( name="", metadata={}, ): + + self._id = uuid.uuid4() + self.input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) + + def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): + # Check xtype is valid. An empty string is the default value. if xtype not in XQUANTITIES: raise ValueError(_xtype_wmsg(xtype)) @@ -63,7 +69,7 @@ def __init__( self.name = name self._input_xtype = xtype - self._id = uuid.uuid4() + self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray From 6dc2738613ff034efe26ad74cdb651a0ea5e0527 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:16:37 -0500 Subject: [PATCH 391/696] Remove print diff function in pytest for debugging --- tests/test_diffraction_objects.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 089dfc2a..9118fad8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -300,9 +300,6 @@ def test_dump(tmp_path, mocker): def test_constructor(inputs, expected): actual = DiffractionObject(**inputs).__dict__ diff = DeepDiff(actual, expected, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") - print("Print diff") - print(diff) - # {'dictionary_item_added': ["root['name']", "root['scat_quantity']"]} assert diff == {} @@ -348,6 +345,18 @@ def test_id_getter_with_mock(mocker, do_minimal): assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + def input_data( + self, + xarray, + yarray, + xtype, + metadata={}, + scat_quantity=None, + name=None, + wavelength=None, + ): + + def test_id_setter_error(do_minimal): do = do_minimal From 50b7d0d9c867d060d6be6243920653f12fa46a70 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:30:32 -0500 Subject: [PATCH 392/696] Update input_data docstrings for clarity --- src/diffpy/utils/diffraction_objects.py | 28 ++++++++++++++++++++++++- tests/test_diffraction_objects.py | 12 ----------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2eddc8a6..d49be5d2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -49,7 +49,33 @@ def __init__( self._id = uuid.uuid4() self.input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) - def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): + def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity=None, name="", metadata={}): + """ + Insert a new scattering quantity into the scattering object. + + Parameters + ---------- + xarray : array-like + The independent variable array (e.g., q, tth, or d). + yarray : array-like + The dependent variable array corresponding to intensity values + xtype : str + The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}, + such as "q", "tth", or "d". + wavelength : float + The wavelength of the incoming beam, specified in angstroms (Å). + scat_quantity : str, optional + The type of scattering experiment (e.g., "x-ray", "neutron"). Default is "". + name : str, optional + The name or label for the scattering data. Default is an empty string "". + metadata : dict, optional + The additional metadata associated with the diffraction object. Default is {}. + + Returns + ------- + None + This method updates the object in place and does not return a value. + """ # Check xtype is valid. An empty string is the default value. if xtype not in XQUANTITIES: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9118fad8..d21591cf 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -345,18 +345,6 @@ def test_id_getter_with_mock(mocker, do_minimal): assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") - def input_data( - self, - xarray, - yarray, - xtype, - metadata={}, - scat_quantity=None, - name=None, - wavelength=None, - ): - - def test_id_setter_error(do_minimal): do = do_minimal From 291cc89852387e32f326559557657893cabfa62e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:31:27 -0500 Subject: [PATCH 393/696] Add quotation marks around q, tth, d in docstrings --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index d49be5d2..c3c1d3ee 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -56,7 +56,7 @@ def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity=None, name Parameters ---------- xarray : array-like - The independent variable array (e.g., q, tth, or d). + The independent variable array (e.g., "q", "tth", or "d"). yarray : array-like The dependent variable array corresponding to intensity values xtype : str From b8a9adc52d69d197966014aabed7b4817f99c145 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:32:10 -0500 Subject: [PATCH 394/696] Use empty string for default scattering quantity --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index c3c1d3ee..7e704df1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -49,7 +49,7 @@ def __init__( self._id = uuid.uuid4() self.input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) - def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity=None, name="", metadata={}): + def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name="", metadata={}): """ Insert a new scattering quantity into the scattering object. From e3c52089a5a31f450eb819b4550894c191f4c586 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:35:05 -0500 Subject: [PATCH 395/696] Final cleanup on input_data docstrings --- src/diffpy/utils/diffraction_objects.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 7e704df1..fc86984e 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -58,14 +58,14 @@ def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name=" xarray : array-like The independent variable array (e.g., "q", "tth", or "d"). yarray : array-like - The dependent variable array corresponding to intensity values + The dependent variable array corresponding to intensity values. xtype : str The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}, such as "q", "tth", or "d". wavelength : float The wavelength of the incoming beam, specified in angstroms (Å). scat_quantity : str, optional - The type of scattering experiment (e.g., "x-ray", "neutron"). Default is "". + The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". name : str, optional The name or label for the scattering data. Default is an empty string "". metadata : dict, optional @@ -95,7 +95,6 @@ def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name=" self.name = name self._input_xtype = xtype - self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray From 69aa7c7665b1171e64b20c2e1c6d77ebb743fdd6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 11:25:03 -0500 Subject: [PATCH 396/696] Improve wavelength warning msg --- src/diffpy/utils/transforms.py | 2 +- tests/test_transforms.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 77df975e..fa3e8747 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -7,7 +7,7 @@ "No wavelength has been specified. You can continue to use the DiffractionObject, but " "some of its powerful features will not be available. " "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 with the unit in angstroms." + "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." invalid_q_or_d_or_wavelength_emsg = ( diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 19096784..909026e6 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -29,7 +29,7 @@ def test_q_to_tth(wavelength, q, expected_tth): "No wavelength has been specified. You can continue to use the DiffractionObject, but " "some of its powerful features will not be available. " "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 with the unit in angstroms." + "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_emsg)): From 53413fb9038e00e5dae3b206a0a5742ec8dbd0a4 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 14 Dec 2024 11:51:20 -0500 Subject: [PATCH 397/696] make tests more readable --- src/diffpy/utils/diffraction_objects.py | 8 +- tests/test_diffraction_objects.py | 202 +++++++++++++----------- 2 files changed, 114 insertions(+), 96 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 0813ef9b..7c8e3aee 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -405,7 +405,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): the diffraction object you want to scale the current one onto q, tth, d : float, optional, must specify exactly one of them - the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects + The value of the x-array where you want the curves to line up vertically. + Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. offset : float, optional, default is 0 an offset to add to the scaled y-values @@ -421,8 +422,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): "You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ) - xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" - data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) + xtype = "q" if q is not None else "tth" if tth is not None else "d" + data = self.on_xtype(xtype) + target = target_diff_object.on_xtype(xtype) xvalue = q if xtype == "q" else tth if xtype == "tth" else d diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index bd8c6c6e..8a2ed670 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -183,137 +183,153 @@ def test_init_invalid_xtype(): params_scale_to = [ # UC1: same x-array and y-array, check offset ( - [ - np.array([10, 15, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - np.array([10, 15, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - None, - 60, - None, - 2.1, - ], - ["tth", np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])], + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([10, 15, 25, 30, 60, 140]), + "target_yarray": np.array([2, 3, 4, 5, 6, 7]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 2.1, + }, + {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), # UC2: same length x-arrays with exact x-value match ( - [ - np.array([10, 15, 25, 30, 60, 140]), - np.array([10, 20, 25, 30, 60, 100]), - "tth", - 2 * np.pi, - np.array([10, 20, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - None, - 60, - None, - 0, - ], - ["tth", np.array([1, 2, 2.5, 3, 6, 10])], + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([10, 20, 25, 30, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([10, 20, 25, 30, 60, 140]), + "target_yarray": np.array([2, 3, 4, 5, 6, 7]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), # UC3: same length x-arrays with approximate x-value match ( - [ - np.array([0.12, 0.24, 0.31, 0.4]), - np.array([10, 20, 40, 60]), - "q", - 2 * np.pi, - np.array([0.14, 0.24, 0.31, 0.4]), - np.array([1, 3, 4, 5]), - "q", - 2 * np.pi, - 0.1, - None, - None, - 0, - ], - ["q", np.array([1, 2, 4, 6])], + { + "xarray": np.array([0.12, 0.24, 0.31, 0.4]), + "yarray": np.array([10, 20, 40, 60]), + "xtype": "q", + "wavelength": 2 * np.pi, + "target_xarray": np.array([0.14, 0.24, 0.31, 0.4]), + "target_yarray": np.array([1, 3, 4, 5]), + "target_xtype": "q", + "target_wavelength": 2 * np.pi, + "q": 0.1, + "tth": None, + "d": None, + "offset": 0, + }, + {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), # UC4: different x-array lengths with approximate x-value match ( - [ - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - None, - 0, - ], - # scaling factor is calculated at index = 5 for self and index = 6 for target - ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + # scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ] @pytest.mark.parametrize("inputs, expected", params_scale_to) def test_scale_to(inputs, expected): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + orig_diff_object = DiffractionObject( + xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] + ) target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + xarray=inputs["target_xarray"], + yarray=inputs["target_yarray"], + xtype=inputs["target_xtype"], + wavelength=inputs["target_wavelength"], ) scaled_diff_object = orig_diff_object.scale_to( - target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11] + target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] ) - # Check the intensity data is same as expected - assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) + # Check the intensity data is the same as expected + assert np.allclose(scaled_diff_object.on_xtype(expected["xtype"])[1], expected["yarray"]) params_scale_to_bad = [ # UC1: user did not specify anything ( - np.array([0.1, 0.2, 0.3]), - np.array([1, 2, 3]), - "q", - 2 * np.pi, - np.array([0.05, 0.1, 0.2, 0.3]), - np.array([5, 10, 20, 30]), - "q", - 2 * np.pi, - None, - None, - None, - 0, + { + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + "target_xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "target_yarray": np.array([5, 10, 20, 30]), + "target_xtype": "q", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": None, + "d": None, + "offset": 0, + } ), # UC2: user specified more than one of q, tth, and d ( - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - 10, - 0, + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": 10, + "offset": 0, + } ), ] @pytest.mark.parametrize("inputs", params_scale_to_bad) def test_scale_to_bad(inputs): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + orig_diff_object = DiffractionObject( + xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] + ) target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + xarray=inputs["target_xarray"], + yarray=inputs["target_yarray"], + xtype=inputs["target_xtype"], + wavelength=inputs["target_wavelength"], ) with pytest.raises( ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): - orig_diff_object.scale_to(target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11]) + orig_diff_object.scale_to( + target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] + ) params_index = [ From de6f12d8fe5f53ea1bded2da97d58bfc1ebb99b8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:41:39 -0500 Subject: [PATCH 398/696] make wavelength optional, test invalid init args --- src/diffpy/utils/diffraction_objects.py | 87 +++++++++++++++---------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 8765a258..ed42ba83 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -35,49 +35,64 @@ def _setter_wmsg(attribute): class DiffractionObject: + """ + Initialize a DiffractionObject instance. + + Parameters + ---------- + xarray : array-like + The independent variable array containing "q", "tth", or "d" values. + yarray : array-like + The dependent variable array corresponding to intensity values. + xtype : str + The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. + wavelength : float, optional + The wavelength of the incoming beam, specified in angstroms (Å). Default is none. + scat_quantity : str, optional + The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". + name : str, optional + The name or label for the scattering data. Default is an empty string "". + metadata : dict, optional + The additional metadata associated with the diffraction object. Default is {}. + + Examples + -------- + Create a DiffractionObject for X-ray scattering data: + + >>> import numpy as np + >>> from diffpy.utils.diffraction_objects import DiffractionObject + ... + >>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) + >>> y = np.array([10, 20, 40, 60]) # intensity valuester + >>> metadata = { + ... "package_info": {"version": "3.6.0"} + ... } + >>> do = DiffractionObject( + ... xarray=x, + ... yarray=y, + ... xtype="q", + ... wavelength=1.54, + ... scat_quantity="x-ray", + ... metadata=metadata + ... ) + >>> print(do.metadata) + """ + def __init__( self, xarray, yarray, xtype, - wavelength, + wavelength=None, scat_quantity="", name="", metadata={}, ): self._id = uuid.uuid4() - self.input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) - - def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name="", metadata={}): - """ - Insert a new scattering quantity into the scattering object. - - Parameters - ---------- - xarray : array-like - The independent variable array (e.g., "q", "tth", or "d"). - yarray : array-like - The dependent variable array corresponding to intensity values. - xtype : str - The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}, - such as "q", "tth", or "d". - wavelength : float - The wavelength of the incoming beam, specified in angstroms (Å). - scat_quantity : str, optional - The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". - name : str, optional - The name or label for the scattering data. Default is an empty string "". - metadata : dict, optional - The additional metadata associated with the diffraction object. Default is {}. - - Returns - ------- - None - This method updates the object in place and does not return a value. - """ + self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) - # Check xtype is valid. An empty string is the default value. + def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): if xtype not in XQUANTITIES: raise ValueError(_xtype_wmsg(xtype)) @@ -93,10 +108,9 @@ def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name=" self.wavelength = wavelength self.metadata = metadata self.name = name - self._input_xtype = xtype - self._set_xarrays(xarray, xtype) - self._all_arrays[:, 0] = yarray + self._set_arrays(xarray, xtype, yarray) + self._set_min_max_xarray() def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -346,8 +360,9 @@ def get_array_index(self, value, xtype=None): i = (np.abs(array - value)).argmin() return i - def _set_xarrays(self, xarray, xtype): + def _set_arrays(self, xarray, xtype, yarray): self._all_arrays = np.empty(shape=(len(xarray), 4)) + self._all_arrays[:, 0] = yarray if xtype.lower() in QQUANTITIES: self._all_arrays[:, 1] = xarray self._all_arrays[:, 2] = q_to_tth(xarray, self.wavelength) @@ -360,6 +375,8 @@ def _set_xarrays(self, xarray, xtype): self._all_arrays[:, 3] = xarray self._all_arrays[:, 1] = d_to_q(xarray) self._all_arrays[:, 2] = d_to_tth(xarray, self.wavelength) + + def _set_min_max_xarray(self): self.qmin = np.nanmin(self._all_arrays[:, 1], initial=np.inf) self.qmax = np.nanmax(self._all_arrays[:, 1], initial=0.0) self.tthmin = np.nanmin(self._all_arrays[:, 2], initial=np.inf) From 7ccebe86ea05baf1db7d24416809f2992f1fb8f0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:41:59 -0500 Subject: [PATCH 399/696] Add wavelength user behavior back --- tests/test_diffraction_objects.py | 47 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 7f18f29e..7ef5f945 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -15,7 +15,6 @@ { "name": "same", "scat_quantity": "x-ray", - "wavelength": 0.71, "xtype": "q", "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), @@ -24,7 +23,6 @@ { "name": "same", "scat_quantity": "x-ray", - "wavelength": 0.71, "xtype": "q", "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), @@ -35,8 +33,6 @@ ( # Different names { "name": "something", - "scat_quantity": "", - "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), @@ -44,8 +40,6 @@ }, { "name": "something else", - "scat_quantity": "", - "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), @@ -55,7 +49,6 @@ ), ( # Different wavelengths { - "scat_quantity": "", "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), @@ -63,7 +56,6 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, { - "scat_quantity": "", "wavelength": 0.42, "xtype": "tth", "xarray": np.empty(0), @@ -74,7 +66,6 @@ ), ( # Different wavelengths { - "scat_quantity": "", "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), @@ -82,7 +73,6 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, { - "scat_quantity": "", "wavelength": 0.711, "xtype": "tth", "xarray": np.empty(0), @@ -112,15 +102,12 @@ ), ( # Different on_q { - "scat_quantity": "", "xtype": "q", "wavelength": 0.71, "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), - "metadata": {}, }, { - "scat_quantity": "", "xtype": "q", "wavelength": 0.71, "xarray": np.array([3.0, 4.0]), @@ -131,7 +118,6 @@ ), ( # Different metadata { - "scat_quantity": "", "xtype": "q", "wavelength": 0.71, "xarray": np.empty(0), @@ -139,7 +125,6 @@ "metadata": {"thing1": 0, "thing2": "thing2"}, }, { - "scat_quantity": "", "xtype": "q", "wavelength": 0.71, "xarray": np.empty(0), @@ -166,7 +151,7 @@ def test_on_xtype(): assert np.allclose(do.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) -def test_init_invalid_xtype(do_minimal): +def test_init_invalid_xtype(): with pytest.raises( ValueError, match=re.escape( @@ -387,7 +372,8 @@ def test_dump(tmp_path, mocker): assert actual == expected -tc_params = [ + +@pytest.mark.parametrize("init_args, expected_do_dict", [ ( # instantiate just array attributes { "xarray": np.array([0.0, 90.0, 180.0]), @@ -445,14 +431,27 @@ def test_dump(tmp_path, mocker): "wavelength": 4.0 * np.pi, }, ), -] +]) +def test_init_valid(init_args, expected_do_dict): + actual_do_dict = DiffractionObject(**init_args).__dict__ + diff = DeepDiff(actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") + assert diff == {} -@pytest.mark.parametrize("inputs, expected", tc_params) -def test_constructor(inputs, expected): - actual = DiffractionObject(**inputs).__dict__ - diff = DeepDiff(actual, expected, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") - assert diff == {} + +@pytest.mark.parametrize("init_args, error_message", [ + ( # UC1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # UC2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), +]) +def test_init_invalid_args(init_args, error_message): + with pytest.raises(TypeError, match=error_message): + DiffractionObject(**init_args) def test_all_array_getter(): @@ -473,7 +472,7 @@ def test_all_array_getter(): def test_all_array_setter(do_minimal): - actual_do = do_minimal + do = do_minimal # Attempt to directly modify the property with pytest.raises( AttributeError, From 2752578001a76e8458e02a966e7f2adfb7ce3099 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 14 Dec 2024 18:42:08 +0000 Subject: [PATCH 400/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 150 ++++++++++++++++-------------- 1 file changed, 78 insertions(+), 72 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 7ef5f945..9fef29fa 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -372,83 +372,89 @@ def test_dump(tmp_path, mocker): assert actual == expected - -@pytest.mark.parametrize("init_args, expected_do_dict", [ - ( # instantiate just array attributes - { - "xarray": np.array([0.0, 90.0, 180.0]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "tth", - "wavelength": 4.0 * np.pi, - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "tth", - "name": "", - "scat_quantity": "", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ( # instantiate just array attributes - { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "d", - "wavelength": 4.0 * np.pi, - "scat_quantity": "x-ray", - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "d", - "name": "", - "scat_quantity": "x-ray", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), -]) +@pytest.mark.parametrize( + "init_args, expected_do_dict", + [ + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ], +) def test_init_valid(init_args, expected_do_dict): actual_do_dict = DiffractionObject(**init_args).__dict__ - diff = DeepDiff(actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") + diff = DeepDiff( + actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" + ) assert diff == {} - -@pytest.mark.parametrize("init_args, error_message", [ - ( # UC1: no arguments provided - {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", - ), - ( # UC2: only xarray and yarray provided - {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, - "missing 1 required positional argument: 'xtype'", - ), -]) +@pytest.mark.parametrize( + "init_args, error_message", + [ + ( # UC1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # UC2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), + ], +) def test_init_invalid_args(init_args, error_message): with pytest.raises(TypeError, match=error_message): DiffractionObject(**init_args) From d1f767b98c7c80a5688bc2bb0077c4bf440bb673 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:43:39 -0500 Subject: [PATCH 401/696] Restore wavelenght none in one of the test cases --- tests/test_diffraction_objects.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9fef29fa..9b6769b7 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -103,13 +103,11 @@ ( # Different on_q { "xtype": "q", - "wavelength": 0.71, "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), }, { "xtype": "q", - "wavelength": 0.71, "xarray": np.array([3.0, 4.0]), "yarray": np.array([100.0, 200.0]), "metadata": {"thing1": 1, "thing2": "thing2"}, From afe814b82d09e6bceeba64b3716aa10a2bae4ad8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:46:16 -0500 Subject: [PATCH 402/696] Fix typo in docstrings --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ed42ba83..6aa54682 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -63,7 +63,7 @@ class DiffractionObject: >>> from diffpy.utils.diffraction_objects import DiffractionObject ... >>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) - >>> y = np.array([10, 20, 40, 60]) # intensity valuester + >>> y = np.array([10, 20, 40, 60]) # intensity values >>> metadata = { ... "package_info": {"version": "3.6.0"} ... } From 46c7be89e9cb53433bbf4b428dd16817a5be3f5a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:56:07 -0500 Subject: [PATCH 403/696] Use refined param names for pytest parametrize --- tests/test_diffraction_objects.py | 154 ++++++++++++++++-------------- 1 file changed, 80 insertions(+), 74 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9b6769b7..ecd5db16 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -370,67 +370,70 @@ def test_dump(tmp_path, mocker): assert actual == expected +test_init_valid_params = [ + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), +] + + @pytest.mark.parametrize( "init_args, expected_do_dict", - [ - ( # instantiate just array attributes - { - "xarray": np.array([0.0, 90.0, 180.0]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "tth", - "wavelength": 4.0 * np.pi, - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "tth", - "name": "", - "scat_quantity": "", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ( # instantiate just array attributes - { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "d", - "wavelength": 4.0 * np.pi, - "scat_quantity": "x-ray", - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "d", - "name": "", - "scat_quantity": "x-ray", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ], + test_init_valid_params, ) def test_init_valid(init_args, expected_do_dict): actual_do_dict = DiffractionObject(**init_args).__dict__ @@ -440,21 +443,24 @@ def test_init_valid(init_args, expected_do_dict): assert diff == {} -@pytest.mark.parametrize( - "init_args, error_message", - [ - ( # UC1: no arguments provided - {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", - ), - ( # UC2: only xarray and yarray provided - {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, - "missing 1 required positional argument: 'xtype'", - ), - ], -) -def test_init_invalid_args(init_args, error_message): - with pytest.raises(TypeError, match=error_message): +test_init_invalid_params = [ + ( # UC1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # UC2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), +] + + +@pytest.mark.parametrize("init_args, expected_error_msg", test_init_invalid_params) +def test_init_invalid_args( + init_args, + expected_error_msg, +): + with pytest.raises(TypeError, match=expected_error_msg): DiffractionObject(**init_args) From d24aacdb5ffea1778f4602f71fdd585f70babfac Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:58:25 -0500 Subject: [PATCH 404/696] Remove hard coded wavelength in test func --- tests/test_diffraction_objects.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ecd5db16..992be091 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -85,7 +85,6 @@ { "scat_quantity": "x-ray", "xtype": "tth", - "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -93,7 +92,6 @@ { "scat_quantity": "neutron", "xtype": "tth", - "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -117,14 +115,12 @@ ( # Different metadata { "xtype": "q", - "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 0, "thing2": "thing2"}, }, { "xtype": "q", - "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, From e285214524534ee10c4335c9277edec5e45e9b8a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 14:08:03 -0500 Subject: [PATCH 405/696] Add news for requires 3 input parameters of , , --- news/no-empty-object.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/no-empty-object.rst diff --git a/news/no-empty-object.rst b/news/no-empty-object.rst new file mode 100644 index 00000000..02126c98 --- /dev/null +++ b/news/no-empty-object.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* `DiffractionObject` requires 3 input parameters of `xarrays`, `yarrays`, `xtype` + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 1ee215716b2a30b5230519a266f4eb3d719faf1d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 14:22:35 -0500 Subject: [PATCH 406/696] Refactor test_on_xtype using a minimal do defined in conftest --- tests/conftest.py | 8 ++++++++ tests/test_diffraction_objects.py | 20 ++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 09de40f6..c0767b49 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,11 @@ import json from pathlib import Path +import numpy as np import pytest +from diffpy.utils.diffraction_objects import DiffractionObject + @pytest.fixture def user_filesystem(tmp_path): @@ -28,3 +31,8 @@ def _load(filename): return base_path / filename return _load + + +@pytest.fixture +def do_minimal_tth(): + return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 65519eac..74e0e3cd 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -163,12 +163,20 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (do_1 == do_2) == expected -def test_on_xtype(): - do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") - assert np.allclose(do.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(do.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(do.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])]) - assert np.allclose(do.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) +@pytest.mark.parametrize( + "input_xtype, expected_xarray", + [ + ("tth", np.array([30, 60])), + ("2theta", np.array([30, 60])), + ("q", np.array([0.51764, 1])), + ("d", np.array([12.13818, 6.28319])), + ], +) +def test_on_xtype(input_xtype, expected_xarray, do_minimal_tth): + do = do_minimal_tth + result = do.on_xtype(input_xtype) + assert np.allclose(result[0], expected_xarray) + assert np.allclose(result[1], np.array([1, 2])) def test_init_invalid_xtype(): From 8053d315c22c9454c2071e35c1b17b9a24d8b57c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 14:30:11 -0500 Subject: [PATCH 407/696] improve test variable name --- tests/test_diffraction_objects.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 74e0e3cd..4d210e3e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -164,7 +164,7 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): @pytest.mark.parametrize( - "input_xtype, expected_xarray", + "xtype, expected_xarray", [ ("tth", np.array([30, 60])), ("2theta", np.array([30, 60])), @@ -172,11 +172,11 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): ("d", np.array([12.13818, 6.28319])), ], ) -def test_on_xtype(input_xtype, expected_xarray, do_minimal_tth): +def test_on_xtype(xtype, expected_xarray, do_minimal_tth): do = do_minimal_tth - result = do.on_xtype(input_xtype) - assert np.allclose(result[0], expected_xarray) - assert np.allclose(result[1], np.array([1, 2])) + actual_xrray, actual_yarray = do.on_xtype(xtype) + assert np.allclose(actual_xrray, expected_xarray) + assert np.allclose(actual_yarray, np.array([1, 2])) def test_init_invalid_xtype(): From 3a3fcecd2ca0fffaf4862e42ab73f238be9f17dd Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 15:04:27 -0500 Subject: [PATCH 408/696] Remove set_angles_from_list, set_angles_from, set_qs_from_range and add news --- news/rm-range-methods.rst | 23 +++++++++ src/diffpy/utils/diffraction_objects.py | 67 ------------------------- 2 files changed, 23 insertions(+), 67 deletions(-) create mode 100644 news/rm-range-methods.rst diff --git a/news/rm-range-methods.rst b/news/rm-range-methods.rst new file mode 100644 index 00000000..8150da16 --- /dev/null +++ b/news/rm-range-methods.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* `set_angles_from_list`, `set_angles_from`, `set_qs_from_range` methods in `DiffractionObject` + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index b5179874..a242c895 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -215,73 +215,6 @@ def id(self): def id(self, _): raise AttributeError(_setter_wmsg("id")) - def set_angles_from_list(self, angles_list): - self.angles = angles_list - self.n_steps = len(angles_list) - 1.0 - self.begin_angle = self.angles[0] - self.end_angle = self.angles[-1] - - def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): - """ - create an array of linear spaced Q-values - - Parameters - ---------- - begin_q float - the beginning angle - end_q float - the ending angle - step_size float - the size of the step between points. Only specify step_size or n_steps, not both - n_steps integer - the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both - - Returns - ------- - Sets self.qs - self.qs array of floats - the q values in the independent array - - """ - self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) - - def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): - """ - create an array of linear spaced angle-values - - Parameters - ---------- - begin_angle float - the beginning angle - end_angle float - the ending angle - step_size float - the size of the step between points. Only specify step_size or n_steps, not both - n_steps integer - the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both - - Returns - ------- - Sets self.angles - self.angles array of floats - the q values in the independent array - - """ - self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) - - def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): - if step_size is not None and n_steps is not None: - print( - "WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be " - "reset." - ) - array = np.linspace(begin, end, n_steps) - elif step_size is not None: - array = np.arange(begin, end, step_size) - elif n_steps is not None: - array = np.linspace(begin, end, n_steps) - return array - def get_array_index(self, value, xtype=None): """ Return the index of the closest value in the array associated with the specified xtype. From 784bc65f9182c5d035c7ac6fe7f75002f23f2843 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Sat, 14 Dec 2024 16:53:43 -0500 Subject: [PATCH 409/696] add deprecation warning to resample function --- news/resample-dep.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/resampler.py | 9 +++++++++ 2 files changed, 32 insertions(+) create mode 100644 news/resample-dep.rst diff --git a/news/resample-dep.rst b/news/resample-dep.rst new file mode 100644 index 00000000..91bb3e3e --- /dev/null +++ b/news/resample-dep.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* `resample` function in resampler. Replaced with `wsinterp` with better functionality. + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 5f6062d2..07f915f9 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -15,6 +15,8 @@ """Various utilities related to data parsing and manipulation.""" +import warnings + import numpy as np @@ -97,6 +99,13 @@ def resample(r, s, dr): Returns resampled (r, s). """ + warnings.warn( + "The 'resample' function is deprecated and will be removed in a future release (3.8.0). \n" + "'resample' has been renamed 'wsinterp' to better reflect functionality. Please use 'wsinterp' instead.", + DeprecationWarning, + stacklevel=2, + ) + dr0 = r[1] - r[0] # Constant timestep if dr0 < dr: From ebfd3edb2d710a8a13b8c5ae53e49361f6b09b5f Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Sat, 14 Dec 2024 20:02:10 -0500 Subject: [PATCH 410/696] nsinterp --- doc/source/examples/resampleexample.rst | 10 ++++++- news/nsinterp.rst | 23 ++++++++++++++ src/diffpy/utils/resampler.py | 40 +++++++++++++++++++++++++ tests/test_resample.py | 22 +++++++++++++- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 news/nsinterp.rst diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resampleexample.rst index f7ba5e18..2757b791 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resampleexample.rst @@ -58,7 +58,7 @@ given enough datapoints. nickel_resample = wsinterp(grid, nickel_grid, nickel_func) target_resample = wsinterp(grid, target_grid, target_func) - We can now plot the difference to see that these two functions are quite similar.: + We can now plot the difference to see that these two functions are quite similar.:: plt.plot(grid, target_resample) plt.plot(grid, nickel_resample) @@ -78,3 +78,11 @@ given enough datapoints. In the case of our dataset, our band-limit is ``qmax=25.0`` and our function spans :math:`r \in (0.0, 60.0)`. Thus, our original grid requires :math:`25.0 * 60.0 / \pi < 478`. Since our grid has :math:`601` datapoints, our reconstruction was perfect as shown from the comparison between ``Nickel.gr`` and ``NiTarget.gr``. + + This computation is implemented in the function ``nsinterp``.:: + + from diffpy.utils.resampler import nsinterp + qmin = 0 + qmax = 25 + nickel_resample = (nickel_grid, nickel_func, qmin, qmax) + diff --git a/news/nsinterp.rst b/news/nsinterp.rst new file mode 100644 index 00000000..9b716b87 --- /dev/null +++ b/news/nsinterp.rst @@ -0,0 +1,23 @@ +**Added:** + +* Function nsinterp for automatic interpolation onto the Nyquist-Shannon grid. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 5f6062d2..76108404 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -77,6 +77,46 @@ def wsinterp(x, xp, fp, left=None, right=None): return fp_at_x +def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): + """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon grid. + + Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. + Uses the minimum number of points N required by the Nyquist sampling theorem. + N = (qmax-qmin)(rmax-rmin)/pi, where rmin and rmax are the ends of the real-space ranges. + fp must be finite, and the user inputs qmin and qmax of the frequency-domain. + + Parameters + ---------- + xp: ndarray + The array of known x values. + fp: ndarray + The array of y values associated with xp. + qmin: float + The lower band limit in the frequency domain. + qmax: float + The upper band limit in the frequency domain. + + Returns + ------- + x: ndarray + The Nyquist-Shannon grid computed for the given qmin and qmax. + fp_at_x: ndarray + The interpolated values at points x. Returns a single float if x is a scalar, + otherwise returns a numpy.ndarray. + """ + # Ensure numpy array + xp = np.array(xp) + rmin = np.min(xp) + rmax = np.max(xp) + + nspoints = int(np.round((qmax-qmin)*(rmax-rmin)/np.pi)) + + x = np.linspace(rmin, rmax, nspoints) + fp_at_x = wsinterp(x, xp, fp) + + return x, fp_at_x + + def resample(r, s, dr): """Resample a PDF on a new grid. diff --git a/tests/test_resample.py b/tests/test_resample.py index 784932bb..e43af070 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from diffpy.utils.resampler import wsinterp +from diffpy.utils.resampler import wsinterp, nsinterp def test_wsinterp(): @@ -30,3 +30,23 @@ def test_wsinterp(): assert np.allclose(fp_at_x[1:-1], fp) for i in range(len(x)): assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp)) + + +def test_nsinterp(): + # Create a cosine function cos(2x) for x \in [0, 3pi] + xp = np.linspace(0, 3*np.pi, 100) + fp = np.cos(2 * xp) + + # Want to resample onto the grid {0, pi, 2pi, 3pi} + x = np.array([0, np.pi, 2*np.pi, 3*np.pi]) + + # Get wsinterp result + ws_f = wsinterp(x, xp, fp) + + # Use nsinterp with qmin-qmax=4/3 + qmin = np.random.uniform() + qmax = qmin + 4/3 + ns_x, ns_f = nsinterp(xp, fp, qmin, qmax) + + assert np.allclose(x, ns_x) + assert np.allclose(ws_f, ns_f) From b1213b1f557de11f9aa20b6ad20974fbe50fb598 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 01:07:07 +0000 Subject: [PATCH 411/696] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/examples/resampleexample.rst | 1 - src/diffpy/utils/resampler.py | 2 +- tests/test_resample.py | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resampleexample.rst index 2757b791..3166e42b 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resampleexample.rst @@ -85,4 +85,3 @@ given enough datapoints. qmin = 0 qmax = 25 nickel_resample = (nickel_grid, nickel_func, qmin, qmax) - diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 76108404..890f29fb 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -109,7 +109,7 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): rmin = np.min(xp) rmax = np.max(xp) - nspoints = int(np.round((qmax-qmin)*(rmax-rmin)/np.pi)) + nspoints = int(np.round((qmax - qmin) * (rmax - rmin) / np.pi)) x = np.linspace(rmin, rmax, nspoints) fp_at_x = wsinterp(x, xp, fp) diff --git a/tests/test_resample.py b/tests/test_resample.py index e43af070..6e6294ed 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from diffpy.utils.resampler import wsinterp, nsinterp +from diffpy.utils.resampler import nsinterp, wsinterp def test_wsinterp(): @@ -34,18 +34,18 @@ def test_wsinterp(): def test_nsinterp(): # Create a cosine function cos(2x) for x \in [0, 3pi] - xp = np.linspace(0, 3*np.pi, 100) + xp = np.linspace(0, 3 * np.pi, 100) fp = np.cos(2 * xp) # Want to resample onto the grid {0, pi, 2pi, 3pi} - x = np.array([0, np.pi, 2*np.pi, 3*np.pi]) + x = np.array([0, np.pi, 2 * np.pi, 3 * np.pi]) # Get wsinterp result ws_f = wsinterp(x, xp, fp) # Use nsinterp with qmin-qmax=4/3 qmin = np.random.uniform() - qmax = qmin + 4/3 + qmax = qmin + 4 / 3 ns_x, ns_f = nsinterp(xp, fp, qmin, qmax) assert np.allclose(x, ns_x) From 5d88cf6595e67cb923d007397e4c907c8b85bd76 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 23:45:12 -0500 Subject: [PATCH 412/696] Remove 2 wavelength warnings with test_tth_to_q func --- tests/conftest.py | 9 +++++++ tests/test_transforms.py | 51 +++++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c0767b49..44f66178 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,3 +36,12 @@ def _load(filename): @pytest.fixture def do_minimal_tth(): return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + +@pytest.fixture +def wavelength_warning_msg(): + return ( + "No wavelength has been specified. You can continue to use the DiffractionObject, but " + "some of its powerful features will not be available. " + "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." +) \ No newline at end of file diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 909026e6..e9bb54bf 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,6 +5,7 @@ from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q + params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), @@ -23,16 +24,10 @@ @pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) -def test_q_to_tth(wavelength, q, expected_tth): - - wavelength_warning_emsg = ( - "No wavelength has been specified. You can continue to use the DiffractionObject, but " - "some of its powerful features will not be available. " - "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." - ) +def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): + if wavelength is None: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_emsg)): + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) @@ -66,7 +61,7 @@ def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): q_to_tth(wavelength, q) -params_tth_to_q = [ +test_tth_t_q_params = [ # UC0: User specified empty tth values (without wavelength) (None, np.array([]), np.array([])), # UC1: User specified empty tth values (with wavelength) @@ -86,31 +81,38 @@ def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): ), ] - -@pytest.mark.parametrize("wavelength, tth, expected_q", params_tth_to_q) -def test_tth_to_q(wavelength, tth, expected_q): - actual_q = tth_to_q(tth, wavelength) +@pytest.mark.parametrize("wavelength, tth, expected_q", test_tth_t_q_params) +def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): + if wavelength is None: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + actual_q = tth_to_q(tth, wavelength) + else: + actual_q = tth_to_q(tth, wavelength) + assert np.allclose(actual_q, expected_q) - -params_tth_to_q_bad = [ +tth_to_q_bad_params = [ # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) ( - [None, np.array([0, 30, 60, 90, 120, 181])], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + None, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", ), # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) ( - [4 * np.pi, np.array([0, 30, 60, 90, 120, 181])], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + 4 * np.pi, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", ), ] -@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) -def test_tth_to_q_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - tth_to_q(inputs[1], inputs[0]) +@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", tth_to_q_bad_params) +def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): + with pytest.raises(expected_error_type, match=expected_error_msg): + tth_to_q(tth, wavelength) params_q_to_d = [ @@ -212,6 +214,7 @@ def test_tth_to_d_bad(inputs, expected): @pytest.mark.parametrize("inputs, expected", params_d_to_tth) def test_d_to_tth(inputs, expected): actual = d_to_tth(inputs[1], inputs[0]) + assert np.allclose(expected, actual) From 73d8ada4e489a7f84c5bb6f8f0ecc5ecaf695d23 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 00:00:43 -0500 Subject: [PATCH 413/696] Refactor transform tests, pass variables to parameters --- tests/conftest.py | 11 ++-- tests/test_transforms.py | 136 ++++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 70 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 44f66178..866dce39 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,11 +37,12 @@ def _load(filename): def do_minimal_tth(): return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + @pytest.fixture def wavelength_warning_msg(): return ( - "No wavelength has been specified. You can continue to use the DiffractionObject, but " - "some of its powerful features will not be available. " - "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." -) \ No newline at end of file + "No wavelength has been specified. You can continue to use the DiffractionObject, but " + "some of its powerful features will not be available. " + "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." + ) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index e9bb54bf..177745f1 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,7 +5,6 @@ from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q - params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), @@ -35,7 +34,7 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): assert np.allclose(expected_tth, actual_tth) -params_q_to_tth_bad = [ +test_q_to_tth_bad_params = [ # UC1: user specified invalid q values that result in tth > 180 degrees ( 4 * np.pi, @@ -55,7 +54,7 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): ] -@pytest.mark.parametrize("q, wavelength, expected_error_type, expected_error_msg", params_q_to_tth_bad) +@pytest.mark.parametrize("q, wavelength, expected_error_type, expected_error_msg", test_q_to_tth_bad_params) def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): with pytest.raises(expected_error_type, match=expected_error_msg): q_to_tth(wavelength, q) @@ -81,6 +80,7 @@ def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): ), ] + @pytest.mark.parametrize("wavelength, tth, expected_q", test_tth_t_q_params) def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): if wavelength is None: @@ -88,10 +88,11 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): actual_q = tth_to_q(tth, wavelength) else: actual_q = tth_to_q(tth, wavelength) - + assert np.allclose(actual_q, expected_q) -tth_to_q_bad_params = [ + +test_tth_to_q_bad_params = [ # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) ( None, @@ -109,138 +110,143 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): ] -@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", tth_to_q_bad_params) +@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", test_tth_to_q_bad_params) def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_q(tth, wavelength) -params_q_to_d = [ +test_q_to_d_params = [ # UC1: User specified empty q values - ([np.array([])], np.array([])), + (np.array([]), np.array([])), # UC2: User specified valid q values ( - [np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), ), ] -@pytest.mark.parametrize("inputs, expected", params_q_to_d) -def test_q_to_d(inputs, expected): - actual = q_to_d(inputs[0]) - assert np.allclose(actual, expected) +@pytest.mark.parametrize("q, expected_d", test_q_to_d_params) +def test_q_to_d(q, expected_d): + actual_d = q_to_d(q) + assert np.allclose(actual_d, expected_d) -params_d_to_q = [ +test_d_to_q_params = [ # UC1: User specified empty d values - ([np.array([])], np.array([])), + (np.array([]), np.array([])), # UC2: User specified valid d values ( - [np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0])], + np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), ), ] -@pytest.mark.parametrize("inputs, expected", params_d_to_q) -def test_d_to_q(inputs, expected): - actual = d_to_q(inputs[0]) - assert np.allclose(actual, expected) +@pytest.mark.parametrize("d, expected_q", test_d_to_q_params) +def test_d_to_q(d, expected_q): + actual_q = d_to_q(d) + assert np.allclose(actual_q, expected_q) -params_tth_to_d = [ +test_tth_to_d_params = [ # UC0: User specified empty tth values (without wavelength) - ([None, np.array([])], np.array([])), + (None, np.array([]), np.array([])), # UC1: User specified empty tth values (with wavelength) - ([4 * np.pi, np.array([])], np.array([])), + (4 * np.pi, np.array([]), np.array([])), # UC2: User specified valid tth values between 0-180 degrees (without wavelength) ( - [None, np.array([0, 30, 60, 90, 120, 180])], + None, + np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) ( - [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], + 4 * np.pi, + np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), ), ] -@pytest.mark.parametrize("inputs, expected", params_tth_to_d) -def test_tth_to_d(inputs, expected): - actual = tth_to_d(inputs[1], inputs[0]) - assert np.allclose(actual, expected) +@pytest.mark.parametrize("wavelength, tth, expected_d", test_tth_to_d_params) +def test_tth_to_d(wavelength, tth, expected_d): + actual_d = tth_to_d(tth, wavelength) + assert np.allclose(actual_d, expected_d) -params_tth_to_d_bad = [ +test_tth_to_d_invalid_params = [ # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) ( - [None, np.array([0, 30, 60, 90, 120, 181])], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + None, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", ), # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) ( - [4 * np.pi, np.array([0, 30, 60, 90, 120, 181])], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + 4 * np.pi, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", ), ] -@pytest.mark.parametrize("inputs, expected", params_tth_to_d_bad) -def test_tth_to_d_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - tth_to_d(inputs[1], inputs[0]) +@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", test_tth_to_d_invalid_params) +def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_msg): + with pytest.raises(expected_error_type, match=expected_error_msg): + tth_to_d(tth, wavelength) -params_d_to_tth = [ +test_d_to_tth_params = [ # UC1: Empty d values, no wavelength, return empty arrays - ([None, np.empty((0))], np.empty((0))), + (None, np.empty((0)), np.empty((0))), # UC2: Empty d values, wavelength specified, return empty arrays - ([4 * np.pi, np.empty((0))], np.empty(0)), + (4 * np.pi, np.empty((0)), np.empty(0)), # UC3: User specified valid d values, no wavelength, return empty arrays ( - [None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])], + None, + np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), ), # UC4: User specified valid d values (with wavelength) ( - [4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi])], + 4 * np.pi, + np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), ), ] -@pytest.mark.parametrize("inputs, expected", params_d_to_tth) -def test_d_to_tth(inputs, expected): - actual = d_to_tth(inputs[1], inputs[0]) +@pytest.mark.parametrize("wavelength, d, expected_tth", test_d_to_tth_params) +def test_d_to_tth(wavelength, d, expected_tth): + actual_tth = d_to_tth(d, wavelength) + assert np.allclose(actual_tth, expected_tth) - assert np.allclose(expected, actual) - -params_d_to_tth_bad = [ +test_d_to_tth_bad_params = [ # UC1: user specified invalid d values that result in tth > 180 degrees ( - [4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2])], - [ - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], + 4 * np.pi, + np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])], - [ - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], + 100, + np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", ), ] -@pytest.mark.parametrize("inputs, expected", params_d_to_tth_bad) -def test_d_to_tth_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - d_to_tth(inputs[1], inputs[0]) +@pytest.mark.parametrize("wavelength, d, expected_error_type, expected_error_msg", test_d_to_tth_bad_params) +def test_d_to_tth_bad(wavelength, d, expected_error_type, expected_error_msg): + with pytest.raises(expected_error_type, match=expected_error_msg): + d_to_tth(d, wavelength) From 5e395388634b268b0d9f81b8d3dc7e4d2a7cd43f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 00:16:16 -0500 Subject: [PATCH 414/696] Remove the use of extra variable for params to prevent global scope in the file --- tests/conftest.py | 8 + tests/test_transforms.py | 327 +++++++++++++++++++-------------------- 2 files changed, 164 insertions(+), 171 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 866dce39..6d23ba55 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,3 +46,11 @@ def wavelength_warning_msg(): "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) + + +@pytest.fixture +def invalid_q_or_d_or_wavelength_error_msg(): + return ( + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values." + ) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 177745f1..4ebb8a73 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,24 +5,25 @@ from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q -params_q_to_tth = [ - # UC1: Empty q values, no wavelength, return empty arrays - (None, np.empty((0)), np.empty((0))), - # UC2: Empty q values, wavelength specified, return empty arrays - (4 * np.pi, np.empty((0)), np.empty(0)), - # UC3: User specified valid q values, no wavelength, return empty arrays - ( - None, - np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC4: User specified valid q values (with wavelength) - # expected tth values are 2*arcsin(q) in degrees - (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), -] - -@pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) +@pytest.mark.parametrize( + "wavelength, q, expected_tth", + [ + # UC1: Empty q values, no wavelength, return empty arrays + (None, np.empty((0)), np.empty((0))), + # UC2: Empty q values, wavelength specified, return empty arrays + (4 * np.pi, np.empty((0)), np.empty(0)), + # UC3: user specified valid q values, no wavelength, return empty arrays + ( + None, + np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC4: user specified valid q values (with wavelength) + # expected tth values are 2*arcsin(q) in degrees + (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), + ], +) def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): if wavelength is None: @@ -34,54 +35,51 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): assert np.allclose(expected_tth, actual_tth) -test_q_to_tth_bad_params = [ - # UC1: user specified invalid q values that result in tth > 180 degrees - ( - 4 * np.pi, - np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees - ( - 100, - np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ), -] - - -@pytest.mark.parametrize("q, wavelength, expected_error_type, expected_error_msg", test_q_to_tth_bad_params) -def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): +@pytest.mark.parametrize( + "wavelength, q, expected_error_type", + [ + # UC1: user specified invalid q values that result in tth > 180 degrees + ( + 4 * np.pi, + np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), + ValueError, + ), + # UC2: user specified a wrong wavelength that result in tth > 180 degrees + ( + 100, + np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), + ValueError, + ), + ], +) +def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): + expected_error_msg = invalid_q_or_d_or_wavelength_error_msg with pytest.raises(expected_error_type, match=expected_error_msg): q_to_tth(wavelength, q) -test_tth_t_q_params = [ - # UC0: User specified empty tth values (without wavelength) - (None, np.array([]), np.array([])), - # UC1: User specified empty tth values (with wavelength) - (4 * np.pi, np.array([]), np.array([])), - # UC2: User specified valid tth values between 0-180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 180]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC3: User specified valid tth values between 0-180 degrees (with wavelength) - # expected q values are sin15, sin30, sin45, sin60, sin90 - ( - 4 * np.pi, - np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), - np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), - ), -] - - -@pytest.mark.parametrize("wavelength, tth, expected_q", test_tth_t_q_params) +@pytest.mark.parametrize( + "wavelength, tth, expected_q", + [ + # UC0: user specified empty tth values (without wavelength) + (None, np.array([]), np.array([])), + # UC1: user specified empty tth values (with wavelength) + (4 * np.pi, np.array([]), np.array([])), + # UC2: user specified valid tth values between 0-180 degrees (without wavelength) + ( + None, + np.array([0, 30, 60, 90, 120, 180]), + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC3: user specified valid tth values between 0-180 degrees (with wavelength) + # expected q values are sin15, sin30, sin45, sin60, sin90 + ( + 4 * np.pi, + np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), + np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), + ), + ], +) def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): @@ -92,115 +90,116 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): assert np.allclose(actual_q, expected_q) -test_tth_to_q_bad_params = [ - # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 181]), - ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", - ), - # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) - ( - 4 * np.pi, - np.array([0, 30, 60, 90, 120, 181]), - ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", - ), -] - - -@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", test_tth_to_q_bad_params) +@pytest.mark.parametrize( + "wavelength, tth, expected_error_type, expected_error_msg", + [ + # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + None, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + 4 * np.pi, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + ], +) def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_q(tth, wavelength) -test_q_to_d_params = [ - # UC1: User specified empty q values - (np.array([]), np.array([])), - # UC2: User specified valid q values - ( - np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), - np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), - ), -] - - -@pytest.mark.parametrize("q, expected_d", test_q_to_d_params) +@pytest.mark.parametrize( + "q, expected_d", + [ + # UC1: User specified empty q values + (np.array([]), np.array([])), + # UC2: User specified valid q values + ( + np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), + np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), + ), + ], +) def test_q_to_d(q, expected_d): actual_d = q_to_d(q) assert np.allclose(actual_d, expected_d) -test_d_to_q_params = [ - # UC1: User specified empty d values - (np.array([]), np.array([])), - # UC2: User specified valid d values - ( - np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), - np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), - ), -] - - -@pytest.mark.parametrize("d, expected_q", test_d_to_q_params) +@pytest.mark.parametrize( + "d, expected_q", + [ + # UC1: User specified empty d values + (np.array([]), np.array([])), + # UC2: User specified valid d values + ( + np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), + np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), + ), + ], +) def test_d_to_q(d, expected_q): actual_q = d_to_q(d) assert np.allclose(actual_q, expected_q) -test_tth_to_d_params = [ - # UC0: User specified empty tth values (without wavelength) - (None, np.array([]), np.array([])), - # UC1: User specified empty tth values (with wavelength) - (4 * np.pi, np.array([]), np.array([])), - # UC2: User specified valid tth values between 0-180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 180]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC3: User specified valid tth values between 0-180 degrees (with wavelength) - ( - 4 * np.pi, - np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), - np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), - ), -] - - -@pytest.mark.parametrize("wavelength, tth, expected_d", test_tth_to_d_params) +@pytest.mark.parametrize( + "wavelength, tth, expected_d", + [ + # UC0: User specified empty tth values (without wavelength) + (None, np.array([]), np.array([])), + # UC1: User specified empty tth values (with wavelength) + (4 * np.pi, np.array([]), np.array([])), + # UC2: User specified valid tth values between 0-180 degrees (without wavelength) + ( + None, + np.array([0, 30, 60, 90, 120, 180]), + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + ( + 4 * np.pi, + np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), + np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), + ), + ], +) def test_tth_to_d(wavelength, tth, expected_d): actual_d = tth_to_d(tth, wavelength) assert np.allclose(actual_d, expected_d) -test_tth_to_d_invalid_params = [ - # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 181]), - ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", - ), - # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) - ( - 4 * np.pi, - np.array([0, 30, 60, 90, 120, 181]), - ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", - ), -] - - -@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", test_tth_to_d_invalid_params) +@pytest.mark.parametrize( + "wavelength, tth, expected_error_type, expected_error_msg", + [ + # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + None, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + 4 * np.pi, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + ], +) def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_msg): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_d(tth, wavelength) -test_d_to_tth_params = [ + +@pytest.mark.parametrize("wavelength, d, expected_tth", [ # UC1: Empty d values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), # UC2: Empty d values, wavelength specified, return empty arrays @@ -217,36 +216,22 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), ), -] - - -@pytest.mark.parametrize("wavelength, d, expected_tth", test_d_to_tth_params) +]) def test_d_to_tth(wavelength, d, expected_tth): actual_tth = d_to_tth(d, wavelength) assert np.allclose(actual_tth, expected_tth) -test_d_to_tth_bad_params = [ - # UC1: user specified invalid d values that result in tth > 180 degrees - ( - 4 * np.pi, - np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees - ( - 100, - np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ), -] - - -@pytest.mark.parametrize("wavelength, d, expected_error_type, expected_error_msg", test_d_to_tth_bad_params) -def test_d_to_tth_bad(wavelength, d, expected_error_type, expected_error_msg): +@pytest.mark.parametrize( + "wavelength, d, expected_error_type", + [ + # UC1: user specified invalid d values that result in tth > 180 degrees + (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), + # UC2: user specified a wrong wavelength that result in tth > 180 degrees + (100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), ValueError), + ], +) +def test_d_to_tth_bad(wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): + expected_error_msg = invalid_q_or_d_or_wavelength_error_msg with pytest.raises(expected_error_type, match=expected_error_msg): d_to_tth(d, wavelength) From cdf96dffe5c1745af5ddb952592f310428d23fa5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 00:17:38 -0500 Subject: [PATCH 415/696] Apply pre-commit --- tests/test_transforms.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 4ebb8a73..487cf6ca 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -198,25 +198,27 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m tth_to_d(tth, wavelength) - -@pytest.mark.parametrize("wavelength, d, expected_tth", [ - # UC1: Empty d values, no wavelength, return empty arrays - (None, np.empty((0)), np.empty((0))), - # UC2: Empty d values, wavelength specified, return empty arrays - (4 * np.pi, np.empty((0)), np.empty(0)), - # UC3: User specified valid d values, no wavelength, return empty arrays - ( - None, - np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC4: User specified valid d values (with wavelength) - ( - 4 * np.pi, - np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), - np.array([60.0, 90.0, 120.0]), - ), -]) +@pytest.mark.parametrize( + "wavelength, d, expected_tth", + [ + # UC1: Empty d values, no wavelength, return empty arrays + (None, np.empty((0)), np.empty((0))), + # UC2: Empty d values, wavelength specified, return empty arrays + (4 * np.pi, np.empty((0)), np.empty(0)), + # UC3: User specified valid d values, no wavelength, return empty arrays + ( + None, + np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC4: User specified valid d values (with wavelength) + ( + 4 * np.pi, + np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), + np.array([60.0, 90.0, 120.0]), + ), + ], +) def test_d_to_tth(wavelength, d, expected_tth): actual_tth = d_to_tth(d, wavelength) assert np.allclose(actual_tth, expected_tth) From 01e71dc9c61124c903c2cead537a40d0f814612f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 15 Dec 2024 16:25:44 -0500 Subject: [PATCH 416/696] initial commit --- .../examples/diffractionobjectsexample.rst | 34 ------------ doc/source/examples/transformsexample.rst | 53 +++++++++++++++++++ .../utilities/diffractionobjectsutility.rst | 13 ----- doc/source/utilities/transformsutility.rst | 14 +++++ 4 files changed, 67 insertions(+), 47 deletions(-) delete mode 100644 doc/source/examples/diffractionobjectsexample.rst create mode 100644 doc/source/examples/transformsexample.rst delete mode 100644 doc/source/utilities/diffractionobjectsutility.rst create mode 100644 doc/source/utilities/transformsutility.rst diff --git a/doc/source/examples/diffractionobjectsexample.rst b/doc/source/examples/diffractionobjectsexample.rst deleted file mode 100644 index 48409432..00000000 --- a/doc/source/examples/diffractionobjectsexample.rst +++ /dev/null @@ -1,34 +0,0 @@ -.. _Diffraction Objects Example: - -:tocdepth: -1 - -Diffraction Objects Example -########################### - -This example will demonstrate how to use the ``DiffractionObject`` class in the -``diffpy.utils.diffraction_objects`` module to process and analyze diffraction data. - -1) Assuming we have created a ``DiffractionObject`` called my_diffraction_pattern from a measured diffraction pattern, - and we have specified the wavelength (see Section ??, to be added), - we can use the ``q_to_tth`` and ``tth_to_q`` functions to convert between q and two-theta. :: - - # Example: convert q to tth - my_diffraction_pattern.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] - my_diffraction_pattern.q_to_tth() - - This function will convert your provided q array and return a two theta array in degrees. - To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. :: - - # Example: convert tth to q - from diffpy.utils.diffraction_objects import DiffractionObject - my_diffraction_pattern.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] - my_diffraction_pattern.tth_to_q() - - Similarly, to load the converted array, you can either call ``test.tth_to_q()`` or ``test.on_tth[0]``. - -2) Both functions require a wavelength to perform conversions. Without a wavelength, they will return empty arrays. - Therefore, we strongly encourage you to specify a wavelength when using these functions. :: - - # Example: without wavelength specified - my_diffraction_pattern.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] - my_diffraction_pattern.q_to_tth() # returns an empty array diff --git a/doc/source/examples/transformsexample.rst b/doc/source/examples/transformsexample.rst new file mode 100644 index 00000000..7877f866 --- /dev/null +++ b/doc/source/examples/transformsexample.rst @@ -0,0 +1,53 @@ +.. _Transforms Example: + +:tocdepth: -1 + +Transforms Example +################## + +This example will demonstrate how to use the functions in the +``diffpy.utils.transforms`` module to process and analyze diffraction data. + +1) Converting from ``q`` to ``2theta`` or ``d``: + If you have a 1D ``q``-array, you can use the ``q_to_tth`` and ``q_to_d`` functions + to convert it to ``2theta`` or ``d``. :: + + # Example: convert q to 2theta + from diffpy.utils.transformers import q_to_tth + wavelength = 0.71 + q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) + tth = q_to_tth(q, wavelength) + + # Example: convert q to d + from diffpy.utils.transformers import q_to_d + q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) + d = q_to_d(q) + +(2) Converting from ``2theta`` to ``q`` or ``d``: + For a 1D ``2theta`` array, you can convert it to ``q`` or ``d`` in a similar way. :: + + # Example: convert 2theta to q + from diffpy.utils.transformers import tth_to_q + wavelength = 0.71 + tth = np.array([0, 30, 60, 90, 120, 180]) + q = tth_to_q(tth, wavelength) + + # Example: convert 2theta to d + from diffpy.utils.transformers import tth_to_d + wavelength = 0.71 + tth = np.array([0, 30, 60, 90, 120, 180]) + d = tth_to_d(tth, wavelength) + +(3) Converting from ``d`` to ``q`` or ``2theta``: + For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. :: + + # Example: convert d to q + from diffpy.utils.transformers import tth_to_q + d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) + q = d_to_q(d) + + # Example: convert d to 2theta + from diffpy.utils.transformers import d_to_tth + wavelength = 0.71 + d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) + tth = d_to_tth(d, wavelength) diff --git a/doc/source/utilities/diffractionobjectsutility.rst b/doc/source/utilities/diffractionobjectsutility.rst deleted file mode 100644 index 94cb1308..00000000 --- a/doc/source/utilities/diffractionobjectsutility.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. _Diffraction Objects Utility: - -Diffraction Objects Utility -=========================== - -The ``diffpy.utils.diffraction_objects`` module provides functions -for managing and analyzing diffraction data, including angle-space conversions -and interactions between diffraction data. - -These functions help developers standardize diffraction data and update the arrays -in the associated ``DiffractionObject``, enabling easier analysis and further processing. - -For a more in-depth tutorial for how to use these functions, click :ref:`here `. diff --git a/doc/source/utilities/transformsutility.rst b/doc/source/utilities/transformsutility.rst new file mode 100644 index 00000000..5e2bac2a --- /dev/null +++ b/doc/source/utilities/transformsutility.rst @@ -0,0 +1,14 @@ +.. _Transforms Utility: + +Transforms Utility +================== + +The ``diffpy.utils.transforms`` module provides a set of functions for managing and analyzing diffraction data, +including angle-space transformations between ``q``, ``2theta``, and ``d``-spacing. + +These functions allow developers to standardize diffraction data and convert it between different spacings, +simplifying analysis, visualization, and processing. +They are also internally used by the ``DiffractionObject`` class for efficient data manipulation. +For more information about this, click :ref:`here `. + +For a more in-depth tutorial for how to use these functions, click :ref:`here `. From ef751973519f983055f28c273092b017afecf9e2 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 16:45:56 -0500 Subject: [PATCH 417/696] improve UC comment for test_q_to_tth func --- tests/test_transforms.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 487cf6ca..70660033 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,18 +9,23 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # UC1: Empty q values, no wavelength, return empty arrays + # UC1.1: User specified empty 'q' and no 'wavelength'. + # Expect empty 'tth' array and UserWarning about missing wavelength. (None, np.empty((0)), np.empty((0))), - # UC2: Empty q values, wavelength specified, return empty arrays + + # UC1.2: User specified empty 'q' and 'wavelength'. Expect empty 'tth' array. (4 * np.pi, np.empty((0)), np.empty(0)), - # UC3: user specified valid q values, no wavelength, return empty arrays + + # UC2.1: User specified non-empty 'q' values and no 'wavelength'. + # Expect non-empty 'tth' array and UserWarning about missing wavelength. ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC4: user specified valid q values (with wavelength) - # expected tth values are 2*arcsin(q) in degrees + + # UC2.2: User specified non-empty 'q' values and 'wavelength'. + # Expect tth values are 2*arcsin(q) in degrees. (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) From 8ebaede0549d4988162fe828be92ebff39f9971d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:46:06 +0000 Subject: [PATCH 418/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 70660033..d3e51e35 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -12,10 +12,8 @@ # UC1.1: User specified empty 'q' and no 'wavelength'. # Expect empty 'tth' array and UserWarning about missing wavelength. (None, np.empty((0)), np.empty((0))), - # UC1.2: User specified empty 'q' and 'wavelength'. Expect empty 'tth' array. (4 * np.pi, np.empty((0)), np.empty(0)), - # UC2.1: User specified non-empty 'q' values and no 'wavelength'. # Expect non-empty 'tth' array and UserWarning about missing wavelength. ( @@ -23,7 +21,6 @@ np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC2.2: User specified non-empty 'q' values and 'wavelength'. # Expect tth values are 2*arcsin(q) in degrees. (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), From 7a2603e45d93ebf369535757ad0cc4eb0222328c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 22:05:29 +0000 Subject: [PATCH 419/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index cb33f760..0dd5024d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,8 +38,8 @@ def do_minimal(): # Create an instance of DiffractionObject with empty xarray and yarray values, and a non-empty wavelength return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="tth", wavelength=1.54) + @pytest.fixture def do_minimal_tth(): # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") - From 8693153a019b43b9f55b5e1f5c41e1e63c326d50 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:12:41 -0500 Subject: [PATCH 420/696] Add metadata data by materials scientist --- src/diffpy/utils/diffraction_objects.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 3d1653be..4caadd6a 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -65,7 +65,10 @@ class DiffractionObject: >>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) >>> y = np.array([10, 20, 40, 60]) # intensity values >>> metadata = { - ... "package_info": {"version": "3.6.0"} + ... "sample": "rock salt from the beach", + ... "composition": "NaCl", + ... "temperature": "300 K,", + ... "experimenters": "Phill, Sally" ... } >>> do = DiffractionObject( ... xarray=x, @@ -95,7 +98,6 @@ def __init__( def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): if xtype not in XQUANTITIES: raise ValueError(_xtype_wmsg(xtype)) - # Check xarray and yarray have the same length if len(xarray) != len(yarray): raise ValueError( @@ -103,7 +105,6 @@ def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, me "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length." ) - self.scat_quantity = scat_quantity self.wavelength = wavelength self.metadata = metadata From e68b59e25ff66f3619b9fa14fdbbeec6e88ff928 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:15:55 -0500 Subject: [PATCH 421/696] Add name example to diffraction object example --- src/diffpy/utils/diffraction_objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 4caadd6a..d28ef630 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -76,6 +76,7 @@ class DiffractionObject: ... xtype="q", ... wavelength=1.54, ... scat_quantity="x-ray", + ... name="Rock Salt X-ray Diffraction", ... metadata=metadata ... ) >>> print(do.metadata) From 235f624d0c24452cdd5ce5f3ba8915592504a52a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:17:09 -0500 Subject: [PATCH 422/696] Order x, y, xtype for _set_arrays --- src/diffpy/utils/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index d28ef630..2ecc073f 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -111,7 +111,7 @@ def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, me self.metadata = metadata self.name = name self._input_xtype = xtype - self._set_arrays(xarray, xtype, yarray) + self._set_arrays(xarray, yarray, xtype) self._set_min_max_xarray() def __eq__(self, other): @@ -295,7 +295,7 @@ def get_array_index(self, value, xtype=None): i = (np.abs(array - value)).argmin() return i - def _set_arrays(self, xarray, xtype, yarray): + def _set_arrays(self, xarray, yarray, xtype): self._all_arrays = np.empty(shape=(len(xarray), 4)) self._all_arrays[:, 0] = yarray if xtype.lower() in QQUANTITIES: From 88ea61b1550453cc0244f08a723f9fcd13ce41c8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:20:38 -0500 Subject: [PATCH 423/696] Remove 0.71 harded coded wavelength --- tests/test_diffraction_objects.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 94345451..cfc86c63 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -49,7 +49,6 @@ ), ( # Different wavelengths { - "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), @@ -66,7 +65,6 @@ ), ( # Different wavelengths { - "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), From 6b20c1a2913d3f8b063838a74f78a4e498ffd240 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:24:10 -0500 Subject: [PATCH 424/696] Add wavelength of 0.711 and 0.71 back --- tests/test_diffraction_objects.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index cfc86c63..787d7be4 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -15,6 +15,7 @@ { "name": "same", "scat_quantity": "x-ray", + "wavelength": 0.71, "xtype": "q", "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), @@ -23,6 +24,7 @@ { "name": "same", "scat_quantity": "x-ray", + "wavelength": 0.71, "xtype": "q", "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), @@ -64,7 +66,8 @@ False, ), ( # Different wavelengths - { + { + "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), From 9f0f11ac3a8e5713aa71984bcaf0091999422119 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:25:12 +0000 Subject: [PATCH 425/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 787d7be4..0dcc2163 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -66,7 +66,7 @@ False, ), ( # Different wavelengths - { + { "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), From aa1af39567c0c24caa8195694a3c6f62a86bf2b9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:28:00 -0500 Subject: [PATCH 426/696] Final setup of wavelenth value comparison --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0dcc2163..cb4e08ae 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -51,13 +51,13 @@ ), ( # Different wavelengths { + "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, }, { - "wavelength": 0.42, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), From 9343e86fccb8080e65818dc4456bde3325fae348 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:38:10 -0500 Subject: [PATCH 427/696] Do not call UCs for test function --- tests/test_transforms.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index d3e51e35..5fb2326f 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,20 +9,22 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # UC1.1: User specified empty 'q' and no 'wavelength'. - # Expect empty 'tth' array and UserWarning about missing wavelength. + # Case 1: Allow empty arrays for q + # 1. Empty q values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), - # UC1.2: User specified empty 'q' and 'wavelength'. Expect empty 'tth' array. + # 2. Empty q values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # UC2.1: User specified non-empty 'q' values and no 'wavelength'. - # Expect non-empty 'tth' array and UserWarning about missing wavelength. + + # Case 2: Allow wavelength to be missing. + # Valid q values, no wavelength, return index array ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC2.2: User specified non-empty 'q' values and 'wavelength'. - # Expect tth values are 2*arcsin(q) in degrees. + + # Case 3: Correctly specified q and wavelength + # Expected tth values are 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) From d962c1b37abcc3a873db22271627ca184f9fa9f1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:38:44 +0000 Subject: [PATCH 428/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 5fb2326f..cf6a6ebb 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -14,7 +14,6 @@ (None, np.empty((0)), np.empty((0))), # 2. Empty q values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # Case 2: Allow wavelength to be missing. # Valid q values, no wavelength, return index array ( @@ -22,7 +21,6 @@ np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # Case 3: Correctly specified q and wavelength # Expected tth values are 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), From d09d7fb38cd79666a5426a9dc934dce3a9955868 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 05:36:28 -0500 Subject: [PATCH 429/696] news tweak --- news/no-empty-object.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/no-empty-object.rst b/news/no-empty-object.rst index 02126c98..7e4ec7a4 100644 --- a/news/no-empty-object.rst +++ b/news/no-empty-object.rst @@ -4,7 +4,7 @@ **Changed:** -* `DiffractionObject` requires 3 input parameters of `xarrays`, `yarrays`, `xtype` +* `DiffractionObject` requires 3 input parameters of `xarray`, `yarray`, `xtype`, to be instantiated. It can be instantiated with empty arrays. **Deprecated:** From 0e362c155b39eefbd7988f4f8bfec6696f97cd02 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 05:41:39 -0500 Subject: [PATCH 430/696] tweak name example --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2ecc073f..a388f1b2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -76,7 +76,7 @@ class DiffractionObject: ... xtype="q", ... wavelength=1.54, ... scat_quantity="x-ray", - ... name="Rock Salt X-ray Diffraction", + ... name="beach_rock_salt_1", ... metadata=metadata ... ) >>> print(do.metadata) From c1422402eb3695fd479fdd0cf5912d24ef1f913e Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 05:45:56 -0500 Subject: [PATCH 431/696] tweak array length error message --- src/diffpy/utils/diffraction_objects.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a388f1b2..ae61c094 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -99,12 +99,12 @@ def __init__( def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): if xtype not in XQUANTITIES: raise ValueError(_xtype_wmsg(xtype)) - # Check xarray and yarray have the same length if len(xarray) != len(yarray): raise ValueError( - "'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " - "with 'xarray' and 'yarray' of identical length." + "'xarray' and 'yarray' are different lengths. They must " + "correspond to each other and have the same length. " + "Please re-initialize 'DiffractionObject' + "with valid 'xarray' and 'yarray's" ) self.scat_quantity = scat_quantity self.wavelength = wavelength From 24b1bbcd832249ee23124f51aeb2f3c913181bd6 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 05:53:08 -0500 Subject: [PATCH 432/696] fix missing quote --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ae61c094..d72a2889 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -103,7 +103,7 @@ def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, me raise ValueError( "'xarray' and 'yarray' are different lengths. They must " "correspond to each other and have the same length. " - "Please re-initialize 'DiffractionObject' + "Please re-initialize 'DiffractionObject'" "with valid 'xarray' and 'yarray's" ) self.scat_quantity = scat_quantity From 2c51af145f5d427594ebc51df4984e047b510b47 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 06:01:29 -0500 Subject: [PATCH 433/696] fix error message check --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index cb4e08ae..0aa807e8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -523,9 +523,9 @@ def test_id_setter_error(do_minimal): def test_xarray_yarray_length_mismatch(): with pytest.raises( ValueError, - match="'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " - "with 'xarray' and 'yarray' of identical length", + match="'xarray' and 'yarray' are different lengths. " + "They must correspond to each other and have the same length. Please " + "re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's" ): DiffractionObject( xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0]), xtype="tth", wavelength=1.54 From ba4b985df971440325442a50ac6de63eaad05fa5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:03:49 +0000 Subject: [PATCH 434/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0aa807e8..dfb67a3d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -525,7 +525,7 @@ def test_xarray_yarray_length_mismatch(): ValueError, match="'xarray' and 'yarray' are different lengths. " "They must correspond to each other and have the same length. Please " - "re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's" + "re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's", ): DiffractionObject( xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0]), xtype="tth", wavelength=1.54 From fffe01afa1c6f38a6fe88dd1a2a0a58b4095cced Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Mon, 16 Dec 2024 13:40:21 -0500 Subject: [PATCH 435/696] updated tools example page with better get_user_info example --- doc/source/examples/toolsexample.rst | 38 +++++++++++++++++++--------- news/tools-doc-update.rst | 23 +++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 news/tools-doc-update.rst diff --git a/doc/source/examples/toolsexample.rst b/doc/source/examples/toolsexample.rst index b3f615fd..05bac195 100644 --- a/doc/source/examples/toolsexample.rst +++ b/doc/source/examples/toolsexample.rst @@ -24,30 +24,44 @@ Using the tools module, we can efficiently get them in terms of a dictionary. The function returns a dictionary containing the username and email information. -2) You can also override existing values by passing a dictionary to the function with the keys ``"username"`` and ``"email"`` :: +2) There are a few ways you could use to override the user information in the configuration files + * You can override existing values by passing a dictionary to the function with the keys ``"username"`` and ``"email"`` :: - new_args = {"username": "new_username", "email": "new@example.com"} - new_user_info = get_user_info(new_args) + new_args = {"username": "new_username", "email": "new@example.com"} + new_user_info = get_user_info(new_args) -3) You can update only the username or email individually, for example :: + This returns "new_username" and "new@example.com" instead of the + * You can update only the username or email individually, for example :: - new_username = {"username": new_username} - new_user_info = get_user_info(new_username) + new_username = {"username": new_username} + new_user_info = get_user_info(new_username) - This updates username to "new_username" while fetching the email from inputs or the configuration files. - Similarly, you can update only the email. :: + This updates username to "new_username" while fetching the email from inputs or the configuration files. + Similarly, you can update only the email. :: - new_email = {"email": new@email.com} - new_user_info = get_user_info(new_email) + new_email = {"email": new@email.com} + new_user_info = get_user_info(new_email) - This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. + This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. + +3) You can also permanently update your default configuration file manually. + Locate the file ``diffpyconfig.json``, which is usually in the user's home directory (``~/.diffpy/``) or ``~/.config/diffpy/``. + Open the file using a text editor such as nano, vim, or a graphical editor like VS Code. + Look for entries like these :: + + { + "username": "John Doe", + "email": "john.doe@example.com" + } + + Then you can update the username and email as needed, make sure to save your edits. 4) We also have the function ``get_package_info``, which inserts or updates package names and versions in the given metadata dictionary under the key "package_info". It stores the package information as {"package_info": {"package_name": "version_number"}}. This function can be used as follows. :: - from diffpy.utils.tools import get_user_info + from diffpy.utils.tools import get_package_info package_metadata = get_package_info("my_package") You can also specify an existing dictionary to be updated with the information. :: diff --git a/news/tools-doc-update.rst b/news/tools-doc-update.rst new file mode 100644 index 00000000..87f2876e --- /dev/null +++ b/news/tools-doc-update.rst @@ -0,0 +1,23 @@ +**Added:** + +* Information on how to update the default user information + +**Changed:** + +* Enumerated list for the different ways to set user information + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Typo for get_package_info example + +**Security:** + +* From 9f48f34e05b710d1ebac69ac20baddba3347eadb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:41:26 +0000 Subject: [PATCH 436/696] [pre-commit.ci] auto fixes from pre-commit hooks --- news/tools-doc-update.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/tools-doc-update.rst b/news/tools-doc-update.rst index 87f2876e..e04b7b17 100644 --- a/news/tools-doc-update.rst +++ b/news/tools-doc-update.rst @@ -16,7 +16,7 @@ **Fixed:** -* Typo for get_package_info example +* Typo for get_package_info example **Security:** From b5ab9fec08fbd942d2d91ce7d114e31d4bbc4f91 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 20:09:35 -0500 Subject: [PATCH 437/696] simon tweaks to tools example --- doc/source/examples/toolsexample.rst | 174 +++++++++++++++++++++------ src/diffpy/utils/tools.py | 4 +- 2 files changed, 140 insertions(+), 38 deletions(-) diff --git a/doc/source/examples/toolsexample.rst b/doc/source/examples/toolsexample.rst index 05bac195..a56f76d3 100644 --- a/doc/source/examples/toolsexample.rst +++ b/doc/source/examples/toolsexample.rst @@ -5,69 +5,171 @@ Tools Example ############# -This example will demonstrate how diffpy.utils allows us to conveniently load and manage user and package information. +The tools module contains various tools that may be useful when you manipulate and analyze diffraction data. + +Automatically Capture User Info +=============================== + +One task we would like to do is to capture and propagate useful metadata that describes the diffraction data. +Some is essential such as wavelength and radiation type. Other metadata is useful such as information about the +sample, co-workers and so on. However, one of the most important bits of information is the name of the data owner. +For example, in ``DiffractionObjects`` this is stored in the ``metadata`` dictionary as ``username``, ``user_email``, +and ``user_orcid``. + +To reduce experimenter overhead when collecting this information, we have developed an infrastructure that helps +to capture this information automatically when you are using `DiffractionObjects` and other diffpy tools. +You may also reuse this infrastructure for your own projects using tools in this tutorial. + +This example will demonstrate how ``diffpy.utils`` allows us to conveniently load and manage user and package information. Using the tools module, we can efficiently get them in terms of a dictionary. -1) We have the function ``get_user_info`` that neatly returns a dictionary containing the username and email. - You can use this function without arguments. :: +Load user info into your program +-------------------------------- + +To use this functionality in your own code make use of the ``get_user_info`` function in +``diffpy.utils.tools`` which will search for information about the user, parse it, and return +it in a dictionary object e.g. if the user is "Jane Doe" with email "janedoe@gmail.com" and the +function can find the information, if you type this + +.. code-block:: python from diffpy.utils.tools import get_user_info user_info = get_user_info() - This function will first attempt to load the information from configuration files looking first in - the current working directory and then in the user's home directory. - If no configuration files exist, it prompts for user input and creates a configuration file in the home directory - so that the next time the program is run it will no longer have to prompt the user. - It can be passed user information which overrides looking in config files, and so could be passed - information that is entered through a gui or command line interface to override default information at runtime. - It prioritizes prompted user inputs, then current working directory config file, and finally home directory config file. +The function will return + +.. code-block:: python + + {"email": "janedoe@email.com", "username": "Jane Doe"} + + +Where does ``get_user_info()`` get the user information from? +------------------------------------------------------------- + +The function will first attempt to load the information from configuration files with the name ``diffpyconfig.json`` +on your hard-drive. +It looks first for the file in the current working directory. If it cannot find it there it will look +user's home, i.e., login, directory. To find this directory, open a terminal and a unix or mac system type :: + + cd ~ + pwd + +Or type ``Echo $HOME``. On a Windows computer :: + + echo %USERPROFILE%" + +What if no config files exist yet? +----------------------------------- + +If no configuration files can be found, the function attempts to create one in the user's home +directory. The function will pause execution and ask for a user-response to enter the information. +It will then write the config file in the user's home directory. + +In this way, the next, and subsequent times the program is run, it will no longer have to prompt the user +as it will successfully find the new config file. + +Getting user data with no config files and with no interruption of execution +---------------------------------------------------------------------------- + +If you would like get run ``get_user_data()`` but without execution interruption even if it cannot find +an input file, type + +.. code-block:: python + + user_data = get_user_data(skip_config_creation=True) + +Passing user information directly to ``get_user_data()`` +-------------------------------------------------------- - The function returns a dictionary containing the username and email information. +It can be passed user information which fully or partially overrides looking in config files +For example, in this way it would be possible to pass in information +that is entered through a gui or command line interface. E.g., -2) There are a few ways you could use to override the user information in the configuration files - * You can override existing values by passing a dictionary to the function with the keys ``"username"`` and ``"email"`` :: + .. code-block:: python - new_args = {"username": "new_username", "email": "new@example.com"} - new_user_info = get_user_info(new_args) + new_user_info = get_user_info({"username": "new_username", "email": "new@example.com"}) - This returns "new_username" and "new@example.com" instead of the - * You can update only the username or email individually, for example :: +This returns ``{"username": "new_username", "email": "new@example.com"}`` (and so, effectively, does nothing) +However, You can update only the username or email individually, for example - new_username = {"username": new_username} - new_user_info = get_user_info(new_username) +.. code-block:: python - This updates username to "new_username" while fetching the email from inputs or the configuration files. - Similarly, you can update only the email. :: + new_user_info = get_user_info({"username": new_username}) - new_email = {"email": new@email.com} - new_user_info = get_user_info(new_email) +will return ``{"username": "new_username", "email": "janedoe@gmail.com"}`` +if it found ``janedoe@gmail.com`` as the email in the config file. +Similarly, you can update only the email in the returned dictionary, - This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. +.. code-block:: python -3) You can also permanently update your default configuration file manually. - Locate the file ``diffpyconfig.json``, which is usually in the user's home directory (``~/.diffpy/``) or ``~/.config/diffpy/``. - Open the file using a text editor such as nano, vim, or a graphical editor like VS Code. - Look for entries like these :: + new_user_info = get_user_info({"email": new@email.com}) + +which will return ``{"username": "Jane Doe", "email": "new@email.com"}`` +if it found ``Jane Doe`` as the user in the config file. + +I entered the wrong information in my config file so it always loads incorrect information +------------------------------------------------------------------------------------------ + +You can use of the above methods to temporarily override the incorrect information in your +global config file. However, it is easy to fix this simply by editing that file using a text +editor. + +Locate the file ``diffpyconfig.json``, in your home directory and open it in an editor :: { "username": "John Doe", "email": "john.doe@example.com" } - Then you can update the username and email as needed, make sure to save your edits. + Then you can edit the username and email as needed, make sure to save your edits. + +Automatically Capture Info about a Software Package Being Used +============================================================== + +We also have a handy tool for capturing information about a python package that is being used +to save in the metadata. To use this functionality, use he function ``get_package_info``, which +inserts or updates software package names and versions in a given metadata dictionary under +the key "package_info", e.g., -4) We also have the function ``get_package_info``, which inserts or updates package names and versions - in the given metadata dictionary under the key "package_info". - It stores the package information as {"package_info": {"package_name": "version_number"}}. - This function can be used as follows. :: +.. code-block:: python + + {"package_info": {"diffpy.utils": "0.3.0", "my_package": "0.3.1"}} + +If the installed version of the package "my_package" is 0.3.1. + +This function can be used in your code as follows + +.. code-block:: python from diffpy.utils.tools import get_package_info package_metadata = get_package_info("my_package") - You can also specify an existing dictionary to be updated with the information. :: +or + +.. code-block:: python + + package_metadata = get_package_info(["first_package", "second_package"]) + +which returns (for example) + +.. code-block:: python + + {"package_info": {"diffpy.utils": "0.3.0", "first_package": "1.0.1", "second_package": "0.0.7"}} + + +You can also specify an existing dictionary to be updated with the information. + +.. code-block:: python existing_dict = {"key": "value"} updated_dict = get_package_info("my_package", metadata=existing_dict)) - note that `"diffpy.utils"` is automatically included in the package info since the `get_user_info` function is - part of diffpy.utils. +Which returns + +.. code-block:: python + + {"key": "value", "package_info": {"diffpy.utils": "0.3.0", "my_package": "0.3.1"}} + + +Note that ``"diffpy.utils"`` is automatically included in the package info since the ``get_user_info`` function is +part of ``diffpy.utils``. diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 7e5e858f..3fc10031 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -118,8 +118,8 @@ def get_user_info(args=None): print( "No global configuration file was found containing " "information about the user to associate with the data.\n" - "By following the prompts below you can add your name and email to this file on the current" - " computer and your name will be automatically associated with subsequent diffpy data by default.\n" + "By following the prompts below you can add your name and email to this file on the current " + "computer and your name will be automatically associated with subsequent diffpy data by default.\n" "This is not recommended on a shared or public computer. " "You will only have to do that once.\n" "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" From 14b729919cce6185a97327fe0fa69a9b26d8b44d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 17 Dec 2024 17:51:47 -0500 Subject: [PATCH 438/696] initial commit --- .../parserdata.zip => example_data/parser_data.zip} | Bin doc/source/examples/examples.rst | 7 ++++--- .../{parsersexample.rst => parsers_example.rst} | 2 +- .../{resampleexample.rst => resample_example.rst} | 2 +- .../{toolsexample.rst => tools_example.rst} | 0 ...transformsexample.rst => transforms_example.rst} | 12 ++++++------ .../{parsersutility.rst => parsers_utility.rst} | 0 .../{resampleutility.rst => resample_utility.rst} | 2 +- .../{toolsutility.rst => tools_utility.rst} | 0 ...transformsutility.rst => transforms_utility.rst} | 0 doc/source/utilities/utilities.rst | 7 ++++--- 11 files changed, 17 insertions(+), 15 deletions(-) rename doc/source/examples/{exampledata/parserdata.zip => example_data/parser_data.zip} (100%) rename doc/source/examples/{parsersexample.rst => parsers_example.rst} (97%) rename doc/source/examples/{resampleexample.rst => resample_example.rst} (97%) rename doc/source/examples/{toolsexample.rst => tools_example.rst} (100%) rename doc/source/examples/{transformsexample.rst => transforms_example.rst} (82%) rename doc/source/utilities/{parsersutility.rst => parsers_utility.rst} (100%) rename doc/source/utilities/{resampleutility.rst => resample_utility.rst} (77%) rename doc/source/utilities/{toolsutility.rst => tools_utility.rst} (100%) rename doc/source/utilities/{transformsutility.rst => transforms_utility.rst} (100%) diff --git a/doc/source/examples/exampledata/parserdata.zip b/doc/source/examples/example_data/parser_data.zip similarity index 100% rename from doc/source/examples/exampledata/parserdata.zip rename to doc/source/examples/example_data/parser_data.zip diff --git a/doc/source/examples/examples.rst b/doc/source/examples/examples.rst index fefe843a..002a1e23 100644 --- a/doc/source/examples/examples.rst +++ b/doc/source/examples/examples.rst @@ -7,6 +7,7 @@ Examples Landing page for diffpy.utils examples. .. toctree:: - parsersexample - resampleexample - toolsexample + parsers_example + resample_example + tools_example + transforms_example diff --git a/doc/source/examples/parsersexample.rst b/doc/source/examples/parsers_example.rst similarity index 97% rename from doc/source/examples/parsersexample.rst rename to doc/source/examples/parsers_example.rst index b709de60..f9320885 100644 --- a/doc/source/examples/parsersexample.rst +++ b/doc/source/examples/parsers_example.rst @@ -8,7 +8,7 @@ Parsers Example This example will demonstrate how diffpy.utils lets us easily process and serialize files. Using the parsers module, we can load file data into simple and easy-to-work-with Python objects. -1) To begin, unzip :download:`parserdata<./exampledata/parserdata.zip>` and take a look at ``data.txt``. +1) To begin, unzip :download:`parser_data<./example_data/parser_data.zip>` and take a look at ``data.txt``. Our goal will be to extract and serialize the data table as well as the parameters listed in the header of this file. 2) To get the data table, we will use the ``loadData`` function. The default behavior of this diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resample_example.rst similarity index 97% rename from doc/source/examples/resampleexample.rst rename to doc/source/examples/resample_example.rst index 3166e42b..5e54fca1 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resample_example.rst @@ -10,7 +10,7 @@ Specifically, we will resample the grid of one function to match another for us Then we will show how this resampling method lets us create a perfect reconstruction of certain functions given enough datapoints. -1) To start, unzip :download:`parserdata<./exampledata/parserdata.zip>`. Then, load the data table from ``Nickel.gr`` +1) To start, unzip :download:`parser_data<./example_data/parser_data.zip>`. Then, load the data table from ``Nickel.gr`` and ``NiTarget.gr``. These datasets are based on data from `Atomic Pair Distribution Function Analysis: A Primer `_. :: diff --git a/doc/source/examples/toolsexample.rst b/doc/source/examples/tools_example.rst similarity index 100% rename from doc/source/examples/toolsexample.rst rename to doc/source/examples/tools_example.rst diff --git a/doc/source/examples/transformsexample.rst b/doc/source/examples/transforms_example.rst similarity index 82% rename from doc/source/examples/transformsexample.rst rename to doc/source/examples/transforms_example.rst index 7877f866..9a47aee0 100644 --- a/doc/source/examples/transformsexample.rst +++ b/doc/source/examples/transforms_example.rst @@ -13,13 +13,13 @@ This example will demonstrate how to use the functions in the to convert it to ``2theta`` or ``d``. :: # Example: convert q to 2theta - from diffpy.utils.transformers import q_to_tth + from diffpy.utils.transforms import q_to_tth wavelength = 0.71 q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) tth = q_to_tth(q, wavelength) # Example: convert q to d - from diffpy.utils.transformers import q_to_d + from diffpy.utils.transforms import q_to_d q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) d = q_to_d(q) @@ -27,13 +27,13 @@ This example will demonstrate how to use the functions in the For a 1D ``2theta`` array, you can convert it to ``q`` or ``d`` in a similar way. :: # Example: convert 2theta to q - from diffpy.utils.transformers import tth_to_q + from diffpy.utils.transforms import tth_to_q wavelength = 0.71 tth = np.array([0, 30, 60, 90, 120, 180]) q = tth_to_q(tth, wavelength) # Example: convert 2theta to d - from diffpy.utils.transformers import tth_to_d + from diffpy.utils.transforms import tth_to_d wavelength = 0.71 tth = np.array([0, 30, 60, 90, 120, 180]) d = tth_to_d(tth, wavelength) @@ -42,12 +42,12 @@ This example will demonstrate how to use the functions in the For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. :: # Example: convert d to q - from diffpy.utils.transformers import tth_to_q + from diffpy.utils.transforms import tth_to_q d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) q = d_to_q(d) # Example: convert d to 2theta - from diffpy.utils.transformers import d_to_tth + from diffpy.utils.transforms import d_to_tth wavelength = 0.71 d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) tth = d_to_tth(d, wavelength) diff --git a/doc/source/utilities/parsersutility.rst b/doc/source/utilities/parsers_utility.rst similarity index 100% rename from doc/source/utilities/parsersutility.rst rename to doc/source/utilities/parsers_utility.rst diff --git a/doc/source/utilities/resampleutility.rst b/doc/source/utilities/resample_utility.rst similarity index 77% rename from doc/source/utilities/resampleutility.rst rename to doc/source/utilities/resample_utility.rst index 184cf22d..1631ca2b 100644 --- a/doc/source/utilities/resampleutility.rst +++ b/doc/source/utilities/resample_utility.rst @@ -3,7 +3,7 @@ Resample Utility ================ -- ``wsinterp()``: Allows users easily reample a PDF onto another grid. +- ``wsinterp()``: Allows users easily resample a PDF onto another grid. This makes use of the Whittaker-Shannon interpolation formula. To see the theory behind how this interpolation works and how to use it in practice, click :ref:`here `. diff --git a/doc/source/utilities/toolsutility.rst b/doc/source/utilities/tools_utility.rst similarity index 100% rename from doc/source/utilities/toolsutility.rst rename to doc/source/utilities/tools_utility.rst diff --git a/doc/source/utilities/transformsutility.rst b/doc/source/utilities/transforms_utility.rst similarity index 100% rename from doc/source/utilities/transformsutility.rst rename to doc/source/utilities/transforms_utility.rst diff --git a/doc/source/utilities/utilities.rst b/doc/source/utilities/utilities.rst index 5ec89656..147d7265 100644 --- a/doc/source/utilities/utilities.rst +++ b/doc/source/utilities/utilities.rst @@ -10,6 +10,7 @@ Check the :ref:`examples` provided for how to use these. .. contents:: :depth: 2 -.. include:: parsersutility.rst -.. include:: resampleutility.rst -.. include:: toolsutility.rst +.. include:: parsers_utility.rst +.. include:: resample_utility.rst +.. include:: tools_utility.rst +.. include:: transforms_utility.rst From 6606e6dae202844956d8c786035dcb475ccf1006 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 17 Dec 2024 17:53:54 -0500 Subject: [PATCH 439/696] fix more typos --- doc/source/examples/transforms_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/examples/transforms_example.rst b/doc/source/examples/transforms_example.rst index 9a47aee0..63b2fef6 100644 --- a/doc/source/examples/transforms_example.rst +++ b/doc/source/examples/transforms_example.rst @@ -42,7 +42,7 @@ This example will demonstrate how to use the functions in the For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. :: # Example: convert d to q - from diffpy.utils.transforms import tth_to_q + from diffpy.utils.transforms import d_to_q d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) q = d_to_q(d) From c982502c696a52a61438337c02d2072af95918ae Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 17 Dec 2024 19:42:50 -0500 Subject: [PATCH 440/696] reformat transforms example --- doc/source/examples/transforms_example.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/source/examples/transforms_example.rst b/doc/source/examples/transforms_example.rst index 63b2fef6..df7666b9 100644 --- a/doc/source/examples/transforms_example.rst +++ b/doc/source/examples/transforms_example.rst @@ -10,7 +10,9 @@ This example will demonstrate how to use the functions in the 1) Converting from ``q`` to ``2theta`` or ``d``: If you have a 1D ``q``-array, you can use the ``q_to_tth`` and ``q_to_d`` functions - to convert it to ``2theta`` or ``d``. :: + to convert it to ``2theta`` or ``d``. + +.. code-block:: python # Example: convert q to 2theta from diffpy.utils.transforms import q_to_tth @@ -24,7 +26,9 @@ This example will demonstrate how to use the functions in the d = q_to_d(q) (2) Converting from ``2theta`` to ``q`` or ``d``: - For a 1D ``2theta`` array, you can convert it to ``q`` or ``d`` in a similar way. :: + For a 1D ``2theta`` array, you can convert it to ``q`` or ``d`` in a similar way. + +.. code-block:: python # Example: convert 2theta to q from diffpy.utils.transforms import tth_to_q @@ -39,7 +43,9 @@ This example will demonstrate how to use the functions in the d = tth_to_d(tth, wavelength) (3) Converting from ``d`` to ``q`` or ``2theta``: - For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. :: + For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. + +.. code-block:: python # Example: convert d to q from diffpy.utils.transforms import d_to_q From a2b785030946cd61b6b8c51ba330ff7a27775253 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 18 Dec 2024 11:57:28 -0500 Subject: [PATCH 441/696] add utility and example --- .../examples/diffraction_objects_example.rst | 126 ++++++++++++++++++ .../utilities/diffraction_objects_utility.rst | 30 +++++ 2 files changed, 156 insertions(+) create mode 100644 doc/source/examples/diffraction_objects_example.rst create mode 100644 doc/source/utilities/diffraction_objects_utility.rst diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst new file mode 100644 index 00000000..7d431fad --- /dev/null +++ b/doc/source/examples/diffraction_objects_example.rst @@ -0,0 +1,126 @@ +.. _Diffraction Objects Example: + +:tocdepth: -1 + +Diffraction Objects Example +########################### + +This example will demonstrate how to use the functions in the ``diffpy.utils.diffraction_objects`` module +to create a ``DiffractionObject`` instance and analyze your diffraction data using relevant functions. + +1) To create a ``DiffractionObject``, you need to specify the type of the independent variable + (referred to as ``xtype``, one of ``q``, ``tth``, or ``d``), + an ``xarray`` of the corresponding values, and a ``yarray`` of the intensity values. + It is strongly encouraged to specify the ``wavelength`` in order to access + most of the other functionalities in the class. + Additionally, you can specify the type of your scattering experiment using the ``scat_quantity`` parameter, + the name of your diffraction object using the ``name`` parameter, + and a ``metadata`` dictionary containing relevant information about the data. Here's an example: + +.. code-block:: python + import numpy as np + from diffpy.utils.diffraction_objects import DiffractionObject + x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) + y = np.array([10, 20, 40, 60]) # intensity values + metadata = { + "sample": "rock salt from the beach", + "composition": "NaCl", + "temperature": "300 K,", + "experimenters": "Phill, Sally" + } + do = DiffractionObject( + xarray=x, + yarray=y, + xtype="q", + wavelength=1.54, + scat_quantity="x-ray", + name="beach_rock_salt_1", + metadata=metadata + ) + print(do.metadata) + + By creating a ``DiffractionObject`` instance, you store not only the diffraction data + but also all the associated information for analysis. + +2) ``DiffractionObject`` automatically populates the ``xarray`` onto ``q``, ``tth``, and ``d``-spacing. + If you want to access your diffraction data in a specific spacing, you can do this: + +.. code-block:: python + q = do.on_xtype("q") + tth = do.on_xtype("tth") + d = do.on_xtype("d") + + This will return the ``xarray`` and ``yarray`` as a list of two 1D arrays, based on the specified ``xtype``. + +3) Once the ``DiffractionObject`` is created, you can use ``get_array_index`` to get the index of the closest value + in the ``xarray`` to a specified value. + This is useful for alignment or comparison tasks. + For example, assume you have created a ``DiffractionObject`` called ``do``, + and you want to find the closest index of ``tth=80``, you can type the following: :: + + index = do.get_array_index(80, xtype="tth") + + If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. + For example, if you have created a ``DiffractionObject`` called ``do`` with ``xtype="q"``, + you can find its closest index for ``q=0.25`` by typing either of the following: :: + + index = do.get_array_index(0.25) # no input xtype, defaults to q + index = do.get_array_index(0.25, xtype="q") + +4) You can compare diffraction objects too. + For example, you can use the ``scale_to`` function to rescale one diffraction object to align its intensity values + with a second diffraction object at a (closest) specified value on a specified ``xarray``. + This makes it easier for visualizing and comparing two intensity curves on the same plot. + For example, to scale ``do1`` to match ``do2`` at ``tth=60``: + +.. code-block:: python + # Create Diffraction Objects do1 and do2 + do1 = DiffractionObject( + xarray=np.array([10, 15, 25, 30, 60, 140]), + yarray=np.array([10, 20, 25, 30, 60, 100]), + xtype="tth", + wavelength=2*np.pi + ) + do2 = DiffractionObject( + xarray=np.array([10, 20, 25, 30, 60, 140]), + yarray=np.array([2, 3, 4, 5, 6, 7]), + xtype="tth", + wavelength=2*np.pi + ) + do1_scaled = do1.scale_to(do2, tth=60) + + Here, the scaling factor is computed at ``tth=60``, aligning the intensity values. + ``do1_scaled`` will have the intensity array ``np.array([1, 2, 2.5, 3, 6, 10])``. + You can also scale based on other axes (e.g., ``q=0.2``): :: + + do1_scaled = do1.scale_to(do2, q=0.2) + + The function finds the closest indices for ``q=0.2`` and scales the ``yarray`` accordingly. + + Additionally, you can apply an ``offset`` to the scaled ``yarray``. For example: :: + + do1_scaled = do1.scale_to(do2, tth=60, offset=2) # add 2 to the scaled yarray + do1_scaled = do1.scale_to(do2, tth=60, offset=-2) # subtract 2 from the scaled yarray + + This allows you to shift the scaled data for easier comparison. + +5) You can create a copy of a diffraction object using the ``copy`` function, + when you want to preserve the original data while working with a modified version. :: + + # Create a copy of Diffraction Object do + do_copy = do.copy() + +6) The ``dump`` function saves the diffraction data and relevant information to a specified file. + You can choose one of the data axis (``q``, ``tth``, or ``d``) to export, with ``q`` as the default. + +.. code-block:: python + # Assume you have created a Diffraction Object do + file = "diffraction_data.xy" + do.dump(file, xtype="q") + + In the saved file "diffraction_data.xy", + the relevant information (name, scattering quantity, metadata, etc.) is included in the header. + Your analysis time and software version are automatically recorded as well. + The diffraction data is saved as two columns: the ``q`` values and corresponding intensity values. + This ensures your diffraction data, along with all other information, + is properly documented and saved for future reference. diff --git a/doc/source/utilities/diffraction_objects_utility.rst b/doc/source/utilities/diffraction_objects_utility.rst new file mode 100644 index 00000000..f7d291de --- /dev/null +++ b/doc/source/utilities/diffraction_objects_utility.rst @@ -0,0 +1,30 @@ +.. _Diffraction Objects Utility: + +Diffraction Objects Utility +=========================== + +The ``diffpy.utils.diffraction_objects`` module provides a set of powerful functions for analyzing diffraction data. + +- ``DiffractionObject()``: This function creates a diffraction object that stores your diffraction data + and associated information. If a ``wavelength`` is specified, it can automatically populate data + across different independent axes (e.g., ``q``, ``tth``, and ``d``). + +- ``on_xtype()``: This function allows developers to access diffraction data on different independent axes + (``q``, ``tth``, and ``d``). + It is useful when you need to convert or view the data between axes, + working with the axis that best fits your analysis. + +- ``get_array_index()``: This function finds the closest index in the independent variable axis (``xarray``) + to a targeted value. It simplifies the process of working with different spacing. + +- ``scale_to()``: This function rescales one diffraction object to align with another at a specific value. + This is helpful for comparing diffraction data with different intensity values or lengths, + ensuring they are directly comparable and visually aligned. + +- ``copy()``: This function creates a deep copy of a diffraction object, + allowing you to preserve the original data while making modifications to a separate copy. + +- ``dump()``: This function saves both diffraction data and all associated information to a file. + It also automatically tracks the analysis time and software version you used. + +For a more in-depth tutorial for how to use these tools, click :ref:`here `. From 15d03bad84bd9d421c0eb28bfab32080e9ad3bd3 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 18 Dec 2024 11:58:35 -0500 Subject: [PATCH 442/696] add references on main page --- doc/source/examples/examples.rst | 1 + doc/source/utilities/utilities.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/source/examples/examples.rst b/doc/source/examples/examples.rst index 002a1e23..5e285ea1 100644 --- a/doc/source/examples/examples.rst +++ b/doc/source/examples/examples.rst @@ -11,3 +11,4 @@ Landing page for diffpy.utils examples. resample_example tools_example transforms_example + diffraction_objects_example diff --git a/doc/source/utilities/utilities.rst b/doc/source/utilities/utilities.rst index 147d7265..9ef8f4ef 100644 --- a/doc/source/utilities/utilities.rst +++ b/doc/source/utilities/utilities.rst @@ -14,3 +14,4 @@ Check the :ref:`examples` provided for how to use these. .. include:: resample_utility.rst .. include:: tools_utility.rst .. include:: transforms_utility.rst +.. include:: diffraction_objects_utility.rst From 71d6a79b76ab9829b2e7c9a8b388003b45833970 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 18 Dec 2024 18:41:17 -0500 Subject: [PATCH 443/696] simon edits to docs --- .../examples/diffraction_objects_example.rst | 198 +++++++++++------- 1 file changed, 127 insertions(+), 71 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 7d431fad..891b86b8 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -8,27 +8,30 @@ Diffraction Objects Example This example will demonstrate how to use the functions in the ``diffpy.utils.diffraction_objects`` module to create a ``DiffractionObject`` instance and analyze your diffraction data using relevant functions. -1) To create a ``DiffractionObject``, you need to specify the type of the independent variable - (referred to as ``xtype``, one of ``q``, ``tth``, or ``d``), - an ``xarray`` of the corresponding values, and a ``yarray`` of the intensity values. - It is strongly encouraged to specify the ``wavelength`` in order to access - most of the other functionalities in the class. - Additionally, you can specify the type of your scattering experiment using the ``scat_quantity`` parameter, - the name of your diffraction object using the ``name`` parameter, - and a ``metadata`` dictionary containing relevant information about the data. Here's an example: +To create a ``DiffractionObject``, you need to specify the type of the independent variable +(referred to as ``xtype``, e.g., one of ``q``, ``tth``, or ``d``), +the ``xarray`` of the ``x`` values, and a ``yarray`` of the corresponding intensity values. +It is strongly encouraged to specify the ``wavelength`` in order to access +most of the other functionalities in the class. +Additionally, you can specify the type of your scattering experiment using the ``scat_quantity`` parameter, +the name of your diffraction object using the ``name`` parameter, +and a ``metadata`` dictionary containing relevant information about the data. Here's an example: .. code-block:: python + import numpy as np from diffpy.utils.diffraction_objects import DiffractionObject + x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) y = np.array([10, 20, 40, 60]) # intensity values metadata = { "sample": "rock salt from the beach", "composition": "NaCl", "temperature": "300 K,", - "experimenters": "Phill, Sally" + "experimenters": ["Phil", "Sally"] } - do = DiffractionObject( + + my_do = DiffractionObject( xarray=x, yarray=y, xtype="q", @@ -37,90 +40,143 @@ to create a ``DiffractionObject`` instance and analyze your diffraction data usi name="beach_rock_salt_1", metadata=metadata ) - print(do.metadata) - By creating a ``DiffractionObject`` instance, you store not only the diffraction data - but also all the associated information for analysis. +By creating a ``DiffractionObject`` instance, you store not only the diffraction data +but also all the associated information for analysis. -2) ``DiffractionObject`` automatically populates the ``xarray`` onto ``q``, ``tth``, and ``d``-spacing. - If you want to access your diffraction data in a specific spacing, you can do this: +``DiffractionObject`` automatically populates the ``xarray`` onto each of ``q``, ``tth``, and ``d``-spacing. +Let's say you want to plot your data vs. Q. To do this you would type .. code-block:: python - q = do.on_xtype("q") - tth = do.on_xtype("tth") - d = do.on_xtype("d") - This will return the ``xarray`` and ``yarray`` as a list of two 1D arrays, based on the specified ``xtype``. + import matplotlib.pyplot as plt -3) Once the ``DiffractionObject`` is created, you can use ``get_array_index`` to get the index of the closest value - in the ``xarray`` to a specified value. - This is useful for alignment or comparison tasks. - For example, assume you have created a ``DiffractionObject`` called ``do``, - and you want to find the closest index of ``tth=80``, you can type the following: :: + plt.plot(my_do.on_q()[0], my_do.on_q()[1]) - index = do.get_array_index(80, xtype="tth") +and to plot the same data vs. two-theta type + +.. code-block:: python - If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. - For example, if you have created a ``DiffractionObject`` called ``do`` with ``xtype="q"``, - you can find its closest index for ``q=0.25`` by typing either of the following: :: + plt.plot(my_do.on_tth()[0], my_do.on_tth()[1]) - index = do.get_array_index(0.25) # no input xtype, defaults to q - index = do.get_array_index(0.25, xtype="q") +These `on_q()`, `on_tth()`, etc., methods return a list with the x-array as the first element +and the intensity array as the second element. -4) You can compare diffraction objects too. - For example, you can use the ``scale_to`` function to rescale one diffraction object to align its intensity values - with a second diffraction object at a (closest) specified value on a specified ``xarray``. - This makes it easier for visualizing and comparing two intensity curves on the same plot. - For example, to scale ``do1`` to match ``do2`` at ``tth=60``: +We can also accomplish the same thing by passing the xtype as a string to the ``on_xtype()`` method, +i.e., + +.. code-block:: python + + data_on_q = my_do.on_xtype("q") + data_on_tth = my_do.on_xtype("tth") + data_on_d = my_do.on_xtype("d") + plt.plot(data_on_d[0], data_on_d[1]) + +This makes it very easy to compare a diffractioh pattern that was measured or calculated +on one ``xtype`` with one that was measured or calculated on another. E.g., suppose that you +have a calculated powder pattern from a CIF file that was calculated on a d-spacing grid using +some software package, and +you want to know if a diffraction pattern you have measured on a Q-grid is the same material. +You could simply load them both as diffraction objects and plot them together on the same grid. + +.. code-block:: python + + calculated = DiffractionObject(xcalc, ycalc, "d") + measured = DiffractionObject(xmeas, ymeas, "tth", wavelength=0.717) + plt.plot(calculated.on_q()[0], calculated.on_q()[1]) + plt.plot(measured.on_q()[0], measured.on_q()[1]) + plt.show + +Now, let's say that these two diffraction patterns were on very different scales. The measured one +has a peak intensity of 10,000, but the calculated one only goes to 1. +With diffraction objects this is easy to handle. We choose a point on the x-axis where +we want to scale the two together and we use the ``scale_to()`` method, + +Continuing the example above, if we wanted to scale the two patterns together at a position +Q=5.5 inverse angstroms, where for the sake of argument we assume the +calculated curve has a strong peak, +we would replace the code above with + +.. code-block:: python + + plt.plot(calculated.on_q()[0], calculated.on_q()[1]) + plt.plot(measured.on_q().scale_to(calculated, q=5.5)[0], measured.on_q().scale_to(calculated, q=5.5)[1]) + plt.show + +The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new +variable and make use of, .. code-block:: python - # Create Diffraction Objects do1 and do2 - do1 = DiffractionObject( - xarray=np.array([10, 15, 25, 30, 60, 140]), - yarray=np.array([10, 20, 25, 30, 60, 100]), - xtype="tth", - wavelength=2*np.pi - ) - do2 = DiffractionObject( - xarray=np.array([10, 20, 25, 30, 60, 140]), - yarray=np.array([2, 3, 4, 5, 6, 7]), - xtype="tth", - wavelength=2*np.pi - ) - do1_scaled = do1.scale_to(do2, tth=60) - Here, the scaling factor is computed at ``tth=60``, aligning the intensity values. - ``do1_scaled`` will have the intensity array ``np.array([1, 2, 2.5, 3, 6, 10])``. - You can also scale based on other axes (e.g., ``q=0.2``): :: + scaled_measured = measured.scale_to(calculated, q=5.5) - do1_scaled = do1.scale_to(do2, q=0.2) +For convenience, you can also apply an offset to the scaled new diffraction object with the optional +``offset`` argument, for example, - The function finds the closest indices for ``q=0.2`` and scales the ``yarray`` accordingly. +.. code-block:: python + + scaled_and_offset_measured = measured.scale_to(calculated, q=5.5, offset=0.5) + +DiffractionObject convenience functions +--------------------------------------- + +1) create a copy of a diffraction object using the ``copy`` method + when you want to preserve the original data while working with a modified version. + +.. code-block:: python + + copy_of_calculated = calculated.copy() + +2) test the equality of two diffraction objects. For example, + +.. code-block:: python - Additionally, you can apply an ``offset`` to the scaled ``yarray``. For example: :: + diff_object2 = diff_object1.copy() + diff_object2 == diff_object1 - do1_scaled = do1.scale_to(do2, tth=60, offset=2) # add 2 to the scaled yarray - do1_scaled = do1.scale_to(do2, tth=60, offset=-2) # subtract 2 from the scaled yarray + will return ``True`` - This allows you to shift the scaled data for easier comparison. +3) make arithmetic operations on the intensities of diffraction objects. e.g., + +.. code-block:: python + + doubled_object = 2 * diff_object1 # Double the intensities + sum_object = diff_object1 + diff_object2 # Sum the intensities + subtract_scaled = diff_object1 - 5 * diff_object2 # subtract 5 * obj2 from obj 1 + +4) get the value of the DiffractionObject at a given point in one of the xarrays + +.. code-block:: python + + tth_ninety_index = diff_object1.get_array_index(90, xtype="tth") + intensity_at_ninety = diff_object1.on_tth()[tth_ninety_index] + +If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. +For example, if you have created a ``DiffractionObject`` called ``do`` with ``xtype="q"``, +you can find its closest index for ``q=0.25`` by typing either of the following: + +.. code-block:: python -5) You can create a copy of a diffraction object using the ``copy`` function, - when you want to preserve the original data while working with a modified version. :: + print(do._input_xtype) # remind ourselves which array was input. prints "q" in this case. + index = do.get_array_index(0.25) # no xtype passed, defaults to do._input_xtype, or in this example, q + index = do.get_array_index(0.25, xtype="q") # explicitly choose an xtype to specify a value - # Create a copy of Diffraction Object do - do_copy = do.copy() +5) The ``dump`` function saves the diffraction data and relevant information to an xy format file with headers +(widely used chi format used, for example, by Fit2D and diffpy. These files can be read by ``LoadData()`` +in ``diffpy.utils.parsers``). -6) The ``dump`` function saves the diffraction data and relevant information to a specified file. - You can choose one of the data axis (``q``, ``tth``, or ``d``) to export, with ``q`` as the default. +You can choose which of the data axes (``q``, ``tth``, or ``d``) to export, with ``q`` as the default. .. code-block:: python # Assume you have created a Diffraction Object do - file = "diffraction_data.xy" + file = "diffraction_data.chi" do.dump(file, xtype="q") - In the saved file "diffraction_data.xy", - the relevant information (name, scattering quantity, metadata, etc.) is included in the header. - Your analysis time and software version are automatically recorded as well. - The diffraction data is saved as two columns: the ``q`` values and corresponding intensity values. - This ensures your diffraction data, along with all other information, - is properly documented and saved for future reference. +In the saved file ``diffraction_data.chi``, +relevant metadata are also written in the header (``username``, ``name``, ``scattering quantity``, ``metadata``, etc.). +The datetime when the DiffractionObject was created and the version of the +software (see the Section on ``get_package_info()`` for more information) +is automatically recorded as well. +The diffraction data is saved as two columns: the ``q`` values and corresponding intensity values. +This ensures your diffraction data, along with all other information, +is properly documented and saved for future reference. From 6ee7a9506e21042934f5c414a5059bea71b67be3 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 18 Dec 2024 18:46:59 -0500 Subject: [PATCH 444/696] news --- news/doccln.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/doccln.rst diff --git a/news/doccln.rst b/news/doccln.rst new file mode 100644 index 00000000..24127849 --- /dev/null +++ b/news/doccln.rst @@ -0,0 +1,23 @@ +**Added:** + +* Example docs for basic DiffractionObject usage + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 6603bb1a5efc0514411537085151ca9da61dcecf Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 18 Dec 2024 21:46:07 -0500 Subject: [PATCH 445/696] fix typos --- doc/source/examples/diffraction_objects_example.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 891b86b8..23c87e18 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -72,7 +72,7 @@ i.e., data_on_d = my_do.on_xtype("d") plt.plot(data_on_d[0], data_on_d[1]) -This makes it very easy to compare a diffractioh pattern that was measured or calculated +This makes it very easy to compare a diffraction pattern that was measured or calculated on one ``xtype`` with one that was measured or calculated on another. E.g., suppose that you have a calculated powder pattern from a CIF file that was calculated on a d-spacing grid using some software package, and @@ -85,7 +85,7 @@ You could simply load them both as diffraction objects and plot them together on measured = DiffractionObject(xmeas, ymeas, "tth", wavelength=0.717) plt.plot(calculated.on_q()[0], calculated.on_q()[1]) plt.plot(measured.on_q()[0], measured.on_q()[1]) - plt.show + plt.show() Now, let's say that these two diffraction patterns were on very different scales. The measured one has a peak intensity of 10,000, but the calculated one only goes to 1. @@ -100,8 +100,8 @@ we would replace the code above with .. code-block:: python plt.plot(calculated.on_q()[0], calculated.on_q()[1]) - plt.plot(measured.on_q().scale_to(calculated, q=5.5)[0], measured.on_q().scale_to(calculated, q=5.5)[1]) - plt.show + plt.plot(measured.scale_to(calculated, q=5.5)[0], measured.scale_to(calculated, q=5.5)[1]) + plt.show() The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new variable and make use of, @@ -134,7 +134,7 @@ DiffractionObject convenience functions diff_object2 = diff_object1.copy() diff_object2 == diff_object1 - will return ``True`` +will return ``True``. 3) make arithmetic operations on the intensities of diffraction objects. e.g., @@ -149,7 +149,7 @@ DiffractionObject convenience functions .. code-block:: python tth_ninety_index = diff_object1.get_array_index(90, xtype="tth") - intensity_at_ninety = diff_object1.on_tth()[tth_ninety_index] + intensity_at_ninety = diff_object1.on_tth()[1][tth_ninety_index] If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. For example, if you have created a ``DiffractionObject`` called ``do`` with ``xtype="q"``, @@ -168,6 +168,7 @@ in ``diffpy.utils.parsers``). You can choose which of the data axes (``q``, ``tth``, or ``d``) to export, with ``q`` as the default. .. code-block:: python + # Assume you have created a Diffraction Object do file = "diffraction_data.chi" do.dump(file, xtype="q") From 82677a49068365e7ecb000c54468e6f12586d746 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 18 Dec 2024 21:52:16 -0500 Subject: [PATCH 446/696] more fix --- doc/source/examples/diffraction_objects_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 23c87e18..8621f50f 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -100,7 +100,7 @@ we would replace the code above with .. code-block:: python plt.plot(calculated.on_q()[0], calculated.on_q()[1]) - plt.plot(measured.scale_to(calculated, q=5.5)[0], measured.scale_to(calculated, q=5.5)[1]) + plt.plot(measured.scale_to(calculated, q=5.5).on_q()[0], measured.scale_to(calculated, q=5.5).on_q()[1]) plt.show() The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new From 91eac1669b74d576ea7fea267b09de41bf68f0c9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 19 Dec 2024 01:11:58 -0500 Subject: [PATCH 447/696] Refactor test_scale_to function --- src/diffpy/utils/transforms.py | 4 +- tests/test_diffraction_objects.py | 443 +++++++++++++++--------------- 2 files changed, 228 insertions(+), 219 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index fa3e8747..0f21cbdd 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -14,9 +14,7 @@ "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_wmsg = ( - "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." -) +inf_output_wmsg = "The largest output value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index dfb67a3d..3ce1bf66 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -10,132 +10,134 @@ from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject -params = [ - ( # Compare same attributes - { - "name": "same", - "scat_quantity": "x-ray", - "wavelength": 0.71, - "xtype": "q", - "xarray": np.array([1.0, 2.0]), - "yarray": np.array([100.0, 200.0]), - "metadata": {"thing1": 1}, - }, - { - "name": "same", - "scat_quantity": "x-ray", - "wavelength": 0.71, - "xtype": "q", - "xarray": np.array([1.0, 2.0]), - "yarray": np.array([100.0, 200.0]), - "metadata": {"thing1": 1}, - }, - True, - ), - ( # Different names - { - "name": "something", - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - { - "name": "something else", - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different wavelengths - { - "wavelength": 0.71, - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - { - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different wavelengths - { - "wavelength": 0.71, - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - { - "wavelength": 0.711, - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different scat_quantity - { - "scat_quantity": "x-ray", - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - { - "scat_quantity": "neutron", - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different on_q - { - "xtype": "q", - "xarray": np.array([1.0, 2.0]), - "yarray": np.array([100.0, 200.0]), - }, - { - "xtype": "q", - "xarray": np.array([3.0, 4.0]), - "yarray": np.array([100.0, 200.0]), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different metadata - { - "xtype": "q", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 0, "thing2": "thing2"}, - }, - { - "xtype": "q", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), -] - -@pytest.mark.parametrize("inputs1, inputs2, expected", params) -def test_diffraction_objects_equality(inputs1, inputs2, expected): - do_1 = DiffractionObject(**inputs1) - do_2 = DiffractionObject(**inputs2) - assert (do_1 == do_2) == expected +@pytest.mark.parametrize( + "do_args_1, do_args_2, expected_equality", + [ + # Identical args, expect equality + ( + { + "name": "same", + "scat_quantity": "x-ray", + "wavelength": 0.71, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1}, + }, + { + "name": "same", + "scat_quantity": "x-ray", + "wavelength": 0.71, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1}, + }, + True, + ), + ( # Different names, expect inequality + { + "name": "something", + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "name": "something else", + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # One without wavelnegth, expect inequality + { + "wavelength": 0.71, + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # Different wavelengths, expect inequality + { + "wavelength": 0.71, + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "wavelength": 0.711, + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # Different scat_quantity, expect inequality + { + "scat_quantity": "x-ray", + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "scat_quantity": "neutron", + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # Different q xarray values, expect inequality + { + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + }, + { + "xtype": "q", + "xarray": np.array([3.0, 4.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # Different metadata, expect inequality + { + "xtype": "q", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 0, "thing2": "thing2"}, + }, + { + "xtype": "q", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ], +) +def test_diffraction_objects_equality(do_args_1, do_args_2, expected_equality): + do_1 = DiffractionObject(**do_args_1) + do_2 = DiffractionObject(**do_args_2) + assert (do_1 == do_2) == expected_equality @pytest.mark.parametrize( @@ -165,99 +167,108 @@ def test_init_invalid_xtype(): return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="invalid_type", wavelength=1.54) -params_scale_to = [ - # UC1: same x-array and y-array, check offset - ( - { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([10, 15, 25, 30, 60, 140]), - "target_yarray": np.array([2, 3, 4, 5, 6, 7]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": None, - "offset": 2.1, - }, - {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, - ), - # UC2: same length x-arrays with exact x-value match - ( - { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([10, 20, 25, 30, 60, 100]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([10, 20, 25, 30, 60, 140]), - "target_yarray": np.array([2, 3, 4, 5, 6, 7]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": None, - "offset": 0, - }, - {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, - ), - # UC3: same length x-arrays with approximate x-value match - ( - { - "xarray": np.array([0.12, 0.24, 0.31, 0.4]), - "yarray": np.array([10, 20, 40, 60]), - "xtype": "q", - "wavelength": 2 * np.pi, - "target_xarray": np.array([0.14, 0.24, 0.31, 0.4]), - "target_yarray": np.array([1, 3, 4, 5]), - "target_xtype": "q", - "target_wavelength": 2 * np.pi, - "q": 0.1, - "tth": None, - "d": None, - "offset": 0, - }, - {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, - ), - # UC4: different x-array lengths with approximate x-value match - ( - { - "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": None, - "offset": 0, - }, - # scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) - {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_scale_to) -def test_scale_to(inputs, expected): - orig_diff_object = DiffractionObject( - xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] - ) - target_diff_object = DiffractionObject( - xarray=inputs["target_xarray"], - yarray=inputs["target_yarray"], - xtype=inputs["target_xtype"], - wavelength=inputs["target_wavelength"], - ) - scaled_diff_object = orig_diff_object.scale_to( - target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] +@pytest.mark.parametrize( + "org_do_args, target_do_args, scale_inputs, expected", + [ + # UC1: same x-array and y-array, check offset + ( + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": None, + "offset": 2.1, + }, + {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, + ), + # UC2: same length x-arrays with exact x-value match + ( + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([10, 20, 25, 30, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([10, 20, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, + ), + # UC3: same length x-arrays with approximate x-value match + ( + { + "xarray": np.array([0.12, 0.24, 0.31, 0.4]), + "yarray": np.array([10, 20, 40, 60]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.14, 0.24, 0.31, 0.4]), + "yarray": np.array([1, 3, 4, 5]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "q": 0.1, + "tth": None, + "d": None, + "offset": 0, + }, + {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, + ), + # UC4: different x-array lengths with approximate x-value match + ( + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + # Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, + ), + ], +) +def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): + original_do = DiffractionObject(**org_do_args) + target_do = DiffractionObject(**target_do_args) + scaled_do = original_do.scale_to( + target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"] ) # Check the intensity data is the same as expected - assert np.allclose(scaled_diff_object.on_xtype(expected["xtype"])[1], expected["yarray"]) + assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) params_scale_to_bad = [ From e8fd9d13620661bd6ab2ade5a4bd3b35e85f6a60 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 10:46:17 -0500 Subject: [PATCH 448/696] Add no news --- news/pytest-warning.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/pytest-warning.rst diff --git a/news/pytest-warning.rst b/news/pytest-warning.rst new file mode 100644 index 00000000..8900165f --- /dev/null +++ b/news/pytest-warning.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 890f7299381340a2dc4a814802f2f984338d778c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 10:46:38 -0500 Subject: [PATCH 449/696] Change UC to Case for unit testing --- tests/test_diffraction_objects.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 3ce1bf66..b9ee3f00 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -53,7 +53,7 @@ }, False, ), - ( # One without wavelnegth, expect inequality + ( # One without wavelength, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -170,7 +170,7 @@ def test_init_invalid_xtype(): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs, expected", [ - # UC1: same x-array and y-array, check offset + # Case 1: same x-array and y-array, check offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -192,7 +192,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # UC2: same length x-arrays with exact x-value match + # Case 2: same length x-arrays with exact x-value match ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -214,7 +214,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # UC3: same length x-arrays with approximate x-value match + # Case 3: same length x-arrays with approximate x-value match ( { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), @@ -236,7 +236,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # UC4: different x-array lengths with approximate x-value match + # Case 4: different x-array lengths with approximate x-value match ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -256,7 +256,7 @@ def test_init_invalid_xtype(): "d": None, "offset": 0, }, - # Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + # Case 5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ], From 5080163ccf934979992fb4b3be11a26301399e97 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 11:23:07 -0500 Subject: [PATCH 450/696] Remove 6 pytest warnings with capturing wavelength --- news/pytest-warnings-others.rst | 23 +++++++++++++++++++++++ tests/test_transforms.py | 18 ++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 news/pytest-warnings-others.rst diff --git a/news/pytest-warnings-others.rst b/news/pytest-warnings-others.rst new file mode 100644 index 00000000..8900165f --- /dev/null +++ b/news/pytest-warnings-others.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_transforms.py b/tests/test_transforms.py index cf6a6ebb..0ed522ce 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -171,8 +171,13 @@ def test_d_to_q(d, expected_q): ), ], ) -def test_tth_to_d(wavelength, tth, expected_d): - actual_d = tth_to_d(tth, wavelength) +def test_tth_to_d(wavelength, tth, expected_d, wavelength_warning_msg): + if wavelength is None: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + actual_d = tth_to_d(tth, wavelength) + else: + actual_d = tth_to_d(tth, wavelength) + assert np.allclose(actual_d, expected_d) @@ -221,8 +226,13 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m ), ], ) -def test_d_to_tth(wavelength, d, expected_tth): - actual_tth = d_to_tth(d, wavelength) +def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg): + if wavelength is None: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + actual_tth = d_to_tth(d, wavelength) + else: + actual_tth = d_to_tth(d, wavelength) + assert np.allclose(actual_tth, expected_tth) From 2a3d7c89d83855342f3fc72ea8763a41fb6e5bbb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 11:53:21 -0500 Subject: [PATCH 451/696] Refactor expecting divison by zero test --- news/pytest-handle-warning.rst | 23 +++++++++++++++++++++++ tests/test_transforms.py | 25 +++++++++++++++++++------ 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 news/pytest-handle-warning.rst diff --git a/news/pytest-handle-warning.rst b/news/pytest-handle-warning.rst new file mode 100644 index 00000000..8900165f --- /dev/null +++ b/news/pytest-handle-warning.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_transforms.py b/tests/test_transforms.py index cf6a6ebb..fc4e4991 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -117,19 +117,32 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): @pytest.mark.parametrize( - "q, expected_d", + "q, expected_d, warning_expected", [ - # UC1: User specified empty q values - (np.array([]), np.array([])), - # UC2: User specified valid q values + # Case 1: empty q values, expect empty d values + (np.array([]), np.array([]), False), + + # Case 2: + # 1. valid q values, expect d values without warning + ( + np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), + np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), + False + ), + # 2. valid q values containing 0, expect d values with divide by zero warning ( np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), + True ), ], ) -def test_q_to_d(q, expected_d): - actual_d = q_to_d(q) +def test_q_to_d(q, expected_d, warning_expected): + if warning_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_d = q_to_d(q) + else: + actual_d = q_to_d(q) assert np.allclose(actual_d, expected_d) From f8704f1b0dcbf90082968e9e502f2969d090708b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:55:34 +0000 Subject: [PATCH 452/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index fc4e4991..fb1d0f17 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -121,19 +121,18 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): [ # Case 1: empty q values, expect empty d values (np.array([]), np.array([]), False), - # Case 2: # 1. valid q values, expect d values without warning ( np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), - False + False, ), # 2. valid q values containing 0, expect d values with divide by zero warning ( np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), - True + True, ), ], ) From 49a1f7a190ab1c9abb2054975a1726b564a851cd Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 20 Dec 2024 15:57:51 -0500 Subject: [PATCH 453/696] add INFO back to info message --- src/diffpy/utils/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 0f21cbdd..fbd481a0 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -14,7 +14,7 @@ "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_wmsg = "The largest output value in the array is infinite. This is allowed, but it will not be plotted." +inf_output_imsg = "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): From ba488b82882d0366cdd1da5525b1b7e68efdf6a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:01:14 +0000 Subject: [PATCH 454/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/transforms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index fbd481a0..4b6c931e 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -14,7 +14,9 @@ "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_imsg = "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." +inf_output_imsg = ( + "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." +) def _validate_inputs(q, wavelength): From fbfd3be192d512848afc7b2a6fd0cdaf7fb63f78 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 20 Dec 2024 16:01:44 -0500 Subject: [PATCH 455/696] added high level meta-description of the group of tests in __eq__ --- tests/test_diffraction_objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index b9ee3f00..e561244e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -14,6 +14,7 @@ @pytest.mark.parametrize( "do_args_1, do_args_2, expected_equality", [ + # Test when __eqal__ returns True and False # Identical args, expect equality ( { From b3110bfcaef4a8176040a2d0d62cc303da2a3b0b Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 20 Dec 2024 16:04:15 -0500 Subject: [PATCH 456/696] meta description for scale_to tests --- tests/test_diffraction_objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e561244e..26893904 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -171,6 +171,7 @@ def test_init_invalid_xtype(): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs, expected", [ + # Test that scale_to() scales to the correct values # Case 1: same x-array and y-array, check offset ( { From 206d39d182c40f4ab7ccd5ff1678cca6bfb43839 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 20 Dec 2024 16:11:09 -0500 Subject: [PATCH 457/696] fix info message name and change warn to print in q to do transforms. --- src/diffpy/utils/transforms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 4b6c931e..1d50aeab 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -135,7 +135,7 @@ def q_to_d(q): The array of :math:`d` values np.array([ds]). """ if 0 in q: - print(inf_output_wmsg) + print(inf_output_imsg) return 2.0 * np.pi / copy(q) @@ -169,7 +169,7 @@ def tth_to_d(tth, wavelength): d[i] = i return d if 0 in q: - warnings.warn(inf_output_wmsg) + print(inf_output_imsg) return 2.0 * np.pi / copy(q) @@ -189,7 +189,7 @@ def d_to_q(d): The units of q must be reciprocal of the units of wavelength. """ if 0 in d: - warnings.warn(inf_output_wmsg) + print(inf_output_imsg) return 2.0 * np.pi / copy(d) From 12e2b89444edc933546cddacef9b74bd1149ffb3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 18:41:00 -0500 Subject: [PATCH 458/696] Add higher-level comment to test func and add news --- news/pytest-handle-warning.rst | 2 +- tests/test_transforms.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/news/pytest-handle-warning.rst b/news/pytest-handle-warning.rst index 8900165f..81bf78bf 100644 --- a/news/pytest-handle-warning.rst +++ b/news/pytest-handle-warning.rst @@ -1,6 +1,6 @@ **Added:** -* No news added +* catch division by zero warning messages in tests **Changed:** diff --git a/tests/test_transforms.py b/tests/test_transforms.py index fb1d0f17..14a126ae 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -119,8 +119,10 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): @pytest.mark.parametrize( "q, expected_d, warning_expected", [ + # Test conversion of q to d with valid values # Case 1: empty q values, expect empty d values (np.array([]), np.array([]), False), + # Case 2: # 1. valid q values, expect d values without warning ( From 765c9e21920c231305b846f020fcaa706fe2cdde Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:41:09 +0000 Subject: [PATCH 459/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 14a126ae..188ff150 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -122,7 +122,6 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): # Test conversion of q to d with valid values # Case 1: empty q values, expect empty d values (np.array([]), np.array([]), False), - # Case 2: # 1. valid q values, expect d values without warning ( From e9a3113b21b07a1e8ee0709d60d18d2d63f36a29 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 01:29:22 -0500 Subject: [PATCH 460/696] Catch expected wavelength warnings --- tests/test_diffraction_objects.py | 22 +++++++++++++++++----- tests/test_transforms.py | 1 - 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 26893904..382f5776 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -12,9 +12,9 @@ @pytest.mark.parametrize( - "do_args_1, do_args_2, expected_equality", + "do_args_1, do_args_2, expected_equality, warning_expected", [ - # Test when __eqal__ returns True and False + # Test when __eq__ returns True and False # Identical args, expect equality ( { @@ -36,6 +36,7 @@ "metadata": {"thing1": 1}, }, True, + False ), ( # Different names, expect inequality { @@ -53,6 +54,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ( # One without wavelength, expect inequality { @@ -69,6 +71,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ( # Different wavelengths, expect inequality { @@ -86,6 +89,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + False ), ( # Different scat_quantity, expect inequality { @@ -103,6 +107,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ( # Different q xarray values, expect inequality { @@ -117,6 +122,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ( # Different metadata, expect inequality { @@ -132,12 +138,18 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ], ) -def test_diffraction_objects_equality(do_args_1, do_args_2, expected_equality): - do_1 = DiffractionObject(**do_args_1) - do_2 = DiffractionObject(**do_args_2) +def test_diffraction_objects_equality(do_args_1, do_args_2, expected_equality, warning_expected, wavelength_warning_msg): + if warning_expected: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + do_1 = DiffractionObject(**do_args_1) + do_2 = DiffractionObject(**do_args_2) + else: + do_1 = DiffractionObject(**do_args_1) + do_2 = DiffractionObject(**do_args_2) assert (do_1 == do_2) == expected_equality diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 0ed522ce..5493ea19 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -27,7 +27,6 @@ ], ) def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): - if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_tth = q_to_tth(q, wavelength) From 968219fb742a2a07b2a312b0870f0f8c3951333a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 01:29:29 -0500 Subject: [PATCH 461/696] Add news --- news/pytest-wavelength-warnings.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/pytest-wavelength-warnings.rst diff --git a/news/pytest-wavelength-warnings.rst b/news/pytest-wavelength-warnings.rst new file mode 100644 index 00000000..3446720a --- /dev/null +++ b/news/pytest-wavelength-warnings.rst @@ -0,0 +1,23 @@ +**Added:** + +* unit test for expected warning when no wavelength is provided for DiffractionObject init + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 13b42b527b42e674324bab6005f1628007705c80 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 06:35:35 +0000 Subject: [PATCH 462/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 382f5776..220a4aad 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -36,7 +36,7 @@ "metadata": {"thing1": 1}, }, True, - False + False, ), ( # Different names, expect inequality { @@ -54,7 +54,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ( # One without wavelength, expect inequality { @@ -71,7 +71,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ( # Different wavelengths, expect inequality { @@ -89,7 +89,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - False + False, ), ( # Different scat_quantity, expect inequality { @@ -107,7 +107,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ( # Different q xarray values, expect inequality { @@ -122,7 +122,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ( # Different metadata, expect inequality { @@ -138,11 +138,13 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ], ) -def test_diffraction_objects_equality(do_args_1, do_args_2, expected_equality, warning_expected, wavelength_warning_msg): +def test_diffraction_objects_equality( + do_args_1, do_args_2, expected_equality, warning_expected, wavelength_warning_msg +): if warning_expected: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): do_1 = DiffractionObject(**do_args_1) From b7ad8104f20009562749a3bace7ea40d5eb3a218 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 13:23:03 -0500 Subject: [PATCH 463/696] catch divide by zero in dump func --- tests/test_diffraction_objects.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 220a4aad..fe4cac4e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -373,15 +373,16 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - do = DiffractionObject( - wavelength=1.54, - name="test", - scat_quantity="x-ray", - xarray=np.array(x), - yarray=np.array(y), - xtype="q", - metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, - ) + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + do = DiffractionObject( + wavelength=1.54, + name="test", + scat_quantity="x-ray", + xarray=np.array(x), + yarray=np.array(y), + xtype="q", + metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, + ) mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): do.dump(file, "q") From 25cb8750daf2f272c04f35f09be5f86b41b1a700 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 13:34:32 -0500 Subject: [PATCH 464/696] Catch warnings in zero to test_tth_to_d --- tests/test_transforms.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 7a3bdeef..774bb781 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -163,33 +163,38 @@ def test_d_to_q(d, expected_q): @pytest.mark.parametrize( - "wavelength, tth, expected_d", - [ - # UC0: User specified empty tth values (without wavelength) - (None, np.array([]), np.array([])), - # UC1: User specified empty tth values (with wavelength) - (4 * np.pi, np.array([]), np.array([])), - # UC2: User specified valid tth values between 0-180 degrees (without wavelength) + "wavelength, tth, expected_d, divide_by_zero_warning_expected", + [ + # Test conversion of q to d with valid values + # Case 1: empty tth values, no, expect empty d values + (None, np.array([]), np.array([]), False), + # Case 2: empty tth values, wavelength provided, expect empty d values + (4 * np.pi, np.array([]), np.array([]), False), + # Case 3: User specified valid tth values between 0-180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), + False ), - # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + # Case 4: User specified valid tth values between 0-180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), + True ), ], ) -def test_tth_to_d(wavelength, tth, expected_d, wavelength_warning_msg): +def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, wavelength_warning_msg): if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_d = tth_to_d(tth, wavelength) + elif divide_by_zero_warning_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_d = tth_to_d(tth, wavelength) else: actual_d = tth_to_d(tth, wavelength) - assert np.allclose(actual_d, expected_d) From 9c8fd9524f4db9fa2fe18f0b10b3cb6fcdc43fef Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 13:59:13 -0500 Subject: [PATCH 465/696] Fix another divide by zero warning in transform --- tests/test_diffraction_objects.py | 185 ++++++++++++++---------------- tests/test_transforms.py | 24 ++-- 2 files changed, 95 insertions(+), 114 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index fe4cac4e..cff847a2 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -403,114 +403,101 @@ def test_dump(tmp_path, mocker): assert actual == expected -test_init_valid_params = [ - ( # instantiate just array attributes - { - "xarray": np.array([0.0, 90.0, 180.0]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "tth", - "wavelength": 4.0 * np.pi, - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "tth", - "name": "", - "scat_quantity": "", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ( # instantiate just array attributes - { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "d", - "wavelength": 4.0 * np.pi, - "scat_quantity": "x-ray", - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "d", - "name": "", - "scat_quantity": "x-ray", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), -] - - @pytest.mark.parametrize( - "init_args, expected_do_dict", - test_init_valid_params, + "do_init_args, expected_do_dict", + [ + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ], ) -def test_init_valid(init_args, expected_do_dict): - actual_do_dict = DiffractionObject(**init_args).__dict__ +def test_init_valid(do_init_args, expected_do_dict): + actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" ) assert diff == {} -test_init_invalid_params = [ - ( # UC1: no arguments provided - {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", - ), - ( # UC2: only xarray and yarray provided - {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, - "missing 1 required positional argument: 'xtype'", - ), -] - - -@pytest.mark.parametrize("init_args, expected_error_msg", test_init_invalid_params) +@pytest.mark.parametrize( + "do_init_args, expected_error_msg", + [ + ( # Case 1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # Case 2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), + ], +) def test_init_invalid_args( - init_args, + do_init_args, expected_error_msg, ): with pytest.raises(TypeError, match=expected_error_msg): - DiffractionObject(**init_args) + DiffractionObject(**do_init_args) -def test_all_array_getter(): - actual_do = DiffractionObject( - xarray=np.array([0.0, 90.0, 180.0]), - yarray=np.array([1.0, 2.0, 3.0]), - xtype="tth", - wavelength=4.0 * np.pi, - ) - expected_all_arrays = np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ) +def test_all_array_getter(do_minimal_tth): + actual_do = do_minimal_tth + print(actual_do.all_arrays) + expected_all_arrays = [[1, 0.51763809, 30, 12.13818192], [2, 1, 60, 6.28318531]] assert np.allclose(actual_do.all_arrays, expected_all_arrays) @@ -575,14 +562,8 @@ def test_input_xtype_setter_error(do_minimal): do.input_xtype = "q" -def test_copy_object(): - do = DiffractionObject( - name="test", - wavelength=4.0 * np.pi, - xarray=np.array([0.0, 90.0, 180.0]), - yarray=np.array([1.0, 2.0, 3.0]), - xtype="tth", - ) +def test_copy_object(do_minimal): + do = do_minimal do_copy = do.copy() assert do == do_copy assert id(do) != id(do_copy) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 774bb781..9962b359 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -146,43 +146,43 @@ def test_q_to_d(q, expected_d, warning_expected): @pytest.mark.parametrize( - "d, expected_q", + "d, expected_q, zero_divide_error_expected", [ # UC1: User specified empty d values - (np.array([]), np.array([])), + (np.array([]), np.array([]), False), # UC2: User specified valid d values ( np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), + True, ), ], ) -def test_d_to_q(d, expected_q): - actual_q = d_to_q(d) +def test_d_to_q(d, expected_q, zero_divide_error_expected): + if zero_divide_error_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_q = d_to_q(d) + else: + actual_q = d_to_q(d) assert np.allclose(actual_q, expected_q) @pytest.mark.parametrize( "wavelength, tth, expected_d, divide_by_zero_warning_expected", - [ + [ # Test conversion of q to d with valid values # Case 1: empty tth values, no, expect empty d values (None, np.array([]), np.array([]), False), # Case 2: empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), # Case 3: User specified valid tth values between 0-180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 180]), - np.array([0, 1, 2, 3, 4, 5]), - False - ), + (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), # Case 4: User specified valid tth values between 0-180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), - True + True, ), ], ) From 102310be1b70abd684c2e4349e3b9ba9745e3fca Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 14:09:47 -0500 Subject: [PATCH 466/696] Fix remaining divide by zero --- tests/test_diffraction_objects.py | 12 +++++++++--- tests/test_transforms.py | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index cff847a2..6d9bdf28 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -404,7 +404,7 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( - "do_init_args, expected_do_dict", + "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ ( # instantiate just array attributes { @@ -433,6 +433,7 @@ def test_dump(tmp_path, mocker): "dmax": np.float64(np.inf), "wavelength": 4.0 * np.pi, }, + True ), ( # instantiate just array attributes { @@ -462,11 +463,16 @@ def test_dump(tmp_path, mocker): "dmax": np.float64(np.inf), "wavelength": 4.0 * np.pi, }, + False ), ], ) -def test_init_valid(do_init_args, expected_do_dict): - actual_do_dict = DiffractionObject(**do_init_args).__dict__ +def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expected): + if divide_by_zero_warning_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_do_dict = DiffractionObject(**do_init_args).__dict__ + else: + actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" ) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 9962b359..9b85a6de 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -223,30 +223,31 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m @pytest.mark.parametrize( - "wavelength, d, expected_tth", + "wavelength, d, expected_tth, divide_by_zero_warning_expected", [ # UC1: Empty d values, no wavelength, return empty arrays - (None, np.empty((0)), np.empty((0))), + (None, np.empty((0)), np.empty((0)), False), # UC2: Empty d values, wavelength specified, return empty arrays - (4 * np.pi, np.empty((0)), np.empty(0)), + (4 * np.pi, np.empty((0)), np.empty(0), False), # UC3: User specified valid d values, no wavelength, return empty arrays - ( - None, - np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), - np.array([0, 1, 2, 3, 4, 5]), - ), + (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), # UC4: User specified valid d values (with wavelength) ( 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), + False ), ], ) -def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg): - if wavelength is None: +def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, wavelength_warning_msg): + if wavelength is None and not divide_by_zero_warning_expected: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_tth = d_to_tth(d, wavelength) + elif wavelength is None and divide_by_zero_warning_expected: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_tth = d_to_tth(d, wavelength) else: actual_tth = d_to_tth(d, wavelength) @@ -259,7 +260,7 @@ def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg): # UC1: user specified invalid d values that result in tth > 180 degrees (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), # UC2: user specified a wrong wavelength that result in tth > 180 degrees - (100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), ValueError), + (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) def test_d_to_tth_bad(wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): From efb0585ebc0668c620bd91328da5ddeb8330390e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 14:10:14 -0500 Subject: [PATCH 467/696] Apply pre-commit --- tests/test_diffraction_objects.py | 4 ++-- tests/test_transforms.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6d9bdf28..4576b257 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -433,7 +433,7 @@ def test_dump(tmp_path, mocker): "dmax": np.float64(np.inf), "wavelength": 4.0 * np.pi, }, - True + True, ), ( # instantiate just array attributes { @@ -463,7 +463,7 @@ def test_dump(tmp_path, mocker): "dmax": np.float64(np.inf), "wavelength": 4.0 * np.pi, }, - False + False, ), ], ) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 9b85a6de..65f437ac 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -236,7 +236,7 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), - False + False, ), ], ) From 3287747e5bf68cf69f9e6766d5a06f7ddd3cf1a4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 14:12:47 -0500 Subject: [PATCH 468/696] Add news --- news/pytest-warning-divide-zero.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/pytest-warning-divide-zero.rst diff --git a/news/pytest-warning-divide-zero.rst b/news/pytest-warning-divide-zero.rst new file mode 100644 index 00000000..4b84dba2 --- /dev/null +++ b/news/pytest-warning-divide-zero.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: previous news created for catching divide by zero warnings in pytest + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 5d22f1c6561b0ab5c37663c62469a22108b883c1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 14:55:22 -0500 Subject: [PATCH 469/696] Apply prettier pre-commit --- .codecov.yml | 8 ++--- .github/ISSUE_TEMPLATE/release_checklist.md | 8 ++--- .../workflows/build-wheel-release-upload.yml | 2 +- .github/workflows/check-news-item.yml | 2 +- .pre-commit-config.yaml | 31 ++++++++++++------- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 5a94096e..4af5eb24 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,14 +1,14 @@ coverage: status: - project: # more options at https://docs.codecov.com/docs/commit-status + project: # more options at https://docs.codecov.com/docs/commit-status default: target: auto # use the coverage from the base commit, fail if coverage is lower - threshold: 0% # allow the coverage to drop by + threshold: 0% # allow the coverage to drop by comment: layout: " diff, flags, files" behavior: default require_changes: false - require_base: false # [true :: must have a base report to post] - require_head: false # [true :: must have a head report to post] + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 11df804d..fa94779e 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -12,11 +12,11 @@ assignees: "" - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are - missing), tutorials, and other human-written text is up-to-date with any changes in the code. + missing), tutorials, and other human-written text is up-to-date with any changes in the code. - [ ] Installation instructions in the README, documentation, and the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). -- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. +- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: @@ -42,5 +42,5 @@ Please let @sbillinge know that all checks are done and the package is ready for -- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. -- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. +- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 0c9f1357..841d1a76 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: tags: - - '*' # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml jobs: release: diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index be549960..aa040f09 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -3,7 +3,7 @@ name: Check for News on: pull_request_target: branches: - - main + - main jobs: check-news-item: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cf0556f..9518b6eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,14 @@ default_language_version: - python: python3 + python: python3 ci: - autofix_commit_msg: | - [pre-commit.ci] auto fixes from pre-commit hooks - autofix_prs: true - autoupdate_branch: 'pre-commit-autoupdate' - autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' - autoupdate_schedule: monthly - skip: [no-commit-to-branch] - submodules: false + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: "pre-commit-autoupdate" + autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate" + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 @@ -47,6 +47,13 @@ repos: - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - - id: codespell - additional_dependencies: - - tomli + - id: codespell + additional_dependencies: + - tomli + # prettier - multi formatter + - repo: https://github.com/pre-commit/mirrors-prettier + rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 + hooks: + - id: prettier + additional_dependencies: + - "prettier@^3.2.4" From 2f620bc16bf471939bd4d7219b8aaaf322c871ba Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 15:08:22 -0500 Subject: [PATCH 470/696] Add comment for codespell and prettier --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9518b6eb..b7fb3631 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,13 +44,14 @@ repos: name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] + # codespell - spell checker for source code - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell additional_dependencies: - tomli - # prettier - multi formatter + # prettier - multi formatter for json, yaml, md - repo: https://github.com/pre-commit/mirrors-prettier rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 hooks: From f79e88eadfcd7b58e84c6caa591a960d79689ba9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 15:13:52 -0500 Subject: [PATCH 471/696] Add news --- news/prettier-pre-commit.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/prettier-pre-commit.rst diff --git a/news/prettier-pre-commit.rst b/news/prettier-pre-commit.rst new file mode 100644 index 00000000..d61d93ed --- /dev/null +++ b/news/prettier-pre-commit.rst @@ -0,0 +1,23 @@ +**Added:** + +* prettier pre-commit hook for automatic linting of .yml, .json, and .md files + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From ab0fc8a9f39d5e0bfbf876b5919442bf76b323d2 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 21 Dec 2024 16:08:38 -0500 Subject: [PATCH 472/696] refactor to separate getting info and creating config files --- news/userinfo.rst | 24 +++++++++ src/diffpy/utils/tools.py | 46 ++++++++--------- tests/conftest.py | 12 +++-- tests/test_tools.py | 104 ++++++++++++++++++++++++++++---------- 4 files changed, 129 insertions(+), 57 deletions(-) create mode 100644 news/userinfo.rst diff --git a/news/userinfo.rst b/news/userinfo.rst new file mode 100644 index 00000000..124b49f8 --- /dev/null +++ b/news/userinfo.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* Refactor get_user_info to separate the tasks of getting the info from config files + and creating config files when they are missing. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3fc10031..234aedb2 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,6 +1,5 @@ import importlib.metadata import json -import os from copy import copy from pathlib import Path @@ -56,7 +55,7 @@ def load_config(file_path): Returns ------- dict: - The configuration dictionary or None if file does not exist. + The configuration dictionary or {} if the config file does not exist. """ config_file = Path(file_path).resolve() @@ -65,7 +64,7 @@ def load_config(file_path): config = json.load(f) return config else: - return None + return {} def _sorted_merge(*dicts): @@ -91,9 +90,9 @@ def _create_global_config(args): return return_bool -def get_user_info(args=None): +def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """ - Get username and email configuration. + Get username, email and orcid configuration. First attempts to load config file from global and local paths. If neither exists, creates a global config file. @@ -111,27 +110,26 @@ def get_user_info(args=None): The dictionary containing username and email with corresponding values. """ - config_bool = True + runtime_info = {"owner_name": owner_name, "owner_email": owner_email, "owner_orcid": owner_orcid} + for key, value in copy(runtime_info).items(): + if value is None or value == "": + del runtime_info[key] global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") - if global_config is None and local_config is None: - print( - "No global configuration file was found containing " - "information about the user to associate with the data.\n" - "By following the prompts below you can add your name and email to this file on the current " - "computer and your name will be automatically associated with subsequent diffpy data by default.\n" - "This is not recommended on a shared or public computer. " - "You will only have to do that once.\n" - "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" - ) - config_bool = _create_global_config(args) - global_config = load_config(Path().home() / "diffpyconfig.json") - config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) - if config_bool is False: - os.remove(Path().home() / "diffpyconfig.json") - config = {"username": "", "email": ""} - - return config + # if global_config is None and local_config is None: + # print( + # "No global configuration file was found containing " + # "information about the user to associate with the data.\n" + # "By following the prompts below you can add your name and email to this file on the current " + # "computer and your name will be automatically associated with subsequent diffpy data by default.\n" + # "This is not recommended on a shared or public computer. " + # "You will only have to do that once.\n" + # "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" + # ) + user_info = global_config + user_info.update(local_config) + user_info.update(runtime_info) + return user_info def get_package_info(package_names, metadata=None): diff --git a/tests/conftest.py b/tests/conftest.py index aa76715f..7f8de460 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,14 +12,16 @@ def user_filesystem(tmp_path): base_dir = Path(tmp_path) home_dir = base_dir / "home_dir" home_dir.mkdir(parents=True, exist_ok=True) - cwd_dir = base_dir / "cwd_dir" + cwd_dir = home_dir / "cwd_dir" cwd_dir.mkdir(parents=True, exist_ok=True) - - home_config_data = {"username": "home_username", "email": "home@email.com"} + home_config_data = { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + } with open(home_dir / "diffpyconfig.json", "w") as f: json.dump(home_config_data, f) - - yield tmp_path + yield home_dir, cwd_dir @pytest.fixture diff --git a/tests/test_tools.py b/tests/test_tools.py index 0a42332f..c98acb07 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -8,11 +8,9 @@ from diffpy.utils.tools import get_package_info, get_user_info -def _setup_dirs(monkeypatch, user_filesystem): - cwd = Path(user_filesystem) - home_dir = cwd / "home_dir" - monkeypatch.setattr("pathlib.Path.home", lambda _: home_dir) - os.chdir(cwd) +def _setup_dirs(user_filesystem): + home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir + os.chdir(cwd_dir) return home_dir @@ -24,15 +22,6 @@ def _run_tests(inputs, expected): assert config.get("email") == expected_email -params_user_info_with_home_conf_file = [ - (["", ""], ["home_username", "home@email.com"]), - (["cli_username", ""], ["cli_username", "home@email.com"]), - (["", "cli@email.com"], ["home_username", "cli@email.com"]), - ([None, None], ["home_username", "home@email.com"]), - (["cli_username", None], ["cli_username", "home@email.com"]), - ([None, "cli@email.com"], ["home_username", "cli@email.com"]), - (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), -] params_user_info_with_local_conf_file = [ (["", ""], ["cwd_username", "cwd@email.com"]), (["cli_username", ""], ["cli_username", "cwd@email.com"]), @@ -84,21 +73,80 @@ def _run_tests(inputs, expected): ] -@pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) -def test_get_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): - _setup_dirs(monkeypatch, user_filesystem) - _run_tests(inputs, expected) - - -@pytest.mark.parametrize("inputs, expected", params_user_info_with_local_conf_file) -def test_get_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): - _setup_dirs(monkeypatch, user_filesystem) - local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} - with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: +@pytest.mark.parametrize( + "runtime_inputs, expected", + [ # config file in home is present, no config in cwd. various runtime values passed + # C1: nothing passed in, expect uname, email, orcid from home_config + ({}, {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}), + # C2: empty strings passed in, expect uname, email, orcid from home_config + ( + {"owner_name": "", "owner_email": "", "owner_orcid": ""}, + {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + ), + # C3: just owner name passed in at runtime. expect runtime_oname but others from config + ( + {"owner_name": "runtime_ownername"}, + {"owner_name": "runtime_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + ), + # C4: just owner email passed in at runtime. expect runtime_email but others from config + ( + {"owner_email": "runtime@email.com"}, + {"owner_name": "home_ownername", "owner_email": "runtime@email.com", "owner_orcid": "home_orcid"}, + ), + # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config + ( + {"owner_orcid": "runtime_orcid"}, + {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "runtime_orcid"}, + ), + ], +) +def test_get_user_info_with_home_conf_file(runtime_inputs, expected, user_filesystem, mocker): + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] + # is tmp_dir/cwd_dir + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + actual = get_user_info(**runtime_inputs) + assert actual == expected + + +@pytest.mark.parametrize( + "runtime_inputs, expected", + [ # tests as before but now config file present in cwd and home but orcid + # missing in the cwd config + # C1: nothing passed in, expect uname, email from local config, orcid from home_config + ({}, {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}), + # C2: empty strings passed in, expect uname, email, orcid from home_config + ( + {"owner_name": "", "owner_email": "", "owner_orcid": ""}, + {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}, + ), + # C3: just owner name passed in at runtime. expect runtime_oname but others from config + ( + {"owner_name": "runtime_ownername"}, + {"owner_name": "runtime_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}, + ), + # C4: just owner email passed in at runtime. expect runtime_email but others from config + ( + {"owner_email": "runtime@email.com"}, + {"owner_name": "cwd_ownername", "owner_email": "runtime@email.com", "owner_orcid": "home_orcid"}, + ), + # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config + ( + {"owner_orcid": "runtime_orcid"}, + {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "runtime_orcid"}, + ), + ], +) +def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_filesystem, mocker): + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] i + # s tmp_dir/cwd_dir + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + local_config_data = {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com"} + with open(user_filesystem[1] / "diffpyconfig.json", "w") as f: json.dump(local_config_data, f) - _run_tests(inputs, expected) - os.remove(Path().home() / "diffpyconfig.json") - _run_tests(inputs, expected) + actual = get_user_info(**runtime_inputs) + assert actual == expected @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) From b819f70349115b2a859eb0190a73b57bfa869b5e Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 21 Dec 2024 16:23:06 -0500 Subject: [PATCH 473/696] commenting out broken tests that will be fixed in make_config PR --- tests/test_tools.py | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index c98acb07..cf730ddd 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -7,11 +7,11 @@ from diffpy.utils.tools import get_package_info, get_user_info - -def _setup_dirs(user_filesystem): - home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir - os.chdir(cwd_dir) - return home_dir +# def _setup_dirs(monkeypatch, user_filesystem): +# home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir +# os.chdir(cwd_dir) +# return home_dir +# def _run_tests(inputs, expected): @@ -138,8 +138,8 @@ def test_get_user_info_with_home_conf_file(runtime_inputs, expected, user_filesy ], ) def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_filesystem, mocker): - # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] i - # s tmp_dir/cwd_dir + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] + # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) local_config_data = {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com"} @@ -149,27 +149,27 @@ def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_files assert actual == expected -@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) -def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): - _setup_dirs(monkeypatch, user_filesystem) - os.remove(Path().home() / "diffpyconfig.json") - inp_iter = iter(inputsb) - monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) - _run_tests(inputsa, expected) - confile = Path().home() / "diffpyconfig.json" - assert confile.is_file() - - -@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) -def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): - _setup_dirs(monkeypatch, user_filesystem) - os.remove(Path().home() / "diffpyconfig.json") - inp_iter = iter(inputsb) - monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) - _run_tests(inputsa, expected) - confile = Path().home() / "diffpyconfig.json" - assert confile.exists() is False - +# @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) +# def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): +# _setup_dirs(monkeypatch, user_filesystem) +# os.remove(Path().home() / "diffpyconfig.json") +# inp_iter = iter(inputsb) +# monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) +# _run_tests(inputsa, expected) +# confile = Path().home() / "diffpyconfig.json" +# assert confile.is_file() +# +# +# @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) +# def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): +# _setup_dirs(monkeypatch, user_filesystem) +# os.remove(Path().home() / "diffpyconfig.json") +# inp_iter = iter(inputsb) +# monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) +# _run_tests(inputsa, expected) +# confile = Path().home() / "diffpyconfig.json" +# assert confile.exists() is False +# params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), From c2ac17305026af44d1a1cc1b0d312a324d4f2713 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 21 Dec 2024 18:49:28 -0500 Subject: [PATCH 474/696] docs --- doc/source/examples/tools_example.rst | 106 ++++++++++++-------------- src/diffpy/utils/tools.py | 36 ++++++--- 2 files changed, 74 insertions(+), 68 deletions(-) diff --git a/doc/source/examples/tools_example.rst b/doc/source/examples/tools_example.rst index a56f76d3..60746e6f 100644 --- a/doc/source/examples/tools_example.rst +++ b/doc/source/examples/tools_example.rst @@ -13,11 +13,11 @@ Automatically Capture User Info One task we would like to do is to capture and propagate useful metadata that describes the diffraction data. Some is essential such as wavelength and radiation type. Other metadata is useful such as information about the sample, co-workers and so on. However, one of the most important bits of information is the name of the data owner. -For example, in ``DiffractionObjects`` this is stored in the ``metadata`` dictionary as ``username``, ``user_email``, -and ``user_orcid``. +For example, in ``DiffractionObjects`` this is stored in the ``metadata`` dictionary as ``owner_name``, ``owner_email``, +and ``owner_orcid``. To reduce experimenter overhead when collecting this information, we have developed an infrastructure that helps -to capture this information automatically when you are using `DiffractionObjects` and other diffpy tools. +to capture this information automatically when you are using ``DiffractionObjects`` and other diffpy tools. You may also reuse this infrastructure for your own projects using tools in this tutorial. This example will demonstrate how ``diffpy.utils`` allows us to conveniently load and manage user and package information. @@ -28,8 +28,9 @@ Load user info into your program To use this functionality in your own code make use of the ``get_user_info`` function in ``diffpy.utils.tools`` which will search for information about the user, parse it, and return -it in a dictionary object e.g. if the user is "Jane Doe" with email "janedoe@gmail.com" and the -function can find the information, if you type this +it in a dictionary object e.g. if the user is "Jane Doe" with email "janedoe@gmail.com" and ORCID +"0000-0000-0000-0000", and if the +function can find the information (more on this below), if you type this .. code-block:: python @@ -40,7 +41,7 @@ The function will return .. code-block:: python - {"email": "janedoe@email.com", "username": "Jane Doe"} + {"owner_email": "janedoe@email.com", "owner_name": "Jane Doe", "owner_orcid": "0000-0000-0000-0000"} Where does ``get_user_info()`` get the user information from? @@ -48,8 +49,9 @@ Where does ``get_user_info()`` get the user information from? The function will first attempt to load the information from configuration files with the name ``diffpyconfig.json`` on your hard-drive. -It looks first for the file in the current working directory. If it cannot find it there it will look -user's home, i.e., login, directory. To find this directory, open a terminal and a unix or mac system type :: +It looks for files in the current working directory and in the computer-user's home (i.e., login) directory. +For example, it might be in C:/Users/yourname`` or something like that, but to find this directory, open +a terminal and a unix or mac system type :: cd ~ pwd @@ -58,67 +60,55 @@ Or type ``Echo $HOME``. On a Windows computer :: echo %USERPROFILE%" +It is also possible to override the values in the config files at run-time by passing values directly into the +function according to ``get_user_info``, for example, +``get_user_info(owner_name="Janet Doe", owner_email="janetdoe@email.com", owner_orcid="1111-1111-1111-1111")``. +The information to pass into ``get_user_info`` could be entered by a user through a command-line interface +or into a gui. + What if no config files exist yet? ----------------------------------- -If no configuration files can be found, the function attempts to create one in the user's home -directory. The function will pause execution and ask for a user-response to enter the information. -It will then write the config file in the user's home directory. - -In this way, the next, and subsequent times the program is run, it will no longer have to prompt the user -as it will successfully find the new config file. - -Getting user data with no config files and with no interruption of execution ----------------------------------------------------------------------------- +If no configuration files can be found, they can be created using a text editor, or by using a diffpy tool +called ``check_and_build_global_config()`` which, if no global config file can be found, prompts the user for the +information then writes the config file in the user's home directory. -If you would like get run ``get_user_data()`` but without execution interruption even if it cannot find -an input file, type +When building an application where you want to capture data-owner information, we recommend you execute +``check_and_build_global_config()`` first followed by ``get_user_info`` in your app workflow. E.g., .. code-block:: python - - user_data = get_user_data(skip_config_creation=True) - -Passing user information directly to ``get_user_data()`` --------------------------------------------------------- - -It can be passed user information which fully or partially overrides looking in config files -For example, in this way it would be possible to pass in information -that is entered through a gui or command line interface. E.g., - - .. code-block:: python - - new_user_info = get_user_info({"username": "new_username", "email": "new@example.com"}) - -This returns ``{"username": "new_username", "email": "new@example.com"}`` (and so, effectively, does nothing) -However, You can update only the username or email individually, for example - -.. code-block:: python - - new_user_info = get_user_info({"username": new_username}) - -will return ``{"username": "new_username", "email": "janedoe@gmail.com"}`` -if it found ``janedoe@gmail.com`` as the email in the config file. -Similarly, you can update only the email in the returned dictionary, - -.. code-block:: python - - new_user_info = get_user_info({"email": new@email.com}) - -which will return ``{"username": "Jane Doe", "email": "new@email.com"}`` -if it found ``Jane Doe`` as the user in the config file. - -I entered the wrong information in my config file so it always loads incorrect information ------------------------------------------------------------------------------------------- - -You can use of the above methods to temporarily override the incorrect information in your -global config file. However, it is easy to fix this simply by editing that file using a text + from diffpy.utils.tools import check_and_build_global_config, get_user_info + from datetime import datetime + import json + + def my_cool_data_enhancer_app_main(data, filepath): + check_and_build_global_config() + metadata_enhanced_data = get_user_info() + metadata_enhanced_data.update({"creation_time": datetime.now(), + "data": data}) + with open(filepath, "w") as f: + json.dump(metadata_enhanced_data, f) + +``check_and_build_global_config()`` only +interrupts execution if it can't find a valid config file, and so if the user enters valid information +it will only run once. However, if you want to bypass this behavior, +``check_and_build_global_config()`` takes an optional boolean ``skip_config_creation`` parameter that +could be set to ``True`` at runtime to override the config creation. + +I entered the wrong information in my config file so it always loads incorrect information, how do I fix that? +-------------------------------------------------------------------------------------------------------------- + +It is easy to fix this simply by deleting the global and/or local config files, which will allow +you to re-enter the information during the ``check_and_build_global_config()`` initialization +workflow. You can also simply editi the ``diffpyconfig.json`` file directly using a text editor. Locate the file ``diffpyconfig.json``, in your home directory and open it in an editor :: { - "username": "John Doe", - "email": "john.doe@example.com" + "owner_name": "John Doe", + "owner_email": "john.doe@example.com" + "owner_orcid": "0000-0000-4321-1234" } Then you can edit the username and email as needed, make sure to save your edits. diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 234aedb2..91505e6f 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -92,22 +92,38 @@ def _create_global_config(args): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """ - Get username, email and orcid configuration. - - First attempts to load config file from global and local paths. - If neither exists, creates a global config file. - It prioritizes values from args, then local, then global. - Removes invalid global config file if creation is needed, replacing it with empty username and email. + Get name, email and orcid of the owner/user from various sources and return it as a metadata dictionary + + The function looks for the information in json format configuration files with the name 'diffpyconfig.json'. + These can be in the user's home directory and in the current working directory. The information in the + config files are combined, with the local config overriding the home-directory one. Values for + owner_name, owner_email, and owner_orcid may be passed in to the function and these override the values + in the config files. + + A template for the config file is below. Create a text file called 'diffpyconfig.json' in your home directory + and copy-paste the template into it, editing it with your real information. + { + "owner_name": ">", + "owner_email": ">@email.com", + "owner_orcid": ">" + } + You may also store any other gloabl-level information that you would like associated with your + diffraction data in this file Parameters ---------- - args argparse.Namespace - The arguments from the parser, default is None. + owner_name: string, optional, default is the value stored in the global or local config file. + The name of the user who will show as owner in the metadata that is stored with the data + owner_email: string, optional, default is the value stored in the global or local config file. + The email of the user/owner + owner_name: string, optional, default is the value stored in the global or local config file. + The ORCID id of the user/owner Returns ------- - dict or None: - The dictionary containing username and email with corresponding values. + dict: + The dictionary containing username, email and orcid of the user/owner, and any other information + stored in the global or local config files. """ runtime_info = {"owner_name": owner_name, "owner_email": owner_email, "owner_orcid": owner_orcid} From f96728a922b5169a61fdc12a09affce4ce86ed1e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 20:25:05 -0500 Subject: [PATCH 475/696] Add class docstring for diffraction object and news --- news/class-docstring.rst | 23 +++++ src/diffpy/utils/diffraction_objects.py | 113 +++++++++++++++--------- 2 files changed, 96 insertions(+), 40 deletions(-) create mode 100644 news/class-docstring.rst diff --git a/news/class-docstring.rst b/news/class-docstring.rst new file mode 100644 index 00000000..a31cc6d6 --- /dev/null +++ b/news/class-docstring.rst @@ -0,0 +1,23 @@ +**Added:** + +* class docstring for `DiffractionObject` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index d72a2889..602434f9 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -36,50 +36,38 @@ def _setter_wmsg(attribute): class DiffractionObject: """ - Initialize a DiffractionObject instance. + A class to represent diffraction data for various scientific experiments involving scattering + techniques such as X-ray, neutron, and electron diffraction. This object can manage diffraction + data including transformations between different scattering quantities like q (scattering vector), + 2θ (two-theta angle), and d (interplanar spacing), and perform various operations like scaling, addition, + and subtraction of diffraction patterns. - Parameters + Attributes ---------- - xarray : array-like - The independent variable array containing "q", "tth", or "d" values. - yarray : array-like - The dependent variable array corresponding to intensity values. - xtype : str - The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. - wavelength : float, optional - The wavelength of the incoming beam, specified in angstroms (Å). Default is none. - scat_quantity : str, optional + all_arrays : ndarray + The array containing the quantity of q, tth, d values. + input_xtype : str + The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES} + id : uuid + The unique identifier for the diffraction object. + scat_quantity : str The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". - name : str, optional + wavelength : float + The wavelength of the incoming beam, specified in angstroms (Å). Default is none. + name: str The name or label for the scattering data. Default is an empty string "". - metadata : dict, optional - The additional metadata associated with the diffraction object. Default is {}. - - Examples - -------- - Create a DiffractionObject for X-ray scattering data: - - >>> import numpy as np - >>> from diffpy.utils.diffraction_objects import DiffractionObject - ... - >>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) - >>> y = np.array([10, 20, 40, 60]) # intensity values - >>> metadata = { - ... "sample": "rock salt from the beach", - ... "composition": "NaCl", - ... "temperature": "300 K,", - ... "experimenters": "Phill, Sally" - ... } - >>> do = DiffractionObject( - ... xarray=x, - ... yarray=y, - ... xtype="q", - ... wavelength=1.54, - ... scat_quantity="x-ray", - ... name="beach_rock_salt_1", - ... metadata=metadata - ... ) - >>> print(do.metadata) + qmin : float + The minimum q value. + qmax : float + The maximum q value. + tthmin : float + The minimum two-theta value. + tthmax : float + The maximum two-theta value. + dmin : float + The minimum d-spacing value. + dmax : float + The maximum d-spacing value. """ def __init__( @@ -92,6 +80,51 @@ def __init__( name="", metadata={}, ): + """ + Initialize a DiffractionObject instance. + + Parameters + ---------- + xarray : ndarray + The independent variable array containing "q", "tth", or "d" values. + yarray : ndarray + The dependent variable array corresponding to intensity values. + xtype : str + The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. + wavelength : float, optional + The wavelength of the incoming beam, specified in angstroms (Å). Default is none. + scat_quantity : str, optional + The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". + name : str, optional + The name or label for the scattering data. Default is an empty string "". + metadata : dict, optional + The additional metadata associated with the diffraction object. Default is {}. + + Examples + -------- + Create a DiffractionObject for X-ray scattering data + >>> import numpy as np + >>> from diffpy.utils.diffraction_objects import DiffractionObject + ... + >>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) + >>> y = np.array([10, 20, 40, 60]) # intensity values + >>> metadata = { + ... "sample": "rock salt from the beach", + ... "composition": "NaCl", + ... "temperature": "300 K,", + ... "experimenters": "Phill, Sally" + ... } + >>> do = DiffractionObject( + ... xarray=x, + ... yarray=y, + ... xtype="q", + ... wavelength=1.54, + ... scat_quantity="x-ray", + ... name="beach_rock_salt_1", + ... metadata=metadata + ... ) + >>> print(do.metadata) + """ self._id = uuid.uuid4() self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) From 2dfb953d13d926f3f161a48a6ea32627c4f11bca Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:22:18 -0500 Subject: [PATCH 476/696] Continue refactor test functions, replace UC to C --- tests/test_diffraction_objects.py | 147 +++++++++++++++--------------- tests/test_transforms.py | 68 +++++++------- 2 files changed, 112 insertions(+), 103 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 4576b257..d1d66ee6 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -186,7 +186,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test that scale_to() scales to the correct values - # Case 1: same x-array and y-array, check offset + # C1: same x-array and y-array, check offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -208,7 +208,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # Case 2: same length x-arrays with exact x-value match + # C2: same length x-arrays with exact x-value match ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -230,7 +230,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # Case 3: same length x-arrays with approximate x-value match + # C3: same length x-arrays with approximate x-value match ( { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), @@ -252,7 +252,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # Case 4: different x-array lengths with approximate x-value match + # C4: different x-array lengths with approximate x-value match ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -272,7 +272,7 @@ def test_init_invalid_xtype(): "d": None, "offset": 0, }, - # Case 5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + # C5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ], @@ -287,76 +287,81 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) -params_scale_to_bad = [ - # UC1: user did not specify anything - ( - { - "xarray": np.array([0.1, 0.2, 0.3]), - "yarray": np.array([1, 2, 3]), - "xtype": "q", - "wavelength": 2 * np.pi, - "target_xarray": np.array([0.05, 0.1, 0.2, 0.3]), - "target_yarray": np.array([5, 10, 20, 30]), - "target_xtype": "q", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": None, - "d": None, - "offset": 0, - } - ), - # UC2: user specified more than one of q, tth, and d - ( - { - "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": 10, - "offset": 0, - } - ), -] - - -@pytest.mark.parametrize("inputs", params_scale_to_bad) -def test_scale_to_bad(inputs): - orig_diff_object = DiffractionObject( - xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] - ) - target_diff_object = DiffractionObject( - xarray=inputs["target_xarray"], - yarray=inputs["target_yarray"], - xtype=inputs["target_xtype"], - wavelength=inputs["target_wavelength"], - ) +@pytest.mark.parametrize( + "org_do_args, target_do_args, scale_inputs", + [ + # UC1: user did not specify anything + ( + { + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "yarray": np.array([5, 10, 20, 30]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": None, + "d": None, + "offset": 0, + }, + ), + # UC2: user specified more than one of q, tth, and d + ( + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": 10, + "offset": 0, + }, + ), + ], +) +def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): + original_do = DiffractionObject(**org_do_args) + target_do = DiffractionObject(**target_do_args) with pytest.raises( ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): - orig_diff_object.scale_to( - target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] + original_do.scale_to( + target_do, + q=scale_inputs["q"], + tth=scale_inputs["tth"], + d=scale_inputs["d"], + offset=scale_inputs["offset"], ) -params_index = [ - # UC1: exact match - (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # UC2: target value lies in the array, returns the (first) closest index - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # UC3: target value out of the range, returns the closest index - (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), -] - - -@pytest.mark.parametrize("wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", params_index) +@pytest.mark.parametrize( + "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", + [ + # UC1: exact match + (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), + # UC2: target value lies in the array, returns the (first) closest index + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), + # UC3: target value out of the range, returns the closest index + (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), + ], +) def test_get_array_index(wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index): do = DiffractionObject(wavelength=wavelength, xarray=xarray, yarray=yarray, xtype=xtype_1) actual_index = do.get_array_index(value=value, xtype=xtype_2) @@ -482,11 +487,11 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - ( # Case 1: no arguments provided + ( # C1: no arguments provided {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", ), - ( # Case 2: only xarray and yarray provided + ( # C2: only xarray and yarray provided {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 65f437ac..26887607 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,20 +9,23 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # Case 1: Allow empty arrays for q - # 1. Empty q values, no wavelength, return empty arrays + # Test conversion of q to tth with valid values + # C1. Empty q values + # 1. No wavelength, expect empty arrays (None, np.empty((0)), np.empty((0))), - # 2. Empty q values, wavelength specified, return empty arrays + # 2. 4π wavelength, expect empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # Case 2: Allow wavelength to be missing. - # Valid q values, no wavelength, return index array + + # C2. Non-empty q values + # 2. No wavelength, expect value tth values ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # Case 3: Correctly specified q and wavelength - # Expected tth values are 2*arcsin(q) in degrees + + # C3: Both valid q and wavelength provided + # 1. Expected tth values are 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) @@ -38,14 +41,15 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, q, expected_error_type", - [ - # UC1: user specified invalid q values that result in tth > 180 degrees + [ + # Test error messages in q to tth conversion with invalid Two theta values. + # C1: Invalid q values that result in tth > 180 degrees ( 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees + # C2: Wrong wavelength that results in tth > 180 degrees ( 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), @@ -62,17 +66,17 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave @pytest.mark.parametrize( "wavelength, tth, expected_q", [ - # UC0: user specified empty tth values (without wavelength) + # C0: Empty tth values (without wavelength) (None, np.array([]), np.array([])), - # UC1: user specified empty tth values (with wavelength) + # C1: Empty tth values (with wavelength) (4 * np.pi, np.array([]), np.array([])), - # UC2: user specified valid tth values between 0-180 degrees (without wavelength) + # C2: valid tth values between 0-180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC3: user specified valid tth values between 0-180 degrees (with wavelength) + # C3: valid tth values between 0-180 degrees (with wavelength) # expected q values are sin15, sin30, sin45, sin60, sin90 ( 4 * np.pi, @@ -94,14 +98,14 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) + # C1: invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) + # C2: invalid tth value of > 180 degrees with wavelength, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -119,9 +123,9 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): "q, expected_d, warning_expected", [ # Test conversion of q to d with valid values - # Case 1: empty q values, expect empty d values + # C1: empty q values, expect empty d values (np.array([]), np.array([]), False), - # Case 2: + # C2: # 1. valid q values, expect d values without warning ( np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), @@ -148,9 +152,9 @@ def test_q_to_d(q, expected_d, warning_expected): @pytest.mark.parametrize( "d, expected_q, zero_divide_error_expected", [ - # UC1: User specified empty d values + # C1: User specified empty d values (np.array([]), np.array([]), False), - # UC2: User specified valid d values + # C2: User specified valid d values ( np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), @@ -171,13 +175,13 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): "wavelength, tth, expected_d, divide_by_zero_warning_expected", [ # Test conversion of q to d with valid values - # Case 1: empty tth values, no, expect empty d values + # C1: empty tth values, no, expect empty d values (None, np.array([]), np.array([]), False), - # Case 2: empty tth values, wavelength provided, expect empty d values + # C2: empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), - # Case 3: User specified valid tth values between 0-180 degrees (without wavelength) + # C3: User specified valid tth values between 0-180 degrees (without wavelength) (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), - # Case 4: User specified valid tth values between 0-180 degrees (with wavelength) + # C4: User specified valid tth values between 0-180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), @@ -201,14 +205,14 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + # C1: user specified an invalid tth value of > 180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + # C2: user specified an invalid tth value of > 180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -225,13 +229,13 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m @pytest.mark.parametrize( "wavelength, d, expected_tth, divide_by_zero_warning_expected", [ - # UC1: Empty d values, no wavelength, return empty arrays + # C1: Empty d values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0)), False), - # UC2: Empty d values, wavelength specified, return empty arrays + # C2: Empty d values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0), False), - # UC3: User specified valid d values, no wavelength, return empty arrays + # C3: User specified valid d values, no wavelength, return empty arrays (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - # UC4: User specified valid d values (with wavelength) + # C4: User specified valid d values (with wavelength) ( 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), @@ -257,9 +261,9 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # UC1: user specified invalid d values that result in tth > 180 degrees + # C1: user specified invalid d values that result in tth > 180 degrees (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees + # C2: user specified a wrong wavelength that result in tth > 180 degrees (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) From 65d9d61066f69ada1bcb0cd1c083986b2d730d3d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:23:21 -0500 Subject: [PATCH 477/696] Add news containing no news --- news/pytest-test-refactor.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/pytest-test-refactor.rst diff --git a/news/pytest-test-refactor.rst b/news/pytest-test-refactor.rst new file mode 100644 index 00000000..08d3819b --- /dev/null +++ b/news/pytest-test-refactor.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: refactoring tests that have been mentioned in previous news + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From edd44ee29a01ac50276925015b065e0a89ce8fb2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:24:13 +0000 Subject: [PATCH 478/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 26887607..cdad5bf9 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -15,7 +15,6 @@ (None, np.empty((0)), np.empty((0))), # 2. 4π wavelength, expect empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # C2. Non-empty q values # 2. No wavelength, expect value tth values ( @@ -23,7 +22,6 @@ np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # C3: Both valid q and wavelength provided # 1. Expected tth values are 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), @@ -41,7 +39,7 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, q, expected_error_type", - [ + [ # Test error messages in q to tth conversion with invalid Two theta values. # C1: Invalid q values that result in tth > 180 degrees ( From 47750a006368b0ae57a87735c4024a51df164a1a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:33:37 -0500 Subject: [PATCH 479/696] Fix minor typo from C. to C: --- tests/test_transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index cdad5bf9..1c91e182 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -10,12 +10,12 @@ "wavelength, q, expected_tth", [ # Test conversion of q to tth with valid values - # C1. Empty q values + # C1: Empty q values # 1. No wavelength, expect empty arrays (None, np.empty((0)), np.empty((0))), # 2. 4π wavelength, expect empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # C2. Non-empty q values + # C2: Non-empty q values # 2. No wavelength, expect value tth values ( None, From b61eaca80a06c771c33eca282a632eedbc0f5113 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:35:56 -0500 Subject: [PATCH 480/696] Start comment with a cap letter --- tests/test_diffraction_objects.py | 26 +++++++++++++------------- tests/test_transforms.py | 22 +++++++++++----------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index d1d66ee6..63f349eb 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -186,7 +186,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test that scale_to() scales to the correct values - # C1: same x-array and y-array, check offset + # C1: Same x-array and y-array, check offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -208,7 +208,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # C2: same length x-arrays with exact x-value match + # C2: Same length x-arrays with exact x-value match ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -230,7 +230,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # C3: same length x-arrays with approximate x-value match + # C3: Same length x-arrays with approximate x-value match ( { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), @@ -252,7 +252,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # C4: different x-array lengths with approximate x-value match + # C4: Different x-array lengths with approximate x-value match ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -290,7 +290,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs", [ - # UC1: user did not specify anything + # UC1: User did not specify anything ( { "xarray": np.array([0.1, 0.2, 0.3]), @@ -311,7 +311,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # UC2: user specified more than one of q, tth, and d + # UC2: User specified more than one of q, tth, and d ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -352,12 +352,12 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", [ - # UC1: exact match + # UC1: Exact match (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # UC2: target value lies in the array, returns the (first) closest index + # UC2: Target value lies in the array, returns the (first) closest index (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # UC3: target value out of the range, returns the closest index + # UC3: Target value out of the range, returns the closest index (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), ], @@ -411,7 +411,7 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ - ( # instantiate just array attributes + ( # Instantiate just array attributes { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -440,7 +440,7 @@ def test_dump(tmp_path, mocker): }, True, ), - ( # instantiate just array attributes + ( # Instantiate just array attributes { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -487,11 +487,11 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - ( # C1: no arguments provided + ( # C1: No arguments provided {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", ), - ( # C2: only xarray and yarray provided + ( # C2: Only xarray and yarray provided {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 1c91e182..036e2361 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -96,14 +96,14 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError + # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: invalid tth value of > 180 degrees with wavelength, expect two theta ValueError + # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -121,16 +121,16 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): "q, expected_d, warning_expected", [ # Test conversion of q to d with valid values - # C1: empty q values, expect empty d values + # C1: Empty q values, expect empty d values (np.array([]), np.array([]), False), # C2: - # 1. valid q values, expect d values without warning + # 1. Valid q values, expect d values without warning ( np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), - # 2. valid q values containing 0, expect d values with divide by zero warning + # 2. Valid q values containing 0, expect d values with divide by zero warning ( np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), @@ -173,9 +173,9 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): "wavelength, tth, expected_d, divide_by_zero_warning_expected", [ # Test conversion of q to d with valid values - # C1: empty tth values, no, expect empty d values + # C1: Empty tth values, no, expect empty d values (None, np.array([]), np.array([]), False), - # C2: empty tth values, wavelength provided, expect empty d values + # C2: Empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), # C3: User specified valid tth values between 0-180 degrees (without wavelength) (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), @@ -203,14 +203,14 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: user specified an invalid tth value of > 180 degrees (without wavelength) + # C1: User specified an invalid tth value of > 180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: user specified an invalid tth value of > 180 degrees (with wavelength) + # C2: User specified an invalid tth value of > 180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -259,9 +259,9 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # C1: user specified invalid d values that result in tth > 180 degrees + # C1: User specified invalid d values that result in tth > 180 degrees (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # C2: user specified a wrong wavelength that result in tth > 180 degrees + # C2: User specified a wrong wavelength that result in tth > 180 degrees (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) From 65700613f8ec1bd23b2a5885012f0731935d97da Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:44:31 -0500 Subject: [PATCH 481/696] Refactor test_q_to_tth with full sentence --- tests/test_transforms.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 036e2361..f069437a 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -10,20 +10,20 @@ "wavelength, q, expected_tth", [ # Test conversion of q to tth with valid values - # C1: Empty q values - # 1. No wavelength, expect empty arrays - (None, np.empty((0)), np.empty((0))), - # 2. 4π wavelength, expect empty arrays + # Case 1: Empty q values are provided (1) with and (2) without wavelength + # 1. Expect empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # C2: Non-empty q values - # 2. No wavelength, expect value tth values + # 2. Expect empty arrays with wavelength warning message + (None, np.empty((0)), np.empty((0))), + # Case 2: Non-empty q values are provided without wavelength + # 1. Expect valid tth values in degrees. ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # C3: Both valid q and wavelength provided - # 1. Expected tth values are 2*arcsin(q) in degrees + # Case 3: Both valid q and wavelength are provided. + # 1. Expect tth values of 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) From df0f5390d0ee088d52b2c4a4d89e8cb6820ea3ea Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 22:08:29 -0500 Subject: [PATCH 482/696] Refine test comment using Expect --- tests/test_transforms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index f069437a..96c9a64c 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -10,10 +10,10 @@ "wavelength, q, expected_tth", [ # Test conversion of q to tth with valid values - # Case 1: Empty q values are provided (1) with and (2) without wavelength - # 1. Expect empty arrays + # Case 1: Allow empty arrays for q + # 1. Expect empty arrays when wavelength is provided (4 * np.pi, np.empty((0)), np.empty(0)), - # 2. Expect empty arrays with wavelength warning message + # 2. Expect empty arrays and wavelength warning when no wavelength is provided (None, np.empty((0)), np.empty((0))), # Case 2: Non-empty q values are provided without wavelength # 1. Expect valid tth values in degrees. From 9188171548821e5e1ad04bc0d9216c1face184f6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 22:27:44 -0500 Subject: [PATCH 483/696] Another round of test comment fix --- tests/test_transforms.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 96c9a64c..f0e7a14b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -10,20 +10,19 @@ "wavelength, q, expected_tth", [ # Test conversion of q to tth with valid values - # Case 1: Allow empty arrays for q - # 1. Expect empty arrays when wavelength is provided + # C1: Allow empty array q to compute tth with or without wavelength + # 1. Wavelength provided, expect empty array of tth (4 * np.pi, np.empty((0)), np.empty(0)), - # 2. Expect empty arrays and wavelength warning when no wavelength is provided + # 2. No wavelength provided, expected empty tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), - # Case 2: Non-empty q values are provided without wavelength - # 1. Expect valid tth values in degrees. + # C2: Use non-empty q values to compute tth with or without + # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # Case 3: Both valid q and wavelength are provided. - # 1. Expect tth values of 2*arcsin(q) in degrees + # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) From 704ac0fe38505794e0f2d979345c9408568c2a3d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 03:27:55 +0000 Subject: [PATCH 484/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index f0e7a14b..3acf3f3a 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -15,7 +15,7 @@ (4 * np.pi, np.empty((0)), np.empty(0)), # 2. No wavelength provided, expected empty tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), - # C2: Use non-empty q values to compute tth with or without + # C2: Use non-empty q values to compute tth with or without # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, From d5e2cfd18123ce0ef3cf3d0c187bea275793b519 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 22:36:50 -0500 Subject: [PATCH 485/696] Continue to refactor test_tth_to_q_bad --- tests/test_transforms.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index f0e7a14b..6d24baa9 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,13 +9,13 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # Test conversion of q to tth with valid values + # Test conversion of q to tth with q and wavelength # C1: Allow empty array q to compute tth with or without wavelength # 1. Wavelength provided, expect empty array of tth (4 * np.pi, np.empty((0)), np.empty(0)), - # 2. No wavelength provided, expected empty tth and wavelength UserWarning + # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), - # C2: Use non-empty q values to compute tth with or without + # C2: Use non-empty q values to compute tth with or without # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, @@ -39,14 +39,14 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, q, expected_error_type", [ - # Test error messages in q to tth conversion with invalid Two theta values. - # C1: Invalid q values that result in tth > 180 degrees + # Test ValeuError in q to tth conversion with invalid two-theta values. + # C1: Invalid q values that result in tth > 180 degrees, expect ValueError ( 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - # C2: Wrong wavelength that results in tth > 180 degrees + # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError ( 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), @@ -63,18 +63,20 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave @pytest.mark.parametrize( "wavelength, tth, expected_q", [ - # C0: Empty tth values (without wavelength) + # Test conversion of q to tth with q and wavelength + # C1: Allow empty tth values to compute 1, with or without wavelength + # 1. Wavelength provided, expect empty array of q (None, np.array([]), np.array([])), - # C1: Empty tth values (with wavelength) + # 2. No wavelength provided, expected empty array of q and wavelength UserWarning (4 * np.pi, np.array([]), np.array([])), - # C2: valid tth values between 0-180 degrees (without wavelength) + # C2: Use non-empty tth values between 0-180 degrees to compute q, with or without wavelength + # 1. No wavelength provided, expect valid q values between 0-1 ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - # C3: valid tth values between 0-180 degrees (with wavelength) - # expected q values are sin15, sin30, sin45, sin60, sin90 + # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), @@ -95,14 +97,15 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError + # C1: Invalid tth value of > 180 degrees provided, with or without wavelength + # 1. No wavelength provided, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError + # 2. Wavelength provided, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), From 17c87f0ca35029a242e3deb0b0dddf44021ec470 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 22:41:00 -0500 Subject: [PATCH 486/696] Add warning to one of the comments --- tests/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 6d24baa9..2a40805d 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -15,7 +15,7 @@ (4 * np.pi, np.empty((0)), np.empty(0)), # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), - # C2: Use non-empty q values to compute tth with or without + # C2: Use non-empty q values to compute tth with or without wavelength # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, From f064217187ece84eb10dd57e130112cf3ea19cef Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 22 Dec 2024 07:13:13 -0500 Subject: [PATCH 487/696] initial commit of tests for the config updater. I need to get the updated user_filesystem fixture so merging main next --- news/configupdate.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/tools.py | 5 ++++- tests/test_tools.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 news/configupdate.rst diff --git a/news/configupdate.rst b/news/configupdate.rst new file mode 100644 index 00000000..0bc3ffa9 --- /dev/null +++ b/news/configupdate.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news added: covered in the news from the get_user_info work + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3fc10031..98db017e 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -130,10 +130,13 @@ def get_user_info(args=None): if config_bool is False: os.remove(Path().home() / "diffpyconfig.json") config = {"username": "", "email": ""} - return config +def check_and_build_global_config(): + return + + def get_package_info(package_names, metadata=None): """ Fetches package version and updates it into (given) metadata. diff --git a/tests/test_tools.py b/tests/test_tools.py index 0a42332f..117a8565 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -5,7 +5,7 @@ import pytest -from diffpy.utils.tools import get_package_info, get_user_info +from diffpy.utils.tools import check_and_build_global_config, get_package_info, get_user_info def _setup_dirs(monkeypatch, user_filesystem): @@ -123,6 +123,36 @@ def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, exp assert confile.exists() is False +@pytest.mark.parametrize( + "test_inputs,expected", + [ # Check check_and_build_global_config() builds correct config when config is found missing + ( # C1: user inputs valid name, email and orcid + {"user_inputs": ["input_name", "input@email.com", "input_orcid"]}, + {"owner_email": "input@email.com", "owner_orcid": "input_orcid", "owner_name": "input_name"}, + ), + # ( # C2: empty strings passed in, expect uname, email, orcid from home_config + # {"owner_name": "", "owner_email": "", "owner_orcid": ""}, + # {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + # ), + ], +) +def test_check_and_build_global_config(test_inputs, expected, user_filesystem, mocker): + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] + # is tmp_dir/cwd_dir + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + # remove the config file from home that came with user_filesystem + old_confile = user_filesystem[0] / "diffpyconfig.json" + os.remove(old_confile) + check_and_build_global_config() + inp_iter = iter(test_inputs["user_inputs"]) + mocker.patch("builtins.input", lambda _: next(inp_iter)) + with open(old_confile, "r") as f: + actual = json.load(f) + print(actual) + assert actual == expected + + params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), (["package1", None], {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), From ad05e37e7c0c9707d6270ff6c1c77734aee913eb Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 22 Dec 2024 09:18:48 -0500 Subject: [PATCH 488/696] check_and_build_global_config added wih tests --- src/diffpy/utils/tools.py | 70 +++++++++++++++++++++++---------------- tests/test_tools.py | 66 ++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 62 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 1fd0ed70..59c5d31d 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -74,22 +74,6 @@ def _sorted_merge(*dicts): return merged -def _create_global_config(args): - username = input( - f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " - ).strip() or args.get("username", "") - email = input(f"Please enter the your email " f"[{args.get('email', '')}]: ").strip() or args.get("email", "") - return_bool = False if username is None or email is None else True - with open(Path().home() / "diffpyconfig.json", "w") as f: - f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) - print( - f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" - f"Or you can update the config file by passing new values to get_user_info(), " - f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html" - ) - return return_bool - - def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """ Get name, email and orcid of the owner/user from various sources and return it as a metadata dictionary @@ -107,7 +91,7 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): "owner_email": ">@email.com", "owner_orcid": ">" } - You may also store any other gloabl-level information that you would like associated with your + You may also store any other global-level information that you would like associated with your diffraction data in this file Parameters @@ -132,24 +116,52 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): del runtime_info[key] global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") - # if global_config is None and local_config is None: - # print( - # "No global configuration file was found containing " - # "information about the user to associate with the data.\n" - # "By following the prompts below you can add your name and email to this file on the current " - # "computer and your name will be automatically associated with subsequent diffpy data by default.\n" - # "This is not recommended on a shared or public computer. " - # "You will only have to do that once.\n" - # "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" - # ) user_info = global_config user_info.update(local_config) user_info.update(runtime_info) return user_info -def check_and_build_global_config(): - return +def _get_value(mystring): + return mystring.strip() + + +def check_and_build_global_config(skip_config_creation=False): + config_path = Path().home() / "diffpyconfig.json" + if skip_config_creation: + return + if config_path.is_file(): + return + intro_text = ( + "No global configuration file was found containing information about the user to " + "associate with the data.\n By following the prompts below you can add your name " + "and email to this file on the current " + "computer and your name will be automatically associated with subsequent diffpy data by default.\n" + "This is not recommended on a shared or public computer. " + "You will only have to do that once.\n" + "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" + ) + print(intro_text) + username = input("Please enter the name you would want future work to be credited to: ").strip() + email = input("Please enter your email: ").strip() + orcid = input("Please enter your orcid ID if you know it: ").strip() + config = {"owner_name": stringify(username), "owner_email": stringify(email), "owner_orcid": stringify(orcid)} + if email != "" or orcid != "" or username != "": + config["owner_orcid"] = stringify(orcid) + with open(config_path, "w") as f: + f.write(json.dumps(config)) + outro_text = ( + f"The config file at {Path().home() / 'diffpyconfig.json'} has been created. " + f"The values {config} were entered.\n" + f"These values will be inserted as metadata with your data in apps that use " + f"diffpy.get_user_info(). If you would like to update these values, either " + f"delete the config file and this workflow will rerun next time you run this " + f"program. Or you may open the config file in a text editor and manually edit the" + f"entries. For more information, see: " + f"https://diffpy.githu.io/diffpy.utils/examples/tools_example.html" + ) + print(outro_text) + return def get_package_info(package_names, metadata=None): diff --git a/tests/test_tools.py b/tests/test_tools.py index 3169f01f..88e81566 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -149,29 +149,6 @@ def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_files assert actual == expected -# @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) -# def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): -# _setup_dirs(monkeypatch, user_filesystem) -# os.remove(Path().home() / "diffpyconfig.json") -# inp_iter = iter(inputsb) -# monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) -# _run_tests(inputsa, expected) -# confile = Path().home() / "diffpyconfig.json" -# assert confile.is_file() -# -# -# @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) -# def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): -# _setup_dirs(monkeypatch, user_filesystem) -# os.remove(Path().home() / "diffpyconfig.json") -# inp_iter = iter(inputsb) -# monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) -# _run_tests(inputsa, expected) -# confile = Path().home() / "diffpyconfig.json" -# assert confile.exists() is False -# - - @pytest.mark.parametrize( "test_inputs,expected", [ # Check check_and_build_global_config() builds correct config when config is found missing @@ -179,10 +156,11 @@ def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_files {"user_inputs": ["input_name", "input@email.com", "input_orcid"]}, {"owner_email": "input@email.com", "owner_orcid": "input_orcid", "owner_name": "input_name"}, ), - # ( # C2: empty strings passed in, expect uname, email, orcid from home_config - # {"owner_name": "", "owner_email": "", "owner_orcid": ""}, - # {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, - # ), + ({"user_inputs": ["", "", ""]}, None), # C2: empty strings passed in, expect no config file created + ( # C3: just username input, expect config file but with some empty values + {"user_inputs": ["input_name", "", ""]}, + {"owner_email": "", "owner_orcid": "", "owner_name": "input_name"}, + ), ], ) def test_check_and_build_global_config(test_inputs, expected, user_filesystem, mocker): @@ -190,18 +168,40 @@ def test_check_and_build_global_config(test_inputs, expected, user_filesystem, m # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) + confile = user_filesystem[0] / "diffpyconfig.json" # remove the config file from home that came with user_filesystem - old_confile = user_filesystem[0] / "diffpyconfig.json" - os.remove(old_confile) + os.remove(confile) + mocker.patch("builtins.input", side_effect=test_inputs["user_inputs"]) check_and_build_global_config() - inp_iter = iter(test_inputs["user_inputs"]) - mocker.patch("builtins.input", lambda _: next(inp_iter)) - with open(old_confile, "r") as f: + try: + with open(confile, "r") as f: + actual = json.load(f) + except FileNotFoundError: + actual = None + assert actual == expected + + +def test_check_and_build_global_config_file_exists(user_filesystem, mocker): + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + confile = user_filesystem[0] / "diffpyconfig.json" + expected = {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"} + check_and_build_global_config() + with open(confile, "r") as f: actual = json.load(f) - print(actual) assert actual == expected +def test_check_and_build_global_config_skipped(user_filesystem, mocker): + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + confile = user_filesystem[0] / "diffpyconfig.json" + # remove the config file from home that came with user_filesystem + os.remove(confile) + check_and_build_global_config(skip_config_creation=True) + assert not confile.exists() + + params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), (["package1", None], {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), From 552dd32f8b516577e6dfd3040a26b242c56c0eed Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 22 Dec 2024 09:25:15 -0500 Subject: [PATCH 489/696] delete unneeded _get_value() function and unneeded test parms --- src/diffpy/utils/tools.py | 4 --- tests/test_tools.py | 65 --------------------------------------- 2 files changed, 69 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 59c5d31d..27e49ff8 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -122,10 +122,6 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): return user_info -def _get_value(mystring): - return mystring.strip() - - def check_and_build_global_config(skip_config_creation=False): config_path = Path().home() / "diffpyconfig.json" if skip_config_creation: diff --git a/tests/test_tools.py b/tests/test_tools.py index 88e81566..d364feb9 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -7,71 +7,6 @@ from diffpy.utils.tools import check_and_build_global_config, get_package_info, get_user_info -# def _setup_dirs(monkeypatch, user_filesystem): -# home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir -# os.chdir(cwd_dir) -# return home_dir -# - - -def _run_tests(inputs, expected): - args = {"username": inputs[0], "email": inputs[1]} - expected_username, expected_email = expected - config = get_user_info(args) - assert config.get("username") == expected_username - assert config.get("email") == expected_email - - -params_user_info_with_local_conf_file = [ - (["", ""], ["cwd_username", "cwd@email.com"]), - (["cli_username", ""], ["cli_username", "cwd@email.com"]), - (["", "cli@email.com"], ["cwd_username", "cli@email.com"]), - ([None, None], ["cwd_username", "cwd@email.com"]), - (["cli_username", None], ["cli_username", "cwd@email.com"]), - ([None, "cli@email.com"], ["cwd_username", "cli@email.com"]), - (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), -] -params_user_info_with_no_home_conf_file = [ - ( - [None, None], - ["input_username", "input@email.com"], - ["input_username", "input@email.com"], - ), - ( - ["cli_username", None], - ["", "input@email.com"], - ["cli_username", "input@email.com"], - ), - ( - [None, "cli@email.com"], - ["input_username", ""], - ["input_username", "cli@email.com"], - ), - ( - ["", ""], - ["input_username", "input@email.com"], - ["input_username", "input@email.com"], - ), - ( - ["cli_username", ""], - ["", "input@email.com"], - ["cli_username", "input@email.com"], - ), - ( - ["", "cli@email.com"], - ["input_username", ""], - ["input_username", "cli@email.com"], - ), - ( - ["cli_username", "cli@email.com"], - ["input_username", "input@email.com"], - ["cli_username", "cli@email.com"], - ), -] -params_user_info_no_conf_file_no_inputs = [ - ([None, None], ["", ""], ["", ""]), -] - @pytest.mark.parametrize( "runtime_inputs, expected", From 1af98b49dce1cd0c3e25ec82b4a756e6000c5345 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 22 Dec 2024 15:23:49 -0500 Subject: [PATCH 490/696] Fix docstring for DiffractionObject with PEP256 --- src/diffpy/utils/diffraction_objects.py | 33 ++++++++++++------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 602434f9..6870a68f 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -35,12 +35,13 @@ def _setter_wmsg(attribute): class DiffractionObject: - """ - A class to represent diffraction data for various scientific experiments involving scattering - techniques such as X-ray, neutron, and electron diffraction. This object can manage diffraction - data including transformations between different scattering quantities like q (scattering vector), - 2θ (two-theta angle), and d (interplanar spacing), and perform various operations like scaling, addition, - and subtraction of diffraction patterns. + """Class for storing and manipulating diffraction data. + + DiffractionObjct stores data produced from X-ray, neutron, and + electron scattering experiment. The object can transform between different + scattering quantities like q (scattering vector), 2θ (two-theta angle), + and d (interplanar spacing), and perform various operations like scaling, + addition, subtraction, and equality between diffraction objects. Attributes ---------- @@ -49,7 +50,7 @@ class DiffractionObject: input_xtype : str The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES} id : uuid - The unique identifier for the diffraction object. + The unique identifier for the diffraction object. scat_quantity : str The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". wavelength : float @@ -80,8 +81,7 @@ def __init__( name="", metadata={}, ): - """ - Initialize a DiffractionObject instance. + """Initialize a DiffractionObject instance. Parameters ---------- @@ -306,8 +306,8 @@ def id(self, _): raise AttributeError(_setter_wmsg("id")) def get_array_index(self, value, xtype=None): - """ - Return the index of the closest value in the array associated with the specified xtype. + """Return the index of the closest value in the array associated with + the specified xtype. Parameters ---------- @@ -370,8 +370,8 @@ def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): - """ - returns a new diffraction object which is the current object but rescaled in y to the target + """Returns a new diffraction object which is the current object but + rescaled in y to the target. The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. @@ -412,8 +412,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): return scaled def on_xtype(self, xtype): - """ - Return a list of two 1D np array with x and y data, raise an error if the specified xtype is invalid + """Return a list of two 1D np array with x and y data, raise an error + if the specified xtype is invalid. Parameters ---------- @@ -458,8 +458,7 @@ def dump(self, filepath, xtype=None): np.savetxt(f, data_to_save, delimiter=" ") def copy(self): - """ - Create a deep copy of the DiffractionObject instance. + """Create a deep copy of the DiffractionObject instance. Returns ------- From 90672eed6dce72f62d712735ce803d6c8b221253 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 22 Dec 2024 15:26:32 -0500 Subject: [PATCH 491/696] Fix small typo in diffractionobjet --- src/diffpy/utils/diffraction_objects.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6870a68f..92ad17dc 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -37,11 +37,12 @@ def _setter_wmsg(attribute): class DiffractionObject: """Class for storing and manipulating diffraction data. - DiffractionObjct stores data produced from X-ray, neutron, and - electron scattering experiment. The object can transform between different - scattering quantities like q (scattering vector), 2θ (two-theta angle), - and d (interplanar spacing), and perform various operations like scaling, - addition, subtraction, and equality between diffraction objects. + DiffractionObject stores data produced from X-ray, neutron, + and electron scattering experiments. The object can transform + between different scattering quantities such as q (scattering vector), + 2θ (two-theta angle), and d (interplanar spacing), and perform various + operations like scaling, addition, subtraction, and comparison for equality + between diffraction objects. Attributes ---------- From 77314a170362ead463e2a3bc7a8e2506d478b4b1 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 22 Dec 2024 20:27:47 -0500 Subject: [PATCH 492/696] fix typo and add examples --- doc/source/examples/tools_example.rst | 27 +++++++++++++++++++++++--- doc/source/utilities/tools_utility.rst | 9 +++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/doc/source/examples/tools_example.rst b/doc/source/examples/tools_example.rst index 60746e6f..51cb4470 100644 --- a/doc/source/examples/tools_example.rst +++ b/doc/source/examples/tools_example.rst @@ -41,7 +41,7 @@ The function will return .. code-block:: python - {"owner_email": "janedoe@email.com", "owner_name": "Jane Doe", "owner_orcid": "0000-0000-0000-0000"} + {"owner_name": "Jane Doe", "owner_email": "janedoe@email.com", "owner_orcid": "0000-0000-0000-0000"} Where does ``get_user_info()`` get the user information from? @@ -77,6 +77,7 @@ When building an application where you want to capture data-owner information, w ``check_and_build_global_config()`` first followed by ``get_user_info`` in your app workflow. E.g., .. code-block:: python + from diffpy.utils.tools import check_and_build_global_config, get_user_info from datetime import datetime import json @@ -95,12 +96,32 @@ it will only run once. However, if you want to bypass this behavior, ``check_and_build_global_config()`` takes an optional boolean ``skip_config_creation`` parameter that could be set to ``True`` at runtime to override the config creation. +What happens when you run ``check_and_build_global_config()``? +-------------------------------------------------------------- + +When you set ``skip_config_creation`` to ``False`` and there is no existing global configuration file, +the function will prompt you for inputs (name, email, ORCID). +An example of the prompts you may see is: + +.. code-block:: python + + Please enter the name you would want future work to be credited to: Jane Doe + Please enter your email: janedoe@example.com + Please enter your orcid ID if you know it: 0000-0000-0000-0000 + + +After receiving the inputs, the function will write the following to the file: + +.. code-block:: python + {"owner_name": "Jane Doe", "owner_email": "janedoe@email.com", "owner_orcid": "0000-0000-0000-0000"} + + I entered the wrong information in my config file so it always loads incorrect information, how do I fix that? -------------------------------------------------------------------------------------------------------------- It is easy to fix this simply by deleting the global and/or local config files, which will allow you to re-enter the information during the ``check_and_build_global_config()`` initialization -workflow. You can also simply editi the ``diffpyconfig.json`` file directly using a text +workflow. You can also simply edit the ``diffpyconfig.json`` file directly using a text editor. Locate the file ``diffpyconfig.json``, in your home directory and open it in an editor :: @@ -111,7 +132,7 @@ Locate the file ``diffpyconfig.json``, in your home directory and open it in an "owner_orcid": "0000-0000-4321-1234" } - Then you can edit the username and email as needed, make sure to save your edits. +Then you can edit the username and email as needed, make sure to save your edits. Automatically Capture Info about a Software Package Being Used ============================================================== diff --git a/doc/source/utilities/tools_utility.rst b/doc/source/utilities/tools_utility.rst index 01685a93..e0562faf 100644 --- a/doc/source/utilities/tools_utility.rst +++ b/doc/source/utilities/tools_utility.rst @@ -5,12 +5,17 @@ Tools Utility The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. -- ``get_user_info()``: This function is designed for managing and tracking username and email information. - +- ``get_user_info()``: This function is designed for managing and tracking user information (name, email, orcid). Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. Additionally, it saves the effort of re-entering information, and allows overriding current information by passing parameters. +- ``check_and_build_global_config()``: This function helps create a global configuration file + that can be used by ``get_user_info()``. + If no existing configuration file is found, and the user allows inputs, this function prompts for information. + The provided inputs are then saved to a global configuration file. + This file can be reused later by ``get_user_info()`` to ensure that the work credits and user information are consistently stored. + - ``get_package_info()``: This function loads package name and version information into a dictionary. It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, resulting in an entry in the passed metadata dictionary that looks like From ddf7180afdde32c7bbc9993a38eecb582ad14858 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 22 Dec 2024 20:28:24 -0500 Subject: [PATCH 493/696] fix typos --- src/diffpy/utils/tools.py | 41 ++++++--------------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 91505e6f..87d69a64 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -4,29 +4,7 @@ from pathlib import Path -def clean_dict(obj): - """ - Remove keys from the dictionary where the corresponding value is None. - - Parameters - ---------- - obj: dict - The dictionary to clean. If None, initialize as an empty dictionary. - - Returns - ------- - dict: - The cleaned dictionary with keys removed where the value is None. - - """ - obj = obj if obj is not None else {} - for key, value in copy(obj).items(): - if not value: - del obj[key] - return obj - - -def stringify(obj): +def _stringify(obj): """ Convert None to an empty string. @@ -43,7 +21,7 @@ def stringify(obj): return obj if obj is not None else "" -def load_config(file_path): +def _load_config(file_path): """ Load configuration from a .json file. @@ -67,13 +45,6 @@ def load_config(file_path): return {} -def _sorted_merge(*dicts): - merged = {} - for d in dicts: - merged.update(d) - return merged - - def _create_global_config(args): username = input( f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " @@ -81,7 +52,7 @@ def _create_global_config(args): email = input(f"Please enter the your email " f"[{args.get('email', '')}]: ").strip() or args.get("email", "") return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: - f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) + f.write(json.dumps({"username": _stringify(username), "email": _stringify(email)})) print( f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" f"Or you can update the config file by passing new values to get_user_info(), " @@ -116,7 +87,7 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): The name of the user who will show as owner in the metadata that is stored with the data owner_email: string, optional, default is the value stored in the global or local config file. The email of the user/owner - owner_name: string, optional, default is the value stored in the global or local config file. + owner_orcid: string, optional, default is the value stored in the global or local config file. The ORCID id of the user/owner Returns @@ -130,8 +101,8 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): for key, value in copy(runtime_info).items(): if value is None or value == "": del runtime_info[key] - global_config = load_config(Path().home() / "diffpyconfig.json") - local_config = load_config(Path().cwd() / "diffpyconfig.json") + global_config = _load_config(Path().home() / "diffpyconfig.json") + local_config = _load_config(Path().cwd() / "diffpyconfig.json") # if global_config is None and local_config is None: # print( # "No global configuration file was found containing " From c29053dacc307a9e3fbca8bf59679aed8ee45d1a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 23 Dec 2024 10:21:50 -0500 Subject: [PATCH 494/696] remove unnecessary function --- src/diffpy/utils/tools.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 87d69a64..a6c30ed3 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -45,22 +45,6 @@ def _load_config(file_path): return {} -def _create_global_config(args): - username = input( - f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " - ).strip() or args.get("username", "") - email = input(f"Please enter the your email " f"[{args.get('email', '')}]: ").strip() or args.get("email", "") - return_bool = False if username is None or email is None else True - with open(Path().home() / "diffpyconfig.json", "w") as f: - f.write(json.dumps({"username": _stringify(username), "email": _stringify(email)})) - print( - f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" - f"Or you can update the config file by passing new values to get_user_info(), " - f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html" - ) - return return_bool - - def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """ Get name, email and orcid of the owner/user from various sources and return it as a metadata dictionary From cc8cf059cc9dee12c9c1eb8501d4b621bb088257 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 23 Dec 2024 10:23:45 -0500 Subject: [PATCH 495/696] no news added --- news/user_info_doc.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/user_info_doc.rst diff --git a/news/user_info_doc.rst b/news/user_info_doc.rst new file mode 100644 index 00000000..43bee6bd --- /dev/null +++ b/news/user_info_doc.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news added: simply adding documentation for config update workflow + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 1a2214fc14916eef8f1737ca70bd4ff38f82980d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:03:55 -0500 Subject: [PATCH 496/696] Rename id to uuid in DiffractionObjet --- news/uuid-rename.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 14 +++++++------- tests/test_diffraction_objects.py | 24 +++++++++++++----------- 3 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 news/uuid-rename.rst diff --git a/news/uuid-rename.rst b/news/uuid-rename.rst new file mode 100644 index 00000000..ecd7f22d --- /dev/null +++ b/news/uuid-rename.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* DiffractionObject's "id" property renamed to "uuid" + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 92ad17dc..2922cdbd 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -50,7 +50,7 @@ class DiffractionObject: The array containing the quantity of q, tth, d values. input_xtype : str The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES} - id : uuid + _uuid : uuid The unique identifier for the diffraction object. scat_quantity : str The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". @@ -127,7 +127,7 @@ def __init__( >>> print(do.metadata) """ - self._id = uuid.uuid4() + self._uuid = uuid.uuid4() self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): @@ -299,12 +299,12 @@ def input_xtype(self, _): raise AttributeError(_setter_wmsg("input_xtype")) @property - def id(self): - return self._id + def uuid(self): + return self._uuid - @id.setter - def id(self, _): - raise AttributeError(_setter_wmsg("id")) + @uuid.setter + def uuid(self, _): + raise AttributeError(_setter_wmsg("uuid")) def get_array_index(self, value, xtype=None): """Return the index of the closest value in the array associated with diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 63f349eb..33675b9e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -479,7 +479,7 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec else: actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( - actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" + actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_uuid']" ) assert diff == {} @@ -523,27 +523,29 @@ def test_all_array_setter(do_minimal): do.all_arrays = np.empty((4, 4)) -def test_id_getter(do_minimal): +def test_uuid_getter(do_minimal): do = do_minimal - assert hasattr(do, "id") - assert isinstance(do.id, UUID) - assert len(str(do.id)) == 36 + assert hasattr(do, "uuid") + assert isinstance(do.uuid, UUID) + assert len(str(do.uuid)) == 36 -def test_id_getter_with_mock(mocker, do_minimal): - mocker.patch.object(DiffractionObject, "id", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87")) +def test_uuid_getter_with_mock(mocker, do_minimal): + mocker.patch.object( + DiffractionObject, "uuid", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + ) do = do_minimal - assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + assert do.uuid == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") -def test_id_setter_error(do_minimal): +def test_uuid_setter_error(do_minimal): do = do_minimal with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", + match="Direct modification of attribute 'uuid' is not allowed. Please use 'input_data' to modify 'uuid'.", ): - do.id = uuid.uuid4() + do.uuid = uuid.uuid4() def test_xarray_yarray_length_mismatch(): From 576afb2b35a5a1e79d191f1b655f9e766e633d3e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:28:16 -0500 Subject: [PATCH 497/696] No manual edits made to reformat docstrings --- .pre-commit-config.yaml | 7 +++++++ src/diffpy/__init__.py | 1 - src/diffpy/utils/__init__.py | 3 +-- src/diffpy/utils/parsers/__init__.py | 4 +--- src/diffpy/utils/parsers/serialization.py | 4 ++-- src/diffpy/utils/resampler.py | 4 ++-- src/diffpy/utils/tools.py | 20 ++++++------------ src/diffpy/utils/transforms.py | 20 +++++++----------- src/diffpy/utils/version.py | 1 - src/diffpy/utils/wx/__init__.py | 4 +--- src/diffpy/utils/wx/gridutils.py | 25 ++++++++++++----------- tests/test_loaddata.py | 9 ++++---- tests/test_version.py | 6 +++--- 13 files changed, 47 insertions(+), 61 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b7fb3631..6dca6f1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,3 +58,10 @@ repos: - id: prettier additional_dependencies: - "prettier@^3.2.4" + # docformatter - formats docstrings using PEP 257 + - repo: https://github.com/s-weigand/docformatter + rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + hooks: + - id: docformatter + additional_dependencies: [tomli] + args: [--in-place, --config, ./pyproject.toml] diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 11a4204c..00208e23 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -16,7 +16,6 @@ # See LICENSE.rst for license information. # ############################################################################## - """diffpy - tools for structure analysis by diffraction. Blank namespace package. diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index c9909a8a..12f4a49d 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -12,8 +12,7 @@ # See LICENSE.rst for license information. # ############################################################################## - -"""Shared utilities for diffpy packages""" +"""Shared utilities for diffpy packages.""" # package version from diffpy.utils.version import __version__ diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index bab9943c..a0278e27 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -12,6 +12,4 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Various utilities related to data parsing and manipulation. -""" +"""Various utilities related to data parsing and manipulation.""" diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 46d4b8ff..20b34b31 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -33,8 +33,8 @@ def serialize_data( show_path=True, serial_file=None, ): - """Serialize file data into a dictionary. Can also save dictionary into a serial language file. Dictionary is - formatted as {filename: data}. + """Serialize file data into a dictionary. Can also save dictionary into a + serial language file. Dictionary is formatted as {filename: data}. Requires hdata and data_table (can be generated by loadData). diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 21fabf56..115087a2 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Various utilities related to data parsing and manipulation.""" import warnings @@ -80,7 +79,8 @@ def wsinterp(x, xp, fp, left=None, right=None): def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): - """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon grid. + """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon + grid. Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. Uses the minimum number of points N required by the Nyquist sampling theorem. diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 91505e6f..8b021ad1 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -5,8 +5,7 @@ def clean_dict(obj): - """ - Remove keys from the dictionary where the corresponding value is None. + """Remove keys from the dictionary where the corresponding value is None. Parameters ---------- @@ -17,7 +16,6 @@ def clean_dict(obj): ------- dict: The cleaned dictionary with keys removed where the value is None. - """ obj = obj if obj is not None else {} for key, value in copy(obj).items(): @@ -27,8 +25,7 @@ def clean_dict(obj): def stringify(obj): - """ - Convert None to an empty string. + """Convert None to an empty string. Parameters ---------- @@ -44,8 +41,7 @@ def stringify(obj): def load_config(file_path): - """ - Load configuration from a .json file. + """Load configuration from a .json file. Parameters ---------- @@ -56,7 +52,6 @@ def load_config(file_path): ------- dict: The configuration dictionary or {} if the config file does not exist. - """ config_file = Path(file_path).resolve() if config_file.is_file(): @@ -91,8 +86,8 @@ def _create_global_config(args): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """ - Get name, email and orcid of the owner/user from various sources and return it as a metadata dictionary + """Get name, email and orcid of the owner/user from various sources and + return it as a metadata dictionary. The function looks for the information in json format configuration files with the name 'diffpyconfig.json'. These can be in the user's home directory and in the current working directory. The information in the @@ -124,7 +119,6 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): dict: The dictionary containing username, email and orcid of the user/owner, and any other information stored in the global or local config files. - """ runtime_info = {"owner_name": owner_name, "owner_email": owner_email, "owner_orcid": owner_orcid} for key, value in copy(runtime_info).items(): @@ -149,8 +143,7 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def get_package_info(package_names, metadata=None): - """ - Fetches package version and updates it into (given) metadata. + """Fetches package version and updates it into (given) metadata. Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. @@ -164,7 +157,6 @@ def get_package_info(package_names, metadata=None): ------- dict: The updated metadata dict with package info inserted. - """ if metadata is None: metadata = {} diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 1d50aeab..f2956879 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -29,8 +29,7 @@ def _validate_inputs(q, wavelength): def q_to_tth(q, wavelength): - r""" - Helper function to convert q to two-theta. + r"""Helper function to convert q to two-theta. If wavelength is missing, returns x-values that are integer indexes @@ -72,9 +71,7 @@ def q_to_tth(q, wavelength): def tth_to_q(tth, wavelength): - r""" - - Helper function to convert two-theta to q on independent variable axis. + r"""Helper function to convert two-theta to q on independent variable axis. If wavelength is missing, returns independent variable axis as integer indexes. @@ -120,8 +117,8 @@ def tth_to_q(tth, wavelength): def q_to_d(q): - r""" - Helper function to convert q to d on independent variable axis, using :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d on independent variable axis, using + :math:`d = \frac{2 \pi}{q}`. Parameters ---------- @@ -140,8 +137,7 @@ def q_to_d(q): def tth_to_d(tth, wavelength): - r""" - Helper function to convert two-theta to d on independent variable axis. + r"""Helper function to convert two-theta to d on independent variable axis. The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. @@ -174,8 +170,7 @@ def tth_to_d(tth, wavelength): def d_to_q(d): - r""" - Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. Parameters ---------- @@ -194,8 +189,7 @@ def d_to_q(d): def d_to_tth(d, wavelength): - r""" - Helper function to convert d to two-theta on independent variable axis. + r"""Helper function to convert d to two-theta on independent variable axis. The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index 0bc397e4..e74c47bd 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -12,7 +12,6 @@ # See LICENSE.rst for license information. # ############################################################################## - """Definition of __version__.""" # We do not use the other three variables, but can be added back if needed. diff --git a/src/diffpy/utils/wx/__init__.py b/src/diffpy/utils/wx/__init__.py index 3f7417ef..e2f08735 100644 --- a/src/diffpy/utils/wx/__init__.py +++ b/src/diffpy/utils/wx/__init__.py @@ -12,6 +12,4 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Utilities related wx Python GUIs. -""" +"""Utilities related wx Python GUIs.""" diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index c459ac30..a01e8f92 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -12,9 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Common functions for manipulating wx.grid.Grid. -""" +"""Common functions for manipulating wx.grid.Grid.""" import wx @@ -53,7 +51,9 @@ def getSelectionColumns(grid): def getSelectedCells(grid): """Get list of (row, col) pairs of all selected cells. - Unlike grid.GetSelectedCells this returns them all no matter how they were selected. + + Unlike grid.GetSelectedCells this returns them all no matter how + they were selected. """ rows = grid.GetNumberRows() cols = grid.GetNumberCols() @@ -75,8 +75,8 @@ def getSelectedCells(grid): def limitSelectionToRows(grid, indices): - """Limit selection to the specified row indices. - No action for empty indices. + """Limit selection to the specified row indices. No action for empty + indices. Parameters ---------- @@ -112,10 +112,10 @@ def limitSelectionToRows(grid, indices): def quickResizeColumns(grid, indices): """Resize the columns that were recently affected by cell changes. - This is faster than the normal grid AutoSizeColumns, since the latter loops - over the entire grid. In addition, this will not cause a - EVT_GRID_CMD_CELL_CHANGE event to be thrown, which can cause recursion. - This method will only increase column size. + This is faster than the normal grid AutoSizeColumns, since the + latter loops over the entire grid. In addition, this will not cause + a EVT_GRID_CMD_CELL_CHANGE event to be thrown, which can cause + recursion. This method will only increase column size. """ # Get the columns and maximum text width in each one dc = wx.ScreenDC() @@ -140,8 +140,9 @@ def quickResizeColumns(grid, indices): def _indicesToBlocks(indices): - """Convert a list of integer indices to a list of (start, stop) tuples. - The (start, stop) tuple defines a continuous block, where the stop index is included in the block. + """Convert a list of integer indices to a list of (start, stop) tuples. The + (start, stop) tuple defines a continuous block, where the stop index is + included in the block. Parameters ---------- diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index a7e273e7..f825139c 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -"""Unit tests for diffpy.utils.parsers.loaddata -""" +"""Unit tests for diffpy.utils.parsers.loaddata.""" import numpy as np import pytest @@ -10,7 +9,7 @@ def test_loadData_default(datafile): - """check loadData() with default options""" + """Check loadData() with default options.""" loaddata01 = datafile("loaddata01.txt") d2c = np.array([[3, 31], [4, 32], [5, 33]]) @@ -38,7 +37,7 @@ def test_loadData_default(datafile): def test_loadData_1column(datafile): - """check loading of one-column data.""" + """Check loading of one-column data.""" loaddata01 = datafile("loaddata01.txt") d1c = np.arange(1, 6) @@ -54,7 +53,7 @@ def test_loadData_1column(datafile): def test_loadData_headers(datafile): - """check loadData() with headers options enabled""" + """Check loadData() with headers options enabled.""" expected = { "wavelength": 0.1, "dataformat": "Qnm", diff --git a/tests/test_version.py b/tests/test_version.py index 421a96e4..4152a197 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,10 +1,10 @@ -"""Unit tests for __version__.py -""" +"""Unit tests for __version__.py.""" import diffpy.utils def test_package_version(): - """Ensure the package version is defined and not set to the initial placeholder.""" + """Ensure the package version is defined and not set to the initial + placeholder.""" assert hasattr(diffpy.utils, "__version__") assert diffpy.utils.__version__ != "0.0.0" From 660f8a1987298c19f001c5c2e265566aef9f4f3a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:31:15 -0500 Subject: [PATCH 498/696] Add news for docformatt --- news/docformatter.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/docformatter.rst diff --git a/news/docformatter.rst b/news/docformatter.rst new file mode 100644 index 00000000..56368125 --- /dev/null +++ b/news/docformatter.rst @@ -0,0 +1,23 @@ +**Added:** + +* docforamtter in pre-commit for automatic formatting of docstrings to PEP 257 + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From c39b301488ba3f912a6b4a9a1a4817a4a9896e51 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:50:24 -0500 Subject: [PATCH 499/696] Refactor test_on_xtype --- tests/test_diffraction_objects.py | 35 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 63f349eb..8c739a2f 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -12,10 +12,10 @@ @pytest.mark.parametrize( - "do_args_1, do_args_2, expected_equality, warning_expected", + "do_args_1, do_args_2, expected_equality, wavelength_warning_expected", [ # Test when __eq__ returns True and False - # Identical args, expect equality + # C1: Identical args, expect equality ( { "name": "same", @@ -56,7 +56,7 @@ False, True, ), - ( # One without wavelength, expect inequality + ( # C2: One without wavelength, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -73,7 +73,7 @@ False, True, ), - ( # Different wavelengths, expect inequality + ( # C3: Different wavelength values, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -91,7 +91,7 @@ False, False, ), - ( # Different scat_quantity, expect inequality + ( # C4: Different scat_quantity, expect inequality { "scat_quantity": "x-ray", "xtype": "tth", @@ -109,7 +109,7 @@ False, True, ), - ( # Different q xarray values, expect inequality + ( # C5: Different q xarray values, expect inequality { "xtype": "q", "xarray": np.array([1.0, 2.0]), @@ -124,7 +124,7 @@ False, True, ), - ( # Different metadata, expect inequality + ( # C6: Different metadata, expect inequality { "xtype": "q", "xarray": np.empty(0), @@ -143,9 +143,9 @@ ], ) def test_diffraction_objects_equality( - do_args_1, do_args_2, expected_equality, warning_expected, wavelength_warning_msg + do_args_1, do_args_2, expected_equality, wavelength_warning_expected, wavelength_warning_msg ): - if warning_expected: + if wavelength_warning_expected: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): do_1 = DiffractionObject(**do_args_1) do_2 = DiffractionObject(**do_args_2) @@ -158,9 +158,16 @@ def test_diffraction_objects_equality( @pytest.mark.parametrize( "xtype, expected_xarray", [ + # Test whether on_xtype returns the correct xarray values. + # The test DO instance is initialized with tth. + # C1: tth to tth, expect no change in xaray value + # 1. "tth" provided, expect tth + # 2. "2theta" provided, expect tth ("tth", np.array([30, 60])), ("2theta", np.array([30, 60])), + # C2: "q" provided, expect q ("q", np.array([0.51764, 1])), + # C3: "d" provided, expect d ("d", np.array([12.13818, 6.28319])), ], ) @@ -290,7 +297,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs", [ - # UC1: User did not specify anything + # C1: User did not specify anything ( { "xarray": np.array([0.1, 0.2, 0.3]), @@ -311,7 +318,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # UC2: User specified more than one of q, tth, and d + # C2: User specified more than one of q, tth, and d ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -352,12 +359,12 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", [ - # UC1: Exact match + # U1: Exact match (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # UC2: Target value lies in the array, returns the (first) closest index + # U2: Target value lies in the array, returns the (first) closest index (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # UC3: Target value out of the range, returns the closest index + # U3: Target value out of the range, returns the closest index (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), ], From 697e7996fcd61748455df06de14ca75df3ad16de Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:55:51 -0500 Subject: [PATCH 500/696] Refactor test_scale_to() with improved test cases --- tests/test_diffraction_objects.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8c739a2f..65cdc6ad 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -165,9 +165,9 @@ def test_diffraction_objects_equality( # 2. "2theta" provided, expect tth ("tth", np.array([30, 60])), ("2theta", np.array([30, 60])), - # C2: "q" provided, expect q + # C2: "q" provided, expect q converted from tth ("q", np.array([0.51764, 1])), - # C3: "d" provided, expect d + # C3: "d" provided, expect d converted from tth ("d", np.array([12.13818, 6.28319])), ], ) @@ -192,7 +192,7 @@ def test_init_invalid_xtype(): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs, expected", [ - # Test that scale_to() scales to the correct values + # Test whether scale_to() scales to the expected values # C1: Same x-array and y-array, check offset ( { @@ -297,7 +297,8 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs", [ - # C1: User did not specify anything + # Test expected errors produced from scale_to() with invalid inputs + # C1: none of q, tth, d, provided, expect ValueError ( { "xarray": np.array([0.1, 0.2, 0.3]), @@ -318,7 +319,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # C2: User specified more than one of q, tth, and d + # C2: more than one of either q, tth, d, provided, expect ValueError ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -359,12 +360,13 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", [ - # U1: Exact match + # Test whether get_array_index() returns the expected index + # C1: Exact match (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # U2: Target value lies in the array, returns the (first) closest index + # C2: Target value lies in the array, returns the (first) closest index (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # U3: Target value out of the range, returns the closest index + # C3: Target value out of the range, returns the closest index (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), ], From 13742d0ada437cd21e207e1df96048feb97a7945 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 23 Dec 2024 15:11:52 -0500 Subject: [PATCH 501/696] edit docs --- doc/source/examples/tools_example.rst | 6 ++---- doc/source/utilities/tools_utility.rst | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/source/examples/tools_example.rst b/doc/source/examples/tools_example.rst index 51cb4470..268c26dd 100644 --- a/doc/source/examples/tools_example.rst +++ b/doc/source/examples/tools_example.rst @@ -110,10 +110,8 @@ An example of the prompts you may see is: Please enter your orcid ID if you know it: 0000-0000-0000-0000 -After receiving the inputs, the function will write the following to the file: - -.. code-block:: python - {"owner_name": "Jane Doe", "owner_email": "janedoe@email.com", "owner_orcid": "0000-0000-0000-0000"} +After receiving the inputs, the function will write the information to +the `diffpyconfig.json` file in your home directory. I entered the wrong information in my config file so it always loads incorrect information, how do I fix that? diff --git a/doc/source/utilities/tools_utility.rst b/doc/source/utilities/tools_utility.rst index e0562faf..e524a65b 100644 --- a/doc/source/utilities/tools_utility.rst +++ b/doc/source/utilities/tools_utility.rst @@ -11,8 +11,8 @@ The ``diffpy.utils.tools`` module provides tool functions for use with diffpy ap passing parameters. - ``check_and_build_global_config()``: This function helps create a global configuration file - that can be used by ``get_user_info()``. - If no existing configuration file is found, and the user allows inputs, this function prompts for information. + that can be used by, for example, ``get_user_info()``. + If no existing configuration file is found, this function prompts for information. The provided inputs are then saved to a global configuration file. This file can be reused later by ``get_user_info()`` to ensure that the work credits and user information are consistently stored. From 7543305c0a1cf8a13e9d7ed2e1b35cfb917180a5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:19:25 -0500 Subject: [PATCH 502/696] Refactor test.parameter test cases using BG standard --- tests/test_diffraction_objects.py | 119 ++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 65cdc6ad..71c11d4a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -38,7 +38,8 @@ True, False, ), - ( # Different names, expect inequality + # Different names, expect inequality + ( { "name": "something", "xtype": "tth", @@ -56,7 +57,8 @@ False, True, ), - ( # C2: One without wavelength, expect inequality + # C2: One without wavelength, expect inequality + ( { "wavelength": 0.71, "xtype": "tth", @@ -73,7 +75,8 @@ False, True, ), - ( # C3: Different wavelength values, expect inequality + # C3: Different wavelength values, expect inequality + ( { "wavelength": 0.71, "xtype": "tth", @@ -91,7 +94,8 @@ False, False, ), - ( # C4: Different scat_quantity, expect inequality + # C4: Different scat_quantity, expect inequality + ( { "scat_quantity": "x-ray", "xtype": "tth", @@ -109,7 +113,8 @@ False, True, ), - ( # C5: Different q xarray values, expect inequality + # C5: Different q xarray values, expect inequality + ( { "xtype": "q", "xarray": np.array([1.0, 2.0]), @@ -124,7 +129,8 @@ False, True, ), - ( # C6: Different metadata, expect inequality + # C6: Different metadata, expect inequality + ( { "xtype": "q", "xarray": np.empty(0), @@ -358,22 +364,84 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( - "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", + "do_args, get_array_index_inputs, expected_index", [ - # Test whether get_array_index() returns the expected index - # C1: Exact match - (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # C2: Target value lies in the array, returns the (first) closest index - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # C3: Target value out of the range, returns the closest index - (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), + # Test get_array_index() returns the expected index given xtype and value + # C1: Target value is in the xarray and xtype is identical, expect exact index match + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([30.005, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": "tth", + "value": 30.005, + }, + [0], + ), + # C2: Target value lies in the array, expect the (first) closest index + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([30, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": "tth", + "value": 45, + }, + [0], + ), + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([30, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": "q", + "value": 0.25, + }, + [0], + ), + # C3: Target value out of the range, expect the closest index + # 1. Test with xtype of "q" + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([0.25, 0.5, 0.71]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + }, + { + "xtype": "q", + "value": 0.1, + }, + [0], + ), + # 2. Test with xtype of "tth" + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([30, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": "tth", + "value": 63, + }, + [1], + ), ], ) -def test_get_array_index(wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index): - do = DiffractionObject(wavelength=wavelength, xarray=xarray, yarray=yarray, xtype=xtype_1) - actual_index = do.get_array_index(value=value, xtype=xtype_2) +def test_get_array_index(do_args, get_array_index_inputs, expected_index): + do = DiffractionObject(**do_args) + actual_index = do.get_array_index(get_array_index_inputs["value"], get_array_index_inputs["xtype"]) assert actual_index == expected_index @@ -420,7 +488,9 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ - ( # Instantiate just array attributes + # Test __dict__ of DiffractionObject instance initialized with valid arguments + ( + # C1: Minimum arguments provided for init, expect all attributes set without None { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -449,7 +519,8 @@ def test_dump(tmp_path, mocker): }, True, ), - ( # Instantiate just array attributes + # C2: Initialize with an optional scat_quantity argument, expect non-empty string for scat_quantity + ( { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -496,11 +567,12 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - ( # C1: No arguments provided + # Test expected error messages due to required arguments in DiffractionObject constructor + ( # C1: No arguments provided, expect 3 required positional arguments error {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", ), - ( # C2: Only xarray and yarray provided + ( # C2: Only xarray and yarray provided, expect 1 required positional argument error {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), @@ -523,7 +595,6 @@ def test_all_array_getter(do_minimal_tth): def test_all_array_setter(do_minimal): do = do_minimal - # Attempt to directly modify the property with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed. " From 625b69b3eb9deba8f32072f739d0aeb14ec0c104 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:28:33 -0500 Subject: [PATCH 503/696] Enhance last bit of comment --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 71c11d4a..91b7f603 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -567,7 +567,7 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - # Test expected error messages due to required arguments in DiffractionObject constructor + # Test expected error messages when 3 required arguments not provided in DiffractionObject init ( # C1: No arguments provided, expect 3 required positional arguments error {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", From 406175d6313d2750dfd93ea100a703cdc7786bb1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:36:40 -0500 Subject: [PATCH 504/696] Add news and scale to need to be more refined --- news/pytest-comment.rst | 23 +++++++++++++++++++++++ tests/test_diffraction_objects.py | 3 +-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 news/pytest-comment.rst diff --git a/news/pytest-comment.rst b/news/pytest-comment.rst new file mode 100644 index 00000000..9d0f81bb --- /dev/null +++ b/news/pytest-comment.rst @@ -0,0 +1,23 @@ +**Added:** + +* Group's Pytest practices for using @pytest.mark.parametrize in test_diffraction_objects.py + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 91b7f603..39aa0d47 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -165,7 +165,6 @@ def test_diffraction_objects_equality( "xtype, expected_xarray", [ # Test whether on_xtype returns the correct xarray values. - # The test DO instance is initialized with tth. # C1: tth to tth, expect no change in xaray value # 1. "tth" provided, expect tth # 2. "2theta" provided, expect tth @@ -199,7 +198,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether scale_to() scales to the expected values - # C1: Same x-array and y-array, check offset + # C1: Same x-array and y-array with 2.1 offset, expect yarray shifted by 2.1 offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), From be0dd64e81d4f2fbdeda448c510dd7322cfcb17e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:37:20 -0500 Subject: [PATCH 505/696] Fix small typo in aray --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 39aa0d47..c3802b63 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -165,7 +165,7 @@ def test_diffraction_objects_equality( "xtype, expected_xarray", [ # Test whether on_xtype returns the correct xarray values. - # C1: tth to tth, expect no change in xaray value + # C1: tth to tth, expect no change in xarray value # 1. "tth" provided, expect tth # 2. "2theta" provided, expect tth ("tth", np.array([30, 60])), From 66e24bf636ac0383c3a2f36d7f2c258266bad989 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:42:25 -0500 Subject: [PATCH 506/696] improve C2 under test_scale_to_bad --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c3802b63..46b23173 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -324,7 +324,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # C2: more than one of either q, tth, d, provided, expect ValueError + # C2: tth and d both provided, expect ValueError ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), From 499a1c37930073c11d2b6fc2f2e6ef17ac0bd06d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:42:54 -0500 Subject: [PATCH 507/696] improve clarify for test_scale_to --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 46b23173..6fa843d8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -324,7 +324,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # C2: tth and d both provided, expect ValueError + # C2: tth and d both provided, expect ValueErrort ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), From 1d64d28fabc44a5bf72d1ef0a7842c23203d0c31 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:57:42 -0500 Subject: [PATCH 508/696] Refactor test_transform.py --- news/pytest-reformat-transform.rst | 23 +++++++++++++++++++++++ tests/test_transforms.py | 18 ++++++++---------- 2 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 news/pytest-reformat-transform.rst diff --git a/news/pytest-reformat-transform.rst b/news/pytest-reformat-transform.rst new file mode 100644 index 00000000..2bd19e87 --- /dev/null +++ b/news/pytest-reformat-transform.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: previous PR already contains has an news item for refactoring test_transform.py + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 2a40805d..ff202384 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -32,7 +32,6 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) - assert np.allclose(expected_tth, actual_tth) @@ -205,14 +204,14 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: User specified an invalid tth value of > 180 degrees (without wavelength) + # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: User specified an invalid tth value of > 180 degrees (with wavelength) + # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -229,13 +228,13 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m @pytest.mark.parametrize( "wavelength, d, expected_tth, divide_by_zero_warning_expected", [ - # C1: Empty d values, no wavelength, return empty arrays + # C1: Empty d values, no wavelength, expect empty tth values (None, np.empty((0)), np.empty((0)), False), - # C2: Empty d values, wavelength specified, return empty arrays + # C2: Empty d values with wavelength, expect empty tth values (4 * np.pi, np.empty((0)), np.empty(0), False), - # C3: User specified valid d values, no wavelength, return empty arrays + # C3: Valid d values, no wavelength, expect valid and non-empty tth values (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - # C4: User specified valid d values (with wavelength) + # C4: Valid d values with wavelength, expect valid and non-empty thh values ( 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), @@ -254,16 +253,15 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, actual_tth = d_to_tth(d, wavelength) else: actual_tth = d_to_tth(d, wavelength) - assert np.allclose(actual_tth, expected_tth) @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # C1: User specified invalid d values that result in tth > 180 degrees + # C1: Invalid d values that result in tth > 180 degrees, expect invalid q, d, or wavelength ValueError (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # C2: User specified a wrong wavelength that result in tth > 180 degrees + # C2: Wrong wavelength that result in tth > 180 degreesm, expect invalid q, d, or wavelength ValueError (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) From f498a0740d26627175ffa8c073311ca3db0c2969 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 25 Dec 2024 07:18:22 -0500 Subject: [PATCH 509/696] docstring for create global config and have it return a bool --- src/diffpy/utils/tools.py | 40 ++++++++++++++++++++++++++++++++++----- tests/test_tools.py | 11 ++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 27e49ff8..e7d44d12 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -123,11 +123,40 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def check_and_build_global_config(skip_config_creation=False): + """ + Checks for a global diffpu config file in user's home directory and creates one if it is missing + + The file it looks for is called diffpyconfig.json. This can contain anything in json format, but + minimally contains information about the computer owner. The information is used + when diffpy objects are created and saved to files or databases to retain ownership information + of datasets. For example, it is used by diffpy.utils.tools.get_user_info(). + + If the function finds no config file in the user's home directory it interrupts execution + and prompts the user for name, email, and orcid information. It then creates the config file + with this information inside it. + + The function returns True if the file exists and False otherwise. + + If you would like to check for a file but not run the file creation workflow you can set + the optional argument skip_config_creation to True. + + Parameters + ---------- + skip_config_creation: bool, optional, Default is False + The bool that will override the creation workflow even if no config file exists. + + Returns + ------- + bool: True if the file exists and False otherwise. + + """ + config_exists = False config_path = Path().home() / "diffpyconfig.json" - if skip_config_creation: - return if config_path.is_file(): - return + config_exists = True + return config_exists + if skip_config_creation: + return config_exists intro_text = ( "No global configuration file was found containing information about the user to " "associate with the data.\n By following the prompts below you can add your name " @@ -154,10 +183,11 @@ def check_and_build_global_config(skip_config_creation=False): f"delete the config file and this workflow will rerun next time you run this " f"program. Or you may open the config file in a text editor and manually edit the" f"entries. For more information, see: " - f"https://diffpy.githu.io/diffpy.utils/examples/tools_example.html" + f"https://diffpy.github.io/diffpy.utils/examples/tools_example.html" ) print(outro_text) - return + config_exists = True + return config_exists def get_package_info(package_names, metadata=None): diff --git a/tests/test_tools.py b/tests/test_tools.py index d364feb9..3808537d 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -107,13 +107,16 @@ def test_check_and_build_global_config(test_inputs, expected, user_filesystem, m # remove the config file from home that came with user_filesystem os.remove(confile) mocker.patch("builtins.input", side_effect=test_inputs["user_inputs"]) - check_and_build_global_config() + actual_bool = check_and_build_global_config() try: with open(confile, "r") as f: actual = json.load(f) + expected_bool = True except FileNotFoundError: + expected_bool = False actual = None assert actual == expected + assert actual_bool == expected_bool def test_check_and_build_global_config_file_exists(user_filesystem, mocker): @@ -121,7 +124,8 @@ def test_check_and_build_global_config_file_exists(user_filesystem, mocker): os.chdir(user_filesystem[1]) confile = user_filesystem[0] / "diffpyconfig.json" expected = {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"} - check_and_build_global_config() + actual_bool = check_and_build_global_config() + assert actual_bool is True with open(confile, "r") as f: actual = json.load(f) assert actual == expected @@ -133,7 +137,8 @@ def test_check_and_build_global_config_skipped(user_filesystem, mocker): confile = user_filesystem[0] / "diffpyconfig.json" # remove the config file from home that came with user_filesystem os.remove(confile) - check_and_build_global_config(skip_config_creation=True) + actual_bool = check_and_build_global_config(skip_config_creation=True) + assert actual_bool is False assert not confile.exists() From 7b99d258409ab4c21e64235a1fc822ecedfd7c95 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 25 Dec 2024 08:26:19 -0500 Subject: [PATCH 510/696] update docs with new return value for create_config --- doc/source/examples/tools_example.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/examples/tools_example.rst b/doc/source/examples/tools_example.rst index 60746e6f..27de73b5 100644 --- a/doc/source/examples/tools_example.rst +++ b/doc/source/examples/tools_example.rst @@ -95,6 +95,10 @@ it will only run once. However, if you want to bypass this behavior, ``check_and_build_global_config()`` takes an optional boolean ``skip_config_creation`` parameter that could be set to ``True`` at runtime to override the config creation. +``check_and_build_global_config()`` returns ``True`` if the config file exists (whether it created it or not) +and ``False`` if the config file does not exist in the user's home allowing you to develop your own +workflow for handling missing config files after running it with ``skip_config_creation=True``. + I entered the wrong information in my config file so it always loads incorrect information, how do I fix that? -------------------------------------------------------------------------------------------------------------- From 7688cb4094eaf4ae106d031012b0b582525a86cb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 12:05:26 -0500 Subject: [PATCH 511/696] Use compact test comment style for each case under @pytest.mark.parametrize --- news/test-func-format-compact.rst | 23 ++++++++++++ tests/test_diffraction_objects.py | 59 +++++++++++-------------------- tests/test_transforms.py | 46 ++++++++++-------------- 3 files changed, 61 insertions(+), 67 deletions(-) create mode 100644 news/test-func-format-compact.rst diff --git a/news/test-func-format-compact.rst b/news/test-func-format-compact.rst new file mode 100644 index 00000000..ecb92ac0 --- /dev/null +++ b/news/test-func-format-compact.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* test comment format with compact style without extra line for each comment + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6fa843d8..9dee757d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -15,8 +15,7 @@ "do_args_1, do_args_2, expected_equality, wavelength_warning_expected", [ # Test when __eq__ returns True and False - # C1: Identical args, expect equality - ( + ( # C1: Identical args, expect equality { "name": "same", "scat_quantity": "x-ray", @@ -38,8 +37,7 @@ True, False, ), - # Different names, expect inequality - ( + ( # C2: Different names, expect inequality { "name": "something", "xtype": "tth", @@ -57,8 +55,7 @@ False, True, ), - # C2: One without wavelength, expect inequality - ( + ( # C3: One without wavelength, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -75,8 +72,7 @@ False, True, ), - # C3: Different wavelength values, expect inequality - ( + ( # C4: Different wavelength values, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -94,8 +90,7 @@ False, False, ), - # C4: Different scat_quantity, expect inequality - ( + ( # C5: Different scat_quantity, expect inequality { "scat_quantity": "x-ray", "xtype": "tth", @@ -113,8 +108,7 @@ False, True, ), - # C5: Different q xarray values, expect inequality - ( + ( # C6: Different q xarray values, expect inequality { "xtype": "q", "xarray": np.array([1.0, 2.0]), @@ -129,8 +123,7 @@ False, True, ), - # C6: Different metadata, expect inequality - ( + ( # C7: Different metadata, expect inequality { "xtype": "q", "xarray": np.empty(0), @@ -167,8 +160,8 @@ def test_diffraction_objects_equality( # Test whether on_xtype returns the correct xarray values. # C1: tth to tth, expect no change in xarray value # 1. "tth" provided, expect tth - # 2. "2theta" provided, expect tth ("tth", np.array([30, 60])), + # 2. "2theta" provided, expect tth ("2theta", np.array([30, 60])), # C2: "q" provided, expect q converted from tth ("q", np.array([0.51764, 1])), @@ -198,8 +191,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether scale_to() scales to the expected values - # C1: Same x-array and y-array with 2.1 offset, expect yarray shifted by 2.1 offset - ( + ( # C1: Same x-array and y-array with 2.1 offset, expect yarray shifted by 2.1 offset { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([2, 3, 4, 5, 6, 7]), @@ -220,8 +212,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # C2: Same length x-arrays with exact x-value match - ( + ( # C2: Same length x-arrays with exact x-value match { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([10, 20, 25, 30, 60, 100]), @@ -242,8 +233,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # C3: Same length x-arrays with approximate x-value match - ( + ( # C3: Same length x-arrays with approximate x-value match { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), "yarray": np.array([10, 20, 40, 60]), @@ -264,8 +254,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # C4: Different x-array lengths with approximate x-value match - ( + ( # C4: Different x-array lengths with approximate x-value match { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -303,8 +292,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "org_do_args, target_do_args, scale_inputs", [ # Test expected errors produced from scale_to() with invalid inputs - # C1: none of q, tth, d, provided, expect ValueError - ( + ( # C1: none of q, tth, d, provided, expect ValueError { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), @@ -324,8 +312,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # C2: tth and d both provided, expect ValueErrort - ( + ( # C2: tth and d both provided, expect ValueErrort { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -366,8 +353,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "do_args, get_array_index_inputs, expected_index", [ # Test get_array_index() returns the expected index given xtype and value - # C1: Target value is in the xarray and xtype is identical, expect exact index match - ( + ( # C1: Target value is in the xarray and xtype is identical, expect exact index match { "wavelength": 4 * np.pi, "xarray": np.array([30.005, 60]), @@ -380,8 +366,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, [0], ), - # C2: Target value lies in the array, expect the (first) closest index - ( + ( # C2: Target value lies in the array, expect the (first) closest index { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -408,8 +393,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): [0], ), # C3: Target value out of the range, expect the closest index - # 1. Test with xtype of "q" - ( + ( # 1. Test with xtype of "q" { "wavelength": 4 * np.pi, "xarray": np.array([0.25, 0.5, 0.71]), @@ -422,8 +406,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, [0], ), - # 2. Test with xtype of "tth" - ( + ( # 2. Test with xtype of "tth" { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -488,8 +471,7 @@ def test_dump(tmp_path, mocker): "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ # Test __dict__ of DiffractionObject instance initialized with valid arguments - ( - # C1: Minimum arguments provided for init, expect all attributes set without None + ( # C1: Minimum arguments provided for init, expect all attributes set without None { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -518,8 +500,7 @@ def test_dump(tmp_path, mocker): }, True, ), - # C2: Initialize with an optional scat_quantity argument, expect non-empty string for scat_quantity - ( + ( # C2: Initialize with an optional scat_quantity argument, expect non-empty string for scat_quantity { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), diff --git a/tests/test_transforms.py b/tests/test_transforms.py index ff202384..60f87c2e 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -16,14 +16,16 @@ # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), # C2: Use non-empty q values to compute tth with or without wavelength - # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning - ( + ( # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees - (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), + ( # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees + 4 * np.pi, + np.array([0, 1 / np.sqrt(2), 1.0]), + np.array([0, 90.0, 180.0]), + ), ], ) def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @@ -39,14 +41,12 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): "wavelength, q, expected_error_type", [ # Test ValeuError in q to tth conversion with invalid two-theta values. - # C1: Invalid q values that result in tth > 180 degrees, expect ValueError - ( + ( # C1: Invalid q values that result in tth > 180 degrees, expect ValueError 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError - ( + ( # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), ValueError, @@ -69,14 +69,12 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave # 2. No wavelength provided, expected empty array of q and wavelength UserWarning (4 * np.pi, np.array([]), np.array([])), # C2: Use non-empty tth values between 0-180 degrees to compute q, with or without wavelength - # 1. No wavelength provided, expect valid q values between 0-1 - ( + ( # 1. No wavelength provided, expect valid q values between 0-1 None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 - ( + ( # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), @@ -97,15 +95,13 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): "wavelength, tth, expected_error_type, expected_error_msg", [ # C1: Invalid tth value of > 180 degrees provided, with or without wavelength - # 1. No wavelength provided, expect two theta ValueError - ( + ( # 1. No wavelength provided, expect two theta ValueError None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # 2. Wavelength provided, expect two theta ValueError - ( + ( # 2. Wavelength provided, expect two theta ValueError 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), ValueError, @@ -125,14 +121,12 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): # C1: Empty q values, expect empty d values (np.array([]), np.array([]), False), # C2: - # 1. Valid q values, expect d values without warning - ( + ( # 1. Valid q values, expect d values without warning np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), - # 2. Valid q values containing 0, expect d values with divide by zero warning - ( + ( # 2. Valid q values containing 0, expect d values with divide by zero warning np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), True, @@ -180,8 +174,7 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): (4 * np.pi, np.array([]), np.array([]), False), # C3: User specified valid tth values between 0-180 degrees (without wavelength) (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), - # C4: User specified valid tth values between 0-180 degrees (with wavelength) - ( + ( # C4: User specified valid tth values between 0-180 degrees (with wavelength) 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), @@ -204,15 +197,13 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError - ( + ( # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError - ( + ( # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), ValueError, @@ -234,8 +225,7 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m (4 * np.pi, np.empty((0)), np.empty(0), False), # C3: Valid d values, no wavelength, expect valid and non-empty tth values (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - # C4: Valid d values with wavelength, expect valid and non-empty thh values - ( + ( # C4: Valid d values with wavelength, expect valid and non-empty thh values 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), From 03241387b40ea89a3dc94df53b282ae480e7e057 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:09:50 +0000 Subject: [PATCH 512/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 60f87c2e..0396c420 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -225,7 +225,7 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m (4 * np.pi, np.empty((0)), np.empty(0), False), # C3: Valid d values, no wavelength, expect valid and non-empty tth values (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - ( # C4: Valid d values with wavelength, expect valid and non-empty thh values + ( # C4: Valid d values with wavelength, expect valid and non-empty thh values 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), From 2a9e1c34dc414fc1572047724a8e595f004833ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:16:45 +0000 Subject: [PATCH 513/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 248230eb..b7c89b4c 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -4,7 +4,6 @@ from pathlib import Path - def clean_dict(obj): """Remove keys from the dictionary where the corresponding value is None. @@ -26,9 +25,7 @@ def clean_dict(obj): def _stringify(obj): - """ - Convert None to an empty string. - + """Convert None to an empty string. Parameters ---------- @@ -44,9 +41,8 @@ def _stringify(obj): def _load_config(file_path): - """ - Load configuration from a .json file. ->>>>>>> de55560eb525ef412c38bb31d21d43d9b170d3f6 + """Load configuration from a .json file. >>>>>>> + de55560eb525ef412c38bb31d21d43d9b170d3f6. Parameters ---------- From 32c00188343d035dd4625541adf4d338cfd38141 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 17:19:37 -0500 Subject: [PATCH 514/696] Remove >>>> extra due to merge conflict in tools --- src/diffpy/utils/tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index b7c89b4c..f2f4e136 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -41,8 +41,7 @@ def _stringify(obj): def _load_config(file_path): - """Load configuration from a .json file. >>>>>>> - de55560eb525ef412c38bb31d21d43d9b170d3f6. + """Load configuration from a .json file. Parameters ---------- From 209a1945c33950ddfff1d1eaf8e086c16695995a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 23:10:38 -0500 Subject: [PATCH 515/696] Add separate docstring for @property methods --- src/diffpy/utils/diffraction_objects.py | 37 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2922cdbd..a1b1bd13 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -46,12 +46,6 @@ class DiffractionObject: Attributes ---------- - all_arrays : ndarray - The array containing the quantity of q, tth, d values. - input_xtype : str - The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES} - _uuid : uuid - The unique identifier for the diffraction object. scat_quantity : str The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". wavelength : float @@ -284,6 +278,20 @@ def __rtruediv__(self, other): @property def all_arrays(self): + """The array containing `xarray` values in q, d, tth, and `yarray`. + + Returns + ------- + ndarray + The 2D matrix containing the `xarray` objects values and `yarray`. + + Examples + -------- + >>> my_do.all_arrays[:, 0] # yarray + >>> my_do.all_arrays[:, 1] # `xarray` in q + >>> my_do.all_arrays[:, 2] # `xarray` in tth + >>> my_do.all_arrays[:, 3] # `xarray` in d + """ return self._all_arrays @all_arrays.setter @@ -292,6 +300,13 @@ def all_arrays(self, _): @property def input_xtype(self): + """The type of the independent variable in `xarray`. + + Returns + ------- + str + The type of `xarray`, which must be one of {*XQUANTITIES}. + """ return self._input_xtype @input_xtype.setter @@ -300,6 +315,13 @@ def input_xtype(self, _): @property def uuid(self): + """The unique identifier for the DiffractionObject instance. + + Returns + ------- + uuid + The unique identifier of the DiffractionObject instance. + """ return self._uuid @uuid.setter @@ -319,7 +341,8 @@ def get_array_index(self, value, xtype=None): Returns ------- - the index of the value in the array + list + The list containing the index of the closest value in the array. """ xtype = self._input_xtype From 5824250c02fca1e067598615b48418eb24a48504 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 23:15:40 -0500 Subject: [PATCH 516/696] Improve docstring for all_arrays --- src/diffpy/utils/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a1b1bd13..684c71b7 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -278,12 +278,12 @@ def __rtruediv__(self, other): @property def all_arrays(self): - """The array containing `xarray` values in q, d, tth, and `yarray`. + """The 2D array containing `xarray` and `yarray` values. Returns ------- ndarray - The 2D matrix containing the `xarray` objects values and `yarray`. + The 2D array containing the `xarray` values in q, tth, d, and `yarray`. Examples -------- From 2f293c09999cc1b8617cd39b2c9a0b07f1b2add3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 23:16:53 -0500 Subject: [PATCH 517/696] Remove extra singl quote around comment --- src/diffpy/utils/diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 684c71b7..286154d7 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -288,9 +288,9 @@ def all_arrays(self): Examples -------- >>> my_do.all_arrays[:, 0] # yarray - >>> my_do.all_arrays[:, 1] # `xarray` in q - >>> my_do.all_arrays[:, 2] # `xarray` in tth - >>> my_do.all_arrays[:, 3] # `xarray` in d + >>> my_do.all_arrays[:, 1] # xarray in q + >>> my_do.all_arrays[:, 2] # xarray in tth + >>> my_do.all_arrays[:, 3] # xarray in d """ return self._all_arrays From 5180eeb379535d6a803f627af819e6b5eb36a5b3 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 11:47:04 -0500 Subject: [PATCH 518/696] initial commit --- news/mu.rst | 23 +++++++++++++++++++++++ requirements/conda.txt | 1 + requirements/pip.txt | 1 + src/diffpy/utils/tools.py | 25 +++++++++++++++++++++++++ tests/test_tools.py | 8 +++++++- 5 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 news/mu.rst diff --git a/news/mu.rst b/news/mu.rst new file mode 100644 index 00000000..bbf9cb1a --- /dev/null +++ b/news/mu.rst @@ -0,0 +1,23 @@ +**Added:** + +* function to compute x-ray attenuation coefficient (mu) using XrayDB + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/requirements/conda.txt b/requirements/conda.txt index 24ce15ab..b26085a8 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -1 +1,2 @@ numpy +xraydb diff --git a/requirements/pip.txt b/requirements/pip.txt index 24ce15ab..b26085a8 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1 +1,2 @@ numpy +xraydb diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index a6c30ed3..ca965e22 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -3,6 +3,8 @@ from copy import copy from pathlib import Path +from xraydb import material_mu + def _stringify(obj): """ @@ -131,3 +133,26 @@ def get_package_info(package_names, metadata=None): pkg_info.update({package: importlib.metadata.version(package)}) metadata["package_info"] = pkg_info return metadata + + +def compute_mu_using_xraydb(sample, energy, density=None): + """ + compute mu using the XrayDB database + + Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu + + Parameters + ---------- + sample str + the chemical formula or the name of the material + energy float + the energy in eV + density float or None + material density in gr/cm^3 + + Returns + ------- + the attenuation coefficient mu in mm^{-1} + """ + mu = material_mu(sample, energy, density=density, kind="total") / 10 + return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index cf730ddd..62b1673c 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -5,7 +5,7 @@ import pytest -from diffpy.utils.tools import get_package_info, get_user_info +from diffpy.utils.tools import compute_mu_using_xraydb, get_package_info, get_user_info # def _setup_dirs(monkeypatch, user_filesystem): # home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir @@ -189,3 +189,9 @@ def test_get_package_info(monkeypatch, inputs, expected): ) actual_metadata = get_package_info(inputs[0], metadata=inputs[1]) assert actual_metadata == expected + + +def test_compute_mu_using_xraydb(): + sample, energy, density = "ZrO2", 17445.362740959618, 1.009 + actual_mu = compute_mu_using_xraydb(sample, energy, density=density) + assert actual_mu == pytest.approx(1.252, rel=1e-4, abs=0.1) From 5ff2c2312de6d4eab46c201327053f86cd64e829 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 16:49:30 +0000 Subject: [PATCH 519/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index ca965e22..2440abef 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -136,8 +136,7 @@ def get_package_info(package_names, metadata=None): def compute_mu_using_xraydb(sample, energy, density=None): - """ - compute mu using the XrayDB database + """Compute mu using the XrayDB database. Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu From ceba7f9dead7867476bdba5482caacfe77810de0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 26 Dec 2024 14:49:52 -0500 Subject: [PATCH 520/696] Fix docstring for all_arrays per sbilling pr review --- src/diffpy/utils/diffraction_objects.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 286154d7..83d7f1cf 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -283,10 +283,13 @@ def all_arrays(self): Returns ------- ndarray - The 2D array containing the `xarray` values in q, tth, d, and `yarray`. + The shape (len(data), 4) 2D array with columns containing the `yarray` (intensity) + and the `xarray` values in q, tth, d, and `yarray`. Examples -------- + To access specific arrays individually, use these slices: + >>> my_do.all_arrays[:, 0] # yarray >>> my_do.all_arrays[:, 1] # xarray in q >>> my_do.all_arrays[:, 2] # xarray in tth From 2d1c104b4197b20c94c21d5fef6683f65f876d00 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 19:50:06 +0000 Subject: [PATCH 521/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 83d7f1cf..87ed9d13 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -289,7 +289,7 @@ def all_arrays(self): Examples -------- To access specific arrays individually, use these slices: - + >>> my_do.all_arrays[:, 0] # yarray >>> my_do.all_arrays[:, 1] # xarray in q >>> my_do.all_arrays[:, 2] # xarray in tth From 182622167e58bc704f15efefa890e021f2981449 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 14:58:40 -0500 Subject: [PATCH 522/696] add packing fraction, add more tests --- src/diffpy/utils/tools.py | 24 +++++++++++++++--------- tests/test_tools.py | 26 ++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index ca965e22..3674401a 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -135,24 +135,30 @@ def get_package_info(package_names, metadata=None): return metadata -def compute_mu_using_xraydb(sample, energy, density=None): +def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1): """ - compute mu using the XrayDB database + Compute the attenuation coefficient (mu) using the XrayDB database + This function calculates mu based on the sample composition and energy. + If density is not provided, a standard reference density (e.g., 0.987 g/cm^3 for H2O) is used. + User can provide either a measured density or an estimated packing fraction (with a standard density). + It is recommended to specify the density, especially for materials like ZrO2, where it can vary. Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu Parameters ---------- - sample str - the chemical formula or the name of the material - energy float - the energy in eV - density float or None - material density in gr/cm^3 + sample_composition: str + The chemical formula or the name of the material + energy: float + The energy in eV + density: float, optional, Default is None + The mass density of the packed powder/sample in gr/cm^3. If None, a standard density from XrayDB is used. + packing_fraction: float, optional, Default is 1 + The fraction of sample in the capillary (between 0 and 1) Returns ------- the attenuation coefficient mu in mm^{-1} """ - mu = material_mu(sample, energy, density=density, kind="total") / 10 + mu = material_mu(sample_composition, energy, density=density, kind="total") * packing_fraction / 10 return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index 62b1673c..dd70d347 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -191,7 +191,25 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected -def test_compute_mu_using_xraydb(): - sample, energy, density = "ZrO2", 17445.362740959618, 1.009 - actual_mu = compute_mu_using_xraydb(sample, energy, density=density) - assert actual_mu == pytest.approx(1.252, rel=1e-4, abs=0.1) +params_mu = [ + # C1: user didn't specify density or packing fraction + ({"sample_composition": "H2O", "energy": 10000, "density": None, "packing_fraction": 1}, 0.5330), + # C2: user specified packing fraction only + ({"sample_composition": "H2O", "energy": 10000, "density": None, "packing_fraction": 0.5}, 0.2665), + # C3: user specified density only + ({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 1}, 0.5330), + ({"sample_composition": "H2O", "energy": 10000, "density": 0.4985, "packing_fraction": 1}, 0.2665), + # C4: user specified a standard density and a packing fraction + ({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 0.5}, 0.2665), +] + + +@pytest.mark.parametrize("inputs, expected", params_mu) +def test_compute_mu_using_xraydb(inputs, expected): + actual_mu = compute_mu_using_xraydb( + inputs["sample_composition"], + inputs["energy"], + density=inputs["density"], + packing_fraction=inputs["packing_fraction"], + ) + assert actual_mu == pytest.approx(expected, rel=0.01, abs=0.1) From be7c275a66ed254e82f6c772fbee2d21bcea13c2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 20:00:29 +0000 Subject: [PATCH 523/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3674401a..611048f1 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -136,8 +136,7 @@ def get_package_info(package_names, metadata=None): def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1): - """ - Compute the attenuation coefficient (mu) using the XrayDB database + """Compute the attenuation coefficient (mu) using the XrayDB database. This function calculates mu based on the sample composition and energy. If density is not provided, a standard reference density (e.g., 0.987 g/cm^3 for H2O) is used. From 9e4bc0dc1173978544be084e80ee182311587ccc Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 15:27:55 -0500 Subject: [PATCH 524/696] refine docstring and test comments --- src/diffpy/utils/tools.py | 24 +++++++------- tests/test_tools.py | 70 ++++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3674401a..9b78dec2 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -136,29 +136,29 @@ def get_package_info(package_names, metadata=None): def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1): - """ - Compute the attenuation coefficient (mu) using the XrayDB database + """Compute the attenuation coefficient (mu) using the XrayDB database. - This function calculates mu based on the sample composition and energy. + Computes mu based on the sample composition and energy. If density is not provided, a standard reference density (e.g., 0.987 g/cm^3 for H2O) is used. User can provide either a measured density or an estimated packing fraction (with a standard density). It is recommended to specify the density, especially for materials like ZrO2, where it can vary. - Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu + Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. Parameters ---------- - sample_composition: str - The chemical formula or the name of the material - energy: float - The energy in eV - density: float, optional, Default is None + sample_composition : str + The chemical formula or the name of the material. + energy : float + The energy in eV. + density : float, optional, Default is None The mass density of the packed powder/sample in gr/cm^3. If None, a standard density from XrayDB is used. - packing_fraction: float, optional, Default is 1 - The fraction of sample in the capillary (between 0 and 1) + packing_fraction : float, optional, Default is 1 + The fraction of sample in the capillary (between 0 and 1). Returns ------- - the attenuation coefficient mu in mm^{-1} + mu : float + The attenuation coefficient mu in mm^{-1}. """ mu = material_mu(sample_composition, energy, density=density, kind="total") * packing_fraction / 10 return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index dd70d347..28850985 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -191,25 +191,63 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected -params_mu = [ - # C1: user didn't specify density or packing fraction - ({"sample_composition": "H2O", "energy": 10000, "density": None, "packing_fraction": 1}, 0.5330), - # C2: user specified packing fraction only - ({"sample_composition": "H2O", "energy": 10000, "density": None, "packing_fraction": 0.5}, 0.2665), - # C3: user specified density only - ({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 1}, 0.5330), - ({"sample_composition": "H2O", "energy": 10000, "density": 0.4985, "packing_fraction": 1}, 0.2665), - # C4: user specified a standard density and a packing fraction - ({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 0.5}, 0.2665), -] - - -@pytest.mark.parametrize("inputs, expected", params_mu) -def test_compute_mu_using_xraydb(inputs, expected): +@pytest.mark.parametrize( + "inputs, expected_mu", + [ + # Test whether the function returns the correct mu + ( # C1: No density or packing fraction provided, expect to compute mu based on standard density + { + "sample_composition": "H2O", + "energy": 10000, + "density": None, + "packing_fraction": 1, + }, + 0.5330, + ), + ( # C2: Packing fraction (=0.5) provided only, expect to return half of mu based on standard density + { + "sample_composition": "H2O", + "energy": 10000, + "density": None, + "packing_fraction": 0.5, + }, + 0.2665, + ), + ( # C3: Density provided only, expect to compute mu based on density + # 1. Standard density provided, expect to return the same mu as C1 + { + "sample_composition": "H2O", + "energy": 10000, + "density": 0.997, + "packing_fraction": 1, + }, + 0.5330, + ), + ( # 2. Lower density for H2O (half of standard), expect to return half of mu based on standard density + { + "sample_composition": "H2O", + "energy": 10000, + "density": 0.4985, + "packing_fraction": 1, + }, + 0.2665, + ), + ( # C4: Both standard density and packing fraction are provided, expect to compute the same mu as C2 + { + "sample_composition": "H2O", + "energy": 10000, + "density": 0.997, + "packing_fraction": 0.5, + }, + 0.2665, + ), + ], +) +def test_compute_mu_using_xraydb(inputs, expected_mu): actual_mu = compute_mu_using_xraydb( inputs["sample_composition"], inputs["energy"], density=inputs["density"], packing_fraction=inputs["packing_fraction"], ) - assert actual_mu == pytest.approx(expected, rel=0.01, abs=0.1) + assert actual_mu == pytest.approx(expected_mu, rel=0.01, abs=0.1) From b198d66ae05bce8d6e01a5223e625bc58fafc0fb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 26 Dec 2024 15:33:56 -0500 Subject: [PATCH 525/696] Remove extra mention of yarray --- src/diffpy/utils/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 83d7f1cf..6118f46d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -284,12 +284,12 @@ def all_arrays(self): ------- ndarray The shape (len(data), 4) 2D array with columns containing the `yarray` (intensity) - and the `xarray` values in q, tth, d, and `yarray`. + and the `xarray` values in q, tth, and d. Examples -------- To access specific arrays individually, use these slices: - + >>> my_do.all_arrays[:, 0] # yarray >>> my_do.all_arrays[:, 1] # xarray in q >>> my_do.all_arrays[:, 2] # xarray in tth From 56e7e83b7beec8dde72cf65bff2441dac0c499c5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 16:24:07 -0500 Subject: [PATCH 526/696] improve comment --- tests/test_diffraction_objects.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9dee757d..f3091561 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -191,7 +191,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether scale_to() scales to the expected values - ( # C1: Same x-array and y-array with 2.1 offset, expect yarray shifted by 2.1 offset + ( # C1: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([2, 3, 4, 5, 6, 7]), @@ -212,7 +212,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - ( # C2: Same length x-arrays with exact x-value match + ( # C2: Same x-arrays, x-value matches at tth=60, expect y-array to divide by 10 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([10, 20, 25, 30, 60, 100]), @@ -233,7 +233,10 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - ( # C3: Same length x-arrays with approximate x-value match + ( # C3: Different x-arrays with same length, + # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) + # for original and target diffraction objects, + # expect y-array to divide by 10 { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), "yarray": np.array([10, 20, 40, 60]), @@ -254,7 +257,10 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - ( # C4: Different x-array lengths with approximate x-value match + ( # C4: Different x-array lengths + # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), + # for original and target diffraction objects, + # expect y-array to divide by 10 { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -273,7 +279,6 @@ def test_init_invalid_xtype(): "d": None, "offset": 0, }, - # C5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ], From 0a3081877eaaba0ae1574e01e71f9393cb4a64a5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 16:26:31 -0500 Subject: [PATCH 527/696] no news added --- news/scaleto-comments.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scaleto-comments.rst diff --git a/news/scaleto-comments.rst b/news/scaleto-comments.rst new file mode 100644 index 00000000..73d0f042 --- /dev/null +++ b/news/scaleto-comments.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news added - make test comments for `test_scale_to` more readable + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From faf0e2fcd29c112c353e55c4dfca549fc13dc8ad Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 26 Dec 2024 21:29:05 -0500 Subject: [PATCH 528/696] return type of method in to return integer instead of list --- news/get-index.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 24 ++++++++++++------------ tests/test_diffraction_objects.py | 14 +++++++------- 3 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 news/get-index.rst diff --git a/news/get-index.rst b/news/get-index.rst new file mode 100644 index 00000000..eef8c787 --- /dev/null +++ b/news/get-index.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* return type of `get_array_index` method in `DiffractionObject` to return integer instead of list + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6118f46d..25e3e28c 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -331,29 +331,29 @@ def uuid(self): def uuid(self, _): raise AttributeError(_setter_wmsg("uuid")) - def get_array_index(self, value, xtype=None): + def get_array_index(self, xtype, xvalue): """Return the index of the closest value in the array associated with - the specified xtype. + the specified xtype and the value provided. Parameters ---------- - xtype str - the xtype used to access the array - value float - the target value to search for + xtype : str + The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. + xvalue : float + The value of the xtype to find the closest index for. Returns ------- - list - The list containing the index of the closest value in the array. + int + The index of the closest value in the array associated with the specified xtype and the value provided. """ xtype = self._input_xtype - array = self.on_xtype(xtype)[0] - if len(array) == 0: + xarray = self.on_xtype(xtype)[0] + if len(xarray) == 0: raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") - i = (np.abs(array - value)).argmin() - return i + index = (np.abs(xarray - xvalue)).argmin() + return index def _set_arrays(self, xarray, yarray, xtype): self._all_arrays = np.empty(shape=(len(xarray), 4)) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 56478e72..f5a88f7a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -364,7 +364,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "tth", "value": 30.005, }, - [0], + 0, ), ( # C2: Target value lies in the array, expect the (first) closest index { @@ -377,7 +377,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "tth", "value": 45, }, - [0], + 0, ), ( { @@ -390,7 +390,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "q", "value": 0.25, }, - [0], + 0, ), # C3: Target value out of the range, expect the closest index ( # 1. Test with xtype of "q" @@ -404,7 +404,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "q", "value": 0.1, }, - [0], + 0, ), ( # 2. Test with xtype of "tth" { @@ -417,20 +417,20 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "tth", "value": 63, }, - [1], + 1, ), ], ) def test_get_array_index(do_args, get_array_index_inputs, expected_index): do = DiffractionObject(**do_args) - actual_index = do.get_array_index(get_array_index_inputs["value"], get_array_index_inputs["xtype"]) + actual_index = do.get_array_index(get_array_index_inputs["xtype"], get_array_index_inputs["value"]) assert actual_index == expected_index def test_get_array_index_bad(): do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")): - do.get_array_index(value=30) + do.get_array_index(xtype="tth", xvalue=30) def test_dump(tmp_path, mocker): From a2a43a091e34b350d697531c221be59ab7a90f32 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 22:10:13 -0500 Subject: [PATCH 529/696] edit test comments --- tests/test_diffraction_objects.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index f3091561..441ae2dd 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -212,7 +212,10 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - ( # C2: Same x-arrays, x-value matches at tth=60, expect y-array to divide by 10 + ( # C2: Same x-arrays + # x-value has exact matches at tth=60 (y=60) and tth=60 (y=6), + # for original and target diffraction objects, + # expect y-array to multiply by 6/60=1/10 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([10, 20, 25, 30, 60, 100]), @@ -236,7 +239,7 @@ def test_init_invalid_xtype(): ( # C3: Different x-arrays with same length, # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) # for original and target diffraction objects, - # expect y-array to divide by 10 + # expect y-array to multiply by 1/10 { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), "yarray": np.array([10, 20, 40, 60]), @@ -260,7 +263,7 @@ def test_init_invalid_xtype(): ( # C4: Different x-array lengths # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), # for original and target diffraction objects, - # expect y-array to divide by 10 + # expect y-array to multiply by 5/50=1/10 { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), From 5fc00b2a21878140fa0283f691f8527c682cb6cb Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 22:11:01 -0500 Subject: [PATCH 530/696] edit test comments --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 441ae2dd..e5c24806 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -215,7 +215,7 @@ def test_init_invalid_xtype(): ( # C2: Same x-arrays # x-value has exact matches at tth=60 (y=60) and tth=60 (y=6), # for original and target diffraction objects, - # expect y-array to multiply by 6/60=1/10 + # expect original y-array to multiply by 6/60=1/10 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([10, 20, 25, 30, 60, 100]), @@ -239,7 +239,7 @@ def test_init_invalid_xtype(): ( # C3: Different x-arrays with same length, # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) # for original and target diffraction objects, - # expect y-array to multiply by 1/10 + # expect original y-array to multiply by 1/10 { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), "yarray": np.array([10, 20, 40, 60]), @@ -263,7 +263,7 @@ def test_init_invalid_xtype(): ( # C4: Different x-array lengths # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), # for original and target diffraction objects, - # expect y-array to multiply by 5/50=1/10 + # expect original y-array to multiply by 5/50=1/10 { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), From d5fd01f4e740f73d6b2ca9b0ffb3bcc1d26f7329 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 22:11:57 -0500 Subject: [PATCH 531/696] edit test comments --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e5c24806..861bacbe 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -190,7 +190,7 @@ def test_init_invalid_xtype(): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs, expected", [ - # Test whether scale_to() scales to the expected values + # Test whether the original y-array is scaled as expected ( # C1: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 { "xarray": np.array([10, 15, 25, 30, 60, 140]), From e5c1f3f34b2d19134c0aab27417cc29e7cf432a4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 00:16:57 -0500 Subject: [PATCH 532/696] Add news and add empty array case for init DiffractionObject --- news/valid-empty-do.rst | 23 ++++++++++ tests/test_diffraction_objects.py | 70 +++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 news/valid-empty-do.rst diff --git a/news/valid-empty-do.rst b/news/valid-empty-do.rst new file mode 100644 index 00000000..74b840b0 --- /dev/null +++ b/news/valid-empty-do.rst @@ -0,0 +1,23 @@ +**Added:** + +* unit tests for initializing DiffractionObject object with empty xarray and yarray array + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 56478e72..e1147520 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -468,10 +468,61 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( - "do_init_args, expected_do_dict, divide_by_zero_warning_expected", + "do_init_args, expected_do_dict, divide_by_zero_warning_expected, wavelength_warning_expected", [ # Test __dict__ of DiffractionObject instance initialized with valid arguments - ( # C1: Minimum arguments provided for init, expect all attributes set without None + ( # C1: Instantiate DO with empty arrays, expect it to be a valid DO, but with everything empty + { + "xarray": np.empty(0), + "yarray": np.empty(0), + "xtype": "tth", + }, + { + "_all_arrays": np.array([]), + "_input_xtype": "tth", + "metadata": {}, + "name": "", + "scat_quantity": "", + "qmin": np.float64(np.inf), + "qmax": np.float64(0.0), + "tthmin": np.float64(np.inf), + "tthmax": np.float64(0.0), + "dmin": np.float64(np.inf), + "dmax": np.float64(0.0), + "wavelength": None, + }, + False, + True, + ), + ( # C2: Instantiate just DO with empty array like in C1 but with wavelength, xtype, name, and metadata + # expect a valid DO with empty arrays, but with some non-array attributes + { + "xarray": np.empty(0), + "yarray": np.empty(0), + "xtype": "tth", + "name": "test_name", + "wavelength": 1.54, + "metadata": {"item_1": "1", "item_2": "2"}, + }, + { + "_all_arrays": np.array([]), + "_input_xtype": "tth", + "metadata": {"item_1": "1", "item_2": "2"}, + "name": "test_name", + "scat_quantity": "", + "qmin": np.float64(np.inf), + "qmax": np.float64(0.0), + "tthmin": np.float64(np.inf), + "tthmax": np.float64(0.0), + "dmin": np.float64(np.inf), + "dmax": np.float64(0.0), + "wavelength": 1.54, + }, + False, + False, + ), + ( # C3: Minimum arguments provided for init with non-empty values for xarray and yarray and wavelength + # expect all attributes set without None { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -499,8 +550,9 @@ def test_dump(tmp_path, mocker): "wavelength": 4.0 * np.pi, }, True, + False, ), - ( # C2: Initialize with an optional scat_quantity argument, expect non-empty string for scat_quantity + ( # C4: Same as C3, but with an optional scat_quantity argument, expect non-empty string for scat_quantity { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -529,13 +581,23 @@ def test_dump(tmp_path, mocker): "wavelength": 4.0 * np.pi, }, False, + False, ), ], ) -def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expected): +def test_init_valid( + do_init_args, + expected_do_dict, + divide_by_zero_warning_expected, + wavelength_warning_expected, + wavelength_warning_msg, +): if divide_by_zero_warning_expected: with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): actual_do_dict = DiffractionObject(**do_init_args).__dict__ + elif wavelength_warning_expected: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + actual_do_dict = DiffractionObject(**do_init_args).__dict__ else: actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( From b865a8e29ab54f0f81f29de554c86d7ffe9f7117 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 00:18:13 -0500 Subject: [PATCH 533/696] Clarify language for news --- news/valid-empty-do.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/valid-empty-do.rst b/news/valid-empty-do.rst index 74b840b0..dfd98716 100644 --- a/news/valid-empty-do.rst +++ b/news/valid-empty-do.rst @@ -1,6 +1,6 @@ **Added:** -* unit tests for initializing DiffractionObject object with empty xarray and yarray array +* unit tests for initializing DiffractionObject with empty array in xarray and yarray **Changed:** From 07938c9aaff79e628fd4846ac9f053ad62a74781 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 27 Dec 2024 10:01:20 -0500 Subject: [PATCH 534/696] offset test case goes to the end --- tests/test_diffraction_objects.py | 48 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 861bacbe..d76f248a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -191,28 +191,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether the original y-array is scaled as expected - ( # C1: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 - { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), - "xtype": "tth", - "wavelength": 2 * np.pi, - }, - { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), - "xtype": "tth", - "wavelength": 2 * np.pi, - }, - { - "q": None, - "tth": 60, - "d": None, - "offset": 2.1, - }, - {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, - ), - ( # C2: Same x-arrays + ( # C1: Same x-arrays # x-value has exact matches at tth=60 (y=60) and tth=60 (y=6), # for original and target diffraction objects, # expect original y-array to multiply by 6/60=1/10 @@ -236,7 +215,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - ( # C3: Different x-arrays with same length, + ( # C2: Different x-arrays with same length, # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) # for original and target diffraction objects, # expect original y-array to multiply by 1/10 @@ -260,7 +239,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - ( # C4: Different x-array lengths + ( # C3: Different x-array lengths # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), # for original and target diffraction objects, # expect original y-array to multiply by 5/50=1/10 @@ -284,6 +263,27 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), + ( # C4: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": None, + "offset": 2.1, + }, + {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, + ), ], ) def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): From 7db3a4f44f444c4e6d84c2da799b7de94af9567d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:40:34 -0500 Subject: [PATCH 535/696] Refactor __eq__ function for DO --- src/diffpy/utils/diffraction_objects.py | 94 +++++++++++++------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25e3e28c..316758d0 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -14,9 +14,15 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -x_grid_emsg = ( - "objects are not on the same x-grid. You may add them using the self.add method " - "and specifying how to handle the mismatch." +x_grid_length_mismatch_emsg = ( + "The two objects have different x-array lengths. " + "Please ensure the length of the x-value during initialization is identical." +) + +invalid_add_type_emsg = ( + "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " + "Please rerun by adding another DiffractionObject instance or a scalar value. " + "e.g., my_do_1 + my_do_2 or my_do + 10" ) @@ -169,32 +175,44 @@ def __eq__(self, other): return True def __add__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to sum two DiffractionObject objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed + """Add a scalar value or another DiffractionObject to the xarrays of + the DiffractionObject. - def __radd__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to sum two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + Parameters + ---------- + other : DiffractionObject or int or float + The object to add to the current DiffractionObject. If `other` is a scalar value, + it will be added to all xarrays. The length of the xarrays must match if `other` is + an instance of DiffractionObject. + + Returns + ------- + DiffractionObject + The new and deep-copied DiffractionObject instance after adding values to the xarrays. + + Raises + ------ + ValueError + Raised when the length of the xarrays of the two DiffractionObject instances do not match. + TypeError + Raised when the type of `other` is not an instance of DiffractionObject, int, or float. + """ + summed_do = deepcopy(self) + # Add scalar value to all xarrays by broadcasting + if isinstance(other, (int, float)): + summed_do._all_arrays[:, 1] += other + summed_do._all_arrays[:, 2] += other + summed_do._all_arrays[:, 3] += other + # Add xarrays of two DiffractionObject instances + elif isinstance(other, DiffractionObject): + if len(self.on_tth()[0]) != len(other.on_tth()[0]): + raise ValueError(x_grid_length_mismatch_emsg) + summed_do._all_arrays[:, 1] += other.on_q()[0] + summed_do._all_arrays[:, 2] += other.on_tth()[0] + summed_do._all_arrays[:, 3] += other.on_d()[0] else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed + raise TypeError(invalid_add_type_emsg) + return summed_do def __sub__(self, other): subtracted = deepcopy(self) @@ -204,7 +222,7 @@ def __sub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] subtracted.on_q[1] = self.on_q[1] - other.on_q[1] @@ -218,7 +236,7 @@ def __rsub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] subtracted.on_q[1] = other.on_q[1] - self.on_q[1] @@ -232,19 +250,7 @@ def __mul__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied - - def __rmul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -258,7 +264,7 @@ def __truediv__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] divided.on_q[1] = self.on_q[1] / other.on_q[1] @@ -270,7 +276,7 @@ def __rtruediv__(self, other): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] divided.on_q[1] = other.on_q[1] / self.on_q[1] From 3d4841b75bd85767678cfdb19320bd8ada213558 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:41:17 -0500 Subject: [PATCH 536/696] Add __eq__ news and test --- news/add-operations-tests.rst | 23 +++++++++++ tests/conftest.py | 17 ++++++++ tests/test_diffraction_objects.py | 65 +++++++++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 news/add-operations-tests.rst diff --git a/news/add-operations-tests.rst b/news/add-operations-tests.rst new file mode 100644 index 00000000..e59edbd5 --- /dev/null +++ b/news/add-operations-tests.rst @@ -0,0 +1,23 @@ +**Added:** + +* unit tests for __add__ operation for DiffractionObject + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/conftest.py b/tests/conftest.py index 7f8de460..5eaaa902 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -63,3 +63,20 @@ def invalid_q_or_d_or_wavelength_error_msg(): "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) + + +@pytest.fixture +def invalid_add_type_error_msg(): + return ( + "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " + "Please rerun by adding another DiffractionObject instance or a scalar value. " + "e.g., my_do_1 + my_do_2 or my_do + 10" + ) + + +@pytest.fixture +def x_grid_size_mismatch_error_msg(): + return ( + "The two objects have different x-array lengths. " + "Please ensure the length of the x-value during initialization is identical." + ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 71271931..9675ad7f 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -155,7 +155,7 @@ def test_diffraction_objects_equality( @pytest.mark.parametrize( - "xtype, expected_xarray", + "xtype, expected_all_arrays", [ # Test whether on_xtype returns the correct xarray values. # C1: tth to tth, expect no change in xarray value @@ -169,10 +169,10 @@ def test_diffraction_objects_equality( ("d", np.array([12.13818, 6.28319])), ], ) -def test_on_xtype(xtype, expected_xarray, do_minimal_tth): +def test_on_xtype(xtype, expected_all_arrays, do_minimal_tth): do = do_minimal_tth actual_xrray, actual_yarray = do.on_xtype(xtype) - assert np.allclose(actual_xrray, expected_xarray) + assert np.allclose(actual_xrray, expected_all_arrays) assert np.allclose(actual_yarray, np.array([1, 2])) @@ -702,3 +702,62 @@ def test_copy_object(do_minimal): do_copy = do.copy() assert do == do_copy assert id(do) != id(do_copy) + + +@pytest.mark.parametrize( + "starting_all_arrays, scalar_value, expected_all_arrays", + [ + # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values + ( # C1: Add integer of 5, expect xarray to increase by by 5 + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 5, + np.array([[1.0, 5.51763809, 35.0, 17.13818192], [2.0, 6.0, 65.0, 11.28318531]]), + ), + ( # C2: Add float of 5.1, expect xarray to be added by 5.1 + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 5.1, + np.array([[1.0, 5.61763809, 35.1, 17.23818192], [2.0, 6.1, 65.1, 11.38318531]]), + ), + ], +) +def test_addition_operator_by_scalar(starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): + do = do_minimal_tth + assert np.allclose(do.all_arrays, starting_all_arrays) + do_sum = do + scalar_value + assert np.allclose(do_sum.all_arrays, expected_all_arrays) + + +@pytest.mark.parametrize( + "LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum", + [ + # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray + ( # C1: Add two DO objects with identical xarray values, expect sum of xarray values + (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), + (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), + np.array([[1.0, 1.03527618, 60.0, 24.27636384], [2.0, 2.0, 120.0, 12.56637061]]), + ), + ], +) +def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum, do_minimal_tth): + assert np.allclose(do_minimal_tth.all_arrays, LHS_all_arrays) + do_LHS = do_minimal_tth + do_RHS = do_minimal_tth + do_sum = do_LHS + do_RHS + assert np.allclose(do_LHS.all_arrays, LHS_all_arrays) + assert np.allclose(do_RHS.all_arrays, RHS_all_arrays) + assert np.allclose(do_sum.all_arrays, expected_all_arrays_sum) + + +def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): + # Add a string to a DO object, expect TypeError, only scalar (int, float) allowed for addition + do_LHS = do_minimal_tth + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + do_LHS + "string_value" + + +def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): + # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays + do_LHS = do_minimal + do_RHS = do_minimal_tth + with pytest.raises(ValueError, match=re.escape(x_grid_size_mismatch_error_msg)): + do_LHS + do_RHS From 5d0ebcc62552af1df5953d2fa75fe0f7efdfaa34 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:44:34 -0500 Subject: [PATCH 537/696] Add example to __eq__ --- src/diffpy/utils/diffraction_objects.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 316758d0..f2a0ce68 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -196,7 +196,16 @@ def __add__(self, other): Raised when the length of the xarrays of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. + + Examples + -------- + Add a scalar value to the xarrays of the DiffractionObject instance: + >>> new_do = my_do + 10.1 + + Add the xarrays of two DiffractionObject instances: + >>> new_do = my_do_1 + my_do_2 """ + summed_do = deepcopy(self) # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): From 9741a8ed56acc859770cb12e19d7d4d712cd6bb5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:46:08 -0500 Subject: [PATCH 538/696] Revert not needed change to naming in a test func --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9675ad7f..0555dc37 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -155,7 +155,7 @@ def test_diffraction_objects_equality( @pytest.mark.parametrize( - "xtype, expected_all_arrays", + "xtype, expected_xarray", [ # Test whether on_xtype returns the correct xarray values. # C1: tth to tth, expect no change in xarray value @@ -169,10 +169,10 @@ def test_diffraction_objects_equality( ("d", np.array([12.13818, 6.28319])), ], ) -def test_on_xtype(xtype, expected_all_arrays, do_minimal_tth): +def test_on_xtype(xtype, expected_xarray, do_minimal_tth): do = do_minimal_tth actual_xrray, actual_yarray = do.on_xtype(xtype) - assert np.allclose(actual_xrray, expected_all_arrays) + assert np.allclose(actual_xrray, expected_xarray) assert np.allclose(actual_yarray, np.array([1, 2])) From 3f577d7e558cd6f346c01ea3c71c1063f0d91be5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:46:40 -0500 Subject: [PATCH 539/696] Apply pre-commit --- src/diffpy/utils/diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index f2a0ce68..097f1d9d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -196,16 +196,16 @@ def __add__(self, other): Raised when the length of the xarrays of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. - + Examples -------- Add a scalar value to the xarrays of the DiffractionObject instance: >>> new_do = my_do + 10.1 - + Add the xarrays of two DiffractionObject instances: >>> new_do = my_do_1 + my_do_2 """ - + summed_do = deepcopy(self) # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): From 0bdbbb48a8c9fff3e9a2c9a715baa04dd08e9164 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 27 Dec 2024 21:18:59 -0500 Subject: [PATCH 540/696] enforce requirement to specify either density or packing fraction --- src/diffpy/utils/tools.py | 31 +++++++++++---- tests/test_tools.py | 82 ++++++++++++++++++++------------------- 2 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 7144a9bb..d8b59f10 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,5 +1,6 @@ import importlib.metadata import json +import warnings from copy import copy from pathlib import Path @@ -210,12 +211,12 @@ def get_package_info(package_names, metadata=None): return metadata -def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1): +def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): """Compute the attenuation coefficient (mu) using the XrayDB database. Computes mu based on the sample composition and energy. - User can provide a measured density or an estimated packing fraction. - Specifying the density is recommended, though not required for some pure or standard materials. + User should provide a sample mass density or a packing fraction. + If neither density nor packing fraction is specified, or if both are specified, a ValueError will be raised. Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. Parameters @@ -223,10 +224,10 @@ def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fr sample_composition : str The chemical formula or the name of the material. energy : float - The energy in keV. - density : float, optional, Default is None + The energy of the incident x-rays in keV. + sample_mass_density : float, optional, Default is None The mass density of the packed powder/sample in gr/cm^3. - packing_fraction : float, optional, Default is 1 + packing_fraction : float, optional, Default is None The fraction of sample in the capillary (between 0 and 1). Returns @@ -234,5 +235,21 @@ def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fr mu : float The attenuation coefficient mu in mm^{-1}. """ - mu = material_mu(sample_composition, energy * 1000, density=density, kind="total") * packing_fraction / 10 + if (sample_mass_density is None and packing_fraction is None) or ( + sample_mass_density is not None and packing_fraction is not None + ): + raise ValueError( + "You must specify either sample_mass_density or packing_fraction, but not both. " + "Please rerun specifying only one." + ) + if sample_mass_density is not None: + mu = material_mu(sample_composition, energy * 1000, density=sample_mass_density, kind="total") / 10 + else: + warnings.warn( + "Warning: Density is set to None if a packing fraction is specified, " + "which may cause errors for some materials. " + "We recommend specifying sample mass density for now. " + "Auto-density calculation is coming soon." + ) + mu = material_mu(sample_composition, energy * 1000, density=None, kind="total") * packing_fraction / 10 return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index 06202cdf..a3363320 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -171,59 +171,61 @@ def test_get_package_info(monkeypatch, inputs, expected): "inputs, expected_mu", [ # Test whether the function returns the correct mu - ( # C1: No density or packing fraction (only for known material), expect to get mu from database + ( # C1: Composition, energy, and mass density provided, expect to get mu based on mass density + # 1. Fully dense mass density + {"sample_composition": "quartz", "energy": 10, "sample_mass_density": 2.65}, + 5.0368, + ), + ( # 2. Measured mass density { - "sample_composition": "H2O", - "energy": 10, + "sample_composition": "ZrO2", + "energy": 17.445, + "sample_mass_density": 1.009, }, - 0.5330, + 1.2522, ), - ( # C2: Packing fraction (=0.5) provided only (only for known material) + ( # C2: Composition, energy, and packing fraction provided, expect to get mu based on packing fraction + # Reuse pattern from C1.1 here { - "sample_composition": "H2O", + "sample_composition": "quartz", "energy": 10, "packing_fraction": 0.5, }, - 0.2665, + 2.5184, ), - ( # C3: Density provided only, expect to compute mu based on it - # 1. Known material + ], +) +def test_compute_mu_using_xraydb(inputs, expected_mu): + actual_mu = compute_mu_using_xraydb(**inputs) + assert actual_mu == pytest.approx(expected_mu, rel=1e-6, abs=1e-4) + + +@pytest.mark.parametrize( + "inputs", + [ + # Test when the function raises ValueError + # C1: Both mass density and packing fraction are provided + ( { - "sample_composition": "H2O", + "sample_composition": "quartz", "energy": 10, - "density": 0.987, - }, - 0.5330, + "sample_mass_density": 2.65, + "packing_fraction": 1, + } ), - ( # 2. Unknown material - { - "sample_composition": "ZrO2", - "energy": 17, - "density": 1.009, - }, - 1.252, - ), - ( # C4: Both density and packing fraction are provided, expect to compute mu based on both - # 1. Known material + # C2: None of mass density or packing fraction are provided + ( { - "sample_composition": "H2O", + "sample_composition": "quartz", "energy": 10, - "density": 0.997, - "packing_fraction": 0.5, - }, - 0.2665, - ), - ( # 2. Unknown material - { - "sample_composition": "ZrO2", - "energy": 17, - "density": 1.009, - "packing_fraction": 0.5, - }, - 0.626, + } ), ], ) -def test_compute_mu_using_xraydb(inputs, expected_mu): - actual_mu = compute_mu_using_xraydb(**inputs) - assert actual_mu == pytest.approx(expected_mu, rel=0.01, abs=0.1) +def test_compute_mu_using_xraydb_bad(inputs): + with pytest.raises( + ValueError, + match="You must specify either sample_mass_density or packing_fraction, but not both. " + "Please rerun specifying only one.", + ): + compute_mu_using_xraydb(**inputs) From d56158344269410a37f36ea0e27c18778168eaec Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 23:42:14 -0500 Subject: [PATCH 541/696] Add radd and rmul back and add tests --- src/diffpy/utils/diffraction_objects.py | 14 ++++++++++++++ tests/test_diffraction_objects.py | 18 +++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 097f1d9d..e741d94a 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -223,6 +223,8 @@ def __add__(self, other): raise TypeError(invalid_add_type_emsg) return summed_do + __radd__ = __add__ + def __sub__(self, other): subtracted = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): @@ -265,6 +267,18 @@ def __mul__(self, other): multiplied.on_q[1] = self.on_q[1] * other.on_q[1] return multiplied + def __rmul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_length_mismatch_emsg) + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + def __truediv__(self, other): divided = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0555dc37..30dffb25 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -705,7 +705,7 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "starting_all_arrays, scalar_value, expected_all_arrays", + "starting_all_arrays, scalar_to_add, expected_all_arrays", [ # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values ( # C1: Add integer of 5, expect xarray to increase by by 5 @@ -720,11 +720,13 @@ def test_copy_object(do_minimal): ), ], ) -def test_addition_operator_by_scalar(starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): +def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - do_sum = do + scalar_value - assert np.allclose(do_sum.all_arrays, expected_all_arrays) + do_sum_RHS = do + scalar_to_add + do_sum_LHS = scalar_to_add + do + assert np.allclose(do_sum_RHS.all_arrays, expected_all_arrays) + assert np.allclose(do_sum_LHS.all_arrays, expected_all_arrays) @pytest.mark.parametrize( @@ -750,10 +752,12 @@ def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expecte def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): # Add a string to a DO object, expect TypeError, only scalar (int, float) allowed for addition - do_LHS = do_minimal_tth + do = do_minimal_tth with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do_LHS + "string_value" - + do + "string_value" + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + "string_value" + do + def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays From ac5a2f3b608b53128a6944bc2919ca902d6d3849 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2024 04:42:24 +0000 Subject: [PATCH 542/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 30dffb25..0f4cc08e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -757,7 +757,7 @@ def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_m do + "string_value" with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): "string_value" + do - + def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays From e91661df5764bae3a0bb0a6d81e29daab8f8b793 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 28 Dec 2024 23:02:00 -0500 Subject: [PATCH 543/696] add news --- news/scaleto-max.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scaleto-max.rst diff --git a/news/scaleto-max.rst b/news/scaleto-max.rst new file mode 100644 index 00000000..0037f533 --- /dev/null +++ b/news/scaleto-max.rst @@ -0,0 +1,23 @@ +**Added:** + +* new feature in `scale_to()` to run without specifying an x-value + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 4a87d00a4ed1f093b177b546bc7dcd8febd92196 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 28 Dec 2024 23:02:33 -0500 Subject: [PATCH 544/696] add example --- doc/source/examples/diffraction_objects_example.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 8621f50f..aa4de157 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -117,6 +117,13 @@ For convenience, you can also apply an offset to the scaled new diffraction obje scaled_and_offset_measured = measured.scale_to(calculated, q=5.5, offset=0.5) +You can call `scale_to()` without specifying a value for `q`, `tth`, or `d`. +In this case, the scaling will be done based on the maximal x-array value of both diffraction objects: + +.. code-block:: python + + scaled_measured = measured.scale_to(calculated) + DiffractionObject convenience functions --------------------------------------- From 2c8335cd1dda1762e60f5c4a6501c4c0364b49f5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 28 Dec 2024 23:07:19 -0500 Subject: [PATCH 545/696] add feature so that user can run scale_to without xvalue --- src/diffpy/utils/diffraction_objects.py | 15 ++++++--- tests/test_diffraction_objects.py | 41 +++++++++++++------------ 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25e3e28c..413d9eac 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -402,14 +402,16 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, or none are provided, an error will be raised. + If none of `q`, `tth`, or `d` are provided, + the scaling will be based on the maximal x-array value from both objects. + If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. Parameters ---------- target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, must specify exactly one of them + q, tth, d : float, optional, default is q with the maximal x-array value of the current object The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. @@ -422,11 +424,16 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): """ scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) - if count != 1: + if count > 1: raise ValueError( - "You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." + "You must specify none or exactly one of 'q', 'tth', or 'd'. " + "Please provide either none or one value." ) + if count == 0: + scaled._all_arrays[:, 0] *= max(target_diff_object.on_q()[1]) / max(self.on_q()[1]) + return scaled + xtype = "q" if q is not None else "tth" if tth is not None else "d" data = self.on_xtype(xtype) target = target_diff_object.on_xtype(xtype) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c6355882..789a5e77 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -284,23 +284,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - ], -) -def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): - original_do = DiffractionObject(**org_do_args) - target_do = DiffractionObject(**target_do_args) - scaled_do = original_do.scale_to( - target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"] - ) - # Check the intensity data is the same as expected - assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) - - -@pytest.mark.parametrize( - "org_do_args, target_do_args, scale_inputs", - [ - # Test expected errors produced from scale_to() with invalid inputs - ( # C1: none of q, tth, d, provided, expect ValueError + ( # C5: none of q, tth, d, provided, expect to scale on the maximal x-arrays { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), @@ -319,8 +303,25 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "d": None, "offset": 0, }, + {"xtype": "q", "yarray": np.array([10, 20, 30])}, ), - ( # C2: tth and d both provided, expect ValueErrort + ], +) +def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): + original_do = DiffractionObject(**org_do_args) + target_do = DiffractionObject(**target_do_args) + scaled_do = original_do.scale_to( + target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"] + ) + # Check the intensity data is the same as expected + assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) + + +@pytest.mark.parametrize( + "org_do_args, target_do_args, scale_inputs", + [ + # Test expected errors produced from scale_to() with invalid inputs + ( # C2: tth and d both provided, expect ValueError { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -346,7 +347,9 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): original_do = DiffractionObject(**org_do_args) target_do = DiffractionObject(**target_do_args) with pytest.raises( - ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." + ValueError, + match="You must specify none or exactly one of 'q', 'tth', or 'd'. " + "Please provide either none or one value.", ): original_do.scale_to( target_do, From 00f36ad2ff38985ce74c26563cc13887abd7e735 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 28 Dec 2024 23:35:37 -0500 Subject: [PATCH 546/696] add notimplementederror --- src/diffpy/utils/tools.py | 29 +++++++++++++++++++---------- tests/test_tools.py | 9 --------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index d8b59f10..e1e7ad0a 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -211,6 +211,14 @@ def get_package_info(package_names, metadata=None): return metadata +def get_density_from_cloud(sample_composition, mp_token=""): + """Function to get material density from the MP database. + + It is not implemented yet. + """ + raise NotImplementedError + + def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): """Compute the attenuation coefficient (mu) using the XrayDB database. @@ -242,14 +250,15 @@ def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None "You must specify either sample_mass_density or packing_fraction, but not both. " "Please rerun specifying only one." ) - if sample_mass_density is not None: - mu = material_mu(sample_composition, energy * 1000, density=sample_mass_density, kind="total") / 10 - else: - warnings.warn( - "Warning: Density is set to None if a packing fraction is specified, " - "which may cause errors for some materials. " - "We recommend specifying sample mass density for now. " - "Auto-density calculation is coming soon." - ) - mu = material_mu(sample_composition, energy * 1000, density=None, kind="total") * packing_fraction / 10 + if packing_fraction is None: + packing_fraction = 1 + try: + sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction + except NotImplementedError: + warnings.warn( + "Density computation is not implemented right now. " + "Please rerun specifying a sample mass density." + ) + energy_eV = energy * 1000 + mu = material_mu(sample_composition, energy_eV, density=sample_mass_density, kind="total") / 10 return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index a3363320..d02996ca 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -184,15 +184,6 @@ def test_get_package_info(monkeypatch, inputs, expected): }, 1.2522, ), - ( # C2: Composition, energy, and packing fraction provided, expect to get mu based on packing fraction - # Reuse pattern from C1.1 here - { - "sample_composition": "quartz", - "energy": 10, - "packing_fraction": 0.5, - }, - 2.5184, - ), ], ) def test_compute_mu_using_xraydb(inputs, expected_mu): From 11c41669b5c5cc7fc1d8340ac0011dfdce6f0782 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 28 Dec 2024 23:57:16 -0500 Subject: [PATCH 547/696] refactor: add yarrays together instead of xarrays for __add__ --- src/diffpy/utils/diffraction_objects.py | 61 +++++++++++++------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index e741d94a..25e09381 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -14,15 +14,15 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -x_grid_length_mismatch_emsg = ( - "The two objects have different x-array lengths. " - "Please ensure the length of the x-value during initialization is identical." +y_grid_length_mismatch_emsg = ( + "The two objects have different y-array lengths. " + "Please ensure the length of the y-value during initialization is identical." ) invalid_add_type_emsg = ( "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " "Please rerun by adding another DiffractionObject instance or a scalar value. " - "e.g., my_do_1 + my_do_2 or my_do + 10" + "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @@ -175,56 +175,57 @@ def __eq__(self, other): return True def __add__(self, other): - """Add a scalar value or another DiffractionObject to the xarrays of - the DiffractionObject. + """Add a scalar value or another DiffractionObject to the yarray of the + DiffractionObject. Parameters ---------- other : DiffractionObject or int or float The object to add to the current DiffractionObject. If `other` is a scalar value, - it will be added to all xarrays. The length of the xarrays must match if `other` is + it will be added to all yarray. The length of the yarray must match if `other` is an instance of DiffractionObject. Returns ------- DiffractionObject - The new and deep-copied DiffractionObject instance after adding values to the xarrays. + The new and deep-copied DiffractionObject instance after adding values to the yarray. Raises ------ ValueError - Raised when the length of the xarrays of the two DiffractionObject instances do not match. + Raised when the length of the yarray of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. Examples -------- - Add a scalar value to the xarrays of the DiffractionObject instance: + Add a scalar value to the yarray of the DiffractionObject instance: >>> new_do = my_do + 10.1 + >>> new_do = 10.1 + my_do - Add the xarrays of two DiffractionObject instances: + Add the yarray of two DiffractionObject instances: >>> new_do = my_do_1 + my_do_2 """ + self._check_operation_compatibility(other) summed_do = deepcopy(self) - # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): - summed_do._all_arrays[:, 1] += other - summed_do._all_arrays[:, 2] += other - summed_do._all_arrays[:, 3] += other - # Add xarrays of two DiffractionObject instances - elif isinstance(other, DiffractionObject): - if len(self.on_tth()[0]) != len(other.on_tth()[0]): - raise ValueError(x_grid_length_mismatch_emsg) - summed_do._all_arrays[:, 1] += other.on_q()[0] - summed_do._all_arrays[:, 2] += other.on_tth()[0] - summed_do._all_arrays[:, 3] += other.on_d()[0] - else: - raise TypeError(invalid_add_type_emsg) + summed_do._all_arrays[:, 0] += other + if isinstance(other, DiffractionObject): + summed_do._all_arrays[:, 0] += other.all_arrays[:, 0] return summed_do __radd__ = __add__ + def _check_operation_compatibility(self, other): + if not isinstance(other, (DiffractionObject, int, float)): + raise TypeError(invalid_add_type_emsg) + if isinstance(other, DiffractionObject): + self_yarray = self.all_arrays[:, 0] + other_yarray = other.all_arrays[:, 0] + if len(self_yarray) != len(other_yarray): + raise ValueError(y_grid_length_mismatch_emsg) + def __sub__(self, other): subtracted = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): @@ -233,7 +234,7 @@ def __sub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] subtracted.on_q[1] = self.on_q[1] - other.on_q[1] @@ -247,7 +248,7 @@ def __rsub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] subtracted.on_q[1] = other.on_q[1] - self.on_q[1] @@ -261,7 +262,7 @@ def __mul__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -273,7 +274,7 @@ def __rmul__(self, other): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -287,7 +288,7 @@ def __truediv__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] divided.on_q[1] = self.on_q[1] / other.on_q[1] @@ -299,7 +300,7 @@ def __rtruediv__(self, other): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] divided.on_q[1] = other.on_q[1] / self.on_q[1] From 846c72ae76f29426ae009ed257c17226484a6a8d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 28 Dec 2024 23:57:55 -0500 Subject: [PATCH 548/696] test: combine do with another do or scalar value tests --- tests/conftest.py | 14 +++++-- tests/test_diffraction_objects.py | 64 ++++++++++++++++++------------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5eaaa902..9e5f1e60 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,6 +47,12 @@ def do_minimal_tth(): return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") +@pytest.fixture +def do_minimal_d(): + # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values + return DiffractionObject(wavelength=1.54, xarray=np.array([1, 2]), yarray=np.array([1, 2]), xtype="d") + + @pytest.fixture def wavelength_warning_msg(): return ( @@ -70,13 +76,13 @@ def invalid_add_type_error_msg(): return ( "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " "Please rerun by adding another DiffractionObject instance or a scalar value. " - "e.g., my_do_1 + my_do_2 or my_do + 10" + "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @pytest.fixture -def x_grid_size_mismatch_error_msg(): +def y_grid_size_mismatch_error_msg(): return ( - "The two objects have different x-array lengths. " - "Please ensure the length of the x-value during initialization is identical." + "The two objects have different y-array lengths. " + "Please ensure the length of the y-value during initialization is identical." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ab7eaf26..649de01b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -715,47 +715,57 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( "starting_all_arrays, scalar_to_add, expected_all_arrays", [ - # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values - ( # C1: Add integer of 5, expect xarray to increase by by 5 + # Test scalar addition to yarray values (intensity) and expect no change to xarrays (q, tth, d) + ( # C1: Add integer of 5, expect yarray to increase by by 5 np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5, - np.array([[1.0, 5.51763809, 35.0, 17.13818192], [2.0, 6.0, 65.0, 11.28318531]]), + np.array([[6.0, 0.51763809, 30.0, 12.13818192], [7.0, 1.0, 60.0, 6.28318531]]), ), - ( # C2: Add float of 5.1, expect xarray to be added by 5.1 + ( # C2: Add float of 5.1, expect yarray to be added by 5.1 np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5.1, - np.array([[1.0, 5.61763809, 35.1, 17.23818192], [2.0, 6.1, 65.1, 11.38318531]]), + np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), ), ], ) def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - do_sum_RHS = do + scalar_to_add - do_sum_LHS = scalar_to_add + do - assert np.allclose(do_sum_RHS.all_arrays, expected_all_arrays) - assert np.allclose(do_sum_LHS.all_arrays, expected_all_arrays) + do_scalar_right_sum = do + scalar_to_add + assert np.allclose(do_scalar_right_sum.all_arrays, expected_all_arrays) + do_scalar_left_sum = scalar_to_add + do + assert np.allclose(do_scalar_left_sum.all_arrays, expected_all_arrays) @pytest.mark.parametrize( - "LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum", + "do_1_all_arrays, " + "do_2_all_arrays, " + "expected_do_1_all_arrays_with_y_summed, " + "expected_do_2_all_arrays_with_y_summed", [ # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray - ( # C1: Add two DO objects with identical xarray values, expect sum of xarray values + ( # C1: Add two DO objects, expect sum of yarray values (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), - (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), - np.array([[1.0, 1.03527618, 60.0, 24.27636384], [2.0, 2.0, 120.0, 12.56637061]]), + (np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]),), + (np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]),), + (np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]),), ), ], ) -def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum, do_minimal_tth): - assert np.allclose(do_minimal_tth.all_arrays, LHS_all_arrays) - do_LHS = do_minimal_tth - do_RHS = do_minimal_tth - do_sum = do_LHS + do_RHS - assert np.allclose(do_LHS.all_arrays, LHS_all_arrays) - assert np.allclose(do_RHS.all_arrays, RHS_all_arrays) - assert np.allclose(do_sum.all_arrays, expected_all_arrays_sum) +def test_addition_operator_by_another_do( + do_1_all_arrays, + do_2_all_arrays, + expected_do_1_all_arrays_with_y_summed, + expected_do_2_all_arrays_with_y_summed, + do_minimal_tth, + do_minimal_d, +): + do_1 = do_minimal_tth + assert np.allclose(do_1.all_arrays, do_1_all_arrays) + do_2 = do_minimal_d + assert np.allclose(do_2.all_arrays, do_2_all_arrays) + assert np.allclose((do_1 + do_2).all_arrays, expected_do_1_all_arrays_with_y_summed) + assert np.allclose((do_2 + do_1).all_arrays, expected_do_2_all_arrays_with_y_summed) def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @@ -767,9 +777,11 @@ def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_m "string_value" + do -def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): +def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays - do_LHS = do_minimal - do_RHS = do_minimal_tth - with pytest.raises(ValueError, match=re.escape(x_grid_size_mismatch_error_msg)): - do_LHS + do_RHS + do_1 = do_minimal + do_2 = do_minimal_tth + assert len(do_1.all_arrays[:, 0]) == 0 + assert len(do_2.all_arrays[:, 0]) == 2 + with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): + do_1 + do_2 From da70bd6629ab7589bd628feca625f106c5b515a0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 01:00:04 -0500 Subject: [PATCH 549/696] test: changed xarray to yarray in one of the test cases --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 649de01b..ccab71fe 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -743,7 +743,7 @@ def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expecte "expected_do_1_all_arrays_with_y_summed, " "expected_do_2_all_arrays_with_y_summed", [ - # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray + # Test addition of two DO objects, expect combined yarray values and no change to xarrays ((q, tth, d) ( # C1: Add two DO objects, expect sum of yarray values (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), (np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]),), From 255c603a54ccc0d680a8a851308355114663ca6c Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 15:07:29 -0500 Subject: [PATCH 550/696] refactor: rearrange error msg and docstring, update condition in function --- src/diffpy/utils/tools.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index e1e7ad0a..9c963e14 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,6 +1,5 @@ import importlib.metadata import json -import warnings from copy import copy from pathlib import Path @@ -212,11 +211,15 @@ def get_package_info(package_names, metadata=None): def get_density_from_cloud(sample_composition, mp_token=""): - """Function to get material density from the MP database. + """Function to get material density from the MP or COD database. It is not implemented yet. """ - raise NotImplementedError + raise NotImplementedError( + "So sorry, density computation from composition is not implemented right now. " + "We hope to have this implemented in the next release. " + "Please rerun specifying a sample mass density." + ) def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): @@ -250,15 +253,8 @@ def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None "You must specify either sample_mass_density or packing_fraction, but not both. " "Please rerun specifying only one." ) - if packing_fraction is None: - packing_fraction = 1 - try: - sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction - except NotImplementedError: - warnings.warn( - "Density computation is not implemented right now. " - "Please rerun specifying a sample mass density." - ) + if packing_fraction is not None: + sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction energy_eV = energy * 1000 mu = material_mu(sample_composition, energy_eV, density=sample_mass_density, kind="total") / 10 return mu From 630011a6210663b3428e81784a921efab5d3624f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 16:28:47 -0500 Subject: [PATCH 551/696] refactor: reorder docstring and tests, change offset default to None, update tests for flexible inputs --- src/diffpy/utils/diffraction_objects.py | 13 ++-- tests/test_diffraction_objects.py | 87 ++++++++----------------- 2 files changed, 34 insertions(+), 66 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 413d9eac..36386ce1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -396,14 +396,14 @@ def on_tth(self): def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] - def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): + def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): """Returns a new diffraction object which is the current object but rescaled in y to the target. + By default, if none of `q`, `tth`, or `d` are provided, + the scaling is based on the maximal x-array value from both objects. The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If none of `q`, `tth`, or `d` are provided, - the scaling will be based on the maximal x-array value from both objects. If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. Parameters @@ -411,17 +411,20 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, default is q with the maximal x-array value of the current object + q, tth, d : float, optional, default is q with the maximal x-array value from each object The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. - offset : float, optional, default is 0 + offset : float, optional, default is None an offset to add to the scaled y-values Returns ------- the rescaled DiffractionObject as a new object """ + if offset is None: + offset = 0 + scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) if count > 1: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 789a5e77..6499bdd7 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -191,7 +191,23 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether the original y-array is scaled as expected - ( # C1: Same x-arrays + ( # C1: none of q, tth, d, provided, expect to scale on the maximal x-arrays + { + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "yarray": np.array([5, 10, 20, 30]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + {}, + {"xtype": "q", "yarray": np.array([10, 20, 30])}, + ), + ( # C2: Same x-arrays # x-value has exact matches at tth=60 (y=60) and tth=60 (y=6), # for original and target diffraction objects, # expect original y-array to multiply by 6/60=1/10 @@ -207,15 +223,10 @@ def test_init_invalid_xtype(): "xtype": "tth", "wavelength": 2 * np.pi, }, - { - "q": None, - "tth": 60, - "d": None, - "offset": 0, - }, + {"tth": 60}, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - ( # C2: Different x-arrays with same length, + ( # C3: Different x-arrays with same length, # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) # for original and target diffraction objects, # expect original y-array to multiply by 1/10 @@ -231,15 +242,10 @@ def test_init_invalid_xtype(): "xtype": "q", "wavelength": 2 * np.pi, }, - { - "q": 0.1, - "tth": None, - "d": None, - "offset": 0, - }, + {"q": 0.1}, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - ( # C3: Different x-array lengths + ( # C4: Different x-array lengths # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), # for original and target diffraction objects, # expect original y-array to multiply by 5/50=1/10 @@ -255,15 +261,10 @@ def test_init_invalid_xtype(): "xtype": "tth", "wavelength": 2 * np.pi, }, - { - "q": None, - "tth": 60, - "d": None, - "offset": 0, - }, + {"tth": 60}, {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), - ( # C4: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 + ( # C5: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([2, 3, 4, 5, 6, 7]), @@ -276,43 +277,15 @@ def test_init_invalid_xtype(): "xtype": "tth", "wavelength": 2 * np.pi, }, - { - "q": None, - "tth": 60, - "d": None, - "offset": 2.1, - }, + {"tth": 60, "offset": 2.1}, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - ( # C5: none of q, tth, d, provided, expect to scale on the maximal x-arrays - { - "xarray": np.array([0.1, 0.2, 0.3]), - "yarray": np.array([1, 2, 3]), - "xtype": "q", - "wavelength": 2 * np.pi, - }, - { - "xarray": np.array([0.05, 0.1, 0.2, 0.3]), - "yarray": np.array([5, 10, 20, 30]), - "xtype": "q", - "wavelength": 2 * np.pi, - }, - { - "q": None, - "tth": None, - "d": None, - "offset": 0, - }, - {"xtype": "q", "yarray": np.array([10, 20, 30])}, - ), ], ) def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): original_do = DiffractionObject(**org_do_args) target_do = DiffractionObject(**target_do_args) - scaled_do = original_do.scale_to( - target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"] - ) + scaled_do = original_do.scale_to(target_do, **scale_inputs) # Check the intensity data is the same as expected assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) @@ -335,10 +308,8 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "wavelength": 2 * np.pi, }, { - "q": None, "tth": 60, "d": 10, - "offset": 0, }, ), ], @@ -351,13 +322,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): match="You must specify none or exactly one of 'q', 'tth', or 'd'. " "Please provide either none or one value.", ): - original_do.scale_to( - target_do, - q=scale_inputs["q"], - tth=scale_inputs["tth"], - d=scale_inputs["d"], - offset=scale_inputs["offset"], - ) + original_do.scale_to(target_do, **scale_inputs) @pytest.mark.parametrize( From 66c67c6a169ce38f1dd3256f24bb03e4c94972b2 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 16:50:45 -0500 Subject: [PATCH 552/696] docs: clarify docstring and news --- news/scaleto-max.rst | 2 +- src/diffpy/utils/diffraction_objects.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/news/scaleto-max.rst b/news/scaleto-max.rst index 0037f533..d70e9d7f 100644 --- a/news/scaleto-max.rst +++ b/news/scaleto-max.rst @@ -1,6 +1,6 @@ **Added:** -* new feature in `scale_to()` to run without specifying an x-value +* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object. **Changed:** diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 36386ce1..80202cd3 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -400,9 +400,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): """Returns a new diffraction object which is the current object but rescaled in y to the target. - By default, if none of `q`, `tth`, or `d` are provided, - the scaling is based on the maximal x-array value from both objects. - The y-value in the target at the closest specified x-value will be used as the factor to scale to. + By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max q-value from each object. + Otherwise, y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. @@ -411,7 +410,7 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, default is q with the maximal x-array value from each object + q, tth, d : float, optional, default is the max q-value from each object The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. From 0a155aaf231c3dc237e7f6ddeefde23e64d5a4c9 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 17:01:33 -0500 Subject: [PATCH 553/696] docs: edit example file --- .../examples/diffraction_objects_example.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index aa4de157..f956081c 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -106,23 +106,26 @@ we would replace the code above with The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new variable and make use of, +The default behavior is to align the objects based on the maximal q-value of each diffraction object, +so they will align at the intensity at these indices. + .. code-block:: python - scaled_measured = measured.scale_to(calculated, q=5.5) + scaled_measured = measured.scale_to(calculated) -For convenience, you can also apply an offset to the scaled new diffraction object with the optional -``offset`` argument, for example, +If this doesn't give the desirable results, you can specify an ``xtype=value`` to scale +based on the closest x-value in both objects. For example: .. code-block:: python - scaled_and_offset_measured = measured.scale_to(calculated, q=5.5, offset=0.5) + scaled_measured = measured.scale_to(calculated, q=5.5) -You can call `scale_to()` without specifying a value for `q`, `tth`, or `d`. -In this case, the scaling will be done based on the maximal x-array value of both diffraction objects: +For convenience, you can also apply an offset to the scaled new diffraction object with the optional +``offset`` argument, for example, .. code-block:: python - scaled_measured = measured.scale_to(calculated) + scaled_and_offset_measured = measured.scale_to(calculated, q=5.5, offset=0.5) DiffractionObject convenience functions --------------------------------------- From 46bc3d18d8ce8b2d190b1cf3fc1a6d8e65dd18db Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 17:10:44 -0500 Subject: [PATCH 554/696] docs: add news --- news/muD_calculator.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/muD_calculator.rst diff --git a/news/muD_calculator.rst b/news/muD_calculator.rst new file mode 100644 index 00000000..fc21cef3 --- /dev/null +++ b/news/muD_calculator.rst @@ -0,0 +1,23 @@ +**Added:** + +* function to compute muD from a given z-scan file + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From bbb6151ebb2db0a1c902a43e43044d2359508fa8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 17:11:58 -0500 Subject: [PATCH 555/696] feat: move muD calculator from labpdfproc to here --- src/diffpy/utils/tools.py | 122 ++++++++++++++++++++++++++++++++------ tests/test_tools.py | 25 +++++++- 2 files changed, 128 insertions(+), 19 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index b7a4c47d..040e62ea 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -3,25 +3,11 @@ from copy import copy from pathlib import Path +import numpy as np +from scipy.optimize import dual_annealing +from scipy.signal import convolve -def clean_dict(obj): - """Remove keys from the dictionary where the corresponding value is None. - - Parameters - ---------- - obj: dict - The dictionary to clean. If None, initialize as an empty dictionary. - - Returns - ------- - dict: - The cleaned dictionary with keys removed where the value is None. - """ - obj = obj if obj is not None else {} - for key, value in copy(obj).items(): - if not value: - del obj[key] - return obj +from diffpy.utils.parsers.loaddata import loadData def _stringify(obj): @@ -206,3 +192,103 @@ def get_package_info(package_names, metadata=None): pkg_info.update({package: importlib.metadata.version(package)}) metadata["package_info"] = pkg_info return metadata + + +def _top_hat(z, half_slit_width): + """Create a top-hat function, return 1.0 for values within the specified + slit width and 0 otherwise.""" + return np.where((z >= -half_slit_width) & (z <= half_slit_width), 1.0, 0.0) + + +def _model_function(z, diameter, z0, I0, mud, slope): + """ + Compute the model function with the following steps: + 1. Let dz = z-z0, so that dz is centered at 0 + 2. Compute length l that is the effective length for computing intensity I = I0 * e^{-mu * l}: + - For dz within the capillary diameter, l is the chord length of the circle at position dz + - For dz outside this range, l = 0 + 3. Apply a linear adjustment to I0 by taking I0 as I0 - slope * z + """ + min_radius = -diameter / 2 + max_radius = diameter / 2 + dz = z - z0 + length = np.piecewise( + dz, + [dz < min_radius, (min_radius <= dz) & (dz <= max_radius), dz > max_radius], + [0, lambda dz: 2 * np.sqrt((diameter / 2) ** 2 - dz**2), 0], + ) + return (I0 - slope * z) * np.exp(-mud / diameter * length) + + +def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): + """Extend z values and I values for padding (so that we don't have tails in + convolution), then perform convolution (note that the convolved I values + are the same as modeled I values if slit width is close to 0)""" + n_points = len(z) + z_left_pad = np.linspace(z.min() - n_points * (z[1] - z[0]), z.min(), n_points) + z_right_pad = np.linspace(z.max(), z.max() + n_points * (z[1] - z[0]), n_points) + z_extended = np.concatenate([z_left_pad, z, z_right_pad]) + I_extended = _model_function(z_extended, diameter, z0, I0, mud, slope) + kernel = _top_hat(z_extended - z_extended.mean(), half_slit_width) + I_convolved = I_extended # this takes care of the case where slit width is close to 0 + if kernel.sum() != 0: + kernel /= kernel.sum() + I_convolved = convolve(I_extended, kernel, mode="same") + padding_length = len(z_left_pad) + return I_convolved[padding_length:-padding_length] + + +def _objective_function(params, z, observed_data): + """Compute the objective function for fitting a model to the + observed/experimental data by minimizing the sum of squared residuals + between the observed data and the convolved model data.""" + diameter, half_slit_width, z0, I0, mud, slope = params + convolved_model_data = _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope) + residuals = observed_data - convolved_model_data + return np.sum(residuals**2) + + +def _compute_single_mud(z_data, I_data): + """Perform dual annealing optimization and extract the parameters.""" + bounds = [ + (1e-5, z_data.max() - z_data.min()), # diameter: [small positive value, upper bound] + (0, (z_data.max() - z_data.min()) / 2), # half slit width: [0, upper bound] + (z_data.min(), z_data.max()), # z0: [min z, max z] + (1e-5, I_data.max()), # I0: [small positive value, max observed intensity] + (1e-5, 20), # muD: [small positive value, upper bound] + (-100000, 100000), # slope: [lower bound, upper bound] + ] + result = dual_annealing(_objective_function, bounds, args=(z_data, I_data)) + diameter, half_slit_width, z0, I0, mud, slope = result.x + convolved_fitted_signal = _extend_z_and_convolve(z_data, diameter, half_slit_width, z0, I0, mud, slope) + residuals = I_data - convolved_fitted_signal + rmse = np.sqrt(np.mean(residuals**2)) + return mud, rmse + + +def compute_mud(filepath): + """Compute the best-fit mu*D value from a z-scan file, removing the sample + holder effect. + + This function loads z-scan data and fits it to a model + that convolves a top-hat function with I = I0 * e^{-mu * l}. + The fitting procedure is run multiple times, and we return the best-fit parameters based on the lowest rmse. + + The full mathematical details are described in the paper: + An ad hoc Absorption Correction for Reliable Pair-Distribution Functions from Low Energy x-ray Sources, + Yucong Chen, Till Schertenleib, Andrew Yang, Pascal Schouwink, Wendy L. Queen and Simon J. L. Billinge, + in preparation. + + Parameters + ---------- + filepath : str + The path to the z-scan file. + + Returns + ------- + mu*D : float + The best-fit mu*D value. + """ + z_data, I_data = loadData(filepath, unpack=True) + best_mud, _ = min((_compute_single_mud(z_data, I_data) for _ in range(20)), key=lambda pair: pair[1]) + return best_mud diff --git a/tests/test_tools.py b/tests/test_tools.py index 3808537d..fa8b3c32 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -3,9 +3,16 @@ import os from pathlib import Path +import numpy as np import pytest -from diffpy.utils.tools import check_and_build_global_config, get_package_info, get_user_info +from diffpy.utils.tools import ( + _extend_z_and_convolve, + check_and_build_global_config, + compute_mud, + get_package_info, + get_user_info, +) @pytest.mark.parametrize( @@ -160,3 +167,19 @@ def test_get_package_info(monkeypatch, inputs, expected): ) actual_metadata = get_package_info(inputs[0], metadata=inputs[1]) assert actual_metadata == expected + + +def test_compute_mud(tmp_path): + diameter, slit_width, z0, I0, mud, slope = 1, 0.1, 0, 1e5, 3, 0 + z_data = np.linspace(-1, 1, 50) + convolved_I_data = _extend_z_and_convolve(z_data, diameter, slit_width, z0, I0, mud, slope) + + directory = Path(tmp_path) + file = directory / "testfile" + with open(file, "w") as f: + for x, I in zip(z_data, convolved_I_data): + f.write(f"{x}\t{I}\n") + + expected_mud = 3 + actual_mud = compute_mud(file) + assert actual_mud == pytest.approx(expected_mud, rel=1e-4, abs=1e-3) From 7f94407ba76f05a3909b8fa4065e8cf296d45cfe Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 17:13:26 -0500 Subject: [PATCH 556/696] build: add scipy package to requirements for pip and conda --- requirements/conda.txt | 1 + requirements/pip.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements/conda.txt b/requirements/conda.txt index 24ce15ab..6bad1038 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -1 +1,2 @@ numpy +scipy diff --git a/requirements/pip.txt b/requirements/pip.txt index 24ce15ab..6bad1038 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1 +1,2 @@ numpy +scipy From 630f00e7cd1898eab370f1a79006a65acc188936 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:11:53 -0500 Subject: [PATCH 557/696] feat: implement __sub__ feature for DiffractionObject --- src/diffpy/utils/diffraction_objects.py | 76 ++++++-------------- tests/test_diffraction_objects.py | 96 +++++++++++++++++-------- 2 files changed, 88 insertions(+), 84 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25e09381..58393ef4 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -217,42 +217,16 @@ def __add__(self, other): __radd__ = __add__ - def _check_operation_compatibility(self, other): - if not isinstance(other, (DiffractionObject, int, float)): - raise TypeError(invalid_add_type_emsg) - if isinstance(other, DiffractionObject): - self_yarray = self.all_arrays[:, 0] - other_yarray = other.all_arrays[:, 0] - if len(self_yarray) != len(other_yarray): - raise ValueError(y_grid_length_mismatch_emsg) - def __sub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = self.on_tth[1] - other - subtracted.on_q[1] = self.on_q[1] - other - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to subtract two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] - subtracted.on_q[1] = self.on_q[1] - other.on_q[1] - return subtracted + self._check_operation_compatibility(other) + subtracted_do = deepcopy(self) + if isinstance(other, (int, float)): + subtracted_do._all_arrays[:, 0] -= other + if isinstance(other, DiffractionObject): + subtracted_do._all_arrays[:, 0] -= other.all_arrays[:, 0] + return subtracted_do - def __rsub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = other - self.on_tth[1] - subtracted.on_q[1] = other - self.on_q[1] - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to subtract two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] - subtracted.on_q[1] = other.on_q[1] - self.on_q[1] - return subtracted + __rsub__ = __sub__ def __mul__(self, other): multiplied = deepcopy(self) @@ -268,19 +242,10 @@ def __mul__(self, other): multiplied.on_q[1] = self.on_q[1] * other.on_q[1] return multiplied - def __rmul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied + __rmul__ = __mul__ def __truediv__(self, other): + divided = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): divided.on_tth[1] = other / self.on_tth[1] @@ -294,17 +259,16 @@ def __truediv__(self, other): divided.on_q[1] = self.on_q[1] / other.on_q[1] return divided - def __rtruediv__(self, other): - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] - divided.on_q[1] = other.on_q[1] / self.on_q[1] - return divided + __rmul__ = __mul__ + + def _check_operation_compatibility(self, other): + if not isinstance(other, (DiffractionObject, int, float)): + raise TypeError(invalid_add_type_emsg) + if isinstance(other, DiffractionObject): + self_yarray = self.all_arrays[:, 0] + other_yarray = other.all_arrays[:, 0] + if self_yarray.shape != other_yarray.shape: + raise ValueError(y_grid_length_mismatch_emsg) @property def all_arrays(self): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ccab71fe..979a7efe 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -713,59 +713,93 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "starting_all_arrays, scalar_to_add, expected_all_arrays", + "operation, starting_all_arrays, scalar_value, expected_all_arrays", [ - # Test scalar addition to yarray values (intensity) and expect no change to xarrays (q, tth, d) - ( # C1: Add integer of 5, expect yarray to increase by by 5 + # C1: Test scalar addition to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Add integer 5 + "add", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5, np.array([[6.0, 0.51763809, 30.0, 12.13818192], [7.0, 1.0, 60.0, 6.28318531]]), ), - ( # C2: Add float of 5.1, expect yarray to be added by 5.1 + ( # 2. Add float 5.1 + "add", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5.1, np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), ), + # C2. Test scalar subtraction to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Subtract integer 1 + "sub", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 1, + np.array([[0.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + ), + ( # 2. Subtract float 0.5 + "sub", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 0.5, + np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), + ), ], ) -def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): +def test_scalar_operations(operation, starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - do_scalar_right_sum = do + scalar_to_add - assert np.allclose(do_scalar_right_sum.all_arrays, expected_all_arrays) - do_scalar_left_sum = scalar_to_add + do - assert np.allclose(do_scalar_left_sum.all_arrays, expected_all_arrays) + + if operation == "add": + result_right = do + scalar_value + result_left = scalar_value + do + elif operation == "sub": + result_right = do - scalar_value + result_left = scalar_value - do + + assert np.allclose(result_right.all_arrays, expected_all_arrays) + assert np.allclose(result_left.all_arrays, expected_all_arrays) @pytest.mark.parametrize( - "do_1_all_arrays, " - "do_2_all_arrays, " - "expected_do_1_all_arrays_with_y_summed, " - "expected_do_2_all_arrays_with_y_summed", + "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ - # Test addition of two DO objects, expect combined yarray values and no change to xarrays ((q, tth, d) - ( # C1: Add two DO objects, expect sum of yarray values - (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), - (np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]),), - (np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]),), - (np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]),), + # Test addition of two DO objects, expect combined yarray values + ( + "add", + np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + ), + # Test subtraction of two DO objects, expect differences in yarray values + ( + "sub", + np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), + np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), ], ) -def test_addition_operator_by_another_do( - do_1_all_arrays, - do_2_all_arrays, - expected_do_1_all_arrays_with_y_summed, - expected_do_2_all_arrays_with_y_summed, +def test_binary_operator_on_do( + operation, + expected_do_1_all_arrays_with_y_modified, + expected_do_2_all_arrays_with_y_modified, do_minimal_tth, do_minimal_d, ): do_1 = do_minimal_tth - assert np.allclose(do_1.all_arrays, do_1_all_arrays) do_2 = do_minimal_d - assert np.allclose(do_2.all_arrays, do_2_all_arrays) - assert np.allclose((do_1 + do_2).all_arrays, expected_do_1_all_arrays_with_y_summed) - assert np.allclose((do_2 + do_1).all_arrays, expected_do_2_all_arrays_with_y_summed) + assert np.allclose( + do_1.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) + ) + assert np.allclose( + do_2.all_arrays, np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]) + ) + + if operation == "add": + do_1_y_modified = do_1 + do_2 + do_2_y_modified = do_2 + do_1 + elif operation == "sub": + do_1_y_modified = do_1 - do_2 + do_2_y_modified = do_2 - do_1 + + assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) + assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @@ -775,6 +809,10 @@ def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_m do + "string_value" with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): "string_value" + do + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + do - "string_value" + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + "string_value" - do def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): @@ -785,3 +823,5 @@ def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_g assert len(do_2.all_arrays[:, 0]) == 2 with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): do_1 + do_2 + with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): + do_1 - do_2 From ddeeb266a7b9e60e0e5b40e1059b43cab3d0d954 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:17:43 -0500 Subject: [PATCH 558/696] feat: implement __mul__ for DiffracitonObject wih test funcs --- src/diffpy/utils/diffraction_objects.py | 19 +++++++------------ tests/test_diffraction_objects.py | 25 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 58393ef4..1a88a147 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -229,18 +229,13 @@ def __sub__(self, other): __rsub__ = __sub__ def __mul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to multiply two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied + self._check_operation_compatibility(other) + multiplied_do = deepcopy(self) + if isinstance(other, (int, float)): + multiplied_do._all_arrays[:, 0] *= other + if isinstance(other, DiffractionObject): + multiplied_do._all_arrays[:, 0] *= other.all_arrays[:, 0] + return multiplied_do __rmul__ = __mul__ diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 979a7efe..a21ed57d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -741,18 +741,33 @@ def test_copy_object(do_minimal): 0.5, np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), ), + # C2. Test scalar multiplication to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Multipliy by integer 2 + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 2, + np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + ), + ( # 2. Multipliy by float 0.5 + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 2.5, + np.array([[2.5, 0.51763809, 30.0, 12.13818192], [5.0, 1.0, 60.0, 6.28318531]]), + ), ], ) def test_scalar_operations(operation, starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - if operation == "add": result_right = do + scalar_value result_left = scalar_value + do elif operation == "sub": result_right = do - scalar_value result_left = scalar_value - do + elif operation == "mul": + result_right = do * scalar_value + result_left = scalar_value * do assert np.allclose(result_right.all_arrays, expected_all_arrays) assert np.allclose(result_left.all_arrays, expected_all_arrays) @@ -773,6 +788,11 @@ def test_scalar_operations(operation, starting_all_arrays, scalar_value, expecte np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), + ( + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + ), ], ) def test_binary_operator_on_do( @@ -797,6 +817,9 @@ def test_binary_operator_on_do( elif operation == "sub": do_1_y_modified = do_1 - do_2 do_2_y_modified = do_2 - do_1 + elif operation == "mul": + do_1_y_modified = do_1 * do_2 + do_2_y_modified = do_2 * do_1 assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) From eb2941b4e0613aabbb7d182d80b47602796ab952 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:42:53 -0500 Subject: [PATCH 559/696] feat: add __div__ function to DiffractionObject --- src/diffpy/utils/diffraction_objects.py | 22 ++-- tests/test_diffraction_objects.py | 139 +++++++++++++++--------- 2 files changed, 97 insertions(+), 64 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1a88a147..477b1f2c 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -240,21 +240,15 @@ def __mul__(self, other): __rmul__ = __mul__ def __truediv__(self, other): + self._check_operation_compatibility(other) + divided_do = deepcopy(self) + if isinstance(other, (int, float)): + divided_do._all_arrays[:, 0] /= other + if isinstance(other, DiffractionObject): + divided_do._all_arrays[:, 0] /= other.all_arrays[:, 0] + return divided_do - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to multiply two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] - divided.on_q[1] = self.on_q[1] / other.on_q[1] - return divided - - __rmul__ = __mul__ + __rtruediv__ = __truediv__ def _check_operation_compatibility(self, other): if not isinstance(other, (DiffractionObject, int, float)): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a21ed57d..6773aced 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -713,64 +713,84 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "operation, starting_all_arrays, scalar_value, expected_all_arrays", + "operation, starting_yarray, scalar_value, expected_yarray", [ - # C1: Test scalar addition to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Add integer 5 + # C1: Test scalar addition to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Add 5 "add", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 5, - np.array([[6.0, 0.51763809, 30.0, 12.13818192], [7.0, 1.0, 60.0, 6.28318531]]), + np.array([6.0, 7.0]), ), - ( # 2. Add float 5.1 + ( # 2. Add 5.1 "add", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 5.1, - np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), + np.array([6.1, 7.1]), ), - # C2. Test scalar subtraction to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Subtract integer 1 + # C2: Test scalar subtraction to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Subtract 1 "sub", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 1, - np.array([[0.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + np.array([0.0, 1.0]), ), - ( # 2. Subtract float 0.5 + ( # 2. Subtract 0.5 "sub", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 0.5, - np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), + np.array([0.5, 1.5]), ), - # C2. Test scalar multiplication to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Multipliy by integer 2 + # C3: Test scalar multiplication to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Multiply by 2 "mul", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 2, - np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array([2.0, 4.0]), ), - ( # 2. Multipliy by float 0.5 + ( # 2. Multiply by 2.5 "mul", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 2.5, - np.array([[2.5, 0.51763809, 30.0, 12.13818192], [5.0, 1.0, 60.0, 6.28318531]]), + np.array([2.5, 5.0]), + ), + # C4: Test scalar division to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Divide by 2 + "div", + np.array([1.0, 2.0]), + 2, + np.array([0.5, 1.0]), + ), + ( # 2. Divide by 2.5 + "div", + np.array([1.0, 2.0]), + 2.5, + np.array([0.4, 0.8]), ), ], ) -def test_scalar_operations(operation, starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): +def test_scalar_operations(operation, starting_yarray, scalar_value, expected_yarray, do_minimal_tth): do = do_minimal_tth - assert np.allclose(do.all_arrays, starting_all_arrays) + expected_xarray_constant = np.array([[0.51763809, 30.0, 12.13818192], [1.0, 60.0, 6.28318531]]) + assert np.allclose(do.all_arrays[:, [1, 2, 3]], expected_xarray_constant) + assert np.allclose(do.all_arrays[:, 0], starting_yarray) if operation == "add": - result_right = do + scalar_value - result_left = scalar_value + do + do_right_op = do + scalar_value + do_left_op = scalar_value + do elif operation == "sub": - result_right = do - scalar_value - result_left = scalar_value - do + do_right_op = do - scalar_value + do_left_op = scalar_value - do elif operation == "mul": - result_right = do * scalar_value - result_left = scalar_value * do - - assert np.allclose(result_right.all_arrays, expected_all_arrays) - assert np.allclose(result_left.all_arrays, expected_all_arrays) + do_right_op = do * scalar_value + do_left_op = scalar_value * do + elif operation == "div": + do_right_op = do / scalar_value + do_left_op = scalar_value / do + assert np.allclose(do_right_op.all_arrays[:, 0], expected_yarray) + assert np.allclose(do_left_op.all_arrays[:, 0], expected_yarray) + # Ensure x-values are unchanged + assert np.allclose(do_right_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) + assert np.allclose(do_left_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) @pytest.mark.parametrize( @@ -793,6 +813,11 @@ def test_scalar_operations(operation, starting_all_arrays, scalar_value, expecte np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), ), + ( + "div", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + np.array([[1.0, 6.28318531, 100.70777771, 1], [1.0, 3.14159265, 45.28748053, 2.0]]), + ), ], ) def test_binary_operator_on_do( @@ -820,31 +845,45 @@ def test_binary_operator_on_do( elif operation == "mul": do_1_y_modified = do_1 * do_2 do_2_y_modified = do_2 * do_1 + elif operation == "div": + do_1_y_modified = do_1 / do_2 + do_2_y_modified = do_2 / do_1 assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) -def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): - # Add a string to a DO object, expect TypeError, only scalar (int, float) allowed for addition +def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): do = do_minimal_tth - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do + "string_value" - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - "string_value" + do - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do - "string_value" - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - "string_value" - do - - -def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): - # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays + invalid_value = "string_value" + + operations = [ + (lambda x, y: x + y), # Test addition + (lambda x, y: x - y), # Test subtraction + (lambda x, y: x * y), # Test multiplication + (lambda x, y: x / y), # Test division + ] + + # Test each operation with both orderings of operands + for operation in operations: + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + operation(do, invalid_value) + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + operation(invalid_value, do) + + +@pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) +def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): do_1 = do_minimal do_2 = do_minimal_tth assert len(do_1.all_arrays[:, 0]) == 0 assert len(do_2.all_arrays[:, 0]) == 2 with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): - do_1 + do_2 - with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): - do_1 - do_2 + if operation == "add": + do_1 + do_2 + elif operation == "sub": + do_1 - do_2 + elif operation == "mul": + do_1 * do_2 + elif operation == "div": + do_1 / do_2 From 48e4c5883a4d50782c1b0bdb6f22c6eb67ae04ee Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:45:20 -0500 Subject: [PATCH 560/696] chore: add news --- news/op-mul-sub-div.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/op-mul-sub-div.rst diff --git a/news/op-mul-sub-div.rst b/news/op-mul-sub-div.rst new file mode 100644 index 00000000..9fe75ccc --- /dev/null +++ b/news/op-mul-sub-div.rst @@ -0,0 +1,23 @@ +**Added:** + +* addition, multiplication, subtraction, and division operators between two DiffractionObject instances or a scalar value with another DiffractionObject for modifying yarray (intensity) values. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From e8653efc5613e319e30478aa7acd0c13b7de9cec Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:49:40 -0500 Subject: [PATCH 561/696] test: improve comments for tests --- tests/test_diffraction_objects.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6773aced..6c390e16 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -796,24 +796,23 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya @pytest.mark.parametrize( "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ - # Test addition of two DO objects, expect combined yarray values - ( + ( # Test addition of two DO objects, expect combined yarray values "add", np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), ), - # Test subtraction of two DO objects, expect differences in yarray values - ( + ( # Test subtraction of two DO objects, expect differences in yarray values "sub", np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), - ( + ( # Test multiplication of two DO objects, expect multiplication in yarray values + "mul", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), ), - ( + ( # Test division of two DO objects, expect division in yarray values "div", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [1.0, 3.14159265, 45.28748053, 2.0]]), @@ -856,15 +855,13 @@ def test_binary_operator_on_do( def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): do = do_minimal_tth invalid_value = "string_value" - operations = [ (lambda x, y: x + y), # Test addition (lambda x, y: x - y), # Test subtraction (lambda x, y: x * y), # Test multiplication (lambda x, y: x / y), # Test division ] - - # Test each operation with both orderings of operands + # Add a string to a DiffractionObject, expect TypeError for operation in operations: with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): operation(do, invalid_value) @@ -878,6 +875,7 @@ def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y do_2 = do_minimal_tth assert len(do_1.all_arrays[:, 0]) == 0 assert len(do_2.all_arrays[:, 0]) == 2 + # Add two DO objets with different yarray lengths, expect ValueError with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): if operation == "add": do_1 + do_2 From 116280c1a8dfef0197b90f06cf3a6f3ce9719f52 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Dec 2024 23:50:46 +0000 Subject: [PATCH 562/696] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6c390e16..067fce5d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -807,7 +807,6 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), ( # Test multiplication of two DO objects, expect multiplication in yarray values - "mul", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), From 6656dabc93ffd54ebcc770fab17e8124da9bc5c7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:53:22 -0500 Subject: [PATCH 563/696] test: add higher-level test comments --- tests/test_diffraction_objects.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 067fce5d..17f7ca19 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -715,6 +715,7 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( "operation, starting_yarray, scalar_value, expected_yarray", [ + # Test scalar addition, subtraction, multiplication, and division to y-values by adding a scalar value # C1: Test scalar addition to y-values (intensity), expect no change to x-values (q, tth, d) ( # 1. Add 5 "add", @@ -796,6 +797,7 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya @pytest.mark.parametrize( "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ + # Test addition, subtraction, multiplication, and division of two DO objects ( # Test addition of two DO objects, expect combined yarray values "add", np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), @@ -852,6 +854,7 @@ def test_binary_operator_on_do( def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): + # Add a string to a DiffractionObject, expect TypeError do = do_minimal_tth invalid_value = "string_value" operations = [ @@ -860,7 +863,6 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): (lambda x, y: x * y), # Test multiplication (lambda x, y: x / y), # Test division ] - # Add a string to a DiffractionObject, expect TypeError for operation in operations: with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): operation(do, invalid_value) @@ -870,11 +872,11 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): + # Add two DO objects with different yarray lengths, expect ValueError do_1 = do_minimal do_2 = do_minimal_tth assert len(do_1.all_arrays[:, 0]) == 0 assert len(do_2.all_arrays[:, 0]) == 2 - # Add two DO objets with different yarray lengths, expect ValueError with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): if operation == "add": do_1 + do_2 From b64e224685282c466029d02699ed724bb26b71d2 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 29 Dec 2024 21:03:29 -0500 Subject: [PATCH 564/696] extend news --- news/muD_calculator.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/news/muD_calculator.rst b/news/muD_calculator.rst index fc21cef3..7534c4ff 100644 --- a/news/muD_calculator.rst +++ b/news/muD_calculator.rst @@ -1,6 +1,7 @@ **Added:** -* function to compute muD from a given z-scan file +* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile + from a line-scan through the sample **Changed:** From a23268644f2275bc1c4a7b3408f3c27bc8867497 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:03:37 +0000 Subject: [PATCH 565/696] [pre-commit.ci] auto fixes from pre-commit hooks --- news/muD_calculator.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/muD_calculator.rst b/news/muD_calculator.rst index 7534c4ff..a6731760 100644 --- a/news/muD_calculator.rst +++ b/news/muD_calculator.rst @@ -1,6 +1,6 @@ **Added:** -* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile +* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile from a line-scan through the sample **Changed:** From 13f227dfb03bfc7b7e20b4fbc3d31891d615e689 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 21:04:49 -0500 Subject: [PATCH 566/696] docs: improve docstring --- src/diffpy/utils/diffraction_objects.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 80202cd3..3ce01346 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -408,22 +408,22 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): Parameters ---------- target_diff_object: DiffractionObject - the diffraction object you want to scale the current one onto + The diffraction object you want to scale the current one onto. - q, tth, d : float, optional, default is the max q-value from each object + q, tth, d : float, optional, default is None The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. offset : float, optional, default is None - an offset to add to the scaled y-values + The offset to add to the scaled y-values. Returns ------- - the rescaled DiffractionObject as a new object + scaled : DiffractionObject + The rescaled DiffractionObject as a new object. """ if offset is None: offset = 0 - scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) if count > 1: From 5492ad8718b46854c8fc0530e9f1d28be4dba89e Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 21:08:09 -0500 Subject: [PATCH 567/696] test: remove test for the correct mu, improve test comment for invalid inputs --- tests/test_tools.py | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index d02996ca..d3f7cf16 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -167,35 +167,11 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected -@pytest.mark.parametrize( - "inputs, expected_mu", - [ - # Test whether the function returns the correct mu - ( # C1: Composition, energy, and mass density provided, expect to get mu based on mass density - # 1. Fully dense mass density - {"sample_composition": "quartz", "energy": 10, "sample_mass_density": 2.65}, - 5.0368, - ), - ( # 2. Measured mass density - { - "sample_composition": "ZrO2", - "energy": 17.445, - "sample_mass_density": 1.009, - }, - 1.2522, - ), - ], -) -def test_compute_mu_using_xraydb(inputs, expected_mu): - actual_mu = compute_mu_using_xraydb(**inputs) - assert actual_mu == pytest.approx(expected_mu, rel=1e-6, abs=1e-4) - - @pytest.mark.parametrize( "inputs", [ - # Test when the function raises ValueError - # C1: Both mass density and packing fraction are provided + # Test when the function has invalid inputs + # C1: Both mass density and packing fraction are provided, expect ValueError exception ( { "sample_composition": "quartz", @@ -204,7 +180,7 @@ def test_compute_mu_using_xraydb(inputs, expected_mu): "packing_fraction": 1, } ), - # C2: None of mass density or packing fraction are provided + # C2: None of mass density or packing fraction are provided, expect ValueError exception ( { "sample_composition": "quartz", From 21711fb8a3d15030e5b74d63ccbfe09830395acf Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 29 Dec 2024 21:16:25 -0500 Subject: [PATCH 568/696] fix: shorten all_arrays shape check --- src/diffpy/utils/diffraction_objects.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 477b1f2c..bf53a5ac 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -254,9 +254,7 @@ def _check_operation_compatibility(self, other): if not isinstance(other, (DiffractionObject, int, float)): raise TypeError(invalid_add_type_emsg) if isinstance(other, DiffractionObject): - self_yarray = self.all_arrays[:, 0] - other_yarray = other.all_arrays[:, 0] - if self_yarray.shape != other_yarray.shape: + if self.all_arrays.shape != other.all_arrays.shape: raise ValueError(y_grid_length_mismatch_emsg) @property From a4a217d482af895970ea4934745b95fc45bd6497 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 21:29:17 -0500 Subject: [PATCH 569/696] test: reformat tests --- tests/test_tools.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index d3f7cf16..91bf63bb 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -171,19 +171,17 @@ def test_get_package_info(monkeypatch, inputs, expected): "inputs", [ # Test when the function has invalid inputs - # C1: Both mass density and packing fraction are provided, expect ValueError exception - ( + ( # C1: Both mass density and packing fraction are provided, expect ValueError exception { - "sample_composition": "quartz", + "sample_composition": "SiO2", "energy": 10, "sample_mass_density": 2.65, "packing_fraction": 1, } ), - # C2: None of mass density or packing fraction are provided, expect ValueError exception - ( + ( # C2: None of mass density or packing fraction are provided, expect ValueError exception { - "sample_composition": "quartz", + "sample_composition": "SiO2", "energy": 10, } ), From cdf4b49e45ce7f32023633fce6dcf397385e3280 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 29 Dec 2024 21:33:19 -0500 Subject: [PATCH 570/696] doc: small tweak to description of scale_to --- doc/source/examples/diffraction_objects_example.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index f956081c..4494fcfc 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -104,10 +104,9 @@ we would replace the code above with plt.show() The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new -variable and make use of, +variable and make use of. -The default behavior is to align the objects based on the maximal q-value of each diffraction object, -so they will align at the intensity at these indices. +The default behavior is to align the objects based on the maximal value of each diffraction object. .. code-block:: python From dd52ad9b401eb9475fccfb57ee09297a02bafa58 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 21:48:37 -0500 Subject: [PATCH 571/696] docs: improve docstring --- src/diffpy/utils/tools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index c562a2dd..036cc696 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -218,13 +218,14 @@ def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None Parameters ---------- sample_composition : str - The chemical formula or the name of the material. + The chemical formula of the material. energy : float The energy of the incident x-rays in keV. sample_mass_density : float, optional, Default is None - The mass density of the packed powder/sample in gr/cm^3. + The mass density of the packed powder/sample in g/cm*3. packing_fraction : float, optional, Default is None The fraction of sample in the capillary (between 0 and 1). + Specify either sample_mass_density or packing_fraction but not both. Returns ------- From 27da4ded721f3d0978745ca9f7bcc4d567b88aca Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 22:19:06 -0500 Subject: [PATCH 572/696] feat: add offset to default behavior and test case --- src/diffpy/utils/diffraction_objects.py | 4 +++- tests/test_diffraction_objects.py | 31 +++++++++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 3ce01346..a404b155 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -433,7 +433,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): ) if count == 0: - scaled._all_arrays[:, 0] *= max(target_diff_object.on_q()[1]) / max(self.on_q()[1]) + q_target_max = max(target_diff_object.on_q()[1]) + q_self_max = max(self.on_q()[1]) + scaled._all_arrays[:, 0] = scaled._all_arrays[:, 0] * q_target_max / q_self_max + offset return scaled xtype = "q" if q is not None else "tth" if tth is not None else "d" diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6499bdd7..455dc41c 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -264,21 +264,38 @@ def test_init_invalid_xtype(): {"tth": 60}, {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), - ( # C5: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 + ( # C5.1: Reuse test case from C1, none of q, tth, d, provided, but include an offset, + # expect scaled y-array in C1 to shift up by 2 { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "yarray": np.array([5, 10, 20, 30]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + {"offset": 2}, + {"xtype": "q", "yarray": np.array([12, 22, 32])}, + ), + ( # C5.2: Reuse test case from C4, but include an offset, expect scaled y-array in C4 to shift up by 2 + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), "xtype": "tth", "wavelength": 2 * np.pi, }, { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), "xtype": "tth", "wavelength": 2 * np.pi, }, - {"tth": 60, "offset": 2.1}, - {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, + {"tth": 60, "offset": 2}, + {"xtype": "tth", "yarray": np.array([3, 4, 5, 6, 7, 8, 12])}, ), ], ) From 0691e545f1680b45461e357277de5556d079a8da Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 22:23:19 -0500 Subject: [PATCH 573/696] docs: improve docstring and test comment --- src/diffpy/utils/diffraction_objects.py | 2 +- tests/test_diffraction_objects.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a404b155..1b32e1d7 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -400,7 +400,7 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): """Returns a new diffraction object which is the current object but rescaled in y to the target. - By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max q-value from each object. + By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. Otherwise, y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 455dc41c..9f0d9a0d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -191,7 +191,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether the original y-array is scaled as expected - ( # C1: none of q, tth, d, provided, expect to scale on the maximal x-arrays + ( # C1: none of q, tth, d, provided, expect to scale on the maximal intensity from each object { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), From 6325cf6d7e9218bafc9e5379018ff47d7993c39d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 30 Dec 2024 11:54:22 -0500 Subject: [PATCH 574/696] docs: add examples for operations --- .../examples/diffraction_objects_example.rst | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 4494fcfc..0e4e5b55 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -129,14 +129,14 @@ For convenience, you can also apply an offset to the scaled new diffraction obje DiffractionObject convenience functions --------------------------------------- -1) create a copy of a diffraction object using the ``copy`` method +1. create a copy of a diffraction object using the ``copy`` method when you want to preserve the original data while working with a modified version. .. code-block:: python copy_of_calculated = calculated.copy() -2) test the equality of two diffraction objects. For example, +2. test the equality of two diffraction objects. For example, .. code-block:: python @@ -145,15 +145,38 @@ DiffractionObject convenience functions will return ``True``. -3) make arithmetic operations on the intensities of diffraction objects. e.g., +3. make arithmetic operations on the intensities of diffraction objects. +For example, you can do scalar operations on a single diffraction object, +which will modify the intensity values (``yarrays``) without affecting other properties: .. code-block:: python - doubled_object = 2 * diff_object1 # Double the intensities - sum_object = diff_object1 + diff_object2 # Sum the intensities - subtract_scaled = diff_object1 - 5 * diff_object2 # subtract 5 * obj2 from obj 1 + increased_intensity = diff_object1 + 5 # Increases the intensities by 5 + decreased_intensity = diff_object1 - 1 # Decreases the intensities by 1 + doubled_object = 2 * diff_object1 # Double the intensities + reduced_intensity = diff_object1 / 2 # Halves the intensities -4) get the value of the DiffractionObject at a given point in one of the xarrays +You can also do binary operations between two diffraction objects, as long as their yarrays have the same length. +The operation will apply to the intensity values, while other properties +(such as ``xarrays``, ``xtype``, and ``metadata``) will be inherited +from the left-hand side diffraction object (``diff_object1``). +For example: + +.. code-block:: python + + sum_object = diff_object1 + diff_object2 # Sum the intensities + subtract_scaled = diff_object1 - 5 * diff_object2 # Subtract 5 * obj2 from obj 1 + multiplied_object = diff_object1 * diff_object2 # Multiply the intensities + divided_object = diff_object1 / diff_object2 # Divide the intensities + +You cannot perform operations between diffraction objects and incompatible types. +For example, attempting to add a diffraction object and a string will raise an error: + +.. code-block:: python + + diff_object1 + "string_value" # This will raise an error + +4. get the value of the DiffractionObject at a given point in one of the xarrays .. code-block:: python @@ -170,7 +193,7 @@ you can find its closest index for ``q=0.25`` by typing either of the following: index = do.get_array_index(0.25) # no xtype passed, defaults to do._input_xtype, or in this example, q index = do.get_array_index(0.25, xtype="q") # explicitly choose an xtype to specify a value -5) The ``dump`` function saves the diffraction data and relevant information to an xy format file with headers +5. The ``dump`` function saves the diffraction data and relevant information to an xy format file with headers (widely used chi format used, for example, by Fit2D and diffpy. These files can be read by ``LoadData()`` in ``diffpy.utils.parsers``). From 0865819658482dba27a85d845181f92062ca1c10 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 30 Dec 2024 11:57:14 -0500 Subject: [PATCH 575/696] docs: no news added --- news/docs-do.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/docs-do.rst diff --git a/news/docs-do.rst b/news/docs-do.rst new file mode 100644 index 00000000..21faf520 --- /dev/null +++ b/news/docs-do.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news added: adding documentation for diffraction object operation examples + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From cfb7362b787380b58b0b11cb743f8728229958be Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 30 Dec 2024 15:08:20 -0500 Subject: [PATCH 576/696] docs: fix error --- doc/source/examples/diffraction_objects_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 0e4e5b55..1122bf21 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -156,7 +156,7 @@ which will modify the intensity values (``yarrays``) without affecting other pro doubled_object = 2 * diff_object1 # Double the intensities reduced_intensity = diff_object1 / 2 # Halves the intensities -You can also do binary operations between two diffraction objects, as long as their yarrays have the same length. +You can also do binary operations between two diffraction objects, as long as they are on the same ``q/tth/d-array``. The operation will apply to the intensity values, while other properties (such as ``xarrays``, ``xtype``, and ``metadata``) will be inherited from the left-hand side diffraction object (``diff_object1``). From 98c65dd62435ab3e53839ffe7126f8bf8bba5c5c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 18:53:16 -0500 Subject: [PATCH 577/696] test: ensure we compare the values of xarrays before applying operations between 2 DO --- news/no-news.rst | 23 +++++++++++++ src/diffpy/utils/diffraction_objects.py | 11 ++++--- tests/conftest.py | 7 ++-- tests/test_diffraction_objects.py | 43 +++++++++++++++++-------- 4 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 news/no-news.rst diff --git a/news/no-news.rst b/news/no-news.rst new file mode 100644 index 00000000..31779a23 --- /dev/null +++ b/news/no-news.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: just making sure the previous add, mul, div, and sub tests are working as expected. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 9e56e0d3..5c5cd7e2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -14,9 +14,10 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -y_grid_length_mismatch_emsg = ( - "The two objects have different y-array lengths. " - "Please ensure the length of the y-value during initialization is identical." +x_values_not_equal_emsg = ( + "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " + "Please ensure the x values of the two objects are identical by re-instantiating " + "the DiffractionObject with the correct x value inputs." ) invalid_add_type_emsg = ( @@ -255,7 +256,9 @@ def _check_operation_compatibility(self, other): raise TypeError(invalid_add_type_emsg) if isinstance(other, DiffractionObject): if self.all_arrays.shape != other.all_arrays.shape: - raise ValueError(y_grid_length_mismatch_emsg) + raise ValueError(x_values_not_equal_emsg) + if not np.allclose(self.all_arrays[:, [1, 2, 3]], other.all_arrays[:, [1, 2, 3]]): + raise ValueError(x_values_not_equal_emsg) @property def all_arrays(self): diff --git a/tests/conftest.py b/tests/conftest.py index 9e5f1e60..296ae14f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,8 +81,9 @@ def invalid_add_type_error_msg(): @pytest.fixture -def y_grid_size_mismatch_error_msg(): +def x_values_not_equal_error_msg(): return ( - "The two objects have different y-array lengths. " - "Please ensure the length of the y-value during initialization is identical." + "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " + "Please ensure the x values of the two objects are identical by re-instantiating " + "the DiffractionObject with the correct x value inputs." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ff81ff28..13b3719b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -780,28 +780,28 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya @pytest.mark.parametrize( - "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", + "operation, expected_do_1_all_arrays_with_y_modified, expected_do_2_all_arrays_with_y_modified", [ # Test addition, subtraction, multiplication, and division of two DO objects ( # Test addition of two DO objects, expect combined yarray values "add", np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), - np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), ), ( # Test subtraction of two DO objects, expect differences in yarray values "sub", np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), - np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), + np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), ), ( # Test multiplication of two DO objects, expect multiplication in yarray values "mul", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), - np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), ), ( # Test division of two DO objects, expect division in yarray values "div", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), - np.array([[1.0, 6.28318531, 100.70777771, 1], [1.0, 3.14159265, 45.28748053, 2.0]]), + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), ), ], ) @@ -810,15 +810,14 @@ def test_binary_operator_on_do( expected_do_1_all_arrays_with_y_modified, expected_do_2_all_arrays_with_y_modified, do_minimal_tth, - do_minimal_d, ): do_1 = do_minimal_tth - do_2 = do_minimal_d + do_2 = do_minimal_tth assert np.allclose( do_1.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) ) assert np.allclose( - do_2.all_arrays, np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]) + do_2.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) ) if operation == "add": @@ -856,13 +855,31 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) -def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): - # Add two DO objects with different yarray lengths, expect ValueError +def test_operator_invalid_xarray_values_not_equal( + operation, do_minimal_tth, do_minimal_d, x_values_not_equal_error_msg +): + # Add two DO objects with different xarray values but equal in shape, expect ValueError + do_1 = do_minimal_tth + do_2 = do_minimal_d + with pytest.raises(ValueError, match=re.escape(x_values_not_equal_error_msg)): + if operation == "add": + do_1 + do_2 + elif operation == "sub": + do_1 - do_2 + elif operation == "mul": + do_1 * do_2 + elif operation == "div": + do_1 / do_2 + + +@pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) +def test_operator_invalid_xarray_shape_not_equal( + operation, do_minimal, do_minimal_tth, x_values_not_equal_error_msg +): + # Add two DO objects with different xarrays shape, expect ValueError do_1 = do_minimal do_2 = do_minimal_tth - assert len(do_1.all_arrays[:, 0]) == 0 - assert len(do_2.all_arrays[:, 0]) == 2 - with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): + with pytest.raises(ValueError, match=re.escape(x_values_not_equal_error_msg)): if operation == "add": do_1 + do_2 elif operation == "sub": From 13af9e2a2470778d5f57eec634f6df0cc1c527d0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:03:43 -0500 Subject: [PATCH 578/696] docs: improve docstring based on group standards in diffraction_objects.py transforms.py --- doc/source/api/diffpy.utils.parsers.rst | 16 ++-- doc/source/api/diffpy.utils.rst | 26 ++++-- news/docstrings-refactor.rst | 23 +++++ src/diffpy/utils/diffraction_objects.py | 119 +++++++++++++++++------- src/diffpy/utils/tools.py | 39 ++++---- src/diffpy/utils/transforms.py | 56 +++++------ 6 files changed, 180 insertions(+), 99 deletions(-) create mode 100644 news/docstrings-refactor.rst diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index 3456f24e..29ec4782 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -11,14 +11,6 @@ diffpy.utils.parsers package Submodules ---------- -diffpy.utils.parsers.serialization module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.parsers.serialization - :members: - :undoc-members: - :show-inheritance: - diffpy.utils.parsers.loaddata module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,3 +26,11 @@ diffpy.utils.parsers.custom_exceptions module :members: :undoc-members: :show-inheritance: + +diffpy.utils.parsers.serialization module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.parsers.serialization + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 2513e821..8073d8e6 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -16,30 +16,28 @@ Subpackages diffpy.utils.parsers diffpy.utils.wx - diffpy.utils.scattering_objects Submodules ---------- -diffpy.utils.tools module -^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.transforms module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.tools +.. automodule:: diffpy.utils.transforms :members: :undoc-members: :show-inheritance: -diffpy.utils.resampler module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.tools module +^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.resampler +.. automodule:: diffpy.utils.tools :members: :undoc-members: :show-inheritance: - diffpy.utils.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.user_config :members: @@ -47,9 +45,17 @@ diffpy.utils.user_config module :show-inheritance: diffpy.utils.diffraction_objects module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.diffraction_objects :members: :undoc-members: :show-inheritance: + +diffpy.utils.resampler module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.resampler + :members: + :undoc-members: + :show-inheritance: diff --git a/news/docstrings-refactor.rst b/news/docstrings-refactor.rst new file mode 100644 index 00000000..0dca3d1d --- /dev/null +++ b/news/docstrings-refactor.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: this branch was used to refactor the docstrings in the codebase without adding new features + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 9e56e0d3..ce43a831 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -92,18 +92,18 @@ def __init__( The dependent variable array corresponding to intensity values. xtype : str The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. - wavelength : float, optional - The wavelength of the incoming beam, specified in angstroms (Å). Default is none. - scat_quantity : str, optional - The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". - name : str, optional - The name or label for the scattering data. Default is an empty string "". - metadata : dict, optional - The additional metadata associated with the diffraction object. Default is {}. + wavelength : float, optional, default is None. + The wavelength of the incoming beam, specified in angstroms (Å) + scat_quantity : str, optional, default is an empty string "". + The type of scattering experiment (e.g., "x-ray", "neutron"). + name : str, optional, default is an empty string "". + The name or label for the scattering data. + metadata : dict, optional, default is an empty dictionary {} + The additional metadata associated with the diffraction object. Examples -------- - Create a DiffractionObject for X-ray scattering data + Create a DiffractionObject for X-ray scattering data: >>> import numpy as np >>> from diffpy.utils.diffraction_objects import DiffractionObject ... @@ -180,30 +180,32 @@ def __add__(self, other): Parameters ---------- - other : DiffractionObject or int or float - The object to add to the current DiffractionObject. If `other` is a scalar value, - it will be added to all yarray. The length of the yarray must match if `other` is - an instance of DiffractionObject. + other : DiffractionObject, int, or float + The item to be added. If `other` is a scalar value, this value will be added to each element of the + yarray of this DiffractionObject instance. If `other` is another DiffractionObject, the yarrays of the + two DiffractionObjects will be combined element-wise. The result is a new DiffractionObject instance, + representing the addition and using the xarray from the left-hand side DiffractionObject. Returns ------- DiffractionObject - The new and deep-copied DiffractionObject instance after adding values to the yarray. + THe new DiffractionObject instance with modified yarray values. This instance is a deep copy of the + original with the additions applied. Raises ------ ValueError - Raised when the length of the yarray of the two DiffractionObject instances do not match. + Raised when the xarrays of two DiffractionObject instances are not equal. TypeError - Raised when the type of `other` is not an instance of DiffractionObject, int, or float. + Raised when `other` is not an instance of DiffractionObject, int, or float. Examples -------- - Add a scalar value to the yarray of the DiffractionObject instance: + Add a scalar value to the yarray of a DiffractionObject instance: >>> new_do = my_do + 10.1 >>> new_do = 10.1 + my_do - Add the yarray of two DiffractionObject instances: + Combine the yarrays of two DiffractionObject instances: >>> new_do = my_do_1 + my_do_2 """ @@ -218,6 +220,21 @@ def __add__(self, other): __radd__ = __add__ def __sub__(self, other): + """Subtract scalar value or another DiffractionObject to the yarray of + the DiffractionObject. + + This method behaves similarly to the `__add__` method, but performs subtraction instead of addition. + For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + + Examples + -------- + Subtract a scalar value from the yarray of a DiffractionObject instance: + >>> new_do = my_do - 10.1 + + Subtract the yarrays of two DiffractionObject instances: + >>> new_do = my_do_1 - my_do_2 + """ + self._check_operation_compatibility(other) subtracted_do = deepcopy(self) if isinstance(other, (int, float)): @@ -229,6 +246,21 @@ def __sub__(self, other): __rsub__ = __sub__ def __mul__(self, other): + """Multiply a scalar value or another DiffractionObject with the yarray + of this DiffractionObject. + + This method behaves similarly to the `__add__` method, but performs multiplication instead of addition. + For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + + Examples + -------- + Multiply a scalar value with the yarray of a DiffractionObject instance: + >>> new_do = my_do * 3.5 + + Multiply the yarrays of two DiffractionObject instances: + >>> new_do = my_do_1 * my_do_2 + """ + self._check_operation_compatibility(other) multiplied_do = deepcopy(self) if isinstance(other, (int, float)): @@ -240,6 +272,20 @@ def __mul__(self, other): __rmul__ = __mul__ def __truediv__(self, other): + """Divide the yarray of this DiffractionObject by a scalar value or + another DiffractionObject. + + This method behaves similarly to the `__add__` method, but performs division instead of addition. + For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + + Examples + -------- + Divide the yarray of a DiffractionObject instance by a scalar value: + >>> new_do = my_do / 2.0 + + Divide the yarrays of two DiffractionObject instances: + >>> new_do = my_do_1 / my_do_2 + """ self._check_operation_compatibility(other) divided_do = deepcopy(self) if isinstance(other, (int, float)): @@ -288,7 +334,7 @@ def input_xtype(self): Returns ------- - str + input_xtype : str The type of `xarray`, which must be one of {*XQUANTITIES}. """ return self._input_xtype @@ -303,7 +349,7 @@ def uuid(self): Returns ------- - uuid + uuid : UUID The unique identifier of the DiffractionObject instance. """ return self._uuid @@ -325,7 +371,7 @@ def get_array_index(self, xtype, xvalue): Returns ------- - int + index : int The index of the closest value in the array associated with the specified xtype and the value provided. """ @@ -378,7 +424,7 @@ def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): - """Returns a new diffraction object which is the current object but + """Return a new diffraction object which is the current object but rescaled in y to the target. By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. @@ -400,12 +446,12 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): Returns ------- - scaled : DiffractionObject + scaled_do : DiffractionObject The rescaled DiffractionObject as a new object. """ if offset is None: offset = 0 - scaled = self.copy() + scaled_do = self.copy() count = sum([q is not None, tth is not None, d is not None]) if count > 1: raise ValueError( @@ -416,8 +462,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): if count == 0: q_target_max = max(target_diff_object.on_q()[1]) q_self_max = max(self.on_q()[1]) - scaled._all_arrays[:, 0] = scaled._all_arrays[:, 0] * q_target_max / q_self_max + offset - return scaled + scaled_do._all_arrays[:, 0] = scaled_do._all_arrays[:, 0] * q_target_max / q_self_max + offset + return scaled_do xtype = "q" if q is not None else "tth" if tth is not None else "d" data = self.on_xtype(xtype) @@ -427,21 +473,26 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): xindex_data = (np.abs(data[0] - xvalue)).argmin() xindex_target = (np.abs(target[0] - xvalue)).argmin() - scaled._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset - return scaled + scaled_do._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset + return scaled_do def on_xtype(self, xtype): - """Return a list of two 1D np array with x and y data, raise an error - if the specified xtype is invalid. + """Return a tuple of two 1D numpy arrays containing x and y data. Parameters ---------- - xtype str - the type of quantity for the independent variable from {*XQUANTITIES, } + xtype : str + The type of quantity for the independent variable chosen from {*XQUANTITIES, } + + Raises + ------ + ValueError + Raised when the specified xtype is not among {*XQUANTITIES, } Returns ------- - a list of two 1D np array with x and y data + (xarray, yarray) : tuple of ndarray + A tuple containing two 1D numpy arrays with x and y data for the specified xtype. """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() @@ -482,6 +533,6 @@ def copy(self): Returns ------- DiffractionObject - A new instance of DiffractionObject, which is a deep copy of the current instance. + The new instance of DiffractionObject, which is a deep copy of the current instance. """ return deepcopy(self) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 036cc696..61868072 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -11,20 +11,20 @@ from diffpy.utils.parsers.loaddata import loadData -def _stringify(obj): +def _stringify(string_value): """Convert None to an empty string. Parameters ---------- - obj: str - The object to convert. If None, return an empty string. + string_value : str or None + The value to be converted. If None, an empty string is returned. Returns ------- - str or None: - The converted string if obj is not None, otherwise an empty string. + str + The original string if string_value is not None, otherwise an empty string. """ - return obj if obj is not None else "" + return string_value if string_value is not None else "" def _load_config(file_path): @@ -32,12 +32,12 @@ def _load_config(file_path): Parameters ---------- - file_path: Path + file_path : Path The path to the configuration file. Returns ------- - dict: + config : dict The configuration dictionary or {} if the config file does not exist. """ config_file = Path(file_path).resolve() @@ -50,7 +50,7 @@ def _load_config(file_path): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """Get name, email and orcid of the owner/user from various sources and + """Get name, email, and orcid of the owner/user from various sources and return it as a metadata dictionary. The function looks for the information in json format configuration files with the name 'diffpyconfig.json'. @@ -71,16 +71,16 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): Parameters ---------- - owner_name: string, optional, default is the value stored in the global or local config file. + owner_name : str, optional, default is the value stored in the global or local config file. The name of the user who will show as owner in the metadata that is stored with the data - owner_email: string, optional, default is the value stored in the global or local config file. + owner_email : str, optional, default is the value stored in the global or local config file. The email of the user/owner - owner_orcid: string, optional, default is the value stored in the global or local config file. + owner_orcid : str, optional, default is the value stored in the global or local config file. The ORCID id of the user/owner Returns ------- - dict: + user_info : dict The dictionary containing username, email and orcid of the user/owner, and any other information stored in the global or local config files. """ @@ -97,7 +97,7 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def check_and_build_global_config(skip_config_creation=False): - """Checks for a global diffpu config file in user's home directory and + """Check for a global diffpu config file in user's home directory and creates one if it is missing. The file it looks for is called diffpyconfig.json. This can contain anything in json format, but @@ -116,12 +116,13 @@ def check_and_build_global_config(skip_config_creation=False): Parameters ---------- - skip_config_creation: bool, optional, Default is False - The bool that will override the creation workflow even if no config file exists. + skip_config_creation : bool, optional, default is False. + The boolean that will override the creation workflow even if no config file exists. Returns ------- - bool: True if the file exists and False otherwise. + config_exists : bool + The boolean indicating whether the config file exists. """ config_exists = False config_path = Path().home() / "diffpyconfig.json" @@ -168,7 +169,7 @@ def check_and_build_global_config(skip_config_creation=False): def get_package_info(package_names, metadata=None): - """Fetches package version and updates it into (given) metadata. + """Fetch package version and updates it into (given) metadata. Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. @@ -180,7 +181,7 @@ def get_package_info(package_names, metadata=None): Returns ------- - dict: + metadata : dict The updated metadata dict with package info inserted. """ if metadata is None: diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index f2956879..f3c169a6 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -28,7 +28,7 @@ def _validate_inputs(q, wavelength): raise ValueError(invalid_q_or_d_or_wavelength_emsg) -def q_to_tth(q, wavelength): +def q_to_tth(q, wavelength) -> np.ndarray: r"""Helper function to convert q to two-theta. If wavelength is missing, returns x-values that are integer indexes @@ -47,8 +47,8 @@ def q_to_tth(q, wavelength): Parameters ---------- - q : 1D array - The array of :math:`q` values numpy.array([qs]). + q : ndarray + The 1D array of :math:`q` values numpy.array([qs]). The units of q must be reciprocal of the units of wavelength. wavelength : float @@ -56,8 +56,8 @@ def q_to_tth(q, wavelength): Returns ------- - tth : 1D array - The array of :math:`2\theta` values in degrees numpy.array([tths]). + tth : ndarray + The 1D array of :math:`2\theta` values in degrees numpy.array([tths]). """ _validate_inputs(q, wavelength) q.astype(float) @@ -89,17 +89,17 @@ def tth_to_q(tth, wavelength): Parameters ---------- - tth : 1D array - The array of :math:`2\theta` values np.array([tths]). + tth : ndarray + The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. wavelength : float - Wavelength of the incoming x-rays/neutrons/electrons + The wavelength of the incoming x-rays/neutrons/electrons. Returns ------- - q : 1D array - The array of :math:`q` values np.array([qs]). + q : ndarray + The 1D array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. """ tth.astype(float) @@ -122,14 +122,14 @@ def q_to_d(q): Parameters ---------- - q : 1D array - The array of :math:`q` values np.array([qs]). + q : ndarray + The 1D array of :math:`q` values np.array([qs]). The units of q must be reciprocal of the units of wavelength. Returns ------- - d : 1D array - The array of :math:`d` values np.array([ds]). + d : ndarray + The 1D array of :math:`d` values np.array([ds]). """ if 0 in q: print(inf_output_imsg) @@ -145,17 +145,17 @@ def tth_to_d(tth, wavelength): Parameters ---------- - tth : 1D array - The array of :math:`2\theta` values np.array([tths]). + tth : nsarray + The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. wavelength : float - Wavelength of the incoming x-rays/neutrons/electrons + The wavelength of the incoming x-rays/neutrons/electrons. Returns ------- - d : 1D array - The array of :math:`d` values np.array([ds]). + d : nsarray + The 1D array of :math:`d` values np.array([ds]). """ q = tth_to_q(tth, wavelength) d = copy(tth) @@ -174,13 +174,13 @@ def d_to_q(d): Parameters ---------- - d : 1D array - The array of :math:`d` values np.array([ds]). + d : nsarray + The 1D array of :math:`d` values np.array([ds]). Returns ------- - q : 1D array - The array of :math:`q` values np.array([qs]). + q : nsarray + The 1D array of :math:`q` values np.array([qs]). The units of q must be reciprocal of the units of wavelength. """ if 0 in d: @@ -197,16 +197,16 @@ def d_to_tth(d, wavelength): Parameters ---------- - d : 1D array - The array of :math:`d` values np.array([ds]). + d : nsarray + The 1D array of :math:`d` values np.array([ds]). wavelength : float - Wavelength of the incoming x-rays/neutrons/electrons + The wavelength of the incoming x-rays/neutrons/electrons. Returns ------- - tth : 1D array - The array of :math:`2\theta` values np.array([tths]). + tth : nsarray + The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. """ q = d_to_q(d) From ee5f3a3b0fb206b1633f013fa074664accf8af96 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:09:29 -0500 Subject: [PATCH 579/696] chore: fix from a tuple to The tuple in docstring --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ce43a831..cc9a92a6 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -492,7 +492,7 @@ def on_xtype(self, xtype): Returns ------- (xarray, yarray) : tuple of ndarray - A tuple containing two 1D numpy arrays with x and y data for the specified xtype. + The tuple containing two 1D numpy arrays with x and y data for the specified xtype. """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() From 77c5957ff336b2efaf874d80d4066db7476e0c40 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:27:10 -0500 Subject: [PATCH 580/696] docs: generate docs with auto api --- doc/source/api/diffpy.utils.parsers.rst | 17 +++++++------ doc/source/api/diffpy.utils.rst | 33 ++++++++++++++++++------- doc/source/api/diffpy.utils.wx.rst | 1 + 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index 3456f24e..b08a1840 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -11,14 +11,6 @@ diffpy.utils.parsers package Submodules ---------- -diffpy.utils.parsers.serialization module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.parsers.serialization - :members: - :undoc-members: - :show-inheritance: - diffpy.utils.parsers.loaddata module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,3 +26,12 @@ diffpy.utils.parsers.custom_exceptions module :members: :undoc-members: :show-inheritance: + +diffpy.utils.parsers.serialization module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.parsers.serialization + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 2513e821..128d914d 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -16,30 +16,36 @@ Subpackages diffpy.utils.parsers diffpy.utils.wx - diffpy.utils.scattering_objects Submodules ---------- -diffpy.utils.tools module -^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.transforms module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.tools +.. automodule:: diffpy.utils.transforms :members: :undoc-members: :show-inheritance: -diffpy.utils.resampler module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.validators module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.resampler +.. automodule:: diffpy.utils.validators :members: :undoc-members: :show-inheritance: +diffpy.utils.tools module +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.tools + :members: + :undoc-members: + :show-inheritance: diffpy.utils.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.user_config :members: @@ -47,9 +53,18 @@ diffpy.utils.user_config module :show-inheritance: diffpy.utils.diffraction_objects module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.diffraction_objects :members: :undoc-members: :show-inheritance: + +diffpy.utils.resampler module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.resampler + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 76c89035..60f60599 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,3 +18,4 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: + From c65eace677d6e5aff1bf816d8bd69d5a5e7663f5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:27:32 -0500 Subject: [PATCH 581/696] refactor: rename isfloat to is_number, in a separate validators.py folder --- src/diffpy/utils/parsers/loaddata.py | 17 ++--------- src/diffpy/utils/validators.py | 45 ++++++++++++++++++++++++++++ tests/test_validators.py | 22 ++++++++++++++ 3 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/diffpy/utils/validators.py create mode 100644 tests/test_validators.py diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 39c4b163..8369e516 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -17,6 +17,8 @@ import numpy +from diffpy.utils import validators + def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs): """Find and load data from a text file. @@ -139,7 +141,7 @@ def countcolumnsvalues(line): name = hpair[0] value = hpair[1] # check if data value should be stored as float - if isfloat(hpair[1]): + if validators.is_number(hpair[1]): value = float(hpair[1]) hdata.update({name: value}) # continue search for the start of datablock @@ -331,16 +333,3 @@ def _findDataBlocks(self): self.headers.append(header) self.datasets.append(data) return - - -# End of class TextDataLoader - - -def isfloat(s): - """True if s is convertible to float.""" - try: - float(s) - return True - except ValueError: - pass - return False diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py new file mode 100644 index 00000000..0f8cd204 --- /dev/null +++ b/src/diffpy/utils/validators.py @@ -0,0 +1,45 @@ +def is_number(string): + """ + Check if the provided string can be converted to a float. + + Parameters + ---------- + string : str + The string to evaluate for numeric conversion. + + Returns + ------- + bool + The boolean whether `string` can be successfully converted to float. + + Examples + -------- + >>> is_number("3.14") + True + + >>> is_number("-1.23") + True + + >>> is_number("007") + True + + >>> is_number("five") + False + + >>> is_number("3.14.15") + False + + >>> is_number("NaN") + True + + >>> is_number("Infinity") + True + + >>> is_number("Inf") + True + """ + try: + float(string) + return True + except ValueError: + return False \ No newline at end of file diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 00000000..0d732e77 --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,22 @@ +from diffpy.utils.validators import is_number +import pytest + +@pytest.mark.parametrize("input,expected", [ + ("3.14", True), # Standard float + ("2", True), # Integer + ("-100", True), # Negative integer + ("-3.14", True), # Negative float + ("0", True), # Zero + ("4.5e-1", True), # Scientific notation + ("abc", False), # Non-numeric string + ("", False), # Empty string + ("3.14.15", False), # Multiple dots + ("2+3", False), # Arithmetic expression + ("NaN", True), # Not a Number (special float value) + ("Infinity", True), # Positive infinity + ("-Infinity", True), # Negative infinity + ("Inf", True), # Positive infinity + ("-Inf", True), # Negative infinity +]) +def test_is_number(input, expected): + assert is_number(input) == expected \ No newline at end of file From 23c32d6ec7a9c5e6431f15793a5eebaa5d21ec39 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:27:38 -0500 Subject: [PATCH 582/696] chore: add news file --- news/is-float.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/is-float.rst diff --git a/news/is-float.rst b/news/is-float.rst new file mode 100644 index 00000000..918600e5 --- /dev/null +++ b/news/is-float.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Rename the `isfloat` function to `is_number`, and move it to the `diffpy/utils/utilsvalidators.py` directory + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 7d6dbc1cd1573200ee4766d9c9f2d84e707fcd25 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 03:30:08 +0000 Subject: [PATCH 583/696] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/api/diffpy.utils.parsers.rst | 1 - doc/source/api/diffpy.utils.rst | 1 - doc/source/api/diffpy.utils.wx.rst | 1 - src/diffpy/utils/validators.py | 9 +++--- tests/test_validators.py | 43 ++++++++++++++----------- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index b08a1840..29ec4782 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -34,4 +34,3 @@ diffpy.utils.parsers.serialization module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 128d914d..e4de1d4f 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -67,4 +67,3 @@ diffpy.utils.resampler module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 60f60599..76c89035 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,4 +18,3 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: - diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py index 0f8cd204..dba25b70 100644 --- a/src/diffpy/utils/validators.py +++ b/src/diffpy/utils/validators.py @@ -1,6 +1,5 @@ def is_number(string): - """ - Check if the provided string can be converted to a float. + """Check if the provided string can be converted to a float. Parameters ---------- @@ -31,10 +30,10 @@ def is_number(string): >>> is_number("NaN") True - + >>> is_number("Infinity") True - + >>> is_number("Inf") True """ @@ -42,4 +41,4 @@ def is_number(string): float(string) return True except ValueError: - return False \ No newline at end of file + return False diff --git a/tests/test_validators.py b/tests/test_validators.py index 0d732e77..e340d065 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,22 +1,27 @@ -from diffpy.utils.validators import is_number import pytest -@pytest.mark.parametrize("input,expected", [ - ("3.14", True), # Standard float - ("2", True), # Integer - ("-100", True), # Negative integer - ("-3.14", True), # Negative float - ("0", True), # Zero - ("4.5e-1", True), # Scientific notation - ("abc", False), # Non-numeric string - ("", False), # Empty string - ("3.14.15", False), # Multiple dots - ("2+3", False), # Arithmetic expression - ("NaN", True), # Not a Number (special float value) - ("Infinity", True), # Positive infinity - ("-Infinity", True), # Negative infinity - ("Inf", True), # Positive infinity - ("-Inf", True), # Negative infinity -]) +from diffpy.utils.validators import is_number + + +@pytest.mark.parametrize( + "input,expected", + [ + ("3.14", True), # Standard float + ("2", True), # Integer + ("-100", True), # Negative integer + ("-3.14", True), # Negative float + ("0", True), # Zero + ("4.5e-1", True), # Scientific notation + ("abc", False), # Non-numeric string + ("", False), # Empty string + ("3.14.15", False), # Multiple dots + ("2+3", False), # Arithmetic expression + ("NaN", True), # Not a Number (special float value) + ("Infinity", True), # Positive infinity + ("-Infinity", True), # Negative infinity + ("Inf", True), # Positive infinity + ("-Inf", True), # Negative infinity + ], +) def test_is_number(input, expected): - assert is_number(input) == expected \ No newline at end of file + assert is_number(input) == expected From f29ad9195c576447d7ba96a8ee92fd5db0b46803 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:42:19 -0500 Subject: [PATCH 584/696] chore: removed np.nsarray return type, not needed --- src/diffpy/utils/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index f3c169a6..21c069b1 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -28,7 +28,7 @@ def _validate_inputs(q, wavelength): raise ValueError(invalid_q_or_d_or_wavelength_emsg) -def q_to_tth(q, wavelength) -> np.ndarray: +def q_to_tth(q, wavelength): r"""Helper function to convert q to two-theta. If wavelength is missing, returns x-values that are integer indexes From 84dcb4e6256169202f9bb439079f502976147d6b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:46:54 -0500 Subject: [PATCH 585/696] docs: improve description on how int is also a float --- src/diffpy/utils/validators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py index dba25b70..0f281073 100644 --- a/src/diffpy/utils/validators.py +++ b/src/diffpy/utils/validators.py @@ -1,5 +1,8 @@ def is_number(string): """Check if the provided string can be converted to a float. + + Since integers can be converted to floats, this function will return True for integers as well. + Hence, we can use this function to check if a string is a number. Parameters ---------- From f8b1c41be6fe43cbfcf2d58b50d2c892d6662460 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 03:47:03 +0000 Subject: [PATCH 586/696] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py index 0f281073..91a461bf 100644 --- a/src/diffpy/utils/validators.py +++ b/src/diffpy/utils/validators.py @@ -1,6 +1,6 @@ def is_number(string): """Check if the provided string can be converted to a float. - + Since integers can be converted to floats, this function will return True for integers as well. Hence, we can use this function to check if a string is a number. From 5dfff333783e6a2f2a0a30b70834372851bac87e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 31 Dec 2024 09:28:20 -0500 Subject: [PATCH 587/696] chore: added news for docstring --- news/docstrings-refactor.rst | 2 +- src/diffpy/utils/diffraction_objects.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/news/docstrings-refactor.rst b/news/docstrings-refactor.rst index 0dca3d1d..203b036e 100644 --- a/news/docstrings-refactor.rst +++ b/news/docstrings-refactor.rst @@ -1,6 +1,6 @@ **Added:** -* No news added: this branch was used to refactor the docstrings in the codebase without adding new features +* Improved API documentation in `DiffractionObject` methods and properties using the NumPy docstring format and PEP 256 **Changed:** diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2f4dfa8b..f95f1758 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -190,7 +190,7 @@ def __add__(self, other): Returns ------- DiffractionObject - THe new DiffractionObject instance with modified yarray values. This instance is a deep copy of the + The new DiffractionObject instance with modified yarray values. This instance is a deep copy of the original with the additions applied. Raises From a7850fb51a3955cc0f3448841e02502f6f055347 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 10:56:06 -0500 Subject: [PATCH 588/696] fix: remove user_config.py --- src/diffpy/utils/user_config.py | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 src/diffpy/utils/user_config.py diff --git a/src/diffpy/utils/user_config.py b/src/diffpy/utils/user_config.py deleted file mode 100644 index ca2cc004..00000000 --- a/src/diffpy/utils/user_config.py +++ /dev/null @@ -1,30 +0,0 @@ -import json -from pathlib import Path - -CONFIG_FILE = "diffpyconfig.json" -CWD_CONFIG_PATH = Path.cwd() / CONFIG_FILE -HOME_CONFIG_PATH = Path.home() / CONFIG_FILE - - -def find_conf_file(): - if CWD_CONFIG_PATH.exists() and CWD_CONFIG_PATH.is_file(): - return CWD_CONFIG_PATH - elif HOME_CONFIG_PATH.exists() and HOME_CONFIG_PATH.is_file(): - return HOME_CONFIG_PATH - return None - - -def read_conf_file(): - conf_file = find_conf_file() - if conf_file: - with open(conf_file, "r") as f: - config = json.load(f) - if not config.get("username") or not config.get("email"): - raise ValueError("Please provide a configuration file with username and email.") - return config.get("username"), config.get("email") - return None, None - - -def write_conf_file(username, email): - with open(HOME_CONFIG_PATH, "w") as f: - json.dump({"username": username, "email": email}, f) From f870c2f8cfb624b16f3fa1396655e7bee273de1d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 10:59:05 -0500 Subject: [PATCH 589/696] add news --- news/remove-userconfig.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/remove-userconfig.rst diff --git a/news/remove-userconfig.rst b/news/remove-userconfig.rst new file mode 100644 index 00000000..0d8e047b --- /dev/null +++ b/news/remove-userconfig.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* `user_config.py` function. + +**Fixed:** + +* + +**Security:** + +* From 59c8ecb973b623921ee71252fc232cf552839ba6 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 11:24:41 -0500 Subject: [PATCH 590/696] docs: improve news --- news/remove-userconfig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/remove-userconfig.rst b/news/remove-userconfig.rst index 0d8e047b..967d9559 100644 --- a/news/remove-userconfig.rst +++ b/news/remove-userconfig.rst @@ -12,7 +12,7 @@ **Removed:** -* `user_config.py` function. +* `user_config.py`. Replaced by `_load_config` and `check_and_build_global_config` in `tools.py`. **Fixed:** From 5693321abad7e99b9d435774b334e1040ba8555d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 31 Dec 2024 11:49:50 -0500 Subject: [PATCH 591/696] fix: Add sbillinge as the designated authorized user for release --- .../workflows/build-wheel-release-upload.yml | 2 ++ news/set-github-admin.rst | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 news/set-github-admin.rst diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 841d1a76..db92d9d7 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -11,6 +11,8 @@ jobs: uses: Billingegroup/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.utils + github_admin_username: sbillinge + secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/news/set-github-admin.rst b/news/set-github-admin.rst new file mode 100644 index 00000000..3f04cf1a --- /dev/null +++ b/news/set-github-admin.rst @@ -0,0 +1,23 @@ +**Added:** + +* sbillinge username as the authorized admin for GitHub release workflow in `build-wheel-release-upload.yml` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 6757a57f3900617c3db3796a32a78dbf98e4affb Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 16:16:26 -0500 Subject: [PATCH 592/696] docs: add docstrings and news --- news/DO-docstring.rst | 23 ++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 31 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 news/DO-docstring.rst diff --git a/news/DO-docstring.rst b/news/DO-docstring.rst new file mode 100644 index 00000000..e3107199 --- /dev/null +++ b/news/DO-docstring.rst @@ -0,0 +1,23 @@ +**Added:** + +* docstrings for `on_q`, `on_tth`, `on_d`, and `dump` in `diffraction_objects.py`. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index f95f1758..dc7bcea3 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -418,12 +418,33 @@ def _get_original_array(self): return self.on_d(), "d" def on_q(self): + """Return the tuple of two 1D numpy arrays containing q and y data. + + Returns + ------- + (xarray, yarray) : tuple of ndarray + The tuple containing two 1D numpy arrays with q and y data + """ return [self.all_arrays[:, 1], self.all_arrays[:, 0]] def on_tth(self): + """Return the tuple of two 1D numpy arrays containing tth and y data. + + Returns + ------- + (xarray, yarray) : tuple of ndarray + The tuple containing two 1D numpy arrays with tth and y data + """ return [self.all_arrays[:, 2], self.all_arrays[:, 0]] def on_d(self): + """Return the tuple of two 1D numpy arrays containing d and y data. + + Returns + ------- + (xarray, yarray) : tuple of ndarray + The tuple containing two 1D numpy arrays with d and y data + """ return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): @@ -507,6 +528,16 @@ def on_xtype(self, xtype): raise ValueError(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): + """Dump the xarray and yarray of the diffraction object to a two-column + file, with the associated information included in the header. + + Parameters + ---------- + filepath : str + The filepath where the diffraction object will be dumped + xtype : str, optional, default is q + The type of quantity for the independent variable chosen from {*XQUANTITIES, } + """ if xtype is None: xtype = "q" if xtype in QQUANTITIES: From da009de589d160e221d046104fc0e89434299773 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 16:40:17 -0500 Subject: [PATCH 593/696] docs: refine docstring and add example for dump --- src/diffpy/utils/diffraction_objects.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index dc7bcea3..a5784fde 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -422,7 +422,7 @@ def on_q(self): Returns ------- - (xarray, yarray) : tuple of ndarray + (q-array, y-array) : tuple of ndarray The tuple containing two 1D numpy arrays with q and y data """ return [self.all_arrays[:, 1], self.all_arrays[:, 0]] @@ -432,7 +432,7 @@ def on_tth(self): Returns ------- - (xarray, yarray) : tuple of ndarray + (tth-array, y-array) : tuple of ndarray The tuple containing two 1D numpy arrays with tth and y data """ return [self.all_arrays[:, 2], self.all_arrays[:, 0]] @@ -442,7 +442,7 @@ def on_d(self): Returns ------- - (xarray, yarray) : tuple of ndarray + (d-array, y-array) : tuple of ndarray The tuple containing two 1D numpy arrays with d and y data """ return [self.all_arrays[:, 3], self.all_arrays[:, 0]] @@ -537,6 +537,14 @@ def dump(self, filepath, xtype=None): The filepath where the diffraction object will be dumped xtype : str, optional, default is q The type of quantity for the independent variable chosen from {*XQUANTITIES, } + + Examples + -------- + To save a diffraction object to a file named "diffraction_data.chi" with + the independent variable 'q': + + >>> file = "diffraction_data.chi" + >>> do.dump(file, xtype="q") """ if xtype is None: xtype = "q" From dd5995b7be4f3ddce8cb1c7e45e52d30c72ce120 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 16:54:28 -0500 Subject: [PATCH 594/696] docs: add more examples --- src/diffpy/utils/diffraction_objects.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a5784fde..99b6b7a1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -540,11 +540,21 @@ def dump(self, filepath, xtype=None): Examples -------- - To save a diffraction object to a file named "diffraction_data.chi" with - the independent variable 'q': + To save a diffraction object to a file named "diffraction_data.chi" in the current directory + with the independent variable 'q': >>> file = "diffraction_data.chi" >>> do.dump(file, xtype="q") + + To save the diffraction data to a file in a subfolder `output`: + + >>> file = "./output/diffraction_data.chi" + >>> do.dump(file, xtype="q") + + To save the diffraction data with a different independent variable, such as 'tth': + + >>> file = "diffraction_data_tth.chi" + >>> do.dump(file, xtype="tth") """ if xtype is None: xtype = "q" From c23df0a92f8193024b0e1b97b326ffe2fc72d938 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 31 Dec 2024 18:25:55 -0500 Subject: [PATCH 595/696] docs: deploy docs on github pre-release with tag push --- .github/workflows/publish-docs-on-release.yml | 2 +- news/build-doc-automatic.rst | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 news/build-doc-automatic.rst diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml index b09c688a..e222ac53 100644 --- a/.github/workflows/publish-docs-on-release.yml +++ b/.github/workflows/publish-docs-on-release.yml @@ -2,7 +2,7 @@ name: Deploy Documentation on Release on: release: - types: [published] + types: [published, prereleased] workflow_dispatch: jobs: diff --git a/news/build-doc-automatic.rst b/news/build-doc-automatic.rst new file mode 100644 index 00000000..309fc888 --- /dev/null +++ b/news/build-doc-automatic.rst @@ -0,0 +1,23 @@ +**Added:** + +* deploy github pages documentation on pre-release + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 8bafe8da57e92d2738c83eea85f3044b59049ecc Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 15:17:05 -0500 Subject: [PATCH 596/696] simon doing some docs tweaking --- doc/source/examples/examples.rst | 6 +- doc/source/examples/parsers_example.rst | 82 +++++++++++++++------- doc/source/examples/resample_example.rst | 63 ++++++++++++----- doc/source/examples/transforms_example.rst | 4 ++ doc/source/index.rst | 18 +++-- doc/source/license.rst | 4 +- 6 files changed, 122 insertions(+), 55 deletions(-) diff --git a/doc/source/examples/examples.rst b/doc/source/examples/examples.rst index 5e285ea1..5a30d3b4 100644 --- a/doc/source/examples/examples.rst +++ b/doc/source/examples/examples.rst @@ -7,8 +7,8 @@ Examples Landing page for diffpy.utils examples. .. toctree:: - parsers_example + diffraction_objects_example + transforms_example resample_example + parsers_example tools_example - transforms_example - diffraction_objects_example diff --git a/doc/source/examples/parsers_example.rst b/doc/source/examples/parsers_example.rst index f9320885..92135f3b 100644 --- a/doc/source/examples/parsers_example.rst +++ b/doc/source/examples/parsers_example.rst @@ -9,49 +9,65 @@ This example will demonstrate how diffpy.utils lets us easily process and serial Using the parsers module, we can load file data into simple and easy-to-work-with Python objects. 1) To begin, unzip :download:`parser_data<./example_data/parser_data.zip>` and take a look at ``data.txt``. - Our goal will be to extract and serialize the data table as well as the parameters listed in the header of this file. + This is a fairly standard format for 1D powder diffraction data. + Our goal will be to extract the data, and the parameters listed in the header, from this file and + load it into our program. 2) To get the data table, we will use the ``loadData`` function. The default behavior of this - function is to find and extract a data table from a file.:: + function is to find and extract a data table from a file. + +.. code-block:: python from diffpy.utils.parsers.loaddata import loadData data_table = loadData('') - While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is - due to the comments ``$ Phase Transition Near This Temperature Range`` and ``--> Note Significant Jump in Rw <--`` - embedded within the dataset. To fix this, try using the ``comments`` parameter. :: +While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is +due to the comments ``$ Phase Transition Near This Temperature Range`` and ``--> Note Significant Jump in Rw <--`` +embedded within the dataset. To fix this, try using the ``comments`` parameter. + +.. code-block:: python data_table = loadData('', comments=['$', '-->']) - This parameter tells ``loadData`` that any lines beginning with ``$`` and ``-->`` are just comments and - more entries in our data table may follow. +This parameter tells ``loadData`` that any lines beginning with ``$`` and ``-->`` are just comments and +more entries in our data table may follow. - Here are a few other parameters to test out: +Here are a few other parameters to test out: * ``delimiter=','``: Look for a comma-separated data table. Useful for csv file types. - However, since ``data.txt`` is whitespace separated, running :: + However, since ``data.txt`` is whitespace separated, running + +.. code-block:: python loadData('', comments=['$', '-->'], delimiter=',') - returns an empty list. +returns an empty list. * ``minrows=50``: Only look for data tables with at least 50 rows. Since our data table has much less than that many - rows, running :: + rows, running + +.. code-block:: python loadData('', comments=['$', '-->'], minrows=50) - returns an empty list. +returns an empty list. * ``usecols=[0, 3]``: Only return the 0th and 3rd columns (zero-indexed) of the data table. For ``data.txt``, this - corresponds to the temperature and rw columns. :: + corresponds to the temperature and rw columns. + +.. code-block:: python loadData('', comments=['$', '-->'], usecols=[0, 3]) 3) Next, to get the header information, we can again use ``loadData``, - but this time with the ``headers`` parameter enabled. :: + but this time with the ``headers`` parameter enabled. + +.. code-block:: python hdata = loadData('', comments=['$', '-->'], headers=True) 4) Rather than working with separate ``data_table`` and ``hdata`` objects, it may be easier to combine them into a single - dictionary. We can do so using the ``serialize_data`` function. :: + dictionary. We can do so using the ``serialize_data`` function. + +.. code-block:: python from diffpy.utils.parsers.loaddata import serialize_data file_data = serialize_data('', hdata, data_table, dt_colnames=data_table_column_names) data_dict = file_data['data.txt'] - Now we can extract specific data table columns from the dictionary. :: +Now we can extract specific data table columns from the dictionary. +.. code-block:: python data_table_temperature_column = data_dict['temperature'] data_table_rw_column = data_dict['rw'] -5) When we are done working with the data, we can store it on disc for later use. This can also be done using the - ``serialize_data`` function with an additional ``serial_file`` parameter.:: +5) When we are done working with the data, we can store it on disk for later use. This can also be done using the + ``serialize_data`` function with an additional ``serial_file`` parameter. + +.. code-block:: python parsed_file_data = serialize_data('', hdata, data_table, serial_file='') The returned value, ``parsed_file_data``, is the dictionary we just added to ``serialfile.json``. - To extract the data from the serial file, we use ``deserialize_data``. :: + To extract the data from the serial file, we use ``deserialize_data``. + +.. code-block:: python from diffpy.utils.parsers.serialization import deserialize_data parsed_file_data = deserialize_data('') -6) Finally, ``serialize_data`` allows us to store data from multiple text file in a single serial file. For one last bit - of practice, we will extract and add the data from ``moredata.txt`` into the same ``serialdata.json`` file.:: +6) Finally, ``serialize_data`` allows us to store data from multiple text files in a single serial file. For one last bit + of practice, we will extract and add the data from ``moredata.txt`` into the same ``serialdata.json`` file. + +.. code-block:: python data_table = loadData('') hdata = loadData('', headers=True) serialize_data('', hdata, data_table, serial_file='') - The serial file ``serialfile.json`` should now contain two entries: ``data.txt`` and ``moredata.txt``. - The data from each file can be accessed using :: +The serial file ``serialfile.json`` should now contain two entries: ``data.txt`` and ``moredata.txt``. +The data from each file can be accessed using + +.. code-block:: python serial_data = deserialize_data('') data_txt_data = serial_data['data.txt'] # Access data.txt data moredata_txt_data = serial_data['moredata.txt'] # Access moredata.txt data For more information, check out the :ref:`documentation` of the ``parsers`` module. +b diff --git a/doc/source/examples/resample_example.rst b/doc/source/examples/resample_example.rst index 5e54fca1..4884f5c9 100644 --- a/doc/source/examples/resample_example.rst +++ b/doc/source/examples/resample_example.rst @@ -13,14 +13,29 @@ given enough datapoints. 1) To start, unzip :download:`parser_data<./example_data/parser_data.zip>`. Then, load the data table from ``Nickel.gr`` and ``NiTarget.gr``. These datasets are based on data from `Atomic Pair Distribution Function Analysis: A Primer `_. - :: - from diffpy.utils.parsers.loaddata import loadData - nickel_datatable = loadData('') - nitarget_datatable = loadData('') +.. code-block:: python - Each data table has two columns: first is the grid and second is the function value. - To extract the columns, we can utilize the serialize function ... :: + from diffpy.utils.parsers.loaddata import loadData + nickel_datatable = loadData('') + nitarget_datatable = loadData('') + +Each data table has two columns: first is the grid and second is the function value. +To extract the columns, we can utilize the serialize function ... + +.. code-block:: python + + from diffpy.utils.parsers.serialization import serialize_data + nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) + nickel_grid = nickel_data['Nickel.gr']['grid'] + nickel_func = nickel_data['Nickel.gr']['func'] + target_data = serialize_data('NiTarget.gr', {}, nitarget_datatable, dt_colnames=['grid', 'function']) + target_grid = nickel_data['Nickel.gr']['grid'] + target_func = nickel_data['Nickel.gr']['func'] + +To extract the columns, we can utilize the serialize function ... + +.. code-block:: python from diffpy.utils.parsers.serialization import serialize_data nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) @@ -30,42 +45,52 @@ given enough datapoints. target_grid = nickel_data['Nickel.gr']['grid'] target_func = nickel_data['Nickel.gr']['func'] - ... or you can use any other column extracting method you prefer. +... or you can use any other column extracting method you prefer. -2) If we plot the two on top of each other :: +2) If we plot the two on top of each other + +.. code-block:: python import matplotlib.pyplot as plt plt.plot(target_grid, target_func, linewidth=3) plt.plot(nickel_grid, nickel_func, linewidth=1) - they look pretty similar, but to truly see the difference, we should plot the difference between the two. - We may want to run something like ... :: +they look pretty similar, but to truly see the difference, we should plot the difference between the two. +We may want to run something like ... + +.. code-block:: python import numpy as np difference = np.subtract(target_func, nickel_func) - ... but this will only produce the right result if the ``target_func`` and ``nickel_func`` are on the same grid. - Checking the lengths of ``target_grid`` and ``nickel_grid`` shows that these grids are clearly distinct. +... but this will only produce the right result if the ``target_func`` and ``nickel_func`` are on the same grid. +Checking the lengths of ``target_grid`` and ``nickel_grid`` shows that these grids are clearly distinct. 3) However, we can resample the two functions to be on the same grid. Since both functions have grids spanning - ``[0, 60]``, let us define a new grid ... :: + ``[0, 60]``, let us define a new grid ... + +.. code-block:: python grid = np.linspace(0, 60, 6001) - ... and use the diffpy.utils ``wsinterp`` function to resample on this grid.:: +... and use the diffpy.utils ``wsinterp`` function to resample on this grid. + +.. code-block:: python from diffpy.utils.resampler import wsinterp nickel_resample = wsinterp(grid, nickel_grid, nickel_func) target_resample = wsinterp(grid, target_grid, target_func) - We can now plot the difference to see that these two functions are quite similar.:: +We can now plot the difference to see that these two functions are quite similar. + +.. code-block:: python plt.plot(grid, target_resample) plt.plot(grid, nickel_resample) plt.plot(grid, target_resample - nickel_resample) - This is the desired result as the data in ``Nickel.gr`` is every tenth data point in ``NiTarget.gr``. - This also shows us that ``wsinterp`` can help us reconstruct a function from incomplete data. +This is the desired result as the data in ``Nickel.gr`` is every tenth data point in ``NiTarget.gr``. +This also shows us that ``wsinterp`` can help us reconstruct a function from incomplete data. 4) In order for our function reconstruction to be perfect up to a truncation error, we require that (a) the function is a Fourier transform of a band-limited dataset and (b) the original grid has enough equally-spaced datapoints based on @@ -79,7 +104,9 @@ given enough datapoints. Thus, our original grid requires :math:`25.0 * 60.0 / \pi < 478`. Since our grid has :math:`601` datapoints, our reconstruction was perfect as shown from the comparison between ``Nickel.gr`` and ``NiTarget.gr``. - This computation is implemented in the function ``nsinterp``.:: + This computation is implemented in the function ``nsinterp``. + +.. code-block:: python from diffpy.utils.resampler import nsinterp qmin = 0 diff --git a/doc/source/examples/transforms_example.rst b/doc/source/examples/transforms_example.rst index df7666b9..2fc40c4a 100644 --- a/doc/source/examples/transforms_example.rst +++ b/doc/source/examples/transforms_example.rst @@ -16,6 +16,7 @@ This example will demonstrate how to use the functions in the # Example: convert q to 2theta from diffpy.utils.transforms import q_to_tth + wavelength = 0.71 q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) tth = q_to_tth(q, wavelength) @@ -32,6 +33,7 @@ This example will demonstrate how to use the functions in the # Example: convert 2theta to q from diffpy.utils.transforms import tth_to_q + wavelength = 0.71 tth = np.array([0, 30, 60, 90, 120, 180]) q = tth_to_q(tth, wavelength) @@ -49,11 +51,13 @@ This example will demonstrate how to use the functions in the # Example: convert d to q from diffpy.utils.transforms import d_to_q + d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) q = d_to_q(d) # Example: convert d to 2theta from diffpy.utils.transforms import d_to_tth + wavelength = 0.71 d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) tth = d_to_tth(d, wavelength) diff --git a/doc/source/index.rst b/doc/source/index.rst index f9f9fe80..7623146a 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -4,14 +4,18 @@ .. |title| replace:: diffpy.utils documentation -diffpy.utils - Shared utilities for diffpy packages. +diffpy.utils - General utilities for analyzing diffraction data | Software version |release|. | Last updated |today|. -The diffpy.utils package provides general functions for extracting data from variously formatted text files as well as -some PDF-specific functionality. These include wx GUI utilities used by the PDFgui program and an interpolation function -based on the Whittaker-Shannon formula for resampling a bandlimited PDF or other profile function. +The diffpy.utils package provides a number of functions and classes designed to help +researchers analyze their diffraction data. It also includes some functionality for +carrying out PDF analysis. Examples are parsers for reading common format diffraction +data files, ``DiffractionObjects`` that allow you to do algebra on diffraction patterns, +tools for better capture and propagation of metadata, +diffraction-friendly interpolation routines, as well as some other tools used across +diffpy libraries. Click :ref:`here` for a full list of utilities offered by diffpy.utils. @@ -20,6 +24,7 @@ Examples ======== Illustrations of when and how one would use various diffpy.utils functions. +* :ref:`Manipulate and do algebra on diffraction data` * :ref:`File Data Extraction` * :ref:`Resampling and Data Reconstruction` * :ref:`Load and Manage User and Package Information` @@ -30,8 +35,9 @@ Authors diffpy.utils is developed by members of the Billinge Group at Columbia University and at Brookhaven National Laboratory including -Pavol Juhás, Christopher L. Farrow, the Billinge Group -and its community contributors. +Pavol Juhás, Christopher L. Farrow, Simon J. L. Billinge, Andrew Yang, +with contributions from many Billinge Group members and +members of the diffpy community. For a detailed list of contributors see https://github.com/diffpy/diffpy.utils/graphs/contributors. diff --git a/doc/source/license.rst b/doc/source/license.rst index 38f27eec..b80d4eee 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -14,10 +14,10 @@ OPEN SOURCE LICENSE AGREEMENT Lawrence Berkeley National Laboratory | Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") | Copyright (c) 2006-2007, Board of Trustees of Michigan State University -| Copyright (c) 2008-2012, The Trustees of Columbia University in - the City of New York | Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National Laboratory +| Copyright (c) 2008-2025, The Trustees of Columbia University in + the City of New York The "DiffPy-CMI" is distributed subject to the following license conditions: From 0977b7fba5d6ec544c1bd22e9f9385a79aed0395 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 15:29:06 -0500 Subject: [PATCH 597/696] citation added to index of docs --- doc/source/index.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 7623146a..a406daa3 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -49,6 +49,14 @@ Installation See the `README `_ file included with the distribution. +======== +Citation +======== + +If you use this program for a scientific research that leads to publication, we ask that you acknowledge use of the program by citing the following paper in your publication: + + P. Juhás, C. L. Farrow, X. Yang, K. R. Knox and S. J. L. Billinge, Complex modeling: a strategy and software program for combining multiple information sources to solve ill posed structure and nanostructure inverse problems, Acta Crystallogr. A 71, 562-568 (2015). + ================= Table of contents ================= From 4d0b0f2be0442fb96c63da170321bf7356bec09b Mon Sep 17 00:00:00 2001 From: sbillinge Date: Wed, 1 Jan 2025 20:43:53 +0000 Subject: [PATCH 598/696] update changelog --- CHANGELOG.rst | 79 +++++++++++++++++++++++++++++ news/DO-docstring.rst | 23 --------- news/add-operations-tests.rst | 23 --------- news/array_index.rst | 23 --------- news/build-doc-automatic.rst | 23 --------- news/capture-user.rst | 23 --------- news/class-docstring.rst | 23 --------- news/codespell.rst | 23 --------- news/config-UX.rst | 23 --------- news/configupdate.rst | 23 --------- news/constructor.rst | 25 --------- news/d-q.rst | 23 --------- news/d-tth.rst | 23 --------- news/deepcopy.rst | 23 --------- news/deprecate.rst | 23 --------- news/doc-update.rst | 23 --------- news/doccln.rst | 23 --------- news/docformatter.rst | 23 --------- news/docs-do.rst | 23 --------- news/docstrings-refactor.rst | 23 --------- news/dump.rst | 23 --------- news/get-index.rst | 23 --------- news/is-float.rst | 23 --------- news/mu.rst | 23 --------- news/muD_calculator.rst | 24 --------- news/mv-input-scattering-quan.rst | 23 --------- news/no-empty-object.rst | 23 --------- news/no-news.rst | 23 --------- news/nsinterp.rst | 23 --------- news/op-mul-sub-div.rst | 23 --------- news/prettier-pre-commit.rst | 23 --------- news/private_f.rst | 24 --------- news/pytest-comment.rst | 23 --------- news/pytest-handle-warning.rst | 23 --------- news/pytest-reformat-transform.rst | 23 --------- news/pytest-test-refactor.rst | 23 --------- news/pytest-warning-divide-zero.rst | 23 --------- news/pytest-warning.rst | 23 --------- news/pytest-warnings-others.rst | 23 --------- news/pytest-wavelength-warnings.rst | 23 --------- news/remove-userconfig.rst | 23 --------- news/resample-dep.rst | 23 --------- news/resampler-relocation.rst | 23 --------- news/rm-range-methods.rst | 23 --------- news/scaleto-comments.rst | 23 --------- news/scaleto-max.rst | 23 --------- news/scaleto.rst | 23 --------- news/scattering-obj-valid.rst | 23 --------- news/scattering_obt.rst | 23 --------- news/set-github-admin.rst | 23 --------- news/setter-property.rst | 23 --------- news/test-func-format-compact.rst | 23 --------- news/test-refactor.rst | 23 --------- news/tests-restruct.rst | 23 --------- news/tools-doc-update.rst | 23 --------- news/tth-q.rst | 23 --------- news/user_info_doc.rst | 23 --------- news/userinfo.rst | 24 --------- news/uuid-rename.rst | 23 --------- news/uuid.rst | 23 --------- news/valid-empty-do.rst | 23 --------- news/xtype.rst | 23 --------- 62 files changed, 79 insertions(+), 1408 deletions(-) delete mode 100644 news/DO-docstring.rst delete mode 100644 news/add-operations-tests.rst delete mode 100644 news/array_index.rst delete mode 100644 news/build-doc-automatic.rst delete mode 100644 news/capture-user.rst delete mode 100644 news/class-docstring.rst delete mode 100644 news/codespell.rst delete mode 100644 news/config-UX.rst delete mode 100644 news/configupdate.rst delete mode 100644 news/constructor.rst delete mode 100644 news/d-q.rst delete mode 100644 news/d-tth.rst delete mode 100644 news/deepcopy.rst delete mode 100644 news/deprecate.rst delete mode 100644 news/doc-update.rst delete mode 100644 news/doccln.rst delete mode 100644 news/docformatter.rst delete mode 100644 news/docs-do.rst delete mode 100644 news/docstrings-refactor.rst delete mode 100644 news/dump.rst delete mode 100644 news/get-index.rst delete mode 100644 news/is-float.rst delete mode 100644 news/mu.rst delete mode 100644 news/muD_calculator.rst delete mode 100644 news/mv-input-scattering-quan.rst delete mode 100644 news/no-empty-object.rst delete mode 100644 news/no-news.rst delete mode 100644 news/nsinterp.rst delete mode 100644 news/op-mul-sub-div.rst delete mode 100644 news/prettier-pre-commit.rst delete mode 100644 news/private_f.rst delete mode 100644 news/pytest-comment.rst delete mode 100644 news/pytest-handle-warning.rst delete mode 100644 news/pytest-reformat-transform.rst delete mode 100644 news/pytest-test-refactor.rst delete mode 100644 news/pytest-warning-divide-zero.rst delete mode 100644 news/pytest-warning.rst delete mode 100644 news/pytest-warnings-others.rst delete mode 100644 news/pytest-wavelength-warnings.rst delete mode 100644 news/remove-userconfig.rst delete mode 100644 news/resample-dep.rst delete mode 100644 news/resampler-relocation.rst delete mode 100644 news/rm-range-methods.rst delete mode 100644 news/scaleto-comments.rst delete mode 100644 news/scaleto-max.rst delete mode 100644 news/scaleto.rst delete mode 100644 news/scattering-obj-valid.rst delete mode 100644 news/scattering_obt.rst delete mode 100644 news/set-github-admin.rst delete mode 100644 news/setter-property.rst delete mode 100644 news/test-func-format-compact.rst delete mode 100644 news/test-refactor.rst delete mode 100644 news/tests-restruct.rst delete mode 100644 news/tools-doc-update.rst delete mode 100644 news/tth-q.rst delete mode 100644 news/user_info_doc.rst delete mode 100644 news/userinfo.rst delete mode 100644 news/uuid-rename.rst delete mode 100644 news/uuid.rst delete mode 100644 news/valid-empty-do.rst delete mode 100644 news/xtype.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3032bdd3..16a17067 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,85 @@ Release Notes .. current developments +3.6.0 +===== + +**Added:** + +* unit tests for initializing DiffractionObject with empty array in xarray and yarray +* function to return the index of the closest value to the specified value in an array. +* functions to convert between d and q +* catch division by zero warning messages in tests +* functionality to raise useful warning and error messages during angular conversion between two theta and q +* Improved API documentation in `DiffractionObject` methods and properties using the NumPy docstring format and PEP 256 +* validate xtype belongs to XQUANTITIES during DiffractionObject init +* Group's Pytest practices for using @pytest.mark.parametrize in test_diffraction_objects.py +* unit tests for __add__ operation for DiffractionObject +* Better wording on the capture user info functionality +* Spelling check via Codespell in pre-commit +* prettier pre-commit hook for automatic linting of .yml, .json, and .md files +* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile +from a line-scan through the sample +* sbillinge username as the authorized admin for GitHub release workflow in `build-wheel-release-upload.yml` +* function to compute x-ray attenuation coefficient (mu) using XrayDB +* class docstring for `DiffractionObject` +* docforamtter in pre-commit for automatic formatting of docstrings to PEP 257 +* Function nsinterp for automatic interpolation onto the Nyquist-Shannon grid. +* functionality to return the 2D array based on the specified xtype +* functionality in dump to allow writing data on dspace +* addition, multiplication, subtraction, and division operators between two DiffractionObject instances or a scalar value with another DiffractionObject for modifying yarray (intensity) values. +* functionality to rescale diffraction objects, placing one on top of another at a specified point +* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object. +* functions to convert between d and tth +* unit test for expected warning when no wavelength is provided for DiffractionObject init +* copy() method for DiffractionObject instance +* docstrings for `on_q`, `on_tth`, `on_d`, and `dump` in `diffraction_objects.py`. +* prevent direct modification of `all_arrays` using `@property` +* Information on how to update the default user information +* Example docs for basic DiffractionObject usage +* deploy github pages documentation on pre-release +* Gettable `id` property to `DiffractionObject` + +**Changed:** + +* Refactor get_user_info to separate the tasks of getting the info from config files +and creating config files when they are missing. +* test comment format with compact style without extra line for each comment +* Rename `input_scattering_quantity` to `input_data` in `DiffractionObject` init +* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside +DiffractionObject +* Moved resampler out of parsers, new path is diffpy.utils.resampler +* Rename the `isfloat` function to `is_number`, and move it to the `diffpy/utils/utilsvalidators.py` directory +* arrays and attributes now can be inserted when a DiffractionObject is instantiated +* data are now stored as a (len(x),4) numpy array with intensity in column 0, the q, then tth, then d +* `DiffractionObject.on_q`, `...on_tth` and `...on_d` are now methods and called as `DiffractionObject.on_q()` etc.` +* \tests directory tree to match \src +* DiffractionObject's "id" property renamed to "uuid" +* `DiffractionObject` requires 3 input parameters of `xarray`, `yarray`, `xtype`, to be instantiated. It can be instantiated with empty arrays. +* Paths in our documentation reflect changes made in code. +* Enumerated list for the different ways to set user information + +**Deprecated:** + +* `resample` function in resampler. Replaced with `wsinterp` with better functionality. +* Diffraction_object class, renamed to DiffractionObject + +**Fixed:** + +* additional information to users to relieve frustration in finding how to update global config +* Unittest to Pytest migration for test_loaddata.py +* file paths of the test files according to new \tests directory tree +* Typo for get_package_info example +* return type of `get_array_index` method in `DiffractionObject` to return integer instead of list + +**Removed:** + +* scattering_objects layer in importing diffraction_objects +* `user_config.py`. Replaced by `_load_config` and `check_and_build_global_config` in `tools.py`. +* Relative imports in parser's __init__.py +* `set_angles_from_list`, `set_angles_from`, `set_qs_from_range` methods in `DiffractionObject` + + 3.5.0 ===== diff --git a/news/DO-docstring.rst b/news/DO-docstring.rst deleted file mode 100644 index e3107199..00000000 --- a/news/DO-docstring.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* docstrings for `on_q`, `on_tth`, `on_d`, and `dump` in `diffraction_objects.py`. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/add-operations-tests.rst b/news/add-operations-tests.rst deleted file mode 100644 index e59edbd5..00000000 --- a/news/add-operations-tests.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* unit tests for __add__ operation for DiffractionObject - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/array_index.rst b/news/array_index.rst deleted file mode 100644 index 6f687373..00000000 --- a/news/array_index.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* function to return the index of the closest value to the specified value in an array. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/build-doc-automatic.rst b/news/build-doc-automatic.rst deleted file mode 100644 index 309fc888..00000000 --- a/news/build-doc-automatic.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* deploy github pages documentation on pre-release - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/capture-user.rst b/news/capture-user.rst deleted file mode 100644 index b789d46e..00000000 --- a/news/capture-user.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Better wording on the capture user info functionality - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/class-docstring.rst b/news/class-docstring.rst deleted file mode 100644 index a31cc6d6..00000000 --- a/news/class-docstring.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* class docstring for `DiffractionObject` - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/codespell.rst b/news/codespell.rst deleted file mode 100644 index 8c5ba6d2..00000000 --- a/news/codespell.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Spelling check via Codespell in pre-commit - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/config-UX.rst b/news/config-UX.rst deleted file mode 100644 index af826723..00000000 --- a/news/config-UX.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* additional information to users to relieve frustration in finding how to update global config - -**Security:** - -* diff --git a/news/configupdate.rst b/news/configupdate.rst deleted file mode 100644 index 0bc3ffa9..00000000 --- a/news/configupdate.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news added: covered in the news from the get_user_info work - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/constructor.rst b/news/constructor.rst deleted file mode 100644 index 39ffa77f..00000000 --- a/news/constructor.rst +++ /dev/null @@ -1,25 +0,0 @@ -**Added:** - -* - -**Changed:** - -* arrays and attributes now can be inserted when a DiffractionObject is instantiated -* data are now stored as a (len(x),4) numpy array with intensity in column 0, the q, then tth, then d -* `DiffractionObject.on_q`, `...on_tth` and `...on_d` are now methods and called as `DiffractionObject.on_q()` etc.` - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/d-q.rst b/news/d-q.rst deleted file mode 100644 index 92105f07..00000000 --- a/news/d-q.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functions to convert between d and q - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/d-tth.rst b/news/d-tth.rst deleted file mode 100644 index 2bfc091f..00000000 --- a/news/d-tth.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functions to convert between d and tth - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/deepcopy.rst b/news/deepcopy.rst deleted file mode 100644 index 578d360c..00000000 --- a/news/deepcopy.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* copy() method for DiffractionObject instance - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/deprecate.rst b/news/deprecate.rst deleted file mode 100644 index 31dd10e5..00000000 --- a/news/deprecate.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* Diffraction_object class, renamed to DiffractionObject - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/doc-update.rst b/news/doc-update.rst deleted file mode 100644 index 1993a616..00000000 --- a/news/doc-update.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Paths in our documentation reflect changes made in code. - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/doccln.rst b/news/doccln.rst deleted file mode 100644 index 24127849..00000000 --- a/news/doccln.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Example docs for basic DiffractionObject usage - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docformatter.rst b/news/docformatter.rst deleted file mode 100644 index 56368125..00000000 --- a/news/docformatter.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* docforamtter in pre-commit for automatic formatting of docstrings to PEP 257 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docs-do.rst b/news/docs-do.rst deleted file mode 100644 index 21faf520..00000000 --- a/news/docs-do.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news added: adding documentation for diffraction object operation examples - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docstrings-refactor.rst b/news/docstrings-refactor.rst deleted file mode 100644 index 203b036e..00000000 --- a/news/docstrings-refactor.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Improved API documentation in `DiffractionObject` methods and properties using the NumPy docstring format and PEP 256 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/dump.rst b/news/dump.rst deleted file mode 100644 index 7f99d586..00000000 --- a/news/dump.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functionality in dump to allow writing data on dspace - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/get-index.rst b/news/get-index.rst deleted file mode 100644 index eef8c787..00000000 --- a/news/get-index.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* return type of `get_array_index` method in `DiffractionObject` to return integer instead of list - -**Security:** - -* diff --git a/news/is-float.rst b/news/is-float.rst deleted file mode 100644 index 918600e5..00000000 --- a/news/is-float.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Rename the `isfloat` function to `is_number`, and move it to the `diffpy/utils/utilsvalidators.py` directory - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/mu.rst b/news/mu.rst deleted file mode 100644 index bbf9cb1a..00000000 --- a/news/mu.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* function to compute x-ray attenuation coefficient (mu) using XrayDB - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/muD_calculator.rst b/news/muD_calculator.rst deleted file mode 100644 index a6731760..00000000 --- a/news/muD_calculator.rst +++ /dev/null @@ -1,24 +0,0 @@ -**Added:** - -* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile - from a line-scan through the sample - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/mv-input-scattering-quan.rst b/news/mv-input-scattering-quan.rst deleted file mode 100644 index 25e8aac9..00000000 --- a/news/mv-input-scattering-quan.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Rename `input_scattering_quantity` to `input_data` in `DiffractionObject` init - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/no-empty-object.rst b/news/no-empty-object.rst deleted file mode 100644 index 7e4ec7a4..00000000 --- a/news/no-empty-object.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* `DiffractionObject` requires 3 input parameters of `xarray`, `yarray`, `xtype`, to be instantiated. It can be instantiated with empty arrays. - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/no-news.rst b/news/no-news.rst deleted file mode 100644 index 31779a23..00000000 --- a/news/no-news.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: just making sure the previous add, mul, div, and sub tests are working as expected. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/nsinterp.rst b/news/nsinterp.rst deleted file mode 100644 index 9b716b87..00000000 --- a/news/nsinterp.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Function nsinterp for automatic interpolation onto the Nyquist-Shannon grid. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/op-mul-sub-div.rst b/news/op-mul-sub-div.rst deleted file mode 100644 index 9fe75ccc..00000000 --- a/news/op-mul-sub-div.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* addition, multiplication, subtraction, and division operators between two DiffractionObject instances or a scalar value with another DiffractionObject for modifying yarray (intensity) values. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/prettier-pre-commit.rst b/news/prettier-pre-commit.rst deleted file mode 100644 index d61d93ed..00000000 --- a/news/prettier-pre-commit.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* prettier pre-commit hook for automatic linting of .yml, .json, and .md files - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/private_f.rst b/news/private_f.rst deleted file mode 100644 index b67df7b7..00000000 --- a/news/private_f.rst +++ /dev/null @@ -1,24 +0,0 @@ -**Added:** - -* - -**Changed:** - -* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside - DiffractionObject - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-comment.rst b/news/pytest-comment.rst deleted file mode 100644 index 9d0f81bb..00000000 --- a/news/pytest-comment.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Group's Pytest practices for using @pytest.mark.parametrize in test_diffraction_objects.py - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-handle-warning.rst b/news/pytest-handle-warning.rst deleted file mode 100644 index 81bf78bf..00000000 --- a/news/pytest-handle-warning.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* catch division by zero warning messages in tests - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-reformat-transform.rst b/news/pytest-reformat-transform.rst deleted file mode 100644 index 2bd19e87..00000000 --- a/news/pytest-reformat-transform.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: previous PR already contains has an news item for refactoring test_transform.py - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-test-refactor.rst b/news/pytest-test-refactor.rst deleted file mode 100644 index 08d3819b..00000000 --- a/news/pytest-test-refactor.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: refactoring tests that have been mentioned in previous news - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-warning-divide-zero.rst b/news/pytest-warning-divide-zero.rst deleted file mode 100644 index 4b84dba2..00000000 --- a/news/pytest-warning-divide-zero.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: previous news created for catching divide by zero warnings in pytest - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-warning.rst b/news/pytest-warning.rst deleted file mode 100644 index 8900165f..00000000 --- a/news/pytest-warning.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-warnings-others.rst b/news/pytest-warnings-others.rst deleted file mode 100644 index 8900165f..00000000 --- a/news/pytest-warnings-others.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-wavelength-warnings.rst b/news/pytest-wavelength-warnings.rst deleted file mode 100644 index 3446720a..00000000 --- a/news/pytest-wavelength-warnings.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* unit test for expected warning when no wavelength is provided for DiffractionObject init - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/remove-userconfig.rst b/news/remove-userconfig.rst deleted file mode 100644 index 967d9559..00000000 --- a/news/remove-userconfig.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* `user_config.py`. Replaced by `_load_config` and `check_and_build_global_config` in `tools.py`. - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/resample-dep.rst b/news/resample-dep.rst deleted file mode 100644 index 91bb3e3e..00000000 --- a/news/resample-dep.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* `resample` function in resampler. Replaced with `wsinterp` with better functionality. - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/resampler-relocation.rst b/news/resampler-relocation.rst deleted file mode 100644 index 220fc2f5..00000000 --- a/news/resampler-relocation.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Moved resampler out of parsers, new path is diffpy.utils.resampler - -**Deprecated:** - -* - -**Removed:** - -* Relative imports in parser's __init__.py - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/rm-range-methods.rst b/news/rm-range-methods.rst deleted file mode 100644 index 8150da16..00000000 --- a/news/rm-range-methods.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* `set_angles_from_list`, `set_angles_from`, `set_qs_from_range` methods in `DiffractionObject` - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scaleto-comments.rst b/news/scaleto-comments.rst deleted file mode 100644 index 73d0f042..00000000 --- a/news/scaleto-comments.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news added - make test comments for `test_scale_to` more readable - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scaleto-max.rst b/news/scaleto-max.rst deleted file mode 100644 index d70e9d7f..00000000 --- a/news/scaleto-max.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scaleto.rst b/news/scaleto.rst deleted file mode 100644 index 6f6b0635..00000000 --- a/news/scaleto.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functionality to rescale diffraction objects, placing one on top of another at a specified point - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scattering-obj-valid.rst b/news/scattering-obj-valid.rst deleted file mode 100644 index adf4dfa9..00000000 --- a/news/scattering-obj-valid.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* validate xtype belongs to XQUANTITIES during DiffractionObject init - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst deleted file mode 100644 index 0090b88e..00000000 --- a/news/scattering_obt.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* scattering_objects layer in importing diffraction_objects - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/set-github-admin.rst b/news/set-github-admin.rst deleted file mode 100644 index 3f04cf1a..00000000 --- a/news/set-github-admin.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* sbillinge username as the authorized admin for GitHub release workflow in `build-wheel-release-upload.yml` - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/setter-property.rst b/news/setter-property.rst deleted file mode 100644 index 8b2ddc97..00000000 --- a/news/setter-property.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* prevent direct modification of `all_arrays` using `@property` - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/test-func-format-compact.rst b/news/test-func-format-compact.rst deleted file mode 100644 index ecb92ac0..00000000 --- a/news/test-func-format-compact.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* test comment format with compact style without extra line for each comment - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/test-refactor.rst b/news/test-refactor.rst deleted file mode 100644 index 095df231..00000000 --- a/news/test-refactor.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Unittest to Pytest migration for test_loaddata.py - -**Security:** - -* diff --git a/news/tests-restruct.rst b/news/tests-restruct.rst deleted file mode 100644 index 4b28c56f..00000000 --- a/news/tests-restruct.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* \tests directory tree to match \src - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* file paths of the test files according to new \tests directory tree - -**Security:** - -* diff --git a/news/tools-doc-update.rst b/news/tools-doc-update.rst deleted file mode 100644 index e04b7b17..00000000 --- a/news/tools-doc-update.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Information on how to update the default user information - -**Changed:** - -* Enumerated list for the different ways to set user information - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Typo for get_package_info example - -**Security:** - -* diff --git a/news/tth-q.rst b/news/tth-q.rst deleted file mode 100644 index c512bf9b..00000000 --- a/news/tth-q.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functionality to raise useful warning and error messages during angular conversion between two theta and q - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/user_info_doc.rst b/news/user_info_doc.rst deleted file mode 100644 index 43bee6bd..00000000 --- a/news/user_info_doc.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news added: simply adding documentation for config update workflow - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/userinfo.rst b/news/userinfo.rst deleted file mode 100644 index 124b49f8..00000000 --- a/news/userinfo.rst +++ /dev/null @@ -1,24 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Refactor get_user_info to separate the tasks of getting the info from config files - and creating config files when they are missing. - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/uuid-rename.rst b/news/uuid-rename.rst deleted file mode 100644 index ecd7f22d..00000000 --- a/news/uuid-rename.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* DiffractionObject's "id" property renamed to "uuid" - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/uuid.rst b/news/uuid.rst deleted file mode 100644 index 474793f4..00000000 --- a/news/uuid.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Gettable `id` property to `DiffractionObject` - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/valid-empty-do.rst b/news/valid-empty-do.rst deleted file mode 100644 index dfd98716..00000000 --- a/news/valid-empty-do.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* unit tests for initializing DiffractionObject with empty array in xarray and yarray - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/xtype.rst b/news/xtype.rst deleted file mode 100644 index 24a78758..00000000 --- a/news/xtype.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functionality to return the 2D array based on the specified xtype - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From b6c74c476b299394c4d5900c6cf0880b2cb0ff91 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 1 Jan 2025 16:04:03 -0500 Subject: [PATCH 599/696] docs: extra line break in 3.6.0 changelog.rst --- CHANGELOG.rst | 9 +++------ news/changelog-3.6.0-line-break.rst | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 news/changelog-3.6.0-line-break.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 16a17067..bc99672e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,8 +21,7 @@ Release Notes * Better wording on the capture user info functionality * Spelling check via Codespell in pre-commit * prettier pre-commit hook for automatic linting of .yml, .json, and .md files -* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile -from a line-scan through the sample +* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile from a line-scan through the sample * sbillinge username as the authorized admin for GitHub release workflow in `build-wheel-release-upload.yml` * function to compute x-ray attenuation coefficient (mu) using XrayDB * class docstring for `DiffractionObject` @@ -45,12 +44,10 @@ from a line-scan through the sample **Changed:** -* Refactor get_user_info to separate the tasks of getting the info from config files -and creating config files when they are missing. +* Refactor get_user_info to separate the tasks of getting the info from config files and creating config files when they are missing. * test comment format with compact style without extra line for each comment * Rename `input_scattering_quantity` to `input_data` in `DiffractionObject` init -* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside -DiffractionObject +* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside DiffractionObject * Moved resampler out of parsers, new path is diffpy.utils.resampler * Rename the `isfloat` function to `is_number`, and move it to the `diffpy/utils/utilsvalidators.py` directory * arrays and attributes now can be inserted when a DiffractionObject is instantiated diff --git a/news/changelog-3.6.0-line-break.rst b/news/changelog-3.6.0-line-break.rst new file mode 100644 index 00000000..ac80a0a1 --- /dev/null +++ b/news/changelog-3.6.0-line-break.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Extra line break in each news after 3.6.0 in `CHANGELOG.rst` so that this rst can be rendered correctly when deployed + +**Fixed:** + +* + +**Security:** + +* From a04340df7c7aeac199f43d861711dd3e4be96535 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 16:37:02 -0500 Subject: [PATCH 600/696] chore: synchronize README and index of the docs with updated text from docs --- README.rst | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 520d4029..cc86c81e 100644 --- a/README.rst +++ b/README.rst @@ -38,14 +38,16 @@ diffpy.utils Package ======================================================================== -General purpose shared utilities for the diffpy libraries. - -The diffpy.utils package provides functions for extracting array data from -variously formatted text files, an interpolation function based on the -Whittaker-Shannon formula that can be used to resample a PDF or other profile -function over a new grid, `DiffractionObject` for conveniently manipulating -diffraction data, and some wx GUI utilities used by the PDFgui -program. +General utilities for analyzing diffraction data + +The diffpy.utils package provides a number of functions and classes designed to help +researchers analyze their diffraction data. It also includes some functionality for +carrying out PDF analysis. Examples are parsers for reading common format diffraction +data files, ``DiffractionObjects`` that allow you to do algebra on diffraction patterns, +tools for better capture and propagation of metadata, +diffraction-friendly interpolation routines, as well as some other tools used across +diffpy libraries. + Citation -------- From 622c335c45f3395dada02d4e426ac2c1cea886cb Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 16:38:09 -0500 Subject: [PATCH 601/696] news --- news/readme.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/readme.rst diff --git a/news/readme.rst b/news/readme.rst new file mode 100644 index 00000000..978de17c --- /dev/null +++ b/news/readme.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news needed: just a chore of no import + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From f0350aa7e058b63c31b8ae4c3d8fcb75ae638361 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 16:48:24 -0500 Subject: [PATCH 602/696] shynch pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4f9238ae..90560367 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ maintainers = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] -description = "Shared utilities for diffpy packages" +description = "General utilities for analyzing diffraction data" keywords = ["text data parsers", "wx grid", "diffraction objects"] readme = "README.rst" requires-python = ">=3.11, <3.14" From 62525fd010843b17173cf117ecdffb171139e55d Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 31 Jan 2025 16:19:53 -0500 Subject: [PATCH 603/696] style: change the line-width limit to 79 --- .flake8 | 2 +- .isort.cfg | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.flake8 b/.flake8 index 2d2cb168..5a56eddd 100644 --- a/.flake8 +++ b/.flake8 @@ -5,7 +5,7 @@ exclude = build, dist, doc/source/conf.py -max-line-length = 115 +max-line-length = 79 # Ignore some style 'errors' produced while formatting by 'black' # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings extend-ignore = E203 diff --git a/.isort.cfg b/.isort.cfg index e0926f42..6d831957 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,4 +1,4 @@ [settings] -line_length = 115 +line_length = 79 multi_line_output = 3 include_trailing_comma = True diff --git a/pyproject.toml b/pyproject.toml index 90560367..10a6f890 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ ignore-words = ".codespell/ignore_words.txt" skip = "*.cif,*.dat" [tool.black] -line-length = 115 +line-length = 79 include = '\.pyi?$' exclude = ''' /( From e2f789c2f7217670f86467c81886aabe7f387a08 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 31 Jan 2025 16:23:52 -0500 Subject: [PATCH 604/696] style: apply black and flake8 to all files in the project --- doc/source/conf.py | 12 +- src/diffpy/utils/diffraction_objects.py | 74 +++++-- src/diffpy/utils/parsers/custom_exceptions.py | 4 +- src/diffpy/utils/parsers/loaddata.py | 25 ++- src/diffpy/utils/parsers/serialization.py | 8 +- src/diffpy/utils/tools.py | 70 +++++-- src/diffpy/utils/transforms.py | 8 +- src/diffpy/utils/wx/gridutils.py | 12 +- tests/conftest.py | 18 +- tests/test_diffraction_objects.py | 193 ++++++++++++++---- tests/test_loaddata.py | 4 +- tests/test_serialization.py | 61 ++++-- tests/test_tools.py | 136 +++++++++--- tests/test_transforms.py | 103 ++++++++-- 14 files changed, 585 insertions(+), 143 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 721f940e..045aba0a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -221,7 +221,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ("index", "diffpy.utils.tex", "diffpy.utils Documentation", ab_authors, "manual"), + ( + "index", + "diffpy.utils.tex", + "diffpy.utils Documentation", + ab_authors, + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -249,7 +255,9 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "diffpy.utils", "diffpy.utils Documentation", ab_authors, 1)] +man_pages = [ + ("index", "diffpy.utils", "diffpy.utils Documentation", ab_authors, 1) +] # If true, show URL addresses after external links. # man_show_urls = False diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 99b6b7a1..1276e08d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -6,13 +6,29 @@ import numpy as np from diffpy.utils.tools import get_package_info -from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q +from diffpy.utils.transforms import ( + d_to_q, + d_to_tth, + q_to_d, + q_to_tth, + tth_to_d, + tth_to_q, +) QQUANTITIES = ["q"] ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] DQUANTITIES = ["d", "dspace"] XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES -XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] +XUNITS = [ + "degrees", + "radians", + "rad", + "deg", + "inv_angs", + "inv_nm", + "nm-1", + "A-1", +] x_values_not_equal_emsg = ( "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " @@ -129,9 +145,13 @@ def __init__( """ self._uuid = uuid.uuid4() - self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) + self._input_data( + xarray, yarray, xtype, wavelength, scat_quantity, name, metadata + ) - def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): + def _input_data( + self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata + ): if xtype not in XQUANTITIES: raise ValueError(_xtype_wmsg(xtype)) if len(xarray) != len(yarray): @@ -152,8 +172,12 @@ def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, me def __eq__(self, other): if not isinstance(other, DiffractionObject): return NotImplemented - self_attributes = [key for key in self.__dict__ if not key.startswith("_")] - other_attributes = [key for key in other.__dict__ if not key.startswith("_")] + self_attributes = [ + key for key in self.__dict__ if not key.startswith("_") + ] + other_attributes = [ + key for key in other.__dict__ if not key.startswith("_") + ] if not sorted(self_attributes) == sorted(other_attributes): return False for key in self_attributes: @@ -167,8 +191,13 @@ def __eq__(self, other): or not np.isclose(value, other_value, rtol=1e-5) ): return False - elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): - if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): + elif isinstance(value, list) and all( + isinstance(i, np.ndarray) for i in value + ): + if not all( + np.allclose(i, j, rtol=1e-5) + for i, j in zip(value, other_value) + ): return False else: if value != other_value: @@ -303,7 +332,9 @@ def _check_operation_compatibility(self, other): if isinstance(other, DiffractionObject): if self.all_arrays.shape != other.all_arrays.shape: raise ValueError(x_values_not_equal_emsg) - if not np.allclose(self.all_arrays[:, [1, 2, 3]], other.all_arrays[:, [1, 2, 3]]): + if not np.allclose( + self.all_arrays[:, [1, 2, 3]], other.all_arrays[:, [1, 2, 3]] + ): raise ValueError(x_values_not_equal_emsg) @property @@ -381,7 +412,9 @@ def get_array_index(self, xtype, xvalue): xtype = self._input_xtype xarray = self.on_xtype(xtype)[0] if len(xarray) == 0: - raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") + raise ValueError( + f"The '{xtype}' array is empty. Please ensure it is initialized." + ) index = (np.abs(xarray - xvalue)).argmin() return index @@ -447,7 +480,9 @@ def on_d(self): """ return [self.all_arrays[:, 3], self.all_arrays[:, 0]] - def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): + def scale_to( + self, target_diff_object, q=None, tth=None, d=None, offset=None + ): """Return a new diffraction object which is the current object but rescaled in y to the target. @@ -486,7 +521,10 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): if count == 0: q_target_max = max(target_diff_object.on_q()[1]) q_self_max = max(self.on_q()[1]) - scaled_do._all_arrays[:, 0] = scaled_do._all_arrays[:, 0] * q_target_max / q_self_max + offset + scaled_do._all_arrays[:, 0] = ( + scaled_do._all_arrays[:, 0] * q_target_max / q_self_max + + offset + ) return scaled_do xtype = "q" if q is not None else "tth" if tth is not None else "d" @@ -497,7 +535,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): xindex_data = (np.abs(data[0] - xvalue)).argmin() xindex_target = (np.abs(target[0] - xvalue)).argmin() - scaled_do._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset + scaled_do._all_arrays[:, 0] = ( + data[1] * target[1][xindex_target] / data[1][xindex_data] + offset + ) return scaled_do def on_xtype(self, xtype): @@ -561,12 +601,16 @@ def dump(self, filepath, xtype=None): if xtype in QQUANTITIES: data_to_save = np.column_stack((self.on_q()[0], self.on_q()[1])) elif xtype in ANGLEQUANTITIES: - data_to_save = np.column_stack((self.on_tth()[0], self.on_tth()[1])) + data_to_save = np.column_stack( + (self.on_tth()[0], self.on_tth()[1]) + ) elif xtype in DQUANTITIES: data_to_save = np.column_stack((self.on_d()[0], self.on_d()[1])) else: warnings.warn(_xtype_wmsg(xtype)) - self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) + self.metadata.update( + get_package_info("diffpy.utils", metadata=self.metadata) + ) self.metadata["creation_time"] = datetime.datetime.now() with open(filepath, "w") as f: diff --git a/src/diffpy/utils/parsers/custom_exceptions.py b/src/diffpy/utils/parsers/custom_exceptions.py index ffb6aca6..f5fa9717 100644 --- a/src/diffpy/utils/parsers/custom_exceptions.py +++ b/src/diffpy/utils/parsers/custom_exceptions.py @@ -51,5 +51,7 @@ class ImproperSizeError(Exception): def __init__(self, bad_object, message=None): if message is None: - self.message = f"The size of {bad_object} is different than expected." + self.message = ( + f"The size of {bad_object} is different than expected." + ) super().__init__(self.message) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 8369e516..8154f8d5 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -20,7 +20,9 @@ from diffpy.utils import validators -def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs): +def loadData( + filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs +): """Find and load data from a text file. The data block is identified as the first matrix block of at least minrows rows and constant number of columns. @@ -105,7 +107,9 @@ def countcolumnsvalues(line): # Check if file exists before trying to open if not os.path.exists(filename): - raise IOError(f"File {filename} cannot be found. Please rerun the program specifying a valid filename.") + raise IOError( + f"File {filename} cannot be found. Please rerun the program specifying a valid filename." + ) # make sure fid gets cleaned up with open(filename, "rb") as fid: @@ -134,7 +138,10 @@ def countcolumnsvalues(line): if hignore is not None: for tag in hignore: taglen = len(tag) - if len(hpair[0]) >= taglen and hpair[0][:taglen] == tag: + if ( + len(hpair[0]) >= taglen + and hpair[0][:taglen] == tag + ): flag = False # add header data if flag: @@ -258,7 +265,13 @@ def _findDataBlocks(self): # nf - number of words, ok - has data self._linerecs = numpy.recarray( (nlines,), - dtype=[("idx", int), ("nw0", int), ("nw1", int), ("nf", int), ("ok", bool)], + dtype=[ + ("idx", int), + ("nw0", int), + ("nw1", int), + ("nf", int), + ("ok", bool), + ], ) lr = self._linerecs lr.idx = numpy.arange(nlines) @@ -319,7 +332,9 @@ def _findDataBlocks(self): if self.usecols is None: data = numpy.reshape(lw.value[bb1.nw0 : ee1.nw1], (-1, bb1.nf)) else: - tdata = numpy.empty((len(self.usecols), dend - dbeg), dtype=float) + tdata = numpy.empty( + (len(self.usecols), dend - dbeg), dtype=float + ) for j, trow in zip(self.usecols, tdata): j %= bb1.nf trow[:] = lw.value[bb1.nw0 + j : ee1.nw1 : bb1.nf] diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 20b34b31..bf585b7f 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -86,8 +86,12 @@ def serialize_data( num_columns = [len(row) for row in data_table] max_columns = max(num_columns) num_col_names = len(dt_colnames) - if max_columns < num_col_names: # assume numpy.loadtxt gives non-irregular array - raise ImproperSizeError("More entries in dt_colnames than columns in data_table.") + if ( + max_columns < num_col_names + ): # assume numpy.loadtxt gives non-irregular array + raise ImproperSizeError( + "More entries in dt_colnames than columns in data_table." + ) named_columns = 0 for idx in range(num_col_names): colname = dt_colnames[idx] diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 61868072..5924df23 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -84,7 +84,11 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): The dictionary containing username, email and orcid of the user/owner, and any other information stored in the global or local config files. """ - runtime_info = {"owner_name": owner_name, "owner_email": owner_email, "owner_orcid": owner_orcid} + runtime_info = { + "owner_name": owner_name, + "owner_email": owner_email, + "owner_orcid": owner_orcid, + } for key, value in copy(runtime_info).items(): if value is None or value == "": del runtime_info[key] @@ -141,7 +145,9 @@ def check_and_build_global_config(skip_config_creation=False): "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" ) print(intro_text) - username = input("Please enter the name you would want future work to be credited to: ").strip() + username = input( + "Please enter the name you would want future work to be credited to: " + ).strip() email = input("Please enter your email: ").strip() orcid = input("Please enter your orcid ID if you know it: ").strip() config = { @@ -208,7 +214,9 @@ def get_density_from_cloud(sample_composition, mp_token=""): ) -def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): +def compute_mu_using_xraydb( + sample_composition, energy, sample_mass_density=None, packing_fraction=None +): """Compute the attenuation coefficient (mu) using the XrayDB database. Computes mu based on the sample composition and energy. @@ -241,9 +249,19 @@ def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None "Please rerun specifying only one." ) if packing_fraction is not None: - sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction + sample_mass_density = ( + get_density_from_cloud(sample_composition) * packing_fraction + ) energy_eV = energy * 1000 - mu = material_mu(sample_composition, energy_eV, density=sample_mass_density, kind="total") / 10 + mu = ( + material_mu( + sample_composition, + energy_eV, + density=sample_mass_density, + kind="total", + ) + / 10 + ) return mu @@ -267,7 +285,11 @@ def _model_function(z, diameter, z0, I0, mud, slope): dz = z - z0 length = np.piecewise( dz, - [dz < min_radius, (min_radius <= dz) & (dz <= max_radius), dz > max_radius], + [ + dz < min_radius, + (min_radius <= dz) & (dz <= max_radius), + dz > max_radius, + ], [0, lambda dz: 2 * np.sqrt((diameter / 2) ** 2 - dz**2), 0], ) return (I0 - slope * z) * np.exp(-mud / diameter * length) @@ -278,8 +300,12 @@ def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): convolution), then perform convolution (note that the convolved I values are the same as modeled I values if slit width is close to 0)""" n_points = len(z) - z_left_pad = np.linspace(z.min() - n_points * (z[1] - z[0]), z.min(), n_points) - z_right_pad = np.linspace(z.max(), z.max() + n_points * (z[1] - z[0]), n_points) + z_left_pad = np.linspace( + z.min() - n_points * (z[1] - z[0]), z.min(), n_points + ) + z_right_pad = np.linspace( + z.max(), z.max() + n_points * (z[1] - z[0]), n_points + ) z_extended = np.concatenate([z_left_pad, z, z_right_pad]) I_extended = _model_function(z_extended, diameter, z0, I0, mud, slope) kernel = _top_hat(z_extended - z_extended.mean(), half_slit_width) @@ -296,7 +322,9 @@ def _objective_function(params, z, observed_data): observed/experimental data by minimizing the sum of squared residuals between the observed data and the convolved model data.""" diameter, half_slit_width, z0, I0, mud, slope = params - convolved_model_data = _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope) + convolved_model_data = _extend_z_and_convolve( + z, diameter, half_slit_width, z0, I0, mud, slope + ) residuals = observed_data - convolved_model_data return np.sum(residuals**2) @@ -304,16 +332,27 @@ def _objective_function(params, z, observed_data): def _compute_single_mud(z_data, I_data): """Perform dual annealing optimization and extract the parameters.""" bounds = [ - (1e-5, z_data.max() - z_data.min()), # diameter: [small positive value, upper bound] - (0, (z_data.max() - z_data.min()) / 2), # half slit width: [0, upper bound] + ( + 1e-5, + z_data.max() - z_data.min(), + ), # diameter: [small positive value, upper bound] + ( + 0, + (z_data.max() - z_data.min()) / 2, + ), # half slit width: [0, upper bound] (z_data.min(), z_data.max()), # z0: [min z, max z] - (1e-5, I_data.max()), # I0: [small positive value, max observed intensity] + ( + 1e-5, + I_data.max(), + ), # I0: [small positive value, max observed intensity] (1e-5, 20), # muD: [small positive value, upper bound] (-100000, 100000), # slope: [lower bound, upper bound] ] result = dual_annealing(_objective_function, bounds, args=(z_data, I_data)) diameter, half_slit_width, z0, I0, mud, slope = result.x - convolved_fitted_signal = _extend_z_and_convolve(z_data, diameter, half_slit_width, z0, I0, mud, slope) + convolved_fitted_signal = _extend_z_and_convolve( + z_data, diameter, half_slit_width, z0, I0, mud, slope + ) residuals = I_data - convolved_fitted_signal rmse = np.sqrt(np.mean(residuals**2)) return mud, rmse @@ -343,5 +382,8 @@ def compute_mud(filepath): The best-fit mu*D value. """ z_data, I_data = loadData(filepath, unpack=True) - best_mud, _ = min((_compute_single_mud(z_data, I_data) for _ in range(20)), key=lambda pair: pair[1]) + best_mud, _ = min( + (_compute_single_mud(z_data, I_data) for _ in range(20)), + key=lambda pair: pair[1], + ) return best_mud diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 21c069b1..693bb693 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -9,14 +9,14 @@ "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) -invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." +invalid_tth_emsg = ( + "Two theta exceeds 180 degrees. Please check the input values for errors." +) invalid_q_or_d_or_wavelength_emsg = ( "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_imsg = ( - "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." -) +inf_output_imsg = "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index a01e8f92..d6f2874a 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -26,7 +26,9 @@ def getSelectionRows(grid): rset.update(grid.GetSelectedRows()) for r, c in grid.GetSelectedCells(): rset.add(r) - blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) + blocks = zip( + grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight() + ) for tl, br in blocks: rset.update(range(tl[0], br[0] + 1)) rv = sorted(rset) @@ -42,7 +44,9 @@ def getSelectionColumns(grid): cset.update(grid.GetSelectedCols()) for r, c in grid.GetSelectedCells(): cset.add(c) - blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) + blocks = zip( + grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight() + ) for tl, br in blocks: cset.update(range(tl[1], br[1] + 1)) rv = sorted(cset) @@ -64,7 +68,9 @@ def getSelectedCells(grid): rcset.update(zip(cols * [r], allcols)) for c in grid.GetSelectedCols(): rcset.update(zip(allrows, rows * [c])) - blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) + blocks = zip( + grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight() + ) for tl, br in blocks: brows = range(tl[0], br[0] + 1) bcols = range(tl[1], br[1] + 1) diff --git a/tests/conftest.py b/tests/conftest.py index 296ae14f..b6d3bf3c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,19 +38,31 @@ def _load(filename): @pytest.fixture def do_minimal(): # Create an instance of DiffractionObject with empty xarray and yarray values, and a non-empty wavelength - return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="tth", wavelength=1.54) + return DiffractionObject( + xarray=np.empty(0), yarray=np.empty(0), xtype="tth", wavelength=1.54 + ) @pytest.fixture def do_minimal_tth(): # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values - return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + return DiffractionObject( + wavelength=2 * np.pi, + xarray=np.array([30, 60]), + yarray=np.array([1, 2]), + xtype="tth", + ) @pytest.fixture def do_minimal_d(): # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values - return DiffractionObject(wavelength=1.54, xarray=np.array([1, 2]), yarray=np.array([1, 2]), xtype="d") + return DiffractionObject( + wavelength=1.54, + xarray=np.array([1, 2]), + yarray=np.array([1, 2]), + xtype="d", + ) @pytest.fixture diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 13b3719b..7db4cc33 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -142,10 +142,16 @@ ], ) def test_diffraction_objects_equality( - do_args_1, do_args_2, expected_equality, wavelength_warning_expected, wavelength_warning_msg + do_args_1, + do_args_2, + expected_equality, + wavelength_warning_expected, + wavelength_warning_msg, ): if wavelength_warning_expected: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): do_1 = DiffractionObject(**do_args_1) do_2 = DiffractionObject(**do_args_2) else: @@ -184,7 +190,12 @@ def test_init_invalid_xtype(): f"Please rerun specifying an xtype from {*XQUANTITIES, }" ), ): - return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="invalid_type", wavelength=1.54) + return DiffractionObject( + xarray=np.empty(0), + yarray=np.empty(0), + xtype="invalid_type", + wavelength=1.54, + ) @pytest.mark.parametrize( @@ -304,7 +315,9 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): target_do = DiffractionObject(**target_do_args) scaled_do = original_do.scale_to(target_do, **scale_inputs) # Check the intensity data is the same as expected - assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) + assert np.allclose( + scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"] + ) @pytest.mark.parametrize( @@ -416,13 +429,25 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): ) def test_get_array_index(do_args, get_array_index_inputs, expected_index): do = DiffractionObject(**do_args) - actual_index = do.get_array_index(get_array_index_inputs["xtype"], get_array_index_inputs["value"]) + actual_index = do.get_array_index( + get_array_index_inputs["xtype"], get_array_index_inputs["value"] + ) assert actual_index == expected_index def test_get_array_index_bad(): - do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") - with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")): + do = DiffractionObject( + wavelength=2 * np.pi, + xarray=np.array([]), + yarray=np.array([]), + xtype="tth", + ) + with pytest.raises( + ValueError, + match=re.escape( + "The 'tth' array is empty. Please ensure it is initialized." + ), + ): do.get_array_index(xtype="tth", xvalue=30) @@ -430,7 +455,9 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): do = DiffractionObject( wavelength=1.54, name="test", @@ -438,7 +465,11 @@ def test_dump(tmp_path, mocker): xarray=np.array(x), yarray=np.array(y), xtype="q", - metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, + metadata={ + "thing1": 1, + "thing2": "thing2", + "package_info": {"package2": "3.4.5"}, + }, ) mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): @@ -547,7 +578,9 @@ def test_dump(tmp_path, mocker): ), ( # C4: Same as C3, but with an optional scat_quantity argument, expect non-empty string for scat_quantity { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "xarray": np.array( + [np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi] + ), "yarray": np.array([1.0, 2.0, 3.0]), "xtype": "d", "wavelength": 4.0 * np.pi, @@ -586,15 +619,23 @@ def test_init_valid( wavelength_warning_msg, ): if divide_by_zero_warning_expected: - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_do_dict = DiffractionObject(**do_init_args).__dict__ elif wavelength_warning_expected: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_do_dict = DiffractionObject(**do_init_args).__dict__ else: actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( - actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_uuid']" + actual_do_dict, + expected_do_dict, + ignore_order=True, + significant_digits=13, + exclude_paths="root['_uuid']", ) assert diff == {} @@ -624,7 +665,10 @@ def test_init_invalid_args( def test_all_array_getter(do_minimal_tth): actual_do = do_minimal_tth print(actual_do.all_arrays) - expected_all_arrays = [[1, 0.51763809, 30, 12.13818192], [2, 1, 60, 6.28318531]] + expected_all_arrays = [ + [1, 0.51763809, 30, 12.13818192], + [2, 1, 60, 6.28318531], + ] assert np.allclose(actual_do.all_arrays, expected_all_arrays) @@ -647,7 +691,9 @@ def test_uuid_getter(do_minimal): def test_uuid_getter_with_mock(mocker, do_minimal): mocker.patch.object( - DiffractionObject, "uuid", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + DiffractionObject, + "uuid", + new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87"), ) do = do_minimal assert do.uuid == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") @@ -671,7 +717,10 @@ def test_xarray_yarray_length_mismatch(): "re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's", ): DiffractionObject( - xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0]), xtype="tth", wavelength=1.54 + xarray=np.array([1.0, 2.0]), + yarray=np.array([0.0, 0.0, 0.0]), + xtype="tth", + wavelength=1.54, ) @@ -755,9 +804,13 @@ def test_copy_object(do_minimal): ), ], ) -def test_scalar_operations(operation, starting_yarray, scalar_value, expected_yarray, do_minimal_tth): +def test_scalar_operations( + operation, starting_yarray, scalar_value, expected_yarray, do_minimal_tth +): do = do_minimal_tth - expected_xarray_constant = np.array([[0.51763809, 30.0, 12.13818192], [1.0, 60.0, 6.28318531]]) + expected_xarray_constant = np.array( + [[0.51763809, 30.0, 12.13818192], [1.0, 60.0, 6.28318531]] + ) assert np.allclose(do.all_arrays[:, [1, 2, 3]], expected_xarray_constant) assert np.allclose(do.all_arrays[:, 0], starting_yarray) if operation == "add": @@ -775,8 +828,12 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya assert np.allclose(do_right_op.all_arrays[:, 0], expected_yarray) assert np.allclose(do_left_op.all_arrays[:, 0], expected_yarray) # Ensure x-values are unchanged - assert np.allclose(do_right_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) - assert np.allclose(do_left_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) + assert np.allclose( + do_right_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant + ) + assert np.allclose( + do_left_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant + ) @pytest.mark.parametrize( @@ -785,23 +842,63 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya # Test addition, subtraction, multiplication, and division of two DO objects ( # Test addition of two DO objects, expect combined yarray values "add", - np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), - np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array( + [ + [2.0, 0.51763809, 30.0, 12.13818192], + [4.0, 1.0, 60.0, 6.28318531], + ] + ), + np.array( + [ + [2.0, 0.51763809, 30.0, 12.13818192], + [4.0, 1.0, 60.0, 6.28318531], + ] + ), ), ( # Test subtraction of two DO objects, expect differences in yarray values "sub", - np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), - np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), + np.array( + [ + [0.0, 0.51763809, 30.0, 12.13818192], + [0.0, 1.0, 60.0, 6.28318531], + ] + ), + np.array( + [ + [0.0, 0.51763809, 30.0, 12.13818192], + [0.0, 1.0, 60.0, 6.28318531], + ] + ), ), ( # Test multiplication of two DO objects, expect multiplication in yarray values "mul", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [4.0, 1.0, 60.0, 6.28318531], + ] + ), + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [4.0, 1.0, 60.0, 6.28318531], + ] + ), ), ( # Test division of two DO objects, expect division in yarray values "div", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [1.0, 1.0, 60.0, 6.28318531], + ] + ), + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [1.0, 1.0, 60.0, 6.28318531], + ] + ), ), ], ) @@ -814,10 +911,22 @@ def test_binary_operator_on_do( do_1 = do_minimal_tth do_2 = do_minimal_tth assert np.allclose( - do_1.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) + do_1.all_arrays, + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [2.0, 1.0, 60.0, 6.28318531], + ] + ), ) assert np.allclose( - do_2.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) + do_2.all_arrays, + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [2.0, 1.0, 60.0, 6.28318531], + ] + ), ) if operation == "add": @@ -833,8 +942,12 @@ def test_binary_operator_on_do( do_1_y_modified = do_1 / do_2 do_2_y_modified = do_2 / do_1 - assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) - assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) + assert np.allclose( + do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified + ) + assert np.allclose( + do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified + ) def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @@ -848,9 +961,13 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): (lambda x, y: x / y), # Test division ] for operation in operations: - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + with pytest.raises( + TypeError, match=re.escape(invalid_add_type_error_msg) + ): operation(do, invalid_value) - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + with pytest.raises( + TypeError, match=re.escape(invalid_add_type_error_msg) + ): operation(invalid_value, do) @@ -861,7 +978,9 @@ def test_operator_invalid_xarray_values_not_equal( # Add two DO objects with different xarray values but equal in shape, expect ValueError do_1 = do_minimal_tth do_2 = do_minimal_d - with pytest.raises(ValueError, match=re.escape(x_values_not_equal_error_msg)): + with pytest.raises( + ValueError, match=re.escape(x_values_not_equal_error_msg) + ): if operation == "add": do_1 + do_2 elif operation == "sub": @@ -879,7 +998,9 @@ def test_operator_invalid_xarray_shape_not_equal( # Add two DO objects with different xarrays shape, expect ValueError do_1 = do_minimal do_2 = do_minimal_tth - with pytest.raises(ValueError, match=re.escape(x_values_not_equal_error_msg)): + with pytest.raises( + ValueError, match=re.escape(x_values_not_equal_error_msg) + ): if operation == "add": do_1 + do_2 elif operation == "sub": diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index f825139c..7580c672 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -76,5 +76,7 @@ def test_loadData_headers(datafile): delimiter = ": " # what our data should be separated by # Load data with headers - hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) + hdata = loadData( + loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore + ) assert hdata == expected diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 0ba397ad..039877f6 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -3,7 +3,10 @@ import numpy import pytest -from diffpy.utils.parsers.custom_exceptions import ImproperSizeError, UnsupportedTypeError +from diffpy.utils.parsers.custom_exceptions import ( + ImproperSizeError, + UnsupportedTypeError, +) from diffpy.utils.parsers.loaddata import loadData from diffpy.utils.parsers.serialization import deserialize_data, serialize_data @@ -23,7 +26,13 @@ def test_load_multiple(tmp_path, datafile): data_table = loadData(headerfile) # check path extraction - generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=["r", "gr"], show_path=True) + generated_data = serialize_data( + headerfile, + hdata, + data_table, + dt_colnames=["r", "gr"], + show_path=True, + ) assert headerfile == Path(generated_data[headerfile.name].pop("path")) # rerun without path information and save to file @@ -60,28 +69,54 @@ def test_exceptions(datafile): # various dt_colnames inputs with pytest.raises(ImproperSizeError): - serialize_data(loadfile, hdata, data_table, dt_colnames=["one", "two", "three is too many"]) + serialize_data( + loadfile, + hdata, + data_table, + dt_colnames=["one", "two", "three is too many"], + ) # check proper output - normal = serialize_data(loadfile, hdata, data_table, dt_colnames=["r", "gr"]) + normal = serialize_data( + loadfile, hdata, data_table, dt_colnames=["r", "gr"] + ) data_name = list(normal.keys())[0] r_list = normal[data_name]["r"] gr_list = normal[data_name]["gr"] # three equivalent ways to denote no column names - missing_parameter = serialize_data(loadfile, hdata, data_table, show_path=False) - empty_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[]) - none_entry_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None]) + missing_parameter = serialize_data( + loadfile, hdata, data_table, show_path=False + ) + empty_parameter = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[] + ) + none_entry_parameter = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None] + ) # check equivalence assert missing_parameter == empty_parameter assert missing_parameter == none_entry_parameter - assert numpy.allclose(missing_parameter[data_name]["data table"], data_table) + assert numpy.allclose( + missing_parameter[data_name]["data table"], data_table + ) # extract a single column - r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=["r"]) - gr_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "gr"]) - incorrect_r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "r"]) + r_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=["r"] + ) + gr_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "gr"] + ) + incorrect_r_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "r"] + ) # check proper columns extracted - assert numpy.allclose(gr_extract[data_name]["gr"], incorrect_r_extract[data_name]["r"]) + assert numpy.allclose( + gr_extract[data_name]["gr"], incorrect_r_extract[data_name]["r"] + ) assert "r" not in gr_extract[data_name] - assert "gr" not in r_extract[data_name] and "gr" not in incorrect_r_extract[data_name] + assert ( + "gr" not in r_extract[data_name] + and "gr" not in incorrect_r_extract[data_name] + ) # check correct values extracted assert numpy.allclose(r_extract[data_name]["r"], r_list) assert numpy.allclose(gr_extract[data_name]["gr"], gr_list) diff --git a/tests/test_tools.py b/tests/test_tools.py index 4843fede..8d69c6c0 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -20,30 +20,55 @@ "runtime_inputs, expected", [ # config file in home is present, no config in cwd. various runtime values passed # C1: nothing passed in, expect uname, email, orcid from home_config - ({}, {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}), + ( + {}, + { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + }, + ), # C2: empty strings passed in, expect uname, email, orcid from home_config ( {"owner_name": "", "owner_email": "", "owner_orcid": ""}, - {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + }, ), # C3: just owner name passed in at runtime. expect runtime_oname but others from config ( {"owner_name": "runtime_ownername"}, - {"owner_name": "runtime_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "runtime_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + }, ), # C4: just owner email passed in at runtime. expect runtime_email but others from config ( {"owner_email": "runtime@email.com"}, - {"owner_name": "home_ownername", "owner_email": "runtime@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "home_ownername", + "owner_email": "runtime@email.com", + "owner_orcid": "home_orcid", + }, ), # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config ( {"owner_orcid": "runtime_orcid"}, - {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "runtime_orcid"}, + { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "runtime_orcid", + }, ), ], ) -def test_get_user_info_with_home_conf_file(runtime_inputs, expected, user_filesystem, mocker): +def test_get_user_info_with_home_conf_file( + runtime_inputs, expected, user_filesystem, mocker +): # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) @@ -57,35 +82,63 @@ def test_get_user_info_with_home_conf_file(runtime_inputs, expected, user_filesy [ # tests as before but now config file present in cwd and home but orcid # missing in the cwd config # C1: nothing passed in, expect uname, email from local config, orcid from home_config - ({}, {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}), + ( + {}, + { + "owner_name": "cwd_ownername", + "owner_email": "cwd@email.com", + "owner_orcid": "home_orcid", + }, + ), # C2: empty strings passed in, expect uname, email, orcid from home_config ( {"owner_name": "", "owner_email": "", "owner_orcid": ""}, - {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "cwd_ownername", + "owner_email": "cwd@email.com", + "owner_orcid": "home_orcid", + }, ), # C3: just owner name passed in at runtime. expect runtime_oname but others from config ( {"owner_name": "runtime_ownername"}, - {"owner_name": "runtime_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "runtime_ownername", + "owner_email": "cwd@email.com", + "owner_orcid": "home_orcid", + }, ), # C4: just owner email passed in at runtime. expect runtime_email but others from config ( {"owner_email": "runtime@email.com"}, - {"owner_name": "cwd_ownername", "owner_email": "runtime@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "cwd_ownername", + "owner_email": "runtime@email.com", + "owner_orcid": "home_orcid", + }, ), # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config ( {"owner_orcid": "runtime_orcid"}, - {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "runtime_orcid"}, + { + "owner_name": "cwd_ownername", + "owner_email": "cwd@email.com", + "owner_orcid": "runtime_orcid", + }, ), ], ) -def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_filesystem, mocker): +def test_get_user_info_with_local_conf_file( + runtime_inputs, expected, user_filesystem, mocker +): # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) - local_config_data = {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com"} + local_config_data = { + "owner_name": "cwd_ownername", + "owner_email": "cwd@email.com", + } with open(user_filesystem[1] / "diffpyconfig.json", "w") as f: json.dump(local_config_data, f) actual = get_user_info(**runtime_inputs) @@ -97,16 +150,25 @@ def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_files [ # Check check_and_build_global_config() builds correct config when config is found missing ( # C1: user inputs valid name, email and orcid {"user_inputs": ["input_name", "input@email.com", "input_orcid"]}, - {"owner_email": "input@email.com", "owner_orcid": "input_orcid", "owner_name": "input_name"}, + { + "owner_email": "input@email.com", + "owner_orcid": "input_orcid", + "owner_name": "input_name", + }, ), - ({"user_inputs": ["", "", ""]}, None), # C2: empty strings passed in, expect no config file created + ( + {"user_inputs": ["", "", ""]}, + None, + ), # C2: empty strings passed in, expect no config file created ( # C3: just username input, expect config file but with some empty values {"user_inputs": ["input_name", "", ""]}, {"owner_email": "", "owner_orcid": "", "owner_name": "input_name"}, ), ], ) -def test_check_and_build_global_config(test_inputs, expected, user_filesystem, mocker): +def test_check_and_build_global_config( + test_inputs, expected, user_filesystem, mocker +): # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) @@ -131,7 +193,11 @@ def test_check_and_build_global_config_file_exists(user_filesystem, mocker): mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) confile = user_filesystem[0] / "diffpyconfig.json" - expected = {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"} + expected = { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + } actual_bool = check_and_build_global_config() assert actual_bool is True with open(confile, "r") as f: @@ -152,11 +218,29 @@ def test_check_and_build_global_config_skipped(user_filesystem, mocker): params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), - (["package1", None], {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), - (["package1", {"thing1": 1}], {"thing1": 1, "package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), ( - ["package1", {"package_info": {"package1": "1.1.0", "package2": "3.4.5"}}], - {"package_info": {"package1": "1.2.3", "package2": "3.4.5", "diffpy.utils": "3.3.0"}}, + ["package1", None], + {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}, + ), + ( + ["package1", {"thing1": 1}], + { + "thing1": 1, + "package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}, + }, + ), + ( + [ + "package1", + {"package_info": {"package1": "1.1.0", "package2": "3.4.5"}}, + ], + { + "package_info": { + "package1": "1.2.3", + "package2": "3.4.5", + "diffpy.utils": "3.3.0", + } + }, ), ] @@ -164,7 +248,11 @@ def test_check_and_build_global_config_skipped(user_filesystem, mocker): @pytest.mark.parametrize("inputs, expected", params_package_info) def test_get_package_info(monkeypatch, inputs, expected): monkeypatch.setattr( - importlib.metadata, "version", lambda package_name: "3.3.0" if package_name == "diffpy.utils" else "1.2.3" + importlib.metadata, + "version", + lambda package_name: ( + "3.3.0" if package_name == "diffpy.utils" else "1.2.3" + ), ) actual_metadata = get_package_info(inputs[0], metadata=inputs[1]) assert actual_metadata == expected @@ -202,7 +290,9 @@ def test_compute_mu_using_xraydb_bad(inputs): def test_compute_mud(tmp_path): diameter, slit_width, z0, I0, mud, slope = 1, 0.1, 0, 1e5, 3, 0 z_data = np.linspace(-1, 1, 50) - convolved_I_data = _extend_z_and_convolve(z_data, diameter, slit_width, z0, I0, mud, slope) + convolved_I_data = _extend_z_and_convolve( + z_data, diameter, slit_width, z0, I0, mud, slope + ) directory = Path(tmp_path) file = directory / "testfile" diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 0396c420..4617584c 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -3,7 +3,14 @@ import numpy as np import pytest -from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q +from diffpy.utils.transforms import ( + d_to_q, + d_to_tth, + q_to_d, + q_to_tth, + tth_to_d, + tth_to_q, +) @pytest.mark.parametrize( @@ -30,7 +37,9 @@ ) def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): if wavelength is None: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) @@ -53,7 +62,9 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): ), ], ) -def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): +def test_q_to_tth_bad( + wavelength, q, expected_error_type, invalid_q_or_d_or_wavelength_error_msg +): expected_error_msg = invalid_q_or_d_or_wavelength_error_msg with pytest.raises(expected_error_type, match=expected_error_msg): q_to_tth(wavelength, q) @@ -83,7 +94,9 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave ) def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): if wavelength is None: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_q = tth_to_q(tth, wavelength) else: actual_q = tth_to_q(tth, wavelength) @@ -109,7 +122,9 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): ), ], ) -def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): +def test_tth_to_q_bad( + wavelength, tth, expected_error_type, expected_error_msg +): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_q(tth, wavelength) @@ -122,12 +137,16 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): (np.array([]), np.array([]), False), # C2: ( # 1. Valid q values, expect d values without warning - np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), + np.array( + [0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi] + ), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), ( # 2. Valid q values containing 0, expect d values with divide by zero warning - np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), + np.array( + [0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi] + ), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), True, ), @@ -135,7 +154,9 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): ) def test_q_to_d(q, expected_d, warning_expected): if warning_expected: - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_d = q_to_d(q) else: actual_d = q_to_d(q) @@ -157,7 +178,9 @@ def test_q_to_d(q, expected_d, warning_expected): ) def test_d_to_q(d, expected_q, zero_divide_error_expected): if zero_divide_error_expected: - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_q = d_to_q(d) else: actual_q = d_to_q(d) @@ -173,7 +196,12 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): # C2: Empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), # C3: User specified valid tth values between 0-180 degrees (without wavelength) - (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), + ( + None, + np.array([0, 30, 60, 90, 120, 180]), + np.array([0, 1, 2, 3, 4, 5]), + False, + ), ( # C4: User specified valid tth values between 0-180 degrees (with wavelength) 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), @@ -182,12 +210,22 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): ), ], ) -def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, wavelength_warning_msg): +def test_tth_to_d( + wavelength, + tth, + expected_d, + divide_by_zero_warning_expected, + wavelength_warning_msg, +): if wavelength is None: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_d = tth_to_d(tth, wavelength) elif divide_by_zero_warning_expected: - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_d = tth_to_d(tth, wavelength) else: actual_d = tth_to_d(tth, wavelength) @@ -211,7 +249,9 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, ), ], ) -def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_msg): +def test_tth_to_d_invalid( + wavelength, tth, expected_error_type, expected_error_msg +): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_d(tth, wavelength) @@ -224,22 +264,41 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m # C2: Empty d values with wavelength, expect empty tth values (4 * np.pi, np.empty((0)), np.empty(0), False), # C3: Valid d values, no wavelength, expect valid and non-empty tth values - (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), + ( + None, + np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), + np.array([0, 1, 2, 3, 4, 5]), + True, + ), ( # C4: Valid d values with wavelength, expect valid and non-empty thh values 4 * np.pi, - np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), + np.array( + [4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi] + ), np.array([60.0, 90.0, 120.0]), False, ), ], ) -def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, wavelength_warning_msg): +def test_d_to_tth( + wavelength, + d, + expected_tth, + divide_by_zero_warning_expected, + wavelength_warning_msg, +): if wavelength is None and not divide_by_zero_warning_expected: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_tth = d_to_tth(d, wavelength) elif wavelength is None and divide_by_zero_warning_expected: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_tth = d_to_tth(d, wavelength) else: actual_tth = d_to_tth(d, wavelength) @@ -255,7 +314,9 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) -def test_d_to_tth_bad(wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): +def test_d_to_tth_bad( + wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg +): expected_error_msg = invalid_q_or_d_or_wavelength_error_msg with pytest.raises(expected_error_type, match=expected_error_msg): d_to_tth(d, wavelength) From f4aebe83df09835bebcde425fe4b1c38ac7b7ca2 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Sun, 2 Feb 2025 20:44:25 -0500 Subject: [PATCH 605/696] style: apply manual line-shortening to files --- news/linelength79.rst | 23 ++++ src/diffpy/utils/diffraction_objects.py | 123 ++++++++++++-------- src/diffpy/utils/parsers/loaddata.py | 70 +++++++----- src/diffpy/utils/parsers/serialization.py | 35 ++++-- src/diffpy/utils/resampler.py | 49 ++++---- src/diffpy/utils/tools.py | 131 +++++++++++++--------- src/diffpy/utils/transforms.py | 31 +++-- src/diffpy/utils/validators.py | 5 +- tests/conftest.py | 37 +++--- tests/test_diffraction_objects.py | 103 +++++++++++------ tests/test_loaddata.py | 6 +- tests/test_resample.py | 3 +- tests/test_tools.py | 61 ++++++---- tests/test_transforms.py | 74 ++++++++---- 14 files changed, 482 insertions(+), 269 deletions(-) create mode 100644 news/linelength79.rst diff --git a/news/linelength79.rst b/news/linelength79.rst new file mode 100644 index 00000000..be12d2fb --- /dev/null +++ b/news/linelength79.rst @@ -0,0 +1,23 @@ +**Added:** + +* update isort, flake8 and black to set line limit to 79 + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1276e08d..25f32f4d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -31,15 +31,17 @@ ] x_values_not_equal_emsg = ( - "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " - "Please ensure the x values of the two objects are identical by re-instantiating " - "the DiffractionObject with the correct x value inputs." + "The two objects have different values in x arrays " + "(my_do.all_arrays[:, [1, 2, 3]]). " + "Please ensure the x values of the two objects are identical by " + "re-instantiating the DiffractionObject with the correct x value inputs." ) invalid_add_type_emsg = ( - "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " - "Please rerun by adding another DiffractionObject instance or a scalar value. " - "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" + "You may only add a DiffractionObject with another DiffractionObject or " + "a scalar value. " + "Please rerun by adding another DiffractionObject instance or a " + "scalar value. e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @@ -70,11 +72,14 @@ class DiffractionObject: Attributes ---------- scat_quantity : str - The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". + The type of scattering experiment (e.g., "x-ray", "neutron"). Default + is an empty string "". wavelength : float - The wavelength of the incoming beam, specified in angstroms (Å). Default is none. + The wavelength of the incoming beam, specified in angstroms (Å). + Default is none. name: str - The name or label for the scattering data. Default is an empty string "". + The name or label for the scattering data. Default is an empty string + "". qmin : float The minimum q value. qmax : float @@ -108,7 +113,8 @@ def __init__( yarray : ndarray The dependent variable array corresponding to intensity values. xtype : str - The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. + The type of the independent variable in `xarray`. Must be one of + {*XQUANTITIES}. wavelength : float, optional, default is None. The wavelength of the incoming beam, specified in angstroms (Å) scat_quantity : str, optional, default is an empty string "". @@ -124,7 +130,7 @@ def __init__( >>> import numpy as np >>> from diffpy.utils.diffraction_objects import DiffractionObject ... - >>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) + >>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) # noqa: E501 >>> y = np.array([10, 20, 40, 60]) # intensity values >>> metadata = { ... "sample": "rock salt from the beach", @@ -211,23 +217,29 @@ def __add__(self, other): Parameters ---------- other : DiffractionObject, int, or float - The item to be added. If `other` is a scalar value, this value will be added to each element of the - yarray of this DiffractionObject instance. If `other` is another DiffractionObject, the yarrays of the - two DiffractionObjects will be combined element-wise. The result is a new DiffractionObject instance, - representing the addition and using the xarray from the left-hand side DiffractionObject. + The item to be added. If `other` is a scalar value, this value + will be added to each element of the yarray of this + DiffractionObject instance. If `other` is another DiffractionObject + , the yarrays of the two DiffractionObjects will be combined + element -wise. The result is a new DiffractionObject instance, + representing the addition and using the xarray from the left-hand + side DiffractionObject. Returns ------- DiffractionObject - The new DiffractionObject instance with modified yarray values. This instance is a deep copy of the - original with the additions applied. + The new DiffractionObject instance with modified yarray values. + This instance is a deep copy of the original with the additions + applied. Raises ------ ValueError - Raised when the xarrays of two DiffractionObject instances are not equal. + Raised when the xarrays of two DiffractionObject instances are + not equal. TypeError - Raised when `other` is not an instance of DiffractionObject, int, or float. + Raised when `other` is not an instance of DiffractionObject, int, + or float. Examples -------- @@ -253,12 +265,14 @@ def __sub__(self, other): """Subtract scalar value or another DiffractionObject to the yarray of the DiffractionObject. - This method behaves similarly to the `__add__` method, but performs subtraction instead of addition. - For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + This method behaves similarly to the `__add__` method, but performs + subtraction instead of addition. For details on parameters, returns + , and exceptions, refer to the documentation for `__add__`. Examples -------- - Subtract a scalar value from the yarray of a DiffractionObject instance: + Subtract a scalar value from the yarray of a DiffractionObject + instance: >>> new_do = my_do - 10.1 Subtract the yarrays of two DiffractionObject instances: @@ -279,12 +293,14 @@ def __mul__(self, other): """Multiply a scalar value or another DiffractionObject with the yarray of this DiffractionObject. - This method behaves similarly to the `__add__` method, but performs multiplication instead of addition. - For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + This method behaves similarly to the `__add__` method, but performs + multiplication instead of addition. For details on parameters, + returns, and exceptions, refer to the documentation for `__add__`. Examples -------- - Multiply a scalar value with the yarray of a DiffractionObject instance: + Multiply a scalar value with the yarray of a DiffractionObject + instance: >>> new_do = my_do * 3.5 Multiply the yarrays of two DiffractionObject instances: @@ -305,8 +321,9 @@ def __truediv__(self, other): """Divide the yarray of this DiffractionObject by a scalar value or another DiffractionObject. - This method behaves similarly to the `__add__` method, but performs division instead of addition. - For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + This method behaves similarly to the `__add__` method, but performs + division instead of addition. For details on parameters, returns, + and exceptions, refer to the documentation for `__add__`. Examples -------- @@ -344,8 +361,8 @@ def all_arrays(self): Returns ------- ndarray - The shape (len(data), 4) 2D array with columns containing the `yarray` (intensity) - and the `xarray` values in q, tth, and d. + The shape (len(data), 4) 2D array with columns containing the ` + yarray` (intensity) and the `xarray` values in q, tth, and d. Examples -------- @@ -399,21 +416,24 @@ def get_array_index(self, xtype, xvalue): Parameters ---------- xtype : str - The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. + The type of the independent variable in `xarray`. Must be one + of {*XQUANTITIES}. xvalue : float The value of the xtype to find the closest index for. Returns ------- index : int - The index of the closest value in the array associated with the specified xtype and the value provided. + The index of the closest value in the array associated with the + specified xtype and the value provided. """ xtype = self._input_xtype xarray = self.on_xtype(xtype)[0] if len(xarray) == 0: raise ValueError( - f"The '{xtype}' array is empty. Please ensure it is initialized." + f"The '{xtype}' array is empty. " + "Please ensure it is initialized." ) index = (np.abs(xarray - xvalue)).argmin() return index @@ -486,10 +506,13 @@ def scale_to( """Return a new diffraction object which is the current object but rescaled in y to the target. - By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. - Otherwise, y-value in the target at the closest specified x-value will be used as the factor to scale to. - The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. + By default, if `q`, `tth`, or `d` are not provided, scaling is + based on the max intensity from each object. Otherwise, y-value in + the target at the closest specified x-value will be used as the + factor to scale to. The entire array is scaled by this factor so + that one object places on top of the other at that point. If + multiple values of `q`, `tth`, or `d` are provided, an error will + be raised. Parameters ---------- @@ -497,8 +520,9 @@ def scale_to( The diffraction object you want to scale the current one onto. q, tth, d : float, optional, default is None - The value of the x-array where you want the curves to line up vertically. - Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. + The value of the x-array where you want the curves to line up + vertically. Specify a value on one of the allowed grids, q, tth, + or d), e.g., q=10. offset : float, optional, default is None The offset to add to the scaled y-values. @@ -546,7 +570,8 @@ def on_xtype(self, xtype): Parameters ---------- xtype : str - The type of quantity for the independent variable chosen from {*XQUANTITIES, } + The type of quantity for the independent variable chosen from + {*XQUANTITIES, } Raises ------ @@ -556,7 +581,8 @@ def on_xtype(self, xtype): Returns ------- (xarray, yarray) : tuple of ndarray - The tuple containing two 1D numpy arrays with x and y data for the specified xtype. + The tuple containing two 1D numpy arrays with x and y data for + the specified xtype. """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() @@ -576,12 +602,13 @@ def dump(self, filepath, xtype=None): filepath : str The filepath where the diffraction object will be dumped xtype : str, optional, default is q - The type of quantity for the independent variable chosen from {*XQUANTITIES, } + The type of quantity for the independent variable chosen from + {*XQUANTITIES, } Examples -------- - To save a diffraction object to a file named "diffraction_data.chi" in the current directory - with the independent variable 'q': + To save a diffraction object to a file named "diffraction_data.chi" + in the current directory with the independent variable 'q': >>> file = "diffraction_data.chi" >>> do.dump(file, xtype="q") @@ -591,7 +618,8 @@ def dump(self, filepath, xtype=None): >>> file = "./output/diffraction_data.chi" >>> do.dump(file, xtype="q") - To save the diffraction data with a different independent variable, such as 'tth': + To save the diffraction data with a different independent variable, + such as 'tth': >>> file = "diffraction_data_tth.chi" >>> do.dump(file, xtype="tth") @@ -615,7 +643,9 @@ def dump(self, filepath, xtype=None): with open(filepath, "w") as f: f.write( - f"[DiffractionObject]\nname = {self.name}\nwavelength = {self.wavelength}\n" + f"[DiffractionObject]\n" + f"name = {self.name}\n" + f"wavelength = {self.wavelength}\n" f"scat_quantity = {self.scat_quantity}\n" ) for key, value in self.metadata.items(): @@ -629,6 +659,7 @@ def copy(self): Returns ------- DiffractionObject - The new instance of DiffractionObject, which is a deep copy of the current instance. + The new instance of DiffractionObject, which is a deep copy of + the current instance. """ return deepcopy(self) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 8154f8d5..05d37497 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -25,53 +25,62 @@ def loadData( ): """Find and load data from a text file. - The data block is identified as the first matrix block of at least minrows rows and constant number of columns. - This seems to work for most of the datafiles including those generated by diffpy programs. + The data block is identified as the first matrix block of at least + minrows rows and constant number of columns. This seems to work for most + of the datafiles including those generated by diffpy programs. Parameters ---------- filename Name of the file we want to load data from. minrows: int - Minimum number of rows in the first data block. All rows must have the same number of floating - point values. + Minimum number of rows in the first data block. All rows must have + the same number of floating point values. headers: bool - when False (default), the function returns a numpy array of the data in the data block. - When True, the function instead returns a dictionary of parameters and their corresponding - values parsed from header (information prior the data block). See hdel and hignore for options - to help with parsing header information. + when False (default), the function returns a numpy array of the data + in the data block. When True, the function instead returns a + dictionary of parameters and their corresponding values parsed from + header (information prior the data block). See hdel and hignore for + options to help with parsing header information. hdel: str - (Only used when headers enabled.) Delimiter for parsing header information (default '='). e.g. using - default hdel, the line 'parameter = p_value' is put into the dictionary as {parameter: p_value}. + (Only used when headers enabled.) Delimiter for parsing header + information (default '='). e.g. using default hdel, the line ' + parameter = p_value' is put into the dictionary as + {parameter: p_value}. hignore: list - (Only used when headers enabled.) Ignore header rows beginning with any elements in hignore. - e.g. hignore=['# ', '['] causes the following lines to be skipped: '# qmax=10', '[defaults]'. + (Only used when headers enabled.) Ignore header rows beginning with + any elements in hignore. e.g. hignore=['# ', '['] causes the + following lines to be skipped: '# qmax=10', '[defaults]'. kwargs: - Keyword arguments that are passed to numpy.loadtxt including the following arguments below. (See - numpy.loadtxt for more details.) Only pass kwargs used by numpy.loadtxt. + Keyword arguments that are passed to numpy.loadtxt including the + following arguments below. (See numpy.loadtxt for more details.) Only + pass kwargs used by numpy.loadtxt. Useful kwargs ============= comments: str, sequence of str - The characters or list of characters used to indicate the start of a comment (default '#'). - Comment lines are ignored. + The characters or list of characters used to indicate the start of a + comment (default '#'). Comment lines are ignored. delimiter: str - Delimiter for the data in the block (default use whitespace). For comma-separated data blocks, - set delimiter to ','. + Delimiter for the data in the block (default use whitespace). For + comma-separated data blocks, set delimiter to ','. unpack: bool - Return data as a sequence of columns that allows tuple unpacking such as x, y = - loadData(FILENAME, unpack=True). Note transposing the loaded array as loadData(FILENAME).T has the same - effect. + Return data as a sequence of columns that allows tuple unpacking such + as x, y = loadData(FILENAME, unpack=True). Note transposing the + loaded array as loadData(FILENAME).T has the same effect. usecols: - Zero-based index of columns to be loaded, by default use all detected columns. The reading skips - data blocks that do not have the usecols-specified columns. + Zero-based index of columns to be loaded, by default use all detected + columns. The reading skips data blocks that do not have the usecols- + specified columns. Returns ------- data_block: ndarray - A numpy array containing the found data block. (This is not returned if headers is enabled.) + A numpy array containing the found data block. (This is not returned + if headers is enabled.) hdata: dict - If headers are enabled, return a dictionary of parameters read from the header. + If headers are enabled, return a dictionary of parameters read from + the header. """ from numpy import array, loadtxt @@ -108,7 +117,10 @@ def countcolumnsvalues(line): # Check if file exists before trying to open if not os.path.exists(filename): raise IOError( - f"File {filename} cannot be found. Please rerun the program specifying a valid filename." + ( + f"File {filename} cannot be found. " + "Please rerun the program specifying a valid filename." + ) ) # make sure fid gets cleaned up @@ -194,7 +206,8 @@ class TextDataLoader(object): minrows: int Minimum number of rows in the first data block. (Default 10.) usecols: tuple - Which columns in our dataset to use. Ignores all other columns. If None (default), use all columns. + Which columns in our dataset to use. Ignores all other columns. If + None (default), use all columns. skiprows Rows in dataset to skip. (Currently not functional.) """ @@ -242,7 +255,8 @@ def readfp(self, fp, append=False): File details include: * File name. * All data blocks findable by loadData. - * Headers (if present) for each data block. (Generally the headers contain column name information). + * Headers (if present) for each data block. (Generally the headers + contain column name information). """ self._reset() # try to read lines from fp first diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index bf585b7f..7531c77f 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -47,16 +47,19 @@ def serialize_data( data_table: list or ndarray Data table. dt_colnames: list - Names of each column in data_table. Every name in data_table_cols will be put into the Dictionary - as a key with a value of that column in data_table (stored as a List). Put None for columns - without names. If dt_cols has less non-None entries than columns in data_table, - the pair {'data table': data_table} will be put in the dictionary. - (Default None: only entry {'data table': data_table} will be added to dictionary.) + Names of each column in data_table. Every name in data_table_cols + will be put into the Dictionary as a key with a value of that column + in data_table (stored as a List). Put None for columns without names. + If dt_cols has less non-None entries than columns in data_table, the + pair {'data table': data_table} will be put in the dictionary. + (Default None: only entry {'data table': data_table} will be added to + dictionary.) show_path: bool - include a path element in the database entry (default True). If 'path' is not included in hddata, - extract path from filename. + include a path element in the database entry (default True). If + 'path' is not included in hddata, extract path from filename. serial_file - Serial language file to dump dictionary into. If None (default), no dumping will occur. + Serial language file to dump dictionary into. If None (default), no + dumping will occur. Returns ------- @@ -79,7 +82,8 @@ def serialize_data( data.update(hdata) # second add named columns in dt_cols - # performed second to prioritize overwriting hdata entries with data_table column entries + # performed second to prioritize overwriting hdata entries with data_ + # table column entries named_columns = 0 # initial value max_columns = 1 # higher than named_columns to trigger 'data table' entry if dt_colnames is not None: @@ -98,17 +102,24 @@ def serialize_data( if colname is not None: if colname in hdata.keys(): warnings.warn( - f"Entry '{colname}' in hdata has been overwritten by a data_table entry.", + ( + f"Entry '{colname}' in hdata has been " + "overwritten by a data_table entry." + ), RuntimeWarning, ) data.update({colname: list(data_table[:, idx])}) named_columns += 1 - # finally add data_table as an entry named 'data table' if not all columns were parsed + # finally add data_table as an entry named 'data table' if not all + # columns were parsed if named_columns < max_columns: if "data table" in data.keys(): warnings.warn( - "Entry 'data table' in hdata has been overwritten by data_table.", + ( + "Entry 'data table' in hdata has been " + "overwritten by data_table." + ), RuntimeWarning, ) data.update({"data table": data_table}) diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 115087a2..ca93761c 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -22,11 +22,11 @@ def wsinterp(x, xp, fp, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation. - Reconstruct a continuous signal from discrete data points by utilizing sinc functions - as interpolation kernels. This function interpolates the values of fp (array), - which are defined over xp (array), at new points x (array or float). - The implementation is based on E. T. Whittaker's 1915 paper - (https://doi.org/10.1017/S0370164600017806). + Reconstruct a continuous signal from discrete data points by utilizing + sinc functions as interpolation kernels. This function interpolates the + values of fp (array), which are defined over xp (array), at new points x + (array or float). The implementation is based on E. T. Whittaker's 1915 + paper (https://doi.org/10.1017/S0370164600017806). Parameters ---------- @@ -37,17 +37,18 @@ def wsinterp(x, xp, fp, left=None, right=None): fp: ndarray The array of y values associated with xp. left: float - If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, - set fp for x < xp[0] to fp evaluated at xp[-1]. + If given, set fp for x < xp[0] to left. Otherwise, if left is None + (default) or not given, set fp for x < xp[0] to fp evaluated at xp[-1]. right: float - If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for - x > xp[-1] to fp evaluated at xp[-1]. + If given, set fp for x > xp[-1] to right. Otherwise, if right is None + (default) or not given, set fp for x > xp[-1] to fp evaluated at + xp[-1]. Returns ------- ndarray or float - The interpolated values at points x. Returns a single float if x is a scalar, - otherwise returns a numpy.ndarray. + The interpolated values at points x. Returns a single float if x is a + scalar, otherwise returns a numpy.ndarray. """ scalar = np.isscalar(x) if scalar: @@ -82,10 +83,11 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon grid. - Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. - Uses the minimum number of points N required by the Nyquist sampling theorem. - N = (qmax-qmin)(rmax-rmin)/pi, where rmin and rmax are the ends of the real-space ranges. - fp must be finite, and the user inputs qmin and qmax of the frequency-domain. + Takes a band-limited function fp and original grid xp and resamples fp on + the NS grid. Uses the minimum number of points N required by the Nyquist + sampling theorem. N = (qmax-qmin)(rmax-rmin)/pi, where rmin and rmax are + the ends of the real-space ranges. fp must be finite, and the user inputs + qmin and qmax of the frequency-domain. Parameters ---------- @@ -103,8 +105,8 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): x: ndarray The Nyquist-Shannon grid computed for the given qmin and qmax. fp_at_x: ndarray - The interpolated values at points x. Returns a single float if x is a scalar, - otherwise returns a numpy.ndarray. + The interpolated values at points x. Returns a single float if x is a + scalar, otherwise returns a numpy.ndarray. """ # Ensure numpy array xp = np.array(xp) @@ -122,8 +124,9 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): def resample(r, s, dr): """Resample a PDF on a new grid. - This uses the Whittaker-Shannon interpolation formula to put s1 on a new grid if dr is less than the sampling - interval of r1, or linear interpolation if dr is greater than the sampling interval of r1. + This uses the Whittaker-Shannon interpolation formula to put s1 on a new + grid if dr is less than the sampling interval of r1, or linear + interpolation if dr is greater than the sampling interval of r1. Parameters ---------- @@ -140,8 +143,12 @@ def resample(r, s, dr): """ warnings.warn( - "The 'resample' function is deprecated and will be removed in a future release (3.8.0). \n" - "'resample' has been renamed 'wsinterp' to better reflect functionality. Please use 'wsinterp' instead.", + ( + "The 'resample' function is deprecated and will be removed " + "in a future release (3.8.0). \n" + "'resample' has been renamed 'wsinterp' to better reflect " + "functionality. Please use 'wsinterp' instead." + ), DeprecationWarning, stacklevel=2, ) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 5924df23..63e10ba2 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -22,7 +22,8 @@ def _stringify(string_value): Returns ------- str - The original string if string_value is not None, otherwise an empty string. + The original string if string_value is not None, otherwise an empty + string. """ return string_value if string_value is not None else "" @@ -53,36 +54,40 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """Get name, email, and orcid of the owner/user from various sources and return it as a metadata dictionary. - The function looks for the information in json format configuration files with the name 'diffpyconfig.json'. - These can be in the user's home directory and in the current working directory. The information in the - config files are combined, with the local config overriding the home-directory one. Values for - owner_name, owner_email, and owner_orcid may be passed in to the function and these override the values - in the config files. - - A template for the config file is below. Create a text file called 'diffpyconfig.json' in your home directory - and copy-paste the template into it, editing it with your real information. + The function looks for the information in json format configuration files + with the name 'diffpyconfig.json'. These can be in the user's home + directory and in the current working directory. The information in the + config files are combined, with the local config overriding the + home- directory one. Values for owner_name, owner_email, and owner_orcid + may be passed in to the function and these override the values in the + config files. + + A template for the config file is below. Create a text file called ' + diffpyconfig.json' in your home directory and copy-paste the template + into it, editing it with your real information. { "owner_name": ">", "owner_email": ">@email.com", - "owner_orcid": ">" + "owner_orcid": ">" # noqa: E501 } - You may also store any other global-level information that you would like associated with your - diffraction data in this file + You may also store any other global-level information that you would like + associated with your diffraction data in this file Parameters ---------- - owner_name : str, optional, default is the value stored in the global or local config file. - The name of the user who will show as owner in the metadata that is stored with the data - owner_email : str, optional, default is the value stored in the global or local config file. - The email of the user/owner - owner_orcid : str, optional, default is the value stored in the global or local config file. - The ORCID id of the user/owner + owner_name : str, optional, default is the value stored in the global or + local config file. The name of the user who will show as owner in the + metadata that is stored with the data + owner_email : str, optional, default is the value stored in the global or + local config file. The email of the user/owner + owner_orcid : str, optional, default is the value stored in the global or + local config file. The ORCID id of the user/owner Returns ------- user_info : dict - The dictionary containing username, email and orcid of the user/owner, and any other information - stored in the global or local config files. + The dictionary containing username, email and orcid of the user/owner + , and any other information stored in the global or local config files. """ runtime_info = { "owner_name": owner_name, @@ -104,24 +109,27 @@ def check_and_build_global_config(skip_config_creation=False): """Check for a global diffpu config file in user's home directory and creates one if it is missing. - The file it looks for is called diffpyconfig.json. This can contain anything in json format, but - minimally contains information about the computer owner. The information is used - when diffpy objects are created and saved to files or databases to retain ownership information - of datasets. For example, it is used by diffpy.utils.tools.get_user_info(). + The file it looks for is called diffpyconfig.json. This can contain + anything in json format, but minimally contains information about the + computer owner. The information is used when diffpy objects are created + and saved to files or databases to retain ownership information of + datasets. For example, it is used by diffpy.utils.tools.get_user_info(). - If the function finds no config file in the user's home directory it interrupts execution - and prompts the user for name, email, and orcid information. It then creates the config file - with this information inside it. + If the function finds no config file in the user's home directory it + interrupts execution and prompts the user for name, email, and orcid + information. It then creates the config file with this information + inside it. The function returns True if the file exists and False otherwise. - If you would like to check for a file but not run the file creation workflow you can set - the optional argument skip_config_creation to True. + If you would like to check for a file but not run the file creation + workflow you can set the optional argument skip_config_creation to True. Parameters ---------- skip_config_creation : bool, optional, default is False. - The boolean that will override the creation workflow even if no config file exists. + The boolean that will override the creation workflow even if no + config file exists. Returns ------- @@ -136,13 +144,14 @@ def check_and_build_global_config(skip_config_creation=False): if skip_config_creation: return config_exists intro_text = ( - "No global configuration file was found containing information about the user to " - "associate with the data.\n By following the prompts below you can add your name " - "and email to this file on the current " - "computer and your name will be automatically associated with subsequent diffpy data by default.\n" - "This is not recommended on a shared or public computer. " - "You will only have to do that once.\n" - "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" + "No global configuration file was found containing information about " + "the user to associate with the data.\n By following the prompts " + "below you can add your name and email to this file on the current " + "computer and your name will be automatically associated with " + "subsequent diffpy data by default.\n This is not recommended on a " + "shared or public computer. You will only have to do that once.\n " + "For more information, please refer to www.diffpy.org/diffpy.utils/ " + "examples/toolsexample.html " ) print(intro_text) username = input( @@ -160,14 +169,15 @@ def check_and_build_global_config(skip_config_creation=False): with open(config_path, "w") as f: f.write(json.dumps(config)) outro_text = ( - f"The config file at {Path().home() / 'diffpyconfig.json'} has been created. " - f"The values {config} were entered.\n" - f"These values will be inserted as metadata with your data in apps that use " - f"diffpy.get_user_info(). If you would like to update these values, either " - f"delete the config file and this workflow will rerun next time you run this " - f"program. Or you may open the config file in a text editor and manually edit the" - f"entries. For more information, see: " - f"https://diffpy.github.io/diffpy.utils/examples/tools_example.html" + f"The config file at {Path().home() / 'diffpyconfig.json'} has " + f"been created. The values {config} were entered.\n These values " + "will be inserted as metadata with your data in apps that use " + "diffpy.get_user_info(). If you would like to update these values " + ", either delete the config file and this workflow will rerun " + "next time you run this program. Or you may open the config " + "file in a text editor and manually edit the entries. For more " + "information, see: " + "https://diffpy.github.io/diffpy.utils/examples/tools_example.html" ) print(outro_text) config_exists = True @@ -177,13 +187,15 @@ def check_and_build_global_config(skip_config_creation=False): def get_package_info(package_names, metadata=None): """Fetch package version and updates it into (given) metadata. - Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. + Package info stored in metadata as + {'package_info': {'package_name': 'version_number'}}. ---------- package_name : str or list The name of the package(s) to retrieve the version number for. metadata : dict - The dictionary to store the package info. If not provided, a new dictionary will be created. + The dictionary to store the package info. If not provided, a new + dictionary will be created. Returns ------- @@ -208,7 +220,8 @@ def get_density_from_cloud(sample_composition, mp_token=""): It is not implemented yet. """ raise NotImplementedError( - "So sorry, density computation from composition is not implemented right now. " + "So sorry, density computation from composition is not implemented " + "right now. " "We hope to have this implemented in the next release. " "Please rerun specifying a sample mass density." ) @@ -221,7 +234,8 @@ def compute_mu_using_xraydb( Computes mu based on the sample composition and energy. User should provide a sample mass density or a packing fraction. - If neither density nor packing fraction is specified, or if both are specified, a ValueError will be raised. + If neither density nor packing fraction is specified, + or if both are specified, a ValueError will be raised. Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. Parameters @@ -245,7 +259,8 @@ def compute_mu_using_xraydb( sample_mass_density is not None and packing_fraction is not None ): raise ValueError( - "You must specify either sample_mass_density or packing_fraction, but not both. " + "You must specify either sample_mass_density or packing_fraction, " + "but not both. " "Please rerun specifying only one." ) if packing_fraction is not None: @@ -275,8 +290,10 @@ def _model_function(z, diameter, z0, I0, mud, slope): """ Compute the model function with the following steps: 1. Let dz = z-z0, so that dz is centered at 0 - 2. Compute length l that is the effective length for computing intensity I = I0 * e^{-mu * l}: - - For dz within the capillary diameter, l is the chord length of the circle at position dz + 2. Compute length l that is the effective length for computing intensity + I = I0 * e^{-mu * l}: + - For dz within the capillary diameter, l is the chord length of + the circle at position dz - For dz outside this range, l = 0 3. Apply a linear adjustment to I0 by taking I0 as I0 - slope * z """ @@ -309,7 +326,7 @@ def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): z_extended = np.concatenate([z_left_pad, z, z_right_pad]) I_extended = _model_function(z_extended, diameter, z0, I0, mud, slope) kernel = _top_hat(z_extended - z_extended.mean(), half_slit_width) - I_convolved = I_extended # this takes care of the case where slit width is close to 0 + I_convolved = I_extended # this takes care of the case where slit width is close to 0 # noqa: E501 if kernel.sum() != 0: kernel /= kernel.sum() I_convolved = convolve(I_extended, kernel, mode="same") @@ -364,11 +381,13 @@ def compute_mud(filepath): This function loads z-scan data and fits it to a model that convolves a top-hat function with I = I0 * e^{-mu * l}. - The fitting procedure is run multiple times, and we return the best-fit parameters based on the lowest rmse. + The fitting procedure is run multiple times, and we return the best-fit + parameters based on the lowest rmse. The full mathematical details are described in the paper: - An ad hoc Absorption Correction for Reliable Pair-Distribution Functions from Low Energy x-ray Sources, - Yucong Chen, Till Schertenleib, Andrew Yang, Pascal Schouwink, Wendy L. Queen and Simon J. L. Billinge, + An ad hoc Absorption Correction for Reliable Pair-Distribution Functions + from Low Energy x-ray Sources, Yucong Chen, Till Schertenleib, Andrew Yang + , Pascal Schouwink, Wendy L. Queen and Simon J. L. Billinge, in preparation. Parameters diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 693bb693..56eebc68 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -4,19 +4,24 @@ import numpy as np wavelength_warning_emsg = ( - "No wavelength has been specified. You can continue to use the DiffractionObject, but " - "some of its powerful features will not be available. " - "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." + "No wavelength has been specified. You can continue to use the " + "DiffractionObject, but some of its powerful features will not be " + "available. To specify a wavelength, if you have " + "do = DiffractionObject(xarray, yarray, 'tth'), you may set " + "do.wavelength = 1.54 for a wavelength of 1.54 angstroms. " ) invalid_tth_emsg = ( "Two theta exceeds 180 degrees. Please check the input values for errors." ) invalid_q_or_d_or_wavelength_emsg = ( - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values." + "The supplied input array and wavelength will result in an impossible " + "two-theta. Please check these values and re-instantiate the " + "DiffractionObject with correct values. " +) +inf_output_imsg = ( + "INFO: The largest output value in the array is infinite. " + "This is allowed, but it will not be plotted." ) -inf_output_imsg = "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): @@ -73,7 +78,8 @@ def q_to_tth(q, wavelength): def tth_to_q(tth, wavelength): r"""Helper function to convert two-theta to q on independent variable axis. - If wavelength is missing, returns independent variable axis as integer indexes. + If wavelength is missing, returns independent variable axis as integer + indexes. By definition the relationship is: @@ -100,7 +106,8 @@ def tth_to_q(tth, wavelength): ------- q : ndarray The 1D array of :math:`q` values np.array([qs]). - The units for the q-values are the inverse of the units of the provided wavelength. + The units for the q-values are the inverse of the units of the + provided wavelength. """ tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): @@ -139,7 +146,8 @@ def q_to_d(q): def tth_to_d(tth, wavelength): r"""Helper function to convert two-theta to d on independent variable axis. - The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. + The formula is .. + math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. Here we convert tth to q first, then to d. @@ -191,7 +199,8 @@ def d_to_q(d): def d_to_tth(d, wavelength): r"""Helper function to convert d to two-theta on independent variable axis. - The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). + The formula is .. + math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). Here we convert d to q first, then to tth. diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py index 91a461bf..c173f2bd 100644 --- a/src/diffpy/utils/validators.py +++ b/src/diffpy/utils/validators.py @@ -1,8 +1,9 @@ def is_number(string): """Check if the provided string can be converted to a float. - Since integers can be converted to floats, this function will return True for integers as well. - Hence, we can use this function to check if a string is a number. + Since integers can be converted to floats, this function will return True + for integers as well. Hence, we can use this function to check if a + string is a number. Parameters ---------- diff --git a/tests/conftest.py b/tests/conftest.py index b6d3bf3c..fcccb186 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,7 +37,8 @@ def _load(filename): @pytest.fixture def do_minimal(): - # Create an instance of DiffractionObject with empty xarray and yarray values, and a non-empty wavelength + # Create an instance of DiffractionObject with empty xarray and yarray + # values, and a non-empty wavelength return DiffractionObject( xarray=np.empty(0), yarray=np.empty(0), xtype="tth", wavelength=1.54 ) @@ -45,7 +46,8 @@ def do_minimal(): @pytest.fixture def do_minimal_tth(): - # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values + # Create an instance of DiffractionObject with non-empty xarray, yarray, + # and wavelength values return DiffractionObject( wavelength=2 * np.pi, xarray=np.array([30, 60]), @@ -56,7 +58,8 @@ def do_minimal_tth(): @pytest.fixture def do_minimal_d(): - # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values + # Create an instance of DiffractionObject with non-empty xarray, yarray, + # and wavelength values return DiffractionObject( wavelength=1.54, xarray=np.array([1, 2]), @@ -68,9 +71,11 @@ def do_minimal_d(): @pytest.fixture def wavelength_warning_msg(): return ( - "No wavelength has been specified. You can continue to use the DiffractionObject, but " - "some of its powerful features will not be available. " - "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "No wavelength has been specified. You can continue to use the " + "DiffractionObject, but some of its powerful features will not be " + "available. " + "To specify a wavelength, if you have " + "do = DiffractionObject(xarray, yarray, 'tth'), " "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) @@ -78,16 +83,20 @@ def wavelength_warning_msg(): @pytest.fixture def invalid_q_or_d_or_wavelength_error_msg(): return ( - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values." + "The supplied input array and wavelength will result in an " + "impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject " + "with correct values." ) @pytest.fixture def invalid_add_type_error_msg(): return ( - "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " - "Please rerun by adding another DiffractionObject instance or a scalar value. " + "You may only add a DiffractionObject with another DiffractionObject " + "or a scalar value. " + "Please rerun by adding another DiffractionObject instance or a " + "scalar value. " "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @@ -95,7 +104,9 @@ def invalid_add_type_error_msg(): @pytest.fixture def x_values_not_equal_error_msg(): return ( - "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " - "Please ensure the x values of the two objects are identical by re-instantiating " - "the DiffractionObject with the correct x value inputs." + "The two objects have different values in x arrays " + "(my_do.all_arrays[:, [1, 2, 3]]). " + "Please ensure the x values of the two objects are identical " + "by re-instantiating the DiffractionObject with the correct x value " + "inputs." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 7db4cc33..8ed1dd3b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -202,7 +202,8 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether the original y-array is scaled as expected - ( # C1: none of q, tth, d, provided, expect to scale on the maximal intensity from each object + ( # C1: none of q, tth, d, provided, expect to scale on the maximal + # intensity from each object { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), @@ -275,8 +276,8 @@ def test_init_invalid_xtype(): {"tth": 60}, {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), - ( # C5.1: Reuse test case from C1, none of q, tth, d, provided, but include an offset, - # expect scaled y-array in C1 to shift up by 2 + ( # C5.1: Reuse test case from C1, none of q, tth, d, provided, but + # include an offset, expect scaled y-array in C1 to shift up by 2 { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), @@ -292,7 +293,8 @@ def test_init_invalid_xtype(): {"offset": 2}, {"xtype": "q", "yarray": np.array([12, 22, 32])}, ), - ( # C5.2: Reuse test case from C4, but include an offset, expect scaled y-array in C4 to shift up by 2 + ( # C5.2: Reuse test case from C4, but include an offset, expect + # scaled y-array in C4 to shift up by 2 { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -358,8 +360,10 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( "do_args, get_array_index_inputs, expected_index", [ - # Test get_array_index() returns the expected index given xtype and value - ( # C1: Target value is in the xarray and xtype is identical, expect exact index match + # Test get_array_index() returns the expected index given xtype and + # value + ( # C1: Target value is in the xarray and xtype is identical, expect + # exact index match { "wavelength": 4 * np.pi, "xarray": np.array([30.005, 60]), @@ -372,7 +376,8 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, 0, ), - ( # C2: Target value lies in the array, expect the (first) closest index + ( # C2: Target value lies in the array, expect the (first) closest + # index { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -477,8 +482,13 @@ def test_dump(tmp_path, mocker): with open(file, "r") as f: actual = f.read() expected = ( - "[DiffractionObject]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" - "thing2 = thing2\npackage_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" + "[DiffractionObject]\n" + "name = test\n" + "wavelength = 1.54\n" + "scat_quantity = x-ray\n" + "thing1 = 1\n" + "thing2 = thing2\n" + "package_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" "creation_time = 2012-01-14 00:00:00\n\n" "#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" "1.000000000000000000e+00 1.000000000000000000e+00\n" @@ -492,10 +502,15 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( - "do_init_args, expected_do_dict, divide_by_zero_warning_expected, wavelength_warning_expected", + ( + "do_init_args, expected_do_dict, " + "divide_by_zero_warning_expected, wavelength_warning_expected" + ), [ - # Test __dict__ of DiffractionObject instance initialized with valid arguments - ( # C1: Instantiate DO with empty arrays, expect it to be a valid DO, but with everything empty + # Test __dict__ of DiffractionObject instance initialized with valid + # arguments + ( # C1: Instantiate DO with empty arrays, expect it to be a valid DO, + # but with everything empty { "xarray": np.empty(0), "yarray": np.empty(0), @@ -518,8 +533,9 @@ def test_dump(tmp_path, mocker): False, True, ), - ( # C2: Instantiate just DO with empty array like in C1 but with wavelength, xtype, name, and metadata - # expect a valid DO with empty arrays, but with some non-array attributes + ( # C2: Instantiate just DO with empty array like in C1 but with + # wavelength, xtype, name, and metadata expect a valid DO with + # empty arrays, but with some non-array attributes { "xarray": np.empty(0), "yarray": np.empty(0), @@ -545,8 +561,9 @@ def test_dump(tmp_path, mocker): False, False, ), - ( # C3: Minimum arguments provided for init with non-empty values for xarray and yarray and wavelength - # expect all attributes set without None + ( # C3: Minimum arguments provided for init with non-empty values + # for xarray and yarray and wavelength expect all attributes set + # without None { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -576,7 +593,8 @@ def test_dump(tmp_path, mocker): True, False, ), - ( # C4: Same as C3, but with an optional scat_quantity argument, expect non-empty string for scat_quantity + ( # C4: Same as C3, but with an optional scat_quantity argument, + # expect non-empty string for scat_quantity { "xarray": np.array( [np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi] @@ -643,12 +661,18 @@ def test_init_valid( @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - # Test expected error messages when 3 required arguments not provided in DiffractionObject init - ( # C1: No arguments provided, expect 3 required positional arguments error + # Test expected error messages when 3 required arguments not provided + # in DiffractionObject init + ( # C1: No arguments provided, expect 3 required positional + # arguments error {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ( + "missing 3 required positional arguments: " + "'xarray', 'yarray', and 'xtype'" + ), ), - ( # C2: Only xarray and yarray provided, expect 1 required positional argument error + ( # C2: Only xarray and yarray provided, expect 1 required + # positional argument error {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), @@ -704,7 +728,10 @@ def test_uuid_setter_error(do_minimal): with pytest.raises( AttributeError, - match="Direct modification of attribute 'uuid' is not allowed. Please use 'input_data' to modify 'uuid'.", + match=( + "Direct modification of attribute 'uuid' is not allowed. " + "Please use 'input_data' to modify 'uuid'." + ), ): do.uuid = uuid.uuid4() @@ -749,8 +776,10 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( "operation, starting_yarray, scalar_value, expected_yarray", [ - # Test scalar addition, subtraction, multiplication, and division to y-values by adding a scalar value - # C1: Test scalar addition to y-values (intensity), expect no change to x-values (q, tth, d) + # Test scalar addition, subtraction, multiplication, and division to + # y-values by adding a scalar value + # C1: Test scalar addition to y-values (intensity), expect no change + # to x-values (q, tth, d) ( # 1. Add 5 "add", np.array([1.0, 2.0]), @@ -763,7 +792,8 @@ def test_copy_object(do_minimal): 5.1, np.array([6.1, 7.1]), ), - # C2: Test scalar subtraction to y-values (intensity), expect no change to x-values (q, tth, d) + # C2: Test scalar subtraction to y-values (intensity), expect no + # change to x-values (q, tth, d) ( # 1. Subtract 1 "sub", np.array([1.0, 2.0]), @@ -776,7 +806,8 @@ def test_copy_object(do_minimal): 0.5, np.array([0.5, 1.5]), ), - # C3: Test scalar multiplication to y-values (intensity), expect no change to x-values (q, tth, d) + # C3: Test scalar multiplication to y-values (intensity), expect no + # change to x-values (q, tth, d) ( # 1. Multiply by 2 "mul", np.array([1.0, 2.0]), @@ -789,7 +820,8 @@ def test_copy_object(do_minimal): 2.5, np.array([2.5, 5.0]), ), - # C4: Test scalar division to y-values (intensity), expect no change to x-values (q, tth, d) + # C4: Test scalar division to y-values (intensity), expect no change + # to x-values (q, tth, d) ( # 1. Divide by 2 "div", np.array([1.0, 2.0]), @@ -837,9 +869,13 @@ def test_scalar_operations( @pytest.mark.parametrize( - "operation, expected_do_1_all_arrays_with_y_modified, expected_do_2_all_arrays_with_y_modified", + ( + "operation, expected_do_1_all_arrays_with_y_modified, " + "expected_do_2_all_arrays_with_y_modified" + ), [ - # Test addition, subtraction, multiplication, and division of two DO objects + # Test addition, subtraction, multiplication, and division of two DO + # objects ( # Test addition of two DO objects, expect combined yarray values "add", np.array( @@ -855,7 +891,8 @@ def test_scalar_operations( ] ), ), - ( # Test subtraction of two DO objects, expect differences in yarray values + ( # Test subtraction of two DO objects, expect differences in yarray + # values "sub", np.array( [ @@ -870,7 +907,8 @@ def test_scalar_operations( ] ), ), - ( # Test multiplication of two DO objects, expect multiplication in yarray values + ( # Test multiplication of two DO objects, expect multiplication in + # yarray values "mul", np.array( [ @@ -975,7 +1013,8 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): def test_operator_invalid_xarray_values_not_equal( operation, do_minimal_tth, do_minimal_d, x_values_not_equal_error_msg ): - # Add two DO objects with different xarray values but equal in shape, expect ValueError + # Add two DO objects with different xarray values but equal in shape, + # expect ValueError do_1 = do_minimal_tth do_2 = do_minimal_d with pytest.raises( diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index 7580c672..82d947ee 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -15,9 +15,9 @@ def test_loadData_default(datafile): with pytest.raises(IOError) as err: loadData("doesnotexist.txt") - assert ( - str(err.value) - == "File doesnotexist.txt cannot be found. Please rerun the program specifying a valid filename." + assert str(err.value) == ( + "File doesnotexist.txt cannot be found. " + "Please rerun the program specifying a valid filename." ) # The default minrows=10 makes it read from the third line diff --git a/tests/test_resample.py b/tests/test_resample.py index 6e6294ed..14f6190e 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -7,7 +7,8 @@ def test_wsinterp(): - # FIXME: if another SW interp function exists, run comparisons for interpolated points + # FIXME: if another SW interp function exists, run comparisons for + # interpolated points # Sampling rate ssr = 44100**-1 # Standard sampling rate for human-hearable frequencies diff --git a/tests/test_tools.py b/tests/test_tools.py index 8d69c6c0..6be3870f 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -18,7 +18,8 @@ @pytest.mark.parametrize( "runtime_inputs, expected", - [ # config file in home is present, no config in cwd. various runtime values passed + [ # config file in home is present, no config in cwd. various runtime + # values passed # C1: nothing passed in, expect uname, email, orcid from home_config ( {}, @@ -28,7 +29,8 @@ "owner_orcid": "home_orcid", }, ), - # C2: empty strings passed in, expect uname, email, orcid from home_config + # C2: empty strings passed in, expect uname, email, orcid from + # home_config ( {"owner_name": "", "owner_email": "", "owner_orcid": ""}, { @@ -37,7 +39,8 @@ "owner_orcid": "home_orcid", }, ), - # C3: just owner name passed in at runtime. expect runtime_oname but others from config + # C3: just owner name passed in at runtime. expect runtime_oname but + # others from config ( {"owner_name": "runtime_ownername"}, { @@ -46,7 +49,8 @@ "owner_orcid": "home_orcid", }, ), - # C4: just owner email passed in at runtime. expect runtime_email but others from config + # C4: just owner email passed in at runtime. expect runtime_email + # but others from config ( {"owner_email": "runtime@email.com"}, { @@ -55,7 +59,8 @@ "owner_orcid": "home_orcid", }, ), - # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config + # C5: just owner ci passed in at runtime. expect runtime_orcid but + # others from config ( {"owner_orcid": "runtime_orcid"}, { @@ -69,8 +74,8 @@ def test_get_user_info_with_home_conf_file( runtime_inputs, expected, user_filesystem, mocker ): - # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] - # is tmp_dir/cwd_dir + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, + # user_filesystem[1] is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) actual = get_user_info(**runtime_inputs) @@ -81,7 +86,8 @@ def test_get_user_info_with_home_conf_file( "runtime_inputs, expected", [ # tests as before but now config file present in cwd and home but orcid # missing in the cwd config - # C1: nothing passed in, expect uname, email from local config, orcid from home_config + # C1: nothing passed in, expect uname, email from local config, orcid + # from home_config ( {}, { @@ -90,7 +96,8 @@ def test_get_user_info_with_home_conf_file( "owner_orcid": "home_orcid", }, ), - # C2: empty strings passed in, expect uname, email, orcid from home_config + # C2: empty strings passed in, expect uname, email, orcid from + # home_config ( {"owner_name": "", "owner_email": "", "owner_orcid": ""}, { @@ -99,7 +106,8 @@ def test_get_user_info_with_home_conf_file( "owner_orcid": "home_orcid", }, ), - # C3: just owner name passed in at runtime. expect runtime_oname but others from config + # C3: just owner name passed in at runtime. expect runtime_oname but + # others from config ( {"owner_name": "runtime_ownername"}, { @@ -108,7 +116,8 @@ def test_get_user_info_with_home_conf_file( "owner_orcid": "home_orcid", }, ), - # C4: just owner email passed in at runtime. expect runtime_email but others from config + # C4: just owner email passed in at runtime. expect runtime_email + # but others from config ( {"owner_email": "runtime@email.com"}, { @@ -117,7 +126,8 @@ def test_get_user_info_with_home_conf_file( "owner_orcid": "home_orcid", }, ), - # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config + # C5: just owner ci passed in at runtime. expect runtime_orcid but + # others from config ( {"owner_orcid": "runtime_orcid"}, { @@ -131,8 +141,8 @@ def test_get_user_info_with_home_conf_file( def test_get_user_info_with_local_conf_file( runtime_inputs, expected, user_filesystem, mocker ): - # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] - # is tmp_dir/cwd_dir + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, + # user_filesystem[1] is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) local_config_data = { @@ -147,7 +157,8 @@ def test_get_user_info_with_local_conf_file( @pytest.mark.parametrize( "test_inputs,expected", - [ # Check check_and_build_global_config() builds correct config when config is found missing + [ # Check check_and_build_global_config() builds correct config when + # config is found missing ( # C1: user inputs valid name, email and orcid {"user_inputs": ["input_name", "input@email.com", "input_orcid"]}, { @@ -160,7 +171,8 @@ def test_get_user_info_with_local_conf_file( {"user_inputs": ["", "", ""]}, None, ), # C2: empty strings passed in, expect no config file created - ( # C3: just username input, expect config file but with some empty values + ( # C3: just username input, expect config file but with some empty + # values {"user_inputs": ["input_name", "", ""]}, {"owner_email": "", "owner_orcid": "", "owner_name": "input_name"}, ), @@ -169,8 +181,8 @@ def test_get_user_info_with_local_conf_file( def test_check_and_build_global_config( test_inputs, expected, user_filesystem, mocker ): - # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] - # is tmp_dir/cwd_dir + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, + # user_filesystem[1] is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) confile = user_filesystem[0] / "diffpyconfig.json" @@ -262,7 +274,8 @@ def test_get_package_info(monkeypatch, inputs, expected): "inputs", [ # Test when the function has invalid inputs - ( # C1: Both mass density and packing fraction are provided, expect ValueError exception + ( # C1: Both mass density and packing fraction are provided, + # expect ValueError exception { "sample_composition": "SiO2", "energy": 10, @@ -270,7 +283,8 @@ def test_get_package_info(monkeypatch, inputs, expected): "packing_fraction": 1, } ), - ( # C2: None of mass density or packing fraction are provided, expect ValueError exception + ( # C2: None of mass density or packing fraction are provided, + # expect ValueError exception { "sample_composition": "SiO2", "energy": 10, @@ -281,8 +295,11 @@ def test_get_package_info(monkeypatch, inputs, expected): def test_compute_mu_using_xraydb_bad(inputs): with pytest.raises( ValueError, - match="You must specify either sample_mass_density or packing_fraction, but not both. " - "Please rerun specifying only one.", + match=( + "You must specify either sample_mass_density or " + "packing_fraction, but not both. " + "Please rerun specifying only one." + ), ): compute_mu_using_xraydb(**inputs) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 4617584c..7f0775c7 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -20,15 +20,18 @@ # C1: Allow empty array q to compute tth with or without wavelength # 1. Wavelength provided, expect empty array of tth (4 * np.pi, np.empty((0)), np.empty(0)), - # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning + # 2. No wavelength provided, expected empty array of tth and + # wavelength UserWarning (None, np.empty((0)), np.empty((0))), # C2: Use non-empty q values to compute tth with or without wavelength - ( # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning + ( # 1. No wavelength provided, expect valid tth values in degrees + # with wavelength UserWarning None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - ( # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees + ( # 2. Wavelength provided, expect tth values of 2*arcsin(q) in + # degrees 4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0]), @@ -50,12 +53,14 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): "wavelength, q, expected_error_type", [ # Test ValeuError in q to tth conversion with invalid two-theta values. - ( # C1: Invalid q values that result in tth > 180 degrees, expect ValueError + ( # C1: Invalid q values that result in tth > 180 degrees, + # expect ValueError 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - ( # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError + ( # C2: Wrong wavelength that results in tth > 180 degrees, + # expect ValueError 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), ValueError, @@ -77,15 +82,18 @@ def test_q_to_tth_bad( # C1: Allow empty tth values to compute 1, with or without wavelength # 1. Wavelength provided, expect empty array of q (None, np.array([]), np.array([])), - # 2. No wavelength provided, expected empty array of q and wavelength UserWarning + # 2. No wavelength provided, expected empty array of q and wavelength + # UserWarning (4 * np.pi, np.array([]), np.array([])), - # C2: Use non-empty tth values between 0-180 degrees to compute q, with or without wavelength + # C2: Use non-empty tth values between 0-180 degrees to compute q, + # with or without wavelength ( # 1. No wavelength provided, expect valid q values between 0-1 None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - ( # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 + ( # 2. Wavelength provided, expect expected q values are + # sin15, sin30, sin45, sin60, sin90 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), @@ -107,18 +115,25 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: Invalid tth value of > 180 degrees provided, with or without wavelength + # C1: Invalid tth value of > 180 degrees provided, + # with or without wavelength ( # 1. No wavelength provided, expect two theta ValueError None, np.array([0, 30, 60, 90, 120, 181]), ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", + ( + "Two theta exceeds 180 degrees. " + "Please check the input values for errors." + ), ), ( # 2. Wavelength provided, expect two theta ValueError 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", + ( + "Two theta exceeds 180 degrees. " + "Please check the input values for errors." + ), ), ], ) @@ -143,7 +158,8 @@ def test_tth_to_q_bad( np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), - ( # 2. Valid q values containing 0, expect d values with divide by zero warning + ( # 2. Valid q values containing 0, + # expect d values with divide by zero warning np.array( [0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi] ), @@ -195,14 +211,16 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): (None, np.array([]), np.array([]), False), # C2: Empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), - # C3: User specified valid tth values between 0-180 degrees (without wavelength) + # C3: User specified valid tth values between 0-180 degrees + # (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False, ), - ( # C4: User specified valid tth values between 0-180 degrees (with wavelength) + ( # C4: User specified valid tth values between 0-180 degrees + # (with wavelength) 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), @@ -235,17 +253,25 @@ def test_tth_to_d( @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - ( # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError + ( # C1: Invalid tth value of > 180 degrees, no wavelength, + # expect two theta ValueError None, np.array([0, 30, 60, 90, 120, 181]), ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", + ( + "Two theta exceeds 180 degrees. " + "Please check the input values for errors." + ), ), - ( # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError + ( # C2: Invalid tth value of > 180 degrees with wavelength, + # expect two theta ValueError 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", + ( + "Two theta exceeds 180 degrees. " + "Please check the input values for errors." + ), ), ], ) @@ -263,14 +289,16 @@ def test_tth_to_d_invalid( (None, np.empty((0)), np.empty((0)), False), # C2: Empty d values with wavelength, expect empty tth values (4 * np.pi, np.empty((0)), np.empty(0), False), - # C3: Valid d values, no wavelength, expect valid and non-empty tth values + # C3: Valid d values, no wavelength, + # expect valid and non-empty tth values ( None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True, ), - ( # C4: Valid d values with wavelength, expect valid and non-empty thh values + ( # C4: Valid d values with wavelength, + # expect valid and non-empty thh values 4 * np.pi, np.array( [4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi] @@ -308,9 +336,11 @@ def test_d_to_tth( @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # C1: Invalid d values that result in tth > 180 degrees, expect invalid q, d, or wavelength ValueError + # C1: Invalid d values that result in tth > 180 degrees, + # expect invalid q, d, or wavelength ValueError (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # C2: Wrong wavelength that result in tth > 180 degreesm, expect invalid q, d, or wavelength ValueError + # C2: Wrong wavelength that result in tth > 180 degreesm, + # expect invalid q, d, or wavelength ValueError (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) From 03effc390a65fc88f27d58d936c4a5b346aca99a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:13:00 -0400 Subject: [PATCH 606/696] skpkg: update .github dir with skpkg --- .github/ISSUE_TEMPLATE/release_checklist.md | 14 +++++++------- .../pull_request_template.md | 15 +++++++++++++++ .github/workflows/build-wheel-release-upload.yml | 6 +++--- .github/workflows/check-news-item.yml | 2 +- .../matrix-and-codecov-on-merge-to-main.yml | 2 +- .github/workflows/publish-docs-on-release.yml | 6 +----- .github/workflows/tests-on-pr.yml | 5 +---- 7 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index fa94779e..6107962c 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -13,30 +13,30 @@ assignees: "" - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human-written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation, and the website (e.g., diffpy.org) are updated. +- [ ] Installation instructions in the README, documentation, and the website are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). - [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. -Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: +Please tag the maintainer (e.g., @username) in the comment here when you are ready for the PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: ### PyPI/GitHub full-release preparation checklist: - [ ] Create a new conda environment and install the rc from PyPI (`pip install ==??`) - [ ] License information on PyPI is correct. -- [ ] Docs are deployed successfully to `https://www.diffpy.org/`. +- [ ] Docs are deployed successfully to `https:///`. - [ ] Successfully run all tests, tutorial examples or do functional testing. -Please let @sbillinge know that all checks are done and the package is ready for full release. +Please let the maintainer know that all checks are done and the package is ready for full release. ### conda-forge release preparation checklist: - + - [ ] Ensure that the full release has appeared on PyPI successfully. - [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. -- [ ] Close any open issues on the feedstock. Reach out to @bobleesj if you have questions. -- [ ] Tag @sbillinge and @bobleesj for conda-forge release. +- [ ] Close any open issues on the feedstock. Reach out to the maintainer if you have questions. +- [ ] Tag the maintainer for conda-forge release. ### Post-release checklist diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..1099d862 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,15 @@ +### What problem does this PR address? + + + +### What should the reviewer(s) do? + + + + diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index db92d9d7..485aa356 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -8,11 +8,11 @@ on: jobs: release: - uses: Billingegroup/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.utils - github_admin_username: sbillinge - + c_extension: false + maintainer_GITHUB_username: sbillinge secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index aa040f09..9e0f9f16 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -7,6 +7,6 @@ on: jobs: check-news-item: - uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 8543c786..a6a0d7cf 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -12,7 +12,7 @@ on: jobs: matrix-coverage: - uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.utils c_extension: false diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml index e222ac53..a2dd338c 100644 --- a/.github/workflows/publish-docs-on-release.yml +++ b/.github/workflows/publish-docs-on-release.yml @@ -1,15 +1,11 @@ name: Deploy Documentation on Release on: - release: - types: [published, prereleased] workflow_dispatch: jobs: docs: - permissions: - contents: write - uses: Billingegroup/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 with: project: diffpy.utils c_extension: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index baac1aeb..5e4b8fec 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -1,15 +1,12 @@ name: Tests on PR on: - push: - branches: - - main pull_request: workflow_dispatch: jobs: tests-on-pr: - uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.utils c_extension: false From 6cb227380e87859b7f5df7059600c08a5bcf790a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:13:37 -0400 Subject: [PATCH 607/696] skpkg: gitignore file from skpkg --- .gitignore | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index a25212ea..099e2948 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ .Python env/ build/ +_build/ develop-eggs/ dist/ downloads/ @@ -90,10 +91,3 @@ target/ # Ipython Notebook .ipynb_checkpoints - -# version information -setup.cfg -/src/diffpy/*/version.cfg - -# Rever -rever/ From f70eaa0ada15b4fa610571177b19be627fcf87e2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:16:56 -0400 Subject: [PATCH 608/696] skpkg: small modifications to files in src --- src/diffpy/utils/__init__.py | 6 ++++-- src/diffpy/utils/version.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index 12f4a49d..9b9f7a69 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -1,10 +1,10 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2024 The Trustees of Columbia University in the City of New York. +# (c) 2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Billinge Group members and community contributors. +# File coded by: Simon Billinge, Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.utils/graphs/contributors @@ -19,3 +19,5 @@ # silence the pyflakes syntax checker assert __version__ or True + +# End of file \ No newline at end of file diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index e74c47bd..07d612a3 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -1,10 +1,10 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2024 The Trustees of Columbia University in the City of New York. +# (c) 2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Billinge Group members and community contributors. +# File coded by: Simon Billinge, Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.utils/graphs/contributors @@ -21,3 +21,5 @@ from importlib.metadata import version __version__ = version("diffpy.utils") + +# End of file \ No newline at end of file From 629141bdd6c9b5e9400d77861ed3a5376fbd9eb2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:20:43 -0400 Subject: [PATCH 609/696] precommit: end of file fixer --- src/diffpy/utils/__init__.py | 2 +- src/diffpy/utils/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index 9b9f7a69..c8aad4a1 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -20,4 +20,4 @@ # silence the pyflakes syntax checker assert __version__ or True -# End of file \ No newline at end of file +# End of file diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index 07d612a3..a17ccb5c 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -22,4 +22,4 @@ __version__ = version("diffpy.utils") -# End of file \ No newline at end of file +# End of file From 21c9220b90a1beb194b34c1b7d91ab646795238b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:24:04 -0400 Subject: [PATCH 610/696] chore: news and pre-commit file --- .pre-commit-config.yaml | 5 ++--- news/setup-CI.rst | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 news/setup-CI.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6dca6f1e..0e4a84d1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,21 +44,20 @@ repos: name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] - # codespell - spell checker for source code - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell additional_dependencies: - tomli - # prettier - multi formatter for json, yaml, md + # prettier - multi formatter for .json, .yml, and .md files - repo: https://github.com/pre-commit/mirrors-prettier rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 hooks: - id: prettier additional_dependencies: - "prettier@^3.2.4" - # docformatter - formats docstrings using PEP 257 + # docformatter - PEP 257 compliant docstring formatter - repo: https://github.com/s-weigand/docformatter rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c hooks: diff --git a/news/setup-CI.rst b/news/setup-CI.rst new file mode 100644 index 00000000..bf692d68 --- /dev/null +++ b/news/setup-CI.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Update github CI to scikit-package standard + +**Security:** + +* From ae8caf460d5bd8849a02df213900173b360a759b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 11:06:56 -0400 Subject: [PATCH 611/696] skpkg: migration of docs --- doc/source/conf.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 045aba0a..ae2443a1 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -18,6 +18,12 @@ from importlib.metadata import version from pathlib import Path +# Attempt to import the version dynamically from GitHub tag. +try: + fullversion = version("diffpy.utils") +except Exception: + fullversion = "No version found. The correct version will appear in the released version." # noqa: E501 + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use Path().resolve() to make it absolute, like shown here. @@ -43,6 +49,7 @@ "sphinx.ext.viewcode", "sphinx.ext.intersphinx", "sphinx_rtd_theme", + "sphinx_copybutton", "m2r", ] @@ -88,6 +95,11 @@ # substitute YEAR in the copyright string copyright = copyright.replace("%Y", year) +# For sphinx_copybutton extension. +# Do not copy "$" for shell commands in code-blocks. +copybutton_prompt_text = r"^\$ " +copybutton_prompt_is_regexp = True + # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["build"] @@ -123,6 +135,14 @@ # html_theme = "sphinx_rtd_theme" +html_context = { + "display_github": True, + "github_user": "diffpy", + "github_repo": "diffpy.utils", + "github_version": "main", + "conf_py_path": "/doc/source/", +} + # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. @@ -256,7 +276,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ("index", "diffpy.utils", "diffpy.utils Documentation", ab_authors, 1) + ( + "index", + "diffpy.utils", + "diffpy.utils Documentation", + ab_authors, + 1, + ) ] # If true, show URL addresses after external links. From 199cce8d6615be9d2c810bd1a964caac2c86bc53 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 11:15:15 -0400 Subject: [PATCH 612/696] skpkg: add config files for authors, changelog, code of conduct, license, README --- CHANGELOG.rst | 2 +- CODE_OF_CONDUCT.rst | 2 +- README.rst | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bc99672e..48c7a51e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ ============= -Release Notes +Release notes ============= .. current developments diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst index ff9c3561..e8199ca5 100644 --- a/CODE_OF_CONDUCT.rst +++ b/CODE_OF_CONDUCT.rst @@ -8,7 +8,7 @@ Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, +identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. diff --git a/README.rst b/README.rst index cc86c81e..5fb8421e 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ :target: https://diffpy.github.io/diffpy.utils :height: 100px -|PyPi| |Forge| |PythonVersion| |PR| +|PyPI| |Forge| |PythonVersion| |PR| |CI| |Codecov| |Black| |Tracking| @@ -26,7 +26,7 @@ .. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff -.. |PyPi| image:: https://img.shields.io/pypi/v/diffpy.utils +.. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.utils :target: https://pypi.org/project/diffpy.utils/ .. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.utils @@ -141,4 +141,9 @@ Before contributing, please read our `Code of Conduct `_ or email Prof. Simon Billinge at sb2896@columbia.edu. +For more information on diffpy.utils please visit the project `web-page `_ or email Simon Billinge at sb2896@columbia.edu. + +Acknowledgements +---------------- + +``diffpy.utils`` is built and maintained with `scikit-package `_. From b14be774e119cf04ebbf8aaeeaa9dd680b9e34f0 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 11:33:12 -0400 Subject: [PATCH 613/696] chore: news --- news/doc-migration.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/doc-migration.rst diff --git a/news/doc-migration.rst b/news/doc-migration.rst new file mode 100644 index 00000000..b0ec659f --- /dev/null +++ b/news/doc-migration.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Support ``scikit-package`` Level 5 standard (https://scikit-package.github.io/scikit-package/). + +**Security:** + +* From 11ccb9d13cf1a5d78b5cf4062650dca9c4f75f4e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:20:53 -0400 Subject: [PATCH 614/696] skpkg: remove deprecated env.yml file --- environment.yml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 environment.yml diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 42088bbc..00000000 --- a/environment.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: diffpy.utils -channels: - - conda-forge -dependencies: - - python=3 - - pip From d8e4f008742054176455d1f59e9f35becf362065 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:21:56 -0400 Subject: [PATCH 615/696] skpkg: remove socioeconomic from codespell --- .codespell/ignore_words.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt index 9757d7c0..04b4fcfa 100644 --- a/.codespell/ignore_words.txt +++ b/.codespell/ignore_words.txt @@ -4,8 +4,5 @@ ;; abbreviation for "materials" often used in a journal title mater -;; alternative use of socioeconomic -socio-economic - ;; Frobenius norm used in np.linalg.norm fro From b1717b8073afa172f6c1afbf80cf98dc81ace2ba Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:23:06 -0400 Subject: [PATCH 616/696] skpkg: small changes to some config files --- .flake8 | 2 ++ .isort.cfg | 1 + .readthedocs.yaml | 13 +++++++++++++ pyproject.toml | 8 ++++---- 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 .readthedocs.yaml diff --git a/.flake8 b/.flake8 index 5a56eddd..7b2865c1 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,5 @@ +# As of now, flake8 does not natively support configuration via pyproject.toml +# https://github.com/microsoft/vscode-flake8/issues/135 [flake8] exclude = .git, diff --git a/.isort.cfg b/.isort.cfg index 6d831957..86f162b8 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,4 +1,5 @@ [settings] +# Keep import statement below line_length character limit line_length = 79 multi_line_output = 3 include_trailing_comma = True diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..47f7a017 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "latest" + +python: + install: + - requirements: requirements/docs.txt + +sphinx: + configuration: doc/source/conf.py diff --git a/pyproject.toml b/pyproject.toml index 10a6f890..ef9192f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,13 +6,13 @@ build-backend = "setuptools.build_meta" name = "diffpy.utils" dynamic=['version', 'dependencies'] authors = [ - { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + { name="Simon Billinge", email="sb2896@columbia.edu" }, ] maintainers = [ - { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + { name="Simon Billinge", email="sb2896@columbia.edu" }, ] description = "General utilities for analyzing diffraction data" -keywords = ["text data parsers", "wx grid", "diffraction objects"] +keywords = ['text data parsers', 'wx grid', 'diffraction objects'] readme = "README.rst" requires-python = ">=3.11, <3.14" classifiers = [ @@ -27,7 +27,7 @@ classifiers = [ 'Operating System :: Unix', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.13', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Scientific/Engineering :: Chemistry', ] From 629a67047e7ce292e2705ccc93896aedf3780337 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:23:44 -0400 Subject: [PATCH 617/696] doc: add sphinx-copybutton as dependency --- requirements/docs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/docs.txt b/requirements/docs.txt index ab17b1c8..463381e3 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -2,3 +2,4 @@ sphinx sphinx_rtd_theme doctr m2r +sphinx-copybutton From 45a52e20ee9f3f88cadf8681d2b3f05d4d29364a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:29:03 -0400 Subject: [PATCH 618/696] chore: news --- news/news-and-misc.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/news-and-misc.rst diff --git a/news/news-and-misc.rst b/news/news-and-misc.rst new file mode 100644 index 00000000..9882947c --- /dev/null +++ b/news/news-and-misc.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add ``spinx-copybutton`` as a dependency for doc building. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Remove ``environment.yml``. + +**Fixed:** + +* + +**Security:** + +* From 9f3e15307cb9e36ad575780b48e0a7475c5662e1 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 13:49:37 -0400 Subject: [PATCH 619/696] chore: update news to no news --- news/news-and-misc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/news/news-and-misc.rst b/news/news-and-misc.rst index 9882947c..339e8df3 100644 --- a/news/news-and-misc.rst +++ b/news/news-and-misc.rst @@ -1,6 +1,6 @@ **Added:** -* Add ``spinx-copybutton`` as a dependency for doc building. +* No news item needed, bringing package up to ``scikit-package 5v0.1.0`` standards. **Changed:** @@ -12,7 +12,7 @@ **Removed:** -* Remove ``environment.yml``. +* **Fixed:** From f8ddac638fd38b71721244452f8d922344524eba Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Tue, 22 Jul 2025 13:39:14 -0400 Subject: [PATCH 620/696] style: update to `CODE-OF-CONDUCT.rst`, `requirements/tests.text` and `docs/` according to new group standard. --- CODE_OF_CONDUCT.rst => CODE-OF-CONDUCT.rst | 0 README.rst | 2 +- {doc => docs}/Makefile | 0 {doc => docs}/make.bat | 0 {doc => docs}/source/_static/.placeholder | 0 .../source/api/diffpy.utils.parsers.rst | 0 {doc => docs}/source/api/diffpy.utils.rst | 0 {doc => docs}/source/api/diffpy.utils.wx.rst | 0 {doc => docs}/source/conf.py | 4 +-- .../examples/diffraction_objects_example.rst | 0 .../examples/example_data/parser_data.zip | Bin {doc => docs}/source/examples/examples.rst | 0 .../source/examples/parsers_example.rst | 0 .../source/examples/resample_example.rst | 0 .../source/examples/tools_example.rst | 0 .../source/examples/transforms_example.rst | 0 {doc => docs}/source/index.rst | 0 {doc => docs}/source/license.rst | 0 {doc => docs}/source/release.rst | 0 .../utilities/diffraction_objects_utility.rst | 0 .../source/utilities/parsers_utility.rst | 0 .../source/utilities/resample_utility.rst | 0 .../source/utilities/tools_utility.rst | 0 .../source/utilities/transforms_utility.rst | 0 {doc => docs}/source/utilities/utilities.rst | 0 news/group-standard.rst | 23 ++++++++++++++++++ requirements/{test.txt => tests.txt} | 0 27 files changed, 26 insertions(+), 3 deletions(-) rename CODE_OF_CONDUCT.rst => CODE-OF-CONDUCT.rst (100%) rename {doc => docs}/Makefile (100%) rename {doc => docs}/make.bat (100%) rename {doc => docs}/source/_static/.placeholder (100%) rename {doc => docs}/source/api/diffpy.utils.parsers.rst (100%) rename {doc => docs}/source/api/diffpy.utils.rst (100%) rename {doc => docs}/source/api/diffpy.utils.wx.rst (100%) rename {doc => docs}/source/conf.py (99%) rename {doc => docs}/source/examples/diffraction_objects_example.rst (100%) rename {doc => docs}/source/examples/example_data/parser_data.zip (100%) rename {doc => docs}/source/examples/examples.rst (100%) rename {doc => docs}/source/examples/parsers_example.rst (100%) rename {doc => docs}/source/examples/resample_example.rst (100%) rename {doc => docs}/source/examples/tools_example.rst (100%) rename {doc => docs}/source/examples/transforms_example.rst (100%) rename {doc => docs}/source/index.rst (100%) rename {doc => docs}/source/license.rst (100%) rename {doc => docs}/source/release.rst (100%) rename {doc => docs}/source/utilities/diffraction_objects_utility.rst (100%) rename {doc => docs}/source/utilities/parsers_utility.rst (100%) rename {doc => docs}/source/utilities/resample_utility.rst (100%) rename {doc => docs}/source/utilities/tools_utility.rst (100%) rename {doc => docs}/source/utilities/transforms_utility.rst (100%) rename {doc => docs}/source/utilities/utilities.rst (100%) create mode 100644 news/group-standard.rst rename requirements/{test.txt => tests.txt} (100%) diff --git a/CODE_OF_CONDUCT.rst b/CODE-OF-CONDUCT.rst similarity index 100% rename from CODE_OF_CONDUCT.rst rename to CODE-OF-CONDUCT.rst diff --git a/README.rst b/README.rst index 5fb8421e..cf989cdf 100644 --- a/README.rst +++ b/README.rst @@ -136,7 +136,7 @@ trying to commit again. Improvements and fixes are always appreciated. -Before contributing, please read our `Code of Conduct `_. +Before contributing, please read our `Code of Conduct `_. Contact ------- diff --git a/doc/Makefile b/docs/Makefile similarity index 100% rename from doc/Makefile rename to docs/Makefile diff --git a/doc/make.bat b/docs/make.bat similarity index 100% rename from doc/make.bat rename to docs/make.bat diff --git a/doc/source/_static/.placeholder b/docs/source/_static/.placeholder similarity index 100% rename from doc/source/_static/.placeholder rename to docs/source/_static/.placeholder diff --git a/doc/source/api/diffpy.utils.parsers.rst b/docs/source/api/diffpy.utils.parsers.rst similarity index 100% rename from doc/source/api/diffpy.utils.parsers.rst rename to docs/source/api/diffpy.utils.parsers.rst diff --git a/doc/source/api/diffpy.utils.rst b/docs/source/api/diffpy.utils.rst similarity index 100% rename from doc/source/api/diffpy.utils.rst rename to docs/source/api/diffpy.utils.rst diff --git a/doc/source/api/diffpy.utils.wx.rst b/docs/source/api/diffpy.utils.wx.rst similarity index 100% rename from doc/source/api/diffpy.utils.wx.rst rename to docs/source/api/diffpy.utils.wx.rst diff --git a/doc/source/conf.py b/docs/source/conf.py similarity index 99% rename from doc/source/conf.py rename to docs/source/conf.py index ae2443a1..6a8add42 100644 --- a/doc/source/conf.py +++ b/docs/source/conf.py @@ -140,7 +140,7 @@ "github_user": "diffpy", "github_repo": "diffpy.utils", "github_version": "main", - "conf_py_path": "/doc/source/", + "conf_py_path": "/docs/source/", } # Theme options are theme-specific and customize the look and feel of a theme @@ -223,7 +223,7 @@ # Output file base name for HTML help builder. basename = "diffpy.utils".replace(" ", "").replace(".", "") -htmlhelp_basename = basename + "doc" +htmlhelp_basename = basename + "docs" # -- Options for LaTeX output --------------------------------------------- diff --git a/doc/source/examples/diffraction_objects_example.rst b/docs/source/examples/diffraction_objects_example.rst similarity index 100% rename from doc/source/examples/diffraction_objects_example.rst rename to docs/source/examples/diffraction_objects_example.rst diff --git a/doc/source/examples/example_data/parser_data.zip b/docs/source/examples/example_data/parser_data.zip similarity index 100% rename from doc/source/examples/example_data/parser_data.zip rename to docs/source/examples/example_data/parser_data.zip diff --git a/doc/source/examples/examples.rst b/docs/source/examples/examples.rst similarity index 100% rename from doc/source/examples/examples.rst rename to docs/source/examples/examples.rst diff --git a/doc/source/examples/parsers_example.rst b/docs/source/examples/parsers_example.rst similarity index 100% rename from doc/source/examples/parsers_example.rst rename to docs/source/examples/parsers_example.rst diff --git a/doc/source/examples/resample_example.rst b/docs/source/examples/resample_example.rst similarity index 100% rename from doc/source/examples/resample_example.rst rename to docs/source/examples/resample_example.rst diff --git a/doc/source/examples/tools_example.rst b/docs/source/examples/tools_example.rst similarity index 100% rename from doc/source/examples/tools_example.rst rename to docs/source/examples/tools_example.rst diff --git a/doc/source/examples/transforms_example.rst b/docs/source/examples/transforms_example.rst similarity index 100% rename from doc/source/examples/transforms_example.rst rename to docs/source/examples/transforms_example.rst diff --git a/doc/source/index.rst b/docs/source/index.rst similarity index 100% rename from doc/source/index.rst rename to docs/source/index.rst diff --git a/doc/source/license.rst b/docs/source/license.rst similarity index 100% rename from doc/source/license.rst rename to docs/source/license.rst diff --git a/doc/source/release.rst b/docs/source/release.rst similarity index 100% rename from doc/source/release.rst rename to docs/source/release.rst diff --git a/doc/source/utilities/diffraction_objects_utility.rst b/docs/source/utilities/diffraction_objects_utility.rst similarity index 100% rename from doc/source/utilities/diffraction_objects_utility.rst rename to docs/source/utilities/diffraction_objects_utility.rst diff --git a/doc/source/utilities/parsers_utility.rst b/docs/source/utilities/parsers_utility.rst similarity index 100% rename from doc/source/utilities/parsers_utility.rst rename to docs/source/utilities/parsers_utility.rst diff --git a/doc/source/utilities/resample_utility.rst b/docs/source/utilities/resample_utility.rst similarity index 100% rename from doc/source/utilities/resample_utility.rst rename to docs/source/utilities/resample_utility.rst diff --git a/doc/source/utilities/tools_utility.rst b/docs/source/utilities/tools_utility.rst similarity index 100% rename from doc/source/utilities/tools_utility.rst rename to docs/source/utilities/tools_utility.rst diff --git a/doc/source/utilities/transforms_utility.rst b/docs/source/utilities/transforms_utility.rst similarity index 100% rename from doc/source/utilities/transforms_utility.rst rename to docs/source/utilities/transforms_utility.rst diff --git a/doc/source/utilities/utilities.rst b/docs/source/utilities/utilities.rst similarity index 100% rename from doc/source/utilities/utilities.rst rename to docs/source/utilities/utilities.rst diff --git a/news/group-standard.rst b/news/group-standard.rst new file mode 100644 index 00000000..69ba1d29 --- /dev/null +++ b/news/group-standard.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: Use ``CODE-OF-CONDUCT,rst``, ``requirements/tests.text`` and ``docs`` according to new group standard. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/requirements/test.txt b/requirements/tests.txt similarity index 100% rename from requirements/test.txt rename to requirements/tests.txt From e00193016876a48b176283ad7ab91a9259ce67ef Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Tue, 22 Jul 2025 13:55:50 -0400 Subject: [PATCH 621/696] fix: replace the old file name in the project --- .flake8 | 2 +- .readthedocs.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.flake8 b/.flake8 index 7b2865c1..88077af0 100644 --- a/.flake8 +++ b/.flake8 @@ -6,7 +6,7 @@ exclude = __pycache__, build, dist, - doc/source/conf.py + docs/source/conf.py max-line-length = 79 # Ignore some style 'errors' produced while formatting by 'black' # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 47f7a017..aaa88895 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,4 +10,4 @@ python: - requirements: requirements/docs.txt sphinx: - configuration: doc/source/conf.py + configuration: docs/source/conf.py From 0cb0a0489d5201a5d2f7df9f1fc3c4037d1ce74a Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Thu, 24 Jul 2025 13:48:21 -0400 Subject: [PATCH 622/696] docs: fix the example of using `DiffractionObject.get_array_index` in the documentation --- .../examples/diffraction_objects_example.rst | 6 ++--- news/fix-docs-example.rst | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 news/fix-docs-example.rst diff --git a/docs/source/examples/diffraction_objects_example.rst b/docs/source/examples/diffraction_objects_example.rst index 1122bf21..8794c719 100644 --- a/docs/source/examples/diffraction_objects_example.rst +++ b/docs/source/examples/diffraction_objects_example.rst @@ -180,7 +180,7 @@ For example, attempting to add a diffraction object and a string will raise an e .. code-block:: python - tth_ninety_index = diff_object1.get_array_index(90, xtype="tth") + tth_ninety_index = diff_object1.get_array_index(xvalue=90, xtype="tth") intensity_at_ninety = diff_object1.on_tth()[1][tth_ninety_index] If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. @@ -190,8 +190,8 @@ you can find its closest index for ``q=0.25`` by typing either of the following: .. code-block:: python print(do._input_xtype) # remind ourselves which array was input. prints "q" in this case. - index = do.get_array_index(0.25) # no xtype passed, defaults to do._input_xtype, or in this example, q - index = do.get_array_index(0.25, xtype="q") # explicitly choose an xtype to specify a value + index = do.get_array_index(xvalue=0.25) # no xtype passed, defaults to do._input_xtype, or in this example, q + index = do.get_array_index(xvalue=0.25, xtype="q") # explicitly choose an xtype to specify a value 5. The ``dump`` function saves the diffraction data and relevant information to an xy format file with headers (widely used chi format used, for example, by Fit2D and diffpy. These files can be read by ``LoadData()`` diff --git a/news/fix-docs-example.rst b/news/fix-docs-example.rst new file mode 100644 index 00000000..ef351fae --- /dev/null +++ b/news/fix-docs-example.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: Fix the example of using ``DiffractionObject.get_array_index`` in the documentation. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 14ec0bc17f123fd125b3b0eb877a75e35bb17dd4 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Thu, 24 Jul 2025 15:05:41 -0400 Subject: [PATCH 623/696] docs: remove a duplicate code block --- docs/source/examples/resample_example.rst | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/source/examples/resample_example.rst b/docs/source/examples/resample_example.rst index 4884f5c9..ba28390b 100644 --- a/docs/source/examples/resample_example.rst +++ b/docs/source/examples/resample_example.rst @@ -23,18 +23,6 @@ given enough datapoints. Each data table has two columns: first is the grid and second is the function value. To extract the columns, we can utilize the serialize function ... -.. code-block:: python - - from diffpy.utils.parsers.serialization import serialize_data - nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) - nickel_grid = nickel_data['Nickel.gr']['grid'] - nickel_func = nickel_data['Nickel.gr']['func'] - target_data = serialize_data('NiTarget.gr', {}, nitarget_datatable, dt_colnames=['grid', 'function']) - target_grid = nickel_data['Nickel.gr']['grid'] - target_func = nickel_data['Nickel.gr']['func'] - -To extract the columns, we can utilize the serialize function ... - .. code-block:: python from diffpy.utils.parsers.serialization import serialize_data From 174f2be1ebc27129f061d34e9fcf787896adc2c3 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Thu, 24 Jul 2025 15:07:41 -0400 Subject: [PATCH 624/696] docs: fix the `.rst` markup --- docs/source/examples/parsers_example.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/examples/parsers_example.rst b/docs/source/examples/parsers_example.rst index 92135f3b..09220a3a 100644 --- a/docs/source/examples/parsers_example.rst +++ b/docs/source/examples/parsers_example.rst @@ -104,8 +104,7 @@ Now we can extract specific data table columns from the dictionary. parsed_file_data = serialize_data('', hdata, data_table, serial_file='') - The returned value, ``parsed_file_data``, is the dictionary we just added to ``serialfile.json``. - To extract the data from the serial file, we use ``deserialize_data``. +The returned value, ``parsed_file_data``, is the dictionary we just added to ``serialfile.json``. To extract the data from the serial file, we use ``deserialize_data``. .. code-block:: python From 829d62a609fc1ce6432eb14e61ee03030722f8f0 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Thu, 24 Jul 2025 15:08:08 -0400 Subject: [PATCH 625/696] chore: update news --- news/fix-docs-example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/fix-docs-example.rst b/news/fix-docs-example.rst index ef351fae..0048f8a3 100644 --- a/news/fix-docs-example.rst +++ b/news/fix-docs-example.rst @@ -1,6 +1,6 @@ **Added:** -* No news added: Fix the example of using ``DiffractionObject.get_array_index`` in the documentation. +* No news added: Fix some examples in the documentation. **Changed:** From 8e43660cc139a412a20a74adba66a263a6281cd6 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 25 Jul 2025 11:18:43 -0400 Subject: [PATCH 626/696] test: update the test code for `DiffractionObject.get_array_index` --- news/fix-get-array-index.rst | 23 +++++++++++++++++++++++ tests/test_diffraction_objects.py | 10 +++++----- 2 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 news/fix-get-array-index.rst diff --git a/news/fix-get-array-index.rst b/news/fix-get-array-index.rst new file mode 100644 index 00000000..67814f58 --- /dev/null +++ b/news/fix-get-array-index.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Let ``DiffractionObject.get_array_index`` to use the ``xtype`` from its inputs. + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8ed1dd3b..aa8cb7d0 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -376,8 +376,8 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, 0, ), - ( # C2: Target value lies in the array, expect the (first) closest - # index + # C2: Target value lies in the array, expect the closest index + ( # 1. same xtype { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -390,7 +390,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, 0, ), - ( + ( # 2. different xtype { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -399,9 +399,9 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, { "xtype": "q", - "value": 0.25, + "value": 0.5, }, - 0, + 1, ), # C3: Target value out of the range, expect the closest index ( # 1. Test with xtype of "q" From bedf468755208eab679616085515c64fbda4536c Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 25 Jul 2025 15:54:48 -0400 Subject: [PATCH 627/696] fix: let `DiffractionObject.get_array_index` to use an optional input `xtype` --- src/diffpy/utils/diffraction_objects.py | 17 +++++++----- tests/test_diffraction_objects.py | 36 ++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25f32f4d..64a40447 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -409,17 +409,17 @@ def uuid(self): def uuid(self, _): raise AttributeError(_setter_wmsg("uuid")) - def get_array_index(self, xtype, xvalue): - """Return the index of the closest value in the array associated with + def get_array_index(self, xvalue, xtype=None): + f"""Return the index of the closest value in the array associated with the specified xtype and the value provided. Parameters ---------- - xtype : str - The type of the independent variable in `xarray`. Must be one - of {*XQUANTITIES}. xvalue : float The value of the xtype to find the closest index for. + xtype : str, optional + The type of the independent variable in `xarray`. Must be one + of {*XQUANTITIES,}. Default is {self._input_xtype} Returns ------- @@ -427,8 +427,11 @@ def get_array_index(self, xtype, xvalue): The index of the closest value in the array associated with the specified xtype and the value provided. """ - - xtype = self._input_xtype + if xtype is None: + xtype = self._input_xtype + else: + if xtype not in XQUANTITIES: + raise ValueError(_xtype_wmsg(xtype)) xarray = self.on_xtype(xtype)[0] if len(xarray) == 0: raise ValueError( diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aa8cb7d0..59ee5934 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -377,7 +377,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): 0, ), # C2: Target value lies in the array, expect the closest index - ( # 1. same xtype + ( # 1. xtype(tth) is equal to self._input_xtype(tth) { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -390,7 +390,20 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, 0, ), - ( # 2. different xtype + ( # 2. use default xtype(equal to self._input_xtype) + { + "wavelength": 4 * np.pi, + "xarray": np.array([30, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": None, + "value": 45, + }, + 0, + ), + ( # 3. xtype(q) is different from self._input_xtype(tth) { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -435,12 +448,13 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): def test_get_array_index(do_args, get_array_index_inputs, expected_index): do = DiffractionObject(**do_args) actual_index = do.get_array_index( - get_array_index_inputs["xtype"], get_array_index_inputs["value"] + get_array_index_inputs["value"], get_array_index_inputs["xtype"] ) assert actual_index == expected_index def test_get_array_index_bad(): + # empty array in DiffractionObject do = DiffractionObject( wavelength=2 * np.pi, xarray=np.array([]), @@ -454,6 +468,22 @@ def test_get_array_index_bad(): ), ): do.get_array_index(xtype="tth", xvalue=30) + # non-existing xtype + do = DiffractionObject( + wavelength=4 * np.pi, + xarray=np.array([30, 60]), + yarray=np.array([1, 2]), + xtype="tth", + ) + non_existing_xtype = "non_existing_xtype" + with pytest.raises( + ValueError, + match=re.escape( + f"I don't know how to handle the xtype, '{non_existing_xtype}'. " + f"Please rerun specifying an xtype from {*XQUANTITIES, }" + ), + ): + do.get_array_index(xtype=non_existing_xtype, xvalue=30) def test_dump(tmp_path, mocker): From eaeafe58af624b769bf2bf366b54f9c4b1eb170e Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 25 Jul 2025 15:57:45 -0400 Subject: [PATCH 628/696] chore: fix pre-commit --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 64a40447..ec09b414 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -419,7 +419,7 @@ def get_array_index(self, xvalue, xtype=None): The value of the xtype to find the closest index for. xtype : str, optional The type of the independent variable in `xarray`. Must be one - of {*XQUANTITIES,}. Default is {self._input_xtype} + of {*XQUANTITIES, }. Default is {self._input_xtype} Returns ------- From 3ea591b6e660fbf55e4205bf4eaa0e7081f1fc31 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 25 Jul 2025 16:45:46 -0400 Subject: [PATCH 629/696] docs: fix the `diffpy.utils.transforms.d_to_tth` example --- docs/source/examples/transforms_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/examples/transforms_example.rst b/docs/source/examples/transforms_example.rst index 2fc40c4a..70d63fb4 100644 --- a/docs/source/examples/transforms_example.rst +++ b/docs/source/examples/transforms_example.rst @@ -59,5 +59,5 @@ This example will demonstrate how to use the functions in the from diffpy.utils.transforms import d_to_tth wavelength = 0.71 - d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) + d = np.array([1.0, 0.8, 0.6, 0.4]) tth = d_to_tth(d, wavelength) From 208436c913c1775fc386748726ba40be51ae73bb Mon Sep 17 00:00:00 2001 From: sbillinge <4254545+sbillinge@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:01:28 +0000 Subject: [PATCH 630/696] update changelog --- CHANGELOG.rst | 18 ++++++++++++++++++ news/changelog-3.6.0-line-break.rst | 23 ----------------------- news/doc-migration.rst | 23 ----------------------- news/fix-docs-example.rst | 23 ----------------------- news/fix-get-array-index.rst | 23 ----------------------- news/group-standard.rst | 23 ----------------------- news/linelength79.rst | 23 ----------------------- news/news-and-misc.rst | 23 ----------------------- news/readme.rst | 23 ----------------------- news/setup-CI.rst | 23 ----------------------- 10 files changed, 18 insertions(+), 207 deletions(-) delete mode 100644 news/changelog-3.6.0-line-break.rst delete mode 100644 news/doc-migration.rst delete mode 100644 news/fix-docs-example.rst delete mode 100644 news/fix-get-array-index.rst delete mode 100644 news/group-standard.rst delete mode 100644 news/linelength79.rst delete mode 100644 news/news-and-misc.rst delete mode 100644 news/readme.rst delete mode 100644 news/setup-CI.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 48c7a51e..302e6949 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,24 @@ Release notes .. current developments +3.6.1 +===== + +**Added:** + +* update isort, flake8 and black to set line limit to 79 + +**Fixed:** + +* Support ``scikit-package`` Level 5 standard (https://scikit-package.github.io/scikit-package/). +* Update github CI to scikit-package standard +* Let ``DiffractionObject.get_array_index`` to use the ``xtype`` from its inputs. + +**Removed:** + +* Extra line break in each news after 3.6.0 in `CHANGELOG.rst` so that this rst can be rendered correctly when deployed + + 3.6.0 ===== diff --git a/news/changelog-3.6.0-line-break.rst b/news/changelog-3.6.0-line-break.rst deleted file mode 100644 index ac80a0a1..00000000 --- a/news/changelog-3.6.0-line-break.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* Extra line break in each news after 3.6.0 in `CHANGELOG.rst` so that this rst can be rendered correctly when deployed - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/doc-migration.rst b/news/doc-migration.rst deleted file mode 100644 index b0ec659f..00000000 --- a/news/doc-migration.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Support ``scikit-package`` Level 5 standard (https://scikit-package.github.io/scikit-package/). - -**Security:** - -* diff --git a/news/fix-docs-example.rst b/news/fix-docs-example.rst deleted file mode 100644 index 0048f8a3..00000000 --- a/news/fix-docs-example.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: Fix some examples in the documentation. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/fix-get-array-index.rst b/news/fix-get-array-index.rst deleted file mode 100644 index 67814f58..00000000 --- a/news/fix-get-array-index.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Let ``DiffractionObject.get_array_index`` to use the ``xtype`` from its inputs. - -**Security:** - -* diff --git a/news/group-standard.rst b/news/group-standard.rst deleted file mode 100644 index 69ba1d29..00000000 --- a/news/group-standard.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: Use ``CODE-OF-CONDUCT,rst``, ``requirements/tests.text`` and ``docs`` according to new group standard. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/linelength79.rst b/news/linelength79.rst deleted file mode 100644 index be12d2fb..00000000 --- a/news/linelength79.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* update isort, flake8 and black to set line limit to 79 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/news-and-misc.rst b/news/news-and-misc.rst deleted file mode 100644 index 339e8df3..00000000 --- a/news/news-and-misc.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news item needed, bringing package up to ``scikit-package 5v0.1.0`` standards. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/readme.rst b/news/readme.rst deleted file mode 100644 index 978de17c..00000000 --- a/news/readme.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news needed: just a chore of no import - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/setup-CI.rst b/news/setup-CI.rst deleted file mode 100644 index bf692d68..00000000 --- a/news/setup-CI.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Update github CI to scikit-package standard - -**Security:** - -* From c14814b1f27b603154972c9a8f35751e62338185 Mon Sep 17 00:00:00 2001 From: Tieqiong Zhang Date: Thu, 21 Aug 2025 16:59:14 -0500 Subject: [PATCH 631/696] Normalize paths before assert --- news/headerfile.rst | 23 +++++++++++++++++++++++ tests/test_serialization.py | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 news/headerfile.rst diff --git a/news/headerfile.rst b/news/headerfile.rst new file mode 100644 index 00000000..13b35c28 --- /dev/null +++ b/news/headerfile.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Normalize paths before assert in `test_serialization/test_load_multiple` to prevent possible Windows short/long name mismatch. + +**Security:** + +* diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 039877f6..049d325c 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -33,7 +33,9 @@ def test_load_multiple(tmp_path, datafile): dt_colnames=["r", "gr"], show_path=True, ) - assert headerfile == Path(generated_data[headerfile.name].pop("path")) + assert Path(headerfile).resolve() == Path( + generated_data[headerfile.name].pop("path") + ) # rerun without path information and save to file generated_data = serialize_data( From 02a294e15b7ba2534a959554451a00485e3c1d1b Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 20 Nov 2025 15:38:44 -0500 Subject: [PATCH 632/696] fix: fix docstring to interpolate right entry. --- news/docstring-fix.rst | 24 ++++++++++++++++++++++++ src/diffpy/utils/resampler.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 news/docstring-fix.rst diff --git a/news/docstring-fix.rst b/news/docstring-fix.rst new file mode 100644 index 00000000..11ec4ce8 --- /dev/null +++ b/news/docstring-fix.rst @@ -0,0 +1,24 @@ +**Added:** + +* No News Added: Fix docstring + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* + diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index ca93761c..6003574e 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -38,7 +38,7 @@ def wsinterp(x, xp, fp, left=None, right=None): The array of y values associated with xp. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None - (default) or not given, set fp for x < xp[0] to fp evaluated at xp[-1]. + (default) or not given, set fp for x < xp[0] to fp evaluated at xp[0]. right: float If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for x > xp[-1] to fp evaluated at From 3dee8fadb438752f44c45a8023a910b400f47418 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:39:30 +0000 Subject: [PATCH 633/696] [pre-commit.ci] auto fixes from pre-commit hooks --- news/docstring-fix.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/news/docstring-fix.rst b/news/docstring-fix.rst index 11ec4ce8..fc596a63 100644 --- a/news/docstring-fix.rst +++ b/news/docstring-fix.rst @@ -21,4 +21,3 @@ **Security:** * - From 93f030cbd591b34a8ff36fad199348a598c35b75 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 20 Nov 2025 16:43:56 -0500 Subject: [PATCH 634/696] fix: fix format of docs build so that no warning arise --- docs/source/api/diffpy.utils.rst | 8 --- src/diffpy/utils/diffraction_objects.py | 36 ++++++------- src/diffpy/utils/resampler.py | 16 +++--- src/diffpy/utils/tools.py | 68 ++++++++++++++----------- src/diffpy/utils/transforms.py | 24 ++++----- 5 files changed, 75 insertions(+), 77 deletions(-) diff --git a/docs/source/api/diffpy.utils.rst b/docs/source/api/diffpy.utils.rst index e4de1d4f..c50dd0cd 100644 --- a/docs/source/api/diffpy.utils.rst +++ b/docs/source/api/diffpy.utils.rst @@ -44,14 +44,6 @@ diffpy.utils.tools module :undoc-members: :show-inheritance: -diffpy.utils.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.user_config - :members: - :undoc-members: - :show-inheritance: - diffpy.utils.diffraction_objects module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ec09b414..733d3705 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -108,18 +108,18 @@ def __init__( Parameters ---------- - xarray : ndarray + xarray : ``ndarray`` The independent variable array containing "q", "tth", or "d" values. - yarray : ndarray + yarray : ``ndarray`` The dependent variable array corresponding to intensity values. xtype : str The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. - wavelength : float, optional, default is None. + wavelength : float, ``optional``, default is None. The wavelength of the incoming beam, specified in angstroms (Å) - scat_quantity : str, optional, default is an empty string "". + scat_quantity : str, ``optional``, default is an empty string "". The type of scattering experiment (e.g., "x-ray", "neutron"). - name : str, optional, default is an empty string "". + name : str, ``optional``, default is an empty string "". The name or label for the scattering data. metadata : dict, optional, default is an empty dictionary {} The additional metadata associated with the diffraction object. @@ -360,7 +360,7 @@ def all_arrays(self): Returns ------- - ndarray + ``ndarray`` The shape (len(data), 4) 2D array with columns containing the ` yarray` (intensity) and the `xarray` values in q, tth, and d. @@ -386,7 +386,7 @@ def input_xtype(self): Returns ------- input_xtype : str - The type of `xarray`, which must be one of {*XQUANTITIES}. + The type of `xarray`, which must be one of ``{*XQUANTITIES}``. """ return self._input_xtype @@ -400,7 +400,7 @@ def uuid(self): Returns ------- - uuid : UUID + uuid : ``UUID`` The unique identifier of the DiffractionObject instance. """ return self._uuid @@ -478,7 +478,7 @@ def on_q(self): Returns ------- - (q-array, y-array) : tuple of ndarray + (q-array, y-array) : tuple of ``ndarray`` The tuple containing two 1D numpy arrays with q and y data """ return [self.all_arrays[:, 1], self.all_arrays[:, 0]] @@ -488,7 +488,7 @@ def on_tth(self): Returns ------- - (tth-array, y-array) : tuple of ndarray + (tth-array, y-array) : tuple of ``ndarray`` The tuple containing two 1D numpy arrays with tth and y data """ return [self.all_arrays[:, 2], self.all_arrays[:, 0]] @@ -498,7 +498,7 @@ def on_d(self): Returns ------- - (d-array, y-array) : tuple of ndarray + (d-array, y-array) : tuple of ``ndarray`` The tuple containing two 1D numpy arrays with d and y data """ return [self.all_arrays[:, 3], self.all_arrays[:, 0]] @@ -522,12 +522,12 @@ def scale_to( target_diff_object: DiffractionObject The diffraction object you want to scale the current one onto. - q, tth, d : float, optional, default is None + q, tth, d : float, ``optional``, default is None The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. - offset : float, optional, default is None + offset : float, ``optional``, default is None The offset to add to the scaled y-values. Returns @@ -574,16 +574,16 @@ def on_xtype(self, xtype): ---------- xtype : str The type of quantity for the independent variable chosen from - {*XQUANTITIES, } + ``{*XQUANTITIES, }`` Raises ------ ValueError - Raised when the specified xtype is not among {*XQUANTITIES, } + Raised when the specified xtype is not among ``{*XQUANTITIES, }`` Returns ------- - (xarray, yarray) : tuple of ndarray + (xarray, yarray) : tuple of ``ndarray`` The tuple containing two 1D numpy arrays with x and y data for the specified xtype. """ @@ -604,9 +604,9 @@ def dump(self, filepath, xtype=None): ---------- filepath : str The filepath where the diffraction object will be dumped - xtype : str, optional, default is q + xtype : str, ``optional``, default is q The type of quantity for the independent variable chosen from - {*XQUANTITIES, } + ``{*XQUANTITIES, }`` Examples -------- diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index ca93761c..3cf6790d 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -30,11 +30,11 @@ def wsinterp(x, xp, fp, left=None, right=None): Parameters ---------- - x: ndarray + x: ``ndarray`` The x values at which interpolation is computed. - xp: ndarray + xp: ``ndarray`` The array of known x values. - fp: ndarray + fp: ``ndarray`` The array of y values associated with xp. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None @@ -46,7 +46,7 @@ def wsinterp(x, xp, fp, left=None, right=None): Returns ------- - ndarray or float + ``ndarray`` or float The interpolated values at points x. Returns a single float if x is a scalar, otherwise returns a numpy.ndarray. """ @@ -91,9 +91,9 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): Parameters ---------- - xp: ndarray + xp: ``ndarray`` The array of known x values. - fp: ndarray + fp: ``ndarray`` The array of y values associated with xp. qmin: float The lower band limit in the frequency domain. @@ -102,7 +102,7 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): Returns ------- - x: ndarray + x: ``ndarray`` The Nyquist-Shannon grid computed for the given qmin and qmax. fp_at_x: ndarray The interpolated values at points x. Returns a single float if x is a @@ -139,7 +139,7 @@ def resample(r, s, dr): Returns ------- - Returns resampled (r, s). + Returns resampled ``(r, s)``. """ warnings.warn( diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 63e10ba2..3a42990d 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -51,43 +51,46 @@ def _load_config(file_path): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """Get name, email, and orcid of the owner/user from various sources and + """Get name, email, and ORCID of the owner/user from various sources and return it as a metadata dictionary. - The function looks for the information in json format configuration files - with the name 'diffpyconfig.json'. These can be in the user's home - directory and in the current working directory. The information in the - config files are combined, with the local config overriding the - home- directory one. Values for owner_name, owner_email, and owner_orcid - may be passed in to the function and these override the values in the - config files. - - A template for the config file is below. Create a text file called ' - diffpyconfig.json' in your home directory and copy-paste the template - into it, editing it with your real information. - { - "owner_name": ">", - "owner_email": ">@email.com", - "owner_orcid": ">" # noqa: E501 - } + The function looks for information in JSON configuration files named + ``diffpyconfig.json``. These can be in the user's home directory and in + the current working directory. The information in the config files is + combined, with the local config overriding the home-directory one. + Values for ``owner_name``, ``owner_email``, and ``owner_orcid`` may be + passed in to the function, and these override the config files. + + A template for the config file is below. Create a text file called + ``diffpyconfig.json`` in your home directory and paste the template into + it, editing it with your real information:: + + { + "owner_name": "", + "owner_email": "@email.com", + "owner_orcid": "" # noqa: E501 + } + You may also store any other global-level information that you would like - associated with your diffraction data in this file + associated with your diffraction data in this file. Parameters ---------- - owner_name : str, optional, default is the value stored in the global or - local config file. The name of the user who will show as owner in the - metadata that is stored with the data - owner_email : str, optional, default is the value stored in the global or - local config file. The email of the user/owner - owner_orcid : str, optional, default is the value stored in the global or - local config file. The ORCID id of the user/owner + owner_name : str, ``optional`` + Default is the value stored in the global or local config file. + The name of the user stored in metadata. + owner_email : str, ``optional`` + Default is the value stored in the global or local config file. + The email address of the user/owner. + owner_orcid : str, ``optional`` + Default is the value stored in the global or local config file. + The ORCID ID of the user/owner. Returns ------- user_info : dict - The dictionary containing username, email and orcid of the user/owner - , and any other information stored in the global or local config files. + Dictionary containing username, email, ORCID, and any other + information stored in global or local config files. """ runtime_info = { "owner_name": owner_name, @@ -127,9 +130,9 @@ def check_and_build_global_config(skip_config_creation=False): Parameters ---------- - skip_config_creation : bool, optional, default is False. + skip_config_creation : bool, ``optional`` The boolean that will override the creation workflow even if no - config file exists. + config file exists. Default is False. Returns ------- @@ -190,6 +193,7 @@ def get_package_info(package_names, metadata=None): Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. + Parameters ---------- package_name : str or list The name of the package(s) to retrieve the version number for. @@ -244,11 +248,13 @@ def compute_mu_using_xraydb( The chemical formula of the material. energy : float The energy of the incident x-rays in keV. - sample_mass_density : float, optional, Default is None + sample_mass_density : float, ``optional`` The mass density of the packed powder/sample in g/cm*3. - packing_fraction : float, optional, Default is None + Default is None. + packing_fraction : float, ``optional`` The fraction of sample in the capillary (between 0 and 1). Specify either sample_mass_density or packing_fraction but not both. + Default is None. Returns ------- diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 56eebc68..ab1d009e 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -52,7 +52,7 @@ def q_to_tth(q, wavelength): Parameters ---------- - q : ndarray + q : ``ndarray`` The 1D array of :math:`q` values numpy.array([qs]). The units of q must be reciprocal of the units of wavelength. @@ -61,7 +61,7 @@ def q_to_tth(q, wavelength): Returns ------- - tth : ndarray + tth : ``ndarray`` The 1D array of :math:`2\theta` values in degrees numpy.array([tths]). """ _validate_inputs(q, wavelength) @@ -95,7 +95,7 @@ def tth_to_q(tth, wavelength): Parameters ---------- - tth : ndarray + tth : ``ndarray`` The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. @@ -104,7 +104,7 @@ def tth_to_q(tth, wavelength): Returns ------- - q : ndarray + q : ``ndarray`` The 1D array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. @@ -129,13 +129,13 @@ def q_to_d(q): Parameters ---------- - q : ndarray + q : ``ndarray`` The 1D array of :math:`q` values np.array([qs]). The units of q must be reciprocal of the units of wavelength. Returns ------- - d : ndarray + d : ``ndarray`` The 1D array of :math:`d` values np.array([ds]). """ if 0 in q: @@ -153,7 +153,7 @@ def tth_to_d(tth, wavelength): Parameters ---------- - tth : nsarray + tth : ``nsarray`` The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. @@ -162,7 +162,7 @@ def tth_to_d(tth, wavelength): Returns ------- - d : nsarray + d : ``nsarray`` The 1D array of :math:`d` values np.array([ds]). """ q = tth_to_q(tth, wavelength) @@ -182,12 +182,12 @@ def d_to_q(d): Parameters ---------- - d : nsarray + d : ``nsarray`` The 1D array of :math:`d` values np.array([ds]). Returns ------- - q : nsarray + q : ``nsarray`` The 1D array of :math:`q` values np.array([qs]). The units of q must be reciprocal of the units of wavelength. """ @@ -206,7 +206,7 @@ def d_to_tth(d, wavelength): Parameters ---------- - d : nsarray + d : ``nsarray`` The 1D array of :math:`d` values np.array([ds]). wavelength : float @@ -214,7 +214,7 @@ def d_to_tth(d, wavelength): Returns ------- - tth : nsarray + tth : ``nsarray`` The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. """ From 086e9bf16d7fadf39fc2a97f4d2188b694de7be7 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 20 Nov 2025 16:46:24 -0500 Subject: [PATCH 635/696] chore: add news item. --- news/html-build.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/html-build.rst diff --git a/news/html-build.rst b/news/html-build.rst new file mode 100644 index 00000000..cf04edf2 --- /dev/null +++ b/news/html-build.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: Fix format of docs to build html + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 2e8d887b1133620621d4b496f4d74fb45a0269d1 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 21 Nov 2025 16:50:41 -0500 Subject: [PATCH 636/696] deprecator decorator --- src/diffpy/utils/_deprecator.py | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/diffpy/utils/_deprecator.py diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py new file mode 100644 index 00000000..1334de17 --- /dev/null +++ b/src/diffpy/utils/_deprecator.py @@ -0,0 +1,78 @@ +import functools +import warnings + +# Deprecated decorator is available for Python 3.13+, once +# Support for earlier versions is dropped, this custom implementation can be removed. +try: + from warnings import deprecated as _builtin_deprecated +except ImportError: + _builtin_deprecated = None + +def deprecated(*, alt_name=None, message=None): + """ + Marks a function or class as deprecated. + + Emits a DeprecationWarning whenever the decorated function is called + or the decorated class is instantiated. + + Parameters + ---------- + alt_name : str, optional + Name of the recommended alternative. + message : str, optional + Custom deprecation message. If None, a default message is generated. + + Returns + ------- + decorator : function + Decorator that wraps the deprecated object. + + Examples + -------- + .. code-block:: python + + from diffpy._deprecations import deprecated + + # ------------------------------ + # Deprecated function + # ------------------------------ + @deprecated(alt_name="new_function") + def old_function(x, y): + return x + y + + def new_function(x, y): + return x + y + + # Usage + old_function(1, 2) # Emits DeprecationWarning + new_function(1, 2) # No warning + + # ------------------------------ + # Deprecated class + # ------------------------------ + @deprecated(alt_name="NewAtom") + class OldAtom: + def __init__(self, symbol): + self.symbol = symbol + + # Usage + a = OldAtom("C") # Emits DeprecationWarning + atom = NewAtom("C") # No warning + """ + if _builtin_deprecated: + return _builtin_deprecated + + def decorator(obj): + name = getattr(obj, "__name__", repr(obj)) + msg = message or (f"'{name}' is deprecated. Use '{alt_name}' instead." + if alt_name else f"'{name}' is deprecated.") + + if callable(obj): + @functools.wraps(obj) + def wrapper(*args, **kwargs): + warnings.warn(msg, DeprecationWarning, stacklevel=2) + return obj(*args, **kwargs) + return wrapper + else: + raise TypeError("deprecated decorator can only be applied to functions or classes") + return decorator From 614b234383a47d3307a7b91e53e8ffe062e6515c Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 21 Nov 2025 16:51:52 -0500 Subject: [PATCH 637/696] precommit fixes --- src/diffpy/utils/_deprecator.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 1334de17..099e371c 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -2,15 +2,16 @@ import warnings # Deprecated decorator is available for Python 3.13+, once -# Support for earlier versions is dropped, this custom implementation can be removed. +# Support for earlier versions is dropped, this custom +# implementation can be removed. try: from warnings import deprecated as _builtin_deprecated except ImportError: _builtin_deprecated = None + def deprecated(*, alt_name=None, message=None): - """ - Marks a function or class as deprecated. + """Marks a function or class as deprecated. Emits a DeprecationWarning whenever the decorated function is called or the decorated class is instantiated. @@ -64,15 +65,24 @@ def __init__(self, symbol): def decorator(obj): name = getattr(obj, "__name__", repr(obj)) - msg = message or (f"'{name}' is deprecated. Use '{alt_name}' instead." - if alt_name else f"'{name}' is deprecated.") + msg = message or ( + f"'{name}' is deprecated. Use '{alt_name}' instead." + if alt_name + else f"'{name}' is deprecated." + ) if callable(obj): + @functools.wraps(obj) def wrapper(*args, **kwargs): warnings.warn(msg, DeprecationWarning, stacklevel=2) return obj(*args, **kwargs) + return wrapper else: - raise TypeError("deprecated decorator can only be applied to functions or classes") + raise TypeError( + "deprecated decorator can only be applied to functions or " + "classes" + ) + return decorator From 4ec1cf22ee3202964d7ac4922b8868e91dd5fe42 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 21 Nov 2025 16:52:38 -0500 Subject: [PATCH 638/696] news --- news/deprecator.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/deprecator.rst diff --git a/news/deprecator.rst b/news/deprecator.rst new file mode 100644 index 00000000..dabbfdf1 --- /dev/null +++ b/news/deprecator.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add ``@deprecated`` decorator. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 0728a448cc9785e4ec456810be88d1eaeaeda7e8 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 24 Nov 2025 11:00:16 -0500 Subject: [PATCH 639/696] update deprecator to exactly match 3.13 deprecated decorator --- src/diffpy/utils/_deprecator.py | 85 +++++++++------------------------ 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 099e371c..55593c96 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -10,79 +10,40 @@ _builtin_deprecated = None -def deprecated(*, alt_name=None, message=None): - """Marks a function or class as deprecated. +def deprecated(message, *, category=DeprecationWarning, stacklevel=1): + """Compatibility wrapper for Python <3.13. - Emits a DeprecationWarning whenever the decorated function is called - or the decorated class is instantiated. - - Parameters - ---------- - alt_name : str, optional - Name of the recommended alternative. - message : str, optional - Custom deprecation message. If None, a default message is generated. - - Returns - ------- - decorator : function - Decorator that wraps the deprecated object. - - Examples - -------- - .. code-block:: python - - from diffpy._deprecations import deprecated - - # ------------------------------ - # Deprecated function - # ------------------------------ - @deprecated(alt_name="new_function") - def old_function(x, y): - return x + y - - def new_function(x, y): - return x + y - - # Usage - old_function(1, 2) # Emits DeprecationWarning - new_function(1, 2) # No warning - - # ------------------------------ - # Deprecated class - # ------------------------------ - @deprecated(alt_name="NewAtom") - class OldAtom: - def __init__(self, symbol): - self.symbol = symbol - - # Usage - a = OldAtom("C") # Emits DeprecationWarning - atom = NewAtom("C") # No warning + Matches the Python 3.13 warnings.deprecated API exactly. """ - if _builtin_deprecated: - return _builtin_deprecated + # If Python 3.13 implementation exists, delegate to it + if _builtin_deprecated is not None: + return _builtin_deprecated( + message, category=category, stacklevel=stacklevel + ) - def decorator(obj): - name = getattr(obj, "__name__", repr(obj)) - msg = message or ( - f"'{name}' is deprecated. Use '{alt_name}' instead." - if alt_name - else f"'{name}' is deprecated." + # Validate message type like Python 3.13 does + if not isinstance(message, str): + raise TypeError( + f"Expected an object of type str for 'message', not " + f"{type(message).__name__!r}" ) + def decorator(obj): + # Set __deprecated__ attribute (required by PEP 702) + setattr(obj, "__deprecated__", message) + + # Must support functions AND classes if callable(obj): @functools.wraps(obj) def wrapper(*args, **kwargs): - warnings.warn(msg, DeprecationWarning, stacklevel=2) + warnings.warn(message, category, stacklevel=stacklevel + 1) return obj(*args, **kwargs) return wrapper - else: - raise TypeError( - "deprecated decorator can only be applied to functions or " - "classes" - ) + + raise TypeError( + "deprecated decorator can only be applied to functions or classes" + ) return decorator From 1d936d77fc0ccb3acea09ac9047de94f0df75132 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 24 Nov 2025 11:00:53 -0500 Subject: [PATCH 640/696] comment --- src/diffpy/utils/_deprecator.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 55593c96..21dffa77 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -15,13 +15,10 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): Matches the Python 3.13 warnings.deprecated API exactly. """ - # If Python 3.13 implementation exists, delegate to it if _builtin_deprecated is not None: return _builtin_deprecated( message, category=category, stacklevel=stacklevel ) - - # Validate message type like Python 3.13 does if not isinstance(message, str): raise TypeError( f"Expected an object of type str for 'message', not " @@ -29,10 +26,7 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): ) def decorator(obj): - # Set __deprecated__ attribute (required by PEP 702) setattr(obj, "__deprecated__", message) - - # Must support functions AND classes if callable(obj): @functools.wraps(obj) From a3e4a108e7e26af380cc31acd62a21df2d4e4bdd Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Wed, 26 Nov 2025 11:00:51 -0500 Subject: [PATCH 641/696] fix: fix import to right file in examples --- docs/source/examples/parsers_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/examples/parsers_example.rst b/docs/source/examples/parsers_example.rst index 09220a3a..db97e0ed 100644 --- a/docs/source/examples/parsers_example.rst +++ b/docs/source/examples/parsers_example.rst @@ -69,7 +69,7 @@ returns an empty list. .. code-block:: python - from diffpy.utils.parsers.loaddata import serialize_data + from diffpy.utils.parsers.serialization import serialize_data file_data = serialize_data(' Date: Wed, 26 Nov 2025 11:06:49 -0500 Subject: [PATCH 642/696] chore: add news item --- news/docs-fix.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/docs-fix.rst diff --git a/news/docs-fix.rst b/news/docs-fix.rst new file mode 100644 index 00000000..2c81480c --- /dev/null +++ b/news/docs-fix.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: Fix import in examples + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From f5e4d439790691ee21cd2e99f36063e7f8333e8f Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Wed, 26 Nov 2025 16:27:15 -0500 Subject: [PATCH 643/696] fix: add real values into two entries --- docs/source/examples/diffraction_objects_example.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/examples/diffraction_objects_example.rst b/docs/source/examples/diffraction_objects_example.rst index 8794c719..6cc70a67 100644 --- a/docs/source/examples/diffraction_objects_example.rst +++ b/docs/source/examples/diffraction_objects_example.rst @@ -81,8 +81,9 @@ You could simply load them both as diffraction objects and plot them together on .. code-block:: python - calculated = DiffractionObject(xcalc, ycalc, "d") - measured = DiffractionObject(xmeas, ymeas, "tth", wavelength=0.717) + calculated = DiffractionObject(xcalc=[0.251436, 0.251542, 0.251647], ycalc=[-1.020020, -1.036460, -1.142070], "d") + measured = DiffractionObject(xmeas=[5.343089004093959198e-03, 5.343089004093959198e-03, 2.671544437772708711e-02], + ymeas=[3.533951950073242188e+01, 3.585629272460937500e+01, 3.611056518554687500e+01], "q", wavelength=0.717) plt.plot(calculated.on_q()[0], calculated.on_q()[1]) plt.plot(measured.on_q()[0], measured.on_q()[1]) plt.show() From e9c302e9f18d30c365d2d75a949d46f54ca5de92 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Wed, 26 Nov 2025 16:28:40 -0500 Subject: [PATCH 644/696] chore: add news item --- news/docs-add-data.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/docs-add-data.rst diff --git a/news/docs-add-data.rst b/news/docs-add-data.rst new file mode 100644 index 00000000..ad18e7a5 --- /dev/null +++ b/news/docs-add-data.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: Add real values into example + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From af8c0b1a165cb56077719315d7151643fd44f9cd Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 9 Dec 2025 14:46:08 -0500 Subject: [PATCH 645/696] news --- news/deprecated_update.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/deprecated_update.rst diff --git a/news/deprecated_update.rst b/news/deprecated_update.rst new file mode 100644 index 00000000..27fc85d7 --- /dev/null +++ b/news/deprecated_update.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news needed. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From d3c6e879c72fca57cbeed39ef3f09d3ac2ac225a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 9 Dec 2025 14:49:31 -0500 Subject: [PATCH 646/696] docstring --- src/diffpy/utils/_deprecator.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 21dffa77..a3c598de 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -11,10 +11,8 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): - """Compatibility wrapper for Python <3.13. - - Matches the Python 3.13 warnings.deprecated API exactly. - """ + """Deprecation decorator for functions and classes that is compatible with + Python versions prior to 3.13.""" if _builtin_deprecated is not None: return _builtin_deprecated( message, category=category, stacklevel=stacklevel From 3b7b13b50420cd25f3e30559fe204ea6d603125e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 9 Dec 2025 14:52:29 -0500 Subject: [PATCH 647/696] examples in docstring --- src/diffpy/utils/_deprecator.py | 44 ++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index a3c598de..91f3c0fb 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -12,7 +12,49 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): """Deprecation decorator for functions and classes that is compatible with - Python versions prior to 3.13.""" + Python versions prior to 3.13. + + Examples + -------- + Basic usage with a deprecated function: + + .. code-block:: python + + from diffpy._deprecations import deprecated + import warnings + + @deprecated("old_function is deprecated; use new_function instead") + def old_function(x, y): + return x + y + + def new_function(x, y): + return x + y + + old_function(1, 2) # Emits DeprecationWarning + new_function(1, 2) # No warning + + + Deprecating a class: + + .. code-block:: python + + from diffpy._deprecations import deprecated + import warnings + + warnings.simplefilter("always", DeprecationWarning) + + @deprecated("OldAtom is deprecated; use NewAtom instead") + class OldAtom: + def __init__(self, symbol): + self.symbol = symbol + + class NewAtom: + def __init__(self, symbol): + self.symbol = symbol + + a = OldAtom("C") # Emits DeprecationWarning + b = NewAtom("C") # No warning + """ if _builtin_deprecated is not None: return _builtin_deprecated( message, category=category, stacklevel=stacklevel From 804d7c3b1ee6f7de2e1e07b4b2ebc60b3d4b32b1 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Tue, 9 Dec 2025 16:28:30 -0500 Subject: [PATCH 648/696] fix: add instruction and corresponding files for the example --- .../examples/diffraction_objects_example.rst | 14 +- .../examples/example_data/CeO2_dspacing.xy | 2315 +++++++++++++ .../examples/example_data/CeO2_mean_q.chi | 3001 +++++++++++++++++ 3 files changed, 5325 insertions(+), 5 deletions(-) create mode 100644 docs/source/examples/example_data/CeO2_dspacing.xy create mode 100644 docs/source/examples/example_data/CeO2_mean_q.chi diff --git a/docs/source/examples/diffraction_objects_example.rst b/docs/source/examples/diffraction_objects_example.rst index 6cc70a67..14c44c01 100644 --- a/docs/source/examples/diffraction_objects_example.rst +++ b/docs/source/examples/diffraction_objects_example.rst @@ -75,15 +75,19 @@ i.e., This makes it very easy to compare a diffraction pattern that was measured or calculated on one ``xtype`` with one that was measured or calculated on another. E.g., suppose that you have a calculated powder pattern from a CIF file that was calculated on a d-spacing grid using -some software package, and -you want to know if a diffraction pattern you have measured on a Q-grid is the same material. +some software package, which you could find in the example data folder called `CeO2_dspacing.xy` and +you want to know if a diffraction pattern you have measured on a Q-grid, where the example data is also +in the example data folder called `CeO2_mean_q.chi`, is the same material. You could simply load them both as diffraction objects and plot them together on the same grid. +Here the `xcalc` is the first column in the `CeO2_dspacing.xy` and `ycalc` is the second column in the same +file. Similarly, `xmeas` corresponds to the first column in the `CeO2_mean_q.chi` and `ymeas` is the second column in +the same file. .. code-block:: python - calculated = DiffractionObject(xcalc=[0.251436, 0.251542, 0.251647], ycalc=[-1.020020, -1.036460, -1.142070], "d") - measured = DiffractionObject(xmeas=[5.343089004093959198e-03, 5.343089004093959198e-03, 2.671544437772708711e-02], - ymeas=[3.533951950073242188e+01, 3.585629272460937500e+01, 3.611056518554687500e+01], "q", wavelength=0.717) + calculated = DiffractionObject(xcalc, ycalc, "d") + measured = DiffractionObject(xmeas, + ymeas, "q", wavelength=0.1) plt.plot(calculated.on_q()[0], calculated.on_q()[1]) plt.plot(measured.on_q()[0], measured.on_q()[1]) plt.show() diff --git a/docs/source/examples/example_data/CeO2_dspacing.xy b/docs/source/examples/example_data/CeO2_dspacing.xy new file mode 100644 index 00000000..4785f90e --- /dev/null +++ b/docs/source/examples/example_data/CeO2_dspacing.xy @@ -0,0 +1,2315 @@ +7.218335 0.391564 +7.132923 0.370386 +7.049509 0.349966 +6.968024 0.329743 +6.888400 0.309479 +6.810576 0.289928 +6.734490 0.270571 +6.660086 0.251253 +6.587308 0.232565 +6.516103 0.214111 +6.446422 0.195715 +6.378214 0.177916 +6.311435 0.160339 +6.246021 0.142922 +6.181986 0.126044 +6.119251 0.109438 +6.057717 0.092990 +5.997466 0.077080 +5.938402 0.061451 +5.880489 0.045978 +5.823642 0.031012 +5.767936 0.016295 +5.713285 0.001721 +5.659610 -0.012304 +5.606983 -0.026099 +5.555326 -0.039828 +5.504565 -0.052906 +5.454769 -0.065756 +5.405867 -0.078608 +5.357834 -0.090725 +5.310602 -0.102664 +5.264239 -0.114745 +5.218679 -0.125908 +5.173858 -0.136968 +5.129843 -0.148309 +5.086570 -0.158575 +5.044021 -0.168832 +5.002138 -0.179372 +4.960984 -0.188845 +4.920502 -0.198336 +4.880637 -0.208117 +4.841450 -0.216812 +4.802887 -0.225712 +4.764898 -0.234709 +4.727541 -0.242872 +4.690765 -0.251246 +4.654556 -0.259696 +4.618868 -0.267483 +4.583757 -0.275518 +4.549176 -0.283485 +4.515080 -0.290860 +4.481523 -0.298452 +4.448462 -0.305873 +4.415884 -0.312760 +4.383750 -0.319821 +4.352110 -0.326670 +4.320924 -0.333017 +4.290152 -0.339568 +4.259844 -0.345951 +4.229962 -0.351786 +4.200468 -0.357837 +4.171409 -0.363754 +4.142750 -0.369075 +4.114483 -0.374698 +4.086571 -0.380270 +4.059063 -0.385138 +4.031922 -0.390302 +4.005116 -0.395460 +3.978689 -0.399810 +3.952609 -0.404509 +3.926868 -0.409280 +3.901436 -0.413232 +3.876356 -0.417557 +3.851596 -0.421917 +3.827127 -0.425455 +3.802990 -0.429475 +3.779155 -0.433467 +3.755595 -0.436673 +3.732349 -0.440473 +3.709389 -0.444102 +3.686710 -0.447042 +3.664285 -0.450705 +3.642152 -0.454059 +3.620285 -0.456761 +3.598658 -0.460185 +3.577309 -0.462992 +3.556211 -0.465360 +3.535341 -0.468228 +3.514734 -0.470431 +3.494366 -0.472010 +3.474233 -0.474049 +3.454311 -0.475013 +3.434636 -0.475180 +3.415183 -0.475268 +3.395931 -0.473697 +3.376913 -0.470006 +3.358107 -0.465152 +3.339509 -0.455154 +3.321098 -0.438147 +3.302907 -0.411786 +3.284913 -0.365345 +3.267098 -0.281368 +3.249492 -0.141094 +3.232075 0.132309 +3.214826 0.629427 +3.197778 1.434690 +3.180909 2.711830 +3.164217 4.415530 +3.147683 6.041780 +3.131338 7.012460 +3.115161 7.022290 +3.099135 6.011820 +3.083288 4.408110 +3.067602 2.705950 +3.052075 1.420620 +3.036690 0.593567 +3.021474 0.094493 +3.006409 -0.178329 +2.991480 -0.322551 +2.976713 -0.418517 +2.962090 -0.472643 +2.947597 -0.507672 +2.933258 -0.530855 +2.919058 -0.547876 +2.904995 -0.558779 +2.891054 -0.567202 +2.877259 -0.573437 +2.863595 -0.575618 +2.850047 -0.574917 +2.836640 -0.570439 +2.823358 -0.556533 +2.810187 -0.528418 +2.797151 -0.480731 +2.784236 -0.376463 +2.771439 -0.193399 +2.758747 0.116934 +2.746183 0.595629 +2.733733 1.208600 +2.721384 1.764630 +2.709157 2.076910 +2.697040 2.028750 +2.685030 1.634090 +2.673116 1.034660 +2.661318 0.436222 +2.649624 -0.004836 +2.638021 -0.286559 +2.626530 -0.462693 +2.615139 -0.559904 +2.603836 -0.615537 +2.592640 -0.651593 +2.581540 -0.674783 +2.570535 -0.690629 +2.559613 -0.703035 +2.548794 -0.713140 +2.538066 -0.720946 +2.527418 -0.728517 +2.516868 -0.735761 +2.506407 -0.741589 +2.496032 -0.747654 +2.485732 -0.754019 +2.475527 -0.759003 +2.465406 -0.764652 +2.455357 -0.770552 +2.445400 -0.775273 +2.435523 -0.780739 +2.425715 -0.786290 +2.415996 -0.790973 +2.406355 -0.796425 +2.396790 -0.801721 +2.387292 -0.806534 +2.377878 -0.811995 +2.368538 -0.817154 +2.359262 -0.821987 +2.350067 -0.827530 +2.340943 -0.832601 +2.331882 -0.837525 +2.322899 -0.843077 +2.313985 -0.848109 +2.305139 -0.852976 +2.296352 -0.858401 +2.287640 -0.863363 +2.278994 -0.868126 +2.270404 -0.873499 +2.261888 -0.878490 +2.253435 -0.883157 +2.245045 -0.888416 +2.236709 -0.893327 +2.228443 -0.897752 +2.220238 -0.902815 +2.212085 -0.907712 +2.204000 -0.912090 +2.195974 -0.917020 +2.187998 -0.921639 +2.180087 -0.925706 +2.172233 -0.930275 +2.164436 -0.934565 +2.156687 -0.938518 +2.149001 -0.943054 +2.141370 -0.947108 +2.133785 -0.950916 +2.126261 -0.955402 +2.118789 -0.959254 +2.111371 -0.963151 +2.103996 -0.967682 +2.096680 -0.971165 +2.089415 -0.974677 +2.082193 -0.979105 +2.075028 -0.981976 +2.067912 -0.985022 +2.060838 -0.988744 +2.053818 -0.990924 +2.046847 -0.993087 +2.039922 -0.995823 +2.033038 -0.996630 +2.026206 -0.996855 +2.019421 -0.996882 +2.012674 -0.994164 +2.005978 -0.989108 +1.999327 -0.981703 +1.992714 -0.967023 +1.986150 -0.943805 +1.979629 -0.906106 +1.973152 -0.840443 +1.966710 -0.727375 +1.960316 -0.531934 +1.953964 -0.158305 +1.947646 0.495551 +1.941376 1.615580 +1.935146 3.399880 +1.928955 5.730030 +1.922798 8.076660 +1.916686 9.710930 +1.910613 10.009000 +1.904573 8.762850 +1.898576 6.465530 +1.892617 4.017110 +1.886689 2.063250 +1.880805 0.743664 +1.874956 -0.055738 +1.869144 -0.481454 +1.863363 -0.727027 +1.857623 -0.873909 +1.851917 -0.958809 +1.846242 -1.013110 +1.840606 -1.049770 +1.835005 -1.074880 +1.829438 -1.092750 +1.823899 -1.107410 +1.818399 -1.118330 +1.812931 -1.127280 +1.807492 -1.135560 +1.802090 -1.142030 +1.796720 -1.147580 +1.791378 -1.153310 +1.786072 -1.157700 +1.780797 -1.161810 +1.775553 -1.166350 +1.770335 -1.169660 +1.765153 -1.172810 +1.760001 -1.176550 +1.754874 -1.179200 +1.749781 -1.181720 +1.744719 -1.184990 +1.739680 -1.187210 +1.734675 -1.188940 +1.729699 -1.191090 +1.724752 -1.191950 +1.719828 -1.191800 +1.714937 -1.191530 +1.710073 -1.189490 +1.705233 -1.185440 +1.700424 -1.179610 +1.695642 -1.169750 +1.690887 -1.154170 +1.686155 -1.130710 +1.681453 -1.091960 +1.676777 -1.028390 +1.672123 -0.924027 +1.667499 -0.735773 +1.662900 -0.399846 +1.658323 0.195974 +1.653774 1.268090 +1.649251 3.026370 +1.644753 5.422790 +1.640274 8.311170 +1.635824 10.768100 +1.631399 11.793900 +1.626993 10.982800 +1.622615 8.568240 +1.618260 5.766640 +1.613929 3.186140 +1.609616 1.341200 +1.605331 0.269659 +1.601069 -0.336168 +1.596825 -0.661840 +1.592607 -0.807221 +1.588412 -0.826728 +1.584235 -0.726604 +1.580083 -0.477472 +1.575954 -0.052714 +1.571846 0.495872 +1.567755 1.019980 +1.563689 1.346170 +1.559645 1.328280 +1.555617 0.965010 +1.551614 0.400290 +1.547632 -0.167374 +1.543670 -0.612469 +1.539725 -0.910709 +1.535803 -1.089710 +1.531901 -1.190970 +1.528016 -1.250180 +1.524153 -1.286660 +1.520311 -1.309300 +1.516483 -1.324670 +1.512679 -1.336500 +1.508894 -1.345480 +1.505128 -1.352560 +1.501376 -1.359120 +1.497647 -1.364510 +1.493937 -1.369260 +1.490241 -1.374180 +1.486567 -1.378300 +1.482912 -1.382340 +1.479270 -1.386650 +1.475650 -1.389990 +1.472048 -1.393620 +1.468463 -1.397470 +1.464892 -1.400070 +1.461342 -1.403180 +1.457809 -1.406900 +1.454290 -1.409090 +1.450791 -1.412050 +1.447308 -1.415730 +1.443843 -1.417830 +1.440391 -1.420720 +1.436958 -1.424210 +1.433542 -1.426040 +1.430139 -1.428670 +1.426755 -1.431550 +1.423387 -1.433260 +1.420032 -1.435580 +1.416695 -1.437490 +1.413375 -1.438580 +1.410070 -1.440150 +1.406777 -1.440620 +1.403502 -1.440570 +1.400243 -1.440670 +1.396996 -1.439020 +1.393767 -1.435740 +1.390553 -1.430770 +1.387354 -1.421030 +1.384166 -1.403940 +1.380996 -1.376270 +1.377840 -1.325890 +1.374696 -1.233610 +1.371569 -1.069430 +1.368456 -0.780739 +1.365355 -0.304776 +1.362270 0.362700 +1.359200 1.138740 +1.356143 1.834650 +1.353097 2.175630 +1.350067 2.011780 +1.347051 1.420970 +1.344046 0.634696 +1.341057 -0.095087 +1.338081 -0.651016 +1.335115 -1.004360 +1.332166 -1.206770 +1.329229 -1.321120 +1.326305 -1.385890 +1.323392 -1.423140 +1.320493 -1.446960 +1.317608 -1.462670 +1.314732 -1.471920 +1.311872 -1.479180 +1.309024 -1.484870 +1.306189 -1.487450 +1.303363 -1.489890 +1.300551 -1.492110 +1.297752 -1.492000 +1.294963 -1.492110 +1.292188 -1.491690 +1.289425 -1.489560 +1.286671 -1.487390 +1.283931 -1.483110 +1.281203 -1.476680 +1.278486 -1.468390 +1.275779 -1.454910 +1.273085 -1.434650 +1.270403 -1.404780 +1.267730 -1.355110 +1.265070 -1.271110 +1.262421 -1.129670 +1.259784 -0.877461 +1.257155 -0.411019 +1.254539 0.385198 +1.251935 1.675540 +1.249338 3.530830 +1.246755 5.648270 +1.244183 7.473220 +1.241618 8.390130 +1.239067 7.987870 +1.236526 6.434260 +1.233996 4.353390 +1.231473 2.422320 +1.228963 1.060420 +1.226464 0.323352 +1.223972 0.159026 +1.221492 0.527557 +1.219023 1.392570 +1.216561 2.635030 +1.214112 4.024160 +1.211672 5.072440 +1.209242 5.302750 +1.206820 4.627530 +1.204409 3.272480 +1.202008 1.795450 +1.199615 0.546544 +1.197233 -0.342175 +1.194860 -0.861614 +1.192497 -1.156180 +1.190141 -1.327480 +1.187797 -1.424240 +1.185462 -1.483280 +1.183134 -1.523100 +1.180817 -1.548790 +1.178509 -1.567380 +1.176208 -1.582180 +1.173918 -1.592490 +1.171637 -1.601710 +1.169365 -1.610670 +1.167100 -1.617240 +1.164845 -1.623560 +1.162599 -1.630260 +1.160360 -1.635140 +1.158131 -1.639940 +1.155911 -1.644940 +1.153699 -1.648340 +1.151494 -1.651480 +1.149299 -1.654450 +1.147113 -1.656210 +1.144933 -1.657680 +1.142763 -1.658040 +1.140601 -1.657010 +1.138446 -1.655270 +1.136301 -1.650960 +1.134163 -1.643040 +1.132034 -1.631380 +1.129911 -1.612350 +1.127797 -1.580380 +1.125692 -1.528840 +1.123592 -1.442120 +1.121503 -1.288320 +1.119421 -1.014350 +1.117344 -0.522385 +1.115278 0.345628 +1.113219 1.762510 +1.111167 3.804420 +1.109122 6.268800 +1.107085 8.557600 +1.105056 9.827060 +1.103033 9.565250 +1.101019 7.861330 +1.099012 5.415630 +1.097013 2.957880 +1.095019 1.090650 +1.093034 -0.107116 +1.091056 -0.813126 +1.089084 -1.201810 +1.087120 -1.417690 +1.085164 -1.547050 +1.083213 -1.626060 +1.081270 -1.674480 +1.079335 -1.707970 +1.077406 -1.730790 +1.075483 -1.746040 +1.073568 -1.757760 +1.071660 -1.764520 +1.069757 -1.768390 +1.067862 -1.768610 +1.065975 -1.762380 +1.064094 -1.748930 +1.062217 -1.725030 +1.060349 -1.681850 +1.058488 -1.604950 +1.056632 -1.469890 +1.054783 -1.227330 +1.052941 -0.779822 +1.051104 0.012156 +1.049275 1.302910 +1.047452 3.192750 +1.045636 5.489170 +1.043824 7.627500 +1.042020 8.902470 +1.040223 8.807070 +1.038430 7.338920 +1.036645 5.074930 +1.034865 2.796170 +1.033091 0.989055 +1.031324 -0.231767 +1.029563 -0.956643 +1.027808 -1.361260 +1.026057 -1.594130 +1.024314 -1.730070 +1.022577 -1.811800 +1.020844 -1.866610 +1.019119 -1.904180 +1.017400 -1.930170 +1.015686 -1.951160 +1.013976 -1.967190 +1.012274 -1.980500 +1.010578 -1.992790 +1.008885 -2.002140 +1.007200 -2.011170 +1.005520 -2.019610 +1.003845 -2.025400 +1.002176 -2.031930 +1.000514 -2.038800 +0.998856 -2.043860 +0.997203 -2.049370 +0.995556 -2.055190 +0.993915 -2.059230 +0.992278 -2.063760 +0.990648 -2.068300 +0.989023 -2.071570 +0.987404 -2.074940 +0.985788 -2.077210 +0.984179 -2.078900 +0.982575 -2.080810 +0.980975 -2.080720 +0.979382 -2.079640 +0.977794 -2.077600 +0.976209 -2.071980 +0.974631 -2.061830 +0.973059 -2.045310 +0.971491 -2.015870 +0.969927 -1.963900 +0.968369 -1.872130 +0.966816 -1.706160 +0.965267 -1.408060 +0.963725 -0.909593 +0.962187 -0.161827 +0.960654 0.797389 +0.959124 1.770790 +0.957601 2.465900 +0.956083 2.626200 +0.954568 2.174420 +0.953059 1.284200 +0.951555 0.258193 +0.950054 -0.610678 +0.948560 -1.216130 +0.947070 -1.601010 +0.945585 -1.816910 +0.944103 -1.932830 +0.942627 -1.999450 +0.941156 -2.035810 +0.939688 -2.054920 +0.938226 -2.064330 +0.936768 -2.064620 +0.935314 -2.059210 +0.933865 -2.046970 +0.932421 -2.024450 +0.930982 -1.988360 +0.929545 -1.930930 +0.928114 -1.835700 +0.926688 -1.672120 +0.925265 -1.387080 +0.923847 -0.874228 +0.922434 0.065748 +0.921025 1.602100 +0.919619 3.916420 +0.918218 6.892190 +0.916822 9.805050 +0.915429 11.763400 +0.914042 12.040600 +0.912658 10.599600 +0.911277 8.022060 +0.909902 5.370140 +0.908531 3.530140 +0.907165 2.768500 +0.905801 2.958040 +0.904442 3.700260 +0.903087 4.412830 +0.901736 4.587440 +0.900389 4.015880 +0.899047 2.828800 +0.897708 1.432140 +0.896372 0.179366 +0.895042 -0.759105 +0.893715 -1.358860 +0.892391 -1.706920 +0.891073 -1.909270 +0.889758 -2.027340 +0.888446 -2.097630 +0.887139 -2.144230 +0.885835 -2.175360 +0.884536 -2.197110 +0.883239 -2.213950 +0.881947 -2.226160 +0.880659 -2.235950 +0.879374 -2.244060 +0.878093 -2.249500 +0.876816 -2.254140 +0.875542 -2.256810 +0.874272 -2.256050 +0.873007 -2.252730 +0.871744 -2.245220 +0.870485 -2.230180 +0.869230 -2.205200 +0.867979 -2.163960 +0.866730 -2.090740 +0.865486 -1.960620 +0.864245 -1.728570 +0.863009 -1.312250 +0.861774 -0.578183 +0.860544 0.547392 +0.859318 2.137370 +0.858094 3.954730 +0.856874 5.452010 +0.855658 6.115170 +0.854445 5.675000 +0.853236 4.317370 +0.852030 2.483180 +0.850828 0.816455 +0.849628 -0.416977 +0.848432 -1.230220 +0.847240 -1.700220 +0.846050 -1.960710 +0.844865 -2.111440 +0.843683 -2.195130 +0.842504 -2.244400 +0.841328 -2.273520 +0.840155 -2.286730 +0.838986 -2.287700 +0.837819 -2.277180 +0.836657 -2.249780 +0.835498 -2.194560 +0.834341 -2.093750 +0.833188 -1.908350 +0.832038 -1.563170 +0.830892 -0.988176 +0.829747 -0.104998 +0.828607 1.078160 +0.827470 2.361610 +0.826335 3.412760 +0.825204 3.882800 +0.824076 3.634810 +0.822950 2.841470 +0.821829 1.902430 +0.820710 1.266960 +0.819595 1.150270 +0.818481 1.497000 +0.817372 2.036520 +0.816265 2.393340 +0.815161 2.285760 +0.814060 1.662360 +0.812963 0.708205 +0.811868 -0.269872 +0.810775 -1.074080 +0.809687 -1.641380 +0.808601 -1.989900 +0.807517 -2.180290 +0.806437 -2.293720 +0.805360 -2.358510 +0.804285 -2.398930 +0.803213 -2.426150 +0.802145 -2.442940 +0.801079 -2.455110 +0.800015 -2.465540 +0.798955 -2.470980 +0.797898 -2.475930 +0.796843 -2.479560 +0.795791 -2.479420 +0.794742 -2.479150 +0.793696 -2.477590 +0.792652 -2.473100 +0.791611 -2.463290 +0.790573 -2.444140 +0.789537 -2.409090 +0.788505 -2.345980 +0.787475 -2.233430 +0.786447 -2.034230 +0.785423 -1.720580 +0.784401 -1.287550 +0.783382 -0.782998 +0.782365 -0.334923 +0.781351 -0.095156 +0.780340 -0.152977 +0.779330 -0.473659 +0.778324 -0.951708 +0.777321 -1.438390 +0.776319 -1.824150 +0.775321 -2.082010 +0.774325 -2.236540 +0.773332 -2.317700 +0.772341 -2.354890 +0.771353 -2.368610 +0.770367 -2.364310 +0.769383 -2.344110 +0.768403 -2.305920 +0.767425 -2.240750 +0.766450 -2.128730 +0.765476 -1.935850 +0.764505 -1.588200 +0.763537 -0.974058 +0.762571 0.043572 +0.761607 1.615260 +0.760647 3.715820 +0.759687 5.998610 +0.758732 7.890970 +0.757778 8.800240 +0.756827 8.388930 +0.755877 6.922070 +0.754931 5.076470 +0.753987 3.554770 +0.753045 2.701210 +0.752105 2.431420 +0.751168 2.426570 +0.750234 2.253390 +0.749301 1.721280 +0.748371 0.878528 +0.747443 -0.070514 +0.746517 -0.871867 +0.745594 -1.471070 +0.744673 -1.853740 +0.743754 -2.068030 +0.742837 -2.189420 +0.741923 -2.260830 +0.741012 -2.298510 +0.740101 -2.321250 +0.739194 -2.334240 +0.738289 -2.337970 +0.737385 -2.337620 +0.736485 -2.331250 +0.735586 -2.319600 +0.734689 -2.302000 +0.733795 -2.273100 +0.732903 -2.226300 +0.732013 -2.164860 +0.731125 -2.056660 +0.730240 -1.852390 +0.729356 -1.473450 +0.728474 -0.789174 +0.727595 0.385379 +0.726718 2.144690 +0.725844 4.544120 +0.724970 7.184410 +0.724100 9.357850 +0.723231 10.359800 +0.722364 9.834470 +0.721499 7.949180 +0.720637 5.351160 +0.719776 2.830800 +0.718918 0.864216 +0.718062 -0.465378 +0.717208 -1.255600 +0.716355 -1.683670 +0.715505 -1.912110 +0.714657 -2.022570 +0.713810 -2.054990 +0.712966 -2.027070 +0.712124 -1.924460 +0.711284 -1.700990 +0.710445 -1.283350 +0.709609 -0.520504 +0.708775 0.792369 +0.707942 2.792180 +0.707112 5.477190 +0.706284 8.459850 +0.705457 10.930700 +0.704632 12.099000 +0.703810 11.539200 +0.702989 9.415280 +0.702170 6.435670 +0.701353 3.515200 +0.700538 1.233860 +0.699725 -0.311454 +0.698914 -1.243500 +0.698105 -1.755140 +0.697297 -2.050150 +0.696491 -2.223870 +0.695688 -2.327330 +0.694886 -2.397300 +0.694085 -2.446160 +0.693287 -2.478700 +0.692491 -2.506570 +0.691696 -2.525510 +0.690903 -2.538300 +0.690113 -2.553200 +0.689324 -2.563540 +0.688536 -2.573070 +0.687750 -2.584990 +0.686967 -2.589280 +0.686184 -2.594580 +0.685405 -2.598740 +0.684626 -2.597260 +0.683849 -2.595050 +0.683074 -2.583090 +0.682302 -2.553460 +0.681530 -2.501780 +0.680760 -2.402580 +0.679993 -2.237240 +0.679227 -2.003570 +0.678462 -1.709650 +0.677699 -1.426260 +0.676938 -1.239370 +0.676179 -1.210040 +0.675421 -1.355780 +0.674665 -1.619490 +0.673911 -1.916640 +0.673158 -2.175820 +0.672408 -2.361820 +0.671659 -2.477560 +0.670911 -2.541020 +0.670165 -2.566910 +0.669421 -2.571230 +0.668679 -2.557360 +0.667937 -2.519480 +0.667198 -2.450690 +0.666461 -2.321110 +0.665724 -2.089050 +0.664990 -1.701560 +0.664258 -1.093580 +0.663527 -0.268726 +0.662797 0.674892 +0.662069 1.570170 +0.661343 2.208460 +0.660617 2.496600 +0.659894 2.565580 +0.659173 2.672420 +0.658453 3.038140 +0.657734 3.660970 +0.657018 4.231170 +0.656303 4.398680 +0.655588 3.923050 +0.654876 2.838060 +0.654166 1.439570 +0.653456 0.102100 +0.652749 -0.941472 +0.652043 -1.644770 +0.651338 -2.062270 +0.650635 -2.299140 +0.649934 -2.429270 +0.649234 -2.503140 +0.648535 -2.540740 +0.647839 -2.557550 +0.647143 -2.566630 +0.646449 -2.560850 +0.645757 -2.547140 +0.645066 -2.518380 +0.644376 -2.462690 +0.643688 -2.370330 +0.643001 -2.197660 +0.642317 -1.888280 +0.641632 -1.351390 +0.640950 -0.438271 +0.640270 0.835431 +0.639590 2.413950 +0.638912 3.972790 +0.638236 5.020660 +0.637561 5.205300 +0.636887 4.481790 +0.636215 3.101430 +0.635545 1.477720 +0.634875 0.075114 +0.634207 -0.957736 +0.633541 -1.616330 +0.632876 -1.980570 +0.632212 -2.163100 +0.631550 -2.226880 +0.630889 -2.204630 +0.630229 -2.086250 +0.629571 -1.842620 +0.628914 -1.367690 +0.628256 -0.503795 +0.627603 0.801754 +0.626952 2.547170 +0.626302 4.452850 +0.625653 6.038610 +0.625000 6.886370 +0.624354 6.782980 +0.623709 5.855260 +0.623066 4.549840 +0.622418 3.339760 +0.621777 2.433040 +0.621138 1.771960 +0.620500 1.187340 +0.619857 0.522452 +0.619222 -0.213462 +0.618588 -0.926674 +0.617955 -1.533530 +0.617324 -1.968310 +0.616688 -2.247030 +0.616059 -2.412640 +0.615431 -2.506240 +0.614805 -2.560640 +0.614174 -2.590940 +0.613550 -2.609100 +0.612928 -2.618000 +0.612307 -2.616350 +0.611681 -2.605840 +0.611062 -2.576130 +0.610445 -2.520540 +0.609829 -2.418850 +0.609214 -2.229920 +0.608594 -1.909750 +0.607982 -1.384730 +0.607370 -0.624344 +0.606760 0.284606 +0.606146 1.231920 +0.605538 1.896150 +0.604932 2.063480 +0.604327 1.693470 +0.603717 0.915450 +0.603114 -0.051218 +0.602513 -0.928245 +0.601913 -1.573680 +0.601314 -1.986390 +0.600710 -2.199780 +0.600113 -2.277360 +0.599518 -2.248990 +0.598923 -2.117290 +0.598325 -1.819410 +0.597733 -1.266280 +0.597142 -0.332870 +0.596552 1.191810 +0.595964 3.203850 +0.595371 5.485900 +0.594785 7.601220 +0.594200 8.840130 +0.593616 9.021200 +0.593027 8.305810 +0.592446 7.219930 +0.591866 6.126080 +0.591286 5.346600 +0.590703 4.632130 +0.590126 3.772390 +0.589550 2.663790 +0.588975 1.355050 +0.588401 0.093469 +0.587823 -0.911609 +0.587252 -1.643330 +0.586682 -2.093770 +0.586113 -2.359830 +0.585539 -2.513030 +0.584972 -2.601730 +0.584406 -2.657660 +0.583841 -2.688380 +0.583272 -2.704900 +0.582710 -2.703370 +0.582148 -2.680620 +0.581588 -2.627430 +0.581029 -2.514710 +0.580465 -2.310110 +0.579908 -1.942890 +0.579352 -1.378620 +0.578797 -0.641363 +0.578237 0.213719 +0.577684 0.949522 +0.577133 1.347990 +0.576582 1.297260 +0.576027 0.795873 +0.575478 0.001515 +0.574930 -0.840506 +0.574384 -1.544660 +0.573838 -2.052530 +0.573289 -2.361690 +0.572745 -2.511360 +0.572203 -2.547500 +0.571661 -2.479130 +0.571116 -2.277790 +0.570576 -1.889320 +0.570038 -1.230470 +0.569501 -0.236128 +0.568959 1.001640 +0.568424 2.296300 +0.567889 3.326350 +0.567356 3.730250 +0.566824 3.361280 +0.566287 2.382080 +0.565757 1.090490 +0.565228 -0.221123 +0.564699 -1.245370 +0.564167 -1.937210 +0.563641 -2.374620 +0.563115 -2.608530 +0.562591 -2.745460 +0.562068 -2.820180 +0.561540 -2.865180 +0.561019 -2.893140 +0.560498 -2.907760 +0.559979 -2.918170 +0.559455 -2.919360 +0.558938 -2.915930 +0.558421 -2.909060 +0.557905 -2.890190 +0.557386 -2.863480 +0.556872 -2.811110 +0.556359 -2.720240 +0.555847 -2.555050 +0.555336 -2.275380 +0.554821 -1.837510 +0.554312 -1.191480 +0.553804 -0.415962 +0.553297 0.329634 +0.552786 0.902002 +0.552281 1.058130 +0.551776 0.760358 +0.551273 0.125162 +0.550765 -0.617280 +0.550264 -1.364610 +0.549763 -1.872020 +0.549263 -2.197320 +0.548764 -2.335700 +0.548261 -2.331220 +0.547764 -2.167020 +0.547268 -1.808530 +0.546773 -1.136310 +0.546274 -0.034987 +0.545780 1.489850 +0.545288 3.336300 +0.544796 5.199590 +0.544300 6.491620 +0.543810 6.995410 +0.543321 6.637020 +0.542833 5.632570 +0.542346 4.381170 +0.541855 3.222580 +0.541369 2.184240 +0.540885 1.284810 +0.540401 0.420936 +0.539913 -0.407044 +0.539431 -1.123040 +0.538950 -1.684100 +0.538470 -2.075350 +0.537985 -2.311720 +0.537507 -2.446560 +0.537029 -2.509600 +0.536552 -2.532080 +0.536076 -2.519180 +0.535596 -2.471910 +0.535122 -2.373990 +0.534648 -2.193760 +0.534175 -1.858790 +0.533699 -1.256640 +0.533228 -0.265271 +0.532758 1.236950 +0.532288 3.213720 +0.531820 5.331990 +0.531348 7.152100 +0.530881 8.133710 +0.530415 7.928210 +0.529949 6.620460 +0.529480 4.666260 +0.529017 2.558970 +0.528554 0.760343 +0.528092 -0.505518 +0.527626 -1.280430 +0.527166 -1.644170 +0.526706 -1.688510 +0.526248 -1.433570 +0.525790 -0.831591 +0.525328 0.193065 +0.524872 1.618970 +0.524416 3.281430 +0.523961 4.886310 +0.523503 6.018250 +0.523050 6.389920 +0.522597 6.020690 +0.522145 5.120270 +0.521690 3.994010 +0.521240 2.897000 +0.520791 1.899430 +0.520342 0.967846 +0.519895 0.099902 +0.519443 -0.717607 +0.518997 -1.404500 +0.518552 -1.924920 +0.518107 -2.286150 +0.517659 -2.506230 +0.517215 -2.640420 +0.516773 -2.720720 +0.516331 -2.768940 +0.515886 -2.805110 +0.515446 -2.827430 +0.515007 -2.846400 +0.514568 -2.859080 +0.514130 -2.869180 +0.513689 -2.879610 +0.513252 -2.882820 +0.512817 -2.888630 +0.512382 -2.893300 +0.511944 -2.891710 +0.511510 -2.895050 +0.511077 -2.887020 +0.510645 -2.875760 +0.510210 -2.862250 +0.509780 -2.835810 +0.509350 -2.804130 +0.508921 -2.753550 +0.508492 -2.672560 +0.508061 -2.542040 +0.507634 -2.305680 +0.507208 -1.905750 +0.506782 -1.262280 +0.506353 -0.349693 +0.505929 0.797531 +0.505506 2.025300 +0.505083 3.125500 +0.504661 3.969640 +0.504236 4.522060 +0.503816 4.841030 +0.503396 4.995400 +0.502977 4.912890 +0.502554 4.477400 +0.502137 3.647010 +0.501720 2.438280 +0.501303 1.093170 +0.500884 -0.148322 +0.500469 -1.135710 +0.500055 -1.801130 +0.499641 -2.216930 +0.499228 -2.442100 +0.498812 -2.557670 +0.498400 -2.606160 +0.497990 -2.593820 +0.497580 -2.527530 +0.497166 -2.369370 +0.496757 -2.059850 +0.496349 -1.535190 +0.495942 -0.707882 +0.495531 0.381660 +0.495125 1.614110 +0.494720 2.702940 +0.494315 3.335320 +0.493911 3.327810 +0.493503 2.679380 +0.493101 1.590640 +0.492698 0.389038 +0.492297 -0.689009 +0.491892 -1.462410 +0.491492 -1.930360 +0.491093 -2.120170 +0.490694 -2.067030 +0.490292 -1.774920 +0.489894 -1.229680 +0.489497 -0.442590 +0.489101 0.466116 +0.488705 1.319390 +0.488306 1.851510 +0.487912 1.912530 +0.487518 1.470310 +0.487125 0.666122 +0.486729 -0.276709 +0.486337 -1.141890 +0.485946 -1.810880 +0.485556 -2.259640 +0.485162 -2.531060 +0.484773 -2.679360 +0.484384 -2.762410 +0.483996 -2.805880 +0.483608 -2.828400 +0.483218 -2.841840 +0.482832 -2.842510 +0.482446 -2.837800 +0.482061 -2.822770 +0.481673 -2.792540 +0.481289 -2.744250 +0.480906 -2.658830 +0.480524 -2.521880 +0.480142 -2.322240 +0.479757 -2.055430 +0.479376 -1.764450 +0.478996 -1.500840 +0.478617 -1.334990 +0.478234 -1.312020 +0.477856 -1.421440 +0.477478 -1.618920 +0.477101 -1.829990 +0.476721 -1.984630 +0.476345 -2.024560 +0.475970 -1.893350 +0.475595 -1.529220 +0.475221 -0.810111 +0.474844 0.378581 +0.474471 2.102960 +0.474099 4.269870 +0.473727 6.542240 +0.473352 8.470690 +0.472982 9.602310 +0.472612 9.705800 +0.472242 8.892090 +0.471870 7.479180 +0.471502 5.826370 +0.471134 4.195180 +0.470767 2.703720 +0.470400 1.397470 +0.470031 0.262507 +0.469666 -0.645803 +0.469301 -1.326460 +0.468936 -1.785430 +0.468569 -2.060940 +0.468206 -2.216420 +0.467844 -2.282650 +0.467482 -2.282580 +0.467117 -2.215570 +0.466756 -2.041490 +0.466395 -1.717610 +0.466036 -1.180400 +0.465676 -0.389040 +0.465314 0.580476 +0.464956 1.599690 +0.464599 2.419680 +0.464242 2.809320 +0.463882 2.656020 +0.463526 2.003240 +0.463170 1.057930 +0.462816 0.076221 +0.462458 -0.739775 +0.462104 -1.269950 +0.461751 -1.478280 +0.461398 -1.353030 +0.461046 -0.893488 +0.460691 -0.072517 +0.460340 1.064860 +0.459990 2.367090 +0.459640 3.602100 +0.459287 4.513270 +0.458938 4.937040 +0.458590 4.864000 +0.458242 4.377570 +0.457891 3.644280 +0.457545 2.758090 +0.457198 1.814150 +0.456853 0.848905 +0.456507 -0.071874 +0.456159 -0.868480 +0.455815 -1.508080 +0.455472 -1.939340 +0.455128 -2.218470 +0.454783 -2.374790 +0.454441 -2.446240 +0.454099 -2.463830 +0.453758 -2.419040 +0.453417 -2.299990 +0.453074 -2.077760 +0.452735 -1.707370 +0.452395 -1.205110 +0.452057 -0.610868 +0.451716 -0.041384 +0.451378 0.338880 +0.451041 0.444697 +0.450705 0.220218 +0.450366 -0.244294 +0.450030 -0.821922 +0.449695 -1.357740 +0.449361 -1.748970 +0.449027 -1.939720 +0.448690 -1.898590 +0.448357 -1.614240 +0.448025 -1.072760 +0.447693 -0.284714 +0.447358 0.630872 +0.447027 1.533350 +0.446696 2.190630 +0.446366 2.475760 +0.446034 2.348340 +0.445704 1.885670 +0.445376 1.240450 +0.445048 0.523123 +0.444720 -0.164808 +0.444390 -0.793840 +0.444063 -1.336000 +0.443737 -1.768390 +0.443412 -2.098320 +0.443083 -2.311300 +0.442758 -2.433110 +0.442434 -2.480190 +0.442110 -2.453640 +0.441784 -2.356650 +0.441461 -2.136840 +0.441139 -1.732280 +0.440817 -1.063200 +0.440496 -0.061940 +0.440172 1.216230 +0.439851 2.578880 +0.439531 3.738230 +0.439212 4.356750 +0.438889 4.271310 +0.438571 3.498130 +0.438253 2.311580 +0.437935 0.993699 +0.437615 -0.111114 +0.437298 -0.838948 +0.436982 -1.115960 +0.436666 -0.896608 +0.436350 -0.209707 +0.436033 0.889054 +0.435718 2.252480 +0.435404 3.551350 +0.435090 4.450900 +0.434774 4.669080 +0.434462 4.148180 +0.434150 3.014020 +0.433838 1.600930 +0.433526 0.237992 +0.433213 -0.880384 +0.432902 -1.643570 +0.432592 -2.134970 +0.432283 -2.414920 +0.431971 -2.570330 +0.431662 -2.660550 +0.431354 -2.700430 +0.431046 -2.721710 +0.430736 -2.713350 +0.430429 -2.680030 +0.430122 -2.602130 +0.429816 -2.450120 +0.429511 -2.206440 +0.429203 -1.858720 +0.428898 -1.437030 +0.428594 -1.024590 +0.428290 -0.720181 +0.427984 -0.611111 +0.427681 -0.720680 +0.427378 -1.019450 +0.427076 -1.402660 +0.426771 -1.779340 +0.426470 -2.070190 +0.426169 -2.218750 +0.425869 -2.223970 +0.425569 -2.066600 +0.425267 -1.744480 +0.424967 -1.270970 +0.424669 -0.635456 +0.424370 0.124542 +0.424070 0.986634 +0.423772 1.892420 +0.423475 2.709870 +0.423179 3.288810 +0.422879 3.466730 +0.422584 3.157430 +0.422288 2.391810 +0.421993 1.364300 +0.421699 0.252772 +0.421402 -0.712392 +0.421108 -1.433140 +0.420815 -1.915970 +0.420522 -2.172480 +0.420227 -2.275380 +0.419934 -2.247970 +0.419643 -2.085410 +0.419351 -1.773260 +0.419058 -1.259480 +0.418767 -0.566734 +0.418477 0.218260 +0.418188 0.940855 +0.417898 1.414000 +0.417607 1.510180 +0.417318 1.214720 +0.417030 0.640559 +0.416743 -0.053192 +0.416452 -0.634455 +0.416166 -0.956743 +0.415879 -0.907795 +0.415593 -0.382874 +0.415307 0.609303 +0.415019 1.991590 +0.414734 3.584800 +0.414450 5.002200 +0.414166 5.933640 +0.413879 6.133630 +0.413596 5.585170 +0.413313 4.448950 +0.413031 3.067900 +0.412746 1.652830 +0.412464 0.435164 +0.412182 -0.516386 +0.411901 -1.231680 +0.411621 -1.720760 +0.411338 -2.062360 +0.411058 -2.273310 +0.410778 -2.391280 +0.410499 -2.446740 +0.410218 -2.432280 +0.409940 -2.360690 +0.409662 -2.206360 +0.409384 -1.957130 +0.409104 -1.630760 +0.408827 -1.256340 +0.408551 -0.928583 +0.408275 -0.714473 +0.407999 -0.675627 +0.407721 -0.802157 +0.407446 -1.045880 +0.407171 -1.299890 +0.406897 -1.451000 +0.406621 -1.401940 +0.406347 -1.025040 +0.406074 -0.248768 +0.405801 0.939839 +0.405526 2.485880 +0.405254 4.119860 +0.404983 5.579990 +0.404711 6.568730 +0.404440 6.918430 +0.404167 6.619930 +0.403897 5.798490 +0.403627 4.608330 +0.403358 3.272810 +0.403086 1.929500 +0.402817 0.682466 +0.402549 -0.350145 +0.402281 -1.164810 +0.402011 -1.726670 +0.401743 -2.077300 +0.401476 -2.262950 +0.401210 -2.294530 +0.400943 -2.200630 +0.400675 -1.942250 +0.400409 -1.512670 +0.400144 -0.929292 +0.399879 -0.247496 +0.399612 0.354555 +0.399348 0.761287 +0.399084 0.837322 +0.398821 0.572189 +0.398558 0.032599 +0.398293 -0.613099 +0.398030 -1.212570 +0.397768 -1.638590 +0.397506 -1.802630 +0.397243 -1.722090 +0.396982 -1.400540 +0.396721 -0.946250 +0.396461 -0.477878 +0.396198 -0.127203 +0.395938 -0.020933 +0.395679 -0.186647 +0.395420 -0.586836 +0.395161 -1.114610 +0.394901 -1.659440 +0.394643 -2.120150 +0.394385 -2.457190 +0.394128 -2.685350 +0.393868 -2.810680 +0.393612 -2.885590 +0.393355 -2.915280 +0.393100 -2.932080 +0.392841 -2.929180 +0.392586 -2.907680 +0.392331 -2.873770 +0.392077 -2.801330 +0.391822 -2.712850 +0.391566 -2.592430 +0.391312 -2.475120 +0.391059 -2.374280 +0.390806 -2.309940 +0.390551 -2.298260 +0.390299 -2.304310 +0.390047 -2.317720 +0.389795 -2.269370 +0.389541 -2.116460 +0.389290 -1.762280 +0.389040 -1.153350 +0.388789 -0.272432 +0.388539 0.855005 +0.388287 2.025000 +0.388038 3.097400 +0.387789 3.810120 +0.387540 4.077540 +0.387289 3.871280 +0.387041 3.267260 +0.386793 2.406480 +0.386546 1.437980 +0.386296 0.471584 +0.386049 -0.375531 +0.385803 -1.075440 +0.385557 -1.584010 +0.385311 -1.911940 +0.385063 -2.065230 +0.384818 -2.054810 +0.384573 -1.847330 +0.384328 -1.433180 +0.384081 -0.743040 +0.383837 0.160443 +0.383594 1.212010 +0.383350 2.201400 +0.383107 2.893200 +0.382862 3.137140 +0.382619 2.846320 +0.382377 2.145790 +0.382135 1.253730 +0.381892 0.412312 +0.381650 -0.138802 +0.381409 -0.275535 +0.381169 0.045115 +0.380926 0.749947 +0.380686 1.709110 +0.380446 2.654820 +0.380207 3.434280 +0.379968 3.819820 +0.379727 3.775410 +0.379488 3.324330 +0.379250 2.569000 +0.379012 1.662610 +0.378772 0.733320 +0.378535 -0.126438 +0.378298 -0.844998 +0.378061 -1.406570 +0.377822 -1.796430 +0.377586 -2.046830 +0.377350 -2.172000 +0.377115 -2.200680 +0.376880 -2.137790 +0.376642 -1.992230 +0.376408 -1.762750 +0.376173 -1.498730 +0.375939 -1.233760 +0.375703 -1.033730 +0.375470 -0.946908 +0.375237 -0.985990 +0.375004 -1.130390 +0.374769 -1.300550 +0.374536 -1.426130 +0.374304 -1.416620 +0.374073 -1.205230 +0.373841 -0.747801 +0.373608 -0.076518 +0.373377 0.756432 +0.373146 1.620160 +0.372916 2.368200 +0.372684 2.905990 +0.372454 3.120140 +0.372224 3.007040 +0.371995 2.578540 +0.371764 1.918160 +0.371535 1.127610 +0.371307 0.307854 +0.371079 -0.427737 +0.370851 -1.016580 +0.370622 -1.415260 +0.370394 -1.609700 +0.370167 -1.599750 +0.369941 -1.356850 +0.369712 -0.849970 +0.369486 -0.084191 +0.369260 0.891005 +0.369035 1.928660 +0.368809 2.811940 +0.368582 3.339160 +0.368358 3.364410 +0.368133 2.914590 +0.367909 2.118180 +0.367683 1.221520 +0.367459 0.480592 +0.367236 0.070824 +0.367013 0.080320 +0.366788 0.473917 +0.366565 1.097300 +0.366343 1.741140 +0.366121 2.190730 +0.365899 2.273680 +0.365676 1.960500 +0.365454 1.310950 +0.365234 0.487427 +0.365013 -0.340282 +0.364790 -1.050550 +0.364570 -1.572940 +0.364350 -1.912100 +0.364131 -2.106500 +0.363909 -2.197410 +0.363690 -2.205530 +0.363471 -2.140630 +0.363253 -1.987030 +0.363035 -1.731800 +0.362815 -1.360410 +0.362597 -0.901955 +0.362379 -0.410840 +0.362162 -0.000367 +0.361943 0.251308 +0.361726 0.284680 +0.361510 0.109164 +0.361294 -0.181939 +0.361076 -0.458273 +0.360860 -0.566522 +0.360645 -0.387050 +0.360429 0.154349 +0.360214 0.995083 +0.359998 2.026560 +0.359783 3.015110 +0.359569 3.749320 +0.359355 4.073170 +0.359140 3.908640 +0.358926 3.330900 +0.358713 2.454920 +0.358500 1.459590 +0.358286 0.486612 +0.358073 -0.371931 +0.357861 -1.061010 +0.357649 -1.565150 +0.357438 -1.899100 +0.357224 -2.096760 +0.357013 -2.175310 +0.356803 -2.162950 +0.356592 -2.052100 +0.356380 -1.876250 +0.356169 -1.650990 +0.355960 -1.445840 +0.355750 -1.289280 +0.355541 -1.238750 +0.355330 -1.294820 +0.355121 -1.425820 +0.354912 -1.578820 +0.354704 -1.674340 +0.354494 -1.664760 +0.354286 -1.481630 +0.354078 -1.135120 +0.353871 -0.624111 +0.353661 -0.051632 +0.353455 0.536468 +0.353248 1.024950 +0.353041 1.333880 +0.352835 1.421240 +0.352627 1.236720 +0.352422 0.848033 +0.352216 0.281790 +0.352011 -0.331397 +0.351804 -0.939046 +0.351599 -1.457830 +0.351395 -1.856810 +0.351191 -2.118740 +0.350985 -2.289800 +0.350781 -2.381080 +0.350577 -2.431020 +0.350374 -2.449030 +0.350171 -2.448790 +0.349966 -2.444520 +0.349763 -2.419160 +0.349561 -2.384590 +0.349359 -2.312060 +0.349155 -2.205630 +0.348953 -2.013660 +0.348752 -1.698250 +0.348551 -1.203940 +0.348348 -0.516518 +0.348147 0.363596 +0.347947 1.335730 +0.347746 2.276350 +0.347546 3.038400 +0.347345 3.476010 +0.347145 3.547080 +0.346946 3.229240 +0.346747 2.620220 +0.346546 1.815740 +0.346347 0.957054 +0.346149 0.163618 +0.345950 -0.490064 +0.345750 -0.962861 +0.345553 -1.223110 +0.345355 -1.257970 +0.345158 -1.048670 +0.344961 -0.603521 +0.344762 0.061608 +0.344565 0.853785 +0.344369 1.639850 +0.344173 2.258350 +0.343975 2.568880 +0.343779 2.518810 +0.343584 2.161540 +0.343388 1.665890 +0.343193 1.246310 +0.342997 1.109010 +0.342802 1.379990 +0.342608 2.044640 +0.342414 2.933970 +0.342218 3.806510 +0.342024 4.377010 +0.341830 4.466440 +0.341637 4.006870 +0.341442 3.104300 +0.341249 1.964670 +0.341057 0.820650 +0.340864 -0.172302 +0.340672 -0.919245 +0.340478 -1.428060 +0.340286 -1.734070 +0.340095 -1.904310 +0.339904 -1.995140 +0.339711 -2.035150 +0.339520 -2.045350 +0.339329 -2.021120 +0.339139 -1.986370 +0.338946 -1.939050 +0.338756 -1.899960 +0.338567 -1.859000 +0.338377 -1.830550 +0.338187 -1.777840 +0.337996 -1.677930 +0.337807 -1.464630 +0.337619 -1.101140 +0.337430 -0.537606 +0.337240 0.203112 +0.337052 1.085020 +0.336864 1.983440 +0.336676 2.772800 +0.336487 3.309480 +0.336300 3.504840 +0.336112 3.341440 +0.335926 2.831850 +0.335739 2.096860 +0.335551 1.240560 +0.335364 0.409713 +0.335178 -0.311638 +0.334992 -0.833173 +0.334805 -1.133660 +0.334620 -1.181340 +0.334434 -0.978296 +0.334249 -0.534621 +0.334063 0.085489 +0.333878 0.776903 +0.333694 1.382370 +0.333509 1.767800 +0.333325 1.837630 +0.333140 1.582800 +0.332956 1.080730 +0.332773 0.468127 +0.332590 -0.084811 +0.332405 -0.473991 +0.332222 -0.640277 +0.332040 -0.617750 +0.331857 -0.468851 +0.331675 -0.297231 +0.331491 -0.190269 +0.331310 -0.198557 +0.331128 -0.333111 +0.330947 -0.579871 +0.330764 -0.892361 +0.330583 -1.222460 +0.330402 -1.520190 +0.330221 -1.760000 +0.330039 -1.922480 +0.329859 -1.998250 +0.329679 -1.974530 +0.329499 -1.847610 +0.329319 -1.619420 +0.329138 -1.311060 +0.328959 -0.963634 +0.328780 -0.642515 +0.328601 -0.418867 +0.328421 -0.331877 +0.328243 -0.365678 +0.328064 -0.466802 +0.327886 -0.536899 +0.327707 -0.465413 +0.327529 -0.164357 +0.327352 0.395196 +0.327174 1.154580 +0.326997 2.006120 +0.326819 2.789420 +0.326642 3.349850 +0.326465 3.568230 +0.326289 3.396070 +0.326111 2.885620 +0.325935 2.120170 +0.325760 1.235750 +0.325584 0.357582 +0.325407 -0.426838 +0.325232 -1.025120 +0.325057 -1.440490 +0.324882 -1.634140 +0.324707 -1.661870 +0.324531 -1.517670 +0.324357 -1.279590 +0.324183 -1.001290 +0.324009 -0.765534 +0.323834 -0.624881 +0.323660 -0.608819 +0.323487 -0.714087 +0.323314 -0.887268 +0.323139 -1.037530 +0.322967 -1.100790 +0.322794 -1.024890 +0.322622 -0.829471 +0.322449 -0.564767 +0.322276 -0.326945 +0.322104 -0.207127 +0.321932 -0.256530 +0.321761 -0.468238 +0.321588 -0.805796 +0.321417 -1.195260 +0.321246 -1.570460 +0.321075 -1.871980 +0.320905 -2.086750 +0.320733 -2.212720 +0.320563 -2.255010 +0.320393 -2.214600 +0.320223 -2.103490 +0.320051 -1.944990 +0.319882 -1.736520 +0.319713 -1.492530 +0.319544 -1.262970 +0.319373 -1.098140 +0.319204 -1.019250 +0.319036 -1.013030 +0.318867 -1.037640 +0.318699 -1.024230 +0.318529 -0.908590 +0.318362 -0.637004 +0.318194 -0.216806 +0.318026 0.308162 +0.317858 0.831916 +0.317690 1.258290 +0.317523 1.489660 +0.317357 1.480320 +0.317188 1.234720 +0.317022 0.814220 +0.316856 0.296692 +0.316690 -0.228982 +0.316524 -0.686256 +0.316356 -1.010720 +0.316191 -1.166150 +0.316025 -1.113260 +0.315860 -0.846222 +0.315694 -0.349779 +0.315529 0.292168 +0.315364 1.020830 +0.315199 1.674860 +0.315033 2.149520 +0.314869 2.324310 +0.314705 2.219410 +0.314541 1.951120 +0.314378 1.710450 +0.314213 1.650730 +0.314049 1.908270 +0.313886 2.456810 +0.313723 3.205120 +0.313559 3.914160 +0.313396 4.404710 +0.313234 4.508250 +0.313071 4.194080 +0.312908 3.502270 +0.312746 2.574590 +0.312584 1.561360 +0.312422 0.612125 +0.312261 -0.176248 +0.312098 -0.781210 +0.311937 -1.194750 +0.311776 -1.438540 +0.311615 -1.541110 +0.311453 -1.539010 +0.311292 -1.475290 +0.311132 -1.384960 +0.310972 -1.305270 +0.310810 -1.253200 +0.310650 -1.231550 +0.310491 -1.227270 +0.310331 -1.207850 +0.310172 -1.128220 +0.310011 -0.936959 +0.309852 -0.612456 +0.309693 -0.164270 +0.309535 0.351013 +0.309375 0.867758 +0.309216 1.280970 +0.309058 1.519570 +0.308900 1.532090 +0.308742 1.306400 +0.308583 0.893757 +0.308426 0.363664 +0.308268 -0.193327 +0.308111 -0.697989 +0.307952 -1.090160 +0.307796 -1.332310 +0.307639 -1.417920 +0.307482 -1.350070 +0.307324 -1.190430 +0.307168 -0.976276 +0.307012 -0.785287 +0.306856 -0.638051 +0.306700 -0.574551 +0.306543 -0.561808 +0.306388 -0.556794 +0.306232 -0.465682 +0.306077 -0.245573 +0.305921 0.136170 +0.305766 0.610585 +0.305611 1.091950 +0.305457 1.425200 +0.305301 1.528420 +0.305147 1.360570 +0.304993 0.942743 +0.304839 0.363583 +0.304685 -0.265326 +0.304530 -0.843475 +0.304377 -1.305160 +0.304223 -1.632660 +0.304070 -1.834940 +0.303916 -1.928720 +0.303763 -1.931570 +0.303610 -1.872750 +0.303458 -1.776320 +0.303304 -1.657940 +0.303152 -1.544170 +0.303000 -1.456760 +0.302848 -1.398620 +0.302696 -1.343860 +0.302543 -1.255980 +0.302392 -1.082720 +0.302240 -0.796620 +0.302089 -0.376994 +0.301937 0.150104 +0.301786 0.739920 +0.301635 1.277740 +0.301485 1.685230 +0.301333 1.863030 +0.301183 1.811290 +0.301033 1.507620 +0.300883 1.033790 +0.300733 0.447449 +0.300582 -0.141520 +0.300433 -0.658952 +0.300283 -1.036180 +0.300134 -1.246930 +0.299984 -1.307230 +0.299835 -1.230390 +0.299686 -1.078980 +0.299537 -0.896737 +0.299389 -0.742503 +0.299239 -0.649337 +0.299091 -0.638359 +0.298943 -0.674424 +0.298795 -0.719851 +0.298646 -0.701020 +0.298499 -0.592329 +0.298351 -0.369715 +0.298204 -0.062452 +0.298055 0.277385 +0.297908 0.566574 +0.297762 0.753032 +0.297615 0.780844 +0.297468 0.648839 +0.297321 0.369498 +0.297174 0.001355 +0.297028 -0.393235 +0.296882 -0.751070 +0.296735 -1.030380 +0.296589 -1.186570 +0.296444 -1.213240 +0.296298 -1.137670 +0.296152 -0.996025 +0.296007 -0.807705 +0.295862 -0.623152 +0.295717 -0.482340 +0.295572 -0.398692 +0.295426 -0.329169 +0.295282 -0.242116 +0.295138 -0.055315 +0.294994 0.252086 +0.294848 0.707907 +0.294704 1.203490 +0.294561 1.707560 +0.294417 2.054710 +0.294272 2.208160 +0.294129 2.076040 +0.293986 1.749500 +0.293843 1.246340 +0.293700 0.697966 +0.293556 0.170336 +0.293413 -0.248395 +0.293271 -0.490819 +0.293129 -0.538455 +0.292985 -0.386080 +0.292843 -0.069112 +0.292701 0.368198 +0.292560 0.831510 +0.292417 1.236490 +0.292275 1.486560 +0.292134 1.555580 +0.291993 1.452060 +0.291852 1.260130 +0.291709 1.069030 +0.291569 0.956478 +0.291428 0.950570 +0.291287 1.025410 +0.291146 1.113740 +0.291005 1.134780 +0.290865 1.022540 +0.290725 0.774893 +0.290586 0.410285 +0.290444 -0.005209 +0.290305 -0.420923 +0.290165 -0.771826 +0.290026 -1.031430 +0.289886 -1.186390 +0.289747 -1.263190 +0.289608 -1.266670 +0.289469 -1.222800 +0.289329 -1.133490 +0.289191 -1.028400 +0.289052 -0.931153 +0.288914 -0.855828 +0.288776 -0.788041 +0.288637 -0.689466 +0.288499 -0.539620 +0.288361 -0.284233 +0.288223 0.087515 +0.288085 0.570364 +0.287947 1.124000 +0.287810 1.689870 +0.287673 2.172880 +0.287535 2.476510 +0.287398 2.545890 +0.287262 2.381240 +0.287125 2.005480 +0.286989 1.494910 +0.286851 0.954414 +0.286715 0.498674 +0.286579 0.192041 +0.286443 0.083057 +0.286306 0.176171 +0.286170 0.416096 +0.286035 0.745182 +0.285900 1.070530 +0.285763 1.299890 +0.285628 1.381480 +0.285493 1.309460 +0.285358 1.155620 +0.285223 0.981969 +0.285087 0.865786 +0.284953 0.865037 +0.284819 0.955754 +0.284684 1.095010 +0.284549 1.201000 +0.284415 1.209710 +0.284281 1.080090 +0.284148 0.818488 +0.284013 0.453608 +0.283879 0.046044 +0.283746 -0.361982 +0.283613 -0.716786 +0.283480 -1.001910 +0.283345 -1.215790 +0.283213 -1.361260 +0.283080 -1.452950 +0.282947 -1.503310 +0.282814 -1.518520 +0.282681 -1.489540 +0.282549 -1.404170 +0.282417 -1.234770 +0.282285 -0.958253 +0.282152 -0.549208 +0.282020 -0.012801 +0.281889 0.619442 +0.281757 1.269960 +0.281625 1.838390 +0.281493 2.212030 +0.281362 2.331190 +0.281231 2.178200 +0.281099 1.789230 +0.280968 1.241860 +0.280838 0.636035 +0.280707 0.088510 +0.280577 -0.352187 +0.280446 -0.631277 +0.280315 -0.740295 +0.280185 -0.676766 +0.280056 -0.495711 +0.279924 -0.235233 +0.279795 0.000829 +0.279665 0.152551 +0.279536 0.205051 +0.279405 0.142098 +0.279276 0.015605 +0.279147 -0.144039 +0.279018 -0.274831 +0.278890 -0.365013 +0.278760 -0.457882 +0.278631 -0.716285 +0.278503 -0.990560 +0.278374 -0.975798 +0.278245 -0.991576 +0.278117 -1.148440 +0.277989 -1.341860 +0.277861 -1.533450 +0.277732 -1.698110 +0.277604 -1.812080 +0.277477 -1.870870 +0.277349 -1.870180 +0.277222 -1.829050 +0.277094 -1.761790 +0.276967 -1.701670 +0.276840 -1.617620 +0.276713 -1.536070 +0.276585 -1.448250 +0.276459 -1.340110 +0.276332 -1.179020 +0.276206 -0.939642 +0.276078 -0.648901 +0.275952 -0.302726 +0.275826 0.037513 +0.275700 0.334390 +0.275575 0.496829 +0.275448 0.506565 +0.275322 0.378490 +0.275197 0.129260 +0.275072 -0.189910 +0.274945 -0.503486 +0.274820 -0.757848 +0.274695 -0.919310 +0.274570 -0.952724 +0.274446 -0.845720 +0.274320 -0.615887 +0.274195 -0.330519 +0.274071 -0.048172 +0.273947 0.189911 +0.273821 0.360597 +0.273697 0.468483 +0.273573 0.535321 +0.273449 0.636785 +0.273325 0.852649 +0.273201 1.185300 +0.273077 1.553760 +0.272954 1.882600 +0.272831 2.092880 +0.272707 2.122950 +0.272583 1.930220 +0.272461 1.560950 +0.272338 1.043770 +0.272214 0.480503 +0.272091 -0.068598 +0.271969 -0.544338 +0.271846 -0.889780 +0.271723 -1.111450 +0.271601 -1.214410 +0.271479 -1.258050 +0.271357 -1.250820 +0.271235 -1.216430 +0.271112 -1.186390 +0.270990 -1.159960 +0.270869 -1.121440 +0.270748 -1.039930 +0.270625 -0.902885 +0.270504 -0.679071 +0.270383 -0.370141 +0.270262 0.002275 +0.270140 0.365134 +0.270019 0.668894 +0.269899 0.851777 +0.269778 0.872481 +0.269658 0.730296 +0.269536 0.441438 +0.269416 0.084427 +0.269296 -0.291436 +0.269176 -0.597159 +0.269055 -0.814538 +0.268935 -0.945395 +0.268815 -0.977971 +0.268696 -0.968796 +0.268575 -0.904540 +0.268456 -0.845719 +0.268337 -0.771346 +0.268218 -0.678019 +0.268099 -0.543423 +0.267979 -0.353686 +0.267860 -0.148139 +0.267741 0.081377 +0.267622 0.259615 +0.267503 0.348687 +0.267384 0.310676 +0.267266 0.144393 +0.267148 -0.122534 +0.267030 -0.422795 +0.266911 -0.693419 +0.266793 -0.917406 +0.266675 -1.048430 +0.266557 -1.088900 +0.266439 -1.025640 +0.266321 -0.895473 +0.266204 -0.734740 +0.266087 -0.541843 +0.265968 -0.362913 +0.265851 -0.163725 +0.265734 -0.000693 +0.265618 0.194210 +0.265501 0.491866 +0.265383 0.864630 +0.265267 1.304330 +0.265150 1.741000 +0.265034 2.125620 +0.264917 2.363350 +0.264800 2.385520 +0.264684 2.185610 +0.264569 1.799840 +0.264452 1.310920 +0.264336 0.795538 +0.264220 0.319048 +0.264105 -0.073417 +0.263989 -0.328656 +0.263873 -0.441400 +0.263758 -0.440798 +0.263643 -0.379376 +0.263528 -0.313040 +0.263412 -0.253590 +0.263297 -0.241552 +0.263182 -0.263178 +0.263068 -0.297699 +0.262952 -0.332083 +0.262838 -0.309669 +0.262723 -0.257524 +0.262609 -0.173748 +0.262495 -0.070388 +0.262380 0.001438 +0.262266 0.027817 +0.262152 -0.012307 +0.262038 -0.121826 +0.261924 -0.272660 +0.261810 -0.434129 +0.261697 -0.581416 +0.261584 -0.674850 +0.261469 -0.720905 +0.261356 -0.731022 +0.261243 -0.707790 +0.261130 -0.649549 +0.261017 -0.576394 +0.260904 -0.511706 +0.260791 -0.419017 +0.260678 -0.274080 +0.260566 -0.099784 +0.260453 0.108958 +0.260340 0.358585 +0.260228 0.599138 +0.260116 0.813986 +0.260004 0.965053 +0.259891 1.033340 +0.259780 1.002440 +0.259668 0.883573 +0.259556 0.712921 +0.259444 0.546273 +0.259332 0.432851 +0.259221 0.415245 +0.259110 0.510877 +0.258998 0.707588 +0.258887 0.960425 +0.258776 1.219180 +0.258665 1.426050 +0.258555 1.532460 +0.258443 1.545890 +0.258332 1.503110 +0.258222 1.417160 +0.258112 1.300690 +0.258000 1.203930 +0.257890 1.099840 +0.257780 0.981915 +0.257670 0.851193 +0.257559 0.659772 +0.257450 0.448868 +0.257340 0.213842 +0.257230 -0.038387 +0.257121 -0.248771 +0.257010 -0.413391 +0.256901 -0.542009 +0.256792 -0.629132 +0.256683 -0.671440 +0.256573 -0.689430 +0.256464 -0.748888 +0.256355 -0.827158 +0.256246 -0.806314 +0.256137 -0.706977 +0.256028 -0.547498 +0.255920 -0.322463 +0.255811 -0.001369 +0.255703 0.364733 +0.255594 0.719000 +0.255486 1.003050 +0.255378 1.163900 +0.255270 1.173300 +0.255161 1.029950 +0.255053 0.796077 +0.254946 0.499562 +0.254838 0.190767 +0.254730 -0.052653 +0.254622 -0.217734 +0.254515 -0.285743 +0.254408 -0.256614 +0.254301 -0.167850 +0.254193 -0.070276 +0.254086 0.017324 +0.253979 0.110071 +0.253872 0.207980 +0.253765 0.322238 +0.253658 0.469009 +0.253552 0.628295 +0.253445 0.801130 +0.253339 0.952694 +0.253232 1.037630 +0.253126 1.004320 +0.253020 0.812639 +0.252914 0.503949 +0.252807 0.129866 +0.252701 -0.265739 +0.252595 -0.625930 +0.252490 -0.927273 +0.252383 -1.137990 +0.252278 -1.265580 +0.252173 -1.320580 +0.252067 -1.321520 +0.251962 -1.305900 +0.251856 -1.265590 +0.251751 -1.205560 +0.251647 -1.142070 +0.251542 -1.036460 +0.251436 -1.020020 diff --git a/docs/source/examples/example_data/CeO2_mean_q.chi b/docs/source/examples/example_data/CeO2_mean_q.chi new file mode 100644 index 00000000..29e330fb --- /dev/null +++ b/docs/source/examples/example_data/CeO2_mean_q.chi @@ -0,0 +1,3001 @@ +# chi_Q chi_I +5.343089004093959198e-03 3.533951950073242188e+01 +1.602926688373333686e-02 3.585629272460937500e+01 +2.671544437772708711e-02 3.611056518554687500e+01 +3.740162122897814495e-02 3.579096984863281250e+01 +4.808779718038944107e-02 3.584022903442382812e+01 +5.877397197486396163e-02 3.651346588134765625e+01 +6.946014535530466161e-02 3.680017471313476562e+01 +8.014631706461461391e-02 3.759931564331054688e+01 +9.083248684569683595e-02 3.781277465820312500e+01 +1.015186544414544700e-01 3.773645782470703125e+01 +1.122048195947906862e-01 3.811039733886718750e+01 +1.228909820486087101e-01 3.783448028564453125e+01 +1.335771415458117950e-01 3.763015747070312500e+01 +1.442632978293033053e-01 3.769110488891601562e+01 +1.549494506419867301e-01 3.813339996337890625e+01 +1.656355997267655866e-01 3.870289230346679688e+01 +1.763217448265434195e-01 4.036896514892578125e+01 +1.870078856842239956e-01 4.107411956787109375e+01 +1.976940220427111095e-01 4.212387084960937500e+01 +2.083801536449086389e-01 4.362857055664062500e+01 +2.190662802337206561e-01 4.639533233642578125e+01 +2.297524015520513163e-01 4.990507507324218750e+01 +2.404385173428048861e-01 5.699381637573242188e+01 +2.511246273488857983e-01 6.507775878906250000e+01 +2.618107313131984859e-01 7.904644012451171875e+01 +2.724968289786478537e-01 1.002316131591796875e+02 +2.831829200881385566e-01 1.291399841308593750e+02 +2.938690043845757494e-01 1.560103759765625000e+02 +3.045550816108644754e-01 2.098122558593750000e+02 +3.152411515099101669e-01 2.517750549316406250e+02 +3.259272138246183670e-01 3.066466369628906250e+02 +3.366132682978946189e-01 3.714101562500000000e+02 +3.472993146726448543e-01 4.163765258789062500e+02 +3.579853526917753381e-01 4.569295959472656250e+02 +3.686713820981921685e-01 5.115658569335937500e+02 +3.793574026348018324e-01 5.416533813476562500e+02 +3.900434140445111497e-01 5.803795166015625000e+02 +4.007294160702269403e-01 6.207738647460937500e+02 +4.114154084548564128e-01 6.476390380859375000e+02 +4.221013909413068865e-01 6.818058471679687500e+02 +4.327873632724860697e-01 7.151784057617187500e+02 +4.434733251913017815e-01 7.240357055664062500e+02 +4.541592764406621185e-01 7.490963745117187500e+02 +4.648452167634754550e-01 7.661317749023437500e+02 +4.755311459026504428e-01 7.677396240234375000e+02 +4.862170636010959557e-01 7.839503173828125000e+02 +4.969029696017212561e-01 7.960452270507812500e+02 +5.075888636474356064e-01 7.922737426757812500e+02 +5.182747454811489352e-01 8.061403808593750000e+02 +5.289606148457712820e-01 8.148671875000000000e+02 +5.396464714842129640e-01 8.096653442382812500e+02 +5.503323151393845203e-01 8.216730957031250000e+02 +5.610181455541970452e-01 8.275275878906250000e+02 +5.717039624715616331e-01 8.222787475585937500e+02 +5.823897656343903773e-01 8.312442016601562500e+02 +5.930755547855944831e-01 8.332574462890625000e+02 +6.037613296680867103e-01 8.294469604492187500e+02 +6.144470900247794853e-01 8.359780883789062500e+02 +6.251328355985859009e-01 8.359211425781250000e+02 +6.358185661324191607e-01 8.319819335937500000e+02 +6.465042813691931345e-01 8.373393554687500000e+02 +6.571899810518215812e-01 8.358358764648437500e+02 +6.678756649232191478e-01 8.327212524414062500e+02 +6.785613327263005923e-01 8.370446777343750000e+02 +6.892469842039808947e-01 8.334761352539062500e+02 +6.999326190991759233e-01 8.317766113281250000e+02 +7.106182371548015464e-01 8.352678833007812500e+02 +7.213038381137744093e-01 8.300540771484375000e+02 +7.319894217190106023e-01 8.293782348632812500e+02 +7.426749877134279920e-01 8.322225341796875000e+02 +7.533605358399440011e-01 8.265166015625000000e+02 +7.640460658414769402e-01 8.266762695312500000e+02 +7.747315774609447869e-01 8.292753295898437500e+02 +7.854170704412669624e-01 8.224727172851562500e+02 +7.961025445253626653e-01 8.236756591796875000e+02 +8.067879994561518719e-01 8.256440429687500000e+02 +8.174734349765547803e-01 8.183037109375000000e+02 +8.281588508294920326e-01 8.201004638671875000e+02 +8.388442467578850481e-01 8.213957519531250000e+02 +8.495296225046558014e-01 8.133092651367187500e+02 +8.602149778127260449e-01 8.153364257812500000e+02 +8.709003124250187522e-01 8.155524902343750000e+02 +8.815856260844568970e-01 8.079816284179687500e+02 +8.922709185339644522e-01 8.104735717773437500e+02 +9.029561895164656127e-01 8.104391479492187500e+02 +9.136414387748850174e-01 8.043567504882812500e+02 +9.243266660521479716e-01 8.066055908203125000e+02 +9.350118710911802244e-01 8.063153076171875000e+02 +9.456970536349083023e-01 8.013012084960937500e+02 +9.563822134262587316e-01 8.036494140625000000e+02 +9.670673502081590378e-01 8.023903808593750000e+02 +9.777524637235373017e-01 7.982675170898437500e+02 +9.884375537153220481e-01 8.003577880859375000e+02 +9.991226199264423569e-01 7.986368408203125000e+02 +1.009807662099827752e+00 7.954224853515625000e+02 +1.020492679978408423e+00 7.975910644531250000e+02 +1.031177673305115450e+00 7.954527587890625000e+02 +1.041862641822880020e+00 7.933936157226562500e+02 +1.052547585274634434e+00 7.958038940429687500e+02 +1.063232503403310991e+00 7.935961303710937500e+02 +1.073917395951843101e+00 7.923787841796875000e+02 +1.084602262663164840e+00 7.948920288085937500e+02 +1.095287103280210061e+00 7.917245483398437500e+02 +1.105971917545914174e+00 7.915021362304687500e+02 +1.116656705203213029e+00 7.940468750000000000e+02 +1.127341465995042924e+00 7.903366088867187500e+02 +1.138026199664340821e+00 7.908435668945312500e+02 +1.148710905954044570e+00 7.935567626953125000e+02 +1.159395584607092022e+00 7.891414794921875000e+02 +1.170080235366422583e+00 7.907683105468750000e+02 +1.180764857974975657e+00 7.931762084960937500e+02 +1.191449452175691981e+00 7.883082885742187500e+02 +1.202134017711511849e+00 7.908236694335937500e+02 +1.212818554325377773e+00 7.932772827148437500e+02 +1.223503061760232047e+00 7.883348388671875000e+02 +1.234187539759016738e+00 7.916812744140625000e+02 +1.244871988064677470e+00 7.934884033203125000e+02 +1.255556406420156978e+00 7.894774780273437500e+02 +1.266240794568401773e+00 7.928322753906250000e+02 +1.276925152252357032e+00 7.940992431640625000e+02 +1.287609479214969710e+00 7.908642578125000000e+02 +1.298293775199187650e+00 7.944114990234375000e+02 +1.308978039947958694e+00 7.947002563476562500e+02 +1.319662273204231795e+00 7.920859985351562500e+02 +1.330346474710956794e+00 7.950556030273437500e+02 +1.341030644211084200e+00 7.937648925781250000e+02 +1.351714781447564517e+00 7.909371948242187500e+02 +1.362398886163350920e+00 7.925008544921875000e+02 +1.373082958101395024e+00 7.895103759765625000e+02 +1.383766997004651556e+00 7.864347534179687500e+02 +1.394451002616073909e+00 7.874502563476562500e+02 +1.405134974678617921e+00 7.833190917968750000e+02 +1.415818912935239648e+00 7.810859375000000000e+02 +1.426502817128895373e+00 7.815460815429687500e+02 +1.437186687002542929e+00 7.771553955078125000e+02 +1.447870522299140816e+00 7.757736206054687500e+02 +1.458554322761648425e+00 7.764435424804687500e+02 +1.469238088133025588e+00 7.715352172851562500e+02 +1.479921818156233249e+00 7.710051879882812500e+02 +1.490605512574233682e+00 7.716523437500000000e+02 +1.501289171129989164e+00 7.666708374023437500e+02 +1.511972793566463524e+00 7.672438354492187500e+02 +1.522656379626620815e+00 7.676915283203125000e+02 +1.533339929053426198e+00 7.624107055664062500e+02 +1.544023441589845946e+00 7.637574462890625000e+02 +1.554706916978847664e+00 7.639237670898437500e+02 +1.565390354963398734e+00 7.592135620117187500e+02 +1.576073755286467870e+00 7.614262695312500000e+02 +1.586757117691025343e+00 7.616248779296875000e+02 +1.597440441920041421e+00 7.579816284179687500e+02 +1.608123727716487927e+00 7.604234619140625000e+02 +1.618806974823337130e+00 7.603114624023437500e+02 +1.629490182983562407e+00 7.578605957031250000e+02 +1.640173351940138691e+00 7.607775268554687500e+02 +1.650856481436040246e+00 7.603922119140625000e+02 +1.661539571214244448e+00 7.589763793945312500e+02 +1.672222621017727340e+00 7.619127807617187500e+02 +1.682905630589468737e+00 7.613222656250000000e+02 +1.693588599672446016e+00 7.608853759765625000e+02 +1.704271528009640546e+00 7.637795410156250000e+02 +1.714954415344032590e+00 7.627802124023437500e+02 +1.725637261418604851e+00 7.630223999023437500e+02 +1.736320065976340032e+00 7.661149291992187500e+02 +1.747002828760222393e+00 7.651450805664062500e+02 +1.757685549513237078e+00 7.670209960937500000e+02 +1.768368227978369900e+00 7.707496337890625000e+02 +1.779050863898608448e+00 7.708610229492187500e+02 +1.789733457016940532e+00 7.747752685546875000e+02 +1.800416007076355074e+00 7.806038208007812500e+02 +1.811098513819842548e+00 7.827476196289062500e+02 +1.821780976990394318e+00 7.910405273437500000e+02 +1.832463396331001970e+00 8.009752197265625000e+02 +1.843145771584659975e+00 8.090477905273437500e+02 +1.853828102494362140e+00 8.260147094726562500e+02 +1.864510388803103602e+00 8.487339477539062500e+02 +1.875192630253881720e+00 8.721124267578125000e+02 +1.885874826589693631e+00 9.210285034179687500e+02 +1.896556977553537804e+00 9.886934204101562500e+02 +1.907239082888414483e+00 1.089866821289062500e+03 +1.917921142337324358e+00 1.274013549804687500e+03 +1.928603155643269451e+00 1.597372314453125000e+03 +1.939285122549253337e+00 2.112072021484375000e+03 +1.949967042798279149e+00 3.247780761718750000e+03 +1.960648916133353126e+00 5.071626464843750000e+03 +1.971330742297481731e+00 8.052325683593750000e+03 +1.982012521033672092e+00 1.265926464843750000e+04 +1.992694252084933115e+00 1.765331445312500000e+04 +2.003375935194275037e+00 2.099921289062500000e+04 +2.014057570104708983e+00 2.176243554687500000e+04 +2.024739156559246300e+00 1.933409375000000000e+04 +2.035420694300901445e+00 1.498183398437500000e+04 +2.046102183072687986e+00 1.006079785156250000e+04 +2.056783622617622598e+00 6.320898925781250000e+03 +2.067465012678721958e+00 3.946736816406250000e+03 +2.078146352999004076e+00 2.533163085937500000e+03 +2.088827643321487404e+00 1.776117919921875000e+03 +2.099508883389193503e+00 1.392275634765625000e+03 +2.110190072945143491e+00 1.142712646484375000e+03 +2.120871211732361150e+00 1.007680053710937500e+03 +2.131552299493869818e+00 9.251745605468750000e+02 +2.142233335972695052e+00 8.740640869140625000e+02 +2.152914320911864188e+00 8.385794067382812500e+02 +2.163595254054404116e+00 8.200810546875000000e+02 +2.174276135143343502e+00 8.076011352539062500e+02 +2.184956963921714568e+00 8.000727539062500000e+02 +2.195637740132547311e+00 8.041757202148437500e+02 +2.206318463518874839e+00 8.147222900390625000e+02 +2.216999133823731594e+00 8.339907836914062500e+02 +2.227679750790153346e+00 8.796899414062500000e+02 +2.238360314161175868e+00 9.606395263671875000e+02 +2.249040823679838486e+00 1.089637573242187500e+03 +2.259721279089179635e+00 1.379220092773437500e+03 +2.270401680132239086e+00 1.858270019531250000e+03 +2.281082026552060604e+00 2.670435546875000000e+03 +2.291762318091686179e+00 3.884877685546875000e+03 +2.302442554494160909e+00 5.333493164062500000e+03 +2.313122735502530336e+00 6.402265136718750000e+03 +2.323802860859840891e+00 6.774020019531250000e+03 +2.334482930309142557e+00 6.233157226562500000e+03 +2.345162943593484428e+00 5.034031738281250000e+03 +2.355842900455918265e+00 3.590260253906250000e+03 +2.366522800639495383e+00 2.433910888671875000e+03 +2.377202643887271538e+00 1.724281616210937500e+03 +2.387882429942300710e+00 1.268792602539062500e+03 +2.398562158547639989e+00 1.025280395507812500e+03 +2.409241829446347349e+00 8.989053344726562500e+02 +2.419921442381482546e+00 8.213082275390625000e+02 +2.430600997096106219e+00 7.753579101562500000e+02 +2.441280493333280788e+00 7.489624633789062500e+02 +2.451959930836070001e+00 7.311651611328125000e+02 +2.462639309347538497e+00 7.174228515625000000e+02 +2.473318628610753578e+00 7.104360961914062500e+02 +2.483997888368782103e+00 7.040582885742187500e+02 +2.494677088364694040e+00 6.973093261718750000e+02 +2.505356228341561131e+00 6.948115234375000000e+02 +2.516035308042452900e+00 6.918329467773437500e+02 +2.526714327210445532e+00 6.873784179687500000e+02 +2.537393285588613434e+00 6.868483886718750000e+02 +2.548072182920033235e+00 6.847408447265625000e+02 +2.558751018947782896e+00 6.817982177734375000e+02 +2.569429793414942154e+00 6.817931518554687500e+02 +2.580108506064591634e+00 6.801025390625000000e+02 +2.590787156639814182e+00 6.780816040039062500e+02 +2.601465744883694420e+00 6.783452758789062500e+02 +2.612144270539316082e+00 6.766311035156250000e+02 +2.622822733349768676e+00 6.754150390625000000e+02 +2.633501133058138599e+00 6.755415649414062500e+02 +2.644179469407516248e+00 6.738220825195312500e+02 +2.654857742140994681e+00 6.731515502929687500e+02 +2.665535951001665627e+00 6.733184814453125000e+02 +2.676214095732624365e+00 6.713435668945312500e+02 +2.686892176076966177e+00 6.712967529296875000e+02 +2.697570191777789450e+00 6.711826782226562500e+02 +2.708248142578193907e+00 6.692199096679687500e+02 +2.718926028221279712e+00 6.695454711914062500e+02 +2.729603848450149250e+00 6.693747558593750000e+02 +2.740281603007907574e+00 6.675477294921875000e+02 +2.750959291637658399e+00 6.682468872070312500e+02 +2.761636914082509886e+00 6.680734252929687500e+02 +2.772314470085571525e+00 6.663165893554687500e+02 +2.782991959389953252e+00 6.672682495117187500e+02 +2.793669381738765889e+00 6.671719360351562500e+02 +2.804346736875123813e+00 6.656593627929687500e+02 +2.815024024542143177e+00 6.670789184570312500e+02 +2.825701244482939689e+00 6.671594238281250000e+02 +2.836378396440631278e+00 6.659552001953125000e+02 +2.847055480158338980e+00 6.674028930664062500e+02 +2.857732495379184723e+00 6.672885131835937500e+02 +2.868409441846290875e+00 6.668595581054687500e+02 +2.879086319302782915e+00 6.686034545898437500e+02 +2.889763127491787653e+00 6.688637084960937500e+02 +2.900439866156434121e+00 6.692627563476562500e+02 +2.911116535039850017e+00 6.709934082031250000e+02 +2.921793133885169258e+00 6.711219482421875000e+02 +2.932469662435524871e+00 6.720301513671875000e+02 +2.943146120434051660e+00 6.738054199218750000e+02 +2.953822507623885318e+00 6.739323730468750000e+02 +2.964498823748165979e+00 6.752171020507812500e+02 +2.975175068550033330e+00 6.766104125976562500e+02 +2.985851241772628839e+00 6.766295776367187500e+02 +2.996527343159096191e+00 6.784760742187500000e+02 +3.007203372452581291e+00 6.802971191406250000e+02 +3.017879329396231380e+00 6.804688720703125000e+02 +3.028555213733193696e+00 6.831763305664062500e+02 +3.039231025206619918e+00 6.854785156250000000e+02 +3.049906763559663059e+00 6.863349609375000000e+02 +3.060582428535476129e+00 6.899279174804687500e+02 +3.071258019877214807e+00 6.930741577148437500e+02 +3.081933537328037431e+00 6.949030761718750000e+02 +3.092608980631103233e+00 7.004067993164062500e+02 +3.103284349529574104e+00 7.059667358398437500e+02 +3.113959643766610608e+00 7.113367309570312500e+02 +3.124634863085379965e+00 7.218494873046875000e+02 +3.135310007229048068e+00 7.345906372070312500e+02 +3.145985075940783027e+00 7.502580566406250000e+02 +3.156660068963755172e+00 7.784248657226562500e+02 +3.167334986041136613e+00 8.166553344726562500e+02 +3.178009826916100344e+00 8.776434326171875000e+02 +3.188684591331823359e+00 9.821945800781250000e+02 +3.199359279031483094e+00 1.157652221679687500e+03 +3.210033889758257875e+00 1.460894653320312500e+03 +3.220708423255329578e+00 2.066882080078125000e+03 +3.231382879265880526e+00 3.062988281250000000e+03 +3.242057257533096593e+00 4.821234375000000000e+03 +3.252731557800164097e+00 7.448486328125000000e+03 +3.263405779810272467e+00 1.045888378906250000e+04 +3.274079923306611128e+00 1.291577148437500000e+04 +3.284753988032373062e+00 1.393179785156250000e+04 +3.295427973730752580e+00 1.294199121093750000e+04 +3.306101880144946215e+00 1.031859179687500000e+04 +3.316775707018151387e+00 7.246994140625000000e+03 +3.327449454093569514e+00 4.726026367187500000e+03 +3.338123121114401126e+00 3.020931884765625000e+03 +3.348796707823850305e+00 1.983898315429687500e+03 +3.359470213965123797e+00 1.448760131835937500e+03 +3.370143639281428793e+00 1.148870117187500000e+03 +3.380816983515976037e+00 9.732468261718750000e+02 +3.391490246411975384e+00 8.756717529296875000e+02 +3.402163427712641131e+00 8.162207031250000000e+02 +3.412836527161189348e+00 7.778659057617187500e+02 +3.423509544500837887e+00 7.530654296875000000e+02 +3.434182479474805483e+00 7.370991210937500000e+02 +3.444855331826313982e+00 7.246820678710937500e+02 +3.455528101298587451e+00 7.163761596679687500e+02 +3.466200787634849512e+00 7.105205688476562500e+02 +3.476873390578330003e+00 7.051766357421875000e+02 +3.487545909872256988e+00 7.019453735351562500e+02 +3.498218345259863415e+00 6.997832031250000000e+02 +3.508890696484380456e+00 6.971002807617187500e+02 +3.519562963289045943e+00 6.962108764648437500e+02 +3.530235145417096820e+00 6.954377441406250000e+02 +3.540907242611772254e+00 6.938466186523437500e+02 +3.551579254616314074e+00 6.941666870117187500e+02 +3.562251181173966330e+00 6.942121582031250000e+02 +3.572923022027975293e+00 6.932670898437500000e+02 +3.583594776921586789e+00 6.943505859375000000e+02 +3.594266445598052861e+00 6.948228759765625000e+02 +3.604938027800624223e+00 6.941513061523437500e+02 +3.615609523272556469e+00 6.957863159179687500e+02 +3.626280931757103865e+00 6.969299316406250000e+02 +3.636952252997526003e+00 6.973308105468750000e+02 +3.647623486737082477e+00 7.005484008789062500e+02 +3.658294632719036432e+00 7.034169311523437500e+02 +3.668965690686651460e+00 7.063652343750000000e+02 +3.679636660383195146e+00 7.126078491210937500e+02 +3.690307541551935966e+00 7.194368896484375000e+02 +3.700978333936145503e+00 7.287960205078125000e+02 +3.711649037279095342e+00 7.435733642578125000e+02 +3.722319651324061951e+00 7.636727905273437500e+02 +3.732990175814322242e+00 7.937940673828125000e+02 +3.743660610493156238e+00 8.452849731445312500e+02 +3.754330955103844847e+00 9.227620849609375000e+02 +3.765001209389672088e+00 1.061691040039062500e+03 +3.775671373093924643e+00 1.314595703125000000e+03 +3.786341445959889640e+00 1.757694213867187500e+03 +3.797011427730858202e+00 2.574094238281250000e+03 +3.807681318150122785e+00 4.017432128906250000e+03 +3.818351116960978953e+00 6.023881347656250000e+03 +3.829020823906721382e+00 8.577230468750000000e+03 +3.839690438730651856e+00 1.083893750000000000e+04 +3.850359961176069934e+00 1.184232226562500000e+04 +3.861029390986280507e+00 1.119395996093750000e+04 +3.871698727904588910e+00 9.078638671875000000e+03 +3.882367971674303142e+00 6.630632324218750000e+03 +3.893037122038733422e+00 4.399854980468750000e+03 +3.903706178741192634e+00 2.819866210937500000e+03 +3.914375141524995882e+00 1.919382324218750000e+03 +3.925044010133460048e+00 1.423726684570312500e+03 +3.935712784309904233e+00 1.165497436523437500e+03 +3.946381463797649758e+00 1.057260131835937500e+03 +3.957050048340021053e+00 1.052460205078125000e+03 +3.967718537680343882e+00 1.147743530273437500e+03 +3.978386931561947559e+00 1.370183227539062500e+03 +3.989055229728161844e+00 1.739488769531250000e+03 +3.999723431922320493e+00 2.195901855468750000e+03 +4.010391537887759483e+00 2.597444335937500000e+03 +4.021059547367814346e+00 2.798768798828125000e+03 +4.031727460105828165e+00 2.689036132812500000e+03 +4.042395275845140468e+00 2.313292236328125000e+03 +4.053062994329097890e+00 1.826465209960937500e+03 +4.063730615301046178e+00 1.398169311523437500e+03 +4.074398138504335520e+00 1.096313720703125000e+03 +4.085065563682316991e+00 9.104357299804687500e+02 +4.095732890578346108e+00 8.073331298828125000e+02 +4.106400118935776611e+00 7.513488159179687500e+02 +4.117067248497971121e+00 7.178878173828125000e+02 +4.127734279008286933e+00 6.982321777343750000e+02 +4.138401210210090220e+00 6.867126464843750000e+02 +4.149068041846746269e+00 6.784762573242187500e+02 +4.159734773661622143e+00 6.721734619140625000e+02 +4.170401405398091121e+00 6.685241699218750000e+02 +4.181067936799523821e+00 6.649102172851562500e+02 +4.191734367609297962e+00 6.620700683593750000e+02 +4.202400697570790378e+00 6.604096069335937500e+02 +4.213066926427382342e+00 6.581131591796875000e+02 +4.223733053922455127e+00 6.565463256835937500e+02 +4.234399079799395338e+00 6.553806152343750000e+02 +4.245065003801590464e+00 6.535380249023437500e+02 +4.255730825672430662e+00 6.526988525390625000e+02 +4.266396545155308750e+00 6.517832641601562500e+02 +4.277062161993619327e+00 6.503088989257812500e+02 +4.287727675930761428e+00 6.500792846679687500e+02 +4.298393086710133204e+00 6.495330810546875000e+02 +4.309058394075138132e+00 6.482205200195312500e+02 +4.319723597769181467e+00 6.483043212890625000e+02 +4.330388697535670239e+00 6.478114624023437500e+02 +4.341053693118014145e+00 6.466324462890625000e+02 +4.351718584259626432e+00 6.467595825195312500e+02 +4.362383370703923902e+00 6.463022460937500000e+02 +4.373048052194319801e+00 6.452699584960937500e+02 +4.383712628474238038e+00 6.456644897460937500e+02 +4.394377099287099853e+00 6.453466796875000000e+02 +4.405041464376330040e+00 6.447771606445312500e+02 +4.415705723485356948e+00 6.453111572265625000e+02 +4.426369876357609812e+00 6.451341552734375000e+02 +4.437033922736523195e+00 6.453332519531250000e+02 +4.447697862365529886e+00 6.462383422851562500e+02 +4.458361694988071555e+00 6.464781494140625000e+02 +4.469025420347585431e+00 6.477945556640625000e+02 +4.479689038187515848e+00 6.493012084960937500e+02 +4.490352548251308917e+00 6.504808959960937500e+02 +4.501015950282412525e+00 6.534041748046875000e+02 +4.511679244024275448e+00 6.571067504882812500e+02 +4.522342429220354454e+00 6.618771362304687500e+02 +4.533005505614104536e+00 6.707628784179687500e+02 +4.543668472948984238e+00 6.845731811523437500e+02 +4.554331330968454772e+00 7.058615112304687500e+02 +4.564994079415980899e+00 7.461163940429687500e+02 +4.575656718035028270e+00 8.178013305664062500e+02 +4.586319246569065200e+00 9.445160522460937500e+02 +4.596981664761566222e+00 1.167491333007812500e+03 +4.607643972356003204e+00 1.522064819335937500e+03 +4.618306169095856006e+00 1.969571655273437500e+03 +4.628968254724601827e+00 2.438584716796875000e+03 +4.639630228985725857e+00 2.747313964843750000e+03 +4.650292091622709734e+00 2.747208007812500000e+03 +4.660953842379045753e+00 2.451345947265625000e+03 +4.671615480998220882e+00 1.985130737304687500e+03 +4.682277007223730969e+00 1.531981689453125000e+03 +4.692938420799070087e+00 1.176854003906250000e+03 +4.703599721467737638e+00 9.504230346679687500e+02 +4.714260908973236575e+00 8.230952758789062500e+02 +4.724921983059068964e+00 7.529544067382812500e+02 +4.735582943468741313e+00 7.143572998046875000e+02 +4.746243789945766345e+00 6.934056396484375000e+02 +4.756904522233654120e+00 6.807571411132812500e+02 +4.767565140075919139e+00 6.727809448242187500e+02 +4.778225643216081231e+00 6.689298706054687500e+02 +4.788886031397658449e+00 6.662372436523437500e+02 +4.799546304364177729e+00 6.643004150390625000e+02 +4.810206461859161564e+00 6.643281860351562500e+02 +4.820866503626141331e+00 6.643848266601562500e+02 +4.831526429408648404e+00 6.644357910156250000e+02 +4.842186238950215049e+00 6.659909667968750000e+02 +4.852845931994381523e+00 6.672853393554687500e+02 +4.863505508284688972e+00 6.688287963867187500e+02 +4.874164967564673212e+00 6.714851684570312500e+02 +4.884824309577888712e+00 6.739413452148437500e+02 +4.895483534067879283e+00 6.777067260742187500e+02 +4.906142640778197617e+00 6.826550292968750000e+02 +4.916801629452397293e+00 6.884257202148437500e+02 +4.927460499834035446e+00 6.974104003906250000e+02 +4.938119251666672760e+00 7.100382080078125000e+02 +4.948777884693871698e+00 7.279213256835937500e+02 +4.959436398659196499e+00 7.578704833984375000e+02 +4.970094793306215841e+00 8.077319946289062500e+02 +4.980753068378505510e+00 8.910679321289062500e+02 +4.991411223619633297e+00 1.042447265625000000e+03 +5.002069258773180316e+00 1.325277587890625000e+03 +5.012727173582724127e+00 1.795562622070312500e+03 +5.023384967791850286e+00 2.554148925781250000e+03 +5.034042641144143460e+00 3.586247314453125000e+03 +5.044700193383190978e+00 4.618225585937500000e+03 +5.055357624252586390e+00 5.353215332031250000e+03 +5.066014933495925021e+00 5.464512207031250000e+03 +5.076672120856799530e+00 4.878016113281250000e+03 +5.087329186078815901e+00 3.879750000000000000e+03 +5.097986128905573011e+00 2.837323974609375000e+03 +5.108642949080681284e+00 2.059670410156250000e+03 +5.119299646347748478e+00 1.602059204101562500e+03 +5.129956220450385018e+00 1.435255004882812500e+03 +5.140612671132207545e+00 1.527768066406250000e+03 +5.151268998136834476e+00 1.873064819335937500e+03 +5.161925201207885117e+00 2.415413818359375000e+03 +5.172581280088985878e+00 3.078907714843750000e+03 +5.183237234523763171e+00 3.636994140625000000e+03 +5.193893064255843406e+00 3.818624267578125000e+03 +5.204548769028866317e+00 3.560608154296875000e+03 +5.215204348586462757e+00 2.943248535156250000e+03 +5.225859802672274235e+00 2.250387207031250000e+03 +5.236515131029942260e+00 1.661774169921875000e+03 +5.247170333403111897e+00 1.243349975585937500e+03 +5.257825409535429984e+00 1.002456909179687500e+03 +5.268480359170552241e+00 8.686416625976562500e+02 +5.279135182052125508e+00 7.924193725585937500e+02 +5.289789877923813499e+00 7.505543823242187500e+02 +5.300444446529275488e+00 7.257702026367187500e+02 +5.311098887612170749e+00 7.097236328125000000e+02 +5.321753200916169213e+00 7.001299438476562500e+02 +5.332407386184940812e+00 6.936835327148437500e+02 +5.343061443162157254e+00 6.887816772460937500e+02 +5.353715371591493799e+00 6.860388793945312500e+02 +5.364369171216630150e+00 6.836042480468750000e+02 +5.375022841781246008e+00 6.811933593750000000e+02 +5.385676383029030845e+00 6.801546630859375000e+02 +5.396329794703667915e+00 6.789706420898437500e+02 +5.406983076548852019e+00 6.775343627929687500e+02 +5.417636228308274404e+00 6.773240966796875000e+02 +5.428289249725635202e+00 6.767597045898437500e+02 +5.438942140544633652e+00 6.760964965820312500e+02 +5.449594900508974327e+00 6.765576782226562500e+02 +5.460247529362363572e+00 6.766649780273437500e+02 +5.470900026848512177e+00 6.769923095703125000e+02 +5.481552392711131816e+00 6.780775756835937500e+02 +5.492204626693940384e+00 6.788614501953125000e+02 +5.502856728540657549e+00 6.805922241210937500e+02 +5.513508697995003871e+00 6.826817626953125000e+02 +5.524160534800707900e+00 6.849552001953125000e+02 +5.534812238701497300e+00 6.890444335937500000e+02 +5.545463809441105951e+00 6.943037109375000000e+02 +5.556115246763266846e+00 7.013156738281250000e+02 +5.566766550411720971e+00 7.126730346679687500e+02 +5.577417720130208423e+00 7.303010864257812500e+02 +5.588068755662476406e+00 7.575957031250000000e+02 +5.598719656752271234e+00 8.053261718750000000e+02 +5.609370423143347217e+00 8.899309082031250000e+02 +5.620021054579456887e+00 1.040861694335937500e+03 +5.630671550804358105e+00 1.316172363281250000e+03 +5.641321911561814062e+00 1.788488403320312500e+03 +5.651972136595588836e+00 2.511168945312500000e+03 +5.662622225649450947e+00 3.439645751953125000e+03 +5.673272178467168914e+00 4.367238769531250000e+03 +5.683921994792519250e+00 4.940112304687500000e+03 +5.694571674369280245e+00 4.922060546875000000e+03 +5.705221216941231077e+00 4.314236816406250000e+03 +5.715870622252158029e+00 3.394366210937500000e+03 +5.726519890045847383e+00 2.452543701171875000e+03 +5.737169020066088976e+00 1.738268798828125000e+03 +5.747818012056680637e+00 1.285203613281250000e+03 +5.758466865761416642e+00 1.021999877929687500e+03 +5.769115580924100151e+00 8.796880493164062500e+02 +5.779764157288533433e+00 8.020812988281250000e+02 +5.790412594598525864e+00 7.566673583984375000e+02 +5.801060892597885932e+00 7.299285888671875000e+02 +5.811709051030431006e+00 7.145324707031250000e+02 +5.822357069639976679e+00 7.043264770507812500e+02 +5.833004948170342985e+00 6.978503417968750000e+02 +5.843652686365357951e+00 6.941828613281250000e+02 +5.854300283968845164e+00 6.915758056640625000e+02 +5.864947740724638869e+00 6.908864746093750000e+02 +5.875595056376570646e+00 6.912194824218750000e+02 +5.886242230668481845e+00 6.927738647460937500e+02 +5.896889263344211152e+00 6.968969726562500000e+02 +5.907536154147603469e+00 7.035602416992187500e+02 +5.918182902822508140e+00 7.141380004882812500e+02 +5.928829509112776286e+00 7.325109863281250000e+02 +5.939475972762263467e+00 7.641732177734375000e+02 +5.950122293514825245e+00 8.188717041015625000e+02 +5.960768471114326061e+00 9.175960693359375000e+02 +5.971414505304631248e+00 1.101096801757812500e+03 +5.982060395829607913e+00 1.421695190429687500e+03 +5.992706142433131156e+00 1.932250366210937500e+03 +6.003351744859072525e+00 2.651426513671875000e+03 +6.013997202851315116e+00 3.441502197265625000e+03 +6.024642516153739358e+00 4.055655761718750000e+03 +6.035287684510231010e+00 4.256279785156250000e+03 +6.045932707664681161e+00 3.941619384765625000e+03 +6.056577585360981786e+00 3.235073730468750000e+03 +6.067222317343029303e+00 2.433556640625000000e+03 +6.077866903354722794e+00 1.765935668945312500e+03 +6.088511343139969334e+00 1.302019409179687500e+03 +6.099155636442673334e+00 1.025054077148437500e+03 +6.109799783006745422e+00 8.746006469726562500e+02 +6.120443782576100666e+00 7.901195678710937500e+02 +6.131087634894655913e+00 7.420778198242187500e+02 +6.141731339706333337e+00 7.149016113281250000e+02 +6.152374896755056888e+00 6.974500732421875000e+02 +6.163018305784754070e+00 6.859393310546875000e+02 +6.173661566539361267e+00 6.788694458007812500e+02 +6.184304678762805985e+00 6.733462524414062500e+02 +6.194947642199034377e+00 6.695398559570312500e+02 +6.205590456591986381e+00 6.667891845703125000e+02 +6.216233121685607266e+00 6.642049560546875000e+02 +6.226875637223849402e+00 6.626867675781250000e+02 +6.237518002950664275e+00 6.612979125976562500e+02 +6.248160218610008698e+00 6.599806518554687500e+02 +6.258802283945843037e+00 6.596062622070312500e+02 +6.269444198702132987e+00 6.589934082031250000e+02 +6.280085962622845130e+00 6.581966552734375000e+02 +6.290727575451953157e+00 6.580597534179687500e+02 +6.301369036933428092e+00 6.577460937500000000e+02 +6.312010346811252504e+00 6.572639770507812500e+02 +6.322651504829406299e+00 6.574758300781250000e+02 +6.333292510731877378e+00 6.574190673828125000e+02 +6.343933364262655417e+00 6.573334960937500000e+02 +6.354574065165731867e+00 6.577716674804687500e+02 +6.365214613185104398e+00 6.580551147460937500e+02 +6.375855008064776008e+00 6.587269897460937500e+02 +6.386495249548747921e+00 6.595447387695312500e+02 +6.397135337381029352e+00 6.601424560546875000e+02 +6.407775271305633957e+00 6.616143188476562500e+02 +6.418415051066572730e+00 6.631992187500000000e+02 +6.429054676407869984e+00 6.650293579101562500e+02 +6.439694147073545594e+00 6.682530517578125000e+02 +6.450333462807628315e+00 6.727244873046875000e+02 +6.460972623354146904e+00 6.791828002929687500e+02 +6.471611628457133669e+00 6.902700195312500000e+02 +6.482250477860629800e+00 7.087609863281250000e+02 +6.492889171308677376e+00 7.408073120117187500e+02 +6.503527708545317587e+00 7.988739624023437500e+02 +6.514166089314603170e+00 9.023049316406250000e+02 +6.524804313360585084e+00 1.070028564453125000e+03 +6.535442380427322284e+00 1.310592407226562500e+03 +6.546080290258871059e+00 1.592997314453125000e+03 +6.556718042599300134e+00 1.833729614257812500e+03 +6.567355637192675566e+00 1.951127563476562500e+03 +6.577993073783067857e+00 1.886588134765625000e+03 +6.588630352114553723e+00 1.672712768554687500e+03 +6.599267471931211659e+00 1.386576049804687500e+03 +6.609904432977128153e+00 1.127488647460937500e+03 +6.620541234996386137e+00 9.456999511718750000e+02 +6.631177877733078319e+00 8.284180908203125000e+02 +6.641814360931300065e+00 7.630810546875000000e+02 +6.652450684335147635e+00 7.295563964843750000e+02 +6.663086847688726166e+00 7.107470703125000000e+02 +6.673722850736139911e+00 7.009564819335937500e+02 +6.684358693221499337e+00 6.963928833007812500e+02 +6.694994374888920241e+00 6.943372802734375000e+02 +6.705629895482515757e+00 6.949149780273437500e+02 +6.716265254746412339e+00 6.969520874023437500e+02 +6.726900452424732002e+00 7.005670776367187500e+02 +6.737535488261608307e+00 7.068223266601562500e+02 +6.748170362001171263e+00 7.163702392578125000e+02 +6.758805073387557982e+00 7.310372924804687500e+02 +6.769439622164912684e+00 7.550841064453125000e+02 +6.780074008077376924e+00 7.961584472656250000e+02 +6.790708230869102024e+00 8.677875976562500000e+02 +6.801342290284239311e+00 9.958537597656250000e+02 +6.811976186066947214e+00 1.231215209960937500e+03 +6.822609917961384163e+00 1.616306884765625000e+03 +6.833243485711717469e+00 2.196250488281250000e+03 +6.843876889062113555e+00 2.935390869140625000e+03 +6.854510127756747728e+00 3.636816406250000000e+03 +6.865143201539792628e+00 4.071528076171875000e+03 +6.875776110155433329e+00 4.063956787109375000e+03 +6.886408853347850467e+00 3.634684326171875000e+03 +6.897041430861236222e+00 2.957371337890625000e+03 +6.907673842439779222e+00 2.320462646484375000e+03 +6.918306087827677864e+00 1.929380126953125000e+03 +6.928938166769134988e+00 1.809908691406250000e+03 +6.939570079008348991e+00 1.909286743164062500e+03 +6.950201824289534258e+00 2.110392578125000000e+03 +6.960833402356898958e+00 2.258080566406250000e+03 +6.971464812954664581e+00 2.240816894531250000e+03 +6.982096055827046399e+00 2.038132080078125000e+03 +6.992727130718272122e+00 1.711284545898437500e+03 +7.003358037372569456e+00 1.381904418945312500e+03 +7.013988775534173215e+00 1.114066406250000000e+03 +7.024619344947314659e+00 9.326279907226562500e+02 +7.035249745356240147e+00 8.282878417968750000e+02 +7.045879976505193376e+00 7.696282958984375000e+02 +7.056510038138419816e+00 7.354207153320312500e+02 +7.067139930000176484e+00 7.167085571289062500e+02 +7.077769651834718623e+00 7.048060302734375000e+02 +7.088399203386307690e+00 6.969727783203125000e+02 +7.099028584399209585e+00 6.922392578125000000e+02 +7.109657794617693760e+00 6.885267944335937500e+02 +7.120286833786032332e+00 6.860540771484375000e+02 +7.130915701648505411e+00 6.844313354492187500e+02 +7.141544397949390444e+00 6.828943481445312500e+02 +7.152172922432977309e+00 6.822315673828125000e+02 +7.162801274843554999e+00 6.817125244140625000e+02 +7.173429454925416060e+00 6.814032592773437500e+02 +7.184057462422860141e+00 6.820169677734375000e+02 +7.194685297080190445e+00 6.831192626953125000e+02 +7.205312958641711063e+00 6.848960571289062500e+02 +7.215940446851735857e+00 6.883131713867187500e+02 +7.226567761454576910e+00 6.936546630859375000e+02 +7.237194902194556079e+00 7.019376831054687500e+02 +7.247821868815994328e+00 7.165010986328125000e+02 +7.258448661063219731e+00 7.421382446289062500e+02 +7.269075278680563912e+00 7.879049682617187500e+02 +7.279701721412362936e+00 8.706286621093750000e+02 +7.290327989002958198e+00 1.020032287597656250e+03 +7.300954081196691092e+00 1.251599487304687500e+03 +7.311579997737913672e+00 1.582026367187500000e+03 +7.322205738370975325e+00 1.959066162109375000e+03 +7.332831302840235210e+00 2.266651855468750000e+03 +7.343456690890052485e+00 2.396548339843750000e+03 +7.354081902264796078e+00 2.292095703125000000e+03 +7.364706936708833140e+00 1.997203857421875000e+03 +7.375331793966537042e+00 1.609820312500000000e+03 +7.385956473782285592e+00 1.270382324218750000e+03 +7.396580975900465482e+00 1.028067504882812500e+03 +7.407205300065458076e+00 8.734135131835937500e+02 +7.417829446021659834e+00 7.883736572265625000e+02 +7.428453413513460113e+00 7.424445190429687500e+02 +7.439077202285265145e+00 7.159290161132812500e+02 +7.449700812081473167e+00 7.020507202148437500e+02 +7.460324242646494852e+00 6.938139038085937500e+02 +7.470947493724743538e+00 6.892227172851562500e+02 +7.481570565060634337e+00 6.878087158203125000e+02 +7.492193456398588580e+00 6.886027832031250000e+02 +7.502816167483032039e+00 6.916846313476562500e+02 +7.513438698058396703e+00 6.986668090820312500e+02 +7.524061047869113672e+00 7.119370117187500000e+02 +7.534683216659623817e+00 7.356111450195312500e+02 +7.545305204174368896e+00 7.795932617187500000e+02 +7.555927010157796886e+00 8.618198852539062500e+02 +7.566548634354358427e+00 9.933261108398437500e+02 +7.577170076508507712e+00 1.192349487304687500e+03 +7.587791336364708705e+00 1.440743041992187500e+03 +7.598412413667427145e+00 1.679812500000000000e+03 +7.609033308161126996e+00 1.836830810546875000e+03 +7.619654019590285543e+00 1.847573730468750000e+03 +7.630274547699380072e+00 1.727088500976562500e+03 +7.640894892232893199e+00 1.533870483398437500e+03 +7.651515052935311090e+00 1.372615356445312500e+03 +7.662135029551125243e+00 1.308448486328125000e+03 +7.672754821824830707e+00 1.347620971679687500e+03 +7.683374429500929637e+00 1.449842773437500000e+03 +7.693993852323922411e+00 1.540186279296875000e+03 +7.704613090038321843e+00 1.550533813476562500e+03 +7.715232142388641634e+00 1.456975830078125000e+03 +7.725851009119395485e+00 1.284302490234375000e+03 +7.736469689975109532e+00 1.096495727539062500e+03 +7.747088184700309910e+00 9.383493041992187500e+02 +7.757706493039527196e+00 8.253958129882812500e+02 +7.768324614737297296e+00 7.555357666015625000e+02 +7.778942549538160556e+00 7.184893188476562500e+02 +7.789560297186662652e+00 6.966138305664062500e+02 +7.800177857427352812e+00 6.844909667968750000e+02 +7.810795230004780265e+00 6.772030639648437500e+02 +7.821412414663510226e+00 6.723433837890625000e+02 +7.832029411148103470e+00 6.695527954101562500e+02 +7.842646219203122548e+00 6.676491088867187500e+02 +7.853262838573145110e+00 6.659823608398437500e+02 +7.863879269002743477e+00 6.653209838867187500e+02 +7.874495510236503293e+00 6.647116699218750000e+02 +7.885111562019006648e+00 6.643076171875000000e+02 +7.895727424094843627e+00 6.646254272460937500e+02 +7.906343096208609644e+00 6.649099731445312500e+02 +7.916958578104905442e+00 6.654145507812500000e+02 +7.927573869528329986e+00 6.664851074218750000e+02 +7.938188970223497343e+00 6.684416503906250000e+02 +7.948803879935016248e+00 6.721162109375000000e+02 +7.959418598407507872e+00 6.786392211914062500e+02 +7.970033125385590722e+00 6.901878051757812500e+02 +7.980647460613894850e+00 7.107658691406250000e+02 +7.991261603837049421e+00 7.471265258789062500e+02 +8.001875554799692480e+00 8.035787963867187500e+02 +8.012489313246460299e+00 8.801359863281250000e+02 +8.023102878922003356e+00 9.661654663085937500e+02 +8.033716251570968581e+00 1.036288574218750000e+03 +8.044329430938010006e+00 1.064299438476562500e+03 +8.054942416767790547e+00 1.038845092773437500e+03 +8.065555208804969567e+00 9.729641113281250000e+02 +8.076167806794217086e+00 8.872814331054687500e+02 +8.086780210480208453e+00 8.094811401367187500e+02 +8.097392419607617242e+00 7.538461914062500000e+02 +8.108004433921129461e+00 7.195546875000000000e+02 +8.118616253165432894e+00 7.001906738281250000e+02 +8.129227877085215326e+00 6.913977661132812500e+02 +8.139839305425176974e+00 6.876367797851562500e+02 +8.150450537930018058e+00 6.868375244140625000e+02 +8.161061574344444125e+00 6.890332031250000000e+02 +8.171672414413166052e+00 6.934417724609375000e+02 +8.182283057880901822e+00 7.013698730468750000e+02 +8.192893504492367640e+00 7.144827880859375000e+02 +8.203503753992292147e+00 7.375480346679687500e+02 +8.214113806125400430e+00 7.769415283203125000e+02 +8.224723660636430012e+00 8.514718017578125000e+02 +8.235333317270120190e+00 9.778217773437500000e+02 +8.245942775771213817e+00 1.185203857421875000e+03 +8.256552035884460850e+00 1.488963134765625000e+03 +8.267161097354613020e+00 1.850345336914062500e+03 +8.277769959926430943e+00 2.180961181640625000e+03 +8.288378623344673457e+00 2.381870361328125000e+03 +8.298987087354113612e+00 2.369458007812500000e+03 +8.309595351699520904e+00 2.164498779296875000e+03 +8.320203416125675489e+00 1.869843750000000000e+03 +8.330811280377352190e+00 1.609964111328125000e+03 +8.341418944199348928e+00 1.455990356445312500e+03 +8.352026407336449410e+00 1.401854736328125000e+03 +8.362633669533453329e+00 1.402648559570312500e+03 +8.373240730535160381e+00 1.383694335937500000e+03 +8.383847590086380919e+00 1.309516357421875000e+03 +8.394454247931923518e+00 1.184627319335937500e+03 +8.405060703816603862e+00 1.039467407226562500e+03 +8.415666957485242961e+00 9.166905517578125000e+02 +8.426273008682667154e+00 8.248919677734375000e+02 +8.436878857153708111e+00 7.665571289062500000e+02 +8.447484502643199278e+00 7.343247070312500000e+02 +8.458089944895982981e+00 7.162971801757812500e+02 +8.468695183656903325e+00 7.058026123046875000e+02 +8.479300218670809741e+00 7.003860473632812500e+02 +8.489905049682560545e+00 6.971550903320312500e+02 +8.500509676437012274e+00 6.953563232421875000e+02 +8.511114098679032125e+00 6.949784545898437500e+02 +8.521718316153490846e+00 6.951216430664062500e+02 +8.532322328605257411e+00 6.961920166015625000e+02 +8.542926135779218555e+00 6.980186157226562500e+02 +8.553529737420257462e+00 7.006634521484375000e+02 +8.564133133273260867e+00 7.050765991210937500e+02 +8.574736323083124390e+00 7.121492919921875000e+02 +8.585339306594748976e+00 7.210169067382812500e+02 +8.595942083553033797e+00 7.375151367187500000e+02 +8.606544653702897563e+00 7.690372314453125000e+02 +8.617147016789250102e+00 8.278894042968750000e+02 +8.627749172557006574e+00 9.343248291015625000e+02 +8.638351120751096346e+00 1.116160156250000000e+03 +8.648952861116448787e+00 1.381629272460937500e+03 +8.659554393397998595e+00 1.737533081054687500e+03 +8.670155717340682244e+00 2.101072753906250000e+03 +8.680756832689445091e+00 2.363639160156250000e+03 +8.691357739189237819e+00 2.430061035156250000e+03 +8.701958436585016443e+00 2.274398437500000000e+03 +8.712558924621735201e+00 1.949995971679687500e+03 +8.723159203044362542e+00 1.572384765625000000e+03 +8.733759271597866913e+00 1.250865356445312500e+03 +8.744359130027223870e+00 1.022978027343750000e+03 +8.754958778077412518e+00 8.802243041992187500e+02 +8.765558215493417293e+00 8.037642211914062500e+02 +8.776157442020226185e+00 7.630902099609375000e+02 +8.786756457402837839e+00 7.420597534179687500e+02 +8.797355261386249126e+00 7.337076416015625000e+02 +8.807953853715467574e+00 7.338025512695312500e+02 +8.818552234135500711e+00 7.416336059570312500e+02 +8.829150402391364949e+00 7.622483520507812500e+02 +8.839748358228082026e+00 8.032894897460937500e+02 +8.850346101390677234e+00 8.773948364257812500e+02 +8.860943631624175865e+00 1.015917968750000000e+03 +8.871540948673620974e+00 1.239467895507812500e+03 +8.882138052284050289e+00 1.560921752929687500e+03 +8.892734942200508641e+00 1.953891113281250000e+03 +8.903331618168044415e+00 2.313953125000000000e+03 +8.913928079931723758e+00 2.525069580078125000e+03 +8.924524327236600385e+00 2.509851074218750000e+03 +8.935120359827740444e+00 2.270142822265625000e+03 +8.945716177450217188e+00 1.889432006835937500e+03 +8.956311779849107424e+00 1.497576538085937500e+03 +8.966907166769495063e+00 1.186223632812500000e+03 +8.977502337956464018e+00 9.739269409179687500e+02 +8.988097293155110634e+00 8.456494140625000000e+02 +8.998692032110529482e+00 7.764365844726562500e+02 +9.009286554567824012e+00 7.370728149414062500e+02 +9.019880860272101231e+00 7.141510009765625000e+02 +9.030474948968478799e+00 7.008740844726562500e+02 +9.041068820402069051e+00 6.920217895507812500e+02 +9.051662474318002083e+00 6.859138793945312500e+02 +9.062255910461402664e+00 6.820156250000000000e+02 +9.072849128577406219e+00 6.786572875976562500e+02 +9.083442128411146399e+00 6.765030517578125000e+02 +9.094034909707779946e+00 6.751370849609375000e+02 +9.104627472212447614e+00 6.734834594726562500e+02 +9.115219815670306147e+00 6.724407958984375000e+02 +9.125811939826519392e+00 6.714660644531250000e+02 +9.136403844426247645e+00 6.701627807617187500e+02 +9.146995529214665410e+00 6.699064941406250000e+02 +9.157586993936950748e+00 6.694194946289062500e+02 +9.168178238338281716e+00 6.691209106445312500e+02 +9.178769262163847031e+00 6.695682373046875000e+02 +9.189360065158837187e+00 6.699863281250000000e+02 +9.199950647068453335e+00 6.717796630859375000e+02 +9.210541007637893074e+00 6.758046264648437500e+02 +9.221131146612368212e+00 6.824872436523437500e+02 +9.231721063737092337e+00 6.954828491210937500e+02 +9.242310758757284361e+00 7.166214599609375000e+02 +9.252900231418166754e+00 7.457329711914062500e+02 +9.263489481464969089e+00 7.815885009765625000e+02 +9.274078508642926266e+00 8.134724731445312500e+02 +9.284667312697280295e+00 8.312718505859375000e+02 +9.295255893373276734e+00 8.287663574218750000e+02 +9.305844250416166474e+00 8.055675048828125000e+02 +9.316432383571202180e+00 7.717019653320312500e+02 +9.327020292583648953e+00 7.373230590820312500e+02 +9.337607977198775444e+00 7.100646972656250000e+02 +9.348195437161850307e+00 6.923247070312500000e+02 +9.358782672218152854e+00 6.820676879882812500e+02 +9.369369682112969500e+00 6.770531616210937500e+02 +9.379956466591584885e+00 6.758416748046875000e+02 +9.390543025399294308e+00 6.761682739257812500e+02 +9.401129358281396620e+00 6.789955444335937500e+02 +9.411715464983201329e+00 6.847637329101562500e+02 +9.422301345250016169e+00 6.944530639648437500e+02 +9.432886998827155978e+00 7.136517333984375000e+02 +9.443472425459940922e+00 7.462779541015625000e+02 +9.454057624893701828e+00 8.002896728515625000e+02 +9.464642596873771296e+00 8.823135986328125000e+02 +9.475227341145481930e+00 9.835158691406250000e+02 +9.485811857454180540e+00 1.089523559570312500e+03 +9.496396145545215717e+00 1.175358276367187500e+03 +9.506980205163939601e+00 1.219432373046875000e+03 +9.517564036055718546e+00 1.228174194335937500e+03 +9.528147637965910022e+00 1.230171020507812500e+03 +9.538731010639885710e+00 1.254951782226562500e+03 +9.549314153823027951e+00 1.318453369140625000e+03 +9.559897067260713754e+00 1.388763671875000000e+03 +9.570479750698330790e+00 1.425546752929687500e+03 +9.581062203881272055e+00 1.393830078125000000e+03 +9.591644426554937652e+00 1.288382934570312500e+03 +9.602226418464729463e+00 1.138149902343750000e+03 +9.612808179356060023e+00 9.878187255859375000e+02 +9.623389708974336543e+00 8.681583862304687500e+02 +9.633971007064991099e+00 7.866500244140625000e+02 +9.644552073373441559e+00 7.385062866210937500e+02 +9.655132907645123552e+00 7.114185791015625000e+02 +9.665713509625476263e+00 6.968461303710937500e+02 +9.676293879059931768e+00 6.886304931640625000e+02 +9.686874015693950568e+00 6.845813598632812500e+02 +9.697453919272982503e+00 6.829522094726562500e+02 +9.708033589542489850e+00 6.820095825195312500e+02 +9.718613026247933107e+00 6.827423095703125000e+02 +9.729192229134785208e+00 6.842188110351562500e+02 +9.739771197948524417e+00 6.871852416992187500e+02 +9.750349932434632549e+00 6.929224853515625000e+02 +9.760928432338593197e+00 7.022583007812500000e+02 +9.771506697405907715e+00 7.198040161132812500e+02 +9.782084727382072131e+00 7.513469848632812500e+02 +9.792662522012586024e+00 8.057764892578125000e+02 +9.803240081042963183e+00 8.986049804687500000e+02 +9.813817404218724505e+00 1.027781738281250000e+03 +9.824394491285389108e+00 1.187187744140625000e+03 +9.834971341988479665e+00 1.342151000976562500e+03 +9.845547956073536611e+00 1.441205566406250000e+03 +9.856124333286091499e+00 1.449668090820312500e+03 +9.866700473371693647e+00 1.366206787109375000e+03 +9.877276376075895925e+00 1.220043945312500000e+03 +9.887852041144247650e+00 1.055586669921875000e+03 +9.898427468322315903e+00 9.206362915039062500e+02 +9.909002657355660659e+00 8.248157348632812500e+02 +9.919577607989864987e+00 7.666193847656250000e+02 +9.930152319970504848e+00 7.362219238281250000e+02 +9.940726793043159759e+00 7.217100830078125000e+02 +9.951301026953426998e+00 7.179713134765625000e+02 +9.961875021446894962e+00 7.220485839843750000e+02 +9.972448776269173365e+00 7.360645751953125000e+02 +9.983022291165864814e+00 7.629471435546875000e+02 +9.993595565882586129e+00 8.158376464843750000e+02 +1.000416860016495413e+01 9.112163696289062500e+02 +1.001474139375859806e+01 1.049245849609375000e+03 +1.002531394640914009e+01 1.229015991210937500e+03 +1.003588625786222366e+01 1.410225952148437500e+03 +1.004645832786349047e+01 1.542261108398437500e+03 +1.005703015615858753e+01 1.589919311523437500e+03 +1.006760174249316897e+01 1.541504394531250000e+03 +1.007817308661289424e+01 1.424957031250000000e+03 +1.008874418826343167e+01 1.294504882812500000e+03 +1.009931504719044959e+01 1.190503662109375000e+03 +1.010988566313962700e+01 1.118144165039062500e+03 +1.012045603585664288e+01 1.063238403320312500e+03 +1.013102616508719578e+01 1.007510009765625000e+03 +1.014159605057697178e+01 9.387431030273437500e+02 +1.015216569207167652e+01 8.699727783203125000e+02 +1.016273508931701208e+01 8.061417846679687500e+02 +1.017330424205869654e+01 7.582930908203125000e+02 +1.018387315004244620e+01 7.272119140625000000e+02 +1.019444181301398089e+01 7.084058837890625000e+02 +1.020501023071903823e+01 6.981911621093750000e+02 +1.021557840290334873e+01 6.922360839843750000e+02 +1.022614632931265710e+01 6.890814819335937500e+02 +1.023671400969270984e+01 6.873847656250000000e+02 +1.024728144378926586e+01 6.862614746093750000e+02 +1.025784863134807168e+01 6.864813842773437500e+02 +1.026841557211490752e+01 6.870771484375000000e+02 +1.027898226583553942e+01 6.892537231445312500e+02 +1.028954871225574408e+01 6.935203247070312500e+02 +1.030011491112130528e+01 7.006565551757812500e+02 +1.031068086217801394e+01 7.148612060546875000e+02 +1.032124656517166450e+01 7.390040283203125000e+02 +1.033181201984805853e+01 7.797720947265625000e+02 +1.034237722595299935e+01 8.436943969726562500e+02 +1.035294218323230986e+01 9.214270629882812500e+02 +1.036350689143180404e+01 1.008942199707031250e+03 +1.037407135029730121e+01 1.075177734375000000e+03 +1.038463555957464379e+01 1.098153808593750000e+03 +1.039519951900965999e+01 1.071623168945312500e+03 +1.040576322834819933e+01 1.006277893066406250e+03 +1.041632668733611133e+01 9.207026367187500000e+02 +1.042688989571924552e+01 8.414843750000000000e+02 +1.043745285324346384e+01 7.829299316406250000e+02 +1.044801555965464068e+01 7.452891845703125000e+02 +1.045857801469864512e+01 7.257985229492187500e+02 +1.046914021812135864e+01 7.184252319335937500e+02 +1.047970216966866097e+01 7.203538818359375000e+02 +1.049026386908644959e+01 7.308907470703125000e+02 +1.050082531612062375e+01 7.553340454101562500e+02 +1.051138651051708273e+01 8.013146362304687500e+02 +1.052194745202173465e+01 8.793386840820312500e+02 +1.053250814038050187e+01 1.007551391601562500e+03 +1.054306857533930142e+01 1.176235473632812500e+03 +1.055362875664406452e+01 1.366962280273437500e+03 +1.056418868404072597e+01 1.542199707031250000e+03 +1.057474835727522056e+01 1.640827758789062500e+03 +1.058530777609350082e+01 1.649079956054687500e+03 +1.059586694024151932e+01 1.582589355468750000e+03 +1.060642584946522859e+01 1.488293212890625000e+03 +1.061698450351059719e+01 1.396133666992187500e+03 +1.062754290212359898e+01 1.333205322265625000e+03 +1.063810104505020604e+01 1.273243774414062500e+03 +1.064865893203640823e+01 1.199566772460937500e+03 +1.065921656282818475e+01 1.104590209960937500e+03 +1.066977393717154143e+01 9.943576049804687500e+02 +1.068033105481247524e+01 8.922419433593750000e+02 +1.069088791549699557e+01 8.144992065429687500e+02 +1.070144451897111537e+01 7.593032226562500000e+02 +1.071200086498085291e+01 7.276634521484375000e+02 +1.072255695327224068e+01 7.090423583984375000e+02 +1.073311278359130583e+01 6.987014770507812500e+02 +1.074366835568409151e+01 6.927329101562500000e+02 +1.075422366929663731e+01 6.889016113281250000e+02 +1.076477872417500237e+01 6.871348266601562500e+02 +1.077533352006523870e+01 6.861631469726562500e+02 +1.078588805671341078e+01 6.869276733398437500e+02 +1.079644233386558660e+01 6.893882446289062500e+02 +1.080699635126784663e+01 6.945742797851562500e+02 +1.081755010866627487e+01 7.054983520507812500e+02 +1.082810360580695530e+01 7.244885864257812500e+02 +1.083865684243598260e+01 7.589120483398437500e+02 +1.084920981829946207e+01 8.088952026367187500e+02 +1.085976253314350082e+01 8.710745239257812500e+02 +1.087031498671421126e+01 9.402892456054687500e+02 +1.088086717875771292e+01 9.885995483398437500e+02 +1.089141910902013066e+01 1.005946044921875000e+03 +1.090197077724760355e+01 9.846653442382812500e+02 +1.091252218318626532e+01 9.307870483398437500e+02 +1.092307332658226393e+01 8.623673706054687500e+02 +1.093362420718174910e+01 8.001635131835937500e+02 +1.094417482473087766e+01 7.533066406250000000e+02 +1.095472517897581888e+01 7.220283203125000000e+02 +1.096527526966274024e+01 7.062800292968750000e+02 +1.097582509653781635e+01 6.996358032226562500e+02 +1.098637465934723600e+01 7.014745483398437500e+02 +1.099692395783718979e+01 7.108397827148437500e+02 +1.100747299175386473e+01 7.335140991210937500e+02 +1.101802176084346918e+01 7.715427246093750000e+02 +1.102857026485221148e+01 8.368585815429687500e+02 +1.103911850352630886e+01 9.245899047851562500e+02 +1.104966647661198031e+01 1.024052612304687500e+03 +1.106021418385545374e+01 1.114989013671875000e+03 +1.107076162500296412e+01 1.165868286132812500e+03 +1.108130879980075534e+01 1.156306518554687500e+03 +1.109185570799506770e+01 1.092670166015625000e+03 +1.110240234933216108e+01 9.999187011718750000e+02 +1.111294872355829000e+01 8.966014404296875000e+02 +1.112349483041972675e+01 8.127677001953125000e+02 +1.113404066966274009e+01 7.561912231445312500e+02 +1.114458624103360940e+01 7.191755981445312500e+02 +1.115513154427862297e+01 7.000423583984375000e+02 +1.116567657914407263e+01 6.887508544921875000e+02 +1.117622134537625378e+01 6.828145141601562500e+02 +1.118676584272147778e+01 6.793381347656250000e+02 +1.119731007092604713e+01 6.771367187500000000e+02 +1.120785402973628742e+01 6.761496582031250000e+02 +1.121839771889852067e+01 6.753288574218750000e+02 +1.122894113815908135e+01 6.752622070312500000e+02 +1.123948428726429682e+01 6.755961303710937500e+02 +1.125002716596052110e+01 6.760122070312500000e+02 +1.126056977399410286e+01 6.774196166992187500e+02 +1.127111211111139788e+01 6.791550903320312500e+02 +1.128165417705876727e+01 6.826375122070312500e+02 +1.129219597158258459e+01 6.885833740234375000e+02 +1.130273749442922515e+01 6.994708251953125000e+02 +1.131327874534507316e+01 7.182205810546875000e+02 +1.132381972407651638e+01 7.477691650390625000e+02 +1.133436043036995322e+01 7.923176269531250000e+02 +1.134490086397178388e+01 8.465413818359375000e+02 +1.135544102462842275e+01 8.988520507812500000e+02 +1.136598091208628070e+01 9.393861694335937500e+02 +1.137652052609177744e+01 9.505449218750000000e+02 +1.138705986639135226e+01 9.296549682617187500e+02 +1.139759893273143376e+01 8.852344360351562500e+02 +1.140813772485846300e+01 8.335649414062500000e+02 +1.141867624251889168e+01 7.817878417968750000e+02 +1.142921448545917507e+01 7.470804443359375000e+02 +1.143975245342577729e+01 7.250403442382812500e+02 +1.145029014616516250e+01 7.160763549804687500e+02 +1.146082756342381082e+01 7.168729858398437500e+02 +1.147136470494819527e+01 7.287165527343750000e+02 +1.148190157048481552e+01 7.540387573242187500e+02 +1.149243815978016237e+01 8.013792724609375000e+02 +1.150297447258073547e+01 8.785340576171875000e+02 +1.151351050863304515e+01 9.841313476562500000e+02 +1.152404626768360529e+01 1.110267944335937500e+03 +1.153458174947894044e+01 1.234328735351562500e+03 +1.154511695376557867e+01 1.313542480468750000e+03 +1.155565188029005164e+01 1.337609375000000000e+03 +1.156618652879890341e+01 1.302816528320312500e+03 +1.157672089903868340e+01 1.227626098632812500e+03 +1.158725499075594989e+01 1.141229003906250000e+03 +1.159778880369725584e+01 1.065448974609375000e+03 +1.160832233760917731e+01 9.974991455078125000e+02 +1.161885559223828679e+01 9.396141357421875000e+02 +1.162938856733116921e+01 8.822892456054687500e+02 +1.163992126263440952e+01 8.282180786132812500e+02 +1.165045367789460329e+01 7.832050781250000000e+02 +1.166098581285835500e+01 7.490753173828125000e+02 +1.167151766727227624e+01 7.264988403320312500e+02 +1.168204924088297680e+01 7.137516479492187500e+02 +1.169258053343708070e+01 7.065291137695312500e+02 +1.170311154468122261e+01 7.037494506835937500e+02 +1.171364227436203187e+01 7.028441772460937500e+02 +1.172417272222615381e+01 7.044617919921875000e+02 +1.173470288802023909e+01 7.081874389648437500e+02 +1.174523277149094369e+01 7.157470703125000000e+02 +1.175576237238492894e+01 7.293864746093750000e+02 +1.176629169044886680e+01 7.550786743164062500e+02 +1.177682072542943104e+01 8.008106079101562500e+02 +1.178734947707330960e+01 8.743061523437500000e+02 +1.179787794512719401e+01 9.832484741210937500e+02 +1.180840612933777400e+01 1.118622802734375000e+03 +1.181893402945175886e+01 1.250297119140625000e+03 +1.182946164521585786e+01 1.351081420898437500e+03 +1.183998897637679271e+01 1.381074584960937500e+03 +1.185051602268128157e+01 1.331694580078125000e+03 +1.186104278387606215e+01 1.220269042968750000e+03 +1.187156925970786681e+01 1.086376953125000000e+03 +1.188209544992344391e+01 9.549729003906250000e+02 +1.189262135426954536e+01 8.594440307617187500e+02 +1.190314697249293374e+01 7.960217895507812500e+02 +1.191367230434036628e+01 7.627114868164062500e+02 +1.192419734955862154e+01 7.510479736328125000e+02 +1.193472210789447985e+01 7.576512451171875000e+02 +1.194524657909472509e+01 7.833159179687500000e+02 +1.195577076290614826e+01 8.332525024414062500e+02 +1.196629465907555456e+01 9.112811889648437500e+02 +1.197681826734975274e+01 1.009433410644531250e+03 +1.198734158747555156e+01 1.114133911132812500e+03 +1.199786461919977221e+01 1.200647705078125000e+03 +1.200838736226924475e+01 1.241906005859375000e+03 +1.201890981643080636e+01 1.233878417968750000e+03 +1.202943198143129955e+01 1.186364746093750000e+03 +1.203995385701756504e+01 1.117898193359375000e+03 +1.205047544293646489e+01 1.048176391601562500e+03 +1.206099673893486113e+01 9.862639160156250000e+02 +1.207151774475962469e+01 9.279526977539062500e+02 +1.208203846015763006e+01 8.751108398437500000e+02 +1.209255888487576236e+01 8.236754760742187500e+02 +1.210307901866091207e+01 7.792252807617187500e+02 +1.211359886125997853e+01 7.451602783203125000e+02 +1.212411841241986288e+01 7.207249755859375000e+02 +1.213463767188747688e+01 7.059654541015625000e+02 +1.214515663940974655e+01 6.970002441406250000e+02 +1.215567531473358720e+01 6.915884399414062500e+02 +1.216619369760594083e+01 6.885905151367187500e+02 +1.217671178777374230e+01 6.861752319335937500e+02 +1.218722958498393538e+01 6.848314208984375000e+02 +1.219774708898348337e+01 6.836103515625000000e+02 +1.220826429951934244e+01 6.828072509765625000e+02 +1.221878121633847769e+01 6.822663574218750000e+02 +1.222929783918786661e+01 6.815256958007812500e+02 +1.223981416781449205e+01 6.813870239257812500e+02 +1.225033020196534395e+01 6.810576171875000000e+02 +1.226084594138742112e+01 6.806791381835937500e+02 +1.227136138582771707e+01 6.808665771484375000e+02 +1.228187653503325194e+01 6.805249023437500000e+02 +1.229239138875104231e+01 6.809461669921875000e+02 +1.230290594672810833e+01 6.815899047851562500e+02 +1.231342020871148613e+01 6.822404785156250000e+02 +1.232393417444821004e+01 6.837416381835937500e+02 +1.233444784368533398e+01 6.854254760742187500e+02 +1.234496121616990294e+01 6.881037597656250000e+02 +1.235547429164898325e+01 6.924296875000000000e+02 +1.236598706986963769e+01 6.992177734375000000e+02 +1.237649955057894857e+01 7.117470703125000000e+02 +1.238701173352398932e+01 7.331104736328125000e+02 +1.239752361845185291e+01 7.680556640625000000e+02 +1.240803520510963232e+01 8.181621704101562500e+02 +1.241854649324443649e+01 8.816943359375000000e+02 +1.242905748260337440e+01 9.500315551757812500e+02 +1.243956817293356387e+01 1.011162292480468750e+03 +1.245007856398212098e+01 1.057757202148437500e+03 +1.246058865549619021e+01 1.087816284179687500e+03 +1.247109844722290539e+01 1.104677490234375000e+03 +1.248160793890941456e+01 1.112337280273437500e+03 +1.249211713030287108e+01 1.106899291992187500e+03 +1.250262602115043364e+01 1.081947387695312500e+03 +1.251313461119927339e+01 1.035264404296875000e+03 +1.252364290019656501e+01 9.678991699218750000e+02 +1.253415088788949383e+01 8.936472167968750000e+02 +1.254465857402524342e+01 8.258160400390625000e+02 +1.255516595835101334e+01 7.725118408203125000e+02 +1.256567304061401202e+01 7.372329101562500000e+02 +1.257617982056144967e+01 7.154588012695312500e+02 +1.258668629794054006e+01 7.039624633789062500e+02 +1.259719247249851826e+01 6.981387939453125000e+02 +1.260769834398261224e+01 6.957836914062500000e+02 +1.261820391214006243e+01 6.967379150390625000e+02 +1.262870917671811810e+01 7.005480957031250000e+02 +1.263921413746403566e+01 7.095493774414062500e+02 +1.264971879412507683e+01 7.270390625000000000e+02 +1.266022314644851399e+01 7.564002685546875000e+02 +1.267072719418162130e+01 8.025034179687500000e+02 +1.268123093707168536e+01 8.619790649414062500e+02 +1.269173437486599809e+01 9.278380737304687500e+02 +1.270223750731186030e+01 9.832341918945312500e+02 +1.271274033415657634e+01 1.011440490722656250e+03 +1.272324285514745945e+01 1.004211853027343750e+03 +1.273374507003183354e+01 9.633917846679687500e+02 +1.274424697855702782e+01 9.018200073242187500e+02 +1.275474858047037507e+01 8.380989379882812500e+02 +1.276524987551922408e+01 7.833153686523437500e+02 +1.277575086345092359e+01 7.466546630859375000e+02 +1.278625154401282948e+01 7.256193847656250000e+02 +1.279675191695231007e+01 7.189322509765625000e+02 +1.280725198201673720e+01 7.244823608398437500e+02 +1.281775173895349340e+01 7.424971313476562500e+02 +1.282825118750996651e+01 7.738120117187500000e+02 +1.283875032743355149e+01 8.172592163085937500e+02 +1.284924915847164506e+01 8.647427978515625000e+02 +1.285974768037166704e+01 9.066199340820312500e+02 +1.287024589288103193e+01 9.275772094726562500e+02 +1.288074379574716311e+01 9.228789672851562500e+02 +1.289124138871749459e+01 8.922898559570312500e+02 +1.290173867153946397e+01 8.463707275390625000e+02 +1.291223564396052303e+01 7.971900634765625000e+02 +1.292273230572812359e+01 7.554675903320312500e+02 +1.293322865658972987e+01 7.251651000976562500e+02 +1.294372469629280786e+01 7.061458740234375000e+02 +1.295422042458484313e+01 6.951181030273437500e+02 +1.296471584121331233e+01 6.895092773437500000e+02 +1.297521094592571167e+01 6.861082763671875000e+02 +1.298570573846953735e+01 6.845974731445312500e+02 +1.299620021859230512e+01 6.836950073242187500e+02 +1.300669438604152539e+01 6.831593627929687500e+02 +1.301718824056471391e+01 6.834429931640625000e+02 +1.302768178190940773e+01 6.836553344726562500e+02 +1.303817500982314748e+01 6.847479248046875000e+02 +1.304866792405347198e+01 6.865251464843750000e+02 +1.305916052434793606e+01 6.893231811523437500e+02 +1.306965281045410343e+01 6.945164794921875000e+02 +1.308014478211953069e+01 7.023309326171875000e+02 +1.309063643909180641e+01 7.135306396484375000e+02 +1.310112778111850496e+01 7.279290161132812500e+02 +1.311161880794722201e+01 7.418795776367187500e+02 +1.312210951932555147e+01 7.536883544921875000e+02 +1.313259991500109969e+01 7.584838867187500000e+02 +1.314308999472148187e+01 7.562736816406250000e+02 +1.315357975823431858e+01 7.480876464843750000e+02 +1.316406920528723568e+01 7.371174926757812500e+02 +1.317455833562787149e+01 7.274690551757812500e+02 +1.318504714900387143e+01 7.219248046875000000e+02 +1.319553564516288091e+01 7.233309326171875000e+02 +1.320602382385256313e+01 7.343006591796875000e+02 +1.321651168482058480e+01 7.580899047851562500e+02 +1.322699922781461801e+01 8.031383666992187500e+02 +1.323748645258235079e+01 8.717847290039062500e+02 +1.324797335887146055e+01 9.678251953125000000e+02 +1.325845994642965309e+01 1.077533325195312500e+03 +1.326894621500463600e+01 1.181720092773437500e+03 +1.327943216434411511e+01 1.256138793945312500e+03 +1.328991779419581221e+01 1.282376098632812500e+03 +1.330040310430745443e+01 1.258684692382812500e+03 +1.331088809442678134e+01 1.199624633789062500e+03 +1.332137276430153250e+01 1.121562255859375000e+03 +1.333185711367946169e+01 1.042793945312500000e+03 +1.334234114230832269e+01 9.680359497070312500e+02 +1.335282484993589058e+01 9.039510498046875000e+02 +1.336330823630992981e+01 8.464333496093750000e+02 +1.337379130117822790e+01 7.996467285156250000e+02 +1.338427404428857059e+01 7.637827758789062500e+02 +1.339475646538875964e+01 7.385233154296875000e+02 +1.340523856422660032e+01 7.233422851562500000e+02 +1.341572034054990326e+01 7.144063110351562500e+02 +1.342620179410648618e+01 7.100392456054687500e+02 +1.343668292464417924e+01 7.091323852539062500e+02 +1.344716373191082326e+01 7.106524658203125000e+02 +1.345764421565425728e+01 7.163797607421875000e+02 +1.346812437562233455e+01 7.282827148437500000e+02 +1.347860421156291011e+01 7.482400512695312500e+02 +1.348908372322385851e+01 7.809824829101562500e+02 +1.349956291035305256e+01 8.228463745117187500e+02 +1.351004177269837392e+01 8.704761352539062500e+02 +1.352052031000771315e+01 9.128406372070312500e+02 +1.353099852202896791e+01 9.378024291992187500e+02 +1.354147640851004475e+01 9.383411254882812500e+02 +1.355195396919886264e+01 9.138033447265625000e+02 +1.356243120384333523e+01 8.724307250976562500e+02 +1.357290811219140103e+01 8.265563964843750000e+02 +1.358338469399099502e+01 7.862529296875000000e+02 +1.359386094899005570e+01 7.583912963867187500e+02 +1.360433687693654647e+01 7.447263793945312500e+02 +1.361481247757842183e+01 7.462110595703125000e+02 +1.362528775066365583e+01 7.620617675781250000e+02 +1.363576269594022072e+01 7.940957641601562500e+02 +1.364623731315610833e+01 8.415816040039062500e+02 +1.365671160205929979e+01 8.986943359375000000e+02 +1.366718556239780646e+01 9.557219238281250000e+02 +1.367765919391963436e+01 1.000548217773437500e+03 +1.368813249637280016e+01 1.024041381835937500e+03 +1.369860546950532765e+01 1.025089477539062500e+03 +1.370907811306524415e+01 1.006011413574218750e+03 +1.371955042680060011e+01 9.753089599609375000e+02 +1.373002241045943528e+01 9.369115600585937500e+02 +1.374049406378980898e+01 8.958378295898437500e+02 +1.375096538653978762e+01 8.534520874023437500e+02 +1.376143637845743761e+01 8.122576293945312500e+02 +1.377190703929084492e+01 7.764322509765625000e+02 +1.378237736878809372e+01 7.468507690429687500e+02 +1.379284736669728240e+01 7.269019775390625000e+02 +1.380331703276651290e+01 7.138211059570312500e+02 +1.381378636674390137e+01 7.063560180664062500e+02 +1.382425536837756042e+01 7.029116210937500000e+02 +1.383472403741562040e+01 7.017208251953125000e+02 +1.384519237360622235e+01 7.031297607421875000e+02 +1.385566037669750550e+01 7.075427246093750000e+02 +1.386612804643762686e+01 7.159042968750000000e+02 +1.387659538257473457e+01 7.306563110351562500e+02 +1.388706238485700872e+01 7.511306762695312500e+02 +1.389752905303262409e+01 7.761248168945312500e+02 +1.390799538684975722e+01 8.011322631835937500e+02 +1.391846138605660776e+01 8.185717163085937500e+02 +1.392892705040137180e+01 8.248841552734375000e+02 +1.393939237963225963e+01 8.167781372070312500e+02 +1.394985737349748511e+01 7.979476928710937500e+02 +1.396032203174527453e+01 7.735673217773437500e+02 +1.397078635412385950e+01 7.504269409179687500e+02 +1.398125034038148584e+01 7.331237792968750000e+02 +1.399171399026639051e+01 7.240398559570312500e+02 +1.400217730352683887e+01 7.245930786132812500e+02 +1.401264027991109451e+01 7.351683959960937500e+02 +1.402310291916742813e+01 7.563333740234375000e+02 +1.403356522104412463e+01 7.881397094726562500e+02 +1.404402718528947247e+01 8.255968017578125000e+02 +1.405448881165175834e+01 8.633485107421875000e+02 +1.406495009987930267e+01 8.913906860351562500e+02 +1.407541104972041346e+01 9.042471923828125000e+02 +1.408587166092341114e+01 8.999338378906250000e+02 +1.409633193323662326e+01 8.814013671875000000e+02 +1.410679186640839156e+01 8.551547241210937500e+02 +1.411725146018706134e+01 8.257432250976562500e+02 +1.412771071432098680e+01 7.975484619140625000e+02 +1.413816962855852921e+01 7.717686767578125000e+02 +1.414862820264806054e+01 7.495309448242187500e+02 +1.415908643633795982e+01 7.318565063476562500e+02 +1.416954432937661146e+01 7.183331909179687500e+02 +1.418000188151241403e+01 7.096323242187500000e+02 +1.419045909249376614e+01 7.046708374023437500e+02 +1.420091596206908768e+01 7.027170410156250000e+02 +1.421137248998679325e+01 7.037540893554687500e+02 +1.422182867599530809e+01 7.074931030273437500e+02 +1.423228451984306986e+01 7.160107421875000000e+02 +1.424274002127852690e+01 7.317193603515625000e+02 +1.425319518005013109e+01 7.577529907226562500e+02 +1.426364999590634142e+01 7.969297485351562500e+02 +1.427410446859562398e+01 8.470828857421875000e+02 +1.428455859786646265e+01 9.006730346679687500e+02 +1.429501238346734304e+01 9.463171997070312500e+02 +1.430546582514675436e+01 9.706340942382812500e+02 +1.431591892265320176e+01 9.672256469726562500e+02 +1.432637167573519399e+01 9.367326660156250000e+02 +1.433682408414124865e+01 8.901213989257812500e+02 +1.434727614761989756e+01 8.384776000976562500e+02 +1.435772786591967609e+01 7.953200073242187500e+02 +1.436817923878912140e+01 7.669782714843750000e+02 +1.437863026597679550e+01 7.561812133789062500e+02 +1.438908094723124798e+01 7.646918945312500000e+02 +1.439953128230105683e+01 7.912402954101562500e+02 +1.440998127093479120e+01 8.335914306640625000e+02 +1.442043091288103973e+01 8.859393310546875000e+02 +1.443088020788839998e+01 9.354515991210937500e+02 +1.444132915570547304e+01 9.692942504882812500e+02 +1.445177775608085824e+01 9.767083740234375000e+02 +1.446222600876318864e+01 9.558031616210937500e+02 +1.447267391350108667e+01 9.116588134765625000e+02 +1.448312147004318362e+01 8.573433837890625000e+02 +1.449356867813812855e+01 8.054794311523437500e+02 +1.450401553753457229e+01 7.632726440429687500e+02 +1.451446204798117279e+01 7.348009033203125000e+02 +1.452490820922660397e+01 7.166107177734375000e+02 +1.453535402101953977e+01 7.064051513671875000e+02 +1.454579948310866300e+01 7.008240356445312500e+02 +1.455624459524267955e+01 6.975839233398437500e+02 +1.456668935717027757e+01 6.962541503906250000e+02 +1.457713376864017718e+01 6.955492553710937500e+02 +1.458757782940109315e+01 6.959952392578125000e+02 +1.459802153920175805e+01 6.973740234375000000e+02 +1.460846489779090973e+01 7.004251708984375000e+02 +1.461890790491728431e+01 7.062944946289062500e+02 +1.462935056032964098e+01 7.155499877929687500e+02 +1.463979286377674249e+01 7.286762084960937500e+02 +1.465023481500735869e+01 7.444194335937500000e+02 +1.466067641377026476e+01 7.595737304687500000e+02 +1.467111765981425364e+01 7.704754638671875000e+02 +1.468155855288811118e+01 7.739241943359375000e+02 +1.469199909274065163e+01 7.693114624023437500e+02 +1.470243927912068393e+01 7.578452148437500000e+02 +1.471287911177703123e+01 7.436201782226562500e+02 +1.472331859045852021e+01 7.299137573242187500e+02 +1.473375771491398822e+01 7.195794677734375000e+02 +1.474419648489228685e+01 7.146263427734375000e+02 +1.475463490014226942e+01 7.148676147460937500e+02 +1.476507296041279282e+01 7.210720825195312500e+02 +1.477551066545274239e+01 7.332048950195312500e+02 +1.478594801501098921e+01 7.507239990234375000e+02 +1.479638500883642571e+01 7.740746459960937500e+02 +1.480682164667794787e+01 8.016788330078125000e+02 +1.481725792828446586e+01 8.328090820312500000e+02 +1.482769385340489521e+01 8.651865844726562500e+02 +1.483812942178815675e+01 8.938651123046875000e+02 +1.484856463318318021e+01 9.134532470703125000e+02 +1.485899948733891129e+01 9.181367797851562500e+02 +1.486943398400429750e+01 9.052359008789062500e+02 +1.487986812292830052e+01 8.762755126953125000e+02 +1.489030190385988206e+01 8.388368530273437500e+02 +1.490073532654801802e+01 7.991285400390625000e+02 +1.491116839074169320e+01 7.655210571289062500e+02 +1.492160109618990305e+01 7.409478759765625000e+02 +1.493203344264164478e+01 7.247570190429687500e+02 +1.494246542984592985e+01 7.165631103515625000e+02 +1.495289705755177856e+01 7.134932861328125000e+02 +1.496332832550821124e+01 7.149490356445312500e+02 +1.497375923346427129e+01 7.211434936523437500e+02 +1.498418978116899858e+01 7.325695800781250000e+02 +1.499461996837144895e+01 7.511939086914062500e+02 +1.500504979482068357e+01 7.758444824218750000e+02 +1.501547926026577429e+01 8.033033447265625000e+02 +1.502590836445579470e+01 8.279018554687500000e+02 +1.503633710713983618e+01 8.431052856445312500e+02 +1.504676548806699543e+01 8.448269653320312500e+02 +1.505719350698637804e+01 8.330290527343750000e+02 +1.506762116364710025e+01 8.121721191406250000e+02 +1.507804845779827652e+01 7.878616943359375000e+02 +1.508847538918904618e+01 7.683822021484375000e+02 +1.509890195756854681e+01 7.583217163085937500e+02 +1.510932816268592660e+01 7.613522949218750000e+02 +1.511975400429034444e+01 7.809867553710937500e+02 +1.513017948213096275e+01 8.163377075195312500e+02 +1.514060459595696351e+01 8.645189208984375000e+02 +1.515102934551752512e+01 9.189617309570312500e+02 +1.516145373056184020e+01 9.657611083984375000e+02 +1.517187775083911205e+01 9.948972167968750000e+02 +1.518230140609855461e+01 9.983812866210937500e+02 +1.519272469608937648e+01 9.767369995117187500e+02 +1.520314762056081470e+01 9.360689086914062500e+02 +1.521357017926209920e+01 8.886837768554687500e+02 +1.522399237194248300e+01 8.410164184570312500e+02 +1.523441419835121202e+01 8.009737548828125000e+02 +1.524483565823755704e+01 7.701291503906250000e+02 +1.525525675135078174e+01 7.470883789062500000e+02 +1.526567747744017112e+01 7.316399536132812500e+02 +1.527609783625501372e+01 7.208004150390625000e+02 +1.528651782754461053e+01 7.143040161132812500e+02 +1.529693745105826430e+01 7.107585449218750000e+02 +1.530735670654529024e+01 7.091669311523437500e+02 +1.531777559375501951e+01 7.099414062500000000e+02 +1.532819411243677976e+01 7.125107421875000000e+02 +1.533861226233991637e+01 7.179098510742187500e+02 +1.534903004321378006e+01 7.264246826171875000e+02 +1.535944745480773044e+01 7.373665161132812500e+02 +1.536986449687113598e+01 7.497380371093750000e+02 +1.538028116915337762e+01 7.601744384765625000e+02 +1.539069747140383804e+01 7.666937866210937500e+02 +1.540111340337191947e+01 7.672745971679687500e+02 +1.541152896480702239e+01 7.625722045898437500e+02 +1.542194415545855612e+01 7.542581787109375000e+02 +1.543235897507594778e+01 7.460095214843750000e+02 +1.544277342340863335e+01 7.414826049804687500e+02 +1.545318750020604703e+01 7.436962280273437500e+02 +1.546360120521763726e+01 7.568658447265625000e+02 +1.547401453819286488e+01 7.829712524414062500e+02 +1.548442749888120140e+01 8.222162475585937500e+02 +1.549484008703211657e+01 8.725429077148437500e+02 +1.550525230239509966e+01 9.246077270507812500e+02 +1.551566414471963817e+01 9.701860351562500000e+02 +1.552607561375524625e+01 9.997198486328125000e+02 +1.553648670925142561e+01 1.008458435058593750e+03 +1.554689743095770282e+01 9.966508178710937500e+02 +1.555730777862360625e+01 9.687661743164062500e+02 +1.556771775199867314e+01 9.297874145507812500e+02 +1.557812735083245848e+01 8.869936523437500000e+02 +1.558853657487451194e+01 8.444711914062500000e+02 +1.559894542387440275e+01 8.053651733398437500e+02 +1.560935389758170544e+01 7.734443359375000000e+02 +1.561976199574600344e+01 7.484133300781250000e+02 +1.563016971811689260e+01 7.314566650390625000e+02 +1.564057706444397411e+01 7.210183715820312500e+02 +1.565098403447685271e+01 7.156175537109375000e+02 +1.566139062796515979e+01 7.150162963867187500e+02 +1.567179684465852141e+01 7.182182006835937500e+02 +1.568220268430657072e+01 7.266137084960937500e+02 +1.569260814665895865e+01 7.402609252929687500e+02 +1.570301323146534145e+01 7.585636596679687500e+02 +1.571341793847538604e+01 7.797080078125000000e+02 +1.572382226743876821e+01 7.979246826171875000e+02 +1.573422621810516908e+01 8.098950195312500000e+02 +1.574462979022428044e+01 8.114157714843750000e+02 +1.575503298354581005e+01 8.025253906250000000e+02 +1.576543579781946214e+01 7.854091796875000000e+02 +1.577583823279496222e+01 7.654338989257812500e+02 +1.578624028822203584e+01 7.472041015625000000e+02 +1.579664196385042629e+01 7.344868774414062500e+02 +1.580704325942988042e+01 7.299106445312500000e+02 +1.581744417471014685e+01 7.326730346679687500e+02 +1.582784470944100086e+01 7.427111206054687500e+02 +1.583824486337221771e+01 7.565376586914062500e+02 +1.584864463625357800e+01 7.705859985351562500e+02 +1.585904402783487299e+01 7.808970947265625000e+02 +1.586944303786590993e+01 7.836870117187500000e+02 +1.587984166609650138e+01 7.783323364257812500e+02 +1.589023991227646526e+01 7.660123291015625000e+02 +1.590063777615563012e+01 7.500524291992187500e+02 +1.591103525748384051e+01 7.337304687500000000e+02 +1.592143235601094275e+01 7.200535888671875000e+02 +1.593182907148679028e+01 7.101273193359375000e+02 +1.594222540366125962e+01 7.034216308593750000e+02 +1.595262135228421840e+01 6.997868652343750000e+02 +1.596301691710555382e+01 6.976027221679687500e+02 +1.597341209787516725e+01 6.967744750976562500e+02 +1.598380689434295654e+01 6.963051757812500000e+02 +1.599420130625883374e+01 6.964200439453125000e+02 +1.600459533337272333e+01 6.970819702148437500e+02 +1.601498897543455868e+01 6.980866699218750000e+02 +1.602538223219427849e+01 7.002362060546875000e+02 +1.603577510340183565e+01 7.028278808593750000e+02 +1.604616758880718308e+01 7.063532714843750000e+02 +1.605655968816029855e+01 7.097729492187500000e+02 +1.606695140121115628e+01 7.127152709960937500e+02 +1.607734272770974471e+01 7.146091918945312500e+02 +1.608773366740605582e+01 7.149381713867187500e+02 +1.609812422005009935e+01 7.147639160156250000e+02 +1.610851438539189218e+01 7.143027343750000000e+02 +1.611890416318145824e+01 7.155864868164062500e+02 +1.612929355316882862e+01 7.197643432617187500e+02 +1.613968255510404504e+01 7.296390380859375000e+02 +1.615007116873717052e+01 7.467438354492187500e+02 +1.616045939381825747e+01 7.715459594726562500e+02 +1.617084723009737957e+01 8.035701904296875000e+02 +1.618123467732461407e+01 8.368737182617187500e+02 +1.619162173525005599e+01 8.677196655273437500e+02 +1.620200840362380745e+01 8.884152832031250000e+02 +1.621239468219597057e+01 8.965267333984375000e+02 +1.622278057071666169e+01 8.911193237304687500e+02 +1.623316606893601843e+01 8.742457885742187500e+02 +1.624355117660417136e+01 8.499126586914062500e+02 +1.625393589347126522e+01 8.223900146484375000e+02 +1.626432021928745897e+01 7.947615356445312500e+02 +1.627470415380291513e+01 7.704838256835937500e+02 +1.628508769676781043e+01 7.502926635742187500e+02 +1.629547084793233225e+01 7.355236816406250000e+02 +1.630585360704666442e+01 7.258424072265625000e+02 +1.631623597386101565e+01 7.210970458984375000e+02 +1.632661794812559819e+01 7.207870483398437500e+02 +1.633699952959063850e+01 7.259490356445312500e+02 +1.634738071800635595e+01 7.365177612304687500e+02 +1.635776151312300186e+01 7.548806152343750000e+02 +1.636814191469082047e+01 7.791937866210937500e+02 +1.637852192246007732e+01 8.081868286132812500e+02 +1.638890153618103085e+01 8.362327880859375000e+02 +1.639928075560396792e+01 8.565739746093750000e+02 +1.640965958047917539e+01 8.650573120117187500e+02 +1.642003801055695078e+01 8.584819946289062500e+02 +1.643041604558759872e+01 8.399564819335937500e+02 +1.644079368532143448e+01 8.153006591796875000e+02 +1.645117092950879112e+01 7.910407104492187500e+02 +1.646154777789999812e+01 7.741557006835937500e+02 +1.647192423024540275e+01 7.682584228515625000e+02 +1.648230028629535937e+01 7.750477905273437500e+02 +1.649267594580022944e+01 7.925957031250000000e+02 +1.650305120851038865e+01 8.183408813476562500e+02 +1.651342607417621977e+01 8.442299194335937500e+02 +1.652380054254811981e+01 8.668970947265625000e+02 +1.653417461337648220e+01 8.791641235351562500e+02 +1.654454828641172881e+01 8.798620605468750000e+02 +1.655492156140427440e+01 8.692528686523437500e+02 +1.656529443810455149e+01 8.497137451171875000e+02 +1.657566691626299971e+01 8.254815063476562500e+02 +1.658603899563006934e+01 8.001793212890625000e+02 +1.659641067595622488e+01 7.763156738281250000e+02 +1.660678195699193083e+01 7.561918945312500000e+02 +1.661715283848766589e+01 7.401016235351562500e+02 +1.662752332019391943e+01 7.288314819335937500e+02 +1.663789340186119148e+01 7.213060302734375000e+02 +1.664826308323999271e+01 7.173514404296875000e+02 +1.665863236408083381e+01 7.159221191406250000e+02 +1.666900124413424322e+01 7.170377197265625000e+02 +1.667936972315075650e+01 7.201383666992187500e+02 +1.668973780088092695e+01 7.258416748046875000e+02 +1.670010547707530790e+01 7.325659179687500000e+02 +1.671047275148445976e+01 7.397742919921875000e+02 +1.672083962385896427e+01 7.457292480468750000e+02 +1.673120609394939962e+01 7.488215942382812500e+02 +1.674157216150636884e+01 7.487069702148437500e+02 +1.675193782628046790e+01 7.451619873046875000e+02 +1.676230308802231761e+01 7.405700683593750000e+02 +1.677266794648254233e+01 7.363184814453125000e+02 +1.678303240141177000e+01 7.350848388671875000e+02 +1.679339645256064273e+01 7.383744506835937500e+02 +1.680376009967982043e+01 7.482165527343750000e+02 +1.681412334251996299e+01 7.637478027343750000e+02 +1.682448618083174452e+01 7.841622924804687500e+02 +1.683484861436583913e+01 8.070129394531250000e+02 +1.684521064287294578e+01 8.272522583007812500e+02 +1.685557226610376702e+01 8.436844482421875000e+02 +1.686593348380900892e+01 8.518140258789062500e+02 +1.687629429573939888e+01 8.518258056640625000e+02 +1.688665470164566429e+01 8.432887573242187500e+02 +1.689701470127853966e+01 8.278353271484375000e+02 +1.690737429438878436e+01 8.084746704101562500e+02 +1.691773348072715777e+01 7.871266479492187500e+02 +1.692809226004442635e+01 7.673193359375000000e+02 +1.693845063209137081e+01 7.507359619140625000e+02 +1.694880859661878247e+01 7.385547485351562500e+02 +1.695916615337745625e+01 7.315543823242187500e+02 +1.696952330211820481e+01 7.294022216796875000e+02 +1.697988004259184791e+01 7.326395263671875000e+02 +1.699023637454921598e+01 7.421691284179687500e+02 +1.700059229774114300e+01 7.585292358398437500e+02 +1.701094781191847716e+01 7.806693725585937500e+02 +1.702130291683207730e+01 8.071004028320312500e+02 +1.703165761223281649e+01 8.315775756835937500e+02 +1.704201189787156778e+01 8.498919067382812500e+02 +1.705236577349922200e+01 8.563206787109375000e+02 +1.706271923886667707e+01 8.498665161132812500e+02 +1.707307229372483803e+01 8.331644897460937500e+02 +1.708342493782462057e+01 8.099622192382812500e+02 +1.709377717091695814e+01 7.886770629882812500e+02 +1.710412899275278065e+01 7.732526245117187500e+02 +1.711448040308304286e+01 7.681939086914062500e+02 +1.712483140165869244e+01 7.732213134765625000e+02 +1.713518198823070549e+01 7.868538208007812500e+02 +1.714553216255005808e+01 8.032000732421875000e+02 +1.715588192436772985e+01 8.179378662109375000e+02 +1.716623127343472532e+01 8.252298583984375000e+02 +1.717658020950204545e+01 8.224810180664062500e+02 +1.718692873232070895e+01 8.106398925781250000e+02 +1.719727684164174875e+01 7.915667114257812500e+02 +1.720762453721618712e+01 7.707756347656250000e+02 +1.721797181879507832e+01 7.512111206054687500e+02 +1.722831868612948725e+01 7.357273559570312500e+02 +1.723866513897046460e+01 7.250925903320312500e+02 +1.724901117706909659e+01 7.185461425781250000e+02 +1.725935680017646234e+01 7.150523071289062500e+02 +1.726970200804366584e+01 7.136519775390625000e+02 +1.728004680042181107e+01 7.141416625976562500e+02 +1.729039117706201267e+01 7.162445678710937500e+02 +1.730073513771539595e+01 7.207252197265625000e+02 +1.731107868213310397e+01 7.275888671875000000e+02 +1.732142181006627268e+01 7.373516235351562500e+02 +1.733176452126606293e+01 7.486511230468750000e+02 +1.734210681548364263e+01 7.602707519531250000e+02 +1.735244869247019039e+01 7.688552856445312500e+02 +1.736279015197688835e+01 7.733700561523437500e+02 +1.737313119375492931e+01 7.723006591796875000e+02 +1.738347181755553450e+01 7.666949462890625000e+02 +1.739381202312990027e+01 7.592193603515625000e+02 +1.740415181022926561e+01 7.530209350585937500e+02 +1.741449117860486950e+01 7.517874755859375000e+02 +1.742483012800794739e+01 7.578535156250000000e+02 +1.743516865818976669e+01 7.726395263671875000e+02 +1.744550676890159124e+01 7.936083984375000000e+02 +1.745584445989469558e+01 8.182658691406250000e+02 +1.746618173092036841e+01 8.403273315429687500e+02 +1.747651858172990558e+01 8.554758300781250000e+02 +1.748685501207462067e+01 8.604420776367187500e+02 +1.749719102170582374e+01 8.539533691406250000e+02 +1.750752661037484259e+01 8.386215209960937500e+02 +1.751786177783302278e+01 8.170733032226562500e+02 +1.752819652383169924e+01 7.937507934570312500e+02 +1.753853084812224239e+01 7.715438842773437500e+02 +1.754886475045601912e+01 7.523465576171875000e+02 +1.755919823058439633e+01 7.372171630859375000e+02 +1.756953128825876931e+01 7.263425292968750000e+02 +1.757986392323054403e+01 7.192658081054687500e+02 +1.759019613525111936e+01 7.151580810546875000e+02 +1.760052792407191902e+01 7.136967773437500000e+02 +1.761085928944436674e+01 7.141885986328125000e+02 +1.762119023111990757e+01 7.169241333007812500e+02 +1.763152074884999010e+01 7.209940795898437500e+02 +1.764185084238607004e+01 7.261282958984375000e+02 +1.765218051147962086e+01 7.306380615234375000e+02 +1.766250975588212313e+01 7.340248413085937500e+02 +1.767283857534506453e+01 7.349460449218750000e+02 +1.768316696961995049e+01 7.335153198242187500e+02 +1.769349493845828647e+01 7.304680786132812500e+02 +1.770382248161159211e+01 7.270038452148437500e+02 +1.771414959883140838e+01 7.248871459960937500e+02 +1.772447628986926560e+01 7.251074829101562500e+02 +1.773480255447672249e+01 7.291762084960937500e+02 +1.774512839240533779e+01 7.367758789062500000e+02 +1.775545380340668444e+01 7.479902343750000000e+02 +1.776577878723234605e+01 7.605355224609375000e+02 +1.777610334363391331e+01 7.734619750976562500e+02 +1.778642747236298760e+01 7.842545166015625000e+02 +1.779675117317119515e+01 7.911639404296875000e+02 +1.780707444581014087e+01 7.933287963867187500e+02 +1.781739729003147588e+01 7.894792480468750000e+02 +1.782771970558683350e+01 7.812161254882812500e+02 +1.783804169222786840e+01 7.688672485351562500e+02 +1.784836324970624943e+01 7.554520263671875000e+02 +1.785868437777365259e+01 7.419759521484375000e+02 +1.786900507618176448e+01 7.303790893554687500e+02 +1.787932534468227175e+01 7.213001098632812500e+02 +1.788964518302688944e+01 7.153527832031250000e+02 +1.789996459096733616e+01 7.113712158203125000e+02 +1.791028356825532697e+01 7.092667846679687500e+02 +1.792060211464261243e+01 7.080697631835937500e+02 +1.793092022988093603e+01 7.075989990234375000e+02 +1.794123791372205190e+01 7.076199340820312500e+02 +1.795155516591773548e+01 7.076071166992187500e+02 +1.796187198621975512e+01 7.081680908203125000e+02 +1.797218837437990757e+01 7.087044677734375000e+02 +1.798250433014999317e+01 7.101602783203125000e+02 +1.799281985328181932e+01 7.120059204101562500e+02 +1.800313494352720411e+01 7.155232543945312500e+02 +1.801344960063798695e+01 7.212122192382812500e+02 +1.802376382436600366e+01 7.306028442382812500e+02 +1.803407761446310431e+01 7.437815551757812500e+02 +1.804439097068114961e+01 7.614805908203125000e+02 +1.805470389277201448e+01 7.818305664062500000e+02 +1.806501638048758451e+01 8.022124023437500000e+02 +1.807532843357974883e+01 8.202294921875000000e+02 +1.808564005180042145e+01 8.315443725585937500e+02 +1.809595123490150215e+01 8.359487304687500000e+02 +1.810626198263492270e+01 8.314569091796875000e+02 +1.811657229475262554e+01 8.205117187500000000e+02 +1.812688217100653887e+01 8.044221191406250000e+02 +1.813719161114863709e+01 7.862747802734375000e+02 +1.814750061493088040e+01 7.686583862304687500e+02 +1.815780918210523964e+01 7.537433471679687500e+02 +1.816811731242371053e+01 7.420435791015625000e+02 +1.817842500563828878e+01 7.347814941406250000e+02 +1.818873226150098077e+01 7.317902221679687500e+02 +1.819903907976381419e+01 7.338291625976562500e+02 +1.820934546017880962e+01 7.407465820312500000e+02 +1.821965140249801252e+01 7.523010864257812500e+02 +1.822995690647346834e+01 7.680565185546875000e+02 +1.824026197185724030e+01 7.845722656250000000e+02 +1.825056659840140938e+01 7.999602661132812500e+02 +1.826087078585804946e+01 8.097140502929687500e+02 +1.827117453397925573e+01 8.126909179687500000e+02 +1.828147784251713048e+01 8.079832153320312500e+02 +1.829178071122379023e+01 7.984093017578125000e+02 +1.830208313985136215e+01 7.877937011718750000e+02 +1.831238512815197694e+01 7.807188110351562500e+02 +1.832268667587777955e+01 7.808506469726562500e+02 +1.833298778278093266e+01 7.897702636718750000e+02 +1.834328844861359897e+01 8.058382568359375000e+02 +1.835358867312795539e+01 8.245750122070312500e+02 +1.836388845607619658e+01 8.410839233398437500e+02 +1.837418779721051720e+01 8.493847656250000000e+02 +1.838448669628312970e+01 8.474378051757812500e+02 +1.839478515304625006e+01 8.343650512695312500e+02 +1.840508316725211202e+01 8.138207397460937500e+02 +1.841538073865295999e+01 7.900282592773437500e+02 +1.842567786700103483e+01 7.676504516601562500e+02 +1.843597455204861646e+01 7.489623413085937500e+02 +1.844627079354796706e+01 7.356078491210937500e+02 +1.845656659125137011e+01 7.267136840820312500e+02 +1.846686194491113042e+01 7.216346435546875000e+02 +1.847715685427954924e+01 7.188361816406250000e+02 +1.848745131910893136e+01 7.173605957031250000e+02 +1.849774533915162777e+01 7.167833251953125000e+02 +1.850803891415995395e+01 7.166943359375000000e+02 +1.851833204388626797e+01 7.173260498046875000e+02 +1.852862472808292793e+01 7.180328979492187500e+02 +1.853891696650230259e+01 7.190219726562500000e+02 +1.854920875889678200e+01 7.197586669921875000e+02 +1.855950010501873848e+01 7.205858764648437500e+02 +1.856979100462059407e+01 7.211040039062500000e+02 +1.858008145745474593e+01 7.221997680664062500e+02 +1.859037146327362322e+01 7.242365722656250000e+02 +1.860066102182967285e+01 7.285671997070312500e+02 +1.861095013287531685e+01 7.357761840820312500e+02 +1.862123879616302702e+01 7.468242797851562500e+02 +1.863152701144526446e+01 7.611619262695312500e+02 +1.864181477847450807e+01 7.780957031250000000e+02 +1.865210209700324739e+01 7.952112426757812500e+02 +1.866238896678397907e+01 8.101705322265625000e+02 +1.867267538756921397e+01 8.203851318359375000e+02 +1.868296135911147360e+01 8.241625976562500000e+02 +1.869324688116328659e+01 8.212044067382812500e+02 +1.870353195347719577e+01 8.116358642578125000e+02 +1.871381657580575819e+01 7.977703857421875000e+02 +1.872410074790153445e+01 7.814544067382812500e+02 +1.873438446951709935e+01 7.655276489257812500e+02 +1.874466774040503836e+01 7.514714965820312500e+02 +1.875495056031794761e+01 7.411381225585937500e+02 +1.876523292900843742e+01 7.348399047851562500e+02 +1.877551484622911815e+01 7.332492675781250000e+02 +1.878579631173262854e+01 7.362891235351562500e+02 +1.879607732527160380e+01 7.439417724609375000e+02 +1.880635788659869334e+01 7.551285400390625000e+02 +1.881663799546656080e+01 7.681809082031250000e+02 +1.882691765162788045e+01 7.801627807617187500e+02 +1.883719685483532658e+01 7.885447387695312500e+02 +1.884747560484160900e+01 7.912915039062500000e+02 +1.885775390139941976e+01 7.877946166992187500e+02 +1.886803174426147933e+01 7.792626953125000000e+02 +1.887830913318051174e+01 7.677278442382812500e+02 +1.888858606790925876e+01 7.567703857421875000e+02 +1.889886254820047284e+01 7.482346801757812500e+02 +1.890913857380690644e+01 7.439717407226562500e+02 +1.891941414448133330e+01 7.433406982421875000e+02 +1.892968925997653074e+01 7.456917114257812500e+02 +1.893996392004529739e+01 7.490365600585937500e+02 +1.895023812444043898e+01 7.517507934570312500e+02 +1.896051187291475770e+01 7.524260253906250000e+02 +1.897078516522109126e+01 7.509691772460937500e+02 +1.898105800111227737e+01 7.470729370117187500e+02 +1.899133038034115373e+01 7.417830200195312500e+02 +1.900160230266058292e+01 7.356446533203125000e+02 +1.901187376782343108e+01 7.298914184570312500e+02 +1.902214477558258565e+01 7.250061035156250000e+02 +1.903241532569093764e+01 7.214038085937500000e+02 +1.904268541790138158e+01 7.193383178710937500e+02 +1.905295505196683692e+01 7.188428344726562500e+02 +1.906322422764022662e+01 7.202707519531250000e+02 +1.907349294467448431e+01 7.233595581054687500e+02 +1.908376120282256494e+01 7.282464599609375000e+02 +1.909402900183741636e+01 7.341683349609375000e+02 +1.910429634147200773e+01 7.404623413085937500e+02 +1.911456322147932951e+01 7.456713867187500000e+02 +1.912482964161236154e+01 7.487450561523437500e+02 +1.913509560162410494e+01 7.492740478515625000e+02 +1.914536110126757862e+01 7.479727172851562500e+02 +1.915562614029580146e+01 7.458661499023437500e+02 +1.916589071846182080e+01 7.449769287109375000e+02 +1.917615483551866618e+01 7.471109619140625000e+02 +1.918641849121939558e+01 7.536306152343750000e+02 +1.919668168531709540e+01 7.646304321289062500e+02 +1.920694441756482718e+01 7.786569213867187500e+02 +1.921720668771569152e+01 7.937110595703125000e+02 +1.922746849552278192e+01 8.068484497070312500e+02 +1.923772984073921322e+01 8.155707397460937500e+02 +1.924799072311811798e+01 8.180156250000000000e+02 +1.925825114241262526e+01 8.136597290039062500e+02 +1.926851109837587828e+01 8.037609252929687500e+02 +1.927877059076103450e+01 7.898468627929687500e+02 +1.928902961932126559e+01 7.742752685546875000e+02 +1.929928818380975741e+01 7.590690307617187500e+02 +1.930954628397967809e+01 7.456500854492187500e+02 +1.931980391958425614e+01 7.356265869140625000e+02 +1.933006109037669518e+01 7.286975097656250000e+02 +1.934031779611021307e+01 7.255880126953125000e+02 +1.935057403653805608e+01 7.252098388671875000e+02 +1.936082981141345627e+01 7.277471923828125000e+02 +1.937108512048968834e+01 7.318074951171875000e+02 +1.938133996352001276e+01 7.365597534179687500e+02 +1.939159434025770423e+01 7.406057128906250000e+02 +1.940184825045606942e+01 7.431658935546875000e+02 +1.941210169386839368e+01 7.435819091796875000e+02 +1.942235467024800499e+01 7.418815917968750000e+02 +1.943260717934822424e+01 7.388342895507812500e+02 +1.944285922092238295e+01 7.361294555664062500e+02 +1.945311079472384108e+01 7.347207031250000000e+02 +1.946336190050594084e+01 7.356623535156250000e+02 +1.947361253802206349e+01 7.386731567382812500e+02 +1.948386270702559031e+01 7.431148071289062500e+02 +1.949411240726990968e+01 7.474107055664062500e+02 +1.950436163850843130e+01 7.499967651367187500e+02 +1.951461040049456130e+01 7.498016967773437500e+02 +1.952485869298173427e+01 7.468599853515625000e+02 +1.953510651572338475e+01 7.415177612304687500e+02 +1.954535386847297218e+01 7.350593872070312500e+02 +1.955560075098393824e+01 7.284838867187500000e+02 +1.956584716300977078e+01 7.230425415039062500e+02 +1.957609310430394700e+01 7.190072021484375000e+02 +1.958633857461995120e+01 7.164344482421875000e+02 +1.959658357371131387e+01 7.152702636718750000e+02 +1.960682810133152998e+01 7.153690185546875000e+02 +1.961707215723413711e+01 7.169556274414062500e+02 +1.962731574117267641e+01 7.191832275390625000e+02 +1.963755885290068903e+01 7.222608032226562500e+02 +1.964780149217175165e+01 7.260382080078125000e+02 +1.965804365873942672e+01 7.302303466796875000e+02 +1.966828535235730513e+01 7.334452514648437500e+02 +1.967852657277899198e+01 7.355386352539062500e+02 +1.968876731975807459e+01 7.359983520507812500e+02 +1.969900759304818649e+01 7.357324218750000000e+02 +1.970924739240295764e+01 7.351773681640625000e+02 +1.971948671757602511e+01 7.359357299804687500e+02 +1.972972556832105084e+01 7.385322265625000000e+02 +1.973996394439169677e+01 7.440266723632812500e+02 +1.975020184554163549e+01 7.514941406250000000e+02 +1.976043927152455737e+01 7.603914794921875000e+02 +1.977067622209415987e+01 7.684024658203125000e+02 +1.978091269700416177e+01 7.745125122070312500e+02 +1.979114869600827475e+01 7.769787597656250000e+02 +1.980138421886023181e+01 7.756077880859375000e+02 +1.981161926531379436e+01 7.705910034179687500e+02 +1.982185383512269894e+01 7.632259521484375000e+02 +1.983208792804073184e+01 7.546664428710937500e+02 +1.984232154382165447e+01 7.463341064453125000e+02 +1.985255468221927444e+01 7.393134765625000000e+02 +1.986278734298737803e+01 7.345744018554687500e+02 +1.987301952587979414e+01 7.325396118164062500e+02 +1.988325123065033395e+01 7.338235473632812500e+02 +1.989348245705285478e+01 7.383938598632812500e+02 +1.990371320484118556e+01 7.465221557617187500e+02 +1.991394347376919427e+01 7.566868896484375000e+02 +1.992417326359075247e+01 7.680862426757812500e+02 +1.993440257405974236e+01 7.781165771484375000e+02 +1.994463140493006392e+01 7.853065185546875000e+02 +1.995485975595561356e+01 7.878267822265625000e+02 +1.996508762689031258e+01 7.861246337890625000e+02 +1.997531501748809291e+01 7.818289794921875000e+02 +1.998554192750289360e+01 7.778423461914062500e+02 +1.999576835668866792e+01 7.764342041015625000e+02 +2.000599430479937979e+01 7.798640747070312500e+02 +2.001621977158900023e+01 7.877948608398437500e+02 +2.002644475681152869e+01 7.992038574218750000e+02 +2.003666926022094330e+01 8.103577880859375000e+02 +2.004689328157127548e+01 8.186799926757812500e+02 +2.005711682061653534e+01 8.212789916992187500e+02 +2.006733987711075429e+01 8.175823364257812500e+02 +2.007756245080799218e+01 8.077850341796875000e+02 +2.008778454146228398e+01 7.939702148437500000e+02 +2.009800614882771441e+01 7.783119506835937500e+02 +2.010822727265836107e+01 7.631416625976562500e+02 +2.011844791270831223e+01 7.503002319335937500e+02 +2.012866806873168102e+01 7.401235961914062500e+02 +2.013888774048255925e+01 7.328850708007812500e+02 +2.014910692771509204e+01 7.283583984375000000e+02 +2.015932563018341384e+01 7.260407104492187500e+02 +2.016954384764166974e+01 7.256252441406250000e+02 +2.017976157984403329e+01 7.262089843750000000e+02 +2.018997882654466025e+01 7.275722045898437500e+02 +2.020019558749774902e+01 7.288049926757812500e+02 +2.021041186245749444e+01 7.298301391601562500e+02 +2.022062765117810201e+01 7.302282104492187500e+02 +2.023084295341379146e+01 7.303757324218750000e+02 +2.024105776891878961e+01 7.302671508789062500e+02 +2.025127209744734813e+01 7.307817382812500000e+02 +2.026148593875372228e+01 7.323646850585937500e+02 +2.027169929259217085e+01 7.359880371093750000e+02 +2.028191215871697750e+01 7.414580688476562500e+02 +2.029212453688243656e+01 7.486724243164062500e+02 +2.030233642684284590e+01 7.563472290039062500e+02 +2.031254782835251049e+01 7.637346191406250000e+02 +2.032275874116577086e+01 7.690167236328125000e+02 +2.033296916503695329e+01 7.715920410156250000e+02 +2.034317909972041605e+01 7.706908569335937500e+02 +2.035338854497051742e+01 7.664128417968750000e+02 +2.036359750054161566e+01 7.597145385742187500e+02 +2.037380596618811524e+01 7.515830078125000000e+02 +2.038401394166440284e+01 7.433748779296875000e+02 +2.039422142672489002e+01 7.361386108398437500e+02 +2.040442842112399191e+01 7.306543579101562500e+02 +2.041463492461613782e+01 7.273844604492187500e+02 +2.042484093695578196e+01 7.263282470703125000e+02 +2.043504645789736784e+01 7.274461059570312500e+02 +2.044525148719537100e+01 7.297661743164062500e+02 +2.045545602460426693e+01 7.328120117187500000e+02 +2.046566006987854536e+01 7.354628906250000000e+02 +2.047586362277271377e+01 7.375125122070312500e+02 +2.048606668304126899e+01 7.383669433593750000e+02 +2.049626925043875758e+01 7.384850463867187500e+02 +2.050647132471971545e+01 7.383836059570312500e+02 +2.051667290563868207e+01 7.394353027343750000e+02 +2.052687399295023241e+01 7.421872558593750000e+02 +2.053707458640892014e+01 7.473052978515625000e+02 +2.054727468576934513e+01 7.538310546875000000e+02 +2.055747429078611077e+01 7.608587646484375000e+02 +2.056767340121380983e+01 7.660640869140625000e+02 +2.057787201680707412e+01 7.682510375976562500e+02 +2.058807013732053548e+01 7.666866455078125000e+02 +2.059826776250883640e+01 7.614147949218750000e+02 +2.060846489212663712e+01 7.535036010742187500e+02 +2.061866152592860502e+01 7.445009155273437500e+02 +2.062885766366942164e+01 7.358319091796875000e+02 +2.063905330510377567e+01 7.286661987304687500e+02 +2.064924844998637354e+01 7.233254394531250000e+02 +2.065944309807193591e+01 7.198572387695312500e+02 +2.066963724911518341e+01 7.178672485351562500e+02 +2.067983090287086512e+01 7.173746337890625000e+02 +2.069002405909373365e+01 7.177894287109375000e+02 +2.070021671753854520e+01 7.189439086914062500e+02 +2.071040887796007723e+01 7.203477783203125000e+02 +2.072060054011312857e+01 7.220920410156250000e+02 +2.073079170375248736e+01 7.234033813476562500e+02 +2.074098236863297728e+01 7.244211425781250000e+02 +2.075117253450941490e+01 7.249796142578125000e+02 +2.076136220113663100e+01 7.258204345703125000e+02 +2.077155136826949189e+01 7.272450561523437500e+02 +2.078174003566283901e+01 7.301855468750000000e+02 +2.079192820307155998e+01 7.346543579101562500e+02 +2.080211587025053532e+01 7.409938964843750000e+02 +2.081230303695464912e+01 7.484417724609375000e+02 +2.082248970293882095e+01 7.564343872070312500e+02 +2.083267586795797399e+01 7.631658935546875000e+02 +2.084286153176703493e+01 7.680114135742187500e+02 +2.085304669412095890e+01 7.695377197265625000e+02 +2.086323135477468327e+01 7.681224975585937500e+02 +2.087341551348319513e+01 7.633532714843750000e+02 +2.088359917000146027e+01 7.565722656250000000e+02 +2.089378232408448355e+01 7.484278564453125000e+02 +2.090396497548726984e+01 7.404372558593750000e+02 +2.091414712396482756e+01 7.334875488281250000e+02 +2.092432876927219354e+01 7.284376220703125000e+02 +2.093450991116440818e+01 7.255488281250000000e+02 +2.094469054939651897e+01 7.245983886718750000e+02 +2.095487068372360184e+01 7.254913330078125000e+02 +2.096505031390072560e+01 7.273918457031250000e+02 +2.097522943968297682e+01 7.297587890625000000e+02 +2.098540806082547761e+01 7.318069458007812500e+02 +2.099558617708331809e+01 7.331336059570312500e+02 +2.100576378821164170e+01 7.333161010742187500e+02 +2.101594089396557052e+01 7.328394165039062500e+02 +2.102611749410027642e+01 7.319863281250000000e+02 +2.103629358837090280e+01 7.319127807617187500e+02 +2.104646917653263216e+01 7.328280029296875000e+02 +2.105664425834065412e+01 7.353099975585937500e+02 +2.106681883355016538e+01 7.389378051757812500e+02 +2.107699290191638042e+01 7.434800415039062500e+02 +2.108716646319451726e+01 7.474108886718750000e+02 +2.109733951713981170e+01 7.505890502929687500e+02 +2.110751206350751730e+01 7.515474243164062500e+02 +2.111768410205289470e+01 7.507997436523437500e+02 +2.112785563253121168e+01 7.476127319335937500e+02 +2.113802665469775022e+01 7.432864379882812500e+02 +2.114819716830782070e+01 7.379052124023437500e+02 +2.115836717311671578e+01 7.329539184570312500e+02 +2.116853666887977070e+01 7.285154418945312500e+02 +2.117870565535230654e+01 7.254807128906250000e+02 +2.118887413228967986e+01 7.241130981445312500e+02 +2.119904209944724727e+01 7.243327026367187500e+02 +2.120920955658036888e+01 7.256284790039062500e+02 +2.121937650344444393e+01 7.276065673828125000e+02 +2.122954293979485740e+01 7.301130981445312500e+02 +2.123970886538701208e+01 7.322709350585937500e+02 +2.124987427997634271e+01 7.338018188476562500e+02 +2.126003918331826270e+01 7.346123657226562500e+02 +2.127020357516822457e+01 7.354290771484375000e+02 +2.128036745528169504e+01 7.364980468750000000e+02 +2.129053082341413017e+01 7.390200195312500000e+02 +2.130069367932101798e+01 7.430181884765625000e+02 +2.131085602275783941e+01 7.488828735351562500e+02 +2.132101785348011092e+01 7.550213623046875000e+02 +2.133117917124335605e+01 7.612274780273437500e+02 +2.134133997580309483e+01 7.653481445312500000e+02 +2.135150026691487213e+01 7.671152954101562500e+02 +2.136166004433424703e+01 7.654270629882812500e+02 +2.137181930781677863e+01 7.613865356445312500e+02 +2.138197805711805799e+01 7.550834960937500000e+02 +2.139213629199367261e+01 7.481815185546875000e+02 +2.140229401219922067e+01 7.414109497070312500e+02 +2.141245121749032876e+01 7.358438720703125000e+02 +2.142260790762262346e+01 7.323726196289062500e+02 +2.143276408235174557e+01 7.311983642578125000e+02 +2.144291974143334301e+01 7.325395507812500000e+02 +2.145307488462309209e+01 7.359094848632812500e+02 +2.146322951167666915e+01 7.410588378906250000e+02 +2.147338362234976827e+01 7.466958618164062500e+02 +2.148353721639808001e+01 7.521638183593750000e+02 +2.149369029357733751e+01 7.558268432617187500e+02 +2.150384285364325976e+01 7.575520629882812500e+02 +2.151399489635159767e+01 7.567597045898437500e+02 +2.152414642145809154e+01 7.546224365234375000e+02 +2.153429742871851005e+01 7.518607177734375000e+02 +2.154444791788863967e+01 7.498885498046875000e+02 +2.155459788872426685e+01 7.489967041015625000e+02 +2.156474734098119583e+01 7.495333251953125000e+02 +2.157489627441524505e+01 7.505997924804687500e+02 +2.158504468878222937e+01 7.515296020507812500e+02 +2.159519258383800633e+01 7.512020263671875000e+02 +2.160533995933841922e+01 7.491229858398437500e+02 +2.161548681503933977e+01 7.455787353515625000e+02 +2.162563315069664327e+01 7.407432861328125000e+02 +2.163577896606622630e+01 7.356558227539062500e+02 +2.164592426090398902e+01 7.306895751953125000e+02 +2.165606903496583513e+01 7.267203369140625000e+02 +2.166621328800770740e+01 7.238385620117187500e+02 +2.167635701978554863e+01 7.222175903320312500e+02 +2.168650023005530159e+01 7.214168090820312500e+02 +2.169664291857293748e+01 7.214753417968750000e+02 +2.170678508509444171e+01 7.220202026367187500e+02 +2.171692672937579260e+01 7.230988769531250000e+02 +2.172706785117300754e+01 7.243219604492187500e+02 +2.173720845024208970e+01 7.254447021484375000e+02 +2.174734852633907423e+01 7.263069458007812500e+02 +2.175748807922000339e+01 7.270634765625000000e+02 +2.176762710864093009e+01 7.281474609375000000e+02 +2.177776561435791791e+01 7.297429809570312500e+02 +2.178790359612705529e+01 7.325150756835937500e+02 +2.179804105370442358e+01 7.365950927734375000e+02 +2.180817798684613251e+01 7.419956054687500000e+02 +2.181831439530829897e+01 7.482781982421875000e+02 +2.182845027884705402e+01 7.549304809570312500e+02 +2.183858563721853585e+01 7.609252929687500000e+02 +2.184872047017890395e+01 7.651530761718750000e+02 +2.185885477748432137e+01 7.667233886718750000e+02 +2.186898855889097248e+01 7.657097778320312500e+02 +2.187912181415504875e+01 7.620469360351562500e+02 +2.188925454303275941e+01 7.565317382812500000e+02 +2.189938674528031370e+01 7.500823974609375000e+02 +2.190951842065394928e+01 7.441542968750000000e+02 +2.191964956890991090e+01 7.397164306640625000e+02 +2.192978018980444332e+01 7.371303100585937500e+02 +2.193991028309383751e+01 7.370772094726562500e+02 +2.195003984853434886e+01 7.390100708007812500e+02 +2.196016888588229321e+01 7.422629394531250000e+02 +2.197029739489397571e+01 7.462427368164062500e+02 +2.198042537532569440e+01 7.497037353515625000e+02 +2.199055282693380420e+01 7.518077392578125000e+02 +2.200067974947464577e+01 7.521623535156250000e+02 +2.201080614270456692e+01 7.508613891601562500e+02 +2.202093200637996162e+01 7.489658203125000000e+02 +2.203105734025718476e+01 7.469713745117187500e+02 +2.204118214409264453e+01 7.457992553710937500e+02 +2.205130641764275623e+01 7.459979858398437500e+02 +2.206143016066393514e+01 7.471159667968750000e+02 +2.207155337291261787e+01 7.486980590820312500e+02 +2.208167605414524814e+01 7.498161010742187500e+02 +2.209179820411828743e+01 7.498402099609375000e+02 +2.210191982258821497e+01 7.483643798828125000e+02 +2.211204090931150290e+01 7.455035400390625000e+02 +2.212216146404466244e+01 7.414937744140625000e+02 +2.213228148654420124e+01 7.370115356445312500e+02 +2.214240097656663409e+01 7.324604492187500000e+02 +2.215251993386851481e+01 7.284616088867187500e+02 +2.216263835820637595e+01 7.252160034179687500e+02 +2.217275624933678912e+01 7.227407836914062500e+02 +2.218287360701633304e+01 7.210395507812500000e+02 +2.219299043100158642e+01 7.199434814453125000e+02 +2.220310672104915639e+01 7.193479614257812500e+02 +2.221322247691565366e+01 7.190631713867187500e+02 +2.222333769835770312e+01 7.192715454101562500e+02 +2.223345238513195099e+01 7.199358520507812500e+02 +2.224356653699504704e+01 7.214963989257812500e+02 +2.225368015370365526e+01 7.239962158203125000e+02 +2.226379323501444674e+01 7.278535156250000000e+02 +2.227390578068412452e+01 7.330333862304687500e+02 +2.228401779046938458e+01 7.393914184570312500e+02 +2.229412926412695484e+01 7.464702758789062500e+02 +2.230424020141354191e+01 7.532754516601562500e+02 +2.231435060208591636e+01 7.588200683593750000e+02 +2.232446046590080968e+01 7.618510742187500000e+02 +2.233456979261501019e+01 7.621683959960937500e+02 +2.234467858198528489e+01 7.596124877929687500e+02 +2.235478683376843279e+01 7.548233032226562500e+02 +2.236489454772125995e+01 7.486472167968750000e+02 +2.237500172360059025e+01 7.421517944335937500e+02 +2.238510836116325109e+01 7.365338134765625000e+02 +2.239521446016609119e+01 7.320855712890625000e+02 +2.240532002036597348e+01 7.294068603515625000e+02 +2.241542504151976800e+01 7.284702758789062500e+02 +2.242552952338435190e+01 7.293065795898437500e+02 +2.243563346571662720e+01 7.312992553710937500e+02 +2.244573686827351366e+01 7.340942382812500000e+02 +2.245583973081192397e+01 7.366464843750000000e+02 +2.246594205308880277e+01 7.383532104492187500e+02 +2.247604383486108759e+01 7.390750122070312500e+02 +2.248614507588575151e+01 7.385413208007812500e+02 +2.249624577591977470e+01 7.373471679687500000e+02 +2.250634593472013023e+01 7.356700439453125000e+02 +2.251644555204383735e+01 7.342603759765625000e+02 +2.252654462764790111e+01 7.332322998046875000e+02 +2.253664316128934786e+01 7.327053222656250000e+02 +2.254674115272522883e+01 7.308406982421875000e+02 +2.255683860171258459e+01 7.264440307617187500e+02 +2.256693550800849479e+01 7.263438720703125000e+02 +2.257703187137003198e+01 7.271393432617187500e+02 +2.258712769155428290e+01 7.259249877929687500e+02 +2.259722296831837340e+01 7.240044555664062500e+02 +2.260731770141940444e+01 7.220988769531250000e+02 +2.261741189061451607e+01 7.202157592773437500e+02 +2.262750553566085543e+01 7.188223876953125000e+02 +2.263759863631557323e+01 7.179505004882812500e+02 +2.264769119233584860e+01 7.176442871093750000e+02 +2.265778320347887131e+01 7.179425659179687500e+02 +2.266787466950182406e+01 7.185306396484375000e+02 +2.267796559016192859e+01 7.193562011718750000e+02 +2.268805596521641021e+01 7.199742431640625000e+02 +2.269814579442249425e+01 7.209830322265625000e+02 +2.270823507753744153e+01 7.218369140625000000e+02 +2.271832381431851644e+01 7.228035888671875000e+02 +2.272841200452298693e+01 7.239805297851562500e+02 +2.273849964790815648e+01 7.257156372070312500e+02 +2.274858674423131077e+01 7.282328491210937500e+02 +2.275867329324978172e+01 7.312168579101562500e+02 +2.276875929472089055e+01 7.347234497070312500e+02 +2.277884474840197626e+01 7.381640014648437500e+02 +2.278892965405040627e+01 7.412819824218750000e+02 +2.279901401142353379e+01 7.431138916015625000e+02 +2.280909782027875821e+01 7.434581298828125000e+02 +2.281918108037346116e+01 7.424673461914062500e+02 +2.282926379146505624e+01 7.402312011718750000e+02 +2.283934595331096773e+01 7.371472778320312500e+02 +2.284942756566862343e+01 7.339555053710937500e+02 +2.285950862829547603e+01 7.312662353515625000e+02 +2.286958914094899242e+01 7.293098144531250000e+02 +2.287966910338663595e+01 7.285246582031250000e+02 +2.288974851536589838e+01 7.290500488281250000e+02 +2.289982737664428214e+01 7.309379882812500000e+02 +2.290990568697930385e+01 7.337244873046875000e+02 +2.291998344612848371e+01 7.365943603515625000e+02 +2.293006065384937031e+01 7.393100585937500000e+02 +2.294013730989951583e+01 7.413662719726562500e+02 +2.295021341403648307e+01 7.427991943359375000e+02 +2.296028896601785618e+01 7.436527099609375000e+02 +2.297036396560122995e+01 7.442127685546875000e+02 +2.298043841254420983e+01 7.453989257812500000e+02 +2.299051230660441547e+01 7.478574218750000000e+02 +2.300058564753948431e+01 7.513333129882812500e+02 +2.301065843510706088e+01 7.549323730468750000e+02 +2.302073066906480037e+01 7.580065917968750000e+02 +2.303080234917038283e+01 7.598173217773437500e+02 +2.304087347518149542e+01 7.599035034179687500e+02 +2.305094404685583953e+01 7.578930053710937500e+02 +2.306101406395112718e+01 7.543052978515625000e+02 +2.307108352622508107e+01 7.493798828125000000e+02 +2.308115243343544520e+01 7.439818115234375000e+02 +2.309122078533997424e+01 7.387448730468750000e+02 +2.310128858169644062e+01 7.341444702148437500e+02 +2.311135582226260610e+01 7.307239990234375000e+02 +2.312142250679628219e+01 7.284132690429687500e+02 +2.313148863505528041e+01 7.273411254882812500e+02 +2.314155420679740160e+01 7.268229370117187500e+02 +2.315161922178048926e+01 7.268020629882812500e+02 +2.316168367976239395e+01 7.271885986328125000e+02 +2.317174758050097694e+01 7.274960327148437500e+02 +2.318181092375411012e+01 7.277974243164062500e+02 +2.319187370927967251e+01 7.280038452148437500e+02 +2.320193593683557509e+01 7.286057739257812500e+02 +2.321199760617973595e+01 7.295895996093750000e+02 +2.322205871707007319e+01 7.311789550781250000e+02 +2.323211926926454041e+01 7.336929931640625000e+02 +2.324217926252106992e+01 7.368277587890625000e+02 +2.325223869659765086e+01 7.404786376953125000e+02 +2.326229757125225817e+01 7.436553344726562500e+02 +2.327235588624288809e+01 7.462640380859375000e+02 +2.328241364132754754e+01 7.475523071289062500e+02 +2.329247083626426118e+01 7.473375854492187500e+02 +2.330252747081105724e+01 7.457058105468750000e+02 +2.331258354472599592e+01 7.428192749023437500e+02 +2.332263905776712676e+01 7.394597167968750000e+02 +2.333269400969253837e+01 7.359791259765625000e+02 +2.334274840026031228e+01 7.332334594726562500e+02 +2.335280222922855131e+01 7.312667236328125000e+02 +2.336285549635537961e+01 7.300323486328125000e+02 +2.337290820139891778e+01 7.297034912109375000e+02 +2.338296034411731483e+01 7.297107543945312500e+02 +2.339301192426873044e+01 7.302896728515625000e+02 +2.340306294161133138e+01 7.307846069335937500e+02 +2.341311339590329155e+01 7.313889770507812500e+02 +2.342316328690282745e+01 7.321438598632812500e+02 +2.343321261436813785e+01 7.331349487304687500e+02 +2.344326137805745702e+01 7.348269042968750000e+02 +2.345330957772901570e+01 7.364448852539062500e+02 +2.346335721314106237e+01 7.385614013671875000e+02 +2.347340428405187396e+01 7.404287719726562500e+02 +2.348345079021972026e+01 7.415915527343750000e+02 +2.349349673140290307e+01 7.419190673828125000e+02 +2.350354210735972060e+01 7.409401855468750000e+02 +2.351358691784848531e+01 7.391195068359375000e+02 +2.352363116262755227e+01 7.363842163085937500e+02 +2.353367484145524813e+01 7.337169799804687500e+02 +2.354371795408994572e+01 7.313314208984375000e+02 +2.355376050029002144e+01 7.293485107421875000e+02 +2.356380247981384812e+01 7.283361816406250000e+02 +2.357384389241984834e+01 7.280439453125000000e+02 +2.358388473786641626e+01 7.286974487304687500e+02 +2.359392501591198865e+01 7.298231811523437500e+02 +2.360396472631501652e+01 7.311641845703125000e+02 +2.361400386883394376e+01 7.327922973632812500e+02 +2.362404244322725688e+01 7.342432250976562500e+02 +2.363408044925341400e+01 7.358976440429687500e+02 +2.364411788667093361e+01 7.372161254882812500e+02 +2.365415475523832001e+01 7.387265625000000000e+02 +2.366419105471409523e+01 7.411078491210937500e+02 +2.367422678485679910e+01 7.441171264648437500e+02 +2.368426194542498564e+01 7.478220214843750000e+02 +2.369429653617721598e+01 7.515314331054687500e+02 +2.370433055687207613e+01 7.549827270507812500e+02 +2.371436400726815208e+01 7.574323730468750000e+02 +2.372439688712405825e+01 7.581129150390625000e+02 +2.373442919619840552e+01 7.569374389648437500e+02 +2.374446093424983317e+01 7.539581909179687500e+02 +2.375449210103699116e+01 7.498588867187500000e+02 +2.376452269631854008e+01 7.452626342773437500e+02 +2.377455271985315477e+01 7.407767944335937500e+02 +2.378458217139952779e+01 7.368407592773437500e+02 +2.379461105071635174e+01 7.337199707031250000e+02 +2.380463935756235117e+01 7.319590454101562500e+02 +2.381466709169626128e+01 7.312636108398437500e+02 +2.382469425287681730e+01 7.314065551757812500e+02 +2.383472084086278286e+01 7.318652954101562500e+02 +2.384474685541292871e+01 7.322704467773437500e+02 +2.385477229628603624e+01 7.326260986328125000e+02 +2.386479716324091171e+01 7.325028076171875000e+02 +2.387482145603636141e+01 7.321464233398437500e+02 +2.388484517443121646e+01 7.317039184570312500e+02 +2.389486831818432577e+01 7.312595825195312500e+02 +2.390489088705452403e+01 7.312902221679687500e+02 +2.391491288080069921e+01 7.315507202148437500e+02 +2.392493429918172509e+01 7.320613403320312500e+02 +2.393495514195649321e+01 7.327878417968750000e+02 +2.394497540888393061e+01 7.332899169921875000e+02 +2.395499509972294305e+01 7.334602050781250000e+02 +2.396501421423247891e+01 7.331026000976562500e+02 +2.397503275217148300e+01 7.321578979492187500e+02 +2.398505071329892147e+01 7.307741699218750000e+02 +2.399506809737378532e+01 7.293054199218750000e+02 +2.400508490415505491e+01 7.277282104492187500e+02 +2.401510113340173547e+01 7.265909423828125000e+02 +2.402511678487286417e+01 7.258944702148437500e+02 +2.403513185832746046e+01 7.254949951171875000e+02 +2.404514635352458640e+01 7.253560180664062500e+02 +2.405516027022328629e+01 7.254898681640625000e+02 +2.406517360818265416e+01 7.258839111328125000e+02 +2.407518636716177340e+01 7.262957763671875000e+02 +2.408519854691975226e+01 7.266030273437500000e+02 +2.409521014721569898e+01 7.272586669921875000e+02 +2.410522116780876445e+01 7.283383178710937500e+02 +2.411523160845808178e+01 7.295882568359375000e+02 +2.412524146892280896e+01 7.311176147460937500e+02 +2.413525074896212885e+01 7.329538574218750000e+02 +2.414525944833522431e+01 7.346743774414062500e+02 +2.415526756680129949e+01 7.361858520507812500e+02 +2.416527510411957635e+01 7.371785888671875000e+02 +2.417528206004927682e+01 7.375834350585937500e+02 +2.418528843434964060e+01 7.372282104492187500e+02 +2.419529422677993935e+01 7.361264648437500000e+02 +2.420529943709943765e+01 7.345433349609375000e+02 +2.421530406506742850e+01 7.329206542968750000e+02 +2.422530811044319421e+01 7.315998535156250000e+02 +2.423531157298607042e+01 7.309475708007812500e+02 +2.424531445245537498e+01 7.311697998046875000e+02 +2.425531674861045417e+01 7.322641601562500000e+02 +2.426531846121065783e+01 7.339467773437500000e+02 +2.427531959001535711e+01 7.358123779296875000e+02 +2.428532013478395157e+01 7.375750122070312500e+02 +2.429532009527581593e+01 7.386431884765625000e+02 +2.430531947125038172e+01 7.388706665039062500e+02 +2.431531826246706274e+01 7.384968872070312500e+02 +2.432531646868530473e+01 7.378012084960937500e+02 +2.433531408966457121e+01 7.367724609375000000e+02 +2.434531112516431506e+01 7.355616455078125000e+02 +2.435530757494402465e+01 7.346156616210937500e+02 +2.436530343876320615e+01 7.335180053710937500e+02 +2.437529871638135859e+01 7.323394775390625000e+02 +2.438529340755801655e+01 7.310717163085937500e+02 +2.439528751205271107e+01 7.292959594726562500e+02 +2.440528102962499801e+01 7.274204101562500000e+02 +2.441527396003445105e+01 7.253792724609375000e+02 +2.442526630304064028e+01 7.231632080078125000e+02 +2.443525805840317844e+01 7.212538452148437500e+02 +2.444524922588165339e+01 7.197088623046875000e+02 +2.445523980523570629e+01 7.184276123046875000e+02 +2.446522979622497473e+01 7.174297485351562500e+02 +2.447521919860909989e+01 7.168062133789062500e+02 +2.448520801214776199e+01 7.164501342773437500e+02 +2.449519623660062706e+01 7.160970458984375000e+02 +2.450518387172739665e+01 7.149804077148437500e+02 +2.451517091728778652e+01 7.146787109375000000e+02 +2.452515737304150889e+01 7.149536743164062500e+02 +2.453514323874830794e+01 7.157740478515625000e+02 +2.454512851416793495e+01 7.169461059570312500e+02 +2.455511319906015544e+01 7.186826171875000000e+02 +2.456509729318474200e+01 7.211811523437500000e+02 +2.457508079630150277e+01 7.237723999023437500e+02 +2.458506370817023523e+01 7.261978759765625000e+02 +2.459504602855076882e+01 7.279750976562500000e+02 +2.460502775720294011e+01 7.287985229492187500e+02 +2.461500889388658209e+01 7.285061035156250000e+02 +2.462498943836158816e+01 7.271100463867187500e+02 +2.463496939038782330e+01 7.251331787109375000e+02 +2.464494874972518446e+01 7.227344970703125000e+02 +2.465492751613357925e+01 7.202164306640625000e+02 +2.466490568937292238e+01 7.181744384765625000e+02 +2.467488326920316410e+01 7.166704101562500000e+02 +2.468486025538424755e+01 7.158450317382812500e+02 +2.469483664767613718e+01 7.157779541015625000e+02 +2.470481244583881875e+01 7.162350463867187500e+02 +2.471478764963227803e+01 7.168270263671875000e+02 +2.472476225881653278e+01 7.173079833984375000e+02 +2.473473627315159362e+01 7.178070068359375000e+02 +2.474470969239750318e+01 7.183842163085937500e+02 +2.475468251631431826e+01 7.189402465820312500e+02 +2.476465474466209571e+01 7.197562255859375000e+02 +2.477462637720091720e+01 7.207651977539062500e+02 +2.478459741369087865e+01 7.217820434570312500e+02 +2.479456785389209017e+01 7.229616088867187500e+02 +2.480453769756467253e+01 7.238117065429687500e+02 +2.481450694446876071e+01 7.241400756835937500e+02 +2.482447559436450746e+01 7.235448608398437500e+02 +2.483444364701207618e+01 7.217908935546875000e+02 +2.484441110217165516e+01 7.193190917968750000e+02 +2.485437795960343266e+01 7.164542236328125000e+02 +2.486434421906761827e+01 7.134727172851562500e+02 +2.487430988032443580e+01 7.107712402343750000e+02 +2.488427494313411970e+01 7.084592895507812500e+02 +2.489423940725692574e+01 7.067798461914062500e+02 +2.490420327245312038e+01 7.057015380859375000e+02 +2.491416653848298779e+01 7.051328125000000000e+02 +2.492412920510681928e+01 7.050150756835937500e+02 +2.493409127208492038e+01 7.050321044921875000e+02 +2.494405273917761789e+01 7.051548461914062500e+02 +2.495401360614525643e+01 7.055763549804687500e+02 +2.496397387274818769e+01 7.058337402343750000e+02 +2.497393353874677047e+01 7.064252319335937500e+02 +2.498389260390139910e+01 7.072668457031250000e+02 +2.499385106797246436e+01 7.065288696289062500e+02 +2.500380893072036770e+01 7.054935913085937500e+02 +2.501376619190554962e+01 7.126982421875000000e+02 +2.502372285128843998e+01 7.167177734375000000e+02 +2.503367890862949352e+01 7.189946899414062500e+02 +2.504363436368919693e+01 7.198128051757812500e+02 +2.505358921622800494e+01 7.194262695312500000e+02 +2.506354346600643268e+01 7.179938354492187500e+02 +2.507349711278498461e+01 7.161674194335937500e+02 +2.508345015632419717e+01 7.141243896484375000e+02 +2.509340259638460680e+01 7.119711303710937500e+02 +2.510335443272676059e+01 7.102283325195312500e+02 +2.511330566511124118e+01 7.088727416992187500e+02 +2.512325629329863119e+01 7.080216674804687500e+02 +2.513320631704952746e+01 7.075119628906250000e+02 +2.514315573612454457e+01 7.072391967773437500e+02 +2.515310455028431136e+01 7.072614135742187500e+02 +2.516305275928946372e+01 7.073866577148437500e+02 +2.517300036290067311e+01 7.075297241210937500e+02 +2.518294736087860031e+01 7.075089721679687500e+02 +2.519289375298394162e+01 7.077084350585937500e+02 +2.520283953897738627e+01 7.079507446289062500e+02 +2.521278471861965542e+01 7.082542114257812500e+02 +2.522272929167148803e+01 7.085427246093750000e+02 +2.523267325789361948e+01 7.030917968750000000e+02 +2.524261661704681003e+01 7.016478881835937500e+02 +2.525255936889183772e+01 7.072658081054687500e+02 +2.526250151318949122e+01 7.091431274414062500e+02 +2.527244304970056987e+01 7.099122924804687500e+02 +2.528238397818589078e+01 7.102598266601562500e+02 +2.529232429840629237e+01 7.105624389648437500e+02 +2.530226401012262727e+01 7.108206787109375000e+02 +2.531220311309574811e+01 7.112144775390625000e+02 +2.532214160708652528e+01 7.118955078125000000e+02 +2.533207949185586472e+01 7.129283447265625000e+02 +2.534201676716466167e+01 7.139867553710937500e+02 +2.535195343277384694e+01 7.150387573242187500e+02 +2.536188948844434776e+01 7.163543090820312500e+02 +2.537182493393711624e+01 7.175852050781250000e+02 +2.538175976901311515e+01 7.183576049804687500e+02 +2.539169399343332856e+01 7.191803588867187500e+02 +2.540162760695874766e+01 7.204554443359375000e+02 +2.541156060935038141e+01 7.219538574218750000e+02 +2.542149300036925297e+01 7.236217041015625000e+02 +2.543142477977641036e+01 7.253292236328125000e+02 +2.544135594733288386e+01 7.269837036132812500e+02 +2.545128650279975346e+01 7.281899414062500000e+02 +2.546121644593811340e+01 7.290120239257812500e+02 +2.547114577650904010e+01 7.294706420898437500e+02 +2.548107449427366689e+01 7.294021606445312500e+02 +2.549100259899309151e+01 7.290330810546875000e+02 +2.550093009042848280e+01 7.285797729492187500e+02 +2.551085696834098115e+01 7.282573242187500000e+02 +2.552078323249176606e+01 7.281978759765625000e+02 +2.553070888264202054e+01 7.285422363281250000e+02 +2.554063391855293474e+01 7.293423461914062500e+02 +2.555055833998573789e+01 7.303726196289062500e+02 +2.556048214670165919e+01 7.315553588867187500e+02 +2.557040533846193142e+01 7.327090454101562500e+02 +2.558032791502782644e+01 7.338639526367187500e+02 +2.559024987616060898e+01 7.347703247070312500e+02 +2.560017122162157577e+01 7.355370483398437500e+02 +2.561009195117202708e+01 7.362696533203125000e+02 +2.562001206457328095e+01 7.370014648437500000e+02 +2.562993156158667674e+01 7.376657104492187500e+02 +2.563985044197355379e+01 7.382001953125000000e+02 +2.564976870549527987e+01 7.385095825195312500e+02 +2.565968635191323344e+01 7.383316040039062500e+02 +2.566960338098880712e+01 7.376373901367187500e+02 +2.567951979248340777e+01 7.363634033203125000e+02 +2.568943558615845646e+01 7.345343017578125000e+02 +2.569935076177539912e+01 7.323602294921875000e+02 +2.570926531909567103e+01 7.301770629882812500e+02 +2.571917925788076076e+01 7.278530883789062500e+02 +2.572909257789213555e+01 7.258315429687500000e+02 +2.573900527889129819e+01 7.240258178710937500e+02 +2.574891736063976211e+01 7.229814453125000000e+02 +2.575882882289904074e+01 7.222614135742187500e+02 +2.576873966543069372e+01 7.218745727539062500e+02 +2.577864988799626644e+01 7.216661987304687500e+02 +2.578855949035733275e+01 7.215791015625000000e+02 +2.579846847227549134e+01 7.218419189453125000e+02 +2.580837683351231604e+01 7.223738403320312500e+02 +2.581828457382945174e+01 7.232708740234375000e+02 +2.582819169298851136e+01 7.245108032226562500e+02 +2.583809819075115044e+01 7.259854736328125000e+02 +2.584800406687902452e+01 7.274253540039062500e+02 +2.585790932113381047e+01 7.288754882812500000e+02 +2.586781395327720645e+01 7.302161254882812500e+02 +2.587771796307090710e+01 7.312641601562500000e+02 +2.588762135027663902e+01 7.316588134765625000e+02 +2.589752411465614301e+01 7.314030151367187500e+02 +2.590742625597115989e+01 7.307059936523437500e+02 +2.591732777398346244e+01 7.299153442382812500e+02 +2.592722866845483765e+01 7.291088867187500000e+02 +2.593712893914706541e+01 7.284295654296875000e+02 +2.594702858582196825e+01 7.283231201171875000e+02 +2.595692760824136869e+01 7.286881713867187500e+02 +2.596682600616710701e+01 7.292031250000000000e+02 +2.597672377936104127e+01 7.300218505859375000e+02 +2.598662092758504016e+01 7.308476562500000000e+02 +2.599651745060099373e+01 7.312631225585937500e+02 +2.600641334817079908e+01 7.312466430664062500e+02 +2.601630862005637468e+01 7.310377807617187500e+02 +2.602620326601964607e+01 7.306655883789062500e+02 +2.603609728582256722e+01 7.300691528320312500e+02 +2.604599067922709565e+01 7.296461181640625000e+02 +2.605588344599521022e+01 7.293392333984375000e+02 +2.606577558588890398e+01 7.289664916992187500e+02 +2.607566709867017352e+01 7.285045166015625000e+02 +2.608555798410104742e+01 7.277998046875000000e+02 +2.609544824194356494e+01 7.268626708984375000e+02 +2.610533787195977595e+01 7.257697753906250000e+02 +2.611522687391174102e+01 7.248314819335937500e+02 +2.612511524756155268e+01 7.241414184570312500e+02 +2.613500299267131055e+01 7.235937500000000000e+02 +2.614489010900310362e+01 7.232962646484375000e+02 +2.615477659631908836e+01 7.231647338867187500e+02 +2.616466245438138571e+01 7.232199096679687500e+02 +2.617454768295215928e+01 7.235150756835937500e+02 +2.618443228179359750e+01 7.237353515625000000e+02 +2.619431625066786040e+01 7.241082763671875000e+02 +2.620419958933717908e+01 7.247976074218750000e+02 +2.621408229756374908e+01 7.256868286132812500e+02 +2.622396437510981926e+01 7.267857055664062500e+02 +2.623384582173763135e+01 7.282987670898437500e+02 +2.624372663720944487e+01 7.301199951171875000e+02 +2.625360682128755130e+01 7.319218750000000000e+02 +2.626348637373423855e+01 7.337121582031250000e+02 +2.627336529431180878e+01 7.352509155273437500e+02 +2.628324358278259965e+01 7.361162719726562500e+02 +2.629312123890893815e+01 7.362697753906250000e+02 +2.630299826245319039e+01 7.356388549804687500e+02 +2.631287465317772245e+01 7.344775390625000000e+02 +2.632275041084491818e+01 7.330850219726562500e+02 +2.633262553521717919e+01 7.316190795898437500e+02 +2.634250002605691421e+01 7.301757202148437500e+02 +2.635237388312657103e+01 7.290122070312500000e+02 +2.636224710618858325e+01 7.284886474609375000e+02 +2.637211969500541286e+01 7.283223876953125000e+02 +2.638199164933954322e+01 7.281744995117187500e+02 +2.639186296895346473e+01 7.282926025390625000e+02 +2.640173365360968560e+01 7.285200195312500000e+02 +2.641160370307072114e+01 7.289302978515625000e+02 +2.642147311709911506e+01 7.296677856445312500e+02 +2.643134189545741819e+01 7.303970336914062500e+02 +2.644121003790820978e+01 7.313483276367187500e+02 +2.645107754421405843e+01 7.326119384765625000e+02 +2.646094441413756471e+01 7.337763671875000000e+02 +2.647081064744136114e+01 7.347467041015625000e+02 +2.648067624388805541e+01 7.353169555664062500e+02 +2.649054120324030848e+01 7.356198730468750000e+02 +2.650040552526077064e+01 7.354995727539062500e+02 +2.651026920971211709e+01 7.347810058593750000e+02 +2.652013225635705140e+01 7.338282470703125000e+02 +2.652999466495826653e+01 7.326593627929687500e+02 +2.653985643527849092e+01 7.314519653320312500e+02 +2.654971756708046371e+01 7.305530395507812500e+02 +2.655957806012693112e+01 7.299657592773437500e+02 +2.656943791418067136e+01 7.296964721679687500e+02 +2.657929712900444841e+01 7.296464843750000000e+02 +2.658915570436107956e+01 7.299348144531250000e+02 +2.659901364001337498e+01 7.305820312500000000e+02 +2.660887093572416262e+01 7.311842041015625000e+02 +2.661872759125628818e+01 7.318494262695312500e+02 +2.662858360637260446e+01 7.327897338867187500e+02 +2.663843898083599271e+01 7.339793090820312500e+02 +2.664829371440934835e+01 7.353211669921875000e+02 +2.665814780685557039e+01 7.366682739257812500e+02 +2.666800125793759690e+01 7.377418212890625000e+02 +2.667785406741833398e+01 7.383219604492187500e+02 +2.668770623506076234e+01 7.383116455078125000e+02 +2.669755776062784136e+01 7.376874389648437500e+02 +2.670740864388254820e+01 7.366812133789062500e+02 +2.671725888458789910e+01 7.353932495117187500e+02 +2.672710848250688898e+01 7.341818847656250000e+02 +2.673695743740255537e+01 7.334310302734375000e+02 +2.674680574903795360e+01 7.330673828125000000e+02 +2.675665341717612833e+01 7.332625732421875000e+02 +2.676650044158017039e+01 7.339269409179687500e+02 +2.677634682201315997e+01 7.345960083007812500e+02 +2.678619255823820566e+01 7.353226318359375000e+02 +2.679603765001844096e+01 7.360580444335937500e+02 +2.680588209711699932e+01 7.368311157226562500e+02 +2.681572589929703199e+01 7.372202148437500000e+02 +2.682556905632171151e+01 7.372656250000000000e+02 +2.683541156795422111e+01 7.375628051757812500e+02 +2.684525343395775110e+01 7.379965820312500000e+02 +2.685509465409553798e+01 7.382749633789062500e+02 +2.686493522813079693e+01 7.381573486328125000e+02 +2.687477515582679288e+01 7.379122924804687500e+02 +2.688461443694676944e+01 7.374521484375000000e+02 +2.689445307125400930e+01 7.363959960937500000e+02 +2.690429105851181291e+01 7.351165161132812500e+02 +2.691412839848348781e+01 7.339475097656250000e+02 +2.692396509093235224e+01 7.326726684570312500e+02 +2.693380113562176348e+01 7.314790649414062500e+02 +2.694363653231505396e+01 7.304363403320312500e+02 +2.695347128077561294e+01 7.298671264648437500e+02 +2.696330538076682259e+01 7.297805175781250000e+02 +2.697313883205208285e+01 7.299288330078125000e+02 +2.698297163439482205e+01 7.302425537109375000e+02 +2.699280378755846144e+01 7.304841308593750000e+02 +2.700263529130646489e+01 7.305453491210937500e+02 +2.701246614540227853e+01 7.307169799804687500e+02 +2.702229634960939819e+01 7.309422607421875000e+02 +2.703212590369131618e+01 7.314143066406250000e+02 +2.704195480741154967e+01 7.318858642578125000e+02 +2.705178306053361581e+01 7.322851562500000000e+02 +2.706161066282107441e+01 7.329129028320312500e+02 +2.707143761403746751e+01 7.335102539062500000e+02 +2.708126391394637977e+01 7.335581054687500000e+02 +2.709108956231139942e+01 7.334583129882812500e+02 +2.710091455889613599e+01 7.332976074218750000e+02 +2.711073890346420256e+01 7.329574584960937500e+02 +2.712056259577924422e+01 7.323825073242187500e+02 +2.713038563560491667e+01 7.320144653320312500e+02 +2.714020802270487920e+01 7.316758422851562500e+02 +2.715002975684283015e+01 7.315045776367187500e+02 +2.715985083778245368e+01 7.314523315429687500e+02 +2.716967126528747656e+01 7.318482055664062500e+02 +2.717949103912162911e+01 7.320064086914062500e+02 +2.718931015904865944e+01 7.320278930664062500e+02 +2.719912862483233340e+01 7.322275390625000000e+02 +2.720894643623642040e+01 7.328175048828125000e+02 +2.721876359302472181e+01 7.329913330078125000e+02 +2.722858009496104614e+01 7.330700683593750000e+02 +2.723839594180922674e+01 7.332795410156250000e+02 +2.724821113333309697e+01 7.337283325195312500e+02 +2.725802566929651150e+01 7.339998779296875000e+02 +2.726783954946336053e+01 7.341961059570312500e+02 +2.727765277359751650e+01 7.341503295898437500e+02 +2.728746534146289449e+01 7.338649291992187500e+02 +2.729727725282340955e+01 7.336383666992187500e+02 +2.730708850744300165e+01 7.332101440429687500e+02 +2.731689910508562846e+01 7.323241577148437500e+02 +2.732670904551524416e+01 7.315139160156250000e+02 +2.733651832849584906e+01 7.310755004882812500e+02 +2.734632695379142930e+01 7.308010253906250000e+02 +2.735613492116600654e+01 7.308137817382812500e+02 +2.736594223038362017e+01 7.308848266601562500e+02 +2.737574888120830252e+01 7.308961791992187500e+02 +2.738555487340412853e+01 7.311405639648437500e+02 +2.739536020673517314e+01 7.318039550781250000e+02 +2.740516488096553260e+01 7.327438354492187500e+02 +2.741496889585932095e+01 7.336926879882812500e+02 +2.742477225118065220e+01 7.346126098632812500e+02 +2.743457494669367591e+01 7.357812500000000000e+02 +2.744437698216255939e+01 7.371552734375000000e+02 +2.745417835735146639e+01 7.379931640625000000e+02 +2.746397907202459621e+01 7.384804077148437500e+02 +2.747377912594614457e+01 7.380745239257812500e+02 +2.748357851888033920e+01 7.377653198242187500e+02 +2.749337725059142201e+01 7.369566040039062500e+02 +2.750317532084363492e+01 7.360452880859375000e+02 +2.751297272940125893e+01 7.345214233398437500e+02 +2.752276947602857504e+01 7.331337280273437500e+02 +2.753256556048988912e+01 7.321799316406250000e+02 +2.754236098254950704e+01 7.314841918945312500e+02 +2.755215574197177375e+01 7.312870483398437500e+02 +2.756194983852103775e+01 7.312186279296875000e+02 +2.757174327196165819e+01 7.311788940429687500e+02 +2.758153604205802623e+01 7.312631225585937500e+02 +2.759132814857452232e+01 7.312958984375000000e+02 +2.760111959127558023e+01 7.314622802734375000e+02 +2.761091036992561243e+01 7.315698242187500000e+02 +2.762070048428908109e+01 7.317319946289062500e+02 +2.763048993413043064e+01 7.321416625976562500e+02 +2.764027871921413748e+01 7.325386352539062500e+02 +2.765006683930471354e+01 7.327823486328125000e+02 +2.765985429416664942e+01 7.327935791015625000e+02 +2.766964108356448548e+01 7.325598754882812500e+02 +2.767942720726275141e+01 7.318623046875000000e+02 +2.768921266502600176e+01 7.312012329101562500e+02 +2.769899745661882307e+01 7.308709106445312500e+02 +2.770878158180579121e+01 7.306028442382812500e+02 +2.771856504035152113e+01 7.303837280273437500e+02 +2.772834783202063136e+01 7.302053833007812500e+02 +2.773812995657775815e+01 7.300330200195312500e+02 +2.774791141378755910e+01 7.301366577148437500e+02 +2.775769220341469179e+01 7.303701171875000000e+02 +2.776747232522384934e+01 7.308424072265625000e+02 +2.777725177897973907e+01 7.313559570312500000e+02 +2.778703056444707187e+01 7.321154174804687500e+02 +2.779680868139058703e+01 7.328516845703125000e+02 +2.780658612957502385e+01 7.337604980468750000e+02 +2.781636290876515361e+01 7.347424316406250000e+02 +2.782613901872576179e+01 7.358659667968750000e+02 +2.783591445922164098e+01 7.373141479492187500e+02 +2.784568923001761931e+01 7.376210327148437500e+02 +2.785546333087850712e+01 7.375791015625000000e+02 +2.786523676156915741e+01 7.373754882812500000e+02 +2.787500952185444092e+01 7.369012451171875000e+02 +2.788478161149922840e+01 7.362587280273437500e+02 +2.789455303026841904e+01 7.356065673828125000e+02 +2.790432377792691554e+01 7.348991088867187500e+02 +2.791409385423965261e+01 7.345482177734375000e+02 +2.792386325897157562e+01 7.347990722656250000e+02 +2.793363199188764057e+01 7.347173461914062500e+02 +2.794340005275282479e+01 7.353244628906250000e+02 +2.795316744133211273e+01 7.359252319335937500e+02 +2.796293415739052435e+01 7.363357543945312500e+02 +2.797270020069306540e+01 7.365950927734375000e+02 +2.798246557100479848e+01 7.365737304687500000e+02 +2.799223026809076131e+01 7.367456054687500000e+02 +2.800199429171604493e+01 7.369792480468750000e+02 +2.801175764164572257e+01 7.370290527343750000e+02 +2.802152031764489948e+01 7.368079833984375000e+02 +2.803128231947871285e+01 7.363236694335937500e+02 +2.804104364691227858e+01 7.362607421875000000e+02 +2.805080429971076583e+01 7.361060180664062500e+02 +2.806056427763934735e+01 7.353693847656250000e+02 +2.807032358046319231e+01 7.345913696289062500e+02 +2.808008220794751963e+01 7.338859863281250000e+02 +2.808984015985753757e+01 7.326786499023437500e+02 +2.809959743595848281e+01 7.318336181640625000e+02 +2.810935403601562044e+01 7.316445312500000000e+02 +2.811910995979419425e+01 7.315125122070312500e+02 +2.812886520705950133e+01 7.311190185546875000e+02 +2.813861977757684585e+01 7.306495971679687500e+02 +2.814837367111153199e+01 7.307339477539062500e+02 +2.815812688742890657e+01 7.311224365234375000e+02 +2.816787942629430219e+01 7.314318847656250000e+02 +2.817763128747309764e+01 7.318215332031250000e+02 +2.818738247073067171e+01 7.322834472656250000e+02 +2.819713297583241385e+01 7.332420654296875000e+02 +2.820688280254375258e+01 7.341570434570312500e+02 +2.821663195063010576e+01 7.345292358398437500e+02 +2.822638041985692325e+01 7.351846313476562500e+02 +2.823612820998967265e+01 7.355986938476562500e+02 +2.824587532079382513e+01 7.354273681640625000e+02 +2.825562175203488735e+01 7.350325317382812500e+02 +2.826536750347836602e+01 7.345165405273437500e+02 +2.827511257488978558e+01 7.340256958007812500e+02 +2.828485696603469535e+01 7.333426513671875000e+02 +2.829460067667865530e+01 7.328088378906250000e+02 +2.830434370658723964e+01 7.324367675781250000e+02 +2.831408605552605451e+01 7.323919067382812500e+02 +2.832382772326069897e+01 7.325646362304687500e+02 +2.833356870955679696e+01 7.326453247070312500e+02 +2.834330901418000437e+01 7.331408691406250000e+02 +2.835304863689596999e+01 7.331282348632812500e+02 +2.836278757747037460e+01 7.323607788085937500e+02 +2.837252583566891317e+01 7.328208007812500000e+02 +2.838226341125728069e+01 7.330665283203125000e+02 +2.839200030400122188e+01 7.329102783203125000e+02 +2.840173651366646723e+01 7.330521850585937500e+02 +2.841147204001877569e+01 7.326403808593750000e+02 +2.842120688282392038e+01 7.320308837890625000e+02 +2.843094104184769577e+01 7.315514526367187500e+02 +2.844067451685591053e+01 7.313831176757812500e+02 +2.845040730761438041e+01 7.312095336914062500e+02 +2.846013941388895319e+01 7.307658691406250000e+02 +2.846987083544548369e+01 7.305082397460937500e+02 +2.847960157204984455e+01 7.303758544921875000e+02 +2.848933162346793324e+01 7.305944824218750000e+02 +2.849906098946563660e+01 7.309609375000000000e+02 +2.850878966980889828e+01 7.309919433593750000e+02 +2.851851766426364776e+01 7.312819213867187500e+02 +2.852824497259583580e+01 7.315757446289062500e+02 +2.853797159457145227e+01 7.320529785156250000e+02 +2.854769752995646215e+01 7.331019287109375000e+02 +2.855742277851688726e+01 7.337909545898437500e+02 +2.856714734001874234e+01 7.344871826171875000e+02 +2.857687121422807053e+01 7.353767089843750000e+02 +2.858659440091092563e+01 7.355745849609375000e+02 +2.859631689983337921e+01 7.361062011718750000e+02 +2.860603871076150995e+01 7.363441162109375000e+02 +2.861575983346143559e+01 7.358413696289062500e+02 +2.862548026769927034e+01 7.355227661132812500e+02 +2.863520001324115682e+01 7.346726074218750000e+02 +2.864491906985323766e+01 7.333265380859375000e+02 +2.865463743730169455e+01 7.324672241210937500e+02 +2.866435511535271274e+01 7.314595947265625000e+02 +2.867407210377249527e+01 7.307822875976562500e+02 +2.868378840232725580e+01 7.308693847656250000e+02 +2.869350401078325064e+01 7.305715942382812500e+02 +2.870321892890671478e+01 7.305612792968750000e+02 +2.871293315646391875e+01 7.307781982421875000e+02 +2.872264669322116148e+01 7.307393798828125000e+02 +2.873235953894473838e+01 7.308393554687500000e+02 +2.874207169340097323e+01 7.310302734375000000e+02 +2.875178315635620052e+01 7.310723266601562500e+02 +2.876149392757676893e+01 7.309559326171875000e+02 +2.877120400682906265e+01 7.312838134765625000e+02 +2.878091339387945169e+01 7.313255004882812500e+02 +2.879062208849435223e+01 7.308954467773437500e+02 +2.880033009044018044e+01 7.305708007812500000e+02 +2.881003739948336673e+01 7.301951904296875000e+02 +2.881974401539037700e+01 7.297225952148437500e+02 +2.882944993792766653e+01 7.290471191406250000e+02 +2.883915516686172964e+01 7.284782714843750000e+02 +2.884885970195907845e+01 7.278118896484375000e+02 +2.885856354298621440e+01 7.274188232421875000e+02 +2.886826668970969578e+01 7.269110717773437500e+02 +2.887796914189605246e+01 7.265557250976562500e+02 +2.888767089931187115e+01 7.264169921875000000e+02 +2.889737196172373501e+01 7.263269042968750000e+02 +2.890707232889824496e+01 7.263858032226562500e+02 +2.891677200060202324e+01 7.267285156250000000e+02 +2.892647097660171340e+01 7.270115356445312500e+02 +2.893616925666395545e+01 7.270916748046875000e+02 +2.894586684055543557e+01 7.272241821289062500e+02 +2.895556372804282574e+01 7.279049072265625000e+02 +2.896525991889284057e+01 7.277188720703125000e+02 +2.897495541287219822e+01 7.274641723632812500e+02 +2.898465020974763107e+01 7.273262329101562500e+02 +2.899434430928590700e+01 7.274862670898437500e+02 +2.900403771125378327e+01 7.272969970703125000e+02 +2.901373041541805620e+01 7.266459960937500000e+02 +2.902342242154552565e+01 7.265253295898437500e+02 +2.903311372940301283e+01 7.260816650390625000e+02 +2.904280433875735667e+01 7.253526611328125000e+02 +2.905249424937541747e+01 7.256453247070312500e+02 +2.906218346102406613e+01 7.258809204101562500e+02 +2.907187197347018426e+01 7.256018066406250000e+02 +2.908155978648068185e+01 7.258160400390625000e+02 +2.909124689982248313e+01 7.262200317382812500e+02 +2.910093331326252653e+01 7.265287475585937500e+02 +2.911061902656776468e+01 7.265086059570312500e+02 +2.912030403950517510e+01 7.266036987304687500e+02 +2.912998835184175306e+01 7.268823242187500000e+02 +2.913967196334448317e+01 7.267356567382812500e+02 +2.914935487378041401e+01 7.260652465820312500e+02 +2.915903708291656926e+01 7.258501586914062500e+02 +2.916871859052001881e+01 7.253088378906250000e+02 +2.917839939635783253e+01 7.240382690429687500e+02 +2.918807950019708741e+01 7.238420410156250000e+02 +2.919775890180491018e+01 7.231333618164062500e+02 +2.920743760094842045e+01 7.223496704101562500e+02 +2.921711559739475561e+01 7.219378662109375000e+02 +2.922679289091108146e+01 7.213191528320312500e+02 +2.923646948126455669e+01 7.210366210937500000e+02 +2.924614536822239685e+01 7.207994384765625000e+02 +2.925582055155179972e+01 7.202911987304687500e+02 +2.926549503101998795e+01 7.202466430664062500e+02 +2.927516880639421260e+01 7.206307373046875000e+02 +2.928484187744172829e+01 7.205041503906250000e+02 +2.929451424392981451e+01 7.201030883789062500e+02 +2.930418590562576497e+01 7.195729980468750000e+02 +2.931385686229688758e+01 7.196536865234375000e+02 +2.932352711371051868e+01 7.202729492187500000e+02 +2.933319665963399459e+01 7.207434082031250000e+02 +2.934286549983468007e+01 7.208905639648437500e+02 +2.935253363407995764e+01 7.209297485351562500e+02 +2.936220106213721692e+01 7.207445068359375000e+02 +2.937186778377387242e+01 7.196271362304687500e+02 +2.938153379875735638e+01 7.186663818359375000e+02 +2.939119910685511883e+01 7.182761230468750000e+02 +2.940086370783461689e+01 7.176363525390625000e+02 +2.941052760146333256e+01 7.171066894531250000e+02 +2.942019078750876204e+01 7.164257202148437500e+02 +2.942985326573842997e+01 7.163071899414062500e+02 +2.943951503591986096e+01 7.166292114257812500e+02 +2.944917609782059387e+01 7.157980346679687500e+02 +2.945883645120821726e+01 7.149534301757812500e+02 +2.946849609585030194e+01 7.150408325195312500e+02 +2.947815503151444716e+01 7.151278076171875000e+02 +2.948781325796826991e+01 7.147857666015625000e+02 +2.949747077497940140e+01 7.147715454101562500e+02 +2.950712758231550836e+01 7.147714233398437500e+02 +2.951678367974423978e+01 7.138904418945312500e+02 +2.952643906703329435e+01 7.135582275390625000e+02 +2.953609374395036724e+01 7.130081787109375000e+02 +2.954574771026317848e+01 7.119794311523437500e+02 +2.955540096573947650e+01 7.109762573242187500e+02 +2.956505351014699556e+01 7.096732177734375000e+02 +2.957470534325351963e+01 7.089163208007812500e+02 +2.958435646482683623e+01 7.090888671875000000e+02 +2.959400687463474711e+01 7.087586669921875000e+02 +2.960365657244507887e+01 7.079938964843750000e+02 +2.961330555802566522e+01 7.070686035156250000e+02 +2.962295383114435765e+01 7.068906250000000000e+02 +2.963260139156905026e+01 7.062741088867187500e+02 +2.964224823906761586e+01 7.064904785156250000e+02 +2.965189437340796985e+01 7.061801757812500000e+02 +2.966153979435802768e+01 7.063544311523437500e+02 +2.967118450168574739e+01 7.061673583984375000e+02 +2.968082849515907995e+01 7.049190063476562500e+02 +2.969047177454599762e+01 7.043295898437500000e+02 +2.970011433961450464e+01 7.041221923828125000e+02 +2.970975619013260172e+01 7.034354858398437500e+02 +2.971939732586832150e+01 7.018108520507812500e+02 +2.972903774658971798e+01 7.002899169921875000e+02 +2.973867745206483448e+01 6.987274169921875000e+02 +2.974831644206177117e+01 6.980977783203125000e+02 +2.975795471634861045e+01 6.972084960937500000e+02 +2.976759227469347024e+01 6.955151977539062500e+02 +2.977722911686448981e+01 6.943611450195312500e+02 +2.978686524262980839e+01 6.932053222656250000e+02 +2.979650065175759721e+01 6.922052612304687500e+02 +2.980613534401603459e+01 6.917118530273437500e+02 +2.981576931917333084e+01 6.906671142578125000e+02 +2.982540257699768560e+01 6.893287353515625000e+02 +2.983503511725735535e+01 6.884641723632812500e+02 +2.984466693972057882e+01 6.869281616210937500e+02 +2.985429804415562671e+01 6.858670043945312500e+02 +2.986392843033079458e+01 6.849159545898437500e+02 +2.987355809801436735e+01 6.831806640625000000e+02 +2.988318704697469030e+01 6.815989990234375000e+02 +2.989281527698008389e+01 6.800095825195312500e+02 +2.990244278779891118e+01 6.780161743164062500e+02 +2.991206957919955300e+01 6.763162841796875000e+02 +2.992169565095037953e+01 6.746636352539062500e+02 +2.993132100281981778e+01 6.721287841796875000e+02 +2.994094563457628411e+01 6.697698974609375000e+02 +2.995056954598822330e+01 6.686368408203125000e+02 +2.996019273682409434e+01 6.667157592773437500e+02 +2.996981520685237399e+01 6.633543701171875000e+02 +2.997943695584156742e+01 6.601245117187500000e+02 +2.998905798356016206e+01 6.585920410156250000e+02 +2.999867828977670570e+01 6.563908081054687500e+02 +3.000829787425974260e+01 6.547699584960937500e+02 +3.001791673677783479e+01 6.543618164062500000e+02 +3.002753487709956204e+01 6.549055175781250000e+02 +3.003715229499352901e+01 6.544462280273437500e+02 +3.004676899022834391e+01 6.526001586914062500e+02 +3.005638496257264691e+01 6.500903930664062500e+02 +3.006600021179508531e+01 6.471275634765625000e+02 +3.007561473766432414e+01 6.441400146484375000e+02 +3.008522853994904978e+01 6.413679809570312500e+02 +3.009484161841797345e+01 6.406323852539062500e+02 +3.010445397283980640e+01 6.404413452148437500e+02 +3.011406560298329538e+01 6.397152099609375000e+02 +3.012367650861719071e+01 6.402265014648437500e+02 +3.013328668951026046e+01 6.419284057617187500e+02 +3.014289614543129758e+01 6.441613159179687500e+02 +3.015250487614911279e+01 6.459108886718750000e+02 +3.016211288143253455e+01 6.456569213867187500e+02 +3.017172016105039845e+01 6.455060424804687500e+02 +3.018132671477156492e+01 6.452680664062500000e+02 +3.019093254236491219e+01 6.441242065429687500e+02 +3.020053764359933623e+01 6.432831420898437500e+02 +3.021014201824375078e+01 6.439409790039062500e+02 +3.021974566606708024e+01 6.445250244140625000e+02 +3.022934858683827741e+01 6.472052612304687500e+02 +3.023895078032631289e+01 6.479945678710937500e+02 +3.024855224630015371e+01 6.476074218750000000e+02 +3.025815298452880953e+01 6.471079101562500000e+02 +3.026775299478129710e+01 6.462006225585937500e+02 +3.027735227682664743e+01 6.459758300781250000e+02 +3.028695083043391634e+01 6.456336059570312500e+02 +3.029654865537217034e+01 6.435211181640625000e+02 +3.030614575141050437e+01 6.420408935546875000e+02 +3.031574211831801335e+01 6.410575561523437500e+02 +3.032533775586382774e+01 6.398981323242187500e+02 +3.033493266381708864e+01 6.399980468750000000e+02 +3.034452684194694072e+01 6.402353515625000000e+02 +3.035412029002257128e+01 6.388941650390625000e+02 +3.036371300781317473e+01 6.385469360351562500e+02 +3.037330499508794546e+01 6.373518066406250000e+02 +3.038289625161613472e+01 6.366307983398437500e+02 +3.039248677716696889e+01 6.354168090820312500e+02 +3.040207657150971698e+01 6.349065551757812500e+02 +3.041166563441366577e+01 6.337166748046875000e+02 +3.042125396564810202e+01 6.332886962890625000e+02 +3.043084156498235515e+01 6.320107421875000000e+02 +3.044042843218574390e+01 6.292490844726562500e+02 +3.045001456702762610e+01 6.275798950195312500e+02 +3.045959996927737734e+01 6.258865356445312500e+02 +3.046918463870437321e+01 6.250547485351562500e+02 +3.047876857507802129e+01 6.243579711914062500e+02 +3.048835177816774689e+01 6.229226684570312500e+02 +3.049793424774298600e+01 6.204249267578125000e+02 +3.050751598357319594e+01 6.179489135742187500e+02 +3.051709698542784466e+01 6.160234375000000000e+02 +3.052667725307643565e+01 6.148678588867187500e+02 +3.053625678628847595e+01 6.122053833007812500e+02 +3.054583558483348682e+01 6.104899902343750000e+02 +3.055541364848101438e+01 6.080277099609375000e+02 +3.056499097700063317e+01 6.059958496093750000e+02 +3.057456757016190707e+01 6.034841918945312500e+02 +3.058414342773444972e+01 5.998724975585937500e+02 +3.059371854948786762e+01 5.968936157226562500e+02 +3.060329293519178862e+01 5.925253295898437500e+02 +3.061286658461587962e+01 5.899281616210937500e+02 +3.062243949752980043e+01 5.844417724609375000e+02 +3.063201167370323930e+01 5.809273071289062500e+02 +3.064158311290589864e+01 5.767075805664062500e+02 +3.065115381490750224e+01 5.691906738281250000e+02 +3.066072377947780225e+01 5.609036865234375000e+02 +3.067029300638652956e+01 5.543360595703125000e+02 +3.067986149540348251e+01 5.429436035156250000e+02 +3.068942924629844526e+01 5.312633056640625000e+02 +3.069899625884123040e+01 5.154556884765625000e+02 +3.070856253280167181e+01 5.002900695800781250e+02 +3.071812806794959982e+01 4.891472473144531250e+02 +3.072769286405489808e+01 4.822662048339843750e+02 +3.073725692088743955e+01 4.737917480468750000e+02 +3.074682023821711851e+01 4.685761718750000000e+02 +3.075638281581386480e+01 4.580401611328125000e+02 +3.076594465344760465e+01 4.409396057128906250e+02 +3.077550575088829632e+01 4.147247009277343750e+02 +3.078506610790590869e+01 3.878192749023437500e+02 +3.079462572427042844e+01 3.687083129882812500e+02 +3.080418459975187062e+01 3.584149169921875000e+02 +3.081374273412025389e+01 3.561899414062500000e+02 +3.082330012714561462e+01 3.596179809570312500e+02 +3.083285677859802476e+01 3.629963073730468750e+02 +3.084241268824755267e+01 3.744177551269531250e+02 +3.085196785586430224e+01 3.716326904296875000e+02 +3.086152228121838093e+01 3.373220825195312500e+02 +3.087107596407991750e+01 2.990657958984375000e+02 +3.088062890421906914e+01 2.483579406738281250e+02 +3.089018110140600726e+01 1.636023254394531250e+02 +3.089973255541090325e+01 9.403746795654296875e+01 +3.090928326600396403e+01 3.087844657897949219e+01 From cdc091ab8a2240e576f5bdde2b4bcbf707b2b0ed Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 12 Dec 2025 10:47:23 -0500 Subject: [PATCH 649/696] function for deprecation print message --- src/diffpy/utils/_deprecator.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 91f3c0fb..80b183e4 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -81,3 +81,28 @@ def wrapper(*args, **kwargs): ) return decorator + + +def deprecation_message(base, old_name, new_name, removal_version): + """Generate a deprecation message. + + Parameters + ---------- + base : str + The base module or class where the deprecated item resides. + old_name : str + The name of the deprecated item. + new_name : str + The name of the new item to use. + removal_version : str + The version when the deprecated item will be removed. + + Returns + ------- + str + A formatted deprecation message. + """ + return ( + f"'{base}.{old_name}' is deprecated and will be removed in " + f"version {removal_version}. Please use '{base}.{new_name}' instead." + ) From 8e65b06ea9e36b16e8e47e83139079b1dcac8d39 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 12 Dec 2025 10:49:34 -0500 Subject: [PATCH 650/696] news --- news/dep-msg-helper.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/dep-msg-helper.rst diff --git a/news/dep-msg-helper.rst b/news/dep-msg-helper.rst new file mode 100644 index 00000000..733801e6 --- /dev/null +++ b/news/dep-msg-helper.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add ``deprecation_message`` helper for printing consistent deprecation messages. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From e741a273fcc1ccf8dec9d6ff88721f55028204fe Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 12:46:16 -0500 Subject: [PATCH 651/696] scikit:recut diffpy.utils using latest scikit-package. --- .github/ISSUE_TEMPLATE/release_checklist.md | 2 +- .../workflows/build-wheel-release-upload.yml | 2 +- README.rst | 4 +- cookiecutter.json | 18 +++++++++ docs/source/index.rst | 4 +- pyproject.toml | 8 ++++ src/diffpy/utils/_deprecator.py | 4 +- src/diffpy/utils/diffraction_objects.py | 37 +++++++++++-------- src/diffpy/utils/parsers/serialization.py | 5 ++- src/diffpy/utils/resampler.py | 4 +- src/diffpy/utils/tools.py | 33 +++++++++-------- src/diffpy/utils/transforms.py | 16 +++++--- src/diffpy/utils/utils_app.py | 33 +++++++++++++++++ src/diffpy/utils/version.py | 9 +++-- src/diffpy/utils/wx/gridutils.py | 6 +-- 15 files changed, 129 insertions(+), 56 deletions(-) create mode 100644 cookiecutter.json create mode 100644 src/diffpy/utils/utils_app.py diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 6107962c..56bcd015 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -34,7 +34,7 @@ Please let the maintainer know that all checks are done and the package is ready - [ ] Ensure that the full release has appeared on PyPI successfully. -- [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. +- [ ] New package dependencies listed in `conda.txt` and `tests.txt` are added to `meta.yaml` in the feedstock. - [ ] Close any open issues on the feedstock. Reach out to the maintainer if you have questions. - [ ] Tag the maintainer for conda-forge release. diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 485aa356..e40144a7 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -7,7 +7,7 @@ on: - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml jobs: - release: + build-release: uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.utils diff --git a/README.rst b/README.rst index cf989cdf..388598b2 100644 --- a/README.rst +++ b/README.rst @@ -25,6 +25,7 @@ :target: https://anaconda.org/conda-forge/diffpy.utils .. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + :target: https://github.com/diffpy/diffpy.utils/pulls .. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.utils :target: https://pypi.org/project/diffpy.utils/ @@ -35,8 +36,7 @@ .. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue :target: https://github.com/diffpy/diffpy.utils/issues -diffpy.utils Package -======================================================================== +Shared utilities for diffpy packages. General utilities for analyzing diffraction data diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 00000000..30db1791 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,18 @@ +{ + "maintainer_name": "Simon Billinge", + "maintainer_email": "sb2896@columbia.edu", + "maintainer_github_username": "sbillinge", + "contributors": "Timur Davis, Chris Farrow, Pavol Juhas", + "license_holders": "The Trustees of Columbia University in the City of New York", + "project_name": "diffpy.utils", + "github_username_or_orgname": "diffpy", + "github_repo_name": "diffpy.utils", + "conda_pypi_package_dist_name": "diffpy.utils", + "package_dir_name": "diffpy.utils", + "project_short_description": "Shared utilities for diffpy packages.", + "project_keywords": "text data parsers, wx grid, diffraction objects", + "minimum_supported_python_version": "3.11", + "maximum_supported_python_version": "3.13", + "project_needs_c_code_compiled": "No", + "project_has_gui_tests": "No" +} diff --git a/docs/source/index.rst b/docs/source/index.rst index a406daa3..27359a3f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,7 +6,7 @@ diffpy.utils - General utilities for analyzing diffraction data -| Software version |release|. +| Software version |release| | Last updated |today|. The diffpy.utils package provides a number of functions and classes designed to help @@ -33,7 +33,7 @@ Illustrations of when and how one would use various diffpy.utils functions. Authors ======= -diffpy.utils is developed by members of the Billinge Group at +``diffpy.utils`` is developed by members of the Billinge Group at Columbia University and at Brookhaven National Laboratory including Pavol Juhás, Christopher L. Farrow, Simon J. L. Billinge, Andrew Yang, with contributions from many Billinge Group members and diff --git a/pyproject.toml b/pyproject.toml index ef9192f4..02377e62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,9 @@ include = ["*"] # package names should match these glob patterns (["*"] by defa exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) +[project.scripts] +diffpy-utils = "diffpy.utils.app:main" + [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} @@ -56,6 +59,11 @@ exclude-file = ".codespell/ignore_lines.txt" ignore-words = ".codespell/ignore_words.txt" skip = "*.cif,*.dat" +[tool.docformatter] +recursive = true +wrap-summaries = 72 +wrap-descriptions = 72 + [tool.black] line-length = 79 include = '\.pyi?$' diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 80b183e4..72172cae 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -11,8 +11,8 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): - """Deprecation decorator for functions and classes that is compatible with - Python versions prior to 3.13. + """Deprecation decorator for functions and classes that is + compatible with Python versions prior to 3.13. Examples -------- diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 733d3705..aa58033f 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -211,8 +211,8 @@ def __eq__(self, other): return True def __add__(self, other): - """Add a scalar value or another DiffractionObject to the yarray of the - DiffractionObject. + """Add a scalar value or another DiffractionObject to the yarray + of the DiffractionObject. Parameters ---------- @@ -262,8 +262,8 @@ def __add__(self, other): __radd__ = __add__ def __sub__(self, other): - """Subtract scalar value or another DiffractionObject to the yarray of - the DiffractionObject. + """Subtract scalar value or another DiffractionObject to the + yarray of the DiffractionObject. This method behaves similarly to the `__add__` method, but performs subtraction instead of addition. For details on parameters, returns @@ -290,8 +290,8 @@ def __sub__(self, other): __rsub__ = __sub__ def __mul__(self, other): - """Multiply a scalar value or another DiffractionObject with the yarray - of this DiffractionObject. + """Multiply a scalar value or another DiffractionObject with the + yarray of this DiffractionObject. This method behaves similarly to the `__add__` method, but performs multiplication instead of addition. For details on parameters, @@ -318,8 +318,8 @@ def __mul__(self, other): __rmul__ = __mul__ def __truediv__(self, other): - """Divide the yarray of this DiffractionObject by a scalar value or - another DiffractionObject. + """Divide the yarray of this DiffractionObject by a scalar value + or another DiffractionObject. This method behaves similarly to the `__add__` method, but performs division instead of addition. For details on parameters, returns, @@ -474,7 +474,8 @@ def _get_original_array(self): return self.on_d(), "d" def on_q(self): - """Return the tuple of two 1D numpy arrays containing q and y data. + """Return the tuple of two 1D numpy arrays containing q and y + data. Returns ------- @@ -484,7 +485,8 @@ def on_q(self): return [self.all_arrays[:, 1], self.all_arrays[:, 0]] def on_tth(self): - """Return the tuple of two 1D numpy arrays containing tth and y data. + """Return the tuple of two 1D numpy arrays containing tth and y + data. Returns ------- @@ -494,7 +496,8 @@ def on_tth(self): return [self.all_arrays[:, 2], self.all_arrays[:, 0]] def on_d(self): - """Return the tuple of two 1D numpy arrays containing d and y data. + """Return the tuple of two 1D numpy arrays containing d and y + data. Returns ------- @@ -506,8 +509,8 @@ def on_d(self): def scale_to( self, target_diff_object, q=None, tth=None, d=None, offset=None ): - """Return a new diffraction object which is the current object but - rescaled in y to the target. + """Return a new diffraction object which is the current object + but rescaled in y to the target. By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. Otherwise, y-value in @@ -568,7 +571,8 @@ def scale_to( return scaled_do def on_xtype(self, xtype): - """Return a tuple of two 1D numpy arrays containing x and y data. + """Return a tuple of two 1D numpy arrays containing x and y + data. Parameters ---------- @@ -597,8 +601,9 @@ def on_xtype(self, xtype): raise ValueError(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): - """Dump the xarray and yarray of the diffraction object to a two-column - file, with the associated information included in the header. + """Dump the xarray and yarray of the diffraction object to a + two-column file, with the associated information included in the + header. Parameters ---------- diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 7531c77f..b8ed0c60 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -33,8 +33,9 @@ def serialize_data( show_path=True, serial_file=None, ): - """Serialize file data into a dictionary. Can also save dictionary into a - serial language file. Dictionary is formatted as {filename: data}. + """Serialize file data into a dictionary. Can also save dictionary + into a serial language file. Dictionary is formatted as {filename: + data}. Requires hdata and data_table (can be generated by loadData). diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 4bf2d524..354eb37d 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -80,8 +80,8 @@ def wsinterp(x, xp, fp, left=None, right=None): def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): - """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon - grid. + """One-dimensional Whittaker-Shannon interpolation onto the Nyquist- + Shannon grid. Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. Uses the minimum number of points N required by the Nyquist diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3a42990d..d6f96174 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -51,8 +51,8 @@ def _load_config(file_path): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """Get name, email, and ORCID of the owner/user from various sources and - return it as a metadata dictionary. + """Get name, email, and ORCID of the owner/user from various sources + and return it as a metadata dictionary. The function looks for information in JSON configuration files named ``diffpyconfig.json``. These can be in the user's home directory and in @@ -109,8 +109,8 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def check_and_build_global_config(skip_config_creation=False): - """Check for a global diffpu config file in user's home directory and - creates one if it is missing. + """Check for a global diffpu config file in user's home directory + and creates one if it is missing. The file it looks for is called diffpyconfig.json. This can contain anything in json format, but minimally contains information about the @@ -234,7 +234,8 @@ def get_density_from_cloud(sample_composition, mp_token=""): def compute_mu_using_xraydb( sample_composition, energy, sample_mass_density=None, packing_fraction=None ): - """Compute the attenuation coefficient (mu) using the XrayDB database. + """Compute the attenuation coefficient (mu) using the XrayDB + database. Computes mu based on the sample composition and energy. User should provide a sample mass density or a packing fraction. @@ -287,8 +288,8 @@ def compute_mu_using_xraydb( def _top_hat(z, half_slit_width): - """Create a top-hat function, return 1.0 for values within the specified - slit width and 0 otherwise.""" + """Create a top-hat function, return 1.0 for values within the + specified slit width and 0 otherwise.""" return np.where((z >= -half_slit_width) & (z <= half_slit_width), 1.0, 0.0) @@ -319,9 +320,10 @@ def _model_function(z, diameter, z0, I0, mud, slope): def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): - """Extend z values and I values for padding (so that we don't have tails in - convolution), then perform convolution (note that the convolved I values - are the same as modeled I values if slit width is close to 0)""" + """Extend z values and I values for padding (so that we don't have + tails in convolution), then perform convolution (note that the + convolved I values are the same as modeled I values if slit width is + close to 0)""" n_points = len(z) z_left_pad = np.linspace( z.min() - n_points * (z[1] - z[0]), z.min(), n_points @@ -342,8 +344,8 @@ def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): def _objective_function(params, z, observed_data): """Compute the objective function for fitting a model to the - observed/experimental data by minimizing the sum of squared residuals - between the observed data and the convolved model data.""" + observed/experimental data by minimizing the sum of squared + residuals between the observed data and the convolved model data.""" diameter, half_slit_width, z0, I0, mud, slope = params convolved_model_data = _extend_z_and_convolve( z, diameter, half_slit_width, z0, I0, mud, slope @@ -353,7 +355,8 @@ def _objective_function(params, z, observed_data): def _compute_single_mud(z_data, I_data): - """Perform dual annealing optimization and extract the parameters.""" + """Perform dual annealing optimization and extract the + parameters.""" bounds = [ ( 1e-5, @@ -382,8 +385,8 @@ def _compute_single_mud(z_data, I_data): def compute_mud(filepath): - """Compute the best-fit mu*D value from a z-scan file, removing the sample - holder effect. + """Compute the best-fit mu*D value from a z-scan file, removing the + sample holder effect. This function loads z-scan data and fits it to a model that convolves a top-hat function with I = I0 * e^{-mu * l}. diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index ab1d009e..8cb07605 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -76,7 +76,8 @@ def q_to_tth(q, wavelength): def tth_to_q(tth, wavelength): - r"""Helper function to convert two-theta to q on independent variable axis. + r"""Helper function to convert two-theta to q on independent variable + axis. If wavelength is missing, returns independent variable axis as integer indexes. @@ -124,8 +125,8 @@ def tth_to_q(tth, wavelength): def q_to_d(q): - r"""Helper function to convert q to d on independent variable axis, using - :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d on independent variable axis, + using :math:`d = \frac{2 \pi}{q}`. Parameters ---------- @@ -144,7 +145,8 @@ def q_to_d(q): def tth_to_d(tth, wavelength): - r"""Helper function to convert two-theta to d on independent variable axis. + r"""Helper function to convert two-theta to d on independent variable + axis. The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. @@ -178,7 +180,8 @@ def tth_to_d(tth, wavelength): def d_to_q(d): - r"""Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d using :math:`d = \frac{2 + \pi}{q}`. Parameters ---------- @@ -197,7 +200,8 @@ def d_to_q(d): def d_to_tth(d, wavelength): - r"""Helper function to convert d to two-theta on independent variable axis. + r"""Helper function to convert d to two-theta on independent variable + axis. The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). diff --git a/src/diffpy/utils/utils_app.py b/src/diffpy/utils/utils_app.py new file mode 100644 index 00000000..588e28c9 --- /dev/null +++ b/src/diffpy/utils/utils_app.py @@ -0,0 +1,33 @@ +import argparse + +from diffpy.utils.version import __version__ # noqa + + +def main(): + parser = argparse.ArgumentParser( + prog="diffpy.utils", + description=( + "Shared utilities for diffpy packages.\n\n" + "For more information, visit: " + "https://github.com/diffpy/diffpy.utils/" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "--version", + action="store_true", + help="Show the program's version number and exit", + ) + + args = parser.parse_args() + + if args.version: + print(f"diffpy.utils {__version__}") + else: + # Default behavior when no arguments are given + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index a17ccb5c..d94610ae 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -18,8 +18,9 @@ # __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] # obtain version information -from importlib.metadata import version +from importlib.metadata import PackageNotFoundError, version -__version__ = version("diffpy.utils") - -# End of file +try: + __version__ = version("diffpy.utils") +except PackageNotFoundError: + __version__ = "unknown" diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index d6f2874a..d94860ca 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -146,9 +146,9 @@ def quickResizeColumns(grid, indices): def _indicesToBlocks(indices): - """Convert a list of integer indices to a list of (start, stop) tuples. The - (start, stop) tuple defines a continuous block, where the stop index is - included in the block. + """Convert a list of integer indices to a list of (start, stop) + tuples. The (start, stop) tuple defines a continuous block, where + the stop index is included in the block. Parameters ---------- From c8fbcfefb55cc29894c5a1bc90909abd0e0f6fc6 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 13:07:44 -0500 Subject: [PATCH 652/696] chore: add news item --- news/scikit-package.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scikit-package.rst diff --git a/news/scikit-package.rst b/news/scikit-package.rst new file mode 100644 index 00000000..9f7ee0e6 --- /dev/null +++ b/news/scikit-package.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: recut diffpy.utils using latest scikit-package + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From f0d2a4dcb4e8e810d68e3715533eade8768a249b Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 13:09:37 -0500 Subject: [PATCH 653/696] fix: change script character to underscore --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 02377e62..9799bbf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) [project.scripts] -diffpy-utils = "diffpy.utils.app:main" +diffpy-utils = "diffpy.utils_app:main" [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} From e6dbb3c13541b1b1d8a917793cf1693c0b8e8dbe Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Sun, 21 Dec 2025 13:25:31 -0600 Subject: [PATCH 654/696] remove packing fraction from the database mu calculation function --- src/diffpy/utils/tools.py | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index d6f96174..19f8e03b 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -231,49 +231,30 @@ def get_density_from_cloud(sample_composition, mp_token=""): ) -def compute_mu_using_xraydb( - sample_composition, energy, sample_mass_density=None, packing_fraction=None -): +def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density): """Compute the attenuation coefficient (mu) using the XrayDB database. - Computes mu based on the sample composition and energy. - User should provide a sample mass density or a packing fraction. - If neither density nor packing fraction is specified, - or if both are specified, a ValueError will be raised. - Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. + Computes mu based on the sample composition, X-ray energy, and + sample mass density. + + Reference: + https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu Parameters ---------- sample_composition : str The chemical formula of the material. energy : float - The energy of the incident x-rays in keV. - sample_mass_density : float, ``optional`` - The mass density of the packed powder/sample in g/cm*3. - Default is None. - packing_fraction : float, ``optional`` - The fraction of sample in the capillary (between 0 and 1). - Specify either sample_mass_density or packing_fraction but not both. - Default is None. + The energy of the incident X-rays in keV. + sample_mass_density : float + The mass density of the sample in g/cm^3. Returns ------- mu : float - The attenuation coefficient mu in mm^{-1}. + The attenuation coefficient μ in mm⁻¹. """ - if (sample_mass_density is None and packing_fraction is None) or ( - sample_mass_density is not None and packing_fraction is not None - ): - raise ValueError( - "You must specify either sample_mass_density or packing_fraction, " - "but not both. " - "Please rerun specifying only one." - ) - if packing_fraction is not None: - sample_mass_density = ( - get_density_from_cloud(sample_composition) * packing_fraction - ) energy_eV = energy * 1000 mu = ( material_mu( From 6300e2274c9c7a49782ab5ff3bfa03a2527a039e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Sun, 21 Dec 2025 13:26:34 -0600 Subject: [PATCH 655/696] remove the bad test --- tests/test_tools.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index 6be3870f..18c30fe3 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -9,7 +9,6 @@ from diffpy.utils.tools import ( _extend_z_and_convolve, check_and_build_global_config, - compute_mu_using_xraydb, compute_mud, get_package_info, get_user_info, @@ -270,40 +269,6 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected -@pytest.mark.parametrize( - "inputs", - [ - # Test when the function has invalid inputs - ( # C1: Both mass density and packing fraction are provided, - # expect ValueError exception - { - "sample_composition": "SiO2", - "energy": 10, - "sample_mass_density": 2.65, - "packing_fraction": 1, - } - ), - ( # C2: None of mass density or packing fraction are provided, - # expect ValueError exception - { - "sample_composition": "SiO2", - "energy": 10, - } - ), - ], -) -def test_compute_mu_using_xraydb_bad(inputs): - with pytest.raises( - ValueError, - match=( - "You must specify either sample_mass_density or " - "packing_fraction, but not both. " - "Please rerun specifying only one." - ), - ): - compute_mu_using_xraydb(**inputs) - - def test_compute_mud(tmp_path): diameter, slit_width, z0, I0, mud, slope = 1, 0.1, 0, 1e5, 3, 0 z_data = np.linspace(-1, 1, 50) From d41a81755afe1398a577c367e21cf59b040beb00 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Sun, 21 Dec 2025 13:28:35 -0600 Subject: [PATCH 656/696] news --- news/rm-packing-fraction.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/rm-packing-fraction.rst diff --git a/news/rm-packing-fraction.rst b/news/rm-packing-fraction.rst new file mode 100644 index 00000000..70bccad7 --- /dev/null +++ b/news/rm-packing-fraction.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Removed API that calculates mu from packing fraction. + +**Fixed:** + +* + +**Security:** + +* From 7cd01f1defe2413504188802bdf2c72a2523361a Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 17 Jan 2026 13:50:38 -0500 Subject: [PATCH 657/696] deprecated: loadData to load_data, and move it --- docs/source/examples/parsers_example.rst | 24 +-- docs/source/examples/resample_example.rst | 6 +- docs/source/utilities/parsers_utility.rst | 4 +- news/depr-tests.rst | 23 +++ src/diffpy/utils/_deprecator.py | 12 +- src/diffpy/utils/parsers/loaddata.py | 15 +- src/diffpy/utils/tools.py | 180 ++++++++++++++++++++++ tests/test_loaddata.py | 75 +++++++++ tests/test_serialization.py | 20 +-- 9 files changed, 327 insertions(+), 32 deletions(-) create mode 100644 news/depr-tests.rst diff --git a/docs/source/examples/parsers_example.rst b/docs/source/examples/parsers_example.rst index db97e0ed..19e607eb 100644 --- a/docs/source/examples/parsers_example.rst +++ b/docs/source/examples/parsers_example.rst @@ -13,13 +13,13 @@ Using the parsers module, we can load file data into simple and easy-to-work-wit Our goal will be to extract the data, and the parameters listed in the header, from this file and load it into our program. -2) To get the data table, we will use the ``loadData`` function. The default behavior of this +2) To get the data table, we will use the ``load_data`` function. The default behavior of this function is to find and extract a data table from a file. .. code-block:: python - from diffpy.utils.parsers.loaddata import loadData - data_table = loadData('') + from diffpy.utils.tools import load_data + data_table = load_data('') While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is due to the comments ``$ Phase Transition Near This Temperature Range`` and ``--> Note Significant Jump in Rw <--`` @@ -27,9 +27,9 @@ embedded within the dataset. To fix this, try using the ``comments`` parameter. .. code-block:: python - data_table = loadData('', comments=['$', '-->']) + data_table = load_data('', comments=['$', '-->']) -This parameter tells ``loadData`` that any lines beginning with ``$`` and ``-->`` are just comments and +This parameter tells ``load_data`` that any lines beginning with ``$`` and ``-->`` are just comments and more entries in our data table may follow. Here are a few other parameters to test out: @@ -39,7 +39,7 @@ Here are a few other parameters to test out: .. code-block:: python - loadData('', comments=['$', '-->'], delimiter=',') + load_data('', comments=['$', '-->'], delimiter=',') returns an empty list. * ``minrows=50``: Only look for data tables with at least 50 rows. Since our data table has much less than that many @@ -47,7 +47,7 @@ returns an empty list. .. code-block:: python - loadData('', comments=['$', '-->'], minrows=50) + load_data('', comments=['$', '-->'], minrows=50) returns an empty list. * ``usecols=[0, 3]``: Only return the 0th and 3rd columns (zero-indexed) of the data table. For ``data.txt``, this @@ -55,14 +55,14 @@ returns an empty list. .. code-block:: python - loadData('', comments=['$', '-->'], usecols=[0, 3]) + load_data('', comments=['$', '-->'], usecols=[0, 3]) -3) Next, to get the header information, we can again use ``loadData``, +3) Next, to get the header information, we can again use ``load_data``, but this time with the ``headers`` parameter enabled. .. code-block:: python - hdata = loadData('', comments=['$', '-->'], headers=True) + hdata = load_data('', comments=['$', '-->'], headers=True) 4) Rather than working with separate ``data_table`` and ``hdata`` objects, it may be easier to combine them into a single dictionary. We can do so using the ``serialize_data`` function. @@ -116,8 +116,8 @@ The returned value, ``parsed_file_data``, is the dictionary we just added to ``s .. code-block:: python - data_table = loadData('') - hdata = loadData('', headers=True) + data_table = load_data('') + hdata = load_data('', headers=True) serialize_data('', hdata, data_table, serial_file='') The serial file ``serialfile.json`` should now contain two entries: ``data.txt`` and ``moredata.txt``. diff --git a/docs/source/examples/resample_example.rst b/docs/source/examples/resample_example.rst index ba28390b..5af42e73 100644 --- a/docs/source/examples/resample_example.rst +++ b/docs/source/examples/resample_example.rst @@ -16,9 +16,9 @@ given enough datapoints. .. code-block:: python - from diffpy.utils.parsers.loaddata import loadData - nickel_datatable = loadData('') - nitarget_datatable = loadData('') + from diffpy.utils.tools import load_data + nickel_datatable = load_data('') + nitarget_datatable = load_data('') Each data table has two columns: first is the grid and second is the function value. To extract the columns, we can utilize the serialize function ... diff --git a/docs/source/utilities/parsers_utility.rst b/docs/source/utilities/parsers_utility.rst index ffaf768e..954405b5 100644 --- a/docs/source/utilities/parsers_utility.rst +++ b/docs/source/utilities/parsers_utility.rst @@ -5,7 +5,7 @@ Parsers Utility The ``diffpy.utils.parsers`` module allows users to easily and robustly load file data into a Python project. -- ``loaddata.loadData()``: Find and load a data table/block from a text file. This seems to work for most datafiles +- ``loaddata.load_data()``: Find and load a data table/block from a text file. This seems to work for most datafiles including those generated by diffpy programs. Running only ``numpy.loadtxt`` will result in errors for most these files as there is often excess data or parameters stored above the data block. Users can instead choose to load all the parameters of the form `` = `` into a dictionary @@ -17,7 +17,7 @@ The ``diffpy.utils.parsers`` module allows users to easily and robustly load fil - ``serialization.deserialize_data()``: Load data from a serial file format into a Python dictionary. Currently, the only supported serial format is ``.json``. -- ``serialization.serialize_data()``: Serialize the data generated by ``loadData()`` into a serial file format. Currently, the only +- ``serialization.serialize_data()``: Serialize the data generated by ``load_data()`` into a serial file format. Currently, the only supported serial format is ``.json``. For a more in-depth tutorial for how to use these parser utilities, click :ref:`here `. diff --git a/news/depr-tests.rst b/news/depr-tests.rst new file mode 100644 index 00000000..74f7c173 --- /dev/null +++ b/news/depr-tests.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* load_data now takes a Path or a string for the file-path + +**Deprecated:** + +* diffpy.utils.parsers.loaddata.loadData replaced by diffpy.utils.tools.load_data + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 72172cae..c504604a 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -20,7 +20,7 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): .. code-block:: python - from diffpy._deprecations import deprecated + from diffpy.utils._deprecator import deprecated import warnings @deprecated("old_function is deprecated; use new_function instead") @@ -39,7 +39,6 @@ def new_function(x, y): .. code-block:: python from diffpy._deprecations import deprecated - import warnings warnings.simplefilter("always", DeprecationWarning) @@ -83,7 +82,9 @@ def wrapper(*args, **kwargs): return decorator -def deprecation_message(base, old_name, new_name, removal_version): +def deprecation_message( + base, old_name, new_name, removal_version, new_base=None +): """Generate a deprecation message. Parameters @@ -102,7 +103,10 @@ def deprecation_message(base, old_name, new_name, removal_version): str A formatted deprecation message. """ + if new_base is None: + new_base = base return ( f"'{base}.{old_name}' is deprecated and will be removed in " - f"version {removal_version}. Please use '{base}.{new_name}' instead." + f"version {removal_version}. Please use '{new_base}.{new_name}' " + f"instead." ) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 05d37497..7de4204e 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -18,8 +18,21 @@ import numpy from diffpy.utils import validators +from diffpy.utils._deprecator import deprecated, deprecation_message +base = "diffpy.utils.parsers.loaddata" +removal_version = "4.0.0" +loaddata_deprecation_msg = deprecation_message( + base, + "loadData", + "load_data", + removal_version, + new_base="diffpy.utils.tools", +) + + +@deprecated(loaddata_deprecation_msg) def loadData( filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs ): @@ -254,7 +267,7 @@ def readfp(self, fp, append=False): File details include: * File name. - * All data blocks findable by loadData. + * All data blocks findable by load_data. * Headers (if present) for each data block. (Generally the headers contain column name information). """ diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 19f8e03b..4d4e19fb 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -8,6 +8,7 @@ from scipy.signal import convolve from xraydb import material_mu +from diffpy.utils import validators from diffpy.utils.parsers.loaddata import loadData @@ -396,3 +397,182 @@ def compute_mud(filepath): key=lambda pair: pair[1], ) return best_mud + + +def load_data( + filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs +): + """Find and load data from a text file. + + The data block is identified as the first matrix block of at least + minrows rows and constant number of columns. This seems to work for most + of the datafiles including those generated by diffpy programs. + + Parameters + ---------- + filename: Path or string + Name of the file we want to load data from. + minrows: int + Minimum number of rows in the first data block. All rows must have + the same number of floating point values. + headers: bool + when False (default), the function returns a numpy array of the data + in the data block. When True, the function instead returns a + dictionary of parameters and their corresponding values parsed from + header (information prior the data block). See hdel and hignore for + options to help with parsing header information. + hdel: str + (Only used when headers enabled.) Delimiter for parsing header + information (default '='). e.g. using default hdel, the line ' + parameter = p_value' is put into the dictionary as + {parameter: p_value}. + hignore: list + (Only used when headers enabled.) Ignore header rows beginning with + any elements in hignore. e.g. hignore=['# ', '['] causes the + following lines to be skipped: '# qmax=10', '[defaults]'. + kwargs: + Keyword arguments that are passed to numpy.loadtxt including the + following arguments below. (See numpy.loadtxt for more details.) Only + pass kwargs used by numpy.loadtxt. + + Useful kwargs + ============= + comments: str, sequence of str + The characters or list of characters used to indicate the start of a + comment (default '#'). Comment lines are ignored. + delimiter: str + Delimiter for the data in the block (default use whitespace). For + comma-separated data blocks, set delimiter to ','. + unpack: bool + Return data as a sequence of columns that allows tuple unpacking such + as x, y = load_data(FILENAME, unpack=True). Note transposing the + loaded array as load_data(FILENAME).T has the same effect. + usecols: + Zero-based index of columns to be loaded, by default use all detected + columns. The reading skips data blocks that do not have the usecols- + specified columns. + + Returns + ------- + data_block: ndarray + A numpy array containing the found data block. (This is not returned + if headers is enabled.) + hdata: dict + If headers are enabled, return a dictionary of parameters read from + the header. + """ + from numpy import array, loadtxt + + # for storing header data + hdata = {} + # determine the arguments + delimiter = kwargs.get("delimiter") + usecols = kwargs.get("usecols") + # required at least one column of floating point values + mincv = (1, 1) + # but if usecols is specified, require sufficient number of columns + # where the used columns contain floats + if usecols is not None: + hiidx = max(-min(usecols), max(usecols) + 1) + mincv = (hiidx, len(set(usecols))) + + # Check if a line consists of floats only and return their count + # Return zero if some strings cannot be converted. + def countcolumnsvalues(line): + try: + words = line.split(delimiter) + # remove trailing blank columns + while words and not words[-1].strip(): + words.pop(-1) + nc = len(words) + if usecols is not None: + nv = len([float(words[i]) for i in usecols]) + else: + nv = len([float(w) for w in words]) + except (IndexError, ValueError): + nc = nv = 0 + return nc, nv + + # Check if file exists before trying to open + filename = Path(filename) + if not filename.is_file(): + raise IOError( + ( + f"File {str(filename)} cannot be found. " + "Please rerun the program specifying a valid filename." + ) + ) + + # make sure fid gets cleaned up + with open(filename, "rb") as fid: + # search for the start of datablock + start = ncvblock = None + fpos = (0, 0) + nrows = 0 + for line in fid: + # decode line + dline = line.decode() + # find header information if requested + if headers: + hpair = dline.split(hdel) + flag = True + # ensure number of non-blank arguments is two + if len(hpair) != 2: + flag = False + else: + # ignore if an argument is blank + hpair[0] = hpair[0].strip() # name of data entry + hpair[1] = hpair[1].strip() # value of entry + if not hpair[0] or not hpair[1]: + flag = False + else: + # check if row has an ignore tag + if hignore is not None: + for tag in hignore: + taglen = len(tag) + if ( + len(hpair[0]) >= taglen + and hpair[0][:taglen] == tag + ): + flag = False + # add header data + if flag: + name = hpair[0] + value = hpair[1] + # check if data value should be stored as float + if validators.is_number(hpair[1]): + value = float(hpair[1]) + hdata.update({name: value}) + # continue search for the start of datablock + fpos = (fpos[1], fpos[1] + len(line)) + line = dline + ncv = countcolumnsvalues(line) + if ncv < mincv: + start = None + continue + # ncv is acceptable here, require the same number of columns + # throughout the datablock + if start is None or ncv != ncvblock: + ncvblock = ncv + nrows = 0 + start = fpos[0] + nrows += 1 + # block was found here! + if nrows >= minrows: + break + + # Return header data if requested + if headers: + return hdata # Return, so do not proceed to reading datablock + + # Return an empty array when no data found. + # loadtxt would otherwise raise an exception on loading from EOF. + if start is None: + data_block = array([], dtype=float) + else: + fid.seek(start) + # always use usecols argument so that loadtxt does not crash + # in case of trailing delimiters. + kwargs.setdefault("usecols", list(range(ncvblock[0]))) + data_block = loadtxt(fid, **kwargs) + return data_block diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index 82d947ee..95ac009f 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -6,6 +6,7 @@ import pytest from diffpy.utils.parsers.loaddata import loadData +from diffpy.utils.tools import load_data def test_loadData_default(datafile): @@ -80,3 +81,77 @@ def test_loadData_headers(datafile): loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore ) assert hdata == expected + + +def test_load_data_default(datafile): + """Check load_data() with default options.""" + loaddata01 = datafile("loaddata01.txt") + d2c = np.array([[3, 31], [4, 32], [5, 33]]) + + with pytest.raises(IOError) as err: + load_data("doesnotexist.txt") + assert str(err.value) == ( + "File doesnotexist.txt cannot be found. " + "Please rerun the program specifying a valid filename." + ) + + # The default minrows=10 makes it read from the third line + d = load_data(loaddata01) + assert np.array_equal(d2c, d) + + # The usecols=(0, 1) would make it read from the third line + d = load_data(loaddata01, minrows=1, usecols=(0, 1)) + assert np.array_equal(d2c, d) + + # Check the effect of usecols effect + d = load_data(loaddata01, usecols=(0,)) + assert np.array_equal(d2c[:, 0], d) + + d = load_data(loaddata01, usecols=(1,)) + assert np.array_equal(d2c[:, 1], d) + + +def test_load_data_1column(datafile): + """Check loading of one-column data.""" + loaddata01 = datafile("loaddata01.txt") + d1c = np.arange(1, 6) + + # Assertions using pytest's assert + d = load_data(loaddata01, usecols=[0], minrows=1) + assert np.array_equal(d1c, d) + + d = load_data(loaddata01, usecols=[0], minrows=2) + assert np.array_equal(d1c, d) + + d = load_data(loaddata01, usecols=[0], minrows=3) + assert not np.array_equal(d1c, d) + + +def test_load_data_headers(datafile): + """Check loadData() with headers options enabled.""" + expected = { + "wavelength": 0.1, + "dataformat": "Qnm", + "inputfile": "darkSub_rh20_C_01.chi", + "mode": "xray", + "bgscale": 1.2998929285, + "composition": "0.800.20", + "outputtype": "gr", + "qmaxinst": 25.0, + "qmin": 0.1, + "qmax": 25.0, + "rmax": "100.0r", + "rmin": "0.0r", + "rstep": "0.01r", + "rpoly": "0.9r", + } + + loaddatawithheaders = datafile("loaddatawithheaders.txt") + hignore = ["# ", "// ", "["] # ignore lines beginning with these strings + delimiter = ": " # what our data should be separated by + + # Load data with headers + hdata = load_data( + loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore + ) + assert hdata == expected diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 049d325c..33adb4ee 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -7,7 +7,7 @@ ImproperSizeError, UnsupportedTypeError, ) -from diffpy.utils.parsers.loaddata import loadData +from diffpy.utils.parsers.loaddata import load_data from diffpy.utils.parsers.serialization import deserialize_data, serialize_data @@ -21,9 +21,9 @@ def test_load_multiple(tmp_path, datafile): generated_data = None for headerfile in tlm_list: - # gather data using loadData - hdata = loadData(headerfile, headers=True) - data_table = loadData(headerfile) + # gather data using load_data + hdata = load_data(headerfile, headers=True) + data_table = load_data(headerfile) # check path extraction generated_data = serialize_data( @@ -60,8 +60,8 @@ def test_exceptions(datafile): loadfile = datafile("loadfile.txt") warningfile = datafile("generatewarnings.txt") nodt = datafile("loaddatawithheaders.txt") - hdata = loadData(loadfile, headers=True) - data_table = loadData(loadfile) + hdata = load_data(loadfile, headers=True) + data_table = load_data(loadfile) # improper file types with pytest.raises(UnsupportedTypeError): @@ -123,15 +123,15 @@ def test_exceptions(datafile): assert numpy.allclose(r_extract[data_name]["r"], r_list) assert numpy.allclose(gr_extract[data_name]["gr"], gr_list) # no datatable - nodt_hdata = loadData(nodt, headers=True) - nodt_dt = loadData(nodt) + nodt_hdata = load_data(nodt, headers=True) + nodt_dt = load_data(nodt) no_dt = serialize_data(nodt, nodt_hdata, nodt_dt, show_path=False) nodt_data_name = list(no_dt.keys())[0] assert numpy.allclose(no_dt[nodt_data_name]["data table"], nodt_dt) # ensure user is warned when columns are overwritten - hdata = loadData(warningfile, headers=True) - data_table = loadData(warningfile) + hdata = load_data(warningfile, headers=True) + data_table = load_data(warningfile) with pytest.warns(RuntimeWarning) as record: serialize_data( warningfile, From 0b46822b2c7b4fb721b9976c8c121cb11d1e3560 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 17 Jan 2026 14:06:34 -0500 Subject: [PATCH 658/696] fix: bad imports so tests pass --- src/diffpy/utils/parsers/serialization.py | 2 +- src/diffpy/utils/tools.py | 3 +-- tests/test_serialization.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index b8ed0c60..5acbbe41 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -37,7 +37,7 @@ def serialize_data( into a serial language file. Dictionary is formatted as {filename: data}. - Requires hdata and data_table (can be generated by loadData). + Requires hdata and data_table (can be generated by load_data). Parameters ---------- diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 4d4e19fb..94611a96 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -9,7 +9,6 @@ from xraydb import material_mu from diffpy.utils import validators -from diffpy.utils.parsers.loaddata import loadData def _stringify(string_value): @@ -391,7 +390,7 @@ def compute_mud(filepath): mu*D : float The best-fit mu*D value. """ - z_data, I_data = loadData(filepath, unpack=True) + z_data, I_data = load_data(filepath, unpack=True) best_mud, _ = min( (_compute_single_mud(z_data, I_data) for _ in range(20)), key=lambda pair: pair[1], diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 33adb4ee..e5bc8e1d 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -7,8 +7,8 @@ ImproperSizeError, UnsupportedTypeError, ) -from diffpy.utils.parsers.loaddata import load_data from diffpy.utils.parsers.serialization import deserialize_data, serialize_data +from diffpy.utils.tools import load_data def test_load_multiple(tmp_path, datafile): From 4a1ee184a07be3642d37b7655e7016114435fec2 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 18 Jan 2026 08:25:25 -0500 Subject: [PATCH 659/696] change: change structure so load_data is back in parsers --- src/diffpy/utils/parsers/__init__.py | 10 +- src/diffpy/utils/parsers/loaddata.py | 351 ++++++++++++++------------- src/diffpy/utils/tools.py | 181 +------------- tests/test_loaddata.py | 2 +- tests/test_serialization.py | 2 +- 5 files changed, 191 insertions(+), 355 deletions(-) diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index a0278e27..956f2e6d 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -6,10 +6,18 @@ # (c) 2010 The Trustees of Columbia University # in the City of New York. All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Simon Billinge # # See AUTHORS.txt for a list of people who contributed. # See LICENSE_DANSE.txt for license information. # ############################################################################## """Various utilities related to data parsing and manipulation.""" + +# this allows load_data to be imported from diffpy.utils.parsers +# it is needed during deprecation of the old loadData structure +# when we remove loadData we can move all the parser functionality +# a parsers.py module (like tools.py) and remove this if we want +from .loaddata import load_data + +__all__ = ["load_data"] diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 7de4204e..e48090e2 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -13,7 +13,7 @@ # ############################################################################## -import os +from pathlib import Path import numpy @@ -35,6 +35,178 @@ @deprecated(loaddata_deprecation_msg) def loadData( filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs +): + return load_data(filename, minrows, headers, hdel, hignore, **kwargs) + + +class TextDataLoader(object): + """Smart loading of a text data with possibly multiple datasets. + + Parameters + ---------- + minrows: int + Minimum number of rows in the first data block. (Default 10.) + usecols: tuple + Which columns in our dataset to use. Ignores all other columns. If + None (default), use all columns. + skiprows + Rows in dataset to skip. (Currently not functional.) + """ + + def __init__(self, minrows=10, usecols=None, skiprows=None): + if minrows is not None: + self.minrows = minrows + if usecols is not None: + self.usecols = tuple(usecols) + # FIXME: implement usage in _findDataBlocks + if skiprows is not None: + self.skiprows = skiprows + # data items + self._reset() + return + + def _reset(self): + self.filename = "" + self.headers = [] + self.datasets = [] + self._resetvars() + return + + def _resetvars(self): + self._filename = "" + self._lines = None + self._splitlines = None + self._words = None + self._linerecs = None + self._wordrecs = None + return + + def read(self, filename): + """Open a file and run readfp. + + Use if file is not already open for read byte. + """ + with open(filename, "rb") as fp: + self.readfp(fp) + return + + def readfp(self, fp, append=False): + """Get file details. + + File details include: + * File name. + * All data blocks findable by load_data. + * Headers (if present) for each data block. (Generally the headers + contain column name information). + """ + self._reset() + # try to read lines from fp first + self._lines = fp.readlines() + # and if good, assign filename + self.filename = getattr(fp, "name", "") + self._words = "".join(self._lines).split() + self._splitlines = [line.split() for line in self._lines] + self._findDataBlocks() + return + + def _findDataBlocks(self): + mincols = 1 + if self.usecols is not None and len(self.usecols): + mincols = max(mincols, max(self.usecols) + 1) + mincols = max(mincols, abs(min(self.usecols))) + nlines = len(self._lines) + nwords = len(self._words) + # idx - line index, nw0, nw1 - index of the first and last word, + # nf - number of words, ok - has data + self._linerecs = numpy.recarray( + (nlines,), + dtype=[ + ("idx", int), + ("nw0", int), + ("nw1", int), + ("nf", int), + ("ok", bool), + ], + ) + lr = self._linerecs + lr.idx = numpy.arange(nlines) + lr.nf = [len(sl) for sl in self._splitlines] + lr.nw1 = lr.nf.cumsum() + lr.nw0 = lr.nw1 - lr.nf + lr.ok = True + # word records + lw = self._wordrecs = numpy.recarray( + (nwords,), + dtype=[ + ("idx", int), + ("line", int), + ("col", int), + ("ok", bool), + ("value", float), + ], + ) + lw.idx = numpy.arange(nwords) + n1 = numpy.zeros(nwords, dtype=bool) + n1[lr.nw1[:-1]] = True + lw.line = n1.cumsum() + lw.col = lw.idx - lr.nw0[lw.line] + lw.ok = True + values = nwords * [0.0] + for i, w in enumerate(self._words): + try: + values[i] = float(w) + except ValueError: + lw.ok[i] = False + # prune lines that have a non-float values: + lw.values = values + if self.usecols is None: + badlines = lw.line[~lw.ok] + lr.ok[badlines] = False + else: + for col in self.usecols: + badlines = lw.line[(lw.col == col) & ~lw.ok] + lr.ok[badlines] = False + lr1 = lr[lr.nf >= mincols] + okb = numpy.r_[lr1.ok[:1], lr1.ok[1:] & ~lr1.ok[:-1], False] + oke = numpy.r_[False, ~lr1.ok[1:] & lr1.ok[:-1], lr1.ok[-1:]] + blockb = numpy.r_[True, lr1.nf[1:] != lr1.nf[:-1], False] + blocke = numpy.r_[False, blockb[1:-1], True] + beg = numpy.nonzero(okb | blockb)[0] + end = numpy.nonzero(oke | blocke)[0] + rowcounts = end - beg + assert not numpy.any(rowcounts < 0) + goodrows = rowcounts >= self.minrows + begend = numpy.transpose([beg, end - 1])[goodrows] + hbeg = 0 + for dbeg, dend in begend: + bb1 = lr1[dbeg] + ee1 = lr1[dend] + hend = bb1.idx + header = "".join(self._lines[hbeg:hend]) + hbeg = ee1.idx + 1 + if self.usecols is None: + data = numpy.reshape(lw.value[bb1.nw0 : ee1.nw1], (-1, bb1.nf)) + else: + tdata = numpy.empty( + (len(self.usecols), dend - dbeg), dtype=float + ) + for j, trow in zip(self.usecols, tdata): + j %= bb1.nf + trow[:] = lw.value[bb1.nw0 + j : ee1.nw1 : bb1.nf] + data = tdata.transpose() + self.headers.append(header) + self.datasets.append(data) + # finish reading to a last header and empty dataset + if hbeg < len(self._lines): + header = "".join(self._lines[hbeg:]) + data = numpy.empty(0, dtype=float) + self.headers.append(header) + self.datasets.append(data) + return + + +def load_data( + filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs ): """Find and load data from a text file. @@ -44,7 +216,7 @@ def loadData( Parameters ---------- - filename + filename: Path or string Name of the file we want to load data from. minrows: int Minimum number of rows in the first data block. All rows must have @@ -79,8 +251,8 @@ def loadData( comma-separated data blocks, set delimiter to ','. unpack: bool Return data as a sequence of columns that allows tuple unpacking such - as x, y = loadData(FILENAME, unpack=True). Note transposing the - loaded array as loadData(FILENAME).T has the same effect. + as x, y = load_data(FILENAME, unpack=True). Note transposing the + loaded array as load_data(FILENAME).T has the same effect. usecols: Zero-based index of columns to be loaded, by default use all detected columns. The reading skips data blocks that do not have the usecols- @@ -128,10 +300,11 @@ def countcolumnsvalues(line): return nc, nv # Check if file exists before trying to open - if not os.path.exists(filename): + filename = Path(filename) + if not filename.is_file(): raise IOError( ( - f"File {filename} cannot be found. " + f"File {str(filename)} cannot be found. " "Please rerun the program specifying a valid filename." ) ) @@ -209,169 +382,3 @@ def countcolumnsvalues(line): kwargs.setdefault("usecols", list(range(ncvblock[0]))) data_block = loadtxt(fid, **kwargs) return data_block - - -class TextDataLoader(object): - """Smart loading of a text data with possibly multiple datasets. - - Parameters - ---------- - minrows: int - Minimum number of rows in the first data block. (Default 10.) - usecols: tuple - Which columns in our dataset to use. Ignores all other columns. If - None (default), use all columns. - skiprows - Rows in dataset to skip. (Currently not functional.) - """ - - def __init__(self, minrows=10, usecols=None, skiprows=None): - if minrows is not None: - self.minrows = minrows - if usecols is not None: - self.usecols = tuple(usecols) - # FIXME: implement usage in _findDataBlocks - if skiprows is not None: - self.skiprows = skiprows - # data items - self._reset() - return - - def _reset(self): - self.filename = "" - self.headers = [] - self.datasets = [] - self._resetvars() - return - - def _resetvars(self): - self._filename = "" - self._lines = None - self._splitlines = None - self._words = None - self._linerecs = None - self._wordrecs = None - return - - def read(self, filename): - """Open a file and run readfp. - - Use if file is not already open for read byte. - """ - with open(filename, "rb") as fp: - self.readfp(fp) - return - - def readfp(self, fp, append=False): - """Get file details. - - File details include: - * File name. - * All data blocks findable by load_data. - * Headers (if present) for each data block. (Generally the headers - contain column name information). - """ - self._reset() - # try to read lines from fp first - self._lines = fp.readlines() - # and if good, assign filename - self.filename = getattr(fp, "name", "") - self._words = "".join(self._lines).split() - self._splitlines = [line.split() for line in self._lines] - self._findDataBlocks() - return - - def _findDataBlocks(self): - mincols = 1 - if self.usecols is not None and len(self.usecols): - mincols = max(mincols, max(self.usecols) + 1) - mincols = max(mincols, abs(min(self.usecols))) - nlines = len(self._lines) - nwords = len(self._words) - # idx - line index, nw0, nw1 - index of the first and last word, - # nf - number of words, ok - has data - self._linerecs = numpy.recarray( - (nlines,), - dtype=[ - ("idx", int), - ("nw0", int), - ("nw1", int), - ("nf", int), - ("ok", bool), - ], - ) - lr = self._linerecs - lr.idx = numpy.arange(nlines) - lr.nf = [len(sl) for sl in self._splitlines] - lr.nw1 = lr.nf.cumsum() - lr.nw0 = lr.nw1 - lr.nf - lr.ok = True - # word records - lw = self._wordrecs = numpy.recarray( - (nwords,), - dtype=[ - ("idx", int), - ("line", int), - ("col", int), - ("ok", bool), - ("value", float), - ], - ) - lw.idx = numpy.arange(nwords) - n1 = numpy.zeros(nwords, dtype=bool) - n1[lr.nw1[:-1]] = True - lw.line = n1.cumsum() - lw.col = lw.idx - lr.nw0[lw.line] - lw.ok = True - values = nwords * [0.0] - for i, w in enumerate(self._words): - try: - values[i] = float(w) - except ValueError: - lw.ok[i] = False - # prune lines that have a non-float values: - lw.values = values - if self.usecols is None: - badlines = lw.line[~lw.ok] - lr.ok[badlines] = False - else: - for col in self.usecols: - badlines = lw.line[(lw.col == col) & ~lw.ok] - lr.ok[badlines] = False - lr1 = lr[lr.nf >= mincols] - okb = numpy.r_[lr1.ok[:1], lr1.ok[1:] & ~lr1.ok[:-1], False] - oke = numpy.r_[False, ~lr1.ok[1:] & lr1.ok[:-1], lr1.ok[-1:]] - blockb = numpy.r_[True, lr1.nf[1:] != lr1.nf[:-1], False] - blocke = numpy.r_[False, blockb[1:-1], True] - beg = numpy.nonzero(okb | blockb)[0] - end = numpy.nonzero(oke | blocke)[0] - rowcounts = end - beg - assert not numpy.any(rowcounts < 0) - goodrows = rowcounts >= self.minrows - begend = numpy.transpose([beg, end - 1])[goodrows] - hbeg = 0 - for dbeg, dend in begend: - bb1 = lr1[dbeg] - ee1 = lr1[dend] - hend = bb1.idx - header = "".join(self._lines[hbeg:hend]) - hbeg = ee1.idx + 1 - if self.usecols is None: - data = numpy.reshape(lw.value[bb1.nw0 : ee1.nw1], (-1, bb1.nf)) - else: - tdata = numpy.empty( - (len(self.usecols), dend - dbeg), dtype=float - ) - for j, trow in zip(self.usecols, tdata): - j %= bb1.nf - trow[:] = lw.value[bb1.nw0 + j : ee1.nw1 : bb1.nf] - data = tdata.transpose() - self.headers.append(header) - self.datasets.append(data) - # finish reading to a last header and empty dataset - if hbeg < len(self._lines): - header = "".join(self._lines[hbeg:]) - data = numpy.empty(0, dtype=float) - self.headers.append(header) - self.datasets.append(data) - return diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 94611a96..42e43bc8 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -8,7 +8,7 @@ from scipy.signal import convolve from xraydb import material_mu -from diffpy.utils import validators +from diffpy.utils.parsers import load_data def _stringify(string_value): @@ -396,182 +396,3 @@ def compute_mud(filepath): key=lambda pair: pair[1], ) return best_mud - - -def load_data( - filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs -): - """Find and load data from a text file. - - The data block is identified as the first matrix block of at least - minrows rows and constant number of columns. This seems to work for most - of the datafiles including those generated by diffpy programs. - - Parameters - ---------- - filename: Path or string - Name of the file we want to load data from. - minrows: int - Minimum number of rows in the first data block. All rows must have - the same number of floating point values. - headers: bool - when False (default), the function returns a numpy array of the data - in the data block. When True, the function instead returns a - dictionary of parameters and their corresponding values parsed from - header (information prior the data block). See hdel and hignore for - options to help with parsing header information. - hdel: str - (Only used when headers enabled.) Delimiter for parsing header - information (default '='). e.g. using default hdel, the line ' - parameter = p_value' is put into the dictionary as - {parameter: p_value}. - hignore: list - (Only used when headers enabled.) Ignore header rows beginning with - any elements in hignore. e.g. hignore=['# ', '['] causes the - following lines to be skipped: '# qmax=10', '[defaults]'. - kwargs: - Keyword arguments that are passed to numpy.loadtxt including the - following arguments below. (See numpy.loadtxt for more details.) Only - pass kwargs used by numpy.loadtxt. - - Useful kwargs - ============= - comments: str, sequence of str - The characters or list of characters used to indicate the start of a - comment (default '#'). Comment lines are ignored. - delimiter: str - Delimiter for the data in the block (default use whitespace). For - comma-separated data blocks, set delimiter to ','. - unpack: bool - Return data as a sequence of columns that allows tuple unpacking such - as x, y = load_data(FILENAME, unpack=True). Note transposing the - loaded array as load_data(FILENAME).T has the same effect. - usecols: - Zero-based index of columns to be loaded, by default use all detected - columns. The reading skips data blocks that do not have the usecols- - specified columns. - - Returns - ------- - data_block: ndarray - A numpy array containing the found data block. (This is not returned - if headers is enabled.) - hdata: dict - If headers are enabled, return a dictionary of parameters read from - the header. - """ - from numpy import array, loadtxt - - # for storing header data - hdata = {} - # determine the arguments - delimiter = kwargs.get("delimiter") - usecols = kwargs.get("usecols") - # required at least one column of floating point values - mincv = (1, 1) - # but if usecols is specified, require sufficient number of columns - # where the used columns contain floats - if usecols is not None: - hiidx = max(-min(usecols), max(usecols) + 1) - mincv = (hiidx, len(set(usecols))) - - # Check if a line consists of floats only and return their count - # Return zero if some strings cannot be converted. - def countcolumnsvalues(line): - try: - words = line.split(delimiter) - # remove trailing blank columns - while words and not words[-1].strip(): - words.pop(-1) - nc = len(words) - if usecols is not None: - nv = len([float(words[i]) for i in usecols]) - else: - nv = len([float(w) for w in words]) - except (IndexError, ValueError): - nc = nv = 0 - return nc, nv - - # Check if file exists before trying to open - filename = Path(filename) - if not filename.is_file(): - raise IOError( - ( - f"File {str(filename)} cannot be found. " - "Please rerun the program specifying a valid filename." - ) - ) - - # make sure fid gets cleaned up - with open(filename, "rb") as fid: - # search for the start of datablock - start = ncvblock = None - fpos = (0, 0) - nrows = 0 - for line in fid: - # decode line - dline = line.decode() - # find header information if requested - if headers: - hpair = dline.split(hdel) - flag = True - # ensure number of non-blank arguments is two - if len(hpair) != 2: - flag = False - else: - # ignore if an argument is blank - hpair[0] = hpair[0].strip() # name of data entry - hpair[1] = hpair[1].strip() # value of entry - if not hpair[0] or not hpair[1]: - flag = False - else: - # check if row has an ignore tag - if hignore is not None: - for tag in hignore: - taglen = len(tag) - if ( - len(hpair[0]) >= taglen - and hpair[0][:taglen] == tag - ): - flag = False - # add header data - if flag: - name = hpair[0] - value = hpair[1] - # check if data value should be stored as float - if validators.is_number(hpair[1]): - value = float(hpair[1]) - hdata.update({name: value}) - # continue search for the start of datablock - fpos = (fpos[1], fpos[1] + len(line)) - line = dline - ncv = countcolumnsvalues(line) - if ncv < mincv: - start = None - continue - # ncv is acceptable here, require the same number of columns - # throughout the datablock - if start is None or ncv != ncvblock: - ncvblock = ncv - nrows = 0 - start = fpos[0] - nrows += 1 - # block was found here! - if nrows >= minrows: - break - - # Return header data if requested - if headers: - return hdata # Return, so do not proceed to reading datablock - - # Return an empty array when no data found. - # loadtxt would otherwise raise an exception on loading from EOF. - if start is None: - data_block = array([], dtype=float) - else: - fid.seek(start) - # always use usecols argument so that loadtxt does not crash - # in case of trailing delimiters. - kwargs.setdefault("usecols", list(range(ncvblock[0]))) - data_block = loadtxt(fid, **kwargs) - return data_block diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index 95ac009f..92c53571 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -5,8 +5,8 @@ import numpy as np import pytest +from diffpy.utils.parsers import load_data from diffpy.utils.parsers.loaddata import loadData -from diffpy.utils.tools import load_data def test_loadData_default(datafile): diff --git a/tests/test_serialization.py b/tests/test_serialization.py index e5bc8e1d..eeab5307 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -3,12 +3,12 @@ import numpy import pytest +from diffpy.utils.parsers import load_data from diffpy.utils.parsers.custom_exceptions import ( ImproperSizeError, UnsupportedTypeError, ) from diffpy.utils.parsers.serialization import deserialize_data, serialize_data -from diffpy.utils.tools import load_data def test_load_multiple(tmp_path, datafile): From 8d11075c07d46594572da1c7ed2d20918f5a12ea Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 18 Jan 2026 08:46:37 -0500 Subject: [PATCH 660/696] docs: change docstring so API docs are correctly updated --- src/diffpy/utils/_deprecator.py | 7 +++++++ src/diffpy/utils/parsers/loaddata.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index c504604a..8cc50cd7 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -110,3 +110,10 @@ def deprecation_message( f"version {removal_version}. Please use '{new_base}.{new_name}' " f"instead." ) + + +_DEPRECATION_DOCSTRING_TEMPLATE = ( + "This function has been deprecated and will be " + "removed in version {removal_version}. Please use" + "{new_base}.{new_name} instead." +) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index e48090e2..da422058 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -36,6 +36,11 @@ def loadData( filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs ): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.utils.parsers.load_data instead. + """ return load_data(filename, minrows, headers, hdel, hignore, **kwargs) From 34bfb90816314d9da2bd306427ddb13eff0be48a Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 18 Jan 2026 09:03:02 -0500 Subject: [PATCH 661/696] docs; fix typos in examples text that had load_data in tools --- docs/source/examples/parsers_example.rst | 2 +- docs/source/examples/resample_example.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/examples/parsers_example.rst b/docs/source/examples/parsers_example.rst index 19e607eb..747d0c4f 100644 --- a/docs/source/examples/parsers_example.rst +++ b/docs/source/examples/parsers_example.rst @@ -18,7 +18,7 @@ Using the parsers module, we can load file data into simple and easy-to-work-wit .. code-block:: python - from diffpy.utils.tools import load_data + from diffpy.utils.parsers import load_data data_table = load_data('') While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is diff --git a/docs/source/examples/resample_example.rst b/docs/source/examples/resample_example.rst index 5af42e73..32e3e02a 100644 --- a/docs/source/examples/resample_example.rst +++ b/docs/source/examples/resample_example.rst @@ -16,7 +16,7 @@ given enough datapoints. .. code-block:: python - from diffpy.utils.tools import load_data + from diffpy.utils.parsers import load_data nickel_datatable = load_data('') nitarget_datatable = load_data('') From 8e1d474a969192a4969efd0902dc37f1d51762cf Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 18 Jan 2026 09:40:57 -0500 Subject: [PATCH 662/696] feat: docstring generator function --- src/diffpy/utils/_deprecator.py | 70 +++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 72172cae..570be437 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -20,8 +20,7 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): .. code-block:: python - from diffpy._deprecations import deprecated - import warnings + from diffpy.utils._deprecator import deprecated, d @deprecated("old_function is deprecated; use new_function instead") def old_function(x, y): @@ -38,12 +37,17 @@ def new_function(x, y): .. code-block:: python - from diffpy._deprecations import deprecated + from diffpy._deprecations import deprecated, deprecation_message import warnings warnings.simplefilter("always", DeprecationWarning) - @deprecated("OldAtom is deprecated; use NewAtom instead") + deprecation_warning = build_deprecation_message("diffpy.utils", + "OldAtom", + "NewAtom", + "4.0.0") + + @deprecated(deprecation_warning) class OldAtom: def __init__(self, symbol): self.symbol = symbol @@ -52,8 +56,8 @@ class NewAtom: def __init__(self, symbol): self.symbol = symbol - a = OldAtom("C") # Emits DeprecationWarning - b = NewAtom("C") # No warning + a = OldAtom("C") # Works but emits DeprecationWarning + b = NewAtom("C") # Works with no warning """ if _builtin_deprecated is not None: return _builtin_deprecated( @@ -75,7 +79,6 @@ def wrapper(*args, **kwargs): return obj(*args, **kwargs) return wrapper - raise TypeError( "deprecated decorator can only be applied to functions or classes" ) @@ -83,26 +86,71 @@ def wrapper(*args, **kwargs): return decorator -def deprecation_message(base, old_name, new_name, removal_version): +def build_deprecation_message( + old_base, old_name, new_name, removal_version, new_base=None +): """Generate a deprecation message. Parameters ---------- - base : str + old_base : str The base module or class where the deprecated item resides. + This will look like the import statement used in the code + currently old_name : str The name of the deprecated item. new_name : str The name of the new item to use. removal_version : str The version when the deprecated item will be removed. + new_base : str Optional. Defaults to old_base. + The base module or class where the new item resides. + This will look like the import statement that + will be used in the code moving forward. If not specified, + the new base defaults to the old one. Returns ------- str A formatted deprecation message. """ + if new_base is None: + new_base = old_base return ( - f"'{base}.{old_name}' is deprecated and will be removed in " - f"version {removal_version}. Please use '{base}.{new_name}' instead." + f"'{old_base}.{old_name}' is deprecated and will be removed in " + f"version {removal_version}. Please use '{new_base}.{new_name}' " + f"instead." + ) + + +def generate_deprecation_docstring(new_name, removal_version, new_base=None): + """Generate a docstring for copy-pasting into a deprecated function. + + this function will print the text to the terminal for copy-pasting + + usage: + python + >>> import diffpy.utils._deprecator.generate_deprecation_docstring as gdd + >>> gdd("new_name", "4.0.0") + + Parameters + ---------- + new_name: str + The name of the new function or class to replace the existing one + removal_version: str + The version when the deprecated item is targeted for removal, + e.g., 4.0.0 + new_base: str Optional. Defaults to old_base. + The new base for importing. The new import statement would look like + "from new_base import new_name" + + Returns + ------- + None + """ + print( + f"This function has been deprecated and will be " + f"removed in version {removal_version}. Please use" + f"{new_base}.{new_name} instead." ) + return From c3bd3b84423b7d657bfaff6b18ba92d84d6a67ed Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 18 Jan 2026 09:45:15 -0500 Subject: [PATCH 663/696] news --- news/deprecator.rst | 1 + src/diffpy/utils/_deprecator.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/news/deprecator.rst b/news/deprecator.rst index dabbfdf1..01b88147 100644 --- a/news/deprecator.rst +++ b/news/deprecator.rst @@ -1,5 +1,6 @@ **Added:** +* added a function in _deprecator to generate a deprecation message for copy pasting * Add ``@deprecated`` decorator. **Changed:** diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 570be437..956f2471 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -133,6 +133,10 @@ def generate_deprecation_docstring(new_name, removal_version, new_base=None): >>> import diffpy.utils._deprecator.generate_deprecation_docstring as gdd >>> gdd("new_name", "4.0.0") + The message looks like: + This function has been deprecated and will be removed in version + {removal_version}. Please use {new_base}.{new_name} instead. + Parameters ---------- new_name: str From 820cd8b1e78b8977470652bbe125e5c4d12e70fe Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 19 Jan 2026 04:47:07 -0800 Subject: [PATCH 664/696] docs: fix docstrings showing usage in deprecated() function --- src/diffpy/utils/_deprecator.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 956f2471..485ef8bd 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -20,17 +20,24 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): .. code-block:: python - from diffpy.utils._deprecator import deprecated, d + from diffpy._deprecations import deprecated, deprecation_message - @deprecated("old_function is deprecated; use new_function instead") + deprecation_warning = build_deprecation_message("diffpy.utils", + "old_function", + "new_function", + "4.0.0") + + @deprecated(deprecation_warning) def old_function(x, y): - return x + y + '''This function is deprecated and will be removed in version + 4.0.0. Please use new_function instead''' + return new_function(x, y) def new_function(x, y): return x + y - old_function(1, 2) # Emits DeprecationWarning - new_function(1, 2) # No warning + old_function(1, 2) # Works but emits DeprecationWarning + new_function(1, 2) # Works, no warning Deprecating a class: @@ -38,9 +45,6 @@ def new_function(x, y): .. code-block:: python from diffpy._deprecations import deprecated, deprecation_message - import warnings - - warnings.simplefilter("always", DeprecationWarning) deprecation_warning = build_deprecation_message("diffpy.utils", "OldAtom", @@ -49,8 +53,14 @@ def new_function(x, y): @deprecated(deprecation_warning) class OldAtom: - def __init__(self, symbol): - self.symbol = symbol + def __new__(cls, *args, **kwargs): + warnings.warn( + "OldAtom is deprecated and will be removed in + version 4.0.0. Use NewClass instead.", + DeprecationWarning, + stacklevel=2, + ) + return NewAtom(*args, **kwargs) class NewAtom: def __init__(self, symbol): From ed72acace036b31f38c2f0d1326e8a0e46a2382e Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Tue, 20 Jan 2026 23:17:15 -0500 Subject: [PATCH 665/696] build: Add support for python 3.14 and drop for python 3.11 --- news/python-version.rst | 23 +++++++++++++++++++++++ pyproject.toml | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 news/python-version.rst diff --git a/news/python-version.rst b/news/python-version.rst new file mode 100644 index 00000000..a037a121 --- /dev/null +++ b/news/python-version.rst @@ -0,0 +1,23 @@ +**Added:** + +* Support for Python 3.14 + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Support for Python 3.11 + +**Fixed:** + +* + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 9799bbf8..2e3da67d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ description = "General utilities for analyzing diffraction data" keywords = ['text data parsers', 'wx grid', 'diffraction objects'] readme = "README.rst" -requires-python = ">=3.11, <3.14" +requires-python = ">=3.12, <3.15" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -25,9 +25,9 @@ classifiers = [ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', - 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Scientific/Engineering :: Chemistry', ] From 7ac6862a0157abfe3da876077988b0acfdc0357d Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 22 Jan 2026 07:51:51 -0800 Subject: [PATCH 666/696] fix: caught a couple of tools typos fixed to parsers --- news/depr-tests.rst | 2 +- src/diffpy/utils/parsers/loaddata.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/news/depr-tests.rst b/news/depr-tests.rst index 74f7c173..12559e13 100644 --- a/news/depr-tests.rst +++ b/news/depr-tests.rst @@ -8,7 +8,7 @@ **Deprecated:** -* diffpy.utils.parsers.loaddata.loadData replaced by diffpy.utils.tools.load_data +* diffpy.utils.parsers.loaddata.loadData replaced by diffpy.utils.parsers.load_data **Removed:** diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index da422058..18601258 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -28,7 +28,7 @@ "loadData", "load_data", removal_version, - new_base="diffpy.utils.tools", + new_base="diffpy.utils.parsers", ) From a3ca6c307931fbf3dcd45de19e1a001dfb6ec7bc Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 22 Jan 2026 16:41:50 -0800 Subject: [PATCH 667/696] fix: typos in docstring --- src/diffpy/utils/_deprecator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 485ef8bd..560c9530 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -20,7 +20,7 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): .. code-block:: python - from diffpy._deprecations import deprecated, deprecation_message + from diffpy.utils._deprecator import deprecated, deprecation_message deprecation_warning = build_deprecation_message("diffpy.utils", "old_function", @@ -44,7 +44,7 @@ def new_function(x, y): .. code-block:: python - from diffpy._deprecations import deprecated, deprecation_message + from diffpy.utils._deprecator import deprecated, deprecation_message deprecation_warning = build_deprecation_message("diffpy.utils", "OldAtom", From 34dc05f6f71632d292618ee33721de40610c958e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 2 Feb 2026 13:39:23 -0500 Subject: [PATCH 668/696] zscan plot --- docs/source/img/zscan-plot.png | Bin 0 -> 81478 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/img/zscan-plot.png diff --git a/docs/source/img/zscan-plot.png b/docs/source/img/zscan-plot.png new file mode 100644 index 0000000000000000000000000000000000000000..f0cc9ade2e3d41acd9ea68c6a56fbb7d7ed104e8 GIT binary patch literal 81478 zcmdqJcTm&Y7C#zAMWv~Tf(X&00)l{m^r{@Y6s1WQktV&jkXR@R0u~Ud0Sg^M?<5LR zq$(}c#Lz--0g^z<+rfL!z4yI;e{bf^bLPy+0QqL^z1Ld%vp(yy@={;-I@iGy2O$s$ z*UcM$--AHd#UYTr7x(W2KRFrQ^9=k?#pmjM9|L!LAAcJ!JBW^rkB5u9kBig8Q+{?{ z-cIgra#AuErIaq5a`f@>@K%wQcKzosq};t6q`#T_c7dlH@VH^>4S`6VVg0u&;ESdo zWETW-^Y6=s0VxY(>>rH!t2KXLjsNT3-$ALn7H%Exl|TGSQC-~eZSuoAdvv;W_Q-c$ za=zn*x_R`~%USX4o3GA46M39_RN~l!2Qx-muW^iS6)H~#XHjMf1Klmh(^%9sG{5lF z#-6!J4$<*j#?`9=BUt|?^?NOZIsd#qb;NM@pI6GF*?a!H^3v5m@aNTJO8SvMuUeHa zpZoLbwfD1Ie_ov;?uq~NN~`ODI9Ln#_aue)|5Fc4A~Ie>hAO?L+EyYoX{ViBnGCx2 z{6Ik|T6D+W*2>Og#Rw{iC|VHGZjL)r<1_oVynes<)3-;p+oGgu#PB13*7ec1MaVyt zCuBz3G#FlB{r}o(ozu3pcR#WQX1>GXJX?eap}e~Lzqpa6I|&_f-D)#P&9&Au8NTxa z%4587f=upjn9E}R4Cy`0eHo)v%pscKg+1NsWQ(eqk5MP+X2Bt>r5GlBl?;z-jNsw< z;x^G79YG`6G=_&&xQI58k5>l z$0Qp}&};G94CUYW({ZxQ<5w6||NRWEE7c^#X^LViI%xq{A54Zr*~Ef+Gj%w!kno_M z;T9-DjT5S&HXpp>vQJ>i##-3!a`@gqR{sCVZT}AiR;M@O1_bf|QR~0VEoc1!%AhW! z6g?QqEef39EV7%gC~|7AKrm~3M-Xs~tGQCH>-@!Rz1Wp+mB_Eg7Sf zAK+v;@GK?gJIj?43o{j5X?PVA4gwbiE+xTDE z!EE3mts*ARcC{vJGUz{6n#`9DN*hWKk%}4WHKFqwRP@RKmg|5{(xug*UECgcB$TI; zzLl|p6}8^4lYEJ*Wn=!Z##&)%L(?+Mz;Pg-@~qvRwvx6lEKVNHXbf)5K8)fHsfG56 zZL{2{RRJt7(32d8iMWkcmRT6Bc1Ff_Ti3{11Wfri+I_;EEM!h@pQtx`OmFVA>76df z2qf8Oct`j}Z;fhgN{F@KY3&~Elk7}VagiMFTFB;z<^e?Fss-g&f;89lGMBrTkdae) z*sud+UDr6`aIv>T)$DN$2a`uD$cg6uy-#G<#-Dv5OxH5_MfB(ngJr&8U+V&uic9?Y)v~Smy%sR| zm}R`ge1QoPR;==hI_=6H8W$XO`TOt&D%8A?=J?5iGxlB_4*Wgd;_-+LA zK0ouDe^{EOiu_C&QF|bo*&b{Jwt7W{6$=1HN0!Vp! zt}ClxEl<|YX73T>?M3DtT;3XuCQO+qEK={+LDVOxfr*tTWIn4Ee>CBTxiIf|C@)ko z2$Q=)P85`Qwhy=xKH)#S&Y>x%s6uJM_83k2u;`WfGiC$x0W2Ta{IH(25(XXh{VD@Z z+H|3`cMuz-F{?Q%64b+`)G=$ihLiDxv##(_jg<vqhY z*OY>8SUS!q&$@OoZKVK-N3{ADH>@$PJ3PXD@vsvKop{=$wt`Hiv&j;J}zV~RD=?<5lR~(pM9gA5ygbSQ@B8<78 z6t?njE9OetY23#QZ_48oERZ~ZP1F_n}c=bWtQ{GO_2t!{59qJQ4MxQ1GT zq>0cN?s8J1jHiX+yb3BauE&e0D4S zEML#@;VXwE{4{(gVg;wFV`c*k1Be1lZb|$_N>xXGi(#zBDlA|m3w4`YaMQ2ts|Eq> zZYf;j_ZV5mNUY{)+{XBLXPedi)OXkjEuzw!9Sw# zI#lK8NU^QMhc#4IV+bNp?M|?pmE%B}lgK5;&yntqGHZ=NOg%zfRg~oWsc`Ww`}*7o zVLAcZaM#s5!X+?ZxRh|aa+EJEsWg2+Gj{O{f)TA3?&r!q+BnW99cF+F4$RywE2t4j zK!iDKuA(P5r0eTW*Aa7>T+DzVzfa5O;BI;|cnNR0W@lZ2qq?f-zt(4mt96+OuiX@0+*wt_LES#vhUcY_HqCIPK3Vu}xlhOHDF4 zjG!}@TPn|tl(h|6vAlltf4u%4w>WtKwJk%4f+|Z}P^Pg1Ck|TpExHmMFo^tbEwi|4 zTS{l8bhjaK!M*o=LE@7&A|fCkC+X(E7~i^v`Wo4vir*TdC*CUQ+e|91Sxbt=C|U$D zhPVpXU<)sgHzc^%J0rzg&X`voL^8js%6_|-a(npo&y`?*ri_Ju3yPe>t^T1)XgJIo zHerx>aG)mgX%8DBRRF#6T=kuVox%5kFJv60T6^DbRsW@fm_VPPs`+o2Fygf2fzBj_ zA7y9ZctpJh$;|8S%aKE+(~{gErqC<%n3FYJUfNBO#3U3OhB?AZ5XA(aJ>LfMKGz$mC62~s(LND zYSys;{N$4ylBY9SzVj7X0Dk2QxAM6MX1gpkj~nhjm-8C^^Vl(AYX?#Gn@4_MeiVNLe7h9N3564r=L`?ppX~8Er-Z(Y8HFxaSTK8%!CeX)1U^$<6->Ntf;a5bOBC%3b_Hd^z2$P zw^BSyVu-p??p94OFlW`JDn#JZU4q)NwkrLBg?4qPZ3aFk%c3$AWU3f5SoZ?&jX)`` zxe;~R?6mY*qi_KCeg+s9T0*62wzd;rd$~8pp!VaT%r_m;=%Xm7y*~eywZ`v!pSaG{ z%M`zGT;=4D$S7r&CzPB*8ZLS~7Vfddj^fP2@)?(RW$b>Bpiu+~6o$H>4_dl@Ga`}d z8=c`!?jneqAkPfl~hS}o?R$tQ)+M)qpmGIt^~aL<`A74HR62NO|s4Myrq zJ{MwaK>ulLgThfiyfz_nnJ!zsRT$TVnLyOjofDJU(LVE*Hk;4vl=0drs@tvK^W>~; z_hH;EynYMhUA8rv!Iett!Mlm?@?=^NGnR?s{q;>4ydr+1<&LkH6x+gvJ}joHco`rNF(q$k%57%X=fC%*}4XJ4J?4Rts!o zJ|a{|)h|qwUdrGLqt=KqzWSrGO;o%2L&1fztPpfjFs}jaITS{){nV|zP*-nFpLI{X z)ncOHeiP0ZxL(FtY2|xUiJ5OASBJBms%m{i zuf_1_jLyT7a*~?!YgH&bT(1fwlLB(_?Ymh376BMG?aNbqwd)64kZ1(0z^r0_H_q2! zW}v!J1B$wR)t>W)!&pdfTJFN&+NKt>a68(v!`krF18Xxt7q?Gbh>I=QhGTnlQ7 z?yIM<2PC3K%68YlrLyQ{`~W9{0P(L?O(j zLs2MX(^^CmUCg~hjadE*)?Q05eim~V1lzXK={FGv$r?12da07_*Nx&|?p+-o zF1Wg()7otkZVbEW)uR|a3lH6@rFYDbuPu2Ag$J|H%WIS3R3*jr$S9mpT3YET*h)K| z!&}g^*}3rhW``baeCy5%s28>Tz9Z$NtHxD-TxFNZubWTOivaMqDeH6V|6EH~c%Q#= z>NW~#{mzFKv;uQIV8WI{UES>hz4~otBFvA~pitY`4sy4r8glv)BgO z7YXe`&Y6CBPF1ulr)tJu>ocP=uvK>da8{D1)wzd^y%0f!bOuY0Qj8s}wqNMZR81>;H z?Kk(GNZ%LcRwkoJa=WHE`fq!y)aHM5NLcwzM)ONCwj|j(XS)(u7fKk)GKgSVolkUyOw1DGmP4FDDTPsoT9-27JLQC_>6&=e z*se*5@u0Hf1k5TZ~(s(zuj(K0f3;Mq3b+EJZ|`sqGaQGMYDCa*YTMIMS+zRZLz zeVnqH*=`|DiaawxUb;9MxahE1<`^^1_i%;Lp}Fujt?!yS3*VkNtzMTt#`z$Aj}< zU*^-G#4ktSnTs#?EzZttF3O&s+v|W4x#Yfy9Kgsw+%HeS^q!N5_2UHNgcekA@VDz%0zia@s&3r)nRvC*;IxzMK77=0|TXasqr!VEX`* zxx?EG6a_lde9i4$7anfRy<-pOK`*|#I627BP>()kg$afYjCROB8$2ND$B5MI=FvpU z)=j*!Av6L?ghlhP36qJlr47{pQHu2Cqxg++ft^{6L&IN>h5gSl931bcV`r$y(Y8R9 zhOfFbnf!hj#$a3CZxSm|Pb91QEpQ^M2-knOav6*qV*m`%VWGxcs6@P_Rh5P#kHWos zr`YSo80Ao1ZbjN;CD*~T9}hqWIZ?I^ zr0t)zV>H!OFPK(m-rd}oK*27&5~j{w3zPyt+Vi{fHIq?VR7cf|EnFPs2M1H4ZmRtG z{D#~ObQ7{&ICw4W4mnvTxzgUPP(4r!nKJr_+LkFv$Z+Zo#WOcvuCyT3;sqqVFABxD z@HVF*{Hk2~lF2@uFUP#PPHguVEye3_;i8I40{$m6-s6>cBpl#kpLuX0N15XU4VJ~Z zz{`*&%&ALWAfsF9fZj-SdO4)Otl}L&`Nju5uo&y|uDW!gL&hY6g$y(`_N6StC!Qj% z!&f6(8U~%+@~~;+is1<{LEcsCw9-aE1w9fU9~X-FGQNm{p6F7cfA;7n!{aynUKf|F zZ0Vt;>t@T|aaT*hmrxX(v>d{pN*-;{LlY$vG0)4#bOMz?{2T}Py|%$t?{nqjrT#Al zl&>RQt%kE>)GSQc@)$i!#l*pHc~pi*9gvQ&vW&%%iUUNKk~LR5F9L?=G<4UL1CCtL zLHizS$!zpkZj4o|TJ%cMsrFCq*QS0=z$E0r+!r9U5hPuoGE>|AxDPs}g)B0_^%!BumO#Bfr#O8u`!?3cWOPL`;-!?+VCjGM76;;O@!qitfVI{k>)&9L>b3d5$v06e3KjZmWs7php! zwhpu76oqbs`urtdJ@=Ll5r7v77^7CR-OzWyC}oP=8H*8xvD~Nn`J#1q*MT!q+d-)f z@gwK}r-1&OY@2&>SChZ`BbJ&~Fq?&`!wyS_Xsxx~!^FRWT|&H?Ix(D;%xQvt$Cj_A z2y9*sx}Ui*{4EjRV*Gl!dJZ92@b)p&_=`1qlD3jjnJSQ^=>DEowT!2?cr*dW=oQEh zJ6oX@W7M&_^n)L#__taFVyne~5CsSXt86Q;pNI(e5c=yCf`6S%itq5vMCIqfCJu1B zhOtBhF(>0e{%18OEhHPmYC2 z9XkG@P{tPF8cGE;14Y4~2^DxNS&7Q%BWMmZH*SLz3>fYvqO00k2l)2^(FtN>M z!xx*bhABdPr}-w;RFSa|NEkwO?s>G0KZL;;Ib$7~DWlZicyjfuyv9-pu}%sW*fG-> zGx0)`x1~B}wWdF#kf%v7A47RVYEZ>p%>TysKFXx-C|cI$-Ur%>W~oYMj!Z+FHH`i) zxR6Ik_rm=uBB7qC%@8r#pXSjggZX&ykg*`vfm_X5)jfcYhGr1gHrRt|ER4U&``zJRUpGB!p z4X%t?Yv>c-^A{TtBu!xJukjX5?r${%8)HutgiYec6-SmsNb?QSfXTY%ju%9#?qipM zIM?Inqx?k}fYjq=!u)hUos`)3w+swTd%ir{yH#2UC$*NXyg`kb5g&JhLOCo?N<&(% zaWxakEYyv!4m)K$KNdoFXcW+naqlBWFD5M~t&u%i=!gS)b@zq*7DoB@@ysRX*u(MW zR>%W0Jm+v~hn{Z%xG1!?JgBY+lL85fm#1c$2NC?&EA+Uk?{F~fsSI55MBvtsVE6-z z;_0c2t^=cY9I2FiCA^Q7efX`T%QG;nV1&c&Cb)Wx@^9+Hrr!byuyb`5vG0{-UPSlD z$%LY!0_3!8%jAXoDuYEnceL=>uR2qIlcR4J=D)ILX9Tt=@@&ZqgN{L3(i1ltlm6Gd zW!#*g7kXPK%i&5pHbu=tPk!Qk!AJueFsqj!y3a78MxiH%{)V8+BxQIoOi{TT-L?+M z?hs-YAqKDhwhnc=Ws}2tYlfQWZ0yT*!S=QP2nxz=UA?gsvU~W-F}APw@2V_4Uj|I5 zQ=QvU)%Q)*%yQ*I>+XxQTixNH8FHzuJ57n zC}lf+%9ww%X!+@vBu*9{>X)FYe+ZCa#^h~$KP|^HV)_Cw(&7d2FMZJb2?Uu}@Y_DWoQ&Zb7neRTnZ7K~|*^B6>F7e7@CWkuq?+9I zrI>i616@hRFqkqRjm^0wj+^q|_ zq69_))3!4SpziLv$&G&K=-sYv&=8N`w(|;ptJF;}66M~Xm8vg*+p77N(IS(2#Wu3v z{ZsSUQ9xG1f0_oYc42tx=^fe?(Jx72XnH$7Y#ZttU}UcF9lCFcxmjgqo_Q>Z;y>|A zG>wNDVDC3r?(EMWWWl^sK0z4XT(BGyaJb4?i$&ytusC!)51|=IFfTdL)>JWwucG57 zH#~J`@^9h`$(%7HY0P=rEK;wZ>bZLIdu=G5Y>naxTd79$ezE2PXaq_70O-YciEWku zHg7Yi9VsgW6ywwdv(anzj3E*MNVZ6-jB_9&+@6) zp7h|guM_s^m4+W@wd3a1{c>cQko9wd`YnR5C%D#&Z=33PP@15l0wSZT@>Jz<-=~8) z3k`}G1***IiOUNYHanqX$%zh0L7&L9ZH)h5a$MBd|5W*ktI65(@U`SSdeq^DZ2o&V z?+M~2%=WF`YH~alvEH7DBM-SKuB{YUD?chH5X3cY<9`8;u)7SIfBW_ze*ny#*2G@_ z0^>;<=&*Dy7DZU1m1DmV&n;`@;W7xU_91LK6M6Dti*&_;5d4ar2<270H;dq6^>iMY z)21n`t|1yhM#}cbud+YqkBJsuo$I*pi*7z7d@H3lfRu{h#00Il`wBloN~+iT93&$g zVElbq!D7dYKguzR*d|n9hiAY%>)LT>VD_^aVH_V{sMhL<< zX)dI0Fmp5iHpV!s<@+%di!tDGmP_gQ)&nYfg+V2xNs5>2PTd3jK zw1o3_ZXSx;NUb)TNM(9!2Y^P;P3H+Nb}V8IBRGZkw%w-qRz@JzR&ETKPXj8#N%6Dw zdO!523;XGk$KUfBB~9QyNTkz*e4&!=EGTfo zYqMzTY8rX)g1}F;h4eeq!)E7q~a|#S)~VuZ)4OoeCAW=bCV(nF=kRnqjKt^clZSC@GEv7ew;A_-;mR@Tdk6(D{YJV-3>Gz@X(Ub<% zy*JmcDrjLZNcgx>$CgO{X!t_Sh%=YO1U_nTuKc3|fjy`aCwGojc;xl+bYtk7l>9EC zy^tqf-kq__9IR$!kfsLX&Iwi8_uNVfNpJp4Oe-zu@AhMaHv-*Z)Z1jbz{_U7+JmVU zL!xTx{+C%PKNSkS?n<*8@6T|~+AL=To(_#4x|r=wUZDtKL#I2F!|o{*JSjwSu!KrC ztR2sD@AP8HL^qeX>SxP=ByvrA-sGNZ<7ixwh)a0^usoJ?;FLxEf z(dG;q&8-!cfCn1LDW;o%G8Vm^hmWwP5sD}0t*%$N@nz!HzI2GCYg3n`q5G}trV8Iu z&wdyDv5el7BS@kIkKFp7+vH0CZR5xBhJId=FocFV)BUw|@LVy0;U3#QDzty7#I3P; zCDmH(5FExtHYRN_)1D61d$)kzS7~FG6zuk|(7QheB$E79JpHjQDnAkDe9AIKSr#Q_P2sotqF~~41gh6|oM*<9+hOkNVF={6=CKRrd|fBK zeec)1X{YMl&PUfy%*CGb=geIk^yK_5L}s1?!kaGx^2J=U>1g$abd51NNflLA(}y|O zv66*QGhOz$Xw@?Nvrs2GiYi_rRC zP2XVC_3m7N_PSyN1Y3RISZ#+F?dW4^NX+LhqH9OsnMQeNu%4su-|hR5BvuPFLKvYS zXI^aDo0;KWX)BxYh*MOx%-MY2b|4>d&frZT_Bo>a)wdftS77t=;i2R-QjN!Eo^F?+ zm(})!b%Z-}&3~hC?v-YQg}VKEn$&gA&rOyFjI=-;pyB~ml=Nj4r=cuj@@JV-Z&0jQ z;ub|Nk;HdB?j#=L0N@zv;Pnb8qvhlr3~YWZF}E}Pg$-fOci#J6{8ph{Le9 z$nAe}=9hEEsHVX8z4o1*qn3jezIiNd&jE*2zqfwL5K|9;V)mQBnI^vQQInP!Ze%c1 z6{ZV7;bqSLYu2l&7pJWCu(Plxfft%p7_1(uBT_S} z;I6F_bxDyMCBL4sN~^oL2`GQS#n1IEDWpADN+?zEl$z{@Gaai4Ss1R{#)Su>4;PND zC-Q{4rK*NrSs%Hc713Tuw+W!e>-fSK0v%F?8OtOnw_dj;KaSbNY&TOKqL5GFMuWCq zvR$c8VhI`leRD#z8l^teZOCE6FruS;^_XS&(#Mrw{>Z6yHpo{S*pjLy7w=N48S%Aj z%-8dikIM$%u2)l|wA+tK$`&}O`YX|@(jETQ0t!MQg0SnL5z&2%qINcC2n z8%iVgCRPUU!YoG`nr}6_BQ~G!!Ra0eUsxAPd?q z@}{))2`PT+2_!%rN-va-uaWR{)6_0Kl6_}`J4Q|_MP`!nRvp!AZxmLH)#WTb-X|>9 ztQ@1sXtPTcNZ2T5$*u-@r9-dVYa|4k7rAU)##+$|6~kD9o6ITa;ulZd7h=3eLU@F{ z#)>8+8Wyc>mXs2?4l&mQgjhgp$?{PZZk z+A{=YUb|mK`>k0AUD(~0iiN3w7Cig*C2;SQ}&h&akA9rIXzboFnkH zub&7l7`k^r#pAov#3-e9Pg(?y4;R>66x1D&ud~r(L;zJ#duT$twb^k6_n~7}-fihC z3_upLBvN9=#azo(%BFTuRwxQ3UT%*vfyeK`Hkbsx>rT0$XR1)l$xqTg?Vrw~pp;Kp-^ph5KvGfG6fk zuIQVMg8`bny@MnVe5A%<-&tH)wAs!5IiM4G487gX-k^z0<@H+{<=X=A z%}G;jeEnTR4%>E4(uJOR;yBP6r8}mmrR#lwH4+a~7)s zKet|$3}@8dX+I5th&ikVkLQW%=Jwjw_%HD-qIRKZEocrTN|YHyRZEtWv+&b-MJmz`OHtL~JS@q`o;WKtbN6%%yIWiSr6 z1{%YWL$-Q#hu~yv;*WVey_rA&``s~@2Kj5psw0Ipl+np@XDj%*J5|?N27PhI7FrTG zG$}@FTgL!OM8(vf$_w6|4b;UD$gKuehyzS@HjvI_%#m&A*Nng{LRb>OtgLa%!y~fWeq)%E<3I3v}=;Q7HVDao6#AFXkO|*!i zdarE}L=8cQBFG3>qc6C#tzNph3qoD0^Xrovm3M+J)~&CFgQr0tUZ?#ys%PewBZO4_ zMKq^bE_BJM=PYoo?C2fWy2;|_)|%dfOc9#AOtT#zgIymwS{s(KT`kzy>wQc_QzS+w zp!59mk{anJ&kwVPKF-Yo84QqD&o1V#z_Bl}pAjqFuCcP|o|K>Gj+LRR1U zvN3tDhuF@d9$9++s?`QE|F_Z$tUhB2Yc#{r#k|6e12iXU3Cnu*_p@4}mEHf&Ug+-^ z-P8JKX(cg>BWElwuF3zHho&)HjLASZ9_>1^DMX-K*C^8hxAIlg3mf0KHc)u#+rhge zb~dDEyeXkCx2+{D;4~O9P-Tr0JZUbnZRzs(Wr8@wpu}d)ZG^1-S{$MN4GBEA<7&fS zHO3le4m%me=M5C{bel;3*h&Luk-^GK%@%Spw9PlO;Eo5Wct2DyBt~!dYyUroRlmrk z{SK?4LZap`?FDqlW0pe$QHC{NqLbcUnF*9N?>eJHXg@ zNtE-?W`k&*u9*US&3L`RJgw?-NNvP6MipmqD?NQDqDwrQUr)RnC@{sy0KB6#&kBVa zGmNJ^aORLR!SOkY4p1BD{MB@7c67wJZ zv7Z{3(ZN?|aMhpQaUVUov(v|{G$~a-Ki_=!xja^V^~wP2e0+WJ*21XXEw}6bt)X!t zHNJi&PoDuPI0VvmCF@fRrzp=%YS|`A4}HjQ=~5Pmo`zySP|K{8nS%KAcRUU!kF92u zm|*jlIMPq=7&UYw)u0+QQVBq%TZ)E4AU8H@uR8#}k-td6;$WdGi^7fo(q^A$PoIhG zd{DE1w%!fa26sF7N&`V&K?RBzeNiOkQhF>e=TWsC+ZC+*=ZZ6&ddrmKW4mBdJNwgD zfkLFE4Q!ShY@cZyLa7YB+#C69CsqVO2bLFfEE38#-Sz7E{T6OG7l88~TK{S**SPt8 zSqD!Iw*fTtw8m+zyN6hjVt3A0cPd=Ryy0DG-j3BuDcg(`+~x+5j4p-lzN*a{z>14k zJi9OcjWWnxN3V=vBeFD;%PM;Bx`MVzF9yH2^-sPrwhEq0|Cw((9D4lZ0p$b`DS*0KBogR#IK4F zF4#~PNV+vwMOE`tM%zQ402SMfEg@oTS0959@?^9JSkC@u9uA*;3Sr-sl6c_#rlQwm z>qWb^xP9v6$sg|;DXSCjcIV32HiyomFd9BHKQk`3bPzpBfeT)+&WQhqZa;{^rd9ni z{;fLR%W1G8q{Rk{wrO1@@G$-DbT@%36fZh6{o#MP;EUyQ7M=Uu-MB&h>_7hGV=cV! zV@Zdl*N=0ifjEAPXkm4g>PWZo?=LeDOh)4GGXB`)Zrg+j=Y0@I&VpD+0bL+Z1%pry zom?P2dXMjdw7z7iR6f6!4cZnAKXCr($8*^iY>SUb2|m5cS@Ff>CuV!-Tf zrQ2A|#fRTt1Xb?u#Qtk3AZ5$07|NNrL{ zv6*M{!CL+&OOhJ?E@XjPP7@=eFabIH&~j(2JuX(#p3GDHP-2+kEOQd;+(oAXTkkk< z-sBKJ;|WxK@4Z&o@t$`CIMt&3H>!6eFkQ$+-W zcF}FLify1pGs^p-ZB=;^i9Jbws+P)&m3$hm05tQGy&7~g$m8&!p&`)U*-6l55t5Mku6d!SWd`m)_~4o ztB|#?pUYXZD9IHr8!thzn8)(8m+2*((LlG@y)9!gtf`h8%dfBqRT}dfaZP+&bCg%) zj?$@_n$2Nnj?I5O%_T_t;WifV5-jm;izon|EG4)Ou*>eMzNvktZfa6s-Y@>sb~=#q zN^sgIuQZ>glDkwhbf*{ zOW}ZcdsR|N1s;nX^T&_O#R1-6MnF(j)3X|j*_;MMHi}Og?B zZ3wxlr*C>~+@ehhS#m8u^tS6zpjh!0Z)Rjwjq%Xc;&Z^)waL^$khl*n{pXp^Lmvad zIMVaC16=`dcmNnu$zMJI=6GDTRRGKRcEHaCt5D}T_>yH}1u*e%&JWG8P@oVl#4xt5 z1umU8$UJ%I?|wyy`eIq{CY6qJef(c0TY#%&IHe6_Z5DB+%1S-yv^!L6ukYjS6Yf=P zVt&usWIcbJOIRRnH|J))0zn(tdMH<_OJjS1O{2n}&++uhe-4$yfm8W(;37UPNG2~B zJV;GF?!JzI)Aqr*gTSL-?CJ#pUV2YQKnJN2g2XGHg%m6nZ?iw5YW%bL$HPyKNV7(l zoE8mAk3S00WXQ1pF{}wk`kVDoPHx6pp;m_s7I{Tsul-7?!hVQFh@`?%{g2>y; zPXYNJH|2hc<$ddN^@`lw+y^oLP6VxAK-XB$tP-H{A|IpSt7idNTBtxdVCEi)@`(vN zYddmG;`g7uZ+<1v>)0Pxo%<|L@@)HmV!rxPfIeojT(7IJYYvPIh)EmFA2LkytpVy3 zp-xi$t`gDkxb4b$myXA6j!a^#itk)sI`LJ%U$CaTYfJ0DpHp1|2-qUeiRQ*a=U*SM zIu4b9WVZ)0iTiE4J#aporTew_Wo=zVbaG`li(u^;Helc6vQ!a{2id>Ld8ESCW_xon zqF%&G%+ToGPlPrND)a>2T#lUUroDo<=nbVw73QCn+mL|b}y z%2RRTkk?o1YfcKqxZ$OC4vJy{q?KZftw|0W00VD2U(e0l!+pl8MPsylDL|)Af4}(c zzHwo}Pp5;#JrJkqMMOg>Fe{aRpX9|tI9ju}xx`~##>83U zUZ!{+Eh!KS=}BH7sBAhu$Xpf7?l*QyKut`O2B#=VyC7bReYU;N+GcWw`c21J*x#M7 z)(=!Eb~l~7LF3C2L>;)OU*86&DhH=g*sgpTajTVdZgF0CpH;rsxV%%gtfmFXA z-jWaMVO_yqgPXpg)HtmJM-1P;2FDa8=vHNej0G7-+~6+27ALKk!j@OS*x1=raaNXg z%s$TyQVCqV0aPZ>z;Yig3>LG)E+m6ZvPD0fMb5*Zf{;_@Ntc|C=<4L>_HBWiG<*=i zh=51U-Zz~kf_=F_7gdFz_2bvin!oR)s>qWnDp^;jgrsWeSfMu)l3#&+{qJ>qPrbP= ze4sI$OLd{`+1GtBmd{&xsQF=y1aqx&x1b;apv_#lJE` zibR2VCAaHHMd!%st|#NE2GsSGy#$@C)VO|tF7%V;WRdqqOimfKzn7GAkEjJc3HITX z@x5kh+_viWLg~Eam_o?bx)~Hov~0fBsU3FP$jInNi~6X>$H>S?HjVfHUfnLp8lmqp z{Vi65`VpX?CtHCts}Ao5${leR0LK-OB_MVa7^`nS!>d_=kQl7daA=!w=16$Vp<~c* z{Kdpv-WvU%V2{{UUlUeQ5W8Pc(OrFE7zq4`kMuxHz3-n_2u+mk&@d!FN^be>? z)-s%|Vs8D&mb7?$SEcm|6EGYeWDeJ)Y^cnELszIH}Kb~48)i2EVXT; ztVaM%Q_p5!*!AgJblYf%0lpGogp(^NXRgQO`!2?vH+XDWix?T)3cp{j&SdN|MgIeB zS|h$a?-3XO1m@j3cJh(rs>-}}Z_l)W-)x^ois2BeOlv)U`5c!5jI#1MvifLmmYyCr zYRz{PCR}^cS`GwjO49wi0!~a2x0W5bW=G!xwV>1D3xa4^rjBJZ`>h0&$E86=yx*_T z1H5xIHG?eOPQ%&7#Rd-P3swIF;~Jp~t$ro%z2w^g4JX}mpj^55h1=EodK$dMnbTLUrGWDlh8`>uTR$bNo9%Z$BRo z7TK`VOkLQ5MqDfTX#arCYCoXd{ZlI-lPptNuwI`ge2Y_jq}++m%D3kUupYP`vr0G10%vdp#SWG-|Bms;!(~o{ z^=qZ)i8B zV&NiSTibeBov6Z(Z?7F^>!M36ZE|0oFn1*wrG(rE5x=}5{_iV^4KmsD`*O&;0d-Bp z&A}31^PL+Yu`DE{|2)X%S{a87-|_*V*xDicD#I|6KLB9SzKt-yf4sIZB}pmEtEDT| z1_%~+;;E?}cIye76aCge(`GuT%{4-{(u~t(vLpF?i!GGGO+2k^a{jee3(ha|U}Z@P zdE#}mJt&a^I=3f~*X=0J>XKMb4}N&@@_qwDQn#{WMlb1;EK* z`$1=%9ZVwOpAaaS{k*GQyVPE%u`9KfyLR)(5C=>a0?F>5D9wF)rOomCtf*>~*3eIo z{~^CmnL&Tap2Su<^iTsI`<0XSL$nGQ8w)9ua&q^;_~&6(DgE^{r*NO*EDa3$i@A)T z8>{y~ena#`aD4+MS4k`3r(0B?YYpwx#3vhaS~hR1Xv%zsWdyzSJ^=Z;D^2$#@5Kd5 zq{pLV)Qct zs$2&jbe;#IMFm>PE%J*j?mL*9-04SI1tx?$!!mimMr+yaCUNFGA9(NrQ~jakv$o6P z;!oH9Vlf4dpO0DSb(``7o@WZS8=_B49x^L;IUF-T(wNBB7Rf4Xe_z}uiW@=)&R49A zH+<;=yp^vokHL608dONu!dl{#Jf^memdzuXlT7UR3rAL);_A_wQdn z*t+^jv=boy-^^sAafTn-ADsaveyzWyDj(!m)t z-bC#>579~t&In?ayVn7(<89%D#GPQ-r}fNMMVK$hcQ($a5by}y;FH9>_4+f~ug!hf z_2k|e*4Ey*L>XlK;EM$y2YELj4;V-mozF7)#g54@cQ}KwOv#<*TZ#$f ziR`A~Lw)6R6D;V)&Vy``euzbZL5^_U<`n-lJB-;4i7QA|^$*J;O5HIFo;yW4|8#AK z#=CK8y^4`IU~!)H;HkXFKO&2OlAhB^o0RChi`h}B#> z`K#n=&+u%wiE`H1=KxR(kj2kq9z)+Gk&7HD95-tvC@aoyS^V zl+$Iq1l>jL%1^4S$|vm4=6s24a|HDPBt8Fg1w5K`KKYWkCIzg!&bn z7T1ZnDc0beia}?3a)1ej7m`_jr}AfEO{#cbk@0j@$x%#Ke{injr)4V0npX! z+JZw4rbe>j?Sir4wO&^JEXJXC&e$+bHrw4k1)^2G^UXwkjWR;92kAA?cV)&`&jG-0 z4yK}5veF$lrzNw(`c%77eDU7Fibd5F2f*NO_AQiEpnieIlRdyVO;>ijWlA|7WGefm zD@0HQgsP`1A!Sv3U#B1W^My)%y+yn|L_=K!JdMN&(Qin*lcEP?U+ovca0|<8|FzQd zUB?~|Tv%+$6#&QF4cOm>AKwR1S)4P;H1)GuIi&tW5J-C3aGAZrCOmzlkA0@tFed&|a;)BcQz6$U7!ak)% z%DQsAUY!!Y8tnZ9HBNql4MN#r61CV;{H~Z?wYx;G4(yVLXdMU&(jA7iv05*HKkWe9 z8yUvhob+8F1D+{!4f(rgSfHB&MBlAn?@B=t!z&RK@m?~?0XWT{J^BghOp@mUfCX9R z2fhU1M`;wOHMv>5KA6gRTQ&f$x{#gd^}3Yh)s&Rb_%_}-#T{6_HcD%<0oNi}p+KJ^ z06=0QJLX$-<*+!s^v`PXgPZavz&D-%RHg5-lPZ+|FVfyKDypsN7T$^N&3}lJm-1u`0gF! zj&Xk~-MjZ*t5(&lS+i=Vr38}q{5;gyBl0uNT3ONf3zUQLk*gCIa82AXmpc`v!^VMv z#fij5h8l|nU=7U!(XKNCY^@GrpZr2D;_%2hYv`RqLOnQ`q zaswTU>Z`~Kn)mpz6ngi+bv1X?3CKxV#8ccK+4gB4ngwugtGlV^ND3R6+p!9X_m<~<_$<|nC^r(?J6 z)y(B?ZQoIyE!he<#~CrgV{vV~MBIYWA_v7i zvhOlIJHJDv{@CZR4hj6FraeH>FD*qc5pF^KuZO&uCvH%4C^B{lxgrFe$gENbmXh+l z8{t^?tK@CG>L6=?Tf4mcJlnIDW}FElTo)G?B3`Mvfk)7TyPC4CM*SL`? zjOdX_UmX?BifkM~=L)taGhDSG%p8SuB}r9)SYDW6~m{u+(75&jLO zWg}Gh>HbJqFtXI6pt(W5r#giUdF$EbXykQY-zv9I?SjY*fs_h87`ZU!bJgjx!DQ$4 z4G4SR7UT^CjU564WPPrXtE?Atr9Dgh;c&7LJqtOvE+5CRwN==N*+B)SE+ORoowgST z;Vd-NDWQN99o4+oxuC*}6vAl8W)WH|jJLfyI@4e3NdvXIk5Je(XYF^s04F)Jz+&3BH zmeqA%ct>7j4l=$odjEvhjHvfUbFqcTg#rteY&u)$8Kl5;DrM&-a>YpM*wR!Bhr^}Z zn=L&i>1Zvhzec9{-k3x%6Iy=HBMaJos&k0)zjT^x@8r#R2)UzttolGkWc@7WKg@-2 zNNjIqqi*&&ggVE82K2G)FBC7sm0vI;u7zV}ngn+Xehu9^Ee{fzN5Ktzt^>d2+l>#(8`3I)SpFCn&@95KT#&| zjckJ&P77FjIF?Gj%&z8Nj?j0HV1NYd_b$^vHpW5}Hig-PkVxmkgezb?ZfwH=|49r)fah+u`;bcU;$eF(I> z5KRtqafZOeobPT98~@{1QhF@}lL93K+g9WhEvmR4)Ge6Zl(*x`||e z8*0p^3Wx4$lNw4xao1yGOHa8@f5JbI^(omlvhF92dpJ!#YexfU!*D`P4L4pXc0;`Y z0YlnljZsPKpUK zbjC?Zk3vqxfBYjXX=U1Qp*=53q)1)V056QiOrvR>-9q@ zkz$T{`*(z4Tv$s9bdi{Ng2^-VK%!W+$-)(;(E4tmKU%@Zjl7g2b#PwopCTqDL$~NO zq9MM1O@mPYqE0jNA~KX44$kmw(rB%4cWCinGF->NvPW8@Wy~z~&PcOJZEfH8NP@^N za_Adro!TWA)`?bxoN9!g!Hbvrsh!t8OZ3c?MOaMYvu1>vQg@C{uZ_IF6ZuqZKh<@V zk=ky#u@XJi;0Ht1^NydF(-O*7EJ6ilWRpk9?rLB5*5qQl93(=e5Y8~{o{)*Y0j|5W zRCy0har@y#$Qy*SCy(){EJE&ka`7KZui+|&dc z@L4Crh@+-B;q-g906Z|M>9KRSV?FLsE&mw0u%j)E3?2PpXcV)EvndpV*X{u{lRB-g za?!Hqtjf|90?gr@WVjX_qw!2l720{+>iIw!&@?4uHt4@R=_L<(kNwF^gjlkj3On7d z%wM?!!EZlgN(h2sU7b_A0ENIt#fbP&ZEsW;d1ZPb5lhi6?g1jWGoSCSj!67n1@><3fWM0)~=c!hdl=e0s+Fl{iLTfj2ct%xzA5Yj5 z2{sR0+|ukMb_oD3z~fp(Kskt{S+;1Q%-NSg1w6n%UpI%-Z>Ku?bbkv^I0Z=Rcvz|M zNCjQuaBnu8@`4Zf3J0G*Buymhug>(7X%9z;&Wjww2TZZHI5Nicc}RlsAA3Pq`3XJs z&m!TcFW6!N?w1#M?lNU3FCh~{XQj!47GkgMh9T1W;>~q|$Yh_d5%?3_aPPn@_wRp! zP&?K}oHkE*eV8yrURl@Xbh+ea0OSb;JSqso+UYw%@-NYkaG4#1BsioGlWx9ZRVNn% za2nFaVXQYV8)s!At4!TCksEfn)YpfxQ*6i#P@4DV%H)}r5<~0{FxhdCQxAqrXcRbk zf;+@;Gh^pu(^DAQ*gOX=VY|uDurADSBT?^b3!vIlrASZ$;ys`R&S^29cDO%hJQW* z14QBW^9%KP)ObFlH6>{xwgY0#g?-!W$@ub8@`CR!FajTosB#nZXqM%8^E3$p}a z^r|Vt=PP?PE4@4;Bs|?BgzfL9q^0cxzGE*u<9?X!T3^QOEgnI`DDnu1`Q}vRkqKtH66g^6BbQ(ACiQcew;Z_B=z1XAL@NsoRwD9Ml{ zANhPQtD%&p_Mu|;)tQ8Gtx_k;2+##c&l&{}8B?FUn#D zTB0HUarxFtuYfcsKqZ~T!h^j)5l57|_#UAK5(q5%g_s_L5C2}y+49u^UYoD)crCiJ zFM}>U&XVgotpV-g-2-@P7h8Z|P;#Z=df_ zx;xviQ^=Mtn@5Y&bFmqww{JbHDxV%qD!3bLv?-`2;xy65vFPPb!^l5-ZMwH;aGZ~) za??&D@9yW=wHNbSE`3pIS@Y+qo^AAsp6YjJ&LQzv1TjlW+=en>kR4zOpOzO||HGVw zo~mc=1?h;KmS_siU=an*NZQ!Zaan?>^JXiZ#rz`%`}J%Hxa7u1VOtxkS)F`_HpSj9 z!k~?y?5LKqUtjv6PYUOV+?r<8?I^6JQ&Wh(#McLf|DitnCSvCR{2*C@74JSCtlvFR z;IESoZh&C>SvUKF>oh?EfVM1uO4)df=)}< zx!*BTil6F*t@TDzbKtCE)CCTQnLc5$KD0e*_=yua0I zr#UDH&jo^}z(}k5itRr@El!trXe9n}J>B&h zse-$e?&^LuyaI(Ouv=d1fiiIlUFszRnQu!!TA%V0GCo70 zkUy&+iN)fxIitYcz-$O?De91J0lsLyq}!d(0y9Ba&89*87wzP8YY!(ZGze%KHu&D2 z6nAq-+QCQVmYBrq<2mK$6zugR~1!}?5R=S4) z+yIT`%Il@sbgJcJDm%g(0!iT9jK-98K{phC`(r1=xOGh@!xb$s%=LWlnDRTRaU6gu z-befXkRJ6JKt8^3?DRoSPR^=^NP!PZ*BEI@N*Vu|hEh~T5wNF6&2Yu~tuL}2+IyPw z{HXPPheO9xEu9|Qg-5NyF1K4 zF38@y3Xh3xOu#S^vUbJHRSE6HsT}+-pnsot0Bp=9%i?gSV8-7Gs^uH$X&yOR2sU16 z(9A#QI(-U4LoS(|!$}2CzC5NB@}wv2A9UiS=5zlj%{HVB=mG($NI1A7n-je^aXjy%wi7f&8SDBvte zTi}SUAKJebP8NxMAFMZ{syi%g`~c>J`$A>Uxat#o;ZAZ)?Y6sJ$uuMs!r;~a4R6lB zi0>(YdL_6P-vHA4+Ld~TGVGLSz8CRLTg51B$g-nFCDSK!6CRDRzbT=qD7W1l(R7gW zZR`sL03ni=nufsAi{W)3!+U@jY5Tl1-ZHLCBM^7^6lC#^0J`Ash4(#6*apu=^4CK} zyrc+nAJK}IRLZ*l2yh+&dEMziI03KX!18^>QM$hbP&lH}VN}0~1Z+=?FhrJ!UmmJ0 zC1{dlMZc(M5vF-IMIOo78csq0%Y-%x_ggs40u7WNAfc^3j28?WS3a?eDpUt0)Ywr1 zMR_R;@l}zL@z?|Sj=zw}mxXMv;jsi|LHS;FpA0gnJ4ztw0>19B?EL`CLq5B?uM}M` z%fK#^p(`PUi}OwKQ^Yl0=bJ9h2Qs~RcwICV(@a7SO2AM8?K*uM;nEWYuXAB?myN@Q zQzNI;i|rHz*JLQ-OU`=Gp4qsN`OpTP2!}KU(fAgQU3C)|86$7+>d-B;$apgF3a8!nR$t`2Yk1cUuJ+-nJWx zQ0y+#%(EfAt3bG$jOkuyrGO7tPk~w}N*&nmw#Lb&r--HII-7d_T@T*h=TOv3(v6hN z%*HjojpfOgi-xe8qi=H6fe}%KRz|9oAF=HaH;$$YPU`KoKc4(~`6BGSvn*>$MoP*a zNTWaf;A6WPb+i$zHk#=OFq6!wfXw@3>H%#-bH>U?*s4w%$#{N1gM^(ANv4n&2#cy^ zUQvPt-E;uz!L&085oGoHosBx@;8b6V`0-?s;|ydU#jO{@L+O~tzQJVWQ|gk#32Lfo z=mT%*pGX^*-LMFCTE~QyZ%G?pB zER`89Pxe_tS_Q);j*}~Eo*n}~<1{xN>q1YvOAJ8y|Ee&!_;>FU)Ks`(xFiDY9S!^3tMyTb!W`6##Gls`h2NR?^>4tLsYLj=m}HiMq4qcZ|< z=E4i0`e8cr+88N^r1+wqAx<26H^5TgRqVv?tqD4L9ANpEiTSd=x1?bX#K>ov+lIC@oS|wSEnNt=}j~Syf&u0H_PDej9^msMF!|_Xz7k9 zH7(Ag!m?k2C=wdBqv1*YA{F<-`yfanZ>I};SIWF6iA8sZ7r{N+_)wI$La|Jztg4$5 z>#B!1ScTkp<0muVYTz9a9q*CM5P}tg?_gRT^S#p!>y-HH+NVSr2LrSU_E&@%J} zXo%79p5jXh979}Kc5UECf*p*9i-$^Iv@rVY#dl?{KP=J2`3ZBWtbBU>muQ*#7*ovW zP!(!huwOjR7PBA1)4~;;W|IwnV=5H;Kxl>gA|RfUp@l5*acq<_BV=xP2Y$bpxwvO~ z*hnxTHyFu^f2cEokAYJMPw5Y0=rP)!`0 z{8!^+cPp~%R{<#ylDI&q9kRqcfszai;M=%lj=44)IZx?p?MO>c&pWn2gjvM$fI#G9 z#av;KtJzcOhG=2?%kcq2!yry6ZK^nE463!E^~tP*AmOo?%Ngb~#G(%4g6IWU?fU?L z9G(87>)?Py`Iv&_))I(vC4aflNqD#*b$Rn4G#YLwc;mNSmm_0u!GWh<-cQ30VBzIQ zcz(vAlG}$^f7G%fsrXa2JC29oL7s!g0=r__ARK)E!Jr29iU5v2N3#qQs#mz4BB1Up|5m#PR?!9||u^XT3$!1Y|qT@bY5a0X}XFHo#r7`66*2m7;R>x9WdBVJ3(jlNWghF+58TXBESiYgW zy7SqI(Epk^b{X|VH7ECexsm+(UJlSj?AGn|YAi%ipfwjh-@z_afa5jvd3*PB)K%|S zqRv(>0MS2ySelW$a&tl6Me{L|wRw6mUmz`Bt0dLgLhm5RyUpS<*Y8 zWu+G|Zm1&Y0}oS@9v0YGKGx85W6$Yvur{m>CNA==Snt>BT|7qmS5dWD>jOS*c#NpO zCTF#W5+k*~hEDjw{KVIXPgR0|k+pHZ#bXeFg0}05s)<63$g&Sv=-sYjKQox-1ZK9& z`5!$Jws8VRZp$Z+td?J(-qvn+7LjJ|g!D_E zs$czgL8j8t`#HRuxNjsILy=s88NU0k1!*<#K`&b8FDGOVJ&cuG4JNYk+T@*kM1i#HADlc+0ScD+m z*3zzk9B?S~ezFS{F4U_~zfS$?L7K*Y`Oe zbwIqb$M4M2nSBr?GaaYFX;cAA^*q+zI|iC%nk5Vi`Uje{d-?v6Oxkl>4wMO7^u3+W zp47@W{Y$IRA~Fm*axUb*&b_OUC=2zn=yMu{BCf>QFrQobpqkD0+*tXbdxYoc1Jcfe z;!C)QOSa!6m=W3gcCk_P3g6?!oyzIo3|Ne1uSXI&-_rRoa_5u&(Iyu=j76HT5;sWd z6^An`)+fl!A>Un=^DMQrm1bz)<97!Oq)9D%RVx{0vT~$RDD_ZznJa z6w1)5B&7hRR8ErrwApj$K%`DrXz0HZaZ2paWf%qShek#t`Ul|%cDB?-jyDzJ!Khw$ zNlbuqlHD&nkzMV!uJ_jRF4fUvqvn!pVh42vAR%ahDFqPOiD|jpZiNRn%u=g>y4JxI zbs*W8Zl^ovSP_p1F|lv)n75wxsF8&=yosTf7dG_4-nM(``Ts*{f5Af`5kwn=KER70 zB(8QL9w)S!ZfQ}`;&Uvg-=lc0e1qP*Uo<-!xXim3$K$m;E;?;A_3TK$RG*FeR|iZO zvgbU0PLHwBauD`v?hRNq(=l#s$Xm&_0A@L!+`f}9dY<%P|Gs*z+480^yRTl@D)ufbT=i&JUWrWwm;tc8)vLkYW@9 z3@HOn_{0a&{BUij+l$;U+Y<15jsNsTM9YPUEnIhj-WQq;Ctwdp9pf-ekZMUGD-@~Z z*pH{Shbs*-flR5z0shRmqUBshmSj(zX-w}MJRsDEo77A@f4j$f<<6DFM)QX1KOUgC z4RCuiyxZzSMS(>(f7S+INn5BU4W$$k!xZZJs=1;sVT5xZU#zAA_DW1Mu7BZQ-ocpl z=#^sfj6sHOFn|JyQWGT56+;B0UqE+yprX#h`Cnv zMKvEm*!qNt#{b6Zckd6#XMl@7ulWPK8cUW4lp6;@802&Lbjk?EQ@(V|&b;y2nQKgk zOgn@=rV-1h{ui0{*RQusRIVaEB+&}aqW;iAegMe}h?RYQ@Yrp~q#(;l?GOI}GLoTo*>J?&UJy{~YBs z?IjY0kNST%(X24|-`bXKKg?|U$2Vcvg~n)Ei0N=!r)(vR+mf}%q4MS9z{04KsaFZK zp1&mZc(lC&-6fklm)5_(0gcM%6Qe{leQ3aa_JA)T34fQGt2TsrkT8A%>U0#$Pl>`@ zoSYL&5$0Ltg$l&G!vm{XUq!oaH+V4rgPY#HS6!NT?%+1Yqx0heYzC2=7VlXKm8g9( zt+x)a^OR2IHiJ`75vFbB5q2j-3gKeqn`YZf|m19muwA)iyT?d zWpzb@+wbPa?nqnaQ~_%9{qJt===|rjQA5VMJo`oP#}az6kHtXL#XO++=v3LL@H!#| zXeRD6aB#RJ>CrGn3HtTb*|@#^D>Sqq@@p}*_GK*`=_~cTOMTby`pG8gG>so>+M!3| zN(ZJK^ly`#1K$x~`(H5W^m^y%6r>bmHzpsBS|ccQCz$7! zhPD=oxU7x76u^h)jQ$vSZ)8|j7B!x}SJFIIK<#uauJXD6Sr!3&o*iV;_nOE za}M_siv~(ebUfrg*RFa!og6L!T+NL0eQVyC`CvbXIVab!xwytVx+bMQZx2z8$49m8 zYC3g0L;0iljY$-=&Vn?KeRP2}h8;p>{|07|J*UIF%s>mR26$=UIUeDq=P!u5ua4fj z>RIZDRF&*`1iN3GYmpN*JgbHs0bgCI!&H^_sak9A+4(PdII ziTxx@8h$-YSsqpFk;7tiNVmW z4i$A^H%8=si!^9P0Z)pU?a+h!Ul0|vI*Up{HRYRh_5o=awB)d6Pnq^d?Z?iB*M zCpXnQWQksG5efiO^y(@r(;dQ-K;=DyGgh3Zq%rx`AC5r1T(j>>hKX)aYr54x(-AX^R z8Ye6a9NLx4LCJ{QpeYe|f)ZQv+x6A&&+`?alz__w=?a8`eTp}B;-Q^j*4RQe@BS9F2wRW{O*nb0&4BnGYzG+ z1+G_BQ81t#DT#`6ZT_1GmB#MJnZiZ;O8NN%?hkve_P<=}O3y60*R%mzXrX?GRtfW1 zkA+Y2bXrGlb?kiQ&J3&*p_z;G%ru-OyNr~nR-(OWPyShMf6DOi=W8S}@M_`|1`g8& zovh-SfbNH$%W-!vcKw`0*~6D>%#4k~sx2~(+jBvM(?Ug2-BL#~u zG94aJ_~gD)GLvAD(&vQnoqkURi5iAomjH0uY)luiiLXFsQrBWvR5m|V7&KlTfFx;S zCqd9aaP*qw`j>NMz?%D%{1_aL7(o(6bgMwJ9ZiBH{tZ{=+y5NltT|*0^s7F2olmNG z=Rr^Bu^c687V{nTQLE-3L#lE1kc4Qulke(FQu|-=ycBHRM4UgS0wK-Oxp~@_siA(gKc{ z5UR2S-2f<5p9Os z0||=+pt1+#$_Zho3QU3c0;9ReWch84P&$=Dvvz)l)_5JL3P|;ns|vDG(9apxc@J35 zaAg@|5Swr7qI^ooRgG{TFY%Uf=tYI)O-s2DcbH)F=WF9M(?~$Jt>FuQ0Ppu=VYNs$3W@0W z73X^ftvk#j4Ib3qN3-=mR4FTp(~`QuppKv}_*qN%G)uyNa$e`U!P`jf$1_nzcuM=* zssKXeM>ETI;?YnllxVH?JL|CvO?GA-1Q!w&3f5xk0EoG0wu}BnhSo47ZsD6Aq`E*^ zpDKJ2J)SRvLGYf+TWArzTbY1>3wyG2c^2tZK^jHwG~5^eIv2(g zAU+t{oc5{cJ|LaG;y1OI3*jmBW=8*A`;R@k<WLjhD*Hi zpe8ud&_QOUzP^9BSRw>BmcKM%=K#~bG+TPssCrSeNUOE51ar6~eIQWl%e>T3WG_%zs;_y8* zm5FHPiCksZ`s!3vzEuT1oUUhD#ZI1G`NrC06`1e&ENNpvg9$cJ1en>ov~uv=D?#&P z1B|Mx>38QhqA7ANTXxT4y|@QBy>Bw+7>-y8lwgN%QUuNTeng2YQyZ z2iMh-+;Ves`NaN0jufr<>n4rdo<|E4SR3cR#cA;=W zWG5aaBZXt}Qm|)6cZGvNV$$a_926N+oz0V0TrWg*{|K8xLs`2Rs~Y$ufePlqV3CQG z3>EptS3*3kB*DV;plR(+p!~<1Rx@M!HVl#Z7WF&5;)Jdg!dFgC^PWnlnP%>ED6^Sb z?0-pA(1l%*(&vgGz$6BVGSt=#zL)>Bo+ZVdAUca0EQj@N;5UZ4sAm3(YR-nAIVs>j zBUgvBzQA~Z6)|qAkonbl;o*&W*Wp>iOU0F2y=SJGQ)-5hupp3*@Aa2P*fXg&yYX`B zkA~RPL~<}GrN|;9nU)fGMT9@lB#2yNk0bE!k$ZtNcXIr+j4^n3uuJONm$47q&@_K zc&^9v_}xdijtqN{=p+W*CW(=!vSF77rqx!xpRVYt4;HnJhX`CWymUglY99w0#1)`Q z32dLd6_OHLjDcnlR6|Ycx)}TW08$UzpR`lLyBWo&N?hd-tgzKA(l^|W0#E@)3g2*PTyfE_tquQr zo=UOBW2lNi7W_2xt6ivBd(L4H;hNo%x$zVqjZh03GeakAXLR=3U*?l_2kAU!W^w9PE-3; z7kDS50pw|wWDKDC%A-xI0B_+W8`L@bi9;%CD-#nRDUZHQT0@`0g3tGA96Cb8z>YsN)uf*pY zyV-Y;%g%>Yp3$jrYgCB98<)9&_Q2}ew{_^YWSK%Zp{*keb$&cmuLwscbc8d-0^H;G z)`GkvG`OAH@hQc-Df5##detY=vySkm1fHLJC1gF8KzVe#icVkT6*R^}3;xvCTEPH^ zK+61|>&MnQkn+Xl`1BC&`-;R*o8t98y8Wn7e!`!uLft87-f;m*5n8lRLUOt2&e}va zbPFUDP`W{?9_7}JO||Q&z8|W7)TgjAUHd-og`c*7vW`9Dw&>0k3Pr$BX*VY z{Hz0V6(YFM>TN;GYtF+DNodd-^z@T~-?AVZL`KNT;wQgM$Chuql8zSxoVUmTGC zrwi|!5#`anu!We^lQd;qG19Jw!zTUnm?^)WK+lCMEIC2S5)2Ey(ASJeI81o^=kAaQ zawbNUl^9d$uq`uSUOblU9mkJ@D6d48Jcnnutz;n$h3`EDgYB}c?Vmr8p!i_L$O)l! zA5bSM-SrTL4ajY)?D|j`VkK$=`hr5OJrXd45(&BR{$X&MLwetI4`^+_zi3m8fOmXjJi&ZK?X7-+N2?=z0@j zP=gpUfWj&HclTjOskWC_4knG+eXiI4kn|a`?^1@_i4OE>`X1SjGupnfOny{tM%@WV z^~sN7OIEOlza_=~kT!0AjwcuE(*Q^Krd305zttfY{sb~ve`IPC_A7vMl;XhAoQi{O zOZ%7x3T&8!=ce;-oUKkh3)1O{310oGM@T}-3oKs-!x1Or&r$e> zL@S@%TZd`!i{niPL1*1_M7}1V_UEr(_z*@Bhq_i8NHG`EVP!KSm0gi`1tw6w8*0#r zKBj_fq72faXjl&*qE3&3tl$Ia+CG5t@=#Bab==Ex`0`QhKhmI)PH5`9yqv2FeY7hx zFTk;cGP3-of&dBRQSIT7wCpYFvy;CpG<`GVV2D zGM16?MS%1U51IgnME`xLSA=d%`6`NDP;sb>eZGft90Gx&;3(Aa7t6soeI9_~LrePt z(3MhryO1(1KpoOec+#%%RVXVo^n%O8_NmHbao?B(tCFhte1n=(v@PCzC+3?B33%6p zqcxH-lRp;#iweWeeW9b_E=n2?5K{u%!`(x6Q4-#U%j4*KD`2N=Z1CU)kSy27074bJ zEIMpM3`diq4*v#bg>6t9 zuE^elj^?*~PAV8?r1o<53OZgyxH(f8UKY8MH9s9?p1BtYzPi;H=xJpAoh(1VSVict zD{_H62juXtgyHnPxsO0s0VI3Zf=kr=WCQp4x_N!1Y?-up<7$ku*^0nWM9bNJu zl{9{(BFv#D10tRS`rRZKe}R5UVET@A$&aLLK!+aIkhu|3dMcw(Y;wbqXPHY5GAOZXx0YcVWpJlpaAT$IjRm~ z^GsA(Zzkkw<|W0G>)gy(ZaQqRwspDSB7FGW5Hx+CV2V?9>~dUu0Tm?7&iq#{{>G%i z_3m7m8@C6j*v(nG;XGhI57VdG&;Q|`dve>+Z6>+>NK0^p7k7UGfKVqPjhuF5>J;8f z-+JJAej2hWXw{o`5BVhyup@E1^ZV|tKXk@zGNjA%G9;%SiQBN{E~lI0O*zY3%;fai{P5VenYWN3!1X@I4ChiV0#n2SwC5jf6JM~HWR=p9u~ zF(^BVS5pP2^S+dk#wUy>`oc2+c|R~-2ra?W+o$aRp#^~U9k zwZWA6=U56UeJ^W&JJ;+RX?9rP$JOM}&;|#Qn8Lk#=d6=sE=Px(ZOl^H9%j!!Y9JcU z_>HCU>fzqpQP06)&o8r%R-BzLj3?_l-nh@&56*T@RyeT4jmIuB6tI^tdsdn#olYnV z?anjtN4vP$leMK)M1*KV5WRUK&Pic3X}a{o(3{%Jg9GLKdPgu^_pPRswiVh_l5Q%O zrG!x_(WEhY?)#e%zoew3=9U(__t&TeW&w`#f`)UhyUDAts|Zee^C)Z3&HbEj1YfoE zW>a8B{p$s~ps?(#v&0pH3S@4^xw7;Sj$5x{(_s(9hxVa2j#IJX?p2U{AbQ{gtg`wP z@-pA~!)hG=li~n!wa^?0RKAYd&a6S8n+#@=(^EuA5162_nlZ|CwGkwLrbBj8& zZt6-i$l0W~9XN4lf@=9-=_>P-jYSxWhBX~l3ZilRY0LuuX)VB-J?&^!YHavno>caH zLqHw7Um!a5P)eHM{eC<9j`WaNJ>BhCdX1XSC~cu#JurVKr0b)wwMV^y_J zZ98?GX?N7GudnA-R9JN9&iV3q#LIQS^Wx5h0b&j6b@u;oEd*VHV3(+-63i@op{-5D zKC2D)D5cbgH(@mUflJr9=V}3Av>fhmdffvmk900hQ0k8;f(iVTvrlJ> z^WYD2J;r_8W!+WmNS>wCKG+ngb|Y9oEF)+51!Za$fV_>v?+~4g!WB2e+xO1LX)_$Wuav z;38;`I>u24m)_4Rm~IseLgB?S3g}^wDn23NA~jT&d=zCc>6X3aJPg84VrCg|!q&o` z5^@Ev2jYW?v?0*sqV57sFQp>|l3PC%{h_}2ZOObggpYyof+_B083bXeoTI@7bT8FZ z5u@AO-eENmD~71dUn8t39>5eTN?IQ{nyeVdRIyC7)@hj>0IGQ?EN;_iNU#YQf`jm1 za2&(6H#*x2Eif!KN+|jFsxR!~ChVdx^lnsjhZD!I75d@xOqytl2EE)(t*mC+CdHxA z2GI+?SpKr0b~?vw@pvpwPcQD1C#;P_(fY^5*ozjuvdhHTN>WGG=Lo(t9*eIA8*G-* z?phlF6#*3(q;M>ew>OY6zPEc1Mv}AAs*L4OQ}mipjuYXsu{dx}nTa^8U7gi$SASmr z({q+>_iW)X79@!#a_FSG);DrbV-!-ZpNw(z)%Is!a_eYalFPNky1%?pEn|)Kq))$4 zxs=F5yA1dV%jLKFTW6>*`RHfumYzHc%wc5ii=lpfqxg-Js*-mf9oSVD-$#X45__7P znwn}eX5=wYXqQVA_vtTCEZIZ^ z0Y%j%-!6hLVy8ZT{PH2JrrJIsm_humHieHBw2n3xDblHlHV0FVkQ;G*F3T9FE%!Qg z^I>0~3tD%_-o6Rp)VP+yjy(55%1*|31i%o1E8}KHN(S>VbT^lg)6wSC08(Q#+b*m* z_Qi`ABa&YC=)!&ta$w;wWDlM}f28?+ykb`D4bdF#oOS5?_HddKKSaYM(K8wYi6jr4 zMS%3<5{VUiyUL7{87VRD8}@7>7cQ7??F-cwjQ<<;mZ9>VyPnFY zP4^$_&)$C+?{vqzcRyyNr6hd!XJ~V>GfydXs4@$!N-VvCuKf2d9a{VjXfrq!!WyiQ zrS@1ABzoAX$pDhuIJiZv5ZvXGJV$^Ln2GuKk03wpQoji zh09j^`B6#3lf7e!=z1cw1K!DQmPO(7)NbnMlWg0KnS>n3@)n3j4@hf^h-0gD6wsbdSk6=A)opYJ6?M`=yGi zYPUYp%EK?%D2+=YCB2cRQukM|roMEJl^wE5_V)JBta-Dm)Y;n9!h#w``E#v2ho6<5 z2qbl^WLX!x$tF$0SWK3}U~2`jH~a;yl-MYjIxU_{E#vKX^VQiw=9$vj3Irm_ov{Ei zQGdiEG-Mj6j=9*?GB&Z_n~ORnDEi$J0Q($4E9%qOWB>I|m-n2_OJ_G2^g9X$G*diY z1ucI2Og;gXgvK2&}~H6o|noch$TAhEMZU8#G0mZ(4avT}2@0lM&jOSskaP*z-TANJJ% za1oe(@yn+KyZLX`;~vw&qBGy;N+4F|&E;513A?XMW#4)KARB!DWp;Y)idaos`ncqs z@$hJ)>;-Q6!z(%feUC(D?Z#5d0!V*3KD_d~qFg}lAXbfkO5mpoAR2^T0*9AiW52dA zYT_F{zXpWQzHy}ikHydW;QD-d?SHf+`i2nojgR)$ODr}B(D(lZr+Gd>jVD^9{qRLA zSVr>sp8ydEuJqTt{8G2oLLv9p@=smc4-;&gBb9$!#UFn$2s zAGYvXU|brRmc!KvZ_&081#_8$Qm*wfxMGlTx8k3DS?<&u91?{7f}fh5`D)6#*UeRp z%{Mw_o*vWrMuH2-DsDySe=_W}v_;9n*vBVf`X5$wp8OVn4Nd=tA;gQdg1nRak{4nZ zU@lgSMCU@%D=U%7#T8uyBm08r)sDM$sIUcDLXRc(#Gw8sBpfV3mUM5(7x+&~S(jRl zWp9O3LQc;712Q;aQc+*sbL8ruiNOh4uVAtenlZqyVub5nvG~+Qa$K3}X4D0e<>gQs z>;q{wS{4zKDxYKaa*EpCy$HwPxU{jQrDbvrk50)_bk^r+;k*x%&?D%1V-XJA$ayjo zcOBpJBwoEKir1~{(8>Kc3V^4DM2Egg@87PqGrdJw&Fg|{O0gV?uX8QaI}3?-$P4P_ z02C>s`-=ZV|B$-~q;C>?-U^!aeN_6ekNBOUuJZO~mZz9r?sCQ_Q)Ez#Xk=#GVgcPH zniCgNlufj22+F`FV=8Vk#`#v}T8>bg?p7lXKEIHuKlQkwbBB}}?5{8!)@=ajG53B^ zO<4kFlT5+V1EdRK^FpacEYg{IdsPaE&i_nrtv>vD4|VoBDoKpWiK zJ73GLLM{ezfQ7G;=xSaQ8tPp+S$uANxN2w0-|zmp+mZAQX1Z!|U#3M@NqR zc;kWPdkc_?H1flrQIE;wmj#uo;)2%tXWAMSoc;kj`ZWt$M4m;KNPxRLoc7VJ{K z$gIUzx(t)!iu~~NUm)pm$@lWQD`Ny0Ub9oDZgUOZ!m`!ogxb4C*?$d$xhaf_Ai8h2 znpIa+*)?T0pd&bC0CUrcNNg>&CO3ryR;m=OKK>SQNutmWAy)saa+mpUt(JP)dcR+2 zTYdT&4A%-s_iD&au>Fj%FlBm2vZ~6;Y_Dh_)5VWoJ5(O`?Zhi(*xfF1FpGc&njiZ} zD@?Y1pd0)EEygdGN+8`7om`}|8?dcqr0)lWA!YVmUdDKm*>MM!Z{lbmGdg1Xrg0)* zw^T*@qv%MGBWn~|XvXie8>;d}vE28Xj*iaIpjGK~;fN1NP{uMe^Y#rHdBnIVj3PD4 z4{^A|aAln9*XC#0?!PDXzkj_SnY9DZ$FlJ##!1p9VBG6iIAaQn)Dz+ z#_jw5;cmSJGPEx7<2&=b_M-Iws(17_&dq%x2haYR7n+$^ITur{g-?3@UNYIcWJ}h^naCtHj5V?dWlQE>C zutr)rFChbMo~BxwJmS|*LoK?>^xY-{s(N25o7}4wnVuhUWu%qQP^*^hWbTi~9l zC0$!(mgUZrQ^k8ZBWT^AXyL?N706zr+0Pg z;Ko5Fg%hHxz&qNa>{_?%XLMFS*dn8@^LUK&F^R$h>?TdIjK|Jt9D@8)V-JgAeWw5y z_wZu)siVk0w};#?-#Q^^jr_Y!VcR{@be9wO}Rl z&`aReXtJKrh7q;0p5nvees7NVGBQJNl(~7+NwNd_^7Gl1D*S)-qJi9mb~?~2emQ3@-@I9HU}p+17)#Ry z%bxuG!Ftg2gQ>xOh>Q_Y~M>-5Y=>=;H&i=;I5vn;)NKJ1O^gGt2++$8MbbDHiU9Mi!$Y z04r#+($nS4o#C*AvK=|n^t30}-ujZ;lnbP%KsG>g(a^_FK&i2XAiDGk>~2R#hts{y zmPEOxwI0ykh?XJ2_{$4C;B1KA=#ms~J@0Qr$@IOOw|iNjEs zZxVF^BEh#KD<@z+HuuD?#%O&CbRoaK}wAEyT_F zwaQ%PU27$N{Tv?mI8PP)km|rhTlWjT?}$Rj@B}Y;s0NhDK-F;04?kIS*0TC5#a7i^ z&;7y!gdL+$L=rG*{4vK%_2Qt!&#$iO9L#Rt<6GOqZ7n8%&w0)wr=WKn$)sxnAOxPov%45Pz>p<&n@LRaM3lE zrTpN6aqBD0Vhn}T11~RhfS?nP;Kx#zVMvVEzzMaQYs$*XLtoa{B59uiGBeR}J`FU0 zgX@see4fN6uzI!1>t&(AY!EDL3r-F$DT1**&(Nu*KF{{CaB* zp^B@aUBvnJHH%jp0NIOr1B#-@&yV%tU7CQwh_gN8#~Sn$f21}zt`4o3nRbE1vYLc{_sOC?!YosCSb|dmi)cfj9k83|Kc#4TJa6>_40Hx zsbIxqXSWvnR8$IKzBf6_B3|^g%Tp6vO;)uHzZcW*^J=qf%8Ja=zuo#}8!){9SK0V9==Z1e8uvwthVUXe4zu`2S0BDYTJ*h^6E%H# z$au4;D^p;=KEr1+V}HSOOACv04a}V#P6y@}e{Kax+cBtfFCMd>ku5U;>|MERo^R&< z!nuDW=5QDd}6m&Md1P3oy|mix@FEh>!QQbX-Md~V?=Bz}b4 z>ea_k3`&-#Lm3A<45DCoqOW6-)y=7vQJu|RSOy;B*uz8(K6=VT7v1r-w6R+s61o}% z3WLR!qXh~ZgBFWuf1fWLw{&N{8_g5T7xn*f_U6%4u7CLOZXQ&cq!JoV6Ge#3oI2Hs z3}w!AkR(Jh&*v0QrBuiWHdDy&SjA{-Q}s#scJCx>PLSzy%)Z1 z_VrF-pZh~5r@u%<)v4PJ=$!bY# z8IS9ssWnCQf6)3a@FLD$mjX5Cf3GU!@QZ$pI2;G+HINNKK~-JsXH(_Z>$~4pr}kxg z9W*vIbv4t34{&~*{dB8d3H`g@3=-~euk@$1mnET@e_hx5B7=&o4@Xd=qiMkw3e_%& zfz8s8t5tR6>MsmWL&|*>5XNN@o3x2UO}&-yR|ldqFn;>^pB(;XnmHM3Z%97PSGC0$ z7|y(}LuI^jJtOFkv3F^qS^eDT+I&9b-`|idg(P#o>zt~%O}7nkAPOJj$6!?gO7E_z zt=&7aMU-+?^r_0R``R~`~)m6~M+^PPJT~I;z<-js4{u)W$PKRgX zd_pGcw|8Am39?*+s=)jtBQa3a{9CfIiJcrQ7t40&b6BaRFN;FT=48q2qZf?F_ zCF6a6n1(Sf9rP=LUXq5Y&X-K}Tz9FP!I;E${A=X`ub+$|$9AKr6=REXgaZHYU&5$! zs(w@8i#t@i{)L`25|BRH)xN}>ob>BvPf)ap9Gfc9Y={_O&=%>W{&%5AZ5c~Wq(qwU z|I@NwAZ{uAEMr_WRQP9vPYPPXXz2>BQO-cf`Q^s@@%Q)hw)SacW=t@K-WJiiwkV^< zKBDm6_?_qmb?(mV;+?=)#Ymj)MaIbUkMP#>p1{El2 z!*A?g^UV)+LIHi1y%vH;uL$Zf@?9N+ePa6b8(Sta{yT6AAL}=q2>3&R?BEd1BozRi zl1{(HaUI=BQbPt{K|hWMn+@08X(iS(+ZW02 zRj`_qgn%t2#^Kc7k_lEyi)v>^b84T)D~GRcZ^qM;XcpG@{^yyRZ&z-HS1X8amq9d? zZhHcW^TAE0$VpHztWZ}!FL~IhF=wL1H8*Ls!1CX?y*2+iG>);*mGece=^&nr==HHy z3J7HT;Bz^u;?7AW?!}-)+ZKRK62fkzz-22l%CtiYoz^zNF*lW=eNjK@i zS4&Rn??>fptDG=eGO@kQo>^dPzeG@_gBN`E^+c+aONvy%egmknZhrlpMoKH;N859SfLO_O)Dz7eh}Ol$2z)kE*(HSr0-Mgq`_#R@e*JoUrZV}epff4w+R_kd5g;*q)A<%8!JKoU z@3nemgPcecYm8CTpE-j8qFhotGR<>=_^Td71%{)}k`Z~-Vc6;Cy%dRcd{NF{Lymvm zaw98oaS#$H8*p1xlfSg!q;@5y9M*oZ^KFFGu_@8C%k+lvnOcL!5C884x(ib*U-DwQ}+Uu?fTX6R5UGCJCfQUbT0X?u84t^P?YHO)zK=g(J^2G&rR6c`NGcXN0;K zm^i*=oxR+TnT2zGN5D{8S2ouBt#>KK>vGedP?~=8KA+%vkIj^lJ;snXr}Cj@c)4;Z zLSxPTLX_KYsknXl!{iYPNG((E9Ur|uw(D5oVZcU$NC>v@Wn5TNsXg|Wx9vLlMxQ{T zn~0&W5n;vPn~H#_IrC}b@rLaa>FPhuTk~`4)&eq}@T5M*T}f<<+NxgK8eyz0Vf5(* z+2ot)@oRMh%9K9<(HF^g8RL_dbWzDu2LR{S)`%ki>b8!l#g)k`x4v90cZcJA>8Dxa z6HmsIbrI*BOi9V-%O8)Qy8WMRwY$r$Lw_ztl)lRgHF`es@y$&1ahI;)eMtO1JBo)S zZYv78leEit%}?zkA~!0w($sCY)`&&>&1^VPsn`%Oo}3>@NyxW=1d+P%fBTO<`C5eU zsS+F>*~3Tk7#hGT|4{LViDDCbfv`lgoC;nEQqWPPLlIH zZPh`O&!04QnI1)!b^SeWUj2FtoEc!dZf0g%EdGt-%)d7$m~GWLCT`wa+Sf&!UqKl* zotUQ=k2lXeECq|GTpu`VfQW8>X?6RisL5aA>~wqsKVX{%@O?dhNI&POL>}M&1Pr1A zkhebtb9>)4WiLdy1yXp!zFk0`j^#A5*Gkx`UFwh$lx+3uHvTVQ^VBjl87E=T3*+MJ zMGE~tpx;u{_J0;KYs0OdNtb(hzE;?=z5W2qOC!4{URmyj-Q^;{_`Z2QgKbF2sq7EKY(?mY_3WG?TxGK?#g_Z zI%1nDPopxv)ge7nMW^EShV!)dAM5L#>gYTklWiR$YZ}>J^#f{r81Zkg(gN=Y$53g9 zbjMj;T{3E;m)`OqGoS0EeeDx1p@%~)OBW=rh52o zV^8Jv9qksRa{9)Ay?kJy8Z6e@h0wyCZeIrqZ~@tT7a#6B^J$8n=xV(^P>$01Yb8e? zwOC1yJ5wgSF4mJzbFBv+*)LL+9f<0RAL{`sQ=Hp6*H3iJqShU37c5HhpLEs&uY$tY zkoDk1Kq)tf8NG}N7ZTLx(7(eA9Ixz7yJaGb3R7q7zK-&{$6B=Go=J=RV(AXB!C~1h z)SBg2wr{M8^Ao$;$N>m8-HLL5Ps+YXm2LV4a$;bMs7BhN6&gdtygT0nb4Et#oNNeM zgs)K|QplHDO?xOi%?zxR5wa-nh#)phB|9#TNF!9l-xpiO38g&RBnt~@|@eqHmuCSPYxd{ zXcK>77-a*1dtKR8MnSG_-BO?t#OU35Rj$OTY;bdoNA1diy(#}Z|z(Kl)r-ee2mw64t ziKWqK-UU~{8r=$onARtQNtC&O! zvRdqgqB{MB4{L$mTBjsI5RioATV*e*;_af)3QDlqd0M0jz+H)Xv;Mt^@rikP*+_G8!}M_jaB!kj34DoW*;S?c^uRVUkRYWTQ)Cr+rE(^)hH z5;0-#LFT&=IV`9N?-ra0g#>E&T`sgIUabaemM?zHLOLpqNa12<7GP(33(Ua}8#n|_GWIKncc+m@KcLmV3HmMrt};6$ZPB{&QECL1qv6ki%x5%@ ze6rB$$C;>f^`}pE(<60-HMiuBR=KrozkoegZC|>N_@$--sk0GB7~@OiLl`s*!}^Ko z$I+?pW0z8&ypQvHe9?l#v}c62P{)A^0!7jgUm)`3l??t{Q1HQ)jftr?qI5@)bW5Qx$AU!t}I^c<`y^Qqb9k=QOIxdce#DwA6Z+1wO$Md4W3j z3ouNp>+82dPC~PHFP^gFi(lpC_VMPn(1V*q^x4q{Q}^#Lmq`!iO=vX~DAKXX?{k`LA{G~1Bd@ekxX9>h z!F2z3Ky*7!z(Yc{S^!%#e?6aai11zlV0%}VZ&r~RQ@?!q@=A8OS)2NoW-6D>%_X{q zVBa_m7JwwZp9L|4Quz*?NWv)yD80%^2FWfkY>DqW&Ya!RS&1HYFdRF1ZPh$U?|xC) zRzCP|jz~J4?&#D^L`SZINLXZl4sl3s`-LTM>YQaKva9UdzGD?Cb!YYYG&Pd-GGmGf z?SncSJ?m!;g)^_)32|xv;jQ?HqJa+XwTP;I?l;84cxXy?=%;n<=xSuaKnmvMD~ANiRH z&j$J(Hmpj_@~cw&LzS?0gaw31r$1#%CQ7)#Q_}PgfqEipA!S-Xy)F2Z7 z+ly5C9^QpZ9_=3Djj#~7IH!p-6nIB#BPy~$3aYnpY*VMn=nB(pBk*#3>5zjk&EmuR z7_NKyY#&PDKbiL~2MD#(5ymK3;^{^&EGepXQlcKM6mdjyB!hCF zBvb8E50lK4PTk=YlpUxCH+vS5#j9(e!9_?m3NMd96i)GaskAj@tNPVO`?`T^D5_i_ zigotdnov7)KFc(?Vbkrk0(pmFkUf$&m zs+%4wPi;4vi5zh(2DDn-<9qLBge&_`caspY!jUlVig0#c${M1&p9K;1sw<1QqRQmc ztY+%EK}b>xH3N!kmf>f`jD3rj2A+j~V zUnCj$=Njhax0k{$Q5WB+>`pD?k($dBP4x2zhFJ7;Y(#{AHz~j7^dCnfuxDLQEbR`W zinyOZz*!N*1!|P)aGm{=C$>+>LByom6J@bc9@C>!4}{KI4s0k9y5@#z=H+VYIzv~` zU1A#xXa)0!Gn*a`al^(0M$Nms`sVz-laTF0V?o z@Mw2t*?p@yf@17Pj=(;dFIgEDbf2E&T!C-)OsJ|$s3Wiao|JG7EUnkD*e)d6K2`+0 zQ0$@Kdg>cd`)(cO{HOnWe#Oh7r#?O${9!kntU)}eRtDiwQB0u>VoLtb-p&de%k)+;I79i1L;xF;(HdVx@#P~K`^D7V^VZb z{EPw908vyf52hMjbDrskVdwteO^z_A4=Qr2KaRbEyQeXCs;|ZO)2{imAaQ%zOZQEfs59 zOiS*A>Z;vclQNZ?N>bxL-Hi~xlx4Nz^Aq$ehhuOt8Dyce$TZQd_sU}8I@rRF*j}#L zV%40HIe6-FN@Vu**k($lfn`y zqb9}ViCaRNb5mLLT+<3Kn(fg31X3(y+j2;p`)sM9UHb^lb4U%{E72SX?9?9(k@xWw zv{LWV)IB7UsFrxA>#)zC$!_yny^gO7N9sM@_*V%KQpmIf!W!|x`Q|jm!cc-PyI~UD z<>I>0W5*YH)x<+ghTu_M`C1e{@i;w@!@qSU)dmt0{^V?zoZ%<3+0^4dobUyIlc%kt zLXEr(^qf&*o;km{7kzEhDRwp)myWMDg(x{X0v>g+1Zx?%~Pn?iP6(_WQajo3o-F6oEug221^GBN2-CotuZ+Z zZs{S`bG1w=FAd_N^~F!lE%vfah)d*>-nr&`>oH==4dGy(l0tg*yr?XX)92qYq~&*G zLBbk+a+f!xZd#P_0(2Wn%o7PzWS4`_Y;((YlR61a-iC@M47!0`WK`<6t%!J`-QOE? zwMc$fQ$N-S=DL@y#oUDyJYMjDV?Udw!?;&hSF1V|xQu;2E>&OUG5*pB5O#?HMmflz zRFH|ay+K|R#`ik&C8}6fpw9I9JZW6`yJNWX4O>vqkcs6ISG4nN<^gQQ4p9tUqVzy}Nb$qx`gI#n+x&|B1F5@Jv+19cF*2sh<`qc9)`L{e(xBo2pRp zL7&QSc#4*W21ill<%Tp9W^$_pKnx6fvPku3QHDeRl5fJaUiCDkG25nPd@4 zFdyg7olws>uBP_!Ta~V7s?11m(`24#I&2@+)enLeE`+}}Fm?jO@dW~ChZ~N3#TdUm zmU}6|KjF*Xiz`X;vy?=X=7=63aEJB{%zN?XL+tvA#7& zcA@-uq0pXLpOK^qq>l9v22oY0UhUr$)uq{!XU=PBabB^oxRVcw5{f|Dm<7gAcnzD+ zU68xbs6`Rbhr>P1;~YJRzf#jbt?TAoKH9g*K+f$arb3b4J7gkbPkqf$v>Fx+MhJv# zFyg5M(?*?#8Qe^NyytXroxW>8X`Mg%>utUWrMA%MMjS%yn5L`--?6!V_)us_ny=KQ&*t|lL$-hO!{){m}3bjA={(s zr$x_eU;`SSYbdlHIFjh_8;PqRG>S*K2v-Ds8%*`3+>Uj=enC}7&gB;ja;ik8*TEbD z=5&`rn$zPdUiukjjUG$$jNd{#&-)ZRMSUA&6SfWOdK7Wh` zKt&!=QPH;Nv5|b^hhazqy@v>?&l-Xf@eW6NhKl2NkbsgEJJ|2 zBXMjFsW(@c&SbO2CRFCfOmMQO799d{da1@INh-kP>a<4e0yta-n{}Ln#9`Gl&X?pH#QZF0#MMr> zGkhYBD`KH$H^>!H;gg9A!(U8o=X>5QnGa)bljLQ#^cn~)WJNo z9}QZg%`do!XnGZmIK6{jNR{)u$qa8cP9GIO7v4M5YhenCLBh`IEWlk3|5g+?3h8Q^ zK7j|aChJl*oo3H;r`UzSZWCt)Z~IzjS0{p(ViSLvEmsSszB;6Y00!5IDjTvw9}!A= zZGf28;vX?a-h(0@1-6})Yx=Q{JEKbPI<@+j1siqA68)v>*Kv+b@J2 z)$GxtbQ=K1TDM;%QenGp3i40^P}hWMW_K@C#fGC*ATA}P@Uy6poAgIm8Uavx?;=?l z|5inM{Ivt>REX!mhafhl*o)JPV2TE?bngpFuJgGc|PlDH8`NaVncObdPMQeGlo0^K@I1qXGdhDeaF?ZlsnQmob< zGtnkMUFfPkA6x31URZT~dwPuG)hkMTs%xFPuTXJKRm5_y+bj75H7&bbzJ@%4vR?#K zq7U)^ITTqR?!anmZ1e*el4*S*0i+DGtSocwKENmL z8xlxTaJF4a;~W~&y!wIGw!N@u{XIH$42#`uj0Ns`8**Lqa~3Ea(LG;60?v z1Z7wf*e$HHa{p>IGs&VVaq@Q3WX=GHoZU7Zd>LTf+aB4xrhGzjUX*p9;96S zt#kU_nf7POTdfurmH*8+8kLByA7%*1NuvT`a#Gf+!ql z^$0!c@cp$=!3xU7-QbCWHQ$zEsmn`xOqtT^2lC!`56Jnz^$(zBuZmY~5{f%^|&?MabwP8t(+pa>9tou5Q5DbPA1 z6NxW=$r!^d3)seT1q48NGm#@1pu;5|AhGo4qUrH|j#Ho3Dl_(xa`s(~TR)o?%H|jS z)po*-i&FjzzJj(dLZ0P}KWNfJpB(yq1_~ZDB&E-C#X54sco`-Mx%)xg+u@mBRq5=z1vPZn;;pa0pSi4s3C2v z2ziRSI4!W5^m7~#2`HSC+b0v}*QH)y#pn!4UV-wE1z`Tt2~ETkA%H03^hijKj%&{2 z6(W5NBfLq!Uns7e{Bze=jL+0_(z`twisD38G-0X7sT1B936GzvAv-6HHTo84U;uTb zx;jP0x{+P}63m%^PTEh0^s-*8=b>>FS9HJi1nnQ+F8M$JK5hd}MB70Gc(scNg0L1C zd{$DFkjmz5K}kUYNoMLa+4pu|>4IOe7V;AjY7J;m?OX0807?AkFI&#%|K= z|3l&T-S~_%S!!}N={lMAr^Owl`+iM$zNxg{g(4hur!Iwf2ON0HR6<-+o{0*fy?29* z@zr9nR~~4`AV7x-8>yqV3T_m+vzUVr_nq0L$qFNzDN3J-Hi;!{D)%6SpWAtqReS22zG3u7-#s^&XzyQG?vfpQE= zhDU^eW=X<*Q5)kAK7GCkkefQ0w?>gS4H4gWn2Cl5U#*=^sxSd&LzQA`t{e!R#&H1# zc@q)~6>uu|&G_lo%G>E^asTV6!$sUU1?L=cpsmxQ7c!ao5mq)^AZg5^_^%C*9aADn zM$L@p1Kkh}v|zbx&KM|4Kehe76iIEh4C`+vnS9C5U%2J2?M`e?moZPo+@`p0sxACq zi2-aJrTMq+&x?%YTV>Cf&44fv4UUU+AW#nA)}ErFi}XQse0ZO~;$iMa0#)_1+(z8KduQ1B=+@04C__;nBK5%S z#?@1W$7TJkRuYm6_LY8(e`si^u{B_Dd4V;(^kbja^4up+jkn&N7zCb!e}tq_`r75x z#=wh=EyaKdD12q;9xUp2PjM7~Pl}%UCry>??c)Z1iCsb|*Erc35)>3Oi_cLll+H@W zlC;yVB_4xvt~LExt+ro+!Y6~yE($g66O`wBGJ8MaXbH#1g-aUw#`is1I*cqQm{T>eL5+kE3q0?Yx5Hw)n<@RN}cFakm}TQmL@RrsU+O z$6zA+GZu@0dYIKvZHLmM5>FVTU>)UGIaa~kky#m3wR3lwSpC#@?HQz=Z{8EKk#nkT z^VjHW#lCA+{|KLK_OUj$(U&-*GwpD8@;Mr$>>u{sO@VToDQP6H6QQ=P3Tq-+S5xy) z@0^7Fm#w<1w7KW{mlj3EYX-{t<1cd0+U~FOPO4i=LNzNG2$5E1U zAY7LNjyPjyc%)832yjE-Mb&KNQ^tbWH(nd9=C+fLCrO&EzKbe;?lSo~{tDLK55O2* z^4FZ?Cuul%Mt}yk^iryWe*8*manEskdq&E0>?W#-ps3ZajAv)sPwiV+v)n7WBbFGx zy^o(gll0>{7kt!V?okBU+m$*1Or6ZfreS$W_4NVEEaWmf*u2;pv)r}n!P%-2(-$#) z(zld)fc0a|5<&0C8T-RPJ4jP$o4$ziAI<;%QU7EonyZT*JS}5}mcOVm}wybvZhPrqT3*6B786QxjO?F}uN6=%Bq%-qv1T_0lEaGsA%a<9L7sww~NiQ0K4JQHV$n>Cun<}eA%B<0xg zycc|gzi|bz%w~d&P`uJC7Z~Z+bdmFT*^YksK6a+vW#aobL~;ZJ{_MtDtH5a?qS zi_ln0Rs`dE^PYHEcI&8ZjJ>f)_3@97y}^Fk?nqe4mjdDyDB>SM&MH!=yQ;tlw0K6$ z#}{U+uIl;Z%Ui>w$AiA6A2VJ=J)fvom@!x;eK0u^1MjF=KQ6|zd?dj^^>$-Rwy ziMOvXNeK}a0&7S9^pDSMII-LtjkNpjx|{Zul4bAg@P3WRFJa%^Dg?{b8<+>gv~hkc z=(R`|g?c{kudV80e%m%IXYURs4|tq)A?QZg&OHMCwt{&PYD~;tEET9DSJJNFC}Hdj zu^Y>S&=9oJLX6o)rMiyR+qB7{PwGD2QU$S`ldNln^M5bf>u(S4UEVI}3guj?T-vx? z{sYUbHnRFk2Yon6!4Y2wT+BMdX4-wPj<}Umqf=q)or>?k^P<>YoVDSE6&K|sbCx?b zF@ev}yd@CWGj&H^))oDltb~0kEAfuWF_2`+T~dG2KNqz{JsD*A9q>~~EaeYMp%DVY z>@w!Pq5kA`)QpAEOaAfJ;^QBE zl6(0$v9CGzSd(*CL>v0C4!9K{Pd)+!T_9pl!8j?hZ&qa@FOPkBRs&u)m12?C`V)aS z+~m5NwaXktKv^y_01>7efL8O2A^WBlN5`7_nT0okJ~Im=f(&B{U!AqbbV`w-`&mb{;w~bTT|>;? zcGt1!RphOne;)VtLw3MW_^UdR#U`D)jyD6ZMKYV2jB~5%HL}o(yI!>qDBWP5Rc^yp z;}Vf|jXt_DlK=EHrRje-B4FMGvRrX$`+jnC1E17#W%}$XySM*t{-xW1VrzEAz5Sg8 zbfzuZ8QV_Oh?LyU21w$AgrO^ZXsFdV$ZSm}LrU)Dr+-^MsxO=i!C49LK4y*vlKdAg z?!)y7v1i|sBexB$Qg?nox8@-cz^~}ZTpIzp{Ku;O zGdb>u{!^T~p#8qM@IRx=a zfJS)FS)r`&mYrY0a&c77&_8Xq^*=5xCF#nFadzU083Yv$;zQ?hZ9o~p!MoS|yqB}{ zzjh>4L}<&b2J970|GgJZHH|5SkyBhNbg!M0tz6Gd^I22t~Ox* zO&|oswnXfTi&py*;%!xRIQ*#j`QYW+eHsjoe;MeMzHQP}!>v+J1kA4^kWoDYF(3@1 zYYp9jsOI?w^_qidI+g9R80sy$Q`?dO>~yclKS$GRBzt#i{BUhXnB{H?NBe#YF&~6v z(W&&as6wc^EDf?#a8&<0gY)74{9Le$vgyZXR&C&=ih<@cXspIS3$AFue!?Qj+A7Lv z&%u#kB~=}lEReM9qj!AYd$;ECuLpi`+_SaU@yhO4$0J?emW7rB9|Pb*Z(xhEvZ%wK zoyy!g*G(L-T&qY^U~HhQflJ@|v?Kl((cYN_x<5%*`jlJB5I z$(298ISFrOcZFhaQnZ(ASOM?RSsC5Q0`v3Ju z+3eCf>Ziz{f~VT>E5&x8LXBKip&J{Ct%~?>*KTyq-GR*LVyHvW4|;5Vb8?GCj767$b0r~en;_Po4(Nl%93&yaX2oqP{%S5epJ zKbQDug9Y1rWGDalzuyz+NV^rWk}vFI%WIU2(~KIEUo3oX!{!wueG)bMU_D6?pe___ zOwipnjsM+}b2H=XP_5i=^$^+SaCgizBCo=s!QJyzwf`gJ_cjAS>wMpmF{?I8mI?0 zgM;U=NXgM?0XY`uxfKOO9Q;=?>(y<~LEzQ#J)H;rwH-a}I=%$Km*8*{+QDD)pV2Po z82i>`1)gcZyQS3Kx`hSLuetrGTH1gUsu#FbOjJw^QZl~09^zALipLkfKv^*J;(xxG z16y}Ih0;;+!KADh#{Xd{G_2(%a04HJ>e;-f6YA!14*k<@8kW0Up4A(t=ZkDm{I{o@ z(j{X5rMqJ_37wsX89DC=yW)&Visjh>d4Oc+Yinw1&Zjqg+c@<06`RKMk{5ASai0ug z^b+bHjUZ%G2$k|Vhl0uF+^&r@8Q&wxdy-#Ti5i4kLl!CG%8Cm=cH~HmcG|@=SwI#a zmdc{D!w9rFBIe?ieEnS?FFVZ6fK8KEf+Y**=qW+C%Pb zp6z*HWYpP12Om#VEen!Zf1>w;rW0E|EMe~0*cD7lQ0-Wd{W7}nW zNPh*Wk)#Z$=dR0W2q^UIY8t#{IOk=(gkow%sL5Zd=3Hv(UFC*H+9vw5BtYs`jeHbX zJ<}x`k}u3*fnEjg(=q>=pq=qx`;n%GUi4Y}a-|uQ?kv98P$uI)n{XA8SgHZ%NwigHtK&Vlsb#+eKS1rF+v#)`>Q4i@$Wym`Bj=X=qlu4#}fIvIUO6DvPy|efS zGk?Gv3EQFmQJrqCi@Sz`E%oh>tK_?m9l9BGP&3i;J@C)!S_7+=&B_s1&M-3-TNa;g zH=}U=Vqd8Z1Ou6i@-3#;991R${dZqZQ35%zBovBiALBK37{WSjK^a`+;q79A>CwxA zkYTnBPp1o@gC9-4_+Vg$MeNzLJh??k@&ZGGLs2QNXg}rSLBuDplnSh+WFY+89RKTi z5_{N=3}dVh#rnz)=EhB6Wy2Q4N~`VjQ`kOJ6i)~(l$=Uh^Gwk_nVEo6V&rXEaAU8M zA_4(#jh^Bk_=BBy)K8>|NAwI3z)t4W1yY&z_2*#&Fhx*c0Nz&>Ab_Ax$SbBSxypvz zOO>xXQUQ6Y3K(2bNQmnN1Mu=z5kg#**J43p@O_~?>dD#-;RM-C$OA2KOlfWVaetnc z@5`vOli!vRB!El!g=ZXZgyxwv(E&x~LfINIK21koBcM-cW-k#Ou{mgOw3tX&KI zJ;VE$g_?9B=55KF&s2m$HPi~H(TsT;e97^)C6!-M)g!I1pN;c8x_{MvY3_=2WuG}b z$P_auyU#^8c(g^mzy771Wpf7MiM=XuGVOcTk*bA&@qh{IiA?ZB9jW{V7Nix&Ce`4| zg(zvlYp4uif=;(lagkx&?K9@xYUwxc(IbtlfFKi@ySn`Ds;55QyOzxCzpJ0hyx$As zm7&qs0?O{L1~f`oJoj0a_8IK`BP8doo_m{BVgBip0+s~c1gL+6TZZ|d+h%|N; zgm^PjDS3m4F&QQ@XUsXphu0H7~G>o5yPA4yLD#)Iq&#-Rf62R&o5 z7)>#!-0aM}^e#`*yr~CTI$CKEZ3fL1(dbSxzXc63b-i4$W|xN^29qTVuX-s6^0zfa z*5vQ8_GMD0Qm!+s4&#pJ8goCEmAQQStP5$PPo-ElB_#Cdcs4z>7$p>F&#RTC*;S5%XGm z{cGnryw1x;32P!Zj%3|cM#6rTl_(7;Y|nM7Q1q@ug+)M)*pSF%vH8Zj zKkR98a=)O_iN1{@{&!*IzS3R9gyQlXDqj-;TYweGtjp%>9E;BMbpP7GT#aCasLCdH&Uv~;Yd#C#eTwZ-mqW*Mc4Q)?5}YC-q^=(sDt!CD;3H`pwL zy^7e8%sqO7xexe?dbyI7w>!eM4Hk21k)-ZdnJ8Nli8cNCSZs|~6LVIN>ITW{%ALko z@cXGx%kKGhX(cIMow=Z)(doKazMpxwaVxF!U9K=)eErF)g1=zJ;wLnUN!$z_Iaeb`-s zG>bNPghPR|D)hTev*{Swfwx%^N=a5KM$2J1qWq&sr8Rsh5yGj}h=GP#Zl@#*Q2AD{ zDr)M@9u>edO9lbBjFaZd>{}h?*n4KXYq3TEIV1o4C=6Oq)4V;}#oEZ(#_I{%pV4Pw zS4_B)!-~uUb~41ixVFNCQ_%S!xwDW+_k%KVZZ8ps*`8&6U@Byr))qc~wL=~xrKal+ z2cX&D$FdeIqjWrCJ)tmIlUe;~sGi8VT{9I&OhkfQTOxOvsGTj4-DmNgmkuEYR!V*i zqm6iQz}&1$60iVXm8r(2*W0=j!BAOLF2%BsW_5299+n9h3>yQnpr+Y|msi{W@vWDpilqjlaSz8I#;^X`0evvxEw&bUU-THFhkdnp2(t^(K z2bnL7aKynV4s}1_qB=F|Z$NrY)O}(jr>+gxPt1O@C$>o8ch)U@5_k9|4w0^TZxOE? z?>b(YN8}b#`fv*a(h~|b^=&9WWUAy!6LXWwbRTiQ;#3=H8f!2aZ6b;WmDpJwbgjbt zqfoGYcyyf8yyDc_3Bowzu-OM@<+H=#R z>t43H!L~Qgu=@7 z;g!biU@a^ox7Ece?m5osOjWBs*Z$O`k{BZ8N3GV@k$2%D(~Ue*{FCN6}y^xLpS=`$EVq?8y2fd%zh$WnqTr4xJ4dL ze70yMCeBX9Jz)_{JXJI-ks+3MBGe+G&FdBhPNPicloSgOq2ly%C)EgB?@nVeZ?T z>tzS;^#n}p($rWif4X4yt$)C=Z#RySj=n9FH&^U*pBbNOyOfY`c^2 z(7_x^FGpZFdJj-In6W5j*G&-0ixL0+)K@Z+Q0I+GdM_8HYb?mqRm|DZV@MzCuNuN! zf13?`h6_`<@qCNK3u6m{c+|P}+_5p{d)H%rM!a}MONfVZr&eJ*WZvJ?uD-eV zqGP|(nMUDjqc4uk4KLSSDPO`g46n8&7d{ap{MGdR3$)%&LU12v_xe~cZ2is3&~wNP zPK=tyJR+ap8V0(Kt{m_@ePM>)j#hQ+c^mJ!C7*XU7PS1OF*@XhGbAu zC}68M208)hy8!6;_6U50C9m8^>ik|}#EKJX^JsVH>C;tYTu8$SN|Bt$$V^21v&8uL zGz$vE)JSrZPUt3W7*Ja3H5}YJGfoaur#uoIFs{p1vq;hzS_<0yHg9?} zFF_pfz%k|y>wa|;RFfvI)`xj2p40w@?6Jx*`Oa(Qry4Q_7Ag6;pyKvw^4oI@Yw2#rqIN3rXWmcs&e21JisHv~dj;i{i9IeN+s^aSY+aump~FzanfFfJ zT;sH}2g4})6kFUfhxZ1@Z|H@l1 zl+WCEYgE6^&l?tUM!F_Ja&*A%$zom#AJfHCB)^eePP#T~>PbjfC6`^zvm`K8tM2OR zg+7(}ea^Eg`mQ=zZIu<|6$~>8EtH5)nT{!gAHuqR@<+8-aNUY^+_Wl+X?OL5r)_%{ zdIXC=C4ZPQWsM!U^A_>r6oI0MLUDd{Ch(j0w$RzV3#S;FeIoMPa=NPKWoIwGEA1FU z0b}j4{Lav`IC9R+G{Ni65wJN1A4JWBbPRdUPaY#eGrgkn+nn7#uooM8_O(jdzq4+g zZ>fIjo2kEOr*{;Yv2OFgqrJ}2_xtyB!z8p=aL-S&8Pez!Q)B2ew`KCoCn)P*2P|bX zqa+v90NxQRthadp<|@zCf*~ADxe-T0wuRBYz2jWe*W3~=hi1PIEVK?zx9zE*lNJHD zwu!Iap`qW;(w9@5nJAH$;$SL!ex_P`*W8dvTkTh+@g3Bga}!nWLU}K)xW^_tPiA@? z)Dv|+ehGU)nq{r`W&sH~V)0d@qTR&)z3==?l=s{U_C`mqjhp#fyW}$B)aY)5rCddx zQ?GJ`bvIEuSZ_?=Lz(Psf%09$3=hSu9{;bt81B~K>b0>Z&-rxLjGAHhCsByB-CS_3 zO7SLjzq(}2}CvAeV_Y1n(+T`X^3ReRE2y|0I za6Nd;bFjwU_lCCh2mUlXDYyy|iB>-@@9KY|ts?oL}%9S_Fk<;JRA|^9S59_)}6QIN`W!a0})`k7UgRfFNu{yo(bE=co zbDmpJr~E!cseWf1MmL z^E;nGE_Tnysz$5iC+c&qTRT-*%dUqfs8!bINqY841&NDQ84b=?5yZCqM;_N!%0!! z&NSF|sQ~AEJtx~+33+Vbll{WTgRtsPjPD@PfQpEh*jYntiuPPxL^vO$4w+^h9adA@Qpyb?+ipS}=|DXYeaCSc8x|Dl!*A{7R+3C+FZJ4AGlE_Bg z=spzI%6@%dE&wHz56Xs85IPx*S>Y+QhFADNK*l2Z80OZM_wkqr_7rgY96@)S&heZq zohTNZYl$LVVy0;VUJAo>!vq^iQldTepPLpbC01;fsF1`JpYvW1(MFzj?~ABnmdNX_U)z2at=P3I@tnaII~$ znfar=EuJqv(p$tw;s?j%as~Zt_JO(1IvLF!GIFAaXZsfB?-Cd%HbozY^Dp-aUZOyK zF-^=%nn-13jj(t5!mxfH&B%P~q3HDG@q%WY&k9N!>v&!r{oPTIH_~m=vDm91!g@(A znonQ+_mk)7Lgea&+5FCkf_}QSv*TpZfcxl^dFoP1w=`>ey>!&~G@T4y z7^zS_<|H4R(hQ!%WJ9aV&b)LN_k9*i4mhgFj&k zn$ldNqL1Y`@W(c-qlgBlJlRcr*uRZtKresgz+B&o;@k94ygT`97Z20xlif@F?Wc39 zJcsWMN)?V4AgB$h$f0bSN?%Du0DZbfmGd#{>N1HXkZxkdxc z)iA5CKyEF-;+{|T6AQtHYYK-89LY)lJj*<5s=tsjZR_lvg$~Uhy=YwToyj3?k^fEv^D%|UU9-_*&UBh&*@ts zs5Dt~gO?3;=L*XRihS9Xjip#-N;B0ks-=yWcd`%X)b3ul zR?Ccb%F9^Xd02h&gJMDvNne?{^mLMx;B*Ew=!;tyK3MxeaKQ1~c8tkkMZv3(!*41W zbmd(uV-a7>88!cNY_g1PuhXQrzvsT$rp8#`)|@`GV%_9O(l;Wk%P;S!t|B}4l(T5g zbI89f!#*sxLsV`g)o_({n1ge0-WH0DIcqVEuUv^Qo}C%syBXv;GdAEbtpu$tGo3ae zWL-tpE%PtimRLufb5ioMwFNI}FxPWBvkhMKY;gWp?m`JvrXKvI$D(M#&?4Qd} zRa4tE|2?5KCyuT?9Pn%~VPRy!2eS3Wp81{l-+=3B(gYKn!a+^o7Pk|3>zG)0qN;=$ zfgGK&o-@I@y<-$FMfy`VGTenyF7MAHQ?xME?0+*?R($56K`bc+4@AwmXU%?XT?Jz+ z=ndGwv0?k79L87f++wA|`7-_O=aoUMVIJM%u2v95W@fC^*Trbk>eVVrX&vrY^hk0~ z;a$B@Shx^u)9gF2%5y5n4DAiEIT!Cz^g96$6<58TxlvS>T!Ri+*h^Wahcrbg)ov~8 ziL$MYZ9ZU4bL#qm!{-5f;GpdR0?Kdh2 zih>OgX_g~M5fqi)EC>il4InKDA|ldzOHf3Vt~9AZ>7fZyBs7r{>Agr5A}#b10ttk> z2hKU~`@d`5FL&Md!=1Ix(&J2KX771=`8{VwH>`KRb}()L1I%~>3ri&Wqda+rR~zMy zfjQajosE>y9*aN`H$6&c;IFdIjS z0oZ_jSthaufdK+XSaaNM_m3TDE2INt1tw;($!@-De-m%#?c_cqhT;RY-$R%489Mr) zJUNqz`&I9?fc5~pZBqsPN{6A#dJmDn0Sth=?~l^R$m4XrBKzi+h%@jbUPIo}@_-&o zfFj!9jO(II$K`x>0RdJjP=jkr6|ybZae_U*CHu}u@&V)3LHS82S}=aB$&NbXzig+1 zsQSZ_4}{#2YD)7G(-MlZe(kBdaZK)M(D}KiU9M5`iX&{_HO4eT(c(mi~+`ISn z%WRu3S1(fi0&2&Qz%Kv|$nbHpz&QpmtWOMa`l-?AS;z!$B){|?DzHRtq7XZ}n@!bM zj|k_Pj9KJ+wMHHpe-}WF_XU7w`v$c|9)SKCmqYzF9={|JO4Gv$OO2xR)|zMk>h`W@b)<&v^dZD%!O|>)Vn#$)UU!wnHJP@ zO(>2SW7`TjcH6Do+n#`z@~_hQa_j}WGxRad_X!-yVSAW?fmi4ux;KuHTOduAqB6${AU>SqXx+Bc8UGCq%O_L$Uz|cijY%wJq?xWcH}=w&0QT7y%TF?WpxCgZ@iAtwWAjn z&1Di+aL^Kg!DP~v@7>nPGeLk-gS8FSX-=RZ+_$y48SJW_KS*B##U1-Re)Kx1LtdUi zp;=|OlS%`^e5Ue$5JHBqHj9v*DMfvTbYIS|J|(*%z)fE|vfkJ6wPEO##`B5TlKh)? zN1GAGry*yr;>_K~^y9I`&N66Fjm@;XjH_lohYOwW^ElwUI4K78%Vb`YyMb35S48dL z#&4iV?c-ljq+k&Lw@8r=17>v<9*dt15w7F zjQ`@(PF-&KD*VP@4tZS=gvSTUNb;pE)u1?Jd6znC_%AQlmgiOT0||nGf{H}}{z9P5 zXTCX`Vf>FA%I#5UiMK2oBuANFHM&(#4lakz>b(ZWJ*$^807=**dtV3;+@4a7!Y)G~ zi0;TN@`mF@KqP@Ef(uYm^1K z(?@zOb}RTeh&I%loPuc_+}nolk383BP6Ds&)AF}zJ-HJbmB=gJUR&x%KTR?vA=p60 zLeEtxSI~EIXQ1mn>)k6k3%lMch+1e4t{2MFV6&{WonEZ1f**jQW*hb$tv}=U(;`3T zhzcIht2Q1WoOcWqDPDbN_aG;dx?DcSvbgn=uE+(4H4>>CpyEZp|l%oiP8e-2t zJtkeuJWz8`{_e{>ul0`)dT~b&SV*rG_tg2WmPp7kFYf$o4`{?ILd|jV@Ae!PSmLQe z)##nsxuaoM-bA)n_HiKBCz_TwKXbof)y)-t$0-9aCpQVDF)Un~pw;Qq&J&j~Oh*a^ zH#-gArB=9_R{w}iydh+F>_(n@%I6{g8e%qf_%-m>1E8eECe98ix1AYpa2=5|&_(R~ z1ynON1I+}+^9X@PB;QkeodYl-Wk{g|!%NG3dm~_!ol2w=_yKu;{tue9RvXHqdD|&V zLvBl;dcx)ig3?yljfj`Feew~hVEz&L=q~v4?l;#5J~LybZeqC~AF#9s?rf~;{eU7< z%3LjdgAaDEb0lkDXIV9r*zdcDkxC)L#$7*N}HE`0Icm`pDm z83$G9%v@o@+vBtO_0ffXWDnCAoEdOO`8ILQp#E%a zO@E&aBY^8<%)e#FXkGe(2^WT@*Si_Y0A(*8gY+0f?kjB$Y44lfR^v?2E=oOaRPQ@e z#jClEWSSY00`&va>p1ET`OJ?N_o*j*u>hv`qw6D|Z%tOl6NG7a z?D3cUQ?rjNd2{*v(m-oxk9VUey~`~hV)$)wq)KGwGE|}AM704E)5fvQgj$yvoYVC= z2Aikmx1L&`IMdE+1aM;pzPMQGSKH_fKuO*;D5nCvjpK@8+)TL8M0!QA0^HFsJQzuL zZeS_oLAyJnrL>#V&+i8Sco{OInnjK=pYzNcrmZl=igb?J%(J3Y5LsJ}m{NP(PWAIo zE>Rpe>e?*8v7xI^irNlSxoiG35#t#yIC~*CCouc+Z>crEg2XwlHwvPH4(rRVI8mEn z77$+jyj+UdwH>F7>K+qh;*ivGkNj{pPJNej!vK%7(&m-SV21tFfve1+9S{8Z3C1Y> zI4(8}YAb$OqK|`tOD!M+2m8)$)=>g79eqXJ22M|mZ7$ri7M>w6 z0w~b!u-}EQ8fP`du0Yi&V9Np76Y8J@Ly+eP$Qrz9mD+gSDn)wTI^=Y1ybQ=sO!s>5U*ql~YOKZgS&dD>*SZ|YuXSkpjlPBh)=T?{iZV%~7A zO{zTk1HmS{>a--NW6*F5=ZafH8|ZB6Gfq@d=hYFcqmI3icQj;yEQdgsGQU7&j`BiL zT@cnXY}ySf=HA9kL7$mnd=lLA(KEdVvk-E$kyLS&h6}5qU&C~*HQgIdFQy939ky)w z-Ab}kZHZWt-2hf#Uc%m?#yRIz!cAM9Pdm?~Sywtb&HYV9N*+KS+m&>HRCkm)6H<$5 zf{Wwo#!ipicYy;(smj56np^8tqYk7mxN9+=H zGeN|Of9S(0&)s1*C^aG92$fwzdiAQb*23tn=jIes5)*=7w)kb34IHGFT zXrOY5XeP`g(hX+O*e^sLdECGp@q@U__3+_2u|j(!4@iCQ8LhCg=T2Lr=&HPBs8>?I zpirWs#MWtmLjKg@h`F2~?ZTh%d}p9b>lqt6np@oc+ci;H5F0d@AM#y02gWLI{tgj) z%~VOT8E@576Dk@4fVFy570|^G`v)-7{~L?^yyMM?N2ft2 zg^7xG;38*tK7IA-*Cl#x_i5rM6e8E?fUo!dn{AgvPA#ZXI~s-{|0+exx~)v!Vuu3! zkn>Xud<6#Pj}%6(qZEW32S5o}UqW*;dNFYv8**I|ph>dNA}BHmZ;y?Gq(S%NiJ5WH zGq9hWd(Pu`+Pc2De&aq3_tdu+CO2d#KhhV;8S2$?$NNhyA&JCQqXG`U5yo$CK)(=YMo#0?*nAL!CQ%h zz!)~)*Mk4<*3LXGhDw7VhZ3~V^Q;qoeLa!0u?R%Yin!zwrc)17)t@A+S- zYx7n<$ls>*r6ruYQeRFmngB#_BV9QjBTc#VWshwxXc~|Uu%aP@0P?k@{J|vYhHccs z$e11HFQ2o^?+ZqsF4(AAvE!Z%9#pxg%#eCS{=vh?sn;~#)4rPeM1N(r^RU5t)NQ3J zGhb;5a>?rx6)pA;X&0ON5I;?c!#ssFu*ZO7kfO2&z-pp!8T&-Fj?VZ>~ zl7s`tw*2JysnguMk5>G+6I6?yJ4|v5u^`C)XuhiTH95oW2|4QQgcI7wa`~K`xC9?^ z*^hg>wVGF98?nufT8rf_cW=ijm6PqLz4_=_qLjVfNATP*zml?4*0}nUs~yC=|A9(@Qb1+g)&{5?ygEZGn5c zaaRaQAyL0J?%`q>719`c8;8b&6mMthNY{=bdkDS zn_lQMyZB!8nd&tIYEZjF=&gbYR@7ofknqlrcCgSbG@mZD z(`=<}%#+eG*ApdkOShT2hFWFtQ=C^I+wI~}eAE#XIlmqyM~;~ALVH*1Z)df$?#?%~ zGqYD?B?qR@z95K#PJX8sf0)L&>O ztpRE$dIw#@-+}%{#>*MelEr4N#&%l5ddW%GqnHJxE`4LmzA$HGESXzP&aM`cB{wfI zqrFHsvVso6Ixt&K(Bx?t>|;}gEHTS$B?IZW{Okb@&m1GNjbE$$sMbN4YF>IBXxQI7 z@TZ|HBXpeOh0122|RTId?zB=FAZ2Fh%F2t@@AkuGdfLc*DVfPng%n%fTE-qM?6+1c5auc8WKEVdyq z8yIXoLjLl#_ew(sCdzCAR}ht+@E0~ViCa6Hb`%Q5@BCCIy%xZg)mwnVd@y#yG_<`n8sjrwWlRZ9$b;uoz`aZxVhx8^)gKd6i zou5E0CP0{%V1gsCN^v1>p$KD>mX4qanFck!^Z+S2j zzo_=eb%#v6aT_3mf+HCLyoVf}pqef5{#PqvTm`-`nSl{oQHk zuF$3BaTU;^p%K=}^YMY$D>$zfGyjB0O&k}pbYj4aeN;I2pDY={y2JK6NSpR?{?w-w`hV9r4IWvmTU=;xS*imnYp>Sn8TdH+RVel0|WN|oE?2& z3`>>)IK4q2bDZD_hCfp3+uGXnQQTez(Yvy`&+nfovISV$D+AS(Tsxoa8NX)2pWnZe z-N6{)a{^53?~(N6^XrKPC)RJzNSTBEk=X2`ahix|DOSffHG;!v?7&PU5Qw+G7jXPO zXNhhEZR|DtEt+6?QC1d~ME2?x_IkZ)a_=BwU(}K(<`95DqVl~hZ1aVQ0@vPGR#x6< zAHllJsV)~jzx7l?i7o5N^hpRn862ySApA@BVKgZ+_`F3|jVO|iQy zvnJd!o~avq+nAg&uX#lEg`1n(i3Gv?@L+hv%GvanA!8mpWS2>6X|IhJLSEWXd<=}T zHdVNMEo#89u%gg$1|S#vDZ;}Y9UKb8rKLHn)gj;szyal8Yh%Gg&7`Q4-W-ZFqD+j8 z4OE}M&!r-1pB7eC$-3V=H$ZPt1-_YDQ$M{@LsKKWnC-Dwky@4ePr3_u?y9by9vuYe z0qDcwAKu*4E_?q5do~;u0gkCL7SpP#odrAA>aF-{iN2+(T`Hv>%$UQ6?_Jm70X~$& z3LtWzG-FU(F+uGp>Y9^4(4K(+F6j`@f{;qV;oAU2*xuh@A^!)(wtx0n!hikvzq9`S zS$RCozMTKRy`itFj1ReJV0c(iS64SMTQ8qQFVBb%R3rofGMNC7$n~9_nuLo_l`A_* z8|!4^+{AcoKz*)Z>0t}bXenw)`^k1WkJZ0iuFocmZp8Fh*f}M>&k!x)!?PH7HOPL9 zle`=M$~MqJxS>Y8eY&OCl;;-gh0tYuYFP~+_lDI{}pOfgy zIpTe(qGFka`S}FEH^C#*B_$=2PK@UhOEzL6_n%Nwl9k?Y)tqImFIx7aHsYB|VkcKw zY1Y{_!Xj!?F*wU+X4l!Us2EJV7h8COLhb&gm`A$?WNrBSkKVCUK{G(?;VoKcRczx4 zA=||TG72ao6O$yenQI(FKtkJr)b0lA2kg8}-i_6s2GQR2Zx$_PNrOFfPO}U)m>Ywi zj@vv){*V`RJ)OW)#BnGJ-1`zyK%VC!r+KPEX5qJQ`hvp3tPBhcQL}}%qZPk~Zr&jr zU&uO}%IdYXYGDD_Nfc|E4p&lSnKk<)JUyoK&zhiJ>+F;L_yO9rjUWfXhA&F-=kTU2 zeD|8xfW=aSYL2z8QB4btJgDKrt3LeljN8R1PA;yboV+|;Dl#HV1+&-?B61HCWyKva zYSG)^c?n@-qyD}m(24Vq_vwH=?Al5#Ci<}w8}o}hr<0;_K8NB4U0Uz&SW$1 zmqoe5J{Me3oTWDT?Q<8AU9AY8khf;cFr|`w>U@FHEiJA({Hvux;U+kO@gkkKHm2&2 zXlzMAF7bZS;9%0{b~S@3Om+O!MH!-muk+v_LMytm)df12t`%a~AV}x~ODq&FDsKC! zH#DddDbOT6d+A`#v`mxOt`6-p#j!CWJD*ni)TvD8s~^Q)KJcw;a(jn+t+Exms=TOV z_dLwM2jMtmVD~30cfrbeDL{FNCXf68-~^j3Dy~iy^opMgmM$(~wKz0jJL6lne zu>07lD=I41H^&H%Z9|xUSqN)O%jp&;b+l|EnK)DOf8t||dV7$SPO*>)?TWM71n&m`oDk(r z82q1|Yx<_3S5(ZDp4!%tPFVZX7y$_vJEVz`QBK9m86`PJwq2e}fN6hHsnpm5!c|Wm z;(-LJd~`f*v?D37CI&xFJ1cm*=@UlE{*wL?V~O2#{(rtaTj@+-$wp0N58I%Q%*75z zVA7fl|Ax6ZUL9Zr{^`5mbV4RpBe8C9(7^81G4PtRihRh6N*>c~iLu$K&tK(cPoE%r zP}SfYqWx?h&z<5$Y&&Uf7X7j^Zxm&D-V1khhU=$(id#qE2p&e(o(dj^DcwX01Kx_7h0iZUitu?3PkiNXX=8|r(qhq<7~-E_!!(Q7)~V>vWBDjN~~M8~@zbmt6@8oX=L zGsR=DJ4{Ha+2wjdWYYqNyrj*da8}bEbr0(7;G?fI_}EwZ)OZA8iMeX^AKd`nv39#c zcwt*_WWyaSP$OOgDW}fAbR2vsx$#y2rJ3s)=0W-G$8?zQj1cxaXY|#@qrqc|Pq?G= z`q-_Wsmq;lYmFJH@{wwW1VmYN>OPrRX%j>DS3}xcO{pmJ?IuT$i8n8?XBlQC`(G^p zdp-QS^kPRmvDt%DsQg$lo_EDlhj+x19(jbF>3-Iz|0}*%PO!YQ0jn zY+pO*rpH(lN;3`7gFVYrzK;Japbabl_%=^IFNSnzX9&D&KW+8W3>5$@jET+7Zv#U^ z*{aRVtk~{!^BIH6_qt<)+D0Fvd1EIg-1>I-;-%@rAI4KuFomUMZ9lcg274+XP58Aa zQHWT>L%svXU9V#Y)4625^@on9;jgiDu>$?{m&TC}O}_4^y4RXUJ+?MBm1UVB!Uus# z>K8P+SvudBYpB5J_+af0WpX&_^9ukiEJHL}@5V&&`$&Svwe4K)-+vr8BzYtpj`!zD zro=m1X-`)2>FMcNmwhS2QcM}k3nriy7*p9Y$s&nnX#a2aAv(A5?!diGb33+?Hma1;zQET-AEkX{ zVQC2+>;1n`n2)XY+F76%R595;_DMdUvR&>=`dDehcQE_{tvSK|&WYAF8z(YpOw$jb z)-_$mJv&(^90xyq>&|-4eK>iv+^N|O-@v*4{=qY?wga#t3!pY%R>5pe2t+kguJ<#i z-efsgb}Ewwz_C)!tFF?DZ6ve%sCw#o^~NlGld5Va-XFz|CMC)Gcs>>W`Efo(MtRT^ z*fydhR?6EBW}C$THHavu(rkRL1ypsENM1r2TWdpO&|{|;hs(O(QJEy{uYTlx`5>8S zrXKva#nw0lrXe{#j-7u0AXU1f?Na6K5#UYp^QN|?jIO3}PmM99*hNu{=5O6p3m55y zc-s3PNklM=Z?XYv=q#1hS43!7_T(~w9Y1pO{f5iAhC71|`?Q+)ZAh?GYUZLEPQe00 zo-ic6S&*2|wvTlr^q(NQIy>uZQ3zKN-3~9V}gjYhty(h;&&RIkTGd zvX0f()@F_66Ta&CyliRpum~2Og72zTPO8YYlyWybS$Z~DAuc8ZT!@LAauNv;RsAa) ztBSIWAhPwD^>f*vb-6!+9ZkpRYU}2jss8;?Ku^iUi6E_}9)A~~vUm$x&0fyeS=~`B z6BWr{J)2{YA1&o_;^t+<#eX7C31+aXKimCRjWO9^LcpT3<;Wd$me*~+D<)nK$}7#n z?C?1mXnJs&UnqklqE_|6zch&TZqdU1a8!9wk&bf+P!Dm)Lb0mkYA`o=uF+(*ck17r z>K}@92pV>&WNPyRwqTq$`=`PlHrnJ?BZiUyU^ZNw6psID^hehxwi3DH+1XdVD1`OX ztklfNE6Kn#j_SYvFl0>b-rI$Lc`Mrna~#@yAySRU{w@rrW12n;J%3<+!RC*_7f#?w zoZ>@`2Mc?5BWp&-#_T4I-KK3kQJsOp#QTZZfuyF0O3gRJJYJR({~>``hja`f31qzB zx3te{(R;k(x>0x-;~KVR#t|8MW7^_9)yw3f;Xg zX;9>sRP%pm8*E?t;7uS#XUw=?d=}2ULFnrhWyT{O+4d^{!u(lmDg}h-Us_Q=%z;OV z5e}SOX9s>a-;_=CIzQ`3rTe4j5heZX9UvE%d5lr#4 z$rW4e+1TiD4i^dd51m$Asdh^S>{+IvA;q+*s-sWx5(3B%_~avf{mca-Sd@&$?EXXE z)%zQJoCqr)X*h{Uji(;`0(99_^LDBmC*#soeA>KCw%BT4-$vv`rP#gi^4|^lI4-bl zEh{5AL%JU>bulP2rhYqZ3ah3x0SoMPC|ykyl_^tZOdm{ zoB05~J&&)eME2ud1W;=vqYqru#e7ZhN0mOd6WrP8Jn>vZ_2oXwtZW2%4V%Xu@_UXU+*=# z6c%6jtJt)2%lbA@PpUd;Tx>h1&W2&i2YA#RwEEAZMp;KXg(>(*uehR1d~+eT*4lCj zPfwNR$y+>}hILtKqm+#|dX`k&inF-WA+l^yi6@&1`PAqujZq#)B_<}uZVbOr+0EF& z=+3mQo^=z}?SG{(oli^4f55iN-@QJ(8rsDBaz&rG`#c;ph-Y~p*D5$i$G+ z$_G1`oY=rp){B{@`dEYgdvZf|mOm#qN*|0WTSpCM3%qOsBrmKtvC9m5G7Vsx>Vew; zBECTrGc%d)AS_hyw=*qfJA6e{R5Z}v-~S@7Frtbh(hG$`X)IoGdh3WY2eE#A1cG~e z$-~OL^1bjvmJhU{PAKJl^Svv*!LsEJcT0+}xZNi;W+;3S2u0g4fg>X$HEuORB-GEB z)-}>VoctYMqs3!OUc z34NxbqV=h>0C}!hTvAdi4AyaRe?99>VCZSq_dg)s0x~2CMXIW{j~_p_pPv|e{9EK; z$18vZ4jGK#Sm#&T0y;rVZfRlRh?eVleTg$QP2vqJ!m#kaL*Aa_z)H<{6$!*;J(=v; zC33wS$U`#_(P(W4kUYA&2Vq-#p|s?#nJPqW05iY5p^W$D7x&dajq&J9y>o4C54%J( z(LfMs-=3z|y>EIB_H$@2u-D@c-r#(@yoCokACJe2dA+QuxK}qmZ%{Bg4$5Lh`0?fwBA|0=?#t3ky z=qvLn{Za$<*pkH`M5v|7H(`HN_M`oh)?Qwe9>TIdGnbckms)|RNEbO2V{qg+?9c7J zPBGtyf$%R(T+-si1Rp+10me}$=XIXJ@#C=6{rs1_rJiiawD{4mwa2LxlYrgPHg!@wAWL@OrsBxz+`E?Dy5-m;yjU=S5d2DH;TKSz|> zgN&5^dz$ca!&2MCBdW27=Sboufc-eQxOlFfhS?7qrlFzWrDTEFV-Iy~Z!i4i1{dcp z@M;o{MlJ?9=cmk~EjM;PVDf;g6Hgy_r>`kSzCtH7n}-YIMtihz`R`~K zbIrpzj!4owKDaZ1Tnp8zy2{c$+n;sa@B=Gip|Bce$y=^Y7( zCb@_>8?E^Gc)xz`9S)FcjH1LSJpbOgdQ7{v-ZG@0Ekkgn%nY5qSxRsLM*fY=TB|zi zP_Bp*E;ns=80TEQyri;~$ye};i?)z(y!}gYPLLqbBx#e_s%;pG`(mRg@rEchh_Z!59=2dg9>V@R{_sP=PI@7({gl#EBh65QhvgPQTLZu|XoK zN5{nAJQKlK(kxkRxP&5tS5~EBL)_wRzofKuPUW$Px039oiz&7ld`r@k_R?*qC*M*f zY~_MTVfgoyvL_!l#B5O<(*+LC(^0lIylH^U)a1#Q7GNeXOd z`kU%yPM-F6QJp}DjFmiP-Bzz|Xyz&NnzS># zx#w#gm#iB3xC^Fed?429kU@EQId9p|ad(|}l5r*ZYV0ST5sbQs@)m*SZk-+?CU$Xa zPb9uY8T4*yTmH^g!K=o|Hj5J~8bN(Jl6AG2Po()`uc0)i zZY(b+rziS@&88=u5^c=WWIxxJtDS$EZ#kW>79@m&9*tC^s`U*F;xM+Kk98it=x?t5 zH0$e$SsHHOr~lz6uDm6`!xep2uK*c^(@~!qos?tJ#9h}U)$g;tnQrWvaEipCgBs!` z$4UkE-)TBQ=`JHI^)f`LGWH<6)pVqMK!C8hDK3(m;K>0@)D0eir5dp|E}hA-cSk02 zb8~B|9@mlrQFsS;cX4+#|8-@LN8g<|RwQfXwIVNXj0KW7k$sQZ+8E;Mo-Ej}x~#iQ z+)!JU1pBMH9qPIKy9PuOMMV$eu7&DJPoVS`;>GhT2YIq6ngy)Co_W2+1{LZpAkNk# z7?Dn@cu0Si(WP|=@M;MuY0aEoYZ93aIiQxL!s7st_rxd?jNI^=tZ}3}s|=WGSZS7| z#aZ4=CS05G==4~vz_Zk3^4Pr+SBJC34Jdy7`y<#~=wG3Qd8Re=#sZ>!gXy~Y5Sy=` zdL=m03co#ajn;gdonjbrsmoF~Z7!&Ch{r**>DQHA3%=2dlXwrS8J=H=Yh7cym2H0! zk)2hhF_mo=d>kp>YY!vbMytGH(wss>wWA(hE9MBrSu9v5=#)*TVK_5TH+_U+Z>gEX z4bPDhZst3tNxHfu@wU$m#iX^^-08iZ@5S9J6mr9uEZCiHn6wVYkZ9~_tC??@cXd{z zC)gK-*XvTpZhA53s$Ekz=nLX8#_>_aDUKl&NjSw(Ve9GfU^T7MY1%UlBfHjvzAYYJ zXG0C$3fvxV+06~KJFrM_gsl;HxFQzk40>f#7N(trW(|@>CL#Wou-(z6RdxFIDVhH_ z+znQ|w@)fUFJHh+9U<8(kBibCPmDR`z=2Qg2-2~Af6z(m{oogV+F?8{Ea;H}0qfAc zYg}Y!l$sYm>GD*~f_+I&d8DBr^FhYB$;=cJ-mUrfRgS~hAlDPP7dl(h9I4`5aIXXR z&y6UPyn^wX)Oe+@?wXkTbRcmeRtG{Zkq5rvzGA|r!|$v?(6O=8Q;AKb)_B!^+2%}4 z;h*rrvoJC!*Z>+NPc6b8xN2Dmk?39v3QZ`b96l+f4wrx4hrixP3F||^m&YqAZ(B0g zd|0&%mQ4I}_89Ls#b(ZYLsBX+Cr*|NPJ!L8OzQ7z;8XdvvEjvfb z!jM8JW^kscX1yx(hA~Ajrr3YDff+V)2}+;Md;`M)5@Xk!UCeB`vN_ErJ5#euF+4Z$ zw|I=Vbcj`I{XX8&Kv%-^ka3E?vz|}li?S~?zl?aK7d&YijduW%$Q$jggsi&{-NwgJjoap_Be504=xn@`0I3LTb6g0e;$F z%x+Xw_LH>jBg@2l2~T?sd!J0D3LI>YuTZ?=OChP99zj!4$%B$wt448YKUnE+`G{g$|MC(}j9`J31OlEmpJ%1+V z$O1k2-icX|{PdH*yS;?*=^4z+d)LULJK3TQH%(n+j)DttfqEJ^sFSfE;4+1ZTb08U z;Uf>@bXTM&``>xyVdkpPD(O+R1#?cYjCW3Md(Q!5v^k+1ISgh>y~z0+Ob$aW24>KQ z*Im+8IL^y&H&!J>F9N6KA>16E7SjFEa_Ng^KOwv%Up!9HV5HE4E**PjGj82o$7cgO z_yjJ0VKkpWbdrtv!yjYbKe;xffq_@ldhs#Pj?sS}-e=-7D>$>c;)#3%Wk_ zN3$e#N9+H1R6NZo3GC;{`xvxtzv5KUFKBhgN;Y?~0+HX$Bf|x+$;0Xj!V_8B1XLKO zKa@014iIk}ZnV$c-}adbHRt_U?AvcQt**A3Ta8@ z(65Li+pBZO^}4T9Rx*#uw%$;{D_AHy2w8ni2<2RVg!bEnh2PktX)M?cC1l!M)VG;y*WH)aH^{x|AB#ZMQxzj;5aZl{B$;Y_UrJo7 zZfNAJ8FgK@5qCA#><&BSL^9|}xXK^%N6t@cdvHpfn0(T_2t=o>kZn)SY3h# zOi3MDbf$&>0}qi+kTpBqpHo>UB-;AgYrEBtj;T!=5f%8AacF4BoHi4iJd4*PO2LUT zc}kTA)MK4v)4igO`U}wnj_wWfewK~QCtXgiBYYiz;i0#b$F*YPo($?(bywKIB~`)v z#>EO|-LEvtLWVNBCTwSME`2-n=JmF=0x3XNzkg81F&`uuJEh++2B!^Q0<-4E$BOtC z%;2Usdy)QGVa5oZ;*8bjC~`J|qi0&i_`rGAeNlP+c2!Z)O_vj2_$y6&>r6aeiSy9# z#-0z2g0CG-a#5h6@JNIfh?(?We``7+edr$KWvv;}=%%Z=uq}XUz{};ncLVV!cgh5H zpXugyNvp57Vth`g9Aizhwj~&8#Xy?C!#1I?UEqAu>Htw09^bUUL)Volx!dL>NCRsC zsYd|jLIMW2Sj5oE$G`N?)TFI~*I@b)lvM4pHl<&awwXv8LshTb47a)2c0HIJ{M`hu z!K3Q}Y~)rM4GreVcfGG8Xi_enIQz2T@p)dH>D_MJB`q&dWry~VCEOyA3T4k&gcR!Z zPFe`uiSgJwp|KwkoxPk1W^2d3HnrCJ3OXg-WJkyt8Czfs` zoDC+uE4&wezp20Rp2nq6M9=K7e3=)hjU_BfTmB+y{Yzwe+pa9ge>C|o9Gv1R$ zU=m$h@_g;;v7tKP%3ELtmb|B>6hgt*?X<+`er%xBT_T7cd{-M~kzygS)bP^1wV7uD zEU3rAy^4=6YrmIwi-%fVVf0h?<+BMgDOJ3`PTzdx97F36CmqwnC#wgiNZI9ge9G*rZCEJ9z;O;=kHNx|dcdHvj>iRqe6KFJPS+)M`#b*nbjr`qKNv-5sAQB473Zr^pC zxF0(~H19G#SF-X-+Cds;{7&rHv(#g&$D5_C*xuM&dKFn#64Haa^eSM*Ga{!Y`2555 z>CAp*^7Dx44<1V~o+ewLUqo=UmjJz8=0n+tg%{!HV^rOu!ccH|c9_y$X_)e75~y}P zl_&U?S*Cz#X!*M8oOnV-ko>(=-UQeB+o{6AM~LIrOnQ9vbBfF7H;RJ#h1AdJriH%{ zF}4!A*lOirSLyg&2#~SIy8eBi`<#es(sXomj;o#Yne-MaQFcMdR+r z=0sn;Tp2A;ezF@BKtYXuw4}Mc+ zcTl}^Hw7IQSaNO2$MJyg%ATNo@+{%|*-$!-g{CNh3}mROU9YUa^BAZ%jg@(Ce#_~h zSS?H*t-M!XDW4J9Er7#dS6jK)@8&pjGmCB0YaNuA{X1k3sZ?y#_lKJK=fc6BTIU|t z;^VOMoclFal9zQ`qqHqz^!hq|;5D;DrFOi03@{C~{lH9H8Ay<3q0KFfbH1c-Pq>2$ zs6RbUP+aQAk2{4T2mTE^NPq;Z<;KO~U%x=qCC&vHk1BrsFnQ1Y0L}VEqGe7dD1Nml zT*OX6{6V&G;x3&5ipowiOd50&DpahrKJ&30b7C0UsJOA%=5e4VK5Jnpad5r zcblPpC>@e=1_?YckQRh_h(R(+ih9jJI#gE5z>mY&_KVY!*Djhezs&vw3ZWSf8am(2 z`CQ>L*OxU7=1oNBNW;Fn@6~(;ZyJtMA1n zCD9;&Xy@hS72e=>Q>m<=;M0Qx$(KQ)AG8#=i}G8LsFusm%VP-&3Q}&adt# Date: Mon, 2 Feb 2026 13:41:15 -0500 Subject: [PATCH 669/696] add zscan data and mu calculation documentation --- .../example_data/CeO2_635um_accum_0.xy | 5631 +++++++++++++++++ ...35um_zscan_200umSlit_chanClose_exported.xy | 152 + docs/source/examples/example_data/zro2_mo.xy | 5630 ++++++++++++++++ docs/source/examples/examples.rst | 1 + docs/source/examples/mu_calc_examples.rst | 123 + 5 files changed, 11537 insertions(+) create mode 100644 docs/source/examples/example_data/CeO2_635um_accum_0.xy create mode 100644 docs/source/examples/example_data/CeO2_635um_zscan_200umSlit_chanClose_exported.xy create mode 100644 docs/source/examples/example_data/zro2_mo.xy create mode 100644 docs/source/examples/mu_calc_examples.rst diff --git a/docs/source/examples/example_data/CeO2_635um_accum_0.xy b/docs/source/examples/example_data/CeO2_635um_accum_0.xy new file mode 100644 index 00000000..97b1f9ee --- /dev/null +++ b/docs/source/examples/example_data/CeO2_635um_accum_0.xy @@ -0,0 +1,5631 @@ +2.00009989738464 22.6381633008874 +2.02462468668818 21.8208275836126 +2.04914947599173 21.0965692433941 +2.07367426529527 20.3054115384164 +2.09819905459881 19.2641083826002 +2.12272384390235 18.4351379931934 +2.14724863320589 18.0337418046386 +2.17177342250943 17.0477033414494 +2.19629821181297 16.5503211078054 +2.22082300111651 15.5206526241212 +2.24534779042006 15.439209919197 +2.2698725797236 14.7847596117706 +2.29439736902714 14.2088433412354 +2.31892215833068 13.6329270707001 +2.34344694763422 12.7806873370293 +2.36797173693776 12.3618391402764 +2.3924965262413 12.2076797345271 +2.41702131554484 11.7393841812131 +2.44154610484838 11.0965685459187 +2.46607089415193 11.0267605131266 +2.49059568345547 10.2385114761819 +2.51512047275901 10.28505016471 +2.53964526206255 9.7294945704058 +2.56417005136609 9.2641076851248 +2.58869484066963 9.09540493921044 +2.61321962997317 8.42350262358601 +2.63774441927671 8.28097788996871 +2.66226920858026 8.10645780798833 +2.6867939978838 7.60907557434427 +2.71131878718734 7.16114069726131 +2.73584357649088 7.11751067676621 +2.76036836579442 7.03897663987505 +2.78489315509796 6.5968590988581 +2.8094179444015 6.37289166031662 +2.83394273370504 6.27981428326042 +2.85846752300859 5.73298469305526 +2.88299231231213 5.62827264386703 +2.90751710161567 5.8464227463425 +2.93204189091921 5.45084389385365 +2.95656668022275 5.03781303316677 +2.98109146952629 5.15997709055303 +3.00561625882983 4.78475891429523 +3.03014104813337 4.89237963151646 +3.05466583743691 4.5375221314897 +3.07919062674046 4.18557329949595 +3.103715416044 4.03432256177963 +3.12824020534754 3.89470649619533 +3.15276499465108 3.85107647570023 +3.17728978395462 3.67073905765385 +3.20181457325816 3.47294963140942 +3.2263393625617 3.56311834043262 +3.25086415186524 3.40605026665028 +3.27538894116879 3.3187902256601 +3.29991373047233 3.01628875022745 +3.32443851977587 3.04537543055751 +3.34896330907941 2.81559065595002 +3.37348809838295 2.69051793053075 +3.39801288768649 2.4345551436262 +3.42253767699003 2.32402575837197 +3.44706246629357 2.23094838131577 +3.47158725559711 2.35892977476804 +3.49611204490066 2.19313569688669 +3.5206368342042 2.0564282993354 +3.54516162350774 2.1029669878635 +3.56968641281128 1.93135557391613 +3.59421120211482 1.81210018456287 +3.61873599141836 1.91099489768509 +3.6432607807219 1.67248411897858 +3.66778557002544 1.76556149603477 +3.69231035932899 1.6841187911106 +3.71683514863253 1.67539278701158 +3.74135993793607 1.72484014357269 +3.76588472723961 1.63176276651649 +3.79040951654315 1.73356614767171 +3.81493430584669 1.62594543045048 +3.83945909515023 1.60849342225244 +3.86398388445377 1.6375801025825 +3.88850867375731 1.68702745914361 +3.91303346306086 1.58522407798839 +3.9375582523644 1.53577672142728 +3.96208304166794 1.63467143454949 +3.98660783097148 1.52995938536127 +4.01113262027502 1.62885409848348 +4.03565740957856 1.51832471322925 +4.0601821988821 1.65794077881354 +4.08470698818564 1.56777206979035 +4.10923177748919 1.60267608618643 +4.13375656679273 1.39906932387599 +4.15828135609627 1.49214670093219 +4.18280614539981 1.50959870913023 +4.20733093470335 1.53286805339428 +4.23185572400689 1.53577672142728 +4.25638051331043 1.52123338126225 +4.28090530261397 1.52414204929526 +4.30543009191751 1.50087270503121 +4.32995488122106 1.52123338126225 +4.3544796705246 1.61721942635146 +4.37900445982814 1.46596868863513 +4.40352924913168 1.52123338126225 +4.42805403843522 1.46887735666814 +4.45257882773876 1.50087270503121 +4.4771036170423 1.43688200830507 +4.50162840634584 1.43979067633808 +4.52615319564939 1.52414204929526 +4.55067798495293 1.33798729518286 +4.57520277425647 1.39034331977697 +4.59972756356001 1.46596868863513 +4.62425235286355 1.43106467223906 +4.64877714216709 1.30017461075378 +4.67330193147063 1.41361266404102 +4.69782672077417 1.34671329928188 +4.72235151007771 1.50378137306421 +4.74687629938126 1.43106467223906 +4.7714010886848 1.48051202880016 +4.79592587798834 1.46306002060213 +4.82045066729188 1.4979640369982 +4.84497545659542 1.42524733617305 +4.86950024589896 1.27108793042372 +4.8940250352025 1.401977991909 +4.91854982450604 1.55322872962532 +4.94307461380959 1.37579997961194 +4.96759940311313 1.3089006148528 +4.99212419241667 1.43688200830507 +5.01664898172021 1.39906932387599 +5.04117377102375 1.3554393033809 +5.06569856032729 1.38452598371096 +5.09022334963083 1.45433401650311 +5.11474813893437 1.37579997961194 +5.13927292823792 1.34962196731489 +5.16379771754146 1.404886659942 +5.188322506845 1.36998264354593 +5.21284729614854 1.33507862714985 +5.23737208545208 1.27399659845672 +5.26189687475562 1.30017461075378 +5.28642166405916 1.38452598371096 +5.3109464533627 1.32635262305084 +5.33547124266624 1.31762661895182 +5.35999603196979 1.1692845492685 +5.38452082127333 1.36707397551292 +5.40904561057687 1.28272260255574 +5.43357039988041 1.33798729518286 +5.45809518918395 1.21000590173059 +5.48261997848749 1.32344395501783 +5.50714476779103 1.3089006148528 +5.53166955709457 1.3089006148528 +5.55619434639812 1.15183254107046 +5.58071913570166 1.29435727468777 +5.6052439250052 1.25363592222568 +5.62976871430874 1.2158232377966 +5.65429350361228 1.30308327878679 +5.67881829291582 1.2623619263247 +5.70334308221936 1.28563127058875 +5.7278678715229 1.29726594272077 +5.75239266082644 1.38161731567795 +5.77691745012999 1.18382788943353 +5.80144223943353 1.14601520500445 +5.82596702873707 1.31471795091881 +5.85049181804061 1.25072725419267 +5.87501660734415 1.23327524599464 +5.89954139664769 1.19255389353255 +5.92406618595123 1.19546256156556 +5.94859097525477 1.21000590173059 +5.97311576455832 1.25654459025869 +5.99764055386186 1.22164057386261 +6.0221653431654 1.25654459025869 +6.04669013246894 1.15183254107046 +6.07121492177248 1.36707397551292 +6.09573971107602 1.15183254107046 +6.12026450037956 1.2652705943577 +6.1447892896831 1.29144860665476 +6.16931407898664 1.25654459025869 +6.19383886829019 1.29435727468777 +6.21836365759373 1.24490991812666 +6.24288844689727 1.21291456976359 +6.26741323620081 1.18964522549954 +6.29193802550435 1.27690526648973 +6.31646281480789 1.17219321730151 +6.34098760411143 1.15764987713647 +6.36551239341497 1.3118092828858 +6.39003718271852 1.21873190582961 +6.41456197202206 1.13438053287243 +6.4390867613256 1.21873190582961 +6.46361155062914 1.19255389353255 +6.48813633993268 1.13728920090543 +6.51266112923622 1.21000590173059 +6.53718591853976 1.24490991812666 +6.5617107078433 1.28563127058875 +6.58623549714684 1.20127989763157 +6.61076028645039 1.15474120910347 +6.63528507575393 1.2158232377966 +6.65980986505747 1.10820252057537 +6.68433465436101 1.10238518450936 +6.70885944366455 1.15474120910347 +6.73338423296809 1.25654459025869 +6.75790902227163 1.17219321730151 +6.78243381157517 1.16346721320249 +6.80695860087872 1.25363592222568 +6.83148339018226 1.14601520500445 +6.8560081794858 1.17510188533451 +6.88053296878934 1.17801055336752 +6.90505775809288 1.30017461075378 +6.92958254739642 1.22745790992862 +6.95410733669996 1.07911584024531 +6.9786321260035 1.21873190582961 +7.00315691530705 1.25654459025869 +7.02768170461059 1.21873190582961 +7.05220649391413 1.20418856566457 +7.07673128321767 1.1692845492685 +7.10125607252121 1.14019786893844 +7.12578086182475 1.13728920090543 +7.15030565112829 1.17219321730151 +7.17483044043183 1.19546256156556 +7.19935522973537 1.09656784844334 +7.22388001903892 1.15474120910347 +7.24840480834246 1.10529385254236 +7.272929597646 1.2158232377966 +7.29745438694954 1.05875516401426 +7.32197917625308 1.18382788943353 +7.34650396555662 1.18382788943353 +7.37102875486016 1.19255389353255 +7.3955535441637 1.15474120910347 +7.42007833346725 1.09947651647635 +7.44460312277079 1.19255389353255 +7.46912791207433 1.15474120910347 +7.49365270137787 1.14892387303746 +7.51817749068141 1.15474120910347 +7.54270227998495 1.0267598156512 +7.56722706928849 1.1692845492685 +7.59175185859203 1.04130315581623 +7.61627664789557 1.16637588123549 +7.64080143719912 1.10820252057537 +7.66532622650266 1.29144860665476 +7.6898510158062 1.13438053287243 +7.71437580510974 1.11401985664138 +7.73890059441328 1.16055854516948 +7.76342538371682 1.20709723369758 +7.78795017302036 1.1227458607404 +7.8124749623239 1.18091922140052 +7.83699975162745 1.21873190582961 +7.86152454093099 1.06166383204727 +7.88604933023453 1.15764987713647 +7.91057411953807 1.04421182384923 +7.93509890884161 1.21000590173059 +7.95962369814515 1.13728920090543 +7.98414848744869 1.17801055336752 +8.00867327675223 1.17219321730151 +8.03319806605577 1.03548581975021 +8.05772285535932 1.02094247958518 +8.08224764466286 1.17801055336752 +8.1067724339664 1.15474120910347 +8.13129722326994 1.22454924189562 +8.15582201257348 1.05875516401426 +8.18034680187702 1.14310653697144 +8.20487159118056 1.11692852467439 +8.2293963804841 1.10820252057537 +8.25392116978765 1.05875516401426 +8.27844595909119 1.16346721320249 +8.30297074839473 1.18091922140052 +8.32749553769827 0.983129795156102 +8.35202032700181 1.10820252057537 +8.37654511630535 1.15183254107046 +8.40106990560889 1.1692845492685 +8.42559469491243 1.17510188533451 +8.45011948421597 1.07038983614629 +8.47464427351952 1.17219321730151 +8.49916906282306 1.15183254107046 +8.5236938521266 1.12856319680641 +8.54821864143014 1.24490991812666 +8.57274343073368 1.09365918041034 +8.59726822003722 1.13438053287243 +8.62179300934076 1.17219321730151 +8.6463177986443 1.17801055336752 +8.67084258794785 1.09947651647635 +8.69536737725139 1.0296684836842 +8.71989216655493 1.11983719270739 +8.74441695585847 1.00930780745316 +8.76894174516201 1.08493317631132 +8.79346653446555 1.05002915991524 +8.81799132376909 1.19255389353255 +8.84251611307263 1.00930780745316 +8.86704090237617 1.08493317631132 +8.89156569167972 1.13438053287243 +8.91609048098326 1.17219321730151 +8.9406152702868 1.07038983614629 +8.96514005959034 1.10529385254236 +8.98966484889388 1.04130315581623 +9.01418963819742 1.0296684836842 +9.03871442750096 1.07329850417929 +9.0632392168045 1.07038983614629 +9.08776400610805 1.08202450827831 +9.11228879541159 1.08784184434433 +9.13681358471513 1.11111118860838 +9.16133837401867 1.05002915991524 +9.18586316332221 1.10238518450936 +9.21038795262575 1.00930780745316 +9.23491274192929 1.08784184434433 +9.25943753123283 1.17510188533451 +9.28396232053638 1.08202450827831 +9.30848710983992 1.0296684836842 +9.33301189914346 0.980221127123096 +9.357536688447 1.0296684836842 +9.38206147775054 1.05584649598126 +9.40658626705408 1.11401985664138 +9.43111105635762 0.924956434495977 +9.45563584566116 1.03839448778322 +9.4801606349647 1.01221647548616 +9.50468542426825 1.05002915991524 +9.52921021357179 0.994764467288127 +9.55373500287533 1.10238518450936 +9.57825979217887 1.05584649598126 +9.60278458148241 1.07038983614629 +9.62730937078595 1.06748116811328 +9.65183416008949 1.10238518450936 +9.67635894939303 0.997673135321133 +9.70088373869658 1.01803381155218 +9.72540852800012 1.07329850417929 +9.74993331730366 1.06166383204727 +9.7744581066072 1.06166383204727 +9.79898289591074 0.948225778760027 +9.82350768521428 1.03839448778322 +9.84803247451782 0.968586454991071 +9.87255726382136 0.986038463189108 +9.8970820531249 0.933682438594996 +9.92160684242845 1.06457250008028 +9.94613163173199 1.11401985664138 +9.97065642103553 1.06457250008028 +9.99518121033907 0.90750442629794 +10.0197059996426 1.1227458607404 +10.0442307889462 0.988947131222114 +10.0687555782497 0.994764467288127 +10.0932803675532 0.93077377056199 +10.1178051568568 0.922047766462971 +10.1423299461603 1.00349047138715 +10.1668547354639 0.913321762363952 +10.1913795247674 0.983129795156102 +10.2159043140709 0.977312459090089 +10.2404291033745 0.997673135321133 +10.264953892678 1.0267598156512 +10.2894786819816 0.927865102528984 +10.3140034712851 0.951134446793033 +10.3385282605886 1.03839448778322 +10.3630530498922 1.15764987713647 +10.3875778391957 0.974403791057083 +10.4121026284993 0.887143750066897 +10.4366274178028 1.05293782794825 +10.4611522071064 0.971495123024077 +10.4856769964099 0.878417745967878 +10.5102017857134 0.942408442694015 +10.534726575017 1.01803381155218 +10.5592513643205 1.05002915991524 +10.5837761536241 0.988947131222114 +10.6083009429276 0.922047766462971 +10.6328257322311 1.02385114761819 +10.6573505215347 0.878417745967878 +10.6818753108382 0.924956434495977 +10.7064001001418 0.95404311482604 +10.7309248894453 0.922047766462971 +10.7554496787488 0.90750442629794 +10.7799744680524 0.933682438594996 +10.8044992573559 1.03257715171721 +10.8290240466595 0.977312459090089 +10.853548835963 1.00639913942015 +10.8780736252666 1.01803381155218 +10.9025984145701 0.927865102528984 +10.9271232038736 1.02385114761819 +10.9516479931772 0.99185579925512 +10.9761727824807 0.977312459090089 +11.0006975717843 0.922047766462971 +11.0252223610878 0.93077377056199 +11.0497471503913 0.88423508203389 +11.0742719396949 0.90750442629794 +11.0987967289984 0.951134446793033 +11.123321518302 0.951134446793033 +11.1478463076055 0.99185579925512 +11.172371096909 0.878417745967878 +11.1968958862126 0.913321762363952 +11.2214206755161 0.919139098429965 +11.2459454648197 0.962769118925058 +11.2704702541232 0.922047766462971 +11.2949950434268 0.945317110727021 +11.3195198327303 1.06748116811328 +11.3440446220338 0.898778422198921 +11.3685694113374 0.968586454991071 +11.3930942006409 0.956951782859046 +11.4176189899445 0.997673135321133 +11.442143779248 1.03257715171721 +11.4666685685515 1.10529385254236 +11.4911933578551 1.15183254107046 +11.5157181471586 1.16055854516948 +11.5402429364622 1.06748116811328 +11.5647677257657 1.09947651647635 +11.5892925150692 1.23909258206065 +11.6138173043728 1.18964522549954 +11.6383420936763 1.30017461075378 +11.6628668829799 1.31762661895182 +11.6873916722834 1.20127989763157 +11.711916461587 1.25363592222568 +11.7364412508905 1.25363592222568 +11.760966040194 1.14892387303746 +11.7854908294976 1.17219321730151 +11.8100156188011 1.20709723369758 +11.8345404081047 1.13147186483942 +11.8590651974082 1.36125663944691 +11.8835899867117 1.21291456976359 +11.9081147760153 1.33216995911685 +11.9326395653188 1.27690526648973 +11.9571643546224 1.27108793042372 +11.9816891439259 1.28853993862175 +12.0062139332294 1.22164057386261 +12.030738722533 1.28563127058875 +12.0552635118365 1.27981393452274 +12.0797883011401 1.25654459025869 +12.1043130904436 1.37579997961194 +12.1288378797472 1.37870864764495 +12.1533626690507 1.35253063534789 +12.1778874583542 1.33798729518286 +12.2024122476578 1.404886659942 +12.2269370369613 1.50087270503121 +12.2514618262649 1.64921477471453 +12.2759866155684 1.65794077881354 +12.3005114048719 1.62594543045048 +12.3250361941755 1.72193147553968 +12.349560983479 1.7306574796387 +12.3740857727826 1.97498559441122 +12.3986105620861 1.91099489768509 +12.4231353513896 2.12914500016055 +12.4476601406932 2.11460165999552 +12.4721849299967 2.28330440590988 +12.4967097193003 2.32984309443798 +12.5212345086038 2.55671920101247 +12.5457592979074 2.46655049198927 +12.5702840872109 2.9464807174353 +12.5948088765144 2.95520672153432 +12.619333665818 3.01628875022745 +12.6438584551215 3.77545110684207 +12.6683832444251 3.94124518472343 +12.6929080337286 4.50261811509363 +12.7174328230321 4.4909834429616 +12.7419576123357 5.11343840202493 +12.7664824016392 5.92786545126667 +12.7910071909428 6.56777241852804 +12.8155319802463 8.03374110716317 +12.8400567695498 9.33682438594996 +12.8645815588534 11.6404894680909 +12.8891063481569 14.6684128904504 +12.9136311374605 18.2926132595761 +12.938155926764 23.4700423583272 +12.9626807160676 29.1448536907223 +12.9872055053711 36.8411893060568 +13.0117302946746 43.6794678516544 +13.0362550839782 49.6451459873501 +13.0607798732817 52.4549193072341 +13.0853046625853 51.0238546349951 +13.1098294518888 45.6224580977025 +13.1343542411923 38.9645169701513 +13.1588790304959 31.7713809245269 +13.1834038197994 24.3979074608562 +13.207928609103 18.5951147350088 +13.2324533984065 14.2611993658295 +13.25697818771 10.9220484639383 +13.2815029770136 8.60965737769841 +13.3060277663171 7.26876141448254 +13.3305525556207 6.27108827916141 +13.3550773449242 5.20651577908113 +13.3796021342278 4.70040754133805 +13.4041269235313 4.22047731589202 +13.4286517128348 3.68819106585188 +13.4531765021384 3.23734752073592 +13.4777012914419 3.02501475432647 +13.5022260807455 2.85631200841211 +13.526750870049 2.49854584035234 +13.5512756593525 2.40546846329614 +13.5758004486561 2.26294372967884 +13.6003252379596 1.9633509222792 +13.6248500272632 1.92844690588312 +13.6493748165667 1.77137883210079 +13.6738996058702 1.66666678291256 +13.6984243951738 1.66375811487956 +13.7229491844773 1.53577672142728 +13.7474739737809 1.37870864764495 +13.7719987630844 1.4514253484701 +13.796523552388 1.45433401650311 +13.8210483416915 1.2652705943577 +13.845573130995 1.31762661895182 +13.8700979202986 1.27690526648973 +13.8946227096021 1.18673655746654 +13.9191474989057 1.2158232377966 +13.9436722882092 1.28272260255574 +13.9681970775127 1.27108793042372 +13.9927218668163 1.14601520500445 +14.0172466561198 1.14019786893844 +14.0417714454234 1.21000590173059 +14.0662962347269 1.14601520500445 +14.0908210240304 1.04712049188224 +14.115345813334 1.11692852467439 +14.1398706026375 0.994764467288127 +14.1643953919411 1.11111118860838 +14.1889201812446 1.08202450827831 +14.2134449705482 1.08493317631132 +14.2379697598517 1.04130315581623 +14.2624945491552 1.09365918041034 +14.2870193384588 1.09075051237733 +14.3115441277623 1.10238518450936 +14.3360689170659 1.05002915991524 +14.3605937063694 1.04712049188224 +14.3851184956729 1.06457250008028 +14.4096432849765 1.16637588123549 +14.43416807428 1.12565452877341 +14.4586928635836 1.15764987713647 +14.4832176528871 1.18964522549954 +14.5077424421906 1.22745790992862 +14.5322672314942 1.21873190582961 +14.5567920207977 1.18382788943353 +14.5813168101013 1.37870864764495 +14.6058415994048 1.39906932387599 +14.6303663887084 1.43397334027207 +14.6548911780119 1.28853993862175 +14.6794159673154 1.56777206979035 +14.703940756619 1.48923803289918 +14.7284655459225 1.57649807388937 +14.7529903352261 1.85863887309097 +14.7775151245296 1.95753358621318 +14.8020399138331 2.13205366819356 +14.8265647031367 2.26876106574485 +14.8510894924402 2.69342659856376 +14.8756142817438 2.96102405760033 +14.9001390710473 3.31588155762709 +14.9246638603508 4.28155934458515 +14.9491886496544 5.34031450859942 +14.9737134389579 6.58522442672608 +14.9982382282615 8.61547471376442 +15.022763017565 10.779523730321 +15.0472878068686 12.5538112304548 +15.0718125961721 14.1826653289383 +15.0963373854756 14.8254809642327 +15.1208621747792 13.9790585666279 +15.1453869640827 12.8650387099865 +15.1699117533863 11.1547419065788 +15.1944365426898 9.36881973431303 +15.2189613319933 7.45782483662794 +15.2434861212969 5.78824938568237 +15.2680109106004 4.41826674213644 +15.292535699904 3.56602700846562 +15.3170604892075 3.05410143465653 +15.341585278511 2.52181518461639 +15.3661100678146 2.1058756558965 +15.3906348571181 2.03897629113736 +15.4151596464217 1.73356614767171 +15.4396844357252 1.61140209028544 +15.4642092250288 1.56486340175735 +15.4887340143323 1.4514253484701 +15.5132588036358 1.27690526648973 +15.5377835929394 1.27399659845672 +15.5623083822429 1.23618391402764 +15.5868331715465 1.14019786893844 +15.61135796085 0.986038463189108 +15.6358827501535 0.988947131222114 +15.6604075394571 1.04421182384923 +15.6849323287606 0.956951782859046 +15.7094571180642 0.933682438594996 +15.7339819073677 0.904595758264934 +15.7585066966712 0.817335717274747 +15.7830314859748 0.794066373010698 +15.8075562752783 0.788249036944685 +15.8320810645819 0.756253688581617 +15.8566058538854 0.767888360713642 +15.881130643189 0.782431700878673 +15.9056554324925 0.747527684482598 +15.930180221796 0.750436352515604 +15.9547050110996 0.674810983657443 +15.9792298004031 0.767888360713642 +16.0037545897067 0.66899364759143 +16.0282793790102 0.651541639393393 +16.0528041683137 0.712623668086523 +16.0773289576173 0.593368278733268 +16.1018537469208 0.730075676284561 +16.1263785362244 0.639906967261368 +16.1509033255279 0.703897663987505 +16.1754281148314 0.64572430332738 +16.199952904135 0.703897663987505 +16.2244776934385 0.651541639393393 +16.2490024827421 0.677719651690449 +16.2735272720456 0.622454959063331 +16.2980520613492 0.602094282832287 +16.3225768506527 0.695171659888486 +16.3471016399562 0.631180963162349 +16.3716264292598 0.671902315624436 +16.3961512185633 0.570098934469219 +16.4206760078669 0.622454959063331 +16.4452007971704 0.567190266436213 +16.4697255864739 0.613728954964312 +16.4942503757775 0.53810358610615 +16.518775165081 0.64572430332738 +16.5432999543846 0.66899364759143 +16.5678247436881 0.660267643492411 +16.5923495329916 0.578824938568237 +16.6168743222952 0.642815635294374 +16.6413991115987 0.558464262337194 +16.6659239009023 0.6079116188983 +16.6904486902058 0.622454959063331 +16.7149734795094 0.680628319723455 +16.7394982688129 0.575916270535231 +16.7640230581164 0.654450307426399 +16.78854784742 0.593368278733268 +16.8130726367235 0.541012254139157 +16.8375974260271 0.578824938568237 +16.8621222153306 0.628272295129343 +16.8866470046341 0.573007602502225 +16.9111717939377 0.590459610700262 +16.9356965832412 0.567190266436213 +16.9602213725448 0.532286250040138 +16.9847461618483 0.602094282832287 +17.0092709511518 0.5613729303702 +17.0337957404554 0.546829590205169 +17.0583205297589 0.570098934469219 +17.0828453190625 0.546829590205169 +17.107370108366 0.517742909875107 +17.1318948976696 0.613728954964312 +17.1564196869731 0.546829590205169 +17.1809444762766 0.53810358610615 +17.2054692655802 0.517742909875107 +17.2299940548837 0.555555594304188 +17.2545188441873 0.5613729303702 +17.2790436334908 0.520651577908113 +17.3035684227943 0.543920922172163 +17.3280932120979 0.497382233644063 +17.3526180014014 0.491564897578051 +17.377142790705 0.5613729303702 +17.4016675800085 0.575916270535231 +17.426192369312 0.546829590205169 +17.4507171586156 0.541012254139157 +17.4752419479191 0.546829590205169 +17.4997667372227 0.488656229545045 +17.5242915265262 0.514834241842101 +17.5488163158298 0.503199569710076 +17.5733411051333 0.485747561512038 +17.5978658944368 0.596276946766275 +17.6223906837404 0.555555594304188 +17.6469154730439 0.517742909875107 +17.6714402623475 0.526468913974126 +17.695965051651 0.532286250040138 +17.7204898409545 0.596276946766275 +17.7450146302581 0.573007602502225 +17.7695394195616 0.471204221347007 +17.7940642088652 0.447934877082958 +17.8185889981687 0.555555594304188 +17.8431137874722 0.555555594304188 +17.8676385767758 0.526468913974126 +17.8921633660793 0.485747561512038 +17.9166881553829 0.541012254139157 +17.9412129446864 0.482838893479032 +17.96573773399 0.546829590205169 +17.9902625232935 0.526468913974126 +18.014787312597 0.541012254139157 +18.0393121019006 0.450843545115964 +18.0638368912041 0.491564897578051 +18.0883616805077 0.500290901677069 +18.1128864698112 0.546829590205169 +18.1374112591147 0.465386885280995 +18.1619360484183 0.497382233644063 +18.1864608377218 0.485747561512038 +18.2109856270254 0.570098934469219 +18.2355104163289 0.552646926271182 +18.2600352056324 0.558464262337194 +18.284559994936 0.517742909875107 +18.3090847842395 0.552646926271182 +18.3336095735431 0.529377582007132 +18.3581343628466 0.567190266436213 +18.3826591521502 0.543920922172163 +18.4071839414537 0.535194918073144 +18.4317087307572 0.482838893479032 +18.4562335200608 0.509016905776088 +18.4807583093643 0.465386885280995 +18.5052830986679 0.468295553314001 +18.5298078879714 0.482838893479032 +18.5543326772749 0.497382233644063 +18.5788574665785 0.474112889380014 +18.603382255882 0.47702155741302 +18.6279070451856 0.511925573809094 +18.6524318344891 0.500290901677069 +18.6769566237926 0.526468913974126 +18.7014814130962 0.450843545115964 +18.7260062023997 0.514834241842101 +18.7505309917033 0.494473565611057 +18.7750557810068 0.494473565611057 +18.7995805703104 0.529377582007132 +18.8241053596139 0.491564897578051 +18.8486301489174 0.511925573809094 +18.873154938221 0.514834241842101 +18.8976797275245 0.593368278733268 +18.9222045168281 0.53810358610615 +18.9467293061316 0.587550942667256 +18.9712540954351 0.535194918073144 +18.9957788847387 0.625363627096337 +19.0203036740422 0.683536987756461 +19.0448284633458 0.6079116188983 +19.0693532526493 0.567190266436213 +19.0938780419528 0.671902315624436 +19.1184028312564 0.622454959063331 +19.1429276205599 0.660267643492411 +19.1674524098635 0.64572430332738 +19.191977199167 0.671902315624436 +19.2165019884706 0.625363627096337 +19.2410267777741 0.573007602502225 +19.2655515670776 0.619546291030324 +19.2900763563812 0.58464227463425 +19.3146011456847 0.666084979558424 +19.3391259349883 0.654450307426399 +19.3636507242918 0.535194918073144 +19.3881755135953 0.610820286931306 +19.4127003028989 0.567190266436213 +19.4372250922024 0.599185614799281 +19.461749881506 0.555555594304188 +19.4862746708095 0.546829590205169 +19.510799460113 0.555555594304188 +19.5353242494166 0.549738258238175 +19.5598490387201 0.593368278733268 +19.5843738280237 0.657358975459405 +19.6088986173272 0.543920922172163 +19.6334234066308 0.590459610700262 +19.6579481959343 0.53810358610615 +19.6824729852378 0.546829590205169 +19.7069977745414 0.573007602502225 +19.7315225638449 0.541012254139157 +19.7560473531485 0.5613729303702 +19.780572142452 0.613728954964312 +19.8050969317555 0.570098934469219 +19.8296217210591 0.613728954964312 +19.8541465103626 0.596276946766275 +19.8786712996662 0.628272295129343 +19.9031960889697 0.610820286931306 +19.9277208782732 0.514834241842101 +19.9522456675768 0.596276946766275 +19.9767704568803 0.64572430332738 +20.0012952461839 0.590459610700262 +20.0258200354874 0.596276946766275 +20.050344824791 0.570098934469219 +20.0748696140945 0.666084979558424 +20.099394403398 0.657358975459405 +20.1239191927016 0.558464262337194 +20.1484439820051 0.654450307426399 +20.1729687713087 0.703897663987505 +20.1974935606122 0.700988995954499 +20.2220183499157 0.625363627096337 +20.2465431392193 0.663176311525418 +20.2710679285228 0.724258340218548 +20.2955927178264 0.69226299185548 +20.3201175071299 0.802792377109716 +20.3446422964334 0.875509077934872 +20.369167085737 0.750436352515604 +20.3936918750405 0.732984344317567 +20.4182166643441 0.852239733670822 +20.4427414536476 0.875509077934872 +20.4672662429512 0.817335717274747 +20.4917910322547 0.881326414000884 +20.5163158215582 0.945317110727021 +20.5408406108618 0.927865102528984 +20.5653654001653 0.974403791057083 +20.5898901894689 0.90750442629794 +20.6144149787724 1.06166383204727 +20.6389397680759 1.11692852467439 +20.6634645573795 1.15183254107046 +20.687989346683 1.21873190582961 +20.7125141359866 1.29726594272077 +20.7370389252901 1.11401985664138 +20.7615637145936 1.2158232377966 +20.7860885038972 1.34962196731489 +20.8106132932007 1.36998264354593 +20.8351380825043 1.52995938536127 +20.8596628718078 1.47178602470115 +20.8841876611114 1.57068073782336 +20.9087124504149 1.63176276651649 +20.9332372397184 1.75392682390275 +20.957762029022 1.79464817636484 +20.9822868183255 1.77428750013379 +21.0068116076291 1.9662595903122 +21.0313363969326 2.18440969278767 +21.0558611862361 2.4781851641213 +21.0803859755397 2.68179192643173 +21.1049107648432 2.88539868874217 +21.1294355541468 3.27806887319801 +21.1539603434503 3.67946506175287 +21.1784851327538 4.3979060659054 +21.2030099220574 5.39267053319353 +21.2275347113609 6.38743500048165 +21.2520595006645 8.53112334080724 +21.276584289968 10.3635842016012 +21.3011090792716 13.3478776034655 +21.3256338685751 16.9982559848883 +21.3501586578786 20.6922643868062 +21.3746834471822 23.9586985878722 +21.3992082364857 26.4805137724886 +21.4237330257893 26.5474131372478 +21.4482578150928 25.3228638953521 +21.4727826043963 23.374056313238 +21.4973073936999 20.9278664974797 +21.5218321830034 19.173939673577 +21.546356972307 16.6928458414227 +21.5708817616105 13.586388382172 +21.595406550914 11.2623626238001 +21.6199313402176 8.92670219329608 +21.6444561295211 6.82373520543259 +21.6689809188247 5.73880202912127 +21.6935057081282 4.43571875033448 +21.7180304974318 3.94997118882244 +21.7425552867353 3.23734752073592 +21.7670800760388 2.95811538956732 +21.7916048653424 2.54217586084743 +21.8161296546459 2.2920304100089 +21.8406544439495 2.06515430343442 +21.865179233253 1.89063422145404 +21.8897040225565 1.74520081980373 +21.9142288118601 1.53577672142728 +21.9387536011636 1.50669004109722 +21.9632783904672 1.48342069683317 +21.9878031797707 1.25654459025869 +22.0123279690742 1.16637588123549 +22.0368527583778 1.13728920090543 +22.0613775476813 1.12856319680641 +22.0859023369849 1.05293782794825 +22.1104271262884 0.988947131222114 +22.134951915592 0.983129795156102 +22.1594767048955 0.974403791057083 +22.184001494199 0.933682438594996 +22.2085262835026 0.788249036944685 +22.2330510728061 0.863874405802847 +22.2575758621097 0.849331065637816 +22.2821006514132 0.814427049241741 +22.3066254407167 0.869691741868859 +22.3311502300203 0.802792377109716 +22.3556750193238 0.750436352515604 +22.3801998086274 0.846422397604809 +22.4047245979309 0.762071024647629 +22.4292493872344 0.709715000053517 +22.453774176538 0.759162356614623 +22.4782989658415 0.703897663987505 +22.5028237551451 0.738801680383579 +22.5273485444486 0.700988995954499 +22.5518733337522 0.712623668086523 +22.5763981230557 0.654450307426399 +22.6009229123592 0.747527684482598 +22.6254477016628 0.66899364759143 +22.6499724909663 0.654450307426399 +22.6744972802699 0.69226299185548 +22.6990220695734 0.686445655789467 +22.7235468588769 0.6079116188983 +22.7480716481805 0.674810983657443 +22.772596437484 0.53810358610615 +22.7971212267876 0.674810983657443 +22.8216460160911 0.6079116188983 +22.8461708053947 0.526468913974126 +22.8706955946982 0.575916270535231 +22.8952203840017 0.53810358610615 +22.9197451733053 0.605002950865293 +22.9442699626088 0.497382233644063 +22.9687947519124 0.546829590205169 +22.9933195412159 0.625363627096337 +23.0178443305194 0.570098934469219 +23.042369119823 0.529377582007132 +23.0668939091265 0.570098934469219 +23.0914186984301 0.567190266436213 +23.1159434877336 0.491564897578051 +23.1404682770371 0.558464262337194 +23.1649930663407 0.549738258238175 +23.1895178556442 0.509016905776088 +23.2140426449478 0.529377582007132 +23.2385674342513 0.546829590205169 +23.2630922235549 0.482838893479032 +23.2876170128584 0.494473565611057 +23.3121418021619 0.543920922172163 +23.3366665914655 0.605002950865293 +23.361191380769 0.503199569710076 +23.3857161700726 0.514834241842101 +23.4102409593761 0.58464227463425 +23.4347657486796 0.53810358610615 +23.4592905379832 0.541012254139157 +23.4838153272867 0.555555594304188 +23.5083401165903 0.596276946766275 +23.5328649058938 0.587550942667256 +23.5573896951973 0.529377582007132 +23.5819144845009 0.578824938568237 +23.6064392738044 0.628272295129343 +23.630964063108 0.593368278733268 +23.6554888524115 0.485747561512038 +23.6800136417151 0.625363627096337 +23.7045384310186 0.605002950865293 +23.7290632203221 0.634089631195356 +23.7535880096257 0.651541639393393 +23.7781127989292 0.590459610700262 +23.8026375882328 0.625363627096337 +23.8271623775363 0.605002950865293 +23.8516871668398 0.703897663987505 +23.8762119561434 0.596276946766275 +23.9007367454469 0.651541639393393 +23.9252615347505 0.674810983657443 +23.949786324054 0.619546291030324 +23.9743111133575 0.666084979558424 +23.9988359026611 0.689354323822474 +24.0233606919646 0.709715000053517 +24.0478854812682 0.634089631195356 +24.0724102705717 0.759162356614623 +24.0969350598753 0.718441004152536 +24.1214598491788 0.796975041043704 +24.1459846384823 0.895869754165915 +24.1705094277859 0.744619016449592 +24.1950342170894 0.866783073835853 +24.219559006393 0.843513729571803 +24.2440837956965 0.919139098429965 +24.268608585 0.828970389406772 +24.2931333743036 0.828970389406772 +24.3176581636071 0.892961086132909 +24.3421829529107 0.924956434495977 +24.3667077422142 0.95404311482604 +24.3912325315177 0.942408442694015 +24.4157573208213 0.95404311482604 +24.4402821101248 0.939499774661008 +24.4648068994284 1.0762071722123 +24.4893316887319 1.16346721320249 +24.5138564780355 1.21000590173059 +24.538381267339 1.10529385254236 +24.5629060566425 1.25654459025869 +24.5874308459461 1.24490991812666 +24.6119556352496 1.31762661895182 +24.6364804245532 1.41652133207403 +24.6610052138567 1.37870864764495 +24.6855300031602 1.56195473372434 +24.7100547924638 1.62885409848348 +24.7345795817673 1.61431075831845 +24.7591043710709 1.74810948783674 +24.7836291603744 1.93426424194913 +24.8081539496779 1.95753358621318 +24.8326787389815 2.28621307394289 +24.857203528285 2.42582913952719 +24.8817283175886 2.57707987724351 +24.9062531068921 2.94066338136929 +24.9307778961957 3.81326379127115 +24.9553026854992 4.60442149624884 +24.9798274748027 5.38685319712752 +25.0043522641063 6.80919186526756 +25.0288770534098 8.66492207032552 +25.0534018427134 10.8638751032782 +25.0779266320169 13.577662378073 +25.1024514213204 16.4979650832113 +25.126976210624 19.2553823785012 +25.1515009999275 20.4886576244958 +25.1760257892311 20.5410136490899 +25.2005505785346 19.2175696940721 +25.2250753678381 17.751601005437 +25.2496001571417 16.3118103290989 +25.2741249464452 14.9214670093219 +25.2986497357488 14.5317054928991 +25.3231745250523 12.4607338533986 +25.3476993143559 10.7038983614629 +25.3722241036594 8.79872079984381 +25.3967488929629 7.17277536939333 +25.4212736822665 5.87841809470557 +25.44579847157 4.41826674213644 +25.4703232608736 3.85689381176624 +25.4948480501771 3.39441559451826 +25.5193728394806 2.79813864775198 +25.5438976287842 2.3880164550981 +25.5684224180877 2.1495056763916 +25.5929472073913 1.98371159851024 +25.6174719966948 1.90226889358607 +25.6419967859983 1.60267608618643 +25.6665215753019 1.54741139355931 +25.6910463646054 1.5415940574933 +25.715571153909 1.4514253484701 +25.7400959432125 1.33798729518286 +25.7646207325161 1.36998264354593 +25.7891455218196 1.31471795091881 +25.8136703111231 1.21291456976359 +25.8381951004267 1.14601520500445 +25.8627198897302 1.15764987713647 +25.8872446790338 1.14892387303746 +25.9117694683373 1.08202450827831 +25.9362942576408 1.18091922140052 +25.9608190469444 1.04421182384923 +25.9853438362479 1.23327524599464 +26.0098686255515 1.11692852467439 +26.034393414855 1.27981393452274 +26.0589182041585 1.24781858615967 +26.0834429934621 1.44560801240409 +26.1079677827656 1.58231540995538 +26.1324925720692 1.72774881160569 +26.1570173613727 1.92553823785012 +26.1815421506763 2.41128579936216 +26.2060669399798 2.76905196742192 +26.2305917292833 3.19662616827383 +26.2551165185869 3.96160586095447 +26.2796413078904 3.98487520521852 +26.304166097194 4.17393862736392 +26.3286908864975 3.98196653718551 +26.353215675801 3.9296105125914 +26.3777404651046 3.48749297157446 +26.4022652544081 3.46422362731041 +26.4267900437117 3.06573610678855 +26.4513148330152 2.82431666004904 +26.4758396223187 2.60325788954057 +26.5003644116223 2.32111709033896 +26.5248892009258 1.86445620915699 +26.5494139902294 1.46015135256912 +26.5739387795329 1.45433401650311 +26.5984635688365 1.23909258206065 +26.62298835814 1.19255389353255 +26.6475131474435 1.07911584024531 +26.6720379367471 0.869691741868859 +26.6965627260506 0.875509077934872 +26.7210875153542 0.770797028746648 +26.7456123046577 0.689354323822474 +26.7701370939612 0.683536987756461 +26.7946618832648 0.654450307426399 +26.8191866725683 0.651541639393393 +26.8437114618719 0.581733606601244 +26.8682362511754 0.58464227463425 +26.8927610404789 0.631180963162349 +26.9172858297825 0.555555594304188 +26.941810619086 0.514834241842101 +26.9663354083896 0.552646926271182 +26.9908601976931 0.573007602502225 +27.0153849869967 0.532286250040138 +27.0399097763002 0.407213524620871 +27.0644345656037 0.578824938568237 +27.0889593549073 0.514834241842101 +27.1134841442108 0.47702155741302 +27.1380089335144 0.506108237743082 +27.1625337228179 0.511925573809094 +27.1870585121214 0.488656229545045 +27.211583301425 0.494473565611057 +27.2361080907285 0.511925573809094 +27.2606328800321 0.497382233644063 +27.2851576693356 0.488656229545045 +27.3096824586391 0.506108237743082 +27.3342072479427 0.47702155741302 +27.3587320372462 0.459569549214982 +27.3832568265498 0.482838893479032 +27.4077816158533 0.340314159861727 +27.4323064051569 0.474112889380014 +27.4568311944604 0.43048286888492 +27.4813559837639 0.398487520521852 +27.5058807730675 0.506108237743082 +27.530405562371 0.404304856587864 +27.5549303516746 0.398487520521852 +27.5794551409781 0.398487520521852 +27.6039799302816 0.413030860686883 +27.6285047195852 0.488656229545045 +27.6530295088887 0.482838893479032 +27.6775542981923 0.389761516422833 +27.7020790874958 0.395578852488846 +27.7266038767993 0.363583504125777 +27.7511286661029 0.383944180356821 +27.7756534554064 0.418848196752895 +27.80017824471 0.354857500026759 +27.8247030340135 0.401396188554858 +27.8492278233171 0.375218176257802 +27.8737526126206 0.436300204950933 +27.8982774019241 0.340314159861727 +27.9228021912277 0.363583504125777 +27.9473269805312 0.392670184455839 +27.9718517698348 0.357766168059765 +27.9963765591383 0.395578852488846 +28.0209013484418 0.351948831993752 +28.0454261377454 0.459569549214982 +28.0699509270489 0.407213524620871 +28.0944757163525 0.389761516422833 +28.119000505656 0.389761516422833 +28.1435252949595 0.340314159861727 +28.1680500842631 0.410122192653877 +28.1925748735666 0.36940084019179 +28.2170996628702 0.360674836092771 +28.2416244521737 0.363583504125777 +28.2661492414773 0.381035512323815 +28.2906740307808 0.378126844290808 +28.3151988200843 0.337405491828721 +28.3397236093879 0.410122192653877 +28.3642483986914 0.389761516422833 +28.388773187995 0.36940084019179 +28.4132979772985 0.372309508224796 +28.437822766602 0.407213524620871 +28.4623475559056 0.340314159861727 +28.4868723452091 0.366492172158783 +28.5113971345127 0.407213524620871 +28.5359219238162 0.375218176257802 +28.5604467131197 0.424665532818908 +28.5849715024233 0.378126844290808 +28.6094962917268 0.36940084019179 +28.6340210810304 0.363583504125777 +28.6585458703339 0.386852848389827 +28.6830706596375 0.386852848389827 +28.707595448941 0.343222827894734 +28.7321202382445 0.410122192653877 +28.7566450275481 0.354857500026759 +28.7811698168516 0.395578852488846 +28.8056946061552 0.404304856587864 +28.8302193954587 0.378126844290808 +28.8547441847622 0.343222827894734 +28.8792689740658 0.381035512323815 +28.9037937633693 0.410122192653877 +28.9283185526729 0.340314159861727 +28.9528433419764 0.410122192653877 +28.9773681312799 0.354857500026759 +29.0018929205835 0.418848196752895 +29.026417709887 0.29959280739964 +29.0509424991906 0.424665532818908 +29.0754672884941 0.378126844290808 +29.0999920777977 0.351948831993752 +29.1245168671012 0.383944180356821 +29.1490416564047 0.36940084019179 +29.1735664457083 0.415939528719889 +29.1980912350118 0.421756864785902 +29.2226160243154 0.45375221314897 +29.2471408136189 0.424665532818908 +29.2716656029224 0.418848196752895 +29.296190392226 0.424665532818908 +29.3207151815295 0.424665532818908 +29.3452399708331 0.343222827894734 +29.3697647601366 0.398487520521852 +29.3942895494401 0.450843545115964 +29.4188143387437 0.479930225446026 +29.4433391280472 0.462478217247989 +29.4678639173508 0.386852848389827 +29.4923887066543 0.462478217247989 +29.5169134959579 0.450843545115964 +29.5414382852614 0.482838893479032 +29.5659630745649 0.47702155741302 +29.5904878638685 0.433391536917926 +29.615012653172 0.459569549214982 +29.6395374424756 0.421756864785902 +29.6640622317791 0.491564897578051 +29.6885870210826 0.450843545115964 +29.7131118103862 0.456660881181976 +29.7376365996897 0.523560245941119 +29.7621613889933 0.520651577908113 +29.7866861782968 0.503199569710076 +29.8112109676003 0.485747561512038 +29.8357357569039 0.526468913974126 +29.8602605462074 0.541012254139157 +29.884785335511 0.485747561512038 +29.9093101248145 0.514834241842101 +29.9338349141181 0.497382233644063 +29.9583597034216 0.543920922172163 +29.9828844927251 0.570098934469219 +30.0074092820287 0.575916270535231 +30.0319340713322 0.564281598403206 +30.0564588606358 0.590459610700262 +30.0809836499393 0.590459610700262 +30.1055084392428 0.654450307426399 +30.1300332285464 0.732984344317567 +30.1545580178499 0.735893012350573 +30.1790828071535 0.796975041043704 +30.203607596457 0.834787725472785 +30.2281323857605 1.01512514351917 +30.2526571750641 1.09365918041034 +30.2771819643676 1.15474120910347 +30.3017067536712 1.4950553689652 +30.3262315429747 1.84118686489294 +30.3507563322783 2.22222237721675 +30.3752811215818 2.7574172952899 +30.3998059108853 3.35660291008918 +30.4243307001889 4.00814454948257 +30.4488554894924 3.99941854538355 +30.473380278796 4.09831325850576 +30.4979050680995 3.68819106585188 +30.522429857403 3.42641094288132 +30.5469546467066 3.08318811498659 +30.5714794360101 2.87376401661014 +30.5960042253137 2.6207098977386 +30.6205290146172 2.73705661905885 +30.6450538039207 2.62361856577161 +30.6695785932243 2.24840038951381 +30.6941033825278 2.10005831983049 +30.7186281718314 1.64339743864851 +30.7431529611349 1.50087270503121 +30.7676777504385 1.21291456976359 +30.792202539742 1.0267598156512 +30.8167273290455 0.878417745967878 +30.8412521183491 0.817335717274747 +30.8657769076526 0.773705696779654 +30.8903016969562 0.738801680383579 +30.9148264862597 0.657358975459405 +30.9393512755632 0.552646926271182 +30.9638760648668 0.634089631195356 +30.9884008541703 0.660267643492411 +31.0129256434739 0.573007602502225 +31.0374504327774 0.552646926271182 +31.0619752220809 0.578824938568237 +31.0865000113845 0.494473565611057 +31.111024800688 0.555555594304188 +31.1355495899916 0.523560245941119 +31.1600743792951 0.485747561512038 +31.1845991685987 0.526468913974126 +31.2091239579022 0.485747561512038 +31.2336487472057 0.47702155741302 +31.2581735365093 0.520651577908113 +31.2826983258128 0.465386885280995 +31.3072231151164 0.389761516422833 +31.3317479044199 0.465386885280995 +31.3562726937234 0.471204221347007 +31.380797483027 0.520651577908113 +31.4053222723305 0.424665532818908 +31.4298470616341 0.439208872983939 +31.4543718509376 0.415939528719889 +31.4788966402411 0.468295553314001 +31.5034214295447 0.433391536917926 +31.5279462188482 0.442117541016945 +31.5524710081518 0.418848196752895 +31.5769957974553 0.53810358610615 +31.6015205867589 0.45375221314897 +31.6260453760624 0.36940084019179 +31.6505701653659 0.450843545115964 +31.6750949546695 0.462478217247989 +31.699619743973 0.447934877082958 +31.7241445332766 0.418848196752895 +31.7486693225801 0.456660881181976 +31.7731941118836 0.424665532818908 +31.7977189011872 0.447934877082958 +31.8222436904907 0.410122192653877 +31.8467684797943 0.445026209049951 +31.8712932690978 0.47702155741302 +31.8958180584013 0.471204221347007 +31.9203428477049 0.535194918073144 +31.9448676370084 0.564281598403206 +31.969392426312 0.479930225446026 +31.9939172156155 0.514834241842101 +32.0184420049191 0.532286250040138 +32.0429667942226 0.503199569710076 +32.0674915835261 0.5613729303702 +32.0920163728297 0.462478217247989 +32.1165411621332 0.520651577908113 +32.1410659514368 0.564281598403206 +32.1655907407403 0.555555594304188 +32.1901155300438 0.573007602502225 +32.2146403193474 0.581733606601244 +32.2391651086509 0.549738258238175 +32.2636898979545 0.529377582007132 +32.288214687258 0.6079116188983 +32.3127394765615 0.596276946766275 +32.3372642658651 0.605002950865293 +32.3617890551686 0.523560245941119 +32.3863138444722 0.610820286931306 +32.4108386337757 0.642815635294374 +32.4353634230793 0.709715000053517 +32.4598882123828 0.5613729303702 +32.4844130016863 0.66899364759143 +32.5089377909899 0.610820286931306 +32.5334625802934 0.619546291030324 +32.557987369597 0.698080327921492 +32.5825121589005 0.698080327921492 +32.607036948204 0.762071024647629 +32.6315617375076 0.747527684482598 +32.6560865268111 0.680628319723455 +32.6806113161147 0.721349672185542 +32.7051361054182 0.817335717274747 +32.7296608947217 0.671902315624436 +32.7541856840253 0.741710348416586 +32.7787104733288 0.814427049241741 +32.8032352626324 0.843513729571803 +32.8277600519359 0.933682438594996 +32.8522848412395 0.916230430396959 +32.876809630543 1.13147186483942 +32.9013344198465 1.05584649598126 +32.9258592091501 1.1227458607404 +32.9503839984536 1.18382788943353 +32.9749087877572 1.38743465174397 +32.9994335770607 1.55613739765833 +33.0239583663642 1.69575346324262 +33.0484831556678 1.93717290998214 +33.0730079449713 2.36183844280105 +33.0975327342749 2.71669594282781 +33.1220575235784 3.21116950843887 +33.1465823128819 4.01396188554858 +33.1711071021855 5.23269379137819 +33.195631891489 6.06748151685097 +33.2201566807926 7.24258340218548 +33.2446814700961 7.59744090221224 +33.2692062593997 7.92321172190894 +33.2937310487032 7.9988370907671 +33.3182558380067 7.26294407841653 +33.3427806273103 6.50669038983491 +33.3673054166138 6.2216409226003 +33.3918302059174 5.72135002092323 +33.4163549952209 5.45666122991967 +33.4408797845244 5.39267053319353 +33.465404573828 5.2414197954772 +33.4899293631315 4.8632929511864 +33.5144541524351 4.38918006180638 +33.5389789417386 3.80453778717213 +33.5635037310421 3.01628875022745 +33.5880285203457 2.77777797152094 +33.6125533096492 2.1495056763916 +33.6370780989528 1.9197209017841 +33.6616028882563 1.68993612717661 +33.6861276775599 1.50959870913023 +33.7106524668634 1.43106467223906 +33.7351772561669 1.5445027255263 +33.7597020454705 1.34380463124887 +33.784226834774 1.29435727468777 +33.8087516240776 1.23327524599464 +33.8332764133811 1.29726594272077 +33.8578012026846 1.35253063534789 +33.8823259919882 1.40779532797501 +33.9068507812917 1.5445027255263 +33.9313755705953 1.61431075831845 +33.9559003598988 1.81210018456287 +33.9804251492023 1.93426424194913 +34.0049499385059 2.29493907804191 +34.0294747278094 2.81849932398303 +34.053999517113 3.33624223385813 +34.0785243064165 3.93833651669042 +34.1030490957201 4.73822022576713 +34.1275738850236 5.30831916023635 +34.1520986743271 5.27632381187328 +34.1766234636307 5.40139653729255 +34.2011482529342 5.18615510285009 +34.2256730422378 4.46480543066455 +34.2501978315413 4.0692265781757 +34.2747226208448 3.9296105125914 +34.2992474101484 3.7870857789741 +34.3237721994519 3.51657965190452 +34.3482969887555 3.31006422156108 +34.372821778059 3.30133821746206 +34.3973465673625 3.2286215166369 +34.4218713566661 2.63234456987063 +34.4463961459696 2.13205366819356 +34.4709209352732 1.86736487718999 +34.4954457245767 1.55613739765833 +34.5199705138803 1.38161731567795 +34.5444953031838 1.34962196731489 +34.5690200924873 1.19255389353255 +34.5935448817909 0.901687090231928 +34.6180696710944 0.872600409901865 +34.642594460398 0.791157704977691 +34.6671192497015 0.860965737769841 +34.691644039005 0.782431700878673 +34.7161688283086 0.747527684482598 +34.7406936176121 0.680628319723455 +34.7652184069157 0.599185614799281 +34.7897431962192 0.66899364759143 +34.8142679855227 0.639906967261368 +34.8387927748263 0.622454959063331 +34.8633175641298 0.634089631195356 +34.8878423534334 0.596276946766275 +34.9123671427369 0.610820286931306 +34.9368919320405 0.500290901677069 +34.961416721344 0.532286250040138 +34.9859415106475 0.634089631195356 +35.0104662999511 0.567190266436213 +35.0349910892546 0.526468913974126 +35.0595158785582 0.526468913974126 +35.0840406678617 0.520651577908113 +35.1085654571652 0.526468913974126 +35.1330902464688 0.541012254139157 +35.1576150357723 0.488656229545045 +35.1821398250759 0.482838893479032 +35.2066646143794 0.488656229545045 +35.2311894036829 0.465386885280995 +35.2557141929865 0.445026209049951 +35.28023898229 0.456660881181976 +35.3047637715936 0.450843545115964 +35.3292885608971 0.479930225446026 +35.3538133502007 0.514834241842101 +35.3783381395042 0.459569549214982 +35.4028629288077 0.442117541016945 +35.4273877181113 0.494473565611057 +35.4519125074148 0.520651577908113 +35.4764372967184 0.491564897578051 +35.5009620860219 0.500290901677069 +35.5254868753254 0.439208872983939 +35.550011664629 0.43048286888492 +35.5745364539325 0.436300204950933 +35.5990612432361 0.479930225446026 +35.6235860325396 0.424665532818908 +35.6481108218431 0.482838893479032 +35.6726356111467 0.482838893479032 +35.6971604004502 0.509016905776088 +35.7216851897538 0.433391536917926 +35.7462099790573 0.43048286888492 +35.7707347683609 0.479930225446026 +35.7952595576644 0.43048286888492 +35.8197843469679 0.523560245941119 +35.8443091362715 0.485747561512038 +35.868833925575 0.445026209049951 +35.8933587148786 0.415939528719889 +35.9178835041821 0.459569549214982 +35.9424082934856 0.471204221347007 +35.9669330827892 0.415939528719889 +35.9914578720927 0.413030860686883 +36.0159826613963 0.445026209049951 +36.0405074506998 0.479930225446026 +36.0650322400033 0.462478217247989 +36.0895570293069 0.468295553314001 +36.1140818186104 0.523560245941119 +36.138606607914 0.413030860686883 +36.1631313972175 0.47702155741302 +36.1876561865211 0.485747561512038 +36.2121809758246 0.491564897578051 +36.2367057651281 0.506108237743082 +36.2612305544317 0.494473565611057 +36.2857553437352 0.462478217247989 +36.3102801330388 0.47702155741302 +36.3348049223423 0.43048286888492 +36.3593297116458 0.511925573809094 +36.3838545009494 0.503199569710076 +36.4083792902529 0.474112889380014 +36.4329040795565 0.491564897578051 +36.45742886886 0.552646926271182 +36.4819536581635 0.532286250040138 +36.5064784474671 0.494473565611057 +36.5310032367706 0.58464227463425 +36.5555280260742 0.500290901677069 +36.5800528153777 0.532286250040138 +36.6045776046813 0.555555594304188 +36.6291023939848 0.578824938568237 +36.6536271832883 0.619546291030324 +36.6781519725919 0.541012254139157 +36.7026767618954 0.543920922172163 +36.727201551199 0.5613729303702 +36.7517263405025 0.58464227463425 +36.776251129806 0.570098934469219 +36.8007759191096 0.511925573809094 +36.8253007084131 0.590459610700262 +36.8498254977167 0.648632971360387 +36.8743502870202 0.628272295129343 +36.8988750763237 0.66899364759143 +36.9233998656273 0.625363627096337 +36.9479246549308 0.703897663987505 +36.9724494442344 0.764979692680635 +36.9969742335379 0.703897663987505 +37.0214990228415 0.756253688581617 +37.046023812145 0.721349672185542 +37.0705486014485 0.788249036944685 +37.0950733907521 0.869691741868859 +37.1195981800556 0.805701045142722 +37.1441229693592 0.901687090231928 +37.1686477586627 0.945317110727021 +37.1931725479662 1.10529385254236 +37.2176973372698 1.14019786893844 +37.2422221265733 1.27981393452274 +37.2667469158769 1.37579997961194 +37.2912717051804 1.41652133207403 +37.3157964944839 1.80046551243085 +37.3403212837875 1.97789426244423 +37.364846073091 2.39674245919712 +37.3893708623946 2.85049467234609 +37.4138956516981 3.65328704945581 +37.4384204410017 4.40663207000442 +37.4629452303052 5.29377582007132 +37.4874700196087 6.08784219308201 +37.5119948089123 6.67248446771626 +37.5365195982158 6.9691686070829 +37.5610443875194 6.62012844312215 +37.5855691768229 6.06166418078496 +37.6100939661264 5.30541049220334 +37.63461875543 4.89819696758247 +37.6591435447335 4.54915680362172 +37.6836683340371 4.35718471344331 +37.7081931233406 4.34555004131129 +37.7327179126441 4.2553813322881 +37.7572427019477 4.49680077902761 +37.7817674912512 3.81617245930416 +37.8062922805548 3.61256569699372 +37.8308170698583 3.06864477482156 +37.8553418591619 2.68179192643173 +37.8798666484654 2.16114034852362 +37.9043914377689 1.93135557391613 +37.9289162270725 1.70157079930864 +37.953441016376 1.27690526648973 +37.9779658056796 1.13728920090543 +38.0024905949831 1.13147186483942 +38.0270153842866 0.866783073835853 +38.0515401735902 0.965677786958064 +38.0760649628937 0.898778422198921 +38.1005897521973 0.959860450892052 +38.1251145415008 0.875509077934872 +38.1496393308043 0.759162356614623 +38.1741641201079 0.709715000053517 +38.1986889094114 0.660267643492411 +38.223213698715 0.66899364759143 +38.2477384880185 0.666084979558424 +38.2722632773221 0.695171659888486 +38.2967880666256 0.587550942667256 +38.3213128559291 0.619546291030324 +38.3458376452327 0.698080327921492 +38.3703624345362 0.674810983657443 +38.3948872238398 0.689354323822474 +38.4194120131433 0.616637622997318 +38.4439368024468 0.541012254139157 +38.4684615917504 0.593368278733268 +38.4929863810539 0.497382233644063 +38.5175111703575 0.58464227463425 +38.542035959661 0.546829590205169 +38.5665607489645 0.660267643492411 +38.5910855382681 0.581733606601244 +38.6156103275716 0.590459610700262 +38.6401351168752 0.509016905776088 +38.6646599061787 0.558464262337194 +38.6891846954823 0.506108237743082 +38.7137094847858 0.575916270535231 +38.7382342740893 0.58464227463425 +38.7627590633929 0.520651577908113 +38.7872838526964 0.532286250040138 +38.811808642 0.549738258238175 +38.8363334313035 0.549738258238175 +38.860858220607 0.546829590205169 +38.8853830099106 0.514834241842101 +38.9099077992141 0.509016905776088 +38.9344325885177 0.541012254139157 +38.9589573778212 0.575916270535231 +38.9834821671247 0.45375221314897 +39.0080069564283 0.532286250040138 +39.0325317457318 0.567190266436213 +39.0570565350354 0.599185614799281 +39.0815813243389 0.53810358610615 +39.1061061136425 0.602094282832287 +39.130630902946 0.497382233644063 +39.1551556922495 0.581733606601244 +39.1796804815531 0.657358975459405 +39.2042052708566 0.564281598403206 +39.2287300601602 0.549738258238175 +39.2532548494637 0.596276946766275 +39.2777796387672 0.596276946766275 +39.3023044280708 0.587550942667256 +39.3268292173743 0.599185614799281 +39.3513540066779 0.657358975459405 +39.3758787959814 0.610820286931306 +39.4004035852849 0.634089631195356 +39.4249283745885 0.671902315624436 +39.449453163892 0.721349672185542 +39.4739779531956 0.66899364759143 +39.4985027424991 0.730075676284561 +39.5230275318027 0.791157704977691 +39.5475523211062 0.90750442629794 +39.5720771104097 0.968586454991071 +39.5966018997133 1.07038983614629 +39.6211266890168 1.08493317631132 +39.6456514783204 1.1227458607404 +39.6701762676239 1.44560801240409 +39.6947010569274 1.55613739765833 +39.719225846231 1.97789426244423 +39.7437506355345 2.19313569688669 +39.7682754248381 2.42582913952719 +39.7928002141416 3.17335682400978 +39.8173250034451 3.93542784865741 +39.8418497927487 4.68586420117302 +39.8663745820522 5.3315885045004 +39.8908993713558 5.76788870945133 +39.9154241606593 5.44793522582065 +39.9399489499629 5.33740584056641 +39.9644737392664 4.7236768856021 +39.9889985285699 4.31937202901423 +40.0135233178735 3.5573010043666 +40.038048107177 3.53112299206955 +40.0625728964806 3.43804561501335 +40.0870976857841 3.48458430354145 +40.1116224750876 3.56020967239961 +40.1361472643912 3.26352553303298 +40.1606720536947 3.35951157812218 +40.1851968429983 2.98720206989739 +40.2097216323018 2.53054118871541 +40.2342464216053 2.2454917214808 +40.2587712109089 1.83827819685993 +40.2832960002124 1.56486340175735 +40.307820789516 1.37289131157893 +40.3323455788195 1.16346721320249 +40.3568703681231 1.06166383204727 +40.3813951574266 0.968586454991071 +40.4059199467301 0.933682438594996 +40.4304447360337 0.794066373010698 +40.4549695253372 0.79988370907671 +40.4794943146408 0.782431700878673 +40.5040191039443 0.703897663987505 +40.5285438932478 0.689354323822474 +40.5530686825514 0.642815635294374 +40.5775934718549 0.642815635294374 +40.6021182611585 0.628272295129343 +40.626643050462 0.570098934469219 +40.6511678397655 0.570098934469219 +40.6756926290691 0.532286250040138 +40.7002174183726 0.543920922172163 +40.7247422076762 0.450843545115964 +40.7492669969797 0.497382233644063 +40.7737917862833 0.511925573809094 +40.7983165755868 0.546829590205169 +40.8228413648903 0.450843545115964 +40.8473661541939 0.506108237743082 +40.8718909434974 0.474112889380014 +40.896415732801 0.535194918073144 +40.9209405221045 0.485747561512038 +40.945465311408 0.413030860686883 +40.9699901007116 0.485747561512038 +40.9945148900151 0.407213524620871 +41.0190396793187 0.445026209049951 +41.0435644686222 0.433391536917926 +41.0680892579257 0.439208872983939 +41.0926140472293 0.462478217247989 +41.1171388365328 0.479930225446026 +41.1416636258364 0.407213524620871 +41.1661884151399 0.401396188554858 +41.1907132044435 0.418848196752895 +41.215237993747 0.413030860686883 +41.2397627830505 0.43048286888492 +41.2642875723541 0.407213524620871 +41.2888123616576 0.45375221314897 +41.3133371509612 0.459569549214982 +41.3378619402647 0.447934877082958 +41.3623867295682 0.418848196752895 +41.3869115188718 0.392670184455839 +41.4114363081753 0.427574200851914 +41.4359610974789 0.386852848389827 +41.4604858867824 0.404304856587864 +41.4850106760859 0.421756864785902 +41.5095354653895 0.395578852488846 +41.534060254693 0.407213524620871 +41.5585850439966 0.392670184455839 +41.5831098333001 0.439208872983939 +41.6076346226037 0.398487520521852 +41.6321594119072 0.395578852488846 +41.6566842012107 0.415939528719889 +41.6812089905143 0.445026209049951 +41.7057337798178 0.418848196752895 +41.7302585691214 0.407213524620871 +41.7547833584249 0.349040163960746 +41.7793081477284 0.363583504125777 +41.803832937032 0.413030860686883 +41.8283577263355 0.360674836092771 +41.8528825156391 0.375218176257802 +41.8774073049426 0.410122192653877 +41.9019320942461 0.36940084019179 +41.9264568835497 0.398487520521852 +41.9509816728532 0.372309508224796 +41.9755064621568 0.381035512323815 +42.0000312514603 0.401396188554858 +42.0245560407639 0.445026209049951 +42.0490808300674 0.366492172158783 +42.0736056193709 0.334496823795715 +42.0981304086745 0.404304856587864 +42.122655197978 0.378126844290808 +42.1471799872816 0.366492172158783 +42.1717047765851 0.360674836092771 +42.1962295658886 0.381035512323815 +42.2207543551922 0.354857500026759 +42.2452791444957 0.366492172158783 +42.2698039337993 0.354857500026759 +42.2943287231028 0.378126844290808 +42.3188535124063 0.418848196752895 +42.3433783017099 0.386852848389827 +42.3679030910134 0.360674836092771 +42.392427880317 0.404304856587864 +42.4169526696205 0.442117541016945 +42.4414774589241 0.381035512323815 +42.4660022482276 0.32286215166369 +42.4905270375311 0.378126844290808 +42.5150518268347 0.418848196752895 +42.5395766161382 0.462478217247989 +42.5641014054418 0.354857500026759 +42.5886261947453 0.366492172158783 +42.6131509840488 0.404304856587864 +42.6376757733524 0.372309508224796 +42.6622005626559 0.415939528719889 +42.6867253519595 0.447934877082958 +42.711250141263 0.383944180356821 +42.7357749305665 0.395578852488846 +42.7602997198701 0.509016905776088 +42.7848245091736 0.427574200851914 +42.8093492984772 0.418848196752895 +42.8338740877807 0.450843545115964 +42.8583988770843 0.386852848389827 +42.8829236663878 0.378126844290808 +42.9074484556913 0.433391536917926 +42.9319732449949 0.43048286888492 +42.9564980342984 0.474112889380014 +42.981022823602 0.479930225446026 +43.0055476129055 0.45375221314897 +43.030072402209 0.482838893479032 +43.0545971915126 0.410122192653877 +43.0791219808161 0.474112889380014 +43.1036467701197 0.482838893479032 +43.1281715594232 0.494473565611057 +43.1526963487267 0.529377582007132 +43.1772211380303 0.427574200851914 +43.2017459273338 0.474112889380014 +43.2262707166374 0.541012254139157 +43.2507955059409 0.573007602502225 +43.2753202952445 0.587550942667256 +43.299845084548 0.66899364759143 +43.3243698738515 0.613728954964312 +43.3488946631551 0.671902315624436 +43.3734194524586 0.759162356614623 +43.3979442417622 0.802792377109716 +43.4224690310657 0.866783073835853 +43.4469938203692 1.00639913942015 +43.4715186096728 1.23618391402764 +43.4960433989763 1.42524733617305 +43.5205681882799 1.6375801025825 +43.5450929775834 1.92262956981711 +43.569617766887 2.1029669878635 +43.5941425561905 2.13787100425957 +43.618667345494 2.13205366819356 +43.6431921347976 2.07678897556644 +43.6677169241011 1.90808622965208 +43.6922417134047 1.9168122337511 +43.7167665027082 1.51541604519624 +43.7412912920117 1.50669004109722 +43.7658160813153 1.4485166804371 +43.7903408706188 1.36998264354593 +43.8148656599224 1.47760336076716 +43.8393904492259 1.53577672142728 +43.8639152385294 1.401977991909 +43.888440027833 1.46306002060213 +43.9129648171365 1.28272260255574 +43.9374896064401 1.33507862714985 +43.9620143957436 1.09656784844334 +43.9865391850472 0.956951782859046 +44.0110639743507 0.837696393505791 +44.0355887636542 0.892961086132909 +44.0601135529578 0.718441004152536 +44.0846383422613 0.680628319723455 +44.1091631315649 0.636998299228362 +44.1336879208684 0.64572430332738 +44.1582127101719 0.573007602502225 +44.1827374994755 0.593368278733268 +44.207262288779 0.58464227463425 +44.2317870780826 0.532286250040138 +44.2563118673861 0.575916270535231 +44.2808366566896 0.500290901677069 +44.3053614459932 0.625363627096337 +44.3298862352967 0.599185614799281 +44.3544110246003 0.555555594304188 +44.3789358139038 0.43048286888492 +44.4034606032074 0.47702155741302 +44.4279853925109 0.497382233644063 +44.4525101818144 0.459569549214982 +44.477034971118 0.506108237743082 +44.5015597604215 0.570098934469219 +44.5260845497251 0.511925573809094 +44.5506093390286 0.622454959063331 +44.5751341283321 0.520651577908113 +44.5996589176357 0.509016905776088 +44.6241837069392 0.517742909875107 +44.6487084962428 0.529377582007132 +44.6732332855463 0.529377582007132 +44.6977580748498 0.541012254139157 +44.7222828641534 0.590459610700262 +44.7468076534569 0.58464227463425 +44.7713324427605 0.593368278733268 +44.795857232064 0.494473565611057 +44.8203820213676 0.503199569710076 +44.8449068106711 0.570098934469219 +44.8694315999746 0.526468913974126 +44.8939563892782 0.610820286931306 +44.9184811785817 0.66899364759143 +44.9430059678853 0.619546291030324 +44.9675307571888 0.575916270535231 +44.9920555464923 0.552646926271182 +45.0165803357959 0.642815635294374 +45.0411051250994 0.660267643492411 +45.065629914403 0.610820286931306 +45.0901547037065 0.703897663987505 +45.11467949301 0.642815635294374 +45.1392042823136 0.674810983657443 +45.1637290716171 0.66899364759143 +45.1882538609207 0.721349672185542 +45.2127786502242 0.695171659888486 +45.2373034395278 0.738801680383579 +45.2618282288313 0.71553233611953 +45.2863530181348 0.794066373010698 +45.3108778074384 0.82315305334076 +45.3354025967419 0.913321762363952 +45.3599273860455 0.866783073835853 +45.384452175349 0.869691741868859 +45.4089769646525 1.09365918041034 +45.4335017539561 1.25072725419267 +45.4580265432596 1.15474120910347 +45.4825513325632 1.43688200830507 +45.5070761218667 1.53286805339428 +45.5316009111702 1.83536952882692 +45.5561257004738 2.30075641410792 +45.5806504897773 2.46364182395627 +45.6051752790809 3.01628875022745 +45.6297000683844 3.6009310248617 +45.654224857688 4.09831325850576 +45.6787496469915 4.76439823806419 +45.703274436295 4.92728364791253 +45.7277992255986 5.27632381187328 +45.7523240149021 5.24432846351021 +45.7768488042057 4.40954073803743 +45.8013735935092 4.1623039552319 +45.8258983828127 3.6969170699509 +45.8504231721163 3.25770819696696 +45.8749479614198 3.26352553303298 +45.8994727507234 2.91739403710524 +45.9239975400269 3.15008747974573 +45.9485223293304 3.28097754123101 +45.973047118634 3.275160205165 +45.9975719079375 3.09191411908561 +46.0220966972411 3.1820828281088 +46.0466214865446 2.92612004120426 +46.0711462758482 2.82431666004904 +46.0956710651517 2.35020377066902 +46.1201958544552 2.05351963130239 +46.1447206437588 1.97789426244423 +46.1692454330623 1.88772555342104 +46.1937702223659 1.67539278701158 +46.2182950116694 1.75974415996876 +46.2428198009729 1.71611413947367 +46.2673445902765 1.72774881160569 +46.29186937958 1.88481688538803 +46.3163941688836 2.12332766409454 +46.3409189581871 2.35602110673504 +46.3654437474906 2.63525323790363 +46.3899685367942 2.79522997971898 +46.4144933260977 2.74287395512486 +46.4390181154013 2.48691116822032 +46.4635429047048 2.56253653707848 +46.4880676940084 2.16695768458963 +46.5125924833119 2.07388030753343 +46.5371172726154 1.89936022555306 +46.561642061919 1.67248411897858 +46.5861668512225 1.50087270503121 +46.6106916405261 1.55613739765833 +46.6352164298296 1.78301350423281 +46.6597412191331 1.64048877061551 +46.6842660084367 1.83827819685993 +46.7087907977402 1.59395008208741 +46.7333155870438 1.70738813537465 +46.7578403763473 1.42815600420605 +46.7823651656508 1.29435727468777 +46.8068899549544 1.09365918041034 +46.8314147442579 1.16055854516948 +46.8559395335615 1.00930780745316 +46.880464322865 0.826061721373766 +46.9049891121686 0.782431700878673 +46.9295139014721 0.744619016449592 +46.9540386907756 0.628272295129343 +46.9785634800792 0.625363627096337 +47.0030882693827 0.66899364759143 +47.0276130586863 0.686445655789467 +47.0521378479898 0.613728954964312 +47.0766626372933 0.671902315624436 +47.1011874265969 0.605002950865293 +47.1257122159004 0.596276946766275 +47.150237005204 0.529377582007132 +47.1747617945075 0.590459610700262 +47.199286583811 0.567190266436213 +47.2238113731146 0.58464227463425 +47.2483361624181 0.494473565611057 +47.2728609517217 0.506108237743082 +47.2973857410252 0.506108237743082 +47.3219105303288 0.506108237743082 +47.3464353196323 0.506108237743082 +47.3709601089358 0.488656229545045 +47.3954848982394 0.491564897578051 +47.4200096875429 0.474112889380014 +47.4445344768465 0.439208872983939 +47.46905926615 0.509016905776088 +47.4935840554535 0.491564897578051 +47.5181088447571 0.424665532818908 +47.5426336340606 0.526468913974126 +47.5671584233642 0.424665532818908 +47.5916832126677 0.485747561512038 +47.6162080019712 0.410122192653877 +47.6407327912748 0.497382233644063 +47.6652575805783 0.424665532818908 +47.6897823698819 0.47702155741302 +47.7143071591854 0.410122192653877 +47.738831948489 0.503199569710076 +47.7633567377925 0.494473565611057 +47.787881527096 0.447934877082958 +47.8124063163996 0.442117541016945 +47.8369311057031 0.509016905776088 +47.8614558950067 0.36940084019179 +47.8859806843102 0.436300204950933 +47.9105054736137 0.392670184455839 +47.9350302629173 0.421756864785902 +47.9595550522208 0.485747561512038 +47.9840798415244 0.433391536917926 +48.0086046308279 0.433391536917926 +48.0331294201314 0.450843545115964 +48.057654209435 0.43048286888492 +48.0821789987385 0.474112889380014 +48.1067037880421 0.47702155741302 +48.1312285773456 0.43048286888492 +48.1557533666492 0.459569549214982 +48.1802781559527 0.421756864785902 +48.2048029452562 0.479930225446026 +48.2293277345598 0.523560245941119 +48.2538525238633 0.407213524620871 +48.2783773131669 0.494473565611057 +48.3029021024704 0.468295553314001 +48.3274268917739 0.410122192653877 +48.3519516810775 0.45375221314897 +48.376476470381 0.503199569710076 +48.4010012596846 0.474112889380014 +48.4255260489881 0.45375221314897 +48.4500508382916 0.599185614799281 +48.4745756275952 0.506108237743082 +48.4991004168987 0.462478217247989 +48.5236252062023 0.468295553314001 +48.5481499955058 0.442117541016945 +48.5726747848094 0.529377582007132 +48.5971995741129 0.555555594304188 +48.6217243634164 0.546829590205169 +48.64624915272 0.619546291030324 +48.6707739420235 0.6079116188983 +48.6952987313271 0.599185614799281 +48.7198235206306 0.762071024647629 +48.7443483099341 0.680628319723455 +48.7688730992377 0.680628319723455 +48.7933978885412 0.727167008251555 +48.8179226778448 0.770797028746648 +48.8424474671483 0.852239733670822 +48.8669722564518 0.994764467288127 +48.8914970457554 1.10820252057537 +48.9160218350589 1.24200125009366 +48.9405466243625 1.45433401650311 +48.965071413666 1.83536952882692 +48.9895962029696 1.94008157801515 +49.0141209922731 2.48981983625332 +49.0386457815766 2.60616655757357 +49.0631705708802 2.73414795102584 +49.0876953601837 2.83013399611505 +49.1122201494873 2.52181518461639 +49.1367449387908 2.31820842230596 +49.1612697280943 2.17277502065564 +49.1857945173979 1.75101815586974 +49.2103193067014 1.80046551243085 +49.234844096005 1.72774881160569 +49.2593688853085 1.50669004109722 +49.283893674612 1.58522407798839 +49.3084184639156 1.64921477471453 +49.3329432532191 1.60267608618643 +49.3574680425227 1.81791752062889 +49.3819928318262 1.72193147553968 +49.4065176211298 1.67830145504459 +49.4310424104333 1.50378137306421 +49.4555671997368 1.37870864764495 +49.4800919890404 1.28853993862175 +49.5046167783439 1.01512514351917 +49.5291415676475 0.968586454991071 +49.553666356951 0.919139098429965 +49.5781911462545 0.794066373010698 +49.6027159355581 0.811518381208735 +49.6272407248616 0.677719651690449 +49.6517655141652 0.64572430332738 +49.6762903034687 0.657358975459405 +49.7008150927722 0.628272295129343 +49.7253398820758 0.648632971360387 +49.7498646713793 0.485747561512038 +49.7743894606829 0.529377582007132 +49.7989142499864 0.546829590205169 +49.82343903929 0.535194918073144 +49.8479638285935 0.575916270535231 +49.872488617897 0.491564897578051 +49.8970134072006 0.413030860686883 +49.9215381965041 0.465386885280995 +49.9460629858077 0.447934877082958 +49.9705877751112 0.439208872983939 +49.9951125644147 0.509016905776088 +50.0196373537183 0.465386885280995 +50.0441621430218 0.436300204950933 +50.0686869323254 0.494473565611057 +50.0932117216289 0.424665532818908 +50.1177365109324 0.511925573809094 +50.142261300236 0.506108237743082 +50.1667860895395 0.450843545115964 +50.1913108788431 0.532286250040138 +50.2158356681466 0.488656229545045 +50.2403604574502 0.418848196752895 +50.2648852467537 0.517742909875107 +50.2894100360572 0.488656229545045 +50.3139348253608 0.506108237743082 +50.3384596146643 0.509016905776088 +50.3629844039679 0.520651577908113 +50.3875091932714 0.535194918073144 +50.4120339825749 0.482838893479032 +50.4365587718785 0.552646926271182 +50.461083561182 0.616637622997318 +50.4856083504856 0.471204221347007 +50.5101331397891 0.53810358610615 +50.5346579290926 0.439208872983939 +50.5591827183962 0.479930225446026 +50.5837075076997 0.514834241842101 +50.6082322970033 0.511925573809094 +50.6327570863068 0.619546291030324 +50.6572818756104 0.5613729303702 +50.6818066649139 0.570098934469219 +50.7063314542174 0.69226299185548 +50.730856243521 0.727167008251555 +50.7553810328245 0.718441004152536 +50.7799058221281 0.796975041043704 +50.8044306114316 0.788249036944685 +50.8289554007351 0.852239733670822 +50.8534801900387 1.17510188533451 +50.8780049793422 1.05875516401426 +50.9025297686458 1.30308327878679 +50.9270545579493 1.55904606569133 +50.9515793472528 1.79173950833183 +50.9761041365564 1.88190821735502 +51.0006289258599 1.94299024604815 +51.0251537151635 2.01570694687331 +51.049678504467 1.81791752062889 +51.0742032937706 1.75683549193576 +51.0987280830741 1.50087270503121 +51.1232528723776 1.41652133207403 +51.1477776616812 1.29726594272077 +51.1723024509847 1.20127989763157 +51.1968272402883 1.23327524599464 +51.2213520295918 1.10529385254236 +51.2458768188953 1.27108793042372 +51.2704016081989 1.27399659845672 +51.2949263975024 1.31762661895182 +51.319451186806 1.401977991909 +51.3439759761095 1.23618391402764 +51.368500765413 1.32344395501783 +51.3930255547166 1.27108793042372 +51.4175503440201 1.23618391402764 +51.4420751333237 1.30017461075378 +51.4665999226272 1.15474120910347 +51.4911247119308 1.2158232377966 +51.5156495012343 1.24781858615967 +51.5401742905378 1.33216995911685 +51.5646990798414 1.48923803289918 +51.5892238691449 1.50087270503121 +51.6137486584485 1.55613739765833 +51.638273447752 1.72484014357269 +51.6627982370555 1.68121012307759 +51.6873230263591 1.61721942635146 +51.7118478156626 1.36707397551292 +51.7363726049662 1.25654459025869 +51.7608973942697 1.24490991812666 +51.7854221835732 1.09656784844334 +51.8099469728768 1.00639913942015 +51.8344717621803 1.0296684836842 +51.8589965514839 0.872600409901865 +51.8835213407874 1.03839448778322 +51.908046130091 1.04712049188224 +51.9325709193945 1.11692852467439 +51.957095708698 1.15764987713647 +51.9816204980016 1.01221647548616 +52.0061452873051 1.01512514351917 +52.0306700766087 0.922047766462971 +52.0551948659122 0.895869754165915 +52.0797196552157 0.747527684482598 +52.1042444445193 0.767888360713642 +52.1287692338228 0.671902315624436 +52.1532940231264 0.706806332020511 +52.1778188124299 0.555555594304188 +52.2023436017334 0.488656229545045 +52.226868391037 0.447934877082958 +52.2513931803405 0.532286250040138 +52.2759179696441 0.471204221347007 +52.3004427589476 0.456660881181976 +52.3249675482512 0.462478217247989 +52.3494923375547 0.488656229545045 +52.3740171268582 0.439208872983939 +52.3985419161618 0.383944180356821 +52.4230667054653 0.459569549214982 +52.4475914947689 0.506108237743082 +52.4721162840724 0.401396188554858 +52.4966410733759 0.363583504125777 +52.5211658626795 0.509016905776088 +52.545690651983 0.488656229545045 +52.5702154412866 0.421756864785902 +52.5947402305901 0.468295553314001 +52.6192650198936 0.45375221314897 +52.6437898091972 0.378126844290808 +52.6683145985007 0.494473565611057 +52.6928393878043 0.395578852488846 +52.7173641771078 0.45375221314897 +52.7418889664114 0.413030860686883 +52.7664137557149 0.36940084019179 +52.7909385450184 0.398487520521852 +52.815463334322 0.418848196752895 +52.8399881236255 0.375218176257802 +52.8645129129291 0.413030860686883 +52.8890377022326 0.325770819696696 +52.9135624915361 0.410122192653877 +52.9380872808397 0.433391536917926 +52.9626120701432 0.363583504125777 +52.9871368594468 0.383944180356821 +53.0116616487503 0.357766168059765 +53.0361864380538 0.401396188554858 +53.0607112273574 0.343222827894734 +53.0852360166609 0.413030860686883 +53.1097608059645 0.375218176257802 +53.134285595268 0.407213524620871 +53.1588103845716 0.418848196752895 +53.1833351738751 0.386852848389827 +53.2078599631786 0.413030860686883 +53.2323847524822 0.395578852488846 +53.2569095417857 0.328679487729703 +53.2814343310893 0.287958135267616 +53.3059591203928 0.418848196752895 +53.3304839096963 0.372309508224796 +53.3550086989999 0.447934877082958 +53.3795334883034 0.447934877082958 +53.404058277607 0.410122192653877 +53.4285830669105 0.421756864785902 +53.453107856214 0.447934877082958 +53.4776326455176 0.418848196752895 +53.5021574348211 0.401396188554858 +53.5266822241247 0.372309508224796 +53.5512070134282 0.436300204950933 +53.5757318027318 0.424665532818908 +53.6002565920353 0.491564897578051 +53.6247813813388 0.392670184455839 +53.6493061706424 0.459569549214982 +53.6738309599459 0.407213524620871 +53.6983557492495 0.43048286888492 +53.722880538553 0.474112889380014 +53.7474053278565 0.439208872983939 +53.7719301171601 0.471204221347007 +53.7964549064636 0.450843545115964 +53.8209796957672 0.421756864785902 +53.8455044850707 0.500290901677069 +53.8700292743742 0.47702155741302 +53.8945540636778 0.474112889380014 +53.9190788529813 0.482838893479032 +53.9436036422849 0.602094282832287 +53.9681284315884 0.564281598403206 +53.992653220892 0.625363627096337 +54.0171780101955 0.625363627096337 +54.041702799499 0.730075676284561 +54.0662275888026 0.788249036944685 +54.0907523781061 0.939499774661008 +54.1152771674097 0.948225778760027 +54.1398019567132 0.90750442629794 +54.1643267460167 0.913321762363952 +54.1888515353203 0.817335717274747 +54.2133763246238 0.642815635294374 +54.2379011139274 0.738801680383579 +54.2624259032309 0.613728954964312 +54.2869506925344 0.721349672185542 +54.311475481838 0.602094282832287 +54.3360002711415 0.596276946766275 +54.3605250604451 0.619546291030324 +54.3850498497486 0.532286250040138 +54.4095746390522 0.671902315624436 +54.4340994283557 0.64572430332738 +54.4586242176592 0.689354323822474 +54.4831490069628 0.712623668086523 +54.5076737962663 0.654450307426399 +54.5321985855699 0.58464227463425 +54.5567233748734 0.634089631195356 +54.5812481641769 0.543920922172163 +54.6057729534805 0.535194918073144 +54.630297742784 0.573007602502225 +54.6548225320876 0.494473565611057 +54.6793473213911 0.485747561512038 +54.7038721106946 0.447934877082958 +54.7283968999982 0.514834241842101 +54.7529216893017 0.410122192653877 +54.7774464786053 0.436300204950933 +54.8019712679088 0.474112889380014 +54.8264960572124 0.479930225446026 +54.8510208465159 0.468295553314001 +54.8755456358194 0.494473565611057 +54.900070425123 0.401396188554858 +54.9245952144265 0.439208872983939 +54.9491200037301 0.456660881181976 +54.9736447930336 0.520651577908113 +54.9981695823371 0.488656229545045 +55.0226943716407 0.471204221347007 +55.0472191609442 0.506108237743082 +55.0717439502478 0.511925573809094 +55.0962687395513 0.491564897578051 +55.1207935288548 0.45375221314897 +55.1453183181584 0.526468913974126 +55.1698431074619 0.468295553314001 +55.1943678967655 0.543920922172163 +55.218892686069 0.47702155741302 +55.2434174753726 0.523560245941119 +55.2679422646761 0.535194918073144 +55.2924670539796 0.424665532818908 +55.3169918432832 0.47702155741302 +55.3415166325867 0.53810358610615 +55.3660414218903 0.549738258238175 +55.3905662111938 0.520651577908113 +55.4150910004973 0.506108237743082 +55.4396157898009 0.58464227463425 +55.4641405791044 0.567190266436213 +55.488665368408 0.587550942667256 +55.5131901577115 0.660267643492411 +55.537714947015 0.69226299185548 +55.5622397363186 0.69226299185548 +55.5867645256221 0.666084979558424 +55.6112893149257 0.709715000053517 +55.6358141042292 0.689354323822474 +55.6603388935328 0.866783073835853 +55.6848636828363 0.881326414000884 +55.7093884721398 1.03257715171721 +55.7339132614434 1.09365918041034 +55.7584380507469 1.26817926239071 +55.7829628400505 1.46015135256912 +55.807487629354 1.62012809438446 +55.8320124186575 1.90808622965208 +55.8565372079611 2.17859235672166 +55.8810619972646 2.38219911903209 +55.9055867865682 2.57998854527652 +55.9301115758717 2.7574172952899 +55.9546363651752 2.65561391413468 +55.9791611544788 2.35020377066902 +56.0036859437823 2.1495056763916 +56.0282107330859 1.98080293047723 +56.0527355223894 1.80337418046386 +56.077260311693 1.70738813537465 +56.1017851009965 1.41652133207403 +56.1263098903 1.41943000010703 +56.1508346796036 1.46887735666814 +56.1753594689071 1.50669004109722 +56.1998842582107 1.51832471322925 +56.2244090475142 1.61721942635146 +56.2489338368177 1.78592217226582 +56.2734586261213 1.68121012307759 +56.2979834154248 1.70447946734164 +56.3225082047284 1.68121012307759 +56.3470329940319 1.75101815586974 +56.3715577833354 1.73647481570471 +56.396082572639 1.62012809438446 +56.4206073619425 1.4514253484701 +56.4451321512461 1.47469469273415 +56.4696569405496 1.62012809438446 +56.4941817298532 1.48923803289918 +56.5187065191567 1.46015135256912 +56.5432313084602 1.51250737716323 +56.5677560977638 1.25654459025869 +56.5922808870673 1.27981393452274 +56.6168056763709 1.25363592222568 +56.6413304656744 1.05002915991524 +56.6658552549779 1.03548581975021 +56.6903800442815 0.974403791057083 +56.714904833585 0.88423508203389 +56.7394296228886 0.785340368911679 +56.7639544121921 0.831879057439778 +56.7884792014956 0.933682438594996 +56.8130039907992 0.945317110727021 +56.8375287801027 0.866783073835853 +56.8620535694063 0.95404311482604 +56.8865783587098 1.0267598156512 +56.9111031480134 1.01221647548616 +56.9356279373169 0.866783073835853 +56.9601527266204 0.820244385307754 +56.984677515924 0.738801680383579 +57.0092023052275 0.718441004152536 +57.0337270945311 0.648632971360387 +57.0582518838346 0.596276946766275 +57.0827766731381 0.575916270535231 +57.1073014624417 0.564281598403206 +57.1318262517452 0.6079116188983 +57.1563510410488 0.517742909875107 +57.1808758303523 0.5613729303702 +57.2054006196558 0.546829590205169 +57.2299254089594 0.439208872983939 +57.2544501982629 0.532286250040138 +57.2789749875665 0.482838893479032 +57.30349977687 0.479930225446026 +57.3280245661736 0.497382233644063 +57.3525493554771 0.535194918073144 +57.3770741447806 0.546829590205169 +57.4015989340842 0.459569549214982 +57.4261237233877 0.558464262337194 +57.4506485126913 0.500290901677069 +57.4751733019948 0.488656229545045 +57.4996980912983 0.491564897578051 +57.5242228806019 0.447934877082958 +57.5487476699054 0.433391536917926 +57.573272459209 0.488656229545045 +57.5977972485125 0.497382233644063 +57.622322037816 0.494473565611057 +57.6468468271196 0.491564897578051 +57.6713716164231 0.462478217247989 +57.6958964057267 0.526468913974126 +57.7204211950302 0.529377582007132 +57.7449459843338 0.552646926271182 +57.7694707736373 0.479930225446026 +57.7939955629408 0.506108237743082 +57.8185203522444 0.45375221314897 +57.8430451415479 0.485747561512038 +57.8675699308515 0.570098934469219 +57.892094720155 0.520651577908113 +57.9166195094585 0.503199569710076 +57.9411442987621 0.488656229545045 +57.9656690880656 0.462478217247989 +57.9901938773692 0.506108237743082 +58.0147186666727 0.485747561512038 +58.0392434559762 0.543920922172163 +58.0637682452798 0.526468913974126 +58.0882930345833 0.517742909875107 +58.1128178238869 0.468295553314001 +58.1373426131904 0.488656229545045 +58.161867402494 0.511925573809094 +58.1863921917975 0.500290901677069 +58.210916981101 0.509016905776088 +58.2354417704046 0.648632971360387 +58.2599665597081 0.602094282832287 +58.2844913490117 0.558464262337194 +58.3090161383152 0.567190266436213 +58.3335409276187 0.6079116188983 +58.3580657169223 0.590459610700262 +58.3825905062258 0.555555594304188 +58.4071152955294 0.636998299228362 +58.4316400848329 0.613728954964312 +58.4561648741364 0.634089631195356 +58.48068966344 0.700988995954499 +58.5052144527435 0.721349672185542 +58.5297392420471 0.753345020548611 +58.5542640313506 0.718441004152536 +58.5787888206542 0.840605061538797 +58.6033136099577 0.878417745967878 +58.6278383992612 1.0267598156512 +58.6523631885648 1.09365918041034 +58.6768879778683 1.29144860665476 +58.7014127671719 1.32926129108384 +58.7259375564754 1.4979640369982 +58.7504623457789 1.93717290998214 +58.7749871350825 2.12332766409454 +58.799511924386 2.27457840181086 +58.8240367136896 2.67888325839873 +58.8485615029931 2.58871454937553 +58.8730862922966 2.70506127069578 +58.8976110816002 2.47236782805529 +58.9221358709037 2.5247238526494 +58.9466606602073 2.1524143444246 +58.9711854495108 1.88772555342104 +58.9957102388144 1.67248411897858 +59.0202350281179 1.48923803289918 +59.0447598174214 1.36125663944691 +59.069284606725 1.55322872962532 +59.0938093960285 1.43106467223906 +59.1183341853321 1.3554393033809 +59.1428589746356 1.43979067633808 +59.1673837639391 1.60849342225244 +59.1919085532427 1.64921477471453 +59.2164333425462 1.73647481570471 +59.2409581318498 1.62012809438446 +59.2654829211533 1.51541604519624 +59.2900077104568 1.49214670093219 +59.3145324997604 1.41361266404102 +59.3390572890639 1.404886659942 +59.3635820783675 1.21000590173059 +59.388106867671 1.0762071722123 +59.4126316569746 1.00639913942015 +59.4371564462781 0.904595758264934 +59.4616812355816 0.922047766462971 +59.4862060248852 0.831879057439778 +59.5107308141887 0.706806332020511 +59.5352556034923 0.677719651690449 +59.5597803927958 0.794066373010698 +59.5843051820993 0.642815635294374 +59.6088299714029 0.590459610700262 +59.6333547607064 0.639906967261368 +59.65787955001 0.636998299228362 +59.6824043393135 0.663176311525418 +59.706929128617 0.613728954964312 +59.7314539179206 0.5613729303702 +59.7559787072241 0.58464227463425 +59.7805034965277 0.53810358610615 +59.8050282858312 0.564281598403206 +59.8295530751348 0.628272295129343 +59.8540778644383 0.622454959063331 +59.8786026537418 0.570098934469219 +59.9031274430454 0.575916270535231 +59.9276522323489 0.564281598403206 +59.9521770216525 0.564281598403206 +59.976701810956 0.610820286931306 +60.0012266002595 0.506108237743082 +60.0257513895631 0.634089631195356 +60.0502761788666 0.570098934469219 +60.0748009681702 0.66899364759143 +60.0993257574737 0.6079116188983 +60.1238505467772 0.619546291030324 +60.1483753360808 0.689354323822474 +60.1729001253843 0.753345020548611 +60.1974249146879 0.735893012350573 +60.2219497039914 0.703897663987505 +60.246474493295 0.730075676284561 +60.2709992825985 0.811518381208735 +60.295524071902 0.866783073835853 +60.3200488612056 1.01221647548616 +60.3445736505091 1.06457250008028 +60.3690984398127 1.27690526648973 +60.3936232291162 1.45724268453612 +60.4181480184197 1.56195473372434 +60.4426728077233 1.73647481570471 +60.4671975970268 2.06806297146742 +60.4917223863304 2.1495056763916 +60.5162471756339 2.51308918051737 +60.5407719649374 2.62943590183762 +60.565296754241 2.87958135267616 +60.5898215435445 2.81849932398303 +60.6143463328481 2.71669594282781 +60.6388711221516 2.46073315592326 +60.6633959114552 2.25421772557982 +60.6879207007587 1.9662595903122 +60.7124454900622 1.74520081980373 +60.7369702793658 1.5881327460214 +60.7614950686693 1.41652133207403 +60.7860198579729 1.404886659942 +60.8105446472764 1.49214670093219 +60.8350694365799 1.50378137306421 +60.8595942258835 1.44560801240409 +60.884119015187 1.48342069683317 +60.9086438044906 1.68993612717661 +60.9331685937941 1.66957545094557 +60.9576933830976 1.81210018456287 +60.9822181724012 1.70157079930864 +61.0067429617047 1.60558475421943 +61.0312677510083 1.46596868863513 +61.0557925403118 1.42233866814004 +61.0803173296154 1.28853993862175 +61.1048421189189 1.21291456976359 +61.1293669082224 0.93077377056199 +61.153891697526 0.988947131222114 +61.1784164868295 0.837696393505791 +61.2029412761331 0.788249036944685 +61.2274660654366 0.689354323822474 +61.2519908547401 0.703897663987505 +61.2765156440437 0.724258340218548 +61.3010404333472 0.634089631195356 +61.3255652226508 0.514834241842101 +61.3500900119543 0.5613729303702 +61.3746148012578 0.541012254139157 +61.3991395905614 0.677719651690449 +61.4236643798649 0.532286250040138 +61.4481891691685 0.555555594304188 +61.472713958472 0.570098934469219 +61.4972387477756 0.459569549214982 +61.5217635370791 0.546829590205169 +61.5462883263826 0.471204221347007 +61.5708131156862 0.462478217247989 +61.5953379049897 0.485747561512038 +61.6198626942933 0.459569549214982 +61.6443874835968 0.45375221314897 +61.6689122729003 0.392670184455839 +61.6934370622039 0.517742909875107 +61.7179618515074 0.511925573809094 +61.742486640811 0.456660881181976 +61.7670114301145 0.442117541016945 +61.791536219418 0.424665532818908 +61.8160610087216 0.378126844290808 +61.8405857980251 0.488656229545045 +61.8651105873287 0.357766168059765 +61.8896353766322 0.468295553314001 +61.9141601659358 0.392670184455839 +61.9386849552393 0.488656229545045 +61.9632097445428 0.386852848389827 +61.9877345338464 0.474112889380014 +62.0122593231499 0.407213524620871 +62.0367841124535 0.500290901677069 +62.061308901757 0.479930225446026 +62.0858336910605 0.366492172158783 +62.1103584803641 0.436300204950933 +62.1348832696676 0.445026209049951 +62.1594080589712 0.45375221314897 +62.1839328482747 0.372309508224796 +62.2084576375782 0.378126844290808 +62.2329824268818 0.398487520521852 +62.2575072161853 0.372309508224796 +62.2820320054889 0.424665532818908 +62.3065567947924 0.383944180356821 +62.331081584096 0.421756864785902 +62.3556063733995 0.456660881181976 +62.380131162703 0.43048286888492 +62.4046559520066 0.363583504125777 +62.4291807413101 0.395578852488846 +62.4537055306137 0.36940084019179 +62.4782303199172 0.383944180356821 +62.5027551092207 0.404304856587864 +62.5272798985243 0.372309508224796 +62.5518046878278 0.407213524620871 +62.5763294771314 0.319953483630684 +62.6008542664349 0.413030860686883 +62.6253790557384 0.395578852488846 +62.649903845042 0.418848196752895 +62.6744286343455 0.383944180356821 +62.6989534236491 0.311227479531665 +62.7234782129526 0.410122192653877 +62.7480030022562 0.386852848389827 +62.7725277915597 0.337405491828721 +62.7970525808632 0.415939528719889 +62.8215773701668 0.410122192653877 +62.8461021594703 0.389761516422833 +62.8706269487739 0.407213524620871 +62.8951517380774 0.34613149592774 +62.9196765273809 0.407213524620871 +62.9442013166845 0.45375221314897 +62.968726105988 0.375218176257802 +62.9932508952916 0.404304856587864 +63.0177756845951 0.378126844290808 +63.0423004738986 0.407213524620871 +63.0668252632022 0.392670184455839 +63.0913500525057 0.482838893479032 +63.1158748418093 0.398487520521852 +63.1403996311128 0.474112889380014 +63.1649244204164 0.47702155741302 +63.1894492097199 0.479930225446026 +63.2139739990234 0.447934877082958 +63.238498788327 0.500290901677069 +63.2630235776305 0.558464262337194 +63.2875483669341 0.541012254139157 +63.3120731562376 0.639906967261368 +63.3365979455411 0.634089631195356 +63.3611227348447 0.581733606601244 +63.3856475241482 0.613728954964312 +63.4101723134518 0.596276946766275 +63.4346971027553 0.578824938568237 +63.4592218920588 0.5613729303702 +63.4837466813624 0.593368278733268 +63.5082714706659 0.488656229545045 +63.5327962599695 0.541012254139157 +63.557321049273 0.497382233644063 +63.5818458385766 0.439208872983939 +63.6063706278801 0.494473565611057 +63.6308954171836 0.462478217247989 +63.6554202064872 0.517742909875107 +63.6799449957907 0.462478217247989 +63.7044697850943 0.517742909875107 +63.7289945743978 0.555555594304188 +63.7535193637013 0.541012254139157 +63.7780441530049 0.558464262337194 +63.8025689423084 0.570098934469219 +63.827093731612 0.503199569710076 +63.8516185209155 0.450843545115964 +63.876143310219 0.541012254139157 +63.9006680995226 0.474112889380014 +63.9251928888261 0.465386885280995 +63.9497176781297 0.395578852488846 +63.9742424674332 0.47702155741302 +63.9987672567368 0.47702155741302 +64.0232920460403 0.424665532818908 +64.0478168353438 0.474112889380014 +64.0723416246474 0.482838893479032 +64.0968664139509 0.43048286888492 +64.1213912032545 0.436300204950933 +64.145915992558 0.442117541016945 +64.1704407818615 0.433391536917926 +64.1949655711651 0.433391536917926 +64.2194903604686 0.418848196752895 +64.2440151497722 0.410122192653877 +64.2685399390757 0.459569549214982 +64.2930647283793 0.43048286888492 +64.3175895176828 0.439208872983939 +64.3421143069863 0.450843545115964 +64.3666390962899 0.43048286888492 +64.3911638855934 0.445026209049951 +64.415688674897 0.482838893479032 +64.4402134642005 0.450843545115964 +64.464738253504 0.445026209049951 +64.4892630428076 0.491564897578051 +64.5137878321111 0.520651577908113 +64.5383126214147 0.517742909875107 +64.5628374107182 0.43048286888492 +64.5873622000217 0.415939528719889 +64.6118869893253 0.5613729303702 +64.6364117786288 0.45375221314897 +64.6609365679324 0.503199569710076 +64.6854613572359 0.445026209049951 +64.7099861465395 0.555555594304188 +64.734510935843 0.523560245941119 +64.7590357251465 0.610820286931306 +64.7835605144501 0.570098934469219 +64.8080853037536 0.660267643492411 +64.8326100930572 0.64572430332738 +64.8571348823607 0.631180963162349 +64.8816596716642 0.744619016449592 +64.9061844609678 0.869691741868859 +64.9307092502713 0.919139098429965 +64.9552340395749 0.942408442694015 +64.9797588288784 1.1227458607404 +65.0042836181819 1.0267598156512 +65.0288084074855 1.03548581975021 +65.053333196789 1.0267598156512 +65.0778579860926 0.956951782859046 +65.1023827753961 0.956951782859046 +65.1269075646997 0.922047766462971 +65.1514323540032 0.852239733670822 +65.1759571433067 0.927865102528984 +65.2004819326103 0.773705696779654 +65.2250067219138 0.782431700878673 +65.2495315112174 0.855148401703828 +65.2740563005209 0.741710348416586 +65.2985810898244 0.936591106628002 +65.323105879128 0.951134446793033 +65.3476306684315 0.933682438594996 +65.3721554577351 1.03257715171721 +65.3966802470386 1.08202450827831 +65.4212050363421 1.23327524599464 +65.4457298256457 1.29726594272077 +65.4702546149492 1.39906932387599 +65.4947794042528 1.44560801240409 +65.5193041935563 1.60267608618643 +65.5438289828599 1.60558475421943 +65.5683537721634 1.46887735666814 +65.5928785614669 1.401977991909 +65.6174033507705 1.32344395501783 +65.641928140074 1.25654459025869 +65.6664529293776 1.14601520500445 +65.6909777186811 0.962769118925058 +65.7155025079846 0.980221127123096 +65.7400272972882 0.878417745967878 +65.7645520865917 0.924956434495977 +65.7890768758953 0.782431700878673 +65.8136016651988 0.855148401703828 +65.8381264545023 0.747527684482598 +65.8626512438059 0.785340368911679 +65.8871760331094 0.753345020548611 +65.911700822413 0.933682438594996 +65.9362256117165 0.939499774661008 +65.9607504010201 0.90750442629794 +65.9852751903236 0.910413094330946 +66.0097999796271 0.898778422198921 +66.0343247689307 0.922047766462971 +66.0588495582342 0.88423508203389 +66.0833743475378 0.869691741868859 +66.1078991368413 0.762071024647629 +66.1324239261448 0.695171659888486 +66.1569487154484 0.689354323822474 +66.1814735047519 0.602094282832287 +66.2059982940555 0.616637622997318 +66.230523083359 0.549738258238175 +66.2550478726625 0.462478217247989 +66.2795726619661 0.555555594304188 +66.3040974512696 0.491564897578051 +66.3286222405732 0.532286250040138 +66.3531470298767 0.523560245941119 +66.3776718191803 0.506108237743082 +66.4021966084838 0.546829590205169 +66.4267213977873 0.43048286888492 +66.4512461870909 0.450843545115964 +66.4757709763944 0.482838893479032 +66.500295765698 0.456660881181976 +66.5248205550015 0.45375221314897 +66.549345344305 0.442117541016945 +66.5738701336086 0.488656229545045 +66.5983949229121 0.442117541016945 +66.6229197122157 0.497382233644063 +66.6474445015192 0.404304856587864 +66.6719692908227 0.401396188554858 +66.6964940801263 0.436300204950933 +66.7210188694298 0.514834241842101 +66.7455436587334 0.465386885280995 +66.7700684480369 0.427574200851914 +66.7945932373405 0.479930225446026 +66.819118026644 0.462478217247989 +66.8436428159475 0.415939528719889 +66.8681676052511 0.450843545115964 +66.8926923945546 0.482838893479032 +66.9172171838582 0.442117541016945 +66.9417419731617 0.447934877082958 +66.9662667624652 0.488656229545045 +66.9907915517688 0.415939528719889 +67.0153163410723 0.427574200851914 +67.0398411303759 0.456660881181976 +67.0643659196794 0.45375221314897 +67.0888907089829 0.485747561512038 +67.1134154982865 0.433391536917926 +67.13794028759 0.517742909875107 +67.1624650768936 0.494473565611057 +67.1869898661971 0.523560245941119 +67.2115146555007 0.511925573809094 +67.2360394448042 0.506108237743082 +67.2605642341077 0.479930225446026 +67.2850890234113 0.509016905776088 +67.3096138127148 0.564281598403206 +67.3341386020184 0.485747561512038 +67.3586633913219 0.564281598403206 +67.3831881806254 0.602094282832287 +67.407712969929 0.532286250040138 +67.4322377592325 0.654450307426399 +67.4567625485361 0.700988995954499 +67.4812873378396 0.686445655789467 +67.5058121271431 0.846422397604809 +67.5303369164467 0.791157704977691 +67.5548617057502 0.890052418099903 +67.5793864950538 1.05584649598126 +67.6039112843573 1.06166383204727 +67.6284360736609 1.16055854516948 +67.6529608629644 1.4514253484701 +67.6774856522679 1.34380463124887 +67.7020104415715 1.48342069683317 +67.726535230875 1.51832471322925 +67.7510600201786 1.43979067633808 +67.7755848094821 1.2652705943577 +67.8001095987856 1.18382788943353 +67.8246343880892 1.08493317631132 +67.8491591773927 0.956951782859046 +67.8736839666963 0.956951782859046 +67.8982087559998 0.942408442694015 +67.9227335453033 0.756253688581617 +67.9472583346069 0.878417745967878 +67.9717831239104 0.834787725472785 +67.996307913214 0.802792377109716 +68.0208327025175 0.840605061538797 +68.0453574918211 0.820244385307754 +68.0698822811246 0.875509077934872 +68.0944070704281 0.881326414000884 +68.1189318597317 1.01803381155218 +68.1434566490352 0.988947131222114 +68.1679814383388 1.01512514351917 +68.1925062276423 0.913321762363952 +68.2170310169458 0.890052418099903 +68.2415558062494 0.898778422198921 +68.2660805955529 0.805701045142722 +68.2906053848565 0.802792377109716 +68.31513017416 0.796975041043704 +68.3396549634635 0.721349672185542 +68.3641797527671 0.695171659888486 +68.3887045420706 0.663176311525418 +68.4132293313742 0.581733606601244 +68.4377541206777 0.578824938568237 +68.4622789099813 0.520651577908113 +68.4868036992848 0.570098934469219 +68.5113284885883 0.593368278733268 +68.5358532778919 0.526468913974126 +68.5603780671954 0.456660881181976 +68.584902856499 0.578824938568237 +68.6094276458025 0.541012254139157 +68.633952435106 0.570098934469219 +68.6584772244096 0.581733606601244 +68.6830020137131 0.567190266436213 +68.7075268030167 0.5613729303702 +68.7320515923202 0.546829590205169 +68.7565763816237 0.509016905776088 +68.7811011709273 0.482838893479032 +68.8056259602308 0.526468913974126 +68.8301507495344 0.541012254139157 +68.8546755388379 0.520651577908113 +68.8792003281415 0.567190266436213 +68.903725117445 0.610820286931306 +68.9282499067485 0.674810983657443 +68.9527746960521 0.639906967261368 +68.9772994853556 0.680628319723455 +69.0018242746592 0.703897663987505 +69.0263490639627 0.706806332020511 +69.0508738532662 0.718441004152536 +69.0753986425698 0.817335717274747 +69.0999234318733 0.808609713175729 +69.1244482211769 0.997673135321133 +69.1489730104804 1.05293782794825 +69.1734977997839 1.12856319680641 +69.1980225890875 1.15474120910347 +69.222547378391 1.32344395501783 +69.2470721676946 1.39034331977697 +69.2715969569981 1.68702745914361 +69.2961217463017 1.56486340175735 +69.3206465356052 1.62594543045048 +69.3451713249087 1.52414204929526 +69.3696961142123 1.36998264354593 +69.3942209035158 1.41652133207403 +69.4187456928194 1.2158232377966 +69.4432704821229 1.20709723369758 +69.4677952714264 1.14892387303746 +69.49232006073 1.05293782794825 +69.5168448500335 0.927865102528984 +69.5413696393371 1.0267598156512 +69.5658944286406 0.922047766462971 +69.5904192179441 0.939499774661008 +69.6149440072477 0.968586454991071 +69.6394687965512 1.04130315581623 +69.6639935858548 1.18382788943353 +69.6885183751583 1.13147186483942 +69.7130431644619 1.19546256156556 +69.7375679537654 1.28853993862175 +69.7620927430689 1.36707397551292 +69.7866175323725 1.38452598371096 +69.811142321676 1.41361266404102 +69.8356671109796 1.26817926239071 +69.8601919002831 1.2158232377966 +69.8847166895866 1.18091922140052 +69.9092414788902 1.01221647548616 +69.9337662681937 0.924956434495977 +69.9582910574973 0.927865102528984 +69.9828158468008 0.750436352515604 +70.0073406361043 0.738801680383579 +70.0318654254079 0.639906967261368 +70.0563902147114 0.721349672185542 +70.080915004015 0.735893012350573 +70.1054397933185 0.636998299228362 +70.1299645826221 0.64572430332738 +70.1544893719256 0.622454959063331 +70.1790141612291 0.616637622997318 +70.2035389505327 0.648632971360387 +70.2280637398362 0.605002950865293 +70.2525885291398 0.767888360713642 +70.2771133184433 0.686445655789467 +70.3016381077468 0.724258340218548 +70.3261628970504 0.622454959063331 +70.3506876863539 0.703897663987505 +70.3752124756575 0.616637622997318 +70.399737264961 0.599185614799281 +70.4242620542645 0.69226299185548 +70.4487868435681 0.587550942667256 +70.4733116328716 0.541012254139157 +70.4978364221752 0.587550942667256 +70.5223612114787 0.570098934469219 +70.5468860007823 0.509016905776088 +70.5714107900858 0.58464227463425 +70.5959355793893 0.459569549214982 +70.6204603686929 0.450843545115964 +70.6449851579964 0.445026209049951 +70.6695099473 0.517742909875107 +70.6940347366035 0.459569549214982 +70.718559525907 0.381035512323815 +70.7430843152106 0.503199569710076 +70.7676091045141 0.494473565611057 +70.7921338938177 0.497382233644063 +70.8166586831212 0.494473565611057 +70.8411834724247 0.45375221314897 +70.8657082617283 0.488656229545045 +70.8902330510318 0.474112889380014 +70.9147578403354 0.442117541016945 +70.9392826296389 0.491564897578051 +70.9638074189425 0.456660881181976 +70.988332208246 0.442117541016945 +71.0128569975495 0.485747561512038 +71.0373817868531 0.471204221347007 +71.0619065761566 0.517742909875107 +71.0864313654602 0.418848196752895 +71.1109561547637 0.465386885280995 +71.1354809440672 0.471204221347007 +71.1600057333708 0.523560245941119 +71.1845305226743 0.506108237743082 +71.2090553119779 0.529377582007132 +71.2335801012814 0.471204221347007 +71.2581048905849 0.433391536917926 +71.2826296798885 0.468295553314001 +71.307154469192 0.471204221347007 +71.3316792584956 0.468295553314001 +71.3562040477991 0.488656229545045 +71.3807288371027 0.552646926271182 +71.4052536264062 0.459569549214982 +71.4297784157097 0.456660881181976 +71.4543032050133 0.511925573809094 +71.4788279943168 0.45375221314897 +71.5033527836204 0.485747561512038 +71.5278775729239 0.517742909875107 +71.5524023622274 0.541012254139157 +71.576927151531 0.5613729303702 +71.6014519408345 0.450843545115964 +71.6259767301381 0.503199569710076 +71.6505015194416 0.581733606601244 +71.6750263087451 0.535194918073144 +71.6995510980487 0.628272295129343 +71.7240758873522 0.564281598403206 +71.7486006766558 0.66899364759143 +71.7731254659593 0.764979692680635 +71.7976502552629 0.82315305334076 +71.8221750445664 0.828970389406772 +71.8466998338699 0.895869754165915 +71.8712246231735 0.968586454991071 +71.895749412477 0.99185579925512 +71.9202742017806 0.99185579925512 +71.9447989910841 1.06748116811328 +71.9693237803876 0.945317110727021 +71.9938485696912 0.919139098429965 +72.0183733589947 0.82315305334076 +72.0428981482983 0.808609713175729 +72.0674229376018 0.770797028746648 +72.0919477269053 0.677719651690449 +72.1164725162089 0.648632971360387 +72.1409973055124 0.636998299228362 +72.165522094816 0.613728954964312 +72.1900468841195 0.599185614799281 +72.2145716734231 0.619546291030324 +72.2390964627266 0.674810983657443 +72.2636212520301 0.654450307426399 +72.2881460413337 0.622454959063331 +72.3126708306372 0.654450307426399 +72.3371956199408 0.686445655789467 +72.3617204092443 0.79988370907671 +72.3862451985478 0.660267643492411 +72.4107699878514 0.791157704977691 +72.4352947771549 0.712623668086523 +72.4598195664585 0.770797028746648 +72.484344355762 0.613728954964312 +72.5088691450655 0.732984344317567 +72.5333939343691 0.642815635294374 +72.5579187236726 0.666084979558424 +72.5824435129762 0.602094282832287 +72.6069683022797 0.58464227463425 +72.6314930915833 0.570098934469219 +72.6560178808868 0.587550942667256 +72.6805426701903 0.625363627096337 +72.7050674594939 0.602094282832287 +72.7295922487974 0.529377582007132 +72.754117038101 0.58464227463425 +72.7786418274045 0.575916270535231 +72.803166616708 0.509016905776088 +72.8276914060116 0.552646926271182 +72.8522161953151 0.450843545115964 +72.8767409846187 0.552646926271182 +72.9012657739222 0.494473565611057 +72.9257905632257 0.517742909875107 +72.9503153525293 0.587550942667256 +72.9748401418328 0.622454959063331 +72.9993649311364 0.53810358610615 +73.0238897204399 0.58464227463425 +73.0484145097435 0.616637622997318 +73.072939299047 0.605002950865293 +73.0974640883505 0.709715000053517 +73.1219888776541 0.587550942667256 +73.1465136669576 0.712623668086523 +73.1710384562612 0.666084979558424 +73.1955632455647 0.756253688581617 +73.2200880348682 0.79988370907671 +73.2446128241718 0.90750442629794 +73.2691376134753 0.913321762363952 +73.2936624027789 1.05293782794825 +73.3181871920824 1.14601520500445 +73.3427119813859 1.22454924189562 +73.3672367706895 1.37289131157893 +73.391761559993 1.55904606569133 +73.4162863492966 1.55904606569133 +73.4408111386001 1.8266435247279 +73.4653359279037 1.57068073782336 +73.4898607172072 1.71320547144066 +73.5143855065107 1.81210018456287 +73.5389102958143 1.53577672142728 +73.5634350851178 1.49214670093219 +73.5879598744214 1.27399659845672 +73.6124846637249 1.43106467223906 +73.6370094530284 1.16637588123549 +73.661534242332 1.0296684836842 +73.6860590316355 1.05002915991524 +73.7105838209391 1.17219321730151 +73.7351086102426 1.06748116811328 +73.7596333995461 0.994764467288127 +73.7841581888497 1.07038983614629 +73.8086829781532 1.14601520500445 +73.8332077674568 1.22164057386261 +73.8577325567603 1.28853993862175 +73.8822573460639 1.36998264354593 +73.9067821353674 1.43688200830507 +73.9313069246709 1.71320547144066 +73.9558317139745 1.70157079930864 +73.980356503278 1.6841187911106 +74.0048812925816 1.61721942635146 +74.0294060818851 1.64048877061551 +74.0539308711886 1.37870864764495 +74.0784556604922 1.4979640369982 +74.1029804497957 1.15183254107046 +74.1275052390993 1.04712049188224 +74.1520300284028 1.03257715171721 +74.1765548177063 0.983129795156102 +74.2010796070099 0.892961086132909 +74.2256043963134 0.933682438594996 +74.250129185617 0.779523032845666 +74.2746539749205 0.764979692680635 +74.2991787642241 0.759162356614623 +74.3237035535276 0.767888360713642 +74.3482283428311 0.759162356614623 +74.3727531321347 0.826061721373766 +74.3972779214382 0.820244385307754 +74.4218027107418 0.741710348416586 +74.4463275000453 0.863874405802847 +74.4708522893488 0.895869754165915 +74.4953770786524 0.788249036944685 +74.5199018679559 0.820244385307754 +74.5444266572595 0.770797028746648 +74.568951446563 0.779523032845666 +74.5934762358665 0.712623668086523 +74.6180010251701 0.732984344317567 +74.6425258144736 0.636998299228362 +74.6670506037772 0.636998299228362 +74.6915753930807 0.53810358610615 +74.7161001823843 0.581733606601244 +74.7406249716878 0.488656229545045 +74.7651497609913 0.593368278733268 +74.7896745502949 0.570098934469219 +74.8141993395984 0.546829590205169 +74.838724128902 0.526468913974126 +74.8632489182055 0.491564897578051 +74.887773707509 0.541012254139157 +74.9122984968126 0.541012254139157 +74.9368232861161 0.497382233644063 +74.9613480754197 0.514834241842101 +74.9858728647232 0.45375221314897 +75.0103976540267 0.503392714112542 +75.0349224433303 0.521717706120348 +75.0594472326338 0.496433525672776 +75.0839720219374 0.485264700505873 +75.1084968112409 0.477390832176908 +75.1330216005445 0.471707930392888 +75.157546389848 0.47940566836043 +75.1820711791515 0.475838008959836 +75.2065959684551 0.521541575758926 +75.2311207577586 0.506981221552851 +75.2556455470622 0.469092812962252 +75.2801703363657 0.465349023200236 +75.3046951256692 0.486116836275927 +75.3292199149728 0.458092450159429 +75.3537447042763 0.501554008290442 +75.3782694935799 0.465890594326875 +75.4027942828834 0.489235662752443 +75.4273190721869 0.497969334848804 +75.4518438614905 0.445622656298394 +75.476368650794 0.486368690798867 +75.5008934400976 0.486717117523041 +75.5254182294011 0.488286954781156 +75.5499430187047 0.49055747948629 +75.5744678080082 0.499476627987775 +75.5989925973117 0.499048643103412 +75.6235173866153 0.522939116654291 +75.6480421759188 0.517159642669037 +75.6725669652224 0.513012550061045 +75.6970917545259 0.498158645252599 +75.7216165438294 0.562393528504334 +75.746141333133 0.600293439090946 +75.7706661224365 0.577683086194484 +75.7951909117401 0.596932104135191 +75.8197157010436 0.659256391401979 +75.8442404903471 0.662345023795079 +75.8687652796507 0.672443886751076 +75.8932900689542 0.705232660763724 +75.9178148582578 0.72739801473478 +75.9423396475613 0.77979190140965 +75.9668644368649 0.84362731581408 +75.9913892261684 0.867849202048088 +76.0159140154719 0.853419448125334 +76.0404388047755 0.918677085463515 +76.064963594079 0.872638271982624 +76.0894883833826 0.85561424866895 +76.1140131726861 0.813578747470359 +76.1385379619896 0.752030871812106 +76.1630627512932 0.680221182878808 +76.1875875405967 0.668821351228679 +76.2121123299003 0.621720243534225 +76.2366371192038 0.584680136715009 +76.2611619085073 0.642542932731719 +76.2856866978109 0.602425695515417 +76.3102114871144 0.626643747750753 +76.334736276418 0.609985165202296 +76.3592610657215 0.629166126978827 +76.3837858550251 0.638374992084011 +76.4083106443286 0.613200563880045 +76.4328354336321 0.67312756015705 +76.4573602229357 0.627281891078626 +76.4818850122392 0.647701277430144 +76.5064098015428 0.704090808389971 +76.5309345908463 0.67768178960969 +76.5554593801498 0.669069371752948 +76.5799841694534 0.666709942843924 +76.6045089587569 0.687256095480091 +76.6290337480605 0.666533812482502 +76.653558537364 0.650144098498028 +76.6780833266675 0.619919399792884 +76.7026081159711 0.601376581344225 +76.7271329052746 0.586736668977961 +76.7516576945782 0.558464262337194 +76.7761824838817 0.53141710022904 +76.8007072731853 0.502851142985902 +76.8252320624888 0.518750327966868 +76.8497568517923 0.574749736123092 +76.8742816410959 0.540684675454698 +76.8988064303994 0.519799442138059 +76.923331219703 0.512508841015164 +76.9478560090065 0.493852436324228 +76.97238079831 0.516907788146097 +76.9969055876136 0.512294848572983 +77.0214303769171 0.522628552010876 +77.0459551662207 0.497226951322356 +77.0704799555242 0.5265276240946 +77.0950047448277 0.510393598631738 +77.1195295341313 0.525281531522271 +77.1440543234348 0.564595997045292 +77.1685791127384 0.508068197804801 +77.1931039020419 0.538275882468901 +77.2176286913455 0.572327763094922 +77.242153480649 0.59592468604343 +77.2666782699525 0.6233279428731 +77.2912030592561 0.671142918056944 +77.3157278485596 0.679738321872642 +77.3402526378632 0.705073544443346 +77.3647774271667 0.767481223868994 +77.3893022164702 0.792589273955144 +77.4138270057738 0.878917621015087 +77.4383517950773 0.947038396308173 +77.4628765843809 1.04033359980522 +77.4874013736844 1.06376757243468 +77.5119261629879 1.14305166084964 +77.5364509522915 1.14779520070608 +77.560975741595 1.12436122807662 +77.5855005308986 1.12379114091292 +77.6100253202021 1.04792158552916 +77.6345501095057 0.952808524249725 +77.6590748988092 0.873884364554953 +77.6835996881127 0.800215121759839 +77.7081244774163 0.783325532728157 +77.7326492667198 0.765735256249456 +77.7571740560234 0.719490118323727 +77.7816988453269 0.689727432585033 +77.8062236346304 0.657258569259501 +77.830748423934 0.680493885441463 +77.8552732132375 0.667927519379195 +77.8797980025411 0.667810099138247 +77.9043227918446 0.673182436278853 +77.9288475811481 0.67814380257614 +77.9533723704517 0.698021617801018 +77.9778971597552 0.756211992502187 +78.0024219490588 0.753310992466523 +78.0269467383623 0.738038448817416 +78.0514715276659 0.821835070605584 +78.0759963169694 0.804072497764133 +78.1005211062729 0.78994012844242 +78.1250458955765 0.768950654927205 +78.14957068488 0.784043234216219 +78.1740954741836 0.754725547402931 +78.1986202634871 0.646017853929751 +78.2231450527906 0.650437649100399 +78.2476698420942 0.616113065911723 +78.2721946313977 0.62195125001745 +78.2967194207013 0.570161478588364 +78.3212442100048 0.587143805822609 +78.3457689993083 0.546926162406402 +78.3702937886119 0.563135912030782 +78.3948185779154 0.547299271168962 +78.419343367219 0.525226655400468 +78.4438681565225 0.493894132403659 +78.4683929458261 0.49594683066794 +78.4929177351296 0.468820110399597 +78.5174425244331 0.470486519858946 +78.5419673137367 0.462730071770929 +78.5664921030402 0.472484342001424 +78.5910168923438 0.487686673534043 +78.6155416816473 0.476924985211787 +78.6400664709508 0.446247619583894 +78.6645912602544 0.46916853712377 +78.6891160495579 0.46482446611464 +78.7136408388615 0.44318366922918 +78.738165628165 0.466318579211238 +78.7626904174685 0.452413382374079 +78.7872152067721 0.461684791598409 +78.8117399960756 0.435699923703819 +78.8362647853792 0.447448182078121 +78.8607895746827 0.468954544681589 +78.8853143639863 0.454683907079213 +78.9098391532898 0.46398934438563 +78.9343639425933 0.464534749510941 +78.9588887318969 0.459745679576405 +78.9834135212004 0.468467849676752 +79.007938310504 0.458734427485972 +79.0324630998075 0.478498656468574 +79.056987889111 0.432556415188916 +79.0815126784146 0.43917101090318 +79.1060374677181 0.456446888739795 +79.1305622570217 0.471456075869948 +79.1550870463252 0.425034807582796 +79.1796118356287 0.439788306191337 +79.2041366249323 0.465676601884694 +79.2286614142358 0.473533456172615 +79.2531862035394 0.439443713465835 +79.2777109928429 0.43670734179558 +79.3022357821465 0.438974032502043 +79.32676057145 0.436476335312355 +79.3512853607535 0.454680073080542 +79.3758101500571 0.465525153561658 +79.4003349393606 0.434923512095283 +79.4248597286642 0.45751301695203 +79.4493845179677 0.467947126589828 +79.4739093072712 0.444791368568055 +79.4984340965748 0.470158941174487 +79.5229588858783 0.470348251578282 +79.5474836751819 0.478477808428858 +79.5720084644854 0.506553236668488 +79.5965332537889 0.515103110406085 +79.6210580430925 0.499770178590145 +79.645582832396 0.508454486609734 +79.6701076216996 0.501453602090538 +79.6946324110031 0.486058126155453 +79.7191572003067 0.503120011549887 +79.7436819896102 0.538628143191746 +79.7682067789137 0.50564239077796 +79.7927315682173 0.546943176447446 +79.8172563575208 0.57516070696641 +79.8417811468244 0.601759036150486 +79.8663059361279 0.635059187206358 +79.8908307254314 0.647701277430144 +79.915355514735 0.694743675004123 +79.9398803040385 0.71574249456304 +79.9644050933421 0.745084863414713 +79.9889298826456 0.77799105766831 +80.0134546719491 0.797164351447499 +80.0379794612527 0.848420219747288 +80.0625042505562 0.811753221690631 +80.0870290398598 0.825226599644756 +80.1115538291633 0.801612662655204 +80.1360786184669 0.738956962705287 +80.1606034077704 0.76203316256687 +80.1851281970739 0.719628386604391 +80.2096529863775 0.654840430230003 +80.234177775681 0.639730836899946 +80.2587025649846 0.619084278063874 +80.2832273542881 0.597771059862872 +80.3077521435916 0.581905902963995 +80.3322769328952 0.629959552628407 +80.3568017221987 0.61380467912583 +80.3813265115023 0.576675668102723 +80.4058513008058 0.591232188310127 +80.4303760901093 0.625577619538518 +80.4549008794129 0.60925044967319 +80.4794256687164 0.606434519842746 +80.50395045802 0.65524756707465 +80.5284752473235 0.629703864106796 +80.5530000366271 0.67287570563411 +80.5775248259306 0.680137790719947 +80.6020496152341 0.680321589078712 +80.6265744045377 0.655650869920626 +80.6510991938412 0.681849730257397 +80.6756239831448 0.670198044084329 +80.7001487724483 0.64651772897696 +80.7246735617518 0.634811166682089 +80.7491983510554 0.614967379539298 +80.7737231403589 0.608049887178963 +80.7982479296625 0.580650464347965 +80.822772718966 0.565058010011742 +80.8472975082695 0.572789776061372 +80.8718222975731 0.576365103459309 +80.8963470868766 0.55276434651213 +80.9208718761802 0.571047642440506 +80.9453966654837 0.570237202749882 +80.9699214547873 0.558346842096246 +80.9944462440908 0.567362562798964 +81.0189710333943 0.594513965105693 +81.0434958226979 0.565192444293734 +81.0680206120014 0.576730544224526 +81.092545401305 0.613338832160709 +81.1170701906085 0.661778770630053 +81.141594979912 0.626316169066295 +81.1661197692156 0.626622899711038 +81.1906445585191 0.615084799780247 +81.2151693478227 0.662341189796408 +81.2396941371262 0.659998774928427 +81.2642189264297 0.702903425938117 +81.2887437157333 0.747040989477761 +81.3132685050368 0.760870462153402 +81.3377932943404 0.7962496715583 +81.3623180836439 0.861645577177143 +81.3868428729475 0.891287008676218 +81.411367662251 1.01659457457738 +81.4358924515545 1.11861194817478 +81.4604172408581 1.20061707226531 +81.4849420301616 1.24565781369814 +81.5094668194652 1.31435251015359 +81.5339916087687 1.29978448795018 +81.5585163980722 1.31999371585818 +81.5830411873758 1.354234906888 +81.6075659766793 1.29988872814875 +81.6320907659829 1.19482441823768 +81.6566155552864 1.18127148212337 +81.6811403445899 1.13374622354322 +81.7056651338935 0.964909043346871 +81.730189923197 0.965014961591806 +81.7547147125006 0.925929824505651 +81.7792395018041 0.921585753496521 +81.8037642911077 0.898409147435033 +81.8282890804112 0.8918362478002 +81.8528138697147 0.875685208296294 +81.8773386590183 0.875488229895157 +81.9018634483218 0.901120837066902 +81.9263882376254 0.925111716817685 +81.9509130269289 0.963193269810749 +81.9754378162324 0.999201276499818 +81.999962605536 1.03921259547119 +82.0244873948395 1.08511865271644 +82.0490121841431 1.17285220866909 +82.0735369734466 1.16387435004713 +82.0980617627501 1.17979821706649 +82.1225865520537 1.11075892788553 +82.1471113413572 1.1440590789414 +82.1716361306608 1.09307974720294 +82.1961609199643 1.03256013767616 +82.2206857092679 0.967776015300447 +82.2452104985714 0.902018502915057 +82.2697352878749 0.93265417246352 +82.2942600771785 0.896763586015399 +82.318784866482 0.800378072078889 +82.3433096557856 0.717568020342767 +82.3678344450891 0.715435763918297 +82.3923592343926 0.699653999178279 +82.4168840236962 0.665850139076528 +82.4414088129997 0.674462556933269 +82.4659336023033 0.690013315190061 +82.4904583916068 0.679990176395582 +82.5149831809103 0.669963203602432 +82.5395079702139 0.669224654074655 +82.5640327595174 0.672175018187092 +82.588557548821 0.657745264264338 +82.6130823381245 0.696741497146602 +82.6376071274281 0.71929313992259 +82.6621319167316 0.69852916084557 +82.6866567060351 0.67814380257614 +82.7111814953387 0.677530341286654 +82.7357062846422 0.629359271381294 +82.7602310739458 0.639827409101179 +82.7847558632493 0.621254396569103 +82.8092806525528 0.594883239869581 +82.8338054418564 0.599999888488576 +82.8583302311599 0.588289492195033 +82.8828550204635 0.558308980015487 +82.907379809767 0.565075024052786 +82.9319045990705 0.565313698533354 +82.9564293883741 0.516110528497846 +82.9809541776776 0.554099343288349 +83.0054789669812 0.578921510769471 +83.0300037562847 0.596453077127697 +83.0545285455883 0.561607770852096 +83.0790533348918 0.539186728359429 +83.1035781241953 0.548944832588596 +83.1281029134989 0.575546995771343 +83.1526277028024 0.578980220889945 +83.177152492106 0.558871399181841 +83.2016772814095 0.562321638341487 +83.226202070713 0.534909035468116 +83.2507268600166 0.549910554600926 +83.2752516493201 0.541226246581338 +83.2997764386237 0.548651281986225 +83.3243012279272 0.580743202550526 +83.3488260172307 0.595345252836032 +83.3733508065343 0.606010368957055 +83.3978755958378 0.570954904237944 +83.4224003851414 0.566569137149384 +83.4469251744449 0.583089451417178 +83.4714499637485 0.58869662903968 +83.495974753052 0.566451716908436 +83.5204995423555 0.614967379539298 +83.5450243316591 0.646794265538287 +83.5695491209626 0.622782537747789 +83.5940739102662 0.60490637866406 +83.6185986995697 0.647235430465022 +83.6431234888732 0.661778770630053 +83.6676482781768 0.686345249589563 +83.6921730674803 0.693101947583161 +83.7166978567839 0.698152218084339 +83.7412226460874 0.778839359439692 +83.7657474353909 0.8218559186453 +83.7902722246945 0.82198651892862 +83.814797013998 0.905116481351858 +83.8393218033016 0.939285782218827 +83.8638465926051 0.982504831870602 +83.8883713819087 1.08015728641916 +83.9128961712122 1.20276250873215 +83.9374209605157 1.27863038606955 +83.9619457498193 1.39097762910617 +83.9864705391228 1.44816441971425 +84.0109953284264 1.47014046328151 +84.0355201177299 1.4538890175777 +84.0600449070334 1.52554893623432 +84.084569696337 1.49599473089278 +84.1090944856405 1.38030316694145 +84.1336192749441 1.28282684275432 +84.1581440642476 1.17103435088671 +84.1826688535511 1.11076659588287 +84.2071936428547 1.0240064299399 +84.2317184321582 0.972220492509481 +84.2562432214618 0.931856912815269 +84.2807680107653 0.891760523638682 +84.3052928000689 0.894513909349981 +84.3298175893724 0.82674539477974 +84.3543423786759 0.809789427630241 +84.3788671679795 0.839794621848173 +84.403391957283 0.783573553252426 +84.4279167465866 0.826196155655758 +84.4524415358901 0.833893893623301 +84.4769663251936 0.853889129089127 +84.5014911144972 0.848571668070324 +84.5260159038007 0.91393905765211 +84.5505406931043 0.959025329163042 +84.5750654824078 0.96536722231465 +84.5995902717113 1.01467631059509 +84.6241150610149 1.0607908482375 +84.6486398503184 1.04921488622595 +84.673164639622 1.05714363067672 +84.6976894289255 1.03765977225411 +84.7222142182291 0.957580580143217 +84.7467390075326 0.933930459119265 +84.7712637968361 0.912280316190104 +84.7957885861397 0.850917916936975 +84.8203133754432 0.827487778306188 +84.8448381647468 0.770541340225036 +84.8693629540503 0.770473284060861 +84.8938877433538 0.719679428727522 +84.9184125326574 0.707876294231417 +84.9429373219609 0.689295613702 +84.9674621112645 0.6695769147975 +84.991986900568 0.684544405848222 +85.0165116898716 0.682101584780338 +85.0410364791751 0.688888476857352 +85.0655612684786 0.689492592103137 +85.0900860577822 0.669417798477122 +85.1146108470857 0.688905490898396 +85.1391356363893 0.701748393521991 +85.1636604256928 0.689782308706836 +85.1881852149963 0.702441412971666 +85.2127100042999 0.694957667446305 +85.2372347936034 0.712909550691551 +85.261759582907 0.803237376035123 +85.2862843722105 0.82068938423316 +85.310809161514 0.840080504453201 +85.3353339508176 0.887295198389932 +85.3598587401211 0.976339069080416 +85.3843835294247 0.995751037340173 +85.4089083187282 1.09508691538912 +85.4334331080318 1.18109151776328 +85.4579578973353 1.24238370489992 +85.4824826866388 1.22967355851195 +85.5070074759424 1.25466035435849 +85.5315322652459 1.27878566839126 +85.5560570545495 1.23442476636573 +85.580581843853 1.22371795416528 +85.6051066331565 1.14333754345467 +85.6296314224601 1.13657533341604 +85.6541562117636 1.07927495656569 +85.6786810010672 0.97112968225886 +85.7032057903707 0.96164811459102 +85.7277305796742 0.925653287944324 +85.7522553689778 0.906945841130257 +85.7767801582813 0.823870754828822 +85.8013049475849 0.851366749861053 +85.8258297368884 0.901179547187376 +85.850354526192 0.885750043170203 +85.8748793154955 0.922316635026955 +85.899404104799 0.919991234200019 +85.9239288941026 0.923483169439095 +85.9484536834061 0.948729487805908 +85.9729784727097 1.01971723505257 +85.9975032620132 1.03530968938879 +86.0220280513167 1.06550419401052 +86.0465528406203 1.02489642779071 +86.0710776299238 1.06110141288092 +86.0956024192274 1.07927112256701 +86.1201272085309 1.07862914524047 +86.1446519978344 1.06139496348329 +86.169176787138 1.0573576231189 +86.1937015764415 1.02780892982239 +86.2182263657451 0.981429357614665 +86.2427511550486 0.946131384416316 +86.2672759443522 0.883348918181749 +86.2918007336557 0.853264165803627 +86.3163255229592 0.799190689627035 +86.3408503122628 0.736731968078254 +86.3653751015663 0.734743491979478 +86.3898998908699 0.757050948229868 +86.4144246801734 0.727540117014114 +86.4389494694769 0.682508721624985 +86.4634742587805 0.655012726592754 +86.487999048084 0.693967263395588 +86.5125238373876 0.709425283449818 +86.5370486266911 0.683398719475798 +86.5615734159946 0.674252398489759 +86.5860982052982 0.681169890850094 +86.6106229946017 0.668469090505835 +86.6351477839053 0.712061248920169 +86.6596725732088 0.675045824139339 +86.6841973625124 0.634576326200192 +86.7087221518159 0.663155463485703 +86.7332469411194 0.642429346489442 +86.757771730423 0.574291557155313 +86.7822965197265 0.576302559340164 +86.8068213090301 0.608394479904465 +86.8313460983336 0.577683086194484 +86.8558708876371 0.556663418595853 +86.8803956769407 0.563232484232015 +86.9049204662442 0.531009963384393 +86.9294452555478 0.504865979169425 +86.9539700448513 0.52456766403288 +86.9784948341548 0.527400607904368 +87.0030196234584 0.5262379074909 +87.0275444127619 0.500307915718113 +87.0520692020655 0.526913912899532 +87.076593991369 0.512995536020001 +87.1011187806726 0.516093514456802 +87.1256435699761 0.495871106506422 +87.1501683592796 0.501936463096703 +87.1746931485832 0.509407028579692 +87.1992179378867 0.478847083192747 +87.2237427271903 0.479788123166691 +87.2482675164938 0.513402672864648 +87.2727923057973 0.510120896069082 +87.2973170951009 0.485789257591469 +87.3218418844044 0.511925573809094 +87.346366673708 0.467363859383758 +87.3708914630115 0.484429578776863 +87.395416252315 0.490440059245342 +87.4199410416186 0.528425040037174 +87.4444658309221 0.497692798287478 +87.4689906202257 0.515203516605989 +87.4935154095292 0.489353082993391 +87.5180401988328 0.471770474512033 +87.5425649881363 0.47480974282836 +87.5670897774398 0.475875871040595 +87.5916145667434 0.488811511866752 +87.6161393560469 0.478553532590377 +87.6406641453505 0.516731657784674 +87.665188934654 0.513092108221234 +87.6897137239575 0.495522679782248 +87.7142385132611 0.488031266259545 +87.7387633025646 0.474410273981055 +87.7632880918682 0.4771900197771 +87.7878128811717 0.494960260615894 +87.8123376704752 0.504089567560889 +87.8368624597788 0.491724013898429 +87.8613872490823 0.4775252664589 +87.8859120383859 0.501180899527882 +87.9104368276894 0.484295144494871 +87.934961616993 0.500266219638683 +87.9594864062965 0.525385771720847 +87.9840111956 0.490481755324772 +88.0085359849036 0.468195147114097 +88.0330607742071 0.495018970736368 +88.0575855635107 0.513667707429961 +88.0821103528142 0.502309571859263 +88.1066351421177 0.505697266899763 +88.1311599314213 0.518191742799184 +88.1556847207248 0.495871106506422 +88.1802095100284 0.472757044564079 +88.2047342993319 0.474285185742765 +88.2292590886354 0.516286658859268 +88.253783877939 0.508651465010871 +88.2783086672425 0.496916386678942 +88.3028334565461 0.481890185507745 +88.3273582458496 0.458213704399048 +88.3518830351532 0.459062006170431 +88.3764078244567 0.510704163275152 +88.4009326137602 0.488597519424571 +88.4254574030638 0.481600468904046 +88.4499821923673 0.493579733761573 +88.4745069816709 0.498137797212884 +88.4990317709744 0.49916606334436 +88.5235565602779 0.497306509482545 +88.5480813495815 0.476362566045432 +88.572606138885 0.478456960389143 +88.5971309281886 0.525629958246444 +88.6216557174921 0.547005720566591 +88.6461805067956 0.519740732017585 +88.6707052960992 0.500290901677069 +88.6952300854027 0.532848669206493 +88.7197548747063 0.522939116654291 +88.7442796640098 0.536378466526327 +88.7688044533134 0.512895129820096 +88.7933292426169 0.482221598190875 +88.8178540319204 0.487393122931672 +88.842378821224 0.530246731818229 +88.8669036105275 0.549952250680357 +88.8914283998311 0.543455075207041 +88.9159531891346 0.564226722281403 +88.9404779784381 0.548706158108028 +88.9650027677417 0.554279307648443 +88.9895275570452 0.556349019953767 +89.0140523463488 0.551404667697524 +89.0385771356523 0.574888004403755 +89.0631019249558 0.587492232546782 +89.0876267142594 0.601548877706976 +89.1121515035629 0.629380119421008 +89.1366762928665 0.586929813380427 +89.16120108217 0.628696446015034 +89.1857258714736 0.642001361605079 +89.2102506607771 0.65921852932122 +89.2347754500806 0.69226299185548 +89.2593002393842 0.697186496072008 +89.2838250286877 0.744459900129213 +89.3083498179913 0.788072906583263 +89.3328746072948 0.815589749655209 +89.3573993965983 0.872248149179021 +89.3819241859019 0.915143454145008 +89.4064489752054 0.922803330031792 +89.430973764509 0.944671299401806 +89.4554985538125 0.986777012716885 +89.480023343116 1.02294030172766 +89.5045481324196 0.956565494054114 +89.5290729217231 0.983616490160938 +89.5535977110267 0.938958203534369 +89.5781225003302 0.870954848482232 +89.6026472896338 0.875081093050509 +89.6271720789373 0.876188917342175 +89.6516968682408 0.866552067352628 +89.6762216575444 0.826896843102776 +89.7007464468479 0.867924926209606 +89.7252712361515 0.849020500994401 +89.749796025455 0.859685617115424 +89.7743208147585 0.893637091541541 +89.7988456040621 0.909153821716245 +89.8233703933656 0.920066958361537 +89.8478951826692 0.976628785684115 +89.8724199719727 1.049097465985 +89.8969447612762 1.07767660327051 +89.9214695505798 1.10213332998642 +89.9459943398833 1.12235957193547 +89.9705191291869 1.11072489980344 +89.9950439184904 1.11056578348306 +90.019568707794 1.1429891167305 +90.0440934970975 1.15359552273104 +90.068618286401 1.11123627684667 +90.0931430757046 1.02255017892406 +90.1176678650081 0.992535638662423 +90.1421926543117 0.988623386536327 +90.1667174436152 0.946555535302007 +90.1912422329187 0.928293087413346 +90.2157670222223 0.894031048343815 +90.2402918115258 0.863311986636492 +90.2648166008294 0.866862631996042 +90.2893413901329 0.777084045776453 +90.3138661794364 0.803720237041288 +90.33839096874 0.785827063916516 +90.3629157580435 0.741848616697249 +90.3874405473471 0.734768174017864 +90.4119653366506 0.710491411662053 +90.4364901259542 0.728871279791662 +90.4610149152577 0.708904560362894 +90.4855397045612 0.737072726805085 +90.5100644938648 0.703880649946461 +90.5345892831683 0.75000453363257 +90.5591140724719 0.774209405825535 +90.5836388617754 0.784622667423617 +90.6081636510789 0.786368635043155 +90.6326884403825 0.75555683513327 +90.657213229686 0.785978512239552 +90.6817380189896 0.811421809007502 +90.7062628082931 0.748887363297204 +90.7307875975966 0.737890834493052 +90.7553123869002 0.727477572894969 +90.7798371762037 0.715087337194123 +90.8043619655073 0.682046708658535 +90.8288867548108 0.634790318642374 +90.8534115441144 0.625749915901269 +90.8779363334179 0.642232368088305 +90.9024611227214 0.614815931216262 +90.926985912025 0.634110479235071 +90.9515107013285 0.622727661625986 +90.9760354906321 0.595055536232332 +91.0005602799356 0.623735079717747 +91.0250850692391 0.580667478389009 +91.0496098585427 0.573066312622699 +91.0741346478462 0.566703571431376 +91.0986594371498 0.570837483996996 +91.1231842264533 0.564281598403206 +91.1477090157568 0.574833128281952 +91.1722338050604 0.550183257163582 +91.1967585943639 0.558926275303644 +91.2212833836675 0.578673490245202 +91.245808172971 0.558833537101082 +91.2703329622746 0.573835056233893 +91.2948577515781 0.595424810996221 +91.3193825408816 0.590459610700262 +91.3439073301852 0.599340897120988 +91.3684321194887 0.56985091394495 +91.3929569087923 0.563950185720077 +91.4174816980958 0.574791432202522 +91.4420064873993 0.559551238589144 +91.4665312767029 0.546094874676063 +91.4910560660064 0.54992756864197 +91.51558085531 0.594765819628633 +91.5401056446135 0.593444002894787 +91.564630433917 0.655222885036264 +91.5891552232206 0.653501599455112 +91.6136800125241 0.648863977843612 +91.6382048018277 0.655168008914461 +91.6627295911312 0.695868513336833 +91.6872543804348 0.705291370884198 +91.7117791697383 0.724455318619686 +91.7363039590418 0.737400305489544 +91.7608287483454 0.776169365887254 +91.7853535376489 0.816438051426592 +91.8098783269525 0.875681374297623 +91.834403116256 0.915219178306526 +91.8589279055595 0.958887060882379 +91.8834526948631 1.01570841072524 +91.9079774841666 1.03955335419802 +91.9325022734702 1.07388177138536 +91.9570270627737 1.04558851670488 +91.9815518520772 1.01237559180654 +92.0060766413808 0.961920817153675 +92.0306014306843 0.884738791079771 +92.0551262199879 0.877741740559246 +92.0796510092914 0.81697027650953 +92.104175798595 0.783111540285976 +92.1287005878985 0.764652113996177 +92.153225377202 0.745105711454429 +92.1777501665056 0.711922980639506 +92.2022749558091 0.71557403219896 +92.2267997451127 0.690596582396131 +92.2513245344162 0.656682970050774 +92.2758493237197 0.629707698105467 +92.3003741130233 0.624566367448086 +92.3248989023268 0.629048706737879 +92.3494236916304 0.661002359021517 +92.3739484809339 0.640431524346964 +92.3984732702374 0.680276059000611 +92.422998059541 0.732170070628272 +92.4475228488445 0.711578387914004 +92.4720476381481 0.696489642623662 +92.4965724274516 0.749639092867353 +92.5210972167552 0.753790019474017 +92.5456220060587 0.744463734127885 +92.5701467953622 0.781051174024352 +92.5946715846658 0.801322946051505 +92.6191963739693 0.798734188705614 +92.6437211632729 0.791278959217311 +92.6682459525764 0.770990173149114 +92.6927707418799 0.804165235966695 +92.7172955311835 0.815769714015303 +92.741820320487 0.7509609096012 +92.7663451097906 0.725907735636853 +92.7908698990941 0.718906851117658 +92.8153946883976 0.701807103642465 +92.8399194777012 0.680086748596816 +92.8644442670047 0.617006897761207 +92.8889690563083 0.626215762866391 +92.9134938456118 0.631084390961116 +92.9380186349154 0.608083915261051 +92.9625434242189 0.679155054666572 +92.9870682135224 0.638299267922493 +93.011593002826 0.646807445580659 +93.0361177921295 0.660909620818955 +93.0606425814331 0.656679136052102 +93.0851673707366 0.621812981736787 +93.1096921600401 0.69493681940659 +93.1342169493437 0.680556429560608 +93.1587417386472 0.654391597305925 +93.1832665279508 0.641162405877398 +93.2077913172543 0.705992058331216 +93.2323161065578 0.709597579812569 +93.2568408958614 0.770948477069684 +93.2813656851649 0.842112354677767 +93.3058904744685 0.856277074035209 +93.330415263772 0.875118955131268 +93.3549400530756 0.909557124562221 +93.3794648423791 0.919663655515561 +93.4039896316826 0.881674840725058 +93.4285144209862 0.942152754172403 +93.4530392102897 0.934521394322677 +93.4775639995933 0.904982047069866 +93.5020887888968 0.914139870051919 +93.5266135782003 0.812433061097934 +93.5511383675039 0.795153349262648 +93.5756631568074 0.758175786562577 +93.600187946111 0.686953198834019 +93.6247127354145 0.687780652565687 +93.649237524718 0.676543771234608 +93.6737623140216 0.636297611781344 +93.6982871033251 0.626761167991702 +93.7228118926287 0.621913387936691 +93.7473366819322 0.609867744961348 +93.7718614712358 0.625325765015578 +93.7963862605393 0.629628139945278 +93.8209110498428 0.614778069135503 +93.8454358391464 0.614581090734366 +93.8699606284499 0.614211815970477 +93.8944854177535 0.615437060503091 +93.919010207057 0.619659877272601 +93.9435349963605 0.657552119861871 +93.9680597856641 0.686718358352123 +93.9925845749676 0.668120663781661 +94.0171093642712 0.739108411028323 +94.0416341535747 0.71503246107232 +94.0661589428782 0.658718654274011 +94.0906837321818 0.67998250839824 +94.1152085214853 0.737366277407456 +94.1397333107889 0.721290962065068 +94.1642581000924 0.748518088533315 +94.188782889396 0.678848324021829 +94.2133076786995 0.673744855445207 +94.237832468003 0.63114310108159 +94.2623572573066 0.659021550920083 +94.2868820466101 0.684586101927653 +94.3114068359137 0.644368458511446 +94.3359316252172 0.60202239266944 +94.3604564145207 0.582778886773764 +94.3849812038243 0.565095872092501 +94.4095059931278 0.584235137789602 +94.4340307824314 0.562497768702909 +94.4585555717349 0.567269824596402 +94.4830803610384 0.534170485940339 +94.507605150342 0.537499470860366 +94.5321299396455 0.534749919147738 +94.5566547289491 0.521113590874563 +94.5811795182526 0.544428465216715 +94.6057043075562 0.556563012395949 +94.6302290968597 0.54382434997093 +94.6547538861632 0.551463377817998 +94.6792786754668 0.571047642440506 +94.7038034647703 0.569670949584856 +94.7283282540739 0.577082804947371 +94.7528530433774 0.511329126560652 +94.7773778326809 0.487644977454612 +94.8019026219845 0.513902547911858 +94.826427411288 0.529549878369883 +94.8509522005916 0.520168716901948 +94.8754769898951 0.513012550061045 +94.9000017791986 0.523425811659127 +94.9245265685022 0.480647926934088 +94.9490513578057 0.526620362297161 +94.9735761471093 0.528059599271956 +94.9981009364128 0.521331417315416 +95.0226257257164 0.524722946354587 +95.0471505150199 0.520282303144225 +95.0716753043234 0.557242851803252 +95.096200093627 0.540156284370431 +95.1207248829305 0.555169305499255 +95.1452496722341 0.550728662288892 +95.1697744615376 0.533759515097021 +95.1942992508411 0.555593456384947 +95.2188240401447 0.546753866043651 +95.2433488294482 0.488756635744949 +95.2678736187518 0.484329172576959 +95.2923984080553 0.511438878804258 +95.3169231973588 0.532151815758146 +95.3414479866624 0.517335773030459 +95.3659727759659 0.537986165865202 +95.3904975652695 0.543841364011974 +95.415022354573 0.565427284775631 +95.4395471438766 0.558502124417953 +95.4640719331801 0.562090631858262 +95.4885967224836 0.56922595065945 +95.5131215117872 0.566707405430047 +95.5376463010907 0.542972214200876 +95.5621710903943 0.542968380202205 +95.5866958796978 0.548013138658352 +95.6112206690013 0.571026794400791 +95.6357454583049 0.584411268151025 +95.6602702476084 0.583186023618411 +95.684795036912 0.574774418161478 +95.7093198262155 0.604184843177327 +95.733844615519 0.630987818759883 +95.7583694048226 0.612213993827999 +95.7828941941261 0.640259227984213 +95.8074189834297 0.64246337457153 +95.8319437727332 0.640490234467438 +95.8564685620368 0.621212700489673 +95.8809933513403 0.70930786320887 +95.9055181406438 0.67764009353026 +95.9300429299474 0.656896962492955 +95.9545677192509 0.653887888260045 +95.9790925085545 0.63711571946931 +96.003617297858 0.611479278298894 +96.0281420871615 0.611147865615764 +96.0526668764651 0.629900842507933 +96.0771916657686 0.604792792421783 +96.1017164550722 0.625498061378329 +96.1262412443757 0.592071144037808 +96.1507660336792 0.614446656452374 +96.1752908229828 0.577351673511355 +96.1998156122863 0.597670653662968 +96.2243404015899 0.609288311753949 +96.2488651908934 0.599924164327058 +96.273389980197 0.586018967489899 +96.2979147695005 0.580453485946827 +96.322439558804 0.583093285415849 +96.3469643481076 0.577599694035624 +96.3714891374111 0.611231257774625 +96.3960139267147 0.60753851013574 +96.4205387160182 0.645572855004344 +96.4450635053217 0.6224132629839 +96.4695882946253 0.609985165202296 +96.4941130839288 0.634362333758011 +96.5186378732324 0.61062714252884 +96.5431626625359 0.616385768474378 +96.5676874518394 0.603239969204712 +96.592212241143 0.659487397885204 +96.6167370304465 0.648943536003801 +96.6412618197501 0.699919033743592 +96.6657866090536 0.672506430870221 +96.6903113983572 0.725551640915337 +96.7148361876607 0.72585285951505 +96.7393609769642 0.656296681245841 +96.7638857662678 0.649695265573951 +96.7884105555713 0.664380708018316 +96.8129353448749 0.704107822431015 +96.8374601341784 0.698139038041967 +96.8619849234819 0.690151583470725 +96.8865097127855 0.678181664656899 +96.911034502089 0.733022206398326 +96.9355592913926 0.720497536415488 +96.9600840806961 0.739964380797048 +96.9846088699996 0.777424804503284 +97.0091336593032 0.782859685763035 +97.0336584486067 0.827879579156151 +97.0581832379103 0.856294088076253 +97.0827080272138 0.837230546540669 +97.1072328165174 0.934472030245905 +97.1317576058209 0.983302091518853 +97.1562823951244 1.06723865963404 +97.180807184428 1.17922812990279 +97.2053319737315 1.25156237592168 +97.2298567630351 1.26030539406175 +97.2543815523386 1.2866727167626 +97.2789063416421 1.41517866730047 +97.3034311309457 1.46904965303089 +97.3279559202492 1.5206709620959 +97.3524807095528 1.51225552264029 +97.3770054988563 1.53410647796926 +97.4015302881598 1.51268350752466 +97.4260550774634 1.45411067801723 +97.4505798667669 1.40682193796534 +97.4751046560705 1.34989850387621 +97.499629445374 1.29459211516966 +97.5241542346776 1.21908416655245 +97.5486790239811 1.13246610288881 +97.5732038132846 1.09961095075834 +97.5977286025882 1.05100254992492 +97.6222533918917 1.03994731100029 +97.6467781811953 1.04555065462412 +97.6713029704988 1.00600901661655 +97.6958277598023 1.02891675411405 +97.7203525491059 0.995343900495525 +97.7448773384094 1.03922577551356 +97.769402127713 1.0540248042002 +97.7939269170165 1.04482911913739 +97.81845170632 1.11196332437843 +97.8429764956236 1.12803863972082 +97.8675012849271 1.11175316593492 +97.8920260742307 1.11688682859496 +97.9165508635342 1.14927229976163 +97.9410756528378 1.13746533126685 +97.9656004421413 1.17166866021591 +97.9901252314448 1.14757186222019 +98.0146500207484 1.1408700403484 +98.0391748100519 1.15606302583732 +98.0636995993555 1.12373243079245 +98.088224388659 1.159530279038 +98.1127491779625 1.19793941071553 +98.1372739672661 1.2248636405377 +98.1617987565696 1.18390361359505 +98.1863235458732 1.20318498157148 +98.2108483351767 1.12152445020646 +98.2353731244802 1.1066952274364 +98.2598979137838 1.01923820804507 +98.2844227030873 1.00670970406357 +98.3089474923909 0.957228319420372 +98.3334722816944 0.926346307393999 +98.357997070998 0.927155069038264 +98.3825218603015 0.948570371485529 +98.407046649605 0.914084993930116 +98.4315714389086 0.80636003651031 +98.4560962282121 0.798548712300491 +98.4806210175157 0.785495651233386 +98.5051458068192 0.812601523462014 +98.5296705961227 0.829742967016637 +98.5541953854263 0.831186037990103 +98.5787201747298 0.789256455036446 +98.6032449640334 0.798079031336698 +98.6277697533369 0.830892487387732 +98.6522945426404 0.795094639142174 +98.676819331944 0.770041465177827 +98.7013441212475 0.76876134452341 +98.7258689105511 0.735893012350573 +98.7503936998546 0.753055303944911 +98.7749184891582 0.720925521299851 +98.7994432784617 0.720124427652929 +98.8239680677652 0.722650640879674 +98.8484928570688 0.711250809229545 +98.8730176463723 0.660322519614214 +98.8975424356759 0.690986705199735 +98.9220672249794 0.671394772579885 +98.9465920142829 0.711616249994762 +98.9711168035865 0.678886186102588 +98.99564159289 0.658248973310218 +99.0201663821936 0.620926817884645 +99.0446911714971 0.631974388811929 +99.0692159608006 0.637502008274242 +99.0937407501042 0.621544113172803 +99.1182655394077 0.653266758973216 +99.1427903287113 0.63653245226324 +99.1673151180148 0.655050588673513 +99.1918399073184 0.659784782486246 +99.2163646966219 0.640683378869904 +99.2408894859254 0.624587215487801 +99.265414275229 0.653887888260045 +99.2899390645325 0.652410789204491 +99.3144638538361 0.693912387273785 +99.3389886431396 0.657531271822156 +99.3635134324431 0.68390626252035 +99.3880382217467 0.678126788535096 +99.4125630110502 0.688053355128342 +99.4370878003538 0.704888068038222 +99.4616125896573 0.683923276561394 +99.4861373789608 0.719393546122494 +99.5106621682644 0.709480159571621 +99.5351869575679 0.715960321003892 +99.5597117468715 0.709110884807732 +99.584236536175 0.753382882629369 +99.6087613254786 0.734944304379286 +99.6332861147821 0.783359560810245 +99.6578109040856 0.79800330717518 +99.6823356933892 0.828869983206868 +99.7068604826927 0.861762997418092 +99.7313852719963 0.897918618431525 +99.7559100612998 0.92726482128187 +99.7804348506033 0.930673364362086 +99.8049596399069 1.01710211762193 +99.8294844292104 1.00349047138715 +99.854009218514 0.994823177408601 +99.8785340078175 0.999612247343137 +99.903058797121 0.973962626130348 +99.9275835864246 0.950668599827912 +99.9521083757281 0.923269176996913 +99.9766331650317 0.894979756315102 +100.001157954335 0.91343534860623 +100.025682743639 0.913694871126512 +100.050207532942 0.839635505527795 +100.074732322246 0.816407857343175 +100.099257111549 0.792089398907934 +100.123781900853 0.76963432833318 +100.148306690156 0.739208817228227 +100.17283147946 0.749739499067258 +100.197356268764 0.729475395037447 +100.221881058067 0.728333542663694 +100.246405847371 0.703738547667126 +100.270930636674 0.752703043222067 +100.295455425978 0.739968214795719 +100.319980215281 0.719351850043064 +100.344505004585 0.74068208228511 +100.369029793888 0.720594108616721 +100.393554583192 0.706344319054061 +100.418079372495 0.698832057491642 +100.442604161799 0.757685257559069 +100.467128951102 0.76048417334847 +100.491653740406 0.776127669807824 +100.51617852971 0.832538048807366 +100.540703319013 0.788483877426582 +100.565228108317 0.79984201299728 +100.58975289762 0.834204458266715 +100.614277686924 0.844114010818917 +100.638802476227 0.860323760443297 +100.663327265531 0.857553360690954 +100.687852054834 0.912818053318072 +100.712376844138 0.879328591858406 +100.736901633441 0.857401912367918 +100.761426422745 0.813457493230739 +100.785951212049 0.818439707567741 +100.810476001352 0.807547418962165 +100.835000790656 0.80197426942175 +100.859525579959 0.82047922578965 +100.884050369263 0.823770348628917 +100.908575158566 0.829301802089902 +100.93309994787 0.785461623151298 +100.957624737173 0.773177305695387 +100.982149526477 0.789801860161757 +101.00667431578 0.813902492156146 +101.031199105084 0.818687728092011 +101.055723894387 0.852046589268356 +101.080248683691 0.858136627897024 +101.104773472995 0.893813221902963 +101.129298262298 0.904751040586641 +101.153823051602 0.85448941033624 +101.178347840905 0.899744144211252 +101.202872630209 0.968448186710407 +101.227397419512 1.0323214631956 +101.251922208816 1.06788830495793 +101.276446998119 1.05041544872018 +101.300971787423 1.14889919099907 +101.325496576726 1.13866972775975 +101.35002136603 1.12441227019975 +101.374546155334 1.19827082339866 +101.399070944637 1.13834214907529 +101.423595733941 1.11136304313132 +101.448120523244 1.04597863950849 +101.472645312548 1.07318108393835 +101.497170101851 1.03448223565712 +101.521694891155 0.974151936534143 +101.546219680458 0.997031157994589 +101.570744469762 0.981097944931536 +101.595269259065 0.973572503326744 +101.619794048369 0.932729896625038 +101.644318837672 0.918887243907025 +101.668843626976 0.912293496232476 +101.69336841628 0.965132381832754 +101.717893205583 0.968200166186138 +101.742417994887 0.940565902873244 +101.76694278419 0.961933997196048 +101.791467573494 1.01948622856934 +101.815992362797 1.01729909602307 +101.840517152101 0.996275594425768 +101.865041941404 1.00389760823179 +101.889566730708 0.99996067406731 +101.914091520011 0.995133742052015 +101.938616309315 0.982491651828229 +101.963141098619 0.968200166186138 +101.987665887922 0.947470215191206 +102.012190677226 0.952645573930675 +102.036715466529 0.958017911071281 +102.061240255833 0.996082450023302 +102.085765045136 0.957165775301227 +102.11028983444 0.96966959724435 +102.134814623743 0.990499954439186 +102.159339413047 0.953635977981392 +102.18386420235 0.97133984070237 +102.208388991654 0.981715240219693 +102.232913780957 0.978164594860143 +102.257438570261 0.911117615776636 +102.281963359565 0.906765876770163 +102.306488148868 0.902870638685111 +102.331012938172 0.883114077699852 +102.355537727475 0.826993415304009 +102.380062516779 0.823812044708347 +102.404587306082 0.80640173258974 +102.429112095386 0.820765108394678 +102.453636884689 0.779833597489081 +102.478161673993 0.794590930096293 +102.502686463296 0.786855330047992 +102.5272112526 0.743183613473468 +102.551736041903 0.745509014300405 +102.576260831207 0.782607831240095 +102.600785620511 0.792202985150211 +102.625310409814 0.793600526045576 +102.649835199118 0.774171543744776 +102.674359988421 0.791098994857217 +102.698884777725 0.812059952335374 +102.723409567028 0.836882119816496 +102.747934356332 0.762889132335595 +102.772459145635 0.765521263807275 +102.796983934939 0.739057368905191 +102.821508724242 0.705933348210742 +102.846033513546 0.733139626639274 +102.87055830285 0.729005714073654 +102.895083092153 0.722730199039863 +102.919607881457 0.731255390739073 +102.94413267076 0.710457383579965 +102.968657460064 0.679192916747331 +102.993182249367 0.691003719240779 +103.017707038671 0.645199746241785 +103.042231827974 0.686525213949657 +103.066756617278 0.658366393551166 +103.091281406581 0.67225074234861 +103.115806195885 0.651310632910168 +103.140330985188 0.633468501908527 +103.164855774492 0.686324401549848 +103.189380563796 0.711464801671727 +103.213905353099 0.633216647385587 +103.238430142403 0.687008074955822 +103.262954931706 0.65954994200435 +103.28747972101 0.679096344546098 +103.312004510313 0.68299158263115 +103.336529299617 0.704556655355092 +103.36105408892 0.669694335038448 +103.385578878224 0.684426985607274 +103.410103667527 0.684061544842057 +103.434628456831 0.683264285193806 +103.459153246135 0.684195979124049 +103.483678035438 0.691586986446848 +103.508202824742 0.688515368094792 +103.532727614045 0.712917218688894 +103.557252403349 0.700233432385678 +103.581777192652 0.714210519385683 +103.606301981956 0.745899137104008 +103.630826771259 0.744698574609781 +103.655351560563 0.734495471455208 +103.679876349866 0.791775000265849 +103.70440113917 0.809054712101135 +103.728925928473 0.803627498838726 +103.753450717777 0.842095340636723 +103.777975507081 0.847992234862925 +103.802500296384 0.855441952306199 +103.827025085688 0.828597280644213 +103.851549874991 0.915067729983491 +103.876074664295 0.8499351808836 +103.900599453598 0.855920979313693 +103.925124242902 0.825931121090445 +103.949649032205 0.803044231632656 +103.974173821509 0.786948068250554 +103.998698610812 0.812491771218408 +104.023223400116 0.76691880470264 +104.04774818942 0.756056710180479 +104.072272978723 0.769075743165496 +104.096797768027 0.739091396987279 +104.12132255733 0.740199221278944 +104.145847346634 0.738491115740165 +104.170372135937 0.70630262297463 +104.194896925241 0.73228749086922 +104.219421714544 0.686155939185768 +104.243946503848 0.675415098903227 +104.268471293151 0.656423447530491 +104.292996082455 0.69635520834167 +104.317520871758 0.68227771514176 +104.342045661062 0.701568429161897 +104.366570450366 0.655986116602427 +104.391095239669 0.68921605554181 +104.415620028973 0.708628023801567 +104.440144818276 0.70930786320887 +104.46466960758 0.725051765868128 +104.489194396883 0.741534218055163 +104.513719186187 0.731380478977363 +104.53824397549 0.664339011938886 +104.562768764794 0.7130269709325 +104.587293554097 0.71803770130656 +104.611818343401 0.710798142306796 +104.636343132704 0.749114535781758 +104.660867922008 0.780592995056573 +104.685392711312 0.767905374754685 +104.709917500615 0.771376461954046 +104.734442289919 0.806788021394673 +104.758967079222 0.780261582373443 +104.783491868526 0.804496648649824 +104.808016657829 0.763917398467072 +104.832541447133 0.750453366556648 +104.857066236436 0.769672190413939 +104.88159102574 0.783342546769201 +104.906115815043 0.757244092632334 +104.930640604347 0.76249517553332 +104.955165393651 0.807136448118846 +104.979690182954 0.776383358329435 +105.004214972258 0.78906331063398 +105.028739761561 0.762088038688673 +105.053264550865 0.815417453292458 +105.077789340168 0.767577796070227 +105.102314129472 0.78128984850492 +105.126838918775 0.795577500148339 +105.151363708079 0.803485396559392 +105.175888497382 0.844021272616355 +105.200413286686 0.853730012768748 +105.224938075989 0.891777537679726 +105.249462865293 0.92120881073529 +105.273987654597 0.927634096045758 +105.2985124439 0.978047174619195 +105.323037233204 0.996527448948709 +105.347562022507 1.03387428641267 +105.372086811811 1.01252704012958 +105.396611601114 1.00477059204156 +105.421136390418 1.02008650981646 +105.445661179721 1.02019075001503 +105.470185969025 1.0055470036501 +105.494710758328 0.948015620316517 +105.519235547632 0.937153525794357 +105.543760336936 0.884449074476072 +105.568285126239 0.920809341887985 +105.592809915543 0.837641517383988 +105.617334704846 0.863438752921142 +105.64185949415 0.912804873275699 +105.666384283453 0.844810864267264 +105.690909072757 0.844369699340528 +105.71543386206 0.865364684900773 +105.739958651364 0.842006436432833 +105.764483440667 0.838040986231293 +105.789008229971 0.857649932892187 +105.813533019274 0.874401253643206 +105.838057808578 0.854258403853015 +105.862582597882 0.835139986195629 +105.887107387185 0.831610188875794 +105.911632176489 0.817021318632662 +105.936156965792 0.865985814187602 +105.960681755096 0.86183872157961 +105.985206544399 0.863063966112223 +106.009731333703 0.826175307616043 +106.034256123006 0.837251394580384 +106.05878091231 0.856504246519763 +106.083305701613 0.843534577611518 +106.107830490917 0.812912088105428 +106.132355280221 0.874673956205862 +106.156880069524 0.868474165333588 +106.181404858828 0.861175896213351 +106.205929648131 0.890556127145783 +106.230454437435 0.853481992244479 +106.254979226738 0.870409443356921 +106.279504016042 0.850069615165593 +106.304028805345 0.840622075579841 +106.328553594649 0.87696532895071 +106.353078383952 0.846695100167465 +106.377603173256 0.804869757412384 +106.402127962559 0.834939173795821 +106.426652751863 0.794649640216767 +106.451177541167 0.799635688552441 +106.47570233047 0.768895778805403 +106.500227119774 0.763582151785271 +106.524751909077 0.79854487830182 +106.549276698381 0.770335015780197 +106.573801487684 0.771842308919168 +106.598326276988 0.714994598991561 +106.622851066291 0.67893722822572 +106.647375855595 0.755867399776684 +106.671900644898 0.728539867108533 +106.696425434202 0.770624732383897 +106.720950223505 0.754121432157146 +106.745475012809 0.749949657510768 +106.769999802113 0.790457017530673 +106.794524591416 0.815396605252743 +106.81904938072 0.783442952969105 +106.843574170023 0.742583332226355 +106.868098959327 0.784639681464661 +106.89262374863 0.773419814174626 +106.917148537934 0.75576699357678 +106.941673327237 0.719020437359934 +106.966198116541 0.734889428257483 +106.990722905844 0.75225804429666 +107.015247695148 0.734461005420652 +107.039772484452 0.736866928340825 +107.064297273755 0.722087316395547 +107.088822063059 0.712182982601605 +107.113346852362 0.696046942780403 +107.137871641666 0.704852763527506 +107.162396430969 0.710657547067015 +107.186921220273 0.716186194337457 +107.211446009576 0.724073802394208 +107.23597079888 0.706375183236734 +107.260495588183 0.686649467667162 +107.285020377487 0.712519949913152 +107.30954516679 0.71536986271759 +107.334069956094 0.734236082512953 +107.358594745398 0.716346781152258 +107.383119534701 0.733003467330338 +107.407644324005 0.758494573060883 +107.432169113308 0.760161226743534 +107.456693902612 0.757853028532198 +107.481218691915 0.745913177356058 +107.505743481219 0.739408752187235 +107.530268270522 0.761845869593142 +107.554793059826 0.803823105314201 +107.579317849129 0.78472338403622 +107.603842638433 0.791560130929887 +107.628367427737 0.820476976317991 +107.65289221704 0.808224830211686 +107.677417006344 0.858307794137904 +107.701941795647 0.896750967404696 +107.726466584951 0.954799262107067 +107.750991374254 0.960976820180037 +107.775516163558 1.00467043071718 +107.800040952861 1.04386153302885 +107.824565742165 1.06707083509505 +107.849090531468 1.10407901918516 +107.873615320772 1.15783372521092 +107.898140110075 1.1834662497406 +107.922664899379 1.22757414231607 +107.947189688683 1.2321895244378 +107.971714477986 1.22842650869705 +107.99623926729 1.21263042733375 +108.020764056593 1.20557182296823 +108.045288845897 1.16334086709216 +108.0698136352 1.12604247729953 +108.094338424504 1.08893463899343 +108.118863213807 1.02654925756764 +108.143388003111 0.984888831520522 +108.167912792414 0.970627451933738 +108.192437581718 0.969160609289071 +108.216962371022 0.932538613247054 +108.241487160325 0.904197294840024 +108.266011949629 0.8992111195116 +108.290536738932 0.891452918519668 +108.315061528236 0.869046899631301 +108.339586317539 0.848712131057548 +108.364111106843 0.838739980925535 +108.388635896146 0.815784036024629 +108.41316068545 0.812661347777667 +108.437685474753 0.838597559123519 +108.462210264057 0.807355491173816 +108.48673505336 0.83214123675108 +108.511259842664 0.83954989979019 +108.535784631968 0.826449694705138 +108.560309421271 0.832661080756144 +108.584834210575 0.832669713675378 +108.609358999878 0.842354536396832 +108.633883789182 0.837686656153578 +108.658408578485 0.870382888112342 +108.682933367789 0.880232898106167 +108.707458157092 0.900905900889328 +108.731982946396 0.911194051526758 +108.756507735699 0.920879807442955 +108.781032525003 0.981929432534494 +108.805557314306 0.96425811391504 +108.83008210361 0.971455220349066 +108.854606892914 1.0047167911503 +108.879131682217 1.02427025777953 +108.903656471521 1.04767282728557 +108.928181260824 1.04001600488436 +108.952706050128 1.03939599067636 +108.977230839431 1.02971725811571 +109.001755628735 1.02370799788009 +109.026280418038 0.995567112535677 +109.050805207342 0.979294078779556 +109.075329996645 0.98953999593268 +109.099854785949 0.986542249617251 +109.124379575253 0.983274669268927 +109.148904364556 0.967962931125061 +109.17342915386 0.988300639151795 +109.197953943163 0.989721398513028 +109.222478732467 0.993093787960521 +109.24700352177 1.00389585686374 +109.271528311074 1.00835224361958 +109.296053100377 1.03301312180728 +109.320577889681 1.07619384870347 +109.345102678984 1.12101223611774 +109.369627468288 1.1400510451208 +109.394152257591 1.16847575282536 +109.418677046895 1.20415301539469 +109.443201836199 1.24257807534207 +109.467726625502 1.27168821770909 +109.492251414806 1.28037781275922 +109.516776204109 1.26200269393968 +109.541300993413 1.30250533768109 +109.565825782716 1.27153585133955 +109.59035057202 1.2224624052406 +109.614875361323 1.2148406009642 +109.639400150627 1.17883297988201 +109.66392493993 1.12715245194708 +109.688449729234 1.06690423013671 +109.712974518538 1.02871134651648 +109.737499307841 1.01206499270088 +109.762024097145 0.974188467968195 +109.786548886448 0.948217695974401 +109.811073675752 0.949343741579812 +109.835598465055 0.901448643290558 +109.860123254359 0.879910721599916 +109.884648043662 0.861831114280426 +109.909172832966 0.864847512624577 +109.933697622269 0.857934798881953 +109.958222411573 0.859778103520563 +109.982747200876 0.838852151564722 +110.00727199018 0.837950954325042 +110.031796779484 0.847286331624178 +110.056321568787 0.850882609354532 +110.080846358091 0.813622573708014 +110.105371147394 0.838153330401993 +110.129895936698 0.857238454835277 +110.154420726001 0.83614255520932 +110.178945515305 0.848214407775846 +110.203470304608 0.870775281574103 +110.227995093912 0.844277112283746 +110.252519883215 0.851724284990902 +110.277044672519 0.872098798634846 +110.301569461823 0.890832083718846 +110.326094251126 0.909792780250896 +110.35061904043 0.920832845182856 +110.375143829733 0.937713162818001 +110.399668619037 0.944983905291688 +110.42419340834 0.982376388633633 +110.448718197644 0.974043724698298 +110.473242986947 0.962661438224402 +110.497767776251 0.985895457674885 +110.522292565554 0.971056080681446 +110.546817354858 0.967209423298733 +110.571342144161 0.947507521331533 +110.595866933465 0.946139456009076 +110.620391722769 0.913424276883368 +110.644916512072 0.874110076433519 +110.669441301376 0.851265166243104 +110.693966090679 0.849281858408451 +110.718490879983 0.852235401463347 +110.743015669286 0.842770831257644 +110.76754045859 0.825228955806316 +110.792065247893 0.797391881430532 +110.816590037197 0.770193173419426 +110.8411148265 0.779370716086617 +110.865639615804 0.774881788195785 +110.890164405107 0.774562877776455 +110.914689194411 0.767707992758131 +110.939213983715 0.736797387754261 +110.963738773018 0.737567171797117 +110.988263562322 0.740455186403222 +111.012788351625 0.732697035021779 +111.037313140929 0.729373420005791 +111.061837930232 0.733551720787755 +111.086362719536 0.750027123930353 +111.110887508839 0.703326951622253 +111.135412298143 0.701597246606865 +111.159937087446 0.715289061055157 +111.18446187675 0.721801812580693 +111.208986666054 0.74010397368616 +111.233511455357 0.723700130769312 +111.258036244661 0.708307803038381 +111.282561033964 0.720585293161188 +111.307085823268 0.731830549470624 +111.331610612571 0.713327560817108 +111.356135401875 0.696494086023431 +111.380660191178 0.693634329955331 +111.405184980482 0.71578124165872 +111.429709769785 0.718903521913067 +111.454234559089 0.703821922594863 +111.478759348392 0.69887785659829 +111.503284137696 0.700824879410271 +111.527808927 0.726899775360934 +111.552333716303 0.711272343400405 +111.576858505607 0.693303482217422 +111.60138329491 0.740611328156155 +111.625908084214 0.712268831026649 +111.650432873517 0.708181006015426 +111.674957662821 0.714569969652897 +111.699482452124 0.727582444333356 +111.724007241428 0.745831724256159 +111.748532030731 0.745248723006909 +111.773056820035 0.722465270617431 +111.797581609339 0.71684950815032 +111.822106398642 0.743480438389096 +111.846631187946 0.772662823915045 +111.871155977249 0.75851878114869 +111.895680766553 0.754775866744591 +111.920205555856 0.781706954070116 +111.94473034516 0.788179246096562 +111.969255134463 0.781229388552257 +111.993779923767 0.821953637421076 +112.01830471307 0.844044602379714 +112.042829502374 0.867345678610234 +112.067354291677 0.876092976275557 +112.091879080981 0.848464460668031 +112.116403870285 0.871978853855435 +112.140928659588 0.890325513612129 +112.165453448892 0.868790670864518 +112.189978238195 0.874432710542874 +112.214503027499 0.854239806820823 +112.239027816802 0.83624730119082 +112.263552606106 0.810173126485766 +112.288077395409 0.819700953156632 +112.312602184713 0.836993278448011 +112.337126974016 0.811907909759481 +112.36165176332 0.793323073520631 +112.386176552624 0.794585094468011 +112.410701341927 0.785092610725483 +112.435226131231 0.763664885037741 +112.459750920534 0.768383437723936 +112.484275709838 0.757220224917079 +112.508800499141 0.754087573987825 +112.533325288445 0.744816602715964 +112.557850077748 0.730317082750331 +112.582374867052 0.738413957313767 +112.606899656355 0.734629379756622 +112.631424445659 0.747932957231037 +112.655949234962 0.724276203276403 +112.680474024266 0.730144175555815 +112.70499881357 0.737647509165974 +112.729523602873 0.724585489071678 +112.754048392177 0.734657669790345 +112.77857318148 0.740907214070215 +112.803097970784 0.773163882965576 +112.827622760087 0.750536622065754 +112.852147549391 0.725146168946495 +112.876672338694 0.745501591073384 +112.901197127998 0.748539057527524 +112.925721917301 0.744729938431925 +112.950246706605 0.751059073903565 +112.974771495908 0.775676582804497 +112.999296285212 0.777639437737084 +113.023821074516 0.778235561844098 +113.048345863819 0.790124625766256 +113.072870653123 0.797403110884449 +113.097395442426 0.822565913718397 +113.12192023173 0.81413566469051 +113.146445021033 0.824810296431373 +113.170969810337 0.851517570016004 +113.19549459964 0.843082348641238 +113.220019388944 0.842908206297049 +113.244544178247 0.843663062001208 +113.269068967551 0.815080224526485 +113.293593756855 0.831034239742708 +113.318118546158 0.852897818184775 +113.342643335462 0.839019773033132 +113.367168124765 0.823002059654297 +113.391692914069 0.843065037799874 +113.416217703372 0.838307910245079 +113.440742492676 0.851828604991251 +113.465267281979 0.844274177564589 +113.489792071283 0.827250534471755 +113.514316860586 0.826700883399784 +113.53884164989 0.857884300075719 +113.563366439193 0.870567250217091 +113.587891228497 0.867676508233373 +113.612416017801 0.89581101023541 +113.636940807104 0.900150400438176 +113.661465596408 0.887623955039455 +113.685990385711 0.915148112460442 +113.710515175015 0.93430393821423 +113.735039964318 0.949611794937017 +113.759564753622 0.923808207132418 +113.784089542925 0.943927589499613 +113.808614332229 0.932085336923207 +113.833139121532 0.935451356535503 +113.857663910836 0.940693922969357 +113.88218870014 0.957028678295155 +113.906713489443 0.947987268700988 +113.931238278747 0.953804460543126 +113.95576306805 0.97996437763554 +113.980287857354 0.991699551464503 +114.004812646657 0.992807562900058 +114.029337435961 0.98633053749023 +114.053862225264 1.01680146001207 +114.078387014568 1.04756981697686 +114.102911803871 1.08234306713222 +114.127436593175 1.0880106198562 +114.151961382478 1.12253767165233 +114.176486171782 1.14223645857319 +114.201010961086 1.16396763211361 +114.225535750389 1.19569808729495 +114.250060539693 1.20858582069495 +114.274585328996 1.23104531590248 +114.2991101183 1.22669532969473 +114.323634907603 1.20646913878683 +114.348159696907 1.18587067374236 +114.37268448621 1.18783968447813 +114.397209275514 1.17413124893836 +114.421734064817 1.131081101139 +114.446258854121 1.10561859408758 +114.470783643425 1.04776091398941 +114.495308432728 1.01863584801752 +114.519833222032 1.00585825419323 +114.544358011335 1.00361153858807 +114.568882800639 0.986964175079115 +114.593407589942 0.960225996811306 +114.617932379246 0.966417725747824 +114.642457168549 0.96886799627864 +114.666981957853 0.92910799377312 +114.691506747156 0.915079964754607 +114.71603153646 0.927843044797056 +114.740556325763 0.919012337636553 +114.765081115067 0.92455901907685 +114.789605904371 0.931277556990133 +114.814130693674 0.922891335503188 +114.838655482978 0.9126818857828 +114.863180272281 0.906260331399848 +114.887705061585 0.902412238216391 +114.912229850888 0.889785016180164 +114.936754640192 0.893984653448447 +114.961279429495 0.902265044718567 +114.985804218799 0.90607537889242 +115.010329008102 0.8828946171215 +115.034853797406 0.896512062860223 +115.059378586709 0.907903466744584 +115.083903376013 0.910073286913629 +115.108428165317 0.923194558025787 +115.13295295462 0.922221782698057 +115.157477743924 0.954640656619026 +115.182002533227 0.97299396127772 +115.206527322531 0.966691511009941 +115.231052111834 0.975853999103096 +115.255576901138 0.994389848961307 +115.280101690441 0.998483441980749 +115.304626479745 1.01676971466465 +115.329151269048 0.993172112506901 +115.353676058352 1.00389470282027 +115.378200847656 1.0104658352866 +115.402725636959 0.957502556352532 +115.427250426263 0.97829737238747 +115.451775215566 0.981930561772718 +115.47630000487 0.978426276846289 +115.500824794173 0.936705333009682 +115.525349583477 0.940195115578565 +115.54987437278 0.937429414873591 +115.574399162084 0.925479413745034 +115.598923951387 0.901217512886309 +115.623448740691 0.890858649504076 +115.647973529994 0.880725310856519 +115.672498319298 0.874393222550951 +115.697023108602 0.868308477874726 +115.721547897905 0.869864912705881 +115.746072687209 0.861776508723032 +115.770597476512 0.832829103526696 +115.795122265816 0.85645500240864 +115.819647055119 0.850803619989741 +115.844171844423 0.858169064530195 +115.868696633726 0.837745787879045 +115.89322142303 0.867677574353927 +115.917746212333 0.861571831375855 +115.942271001637 0.853919259949678 +115.966795790941 0.858566437029307 +115.991320580244 0.861118536937237 +116.015845369548 0.859968328246918 +116.040370158851 0.869413480175392 +116.064894948155 0.897406908253243 +116.089419737458 0.898587909024818 +116.113944526762 0.885791767126604 +116.138469316065 0.899195012324191 +116.162994105369 0.917784381421587 +116.187518894672 0.943766921578607 +116.212043683976 0.940341161849629 +116.236568473279 0.939506376642993 +116.261093262583 1.01108506973891 +116.285618051887 1.03046326017814 +116.31014284119 1.03147883932144 +116.334667630494 1.05364043579244 +116.359192419797 1.06235867717553 +116.383717209101 1.08931182223299 +116.408241998404 1.10927798418546 +116.432766787708 1.10124233066858 +116.457291577011 1.11798075839431 +116.481816366315 1.10913536185861 +116.506341155618 1.09053920142622 +116.530865944922 1.12103474002848 +116.555390734226 1.08155323817992 +116.579915523529 1.04882932470488 +116.604440312833 1.04177568599577 +116.628965102136 1.01931614786822 +116.65348989144 1.02582835386774 +116.678014680743 0.994762053289743 +116.702539470047 0.966414716612935 +116.72706425935 0.943060527052961 +116.751589048654 0.923359990952127 +116.776113837957 0.937325396828173 +116.800638627261 0.93046856671263 +116.825163416564 0.927813575914622 +116.849688205868 0.906965082909442 +116.874212995172 0.896915586554925 +116.898737784475 0.884675278419988 +116.923262573779 0.863440410222827 +116.947787363082 0.893415408379656 +116.972312152386 0.894364577572792 +116.996836941689 0.896254053417751 +117.021361730993 0.91072355228446 +117.045886520296 0.892769274691464 +117.0704113096 0.90222430806979 +117.094936098903 0.898624651524357 +117.119460888207 0.88986744078723 +117.143985677511 0.905875743574028 +117.168510466814 0.911602998354733 +117.193035256118 0.900105036317467 +117.217560045421 0.909012312153905 +117.242084834725 0.922608942127081 +117.266609624028 0.946263732995962 +117.291134413332 0.949161178265523 +117.315659202635 0.937453516923714 +117.340183991939 0.958349605511687 +117.364708781242 0.956931041509231 +117.389233570546 0.982361622633581 +117.413758359849 0.972751826875869 +117.438283149153 1.01247102488112 +117.462807938457 1.03471809814032 +117.48733272776 1.04160634684311 +117.511857517064 1.07376129700663 +117.536382306367 1.07890674945821 +117.560907095671 1.07510156808466 +117.585431884974 1.0795557165617 +117.609956674278 1.12378207519583 +117.634481463581 1.0885531167297 +117.659006252885 1.11537508755382 +117.683531042188 1.1111850299067 +117.708055831492 1.09513305783485 +117.732580620795 1.1378351033559 +117.757105410099 1.16529632217231 +117.781630199403 1.17546229656824 +117.806154988706 1.18687349970808 +117.83067977801 1.19647168964122 +117.855204567313 1.2127297366748 +117.879729356617 1.24850605163314 +117.90425414592 1.23970786749286 +117.928778935224 1.27916793824171 +117.953303724527 1.33872675156427 +117.977828513831 1.38677053545305 +118.002353303134 1.40298305615351 +118.026878092438 1.42729454730274 +118.051402881742 1.44727874134218 +118.075927671045 1.51816160637838 +118.100452460349 1.5535884759144 +118.124977249652 1.54644577912053 +118.149502038956 1.54067842113997 +118.174026828259 1.5134436106425 +118.198551617563 1.47614970409792 +118.223076406866 1.42166751719552 +118.24760119617 1.37600137749125 +118.272125985473 1.37315189275085 +118.296650774777 1.3121097962718 +118.32117556408 1.29236349695723 +118.345700353384 1.2733817391319 +118.370225142688 1.23583514693092 +118.394749931991 1.20719239336745 +118.419274721295 1.15700663985501 +118.443799510598 1.15517460285651 +118.468324299902 1.1519040944795 +118.492849089205 1.12203658779656 +118.517373878509 1.11850413894551 +118.541898667812 1.11351021428211 +118.566423457116 1.09263287902671 +118.590948246419 1.09177359728452 +118.615473035723 1.11044486566062 +118.639997825027 1.14011606427312 +118.66452261433 1.08613140669182 +118.689047403634 1.09085141525018 +118.713572192937 1.08408576416245 +118.738096982241 1.05817037539885 +118.762621771544 1.0681041397628 +118.787146560848 1.07877805234094 +118.811671350151 1.0699467681587 +118.836196139455 1.0634666390006 +118.860720928758 1.02253158639426 +118.885245718062 1.04168289740427 +118.909770507365 1.04367169609746 +118.934295296669 1.07023606547172 +118.958820085973 1.0530559289343 +118.983344875276 1.06240782128723 +119.00786966458 1.07286997004497 +119.032394453883 1.06519163531161 +119.056919243187 1.09166160918163 +119.08144403249 1.1236583685402 +119.105968821794 1.12983104218921 +119.130493611097 1.15408381433941 +119.155018400401 1.13169595377335 +119.179543189704 1.17328325295105 +119.204067979008 1.21724978328082 +119.228592768312 1.20653525555542 +119.253117557615 1.21557944882816 +119.277642346919 1.23020058612833 +119.302167136222 1.2210630588643 +119.326691925526 1.2067879860885 +119.351216714829 1.22105793560308 +119.375741504133 1.17184715277715 +119.400266293436 1.13516700112442 +119.42479108274 1.11518945880369 +119.449315872043 1.11590296934162 +119.473840661347 1.09502868822401 +119.49836545065 1.07168037940041 +119.522890239954 1.03854106133929 +119.547415029258 1.01786179475862 +119.571939818561 1.00110658139366 +119.596464607865 0.986445259661204 +119.620989397168 0.971375034020792 +119.645514186472 0.997136343464421 +119.670038975775 0.971400741027011 +119.694563765079 0.947754308010561 +119.719088554382 0.965754612586031 +119.743613343686 0.963411955030612 +119.768138132989 0.93914589099353 +119.792662922293 0.946334477236481 +119.817187711596 0.935923773986893 +119.8417125009 0.932160138304398 +119.866237290204 0.927727808722018 +119.890762079507 0.935689007689297 +119.915286868811 0.950615862869121 +119.939811658114 0.906163704433517 +119.964336447418 0.910255579768704 +119.988861236721 0.916142882528666 +120.013386026025 0.908677044285606 +120.037910815328 0.922816917183132 +120.062435604632 0.892899646446743 +120.086960393935 0.88458368679197 +120.111485183239 0.897407710731292 +120.136009972543 0.887069539088385 +120.160534761846 0.860787914655642 +120.18505955115 0.851504535081153 +120.209584340453 0.862887648712107 +120.234109129757 0.895856399161363 +120.25863391906 0.873626310109582 +120.283158708364 0.871014544374467 +120.307683497667 0.868826942380008 +120.332208286971 0.882423083254365 +120.356733076274 0.884703173968277 +120.381257865578 0.874527895017988 +120.405782654881 0.875300212604094 +120.430307444185 0.866509144349861 +120.454832233489 0.861268439697902 +120.479357022792 0.879616616361545 +120.503881812096 0.881647355357463 +120.528406601399 0.885227142839273 +120.552931390703 0.901077266245416 +120.577456180006 0.912000476765294 +120.60198096931 0.937459442662988 +120.626505758613 0.902781602439056 +120.651030547917 0.905582082557814 +120.67555533722 0.89755941897289 +120.700080126524 0.918157200957946 +120.724604915828 0.938018229133959 +120.749129705131 0.942202248313653 +120.773654494435 0.967417887453603 +120.798179283738 0.973088424507226 +120.822704073042 0.98853818439414 +120.847228862345 0.980023121566045 +120.871753651649 0.993269271492067 +120.896278440952 0.996013509727853 +120.920803230256 0.996041229430315 +120.945328019559 1.00668251768437 +120.969852808863 0.985049700092599 +120.994377598166 0.9719609249863 +121.01890238747 0.999870104987615 +121.043427176774 0.982981785799283 +121.067951966077 0.994339532691373 +121.092476755381 0.966619123627134 +121.117001544684 0.957996066719154 +121.141526333988 0.936577864351608 +121.166051123291 0.955308415367522 +121.190575912595 0.930226188739693 +121.215100701898 0.914385739568093 +121.239625491202 0.930250565856618 +121.264150280505 0.913092250296747 +121.288675069809 0.908939009564891 +121.313199859113 0.913102037259447 +121.337724648416 0.871398994666885 +121.36224943772 0.871138846166597 +121.386774227023 0.902215091438351 +121.411299016327 0.900003306161329 +121.43582380563 0.913746685536407 +121.460348594934 0.875840615422673 +121.484873384237 0.89155079205997 +121.509398173541 0.896294332463425 +121.533922962844 0.915419314568821 +121.558447752148 0.908935849515654 +121.582972541451 0.917712697169617 +121.607497330755 0.914658007797026 +121.632022120059 0.937858099922261 +121.656546909362 0.922101901036315 +121.681071698666 0.915532945813996 +121.705596487969 0.93529960998148 +121.730121277273 0.950455803465566 +121.754646066576 0.933027683285634 +121.77917085588 0.945911981569591 +121.803695645183 0.944584375746224 +121.828220434487 0.965929908973688 +121.85274522379 0.951335644021272 +121.877270013094 0.949115764534326 +121.901794802397 0.968852051429877 +121.926319591701 0.957412375720013 +121.950844381005 0.989492474765619 +121.975369170308 0.976072288533093 +121.999893959612 1.01107528277621 +122.024418748915 1.02763711136559 +122.048943538219 1.03844437794577 +122.073468327522 1.05078981524957 +122.097993116826 1.07990985718121 +122.122517906129 1.08560929291225 +122.147042695433 1.09424750557188 +122.171567484736 1.10026036338842 +122.19609227404 1.09865319968713 +122.220617063344 1.11390085856389 +122.245141852647 1.15276118755339 +122.269666641951 1.15715301276002 +122.294191431254 1.18263416041586 +122.318716220558 1.17487707515497 +122.343241009861 1.20010421408182 +122.367765799165 1.24326858826286 +122.392290588468 1.26131347928623 +122.416815377772 1.29309855132302 +122.441340167075 1.29165112695559 +122.465864956379 1.33087308894754 +122.490389745682 1.37413662351193 +122.514914534986 1.40966355525656 +122.53943932429 1.46812457677789 +122.563964113593 1.4899976469182 +122.588488902897 1.54156268169983 +122.6130136922 1.61651895743885 +122.637538481504 1.57334417439671 +122.662063270807 1.59735849278883 +122.686588060111 1.5857720678033 +122.711112849414 1.5747999881123 +122.735637638718 1.60806040487006 +122.760162428021 1.58756380764136 +122.784687217325 1.53810926734271 +122.809212006629 1.4819736106502 +122.833736795932 1.48467206534837 +122.858261585236 1.4728513632903 +122.882786374539 1.40470200938951 +122.907311163843 1.37992861568767 +122.931835953146 1.36524237131228 +122.95636074245 1.37206793167204 +122.980885531753 1.35047587553429 +123.005410321057 1.34671188828638 +123.02993511036 1.33888771474974 +123.054459899664 1.29405576972319 +123.078984688967 1.28296789713307 +123.103509478271 1.31610839391666 +123.128034267575 1.31260692429076 +123.152559056878 1.33394682691717 +123.177083846182 1.33741529124074 +123.201608635485 1.36940495270179 +123.226133424789 1.34245411573128 +123.250658214092 1.33241125954493 +123.275183003396 1.35527342427575 +123.299707792699 1.30358756113046 +123.324232582003 1.32481419537517 +123.348757371306 1.33459481121543 +123.37328216061 1.27983372248015 +123.397806949914 1.25850910644833 +123.422331739217 1.22749579978116 +123.446856528521 1.21787365871225 +123.471381317824 1.18292348253413 +123.495906107128 1.19111556637792 +123.520430896431 1.1961697901912 +123.544955685735 1.18386258761488 +123.569480475038 1.1930169003919 +123.594005264342 1.20791911468606 +123.618530053645 1.19266346763705 +123.643054842949 1.18872370159314 +123.667579632252 1.21278121367867 +123.692104421556 1.20572138085395 +123.71662921086 1.22327047362143 +123.741154000163 1.20905045731564 +123.765678789467 1.2450969578723 +123.79020357877 1.23746004284714 +123.814728368074 1.24229432739766 +123.839253157377 1.2573163556283 +123.863777946681 1.31382948205146 +123.888302735984 1.32608367260219 +123.912827525288 1.31342318149489 +123.937352314591 1.3103265337048 +123.961877103895 1.31464237855529 +123.986401893198 1.29494867210187 +124.010926682502 1.27275215329764 +124.035451471806 1.26407620193125 +124.059976261109 1.24719504457442 +124.084501050413 1.2195812938687 +124.109025839716 1.2058062331349 +124.13355062902 1.20618961777384 +124.158075418323 1.1859189836194 +124.182600207627 1.17812567665549 +124.20712499693 1.17607599451533 +124.231649786234 1.13630928224388 +124.256174575537 1.137873106694 +124.280699364841 1.14465000218612 +124.305224154145 1.10857734488039 +124.329748943448 1.12219401179336 +124.354273732752 1.13185117886837 +124.378798522055 1.11154082726825 +124.403323311359 1.12132117617126 +124.427848100662 1.13337570554639 +124.452372889966 1.12802836991215 +124.476897679269 1.12707178964425 +124.501422468573 1.13144171402527 +124.525947257876 1.13302360953542 +124.55047204718 1.13380136829383 +124.574996836483 1.10924000070417 +124.599521625787 1.11504711414829 +124.624046415091 1.11623071349956 +124.648571204394 1.09140394262801 +124.673095993698 1.07550744876566 +124.697620783001 1.08215306153359 +124.722145572305 1.05224879778354 +124.746670361608 1.03750708912224 +124.771195150912 1.03016077797952 +124.795719940215 1.02924961188464 +124.820244729519 1.02939261736941 +124.844769518822 1.01138972010101 +124.869294308126 1.0117691230838 +124.89381909743 1.01147504753598 +124.918343886733 1.00909918134255 +124.942868676037 1.0052173027392 +124.96739346534 1.00347734512944 +124.991918254644 1.00414155717407 +125.016443043947 0.990652520605602 +125.040967833251 0.990584935337111 +125.065492622554 0.996456911473258 +125.090017411858 1.00306085313953 +125.114542201161 1.00124399588161 +125.139066990465 0.994391148836046 +125.163591779768 0.996869175328321 +125.188116569072 1.01998975042926 +125.212641358376 1.01135279424465 +125.237166147679 1.00968366270957 +125.261690936983 1.02646113473946 +125.286215726286 1.03630992316233 +125.31074051559 1.03927088600643 +125.335265304893 1.03399003653063 +125.359790094197 1.05155522941212 +125.3843148835 1.07095753872864 +125.408839672804 1.09664967463179 +125.433364462107 1.12545609977674 +125.457889251411 1.13593964126707 +125.482414040715 1.1511987986305 +125.506938830018 1.15944987216397 +125.531463619322 1.17217696799835 +125.555988408625 1.21499204638382 +125.580513197929 1.18782802373697 +125.605037987232 1.17687103464756 +125.629562776536 1.1984485119544 +125.654087565839 1.18670922769662 +125.678612355143 1.19104741929905 +125.703137144446 1.20838676805363 +125.72766193375 1.19504618981059 +125.752186723053 1.18061942296941 +125.776711512357 1.17079008191681 +125.801236301661 1.1468520712301 +125.825761090964 1.11999324409686 +125.850285880268 1.08854021893271 +125.874810669571 1.09140824481251 +125.899335458875 1.09039775907999 +125.923860248178 1.05339930053488 +125.948385037482 1.02476141539715 +125.972909826785 1.02475842015657 +125.997434616089 1.02650083192017 +126.021959405392 1.01638925186466 +126.046484194696 1.0095465897387 +126.071008983999 1.0014088670144 +126.095533773303 0.982702404848601 +126.120058562607 0.975308961840186 +126.14458335191 0.970959154280684 +126.169108141214 1.00044420661602 +126.193632930517 0.981065082144692 +126.218157719821 0.966581535599087 +126.242682509124 0.981989051111792 +126.267207298428 0.981739699225483 +126.291732087731 0.95337963732802 +126.316256877035 0.94361410115318 +126.340781666338 0.958337369272033 +126.365306455642 0.953653722386444 +126.389831244946 0.953354103865936 +126.414356034249 0.963506226487599 +126.438880823553 0.979667814208001 +126.463405612856 0.98044107611439 +126.48793040216 0.968693090021298 +126.512455191463 0.973168845843623 +126.536979980767 0.981300089989401 +126.56150477007 0.978733449748126 +126.586029559374 0.977447908800731 +126.610554348677 0.979190601416925 +126.635079137981 0.993702637372347 +126.659603927284 0.982635418339569 +126.684128716588 0.985548600161169 +126.708653505892 1.00536140220887 +126.733178295195 1.01003928962817 +126.757703084499 1.02285910257539 +126.782227873802 1.05181257556784 +126.806752663106 1.04833694397623 +126.831277452409 1.07338294999928 +126.855802241713 1.08051767964331 +126.880327031016 1.08729595056202 +126.90485182032 1.08764568487812 +126.929376609623 1.09334215571554 +126.953901398927 1.11066433399403 +126.978426188231 1.12099816950775 +127.002950977534 1.12956200080905 +127.027475766838 1.12771308276264 +127.052000556141 1.11094272008822 +127.076525345445 1.11883367206019 +127.101050134748 1.13362700007254 +127.125574924052 1.1157705238511 +127.150099713355 1.11735172449041 +127.174624502659 1.13423882844059 +127.199149291962 1.13118341020199 +127.223674081266 1.15300933792754 +127.248198870569 1.12867636876535 +127.272723659873 1.13056318833264 +127.297248449177 1.15219652811793 +127.32177323848 1.1492926771413 +127.346298027784 1.14108874619511 +127.370822817087 1.17235133280287 +127.395347606391 1.18895720419504 +127.419872395694 1.19873094409317 +127.444397184998 1.20228311872232 +127.468921974301 1.19502120529971 +127.493446763605 1.16865574998139 +127.517971552908 1.16284336475597 +127.542496342212 1.16522392691662 +127.567021131516 1.14688217936408 +127.591545920819 1.13275038971505 +127.616070710123 1.11415917984973 +127.640595499426 1.09565456866395 +127.66512028873 1.08860154677016 +127.689645078033 1.09590549977539 +127.714169867337 1.09399775561506 +127.73869465664 1.05169654350746 +127.763219445944 1.02147764901062 +127.787744235247 1.01984672185184 +127.812269024551 1.02221228082886 +127.836793813854 1.0117845130988 +127.861318603158 1.01083934225012 +127.885843392462 1.00188662483918 +127.910368181765 0.988758170241945 +127.934892971069 0.981580703198967 +127.959417760372 0.986677484774103 +127.983942549676 0.97041745361134 +128.008467338979 0.963460720385796 +128.032992128283 0.975837771066389 +128.057516917586 0.968476651024639 +128.08204170689 0.955799933841486 +128.106566496193 0.948553062482993 +128.131091285497 0.93595566631853 +128.1556160748 0.963899622762891 +128.180140864104 0.961234691567335 +128.204665653408 0.948744023716807 +128.229190442711 0.946134217301425 +128.253715232015 0.940995551960156 +128.278240021318 0.923634504478538 +128.302764810622 0.938166543235508 +128.327289599925 0.960263983545255 +128.351814389229 0.950281943278398 +128.376339178532 0.942824372154014 +128.400863967836 0.944380198741228 +128.425388757139 0.944209208733297 +128.449913546443 0.939962050777646 +128.474438335747 0.942820227995116 +128.49896312505 0.950767474048368 +128.523487914354 0.967745484517927 +128.548012703657 0.950681909619174 +128.572537492961 0.951449604134752 +128.597062282264 0.972525450763781 +128.621587071568 0.992289384284156 +128.646111860871 0.98583564823817 +128.670636650175 0.985990007943657 +128.695161439478 1.00655986498021 +128.719686228782 0.997114334219486 +128.744211018085 1.00895517657043 +128.768735807389 1.01031498236516 +128.793260596693 1.0176815158369 +128.817785385996 1.01145557221301 +128.8423101753 1.03664334593704 +128.866834964603 1.03123768065143 +128.891359753907 1.02782673210634 +128.91588454321 1.03168855865953 +128.940409332514 1.03281748091584 +128.964934121817 1.00440861449658 +128.989458911121 1.01167315977452 +129.013983700424 0.993567350841919 +129.038508489728 0.999478463513975 +129.063033279032 0.979735279860092 +129.087558068335 0.979832795619989 +129.112082857639 0.973310123179226 +129.136607646942 0.972431000111523 +129.161132436246 0.958059636517259 +129.185657225549 0.952993007670842 +129.210182014853 0.961456396631051 +129.234706804156 0.969356144075497 +129.25923159346 0.96586107901634 +129.283756382763 0.947372808296666 +129.308281172067 0.943702295434772 +129.33280596137 0.937619441742883 +129.357330750674 0.927049244077037 +129.381855539978 0.936001568117626 +129.406380329281 0.941503124547985 +129.430905118585 0.931423641009771 +129.455429907888 0.924277592402553 +129.479954697192 0.926470812631209 +129.504479486495 0.926353916805521 +129.529004275799 0.90939023099623 +129.553529065102 0.918830281169773 +129.578053854406 0.920878737779395 +129.602578643709 0.912502737008191 +129.627103433013 0.916112990656858 +129.651628222317 0.926309564393727 +129.67615301162 0.926902309778235 +129.700677800924 0.919970639089636 +129.725202590227 0.931459888342778 +129.749727379531 0.929556909221446 +129.774252168834 0.906292024990776 +129.798776958138 0.903994078910949 +129.823301747441 0.930538786723035 +129.847826536745 0.934952409616791 +129.872351326048 0.936344254933566 +129.896876115352 0.931167341946736 +129.921400904655 0.933986932109688 +129.945925693959 0.929484203982264 +129.970450483263 0.914066770440914 +129.994975272566 0.914478841955399 +130.01950006187 0.912834388069926 +130.044024851173 0.933928911558168 +130.068549640477 0.924155096108188 +130.09307442978 0.924276680537375 +130.117599219084 0.93468725178267 +130.142124008387 0.923342495104458 +130.166648797691 0.917798247077539 +130.191173586994 0.943756490853549 +130.215698376298 0.9391551303305 +130.240223165601 0.933802624711127 +130.264747954905 0.930292335821592 +130.289272744209 0.942313852307652 +130.313797533512 0.942035922087145 +130.338322322816 0.936897104611255 +130.362847112119 0.93243811847202 +130.387371901423 0.942722841424465 +130.411896690726 0.941227137762598 +130.43642148003 0.927782446477839 +130.460946269333 0.9395336753506 +130.485471058637 0.941860136583837 +130.50999584794 0.962201794484869 +130.534520637244 0.973450544908534 +130.559045426548 0.961260269152878 +130.583570215851 0.949895554591858 +130.608095005155 0.962284476279607 +130.632619794458 0.960776565765673 +130.657144583762 0.968837645129007 +130.681669373065 0.967286475746356 +130.706194162369 0.975972727790162 +130.730718951672 0.966916891558613 +130.755243740976 0.94895569743883 +130.779768530279 0.972737290758785 +130.804293319583 0.962706580019432 +130.828818108886 0.957279298274352 +130.85334289819 0.955960944996379 +130.877867687494 0.964807102634396 +130.902392476797 0.962335237226145 +130.926917266101 0.955564557928193 +130.951442055404 0.957467065358558 +130.975966844708 0.955991402436167 +131.000491634011 0.955413885594824 +131.025016423315 0.958948143566591 +131.049541212618 0.947758876808308 +131.074066001922 0.963425382915033 +131.098590791225 0.979228294367577 +131.123115580529 0.962441268695763 +131.147640369833 0.940359207090324 +131.172165159136 0.964038953865158 +131.19668994844 0.965550239735323 +131.221214737743 0.964084455460359 +131.245739527047 0.98280890468078 +131.27026431635 0.964570129911392 +131.294789105654 0.955003888147995 +131.319313894957 0.952373700170345 +131.343838684261 0.961651775807175 +131.368363473564 0.975352828864479 +131.392888262868 0.988497678095401 +131.417413052171 0.977369432283835 +131.441937841475 0.977233007939597 +131.466462630779 0.972150524338578 +131.490987420082 0.981807955329604 +131.515512209386 0.986497771487683 +131.540036998689 0.990427028288161 +131.564561787993 0.99980897334513 +131.589086577296 1.00974191430542 +131.6136113666 1.00176854448419 +131.638136155903 1.00455661146971 +131.662660945207 1.0133177686812 +131.68718573451 1.00641594368331 +131.711710523814 1.0155898093423 +131.736235313118 1.00309281557806 +131.760760102421 1.02091169319293 +131.785284891725 1.03595220267692 +131.809809681028 1.05781263429921 +131.834334470332 1.04684751868785 +131.858859259635 1.05512651650918 +131.883384048939 1.06775969344084 +131.907908838242 1.079056333664 +131.932433627546 1.0879711279047 +131.956958416849 1.06983237936684 +131.981483206153 1.07212365190608 +132.006007995456 1.11601670957472 +132.03053278476 1.12178224301076 +132.055057574064 1.12147878479254 +132.079582363367 1.13248848224191 +132.104107152671 1.13901678653128 +132.128631941974 1.16015016175018 +132.153156731278 1.15206139540676 +132.177681520581 1.17897632722525 +132.202206309885 1.2059572072576 +132.226731099188 1.22461922049641 +132.251255888492 1.23951293250252 +132.275780677795 1.26813959282351 +132.300305467099 1.27712734803948 +132.324830256402 1.29779401842844 +132.349355045706 1.32349813383259 +132.37387983501 1.33072128059317 +132.398404624313 1.35676567662816 +132.422929413617 1.3831433420937 +132.44745420292 1.41493364551432 +132.471978992224 1.43310183239095 +132.496503781527 1.46205705563704 +132.521028570831 1.46513874822475 +132.545553360134 1.4926583198335 +132.570078149438 1.50974692203542 +132.594602938741 1.51334538414819 +132.619127728045 1.50990944744836 +132.643652517349 1.51672852884655 +132.668177306652 1.51232139068454 +132.692702095956 1.50907032622682 +132.717226885259 1.4927873745083 +132.741751674563 1.47050796838001 +132.766276463866 1.45777640251753 +132.79080125317 1.44017702067977 +132.815326042473 1.41963479489176 +132.839850831777 1.4026394108887 +132.86437562108 1.38324114539625 +132.888900410384 1.36093791741753 +132.913425199687 1.35336579899789 +132.937949988991 1.34848432146368 +132.962474778295 1.33552272386179 +132.986999567598 1.32636778708109 +133.011524356902 1.32880406501098 +133.036049146205 1.33522944702858 +133.060573935509 1.34083109839643 +133.085098724812 1.34347155432519 +133.109623514116 1.34336763183585 +133.134148303419 1.34986765755652 +133.158673092723 1.35823358236374 +133.183197882026 1.34617557192154 +133.20772267133 1.32903858706152 +133.232247460634 1.31155569162522 +133.256772249937 1.33953824507225 +133.281297039241 1.33019210444523 +133.305821828544 1.32786638780462 +133.330346617848 1.32890757285592 +133.354871407151 1.32000561237424 +133.379396196455 1.29430059180164 +133.403920985758 1.29063538978397 +133.428445775062 1.29620640581006 +133.452970564365 1.2941989037572 +133.477495353669 1.27677799612701 +133.502020142972 1.26502677271671 +133.526544932276 1.24907666100461 +133.55106972158 1.24758838981956 +133.575594510883 1.23034774368776 +133.600119300187 1.22595442304845 +133.62464408949 1.21867274985677 +133.649168878794 1.22518821368091 +133.673693668097 1.20711548209899 +133.698218457401 1.20219594564444 +133.722743246704 1.2084373279932 +133.747268036008 1.19829176755165 +133.771792825311 1.21725592865941 +133.796317614615 1.21384636900465 +133.820842403919 1.21080021196819 +133.845367193222 1.22403094929029 +133.869891982526 1.23311839500881 +133.894416771829 1.24791403337328 +133.918941561133 1.25717039358879 +133.943466350436 1.24828014967266 +133.96799113974 1.2777782062644 +133.992515929043 1.27588712267192 +134.017040718347 1.28790643950693 +134.04156550765 1.28471168378194 +134.066090296954 1.30167057129574 +134.090615086257 1.31949210567434 +134.115139875561 1.31781267084892 +134.139664664865 1.32137181089593 +134.164189454168 1.32082919585394 +134.188714243472 1.32934603499039 +134.213239032775 1.32686525146226 +134.237763822079 1.32112760311529 +134.262288611382 1.34228791826264 +134.286813400686 1.33355374653971 +134.311338189989 1.32746013119628 +134.335862979293 1.31174672142152 +134.360387768596 1.28556495601753 +134.3849125579 1.27324434192394 +134.409437347203 1.27035478601489 +134.433962136507 1.26399366863488 +134.458486925811 1.23856245935534 +134.483011715114 1.24596292618026 +134.507536504418 1.24030022112133 +134.532061293721 1.24002931420615 +134.556586083025 1.24038869416231 +134.581110872328 1.24239975127108 +134.605635661632 1.2356246182602 +134.630160450935 1.21490096082513 +134.654685240239 1.21406992261116 +134.679210029542 1.23586376985334 +134.703734818846 1.25099042356367 +134.72825960815 1.24156628971119 +134.752784397453 1.2262120964891 +134.777309186757 1.24236992830055 +134.80183397606 1.24699770183787 +134.826358765364 1.25110655194541 +134.850883554667 1.24533065560608 +134.875408343971 1.23687800491401 +134.899933133274 1.23602452953929 +134.924457922578 1.2271990892999 +134.948982711881 1.23994939666752 +134.973507501185 1.2306529486203 +134.998032290488 1.22357418438159 +135.022557079792 1.22369931419407 +135.047081869096 1.20883963682139 +135.071606658399 1.20996404036159 +135.096131447703 1.21308132793295 +135.120656237006 1.20455199194182 +135.14518102631 1.20822549173612 +135.169705815613 1.21011534419403 +135.194230604917 1.21706687405727 +135.21875539422 1.20675782697973 +135.243280183524 1.2055103909926 +135.267804972827 1.20728804875332 +135.292329762131 1.20343898540038 +135.316854551435 1.19340271829966 +135.341379340738 1.20588151514968 +135.365904130042 1.21857690912078 +135.390428919345 1.22650973960925 +135.414953708649 1.22593229999584 +135.439478497952 1.23003777136363 +135.464003287256 1.24588828243443 +135.488528076559 1.23837532506147 +135.513052865863 1.2525929537887 +135.537577655166 1.26284629543663 +135.56210244447 1.27345014824262 +135.586627233773 1.28352010569635 +135.611152023077 1.30892082074692 +135.635676812381 1.33212266494129 +135.660201601684 1.32711195119975 +135.684726390988 1.35583197590623 +135.709251180291 1.36222578652871 +135.733775969595 1.38383094029548 +135.758300758898 1.41413963159788 +135.782825548202 1.43132165986958 +135.807350337505 1.45787751855707 +135.831875126809 1.48036819693313 +135.856399916112 1.50046635682284 +135.880924705416 1.5046524733416 +135.90544949472 1.55082645860798 +135.929974284023 1.55600919644436 +135.954499073327 1.5864485079454 +135.97902386263 1.58453975233188 +136.003548651934 1.57500665177457 +136.028073441237 1.57689199443835 +136.052598230541 1.59143497584255 +136.077123019844 1.58434623207523 +136.101647809148 1.57158168073955 +136.126172598451 1.56114132014224 +136.150697387755 1.55224963167645 +136.175222177058 1.52888150749422 +136.199746966362 1.49232689384815 +136.224271755666 1.47493230119393 +136.248796544969 1.46312021521693 +136.273321334273 1.43626751033094 +136.297846123576 1.42645998266385 +136.32237091288 1.41069340173164 +136.346895702183 1.37841978535723 +136.371420491487 1.36127324534739 +136.39594528079 1.34057803473034 +136.420470070094 1.32935326104528 +136.444994859397 1.31268564824779 +136.469519648701 1.30237617920012 +136.494044438004 1.30222429169009 +136.518569227308 1.29219896679477 +136.543094016612 1.27067532653354 +136.567618805915 1.2569792229505 +136.592143595219 1.25509037239193 +136.616668384522 1.25434747590818 +136.641193173826 1.24513613047134 +136.665717963129 1.24501880947919 +136.690242752433 1.24216088767572 +136.714767541736 1.23653724906616 +136.73929233104 1.23833144303322 +136.763817120343 1.23649769739461 +136.788341909647 1.22355356023722 +136.812866698951 1.22007504383434 +136.837391488254 1.23375726101296 +136.861916277558 1.22517390262691 +136.886441066861 1.22203302968056 +136.910965856165 1.22273889716348 +136.935490645468 1.21379194567611 +136.960015434772 1.21087171830978 +136.984540224075 1.2255356517842 +137.009065013379 1.22860259983186 +137.033589802682 1.22039898831945 +137.058114591986 1.22448483712866 +137.082639381289 1.22600326443355 +137.107164170593 1.23952187528471 +137.131688959897 1.23272833296309 +137.1562137492 1.24618816599974 +137.180738538504 1.25310988202824 +137.205263327807 1.23411461993932 +137.229788117111 1.24316495243972 +137.254312906414 1.27585946141642 +137.278837695718 1.2708036529974 +137.303362485021 1.29664031413907 +137.327887274325 1.31445681323841 +137.352412063628 1.32579098819754 +137.376936852932 1.34037797931702 +137.401461642236 1.32721976381837 +137.425986431539 1.35046931463784 +137.450511220843 1.38981892416131 +137.475036010146 1.40440982068863 +137.49956079945 1.4224004005008 +137.524085588753 1.43254477769576 +137.548610378057 1.44033423353345 +137.57313516736 1.43285530453712 +137.597659956664 1.46878054481794 +137.622184745967 1.49015397531619 +137.646709535271 1.51053618942575 +137.671234324574 1.52663752860606 +137.695759113878 1.543884206187 +137.720283903182 1.55710749014305 +137.744808692485 1.57285331421275 +137.769333481789 1.59506430556806 +137.793858271092 1.59947949196182 +137.818383060396 1.62998081668096 +137.842907849699 1.63792151954111 +137.867432639003 1.63917906902918 +137.891957428306 1.65489745566272 +137.91648221761 1.67000475723906 +137.941007006913 1.68716724034259 +137.965531796217 1.68719045079455 +137.990056585521 1.69795005599889 +138.014581374824 1.72111752742218 +138.039106164128 1.7276316728872 +138.063630953431 1.73595944493643 +138.088155742735 1.74583698781867 +138.112680532038 1.74064180058802 +138.137205321342 1.74325089297832 +138.161730110645 1.74025058686473 +138.186254899949 1.73562758392578 +138.210779689252 1.73656319489379 +138.235304478556 1.7197631989784 +138.259829267859 1.711176428274 +138.284354057163 1.69195539708933 +138.308878846467 1.69350081481884 +138.33340363577 1.67156345533343 +138.357928425074 1.63803532816322 +138.382453214377 1.6198317556846 +138.406978003681 1.59672980195363 +138.431502792984 1.58114313695668 +138.456027582288 1.55860102552905 +138.480552371591 1.54187166608991 +138.505077160895 1.53592747786868 +138.529601950198 1.51232761928683 +138.554126739502 1.50409723297401 +138.578651528805 1.49372358209506 +138.603176318109 1.48687759039912 +138.627701107413 1.49586552146092 +138.652225896716 1.48697196336832 +138.67675068602 1.47004231710003 +138.701275475323 1.45062438117525 +138.725800264627 1.46204517502664 +138.75032505393 1.46633098429982 +138.774849843234 1.4647773456425 +138.799374632537 1.46809458821918 +138.823899421841 1.47254338344236 +138.848424211144 1.4660596460222 +138.872949000448 1.45869688400305 +138.897473789752 1.46613388361804 +138.921998579055 1.45730113799503 +138.946523368359 1.43975399014968 +138.971048157662 1.43956480637825 +138.995572946966 1.43882767032046 +139.020097736269 1.43483212829156 +139.044622525573 1.41565594540353 +139.069147314876 1.40952656125445 +139.09367210418 1.40820808041771 +139.118196893483 1.39877541928258 +139.142721682787 1.386486695196 +139.16724647209 1.36784097772594 +139.191771261394 1.36172419128136 +139.216296050698 1.35771929490437 +139.240820840001 1.34767523909343 +139.265345629305 1.337062263683 +139.289870418608 1.33628851650408 +139.314395207912 1.3332491983567 +139.338919997215 1.32150668185175 +139.363444786519 1.3285236093738 +139.387969575822 1.31746347776494 +139.412494365126 1.30317330288545 +139.437019154429 1.30252268968739 +139.461543943733 1.31243750346179 +139.486068733037 1.30229732125446 +139.51059352234 1.3225740372174 +139.535118311644 1.31954152701717 +139.559643100947 1.30202390002045 +139.584167890251 1.3058846800102 +139.608692679554 1.31825837274907 +139.633217468858 1.32506371065141 +139.657742258161 1.33585682310848 +139.682267047465 1.32036629531122 +139.706791836768 1.33294485751727 +139.731316626072 1.34835650131552 +139.755841415375 1.34488543089596 +139.780366204679 1.35058009115227 +139.804890993983 1.37987409282868 +139.829415783286 1.39247202980149 +139.85394057259 1.40107460530875 +139.878465361893 1.40436854569092 +139.902990151197 1.4152229056999 +139.9275149405 1.4218101588038 +139.952039729804 1.4247243749451 +139.976564519107 1.43077213584533 +140.001089308411 1.39585523985731 +140.025614097714 1.4053089333558 +140.050138887018 1.40941275317868 +140.074663676322 1.43070108873493 diff --git a/docs/source/examples/example_data/CeO2_635um_zscan_200umSlit_chanClose_exported.xy b/docs/source/examples/example_data/CeO2_635um_zscan_200umSlit_chanClose_exported.xy new file mode 100644 index 00000000..ff53fffc --- /dev/null +++ b/docs/source/examples/example_data/CeO2_635um_zscan_200umSlit_chanClose_exported.xy @@ -0,0 +1,152 @@ +'Id: "" Comment: "" Operator: "Lab Manager" Anode: "Mo" Scantype: "Z" TimePerStep: "0.1" +-0.5000 438699.993 +-0.4900 437939.993 +-0.4800 436679.993 +-0.4700 434109.994 +-0.4600 439659.993 +-0.4500 436119.994 +-0.4400 435829.994 +-0.4300 438749.993 +-0.4200 432339.994 +-0.4100 431319.994 +-0.4000 435779.994 +-0.3900 437599.993 +-0.3800 434739.994 +-0.3700 433929.994 +-0.3600 436689.993 +-0.3500 433569.994 +-0.3400 435229.994 +-0.3300 437549.993 +-0.3200 434419.994 +-0.3100 435259.994 +-0.3000 437439.993 +-0.2900 437869.993 +-0.2800 434529.994 +-0.2700 436509.993 +-0.2600 434709.994 +-0.2500 432969.994 +-0.2400 434899.994 +-0.2300 432389.994 +-0.2200 436019.994 +-0.2100 437949.993 +-0.2000 437949.993 +-0.1900 435849.994 +-0.1800 437349.993 +-0.1700 435199.994 +-0.1600 439539.993 +-0.1500 434519.994 +-0.1400 436629.993 +-0.1300 435779.994 +-0.1200 437839.993 +-0.1100 433019.994 +-0.1000 440589.993 +-0.0900 440729.993 +-0.0800 432909.994 +-0.0700 435749.994 +-0.0600 439069.993 +-0.0500 437979.993 +-0.0400 437069.993 +-0.0300 436549.993 +-0.0200 430119.994 +-0.0100 426019.994 +0.0000 418459.994 +0.0100 403369.994 +0.0200 383859.994 +0.0300 365809.995 +0.0400 344199.995 +0.0500 320469.995 +0.0600 294339.996 +0.0700 266309.996 +0.0800 239429.996 +0.0900 209889.997 +0.1000 176649.997 +0.1100 144879.998 +0.1200 115089.998 +0.1300 92299.999 +0.1400 72569.999 +0.1500 58649.999 +0.1600 46629.999 +0.1700 38329.999 +0.1800 33500.000 +0.1900 29950.000 +0.2000 26210.000 +0.2100 24490.000 +0.2200 22100.000 +0.2300 21030.000 +0.2400 20140.000 +0.2500 18260.000 +0.2600 17220.000 +0.2700 17280.000 +0.2800 16080.000 +0.2900 16040.000 +0.3000 15250.000 +0.3100 15030.000 +0.3200 14610.000 +0.3300 13840.000 +0.3400 14300.000 +0.3500 14400.000 +0.3600 14140.000 +0.3700 13810.000 +0.3800 13060.000 +0.3900 13520.000 +0.4000 13930.000 +0.4100 13230.000 +0.4200 14140.000 +0.4300 14110.000 +0.4400 14730.000 +0.4500 13240.000 +0.4600 14850.000 +0.4700 14670.000 +0.4800 16110.000 +0.4900 16920.000 +0.5000 18110.000 +0.5100 18900.000 +0.5200 19600.000 +0.5300 21530.000 +0.5400 22640.000 +0.5500 25950.000 +0.5600 27170.000 +0.5700 31850.000 +0.5800 35309.999 +0.5900 40459.999 +0.6000 48749.999 +0.6100 60139.999 +0.6200 77359.999 +0.6300 98939.999 +0.6400 119429.998 +0.6500 144089.998 +0.6600 173249.997 +0.6700 196839.997 +0.6800 222189.997 +0.6900 251099.996 +0.7000 276849.996 +0.7100 306689.995 +0.7200 324769.995 +0.7300 349759.995 +0.7400 370219.994 +0.7500 389929.994 +0.7600 403689.994 +0.7700 415189.994 +0.7800 419159.994 +0.7900 426049.994 +0.8000 430569.994 +0.8100 433369.994 +0.8200 433519.994 +0.8300 431809.994 +0.8400 432409.994 +0.8500 431049.994 +0.8600 432439.994 +0.8700 429879.994 +0.8800 432939.994 +0.8900 430969.994 +0.9000 429989.994 +0.9100 432439.994 +0.9200 433599.994 +0.9300 431779.994 +0.9400 436329.993 +0.9500 432509.994 +0.9600 429609.994 +0.9700 432349.994 +0.9800 429109.994 +0.9900 430159.994 +1.0000 432219.994 diff --git a/docs/source/examples/example_data/zro2_mo.xy b/docs/source/examples/example_data/zro2_mo.xy new file mode 100644 index 00000000..0da57dad --- /dev/null +++ b/docs/source/examples/example_data/zro2_mo.xy @@ -0,0 +1,5630 @@ +2.00009989738464 47.3495377734889 +2.02462468668818 45.9664359001249 +2.04914947599173 43.3738432672084 +2.07367426529527 41.0358802678819 +2.09819905459881 40.3356487755093 +2.12272384390235 38.5503478218157 +2.14724863320589 37.4797459533122 +2.17177342250943 35.9519681517721 +2.19629821181297 34.2766209034924 +2.22082300111651 32.9918986612882 +2.24534779042006 32.0109958682539 +2.2698725797236 30.6163199206359 +2.29439736902714 29.9074078725728 +2.31892215833068 28.6458337788768 +2.34344694763422 27.8761578409797 +2.36797173693776 26.7592596754599 +2.3924965262413 25.7378476225363 +2.41702131554484 24.9681716846392 +2.44154610484838 24.1030096341458 +2.46607089415193 23.6371531454186 +2.49059568345547 22.4045142373579 +2.51512047275901 21.7476855234382 +2.53964526206255 21.0156253268669 +2.56417005136609 20.208333647644 +2.58869484066963 19.6325234535029 +2.61321962997317 18.8657410341694 +2.63774441927671 18.9699077024562 +2.66226920858026 17.6678243488709 +2.6867939978838 17.1440974888732 +2.71131878718734 16.8113428540681 +2.73584357649088 16.1255789545132 +2.76036836579442 15.4398150549582 +2.78489315509796 14.7598381925304 +2.8094179444015 14.7858798596021 +2.83394273370504 14.2129631840245 +2.85846752300859 13.3969909491111 +2.88299231231213 12.948495571765 +2.90751710161567 12.251157597956 +2.93204189091921 12.2193288937572 +2.95656668022275 11.8287038876816 +2.98109146952629 11.0995372096739 +3.00561625882983 10.6221066466926 +3.03014104813337 10.4716436813894 +3.05466583743691 9.9537038585188 +3.07919062674046 9.34027792305195 +3.103715416044 8.65451402349702 +3.12824020534754 8.73842606183918 +3.15276499465108 8.13946771918994 +3.17728978395462 7.85879641852822 +3.20181457325816 7.60706030350172 +3.2263393625617 7.28298622438716 +3.25086415186524 7.30324085433182 +3.27538894116879 7.14988437046511 +3.29991373047233 7.08043992494056 +3.32443851977587 7.02835659079714 +3.34896330907941 6.93576399676441 +3.37348809838295 7.0601852949959 +3.39801288768649 6.46122695234665 +3.42253767699003 6.96759270096316 +3.44706246629357 6.64930565897564 +3.47158725559711 6.51909732361711 +3.49611204490066 7.0225695536701 +3.5206368342042 6.62905102903098 +3.54516162350774 6.5306713978712 +3.56968641281128 6.63483806615803 +3.59421120211482 6.11111120616038 +3.61873599141836 6.12268528041447 +3.6432607807219 6.16608805886732 +3.66778557002544 6.21527787444721 +3.69231035932899 6.51041676792654 +3.71683514863253 5.96643527798424 +3.74135993793607 6.21817139301073 +3.76588472723961 5.89409731389616 +3.79040951654315 5.89120379533264 +3.81493430584669 6.07928250196163 +3.83945909515023 6.05613435345345 +3.86398388445377 5.6336806431791 +3.88850867375731 6.01851861212765 +3.91303346306086 5.7465278671565 +3.9375582523644 5.78993064560934 +3.96208304166794 5.76099545997411 +3.98660783097148 5.54687508627342 +4.01113262027502 5.88252323964207 +4.03565740957856 5.9866899079289 +4.0601821988821 5.75810194141059 +4.08470698818564 5.71180564439422 +4.10923177748919 5.5063658263841 +4.13375656679273 5.6076389761074 +4.15828135609627 5.49768527069353 +4.18280614539981 5.46296304793125 +4.20733093470335 5.33275471257272 +4.23185572400689 5.56712971621807 +4.25638051331043 5.5063658263841 +4.28090530261397 5.34432878682681 +4.30543009191751 5.42245378804193 +4.32995488122106 5.28067137842931 +4.3544796705246 5.32407415688215 +4.37900445982814 5.18518526583305 +4.40352924913168 5.54108804914637 +4.42805403843522 5.19965285865067 +4.45257882773876 5.30671304550101 +4.4771036170423 5.40219915809727 +4.50162840634584 5.15625008019782 +4.52615319564939 5.13888896881669 +4.55067798495293 5.18807878439657 +4.57520277425647 5.3559028610809 +4.59972756356001 5.34722230539033 +4.62425235286355 5.23726859997646 +4.64877714216709 5.18518526583305 +4.67330193147063 5.2922454526834 +4.69782672077417 5.17071767301544 +4.72235151007771 5.23726859997646 +4.74687629938126 5.10127322749089 +4.7714010886848 4.9392361879336 +4.79592587798834 5.00000007776759 +4.82045066729188 5.01736118914872 +4.84497545659542 5.05787044903804 +4.86950024589896 4.90740748373485 +4.8940250352025 4.79745377832098 +4.91854982450604 4.97106489213236 +4.94307461380959 4.97106489213236 +4.96759940311313 4.80324081544803 +4.99212419241667 4.92476859511599 +5.01664898172021 4.85243063102792 +5.04117377102375 4.8379630382103 +5.06569856032729 4.91898155798894 +5.09022334963083 4.92766211367951 +5.11474813893437 4.91898155798894 +5.13927292823792 4.80324081544803 +5.16379771754146 5.07233804185566 +5.188322506845 5.07523156041918 +5.21284729614854 4.72800933279643 +5.23737208545208 4.8379630382103 +5.26189687475562 4.84375007533735 +5.28642166405916 4.56307877467563 +5.3109464533627 4.60937507169199 +5.33547124266624 4.79166674119394 +5.35999603196979 4.80613433401155 +5.38452082127333 4.80324081544803 +5.40904561057687 4.96238433644179 +5.43357039988041 4.74247692561405 +5.45809518918395 4.97974544782292 +5.48261997848749 4.65567136870836 +5.50714476779103 4.62094914594609 +5.53166955709457 4.91898155798894 +5.55619434639812 4.78877322263041 +5.58071913570166 4.58333340462029 +5.6052439250052 4.86111118671849 +5.62976871430874 4.76851859268575 +5.65429350361228 4.5601852561121 +5.67881829291582 4.76273155555871 +5.70334308221936 4.84085655677382 +5.7278678715229 4.58912044174733 +5.75239266082644 4.75405099986814 +5.77691745012999 4.82060192682916 +5.80144223943353 4.56307877467563 +5.82596702873707 4.54571766329449 +5.85049181804061 4.61516210881904 +5.87501660734415 4.52256951478631 +5.89954139664769 4.71354173997882 +5.92406618595123 4.58622692318381 +5.94859097525477 4.65856488727188 +5.97311576455832 4.72800933279643 +5.99764055386186 4.81770840826564 +6.0221653431654 4.55150470042153 +6.04669013246894 4.65567136870836 +6.07121492177248 4.55150470042153 +6.09573971107602 4.72222229566939 +6.12026450037956 4.55439821898506 +6.1447892896831 4.736689888487 +6.16931407898664 4.66724544296245 +6.19383886829019 4.71354173997882 +6.21836365759373 4.58043988605676 +6.24288844689727 4.79456025975746 +6.26741323620081 5.10127322749089 +6.29193802550435 4.86111118671849 +6.31646281480789 4.54861118185801 +6.34098760411143 4.8379630382103 +6.36551239341497 4.60358803456495 +6.39003718271852 4.71932877710586 +6.41456197202206 4.96817137356883 +6.4390867613256 4.8755787795361 +6.46361155062914 4.76273155555871 +6.48813633993268 4.73379636992348 +6.51266112923622 4.89004637235371 +6.53718591853976 4.75405099986814 +6.5617107078433 4.88715285379019 +6.58623549714684 4.6614584058354 +6.61076028645039 4.82060192682916 +6.63528507575393 4.88715285379019 +6.65980986505747 4.94212970649713 +6.68433465436101 4.71643525854234 +6.70885944366455 4.90162044660781 +6.73338423296809 4.85821766815496 +6.75790902227163 4.75694451843166 +6.78243381157517 4.94791674362417 +6.80695860087872 4.78298618550337 +6.83148339018226 4.86689822384553 +6.8560081794858 4.85821766815496 +6.88053296878934 4.5341435890404 +6.90505775809288 4.75115748130462 +6.92958254739642 4.63831025732722 +6.95410733669996 4.9392361879336 +6.9786321260035 4.73379636992348 +7.00315691530705 4.83506951964678 +7.02768170461059 4.76562507412223 +7.05220649391413 4.60648155312847 +7.07673128321767 4.86689822384553 +7.10125607252121 4.74826396274109 +7.12578086182475 4.84085655677382 +7.15030565112829 4.75115748130462 +7.17483044043183 5.02604174483929 +7.19935522973537 4.74826396274109 +7.22388001903892 4.75405099986814 +7.24840480834246 4.84953711246439 +7.272929597646 4.79166674119394 +7.29745438694954 4.64409729445427 +7.32197917625308 4.60937507169199 +7.34650396555662 4.99421304064054 +7.37102875486016 4.78587970406689 +7.3955535441637 4.83506951964678 +7.42007833346725 4.89872692804428 +7.44460312277079 4.95659729931474 +7.46912791207433 4.79456025975746 +7.49365270137787 4.75115748130462 +7.51817749068141 4.90740748373485 +7.54270227998495 4.77141211124928 +7.56722706928849 4.80902785257507 +7.59175185859203 4.82349544539269 +7.61627664789557 4.82060192682916 +7.64080143719912 4.90740748373485 +7.66532622650266 4.75405099986814 +7.6898510158062 4.90740748373485 +7.71437580510974 4.78298618550337 +7.73890059441328 5.01736118914872 +7.76342538371682 4.80613433401155 +7.78795017302036 4.83506951964678 +7.8124749623239 4.85243063102792 +7.83699975162745 4.69039359147063 +7.86152454093099 4.73958340705052 +7.88604933023453 4.71064822141529 +7.91057411953807 4.89293989091724 +7.93509890884161 4.97106489213236 +7.95962369814515 4.97106489213236 +7.98414848744869 4.65277785014484 +8.00867327675223 4.61226859025552 +8.03319806605577 4.78009266693984 +8.05772285535932 4.85821766815496 +8.08224764466286 4.89004637235371 +8.1067724339664 4.74247692561405 +8.13129722326994 4.77141211124928 +8.15582201257348 4.89004637235371 +8.18034680187702 4.71354173997882 +8.20487159118056 4.64988433158131 +8.2293963804841 4.92476859511599 +8.25392116978765 4.6990741471612 +8.27844595909119 5.04050933765691 +8.30297074839473 4.62962970163665 +8.32749553769827 4.82928248251973 +8.35202032700181 4.96817137356883 +8.37654511630535 4.83217600108326 +8.40106990560889 4.76562507412223 +8.42559469491243 4.90451396517133 +8.45011948421597 4.93634266937008 +8.47464427351952 5.12442137599907 +8.49916906282306 4.99421304064054 +8.5236938521266 4.97974544782292 +8.54821864143014 5.17939822870601 +8.57274343073368 5.00289359633111 +8.59726822003722 4.99710655920406 +8.62179300934076 5.0896991532368 +8.6463177986443 5.11284730174498 +8.67084258794785 5.3298611940092 +8.69536737725139 5.20254637721419 +8.71989216655493 5.18229174726953 +8.74441695585847 5.20833341434123 +8.76894174516201 5.26041674848465 +8.79346653446555 5.1909723029601 +8.81799132376909 5.57581027190864 +8.84251611307263 5.52083341920171 +8.86704090237617 5.28935193411988 +8.89156569167972 5.08680563467327 +8.91609048098326 5.00289359633111 +8.9406152702868 5.3935186024067 +8.96514005959034 5.26041674848465 +8.98966484889388 5.37037045389852 +9.01418963819742 5.62210656892501 +9.03871442750096 5.41956026947841 +9.0632392168045 5.43981489942307 +9.08776400610805 5.61342601323444 +9.11228879541159 5.46585656649477 +9.13681358471513 5.54687508627342 +9.16133837401867 5.61921305036149 +9.18586316332221 5.62500008748853 +9.21038795262575 5.62500008748853 +9.23491274192929 5.5700232347816 +9.25943753123283 5.68865749588604 +9.28396232053638 5.92303249953139 +9.30848710983992 5.67708342163195 +9.33301189914346 5.51215286351114 +9.357536688447 5.69444453301308 +9.38206147775054 5.8738426839515 +9.40658626705408 5.76678249710116 +9.43111105635762 5.62789360605206 +9.45563584566116 5.86516212826094 +9.4801606349647 6.10243065046981 +9.50468542426825 5.90567138815026 +9.52921021357179 5.98379638936537 +9.55373500287533 5.78703712704582 +9.57825979217887 5.79861120129991 +9.60278458148241 6.08217602052515 +9.62730937078595 6.12268528041447 +9.65183416008949 6.04745379776288 +9.67635894939303 5.83043990549866 +9.70088373869658 5.9114584252773 +9.72540852800012 6.11979176185095 +9.74993331730366 6.19212972593902 +9.7744581066072 6.32233806129755 +9.79898289591074 5.89409731389616 +9.82350768521428 6.47280102660075 +9.84803247451782 6.55960658350643 +9.87255726382136 6.51331028649007 +9.8970820531249 6.59432880626871 +9.92160684242845 6.97048621952669 +9.94613163173199 7.07465288781351 +9.97065642103553 7.32928252140353 +9.99518121033907 7.58969919212059 +10.0197059996426 7.76041678736844 +10.0442307889462 7.9947917910138 +10.0687555782497 7.8819445670364 +10.0932803675532 7.89641215985402 +10.1178051568568 7.52314826515956 +10.1423299461603 7.1238427033934 +10.1668547354639 7.21354177886261 +10.1913795247674 6.86631955123986 +10.2159043140709 7.09780103632169 +10.2404291033745 6.97627325665373 +10.264953892678 6.86342603267634 +10.2894786819816 7.08912048063113 +10.3140034712851 7.09201399919465 +10.3385282605886 6.89525473687509 +10.3630530498922 7.06307881355942 +10.3875778391957 6.75347232726247 +10.4121026284993 6.883680662621 +10.4366274178028 6.92418992251032 +10.4611522071064 6.97048621952669 +10.4856769964099 6.75636584582599 +10.5102017857134 6.76215288295304 +10.534726575017 6.73900473444485 +10.5592513643205 6.49594917510893 +10.5837761536241 6.60879639908632 +10.6083009429276 6.55381954637939 +10.6328257322311 6.21238435588368 +10.6573505215347 6.09953713190629 +10.6818753108382 6.40625009963972 +10.7064001001418 6.35127324693278 +10.7309248894453 6.04745379776288 +10.7554496787488 6.30497694991642 +10.7799744680524 6.25578713433653 +10.8044992573559 6.17187509599436 +10.8290240466595 6.27025472715414 +10.853548835963 5.86516212826094 +10.8780736252666 5.92303249953139 +10.9025984145701 6.14004639179561 +10.9271232038736 6.12268528041447 +10.9516479931772 6.20370380019312 +10.9761727824807 6.23842602295539 +11.0006975717843 5.93460657378548 +11.0252223610878 5.96354175942071 +11.0497471503913 5.92592601809492 +11.0742719396949 6.03587972350879 +11.0987967289984 6.18634268881198 +11.123321518302 5.7725695342282 +11.1478463076055 5.98379638936537 +11.172371096909 5.7465278671565 +11.1968958862126 6.07928250196163 +11.2214206755161 6.00983805643708 +11.2459454648197 6.27604176428119 +11.2704702541232 6.08217602052515 +11.2949950434268 5.76678249710116 +11.3195198327303 5.8854167582056 +11.3440446220338 5.94039361091253 +11.3685694113374 5.91724546240435 +11.3930942006409 6.10821768759686 +11.4176189899445 5.75231490428354 +11.442143779248 5.68576397732251 +11.4666685685515 6.0879630576522 +11.4911933578551 5.88252323964207 +11.5157181471586 5.89120379533264 +11.5402429364622 5.76388897853763 +11.5647677257657 6.08506953908868 +11.5892925150692 5.95775472229367 +11.6138173043728 6.33101861698812 +11.6383420936763 6.20949083732016 +11.6628668829799 5.95486120373014 +11.6873916722834 6.1892362073755 +11.711916461587 5.82465286837162 +11.7364412508905 6.16898157743084 +11.760966040194 6.2905093570988 +11.7854908294976 5.96064824085719 +11.8100156188011 6.44965287809256 +11.8345404081047 6.25578713433653 +11.8590651974082 6.34548620980574 +11.8835899867117 6.21817139301073 +11.9081147760153 6.24421306008244 +11.9326395653188 6.22395843013777 +11.9571643546224 6.4293982481479 +11.9816891439259 6.3281250984246 +12.0062139332294 6.57407417632405 +12.030738722533 6.72743066019076 +12.0552635118365 6.29629639422585 +12.0797883011401 6.64641214041212 +12.1043130904436 6.49305565654541 +12.1288378797472 6.97916677521725 +12.1533626690507 6.66666677035678 +12.1778874583542 6.9212964039468 +12.2024122476578 6.97627325665373 +12.2269370369613 7.05439825786885 +12.2514618262649 6.89814825543861 +12.2759866155684 7.18460659322738 +12.3005114048719 7.21932881598966 +12.3250361941755 7.45949085676206 +12.349560983479 6.97916677521725 +12.3740857727826 7.70833345322503 +12.3986105620861 7.47395844957967 +12.4231353513896 7.46817141245263 +12.4476601406932 7.72280104604264 +12.4721849299967 7.65335660051809 +12.4967097193003 8.01504642095846 +12.5212345086038 8.30150475874722 +12.5457592979074 8.17129642338869 +12.5702840872109 8.54456031808315 +12.5948088765144 8.82233810018135 +12.619333665818 8.57928254084542 +12.6438584551215 9.1319445864783 +12.6683832444251 8.94097236128579 +12.6929080337286 9.36631959012365 +12.7174328230321 9.36631959012365 +12.7419576123357 9.80902793034266 +12.7664824016392 9.90162052437539 +12.7910071909428 9.86111126448607 +12.8155319802463 10.3761575687931 +12.8400567695498 10.7667825748687 +12.8645815588534 10.9809029485694 +12.8891063481569 11.5451390684564 +12.9136311374605 11.8026622206099 +12.938155926764 12.5896992698882 +12.9626807160676 12.8848381633675 +12.9872055053711 13.1857640939739 +13.0117302946746 13.6805557683363 +13.0362550839782 14.1203705899918 +13.0607798732817 14.7598381925304 +13.0853046625853 15.6278937615872 +13.1098294518888 16.3917826623573 +13.1343542411923 16.7476854456706 +13.1588790304959 17.994791946549 +13.1834038197994 18.8657410341694 +13.207928609103 20.0491901266502 +13.2324533984065 21.3599540359261 +13.25697818771 22.5144679427717 +13.2815029770136 23.8888892604451 +13.3060277663171 26.0300929974521 +13.3305525556207 26.8142365281668 +13.3550773449242 29.9855328737879 +13.3796021342278 30.9114588141152 +13.4041269235313 33.2291671834971 +13.4286517128348 35.8998848176287 +13.4531765021384 38.4722228206006 +13.4777012914419 41.8518525027953 +13.5022260807455 43.642940493616 +13.526750870049 46.9820609159215 +13.5512756593525 49.9594915177865 +13.5758004486561 53.0787045292642 +13.6003252379596 56.2297462449407 +13.6248500272632 59.806135189455 +13.6493748165667 63.2060195015944 +13.6738996058702 65.616320465009 +13.6984243951738 67.957176982899 +13.7229491844773 70.0897001642154 +13.7474739737809 71.935765007743 +13.7719987630844 72.8038205767999 +13.796523552388 73.3680566966869 +13.8210483416915 72.3755798293985 +13.845573130995 72.0833344544827 +13.8700979202986 70.66840387692 +13.8946227096021 67.0775473395881 +13.9191474989057 64.7222232288804 +13.9436722882092 61.0792833574051 +13.9681970775127 58.3246536849313 +13.9927218668163 54.0190980624092 +14.0172466561198 50.9809035707101 +14.0417714454234 47.4855331459744 +14.0662962347269 44.3460655045521 +14.0908210240304 41.0214126750643 +14.115345813334 38.2986117067892 +14.1398706026375 36.0185190787332 +14.1643953919411 33.6979171907878 +14.1889201812446 31.3310190058261 +14.2134449705482 29.3315976784317 +14.2379697598517 27.1701393114801 +14.2624945491552 25.7002318812105 +14.2870193384588 24.435764268951 +14.3115441277623 23.5358799956953 +14.3360689170659 21.9444447857577 +14.3605937063694 20.7407410633322 +14.3851184956729 19.704861417591 +14.4096432849765 18.7847225143907 +14.43416807428 17.491319716496 +14.4586928635836 16.793981742687 +14.4832176528871 16.4467595150642 +14.5077424421906 15.5758104274438 +14.5322672314942 14.7019678212599 +14.5567920207977 13.8570604007112 +14.5813168101013 13.8049770665678 +14.6058415994048 13.2291668724267 +14.6303663887084 12.6822918639209 +14.6548911780119 12.5347224171812 +14.6794159673154 12.1093751883434 +14.703940756619 11.3483798061368 +14.7284655459225 11.3339122133192 +14.7529903352261 11.1053242468009 +14.7775151245296 10.5758103496762 +14.8020399138331 10.425347384373 +14.8265647031367 9.90740756150244 +14.8510894924402 9.93923626570119 +14.8756142817438 9.69618070636527 +14.9001390710473 9.61805570515015 +14.9246638603508 9.57754644526083 +14.9491886496544 9.3721066272507 +14.9737134389579 9.42418996139411 +14.9982382282615 9.18402792062171 +15.022763017565 9.20717606912989 +15.0472878068686 9.44155107277525 +15.0718125961721 9.07696773377136 +15.0963373854756 9.49074088835514 +15.1208621747792 9.4097223685765 +15.1453869640827 9.11458347509716 +15.1699117533863 9.19849551343932 +15.1944365426898 9.72222237343697 +15.2189613319933 9.55150477818912 +15.2434861212969 9.49074088835514 +15.2680109106004 9.788773300398 +15.292535699904 9.46759273984696 +15.3170604892075 10.1331020094572 +15.341585278511 9.94502330282823 +15.3661100678146 9.9276621914471 +15.3906348571181 10.6192131281291 +15.4151596464217 10.4832177556435 +15.4396844357252 10.923032577299 +15.4642092250288 11.2962964719934 +15.4887340143323 11.0995372096739 +15.5132588036358 11.9994214829295 +15.5377835929394 12.0515048170729 +15.5623083822429 12.6851853824844 +15.5868331715465 13.0497687214883 +15.61135796085 12.9774307574002 +15.6358827501535 13.3969909491111 +15.6604075394571 14.2013891097704 +15.6849323287606 14.6643520799341 +15.7094571180642 14.65277800568 +15.7339819073677 15.471643759157 +15.7585066966712 15.8709493209232 +15.7830314859748 15.787037282581 +15.8075562752783 16.0503474718616 +15.8320810645819 16.2789354383799 +15.8566058538854 16.4496530336277 +15.881130643189 16.8200234097587 +15.9056554324925 16.7679400756153 +15.930180221796 16.4293984036831 +15.9547050110996 16.7447919271071 +15.9792298004031 16.5653937761686 +16.0037545897067 16.2528937713082 +16.0282793790102 15.5700233903168 +16.0528041683137 15.5873845016979 +16.0773289576173 14.9363428249053 +16.1018537469208 14.6643520799341 +16.1263785362244 14.1608798498811 +16.1509033255279 13.6921298425904 +16.1754281148314 13.1655094640292 +16.199952904135 12.5231483429271 +16.2244776934385 11.7621529607206 +16.2490024827421 11.1255788767456 +16.2735272720456 10.4398149771907 +16.2980520613492 10.0202547854798 +16.3225768506527 9.38657422006832 +16.3471016399562 9.01331032537386 +16.3716264292598 8.44328716835984 +16.3961512185633 7.84143530714708 +16.4206760078669 7.94849549399743 +16.4452007971704 7.65914363764514 +16.4697255864739 6.9212964039468 +16.4942503757775 6.59722232483223 +16.518775165081 6.92708344107384 +16.5432999543846 6.60879639908632 +16.5678247436881 6.44097232240199 +16.5923495329916 5.85069453544332 +16.6168743222952 5.5700232347816 +16.6413991115987 5.56134267909103 +16.6659239009023 5.5960649018533 +16.6904486902058 5.27777785986578 +16.7149734795094 5.23437508141294 +16.7394982688129 5.35879637964443 +16.7640230581164 5.37905100958909 +16.78854784742 4.86689822384553 +16.8130726367235 5.02604174483929 +16.8375974260271 4.79456025975746 +16.8621222153306 4.81481488970212 +16.8866470046341 4.61805562738256 +16.9111717939377 4.90740748373485 +16.9356965832412 4.75115748130462 +16.9602213725448 4.35474543810198 +16.9847461618483 4.43865747644414 +17.0092709511518 4.69328711003416 +17.0337957404554 4.31423617821266 +17.0583205297589 4.37500006804664 +17.0828453190625 4.3200232153397 +17.107370108366 4.43576395788062 +17.1318948976696 4.16956025003651 +17.1564196869731 4.14641210152833 +17.1809444762766 4.1811343242906 +17.2054692655802 3.98148154340752 +17.2299940548837 4.21296302848935 +17.2545188441873 4.22743062130697 +17.2790436334908 4.12037043445662 +17.3035684227943 3.978588024844 +17.3280932120979 4.36631951235607 +17.3526180014014 4.28530099257743 +17.377142790705 3.72395839125398 +17.4016675800085 3.97280098771695 +17.426192369312 4.16087969434594 +17.4507171586156 4.0162037661698 +17.4752419479191 3.88599543081127 +17.4997667372227 3.81365746672319 +17.5242915265262 3.94675932064525 +17.5488163158298 4.08564821169435 +17.5733411051333 3.93518524639116 +17.5978658944368 3.61689820440364 +17.6223906837404 3.96122691346286 +17.6469154730439 3.77604172539739 +17.6714402623475 3.75000005832569 +17.695965051651 3.89756950506536 +17.7204898409545 3.79629635534206 +17.7450146302581 3.75868061401626 +17.7695394195616 3.77314820683387 +17.7940642088652 3.81365746672319 +17.8185889981687 3.94386580208172 +17.8431137874722 3.71527783556341 +17.8676385767758 3.6255787600942 +17.8921633660793 3.62847227865773 +17.9166881553829 3.84548617092195 +17.9412129446864 3.78182876252444 +17.96573773399 3.75289357688921 +17.9902625232935 3.64004635291182 +18.014787312597 3.88888894937479 +18.0393121019006 3.63425931578477 +18.0638368912041 3.64004635291182 +18.0883616805077 3.72685190981751 +18.1128864698112 3.63425931578477 +18.1374112591147 3.90625006075593 +18.1619360484183 3.63136579722125 +18.1864608377218 3.78182876252444 +18.2109856270254 3.93518524639116 +18.2355104163289 3.70949079843637 +18.2600352056324 3.8396991337949 +18.284559994936 3.64004635291182 +18.3090847842395 3.76736116970683 +18.3336095735431 3.77604172539739 +18.3581343628466 3.98148154340752 +18.3826591521502 3.85416672661251 +18.4071839414537 3.76736116970683 +18.4317087307572 3.76157413257978 +18.4562335200608 3.75868061401626 +18.4807583093643 3.86863431943013 +18.5052830986679 3.87442135655717 +18.5298078879714 4.01041672904275 +18.5543326772749 3.91203709788297 +18.5788574665785 4.03645839611446 +18.603382255882 3.97569450628048 +18.6279070451856 4.10300932307548 +18.6524318344891 4.02199080329684 +18.6769566237926 3.88310191224774 +18.7014814130962 4.0162037661698 +18.7260062023997 4.07696765600378 +18.7505309917033 3.76736116970683 +18.7750557810068 3.99884265478866 +18.7995805703104 4.05960654462264 +18.8241053596139 3.94386580208172 +18.8486301489174 4.03356487755093 +18.873154938221 4.08275469313082 +18.8976797275245 4.17534728716356 +18.9222045168281 4.30844914108561 +18.9467293061316 4.25347228837868 +18.9712540954351 4.421296365063 +18.9957788847387 4.37210654948311 +19.0203036740422 4.33159728959379 +19.0448284633458 4.32581025246675 +19.0693532526493 4.28530099257743 +19.0938780419528 4.69039359147063 +19.1184028312564 4.41261580937244 +19.1429276205599 4.48784729202403 +19.1674524098635 4.64409729445427 +19.191977199167 4.79456025975746 +19.2165019884706 4.71354173997882 +19.2410267777741 4.46469914351585 +19.2655515670776 4.71354173997882 +19.2900763563812 4.86400470528201 +19.3146011456847 4.80902785257507 +19.3391259349883 4.9508102621877 +19.3636507242918 4.83217600108326 +19.3881755135953 4.73090285135995 +19.4127003028989 5.02604174483929 +19.4372250922024 5.16493063588839 +19.461749881506 5.02604174483929 +19.4862746708095 4.92476859511599 +19.510799460113 4.97395841069588 +19.5353242494166 4.96238433644179 +19.5598490387201 4.82638896395621 +19.5843738280237 5.03472230052986 +19.6088986173272 4.86979174240905 +19.6334234066308 4.90451396517133 +19.6579481959343 4.98553248494997 +19.6824729852378 5.10127322749089 +19.7069977745414 4.69328711003416 +19.7315225638449 4.90451396517133 +19.7560473531485 4.70486118428825 +19.780572142452 4.82349544539269 +19.8050969317555 4.89293989091724 +19.8296217210591 4.41261580937244 +19.8541465103626 4.71354173997882 +19.8786712996662 4.43576395788062 +19.9031960889697 4.63831025732722 +19.9277208782732 4.62673618307313 +19.9522456675768 4.67013896152597 +19.9767704568803 4.40972229080891 +20.0012952461839 4.57754636749324 +20.0258200354874 4.61805562738256 +20.050344824791 4.70486118428825 +20.0748696140945 4.45601858782528 +20.099394403398 4.84664359390087 +20.1239191927016 4.44444451357119 +20.1484439820051 4.69328711003416 +20.1729687713087 4.75115748130462 +20.1974935606122 4.77141211124928 +20.2220183499157 4.5717593303662 +20.2465431392193 4.84375007533735 +20.2710679285228 4.85821766815496 +20.2955927178264 4.86689822384553 +20.3201175071299 4.4965278477146 +20.3446422964334 4.6354167387637 +20.369167085737 4.90451396517133 +20.3936918750405 4.94212970649713 +20.4182166643441 4.85243063102792 +20.4427414536476 4.74826396274109 +20.4672662429512 4.5717593303662 +20.4917910322547 4.82349544539269 +20.5163158215582 4.73379636992348 +20.5408406108618 4.58622692318381 +20.5653654001653 4.97106489213236 +20.5898901894689 4.70196766572473 +20.6144149787724 4.73090285135995 +20.6389397680759 4.84375007533735 +20.6634645573795 4.71643525854234 +20.687989346683 4.85243063102792 +20.7125141359866 4.76562507412223 +20.7370389252901 4.6990741471612 +20.7615637145936 4.89583340948076 +20.7860885038972 4.91030100229838 +20.8106132932007 4.77141211124928 +20.8351380825043 4.86689822384553 +20.8596628718078 5.1157408203085 +20.8841876611114 5.00000007776759 +20.9087124504149 5.19386582152362 +20.9332372397184 5.00289359633111 +20.957762029022 5.17650471014248 +20.9822868183255 5.28935193411988 +21.0068116076291 5.10127322749089 +21.0313363969326 5.3935186024067 +21.0558611862361 5.5324074934558 +21.0803859755397 5.45428249224068 +21.1049107648432 5.4311343437325 +21.1294355541468 5.45717601080421 +21.1539603434503 5.49189823356648 +21.1784851327538 5.63078712461558 +21.2030099220574 5.61921305036149 +21.2275347113609 5.65393527312376 +21.2520595006645 5.93171305522196 +21.276584289968 5.96932879654776 +21.3011090792716 6.03298620494526 +21.3256338685751 6.09375009477924 +21.3501586578786 6.04745379776288 +21.3746834471822 6.18344917024846 +21.3992082364857 6.19791676306607 +21.4237330257893 6.51041676792654 +21.4482578150928 6.35416676549631 +21.4727826043963 6.41203713676676 +21.4973073936999 6.70428251168258 +21.5218321830034 6.96180566383612 +21.546356972307 7.04282418361476 +21.5708817616105 7.27141215013307 +21.595406550914 7.29166678007773 +21.6199313402176 7.34953715134819 +21.6444561295211 7.5405093765407 +21.6689809188247 7.71122697178855 +21.6935057081282 7.90219919698106 +21.7180304974318 8.17418994195222 +21.7425552867353 8.50405105819383 +21.7670800760388 8.94675939841283 +21.7916048653424 8.85995384150714 +21.8161296546459 9.43865755421172 +21.8406544439495 9.96238441420937 +21.865179233253 10.109953860949 +21.8897040225565 10.0810186753138 +21.9142288118601 10.4050927544284 +21.9387536011636 11.056134431221 +21.9632783904672 11.304977027684 +21.9878031797707 11.8518520361898 +22.0123279690742 12.8096066807159 +22.0368527583778 13.1568289083387 +22.0613775476813 13.4924770617073 +22.0859023369849 14.3894678163994 +22.1104271262884 14.8668983793807 +22.134951915592 16.2123845114189 +22.1594767048955 17.3466437883198 +22.184001494199 17.6417826817992 +22.2085262835026 18.2523150987025 +22.2330510728061 19.9074077170376 +22.2575758621097 21.2586808862028 +22.2821006514132 22.2251160864195 +22.3066254407167 23.2118059165808 +22.3311502300203 25.2112272439751 +22.3556750193238 26.2094911483905 +22.3801998086274 27.2280096827506 +22.4047245979309 29.0364587849524 +22.4292493872344 30.8622689985353 +22.453774176538 31.5306717867091 +22.4782989658415 33.0005792169788 +22.5028237551451 34.3634264603981 +22.5273485444486 36.1111116727659 +22.5518733337522 36.6695607555258 +22.5763981230557 36.7505792753045 +22.6009229123592 37.9079867007136 +22.6254477016628 38.5416672661251 +22.6499724909663 38.1076394815967 +22.6744972802699 38.2783570768446 +22.6990220695734 38.275463558281 +22.7235468588769 37.4971070646934 +22.7480716481805 36.4004635291182 +22.772596437484 36.4525468632616 +22.7971212267876 35.2546301779631 +22.8216460160911 33.8425931189639 +22.8461708053947 32.4508106899094 +22.8706955946982 30.9114588141152 +22.8952203840017 29.6498847204192 +22.9197451733053 27.9571763607583 +22.9442699626088 26.7071763413164 +22.9687947519124 24.8292827935901 +22.9933195412159 24.1435188940351 +23.0178443305194 22.7054401679643 +23.042369119823 21.4380790371412 +23.0668939091265 20.0520836452138 +23.0914186984301 19.2650465959355 +23.1159434877336 17.7719910171577 +23.1404682770371 17.3987271224632 +23.1649930663407 16.4062502551749 +23.1895178556442 15.16203727286 +23.2140426449478 14.5167826331944 +23.2385674342513 14.0335650330861 +23.2630922235549 13.2812502065701 +23.2876170128584 12.5115742686731 +23.3121418021619 12.6562501968492 +23.3366665914655 11.7592594421571 +23.361191380769 11.7968751834829 +23.3857161700726 10.7465279449241 +23.4102409593761 10.541088126914 +23.4347657486796 10.353009420285 +23.4592905379832 10.0752316381868 +23.4838153272867 9.74826404050868 +23.5083401165903 9.43865755421172 +23.5328649058938 9.46180570271991 +23.5573896951973 8.92071773134113 +23.5819144845009 8.62557883786179 +23.6064392738044 8.48090290968564 +23.630964063108 8.57060198515485 +23.6554888524115 7.96006956825152 +23.6800136417151 7.99189827245027 +23.7045384310186 8.16840290482517 +23.7290632203221 7.47106493101615 +23.7535880096257 7.39872696692808 +23.7781127989292 7.58391215499354 +23.8026375882328 7.27719918726012 +23.8271623775363 7.25694455731545 +23.8516871668398 6.98206029378078 +23.8762119561434 6.88946769974805 +23.9007367454469 6.65798621466621 +23.9252615347505 7.11516214770283 +23.949786324054 6.65509269610269 +23.9743111133575 6.56250010206996 +23.9988359026611 6.1516204660497 +24.0233606919646 6.31944454273403 +24.0478854812682 6.52777787930768 +24.0724102705717 6.46122695234665 +24.0969350598753 6.20370380019312 +24.1214598491788 5.80729175699048 +24.1459846384823 6.35706028405983 +24.1705094277859 6.16608805886732 +24.1950342170894 5.87673620251503 +24.219559006393 6.17766213312141 +24.2440837956965 6.03009268638174 +24.268608585 6.09085657621572 +24.2931333743036 5.68287045875899 +24.3176581636071 5.78414360848229 +24.3421829529107 5.75810194141059 +24.3667077422142 5.6076389761074 +24.3912325315177 5.61342601323444 +24.4157573208213 5.44270841798659 +24.4402821101248 5.7465278671565 +24.4648068994284 5.5700232347816 +24.4893316887319 5.82175934980809 +24.5138564780355 5.94039361091253 +24.538381267339 5.66261582881433 +24.5629060566425 5.64814823599672 +24.5874308459461 5.67708342163195 +24.6119556352496 5.34722230539033 +24.6364804245532 5.40509267666079 +24.6610052138567 5.38483804671613 +24.6855300031602 5.46006952936773 +24.7100547924638 5.45717601080421 +24.7345795817673 5.62789360605206 +24.7591043710709 5.5700232347816 +24.7836291603744 5.89988435102321 +24.8081539496779 5.35011582395386 +24.8326787389815 5.31539360119158 +24.857203528285 5.74074083002945 +24.8817283175886 5.51215286351114 +24.9062531068921 5.44560193655011 +24.9307778961957 5.69444453301308 +24.9553026854992 5.61631953179796 +24.9798274748027 5.55266212340046 +25.0043522641063 5.49479175213 +25.0288770534098 5.93171305522196 +25.0534018427134 5.78703712704582 +25.0779266320169 5.39930563953375 +25.1024514213204 5.65972231025081 +25.126976210624 5.79282416417286 +25.1515009999275 5.84201397975275 +25.1760257892311 5.95196768516662 +25.2005505785346 5.55844916052751 +25.2250753678381 5.7725695342282 +25.2496001571417 5.85358805400684 +25.2741249464452 5.78414360848229 +25.2986497357488 6.07638898339811 +25.3231745250523 6.12847231754152 +25.3476993143559 6.14583342892266 +25.3722241036594 6.0503473163264 +25.3967488929629 6.56539362063348 +25.4212736822665 6.47280102660075 +25.44579847157 6.48437510085484 +25.4703232608736 6.55671306494291 +25.4948480501771 6.43229176671142 +25.5193728394806 6.60011584339575 +25.5438976287842 6.73321769731781 +25.5684224180877 6.95312510814555 +25.5929472073913 6.88657418118452 +25.6174719966948 7.34085659565762 +25.6419967859983 7.45370381963501 +25.6665215753019 7.48553252383376 +25.6910463646054 7.54918993223127 +25.715571153909 7.77777789874958 +25.7400959432125 8.17129642338869 +25.7646207325161 8.16550938626165 +25.7891455218196 8.39120383421643 +25.8136703111231 8.94675939841283 +25.8381951004267 9.10879643797012 +25.8627198897302 9.07986125233489 +25.8872446790338 9.35474551586956 +25.9117694683373 9.89293996868482 +25.9362942576408 10.4687501628259 +25.9608190469444 10.7291668335429 +25.9853438362479 11.0156251713317 +26.0098686255515 11.3512733247004 +26.034393414855 11.614583513981 +26.0589182041585 12.4479168602755 +26.0834429934621 12.6244214926504 +26.1079677827656 13.4317131718733 +26.1324925720692 14.0538196630308 +26.1570173613727 14.4791668918686 +26.1815421506763 15.1909724584953 +26.2060669399798 15.6510419100954 +26.2305917292833 16.7476854456706 +26.2551165185869 17.3813660110821 +26.2796413078904 17.7199076830143 +26.304166097194 18.504051213729 +26.3286908864975 19.3865743756035 +26.353215675801 20.2575234632239 +26.3777404651046 20.5526623567032 +26.4022652544081 21.342592924545 +26.4267900437117 22.0254633055364 +26.4513148330152 22.416088311612 +26.4758396223187 22.9137735045379 +26.5003644116223 23.5127318471872 +26.5248892009258 23.8946762975722 +26.5494139902294 24.4820605659673 +26.5739387795329 24.629630012707 +26.5984635688365 25.199653169721 +26.62298835814 25.5497689159073 +26.6475131474435 25.7667828081715 +26.6720379367471 25.2025466882845 +26.6965627260506 26.0532411459603 +26.7210875153542 25.6539355841941 +26.7456123046577 25.4369216919299 +26.7701370939612 24.8263892750266 +26.7946618832648 25.0231485373461 +26.8191866725683 23.9641207430967 +26.8437114618719 23.3420142519393 +26.8682362511754 22.8616901703945 +26.8927610404789 22.0949077510609 +26.9172858297825 20.5179401339409 +26.941810619086 19.9855327182527 +26.9663354083896 19.0104169623455 +26.9908601976931 18.625578993397 +27.0153849869967 17.9918984279854 +27.0399097763002 16.3483798839044 +27.0644345656037 16.163194695839 +27.0889593549073 15.2083335698764 +27.1134841442108 14.6672455984976 +27.1380089335144 13.9785881803792 +27.1625337228179 13.0642363143059 +27.1870585121214 12.7922455693348 +27.211583301425 12.0920140769622 +27.2361080907285 11.8836807403886 +27.2606328800321 11.5046298085671 +27.2851576693356 11.3368057318828 +27.3096824586391 11.0300927641493 +27.3342072479427 10.7089122035983 +27.3587320372462 10.5671297939857 +27.3832568265498 10.0896992310044 +27.4077816158533 10.4832177556435 +27.4323064051569 10.3356483089038 +27.4568311944604 9.94791682139176 +27.4813559837639 9.84085663454141 +27.5058807730675 9.83796311597789 +27.530405562371 9.86689830161312 +27.5549303516746 9.87557885730368 +27.5794551409781 9.69907422492879 +27.6039799302816 9.96817145133642 +27.6285047195852 9.59780107520549 +27.6530295088887 9.79456033752505 +27.6775542981923 9.49363440691866 +27.7020790874958 9.40393533144945 +27.7266038767993 9.77430570758038 +27.7511286661029 9.54282422249855 +27.7756534554064 9.83796311597789 +27.80017824471 9.7251158920005 +27.8247030340135 9.67881959498413 +27.8492278233171 9.40682885001297 +27.8737526126206 9.31712977454377 +27.8982774019241 9.21006958769342 +27.9228021912277 9.02777791819147 +27.9473269805312 9.09143532658898 +27.9718517698348 8.66319457918759 +27.9963765591383 8.59085661509951 +28.0209013484418 8.06712975510187 +28.0454261377454 8.41724550128814 +28.0699509270489 8.01215290239493 +28.0944757163525 7.68518530471684 +28.119000505656 7.73726863886026 +28.1435252949595 7.41319455974569 +28.1680500842631 7.39004641123751 +28.1925748735666 7.08333344350408 +28.2170996628702 6.82870380991406 +28.2416244521737 6.89236121831157 +28.2661492414773 6.55381954637939 +28.2906740307808 6.2905093570988 +28.3151988200843 6.43518528527495 +28.3397236093879 6.19212972593902 +28.3642483986914 6.03877324207231 +28.388773187995 5.88252323964207 +28.4132979772985 5.88831027676912 +28.437822766602 5.68576397732251 +28.4623475559056 5.69733805157661 +28.4868723452091 5.63946768030615 +28.5113971345127 5.4311343437325 +28.5359219238162 5.28067137842931 +28.5604467131197 5.1157408203085 +28.5849715024233 5.33564823113624 +28.6094962917268 5.18518526583305 +28.6340210810304 4.95949081787827 +28.6585458703339 4.85243063102792 +28.6830706596375 4.82060192682916 +28.707595448941 4.93634266937008 +28.7321202382445 4.86400470528201 +28.7566450275481 4.67881951721654 +28.7811698168516 4.82928248251973 +28.8056946061552 4.72800933279643 +28.8302193954587 4.48495377346051 +28.8547441847622 4.59490747887438 +28.8792689740658 4.70775470285177 +28.9037937633693 4.41840284649948 +28.9283185526729 4.48206025489698 +28.9528433419764 4.33738432672084 +28.9773681312799 4.52835655191335 +29.0018929205835 4.49074081058755 +29.026417709887 4.36921303091959 +29.0509424991906 4.21006950992583 +29.0754672884941 4.1811343242906 +29.0999920777977 4.37789358661016 +29.1245168671012 4.18692136141765 +29.1490416564047 4.05671302605912 +29.1735664457083 4.34606488241141 +29.1980912350118 4.27662043688686 +29.2226160243154 4.06539358174969 +29.2471408136189 3.96701395058991 +29.2716656029224 3.96990746915343 +29.296190392226 4.03645839611446 +29.3207151815295 4.21585654705288 +29.3452399708331 4.12905099014719 +29.3697647601366 4.0046296919157 +29.3942895494401 4.12615747158367 +29.4188143387437 4.10590284163901 +29.4433391280472 3.89467598650183 +29.4678639173508 3.79629635534206 +29.4923887066543 4.18692136141765 +29.5169134959579 4.05381950749559 +29.5414382852614 3.95543987633582 +29.5659630745649 3.88888894937479 +29.5904878638685 3.84837968948547 +29.615012653172 3.92650469070059 +29.6395374424756 3.82523154097728 +29.6640622317791 4.12905099014719 +29.6885870210826 3.96701395058991 +29.7131118103862 3.75578709545273 +29.7376365996897 3.72106487269046 +29.7621613889933 3.88310191224774 +29.7866861782968 3.70949079843637 +29.8112109676003 3.77025468827035 +29.8357357569039 3.59664357445897 +29.8602605462074 3.80497691103262 +29.884785335511 3.87442135655717 +29.9093101248145 3.64872690860239 +29.9338349141181 3.64004635291182 +29.9583597034216 3.79050931821501 +29.9828844927251 3.60821764871307 +30.0074092820287 3.88310191224774 +30.0319340713322 3.75000005832569 +30.0564588606358 3.79340283677853 +30.0809836499393 3.64583339003886 +30.1055084392428 3.81944450385024 +30.1300332285464 3.93518524639116 +30.1545580178499 3.69791672418228 +30.1790828071535 3.59664357445897 +30.203607596457 3.86284728230308 +30.2281323857605 3.76157413257978 +30.2526571750641 3.72685190981751 +30.2771819643676 3.62847227865773 +30.3017067536712 3.73553246550807 +30.3262315429747 3.58217598164136 +30.3507563322783 3.57638894451431 +30.3752811215818 3.4866898690451 +30.3998059108853 3.60532413014954 +30.4243307001889 3.58796301876841 +30.4488554894924 3.48090283191806 +30.473380278796 3.59375005589545 +30.4979050680995 3.52719912893442 +30.522429857403 3.52719912893442 +30.5469546467066 3.50405098042624 +30.5714794360101 3.58506950020488 +30.5960042253137 3.42013894208408 +30.6205290146172 3.51562505468033 +30.6450538039207 3.54745375887909 +30.6695785932243 3.3217593109243 +30.6941033825278 3.39699079357589 +30.7186281718314 3.82233802241376 +30.7431529611349 3.64872690860239 +30.7676777504385 3.54456024031556 +30.792202539742 3.53298616606147 +30.8167273290455 3.56770838882375 +30.8412521183491 3.49537042473567 +30.8657769076526 3.58506950020488 +30.8903016969562 3.54166672175204 +30.9148264862597 3.48090283191806 +30.9393512755632 3.50983801755329 +30.9638760648668 3.63136579722125 +30.9884008541703 3.42881949777465 +31.0129256434739 3.53587968462499 +31.0374504327774 3.68634264992819 +31.0619752220809 3.50115746186272 +31.0865000113845 3.74421302119864 +31.111024800688 3.64004635291182 +31.1355495899916 3.68344913136466 +31.1600743792951 3.55613431456965 +31.1845991685987 3.69791672418228 +31.2091239579022 3.81365746672319 +31.2336487472057 3.82812505954081 +31.2581735365093 3.71238431699989 +31.2826983258128 3.98437506197105 +31.3072231151164 3.80497691103262 +31.3317479044199 3.92361117213706 +31.3562726937234 4.01331024760627 +31.380797483027 4.14641210152833 +31.4053222723305 4.06539358174969 +31.4298470616341 4.08275469313082 +31.4543718509376 4.05671302605912 +31.4788966402411 4.09722228594844 +31.5034214295447 4.13483802727424 +31.5279462188482 4.42418988362653 +31.5524710081518 4.31712969677618 +31.5769957974553 4.15798617578242 +31.6015205867589 4.46759266207937 +31.6260453760624 4.61516210881904 +31.6505701653659 4.56597229323915 +31.6750949546695 4.59201396031086 +31.699619743973 4.60937507169199 +31.7241445332766 4.70486118428825 +31.7486693225801 4.81770840826564 +31.7731941118836 4.81481488970212 +31.7977189011872 5.0144676705852 +31.8222436904907 4.9392361879336 +31.8467684797943 5.13599545025316 +31.8712932690978 5.31828711975511 +31.8958180584013 5.19675934008714 +31.9203428477049 5.29513897124692 +31.9448676370084 5.33275471257272 +31.969392426312 5.57291675334512 +31.9939172156155 5.47164360362182 +32.0184420049191 5.46296304793125 +32.0429667942226 5.64814823599672 +32.0674915835261 5.78993064560934 +32.0920163728297 5.90856490671378 +32.1165411621332 5.83333342406218 +32.1410659514368 5.92592601809492 +32.1655907407403 6.22395843013777 +32.1901155300438 5.96932879654776 +32.2146403193474 6.19791676306607 +32.2391651086509 6.44386584096552 +32.2636898979545 6.31365750560699 +32.288214687258 6.41493065533029 +32.3127394765615 6.57118065776052 +32.3372642658651 6.59143528770518 +32.3617890551686 6.71585658593667 +32.3863138444722 6.82870380991406 +32.4108386337757 6.87789362549395 +32.4353634230793 6.46412047091018 +32.4598882123828 6.49305565654541 +32.4844130016863 6.65509269610269 +32.5089377909899 6.41782417389381 +32.5334625802934 6.51909732361711 +32.557987369597 6.23842602295539 +32.5825121589005 6.19502324450255 +32.607036948204 6.125578798978 +32.6315617375076 5.86805564682446 +32.6560865268111 5.71180564439422 +32.6806113161147 5.85648157257037 +32.7051361054182 5.58738434616274 +32.7296608947217 5.44849545511364 +32.7541856840253 5.39062508384318 +32.7787104733288 5.02604174483929 +32.8032352626324 5.01157415202168 +32.8277600519359 4.85243063102792 +32.8522848412395 4.85821766815496 +32.876809630543 4.8755787795361 +32.9013344198465 4.51678247765926 +32.9258592091501 4.63831025732722 +32.9503839984536 4.421296365063 +32.9749087877572 4.19270839854469 +32.9994335770607 4.36053247522902 +33.0239583663642 4.33738432672084 +33.0484831556678 4.33159728959379 +33.0730079449713 4.21006950992583 +33.0975327342749 4.06539358174969 +33.1220575235784 4.06828710031321 +33.1465823128819 4.06539358174969 +33.1711071021855 4.02488432186037 +33.195631891489 4.20138895423526 +33.2201566807926 4.05381950749559 +33.2446814700961 3.9033565421924 +33.2692062593997 4.07696765600378 +33.2937310487032 3.90625006075593 +33.3182558380067 3.84548617092195 +33.3427806273103 4.00173617335218 +33.3673054166138 3.78761579965149 +33.3918302059174 3.85995376373956 +33.4163549952209 3.74710653976217 +33.4408797845244 3.88310191224774 +33.465404573828 3.75868061401626 +33.4899293631315 3.81944450385024 +33.5144541524351 3.9409722835182 +33.5389789417386 3.96990746915343 +33.5635037310421 3.93229172782763 +33.5880285203457 3.77893524396092 +33.6125533096492 3.71817135412694 +33.6370780989528 3.74710653976217 +33.6616028882563 3.96701395058991 +33.6861276775599 3.86574080086661 +33.7106524668634 3.84837968948547 +33.7351772561669 3.978588024844 +33.7597020454705 3.76736116970683 +33.784226834774 3.72106487269046 +33.8087516240776 4.02777784042389 +33.8332764133811 3.91493061644649 +33.8578012026846 3.99884265478866 +33.8823259919882 3.95543987633582 +33.9068507812917 3.92071765357354 +33.9313755705953 4.05960654462264 +33.9559003598988 3.99594913622514 +33.9804251492023 4.11168987876605 +34.0049499385059 4.06828710031321 +34.0294747278094 4.10590284163901 +34.053999517113 4.23900469556106 +34.0785243064165 4.05381950749559 +34.1030490957201 4.35185191953845 +34.1275738850236 3.98437506197105 +34.1520986743271 4.30266210395857 +34.1766234636307 4.19270839854469 +34.2011482529342 4.26793988119629 +34.2256730422378 4.30555562252209 +34.2501978315413 4.4965278477146 +34.2747226208448 4.51099544053221 +34.2992474101484 4.57754636749324 +34.3237721994519 4.74826396274109 +34.3482969887555 4.71932877710586 +34.372821778059 4.70486118428825 +34.3973465673625 4.71643525854234 +34.4218713566661 5.02893526340281 +34.4463961459696 4.77141211124928 +34.4709209352732 5.07523156041918 +34.4954457245767 5.26909730417522 +34.5199705138803 5.2285880442859 +34.5444953031838 5.10127322749089 +34.5690200924873 5.41956026947841 +34.5935448817909 5.51504638207466 +34.6180696710944 5.61631953179796 +34.642594460398 5.90567138815026 +34.6671192497015 5.76678249710116 +34.691644039005 6.00983805643708 +34.7161688283086 6.21817139301073 +34.7406936176121 6.49884269367245 +34.7652184069157 6.90104177400214 +34.7897431962192 6.7824075128977 +34.8142679855227 7.10358807344874 +34.8387927748263 7.17592603753681 +34.8633175641298 7.49131956096081 +34.8878423534334 7.87905104847288 +34.9123671427369 8.10763901499119 +34.9368919320405 8.08449086648301 +34.961416721344 8.62557883786179 +34.9859415106475 8.42592605697871 +35.0104662999511 9.11747699366068 +35.0349910892546 9.22743069907455 +35.0595158785582 9.43865755421172 +35.0840406678617 9.48784736979162 +35.1085654571652 9.91608811719301 +35.1330902464688 10.1562501579654 +35.1576150357723 10.2517362705617 +35.1821398250759 10.2285881220535 +35.2066646143794 10.4687501628259 +35.2311894036829 10.3414353460309 +35.2557141929865 10.2633103448158 +35.28023898229 10.48032423708 +35.3047637715936 10.8275464647027 +35.3292885608971 10.3819446059202 +35.3538133502007 10.22569460349 +35.3783381395042 10.3211807160862 +35.4028629288077 10.2719909005063 +35.4273877181113 10.3877316430472 +35.4519125074148 10.2285881220535 +35.4764372967184 10.0405094154245 +35.5009620860219 10.22569460349 +35.5254868753254 10.0781251567503 +35.550011664629 9.60358811233253 +35.5745364539325 9.60069459376901 +35.5990612432361 9.60069459376901 +35.6235860325396 9.36342607156013 +35.6481108218431 9.24768532901921 +35.6726356111467 9.40393533144945 +35.6971604004502 9.01620384393738 +35.7216851897538 9.02488439962795 +35.7462099790573 8.76157421034737 +35.7707347683609 8.87731495288828 +35.7952595576644 8.62847235642531 +35.8197843469679 8.75868069178384 +35.8443091362715 8.38252327852586 +35.868833925575 8.69791680194986 +35.8933587148786 8.55902791090076 +35.9178835041821 8.37673624139882 +35.9424082934856 8.30150475874722 +35.9669330827892 8.399884389907 +35.9914578720927 8.2349538317862 +36.0159826613963 8.36805568570825 +36.0405074506998 8.44039364979632 +36.0650322400033 8.24942142460381 +36.0895570293069 8.22627327609563 +36.1140818186104 8.35648161145416 +36.138606607914 8.04398160659368 +36.1631313972175 7.83275475145651 +36.1876561865211 7.90509271554459 +36.2121809758246 8.27256957311199 +36.2367057651281 7.93692141974334 +36.2612305544317 7.7054399346615 +36.2857553437352 7.88483808559992 +36.3102801330388 7.74884271311435 +36.3348049223423 7.56076400648536 +36.3593297116458 7.51157419090547 +36.3838545009494 7.46238437532558 +36.4083792902529 7.4392362268174 +36.4329040795565 7.55208345079479 +36.45742886886 7.24537048306136 +36.4819536581635 6.81712973565997 +36.5064784474671 6.7824075128977 +36.5310032367706 6.84317140273168 +36.5555280260742 6.61458343621337 +36.5800528153777 6.29340287566233 +36.6045776046813 6.34259269124221 +36.6291023939848 6.41493065533029 +36.6536271832883 6.125578798978 +36.6781519725919 5.81886583124457 +36.7026767618954 5.76388897853763 +36.727201551199 5.77835657135525 +36.7517263405025 5.34143526826329 +36.776251129806 5.2922454526834 +36.8007759191096 5.27777785986578 +36.8253007084131 5.07233804185566 +36.8498254977167 4.97395841069588 +36.8743502870202 5.09548619036384 +36.8988750763237 5.1533565616343 +36.9233998656273 4.84664359390087 +36.9479246549308 4.47048618064289 +36.9724494442344 4.67592599865302 +36.9969742335379 4.66435192439893 +37.0214990228415 4.50520840340517 +37.046023812145 4.52546303334983 +37.0705486014485 4.45023155069823 +37.0950733907521 4.57754636749324 +37.1195981800556 4.23032413987049 +37.1441229693592 4.21296302848935 +37.1686477586627 4.18402784285412 +37.1931725479662 4.37210654948311 +37.2176973372698 4.19849543567174 +37.2422221265733 4.2563658069422 +37.2667469158769 4.19560191710822 +37.2912717051804 4.03645839611446 +37.3157964944839 4.02199080329684 +37.3403212837875 4.13194450871071 +37.364846073091 3.93518524639116 +37.3893708623946 4.06828710031321 +37.4138956516981 3.89467598650183 +37.4384204410017 4.05092598893207 +37.4629452303052 3.7644676511433 +37.4874700196087 3.94965283920877 +37.5119948089123 3.90914357931945 +37.5365195982158 3.70370376130932 +37.5610443875194 3.96122691346286 +37.5855691768229 4.08275469313082 +37.6100939661264 3.94675932064525 +37.63461875543 3.72685190981751 +37.6591435447335 3.76736116970683 +37.6836683340371 3.77025468827035 +37.7081931233406 4.01331024760627 +37.7327179126441 3.72395839125398 +37.7572427019477 4.02488432186037 +37.7817674912512 3.93229172782763 +37.8062922805548 3.64583339003886 +37.8308170698583 3.61689820440364 +37.8553418591619 3.74421302119864 +37.8798666484654 3.62268524153068 +37.9043914377689 3.91493061644649 +37.9289162270725 3.78761579965149 +37.953441016376 3.83680561523138 +37.9779658056796 3.71527783556341 +38.0024905949831 3.66898153854705 +38.0270153842866 3.72395839125398 +38.0515401735902 3.7644676511433 +38.0760649628937 3.79629635534206 +38.1005897521973 3.59664357445897 +38.1251145415008 3.69502320561875 +38.1496393308043 3.79629635534206 +38.1741641201079 3.63136579722125 +38.1986889094114 3.63136579722125 +38.223213698715 3.73553246550807 +38.2477384880185 3.68923616849171 +38.2722632773221 3.87152783799365 +38.2967880666256 3.72395839125398 +38.3213128559291 3.77604172539739 +38.3458376452327 3.50115746186272 +38.3703624345362 3.64004635291182 +38.3948872238398 3.82523154097728 +38.4194120131433 3.80497691103262 +38.4439368024468 3.89756950506536 +38.4684615917504 3.8396991337949 +38.4929863810539 3.92071765357354 +38.5175111703575 3.87152783799365 +38.542035959661 3.85127320804899 +38.5665607489645 3.8396991337949 +38.5910855382681 3.79629635534206 +38.6156103275716 4.03067135898741 +38.6401351168752 3.99594913622514 +38.6646599061787 4.10011580451196 +38.6891846954823 3.92650469070059 +38.7137094847858 4.05381950749559 +38.7382342740893 4.12326395302015 +38.7627590633929 4.31423617821266 +38.7872838526964 4.1435185829648 +38.811808642 4.28530099257743 +38.8363334313035 4.08564821169435 +38.860858220607 4.29687506683152 +38.8853830099106 4.20138895423526 +38.9099077992141 4.2563658069422 +38.9344325885177 4.40972229080891 +38.9589573778212 4.51099544053221 +38.9834821671247 4.52835655191335 +39.0080069564283 4.70486118428825 +39.0325317457318 4.49363432915108 +39.0570565350354 4.70486118428825 +39.0815813243389 4.70775470285177 +39.1061061136425 4.6730324800895 +39.130630902946 5.04629637478395 +39.1551556922495 5.05787044903804 +39.1796804815531 5.18518526583305 +39.2042052708566 5.30092600837397 +39.2287300601602 5.39641212097022 +39.2532548494637 5.33854174969977 +39.2777796387672 5.52083341920171 +39.3023044280708 6.00983805643708 +39.3268292173743 5.92013898096787 +39.3513540066779 5.95486120373014 +39.3758787959814 6.1516204660497 +39.4004035852849 6.21817139301073 +39.4249283745885 6.47280102660075 +39.449453163892 6.82291677278702 +39.4739779531956 6.63194454759451 +39.4985027424991 6.92997695963737 +39.5230275318027 6.95023158958202 +39.5475523211062 6.99652788659839 +39.5720771104097 7.32928252140353 +39.5966018997133 7.70833345322503 +39.6211266890168 7.46238437532558 +39.6456514783204 7.58101863643002 +39.6701762676239 7.91377327123515 +39.6947010569274 7.82118067720242 +39.719225846231 7.86747697421879 +39.7437506355345 8.14814827488051 +39.7682754248381 8.12500012637233 +39.7928002141416 8.28993068449313 +39.8173250034451 8.06423623653835 +39.8418497927487 8.22048623896858 +39.8663745820522 8.23206031322267 +39.8908993713558 8.2986112401837 +39.9154241606593 8.20312512758744 +39.9399489499629 8.29571772162018 +39.9644737392664 8.11631957068176 +39.9889985285699 8.04108808803016 +40.0135233178735 8.00636586526789 +40.038048107177 7.73437512029673 +40.0625728964806 7.83564827002003 +40.0870976857841 7.26851863156955 +40.1116224750876 7.35821770703876 +40.1361472643912 6.99074084947135 +40.1606720536947 7.21064826029909 +40.1851968429983 6.98206029378078 +40.2097216323018 6.55092602781586 +40.2342464216053 6.51909732361711 +40.2587712109089 6.45543991521961 +40.2832960002124 6.07638898339811 +40.307820789516 6.13136583610504 +40.3323455788195 6.03587972350879 +40.3568703681231 5.54398156770989 +40.3813951574266 5.46296304793125 +40.4059199467301 5.5063658263841 +40.4304447360337 5.56134267909103 +40.4549695253372 5.24884267423056 +40.4794943146408 5.08101859754623 +40.5040191039443 4.97395841069588 +40.5285438932478 4.99421304064054 +40.5530686825514 4.97395841069588 +40.5775934718549 4.82638896395621 +40.6021182611585 4.89583340948076 +40.626643050462 4.65567136870836 +40.6511678397655 4.74826396274109 +40.6756926290691 4.63831025732722 +40.7002174183726 4.51388895909574 +40.7247422076762 4.77719914837632 +40.7492669969797 4.62384266450961 +40.7737917862833 4.54861118185801 +40.7983165755868 4.52546303334983 +40.8228413648903 4.293981548268 +40.8473661541939 4.35185191953845 +40.8718909434974 4.1550926572189 +40.896415732801 4.27662043688686 +40.9209405221045 4.37210654948311 +40.945465311408 4.29976858539504 +40.9699901007116 4.36342599379255 +40.9945148900151 4.24189821412458 +41.0190396793187 4.05960654462264 +41.0435644686222 4.49074081058755 +41.0680892579257 4.37789358661016 +41.0926140472293 4.28819451114095 +41.1171388365328 4.36342599379255 +41.1416636258364 4.27951395545038 +41.1661884151399 4.30555562252209 +41.1907132044435 4.293981548268 +41.215237993747 4.47048618064289 +41.2397627830505 4.38368062373721 +41.2642875723541 4.42418988362653 +41.2888123616576 4.30555562252209 +41.3133371509612 4.421296365063 +41.3378619402647 4.35474543810198 +41.3623867295682 4.67592599865302 +41.3869115188718 4.57465284892972 +41.4114363081753 4.50231488484165 +41.4359610974789 4.46469914351585 +41.4604858867824 4.5601852561121 +41.4850106760859 4.69618062859768 +41.5095354653895 4.82928248251973 +41.534060254693 4.85821766815496 +41.5585850439966 4.87847229809962 +41.5831098333001 4.78877322263041 +41.6076346226037 4.91608803942542 +41.6321594119072 5.01736118914872 +41.6566842012107 5.24016211853999 +41.6812089905143 5.23726859997646 +41.7057337798178 5.17071767301544 +41.7302585691214 5.30092600837397 +41.7547833584249 5.06076396760157 +41.7793081477284 5.21122693290476 +41.803832937032 5.5324074934558 +41.8283577263355 5.41087971378784 +41.8528825156391 5.50347230782057 +41.8774073049426 5.60185193898035 +41.9019320942461 5.56134267909103 +41.9264568835497 5.5324074934558 +41.9509816728532 5.69733805157661 +41.9755064621568 5.74363434859297 +42.0000312514603 5.75520842284706 +42.0245560407639 5.58159730903569 +42.0490808300674 5.92881953665844 +42.0736056193709 6.04456027919935 +42.0981304086745 5.84490749831627 +42.122655197978 5.82465286837162 +42.1471799872816 5.99537046361947 +42.1717047765851 6.31365750560699 +42.1962295658886 6.28472231997176 +42.2207543551922 6.34837972836926 +42.2452791444957 6.30208343135289 +42.2698039337993 6.31365750560699 +42.2943287231028 6.60879639908632 +42.3188535124063 6.31944454273403 +42.3433783017099 6.42071769245733 +42.3679030910134 6.61747695477689 +42.392427880317 6.42071769245733 +42.4169526696205 6.20081028162959 +42.4414774589241 6.59722232483223 +42.4660022482276 6.58564825057814 +42.4905270375311 6.25578713433653 +42.5150518268347 6.34259269124221 +42.5395766161382 6.34837972836926 +42.5641014054418 6.38888898825858 +42.5886261947453 6.16319454030379 +42.6131509840488 6.33391213555165 +42.6376757733524 6.25000009720948 +42.6622005626559 5.95196768516662 +42.6867253519595 5.75520842284706 +42.711250141263 5.90277786958673 +42.7357749305665 5.70023157014013 +42.7602997198701 5.67418990306842 +42.7848245091736 5.28935193411988 +42.8093492984772 5.24016211853999 +42.8338740877807 5.34432878682681 +42.8583988770843 4.93055563224304 +42.8829236663878 5.02604174483929 +42.9074484556913 4.89583340948076 +42.9319732449949 4.90740748373485 +42.9564980342984 4.52835655191335 +42.981022823602 4.70196766572473 +43.0055476129055 4.48784729202403 +43.030072402209 4.47916673633346 +43.0545971915126 4.24479173268811 +43.0791219808161 4.1435185829648 +43.1036467701197 4.27372691832334 +43.1281715594232 4.01909728473332 +43.1526963487267 4.01909728473332 +43.1772211380303 3.92071765357354 +43.2017459273338 3.74131950263512 +43.2262707166374 3.89756950506536 +43.2507955059409 3.85127320804899 +43.2753202952445 3.83391209666785 +43.299845084548 3.65162042716591 +43.3243698738515 3.49537042473567 +43.3488946631551 3.76736116970683 +43.3734194524586 3.47800931335454 +43.3979442417622 3.59085653733193 +43.4224690310657 3.39699079357589 +43.4469938203692 3.45775468340987 +43.4715186096728 3.59375005589545 +43.4960433989763 3.4606482019734 +43.5205681882799 3.46354172053692 +43.5450929775834 3.32465282948782 +43.569617766887 3.46354172053692 +43.5941425561905 3.31018523667021 +43.618667345494 3.14814819711292 +43.6431921347976 3.35358801512305 +43.6677169241011 3.39409727501237 +43.6922417134047 3.30729171810668 +43.7167665027082 3.23495375401861 +43.7412912920117 3.27546301390793 +43.7658160813153 3.36805560794067 +43.7903408706188 3.2465278282727 +43.8148656599224 3.11631949291417 +43.8393904492259 3.18865745700224 +43.8639152385294 3.10474541866008 +43.888440027833 3.15682875280349 +43.9129648171365 3.04687504738962 +43.9374896064401 3.16840282705758 +43.9620143957436 3.07002319589781 +43.9865391850472 3.1828704198752 +44.0110639743507 2.97743060186507 +44.0355887636542 3.08449078871542 +44.0601135529578 3.17708338274815 +44.0846383422613 2.96296300904746 +44.1091631315649 3.11342597435065 +44.1336879208684 3.02372689888144 +44.1582127101719 3.02662041744496 +44.1827374994755 3.0439815288261 +44.207262288779 2.8674768964512 +44.2317870780826 2.91087967490405 +44.2563118673861 2.90219911921348 +44.2808366566896 3.03819449169905 +44.3053614459932 2.9803241204286 +44.3298862352967 3.0815972701519 +44.3544110246003 2.92245374915814 +44.3789358139038 3.05844912164371 +44.4034606032074 2.93981486053927 +44.4279853925109 3.04108801026258 +44.4525101818144 2.76041670960085 +44.477034971118 2.89641208208643 +44.5015597604215 2.87326393357825 +44.5260845497251 2.97453708330155 +44.5506093390286 2.79803245092665 +44.5751341283321 3.17418986418463 +44.5996589176357 2.85011578507007 +44.6241837069392 2.7662037467279 +44.6487084962428 2.98900467611916 +44.6732332855463 2.88483800783234 +44.6977580748498 2.88483800783234 +44.7222828641534 3.01793986175439 +44.7468076534569 2.87905097070529 +44.7713324427605 2.92245374915814 +44.795857232064 3.04976856595315 +44.8203820213676 2.90219911921348 +44.8449068106711 2.78356485810904 +44.8694315999746 2.78645837667256 +44.8939563892782 2.8414352293795 +44.9184811785817 2.90798615634052 +44.9430059678853 2.87905097070529 +44.9675307571888 3.06712967733428 +44.9920555464923 2.87037041501472 +45.0165803357959 2.82118059943484 +45.0411051250994 2.74594911678324 +45.065629914403 3.05266208451667 +45.0901547037065 2.87905097070529 +45.11467949301 3.06423615877076 +45.1392042823136 3.07870375158837 +45.1637290716171 2.94849541622984 +45.1882538609207 3.07581023302485 +45.2127786502242 2.98321763899212 +45.2373034395278 3.01215282462735 +45.2618282288313 2.8414352293795 +45.2863530181348 2.99479171324621 +45.3108778074384 3.04976856595315 +45.3354025967419 2.87905097070529 +45.3599273860455 3.09895838153304 +45.384452175349 3.02372689888144 +45.4089769646525 3.16261578993054 +45.4335017539561 2.85879634076063 +45.4580265432596 3.17418986418463 +45.4825513325632 3.0063657875003 +45.5070761218667 3.17708338274815 +45.5316009111702 3.19733801269281 +45.5561257004738 3.24074079114566 +45.5806504897773 3.13657412285883 +45.6051752790809 3.26678245821736 +45.6297000683844 3.10185190009656 +45.654224857688 3.05555560308019 +45.6787496469915 3.15682875280349 +45.703274436295 3.20023153125634 +45.7277992255986 3.33622690374191 +45.7523240149021 3.46643523910044 +45.7768488042057 3.13946764142236 +45.8013735935092 3.27256949534441 +45.8258983828127 3.47511579479101 +45.8504231721163 3.39120375644885 +45.8749479614198 3.21759264263747 +45.8994727507234 3.57928246307784 +45.9239975400269 3.41724542352055 +45.9485223293304 3.47800931335454 +45.973047118634 3.68344913136466 +45.9975719079375 3.64583339003886 +46.0220966972411 3.55324079600613 +46.0466214865446 3.53587968462499 +46.0711462758482 3.55902783313318 +46.0956710651517 3.49247690617215 +46.1201958544552 3.67766209423762 +46.1447206437588 3.72685190981751 +46.1692454330623 3.64004635291182 +46.1937702223659 3.77025468827035 +46.2182950116694 3.61111116727659 +46.2428198009729 3.97280098771695 +46.2673445902765 3.72685190981751 +46.29186937958 3.81076394815967 +46.3163941688836 3.84259265235842 +46.3409189581871 3.80497691103262 +46.3654437474906 3.87152783799365 +46.3899685367942 3.72974542838103 +46.4144933260977 3.96990746915343 +46.4390181154013 3.89178246793831 +46.4635429047048 3.81655098528672 +46.4880676940084 3.80787042959615 +46.5125924833119 3.55034727744261 +46.5371172726154 3.82523154097728 +46.561642061919 3.57060190738727 +46.5861668512225 3.75000005832569 +46.6106916405261 3.84548617092195 +46.6352164298296 3.57928246307784 +46.6597412191331 3.58506950020488 +46.6842660084367 3.55324079600613 +46.7087907977402 3.55034727744261 +46.7333155870438 3.4866898690451 +46.7578403763473 3.64872690860239 +46.7823651656508 3.49537042473567 +46.8068899549544 3.56770838882375 +46.8314147442579 3.64583339003886 +46.8559395335615 3.50983801755329 +46.880464322865 3.39409727501237 +46.9049891121686 3.45486116484635 +46.9295139014721 3.43460653490169 +46.9540386907756 3.28993060672555 +46.9785634800792 3.30439819954316 +47.0030882693827 3.27256949534441 +47.0276130586863 3.31307875523373 +47.0521378479898 3.33622690374191 +47.0766626372933 3.4866898690451 +47.1011874265969 3.42013894208408 +47.1257122159004 3.1452546785494 +47.150237005204 3.39409727501237 +47.1747617945075 3.30150468097964 +47.199286583811 3.27256949534441 +47.2238113731146 3.24942134683623 +47.2483361624181 3.42881949777465 +47.2728609517217 3.44618060915578 +47.2973857410252 3.4866898690451 +47.3219105303288 3.46932875766397 +47.3464353196323 3.50983801755329 +47.3709601089358 3.48958338760863 +47.3954848982394 3.41145838639351 +47.4200096875429 3.44039357202874 +47.4445344768465 3.47800931335454 +47.46905926615 3.46354172053692 +47.4935840554535 3.47511579479101 +47.5181088447571 3.54745375887909 +47.5426336340606 3.69212968705523 +47.5671584233642 3.6371528343483 +47.5916832126677 3.81944450385024 +47.6162080019712 3.74131950263512 +47.6407327912748 3.77025468827035 +47.6652575805783 3.6255787600942 +47.6897823698819 3.99594913622514 +47.7143071591854 3.71527783556341 +47.738831948489 3.92071765357354 +47.7633567377925 3.98437506197105 +47.787881527096 3.94386580208172 +47.8124063163996 4.17245376860003 +47.8369311057031 4.09722228594844 +47.8614558950067 3.99884265478866 +47.8859806843102 4.05381950749559 +47.9105054736137 4.19849543567174 +47.9350302629173 4.19270839854469 +47.9595550522208 4.44733803213471 +47.9840798415244 4.51099544053221 +48.0086046308279 4.36921303091959 +48.0331294201314 4.59201396031086 +48.057654209435 4.67592599865302 +48.0821789987385 4.74247692561405 +48.1067037880421 4.60358803456495 +48.1312285773456 4.71064822141529 +48.1557533666492 5.00000007776759 +48.1802781559527 4.9768519292594 +48.2048029452562 5.07523156041918 +48.2293277345598 5.0781250789827 +48.2538525238633 5.09259267180032 +48.2783773131669 5.06655100472861 +48.3029021024704 5.06076396760157 +48.3274268917739 5.30381952693749 +48.3519516810775 5.38194452815261 +48.376476470381 5.51215286351114 +48.4010012596846 5.57581027190864 +48.4255260489881 5.26909730417522 +48.4500508382916 5.50057878925705 +48.4745756275952 5.52083341920171 +48.4991004168987 5.88831027676912 +48.5236252062023 5.89699083245969 +48.5481499955058 5.54398156770989 +48.5726747848094 5.83333342406218 +48.5971995741129 5.78125008991877 +48.6217243634164 5.76388897853763 +48.64624915272 5.78125008991877 +48.6707739420235 6.00405101931003 +48.6952987313271 6.14004639179561 +48.7198235206306 5.98379638936537 +48.7443483099341 5.76099545997411 +48.7688730992377 5.82175934980809 +48.7933978885412 5.98958342649242 +48.8179226778448 5.86805564682446 +48.8424474671483 5.85648157257037 +48.8669722564518 6.09375009477924 +48.8914970457554 5.98958342649242 +48.9160218350589 5.54687508627342 +48.9405466243625 5.56423619765455 +48.965071413666 5.53530101201932 +48.9895962029696 5.83622694262571 +49.0141209922731 5.75810194141059 +49.0386457815766 5.47453712218534 +49.0631705708802 5.41377323235136 +49.0876953601837 5.51793990063819 +49.1122201494873 5.6076389761074 +49.1367449387908 5.48900471500296 +49.1612697280943 5.24594915566703 +49.1857945173979 5.30092600837397 +49.2103193067014 5.32696767544567 +49.234844096005 5.02604174483929 +49.2593688853085 5.08391211610975 +49.283893674612 4.78298618550337 +49.3084184639156 5.09548619036384 +49.3329432532191 4.73090285135995 +49.3574680425227 4.60648155312847 +49.3819928318262 4.92476859511599 +49.4065176211298 4.67592599865302 +49.4310424104333 4.82060192682916 +49.4555671997368 4.45023155069823 +49.4800919890404 4.37210654948311 +49.5046167783439 4.53125007047687 +49.5291415676475 4.29976858539504 +49.553666356951 4.47337969920642 +49.5781911462545 4.48495377346051 +49.6027159355581 4.24189821412458 +49.6272407248616 4.22164358417992 +49.6517655141652 4.10011580451196 +49.6762903034687 4.04513895180503 +49.7008150927722 3.97569450628048 +49.7253398820758 3.94675932064525 +49.7498646713793 3.91203709788297 +49.7743894606829 3.93807876495468 +49.7989142499864 3.82523154097728 +49.82343903929 3.47222227622749 +49.8479638285935 3.71527783556341 +49.872488617897 3.53587968462499 +49.8970134072006 3.72685190981751 +49.9215381965041 3.52141209180738 +49.9460629858077 3.54745375887909 +49.9705877751112 3.57349542595079 +49.9951125644147 3.46643523910044 +50.0196373537183 3.54456024031556 +50.0441621430218 3.45486116484635 +50.0686869323254 3.29861116241612 +50.0932117216289 3.38831023788533 +50.1177365109324 3.41724542352055 +50.142261300236 3.37384264506771 +50.1667860895395 3.26967597678089 +50.1913108788431 3.20601856838338 +50.2158356681466 3.28993060672555 +50.2403604574502 3.17708338274815 +50.2648852467537 3.17708338274815 +50.2894100360572 3.14236115998588 +50.3139348253608 3.20023153125634 +50.3384596146643 3.07581023302485 +50.3629844039679 3.09895838153304 +50.3875091932714 3.10185190009656 +50.4120339825749 2.74305559821972 +50.4365587718785 3.0063657875003 +50.461083561182 3.14814819711292 +50.4856083504856 3.02372689888144 +50.5101331397891 2.91956023059461 +50.5346579290926 3.14236115998588 +50.5591827183962 3.05555560308019 +50.5837075076997 3.02662041744496 +50.6082322970033 2.96585652761098 +50.6327570863068 3.11053245578713 +50.6572818756104 2.905092637777 +50.6818066649139 3.12789356716826 +50.7063314542174 2.91956023059461 +50.730856243521 2.91666671203109 +50.7553810328245 3.00347226893678 +50.7799058221281 3.00057875037326 +50.8044306114316 2.94560189766632 +50.8289554007351 2.95428245335689 +50.8534801900387 2.89930560064995 +50.8780049793422 3.07291671446133 +50.9025297686458 2.99768523180973 +50.9270545579493 3.10474541866008 +50.9515793472528 2.87615745214177 +50.9761041365564 2.79803245092665 +51.0006289258599 3.02951393600848 +51.0251537151635 2.93981486053927 +51.049678504467 3.04687504738962 +51.0742032937706 2.99479171324621 +51.0987280830741 2.98900467611916 +51.1232528723776 3.03240745457201 +51.1477776616812 3.07581023302485 +51.1723024509847 3.06134264020724 +51.1968272402883 3.17708338274815 +51.2213520295918 2.94560189766632 +51.2458768188953 2.92824078628518 +51.2704016081989 2.97453708330155 +51.2949263975024 2.95138893479337 +51.319451186806 2.82118059943484 +51.3439759761095 3.15393523423997 +51.368500765413 2.92824078628518 +51.3930255547166 3.00347226893678 +51.4175503440201 3.23206023545509 +51.4420751333237 3.15972227136702 +51.4665999226272 3.1828704198752 +51.4911247119308 3.17129634562111 +51.5156495012343 3.40567134926646 +51.5401742905378 3.16261578993054 +51.5646990798414 3.07870375158837 +51.5892238691449 3.33912042230544 +51.6137486584485 3.37673616363123 +51.638273447752 3.18576393843872 +51.6627982370555 3.18576393843872 +51.6873230263591 3.26678245821736 +51.7118478156626 3.26099542109032 +51.7363726049662 3.20312504981986 +51.7608973942697 3.17129634562111 +51.7854221835732 3.11342597435065 +51.8099469728768 3.24942134683623 +51.8344717621803 3.27546301390793 +51.8589965514839 3.35648153368657 +51.8835213407874 3.21469912407395 +51.908046130091 3.37094912650419 +51.9325709193945 3.36805560794067 +51.957095708698 3.33622690374191 +51.9816204980016 3.29571764385259 +52.0061452873051 3.46932875766397 +52.0306700766087 3.57638894451431 +52.0551948659122 3.39699079357589 +52.0797196552157 3.5243056103709 +52.1042444445193 3.56770838882375 +52.1287692338228 3.57928246307784 +52.1532940231264 3.6255787600942 +52.1778188124299 3.55902783313318 +52.2023436017334 3.50983801755329 +52.226868391037 3.6255787600942 +52.2513931803405 3.70659727987285 +52.2759179696441 3.6371528343483 +52.3004427589476 3.90625006075593 +52.3249675482512 3.71817135412694 +52.3494923375547 3.78472228108796 +52.3740171268582 3.75578709545273 +52.3985419161618 3.77893524396092 +52.4230667054653 3.50405098042624 +52.4475914947689 3.69502320561875 +52.4721162840724 3.77025468827035 +52.4966410733759 3.85127320804899 +52.5211658626795 3.65740746429296 +52.545690651983 3.67476857567409 +52.5702154412866 3.43171301633817 +52.5947402305901 3.55902783313318 +52.6192650198936 3.56770838882375 +52.6437898091972 3.47222227622749 +52.6683145985007 3.44039357202874 +52.6928393878043 3.39988431213942 +52.7173641771078 3.55034727744261 +52.7418889664114 3.44039357202874 +52.7664137557149 3.3854167193218 +52.7909385450184 3.46354172053692 +52.815463334322 3.36516208937714 +52.8399881236255 3.30150468097964 +52.8645129129291 3.45196764628283 +52.8890377022326 3.26388893965384 +52.9135624915361 3.18865745700224 +52.9380872808397 2.98321763899212 +52.9626120701432 3.09317134440599 +52.9871368594468 2.95717597192041 +53.0116616487503 3.09606486296951 +53.0361864380538 3.05555560308019 +53.0607112273574 3.12500004860474 +53.0852360166609 3.1828704198752 +53.1097608059645 2.99479171324621 +53.134285595268 3.0815972701519 +53.1588103845716 2.93113430484871 +53.1833351738751 2.79803245092665 +53.2078599631786 2.95717597192041 +53.2323847524822 3.00347226893678 +53.2569095417857 3.23495375401861 +53.2814343310893 2.91666671203109 +53.3059591203928 2.87326393357825 +53.3304839096963 2.97743060186507 +53.3550086989999 2.94560189766632 +53.3795334883034 2.91666671203109 +53.404058277607 3.02662041744496 +53.4285830669105 3.04976856595315 +53.453107856214 2.91087967490405 +53.4776326455176 2.9803241204286 +53.5021574348211 3.07291671446133 +53.5266822241247 2.91956023059461 +53.5512070134282 2.95717597192041 +53.5757318027318 3.00347226893678 +53.6002565920353 3.03819449169905 +53.6247813813388 2.96006949048394 +53.6493061706424 2.97453708330155 +53.6738309599459 2.91666671203109 +53.6983557492495 3.21469912407395 +53.722880538553 3.29861116241612 +53.7474053278565 3.26388893965384 +53.7719301171601 3.06423615877076 +53.7964549064636 3.25520838396327 +53.8209796957672 3.26678245821736 +53.8455044850707 3.12500004860474 +53.8700292743742 3.23206023545509 +53.8945540636778 3.35069449655953 +53.9190788529813 3.21180560551043 +53.9436036422849 3.29282412528907 +53.9681284315884 3.31886579236078 +53.992653220892 3.33912042230544 +54.0171780101955 3.21469912407395 +54.041702799499 3.26678245821736 +54.0662275888026 3.43460653490169 +54.0907523781061 3.3854167193218 +54.1152771674097 3.42881949777465 +54.1398019567132 3.4230324606476 +54.1643267460167 3.70370376130932 +54.1888515353203 3.50983801755329 +54.2133763246238 3.60821764871307 +54.2379011139274 3.63425931578477 +54.2624259032309 3.50405098042624 +54.2869506925344 3.36516208937714 +54.311475481838 3.43750005346522 +54.3360002711415 3.49247690617215 +54.3605250604451 3.40277783070294 +54.3850498497486 3.47222227622749 +54.4095746390522 3.42881949777465 +54.4340994283557 3.7008102427458 +54.4586242176592 3.60243061158602 +54.4831490069628 3.51562505468033 +54.5076737962663 3.59664357445897 +54.5321985855699 3.27256949534441 +54.5567233748734 3.44618060915578 +54.5812481641769 3.43460653490169 +54.6057729534805 3.30729171810668 +54.630297742784 3.61400468584011 +54.6548225320876 3.22916671689157 +54.6793473213911 3.29861116241612 +54.7038721106946 3.37384264506771 +54.7283968999982 3.06423615877076 +54.7529216893017 3.27256949534441 +54.7774464786053 3.46932875766397 +54.8019712679088 3.19155097556577 +54.8264960572124 3.19444449412929 +54.8510208465159 3.23784727258213 +54.8755456358194 3.13078708573179 +54.900070425123 3.25520838396327 +54.9245952144265 3.17418986418463 +54.9491200037301 3.07870375158837 +54.9736447930336 3.09027782584247 +54.9981695823371 3.04687504738962 +55.0226943716407 3.1452546785494 +55.0472191609442 2.96296300904746 +55.0717439502478 3.02372689888144 +55.0962687395513 3.23784727258213 +55.1207935288548 3.03240745457201 +55.1453183181584 3.26678245821736 +55.1698431074619 3.09317134440599 +55.1943678967655 3.06423615877076 +55.218892686069 2.98900467611916 +55.2434174753726 3.10185190009656 +55.2679422646761 2.82118059943484 +55.2924670539796 2.99479171324621 +55.3169918432832 2.81250004374427 +55.3415166325867 2.78935189523608 +55.3660414218903 3.13078708573179 +55.3905662111938 2.92245374915814 +55.4150910004973 2.78067133954551 +55.4396157898009 2.73437504252915 +55.4641405791044 2.8038194880537 +55.488665368408 2.67650467125869 +55.5131901577115 2.69965281976687 +55.537714947015 2.60706022573414 +55.5622397363186 2.67071763413164 +55.5867645256221 2.75173615391029 +55.6112893149257 2.69386578263983 +55.6358141042292 2.70833337545744 +55.6603388935328 2.67939818982221 +55.6848636828363 2.59837967004357 +55.7093884721398 2.63888892993289 +55.7339132614434 2.63310189280585 +55.7584380507469 2.58391207722596 +55.7829628400505 2.67361115269517 +55.807487629354 2.51736115026493 +55.8320124186575 2.51446763170141 +55.8565372079611 2.51736115026493 +55.8810619972646 2.47395837181209 +55.9055867865682 2.49131948319322 +55.9301115758717 2.58391207722596 +55.9546363651752 2.50868059457436 +55.9791611544788 2.43344911192277 +56.0036859437823 2.5636574472813 +56.0282107330859 2.41030096341458 +56.0527355223894 2.37847225921583 +56.077260311693 2.4247685562322 +56.1017851009965 2.35821762927117 +56.1263098903 2.41608800054163 +56.1508346796036 2.27430559092901 +56.1753594689071 2.30613429512776 +56.1998842582107 2.39872688916049 +56.2244090475142 2.41030096341458 +56.2489338368177 2.36689818496174 +56.2734586261213 2.46527781612152 +56.2979834154248 2.33796299932651 +56.3225082047284 2.28877318374662 +56.3470329940319 2.46238429755799 +56.3715577833354 2.22222225678559 +56.396082572639 2.37557874065231 +56.4206073619425 2.47685189037561 +56.4451321512461 2.24537040529378 +56.4696569405496 2.42187503766867 +56.4941817298532 2.37268522208878 +56.5187065191567 2.46817133468504 +56.5432313084602 2.29456022087367 +56.5677560977638 2.22222225678559 +56.5922808870673 2.37557874065231 +56.6168056763709 2.27430559092901 +56.6413304656744 2.42766207479572 +56.6658552549779 2.27430559092901 +56.6903800442815 2.19618058971389 +56.714904833585 2.30034725800071 +56.7394296228886 2.39004633346992 +56.7639544121921 2.2482639238573 +56.7884792014956 2.32928244363594 +56.8130039907992 2.31481485081833 +56.8375287801027 2.26851855380196 +56.8620535694063 2.29745373943719 +56.8865783587098 2.27719910949253 +56.9111031480134 2.23958336816673 +56.9356279373169 2.43923614904981 +56.9601527266204 2.22222225678559 +56.984677515924 2.31481485081833 +57.0092023052275 2.43923614904981 +57.0337270945311 2.26851855380196 +57.0582518838346 2.33506948076299 +57.0827766731381 2.39293985203345 +57.1073014624417 2.26562503523844 +57.1318262517452 2.36111114783469 +57.1563510410488 2.44502318617686 +57.1808758303523 2.49710652032027 +57.2054006196558 2.10069447711763 +57.2299254089594 2.36689818496174 +57.2544501982629 2.36400466639822 +57.2789749875665 2.39004633346992 +57.30349977687 2.29456022087367 +57.3280245661736 2.4884259646297 +57.3525493554771 2.37557874065231 +57.3770741447806 2.44791670474038 +57.4015989340842 2.40740744485106 +57.4261237233877 2.36689818496174 +57.4506485126913 2.46527781612152 +57.4751733019948 2.33217596219946 +57.4996980912983 2.37847225921583 +57.5242228806019 2.44212966761333 +57.5487476699054 2.44791670474038 +57.573272459209 2.43923614904981 +57.5977972485125 2.37268522208878 +57.622322037816 2.39872688916049 +57.6468468271196 2.60127318860709 +57.6713716164231 2.47106485324856 +57.6958964057267 2.54340281733664 +57.7204211950302 2.46527781612152 +57.7449459843338 2.52025466882845 +57.7694707736373 2.52025466882845 +57.7939955629408 2.57233800297187 +57.8185203522444 2.39583337059697 +57.8430451415479 2.39872688916049 +57.8675699308515 2.44502318617686 +57.892094720155 2.33506948076299 +57.9166195094585 2.65625004131403 +57.9411442987621 2.53761578020959 +57.9656690880656 2.37847225921583 +57.9901938773692 2.53472226164607 +58.0147186666727 2.45659726043095 +58.0392434559762 2.50289355744732 +58.0637682452798 2.43055559335924 +58.0882930345833 2.55497689159073 +58.1128178238869 2.32928244363594 +58.1373426131904 2.55787041015425 +58.161867402494 2.30902781369128 +58.1863921917975 2.44791670474038 +58.210916981101 2.53761578020959 +58.2354417704046 2.40162040772401 +58.2599665597081 2.47685189037561 +58.2844913490117 2.55787041015425 +58.3090161383152 2.56655096584482 +58.3335409276187 2.49710652032027 +58.3580657169223 2.41030096341458 +58.3825905062258 2.66782411556812 +58.4071152955294 2.47395837181209 +58.4316400848329 2.64467596705994 +58.4561648741364 2.44791670474038 +58.48068966344 2.69386578263983 +58.5052144527435 2.59259263291653 +58.5297392420471 2.58680559578948 +58.5542640313506 2.63888892993289 +58.5787888206542 2.72280096827506 +58.6033136099577 2.58101855866243 +58.6278383992612 2.71990744971153 +58.6523631885648 2.61863429998823 +58.6768879778683 2.82118059943484 +58.7014127671719 2.62442133711528 +58.7259375564754 2.76331022816438 +58.7504623457789 2.91377319346757 +58.7749871350825 2.71122689402096 +58.799511924386 2.75752319103733 +58.8240367136896 2.72569448683858 +58.8485615029931 2.57233800297187 +58.8730862922966 2.85011578507007 +58.8976110816002 2.77777782098199 +58.9221358709037 2.88194448926882 +58.9466606602073 2.93692134197575 +58.9711854495108 2.7025463383304 +58.9957102388144 2.79513893236313 +59.0202350281179 2.94560189766632 +59.0447598174214 2.91377319346757 +59.069284606725 2.74016207965619 +59.0938093960285 2.94560189766632 +59.1183341853321 2.85011578507007 +59.1428589746356 3.01504634319087 +59.1673837639391 2.8414352293795 +59.1919085532427 2.90219911921348 +59.2164333425462 2.97743060186507 +59.2409581318498 3.10185190009656 +59.2654829211533 3.1076389372236 +59.2900077104568 3.17997690131168 +59.3145324997604 3.04108801026258 +59.3390572890639 2.89641208208643 +59.3635820783675 3.00925930606382 +59.388106867671 3.07291671446133 +59.4126316569746 3.05555560308019 +59.4371564462781 3.09027782584247 +59.4616812355816 3.1192130114777 +59.4862060248852 3.09317134440599 +59.5107308141887 3.09027782584247 +59.5352556034923 3.08449078871542 +59.5597803927958 3.2465278282727 +59.5843051820993 3.11342597435065 +59.6088299714029 3.23784727258213 +59.6333547607064 3.20891208694691 +59.65787955001 3.39699079357589 +59.6824043393135 3.35069449655953 +59.706929128617 3.12789356716826 +59.7314539179206 3.27835653247146 +59.7559787072241 3.23495375401861 +59.7805034965277 3.29861116241612 +59.8050282858312 3.30729171810668 +59.8295530751348 3.18865745700224 +59.8540778644383 3.32754634805134 +59.8786026537418 3.33912042230544 +59.9031274430454 3.29282412528907 +59.9276522323489 3.3593750522501 +59.9521770216525 3.26099542109032 +59.976701810956 3.18576393843872 +60.0012266002595 3.16261578993054 +60.0257513895631 3.18576393843872 +60.0502761788666 3.05555560308019 +60.0748009681702 3.10474541866008 +60.0993257574737 3.20601856838338 +60.1238505467772 3.16840282705758 +60.1483753360808 2.94849541622984 +60.1729001253843 2.98611115755564 +60.1974249146879 3.07291671446133 +60.2219497039914 3.01504634319087 +60.246474493295 3.06134264020724 +60.2709992825985 2.87905097070529 +60.295524071902 2.96296300904746 +60.3200488612056 2.97164356473803 +60.3445736505091 2.82696763656188 +60.3690984398127 2.86458337788768 +60.3936232291162 2.90219911921348 +60.4181480184197 2.87326393357825 +60.4426728077233 3.03530097313553 +60.4671975970268 2.97164356473803 +60.4917223863304 2.81539356230779 +60.5162471756339 2.78935189523608 +60.5407719649374 2.71990744971153 +60.565296754241 2.78067133954551 +60.5898215435445 2.72569448683858 +60.6143463328481 2.65335652275051 +60.6388711221516 2.6649305970046 +60.6633959114552 2.63599541136937 +60.6879207007587 2.58101855866243 +60.7124454900622 2.67650467125869 +60.7369702793658 2.61284726286119 +60.7614950686693 2.65046300418698 +60.7860198579729 2.67650467125869 +60.8105446472764 2.52314818739198 +60.8350694365799 2.54050929877311 +60.8595942258835 2.74305559821972 +60.884119015187 2.48553244606618 +60.9086438044906 2.58101855866243 +60.9331685937941 2.46238429755799 +60.9576933830976 2.51736115026493 +60.9822181724012 2.63599541136937 +61.0067429617047 2.50578707601084 +61.0312677510083 2.58101855866243 +61.0557925403118 2.62442133711528 +61.0803173296154 2.48553244606618 +61.1048421189189 2.54918985446368 +61.1293669082224 2.49131948319322 +61.153891697526 2.59259263291653 +61.1784164868295 2.45659726043095 +61.2029412761331 2.53182874308254 +61.2274660654366 2.48263892750266 +61.2519908547401 2.58101855866243 +61.2765156440437 2.39872688916049 +61.3010404333472 2.44212966761333 +61.3255652226508 2.5636574472813 +61.3500900119543 2.48553244606618 +61.3746148012578 2.51446763170141 +61.3991395905614 2.48263892750266 +61.4236643798649 2.45370374186743 +61.4481891691685 2.40740744485106 +61.472713958472 2.40451392628754 +61.4972387477756 2.3119213322548 +61.5217635370791 2.41898151910515 +61.5462883263826 2.58391207722596 +61.5708131156862 2.46527781612152 +61.5953379049897 2.57233800297187 +61.6198626942933 2.40451392628754 +61.6443874835968 2.56076392871777 +61.6689122729003 2.47974540893913 +61.6934370622039 2.42766207479572 +61.7179618515074 2.54629633590016 +61.742486640811 2.62442133711528 +61.7670114301145 2.55787041015425 +61.791536219418 2.60127318860709 +61.8160610087216 2.63020837424232 +61.8405857980251 2.72280096827506 +61.8651105873287 2.5260417059555 +61.8896353766322 2.47685189037561 +61.9141601659358 2.67939818982221 +61.9386849552393 2.63020837424232 +61.9632097445428 2.46817133468504 +61.9877345338464 2.50578707601084 +62.0122593231499 2.72569448683858 +62.0367841124535 2.74016207965619 +62.061308901757 2.54629633590016 +62.0858336910605 2.66782411556812 +62.1103584803641 2.65625004131403 +62.1348832696676 2.71412041258449 +62.1594080589712 2.92824078628518 +62.1839328482747 2.75462967247381 +62.2084576375782 2.71122689402096 +62.2329824268818 2.8038194880537 +62.2575072161853 2.71701393114801 +62.2820320054889 2.87905097070529 +62.3065567947924 2.7662037467279 +62.331081584096 2.71701393114801 +62.3556063733995 2.9687500461745 +62.380131162703 2.98900467611916 +62.4046559520066 2.73437504252915 +62.4291807413101 2.75752319103733 +62.4537055306137 2.98321763899212 +62.4782303199172 2.96006949048394 +62.5027551092207 2.88483800783234 +62.5272798985243 2.97743060186507 +62.5518046878278 2.97164356473803 +62.5763294771314 2.85879634076063 +62.6008542664349 2.97453708330155 +62.6253790557384 2.97743060186507 +62.649903845042 3.05555560308019 +62.6744286343455 2.97164356473803 +62.6989534236491 2.93981486053927 +62.7234782129526 3.04976856595315 +62.7480030022562 3.00057875037326 +62.7725277915597 3.00925930606382 +62.7970525808632 3.03819449169905 +62.8215773701668 3.05266208451667 +62.8461021594703 2.93402782341223 +62.8706269487739 3.00347226893678 +62.8951517380774 3.11342597435065 +62.9196765273809 3.01504634319087 +62.9442013166845 3.04687504738962 +62.968726105988 3.14814819711292 +62.9932508952916 2.97164356473803 +63.0177756845951 3.04108801026258 +63.0423004738986 2.89351856352291 +63.0668252632022 2.9427083791028 +63.0913500525057 2.85011578507007 +63.1158748418093 2.97164356473803 +63.1403996311128 3.03530097313553 +63.1649244204164 3.03819449169905 +63.1894492097199 3.0815972701519 +63.2139739990234 2.91087967490405 +63.238498788327 2.94849541622984 +63.2630235776305 2.93981486053927 +63.2875483669341 2.98900467611916 +63.3120731562376 2.87905097070529 +63.3365979455411 2.78935189523608 +63.3611227348447 2.88483800783234 +63.3856475241482 2.76909726529142 +63.4101723134518 2.98900467611916 +63.4346971027553 2.89351856352291 +63.4592218920588 2.85300930363359 +63.4837466813624 2.67071763413164 +63.5082714706659 2.92534726772166 +63.5327962599695 2.93113430484871 +63.557321049273 2.85011578507007 +63.5818458385766 2.90219911921348 +63.6063706278801 2.76909726529142 +63.6308954171836 2.75752319103733 +63.6554202064872 2.8674768964512 +63.6799449957907 2.85879634076063 +63.7044697850943 2.7662037467279 +63.7289945743978 2.94849541622984 +63.7535193637013 3.02662041744496 +63.7780441530049 2.74305559821972 +63.8025689423084 2.78935189523608 +63.827093731612 2.78645837667256 +63.8516185209155 2.75752319103733 +63.876143310219 2.74884263534676 +63.9006680995226 2.92824078628518 +63.9251928888261 2.87326393357825 +63.9497176781297 2.9803241204286 +63.9742424674332 2.9803241204286 +63.9987672567368 2.8298611551254 +64.0232920460403 2.91666671203109 +64.0478168353438 2.83275467368893 +64.0723416246474 2.79224541379961 +64.0968664139509 2.93692134197575 +64.1213912032545 2.8674768964512 +64.145915992558 2.93981486053927 +64.1704407818615 2.92245374915814 +64.1949655711651 2.93692134197575 +64.2194903604686 2.91377319346757 +64.2440151497722 2.98900467611916 +64.2685399390757 3.02083338031792 +64.2930647283793 2.89641208208643 +64.3175895176828 2.91087967490405 +64.3421143069863 3.05266208451667 +64.3666390962899 2.93981486053927 +64.3911638855934 2.9803241204286 +64.415688674897 2.8038194880537 +64.4402134642005 2.68229170838574 +64.464738253504 2.78645837667256 +64.4892630428076 2.95717597192041 +64.5137878321111 3.14814819711292 +64.5383126214147 2.93402782341223 +64.5628374107182 3.02083338031792 +64.5873622000217 2.86458337788768 +64.6118869893253 2.80671300661722 +64.6364117786288 2.8038194880537 +64.6609365679324 3.11053245578713 +64.6854613572359 2.87905097070529 +64.7099861465395 2.95428245335689 +64.734510935843 3.02372689888144 +64.7590357251465 2.905092637777 +64.7835605144501 2.86168985932416 +64.8080853037536 2.93981486053927 +64.8326100930572 2.83564819225245 +64.8571348823607 2.9427083791028 +64.8816596716642 2.87037041501472 +64.9061844609678 2.73148152396563 +64.9307092502713 2.85011578507007 +64.9552340395749 2.98900467611916 +64.9797588288784 2.86458337788768 +65.0042836181819 2.76909726529142 +65.0288084074855 2.96006949048394 +65.053333196789 2.76041670960085 +65.0778579860926 2.6273148556788 +65.1023827753961 2.82696763656188 +65.1269075646997 2.67071763413164 +65.1514323540032 2.69675930120335 +65.1759571433067 2.83854171081597 +65.2004819326103 2.74305559821972 +65.2250067219138 2.75462967247381 +65.2495315112174 2.91666671203109 +65.2740563005209 2.55787041015425 +65.2985810898244 2.589699114353 +65.323105879128 2.50000003888379 +65.3476306684315 2.57812504009891 +65.3721554577351 2.6273148556788 +65.3966802470386 2.68229170838574 +65.4212050363421 2.59259263291653 +65.4457298256457 2.52893522451902 +65.4702546149492 2.47106485324856 +65.4947794042528 2.41030096341458 +65.5193041935563 2.39004633346992 +65.5438289828599 2.55497689159073 +65.5683537721634 2.44212966761333 +65.5928785614669 2.4508102233039 +65.6174033507705 2.50868059457436 +65.641928140074 2.37557874065231 +65.6664529293776 2.26851855380196 +65.6909777186811 2.25115744242082 +65.7155025079846 2.4247685562322 +65.7400272972882 2.36979170352526 +65.7645520865917 2.27430559092901 +65.7890768758953 2.3234954065089 +65.8136016651988 2.46238429755799 +65.8381264545023 2.10648151424468 +65.8626512438059 2.23379633103968 +65.8871760331094 2.2106481825315 +65.911700822413 2.08622688430002 +65.9362256117165 2.31481485081833 +65.9607504010201 2.10069447711763 +65.9852751903236 2.29166670231014 +66.0097999796271 2.27430559092901 +66.0343247689307 2.25405096098435 +66.0588495582342 2.32638892507242 +66.0833743475378 2.3119213322548 +66.1078991368413 2.21932873822207 +66.1324239261448 2.2106481825315 +66.1569487154484 2.33506948076299 +66.1814735047519 2.18171299689627 +66.2059982940555 2.22222225678559 +66.230523083359 2.21932873822207 +66.2550478726625 2.19328707115036 +66.2795726619661 2.08333336573649 +66.3040974512696 2.28009262805605 +66.3286222405732 2.25983799811139 +66.3531470298767 2.19618058971389 +66.3776718191803 2.25694447954787 +66.4021966084838 2.10648151424468 +66.4267213977873 2.04861114297422 +66.4512461870909 2.15277781126104 +66.4757709763944 2.11516206993525 +66.500295765698 2.26562503523844 +66.5248205550015 2.22222225678559 +66.549345344305 2.08912040286354 +66.5738701336086 2.0341435501566 +66.5983949229121 2.11805558849877 +66.6229197122157 2.11226855137172 +66.6474445015192 2.07754632860945 +66.6719692908227 2.07754632860945 +66.6964940801263 2.16724540407866 +66.7210188694298 2.0081018830849 +66.7455436587334 2.14988429269752 +66.7700684480369 2.15277781126104 +66.7945932373405 2.03703706872013 +66.819118026644 2.09490743999059 +66.8436428159475 2.0081018830849 +66.8681676052511 2.16145836695161 +66.8926923945546 2.08333336573649 +66.9172171838582 2.01388892021194 +66.9417419731617 2.18750003402332 +66.9662667624652 2.22800929391264 +66.9907915517688 2.19618058971389 +67.0153163410723 2.06597225435536 +67.0398411303759 2.10648151424468 +67.0643659196794 2.16435188551513 +67.0888907089829 2.06307873579183 +67.1134154982865 2.22511577534912 +67.13794028759 2.09780095855411 +67.1624650768936 2.03993058728365 +67.1869898661971 2.08622688430002 +67.2115146555007 2.12384262562581 +67.2360394448042 1.99074077170376 +67.2605642341077 2.08622688430002 +67.2850890234113 2.08912040286354 +67.3096138127148 2.06886577291888 +67.3341386020184 2.01388892021194 +67.3586633913219 2.20196762684093 +67.3831881806254 2.14988429269752 +67.407712969929 2.16435188551513 +67.4322377592325 1.99942132739433 +67.4567625485361 2.15277781126104 +67.4812873378396 1.92129632617921 +67.5058121271431 2.14120373700695 +67.5303369164467 1.9704861417591 +67.5548617057502 2.16145836695161 +67.5793864950538 1.94444447468739 +67.6039112843573 2.13252318131638 +67.6284360736609 2.09490743999059 +67.6529608629644 2.08912040286354 +67.6774856522679 2.01678243877547 +67.7020104415715 2.19328707115036 +67.726535230875 1.97337966032262 +67.7510600201786 2.02256947590251 +67.7755848094821 2.18171299689627 +67.8001095987856 2.14409725557047 +67.8246343880892 2.13252318131638 +67.8491591773927 2.08333336573649 +67.8736839666963 2.16145836695161 +67.8982087559998 2.16724540407866 +67.9227335453033 2.08043984717297 +67.9472583346069 2.11516206993525 +67.9717831239104 2.28877318374662 +67.996307913214 2.12094910706229 +68.0208327025175 2.05439818010127 +68.0453574918211 1.99074077170376 +68.0698822811246 2.0341435501566 +68.0944070704281 2.06307873579183 +68.1189318597317 2.2482639238573 +68.1434566490352 2.13252318131638 +68.1679814383388 2.12384262562581 +68.1925062276423 2.01388892021194 +68.2170310169458 2.17013892264218 +68.2415558062494 2.2858796651831 +68.2660805955529 2.15567132982457 +68.2906053848565 2.13831021844343 +68.31513017416 2.12384262562581 +68.3396549634635 2.24537040529378 +68.3641797527671 2.07754632860945 +68.3887045420706 2.11516206993525 +68.4132293313742 2.30324077656423 +68.4377541206777 2.1730324412057 +68.4622789099813 2.11516206993525 +68.4868036992848 2.21354170109502 +68.5113284885883 2.1846065154598 +68.5358532778919 2.25115744242082 +68.5603780671954 2.26562503523844 +68.584902856499 2.25983799811139 +68.6094276458025 2.25694447954787 +68.633952435106 2.17592595976923 +68.6584772244096 2.05729169866479 +68.6830020137131 2.16724540407866 +68.7075268030167 2.22800929391264 +68.7320515923202 2.146990774134 +68.7565763816237 2.08043984717297 +68.7811011709273 2.14988429269752 +68.8056259602308 2.23668984960321 +68.8301507495344 2.20196762684093 +68.8546755388379 2.09780095855411 +68.8792003281415 2.2482639238573 +68.903725117445 2.23379633103968 +68.9282499067485 2.33217596219946 +68.9527746960521 2.33506948076299 +68.9772994853556 2.15277781126104 +69.0018242746592 2.40162040772401 +69.0263490639627 2.27141207236548 +69.0508738532662 2.28877318374662 +69.0753986425698 2.23090281247616 +69.0999234318733 2.22222225678559 +69.1244482211769 2.22800929391264 +69.1489730104804 2.25983799811139 +69.1734977997839 2.24247688673025 +69.1980225890875 2.27430559092901 +69.222547378391 2.34375003645356 +69.2470721676946 2.27430559092901 +69.2715969569981 2.38136577777935 +69.2961217463017 2.34375003645356 +69.3206465356052 2.32928244363594 +69.3451713249087 2.18750003402332 +69.3696961142123 2.39583337059697 +69.3942209035158 2.24537040529378 +69.4187456928194 2.35532411070765 +69.4432704821229 2.31481485081833 +69.4677952714264 2.27141207236548 +69.49232006073 2.28877318374662 +69.5168448500335 2.34375003645356 +69.5413696393371 2.46238429755799 +69.5658944286406 2.33796299932651 +69.5904192179441 2.26851855380196 +69.6149440072477 2.36689818496174 +69.6394687965512 2.3119213322548 +69.6639935858548 2.25983799811139 +69.6885183751583 2.42187503766867 +69.7130431644619 2.43344911192277 +69.7375679537654 2.43344911192277 +69.7620927430689 2.37268522208878 +69.7866175323725 2.29166670231014 +69.811142321676 2.20486114540446 +69.8356671109796 2.4247685562322 +69.8601919002831 2.36979170352526 +69.8847166895866 2.14120373700695 +69.9092414788902 2.25405096098435 +69.9337662681937 2.23958336816673 +69.9582910574973 2.41319448197811 +69.9828158468008 2.33796299932651 +70.0073406361043 2.32928244363594 +70.0318654254079 2.17881947833275 +70.0563902147114 2.2482639238573 +70.080915004015 2.1093750328082 +70.1054397933185 2.08622688430002 +70.1299645826221 2.23668984960321 +70.1544893719256 2.21932873822207 +70.1790141612291 2.08043984717297 +70.2035389505327 2.13252318131638 +70.2280637398362 2.25115744242082 +70.2525885291398 2.36979170352526 +70.2771133184433 2.19907410827741 +70.3016381077468 2.28298614661957 +70.3261628970504 2.07465281004592 +70.3506876863539 2.23379633103968 +70.3752124756575 2.13252318131638 +70.399737264961 2.30034725800071 +70.4242620542645 2.22222225678559 +70.4487868435681 2.26562503523844 +70.4733116328716 2.0341435501566 +70.4978364221752 2.11226855137172 +70.5223612114787 2.25694447954787 +70.5468860007823 2.0457176244107 +70.5714107900858 2.09780095855411 +70.5959355793893 2.146990774134 +70.6204603686929 2.18171299689627 +70.6449851579964 2.09780095855411 +70.6695099473 2.28877318374662 +70.6940347366035 2.00520836452138 +70.718559525907 2.14120373700695 +70.7430843152106 2.04282410584717 +70.7676091045141 2.17013892264218 +70.7921338938177 2.08043984717297 +70.8166586831212 2.1093750328082 +70.8411834724247 2.12962966275286 +70.8657082617283 2.13831021844343 +70.8902330510318 2.08912040286354 +70.9147578403354 2.03125003159308 +70.9392826296389 2.05150466153774 +70.9638074189425 2.07465281004592 +70.988332208246 2.16724540407866 +71.0128569975495 2.12384262562581 +71.0373817868531 2.06018521722831 +71.0619065761566 1.91261577048864 +71.0864313654602 2.02256947590251 +71.1109561547637 2.01099540164842 +71.1354809440672 2.01678243877547 +71.1600057333708 2.20486114540446 +71.1845305226743 2.23668984960321 +71.2090553119779 1.8692129920358 +71.2335801012814 1.92418984474273 +71.2581048905849 2.05150466153774 +71.2826296798885 1.97916669744967 +71.307154469192 2.11805558849877 +71.3316792584956 2.02835651302956 +71.3562040477991 1.96180558606853 +71.3807288371027 1.91550928905217 +71.4052536264062 2.06307873579183 +71.4297784157097 1.99074077170376 +71.4543032050133 1.99942132739433 +71.4788279943168 1.99942132739433 +71.5033527836204 1.92418984474273 +71.5278775729239 1.91261577048864 +71.5524023622274 1.92997688186978 +71.576927151531 2.03703706872013 +71.6014519408345 2.10358799568115 +71.6259767301381 2.0081018830849 +71.6505015194416 2.13831021844343 +71.6750263087451 2.00231484595785 +71.6995510980487 2.09201392142706 +71.7240758873522 1.91550928905217 +71.7486006766558 2.08912040286354 +71.7731254659593 2.17013892264218 +71.7976502552629 2.03125003159308 +71.8221750445664 2.13541669987991 +71.8466998338699 2.10358799568115 +71.8712246231735 2.12673614418934 +71.895749412477 2.12673614418934 +71.9202742017806 2.0341435501566 +71.9447989910841 1.87789354772637 +71.9693237803876 2.05439818010127 +71.9938485696912 2.08912040286354 +72.0183733589947 2.0341435501566 +72.0428981482983 2.0717592914824 +72.0674229376018 2.12094910706229 +72.0919477269053 1.99942132739433 +72.1164725162089 2.03993058728365 +72.1409973055124 1.99363429026728 +72.165522094816 2.20196762684093 +72.1900468841195 2.17013892264218 +72.2145716734231 2.06018521722831 +72.2390964627266 2.06018521722831 +72.2636212520301 2.08043984717297 +72.2881460413337 2.10648151424468 +72.3126708306372 2.12962966275286 +72.3371956199408 2.21643521965855 +72.3617204092443 2.05150466153774 +72.3862451985478 2.06597225435536 +72.4107699878514 2.01967595733899 +72.4352947771549 1.95891206750501 +72.4598195664585 2.1730324412057 +72.484344355762 2.28009262805605 +72.5088691450655 2.11805558849877 +72.5333939343691 2.06307873579183 +72.5579187236726 2.0717592914824 +72.5824435129762 2.16145836695161 +72.6069683022797 2.17881947833275 +72.6314930915833 2.00520836452138 +72.6560178808868 2.11516206993525 +72.6805426701903 2.05150466153774 +72.7050674594939 2.14120373700695 +72.7295922487974 2.24537040529378 +72.754117038101 2.15856484838809 +72.7786418274045 2.19039355258684 +72.803166616708 2.24537040529378 +72.8276914060116 2.26562503523844 +72.8522161953151 2.1846065154598 +72.8767409846187 2.14409725557047 +72.9012657739222 1.99363429026728 +72.9257905632257 2.25405096098435 +72.9503153525293 2.146990774134 +72.9748401418328 2.14988429269752 +72.9993649311364 2.12094910706229 +73.0238897204399 2.08043984717297 +73.0484145097435 2.09490743999059 +73.072939299047 2.21643521965855 +73.0974640883505 2.06018521722831 +73.1219888776541 2.3234954065089 +73.1465136669576 2.20486114540446 +73.1710384562612 2.05729169866479 +73.1955632455647 2.18171299689627 +73.2200880348682 2.09490743999059 +73.2446128241718 2.11516206993525 +73.2691376134753 2.08043984717297 +73.2936624027789 2.2482639238573 +73.3181871920824 2.1093750328082 +73.3427119813859 2.18171299689627 +73.3672367706895 2.13831021844343 +73.391761559993 2.08043984717297 +73.4162863492966 2.01678243877547 +73.4408111386001 2.19039355258684 +73.4653359279037 2.16724540407866 +73.4898607172072 2.02835651302956 +73.5143855065107 2.19618058971389 +73.5389102958143 2.18171299689627 +73.5634350851178 2.08043984717297 +73.5879598744214 2.21643521965855 +73.6124846637249 2.12962966275286 +73.6370094530284 2.12962966275286 +73.661534242332 2.25694447954787 +73.6860590316355 2.09490743999059 +73.7105838209391 2.10358799568115 +73.7351086102426 2.23379633103968 +73.7596333995461 2.0341435501566 +73.7841581888497 2.1846065154598 +73.8086829781532 2.3234954065089 +73.8332077674568 2.15277781126104 +73.8577325567603 2.34085651789003 +73.8822573460639 2.15856484838809 +73.9067821353674 2.38136577777935 +73.9313069246709 2.20196762684093 +73.9558317139745 2.16145836695161 +73.980356503278 2.17013892264218 +74.0048812925816 2.15856484838809 +74.0294060818851 2.1846065154598 +74.0539308711886 2.20486114540446 +74.0784556604922 2.1846065154598 +74.1029804497957 2.17013892264218 +74.1275052390993 2.32928244363594 +74.1520300284028 2.00520836452138 +74.1765548177063 2.39293985203345 +74.2010796070099 2.46527781612152 +74.2256043963134 2.16145836695161 +74.250129185617 2.21354170109502 +74.2746539749205 2.20775466396798 +74.2991787642241 2.15856484838809 +74.3237035535276 2.2858796651831 +74.3482283428311 2.23090281247616 +74.3727531321347 2.20775466396798 +74.3972779214382 2.18750003402332 +74.4218027107418 2.11226855137172 +74.4463275000453 2.03125003159308 +74.4708522893488 2.27141207236548 +74.4953770786524 2.19907410827741 +74.5199018679559 2.22800929391264 +74.5444266572595 2.17013892264218 +74.568951446563 2.13831021844343 +74.5934762358665 2.09201392142706 +74.6180010251701 2.12962966275286 +74.6425258144736 2.23090281247616 +74.6670506037772 2.14409725557047 +74.6915753930807 2.11516206993525 +74.7161001823843 2.14409725557047 +74.7406249716878 2.1093750328082 +74.7651497609913 1.98784725314024 +74.7896745502949 2.12384262562581 +74.8141993395984 2.21354170109502 +74.838724128902 2.08333336573649 +74.8632489182055 2.05150466153774 +74.887773707509 2.05729169866479 +74.9122984968126 1.92129632617921 +74.9368232861161 2.08912040286354 +74.9613480754197 2.02546299446604 +74.9858728647232 2.04282410584717 +75.0103976540267 2.06151326082435 +75.0349224433303 2.06120812775158 +75.0594472326338 1.99637714930928 +75.0839720219374 2.00717504177744 +75.1084968112409 1.99755071935408 +75.1330216005445 1.90577197198767 +75.157546389848 1.95753873096915 +75.1820711791515 2.01454066526758 +75.2065959684551 2.03318285799849 +75.2311207577586 2.00398950153676 +75.2556455470622 2.02830192272385 +75.2801703363657 1.95351312127093 +75.3046951256692 1.96588960010218 +75.3292199149728 1.98623100189676 +75.3537447042763 1.92340055156119 +75.3782694935799 1.92785573106247 +75.4027942828834 1.95970136068656 +75.4273190721869 1.90640679161797 +75.4518438614905 1.94284133483953 +75.476368650794 1.95397654192466 +75.5008934400976 1.89158495875804 +75.5254182294011 1.94313717054641 +75.5499430187047 1.91798086587206 +75.5744678080082 1.87041111556537 +75.5989925973117 1.91573909236417 +75.6235173866153 1.94339701067935 +75.6480421759188 1.91284938783037 +75.6725669652224 1.88644418335047 +75.6970917545259 1.87732642979738 +75.7216165438294 1.90129605303133 +75.746141333133 1.89456143514177 +75.7706661224365 1.81317937795493 +75.7951909117401 1.84208619607567 +75.8197157010436 1.887054449496 +75.8442404903471 1.91745522685318 +75.8687652796507 1.93152543141194 +75.8932900689542 1.89712526714776 +75.9178148582578 1.8771419194252 +75.9423396475613 1.90001878571128 +75.9668644368649 1.86122566031096 +75.9913892261684 1.90736748377609 +76.0159140154719 1.90995586926685 +76.0404388047755 1.88802276971356 +76.064963594079 1.94171854107072 +76.0894883833826 1.88117897121258 +76.1140131726861 1.88379357949455 +76.1385379619896 1.85650301261729 +76.1630627512932 1.88904568023683 +76.1875875405967 1.86851214001062 +76.2121123299003 1.90902521392966 +76.2366371192038 1.9056966422269 +76.2611619085073 1.90792697364563 +76.2856866978109 1.90856560730565 +76.3102114871144 1.89367225871469 +76.334736276418 1.89828572579694 +76.3592610657215 1.86236156547538 +76.3837858550251 1.82612297471516 +76.4083106443286 1.89708378823765 +76.4328354336321 1.90897062362395 +76.4573602229357 1.89552594132961 +76.4818850122392 1.93961812971847 +76.5064098015428 1.92621492633423 +76.5309345908463 1.9023189635546 +76.5554593801498 1.8835430367275 +76.5799841694534 1.91277405806961 +76.6045089587569 1.91507971924909 +76.6290337480605 1.92025267620089 +76.653558537364 1.91765499334424 +76.6780833266675 1.89868692808552 +76.7026081159711 1.89722133636357 +76.7271329052746 1.93774752167822 +76.7516576945782 1.92914992380785 +76.7761824838817 1.93082457938676 +76.8007072731853 1.99708729870035 +76.8252320624888 1.91805619563282 +76.8497568517923 1.93873658135083 +76.8742816410959 1.94060337536136 +76.8988064303994 1.92114566665769 +76.923331219703 1.90111321196559 +76.9478560090065 1.94595154531945 +76.97238079831 1.94775993499455 +76.9969055876136 1.98547555956588 +77.0214303769171 1.94045652986956 +77.0459551662207 1.92763903914608 +77.0704799555242 1.9669332500805 +77.0950047448277 2.0010244672422 +77.1195295341313 2.05605591025484 +77.1440543234348 1.96749655397977 +77.1685791127384 2.01043591177888 +77.1931039020419 1.97010186489585 +77.2176286913455 1.96955930045164 +77.242153480649 2.01903732367897 +77.2666782699525 1.96468217920673 +77.2912030592561 1.99012287749879 +77.3157278485596 1.94909560665003 +77.3402526378632 1.98464097344451 +77.3647774271667 2.000660929834 +77.3893022164702 1.97287476075879 +77.4138270057738 2.0445025754558 +77.4383517950773 1.96495727545855 +77.4628765843809 1.96521330156177 +77.4874013736844 2.02395592383398 +77.5119261629879 1.99616427142261 +77.5364509522915 1.99535042475629 +77.560975741595 1.93653628675304 +77.5855005308986 2.02887070995928 +77.6100253202021 2.00343382569694 +77.6345501095057 2.06979261422634 +77.6590748988092 2.09430647121094 +77.6835996881127 2.08227660436256 +77.7081244774163 2.01687469396156 +77.7326492667198 2.07583400815016 +77.7571740560234 2.03480292327168 +77.7816988453269 1.98483692590585 +77.8062236346304 2.05092061818343 +77.830748423934 2.05396312696203 +77.8552732132375 1.98596901704054 +77.8797980025411 2.07671937054752 +77.9043227918446 2.06538821100104 +77.9288475811481 2.1193290417891 +77.9533723704517 2.02053509694513 +77.9778971597552 1.97572680041221 +78.0024219490588 2.10202252071367 +78.0269467383623 2.07769698813097 +78.0514715276659 2.12170454939318 +78.0759963169694 2.06466113618465 +78.1005211062729 2.09217220900803 +78.1250458955765 1.98013120363751 +78.14957068488 2.00250317035975 +78.1740954741836 2.06907865080555 +78.1986202634871 2.14066117256274 +78.2231450527906 2.15332037570526 +78.2476698420942 2.06566712128259 +78.2721946313977 2.06626046200279 +78.2967194207013 2.10638163099914 +78.3212442100048 2.08188851346959 +78.3457689993083 2.02642750065388 +78.3702937886119 2.13674855750567 +78.3948185779154 2.11970950462262 +78.419343367219 2.1301891526224 +78.4438681565225 2.08980051543367 +78.4683929458261 2.04819301526031 +78.4929177351296 2.03681274874429 +78.5174425244331 2.1170741568856 +78.5419673137367 2.11147925819018 +78.5664921030402 2.13447293314712 +78.5910168923438 2.11617353836935 +78.6155416816473 2.19415932215212 +78.6400664709508 2.16147910640666 +78.6645912602544 2.13067712676091 +78.6891160495579 2.08616466593486 +78.7136408388615 2.0586857746557 +78.738165628165 2.08865316818009 +78.7626904174685 2.11973024407767 +78.7872152067721 2.19257310772958 +78.8117399960756 2.12078152211544 +78.8362647853792 2.15784707093758 +78.8607895746827 2.09930802918616 +78.8853143639863 2.13858150066554 +78.9098391532898 2.11093669374596 +78.9343639425933 2.12284045455759 +78.9588887318969 2.10130855729288 +78.9834135212004 2.18425990347693 +79.007938310504 2.13545817879001 +79.0324630998075 2.03989292240327 +79.056987889111 2.1012708924125 +79.0815126784146 2.1143312978436 +79.1060374677181 2.11208022696982 +79.1305622570217 2.08479514342872 +79.1550870463252 2.08577109170573 +79.1796118356287 2.05784737450461 +79.2041366249323 2.0505401553499 +79.2286614142358 2.05684901746611 +79.2531862035394 2.05794725775014 +79.2777109928429 2.05757990631222 +79.3022357821465 2.06299959200135 +79.32676057145 2.04737916859399 +79.3512853607535 2.01130434875091 +79.3758101500571 2.11029424605622 +79.4003349393606 2.13208431414743 +79.4248597286642 2.03679033998279 +79.4493845179677 2.01627753921163 +79.4739093072712 2.03952938499507 +79.4984340965748 2.03850266044208 +79.5229588858783 2.04278262693707 +79.5474836751819 2.00509536988023 +79.5720084644854 1.99347981671604 +79.5965332537889 2.02305363601129 +79.6210580430925 1.97615637021528 +79.645582832396 1.99924611438804 +79.6701076216996 1.9863308851423 +79.6946324110031 1.98905467403569 +79.7191572003067 2.05947125380753 +79.7436819896102 1.96518326474084 +79.7682067789137 1.95451910636887 +79.7927315682173 1.94453672987348 +79.8172563575208 1.98548867096148 +79.8417811468244 1.96681095807347 +79.8663059361279 1.88740487550858 +79.8908307254314 1.9122860839311 +79.915355514735 1.93959739026342 +79.9398803040385 1.93901167760266 +79.9644050933421 1.89573500518656 +79.9889298826456 1.88011076774949 +80.0134546719491 1.89464057893225 +80.0379794612527 1.92023575077556 +80.0625042505562 1.82920863171031 +80.0870290398598 1.83524239757469 +80.1115538291633 1.84916742594809 +80.1360786184669 1.91562228369331 +80.1606034077704 1.89099161803784 +80.1851281970739 1.8819229714543 +80.2096529863775 1.90436478460114 +80.234177775681 1.84612706189277 +80.2587025649846 1.7675763049043 +80.2832273542881 1.76040830318195 +80.3077521435916 1.95194383227373 +80.3322769328952 1.87385101260282 +80.3568017221987 1.81342229266254 +80.3813265115023 1.87586846613487 +80.4058513008058 1.92644472964623 +80.4303760901093 1.88609375733788 +80.4549008794129 1.83867848058243 +80.4794256687164 1.82751323667635 +80.50395045802 1.82908419498 +80.5284752473235 1.86145546362297 +80.5530000366271 1.87428987977178 +80.5775248259306 1.85349268538291 +80.6020496152341 1.79677895873191 +80.6265744045377 1.82069947499631 +80.6510991938412 1.8561111076946 +80.6756239831448 1.81961816013761 +80.7001487724483 1.8702528279844 +80.7246735617518 1.82438610077111 +80.7491983510554 1.80396936921576 +80.7737231403589 1.84894525069553 +80.7982479296625 1.85252055113562 +80.822772718966 1.84403976193612 +80.8472975082695 1.85858649854422 +80.8718222975731 1.87284121347516 +80.8963470868766 1.79596892609532 +80.9208718761802 1.78406897931341 +80.9453966654837 1.83973524195636 +80.9699214547873 1.8118644457545 +80.9944462440908 1.83134289391322 +81.0189710333943 1.83668724984159 +81.0434958226979 1.82552581996524 +81.0680206120014 1.78130919484608 +81.092545401305 1.83091713813987 +81.1170701906085 1.866323287502 +81.141594979912 1.8074469311336 +81.1661197692156 1.81736327523411 +81.1906445585191 1.80715490945644 +81.2151693478227 1.79050776149609 +81.2396941371262 1.87071243460841 +81.2642189264297 1.90345486871901 +81.2887437157333 1.88748401929907 +81.3132685050368 1.87258685667838 +81.3377932943404 1.8557060913763 +81.3623180836439 1.85957555821683 +81.3868428729475 1.85042013978337 +81.411367662251 1.85083659819083 +81.4358924515545 1.79551861683719 +81.4604172408581 1.83991045496265 +81.4849420301616 1.80534651978134 +81.5094668194652 1.79905458309046 +81.5339916087687 1.85132624163578 +81.5585163980722 1.90543847140041 +81.5830411873758 1.82863221641544 +81.6075659766793 1.84882462799495 +81.6320907659829 1.81896260105226 +81.6566155552864 1.78632386421691 +81.6811403445899 1.81444901721554 +81.7056651338935 1.84476683675252 +81.730189923197 1.84583504021561 +81.7547147125006 1.78260338759146 +81.7792395018041 1.88910956790843 +81.8037642911077 1.90373377900056 +81.8282890804112 1.81820715872137 +81.8528138697147 1.80787268690695 +81.8773386590183 1.8369170531536 +81.9018634483218 1.82137577353672 +81.9263882376254 1.8217769758253 +81.9509130269289 1.84379303319879 +81.9754378162324 1.80814015509933 +81.999962605536 1.80511290243962 +82.0244873948395 1.83457921042989 +82.0490121841431 1.84111239252194 +82.0735369734466 1.82169783203481 +82.0980617627501 1.84905824533667 +82.1225865520537 1.83063274452216 +82.1471113413572 1.78636152909729 +82.1716361306608 1.84722148814708 +82.1961609199643 1.79608573476618 +82.2206857092679 1.77911271427801 +82.2452104985714 1.77611549843923 +82.2697352878749 1.85308385503489 +82.2942600771785 1.78875777615642 +82.318784866482 1.81828630251185 +82.3433096557856 1.87386030996871 +82.3678344450891 1.81224872261775 +82.3923592343926 1.80285801753612 +82.4168840236962 1.78644067288777 +82.4414088129997 1.80840762329172 +82.4659336023033 1.85175581143885 +82.4904583916068 1.82044511819954 +82.5149831809103 1.83084562240883 +82.5395079702139 1.83447384384819 +82.5640327595174 1.79899617875503 +82.588557548821 1.80584760531545 +82.6130823381245 1.84085040766572 +82.6376071274281 1.82753016210168 +82.6621319167316 1.78869555779127 +82.6866567060351 1.79699183661859 +82.7111814953387 1.74934675655113 +82.7357062846422 1.80337221446584 +82.7602310739458 1.82638662887783 +82.7847558632493 1.81525904985214 +82.8092806525528 1.79216930567938 +82.8338054418564 1.82945536044764 +82.8583302311599 1.8047870299118 +82.8828550204635 1.78443251672161 +82.907379809767 1.83134289391322 +82.9319045990705 1.84377610777346 +82.9564293883741 1.77021165264132 +82.9809541776776 1.84426956524813 +83.0054789669812 1.81882505292634 +83.0300037562847 1.84539617304665 +83.0545285455883 1.80273358080581 +83.0790533348918 1.80918380507765 +83.1035781241953 1.76884594416491 +83.1281029134989 1.83069496288731 +83.1526277028024 1.84306762768884 +83.177152492106 1.85196868932552 +83.2016772814095 1.82388501523699 +83.226202070713 1.81392337819666 +83.2507268600166 1.81646098741142 +83.2752516493201 1.78986912783606 +83.2997764386237 1.82085394854756 +83.3243012279272 1.73869570957478 +83.3488260172307 1.78445325617666 +83.3733508065343 1.82035286301345 +83.3978755958378 1.74918846897017 +83.4224003851414 1.83656662714101 +83.4469251744449 1.81507453947995 +83.4714499637485 1.77888839430217 +83.495974753052 1.82859455153506 +83.5204995423555 1.79473695171509 +83.5450243316591 1.74272513330272 +83.5695491209626 1.76653265492598 +83.5940739102662 1.82404878615412 +83.6185986995697 1.84517948113026 +83.6431234888732 1.82334245079278 +83.6676482781768 1.82931781232173 +83.6921730674803 1.78273712168765 +83.7166978567839 1.78694175842188 +83.7412226460874 1.78061597088034 +83.7657474353909 1.75406940424481 +83.7902722246945 1.75143024247806 +83.814797013998 1.75163930633501 +83.8393218033016 1.82480041445529 +83.8638465926051 1.7915383000788 +83.8883713819087 1.72980227599754 +83.9128961712122 1.74866664398101 +83.9374209605157 1.78308373367052 +83.9619457498193 1.75056180550603 +83.9864705391228 1.80068775975927 +84.0109953284264 1.77953847005136 +84.0355201177299 1.79930893988724 +84.0600449070334 1.77450854456165 +84.084569696337 1.83217748003459 +84.1090944856405 1.82953831826785 +84.1336192749441 1.82040745331916 +84.1581440642476 1.76791910285745 +84.1826688535511 1.78080810931197 +84.2071936428547 1.7284742324015 +84.2317184321582 1.7519067745274 +84.2562432214618 1.75607756041097 +84.2807680107653 1.72614020370752 +84.3052928000689 1.81284206333795 +84.3298175893724 1.79890392356894 +84.3543423786759 1.69799812528356 +84.3788671679795 1.7288508812053 +84.403391957283 1.77903357048753 +84.4279167465866 1.77345559721744 +84.4524415358901 1.77363462425345 +84.4769663251936 1.73200257059533 +84.5014911144972 1.7847960541298 +84.5260159038007 1.76209821487974 +84.5505406931043 1.73142234127073 +84.5750654824078 1.68753921673883 +84.5995902717113 1.70883368263209 +84.6241150610149 1.74413613471896 +84.6486398503184 1.79044935716066 +84.673164639622 1.7478434999488 +84.6976894289255 1.70129284613566 +84.7222142182291 1.76047433557682 +84.7467390075326 1.71946399015339 +84.7712637968361 1.68620735911307 +84.7957885861397 1.71291769666574 +84.8203133754432 1.74267435702673 +84.8448381647468 1.73331368876603 +84.8693629540503 1.72413204754135 +84.8938877433538 1.75908788764536 +84.9184125326574 1.69274221051156 +84.9429373219609 1.82662024621955 +84.9674621112645 1.72573518738922 +84.991986900568 1.71753879180743 +85.0165116898716 1.71747275941256 +85.0410364791751 1.70457064156243 +85.0655612684786 1.77523013604188 +85.0900860577822 1.76192300187345 +85.1146108470857 1.8108246098059 +85.1391356363893 1.71277466520366 +85.1636604256928 1.73739770279968 +85.1881852149963 1.75106670506986 +85.2127100042999 1.70880912914732 +85.2372347936034 1.7681696456245 +85.261759582907 1.73528799408154 +85.2862843722105 1.67729318608078 +85.310809161514 1.72733069917764 +85.3353339508176 1.75340073376384 +85.3598587401211 1.70513013143197 +85.3843835294247 1.71293843612079 +85.4089083187282 1.78268253138194 +85.4334331080318 1.71463764518447 +85.4579578973353 1.80383563511957 +85.4824826866388 1.77928411325458 +85.5070074759424 1.76923022102815 +85.5315322652459 1.78273712168765 +85.5560570545495 1.78163506737389 +85.580581843853 1.80114188304711 +85.6051066331565 1.73418212573806 +85.6296314224601 1.76763089521001 +85.6541562117636 1.75897107897449 +85.6786810010672 1.76629522355453 +85.7032057903707 1.83298751267119 +85.7277305796742 1.78227370103392 +85.7522553689778 1.76311731137329 +85.7767801582813 1.73728852218826 +85.8013049475849 1.79535103189034 +85.8258297368884 1.74130102049087 +85.850354526192 1.75310489805696 +85.8748793154955 1.75322552075755 +85.899404104799 1.72908449854703 +85.9239288941026 1.79939738104361 +85.9484536834061 1.80378485884358 +85.9729784727097 1.79408139262974 +85.9975032620132 1.78092110395311 +86.0220280513167 1.77490044948434 +86.0465528406203 1.77874154881037 +86.0710776299238 1.79575604820864 +86.0956024192274 1.77396049678127 +86.1201272085309 1.8088917834005 +86.1446519978344 1.83793996367687 +86.169176787138 1.85393918061131 +86.1937015764415 1.81026511993636 +86.2182263657451 1.78782712081924 +86.2427511550486 1.72372703122306 +86.2672759443522 1.78468305948866 +86.2918007336557 1.78722996606932 +86.3163255229592 1.81535130503823 +86.3408503122628 1.81395174571115 +86.3653751015663 1.80057476511812 +86.3898998908699 1.83977672086646 +86.4144246801734 1.83783078306545 +86.4389494694769 1.75283361583486 +86.4634742587805 1.76500865886859 +86.487999048084 1.82513391504255 +86.5125238373876 1.85069142200547 +86.5370486266911 1.84743818006348 +86.5615734159946 1.81099219475275 +86.5860982052982 1.90069508425168 +86.6106229946017 1.79076593232259 +86.6351477839053 1.81938072876616 +86.6596725732088 1.82604001689496 +86.6841973625124 1.85696643327102 +86.7087221518159 1.846102508408 +86.7332469411194 1.91193947194824 +86.757771730423 1.88244479644347 +86.7822965197265 1.85978080804406 +86.8068213090301 1.82554274539057 +86.8313460983336 1.8796266076407 +86.8558708876371 1.78949629306198 +86.8803956769407 1.73197801711055 +86.9049204662442 1.83386906103883 +86.9294452555478 1.92104196938243 +86.9539700448513 1.87743775513208 +86.9784948341548 1.87549730066723 +87.0030196234584 1.90329658113805 +87.0275444127619 1.89842327392286 +87.0520692020655 1.8421860793212 +87.076593991369 1.88986882426904 +87.1011187806726 1.91707857804937 +87.1256435699761 1.81502161848069 +87.1501683592796 1.89907120494877 +87.1746931485832 1.93208492128148 +87.1992179378867 1.9078685693102 +87.2237427271903 1.87708351508977 +87.2482675164938 1.82224039647903 +87.2727923057973 1.86670375033552 +87.2973170951009 1.88048574724685 +87.3218418844044 1.85738074695521 +87.346366673708 1.95773849746021 +87.3708914630115 1.86472396168385 +87.395416252315 1.82054118741535 +87.4199410416186 1.86292868340436 +87.4444658309221 1.84596496028208 +87.4689906202257 1.85277872196212 +87.4935154095292 1.87835315435038 +87.5180401988328 1.85548391612374 +87.5425649881363 1.84092192339676 +87.5670897774398 1.9110994024907 +87.5916145667434 1.84990594285365 +87.6161393560469 1.85429508996006 +87.6406641453505 1.85949641442635 +87.665188934654 1.93357673579465 +87.6897137239575 1.86260281087655 +87.7142385132611 1.85790638597409 +87.7387633025646 1.87405244840033 +87.7632880918682 1.9401230292823 +87.7878128811717 1.87596834938041 +87.8123376704752 1.8924986886699 +87.8368624597788 1.90163503695475 +87.8613872490823 1.91297930789684 +87.8859120383859 1.88810191350404 +87.9104368276894 1.87205192029361 +87.934961616993 1.87671234962213 +87.9594864062965 1.87130029199245 +87.9840111956 1.95844483282156 +88.0085359849036 1.89128745374472 +88.0330607742071 1.81543044882871 +88.0575855635107 1.87626966842345 +88.0821103528142 1.83800218204202 +88.1066351421177 1.88431540448372 +88.1311599314213 1.86580146251284 +88.1556847207248 1.88433614393877 +88.1802095100284 1.84413583115193 +88.2047342993319 1.90216449000336 +88.2292590886354 1.80355124150185 +88.253783877939 1.83608628106195 +88.2783086672425 1.86901703957445 +88.3028334565461 1.82072784251081 +88.3273582458496 1.8200739527319 +88.3518830351532 1.85513349011115 +88.3764078244567 1.80505831213391 +88.4009326137602 1.8151591666066 +88.4254574030638 1.83550986576707 +88.4499821923673 1.86076605368696 +88.4745069816709 1.83176864968657 +88.4990317709744 1.81460349076678 +88.5235565602779 1.81165156786783 +88.5480813495815 1.88597313463729 +88.572606138885 1.89735888448948 +88.5971309281886 1.84822747324502 +88.6216557174921 1.83288762942566 +88.6461805067956 1.84059056753278 +88.6707052960992 1.79937664158856 +88.6952300854027 1.84616472677315 +88.7197548747063 1.8322320703403 +88.7442796640098 1.85688728948054 +88.7688044533134 1.85975625455929 +88.7933292426169 1.92504517031915 +88.8178540319204 1.81292502115816 +88.842378821224 1.72882084438437 +88.8669036105275 1.81070398710532 +88.8914283998311 1.80252833097858 +88.9159531891346 1.85001893749479 +88.9404779784381 1.81459967673706 +88.9650027677417 1.79331283890324 +88.9895275570452 1.74989861836123 +89.0140523463488 1.82420707373509 +89.0385771356523 1.80575153609964 +89.0631019249558 1.82656565591384 +89.0876267142594 1.75458741520425 +89.1121515035629 1.83053667530635 +89.1366762928665 1.79953874319924 +89.16120108217 1.82437847271166 +89.1857258714736 1.82629055966202 +89.2102506607771 1.86446960488708 +89.2347754500806 1.81593153436282 +89.2593002393842 1.84666962633698 +89.2838250286877 1.79027414415436 +89.3083498179913 1.85160133788761 +89.3328746072948 1.81128803045963 +89.3573993965983 1.79999834982325 +89.3819241859019 1.81809416408023 +89.4064489752054 1.76871602409844 +89.430973764509 1.78577748574297 +89.4554985538125 1.77801447399398 +89.480023343116 1.80439131095939 +89.5045481324196 1.86335062514799 +89.5290729217231 1.82008158079134 +89.5535977110267 1.79957640807963 +89.5781225003302 1.74859131422025 +89.6026472896338 1.84315058550904 +89.6271720789373 1.81793969052898 +89.6516968682408 1.79037784142962 +89.6762216575444 1.78194401447638 +89.7007464468479 1.80923839538336 +89.7252712361515 1.86608585613055 +89.749796025455 1.81367664945932 +89.7743208147585 1.81757615312079 +89.7988456040621 1.82522998425836 +89.8233703933656 1.83560593498289 +89.8478951826692 1.85858649854422 +89.8724199719727 1.83264090068832 +89.8969447612762 1.88641581583597 +89.9214695505798 1.87041111556537 +89.9459943398833 1.89891673139752 +89.9705191291869 1.8748870345217 +89.9950439184904 1.80424446546759 +90.019568707794 1.79382703583296 +90.0440934970975 1.81466189510221 +90.068618286401 1.84130071692384 +90.0931430757046 1.81684145024495 +90.1176678650081 1.84094647688153 +90.1421926543117 1.87426532628701 +90.1667174436152 1.91201861573872 +90.1912422329187 1.87123807362729 +90.2157670222223 1.91117473225146 +90.2402918115258 1.84747203091414 +90.2648166008294 1.91216546123052 +90.2893413901329 1.90654433974388 +90.3138661794364 1.88248627535357 +90.33839096874 1.84521714601064 +90.3629157580435 1.84762650446538 +90.3874405473471 1.86531348837433 +90.4119653366506 1.90989746493141 +90.4364901259542 1.83952236406968 +90.4610149152577 1.91917517537191 +90.4855397045612 1.88798510483318 +90.5100644938648 1.90836584081459 +90.5345892831683 1.93972182699372 +90.5591140724719 1.83813973016793 +90.5836388617754 1.94870370172734 +90.6081636510789 1.90731289347038 +90.6326884403825 1.91812771136386 +90.657213229686 1.92650694801139 +90.6817380189896 1.91018948660857 +90.7062628082931 1.91205246658938 +90.7307875975966 1.90885381495309 +90.7553123869002 1.92118333153807 +90.7798371762037 1.89789382087425 +90.8043619655073 1.90374689039617 +90.8288867548108 1.89816510309636 +90.8534115441144 1.90753888275266 +90.8779363334179 1.95640663983446 +90.9024611227214 1.94757161059264 +90.926985912025 1.85688728948054 +90.9515107013285 1.92368875920862 +90.9760354906321 1.88801895568384 +91.0005602799356 1.95242799238251 +91.0250850692391 1.87363813471615 +91.0496098585427 1.89746043704146 +91.0741346478462 2.00868592643921 +91.0986594371498 1.89232728969332 +91.1231842264533 1.85453252133151 +91.1477090157568 1.88489181977859 +91.1722338050604 1.94734180728064 +91.1967585943639 1.89922567850001 +91.2212833836675 1.93748386751555 +91.245808172971 1.93803572932565 +91.2703329622746 1.94340082470907 +91.2948577515781 1.96626839362926 +91.3193825408816 1.91147605129451 +91.3439073301852 1.92716250709674 +91.3684321194887 1.87200662735379 +91.3929569087923 1.87448583223313 +91.4174816980958 1.86997773173257 +91.4420064873993 1.97293697912394 +91.4665312767029 1.90590570608386 +91.4910560660064 1.93450357710211 +91.51558085531 1.93156309629232 +91.5401056446135 1.88082091714055 +91.564630433917 1.88504080999367 +91.5891552232206 1.88927166951911 +91.6136800125241 1.910758273844 +91.6382048018277 1.92155449700571 +91.6627295911312 1.84251576587874 +91.6872543804348 1.90209845760848 +91.7117791697383 1.91342198909552 +91.7363039590418 1.94257386664714 +91.7608287483454 1.94463661311902 +91.7853535376489 1.93850677803883 +91.8098783269525 1.87033197177488 +91.834403116256 1.87552185415201 +91.8589279055595 1.87569325312858 +91.8834526948631 1.92152994352094 +91.9079774841666 1.88308343010349 +91.9325022734702 1.92758063481065 +91.9570270627737 1.90953392752322 +91.9815518520772 1.87189363271265 +92.0060766413808 1.86095819211858 +92.0306014306843 1.92350973217261 +92.0551262199879 1.93491622147985 +92.0796510092914 1.87586846613487 +92.104175798595 1.8521400883021 +92.1287005878985 1.89454069568672 +92.153225377202 1.88074940140951 +92.1777501665056 1.86229004974434 +92.2022749558091 1.83497111535258 +92.2267997451127 1.93155928226259 +92.2513245344162 1.90061975449092 +92.2758493237197 1.87143021205892 +92.3003741130233 1.8480353348134 +92.3248989023268 1.89590640416314 +92.3494236916304 1.88001469853367 +92.3739484809339 1.88945069655513 +92.3984732702374 1.84805226023873 +92.422998059541 1.87588920558992 +92.4475228488445 1.86452038116307 +92.4720476381481 1.85854883366384 +92.4965724274516 1.88639889041064 +92.5210972167552 1.89357237546916 +92.5456220060587 1.84730444596728 +92.5701467953622 1.86037796279399 +92.5946715846658 1.85879556240117 +92.6191963739693 1.85160515191733 +92.6437211632729 1.8369585320637 +92.6682459525764 1.85239063106915 +92.6927707418799 1.85460785109227 +92.7172955311835 1.8806948111038 +92.741820320487 1.81091686499199 +92.7663451097906 1.85811926386077 +92.7908698990941 1.85497901655991 +92.8153946883976 1.83088328728922 +92.8399194777012 1.8035305020468 +92.8644442670047 1.82180152931007 +92.8889690563083 1.77798992050921 +92.9134938456118 1.74481243325936 +92.9380186349154 1.79540180816633 +92.9625434242189 1.88158017350116 +92.9870682135224 1.82038671386411 +93.011593002826 1.81289498433722 +93.0361177921295 1.72648300166066 +93.0606425814331 1.76639510680006 +93.0851673707366 1.81381038355551 +93.1096921600401 1.82683312410623 +93.1342169493437 1.82991878110137 +93.1587417386472 1.83447384384819 +93.1832665279508 1.77004406769447 +93.2077913172543 1.7639404554055 +93.2323161065578 1.79747599672737 +93.2568408958614 1.81485021950412 +93.2813656851649 1.82621522990125 +93.3058904744685 1.79124627840164 +93.330415263772 1.79793560335138 +93.3549400530756 1.82546360160009 +93.3794648423791 1.76518387187489 +93.4039896316826 1.77631145090058 +93.4285144209862 1.77318431499533 +93.4530392102897 1.82089161342794 +93.4775639995933 1.78892536110327 +93.5020887888968 1.76508780265908 +93.5266135782003 1.76459982852057 +93.5511383675039 1.76431924893258 +93.5756631568074 1.75096300779461 +93.600187946111 1.77318812902505 +93.6247127354145 1.72569370847912 +93.649237524718 1.77480438026852 +93.6737623140216 1.77625304656515 +93.6982871033251 1.7322569273921 +93.7228118926287 1.85012811810621 +93.7473366819322 1.8407412270543 +93.7718614712358 1.79358030709563 +93.7963862605393 1.76971438113694 +93.8209110498428 1.76612763860768 +93.8454358391464 1.81054951355407 +93.8699606284499 1.80497916834342 +93.8944854177535 1.74459955537269 +93.919010207057 1.74978562372009 +93.9435349963605 1.74626109955599 +93.9680597856641 1.76862925224851 +93.9925845749676 1.71140514269752 +94.0171093642712 1.75444986707833 +94.0416341535747 1.79014422408789 +94.0661589428782 1.76609378775702 +94.0906837321818 1.69882126931576 +94.1152085214853 1.74963496419857 +94.1397333107889 1.76955227952625 +94.1642581000924 1.75649020478871 +94.188782889396 1.74503842254165 +94.2133076786995 1.76624063324882 +94.237832468003 1.76776844333592 +94.2623572573066 1.73858271493364 +94.2868820466101 1.72955173323048 +94.3114068359137 1.70727583572405 +94.3359316252172 1.69177770432372 +94.3604564145207 1.76726735780181 +94.3849812038243 1.70908803942887 +94.4095059931278 1.72021180442484 +94.4340307824314 1.76265389071956 +94.4585555717349 1.75997325004271 +94.4830803610384 1.73449107284055 +94.507605150342 1.74224478722366 +94.5321299396455 1.74890026132273 +94.5566547289491 1.74278735166787 +94.5811795182526 1.73792334181856 +94.6057043075562 1.76255400747403 +94.6302290968597 1.79157215092946 +94.6547538861632 1.79097118214982 +94.6792786754668 1.79890392356894 +94.7038034647703 1.74152700977315 +94.7283282540739 1.76817345965422 +94.7528530433774 1.78163506737389 +94.7773778326809 1.8172257271082 +94.8019026219845 1.77303365547381 +94.826427411288 1.75862446699162 +94.8509522005916 1.79277957182491 +94.8754769898951 1.77006862117925 +94.9000017791986 1.7479903454406 +94.9245265685022 1.74254062293054 +94.9490513578057 1.71334726646881 +94.9735761471093 1.75042044335039 +94.9981009364128 1.73603962238271 +95.0226257257164 1.78022025192793 +95.0471505150199 1.79409831805507 +95.0716753043234 1.76981426438247 +95.096200093627 1.75823637609866 +95.1207248829305 1.76643277168044 +95.1452496722341 1.74608588654969 +95.1697744615376 1.76811124128907 +95.1942992508411 1.76582250553491 +95.2188240401447 1.76702611240064 +95.2433488294482 1.79692032088755 +95.2678736187518 1.76124288930332 +95.2923984080553 1.81627814634569 +95.3169231973588 1.7944280046126 +95.3414479866624 1.76358073202702 +95.3659727759659 1.75734719967158 +95.3904975652695 1.77135518586518 +95.415022354573 1.80507142352951 +95.4395471438766 1.803383656555 +95.4640719331801 1.76920566754338 +95.4885967224836 1.77770171286177 +95.5131215117872 1.81051566270341 +95.5376463010907 1.79349734927542 +95.5621710903943 1.81410240523267 +95.5866958796978 1.82929325883696 +95.6112206690013 1.8453754335916 +95.6357454583049 1.81894567562693 +95.6602702476084 1.81116740775905 +95.684795036912 1.82318797724154 +95.7093198262155 1.77330493769591 +95.733844615519 1.79737611348184 +95.7583694048226 1.780649821731 +95.7828941941261 1.8027073580146 +95.8074189834297 1.75882590278914 +95.8319437727332 1.8318139426264 +95.8564685620368 1.78174806201504 +95.8809933513403 1.81680378536457 +95.9055181406438 1.8016391545515 +95.9300429299474 1.77895442669705 +95.9545677192509 1.79118024600677 +95.9790925085545 1.79413979696517 +96.003617297858 1.79195261376299 +96.0281420871615 1.84355941585706 +96.0526668764651 1.8449289383632 +96.0771916657686 1.86014434545226 +96.1017164550722 1.7823113659143 +96.1262412443757 1.84785630777738 +96.1507660336792 1.82247401382075 +96.1752908229828 1.81522138497176 +96.1998156122863 1.79035710197457 +96.2243404015899 1.77858707525913 +96.2488651908934 1.83057434018673 +96.273389980197 1.84853260631779 +96.2979147695005 1.83343019386987 +96.322439558804 1.82935929123183 +96.3469643481076 1.85977699401434 +96.3714891374111 1.85187262010971 +96.3960139267147 1.81068706167999 +96.4205387160182 1.85161826331294 +96.4450635053217 1.85715857170265 +96.4695882946253 1.84892069721076 +96.4941130839288 1.80925913483842 +96.5186378732324 1.84723841357241 +96.5431626625359 1.89637745287631 +96.5676874518394 1.84714615838632 +96.592212241143 1.79874563598798 +96.6167370304465 1.87517524216914 +96.6412618197501 1.79995687091315 +96.6657866090536 1.80347591174109 +96.6903113983572 1.79446566949298 +96.7148361876607 1.8018689578635 +96.7393609769642 1.82291669501943 +96.7638857662678 1.81527597527747 +96.7884105555713 1.88467894189191 +96.8129353448749 1.88223573258651 +96.8374601341784 1.82041126734888 +96.8619849234819 1.83248642713708 +96.8865097127855 1.88190604602897 +96.911034502089 1.84432796958356 +96.9355592913926 1.84616472677315 +96.9600840806961 1.84276249461607 +96.9846088699996 1.84623624250419 +97.0091336593032 1.84603099267696 +97.0336584486067 1.79081670859857 +97.0581832379103 1.78732603528513 +97.0827080272138 1.7922653748952 +97.1072328165174 1.83616923888215 +97.1317576058209 1.796871213918 +97.1562823951244 1.8451041513695 +97.180807184428 1.82624908075191 +97.2053319737315 1.83788918740088 +97.2298567630351 1.81119196124382 +97.2543815523386 1.8134561435132 +97.2789063416421 1.84775094119569 +97.3034311309457 1.83437396060266 +97.3279559202492 1.80906699640679 +97.3524807095528 1.81537204449328 +97.3770054988563 1.79552624489664 +97.4015302881598 1.84415275657726 +97.4260550774634 1.86103352187934 +97.4505798667669 1.7790166450622 +97.4751046560705 1.79909224797084 +97.499629445374 1.80221938387609 +97.5241542346776 1.76894201338072 +97.5486790239811 1.7777186382871 +97.5732038132846 1.80293334729688 +97.5977286025882 1.79014041005817 +97.6222533918917 1.8820567055505 +97.6467781811953 1.85507508577572 +97.6713029704988 1.85003967694984 +97.6958277598023 1.85688347545082 +97.7203525491059 1.90501652965678 +97.7448773384094 1.80067846239338 +97.769402127713 1.82692537929232 +97.7939269170165 1.87553877957734 +97.81845170632 1.79659444835973 +97.8429764956236 1.7670883307658 +97.8675012849271 1.75983951594652 +97.8920260742307 1.77112156852346 +97.9165508635342 1.84442022476965 +97.9410756528378 1.81314171307455 +97.9656004421413 1.77715914841756 +97.9901252314448 1.82247401382075 +98.0146500207484 1.7908620015384 +98.0391748100519 1.77112156852346 +98.0636995993555 1.74523437500299 +98.088224388659 1.78325131861737 +98.1127491779625 1.79375552010192 +98.1372739672661 1.74242000022995 +98.1617987565696 1.738211549466 +98.1863235458732 1.83373151291292 +98.2108483351767 1.83048208500064 +98.2353731244802 1.83059507964178 +98.2598979137838 1.82401493530347 +98.2844227030873 1.78783093484896 +98.3089474923909 1.78981072350063 +98.3334722816944 1.81879120207568 +98.357997070998 1.76526301566537 +98.3825218603015 1.70854166095493 +98.407046649605 1.8133092980214 +98.4315714389086 1.73945496593539 +98.4560962282121 1.74125954158077 +98.4806210175157 1.77644137096705 +98.5051458068192 1.75100830073443 +98.5296705961227 1.75556884681742 +98.5541953854263 1.75391874472328 +98.5787201747298 1.75943449962822 +98.6032449640334 1.75686303956279 +98.6277697533369 1.84669966315792 +98.6522945426404 1.80735467594751 +98.676819331944 1.76043285666672 +98.7013441212475 1.77419411412299 +98.7258689105511 1.74829929254309 +98.7503936998546 1.76672860738732 +98.7749184891582 1.76527612706098 +98.7994432784617 1.81774373806764 +98.8239680677652 1.79518344694349 +98.8484928570688 1.78961858506901 +98.8730176463723 1.75201047180265 +98.8975424356759 1.73334753961669 +98.9220672249794 1.76084168701474 +98.9465920142829 1.76340933305045 +98.9711168035865 1.73306314599898 +98.99564159289 1.74664156238951 +99.0201663821936 1.73935126866014 +99.0446911714971 1.77798610647948 +99.0692159608006 1.81881194153073 +99.0937407501042 1.79093351726944 +99.1182655394077 1.77154732429681 +99.1427903287113 1.78289159523889 +99.1673151180148 1.77148510593165 +99.1918399073184 1.78827361604764 +99.2163646966219 1.77054515322858 +99.2408894859254 1.7459521524535 +99.265414275229 1.74540577397957 +99.2899390645325 1.80655394067679 +99.3144638538361 1.8494801870803 +99.3389886431396 1.79317529077733 +99.3635134324431 1.77052059974381 +99.3880382217467 1.77046982346782 +99.4125630110502 1.71624459906205 +99.4370878003538 1.75219498217483 +99.4616125896573 1.82301276423524 +99.4861373789608 1.74984784208524 +99.5106621682644 1.8101483112655 +99.5351869575679 1.75908407361563 +99.5597117468715 1.78082884876702 +99.584236536175 1.75557266084714 +99.6087613254786 1.83758953766428 +99.6332861147821 1.81646098741142 +99.6578109040856 1.72525484131016 +99.6823356933892 1.77580655133675 +99.7068604826927 1.75204432265331 +99.7313852719963 1.71645366291901 +99.7559100612998 1.80179362810274 +99.7804348506033 1.74350131508866 +99.8049596399069 1.77819898436616 +99.8294844292104 1.84082037084478 +99.854009218514 1.77032846131219 +99.8785340078175 1.8033006987348 +99.903058797121 1.78248276489087 +99.9275835864246 1.82324256754725 +99.9521083757281 1.83071188831264 +99.9766331650317 1.73555546227392 +100.001157954335 1.77771482425738 +100.025682743639 1.77999807667537 +100.050207532942 1.83369766206226 +100.074732322246 1.85769732211714 +100.099257111549 1.83597710045053 +100.123781900853 1.83869540600775 +100.148306690156 1.78615627927006 +100.17283147946 1.73130553259987 +100.197356268764 1.76067028803817 +100.221881058067 1.7543914627429 +100.246405847371 1.78707930654779 +100.270930636674 1.78481679358485 +100.295455425978 1.74313777768046 +100.319980215281 1.76324723143976 +100.344505004585 1.77407730545213 +100.369029793888 1.77325964475609 +100.393554583192 1.78762187099201 +100.418079372495 1.7180737281922 +100.442604161799 1.76095468165588 +100.467128951102 1.75135109868758 +100.491653740406 1.74037417918341 +100.51617852971 1.81378964410046 +100.540703319013 1.77720444135738 +100.565228108317 1.75682537468241 +100.58975289762 1.75193132801217 +100.614277686924 1.7174389085619 +100.638802476227 1.77748502094537 +100.663327265531 1.80050324938709 +100.687852054834 1.75790668954112 +100.712376844138 1.78431570805074 +100.736901633441 1.79022336787837 +100.761426422745 1.77234043150807 +100.785951212049 1.70854547498466 +100.810476001352 1.78323439319204 +100.835000790656 1.7278394127712 +100.859525579959 1.78336812728823 +100.884050369263 1.73568919637012 +100.908575158566 1.74783968591908 +100.93309994787 1.71027853489899 +100.957624737173 1.71497114577173 +100.982149526477 1.79963099838534 +101.00667431578 1.759918659737 +101.031199105084 1.71230361649049 +101.055723894387 1.79083744805362 +101.080248683691 1.78096258286321 +101.104773472995 1.71965612858502 +101.129298262298 1.72795240741234 +101.153823051602 1.73655763334215 +101.178347840905 1.71835812180992 +101.202872630209 1.75680844925708 +101.227397419512 1.76410255701618 +101.251922208816 1.72454087788938 +101.276446998119 1.71404049043454 +101.300971787423 1.7236931803724 +101.325496576726 1.7271762256264 +101.35002136603 1.76429088141809 +101.374546155334 1.81219413231204 +101.399070944637 1.72920512124761 +101.423595733941 1.77034920076724 +101.448120523244 1.78422345286465 +101.472645312548 1.76215280518545 +101.497170101851 1.73531803090248 +101.521694891155 1.75957204775414 +101.546219680458 1.70389434302202 +101.570744469762 1.77430710876414 +101.595269259065 1.73495067946456 +101.619794048369 1.70660335121337 +101.644318837672 1.69009542068537 +101.668843626976 1.75315019099679 +101.69336841628 1.75587397989018 +101.717893205583 1.78835657386784 +101.742417994887 1.75501698500732 +101.76694278419 1.72424122815277 +101.791467573494 1.7603651549654 +101.815992362797 1.72598573015628 +101.840517152101 1.7528636526558 +101.865041941404 1.80628098914824 +101.889566730708 1.66612031411526 +101.914091520011 1.78312521258062 +101.938616309315 1.69186066214392 +101.963141098619 1.76700918697531 +101.987665887922 1.7846077297279 +102.012190677226 1.7982614758792 +102.036715466529 1.73632783003014 +102.061240255833 1.72062063477286 +102.085765045136 1.74301334095015 +102.11028983444 1.73545939305811 +102.134814623743 1.7128876598448 +102.159339413047 1.7165459181051 +102.18386420235 1.77064503647412 +102.208388991654 1.71552682161155 +102.232913780957 1.72841201403635 +102.257438570261 1.75081234827309 +102.281963359565 1.76153491098048 +102.306488148868 1.7883904247185 +102.331012938172 1.72453324982993 +102.355537727475 1.74797723404499 +102.380062516779 1.70497565788073 +102.404587306082 1.74485009813975 +102.429112095386 1.70806131487587 +102.453636884689 1.71699622736322 +102.478161673993 1.72660362436125 +102.502686463296 1.66506736677105 +102.5272112526 1.7301074090703 +102.551736041903 1.75744708291711 +102.576260831207 1.71236583485564 +102.600785620511 1.74698436034266 +102.625310409814 1.77901283103248 +102.649835199118 1.74994391130106 +102.674359988421 1.7719523406151 +102.698884777725 1.78612791175556 +102.723409567028 1.74597289190855 +102.747934356332 1.69178533238316 +102.772459145635 1.68585693310048 +102.796983934939 1.76554740928308 +102.821508724242 1.77303365547381 +102.846033513546 1.7016356440888 +102.87055830285 1.69354294578227 +102.895083092153 1.78670432705043 +102.919607881457 1.78412738364884 +102.94413267076 1.7613220330938 +102.968657460064 1.78720922661427 +102.993182249367 1.74915080408979 +103.017707038671 1.77307132035419 +103.042231827974 1.73173891643266 +103.066756617278 1.72642078329551 +103.091281406581 1.77245724017894 +103.115806195885 1.73998990232016 +103.140330985188 1.70528460498321 +103.164855774492 1.71194007908229 +103.189380563796 1.70030760049277 +103.213905353099 1.74349750105894 +103.238430142403 1.71493348089135 +103.262954931706 1.69161011937687 +103.28747972101 1.71328886213338 +103.312004510313 1.77887528290656 +103.336529299617 1.71759719614286 +103.36105408892 1.69857072654871 +103.385578878224 1.71113004644569 +103.410103667527 1.73963947630757 +103.434628456831 1.79029869763913 +103.459153246135 1.76863306627823 +103.483678035438 1.76120141039322 +103.508202824742 1.81673226963353 +103.532727614045 1.79725549078125 +103.557252403349 1.7486950114955 +103.581777192652 1.72401523887049 +103.606301981956 1.78477531467475 +103.630826771259 1.72084114071898 +103.655351560563 1.66120171396024 +103.679876349866 1.7640234132257 +103.70440113917 1.82453294626291 +103.728925928473 1.79155903953385 +103.753450717777 1.75765233274434 +103.777975507081 1.81097908335714 +103.802500296384 1.75596075174011 +103.827025085688 1.72924660015772 +103.851549874991 1.69963511598208 +103.876074664295 1.69826559347594 +103.900599453598 1.72908068451731 +103.925124242902 1.75608137444069 +103.949649032205 1.75663323625079 +103.974173821509 1.80287112893173 +103.998698610812 1.78121693965999 +104.023223400116 1.75923854716688 +104.04774818942 1.75859991350685 +104.072272978723 1.84498352866891 +104.096797768027 1.85426505313912 +104.12132255733 1.77139666477528 +104.145847346634 1.78093802937844 +104.170372135937 1.79241984844644 +104.194896925241 1.7648749247724 +104.219421714544 1.7743824385249 +104.243946503848 1.72278326449027 +104.268471293151 1.73927212486965 +104.292996082455 1.7652384621806 +104.317520871758 1.84329743100084 +104.342045661062 1.85210623745144 +104.366570450366 1.82988111622099 +104.391095239669 1.8095964494554 +104.415620028973 1.74796960598555 +104.440144818276 1.80999383771425 +104.46466960758 1.77625304656515 +104.489194396883 1.78010725728679 +104.513719186187 1.810866088716 +104.53824397549 1.82194837480187 +104.562768764794 1.8001943022846 +104.587293554097 1.76197759217916 +104.611818343401 1.85601503847879 +104.636343132704 1.83820194853309 +104.660867922008 1.77490044948434 +104.685392711312 1.77051130237793 +104.709917500615 1.78211159942323 +104.734442289919 1.77477052941787 +104.758967079222 1.80347209771137 +104.783491868526 1.78088343907273 +104.808016657829 1.78946411151776 +104.832541447133 1.86126332519135 +104.857066236436 1.86665297405954 +104.88159102574 1.85364334490443 +104.906115815043 1.89035298437782 +104.930640604347 1.7968995814325 +104.955165393651 1.8490336918519 +104.979690182954 1.84508341191445 +105.004214972258 1.75387345178346 +105.028739761561 1.81304182982902 +105.053264550865 1.80436127413845 +105.077789340168 1.82291669501943 +105.102314129472 1.82937621665716 +105.126838918775 1.82114215619499 +105.151363708079 1.85081204470606 +105.175888497382 1.81523449636736 +105.200413286686 1.87409392731044 +105.224938075989 1.84780171747167 +105.249462865293 1.83915882666149 +105.273987654597 1.86306241750056 +105.2985124439 1.8220482580474 +105.323037233204 1.82513391504255 +105.347562022507 1.83042368066521 +105.372086811811 1.84740981254898 +105.396611601114 1.82747557179597 +105.421136390418 1.80421609795309 +105.445661179721 1.83277844881424 +105.470185969025 1.82393579151298 +105.494710758328 1.85863727482021 +105.519235547632 1.91966314951041 +105.543760336936 1.84554301853845 +105.568285126239 1.86535496728443 +105.592809915543 1.88857844555338 +105.617334704846 1.91574290639389 +105.64185949415 1.87826471319401 +105.666384283453 1.86482932826555 +105.690909072757 1.81586001863178 +105.71543386206 1.8416981051827 +105.739958651364 1.85949641442635 +105.764483440667 1.81481255462374 +105.789008229971 1.83891591195387 +105.813533019274 1.79520966973471 +105.838057808578 1.80364731071766 +105.862582597882 1.83279156020985 +105.887107387185 1.87359284177632 +105.911632176489 1.90757654763304 +105.936156965792 1.88761775339526 +105.960681755096 1.91856109519665 +105.985206544399 1.80175214919264 +106.009731333703 1.82208592292778 +106.034256123006 1.84614780134782 +106.05878091231 1.86497998778707 +106.083305701613 1.9307907285361 +106.107830490917 1.87820249482885 +106.132355280221 1.89345938082802 +106.156880069524 1.83999341278286 +106.181404858828 1.81904174484274 +106.205929648131 1.83867085252298 +106.230454437435 1.83294603376109 +106.254979226738 1.90351327305445 +106.279504016042 1.90493357183657 +106.304028805345 1.92269040217012 +106.328553594649 1.86575998360273 +106.353078383952 1.85451178187646 +106.377603173256 1.86307552889616 +106.402127962559 1.93780974004337 +106.426652751863 1.89130056514033 +106.451177541167 1.8848825224127 +106.47570233047 1.8570040981514 +106.500227119774 1.83178176108218 +106.524751909077 1.84923345834297 +106.549276698381 1.83242802280165 +106.573801487684 1.78477531467475 +106.598326276988 1.85066138518454 +106.622851066291 1.87254156373856 +106.647375855595 1.91654912500077 +106.671900644898 1.89896583836707 +106.696425434202 1.83719596343514 +106.720950223505 1.83919649154187 +106.745475012809 1.79986080169734 +106.769999802113 1.82112904479939 +106.794524591416 1.79254976851291 +106.81904938072 1.76786451255174 +106.843574170023 1.81728413144363 +106.868098959327 1.85514660150676 +106.89262374863 1.81896641508198 +106.917148537934 1.84511344873538 +106.941673327237 1.8768706372031 +106.966198116541 1.84238965984199 +106.990722905844 1.83986897605255 +107.015247695148 1.83526488298054 +107.039772484452 1.85205011509588 +107.064297273755 1.80812909026698 +107.088822063059 1.80549081566593 +107.113346852362 1.84022183264548 +107.137871641666 1.83960839768925 +107.162396430969 1.83740109806562 +107.186921220273 1.80552618077624 +107.211446009576 1.81845576859406 +107.23597079888 1.84597301145518 +107.260495588183 1.82234372840644 +107.285020377487 1.79418329328353 +107.30954516679 1.79517795868886 +107.334069956094 1.79404773035516 +107.358594745398 1.78325703933754 +107.383119534701 1.7947988427197 +107.407644324005 1.78419969767831 +107.432169113308 1.79022493967361 +107.456693902612 1.7893552247376 +107.481218691915 1.81879947010081 +107.505743481219 1.79051325946207 +107.530268270522 1.81893127730945 +107.554793059826 1.79424442433282 +107.579317849129 1.80868937012588 +107.603842638433 1.83566930508535 +107.628367427737 1.78319338141063 +107.65289221704 1.77278022307656 +107.677417006344 1.8224764819698 +107.701941795647 1.83653389876802 +107.726466584951 1.8296231134386 +107.750991374254 1.80453310518657 +107.775516163558 1.8137909819023 +107.800040952861 1.80336982570292 +107.824565742165 1.81056559427995 +107.849090531468 1.80037760916046 +107.873615320772 1.79266151105911 +107.898140110075 1.8059277642564 +107.922664899379 1.79538011034294 +107.947189688683 1.81439157332048 +107.971714477986 1.85339912372666 +107.99623926729 1.82120368327498 +108.020764056593 1.8137264562324 +108.045288845897 1.83023381525557 +108.0698136352 1.82304096818458 +108.094338424504 1.81029192547162 +108.118863213807 1.8310664970934 +108.143388003111 1.85345603925289 +108.167912792414 1.8278479280975 +108.192437581718 1.81341796499846 +108.216962371022 1.83580773602359 +108.241487160325 1.82524035922876 +108.266011949629 1.80509049706872 +108.290536738932 1.8548377491737 +108.315061528236 1.89475707072833 +108.339586317539 1.85279218233859 +108.364111106843 1.78817975183248 +108.388635896146 1.82701152852723 +108.41316068545 1.81381505724938 +108.437685474753 1.83751032562223 +108.462210264057 1.82285211999744 +108.48673505336 1.84649831197895 +108.511259842664 1.83938519560749 +108.535784631968 1.83871466548591 +108.560309421271 1.86483204055888 +108.584834210575 1.85367944094065 +108.609358999878 1.80605874849055 +108.633883789182 1.84173326675223 +108.658408578485 1.81277298802418 +108.682933367789 1.83447997937296 +108.707458157092 1.80502604084338 +108.731982946396 1.80995424900204 +108.756507735699 1.86059454715295 +108.781032525003 1.83817098293832 +108.805557314306 1.84319327446644 +108.83008210361 1.86160738844604 +108.854606892914 1.83877763072536 +108.879131682217 1.85053445868486 +108.903656471521 1.84148405448256 +108.928181260824 1.85137517455441 +108.952706050128 1.8610376035364 +108.977230839431 1.83397789310891 +109.001755628735 1.84338604457561 +109.026280418038 1.84361876501935 +109.050805207342 1.83605509620825 +109.075329996645 1.8659390459052 +109.099854785949 1.8844997703995 +109.124379575253 1.84614958333946 +109.148904364556 1.90017180844062 +109.17342915386 1.8990249334435 +109.197953943163 1.86726876219828 +109.222478732467 1.85467062633243 +109.24700352177 1.85197765617049 +109.271528311074 1.84621596787557 +109.296053100377 1.86990897164015 +109.320577889681 1.83834875220746 +109.345102678984 1.82547111544647 +109.369627468288 1.81802423303709 +109.394152257591 1.83746111241382 +109.418677046895 1.83195015895729 +109.443201836199 1.83443087164994 +109.467726625502 1.82357009845624 +109.492251414806 1.83178646665189 +109.516776204109 1.82802293484216 +109.541300993413 1.82689906784314 +109.565825782716 1.86871655834501 +109.59035057202 1.87057081290427 +109.614875361323 1.88632091780111 +109.639400150627 1.87810601604086 +109.66392493993 1.86994096887793 +109.688449729234 1.82439889453846 +109.712974518538 1.83553775349044 +109.737499307841 1.82997342065453 +109.762024097145 1.85634933811795 +109.786548886448 1.88209198309118 +109.811073675752 1.82727747505087 +109.835598465055 1.85432652561367 +109.860123254359 1.83742634612152 +109.884648043662 1.83422135410113 +109.909172832966 1.85301995633337 +109.933697622269 1.86277433618444 +109.958222411573 1.84574617245033 +109.982747200876 1.85633574770561 +110.00727199018 1.88877547212399 +110.031796779484 1.84167593424473 +110.056321568787 1.82763513472472 +110.080846358091 1.83516703265213 +110.105371147394 1.87015897555263 +110.129895936698 1.8760195568489 +110.154420726001 1.88111975832319 +110.178945515305 1.86791656448596 +110.203470304608 1.86867347095 +110.227995093912 1.82037778000425 +110.252519883215 1.82473508655219 +110.277044672519 1.88911589309761 +110.301569461823 1.87398534714953 +110.326094251126 1.85284039098694 +110.35061904043 1.81678081711187 +110.375143829733 1.84848315834157 +110.399668619037 1.89139402579012 +110.42419340834 1.85162524791144 +110.448718197644 1.8652629707678 +110.473242986947 1.85529304845631 +110.497767776251 1.85643395454955 +110.522292565554 1.85150841008559 +110.546817354858 1.88223704428908 +110.571342144161 1.87330160573517 +110.595866933465 1.8406703713814 +110.620391722769 1.85167704205875 +110.644916512072 1.86573762517703 +110.669441301376 1.85909717211509 +110.693966090679 1.8500872675877 +110.718490879983 1.85382782065898 +110.743015669286 1.8730302576684 +110.76754045859 1.85195475353222 +110.792065247893 1.9135769476703 +110.816590037197 1.86564173241904 +110.8411148265 1.86969046497723 +110.865639615804 1.88879384749218 +110.890164405107 1.89535823616289 +110.914689194411 1.88135460668371 +110.939213983715 1.855523024396 +110.963738773018 1.89209979073498 +110.988263562322 1.87444877445889 +111.012788351625 1.84172844588112 +111.037313140929 1.8338147368326 +111.061837930232 1.82994616360409 +111.086362719536 1.81997254158059 +111.110887508839 1.86204660830041 +111.135412298143 1.85788601569701 +111.159937087446 1.8592434060218 +111.18446187675 1.87813699069816 +111.208986666054 1.85957566292783 +111.233511455357 1.87399040561395 +111.258036244661 1.86609944643824 +111.282561033964 1.83399402854487 +111.307085823268 1.83876080247637 +111.331610612571 1.85309660610577 +111.356135401875 1.83017571578131 +111.380660191178 1.8302171932682 +111.405184980482 1.84372671450733 +111.429709769785 1.84589670476149 +111.454234559089 1.87231000976659 +111.478759348392 1.86248704356006 +111.503284137696 1.88536231697475 +111.527808927 1.84917979277166 +111.552333716303 1.84600476639049 +111.576858505607 1.84951471808156 +111.60138329491 1.81806324172611 +111.625908084214 1.81482259765 +111.650432873517 1.87828004290152 +111.674957662821 1.88341266079248 +111.699482452124 1.88910482761644 +111.724007241428 1.87697520232599 +111.748532030731 1.85712852385179 +111.773056820035 1.84682425024678 +111.797581609339 1.87263607807028 +111.822106398642 1.86932695990533 +111.846631187946 1.82641340603249 +111.871155977249 1.85018682194483 +111.895680766553 1.84922234426316 +111.920205555856 1.82185301937059 +111.94473034516 1.82526302622019 +111.969255134463 1.86688900276819 +111.993779923767 1.86810795091552 +112.01830471307 1.88317506792791 +112.042829502374 1.87636344659685 +112.067354291677 1.89445315611657 +112.091879080981 1.89296997225552 +112.116403870285 1.87862323318082 +112.140928659588 1.85413777832834 +112.165453448892 1.83985041899413 +112.189978238195 1.82195382046468 +112.214503027499 1.87435483456214 +112.239027816802 1.86519436639192 +112.263552606106 1.83676185905754 +112.288077395409 1.85411496773863 +112.312602184713 1.88855273191758 +112.337126974016 1.87383085021988 +112.36165176332 1.84973052959253 +112.386176552624 1.85087861541134 +112.410701341927 1.87664177271114 +112.435226131231 1.89087962052674 +112.459750920534 1.84944416279089 +112.484275709838 1.81405458966394 +112.508800499141 1.83190020345817 +112.533325288445 1.86216842390691 +112.557850077748 1.88257415810707 +112.582374867052 1.89084634522033 +112.606899656355 1.84869103006701 +112.631424445659 1.83832284945141 +112.655949234962 1.84261078271026 +112.680474024266 1.85086177385109 +112.70499881357 1.84850643746226 +112.729523602873 1.86164334088398 +112.754048392177 1.85805181570114 +112.77857318148 1.87988688999607 +112.803097970784 1.87067991042501 +112.827622760087 1.86706122231233 +112.852147549391 1.83828057159406 +112.876672338694 1.83730949027524 +112.901197127998 1.84638236657213 +112.925721917301 1.80685872903834 +112.950246706605 1.84540041247136 +112.974771495908 1.88645638660888 +112.999296285212 1.88264588152295 +113.023821074516 1.8640557002309 +113.048345863819 1.84892593443525 +113.072870653123 1.83059234932803 +113.097395442426 1.85284957763502 +113.12192023173 1.87724780378533 +113.146445021033 1.87738241163132 +113.170969810337 1.85790147163119 +113.19549459964 1.87446635818427 +113.220019388944 1.86812700578549 +113.244544178247 1.85885518879751 +113.269068967551 1.87003206544086 +113.293593756855 1.85249144264899 +113.318118546158 1.88987408912066 +113.342643335462 1.87399058719953 +113.367168124765 1.88212215945005 +113.391692914069 1.87405055219563 +113.416217703372 1.90639556987608 +113.440742492676 1.88076709445042 +113.465267281979 1.88034170260169 +113.489792071283 1.91483361300119 +113.514316860586 1.84777143127404 +113.53884164989 1.8421071692939 +113.563366439193 1.85690187779732 +113.587891228497 1.8585043029816 +113.612416017801 1.8747366703593 +113.636940807104 1.85531902065691 +113.661465596408 1.84736450213291 +113.685990385711 1.89441621921102 +113.710515175015 1.86272284034734 +113.735039964318 1.89510031701535 +113.759564753622 1.88745965340668 +113.784089542925 1.87257276322208 +113.808614332229 1.85900205285391 +113.833139121532 1.89296031029526 +113.857663910836 1.86226867578624 +113.88218870014 1.89971909687811 +113.906713489443 1.89930528198917 +113.931238278747 1.88262451934824 +113.95576306805 1.8603443684361 +113.980287857354 1.857300299541 +114.004812646657 1.87246355356031 +114.029337435961 1.89026560684496 +114.053862225264 1.89657804998118 +114.078387014568 1.91723515569946 +114.102911803871 1.89808320356261 +114.127436593175 1.86964781478153 +114.151961382478 1.86624650331943 +114.176486171782 1.88134831259598 +114.201010961086 1.92072468240336 +114.225535750389 1.88710418224093 +114.250060539693 1.85772219793934 +114.274585328996 1.88421346418918 +114.2991101183 1.89193221267398 +114.323634907603 1.89648030501259 +114.348159696907 1.93011923002088 +114.37268448621 1.93110761477531 +114.397209275514 1.88686100166406 +114.421734064817 1.85633107696282 +114.446258854121 1.86828323129434 +114.470783643425 1.86999959301647 +114.495308432728 1.85703500784309 +114.519833222032 1.87531183891721 +114.544358011335 1.87542978666295 +114.568882800639 1.88369670226896 +114.593407589942 1.89261826804872 +114.617932379246 1.90151570718285 +114.642457168549 1.91630853409046 +114.666981957853 1.90689427483077 +114.691506747156 1.87269754095905 +114.71603153646 1.85713731806906 +114.740556325763 1.84669980893288 +114.765081115067 1.87217334357396 +114.789605904371 1.88495458300513 +114.814130693674 1.8925396273022 +114.838655482978 1.88746034621972 +114.863180272281 1.89368299095409 +114.887705061585 1.89327477721428 +114.912229850888 1.86913268066427 +114.936754640192 1.87475752796218 +114.961279429495 1.86638600148109 +114.985804218799 1.87442011376196 +115.010329008102 1.86599213833917 +115.034853797406 1.90828325194213 +115.059378586709 1.93293017926023 +115.083903376013 1.8850563573276 +115.108428165317 1.92257286257335 +115.13295295462 1.88176741048254 +115.157477743924 1.88425721306554 +115.182002533227 1.91128126465612 +115.206527322531 1.93864852454642 +115.231052111834 1.89228495735611 +115.255576901138 1.87827000194728 +115.280101690441 1.89846427239301 +115.304626479745 1.88738536707233 +115.329151269048 1.88792073529842 +115.353676058352 1.86600042798462 +115.378200847656 1.8671004105149 +115.402725636959 1.88432695881855 +115.427250426263 1.87173872043423 +115.451775215566 1.90341722403574 +115.47630000487 1.90431155022234 +115.500824794173 1.87483604325642 +115.525349583477 1.91919373155128 +115.54987437278 1.89910140163032 +115.574399162084 1.88510411753743 +115.598923951387 1.85890145124024 +115.623448740691 1.90812910280034 +115.647973529994 1.89601906162774 +115.672498319298 1.87846163067932 +115.697023108602 1.90922933428873 +115.721547897905 1.90614870967966 +115.746072687209 1.87438392567717 +115.770597476512 1.90868272094234 +115.795122265816 1.90118629568267 +115.819647055119 1.89548792889158 +115.844171844423 1.89021553933039 +115.868696633726 1.8651642526964 +115.89322142303 1.90305877742826 +115.917746212333 1.89155802306134 +115.942271001637 1.916570244873 +115.966795790941 1.87806863528038 +115.991320580244 1.93028065288326 +116.015845369548 1.89937194015943 +116.040370158851 1.88877559092064 +116.064894948155 1.92649245174318 +116.089419737458 1.93166584306878 +116.113944526762 1.89536894525411 +116.138469316065 1.89817520378379 +116.162994105369 1.92194724072569 +116.187518894672 1.91458670138064 +116.212043683976 1.93647175572516 +116.236568473279 1.93348260001737 +116.261093262583 1.91443649412777 +116.285618051887 1.87087303692414 +116.31014284119 1.86284956428993 +116.334667630494 1.88068425607568 +116.359192419797 1.90674779196596 +116.383717209101 1.89582973354482 +116.408241998404 1.91840274482676 +116.432766787708 1.91729508478029 +116.457291577011 1.88919295539327 +116.481816366315 1.88940604707624 +116.506341155618 1.87732286374207 +116.530865944922 1.91566889587037 +116.555390734226 1.93943266796845 +116.579915523529 1.90227048585569 +116.604440312833 1.92837116478757 +116.628965102136 1.91636163776364 +116.65348989144 1.92555283634223 +116.678014680743 1.9116650534401 +116.702539470047 1.92074593909267 +116.72706425935 1.93710022756226 +116.751589048654 1.91499803416046 +116.776113837957 1.91248879758795 +116.800638627261 1.9132645392235 +116.825163416564 1.92240448621392 +116.849688205868 1.89930327965025 +116.874212995172 1.88944411399413 +116.898737784475 1.9124832771226 +116.923262573779 1.93251578357371 +116.947787363082 1.90735318636042 +116.972312152386 1.89894623656368 +116.996836941689 1.90566353234839 +117.021361730993 1.9180115161199 +117.045886520296 1.90563290742542 +117.0704113096 1.92196895263473 +117.094936098903 1.91657633935474 +117.119460888207 1.90765273312317 +117.143985677511 1.90127565217604 +117.168510466814 1.90417948739998 +117.193035256118 1.91271843044895 +117.217560045421 1.93937139803005 +117.242084834725 1.92897876112695 +117.266609624028 1.92596645840637 +117.291134413332 1.93078748404382 +117.315659202635 1.90841214649796 +117.340183991939 1.90403912827654 +117.364708781242 1.91678378041091 +117.389233570546 1.92183844829635 +117.413758359849 1.92236801118372 +117.438283149153 1.94064611367085 +117.462807938457 1.97247843011664 +117.48733272776 1.95529145786276 +117.511857517064 1.90854217564973 +117.536382306367 1.93775673660224 +117.560907095671 1.92194967360829 +117.585431884974 1.93294802996413 +117.609956674278 1.92572079559481 +117.634481463581 1.94030259374968 +117.659006252885 1.94883715105481 +117.683531042188 1.93091999750232 +117.708055831492 1.91182216471236 +117.732580620795 1.89381036972242 +117.757105410099 1.92635032787413 +117.781630199403 1.93460055882941 +117.806154988706 1.89482135214929 +117.83067977801 1.90632119607675 +117.855204567313 1.94277550542033 +117.879729356617 1.92473841107594 +117.90425414592 1.89659656216644 +117.928778935224 1.91958813530587 +117.953303724527 1.9677601205728 +117.977828513831 1.95972444773591 +118.002353303134 1.93824593663178 +118.026878092438 1.92835643105164 +118.051402881742 1.93576986988028 +118.075927671045 1.95619378662383 +118.100452460349 1.92083925562094 +118.124977249652 1.90988443509862 +118.149502038956 1.9118977112205 +118.174026828259 1.90414987162066 +118.198551617563 1.93045295116472 +118.223076406866 1.95375515011049 +118.24760119617 1.93301499988142 +118.272125985473 1.98521955746896 +118.296650774777 1.9532310270047 +118.32117556408 1.9424683417327 +118.345700353384 1.911052360649 +118.370225142688 1.90651655585248 +118.394749931991 1.91389382267029 +118.419274721295 1.951539184359 +118.443799510598 1.97656416249488 +118.468324299902 1.97045416254174 +118.492849089205 2.00606495550399 +118.517373878509 1.96102572095849 +118.541898667812 1.94401001304079 +118.566423457116 1.973251153614 +118.590948246419 1.95894859395933 +118.615473035723 1.95175528294095 +118.639997825027 1.93054921845867 +118.66452261433 1.96642411697319 +118.689047403634 1.9632446173832 +118.713572192937 1.93855871948895 +118.738096982241 1.93779368028899 +118.762621771544 1.9737126212809 +118.787146560848 1.96157526705013 +118.811671350151 1.97497357628858 +118.836196139455 1.96594392420376 +118.860720928758 1.94588608235736 +118.885245718062 1.96527681808802 +118.909770507365 2.00518243501724 +118.934295296669 1.98235783666279 +118.958820085973 1.96326327969679 +118.983344875276 1.95156505119978 +119.00786966458 1.98521031274112 +119.032394453883 1.99138614123442 +119.056919243187 1.95253037653198 +119.08144403249 1.96232066405249 +119.105968821794 1.94275500904215 +119.130493611097 1.94902757103719 +119.155018400401 1.98499527485253 +119.179543189704 1.98602915520851 +119.204067979008 2.00109702782286 +119.228592768312 1.94006466458761 +119.253117557615 1.97293715993513 +119.277642346919 1.99939140664919 +119.302167136222 1.95024265144934 +119.326691925526 1.94784687804895 +119.351216714829 1.9445985431454 +119.375741504133 1.93223068152944 +119.400266293436 1.963974565107 +119.42479108274 1.98952077629989 +119.449315872043 1.98244328730809 +119.473840661347 1.95950126367457 +119.49836545065 1.98591760691033 +119.522890239954 1.97067565601119 +119.547415029258 1.9576696808159 +119.571939818561 1.98016647745272 +119.596464607865 1.96609031514558 +119.620989397168 1.93886706712462 +119.645514186472 1.97484468851395 +119.670038975775 1.99769553477518 +119.694563765079 1.97497670863984 +119.719088554382 1.99162789348875 +119.743613343686 1.99117598688029 +119.768138132989 1.96950239170662 +119.792662922293 1.93517864004012 +119.817187711596 1.95172825682819 +119.8417125009 1.96053403881984 +119.866237290204 1.93849063299616 +119.890762079507 1.97598265049714 +119.915286868811 1.99684602468836 +119.939811658114 1.98494197157334 +119.964336447418 1.96345671128732 +119.988861236721 1.945894271101 +120.013386026025 1.96612053420092 +120.037910815328 1.99135578982002 +120.062435604632 1.99031096485156 +120.086960393935 1.98357597778403 +120.111485183239 2.0145596137731 +120.136009972543 1.99966704633919 +120.160534761846 1.98496648411973 +120.18505955115 1.95838007380708 +120.209584340453 1.9706683147925 +120.234109129757 1.96489709011517 +120.25863391906 1.93918334355775 +120.283158708364 1.92183594804124 +120.307683497667 1.93663001503018 +120.332208286971 1.96537469987368 +120.356733076274 1.95677443753422 +120.381257865578 1.95274109812128 +120.405782654881 1.98076365830191 +120.430307444185 1.96768813488757 +120.454832233489 1.98195482486009 +120.479357022792 1.96998266986899 +120.503881812096 1.96062988888136 +120.528406601399 2.00257605216857 +120.552931390703 1.98152270863111 +120.577456180006 1.98260681799478 +120.60198096931 1.96936980421997 +120.626505758613 1.97876853797534 +120.651030547917 1.96754014289087 +120.67555533722 1.94846609379342 +120.700080126524 1.99661978437594 +120.724604915828 1.96415259677107 +120.749129705131 1.95949663562826 +120.773654494435 1.95811011607555 +120.798179283738 1.9616830662842 +120.822704073042 1.99566966601983 +120.847228862345 1.98327949392798 +120.871753651649 1.96350102739886 +120.896278440952 1.96954852191587 +120.920803230256 2.0478338540939 +120.945328019559 2.04467762031334 +120.969852808863 1.97265800224427 +120.994377598166 1.96685350006292 +121.01890238747 1.9710884865581 +121.043427176774 1.96487793446898 +121.067951966077 1.97738449594816 +121.092476755381 1.97233642975341 +121.117001544684 1.95912310749697 +121.141526333988 1.96213000176764 +121.166051123291 1.9569909352026 +121.190575912595 2.02140503036569 +121.215100701898 1.97355794760042 +121.239625491202 1.93524658971585 +121.264150280505 1.95564178297354 +121.288675069809 1.99056710581666 +121.313199859113 1.98853179950867 +121.337724648416 1.9983929113704 +121.36224943772 1.96820459184197 +121.386774227023 1.97827280846238 +121.411299016327 1.97868739031807 +121.43582380563 1.99848143157803 +121.460348594934 1.9926559351023 +121.484873384237 1.96850783166111 +121.509398173541 1.9869484357244 +121.533922962844 1.99844355290135 +121.558447752148 1.9380579875193 +121.582972541451 1.94032787313725 +121.607497330755 1.95556758410417 +121.632022120059 1.9707336700925 +121.656546909362 2.00804811127473 +121.681071698666 1.98234508711978 +121.705596487969 1.95936051212033 +121.730121277273 1.93832913598006 +121.754646066576 1.97671589206532 +121.77917085588 1.96719539883675 +121.803695645183 1.98721308603341 +121.828220434487 2.00765414930303 +121.85274522379 1.99947587282689 +121.877270013094 1.97778166222722 +121.901794802397 2.01479227820912 +121.926319591701 1.97440115901592 +121.950844381005 1.99852394495665 +121.975369170308 1.99239346871779 +121.999893959612 1.95493056035226 +122.024418748915 1.95469007480177 +122.048943538219 1.97320230150471 +122.073468327522 1.97935215767183 +122.097993116826 1.97464937350673 +122.122517906129 1.96302014847202 +122.147042695433 1.99252047960578 +122.171567484736 1.97548569226764 +122.19609227404 1.98229779544093 +122.220617063344 1.96513423912481 +122.245141852647 1.95440225292951 +122.269666641951 1.93492827973769 +122.294191431254 1.95565328565402 +122.318716220558 2.02554133483032 +122.343241009861 1.996327740325 +122.367765799165 1.98815881393651 +122.392290588468 1.97388176460709 +122.416815377772 2.01333678640021 +122.441340167075 2.00235667330465 +122.465864956379 1.99731280655899 +122.490389745682 1.96783375886263 +122.514914534986 1.98212495252831 +122.53943932429 2.02031949267948 +122.563964113593 1.9933689672377 +122.588488902897 1.98055044782228 +122.6130136922 1.9857610002099 +122.637538481504 1.99731586281007 +122.662063270807 1.94754888311455 +122.686588060111 1.95334764042292 +122.711112849414 1.9818030076991 +122.735637638718 1.98029182920607 +122.760162428021 1.98586907985932 +122.784687217325 2.0043650542761 +122.809212006629 2.02706851859787 +122.833736795932 2.04414650559762 +122.858261585236 1.96939254536512 +122.882786374539 2.01933089017305 +122.907311163843 2.00963103120406 +122.931835953146 1.99429756650957 +122.95636074245 2.03726545484416 +122.980885531753 2.02474471091584 +123.005410321057 2.02334519286202 +123.02993511036 2.02162764053622 +123.054459899664 1.97897342747784 +123.078984688967 2.00707655464372 +123.103509478271 2.02265765303144 +123.128034267575 2.00780892859449 +123.152559056878 2.02225637464268 +123.177083846182 2.00127360636276 +123.201608635485 2.01750452074187 +123.226133424789 2.02610533907729 +123.250658214092 2.03084714995183 +123.275183003396 2.02653923807231 +123.299707792699 2.02305752789929 +123.324232582003 2.00921230107225 +123.348757371306 1.98367837089139 +123.37328216061 2.04116421108716 +123.397806949914 2.01509900482303 +123.422331739217 1.9873462834517 +123.446856528521 2.03661389683675 +123.471381317824 2.03788179592314 +123.495906107128 2.00853204645264 +123.520430896431 2.02107223755615 +123.544955685735 2.05434976048212 +123.569480475038 2.00107814122722 +123.594005264342 1.9648355892391 +123.618530053645 2.01883000807507 +123.643054842949 1.99790931927119 +123.667579632252 1.9957090044763 +123.692104421556 1.94042688493525 +123.71662921086 1.96047693034323 +123.741154000163 2.00534136911487 +123.765678789467 2.03604428007586 +123.79020357877 2.00063572164907 +123.814728368074 2.00574299058233 +123.839253157377 1.99085194559156 +123.863777946681 2.00807781910261 +123.888302735984 2.00421918812414 +123.912827525288 1.96953604807904 +123.937352314591 2.01875932055092 +123.961877103895 2.01900264472606 +123.986401893198 2.03360183183506 +124.010926682502 2.04984113068346 +124.035451471806 2.04793445680427 +124.059976261109 2.02982515111124 +124.084501050413 2.04687290724296 +124.109025839716 2.03938498362928 +124.13355062902 1.99817678070749 +124.158075418323 1.99224039568196 +124.182600207627 2.01828606961655 +124.20712499693 2.06858556833148 +124.231649786234 2.05558744145082 +124.256174575537 2.03831473263598 +124.280699364841 2.04950180800539 +124.305224154145 2.03395206166155 +124.329748943448 2.04409371258311 +124.354273732752 2.05321595263534 +124.378798522055 2.05797975791887 +124.403323311359 2.0450259583764 +124.427848100662 2.0352991756406 +124.452372889966 2.05111458488213 +124.476897679269 2.04212301205532 +124.501422468573 2.02320333728587 +124.525947257876 2.0212167517118 +124.55047204718 2.03187497254504 +124.574996836483 2.03922698994235 +124.599521625787 2.0502863762908 +124.624046415091 2.05116223342813 +124.648571204394 2.05529564261157 +124.673095993698 2.04791546975472 +124.697620783001 2.03431429222285 +124.722145572305 2.03081621304324 +124.746670361608 2.04953524122178 +124.771195150912 2.05928366730529 +124.795719940215 2.05460506627075 +124.820244729519 2.06695085523854 +124.844769518822 2.04699076622608 +124.869294308126 2.03197267904076 +124.89381909743 2.05657413435339 +124.918343886733 2.06365834975563 +124.942868676037 2.03320111309657 +124.96739346534 2.0358789713616 +124.991918254644 2.05073474760632 +125.016443043947 2.06238198823284 +125.040967833251 2.09188674421539 +125.065492622554 2.09142925641213 +125.090017411858 2.06872012216643 +125.114542201161 2.03354504284945 +125.139066990465 2.04476063602075 +125.163591779768 2.04342652130047 +125.188116569072 2.05060203935564 +125.212641358376 2.06768181836395 +125.237166147679 2.06811355911514 +125.261690936983 2.077875532835 +125.286215726286 2.06775836911821 +125.31074051559 2.08504029847041 +125.335265304893 2.08671975679954 +125.359790094197 2.05399622924727 +125.3843148835 2.05998484519245 +125.408839672804 2.06909263940016 +125.433364462107 2.0543809121707 +125.457889251411 2.07662119379203 +125.482414040715 2.06576972033763 +125.506938830018 2.0694106545843 +125.531463619322 2.08157368778301 +125.555988408625 2.05047697771225 +125.580513197929 2.06167603297056 +125.605037987232 2.06493964667653 +125.629562776536 2.07467967248888 +125.654087565839 2.07272049837997 +125.678612355143 2.03806082356612 +125.703137144446 2.03492307301676 +125.72766193375 2.06627228052832 +125.752186723053 2.11804958497308 +125.776711512357 2.09967489688562 +125.801236301661 2.08284478880754 +125.825761090964 2.07310092540706 +125.850285880268 2.06877501023398 +125.874810669571 2.05240611579136 +125.899335458875 2.06011169463755 +125.923860248178 2.07663797414564 +125.948385037482 2.08027094547532 +125.972909826785 2.06536996188046 +125.997434616089 2.07772492300882 +126.021959405392 2.078563432308 +126.046484194696 2.0693047839875 +126.071008983999 2.07483212675657 +126.095533773303 2.06780581618709 +126.120058562607 2.0793754089233 +126.14458335191 2.0819824706626 +126.169108141214 2.08943225031638 +126.193632930517 2.08387016941794 +126.218157719821 2.06118046022062 +126.242682509124 2.07869495610416 +126.267207298428 2.08435157351556 +126.291732087731 2.09857690551783 +126.316256877035 2.08621769886586 +126.340781666338 2.08736964780278 +126.365306455642 2.10086658888249 +126.389831244946 2.09219483931816 +126.414356034249 2.08872669882876 +126.438880823553 2.1054212002104 +126.463405612856 2.10999918399684 +126.48793040216 2.07957421118991 +126.512455191463 2.10670640943826 +126.536979980767 2.11784151965263 +126.56150477007 2.07867793433549 +126.586029559374 2.0994224694461 +126.610554348677 2.10528457054 +126.635079137981 2.11514561799285 +126.659603927284 2.0822286477992 +126.684128716588 2.09736851565108 +126.708653505892 2.09197715346559 +126.733178295195 2.11618776146386 +126.757703084499 2.09619068844998 +126.782227873802 2.10124514392466 +126.806752663106 2.13170837194929 +126.831277452409 2.10681494234003 +126.855802241713 2.10100546442495 +126.880327031016 2.11903145309069 +126.90485182032 2.10229938188273 +126.929376609623 2.10754351592813 +126.953901398927 2.09421004220201 +126.978426188231 2.10636028646812 +127.002950977534 2.11639266318928 +127.027475766838 2.09240411150878 +127.052000556141 2.06377666338082 +127.076525345445 2.09047007281242 +127.101050134748 2.08978166410866 +127.125574924052 2.10324524458111 +127.150099713355 2.11047127288395 +127.174624502659 2.10278777764052 +127.199149291962 2.08176397060925 +127.223674081266 2.09945289963967 +127.248198870569 2.10412952012723 +127.272723659873 2.08875732487356 +127.297248449177 2.1121952580066 +127.32177323848 2.11978882328446 +127.346298027784 2.1225986205479 +127.370822817087 2.11705264199566 +127.395347606391 2.11928200400356 +127.419872395694 2.11714911463452 +127.444397184998 2.13176523411748 +127.468921974301 2.1324431310254 +127.493446763605 2.12947681493673 +127.517971552908 2.11969674798027 +127.542496342212 2.13534005550736 +127.567021131516 2.13226173288272 +127.591545920819 2.11860960944848 +127.616070710123 2.11483977037371 +127.640595499426 2.10386867322406 +127.66512028873 2.1040507601278 +127.689645078033 2.12022741572255 +127.714169867337 2.12795376681217 +127.73869465664 2.13391186416115 +127.763219445944 2.12293636873917 +127.787744235247 2.12444018781309 +127.812269024551 2.11153287310756 +127.836793813854 2.12530504751152 +127.861318603158 2.12342338404469 +127.885843392462 2.13270673051147 +127.910368181765 2.13521949296275 +127.934892971069 2.14048593377113 +127.959417760372 2.13451281070095 +127.983942549676 2.12700660588155 +128.008467338979 2.10957865429249 +128.032992128283 2.09526425405245 +128.057516917586 2.11335543553719 +128.08204170689 2.11769460489209 +128.106566496193 2.12412113667443 +128.131091285497 2.11260858399583 +128.1556160748 2.11924248178901 +128.180140864104 2.12693438966885 +128.204665653408 2.11372404321831 +128.229190442711 2.14174942789189 +128.253715232015 2.1523524854872 +128.278240021318 2.13558587868184 +128.302764810622 2.1316646250445 +128.327289599925 2.13978846032608 +128.351814389229 2.15414187217273 +128.376339178532 2.15156265175159 +128.400863967836 2.16328822240734 +128.425388757139 2.13992945653529 +128.449913546443 2.12609371724505 +128.474438335747 2.15197463233315 +128.49896312505 2.15260662354828 +128.523487914354 2.12318589661948 +128.548012703657 2.12461634500161 +128.572537492961 2.11740261910927 +128.597062282264 2.11873906373831 +128.621587071568 2.12779555192822 +128.646111860871 2.14049540152523 +128.670636650175 2.15183471409683 +128.695161439478 2.14467888465701 +128.719686228782 2.11300763417715 +128.744211018085 2.11592215834124 +128.768735807389 2.17064517716597 +128.793260596693 2.15739510731678 +128.817785385996 2.13275204689249 +128.8423101753 2.13792944732882 +128.866834964603 2.1188360389858 +128.891359753907 2.14659696517055 +128.91588454321 2.18530998560328 +128.940409332514 2.16840757730511 +128.964934121817 2.15178739501689 +128.989458911121 2.13849503185172 +129.013983700424 2.14432677612301 +129.038508489728 2.14854840400583 +129.063033279032 2.17787221106698 +129.087558068335 2.14188067645906 +129.112082857639 2.12925055678389 +129.136607646942 2.12591902873561 +129.161132436246 2.13179526953195 +129.185657225549 2.13940342853899 +129.210182014853 2.14381512874609 +129.234706804156 2.14633085527849 +129.25923159346 2.16045423640419 +129.283756382763 2.16941365227075 +129.308281172067 2.18130830500418 +129.33280596137 2.13453228395029 +129.357330750674 2.13218759800782 +129.381855539978 2.15789315610206 +129.406380329281 2.15510786426893 +129.430905118585 2.15825047628872 +129.455429907888 2.16949421607051 +129.479954697192 2.16909749579795 +129.504479486495 2.15713329818166 +129.529004275799 2.14416472965774 +129.553529065102 2.14778760090695 +129.578053854406 2.17167336547538 +129.602578643709 2.17742754794416 +129.627103433013 2.1612011434426 +129.651628222317 2.15199201960872 +129.67615301162 2.15158318404504 +129.700677800924 2.16484532611214 +129.725202590227 2.14783676123453 +129.749727379531 2.15396490301035 +129.774252168834 2.1417876110865 +129.798776958138 2.11553908647287 +129.823301747441 2.16208447445488 +129.847826536745 2.17827109844917 +129.872351326048 2.15540201467075 +129.896876115352 2.16716564589433 +129.921400904655 2.1764154513738 +129.945925693959 2.15687707657883 +129.970450483263 2.15546908668283 +129.994975272566 2.14848869217888 +130.01950006187 2.14100533362166 +130.044024851173 2.14333677528622 +130.068549640477 2.15121243712002 +130.09307442978 2.15706582207258 +130.117599219084 2.18100035081232 +130.142124008387 2.20196308507916 +130.166648797691 2.18659331174095 +130.191173586994 2.16049447838078 +130.215698376298 2.16870024962207 +130.240223165601 2.17567686569762 +130.264747954905 2.17867347892176 +130.289272744209 2.18427914125942 +130.313797533512 2.16946633311492 +130.338322322816 2.15069762770105 +130.362847112119 2.17755234224067 +130.387371901423 2.19223599098563 +130.411896690726 2.16661049094727 +130.43642148003 2.14921280839353 +130.460946269333 2.14459124056004 +130.485471058637 2.17114702887569 +130.50999584794 2.16757470453033 +130.534520637244 2.15460931750466 +130.559045426548 2.16837494563125 +130.583570215851 2.18597943292561 +130.608095005155 2.17274375521128 +130.632619794458 2.15957391518337 +130.657144583762 2.17868322032257 +130.681669373065 2.1670757149032 +130.706194162369 2.17895840169526 +130.730718951672 2.1778991801674 +130.755243740976 2.16844400138417 +130.779768530279 2.17055477600297 +130.804293319583 2.17677233517323 +130.828818108886 2.18694305145381 +130.85334289819 2.18208727517944 +130.877867687494 2.18942511797622 +130.902392476797 2.18052244607562 +130.926917266101 2.1972415179662 +130.951442055404 2.17515087353706 +130.975966844708 2.19828954394771 +131.000491634011 2.21023090963025 +131.025016423315 2.16743216005918 +131.049541212618 2.15656828650308 +131.074066001922 2.16224372839931 +131.098590791225 2.19335044065424 +131.123115580529 2.20182369693627 +131.147640369833 2.18607385700871 +131.172165159136 2.17494240318199 +131.19668994844 2.18077311009228 +131.221214737743 2.19337117791168 +131.245739527047 2.19403569138886 +131.27026431635 2.19583280722401 +131.294789105654 2.17041262686766 +131.319313894957 2.17963401786233 +131.343838684261 2.2035738905803 +131.368363473564 2.20515330122388 +131.392888262868 2.19555154895468 +131.417413052171 2.21047496897165 +131.441937841475 2.19480440390019 +131.466462630779 2.19433966171951 +131.490987420082 2.20872419591976 +131.515512209386 2.22501592119997 +131.540036998689 2.20519057191118 +131.564561787993 2.19814366355941 +131.589086577296 2.19354799119008 +131.6136113666 2.1925312403988 +131.638136155903 2.16769702818298 +131.662660945207 2.18083316495189 +131.68718573451 2.17662167020163 +131.711710523814 2.17460644903364 +131.736235313118 2.17876695735756 +131.760760102421 2.19706135666913 +131.785284891725 2.20804949721195 +131.809809681028 2.19581800943918 +131.834334470332 2.18821923362212 +131.858859259635 2.19411597770343 +131.883384048939 2.17838778639786 +131.907908838242 2.1780898567179 +131.932433627546 2.20141441531585 +131.956958416849 2.20452156044867 +131.981483206153 2.19994753673063 +132.006007995456 2.21783417529829 +132.03053278476 2.19826939165904 +132.055057574064 2.19802510729104 +132.079582363367 2.19097083767762 +132.104107152671 2.18724638864785 +132.128631941974 2.18980213266276 +132.153156731278 2.20310108196044 +132.177681520581 2.21105967245563 +132.202206309885 2.20304815420272 +132.226731099188 2.21703928057451 +132.251255888492 2.22006002161925 +132.275780677795 2.21092204221545 +132.300305467099 2.23698113193983 +132.324830256402 2.23325473767903 +132.349355045706 2.21591300759657 +132.37387983501 2.2191129572893 +132.398404624313 2.2169350753896 +132.422929413617 2.18367727781835 +132.44745420292 2.18234913800511 +132.471978992224 2.20714208025217 +132.496503781527 2.22773960780056 +132.521028570831 2.22276893996965 +132.545553360134 2.23180669620504 +132.570078149438 2.23981967862204 +132.594602938741 2.23135131908054 +132.619127728045 2.19973294183722 +132.643652517349 2.21211796324682 +132.668177306652 2.20742559962779 +132.692702095956 2.20553197836795 +132.717226885259 2.21995890520736 +132.741751674563 2.22778796408479 +132.766276463866 2.21819552659803 +132.79080125317 2.19000520111436 +132.815326042473 2.18926907639599 +132.839850831777 2.2315905275561 +132.86437562108 2.22726729807186 +132.888900410384 2.23606435109212 +132.913425199687 2.23621414188787 +132.937949988991 2.22740101847279 +132.962474778295 2.19591311403688 +132.986999567598 2.20711057764919 +133.011524356902 2.21247281735302 +133.036049146205 2.22661545217448 +133.060573935509 2.22752690736631 +133.085098724812 2.2117267406223 +133.109623514116 2.22226570604957 +133.134148303419 2.21647039850414 +133.158673092723 2.21815271559715 +133.183197882026 2.23760192383381 +133.20772267133 2.22532471305037 +133.232247460634 2.21852383585767 +133.256772249937 2.22412549059354 +133.281297039241 2.20376894514686 +133.305821828544 2.19943127769257 +133.330346617848 2.2126413579874 +133.354871407151 2.22280328299632 +133.379396196455 2.21148986725332 +133.403920985758 2.22735234863703 +133.428445775062 2.22359075118491 +133.452970564365 2.20568949835791 +133.477495353669 2.22745609175002 +133.502020142972 2.22155471183257 +133.526544932276 2.23330037796265 +133.55106972158 2.2400296654635 +133.575594510883 2.24471652880246 +133.600119300187 2.22582097492063 +133.62464408949 2.24861011073273 +133.649168878794 2.24422754475592 +133.673693668097 2.25101643639944 +133.698218457401 2.2133557362792 +133.722743246704 2.23102495497547 +133.747268036008 2.22761789653014 +133.771792825311 2.23707378077883 +133.796317614615 2.23504373190791 +133.820842403919 2.24376038875336 +133.845367193222 2.24772941437627 +133.869891982526 2.2434420606101 +133.894416771829 2.2435842309648 +133.918941561133 2.24594150402898 +133.943466350436 2.25050895181907 +133.96799113974 2.25130917365107 +133.992515929043 2.24793309454764 +134.017040718347 2.24071681504997 +134.04156550765 2.23597504772638 +134.066090296954 2.23065996557604 +134.090615086257 2.21498571467019 +134.115139875561 2.23082863424523 +134.139664664865 2.23751680271573 +134.164189454168 2.25039087897322 +134.188714243472 2.24000270083475 +134.213239032775 2.23646787683038 +134.237763822079 2.24083118801455 +134.262288611382 2.25378323546746 +134.286813400686 2.25414518182333 +134.311338189989 2.24748463931007 +134.335862979293 2.25373192255362 +134.360387768596 2.23472818280806 +134.3849125579 2.23821838719559 +134.409437347203 2.23151812180474 +134.433962136507 2.24898704706069 +134.458486925811 2.25705957031002 +134.483011715114 2.2579110695692 +134.507536504418 2.24508188799319 +134.532061293721 2.24678604593367 +134.556586083025 2.23322436640695 +134.581110872328 2.24284260617655 +134.605635661632 2.26820990470847 +134.630160450935 2.25388000024355 +134.654685240239 2.25624281574375 +134.679210029542 2.22852345013875 +134.703734818846 2.2395376932946 +134.72825960815 2.25734129127424 +134.752784397453 2.26397728808036 +134.777309186757 2.25704023506565 +134.80183397606 2.23927496721235 +134.826358765364 2.24059415316884 +134.850883554667 2.25206757222301 +134.875408343971 2.2528711950992 +134.899933133274 2.23872194846438 +134.924457922578 2.23814864756859 +134.948982711881 2.24772388594765 +134.973507501185 2.24314306614421 +134.998032290488 2.25532639855313 +135.022557079792 2.2533954272639 +135.047081869096 2.25059691429722 +135.071606658399 2.26526549626194 +135.096131447703 2.23965230562695 +135.120656237006 2.26131596993995 +135.14518102631 2.27310795877922 +135.169705815613 2.25765650595479 +135.194230604917 2.24817364840156 +135.21875539422 2.26861363685032 +135.243280183524 2.27663746372055 +135.267804972827 2.26710803491772 +135.292329762131 2.26526918912996 +135.316854551435 2.25015069957335 +135.341379340738 2.25634244549946 +135.365904130042 2.27274285904053 +135.390428919345 2.25909099408375 +135.414953708649 2.25484657740407 +135.439478497952 2.25735509868238 +135.464003287256 2.28640275441732 +135.488528076559 2.26427760308204 +135.513052865863 2.26212416618149 +135.537577655166 2.25904806250083 +135.56210244447 2.26027696249126 +135.586627233773 2.27134494631124 +135.611152023077 2.28007000862314 +135.635676812381 2.29322482116158 +135.660201601684 2.28791609165921 +135.684726390988 2.27222881112805 +135.709251180291 2.27618080223488 +135.733775969595 2.28652994313174 +135.758300758898 2.28850092919883 +135.782825548202 2.26605974809808 +135.807350337505 2.26783315599929 +135.831875126809 2.27831225454499 +135.856399916112 2.27364727745808 +135.880924705416 2.27805926214707 +135.90544949472 2.27133762959934 +135.929974284023 2.27897448580479 +135.954499073327 2.2696031705575 +135.97902386263 2.28315463298248 +136.003548651934 2.28475257329831 +136.028073441237 2.27869527783911 +136.052598230541 2.27188048704581 +136.077123019844 2.29076722615109 +136.101647809148 2.2886543460934 +136.126172598451 2.29719050075705 +136.150697387755 2.29064041533046 +136.175222177058 2.27550893282 +136.199746966362 2.28629246369947 +136.224271755666 2.29627715504519 +136.248796544969 2.28664282773938 +136.273321334273 2.28577098558273 +136.297846123576 2.29436548519532 +136.32237091288 2.29242921926521 +136.346895702183 2.29850413755092 +136.371420491487 2.29987821570137 +136.39594528079 2.30222975624561 +136.420470070094 2.30423300024465 +136.444994859397 2.29574644089826 +136.469519648701 2.28738320942424 +136.494044438004 2.30773330229846 +136.518569227308 2.28495672249751 +136.543094016612 2.28012126992176 +136.567618805915 2.27146543481095 +136.592143595219 2.28067153062784 +136.616668384522 2.28195202652728 +136.641193173826 2.28862473576854 +136.665717963129 2.28864853692456 +136.690242752433 2.29074616484454 +136.714767541736 2.29907991536618 +136.73929233104 2.29747010243934 +136.763817120343 2.27976721670199 +136.788341909647 2.28371131033482 +136.812866698951 2.31556781719796 +136.837391488254 2.32891787740113 +136.861916277558 2.29632807982246 +136.886441066861 2.27586067934927 +136.910965856165 2.3044748132923 +136.935490645468 2.30720846137599 +136.960015434772 2.29658678138829 +136.984540224075 2.29345983242661 +137.009065013379 2.29617621214914 +137.033589802682 2.32014870919774 +137.058114591986 2.30478815837792 +137.082639381289 2.30256508878811 +137.107164170593 2.30280731657302 +137.131688959897 2.30819414423548 +137.1562137492 2.32199048453671 +137.180738538504 2.30873592869288 +137.205263327807 2.29625115153748 +137.229788117111 2.290573445708 +137.254312906414 2.30625766567203 +137.278837695718 2.29980062608791 +137.303362485021 2.30831314770334 +137.327887274325 2.29281696803769 +137.352412063628 2.29186391529016 +137.376936852932 2.29714960628447 +137.401461642236 2.28926829194676 +137.425986431539 2.31772661992292 +137.450511220843 2.32417643855372 +137.475036010146 2.3088284428469 +137.49956079945 2.31728064479614 +137.524085588753 2.32692824877712 +137.548610378057 2.31560666628572 +137.57313516736 2.31397811857867 +137.597659956664 2.33795479458807 +137.622184745967 2.32080819070926 +137.646709535271 2.32403502861116 +137.671234324574 2.33555421004066 +137.695759113878 2.32431356141295 +137.720283903182 2.32178440436231 +137.744808692485 2.32048481017845 +137.769333481789 2.33338362253276 +137.793858271092 2.31671001510788 +137.818383060396 2.30524102133819 +137.842907849699 2.30818871231071 +137.867432639003 2.32657849676273 +137.891957428306 2.32596804450172 +137.91648221761 2.31779064065517 +137.941007006913 2.31511448547739 +137.965531796217 2.34007571989486 +137.990056585521 2.36078951371786 +138.014581374824 2.34124809692909 +138.039106164128 2.30021207349302 +138.063630953431 2.34405107039938 +138.088155742735 2.3379274948903 +138.112680532038 2.30751018365226 +138.137205321342 2.30561710955972 +138.161730110645 2.31764978405233 +138.186254899949 2.30663026173444 +138.210779689252 2.32949830182912 +138.235304478556 2.32914575915262 +138.259829267859 2.32378861043787 +138.284354057163 2.34591923153508 +138.308878846467 2.33595261142435 +138.33340363577 2.31510732856059 +138.357928425074 2.31821779818042 +138.382453214377 2.3239934072257 +138.406978003681 2.32109179925062 +138.431502792984 2.32433902713622 +138.456027582288 2.32511034617902 +138.480552371591 2.35082498061283 +138.505077160895 2.34341790095947 +138.529601950198 2.3121378595541 +138.554126739502 2.33590385246108 +138.578651528805 2.34794899439151 +138.603176318109 2.31930028481884 +138.627701107413 2.32555068454443 +138.652225896716 2.34504358563608 +138.67675068602 2.35049289478479 +138.701275475323 2.3340322636423 +138.725800264627 2.34316949392778 +138.75032505393 2.33577943214598 +138.774849843234 2.32668540791566 +138.799374632537 2.32468973927795 +138.823899421841 2.31783570655933 +138.848424211144 2.34192940319773 +138.872949000448 2.35278828220235 +138.897473789752 2.34392760241487 +138.921998579055 2.33830241193725 +138.946523368359 2.32340391080494 +138.971048157662 2.32372567779133 +138.995572946966 2.32533684121793 +139.020097736269 2.33165242014241 +139.044622525573 2.34716096390077 +139.069147314876 2.34198049873903 +139.09367210418 2.35626815932087 +139.118196893483 2.34626549765833 +139.142721682787 2.35834122668549 +139.16724647209 2.34931117931162 +139.191771261394 2.37002219145407 +139.216296050698 2.30989042942989 +139.240820840001 2.30202375044605 +139.265345629305 2.33304049435171 +139.289870418608 2.32763670586007 +139.314395207912 2.3294124072661 +139.338919997215 2.32069084133655 +139.363444786519 2.31623499547457 +139.387969575822 2.33405387473416 +139.412494365126 2.31503848991649 +139.437019154429 2.3123206749237 +139.461543943733 2.32439749089 +139.486068733037 2.35612367803332 +139.51059352234 2.35467530664992 +139.535118311644 2.3319722087425 +139.559643100947 2.35778057912643 +139.584167890251 2.35072179225367 +139.608692679554 2.35169167439405 +139.633217468858 2.36351514737636 +139.657742258161 2.36810891470995 +139.682267047465 2.33848545110023 +139.706791836768 2.33034901652153 +139.731316626072 2.35192095875738 +139.755841415375 2.36073921730489 +139.780366204679 2.35135102550208 +139.804890993983 2.35087434346091 +139.829415783286 2.34040919230172 +139.85394057259 2.33321375205618 +139.878465361893 2.32625464661169 +139.902990151197 2.33794151097608 +139.9275149405 2.33593582032729 +139.952039729804 2.35405918609658 +139.976564519107 2.35634615449893 +140.001089308411 2.33819857704049 +140.025614097714 2.35681685015196 +140.050138887018 2.35460073106677 diff --git a/docs/source/examples/examples.rst b/docs/source/examples/examples.rst index 5a30d3b4..f0cd8cae 100644 --- a/docs/source/examples/examples.rst +++ b/docs/source/examples/examples.rst @@ -12,3 +12,4 @@ Landing page for diffpy.utils examples. resample_example parsers_example tools_example + mu_calc_examples diff --git a/docs/source/examples/mu_calc_examples.rst b/docs/source/examples/mu_calc_examples.rst new file mode 100644 index 00000000..2326a46b --- /dev/null +++ b/docs/source/examples/mu_calc_examples.rst @@ -0,0 +1,123 @@ +.. _mu calc Example: + +:tocdepth: -1 + +Linear Absorption Coefficient (μ) Examples +######################################### + +These examples will demonstrate how to calculate the Linear absorption +coefficient, μ, using different methods provided in ``diffpy.utils``. + + +.. admonition:: Methods for obtaining linear absorption coefficient + + Obtaining μ can be done in **two + different ways** using ``diffpy.utils``. + + 1. **Using a "z-scan" measurement**: Perform a z-scan measurement + on the sample and use ``diffpy.utils.tools.compute_mud`` to calculate + μ. + 2. **Using tabulated values**: Given composition, density, and X-ray energy, + use ``diffpy.utils.tools.compute_mu_using_xraydb`` to calculate μ from + tabulated values. + +Why is μ Important? +----------------------- + +The linear absorption coefficient, μ, quantifies how much X-ray +radiation is absorbed by a material per unit length. It is a critical +parameter in many scientific techniques. + +For example, when calculating pair distribution functions (PDFs) +using ``diffpy.pdfgetx``, +a key assumption is that the linear absorption is negligible. +This is frequently the case for high-energy X-rays. However, +this must be corrected for when using low energy X-rays, such +as those from a laboratory source. To correct for linear absorption, +the linear absorption coefficient, μ, must be known. + +.. admonition:: Correcting for linear absorption with ``diffpy.labpdfproc`` + + If your objective is to correct for linear absorption in PDF calculations, + please refer to our package ``diffpy.labpdfproc``. This package is specifically + designed to correct your laboratory X-ray PDF data for absorption effects. + More information can be found in the + `diffpy.labpdfproc documentation `_. + + +Calculating μ from a "z-scan" Measurement +----------------------------------------- + +.. note:: + + The data we will be using for this example can be found, + `here `_. + +A "z-scan" measurement is the measured transmission of your X-ray incident beam +as a function of sample position. This is obtained by moving the sample +perpendicular to the X-ray beam (z-direction) and recording the transmitted +intensity at each position. This measured data looks something like this, + +.. image:: ../img/zscan-plot.png + :alt: Example of a z-scan measurement. + :align: center + :width: 300px + +Using this z-scan data, you can calculate **μ·d**, where d is the inner diameter of +your sample capillary. To do this, simply pass your z-scan measurement to the ``compute_mud`` +function from the ``diffpy.utils.tools`` module. + + +First, import the ``compute_mud`` function, + +.. code-block:: python + + from diffpy.utils.tools import compute_mud + +Next, pass the filepath to the function, + +.. code-block:: python + + filepath = "CeO2_635um_zscan_200umSlit_chanClose_exported.xy" + capillary_diameter = 0.5 # mm + mud = compute_mud(filepath) + print(f"Calculated mu*d: {round(mud, 3)}") + print(f"Calculated mu: {round(mud / capillary_diameter, 3)} mm^-1") + +This will output the calculated value of μ·d, which is unitless, and μ in mm\ :sup:`-1`. + +.. code-block:: console + + Calculated mu*d: 3.489 + Calculated mu: 6.977 mm^-1 + +Calculating μ from Tabulated Values +----------------------------------- + +The function to calculate μ from tabulated values is located +in the ``diffpy.utils.tools`` module. So first, import the function, + +.. code-block:: python + + from diffpy.utils.tools import compute_mu_using_xraydb + +To calculate μ, you need to know the sample composition, and X-ray energy, and sample mass density (g/cm\ :sup:`3`). + +.. code-block:: python + + composition = "Fe2O3" + energy_keV = 17.45 # Mo K-alpha energy + sample_mass_density = 5.24 # g/cm^3 + +Now calculate μ using the ``compute_mu_using_xraydb`` function. + +.. code-block:: python + + mu = compute_mu_using_xraydb(composition, energy_keV, sample_mass_density) + print(f"Calculated mu: {round(mu, 3)} mm^-1") + +This will output the calculated linear absorption coefficient, μ, in mm\ :sup:`-1`. + +.. code-block:: console + + Calculated mu: 13.967 mm^-1 From 4c06b3d4a9fbc6c96ccab01e0ae0d80982c465ce Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 2 Feb 2026 13:41:45 -0500 Subject: [PATCH 670/696] news --- news/mu-docs.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/mu-docs.rst diff --git a/news/mu-docs.rst b/news/mu-docs.rst new file mode 100644 index 00000000..d11e372c --- /dev/null +++ b/news/mu-docs.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add documentation for calculation mu. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 3f9ae8eeeea1a9418b8b8168c98bf5ab3e428934 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 2 Feb 2026 13:46:31 -0500 Subject: [PATCH 671/696] remove unused data --- docs/source/examples/example_data/zro2_mo.xy | 5630 ------------------ 1 file changed, 5630 deletions(-) delete mode 100644 docs/source/examples/example_data/zro2_mo.xy diff --git a/docs/source/examples/example_data/zro2_mo.xy b/docs/source/examples/example_data/zro2_mo.xy deleted file mode 100644 index 0da57dad..00000000 --- a/docs/source/examples/example_data/zro2_mo.xy +++ /dev/null @@ -1,5630 +0,0 @@ -2.00009989738464 47.3495377734889 -2.02462468668818 45.9664359001249 -2.04914947599173 43.3738432672084 -2.07367426529527 41.0358802678819 -2.09819905459881 40.3356487755093 -2.12272384390235 38.5503478218157 -2.14724863320589 37.4797459533122 -2.17177342250943 35.9519681517721 -2.19629821181297 34.2766209034924 -2.22082300111651 32.9918986612882 -2.24534779042006 32.0109958682539 -2.2698725797236 30.6163199206359 -2.29439736902714 29.9074078725728 -2.31892215833068 28.6458337788768 -2.34344694763422 27.8761578409797 -2.36797173693776 26.7592596754599 -2.3924965262413 25.7378476225363 -2.41702131554484 24.9681716846392 -2.44154610484838 24.1030096341458 -2.46607089415193 23.6371531454186 -2.49059568345547 22.4045142373579 -2.51512047275901 21.7476855234382 -2.53964526206255 21.0156253268669 -2.56417005136609 20.208333647644 -2.58869484066963 19.6325234535029 -2.61321962997317 18.8657410341694 -2.63774441927671 18.9699077024562 -2.66226920858026 17.6678243488709 -2.6867939978838 17.1440974888732 -2.71131878718734 16.8113428540681 -2.73584357649088 16.1255789545132 -2.76036836579442 15.4398150549582 -2.78489315509796 14.7598381925304 -2.8094179444015 14.7858798596021 -2.83394273370504 14.2129631840245 -2.85846752300859 13.3969909491111 -2.88299231231213 12.948495571765 -2.90751710161567 12.251157597956 -2.93204189091921 12.2193288937572 -2.95656668022275 11.8287038876816 -2.98109146952629 11.0995372096739 -3.00561625882983 10.6221066466926 -3.03014104813337 10.4716436813894 -3.05466583743691 9.9537038585188 -3.07919062674046 9.34027792305195 -3.103715416044 8.65451402349702 -3.12824020534754 8.73842606183918 -3.15276499465108 8.13946771918994 -3.17728978395462 7.85879641852822 -3.20181457325816 7.60706030350172 -3.2263393625617 7.28298622438716 -3.25086415186524 7.30324085433182 -3.27538894116879 7.14988437046511 -3.29991373047233 7.08043992494056 -3.32443851977587 7.02835659079714 -3.34896330907941 6.93576399676441 -3.37348809838295 7.0601852949959 -3.39801288768649 6.46122695234665 -3.42253767699003 6.96759270096316 -3.44706246629357 6.64930565897564 -3.47158725559711 6.51909732361711 -3.49611204490066 7.0225695536701 -3.5206368342042 6.62905102903098 -3.54516162350774 6.5306713978712 -3.56968641281128 6.63483806615803 -3.59421120211482 6.11111120616038 -3.61873599141836 6.12268528041447 -3.6432607807219 6.16608805886732 -3.66778557002544 6.21527787444721 -3.69231035932899 6.51041676792654 -3.71683514863253 5.96643527798424 -3.74135993793607 6.21817139301073 -3.76588472723961 5.89409731389616 -3.79040951654315 5.89120379533264 -3.81493430584669 6.07928250196163 -3.83945909515023 6.05613435345345 -3.86398388445377 5.6336806431791 -3.88850867375731 6.01851861212765 -3.91303346306086 5.7465278671565 -3.9375582523644 5.78993064560934 -3.96208304166794 5.76099545997411 -3.98660783097148 5.54687508627342 -4.01113262027502 5.88252323964207 -4.03565740957856 5.9866899079289 -4.0601821988821 5.75810194141059 -4.08470698818564 5.71180564439422 -4.10923177748919 5.5063658263841 -4.13375656679273 5.6076389761074 -4.15828135609627 5.49768527069353 -4.18280614539981 5.46296304793125 -4.20733093470335 5.33275471257272 -4.23185572400689 5.56712971621807 -4.25638051331043 5.5063658263841 -4.28090530261397 5.34432878682681 -4.30543009191751 5.42245378804193 -4.32995488122106 5.28067137842931 -4.3544796705246 5.32407415688215 -4.37900445982814 5.18518526583305 -4.40352924913168 5.54108804914637 -4.42805403843522 5.19965285865067 -4.45257882773876 5.30671304550101 -4.4771036170423 5.40219915809727 -4.50162840634584 5.15625008019782 -4.52615319564939 5.13888896881669 -4.55067798495293 5.18807878439657 -4.57520277425647 5.3559028610809 -4.59972756356001 5.34722230539033 -4.62425235286355 5.23726859997646 -4.64877714216709 5.18518526583305 -4.67330193147063 5.2922454526834 -4.69782672077417 5.17071767301544 -4.72235151007771 5.23726859997646 -4.74687629938126 5.10127322749089 -4.7714010886848 4.9392361879336 -4.79592587798834 5.00000007776759 -4.82045066729188 5.01736118914872 -4.84497545659542 5.05787044903804 -4.86950024589896 4.90740748373485 -4.8940250352025 4.79745377832098 -4.91854982450604 4.97106489213236 -4.94307461380959 4.97106489213236 -4.96759940311313 4.80324081544803 -4.99212419241667 4.92476859511599 -5.01664898172021 4.85243063102792 -5.04117377102375 4.8379630382103 -5.06569856032729 4.91898155798894 -5.09022334963083 4.92766211367951 -5.11474813893437 4.91898155798894 -5.13927292823792 4.80324081544803 -5.16379771754146 5.07233804185566 -5.188322506845 5.07523156041918 -5.21284729614854 4.72800933279643 -5.23737208545208 4.8379630382103 -5.26189687475562 4.84375007533735 -5.28642166405916 4.56307877467563 -5.3109464533627 4.60937507169199 -5.33547124266624 4.79166674119394 -5.35999603196979 4.80613433401155 -5.38452082127333 4.80324081544803 -5.40904561057687 4.96238433644179 -5.43357039988041 4.74247692561405 -5.45809518918395 4.97974544782292 -5.48261997848749 4.65567136870836 -5.50714476779103 4.62094914594609 -5.53166955709457 4.91898155798894 -5.55619434639812 4.78877322263041 -5.58071913570166 4.58333340462029 -5.6052439250052 4.86111118671849 -5.62976871430874 4.76851859268575 -5.65429350361228 4.5601852561121 -5.67881829291582 4.76273155555871 -5.70334308221936 4.84085655677382 -5.7278678715229 4.58912044174733 -5.75239266082644 4.75405099986814 -5.77691745012999 4.82060192682916 -5.80144223943353 4.56307877467563 -5.82596702873707 4.54571766329449 -5.85049181804061 4.61516210881904 -5.87501660734415 4.52256951478631 -5.89954139664769 4.71354173997882 -5.92406618595123 4.58622692318381 -5.94859097525477 4.65856488727188 -5.97311576455832 4.72800933279643 -5.99764055386186 4.81770840826564 -6.0221653431654 4.55150470042153 -6.04669013246894 4.65567136870836 -6.07121492177248 4.55150470042153 -6.09573971107602 4.72222229566939 -6.12026450037956 4.55439821898506 -6.1447892896831 4.736689888487 -6.16931407898664 4.66724544296245 -6.19383886829019 4.71354173997882 -6.21836365759373 4.58043988605676 -6.24288844689727 4.79456025975746 -6.26741323620081 5.10127322749089 -6.29193802550435 4.86111118671849 -6.31646281480789 4.54861118185801 -6.34098760411143 4.8379630382103 -6.36551239341497 4.60358803456495 -6.39003718271852 4.71932877710586 -6.41456197202206 4.96817137356883 -6.4390867613256 4.8755787795361 -6.46361155062914 4.76273155555871 -6.48813633993268 4.73379636992348 -6.51266112923622 4.89004637235371 -6.53718591853976 4.75405099986814 -6.5617107078433 4.88715285379019 -6.58623549714684 4.6614584058354 -6.61076028645039 4.82060192682916 -6.63528507575393 4.88715285379019 -6.65980986505747 4.94212970649713 -6.68433465436101 4.71643525854234 -6.70885944366455 4.90162044660781 -6.73338423296809 4.85821766815496 -6.75790902227163 4.75694451843166 -6.78243381157517 4.94791674362417 -6.80695860087872 4.78298618550337 -6.83148339018226 4.86689822384553 -6.8560081794858 4.85821766815496 -6.88053296878934 4.5341435890404 -6.90505775809288 4.75115748130462 -6.92958254739642 4.63831025732722 -6.95410733669996 4.9392361879336 -6.9786321260035 4.73379636992348 -7.00315691530705 4.83506951964678 -7.02768170461059 4.76562507412223 -7.05220649391413 4.60648155312847 -7.07673128321767 4.86689822384553 -7.10125607252121 4.74826396274109 -7.12578086182475 4.84085655677382 -7.15030565112829 4.75115748130462 -7.17483044043183 5.02604174483929 -7.19935522973537 4.74826396274109 -7.22388001903892 4.75405099986814 -7.24840480834246 4.84953711246439 -7.272929597646 4.79166674119394 -7.29745438694954 4.64409729445427 -7.32197917625308 4.60937507169199 -7.34650396555662 4.99421304064054 -7.37102875486016 4.78587970406689 -7.3955535441637 4.83506951964678 -7.42007833346725 4.89872692804428 -7.44460312277079 4.95659729931474 -7.46912791207433 4.79456025975746 -7.49365270137787 4.75115748130462 -7.51817749068141 4.90740748373485 -7.54270227998495 4.77141211124928 -7.56722706928849 4.80902785257507 -7.59175185859203 4.82349544539269 -7.61627664789557 4.82060192682916 -7.64080143719912 4.90740748373485 -7.66532622650266 4.75405099986814 -7.6898510158062 4.90740748373485 -7.71437580510974 4.78298618550337 -7.73890059441328 5.01736118914872 -7.76342538371682 4.80613433401155 -7.78795017302036 4.83506951964678 -7.8124749623239 4.85243063102792 -7.83699975162745 4.69039359147063 -7.86152454093099 4.73958340705052 -7.88604933023453 4.71064822141529 -7.91057411953807 4.89293989091724 -7.93509890884161 4.97106489213236 -7.95962369814515 4.97106489213236 -7.98414848744869 4.65277785014484 -8.00867327675223 4.61226859025552 -8.03319806605577 4.78009266693984 -8.05772285535932 4.85821766815496 -8.08224764466286 4.89004637235371 -8.1067724339664 4.74247692561405 -8.13129722326994 4.77141211124928 -8.15582201257348 4.89004637235371 -8.18034680187702 4.71354173997882 -8.20487159118056 4.64988433158131 -8.2293963804841 4.92476859511599 -8.25392116978765 4.6990741471612 -8.27844595909119 5.04050933765691 -8.30297074839473 4.62962970163665 -8.32749553769827 4.82928248251973 -8.35202032700181 4.96817137356883 -8.37654511630535 4.83217600108326 -8.40106990560889 4.76562507412223 -8.42559469491243 4.90451396517133 -8.45011948421597 4.93634266937008 -8.47464427351952 5.12442137599907 -8.49916906282306 4.99421304064054 -8.5236938521266 4.97974544782292 -8.54821864143014 5.17939822870601 -8.57274343073368 5.00289359633111 -8.59726822003722 4.99710655920406 -8.62179300934076 5.0896991532368 -8.6463177986443 5.11284730174498 -8.67084258794785 5.3298611940092 -8.69536737725139 5.20254637721419 -8.71989216655493 5.18229174726953 -8.74441695585847 5.20833341434123 -8.76894174516201 5.26041674848465 -8.79346653446555 5.1909723029601 -8.81799132376909 5.57581027190864 -8.84251611307263 5.52083341920171 -8.86704090237617 5.28935193411988 -8.89156569167972 5.08680563467327 -8.91609048098326 5.00289359633111 -8.9406152702868 5.3935186024067 -8.96514005959034 5.26041674848465 -8.98966484889388 5.37037045389852 -9.01418963819742 5.62210656892501 -9.03871442750096 5.41956026947841 -9.0632392168045 5.43981489942307 -9.08776400610805 5.61342601323444 -9.11228879541159 5.46585656649477 -9.13681358471513 5.54687508627342 -9.16133837401867 5.61921305036149 -9.18586316332221 5.62500008748853 -9.21038795262575 5.62500008748853 -9.23491274192929 5.5700232347816 -9.25943753123283 5.68865749588604 -9.28396232053638 5.92303249953139 -9.30848710983992 5.67708342163195 -9.33301189914346 5.51215286351114 -9.357536688447 5.69444453301308 -9.38206147775054 5.8738426839515 -9.40658626705408 5.76678249710116 -9.43111105635762 5.62789360605206 -9.45563584566116 5.86516212826094 -9.4801606349647 6.10243065046981 -9.50468542426825 5.90567138815026 -9.52921021357179 5.98379638936537 -9.55373500287533 5.78703712704582 -9.57825979217887 5.79861120129991 -9.60278458148241 6.08217602052515 -9.62730937078595 6.12268528041447 -9.65183416008949 6.04745379776288 -9.67635894939303 5.83043990549866 -9.70088373869658 5.9114584252773 -9.72540852800012 6.11979176185095 -9.74993331730366 6.19212972593902 -9.7744581066072 6.32233806129755 -9.79898289591074 5.89409731389616 -9.82350768521428 6.47280102660075 -9.84803247451782 6.55960658350643 -9.87255726382136 6.51331028649007 -9.8970820531249 6.59432880626871 -9.92160684242845 6.97048621952669 -9.94613163173199 7.07465288781351 -9.97065642103553 7.32928252140353 -9.99518121033907 7.58969919212059 -10.0197059996426 7.76041678736844 -10.0442307889462 7.9947917910138 -10.0687555782497 7.8819445670364 -10.0932803675532 7.89641215985402 -10.1178051568568 7.52314826515956 -10.1423299461603 7.1238427033934 -10.1668547354639 7.21354177886261 -10.1913795247674 6.86631955123986 -10.2159043140709 7.09780103632169 -10.2404291033745 6.97627325665373 -10.264953892678 6.86342603267634 -10.2894786819816 7.08912048063113 -10.3140034712851 7.09201399919465 -10.3385282605886 6.89525473687509 -10.3630530498922 7.06307881355942 -10.3875778391957 6.75347232726247 -10.4121026284993 6.883680662621 -10.4366274178028 6.92418992251032 -10.4611522071064 6.97048621952669 -10.4856769964099 6.75636584582599 -10.5102017857134 6.76215288295304 -10.534726575017 6.73900473444485 -10.5592513643205 6.49594917510893 -10.5837761536241 6.60879639908632 -10.6083009429276 6.55381954637939 -10.6328257322311 6.21238435588368 -10.6573505215347 6.09953713190629 -10.6818753108382 6.40625009963972 -10.7064001001418 6.35127324693278 -10.7309248894453 6.04745379776288 -10.7554496787488 6.30497694991642 -10.7799744680524 6.25578713433653 -10.8044992573559 6.17187509599436 -10.8290240466595 6.27025472715414 -10.853548835963 5.86516212826094 -10.8780736252666 5.92303249953139 -10.9025984145701 6.14004639179561 -10.9271232038736 6.12268528041447 -10.9516479931772 6.20370380019312 -10.9761727824807 6.23842602295539 -11.0006975717843 5.93460657378548 -11.0252223610878 5.96354175942071 -11.0497471503913 5.92592601809492 -11.0742719396949 6.03587972350879 -11.0987967289984 6.18634268881198 -11.123321518302 5.7725695342282 -11.1478463076055 5.98379638936537 -11.172371096909 5.7465278671565 -11.1968958862126 6.07928250196163 -11.2214206755161 6.00983805643708 -11.2459454648197 6.27604176428119 -11.2704702541232 6.08217602052515 -11.2949950434268 5.76678249710116 -11.3195198327303 5.8854167582056 -11.3440446220338 5.94039361091253 -11.3685694113374 5.91724546240435 -11.3930942006409 6.10821768759686 -11.4176189899445 5.75231490428354 -11.442143779248 5.68576397732251 -11.4666685685515 6.0879630576522 -11.4911933578551 5.88252323964207 -11.5157181471586 5.89120379533264 -11.5402429364622 5.76388897853763 -11.5647677257657 6.08506953908868 -11.5892925150692 5.95775472229367 -11.6138173043728 6.33101861698812 -11.6383420936763 6.20949083732016 -11.6628668829799 5.95486120373014 -11.6873916722834 6.1892362073755 -11.711916461587 5.82465286837162 -11.7364412508905 6.16898157743084 -11.760966040194 6.2905093570988 -11.7854908294976 5.96064824085719 -11.8100156188011 6.44965287809256 -11.8345404081047 6.25578713433653 -11.8590651974082 6.34548620980574 -11.8835899867117 6.21817139301073 -11.9081147760153 6.24421306008244 -11.9326395653188 6.22395843013777 -11.9571643546224 6.4293982481479 -11.9816891439259 6.3281250984246 -12.0062139332294 6.57407417632405 -12.030738722533 6.72743066019076 -12.0552635118365 6.29629639422585 -12.0797883011401 6.64641214041212 -12.1043130904436 6.49305565654541 -12.1288378797472 6.97916677521725 -12.1533626690507 6.66666677035678 -12.1778874583542 6.9212964039468 -12.2024122476578 6.97627325665373 -12.2269370369613 7.05439825786885 -12.2514618262649 6.89814825543861 -12.2759866155684 7.18460659322738 -12.3005114048719 7.21932881598966 -12.3250361941755 7.45949085676206 -12.349560983479 6.97916677521725 -12.3740857727826 7.70833345322503 -12.3986105620861 7.47395844957967 -12.4231353513896 7.46817141245263 -12.4476601406932 7.72280104604264 -12.4721849299967 7.65335660051809 -12.4967097193003 8.01504642095846 -12.5212345086038 8.30150475874722 -12.5457592979074 8.17129642338869 -12.5702840872109 8.54456031808315 -12.5948088765144 8.82233810018135 -12.619333665818 8.57928254084542 -12.6438584551215 9.1319445864783 -12.6683832444251 8.94097236128579 -12.6929080337286 9.36631959012365 -12.7174328230321 9.36631959012365 -12.7419576123357 9.80902793034266 -12.7664824016392 9.90162052437539 -12.7910071909428 9.86111126448607 -12.8155319802463 10.3761575687931 -12.8400567695498 10.7667825748687 -12.8645815588534 10.9809029485694 -12.8891063481569 11.5451390684564 -12.9136311374605 11.8026622206099 -12.938155926764 12.5896992698882 -12.9626807160676 12.8848381633675 -12.9872055053711 13.1857640939739 -13.0117302946746 13.6805557683363 -13.0362550839782 14.1203705899918 -13.0607798732817 14.7598381925304 -13.0853046625853 15.6278937615872 -13.1098294518888 16.3917826623573 -13.1343542411923 16.7476854456706 -13.1588790304959 17.994791946549 -13.1834038197994 18.8657410341694 -13.207928609103 20.0491901266502 -13.2324533984065 21.3599540359261 -13.25697818771 22.5144679427717 -13.2815029770136 23.8888892604451 -13.3060277663171 26.0300929974521 -13.3305525556207 26.8142365281668 -13.3550773449242 29.9855328737879 -13.3796021342278 30.9114588141152 -13.4041269235313 33.2291671834971 -13.4286517128348 35.8998848176287 -13.4531765021384 38.4722228206006 -13.4777012914419 41.8518525027953 -13.5022260807455 43.642940493616 -13.526750870049 46.9820609159215 -13.5512756593525 49.9594915177865 -13.5758004486561 53.0787045292642 -13.6003252379596 56.2297462449407 -13.6248500272632 59.806135189455 -13.6493748165667 63.2060195015944 -13.6738996058702 65.616320465009 -13.6984243951738 67.957176982899 -13.7229491844773 70.0897001642154 -13.7474739737809 71.935765007743 -13.7719987630844 72.8038205767999 -13.796523552388 73.3680566966869 -13.8210483416915 72.3755798293985 -13.845573130995 72.0833344544827 -13.8700979202986 70.66840387692 -13.8946227096021 67.0775473395881 -13.9191474989057 64.7222232288804 -13.9436722882092 61.0792833574051 -13.9681970775127 58.3246536849313 -13.9927218668163 54.0190980624092 -14.0172466561198 50.9809035707101 -14.0417714454234 47.4855331459744 -14.0662962347269 44.3460655045521 -14.0908210240304 41.0214126750643 -14.115345813334 38.2986117067892 -14.1398706026375 36.0185190787332 -14.1643953919411 33.6979171907878 -14.1889201812446 31.3310190058261 -14.2134449705482 29.3315976784317 -14.2379697598517 27.1701393114801 -14.2624945491552 25.7002318812105 -14.2870193384588 24.435764268951 -14.3115441277623 23.5358799956953 -14.3360689170659 21.9444447857577 -14.3605937063694 20.7407410633322 -14.3851184956729 19.704861417591 -14.4096432849765 18.7847225143907 -14.43416807428 17.491319716496 -14.4586928635836 16.793981742687 -14.4832176528871 16.4467595150642 -14.5077424421906 15.5758104274438 -14.5322672314942 14.7019678212599 -14.5567920207977 13.8570604007112 -14.5813168101013 13.8049770665678 -14.6058415994048 13.2291668724267 -14.6303663887084 12.6822918639209 -14.6548911780119 12.5347224171812 -14.6794159673154 12.1093751883434 -14.703940756619 11.3483798061368 -14.7284655459225 11.3339122133192 -14.7529903352261 11.1053242468009 -14.7775151245296 10.5758103496762 -14.8020399138331 10.425347384373 -14.8265647031367 9.90740756150244 -14.8510894924402 9.93923626570119 -14.8756142817438 9.69618070636527 -14.9001390710473 9.61805570515015 -14.9246638603508 9.57754644526083 -14.9491886496544 9.3721066272507 -14.9737134389579 9.42418996139411 -14.9982382282615 9.18402792062171 -15.022763017565 9.20717606912989 -15.0472878068686 9.44155107277525 -15.0718125961721 9.07696773377136 -15.0963373854756 9.49074088835514 -15.1208621747792 9.4097223685765 -15.1453869640827 9.11458347509716 -15.1699117533863 9.19849551343932 -15.1944365426898 9.72222237343697 -15.2189613319933 9.55150477818912 -15.2434861212969 9.49074088835514 -15.2680109106004 9.788773300398 -15.292535699904 9.46759273984696 -15.3170604892075 10.1331020094572 -15.341585278511 9.94502330282823 -15.3661100678146 9.9276621914471 -15.3906348571181 10.6192131281291 -15.4151596464217 10.4832177556435 -15.4396844357252 10.923032577299 -15.4642092250288 11.2962964719934 -15.4887340143323 11.0995372096739 -15.5132588036358 11.9994214829295 -15.5377835929394 12.0515048170729 -15.5623083822429 12.6851853824844 -15.5868331715465 13.0497687214883 -15.61135796085 12.9774307574002 -15.6358827501535 13.3969909491111 -15.6604075394571 14.2013891097704 -15.6849323287606 14.6643520799341 -15.7094571180642 14.65277800568 -15.7339819073677 15.471643759157 -15.7585066966712 15.8709493209232 -15.7830314859748 15.787037282581 -15.8075562752783 16.0503474718616 -15.8320810645819 16.2789354383799 -15.8566058538854 16.4496530336277 -15.881130643189 16.8200234097587 -15.9056554324925 16.7679400756153 -15.930180221796 16.4293984036831 -15.9547050110996 16.7447919271071 -15.9792298004031 16.5653937761686 -16.0037545897067 16.2528937713082 -16.0282793790102 15.5700233903168 -16.0528041683137 15.5873845016979 -16.0773289576173 14.9363428249053 -16.1018537469208 14.6643520799341 -16.1263785362244 14.1608798498811 -16.1509033255279 13.6921298425904 -16.1754281148314 13.1655094640292 -16.199952904135 12.5231483429271 -16.2244776934385 11.7621529607206 -16.2490024827421 11.1255788767456 -16.2735272720456 10.4398149771907 -16.2980520613492 10.0202547854798 -16.3225768506527 9.38657422006832 -16.3471016399562 9.01331032537386 -16.3716264292598 8.44328716835984 -16.3961512185633 7.84143530714708 -16.4206760078669 7.94849549399743 -16.4452007971704 7.65914363764514 -16.4697255864739 6.9212964039468 -16.4942503757775 6.59722232483223 -16.518775165081 6.92708344107384 -16.5432999543846 6.60879639908632 -16.5678247436881 6.44097232240199 -16.5923495329916 5.85069453544332 -16.6168743222952 5.5700232347816 -16.6413991115987 5.56134267909103 -16.6659239009023 5.5960649018533 -16.6904486902058 5.27777785986578 -16.7149734795094 5.23437508141294 -16.7394982688129 5.35879637964443 -16.7640230581164 5.37905100958909 -16.78854784742 4.86689822384553 -16.8130726367235 5.02604174483929 -16.8375974260271 4.79456025975746 -16.8621222153306 4.81481488970212 -16.8866470046341 4.61805562738256 -16.9111717939377 4.90740748373485 -16.9356965832412 4.75115748130462 -16.9602213725448 4.35474543810198 -16.9847461618483 4.43865747644414 -17.0092709511518 4.69328711003416 -17.0337957404554 4.31423617821266 -17.0583205297589 4.37500006804664 -17.0828453190625 4.3200232153397 -17.107370108366 4.43576395788062 -17.1318948976696 4.16956025003651 -17.1564196869731 4.14641210152833 -17.1809444762766 4.1811343242906 -17.2054692655802 3.98148154340752 -17.2299940548837 4.21296302848935 -17.2545188441873 4.22743062130697 -17.2790436334908 4.12037043445662 -17.3035684227943 3.978588024844 -17.3280932120979 4.36631951235607 -17.3526180014014 4.28530099257743 -17.377142790705 3.72395839125398 -17.4016675800085 3.97280098771695 -17.426192369312 4.16087969434594 -17.4507171586156 4.0162037661698 -17.4752419479191 3.88599543081127 -17.4997667372227 3.81365746672319 -17.5242915265262 3.94675932064525 -17.5488163158298 4.08564821169435 -17.5733411051333 3.93518524639116 -17.5978658944368 3.61689820440364 -17.6223906837404 3.96122691346286 -17.6469154730439 3.77604172539739 -17.6714402623475 3.75000005832569 -17.695965051651 3.89756950506536 -17.7204898409545 3.79629635534206 -17.7450146302581 3.75868061401626 -17.7695394195616 3.77314820683387 -17.7940642088652 3.81365746672319 -17.8185889981687 3.94386580208172 -17.8431137874722 3.71527783556341 -17.8676385767758 3.6255787600942 -17.8921633660793 3.62847227865773 -17.9166881553829 3.84548617092195 -17.9412129446864 3.78182876252444 -17.96573773399 3.75289357688921 -17.9902625232935 3.64004635291182 -18.014787312597 3.88888894937479 -18.0393121019006 3.63425931578477 -18.0638368912041 3.64004635291182 -18.0883616805077 3.72685190981751 -18.1128864698112 3.63425931578477 -18.1374112591147 3.90625006075593 -18.1619360484183 3.63136579722125 -18.1864608377218 3.78182876252444 -18.2109856270254 3.93518524639116 -18.2355104163289 3.70949079843637 -18.2600352056324 3.8396991337949 -18.284559994936 3.64004635291182 -18.3090847842395 3.76736116970683 -18.3336095735431 3.77604172539739 -18.3581343628466 3.98148154340752 -18.3826591521502 3.85416672661251 -18.4071839414537 3.76736116970683 -18.4317087307572 3.76157413257978 -18.4562335200608 3.75868061401626 -18.4807583093643 3.86863431943013 -18.5052830986679 3.87442135655717 -18.5298078879714 4.01041672904275 -18.5543326772749 3.91203709788297 -18.5788574665785 4.03645839611446 -18.603382255882 3.97569450628048 -18.6279070451856 4.10300932307548 -18.6524318344891 4.02199080329684 -18.6769566237926 3.88310191224774 -18.7014814130962 4.0162037661698 -18.7260062023997 4.07696765600378 -18.7505309917033 3.76736116970683 -18.7750557810068 3.99884265478866 -18.7995805703104 4.05960654462264 -18.8241053596139 3.94386580208172 -18.8486301489174 4.03356487755093 -18.873154938221 4.08275469313082 -18.8976797275245 4.17534728716356 -18.9222045168281 4.30844914108561 -18.9467293061316 4.25347228837868 -18.9712540954351 4.421296365063 -18.9957788847387 4.37210654948311 -19.0203036740422 4.33159728959379 -19.0448284633458 4.32581025246675 -19.0693532526493 4.28530099257743 -19.0938780419528 4.69039359147063 -19.1184028312564 4.41261580937244 -19.1429276205599 4.48784729202403 -19.1674524098635 4.64409729445427 -19.191977199167 4.79456025975746 -19.2165019884706 4.71354173997882 -19.2410267777741 4.46469914351585 -19.2655515670776 4.71354173997882 -19.2900763563812 4.86400470528201 -19.3146011456847 4.80902785257507 -19.3391259349883 4.9508102621877 -19.3636507242918 4.83217600108326 -19.3881755135953 4.73090285135995 -19.4127003028989 5.02604174483929 -19.4372250922024 5.16493063588839 -19.461749881506 5.02604174483929 -19.4862746708095 4.92476859511599 -19.510799460113 4.97395841069588 -19.5353242494166 4.96238433644179 -19.5598490387201 4.82638896395621 -19.5843738280237 5.03472230052986 -19.6088986173272 4.86979174240905 -19.6334234066308 4.90451396517133 -19.6579481959343 4.98553248494997 -19.6824729852378 5.10127322749089 -19.7069977745414 4.69328711003416 -19.7315225638449 4.90451396517133 -19.7560473531485 4.70486118428825 -19.780572142452 4.82349544539269 -19.8050969317555 4.89293989091724 -19.8296217210591 4.41261580937244 -19.8541465103626 4.71354173997882 -19.8786712996662 4.43576395788062 -19.9031960889697 4.63831025732722 -19.9277208782732 4.62673618307313 -19.9522456675768 4.67013896152597 -19.9767704568803 4.40972229080891 -20.0012952461839 4.57754636749324 -20.0258200354874 4.61805562738256 -20.050344824791 4.70486118428825 -20.0748696140945 4.45601858782528 -20.099394403398 4.84664359390087 -20.1239191927016 4.44444451357119 -20.1484439820051 4.69328711003416 -20.1729687713087 4.75115748130462 -20.1974935606122 4.77141211124928 -20.2220183499157 4.5717593303662 -20.2465431392193 4.84375007533735 -20.2710679285228 4.85821766815496 -20.2955927178264 4.86689822384553 -20.3201175071299 4.4965278477146 -20.3446422964334 4.6354167387637 -20.369167085737 4.90451396517133 -20.3936918750405 4.94212970649713 -20.4182166643441 4.85243063102792 -20.4427414536476 4.74826396274109 -20.4672662429512 4.5717593303662 -20.4917910322547 4.82349544539269 -20.5163158215582 4.73379636992348 -20.5408406108618 4.58622692318381 -20.5653654001653 4.97106489213236 -20.5898901894689 4.70196766572473 -20.6144149787724 4.73090285135995 -20.6389397680759 4.84375007533735 -20.6634645573795 4.71643525854234 -20.687989346683 4.85243063102792 -20.7125141359866 4.76562507412223 -20.7370389252901 4.6990741471612 -20.7615637145936 4.89583340948076 -20.7860885038972 4.91030100229838 -20.8106132932007 4.77141211124928 -20.8351380825043 4.86689822384553 -20.8596628718078 5.1157408203085 -20.8841876611114 5.00000007776759 -20.9087124504149 5.19386582152362 -20.9332372397184 5.00289359633111 -20.957762029022 5.17650471014248 -20.9822868183255 5.28935193411988 -21.0068116076291 5.10127322749089 -21.0313363969326 5.3935186024067 -21.0558611862361 5.5324074934558 -21.0803859755397 5.45428249224068 -21.1049107648432 5.4311343437325 -21.1294355541468 5.45717601080421 -21.1539603434503 5.49189823356648 -21.1784851327538 5.63078712461558 -21.2030099220574 5.61921305036149 -21.2275347113609 5.65393527312376 -21.2520595006645 5.93171305522196 -21.276584289968 5.96932879654776 -21.3011090792716 6.03298620494526 -21.3256338685751 6.09375009477924 -21.3501586578786 6.04745379776288 -21.3746834471822 6.18344917024846 -21.3992082364857 6.19791676306607 -21.4237330257893 6.51041676792654 -21.4482578150928 6.35416676549631 -21.4727826043963 6.41203713676676 -21.4973073936999 6.70428251168258 -21.5218321830034 6.96180566383612 -21.546356972307 7.04282418361476 -21.5708817616105 7.27141215013307 -21.595406550914 7.29166678007773 -21.6199313402176 7.34953715134819 -21.6444561295211 7.5405093765407 -21.6689809188247 7.71122697178855 -21.6935057081282 7.90219919698106 -21.7180304974318 8.17418994195222 -21.7425552867353 8.50405105819383 -21.7670800760388 8.94675939841283 -21.7916048653424 8.85995384150714 -21.8161296546459 9.43865755421172 -21.8406544439495 9.96238441420937 -21.865179233253 10.109953860949 -21.8897040225565 10.0810186753138 -21.9142288118601 10.4050927544284 -21.9387536011636 11.056134431221 -21.9632783904672 11.304977027684 -21.9878031797707 11.8518520361898 -22.0123279690742 12.8096066807159 -22.0368527583778 13.1568289083387 -22.0613775476813 13.4924770617073 -22.0859023369849 14.3894678163994 -22.1104271262884 14.8668983793807 -22.134951915592 16.2123845114189 -22.1594767048955 17.3466437883198 -22.184001494199 17.6417826817992 -22.2085262835026 18.2523150987025 -22.2330510728061 19.9074077170376 -22.2575758621097 21.2586808862028 -22.2821006514132 22.2251160864195 -22.3066254407167 23.2118059165808 -22.3311502300203 25.2112272439751 -22.3556750193238 26.2094911483905 -22.3801998086274 27.2280096827506 -22.4047245979309 29.0364587849524 -22.4292493872344 30.8622689985353 -22.453774176538 31.5306717867091 -22.4782989658415 33.0005792169788 -22.5028237551451 34.3634264603981 -22.5273485444486 36.1111116727659 -22.5518733337522 36.6695607555258 -22.5763981230557 36.7505792753045 -22.6009229123592 37.9079867007136 -22.6254477016628 38.5416672661251 -22.6499724909663 38.1076394815967 -22.6744972802699 38.2783570768446 -22.6990220695734 38.275463558281 -22.7235468588769 37.4971070646934 -22.7480716481805 36.4004635291182 -22.772596437484 36.4525468632616 -22.7971212267876 35.2546301779631 -22.8216460160911 33.8425931189639 -22.8461708053947 32.4508106899094 -22.8706955946982 30.9114588141152 -22.8952203840017 29.6498847204192 -22.9197451733053 27.9571763607583 -22.9442699626088 26.7071763413164 -22.9687947519124 24.8292827935901 -22.9933195412159 24.1435188940351 -23.0178443305194 22.7054401679643 -23.042369119823 21.4380790371412 -23.0668939091265 20.0520836452138 -23.0914186984301 19.2650465959355 -23.1159434877336 17.7719910171577 -23.1404682770371 17.3987271224632 -23.1649930663407 16.4062502551749 -23.1895178556442 15.16203727286 -23.2140426449478 14.5167826331944 -23.2385674342513 14.0335650330861 -23.2630922235549 13.2812502065701 -23.2876170128584 12.5115742686731 -23.3121418021619 12.6562501968492 -23.3366665914655 11.7592594421571 -23.361191380769 11.7968751834829 -23.3857161700726 10.7465279449241 -23.4102409593761 10.541088126914 -23.4347657486796 10.353009420285 -23.4592905379832 10.0752316381868 -23.4838153272867 9.74826404050868 -23.5083401165903 9.43865755421172 -23.5328649058938 9.46180570271991 -23.5573896951973 8.92071773134113 -23.5819144845009 8.62557883786179 -23.6064392738044 8.48090290968564 -23.630964063108 8.57060198515485 -23.6554888524115 7.96006956825152 -23.6800136417151 7.99189827245027 -23.7045384310186 8.16840290482517 -23.7290632203221 7.47106493101615 -23.7535880096257 7.39872696692808 -23.7781127989292 7.58391215499354 -23.8026375882328 7.27719918726012 -23.8271623775363 7.25694455731545 -23.8516871668398 6.98206029378078 -23.8762119561434 6.88946769974805 -23.9007367454469 6.65798621466621 -23.9252615347505 7.11516214770283 -23.949786324054 6.65509269610269 -23.9743111133575 6.56250010206996 -23.9988359026611 6.1516204660497 -24.0233606919646 6.31944454273403 -24.0478854812682 6.52777787930768 -24.0724102705717 6.46122695234665 -24.0969350598753 6.20370380019312 -24.1214598491788 5.80729175699048 -24.1459846384823 6.35706028405983 -24.1705094277859 6.16608805886732 -24.1950342170894 5.87673620251503 -24.219559006393 6.17766213312141 -24.2440837956965 6.03009268638174 -24.268608585 6.09085657621572 -24.2931333743036 5.68287045875899 -24.3176581636071 5.78414360848229 -24.3421829529107 5.75810194141059 -24.3667077422142 5.6076389761074 -24.3912325315177 5.61342601323444 -24.4157573208213 5.44270841798659 -24.4402821101248 5.7465278671565 -24.4648068994284 5.5700232347816 -24.4893316887319 5.82175934980809 -24.5138564780355 5.94039361091253 -24.538381267339 5.66261582881433 -24.5629060566425 5.64814823599672 -24.5874308459461 5.67708342163195 -24.6119556352496 5.34722230539033 -24.6364804245532 5.40509267666079 -24.6610052138567 5.38483804671613 -24.6855300031602 5.46006952936773 -24.7100547924638 5.45717601080421 -24.7345795817673 5.62789360605206 -24.7591043710709 5.5700232347816 -24.7836291603744 5.89988435102321 -24.8081539496779 5.35011582395386 -24.8326787389815 5.31539360119158 -24.857203528285 5.74074083002945 -24.8817283175886 5.51215286351114 -24.9062531068921 5.44560193655011 -24.9307778961957 5.69444453301308 -24.9553026854992 5.61631953179796 -24.9798274748027 5.55266212340046 -25.0043522641063 5.49479175213 -25.0288770534098 5.93171305522196 -25.0534018427134 5.78703712704582 -25.0779266320169 5.39930563953375 -25.1024514213204 5.65972231025081 -25.126976210624 5.79282416417286 -25.1515009999275 5.84201397975275 -25.1760257892311 5.95196768516662 -25.2005505785346 5.55844916052751 -25.2250753678381 5.7725695342282 -25.2496001571417 5.85358805400684 -25.2741249464452 5.78414360848229 -25.2986497357488 6.07638898339811 -25.3231745250523 6.12847231754152 -25.3476993143559 6.14583342892266 -25.3722241036594 6.0503473163264 -25.3967488929629 6.56539362063348 -25.4212736822665 6.47280102660075 -25.44579847157 6.48437510085484 -25.4703232608736 6.55671306494291 -25.4948480501771 6.43229176671142 -25.5193728394806 6.60011584339575 -25.5438976287842 6.73321769731781 -25.5684224180877 6.95312510814555 -25.5929472073913 6.88657418118452 -25.6174719966948 7.34085659565762 -25.6419967859983 7.45370381963501 -25.6665215753019 7.48553252383376 -25.6910463646054 7.54918993223127 -25.715571153909 7.77777789874958 -25.7400959432125 8.17129642338869 -25.7646207325161 8.16550938626165 -25.7891455218196 8.39120383421643 -25.8136703111231 8.94675939841283 -25.8381951004267 9.10879643797012 -25.8627198897302 9.07986125233489 -25.8872446790338 9.35474551586956 -25.9117694683373 9.89293996868482 -25.9362942576408 10.4687501628259 -25.9608190469444 10.7291668335429 -25.9853438362479 11.0156251713317 -26.0098686255515 11.3512733247004 -26.034393414855 11.614583513981 -26.0589182041585 12.4479168602755 -26.0834429934621 12.6244214926504 -26.1079677827656 13.4317131718733 -26.1324925720692 14.0538196630308 -26.1570173613727 14.4791668918686 -26.1815421506763 15.1909724584953 -26.2060669399798 15.6510419100954 -26.2305917292833 16.7476854456706 -26.2551165185869 17.3813660110821 -26.2796413078904 17.7199076830143 -26.304166097194 18.504051213729 -26.3286908864975 19.3865743756035 -26.353215675801 20.2575234632239 -26.3777404651046 20.5526623567032 -26.4022652544081 21.342592924545 -26.4267900437117 22.0254633055364 -26.4513148330152 22.416088311612 -26.4758396223187 22.9137735045379 -26.5003644116223 23.5127318471872 -26.5248892009258 23.8946762975722 -26.5494139902294 24.4820605659673 -26.5739387795329 24.629630012707 -26.5984635688365 25.199653169721 -26.62298835814 25.5497689159073 -26.6475131474435 25.7667828081715 -26.6720379367471 25.2025466882845 -26.6965627260506 26.0532411459603 -26.7210875153542 25.6539355841941 -26.7456123046577 25.4369216919299 -26.7701370939612 24.8263892750266 -26.7946618832648 25.0231485373461 -26.8191866725683 23.9641207430967 -26.8437114618719 23.3420142519393 -26.8682362511754 22.8616901703945 -26.8927610404789 22.0949077510609 -26.9172858297825 20.5179401339409 -26.941810619086 19.9855327182527 -26.9663354083896 19.0104169623455 -26.9908601976931 18.625578993397 -27.0153849869967 17.9918984279854 -27.0399097763002 16.3483798839044 -27.0644345656037 16.163194695839 -27.0889593549073 15.2083335698764 -27.1134841442108 14.6672455984976 -27.1380089335144 13.9785881803792 -27.1625337228179 13.0642363143059 -27.1870585121214 12.7922455693348 -27.211583301425 12.0920140769622 -27.2361080907285 11.8836807403886 -27.2606328800321 11.5046298085671 -27.2851576693356 11.3368057318828 -27.3096824586391 11.0300927641493 -27.3342072479427 10.7089122035983 -27.3587320372462 10.5671297939857 -27.3832568265498 10.0896992310044 -27.4077816158533 10.4832177556435 -27.4323064051569 10.3356483089038 -27.4568311944604 9.94791682139176 -27.4813559837639 9.84085663454141 -27.5058807730675 9.83796311597789 -27.530405562371 9.86689830161312 -27.5549303516746 9.87557885730368 -27.5794551409781 9.69907422492879 -27.6039799302816 9.96817145133642 -27.6285047195852 9.59780107520549 -27.6530295088887 9.79456033752505 -27.6775542981923 9.49363440691866 -27.7020790874958 9.40393533144945 -27.7266038767993 9.77430570758038 -27.7511286661029 9.54282422249855 -27.7756534554064 9.83796311597789 -27.80017824471 9.7251158920005 -27.8247030340135 9.67881959498413 -27.8492278233171 9.40682885001297 -27.8737526126206 9.31712977454377 -27.8982774019241 9.21006958769342 -27.9228021912277 9.02777791819147 -27.9473269805312 9.09143532658898 -27.9718517698348 8.66319457918759 -27.9963765591383 8.59085661509951 -28.0209013484418 8.06712975510187 -28.0454261377454 8.41724550128814 -28.0699509270489 8.01215290239493 -28.0944757163525 7.68518530471684 -28.119000505656 7.73726863886026 -28.1435252949595 7.41319455974569 -28.1680500842631 7.39004641123751 -28.1925748735666 7.08333344350408 -28.2170996628702 6.82870380991406 -28.2416244521737 6.89236121831157 -28.2661492414773 6.55381954637939 -28.2906740307808 6.2905093570988 -28.3151988200843 6.43518528527495 -28.3397236093879 6.19212972593902 -28.3642483986914 6.03877324207231 -28.388773187995 5.88252323964207 -28.4132979772985 5.88831027676912 -28.437822766602 5.68576397732251 -28.4623475559056 5.69733805157661 -28.4868723452091 5.63946768030615 -28.5113971345127 5.4311343437325 -28.5359219238162 5.28067137842931 -28.5604467131197 5.1157408203085 -28.5849715024233 5.33564823113624 -28.6094962917268 5.18518526583305 -28.6340210810304 4.95949081787827 -28.6585458703339 4.85243063102792 -28.6830706596375 4.82060192682916 -28.707595448941 4.93634266937008 -28.7321202382445 4.86400470528201 -28.7566450275481 4.67881951721654 -28.7811698168516 4.82928248251973 -28.8056946061552 4.72800933279643 -28.8302193954587 4.48495377346051 -28.8547441847622 4.59490747887438 -28.8792689740658 4.70775470285177 -28.9037937633693 4.41840284649948 -28.9283185526729 4.48206025489698 -28.9528433419764 4.33738432672084 -28.9773681312799 4.52835655191335 -29.0018929205835 4.49074081058755 -29.026417709887 4.36921303091959 -29.0509424991906 4.21006950992583 -29.0754672884941 4.1811343242906 -29.0999920777977 4.37789358661016 -29.1245168671012 4.18692136141765 -29.1490416564047 4.05671302605912 -29.1735664457083 4.34606488241141 -29.1980912350118 4.27662043688686 -29.2226160243154 4.06539358174969 -29.2471408136189 3.96701395058991 -29.2716656029224 3.96990746915343 -29.296190392226 4.03645839611446 -29.3207151815295 4.21585654705288 -29.3452399708331 4.12905099014719 -29.3697647601366 4.0046296919157 -29.3942895494401 4.12615747158367 -29.4188143387437 4.10590284163901 -29.4433391280472 3.89467598650183 -29.4678639173508 3.79629635534206 -29.4923887066543 4.18692136141765 -29.5169134959579 4.05381950749559 -29.5414382852614 3.95543987633582 -29.5659630745649 3.88888894937479 -29.5904878638685 3.84837968948547 -29.615012653172 3.92650469070059 -29.6395374424756 3.82523154097728 -29.6640622317791 4.12905099014719 -29.6885870210826 3.96701395058991 -29.7131118103862 3.75578709545273 -29.7376365996897 3.72106487269046 -29.7621613889933 3.88310191224774 -29.7866861782968 3.70949079843637 -29.8112109676003 3.77025468827035 -29.8357357569039 3.59664357445897 -29.8602605462074 3.80497691103262 -29.884785335511 3.87442135655717 -29.9093101248145 3.64872690860239 -29.9338349141181 3.64004635291182 -29.9583597034216 3.79050931821501 -29.9828844927251 3.60821764871307 -30.0074092820287 3.88310191224774 -30.0319340713322 3.75000005832569 -30.0564588606358 3.79340283677853 -30.0809836499393 3.64583339003886 -30.1055084392428 3.81944450385024 -30.1300332285464 3.93518524639116 -30.1545580178499 3.69791672418228 -30.1790828071535 3.59664357445897 -30.203607596457 3.86284728230308 -30.2281323857605 3.76157413257978 -30.2526571750641 3.72685190981751 -30.2771819643676 3.62847227865773 -30.3017067536712 3.73553246550807 -30.3262315429747 3.58217598164136 -30.3507563322783 3.57638894451431 -30.3752811215818 3.4866898690451 -30.3998059108853 3.60532413014954 -30.4243307001889 3.58796301876841 -30.4488554894924 3.48090283191806 -30.473380278796 3.59375005589545 -30.4979050680995 3.52719912893442 -30.522429857403 3.52719912893442 -30.5469546467066 3.50405098042624 -30.5714794360101 3.58506950020488 -30.5960042253137 3.42013894208408 -30.6205290146172 3.51562505468033 -30.6450538039207 3.54745375887909 -30.6695785932243 3.3217593109243 -30.6941033825278 3.39699079357589 -30.7186281718314 3.82233802241376 -30.7431529611349 3.64872690860239 -30.7676777504385 3.54456024031556 -30.792202539742 3.53298616606147 -30.8167273290455 3.56770838882375 -30.8412521183491 3.49537042473567 -30.8657769076526 3.58506950020488 -30.8903016969562 3.54166672175204 -30.9148264862597 3.48090283191806 -30.9393512755632 3.50983801755329 -30.9638760648668 3.63136579722125 -30.9884008541703 3.42881949777465 -31.0129256434739 3.53587968462499 -31.0374504327774 3.68634264992819 -31.0619752220809 3.50115746186272 -31.0865000113845 3.74421302119864 -31.111024800688 3.64004635291182 -31.1355495899916 3.68344913136466 -31.1600743792951 3.55613431456965 -31.1845991685987 3.69791672418228 -31.2091239579022 3.81365746672319 -31.2336487472057 3.82812505954081 -31.2581735365093 3.71238431699989 -31.2826983258128 3.98437506197105 -31.3072231151164 3.80497691103262 -31.3317479044199 3.92361117213706 -31.3562726937234 4.01331024760627 -31.380797483027 4.14641210152833 -31.4053222723305 4.06539358174969 -31.4298470616341 4.08275469313082 -31.4543718509376 4.05671302605912 -31.4788966402411 4.09722228594844 -31.5034214295447 4.13483802727424 -31.5279462188482 4.42418988362653 -31.5524710081518 4.31712969677618 -31.5769957974553 4.15798617578242 -31.6015205867589 4.46759266207937 -31.6260453760624 4.61516210881904 -31.6505701653659 4.56597229323915 -31.6750949546695 4.59201396031086 -31.699619743973 4.60937507169199 -31.7241445332766 4.70486118428825 -31.7486693225801 4.81770840826564 -31.7731941118836 4.81481488970212 -31.7977189011872 5.0144676705852 -31.8222436904907 4.9392361879336 -31.8467684797943 5.13599545025316 -31.8712932690978 5.31828711975511 -31.8958180584013 5.19675934008714 -31.9203428477049 5.29513897124692 -31.9448676370084 5.33275471257272 -31.969392426312 5.57291675334512 -31.9939172156155 5.47164360362182 -32.0184420049191 5.46296304793125 -32.0429667942226 5.64814823599672 -32.0674915835261 5.78993064560934 -32.0920163728297 5.90856490671378 -32.1165411621332 5.83333342406218 -32.1410659514368 5.92592601809492 -32.1655907407403 6.22395843013777 -32.1901155300438 5.96932879654776 -32.2146403193474 6.19791676306607 -32.2391651086509 6.44386584096552 -32.2636898979545 6.31365750560699 -32.288214687258 6.41493065533029 -32.3127394765615 6.57118065776052 -32.3372642658651 6.59143528770518 -32.3617890551686 6.71585658593667 -32.3863138444722 6.82870380991406 -32.4108386337757 6.87789362549395 -32.4353634230793 6.46412047091018 -32.4598882123828 6.49305565654541 -32.4844130016863 6.65509269610269 -32.5089377909899 6.41782417389381 -32.5334625802934 6.51909732361711 -32.557987369597 6.23842602295539 -32.5825121589005 6.19502324450255 -32.607036948204 6.125578798978 -32.6315617375076 5.86805564682446 -32.6560865268111 5.71180564439422 -32.6806113161147 5.85648157257037 -32.7051361054182 5.58738434616274 -32.7296608947217 5.44849545511364 -32.7541856840253 5.39062508384318 -32.7787104733288 5.02604174483929 -32.8032352626324 5.01157415202168 -32.8277600519359 4.85243063102792 -32.8522848412395 4.85821766815496 -32.876809630543 4.8755787795361 -32.9013344198465 4.51678247765926 -32.9258592091501 4.63831025732722 -32.9503839984536 4.421296365063 -32.9749087877572 4.19270839854469 -32.9994335770607 4.36053247522902 -33.0239583663642 4.33738432672084 -33.0484831556678 4.33159728959379 -33.0730079449713 4.21006950992583 -33.0975327342749 4.06539358174969 -33.1220575235784 4.06828710031321 -33.1465823128819 4.06539358174969 -33.1711071021855 4.02488432186037 -33.195631891489 4.20138895423526 -33.2201566807926 4.05381950749559 -33.2446814700961 3.9033565421924 -33.2692062593997 4.07696765600378 -33.2937310487032 3.90625006075593 -33.3182558380067 3.84548617092195 -33.3427806273103 4.00173617335218 -33.3673054166138 3.78761579965149 -33.3918302059174 3.85995376373956 -33.4163549952209 3.74710653976217 -33.4408797845244 3.88310191224774 -33.465404573828 3.75868061401626 -33.4899293631315 3.81944450385024 -33.5144541524351 3.9409722835182 -33.5389789417386 3.96990746915343 -33.5635037310421 3.93229172782763 -33.5880285203457 3.77893524396092 -33.6125533096492 3.71817135412694 -33.6370780989528 3.74710653976217 -33.6616028882563 3.96701395058991 -33.6861276775599 3.86574080086661 -33.7106524668634 3.84837968948547 -33.7351772561669 3.978588024844 -33.7597020454705 3.76736116970683 -33.784226834774 3.72106487269046 -33.8087516240776 4.02777784042389 -33.8332764133811 3.91493061644649 -33.8578012026846 3.99884265478866 -33.8823259919882 3.95543987633582 -33.9068507812917 3.92071765357354 -33.9313755705953 4.05960654462264 -33.9559003598988 3.99594913622514 -33.9804251492023 4.11168987876605 -34.0049499385059 4.06828710031321 -34.0294747278094 4.10590284163901 -34.053999517113 4.23900469556106 -34.0785243064165 4.05381950749559 -34.1030490957201 4.35185191953845 -34.1275738850236 3.98437506197105 -34.1520986743271 4.30266210395857 -34.1766234636307 4.19270839854469 -34.2011482529342 4.26793988119629 -34.2256730422378 4.30555562252209 -34.2501978315413 4.4965278477146 -34.2747226208448 4.51099544053221 -34.2992474101484 4.57754636749324 -34.3237721994519 4.74826396274109 -34.3482969887555 4.71932877710586 -34.372821778059 4.70486118428825 -34.3973465673625 4.71643525854234 -34.4218713566661 5.02893526340281 -34.4463961459696 4.77141211124928 -34.4709209352732 5.07523156041918 -34.4954457245767 5.26909730417522 -34.5199705138803 5.2285880442859 -34.5444953031838 5.10127322749089 -34.5690200924873 5.41956026947841 -34.5935448817909 5.51504638207466 -34.6180696710944 5.61631953179796 -34.642594460398 5.90567138815026 -34.6671192497015 5.76678249710116 -34.691644039005 6.00983805643708 -34.7161688283086 6.21817139301073 -34.7406936176121 6.49884269367245 -34.7652184069157 6.90104177400214 -34.7897431962192 6.7824075128977 -34.8142679855227 7.10358807344874 -34.8387927748263 7.17592603753681 -34.8633175641298 7.49131956096081 -34.8878423534334 7.87905104847288 -34.9123671427369 8.10763901499119 -34.9368919320405 8.08449086648301 -34.961416721344 8.62557883786179 -34.9859415106475 8.42592605697871 -35.0104662999511 9.11747699366068 -35.0349910892546 9.22743069907455 -35.0595158785582 9.43865755421172 -35.0840406678617 9.48784736979162 -35.1085654571652 9.91608811719301 -35.1330902464688 10.1562501579654 -35.1576150357723 10.2517362705617 -35.1821398250759 10.2285881220535 -35.2066646143794 10.4687501628259 -35.2311894036829 10.3414353460309 -35.2557141929865 10.2633103448158 -35.28023898229 10.48032423708 -35.3047637715936 10.8275464647027 -35.3292885608971 10.3819446059202 -35.3538133502007 10.22569460349 -35.3783381395042 10.3211807160862 -35.4028629288077 10.2719909005063 -35.4273877181113 10.3877316430472 -35.4519125074148 10.2285881220535 -35.4764372967184 10.0405094154245 -35.5009620860219 10.22569460349 -35.5254868753254 10.0781251567503 -35.550011664629 9.60358811233253 -35.5745364539325 9.60069459376901 -35.5990612432361 9.60069459376901 -35.6235860325396 9.36342607156013 -35.6481108218431 9.24768532901921 -35.6726356111467 9.40393533144945 -35.6971604004502 9.01620384393738 -35.7216851897538 9.02488439962795 -35.7462099790573 8.76157421034737 -35.7707347683609 8.87731495288828 -35.7952595576644 8.62847235642531 -35.8197843469679 8.75868069178384 -35.8443091362715 8.38252327852586 -35.868833925575 8.69791680194986 -35.8933587148786 8.55902791090076 -35.9178835041821 8.37673624139882 -35.9424082934856 8.30150475874722 -35.9669330827892 8.399884389907 -35.9914578720927 8.2349538317862 -36.0159826613963 8.36805568570825 -36.0405074506998 8.44039364979632 -36.0650322400033 8.24942142460381 -36.0895570293069 8.22627327609563 -36.1140818186104 8.35648161145416 -36.138606607914 8.04398160659368 -36.1631313972175 7.83275475145651 -36.1876561865211 7.90509271554459 -36.2121809758246 8.27256957311199 -36.2367057651281 7.93692141974334 -36.2612305544317 7.7054399346615 -36.2857553437352 7.88483808559992 -36.3102801330388 7.74884271311435 -36.3348049223423 7.56076400648536 -36.3593297116458 7.51157419090547 -36.3838545009494 7.46238437532558 -36.4083792902529 7.4392362268174 -36.4329040795565 7.55208345079479 -36.45742886886 7.24537048306136 -36.4819536581635 6.81712973565997 -36.5064784474671 6.7824075128977 -36.5310032367706 6.84317140273168 -36.5555280260742 6.61458343621337 -36.5800528153777 6.29340287566233 -36.6045776046813 6.34259269124221 -36.6291023939848 6.41493065533029 -36.6536271832883 6.125578798978 -36.6781519725919 5.81886583124457 -36.7026767618954 5.76388897853763 -36.727201551199 5.77835657135525 -36.7517263405025 5.34143526826329 -36.776251129806 5.2922454526834 -36.8007759191096 5.27777785986578 -36.8253007084131 5.07233804185566 -36.8498254977167 4.97395841069588 -36.8743502870202 5.09548619036384 -36.8988750763237 5.1533565616343 -36.9233998656273 4.84664359390087 -36.9479246549308 4.47048618064289 -36.9724494442344 4.67592599865302 -36.9969742335379 4.66435192439893 -37.0214990228415 4.50520840340517 -37.046023812145 4.52546303334983 -37.0705486014485 4.45023155069823 -37.0950733907521 4.57754636749324 -37.1195981800556 4.23032413987049 -37.1441229693592 4.21296302848935 -37.1686477586627 4.18402784285412 -37.1931725479662 4.37210654948311 -37.2176973372698 4.19849543567174 -37.2422221265733 4.2563658069422 -37.2667469158769 4.19560191710822 -37.2912717051804 4.03645839611446 -37.3157964944839 4.02199080329684 -37.3403212837875 4.13194450871071 -37.364846073091 3.93518524639116 -37.3893708623946 4.06828710031321 -37.4138956516981 3.89467598650183 -37.4384204410017 4.05092598893207 -37.4629452303052 3.7644676511433 -37.4874700196087 3.94965283920877 -37.5119948089123 3.90914357931945 -37.5365195982158 3.70370376130932 -37.5610443875194 3.96122691346286 -37.5855691768229 4.08275469313082 -37.6100939661264 3.94675932064525 -37.63461875543 3.72685190981751 -37.6591435447335 3.76736116970683 -37.6836683340371 3.77025468827035 -37.7081931233406 4.01331024760627 -37.7327179126441 3.72395839125398 -37.7572427019477 4.02488432186037 -37.7817674912512 3.93229172782763 -37.8062922805548 3.64583339003886 -37.8308170698583 3.61689820440364 -37.8553418591619 3.74421302119864 -37.8798666484654 3.62268524153068 -37.9043914377689 3.91493061644649 -37.9289162270725 3.78761579965149 -37.953441016376 3.83680561523138 -37.9779658056796 3.71527783556341 -38.0024905949831 3.66898153854705 -38.0270153842866 3.72395839125398 -38.0515401735902 3.7644676511433 -38.0760649628937 3.79629635534206 -38.1005897521973 3.59664357445897 -38.1251145415008 3.69502320561875 -38.1496393308043 3.79629635534206 -38.1741641201079 3.63136579722125 -38.1986889094114 3.63136579722125 -38.223213698715 3.73553246550807 -38.2477384880185 3.68923616849171 -38.2722632773221 3.87152783799365 -38.2967880666256 3.72395839125398 -38.3213128559291 3.77604172539739 -38.3458376452327 3.50115746186272 -38.3703624345362 3.64004635291182 -38.3948872238398 3.82523154097728 -38.4194120131433 3.80497691103262 -38.4439368024468 3.89756950506536 -38.4684615917504 3.8396991337949 -38.4929863810539 3.92071765357354 -38.5175111703575 3.87152783799365 -38.542035959661 3.85127320804899 -38.5665607489645 3.8396991337949 -38.5910855382681 3.79629635534206 -38.6156103275716 4.03067135898741 -38.6401351168752 3.99594913622514 -38.6646599061787 4.10011580451196 -38.6891846954823 3.92650469070059 -38.7137094847858 4.05381950749559 -38.7382342740893 4.12326395302015 -38.7627590633929 4.31423617821266 -38.7872838526964 4.1435185829648 -38.811808642 4.28530099257743 -38.8363334313035 4.08564821169435 -38.860858220607 4.29687506683152 -38.8853830099106 4.20138895423526 -38.9099077992141 4.2563658069422 -38.9344325885177 4.40972229080891 -38.9589573778212 4.51099544053221 -38.9834821671247 4.52835655191335 -39.0080069564283 4.70486118428825 -39.0325317457318 4.49363432915108 -39.0570565350354 4.70486118428825 -39.0815813243389 4.70775470285177 -39.1061061136425 4.6730324800895 -39.130630902946 5.04629637478395 -39.1551556922495 5.05787044903804 -39.1796804815531 5.18518526583305 -39.2042052708566 5.30092600837397 -39.2287300601602 5.39641212097022 -39.2532548494637 5.33854174969977 -39.2777796387672 5.52083341920171 -39.3023044280708 6.00983805643708 -39.3268292173743 5.92013898096787 -39.3513540066779 5.95486120373014 -39.3758787959814 6.1516204660497 -39.4004035852849 6.21817139301073 -39.4249283745885 6.47280102660075 -39.449453163892 6.82291677278702 -39.4739779531956 6.63194454759451 -39.4985027424991 6.92997695963737 -39.5230275318027 6.95023158958202 -39.5475523211062 6.99652788659839 -39.5720771104097 7.32928252140353 -39.5966018997133 7.70833345322503 -39.6211266890168 7.46238437532558 -39.6456514783204 7.58101863643002 -39.6701762676239 7.91377327123515 -39.6947010569274 7.82118067720242 -39.719225846231 7.86747697421879 -39.7437506355345 8.14814827488051 -39.7682754248381 8.12500012637233 -39.7928002141416 8.28993068449313 -39.8173250034451 8.06423623653835 -39.8418497927487 8.22048623896858 -39.8663745820522 8.23206031322267 -39.8908993713558 8.2986112401837 -39.9154241606593 8.20312512758744 -39.9399489499629 8.29571772162018 -39.9644737392664 8.11631957068176 -39.9889985285699 8.04108808803016 -40.0135233178735 8.00636586526789 -40.038048107177 7.73437512029673 -40.0625728964806 7.83564827002003 -40.0870976857841 7.26851863156955 -40.1116224750876 7.35821770703876 -40.1361472643912 6.99074084947135 -40.1606720536947 7.21064826029909 -40.1851968429983 6.98206029378078 -40.2097216323018 6.55092602781586 -40.2342464216053 6.51909732361711 -40.2587712109089 6.45543991521961 -40.2832960002124 6.07638898339811 -40.307820789516 6.13136583610504 -40.3323455788195 6.03587972350879 -40.3568703681231 5.54398156770989 -40.3813951574266 5.46296304793125 -40.4059199467301 5.5063658263841 -40.4304447360337 5.56134267909103 -40.4549695253372 5.24884267423056 -40.4794943146408 5.08101859754623 -40.5040191039443 4.97395841069588 -40.5285438932478 4.99421304064054 -40.5530686825514 4.97395841069588 -40.5775934718549 4.82638896395621 -40.6021182611585 4.89583340948076 -40.626643050462 4.65567136870836 -40.6511678397655 4.74826396274109 -40.6756926290691 4.63831025732722 -40.7002174183726 4.51388895909574 -40.7247422076762 4.77719914837632 -40.7492669969797 4.62384266450961 -40.7737917862833 4.54861118185801 -40.7983165755868 4.52546303334983 -40.8228413648903 4.293981548268 -40.8473661541939 4.35185191953845 -40.8718909434974 4.1550926572189 -40.896415732801 4.27662043688686 -40.9209405221045 4.37210654948311 -40.945465311408 4.29976858539504 -40.9699901007116 4.36342599379255 -40.9945148900151 4.24189821412458 -41.0190396793187 4.05960654462264 -41.0435644686222 4.49074081058755 -41.0680892579257 4.37789358661016 -41.0926140472293 4.28819451114095 -41.1171388365328 4.36342599379255 -41.1416636258364 4.27951395545038 -41.1661884151399 4.30555562252209 -41.1907132044435 4.293981548268 -41.215237993747 4.47048618064289 -41.2397627830505 4.38368062373721 -41.2642875723541 4.42418988362653 -41.2888123616576 4.30555562252209 -41.3133371509612 4.421296365063 -41.3378619402647 4.35474543810198 -41.3623867295682 4.67592599865302 -41.3869115188718 4.57465284892972 -41.4114363081753 4.50231488484165 -41.4359610974789 4.46469914351585 -41.4604858867824 4.5601852561121 -41.4850106760859 4.69618062859768 -41.5095354653895 4.82928248251973 -41.534060254693 4.85821766815496 -41.5585850439966 4.87847229809962 -41.5831098333001 4.78877322263041 -41.6076346226037 4.91608803942542 -41.6321594119072 5.01736118914872 -41.6566842012107 5.24016211853999 -41.6812089905143 5.23726859997646 -41.7057337798178 5.17071767301544 -41.7302585691214 5.30092600837397 -41.7547833584249 5.06076396760157 -41.7793081477284 5.21122693290476 -41.803832937032 5.5324074934558 -41.8283577263355 5.41087971378784 -41.8528825156391 5.50347230782057 -41.8774073049426 5.60185193898035 -41.9019320942461 5.56134267909103 -41.9264568835497 5.5324074934558 -41.9509816728532 5.69733805157661 -41.9755064621568 5.74363434859297 -42.0000312514603 5.75520842284706 -42.0245560407639 5.58159730903569 -42.0490808300674 5.92881953665844 -42.0736056193709 6.04456027919935 -42.0981304086745 5.84490749831627 -42.122655197978 5.82465286837162 -42.1471799872816 5.99537046361947 -42.1717047765851 6.31365750560699 -42.1962295658886 6.28472231997176 -42.2207543551922 6.34837972836926 -42.2452791444957 6.30208343135289 -42.2698039337993 6.31365750560699 -42.2943287231028 6.60879639908632 -42.3188535124063 6.31944454273403 -42.3433783017099 6.42071769245733 -42.3679030910134 6.61747695477689 -42.392427880317 6.42071769245733 -42.4169526696205 6.20081028162959 -42.4414774589241 6.59722232483223 -42.4660022482276 6.58564825057814 -42.4905270375311 6.25578713433653 -42.5150518268347 6.34259269124221 -42.5395766161382 6.34837972836926 -42.5641014054418 6.38888898825858 -42.5886261947453 6.16319454030379 -42.6131509840488 6.33391213555165 -42.6376757733524 6.25000009720948 -42.6622005626559 5.95196768516662 -42.6867253519595 5.75520842284706 -42.711250141263 5.90277786958673 -42.7357749305665 5.70023157014013 -42.7602997198701 5.67418990306842 -42.7848245091736 5.28935193411988 -42.8093492984772 5.24016211853999 -42.8338740877807 5.34432878682681 -42.8583988770843 4.93055563224304 -42.8829236663878 5.02604174483929 -42.9074484556913 4.89583340948076 -42.9319732449949 4.90740748373485 -42.9564980342984 4.52835655191335 -42.981022823602 4.70196766572473 -43.0055476129055 4.48784729202403 -43.030072402209 4.47916673633346 -43.0545971915126 4.24479173268811 -43.0791219808161 4.1435185829648 -43.1036467701197 4.27372691832334 -43.1281715594232 4.01909728473332 -43.1526963487267 4.01909728473332 -43.1772211380303 3.92071765357354 -43.2017459273338 3.74131950263512 -43.2262707166374 3.89756950506536 -43.2507955059409 3.85127320804899 -43.2753202952445 3.83391209666785 -43.299845084548 3.65162042716591 -43.3243698738515 3.49537042473567 -43.3488946631551 3.76736116970683 -43.3734194524586 3.47800931335454 -43.3979442417622 3.59085653733193 -43.4224690310657 3.39699079357589 -43.4469938203692 3.45775468340987 -43.4715186096728 3.59375005589545 -43.4960433989763 3.4606482019734 -43.5205681882799 3.46354172053692 -43.5450929775834 3.32465282948782 -43.569617766887 3.46354172053692 -43.5941425561905 3.31018523667021 -43.618667345494 3.14814819711292 -43.6431921347976 3.35358801512305 -43.6677169241011 3.39409727501237 -43.6922417134047 3.30729171810668 -43.7167665027082 3.23495375401861 -43.7412912920117 3.27546301390793 -43.7658160813153 3.36805560794067 -43.7903408706188 3.2465278282727 -43.8148656599224 3.11631949291417 -43.8393904492259 3.18865745700224 -43.8639152385294 3.10474541866008 -43.888440027833 3.15682875280349 -43.9129648171365 3.04687504738962 -43.9374896064401 3.16840282705758 -43.9620143957436 3.07002319589781 -43.9865391850472 3.1828704198752 -44.0110639743507 2.97743060186507 -44.0355887636542 3.08449078871542 -44.0601135529578 3.17708338274815 -44.0846383422613 2.96296300904746 -44.1091631315649 3.11342597435065 -44.1336879208684 3.02372689888144 -44.1582127101719 3.02662041744496 -44.1827374994755 3.0439815288261 -44.207262288779 2.8674768964512 -44.2317870780826 2.91087967490405 -44.2563118673861 2.90219911921348 -44.2808366566896 3.03819449169905 -44.3053614459932 2.9803241204286 -44.3298862352967 3.0815972701519 -44.3544110246003 2.92245374915814 -44.3789358139038 3.05844912164371 -44.4034606032074 2.93981486053927 -44.4279853925109 3.04108801026258 -44.4525101818144 2.76041670960085 -44.477034971118 2.89641208208643 -44.5015597604215 2.87326393357825 -44.5260845497251 2.97453708330155 -44.5506093390286 2.79803245092665 -44.5751341283321 3.17418986418463 -44.5996589176357 2.85011578507007 -44.6241837069392 2.7662037467279 -44.6487084962428 2.98900467611916 -44.6732332855463 2.88483800783234 -44.6977580748498 2.88483800783234 -44.7222828641534 3.01793986175439 -44.7468076534569 2.87905097070529 -44.7713324427605 2.92245374915814 -44.795857232064 3.04976856595315 -44.8203820213676 2.90219911921348 -44.8449068106711 2.78356485810904 -44.8694315999746 2.78645837667256 -44.8939563892782 2.8414352293795 -44.9184811785817 2.90798615634052 -44.9430059678853 2.87905097070529 -44.9675307571888 3.06712967733428 -44.9920555464923 2.87037041501472 -45.0165803357959 2.82118059943484 -45.0411051250994 2.74594911678324 -45.065629914403 3.05266208451667 -45.0901547037065 2.87905097070529 -45.11467949301 3.06423615877076 -45.1392042823136 3.07870375158837 -45.1637290716171 2.94849541622984 -45.1882538609207 3.07581023302485 -45.2127786502242 2.98321763899212 -45.2373034395278 3.01215282462735 -45.2618282288313 2.8414352293795 -45.2863530181348 2.99479171324621 -45.3108778074384 3.04976856595315 -45.3354025967419 2.87905097070529 -45.3599273860455 3.09895838153304 -45.384452175349 3.02372689888144 -45.4089769646525 3.16261578993054 -45.4335017539561 2.85879634076063 -45.4580265432596 3.17418986418463 -45.4825513325632 3.0063657875003 -45.5070761218667 3.17708338274815 -45.5316009111702 3.19733801269281 -45.5561257004738 3.24074079114566 -45.5806504897773 3.13657412285883 -45.6051752790809 3.26678245821736 -45.6297000683844 3.10185190009656 -45.654224857688 3.05555560308019 -45.6787496469915 3.15682875280349 -45.703274436295 3.20023153125634 -45.7277992255986 3.33622690374191 -45.7523240149021 3.46643523910044 -45.7768488042057 3.13946764142236 -45.8013735935092 3.27256949534441 -45.8258983828127 3.47511579479101 -45.8504231721163 3.39120375644885 -45.8749479614198 3.21759264263747 -45.8994727507234 3.57928246307784 -45.9239975400269 3.41724542352055 -45.9485223293304 3.47800931335454 -45.973047118634 3.68344913136466 -45.9975719079375 3.64583339003886 -46.0220966972411 3.55324079600613 -46.0466214865446 3.53587968462499 -46.0711462758482 3.55902783313318 -46.0956710651517 3.49247690617215 -46.1201958544552 3.67766209423762 -46.1447206437588 3.72685190981751 -46.1692454330623 3.64004635291182 -46.1937702223659 3.77025468827035 -46.2182950116694 3.61111116727659 -46.2428198009729 3.97280098771695 -46.2673445902765 3.72685190981751 -46.29186937958 3.81076394815967 -46.3163941688836 3.84259265235842 -46.3409189581871 3.80497691103262 -46.3654437474906 3.87152783799365 -46.3899685367942 3.72974542838103 -46.4144933260977 3.96990746915343 -46.4390181154013 3.89178246793831 -46.4635429047048 3.81655098528672 -46.4880676940084 3.80787042959615 -46.5125924833119 3.55034727744261 -46.5371172726154 3.82523154097728 -46.561642061919 3.57060190738727 -46.5861668512225 3.75000005832569 -46.6106916405261 3.84548617092195 -46.6352164298296 3.57928246307784 -46.6597412191331 3.58506950020488 -46.6842660084367 3.55324079600613 -46.7087907977402 3.55034727744261 -46.7333155870438 3.4866898690451 -46.7578403763473 3.64872690860239 -46.7823651656508 3.49537042473567 -46.8068899549544 3.56770838882375 -46.8314147442579 3.64583339003886 -46.8559395335615 3.50983801755329 -46.880464322865 3.39409727501237 -46.9049891121686 3.45486116484635 -46.9295139014721 3.43460653490169 -46.9540386907756 3.28993060672555 -46.9785634800792 3.30439819954316 -47.0030882693827 3.27256949534441 -47.0276130586863 3.31307875523373 -47.0521378479898 3.33622690374191 -47.0766626372933 3.4866898690451 -47.1011874265969 3.42013894208408 -47.1257122159004 3.1452546785494 -47.150237005204 3.39409727501237 -47.1747617945075 3.30150468097964 -47.199286583811 3.27256949534441 -47.2238113731146 3.24942134683623 -47.2483361624181 3.42881949777465 -47.2728609517217 3.44618060915578 -47.2973857410252 3.4866898690451 -47.3219105303288 3.46932875766397 -47.3464353196323 3.50983801755329 -47.3709601089358 3.48958338760863 -47.3954848982394 3.41145838639351 -47.4200096875429 3.44039357202874 -47.4445344768465 3.47800931335454 -47.46905926615 3.46354172053692 -47.4935840554535 3.47511579479101 -47.5181088447571 3.54745375887909 -47.5426336340606 3.69212968705523 -47.5671584233642 3.6371528343483 -47.5916832126677 3.81944450385024 -47.6162080019712 3.74131950263512 -47.6407327912748 3.77025468827035 -47.6652575805783 3.6255787600942 -47.6897823698819 3.99594913622514 -47.7143071591854 3.71527783556341 -47.738831948489 3.92071765357354 -47.7633567377925 3.98437506197105 -47.787881527096 3.94386580208172 -47.8124063163996 4.17245376860003 -47.8369311057031 4.09722228594844 -47.8614558950067 3.99884265478866 -47.8859806843102 4.05381950749559 -47.9105054736137 4.19849543567174 -47.9350302629173 4.19270839854469 -47.9595550522208 4.44733803213471 -47.9840798415244 4.51099544053221 -48.0086046308279 4.36921303091959 -48.0331294201314 4.59201396031086 -48.057654209435 4.67592599865302 -48.0821789987385 4.74247692561405 -48.1067037880421 4.60358803456495 -48.1312285773456 4.71064822141529 -48.1557533666492 5.00000007776759 -48.1802781559527 4.9768519292594 -48.2048029452562 5.07523156041918 -48.2293277345598 5.0781250789827 -48.2538525238633 5.09259267180032 -48.2783773131669 5.06655100472861 -48.3029021024704 5.06076396760157 -48.3274268917739 5.30381952693749 -48.3519516810775 5.38194452815261 -48.376476470381 5.51215286351114 -48.4010012596846 5.57581027190864 -48.4255260489881 5.26909730417522 -48.4500508382916 5.50057878925705 -48.4745756275952 5.52083341920171 -48.4991004168987 5.88831027676912 -48.5236252062023 5.89699083245969 -48.5481499955058 5.54398156770989 -48.5726747848094 5.83333342406218 -48.5971995741129 5.78125008991877 -48.6217243634164 5.76388897853763 -48.64624915272 5.78125008991877 -48.6707739420235 6.00405101931003 -48.6952987313271 6.14004639179561 -48.7198235206306 5.98379638936537 -48.7443483099341 5.76099545997411 -48.7688730992377 5.82175934980809 -48.7933978885412 5.98958342649242 -48.8179226778448 5.86805564682446 -48.8424474671483 5.85648157257037 -48.8669722564518 6.09375009477924 -48.8914970457554 5.98958342649242 -48.9160218350589 5.54687508627342 -48.9405466243625 5.56423619765455 -48.965071413666 5.53530101201932 -48.9895962029696 5.83622694262571 -49.0141209922731 5.75810194141059 -49.0386457815766 5.47453712218534 -49.0631705708802 5.41377323235136 -49.0876953601837 5.51793990063819 -49.1122201494873 5.6076389761074 -49.1367449387908 5.48900471500296 -49.1612697280943 5.24594915566703 -49.1857945173979 5.30092600837397 -49.2103193067014 5.32696767544567 -49.234844096005 5.02604174483929 -49.2593688853085 5.08391211610975 -49.283893674612 4.78298618550337 -49.3084184639156 5.09548619036384 -49.3329432532191 4.73090285135995 -49.3574680425227 4.60648155312847 -49.3819928318262 4.92476859511599 -49.4065176211298 4.67592599865302 -49.4310424104333 4.82060192682916 -49.4555671997368 4.45023155069823 -49.4800919890404 4.37210654948311 -49.5046167783439 4.53125007047687 -49.5291415676475 4.29976858539504 -49.553666356951 4.47337969920642 -49.5781911462545 4.48495377346051 -49.6027159355581 4.24189821412458 -49.6272407248616 4.22164358417992 -49.6517655141652 4.10011580451196 -49.6762903034687 4.04513895180503 -49.7008150927722 3.97569450628048 -49.7253398820758 3.94675932064525 -49.7498646713793 3.91203709788297 -49.7743894606829 3.93807876495468 -49.7989142499864 3.82523154097728 -49.82343903929 3.47222227622749 -49.8479638285935 3.71527783556341 -49.872488617897 3.53587968462499 -49.8970134072006 3.72685190981751 -49.9215381965041 3.52141209180738 -49.9460629858077 3.54745375887909 -49.9705877751112 3.57349542595079 -49.9951125644147 3.46643523910044 -50.0196373537183 3.54456024031556 -50.0441621430218 3.45486116484635 -50.0686869323254 3.29861116241612 -50.0932117216289 3.38831023788533 -50.1177365109324 3.41724542352055 -50.142261300236 3.37384264506771 -50.1667860895395 3.26967597678089 -50.1913108788431 3.20601856838338 -50.2158356681466 3.28993060672555 -50.2403604574502 3.17708338274815 -50.2648852467537 3.17708338274815 -50.2894100360572 3.14236115998588 -50.3139348253608 3.20023153125634 -50.3384596146643 3.07581023302485 -50.3629844039679 3.09895838153304 -50.3875091932714 3.10185190009656 -50.4120339825749 2.74305559821972 -50.4365587718785 3.0063657875003 -50.461083561182 3.14814819711292 -50.4856083504856 3.02372689888144 -50.5101331397891 2.91956023059461 -50.5346579290926 3.14236115998588 -50.5591827183962 3.05555560308019 -50.5837075076997 3.02662041744496 -50.6082322970033 2.96585652761098 -50.6327570863068 3.11053245578713 -50.6572818756104 2.905092637777 -50.6818066649139 3.12789356716826 -50.7063314542174 2.91956023059461 -50.730856243521 2.91666671203109 -50.7553810328245 3.00347226893678 -50.7799058221281 3.00057875037326 -50.8044306114316 2.94560189766632 -50.8289554007351 2.95428245335689 -50.8534801900387 2.89930560064995 -50.8780049793422 3.07291671446133 -50.9025297686458 2.99768523180973 -50.9270545579493 3.10474541866008 -50.9515793472528 2.87615745214177 -50.9761041365564 2.79803245092665 -51.0006289258599 3.02951393600848 -51.0251537151635 2.93981486053927 -51.049678504467 3.04687504738962 -51.0742032937706 2.99479171324621 -51.0987280830741 2.98900467611916 -51.1232528723776 3.03240745457201 -51.1477776616812 3.07581023302485 -51.1723024509847 3.06134264020724 -51.1968272402883 3.17708338274815 -51.2213520295918 2.94560189766632 -51.2458768188953 2.92824078628518 -51.2704016081989 2.97453708330155 -51.2949263975024 2.95138893479337 -51.319451186806 2.82118059943484 -51.3439759761095 3.15393523423997 -51.368500765413 2.92824078628518 -51.3930255547166 3.00347226893678 -51.4175503440201 3.23206023545509 -51.4420751333237 3.15972227136702 -51.4665999226272 3.1828704198752 -51.4911247119308 3.17129634562111 -51.5156495012343 3.40567134926646 -51.5401742905378 3.16261578993054 -51.5646990798414 3.07870375158837 -51.5892238691449 3.33912042230544 -51.6137486584485 3.37673616363123 -51.638273447752 3.18576393843872 -51.6627982370555 3.18576393843872 -51.6873230263591 3.26678245821736 -51.7118478156626 3.26099542109032 -51.7363726049662 3.20312504981986 -51.7608973942697 3.17129634562111 -51.7854221835732 3.11342597435065 -51.8099469728768 3.24942134683623 -51.8344717621803 3.27546301390793 -51.8589965514839 3.35648153368657 -51.8835213407874 3.21469912407395 -51.908046130091 3.37094912650419 -51.9325709193945 3.36805560794067 -51.957095708698 3.33622690374191 -51.9816204980016 3.29571764385259 -52.0061452873051 3.46932875766397 -52.0306700766087 3.57638894451431 -52.0551948659122 3.39699079357589 -52.0797196552157 3.5243056103709 -52.1042444445193 3.56770838882375 -52.1287692338228 3.57928246307784 -52.1532940231264 3.6255787600942 -52.1778188124299 3.55902783313318 -52.2023436017334 3.50983801755329 -52.226868391037 3.6255787600942 -52.2513931803405 3.70659727987285 -52.2759179696441 3.6371528343483 -52.3004427589476 3.90625006075593 -52.3249675482512 3.71817135412694 -52.3494923375547 3.78472228108796 -52.3740171268582 3.75578709545273 -52.3985419161618 3.77893524396092 -52.4230667054653 3.50405098042624 -52.4475914947689 3.69502320561875 -52.4721162840724 3.77025468827035 -52.4966410733759 3.85127320804899 -52.5211658626795 3.65740746429296 -52.545690651983 3.67476857567409 -52.5702154412866 3.43171301633817 -52.5947402305901 3.55902783313318 -52.6192650198936 3.56770838882375 -52.6437898091972 3.47222227622749 -52.6683145985007 3.44039357202874 -52.6928393878043 3.39988431213942 -52.7173641771078 3.55034727744261 -52.7418889664114 3.44039357202874 -52.7664137557149 3.3854167193218 -52.7909385450184 3.46354172053692 -52.815463334322 3.36516208937714 -52.8399881236255 3.30150468097964 -52.8645129129291 3.45196764628283 -52.8890377022326 3.26388893965384 -52.9135624915361 3.18865745700224 -52.9380872808397 2.98321763899212 -52.9626120701432 3.09317134440599 -52.9871368594468 2.95717597192041 -53.0116616487503 3.09606486296951 -53.0361864380538 3.05555560308019 -53.0607112273574 3.12500004860474 -53.0852360166609 3.1828704198752 -53.1097608059645 2.99479171324621 -53.134285595268 3.0815972701519 -53.1588103845716 2.93113430484871 -53.1833351738751 2.79803245092665 -53.2078599631786 2.95717597192041 -53.2323847524822 3.00347226893678 -53.2569095417857 3.23495375401861 -53.2814343310893 2.91666671203109 -53.3059591203928 2.87326393357825 -53.3304839096963 2.97743060186507 -53.3550086989999 2.94560189766632 -53.3795334883034 2.91666671203109 -53.404058277607 3.02662041744496 -53.4285830669105 3.04976856595315 -53.453107856214 2.91087967490405 -53.4776326455176 2.9803241204286 -53.5021574348211 3.07291671446133 -53.5266822241247 2.91956023059461 -53.5512070134282 2.95717597192041 -53.5757318027318 3.00347226893678 -53.6002565920353 3.03819449169905 -53.6247813813388 2.96006949048394 -53.6493061706424 2.97453708330155 -53.6738309599459 2.91666671203109 -53.6983557492495 3.21469912407395 -53.722880538553 3.29861116241612 -53.7474053278565 3.26388893965384 -53.7719301171601 3.06423615877076 -53.7964549064636 3.25520838396327 -53.8209796957672 3.26678245821736 -53.8455044850707 3.12500004860474 -53.8700292743742 3.23206023545509 -53.8945540636778 3.35069449655953 -53.9190788529813 3.21180560551043 -53.9436036422849 3.29282412528907 -53.9681284315884 3.31886579236078 -53.992653220892 3.33912042230544 -54.0171780101955 3.21469912407395 -54.041702799499 3.26678245821736 -54.0662275888026 3.43460653490169 -54.0907523781061 3.3854167193218 -54.1152771674097 3.42881949777465 -54.1398019567132 3.4230324606476 -54.1643267460167 3.70370376130932 -54.1888515353203 3.50983801755329 -54.2133763246238 3.60821764871307 -54.2379011139274 3.63425931578477 -54.2624259032309 3.50405098042624 -54.2869506925344 3.36516208937714 -54.311475481838 3.43750005346522 -54.3360002711415 3.49247690617215 -54.3605250604451 3.40277783070294 -54.3850498497486 3.47222227622749 -54.4095746390522 3.42881949777465 -54.4340994283557 3.7008102427458 -54.4586242176592 3.60243061158602 -54.4831490069628 3.51562505468033 -54.5076737962663 3.59664357445897 -54.5321985855699 3.27256949534441 -54.5567233748734 3.44618060915578 -54.5812481641769 3.43460653490169 -54.6057729534805 3.30729171810668 -54.630297742784 3.61400468584011 -54.6548225320876 3.22916671689157 -54.6793473213911 3.29861116241612 -54.7038721106946 3.37384264506771 -54.7283968999982 3.06423615877076 -54.7529216893017 3.27256949534441 -54.7774464786053 3.46932875766397 -54.8019712679088 3.19155097556577 -54.8264960572124 3.19444449412929 -54.8510208465159 3.23784727258213 -54.8755456358194 3.13078708573179 -54.900070425123 3.25520838396327 -54.9245952144265 3.17418986418463 -54.9491200037301 3.07870375158837 -54.9736447930336 3.09027782584247 -54.9981695823371 3.04687504738962 -55.0226943716407 3.1452546785494 -55.0472191609442 2.96296300904746 -55.0717439502478 3.02372689888144 -55.0962687395513 3.23784727258213 -55.1207935288548 3.03240745457201 -55.1453183181584 3.26678245821736 -55.1698431074619 3.09317134440599 -55.1943678967655 3.06423615877076 -55.218892686069 2.98900467611916 -55.2434174753726 3.10185190009656 -55.2679422646761 2.82118059943484 -55.2924670539796 2.99479171324621 -55.3169918432832 2.81250004374427 -55.3415166325867 2.78935189523608 -55.3660414218903 3.13078708573179 -55.3905662111938 2.92245374915814 -55.4150910004973 2.78067133954551 -55.4396157898009 2.73437504252915 -55.4641405791044 2.8038194880537 -55.488665368408 2.67650467125869 -55.5131901577115 2.69965281976687 -55.537714947015 2.60706022573414 -55.5622397363186 2.67071763413164 -55.5867645256221 2.75173615391029 -55.6112893149257 2.69386578263983 -55.6358141042292 2.70833337545744 -55.6603388935328 2.67939818982221 -55.6848636828363 2.59837967004357 -55.7093884721398 2.63888892993289 -55.7339132614434 2.63310189280585 -55.7584380507469 2.58391207722596 -55.7829628400505 2.67361115269517 -55.807487629354 2.51736115026493 -55.8320124186575 2.51446763170141 -55.8565372079611 2.51736115026493 -55.8810619972646 2.47395837181209 -55.9055867865682 2.49131948319322 -55.9301115758717 2.58391207722596 -55.9546363651752 2.50868059457436 -55.9791611544788 2.43344911192277 -56.0036859437823 2.5636574472813 -56.0282107330859 2.41030096341458 -56.0527355223894 2.37847225921583 -56.077260311693 2.4247685562322 -56.1017851009965 2.35821762927117 -56.1263098903 2.41608800054163 -56.1508346796036 2.27430559092901 -56.1753594689071 2.30613429512776 -56.1998842582107 2.39872688916049 -56.2244090475142 2.41030096341458 -56.2489338368177 2.36689818496174 -56.2734586261213 2.46527781612152 -56.2979834154248 2.33796299932651 -56.3225082047284 2.28877318374662 -56.3470329940319 2.46238429755799 -56.3715577833354 2.22222225678559 -56.396082572639 2.37557874065231 -56.4206073619425 2.47685189037561 -56.4451321512461 2.24537040529378 -56.4696569405496 2.42187503766867 -56.4941817298532 2.37268522208878 -56.5187065191567 2.46817133468504 -56.5432313084602 2.29456022087367 -56.5677560977638 2.22222225678559 -56.5922808870673 2.37557874065231 -56.6168056763709 2.27430559092901 -56.6413304656744 2.42766207479572 -56.6658552549779 2.27430559092901 -56.6903800442815 2.19618058971389 -56.714904833585 2.30034725800071 -56.7394296228886 2.39004633346992 -56.7639544121921 2.2482639238573 -56.7884792014956 2.32928244363594 -56.8130039907992 2.31481485081833 -56.8375287801027 2.26851855380196 -56.8620535694063 2.29745373943719 -56.8865783587098 2.27719910949253 -56.9111031480134 2.23958336816673 -56.9356279373169 2.43923614904981 -56.9601527266204 2.22222225678559 -56.984677515924 2.31481485081833 -57.0092023052275 2.43923614904981 -57.0337270945311 2.26851855380196 -57.0582518838346 2.33506948076299 -57.0827766731381 2.39293985203345 -57.1073014624417 2.26562503523844 -57.1318262517452 2.36111114783469 -57.1563510410488 2.44502318617686 -57.1808758303523 2.49710652032027 -57.2054006196558 2.10069447711763 -57.2299254089594 2.36689818496174 -57.2544501982629 2.36400466639822 -57.2789749875665 2.39004633346992 -57.30349977687 2.29456022087367 -57.3280245661736 2.4884259646297 -57.3525493554771 2.37557874065231 -57.3770741447806 2.44791670474038 -57.4015989340842 2.40740744485106 -57.4261237233877 2.36689818496174 -57.4506485126913 2.46527781612152 -57.4751733019948 2.33217596219946 -57.4996980912983 2.37847225921583 -57.5242228806019 2.44212966761333 -57.5487476699054 2.44791670474038 -57.573272459209 2.43923614904981 -57.5977972485125 2.37268522208878 -57.622322037816 2.39872688916049 -57.6468468271196 2.60127318860709 -57.6713716164231 2.47106485324856 -57.6958964057267 2.54340281733664 -57.7204211950302 2.46527781612152 -57.7449459843338 2.52025466882845 -57.7694707736373 2.52025466882845 -57.7939955629408 2.57233800297187 -57.8185203522444 2.39583337059697 -57.8430451415479 2.39872688916049 -57.8675699308515 2.44502318617686 -57.892094720155 2.33506948076299 -57.9166195094585 2.65625004131403 -57.9411442987621 2.53761578020959 -57.9656690880656 2.37847225921583 -57.9901938773692 2.53472226164607 -58.0147186666727 2.45659726043095 -58.0392434559762 2.50289355744732 -58.0637682452798 2.43055559335924 -58.0882930345833 2.55497689159073 -58.1128178238869 2.32928244363594 -58.1373426131904 2.55787041015425 -58.161867402494 2.30902781369128 -58.1863921917975 2.44791670474038 -58.210916981101 2.53761578020959 -58.2354417704046 2.40162040772401 -58.2599665597081 2.47685189037561 -58.2844913490117 2.55787041015425 -58.3090161383152 2.56655096584482 -58.3335409276187 2.49710652032027 -58.3580657169223 2.41030096341458 -58.3825905062258 2.66782411556812 -58.4071152955294 2.47395837181209 -58.4316400848329 2.64467596705994 -58.4561648741364 2.44791670474038 -58.48068966344 2.69386578263983 -58.5052144527435 2.59259263291653 -58.5297392420471 2.58680559578948 -58.5542640313506 2.63888892993289 -58.5787888206542 2.72280096827506 -58.6033136099577 2.58101855866243 -58.6278383992612 2.71990744971153 -58.6523631885648 2.61863429998823 -58.6768879778683 2.82118059943484 -58.7014127671719 2.62442133711528 -58.7259375564754 2.76331022816438 -58.7504623457789 2.91377319346757 -58.7749871350825 2.71122689402096 -58.799511924386 2.75752319103733 -58.8240367136896 2.72569448683858 -58.8485615029931 2.57233800297187 -58.8730862922966 2.85011578507007 -58.8976110816002 2.77777782098199 -58.9221358709037 2.88194448926882 -58.9466606602073 2.93692134197575 -58.9711854495108 2.7025463383304 -58.9957102388144 2.79513893236313 -59.0202350281179 2.94560189766632 -59.0447598174214 2.91377319346757 -59.069284606725 2.74016207965619 -59.0938093960285 2.94560189766632 -59.1183341853321 2.85011578507007 -59.1428589746356 3.01504634319087 -59.1673837639391 2.8414352293795 -59.1919085532427 2.90219911921348 -59.2164333425462 2.97743060186507 -59.2409581318498 3.10185190009656 -59.2654829211533 3.1076389372236 -59.2900077104568 3.17997690131168 -59.3145324997604 3.04108801026258 -59.3390572890639 2.89641208208643 -59.3635820783675 3.00925930606382 -59.388106867671 3.07291671446133 -59.4126316569746 3.05555560308019 -59.4371564462781 3.09027782584247 -59.4616812355816 3.1192130114777 -59.4862060248852 3.09317134440599 -59.5107308141887 3.09027782584247 -59.5352556034923 3.08449078871542 -59.5597803927958 3.2465278282727 -59.5843051820993 3.11342597435065 -59.6088299714029 3.23784727258213 -59.6333547607064 3.20891208694691 -59.65787955001 3.39699079357589 -59.6824043393135 3.35069449655953 -59.706929128617 3.12789356716826 -59.7314539179206 3.27835653247146 -59.7559787072241 3.23495375401861 -59.7805034965277 3.29861116241612 -59.8050282858312 3.30729171810668 -59.8295530751348 3.18865745700224 -59.8540778644383 3.32754634805134 -59.8786026537418 3.33912042230544 -59.9031274430454 3.29282412528907 -59.9276522323489 3.3593750522501 -59.9521770216525 3.26099542109032 -59.976701810956 3.18576393843872 -60.0012266002595 3.16261578993054 -60.0257513895631 3.18576393843872 -60.0502761788666 3.05555560308019 -60.0748009681702 3.10474541866008 -60.0993257574737 3.20601856838338 -60.1238505467772 3.16840282705758 -60.1483753360808 2.94849541622984 -60.1729001253843 2.98611115755564 -60.1974249146879 3.07291671446133 -60.2219497039914 3.01504634319087 -60.246474493295 3.06134264020724 -60.2709992825985 2.87905097070529 -60.295524071902 2.96296300904746 -60.3200488612056 2.97164356473803 -60.3445736505091 2.82696763656188 -60.3690984398127 2.86458337788768 -60.3936232291162 2.90219911921348 -60.4181480184197 2.87326393357825 -60.4426728077233 3.03530097313553 -60.4671975970268 2.97164356473803 -60.4917223863304 2.81539356230779 -60.5162471756339 2.78935189523608 -60.5407719649374 2.71990744971153 -60.565296754241 2.78067133954551 -60.5898215435445 2.72569448683858 -60.6143463328481 2.65335652275051 -60.6388711221516 2.6649305970046 -60.6633959114552 2.63599541136937 -60.6879207007587 2.58101855866243 -60.7124454900622 2.67650467125869 -60.7369702793658 2.61284726286119 -60.7614950686693 2.65046300418698 -60.7860198579729 2.67650467125869 -60.8105446472764 2.52314818739198 -60.8350694365799 2.54050929877311 -60.8595942258835 2.74305559821972 -60.884119015187 2.48553244606618 -60.9086438044906 2.58101855866243 -60.9331685937941 2.46238429755799 -60.9576933830976 2.51736115026493 -60.9822181724012 2.63599541136937 -61.0067429617047 2.50578707601084 -61.0312677510083 2.58101855866243 -61.0557925403118 2.62442133711528 -61.0803173296154 2.48553244606618 -61.1048421189189 2.54918985446368 -61.1293669082224 2.49131948319322 -61.153891697526 2.59259263291653 -61.1784164868295 2.45659726043095 -61.2029412761331 2.53182874308254 -61.2274660654366 2.48263892750266 -61.2519908547401 2.58101855866243 -61.2765156440437 2.39872688916049 -61.3010404333472 2.44212966761333 -61.3255652226508 2.5636574472813 -61.3500900119543 2.48553244606618 -61.3746148012578 2.51446763170141 -61.3991395905614 2.48263892750266 -61.4236643798649 2.45370374186743 -61.4481891691685 2.40740744485106 -61.472713958472 2.40451392628754 -61.4972387477756 2.3119213322548 -61.5217635370791 2.41898151910515 -61.5462883263826 2.58391207722596 -61.5708131156862 2.46527781612152 -61.5953379049897 2.57233800297187 -61.6198626942933 2.40451392628754 -61.6443874835968 2.56076392871777 -61.6689122729003 2.47974540893913 -61.6934370622039 2.42766207479572 -61.7179618515074 2.54629633590016 -61.742486640811 2.62442133711528 -61.7670114301145 2.55787041015425 -61.791536219418 2.60127318860709 -61.8160610087216 2.63020837424232 -61.8405857980251 2.72280096827506 -61.8651105873287 2.5260417059555 -61.8896353766322 2.47685189037561 -61.9141601659358 2.67939818982221 -61.9386849552393 2.63020837424232 -61.9632097445428 2.46817133468504 -61.9877345338464 2.50578707601084 -62.0122593231499 2.72569448683858 -62.0367841124535 2.74016207965619 -62.061308901757 2.54629633590016 -62.0858336910605 2.66782411556812 -62.1103584803641 2.65625004131403 -62.1348832696676 2.71412041258449 -62.1594080589712 2.92824078628518 -62.1839328482747 2.75462967247381 -62.2084576375782 2.71122689402096 -62.2329824268818 2.8038194880537 -62.2575072161853 2.71701393114801 -62.2820320054889 2.87905097070529 -62.3065567947924 2.7662037467279 -62.331081584096 2.71701393114801 -62.3556063733995 2.9687500461745 -62.380131162703 2.98900467611916 -62.4046559520066 2.73437504252915 -62.4291807413101 2.75752319103733 -62.4537055306137 2.98321763899212 -62.4782303199172 2.96006949048394 -62.5027551092207 2.88483800783234 -62.5272798985243 2.97743060186507 -62.5518046878278 2.97164356473803 -62.5763294771314 2.85879634076063 -62.6008542664349 2.97453708330155 -62.6253790557384 2.97743060186507 -62.649903845042 3.05555560308019 -62.6744286343455 2.97164356473803 -62.6989534236491 2.93981486053927 -62.7234782129526 3.04976856595315 -62.7480030022562 3.00057875037326 -62.7725277915597 3.00925930606382 -62.7970525808632 3.03819449169905 -62.8215773701668 3.05266208451667 -62.8461021594703 2.93402782341223 -62.8706269487739 3.00347226893678 -62.8951517380774 3.11342597435065 -62.9196765273809 3.01504634319087 -62.9442013166845 3.04687504738962 -62.968726105988 3.14814819711292 -62.9932508952916 2.97164356473803 -63.0177756845951 3.04108801026258 -63.0423004738986 2.89351856352291 -63.0668252632022 2.9427083791028 -63.0913500525057 2.85011578507007 -63.1158748418093 2.97164356473803 -63.1403996311128 3.03530097313553 -63.1649244204164 3.03819449169905 -63.1894492097199 3.0815972701519 -63.2139739990234 2.91087967490405 -63.238498788327 2.94849541622984 -63.2630235776305 2.93981486053927 -63.2875483669341 2.98900467611916 -63.3120731562376 2.87905097070529 -63.3365979455411 2.78935189523608 -63.3611227348447 2.88483800783234 -63.3856475241482 2.76909726529142 -63.4101723134518 2.98900467611916 -63.4346971027553 2.89351856352291 -63.4592218920588 2.85300930363359 -63.4837466813624 2.67071763413164 -63.5082714706659 2.92534726772166 -63.5327962599695 2.93113430484871 -63.557321049273 2.85011578507007 -63.5818458385766 2.90219911921348 -63.6063706278801 2.76909726529142 -63.6308954171836 2.75752319103733 -63.6554202064872 2.8674768964512 -63.6799449957907 2.85879634076063 -63.7044697850943 2.7662037467279 -63.7289945743978 2.94849541622984 -63.7535193637013 3.02662041744496 -63.7780441530049 2.74305559821972 -63.8025689423084 2.78935189523608 -63.827093731612 2.78645837667256 -63.8516185209155 2.75752319103733 -63.876143310219 2.74884263534676 -63.9006680995226 2.92824078628518 -63.9251928888261 2.87326393357825 -63.9497176781297 2.9803241204286 -63.9742424674332 2.9803241204286 -63.9987672567368 2.8298611551254 -64.0232920460403 2.91666671203109 -64.0478168353438 2.83275467368893 -64.0723416246474 2.79224541379961 -64.0968664139509 2.93692134197575 -64.1213912032545 2.8674768964512 -64.145915992558 2.93981486053927 -64.1704407818615 2.92245374915814 -64.1949655711651 2.93692134197575 -64.2194903604686 2.91377319346757 -64.2440151497722 2.98900467611916 -64.2685399390757 3.02083338031792 -64.2930647283793 2.89641208208643 -64.3175895176828 2.91087967490405 -64.3421143069863 3.05266208451667 -64.3666390962899 2.93981486053927 -64.3911638855934 2.9803241204286 -64.415688674897 2.8038194880537 -64.4402134642005 2.68229170838574 -64.464738253504 2.78645837667256 -64.4892630428076 2.95717597192041 -64.5137878321111 3.14814819711292 -64.5383126214147 2.93402782341223 -64.5628374107182 3.02083338031792 -64.5873622000217 2.86458337788768 -64.6118869893253 2.80671300661722 -64.6364117786288 2.8038194880537 -64.6609365679324 3.11053245578713 -64.6854613572359 2.87905097070529 -64.7099861465395 2.95428245335689 -64.734510935843 3.02372689888144 -64.7590357251465 2.905092637777 -64.7835605144501 2.86168985932416 -64.8080853037536 2.93981486053927 -64.8326100930572 2.83564819225245 -64.8571348823607 2.9427083791028 -64.8816596716642 2.87037041501472 -64.9061844609678 2.73148152396563 -64.9307092502713 2.85011578507007 -64.9552340395749 2.98900467611916 -64.9797588288784 2.86458337788768 -65.0042836181819 2.76909726529142 -65.0288084074855 2.96006949048394 -65.053333196789 2.76041670960085 -65.0778579860926 2.6273148556788 -65.1023827753961 2.82696763656188 -65.1269075646997 2.67071763413164 -65.1514323540032 2.69675930120335 -65.1759571433067 2.83854171081597 -65.2004819326103 2.74305559821972 -65.2250067219138 2.75462967247381 -65.2495315112174 2.91666671203109 -65.2740563005209 2.55787041015425 -65.2985810898244 2.589699114353 -65.323105879128 2.50000003888379 -65.3476306684315 2.57812504009891 -65.3721554577351 2.6273148556788 -65.3966802470386 2.68229170838574 -65.4212050363421 2.59259263291653 -65.4457298256457 2.52893522451902 -65.4702546149492 2.47106485324856 -65.4947794042528 2.41030096341458 -65.5193041935563 2.39004633346992 -65.5438289828599 2.55497689159073 -65.5683537721634 2.44212966761333 -65.5928785614669 2.4508102233039 -65.6174033507705 2.50868059457436 -65.641928140074 2.37557874065231 -65.6664529293776 2.26851855380196 -65.6909777186811 2.25115744242082 -65.7155025079846 2.4247685562322 -65.7400272972882 2.36979170352526 -65.7645520865917 2.27430559092901 -65.7890768758953 2.3234954065089 -65.8136016651988 2.46238429755799 -65.8381264545023 2.10648151424468 -65.8626512438059 2.23379633103968 -65.8871760331094 2.2106481825315 -65.911700822413 2.08622688430002 -65.9362256117165 2.31481485081833 -65.9607504010201 2.10069447711763 -65.9852751903236 2.29166670231014 -66.0097999796271 2.27430559092901 -66.0343247689307 2.25405096098435 -66.0588495582342 2.32638892507242 -66.0833743475378 2.3119213322548 -66.1078991368413 2.21932873822207 -66.1324239261448 2.2106481825315 -66.1569487154484 2.33506948076299 -66.1814735047519 2.18171299689627 -66.2059982940555 2.22222225678559 -66.230523083359 2.21932873822207 -66.2550478726625 2.19328707115036 -66.2795726619661 2.08333336573649 -66.3040974512696 2.28009262805605 -66.3286222405732 2.25983799811139 -66.3531470298767 2.19618058971389 -66.3776718191803 2.25694447954787 -66.4021966084838 2.10648151424468 -66.4267213977873 2.04861114297422 -66.4512461870909 2.15277781126104 -66.4757709763944 2.11516206993525 -66.500295765698 2.26562503523844 -66.5248205550015 2.22222225678559 -66.549345344305 2.08912040286354 -66.5738701336086 2.0341435501566 -66.5983949229121 2.11805558849877 -66.6229197122157 2.11226855137172 -66.6474445015192 2.07754632860945 -66.6719692908227 2.07754632860945 -66.6964940801263 2.16724540407866 -66.7210188694298 2.0081018830849 -66.7455436587334 2.14988429269752 -66.7700684480369 2.15277781126104 -66.7945932373405 2.03703706872013 -66.819118026644 2.09490743999059 -66.8436428159475 2.0081018830849 -66.8681676052511 2.16145836695161 -66.8926923945546 2.08333336573649 -66.9172171838582 2.01388892021194 -66.9417419731617 2.18750003402332 -66.9662667624652 2.22800929391264 -66.9907915517688 2.19618058971389 -67.0153163410723 2.06597225435536 -67.0398411303759 2.10648151424468 -67.0643659196794 2.16435188551513 -67.0888907089829 2.06307873579183 -67.1134154982865 2.22511577534912 -67.13794028759 2.09780095855411 -67.1624650768936 2.03993058728365 -67.1869898661971 2.08622688430002 -67.2115146555007 2.12384262562581 -67.2360394448042 1.99074077170376 -67.2605642341077 2.08622688430002 -67.2850890234113 2.08912040286354 -67.3096138127148 2.06886577291888 -67.3341386020184 2.01388892021194 -67.3586633913219 2.20196762684093 -67.3831881806254 2.14988429269752 -67.407712969929 2.16435188551513 -67.4322377592325 1.99942132739433 -67.4567625485361 2.15277781126104 -67.4812873378396 1.92129632617921 -67.5058121271431 2.14120373700695 -67.5303369164467 1.9704861417591 -67.5548617057502 2.16145836695161 -67.5793864950538 1.94444447468739 -67.6039112843573 2.13252318131638 -67.6284360736609 2.09490743999059 -67.6529608629644 2.08912040286354 -67.6774856522679 2.01678243877547 -67.7020104415715 2.19328707115036 -67.726535230875 1.97337966032262 -67.7510600201786 2.02256947590251 -67.7755848094821 2.18171299689627 -67.8001095987856 2.14409725557047 -67.8246343880892 2.13252318131638 -67.8491591773927 2.08333336573649 -67.8736839666963 2.16145836695161 -67.8982087559998 2.16724540407866 -67.9227335453033 2.08043984717297 -67.9472583346069 2.11516206993525 -67.9717831239104 2.28877318374662 -67.996307913214 2.12094910706229 -68.0208327025175 2.05439818010127 -68.0453574918211 1.99074077170376 -68.0698822811246 2.0341435501566 -68.0944070704281 2.06307873579183 -68.1189318597317 2.2482639238573 -68.1434566490352 2.13252318131638 -68.1679814383388 2.12384262562581 -68.1925062276423 2.01388892021194 -68.2170310169458 2.17013892264218 -68.2415558062494 2.2858796651831 -68.2660805955529 2.15567132982457 -68.2906053848565 2.13831021844343 -68.31513017416 2.12384262562581 -68.3396549634635 2.24537040529378 -68.3641797527671 2.07754632860945 -68.3887045420706 2.11516206993525 -68.4132293313742 2.30324077656423 -68.4377541206777 2.1730324412057 -68.4622789099813 2.11516206993525 -68.4868036992848 2.21354170109502 -68.5113284885883 2.1846065154598 -68.5358532778919 2.25115744242082 -68.5603780671954 2.26562503523844 -68.584902856499 2.25983799811139 -68.6094276458025 2.25694447954787 -68.633952435106 2.17592595976923 -68.6584772244096 2.05729169866479 -68.6830020137131 2.16724540407866 -68.7075268030167 2.22800929391264 -68.7320515923202 2.146990774134 -68.7565763816237 2.08043984717297 -68.7811011709273 2.14988429269752 -68.8056259602308 2.23668984960321 -68.8301507495344 2.20196762684093 -68.8546755388379 2.09780095855411 -68.8792003281415 2.2482639238573 -68.903725117445 2.23379633103968 -68.9282499067485 2.33217596219946 -68.9527746960521 2.33506948076299 -68.9772994853556 2.15277781126104 -69.0018242746592 2.40162040772401 -69.0263490639627 2.27141207236548 -69.0508738532662 2.28877318374662 -69.0753986425698 2.23090281247616 -69.0999234318733 2.22222225678559 -69.1244482211769 2.22800929391264 -69.1489730104804 2.25983799811139 -69.1734977997839 2.24247688673025 -69.1980225890875 2.27430559092901 -69.222547378391 2.34375003645356 -69.2470721676946 2.27430559092901 -69.2715969569981 2.38136577777935 -69.2961217463017 2.34375003645356 -69.3206465356052 2.32928244363594 -69.3451713249087 2.18750003402332 -69.3696961142123 2.39583337059697 -69.3942209035158 2.24537040529378 -69.4187456928194 2.35532411070765 -69.4432704821229 2.31481485081833 -69.4677952714264 2.27141207236548 -69.49232006073 2.28877318374662 -69.5168448500335 2.34375003645356 -69.5413696393371 2.46238429755799 -69.5658944286406 2.33796299932651 -69.5904192179441 2.26851855380196 -69.6149440072477 2.36689818496174 -69.6394687965512 2.3119213322548 -69.6639935858548 2.25983799811139 -69.6885183751583 2.42187503766867 -69.7130431644619 2.43344911192277 -69.7375679537654 2.43344911192277 -69.7620927430689 2.37268522208878 -69.7866175323725 2.29166670231014 -69.811142321676 2.20486114540446 -69.8356671109796 2.4247685562322 -69.8601919002831 2.36979170352526 -69.8847166895866 2.14120373700695 -69.9092414788902 2.25405096098435 -69.9337662681937 2.23958336816673 -69.9582910574973 2.41319448197811 -69.9828158468008 2.33796299932651 -70.0073406361043 2.32928244363594 -70.0318654254079 2.17881947833275 -70.0563902147114 2.2482639238573 -70.080915004015 2.1093750328082 -70.1054397933185 2.08622688430002 -70.1299645826221 2.23668984960321 -70.1544893719256 2.21932873822207 -70.1790141612291 2.08043984717297 -70.2035389505327 2.13252318131638 -70.2280637398362 2.25115744242082 -70.2525885291398 2.36979170352526 -70.2771133184433 2.19907410827741 -70.3016381077468 2.28298614661957 -70.3261628970504 2.07465281004592 -70.3506876863539 2.23379633103968 -70.3752124756575 2.13252318131638 -70.399737264961 2.30034725800071 -70.4242620542645 2.22222225678559 -70.4487868435681 2.26562503523844 -70.4733116328716 2.0341435501566 -70.4978364221752 2.11226855137172 -70.5223612114787 2.25694447954787 -70.5468860007823 2.0457176244107 -70.5714107900858 2.09780095855411 -70.5959355793893 2.146990774134 -70.6204603686929 2.18171299689627 -70.6449851579964 2.09780095855411 -70.6695099473 2.28877318374662 -70.6940347366035 2.00520836452138 -70.718559525907 2.14120373700695 -70.7430843152106 2.04282410584717 -70.7676091045141 2.17013892264218 -70.7921338938177 2.08043984717297 -70.8166586831212 2.1093750328082 -70.8411834724247 2.12962966275286 -70.8657082617283 2.13831021844343 -70.8902330510318 2.08912040286354 -70.9147578403354 2.03125003159308 -70.9392826296389 2.05150466153774 -70.9638074189425 2.07465281004592 -70.988332208246 2.16724540407866 -71.0128569975495 2.12384262562581 -71.0373817868531 2.06018521722831 -71.0619065761566 1.91261577048864 -71.0864313654602 2.02256947590251 -71.1109561547637 2.01099540164842 -71.1354809440672 2.01678243877547 -71.1600057333708 2.20486114540446 -71.1845305226743 2.23668984960321 -71.2090553119779 1.8692129920358 -71.2335801012814 1.92418984474273 -71.2581048905849 2.05150466153774 -71.2826296798885 1.97916669744967 -71.307154469192 2.11805558849877 -71.3316792584956 2.02835651302956 -71.3562040477991 1.96180558606853 -71.3807288371027 1.91550928905217 -71.4052536264062 2.06307873579183 -71.4297784157097 1.99074077170376 -71.4543032050133 1.99942132739433 -71.4788279943168 1.99942132739433 -71.5033527836204 1.92418984474273 -71.5278775729239 1.91261577048864 -71.5524023622274 1.92997688186978 -71.576927151531 2.03703706872013 -71.6014519408345 2.10358799568115 -71.6259767301381 2.0081018830849 -71.6505015194416 2.13831021844343 -71.6750263087451 2.00231484595785 -71.6995510980487 2.09201392142706 -71.7240758873522 1.91550928905217 -71.7486006766558 2.08912040286354 -71.7731254659593 2.17013892264218 -71.7976502552629 2.03125003159308 -71.8221750445664 2.13541669987991 -71.8466998338699 2.10358799568115 -71.8712246231735 2.12673614418934 -71.895749412477 2.12673614418934 -71.9202742017806 2.0341435501566 -71.9447989910841 1.87789354772637 -71.9693237803876 2.05439818010127 -71.9938485696912 2.08912040286354 -72.0183733589947 2.0341435501566 -72.0428981482983 2.0717592914824 -72.0674229376018 2.12094910706229 -72.0919477269053 1.99942132739433 -72.1164725162089 2.03993058728365 -72.1409973055124 1.99363429026728 -72.165522094816 2.20196762684093 -72.1900468841195 2.17013892264218 -72.2145716734231 2.06018521722831 -72.2390964627266 2.06018521722831 -72.2636212520301 2.08043984717297 -72.2881460413337 2.10648151424468 -72.3126708306372 2.12962966275286 -72.3371956199408 2.21643521965855 -72.3617204092443 2.05150466153774 -72.3862451985478 2.06597225435536 -72.4107699878514 2.01967595733899 -72.4352947771549 1.95891206750501 -72.4598195664585 2.1730324412057 -72.484344355762 2.28009262805605 -72.5088691450655 2.11805558849877 -72.5333939343691 2.06307873579183 -72.5579187236726 2.0717592914824 -72.5824435129762 2.16145836695161 -72.6069683022797 2.17881947833275 -72.6314930915833 2.00520836452138 -72.6560178808868 2.11516206993525 -72.6805426701903 2.05150466153774 -72.7050674594939 2.14120373700695 -72.7295922487974 2.24537040529378 -72.754117038101 2.15856484838809 -72.7786418274045 2.19039355258684 -72.803166616708 2.24537040529378 -72.8276914060116 2.26562503523844 -72.8522161953151 2.1846065154598 -72.8767409846187 2.14409725557047 -72.9012657739222 1.99363429026728 -72.9257905632257 2.25405096098435 -72.9503153525293 2.146990774134 -72.9748401418328 2.14988429269752 -72.9993649311364 2.12094910706229 -73.0238897204399 2.08043984717297 -73.0484145097435 2.09490743999059 -73.072939299047 2.21643521965855 -73.0974640883505 2.06018521722831 -73.1219888776541 2.3234954065089 -73.1465136669576 2.20486114540446 -73.1710384562612 2.05729169866479 -73.1955632455647 2.18171299689627 -73.2200880348682 2.09490743999059 -73.2446128241718 2.11516206993525 -73.2691376134753 2.08043984717297 -73.2936624027789 2.2482639238573 -73.3181871920824 2.1093750328082 -73.3427119813859 2.18171299689627 -73.3672367706895 2.13831021844343 -73.391761559993 2.08043984717297 -73.4162863492966 2.01678243877547 -73.4408111386001 2.19039355258684 -73.4653359279037 2.16724540407866 -73.4898607172072 2.02835651302956 -73.5143855065107 2.19618058971389 -73.5389102958143 2.18171299689627 -73.5634350851178 2.08043984717297 -73.5879598744214 2.21643521965855 -73.6124846637249 2.12962966275286 -73.6370094530284 2.12962966275286 -73.661534242332 2.25694447954787 -73.6860590316355 2.09490743999059 -73.7105838209391 2.10358799568115 -73.7351086102426 2.23379633103968 -73.7596333995461 2.0341435501566 -73.7841581888497 2.1846065154598 -73.8086829781532 2.3234954065089 -73.8332077674568 2.15277781126104 -73.8577325567603 2.34085651789003 -73.8822573460639 2.15856484838809 -73.9067821353674 2.38136577777935 -73.9313069246709 2.20196762684093 -73.9558317139745 2.16145836695161 -73.980356503278 2.17013892264218 -74.0048812925816 2.15856484838809 -74.0294060818851 2.1846065154598 -74.0539308711886 2.20486114540446 -74.0784556604922 2.1846065154598 -74.1029804497957 2.17013892264218 -74.1275052390993 2.32928244363594 -74.1520300284028 2.00520836452138 -74.1765548177063 2.39293985203345 -74.2010796070099 2.46527781612152 -74.2256043963134 2.16145836695161 -74.250129185617 2.21354170109502 -74.2746539749205 2.20775466396798 -74.2991787642241 2.15856484838809 -74.3237035535276 2.2858796651831 -74.3482283428311 2.23090281247616 -74.3727531321347 2.20775466396798 -74.3972779214382 2.18750003402332 -74.4218027107418 2.11226855137172 -74.4463275000453 2.03125003159308 -74.4708522893488 2.27141207236548 -74.4953770786524 2.19907410827741 -74.5199018679559 2.22800929391264 -74.5444266572595 2.17013892264218 -74.568951446563 2.13831021844343 -74.5934762358665 2.09201392142706 -74.6180010251701 2.12962966275286 -74.6425258144736 2.23090281247616 -74.6670506037772 2.14409725557047 -74.6915753930807 2.11516206993525 -74.7161001823843 2.14409725557047 -74.7406249716878 2.1093750328082 -74.7651497609913 1.98784725314024 -74.7896745502949 2.12384262562581 -74.8141993395984 2.21354170109502 -74.838724128902 2.08333336573649 -74.8632489182055 2.05150466153774 -74.887773707509 2.05729169866479 -74.9122984968126 1.92129632617921 -74.9368232861161 2.08912040286354 -74.9613480754197 2.02546299446604 -74.9858728647232 2.04282410584717 -75.0103976540267 2.06151326082435 -75.0349224433303 2.06120812775158 -75.0594472326338 1.99637714930928 -75.0839720219374 2.00717504177744 -75.1084968112409 1.99755071935408 -75.1330216005445 1.90577197198767 -75.157546389848 1.95753873096915 -75.1820711791515 2.01454066526758 -75.2065959684551 2.03318285799849 -75.2311207577586 2.00398950153676 -75.2556455470622 2.02830192272385 -75.2801703363657 1.95351312127093 -75.3046951256692 1.96588960010218 -75.3292199149728 1.98623100189676 -75.3537447042763 1.92340055156119 -75.3782694935799 1.92785573106247 -75.4027942828834 1.95970136068656 -75.4273190721869 1.90640679161797 -75.4518438614905 1.94284133483953 -75.476368650794 1.95397654192466 -75.5008934400976 1.89158495875804 -75.5254182294011 1.94313717054641 -75.5499430187047 1.91798086587206 -75.5744678080082 1.87041111556537 -75.5989925973117 1.91573909236417 -75.6235173866153 1.94339701067935 -75.6480421759188 1.91284938783037 -75.6725669652224 1.88644418335047 -75.6970917545259 1.87732642979738 -75.7216165438294 1.90129605303133 -75.746141333133 1.89456143514177 -75.7706661224365 1.81317937795493 -75.7951909117401 1.84208619607567 -75.8197157010436 1.887054449496 -75.8442404903471 1.91745522685318 -75.8687652796507 1.93152543141194 -75.8932900689542 1.89712526714776 -75.9178148582578 1.8771419194252 -75.9423396475613 1.90001878571128 -75.9668644368649 1.86122566031096 -75.9913892261684 1.90736748377609 -76.0159140154719 1.90995586926685 -76.0404388047755 1.88802276971356 -76.064963594079 1.94171854107072 -76.0894883833826 1.88117897121258 -76.1140131726861 1.88379357949455 -76.1385379619896 1.85650301261729 -76.1630627512932 1.88904568023683 -76.1875875405967 1.86851214001062 -76.2121123299003 1.90902521392966 -76.2366371192038 1.9056966422269 -76.2611619085073 1.90792697364563 -76.2856866978109 1.90856560730565 -76.3102114871144 1.89367225871469 -76.334736276418 1.89828572579694 -76.3592610657215 1.86236156547538 -76.3837858550251 1.82612297471516 -76.4083106443286 1.89708378823765 -76.4328354336321 1.90897062362395 -76.4573602229357 1.89552594132961 -76.4818850122392 1.93961812971847 -76.5064098015428 1.92621492633423 -76.5309345908463 1.9023189635546 -76.5554593801498 1.8835430367275 -76.5799841694534 1.91277405806961 -76.6045089587569 1.91507971924909 -76.6290337480605 1.92025267620089 -76.653558537364 1.91765499334424 -76.6780833266675 1.89868692808552 -76.7026081159711 1.89722133636357 -76.7271329052746 1.93774752167822 -76.7516576945782 1.92914992380785 -76.7761824838817 1.93082457938676 -76.8007072731853 1.99708729870035 -76.8252320624888 1.91805619563282 -76.8497568517923 1.93873658135083 -76.8742816410959 1.94060337536136 -76.8988064303994 1.92114566665769 -76.923331219703 1.90111321196559 -76.9478560090065 1.94595154531945 -76.97238079831 1.94775993499455 -76.9969055876136 1.98547555956588 -77.0214303769171 1.94045652986956 -77.0459551662207 1.92763903914608 -77.0704799555242 1.9669332500805 -77.0950047448277 2.0010244672422 -77.1195295341313 2.05605591025484 -77.1440543234348 1.96749655397977 -77.1685791127384 2.01043591177888 -77.1931039020419 1.97010186489585 -77.2176286913455 1.96955930045164 -77.242153480649 2.01903732367897 -77.2666782699525 1.96468217920673 -77.2912030592561 1.99012287749879 -77.3157278485596 1.94909560665003 -77.3402526378632 1.98464097344451 -77.3647774271667 2.000660929834 -77.3893022164702 1.97287476075879 -77.4138270057738 2.0445025754558 -77.4383517950773 1.96495727545855 -77.4628765843809 1.96521330156177 -77.4874013736844 2.02395592383398 -77.5119261629879 1.99616427142261 -77.5364509522915 1.99535042475629 -77.560975741595 1.93653628675304 -77.5855005308986 2.02887070995928 -77.6100253202021 2.00343382569694 -77.6345501095057 2.06979261422634 -77.6590748988092 2.09430647121094 -77.6835996881127 2.08227660436256 -77.7081244774163 2.01687469396156 -77.7326492667198 2.07583400815016 -77.7571740560234 2.03480292327168 -77.7816988453269 1.98483692590585 -77.8062236346304 2.05092061818343 -77.830748423934 2.05396312696203 -77.8552732132375 1.98596901704054 -77.8797980025411 2.07671937054752 -77.9043227918446 2.06538821100104 -77.9288475811481 2.1193290417891 -77.9533723704517 2.02053509694513 -77.9778971597552 1.97572680041221 -78.0024219490588 2.10202252071367 -78.0269467383623 2.07769698813097 -78.0514715276659 2.12170454939318 -78.0759963169694 2.06466113618465 -78.1005211062729 2.09217220900803 -78.1250458955765 1.98013120363751 -78.14957068488 2.00250317035975 -78.1740954741836 2.06907865080555 -78.1986202634871 2.14066117256274 -78.2231450527906 2.15332037570526 -78.2476698420942 2.06566712128259 -78.2721946313977 2.06626046200279 -78.2967194207013 2.10638163099914 -78.3212442100048 2.08188851346959 -78.3457689993083 2.02642750065388 -78.3702937886119 2.13674855750567 -78.3948185779154 2.11970950462262 -78.419343367219 2.1301891526224 -78.4438681565225 2.08980051543367 -78.4683929458261 2.04819301526031 -78.4929177351296 2.03681274874429 -78.5174425244331 2.1170741568856 -78.5419673137367 2.11147925819018 -78.5664921030402 2.13447293314712 -78.5910168923438 2.11617353836935 -78.6155416816473 2.19415932215212 -78.6400664709508 2.16147910640666 -78.6645912602544 2.13067712676091 -78.6891160495579 2.08616466593486 -78.7136408388615 2.0586857746557 -78.738165628165 2.08865316818009 -78.7626904174685 2.11973024407767 -78.7872152067721 2.19257310772958 -78.8117399960756 2.12078152211544 -78.8362647853792 2.15784707093758 -78.8607895746827 2.09930802918616 -78.8853143639863 2.13858150066554 -78.9098391532898 2.11093669374596 -78.9343639425933 2.12284045455759 -78.9588887318969 2.10130855729288 -78.9834135212004 2.18425990347693 -79.007938310504 2.13545817879001 -79.0324630998075 2.03989292240327 -79.056987889111 2.1012708924125 -79.0815126784146 2.1143312978436 -79.1060374677181 2.11208022696982 -79.1305622570217 2.08479514342872 -79.1550870463252 2.08577109170573 -79.1796118356287 2.05784737450461 -79.2041366249323 2.0505401553499 -79.2286614142358 2.05684901746611 -79.2531862035394 2.05794725775014 -79.2777109928429 2.05757990631222 -79.3022357821465 2.06299959200135 -79.32676057145 2.04737916859399 -79.3512853607535 2.01130434875091 -79.3758101500571 2.11029424605622 -79.4003349393606 2.13208431414743 -79.4248597286642 2.03679033998279 -79.4493845179677 2.01627753921163 -79.4739093072712 2.03952938499507 -79.4984340965748 2.03850266044208 -79.5229588858783 2.04278262693707 -79.5474836751819 2.00509536988023 -79.5720084644854 1.99347981671604 -79.5965332537889 2.02305363601129 -79.6210580430925 1.97615637021528 -79.645582832396 1.99924611438804 -79.6701076216996 1.9863308851423 -79.6946324110031 1.98905467403569 -79.7191572003067 2.05947125380753 -79.7436819896102 1.96518326474084 -79.7682067789137 1.95451910636887 -79.7927315682173 1.94453672987348 -79.8172563575208 1.98548867096148 -79.8417811468244 1.96681095807347 -79.8663059361279 1.88740487550858 -79.8908307254314 1.9122860839311 -79.915355514735 1.93959739026342 -79.9398803040385 1.93901167760266 -79.9644050933421 1.89573500518656 -79.9889298826456 1.88011076774949 -80.0134546719491 1.89464057893225 -80.0379794612527 1.92023575077556 -80.0625042505562 1.82920863171031 -80.0870290398598 1.83524239757469 -80.1115538291633 1.84916742594809 -80.1360786184669 1.91562228369331 -80.1606034077704 1.89099161803784 -80.1851281970739 1.8819229714543 -80.2096529863775 1.90436478460114 -80.234177775681 1.84612706189277 -80.2587025649846 1.7675763049043 -80.2832273542881 1.76040830318195 -80.3077521435916 1.95194383227373 -80.3322769328952 1.87385101260282 -80.3568017221987 1.81342229266254 -80.3813265115023 1.87586846613487 -80.4058513008058 1.92644472964623 -80.4303760901093 1.88609375733788 -80.4549008794129 1.83867848058243 -80.4794256687164 1.82751323667635 -80.50395045802 1.82908419498 -80.5284752473235 1.86145546362297 -80.5530000366271 1.87428987977178 -80.5775248259306 1.85349268538291 -80.6020496152341 1.79677895873191 -80.6265744045377 1.82069947499631 -80.6510991938412 1.8561111076946 -80.6756239831448 1.81961816013761 -80.7001487724483 1.8702528279844 -80.7246735617518 1.82438610077111 -80.7491983510554 1.80396936921576 -80.7737231403589 1.84894525069553 -80.7982479296625 1.85252055113562 -80.822772718966 1.84403976193612 -80.8472975082695 1.85858649854422 -80.8718222975731 1.87284121347516 -80.8963470868766 1.79596892609532 -80.9208718761802 1.78406897931341 -80.9453966654837 1.83973524195636 -80.9699214547873 1.8118644457545 -80.9944462440908 1.83134289391322 -81.0189710333943 1.83668724984159 -81.0434958226979 1.82552581996524 -81.0680206120014 1.78130919484608 -81.092545401305 1.83091713813987 -81.1170701906085 1.866323287502 -81.141594979912 1.8074469311336 -81.1661197692156 1.81736327523411 -81.1906445585191 1.80715490945644 -81.2151693478227 1.79050776149609 -81.2396941371262 1.87071243460841 -81.2642189264297 1.90345486871901 -81.2887437157333 1.88748401929907 -81.3132685050368 1.87258685667838 -81.3377932943404 1.8557060913763 -81.3623180836439 1.85957555821683 -81.3868428729475 1.85042013978337 -81.411367662251 1.85083659819083 -81.4358924515545 1.79551861683719 -81.4604172408581 1.83991045496265 -81.4849420301616 1.80534651978134 -81.5094668194652 1.79905458309046 -81.5339916087687 1.85132624163578 -81.5585163980722 1.90543847140041 -81.5830411873758 1.82863221641544 -81.6075659766793 1.84882462799495 -81.6320907659829 1.81896260105226 -81.6566155552864 1.78632386421691 -81.6811403445899 1.81444901721554 -81.7056651338935 1.84476683675252 -81.730189923197 1.84583504021561 -81.7547147125006 1.78260338759146 -81.7792395018041 1.88910956790843 -81.8037642911077 1.90373377900056 -81.8282890804112 1.81820715872137 -81.8528138697147 1.80787268690695 -81.8773386590183 1.8369170531536 -81.9018634483218 1.82137577353672 -81.9263882376254 1.8217769758253 -81.9509130269289 1.84379303319879 -81.9754378162324 1.80814015509933 -81.999962605536 1.80511290243962 -82.0244873948395 1.83457921042989 -82.0490121841431 1.84111239252194 -82.0735369734466 1.82169783203481 -82.0980617627501 1.84905824533667 -82.1225865520537 1.83063274452216 -82.1471113413572 1.78636152909729 -82.1716361306608 1.84722148814708 -82.1961609199643 1.79608573476618 -82.2206857092679 1.77911271427801 -82.2452104985714 1.77611549843923 -82.2697352878749 1.85308385503489 -82.2942600771785 1.78875777615642 -82.318784866482 1.81828630251185 -82.3433096557856 1.87386030996871 -82.3678344450891 1.81224872261775 -82.3923592343926 1.80285801753612 -82.4168840236962 1.78644067288777 -82.4414088129997 1.80840762329172 -82.4659336023033 1.85175581143885 -82.4904583916068 1.82044511819954 -82.5149831809103 1.83084562240883 -82.5395079702139 1.83447384384819 -82.5640327595174 1.79899617875503 -82.588557548821 1.80584760531545 -82.6130823381245 1.84085040766572 -82.6376071274281 1.82753016210168 -82.6621319167316 1.78869555779127 -82.6866567060351 1.79699183661859 -82.7111814953387 1.74934675655113 -82.7357062846422 1.80337221446584 -82.7602310739458 1.82638662887783 -82.7847558632493 1.81525904985214 -82.8092806525528 1.79216930567938 -82.8338054418564 1.82945536044764 -82.8583302311599 1.8047870299118 -82.8828550204635 1.78443251672161 -82.907379809767 1.83134289391322 -82.9319045990705 1.84377610777346 -82.9564293883741 1.77021165264132 -82.9809541776776 1.84426956524813 -83.0054789669812 1.81882505292634 -83.0300037562847 1.84539617304665 -83.0545285455883 1.80273358080581 -83.0790533348918 1.80918380507765 -83.1035781241953 1.76884594416491 -83.1281029134989 1.83069496288731 -83.1526277028024 1.84306762768884 -83.177152492106 1.85196868932552 -83.2016772814095 1.82388501523699 -83.226202070713 1.81392337819666 -83.2507268600166 1.81646098741142 -83.2752516493201 1.78986912783606 -83.2997764386237 1.82085394854756 -83.3243012279272 1.73869570957478 -83.3488260172307 1.78445325617666 -83.3733508065343 1.82035286301345 -83.3978755958378 1.74918846897017 -83.4224003851414 1.83656662714101 -83.4469251744449 1.81507453947995 -83.4714499637485 1.77888839430217 -83.495974753052 1.82859455153506 -83.5204995423555 1.79473695171509 -83.5450243316591 1.74272513330272 -83.5695491209626 1.76653265492598 -83.5940739102662 1.82404878615412 -83.6185986995697 1.84517948113026 -83.6431234888732 1.82334245079278 -83.6676482781768 1.82931781232173 -83.6921730674803 1.78273712168765 -83.7166978567839 1.78694175842188 -83.7412226460874 1.78061597088034 -83.7657474353909 1.75406940424481 -83.7902722246945 1.75143024247806 -83.814797013998 1.75163930633501 -83.8393218033016 1.82480041445529 -83.8638465926051 1.7915383000788 -83.8883713819087 1.72980227599754 -83.9128961712122 1.74866664398101 -83.9374209605157 1.78308373367052 -83.9619457498193 1.75056180550603 -83.9864705391228 1.80068775975927 -84.0109953284264 1.77953847005136 -84.0355201177299 1.79930893988724 -84.0600449070334 1.77450854456165 -84.084569696337 1.83217748003459 -84.1090944856405 1.82953831826785 -84.1336192749441 1.82040745331916 -84.1581440642476 1.76791910285745 -84.1826688535511 1.78080810931197 -84.2071936428547 1.7284742324015 -84.2317184321582 1.7519067745274 -84.2562432214618 1.75607756041097 -84.2807680107653 1.72614020370752 -84.3052928000689 1.81284206333795 -84.3298175893724 1.79890392356894 -84.3543423786759 1.69799812528356 -84.3788671679795 1.7288508812053 -84.403391957283 1.77903357048753 -84.4279167465866 1.77345559721744 -84.4524415358901 1.77363462425345 -84.4769663251936 1.73200257059533 -84.5014911144972 1.7847960541298 -84.5260159038007 1.76209821487974 -84.5505406931043 1.73142234127073 -84.5750654824078 1.68753921673883 -84.5995902717113 1.70883368263209 -84.6241150610149 1.74413613471896 -84.6486398503184 1.79044935716066 -84.673164639622 1.7478434999488 -84.6976894289255 1.70129284613566 -84.7222142182291 1.76047433557682 -84.7467390075326 1.71946399015339 -84.7712637968361 1.68620735911307 -84.7957885861397 1.71291769666574 -84.8203133754432 1.74267435702673 -84.8448381647468 1.73331368876603 -84.8693629540503 1.72413204754135 -84.8938877433538 1.75908788764536 -84.9184125326574 1.69274221051156 -84.9429373219609 1.82662024621955 -84.9674621112645 1.72573518738922 -84.991986900568 1.71753879180743 -85.0165116898716 1.71747275941256 -85.0410364791751 1.70457064156243 -85.0655612684786 1.77523013604188 -85.0900860577822 1.76192300187345 -85.1146108470857 1.8108246098059 -85.1391356363893 1.71277466520366 -85.1636604256928 1.73739770279968 -85.1881852149963 1.75106670506986 -85.2127100042999 1.70880912914732 -85.2372347936034 1.7681696456245 -85.261759582907 1.73528799408154 -85.2862843722105 1.67729318608078 -85.310809161514 1.72733069917764 -85.3353339508176 1.75340073376384 -85.3598587401211 1.70513013143197 -85.3843835294247 1.71293843612079 -85.4089083187282 1.78268253138194 -85.4334331080318 1.71463764518447 -85.4579578973353 1.80383563511957 -85.4824826866388 1.77928411325458 -85.5070074759424 1.76923022102815 -85.5315322652459 1.78273712168765 -85.5560570545495 1.78163506737389 -85.580581843853 1.80114188304711 -85.6051066331565 1.73418212573806 -85.6296314224601 1.76763089521001 -85.6541562117636 1.75897107897449 -85.6786810010672 1.76629522355453 -85.7032057903707 1.83298751267119 -85.7277305796742 1.78227370103392 -85.7522553689778 1.76311731137329 -85.7767801582813 1.73728852218826 -85.8013049475849 1.79535103189034 -85.8258297368884 1.74130102049087 -85.850354526192 1.75310489805696 -85.8748793154955 1.75322552075755 -85.899404104799 1.72908449854703 -85.9239288941026 1.79939738104361 -85.9484536834061 1.80378485884358 -85.9729784727097 1.79408139262974 -85.9975032620132 1.78092110395311 -86.0220280513167 1.77490044948434 -86.0465528406203 1.77874154881037 -86.0710776299238 1.79575604820864 -86.0956024192274 1.77396049678127 -86.1201272085309 1.8088917834005 -86.1446519978344 1.83793996367687 -86.169176787138 1.85393918061131 -86.1937015764415 1.81026511993636 -86.2182263657451 1.78782712081924 -86.2427511550486 1.72372703122306 -86.2672759443522 1.78468305948866 -86.2918007336557 1.78722996606932 -86.3163255229592 1.81535130503823 -86.3408503122628 1.81395174571115 -86.3653751015663 1.80057476511812 -86.3898998908699 1.83977672086646 -86.4144246801734 1.83783078306545 -86.4389494694769 1.75283361583486 -86.4634742587805 1.76500865886859 -86.487999048084 1.82513391504255 -86.5125238373876 1.85069142200547 -86.5370486266911 1.84743818006348 -86.5615734159946 1.81099219475275 -86.5860982052982 1.90069508425168 -86.6106229946017 1.79076593232259 -86.6351477839053 1.81938072876616 -86.6596725732088 1.82604001689496 -86.6841973625124 1.85696643327102 -86.7087221518159 1.846102508408 -86.7332469411194 1.91193947194824 -86.757771730423 1.88244479644347 -86.7822965197265 1.85978080804406 -86.8068213090301 1.82554274539057 -86.8313460983336 1.8796266076407 -86.8558708876371 1.78949629306198 -86.8803956769407 1.73197801711055 -86.9049204662442 1.83386906103883 -86.9294452555478 1.92104196938243 -86.9539700448513 1.87743775513208 -86.9784948341548 1.87549730066723 -87.0030196234584 1.90329658113805 -87.0275444127619 1.89842327392286 -87.0520692020655 1.8421860793212 -87.076593991369 1.88986882426904 -87.1011187806726 1.91707857804937 -87.1256435699761 1.81502161848069 -87.1501683592796 1.89907120494877 -87.1746931485832 1.93208492128148 -87.1992179378867 1.9078685693102 -87.2237427271903 1.87708351508977 -87.2482675164938 1.82224039647903 -87.2727923057973 1.86670375033552 -87.2973170951009 1.88048574724685 -87.3218418844044 1.85738074695521 -87.346366673708 1.95773849746021 -87.3708914630115 1.86472396168385 -87.395416252315 1.82054118741535 -87.4199410416186 1.86292868340436 -87.4444658309221 1.84596496028208 -87.4689906202257 1.85277872196212 -87.4935154095292 1.87835315435038 -87.5180401988328 1.85548391612374 -87.5425649881363 1.84092192339676 -87.5670897774398 1.9110994024907 -87.5916145667434 1.84990594285365 -87.6161393560469 1.85429508996006 -87.6406641453505 1.85949641442635 -87.665188934654 1.93357673579465 -87.6897137239575 1.86260281087655 -87.7142385132611 1.85790638597409 -87.7387633025646 1.87405244840033 -87.7632880918682 1.9401230292823 -87.7878128811717 1.87596834938041 -87.8123376704752 1.8924986886699 -87.8368624597788 1.90163503695475 -87.8613872490823 1.91297930789684 -87.8859120383859 1.88810191350404 -87.9104368276894 1.87205192029361 -87.934961616993 1.87671234962213 -87.9594864062965 1.87130029199245 -87.9840111956 1.95844483282156 -88.0085359849036 1.89128745374472 -88.0330607742071 1.81543044882871 -88.0575855635107 1.87626966842345 -88.0821103528142 1.83800218204202 -88.1066351421177 1.88431540448372 -88.1311599314213 1.86580146251284 -88.1556847207248 1.88433614393877 -88.1802095100284 1.84413583115193 -88.2047342993319 1.90216449000336 -88.2292590886354 1.80355124150185 -88.253783877939 1.83608628106195 -88.2783086672425 1.86901703957445 -88.3028334565461 1.82072784251081 -88.3273582458496 1.8200739527319 -88.3518830351532 1.85513349011115 -88.3764078244567 1.80505831213391 -88.4009326137602 1.8151591666066 -88.4254574030638 1.83550986576707 -88.4499821923673 1.86076605368696 -88.4745069816709 1.83176864968657 -88.4990317709744 1.81460349076678 -88.5235565602779 1.81165156786783 -88.5480813495815 1.88597313463729 -88.572606138885 1.89735888448948 -88.5971309281886 1.84822747324502 -88.6216557174921 1.83288762942566 -88.6461805067956 1.84059056753278 -88.6707052960992 1.79937664158856 -88.6952300854027 1.84616472677315 -88.7197548747063 1.8322320703403 -88.7442796640098 1.85688728948054 -88.7688044533134 1.85975625455929 -88.7933292426169 1.92504517031915 -88.8178540319204 1.81292502115816 -88.842378821224 1.72882084438437 -88.8669036105275 1.81070398710532 -88.8914283998311 1.80252833097858 -88.9159531891346 1.85001893749479 -88.9404779784381 1.81459967673706 -88.9650027677417 1.79331283890324 -88.9895275570452 1.74989861836123 -89.0140523463488 1.82420707373509 -89.0385771356523 1.80575153609964 -89.0631019249558 1.82656565591384 -89.0876267142594 1.75458741520425 -89.1121515035629 1.83053667530635 -89.1366762928665 1.79953874319924 -89.16120108217 1.82437847271166 -89.1857258714736 1.82629055966202 -89.2102506607771 1.86446960488708 -89.2347754500806 1.81593153436282 -89.2593002393842 1.84666962633698 -89.2838250286877 1.79027414415436 -89.3083498179913 1.85160133788761 -89.3328746072948 1.81128803045963 -89.3573993965983 1.79999834982325 -89.3819241859019 1.81809416408023 -89.4064489752054 1.76871602409844 -89.430973764509 1.78577748574297 -89.4554985538125 1.77801447399398 -89.480023343116 1.80439131095939 -89.5045481324196 1.86335062514799 -89.5290729217231 1.82008158079134 -89.5535977110267 1.79957640807963 -89.5781225003302 1.74859131422025 -89.6026472896338 1.84315058550904 -89.6271720789373 1.81793969052898 -89.6516968682408 1.79037784142962 -89.6762216575444 1.78194401447638 -89.7007464468479 1.80923839538336 -89.7252712361515 1.86608585613055 -89.749796025455 1.81367664945932 -89.7743208147585 1.81757615312079 -89.7988456040621 1.82522998425836 -89.8233703933656 1.83560593498289 -89.8478951826692 1.85858649854422 -89.8724199719727 1.83264090068832 -89.8969447612762 1.88641581583597 -89.9214695505798 1.87041111556537 -89.9459943398833 1.89891673139752 -89.9705191291869 1.8748870345217 -89.9950439184904 1.80424446546759 -90.019568707794 1.79382703583296 -90.0440934970975 1.81466189510221 -90.068618286401 1.84130071692384 -90.0931430757046 1.81684145024495 -90.1176678650081 1.84094647688153 -90.1421926543117 1.87426532628701 -90.1667174436152 1.91201861573872 -90.1912422329187 1.87123807362729 -90.2157670222223 1.91117473225146 -90.2402918115258 1.84747203091414 -90.2648166008294 1.91216546123052 -90.2893413901329 1.90654433974388 -90.3138661794364 1.88248627535357 -90.33839096874 1.84521714601064 -90.3629157580435 1.84762650446538 -90.3874405473471 1.86531348837433 -90.4119653366506 1.90989746493141 -90.4364901259542 1.83952236406968 -90.4610149152577 1.91917517537191 -90.4855397045612 1.88798510483318 -90.5100644938648 1.90836584081459 -90.5345892831683 1.93972182699372 -90.5591140724719 1.83813973016793 -90.5836388617754 1.94870370172734 -90.6081636510789 1.90731289347038 -90.6326884403825 1.91812771136386 -90.657213229686 1.92650694801139 -90.6817380189896 1.91018948660857 -90.7062628082931 1.91205246658938 -90.7307875975966 1.90885381495309 -90.7553123869002 1.92118333153807 -90.7798371762037 1.89789382087425 -90.8043619655073 1.90374689039617 -90.8288867548108 1.89816510309636 -90.8534115441144 1.90753888275266 -90.8779363334179 1.95640663983446 -90.9024611227214 1.94757161059264 -90.926985912025 1.85688728948054 -90.9515107013285 1.92368875920862 -90.9760354906321 1.88801895568384 -91.0005602799356 1.95242799238251 -91.0250850692391 1.87363813471615 -91.0496098585427 1.89746043704146 -91.0741346478462 2.00868592643921 -91.0986594371498 1.89232728969332 -91.1231842264533 1.85453252133151 -91.1477090157568 1.88489181977859 -91.1722338050604 1.94734180728064 -91.1967585943639 1.89922567850001 -91.2212833836675 1.93748386751555 -91.245808172971 1.93803572932565 -91.2703329622746 1.94340082470907 -91.2948577515781 1.96626839362926 -91.3193825408816 1.91147605129451 -91.3439073301852 1.92716250709674 -91.3684321194887 1.87200662735379 -91.3929569087923 1.87448583223313 -91.4174816980958 1.86997773173257 -91.4420064873993 1.97293697912394 -91.4665312767029 1.90590570608386 -91.4910560660064 1.93450357710211 -91.51558085531 1.93156309629232 -91.5401056446135 1.88082091714055 -91.564630433917 1.88504080999367 -91.5891552232206 1.88927166951911 -91.6136800125241 1.910758273844 -91.6382048018277 1.92155449700571 -91.6627295911312 1.84251576587874 -91.6872543804348 1.90209845760848 -91.7117791697383 1.91342198909552 -91.7363039590418 1.94257386664714 -91.7608287483454 1.94463661311902 -91.7853535376489 1.93850677803883 -91.8098783269525 1.87033197177488 -91.834403116256 1.87552185415201 -91.8589279055595 1.87569325312858 -91.8834526948631 1.92152994352094 -91.9079774841666 1.88308343010349 -91.9325022734702 1.92758063481065 -91.9570270627737 1.90953392752322 -91.9815518520772 1.87189363271265 -92.0060766413808 1.86095819211858 -92.0306014306843 1.92350973217261 -92.0551262199879 1.93491622147985 -92.0796510092914 1.87586846613487 -92.104175798595 1.8521400883021 -92.1287005878985 1.89454069568672 -92.153225377202 1.88074940140951 -92.1777501665056 1.86229004974434 -92.2022749558091 1.83497111535258 -92.2267997451127 1.93155928226259 -92.2513245344162 1.90061975449092 -92.2758493237197 1.87143021205892 -92.3003741130233 1.8480353348134 -92.3248989023268 1.89590640416314 -92.3494236916304 1.88001469853367 -92.3739484809339 1.88945069655513 -92.3984732702374 1.84805226023873 -92.422998059541 1.87588920558992 -92.4475228488445 1.86452038116307 -92.4720476381481 1.85854883366384 -92.4965724274516 1.88639889041064 -92.5210972167552 1.89357237546916 -92.5456220060587 1.84730444596728 -92.5701467953622 1.86037796279399 -92.5946715846658 1.85879556240117 -92.6191963739693 1.85160515191733 -92.6437211632729 1.8369585320637 -92.6682459525764 1.85239063106915 -92.6927707418799 1.85460785109227 -92.7172955311835 1.8806948111038 -92.741820320487 1.81091686499199 -92.7663451097906 1.85811926386077 -92.7908698990941 1.85497901655991 -92.8153946883976 1.83088328728922 -92.8399194777012 1.8035305020468 -92.8644442670047 1.82180152931007 -92.8889690563083 1.77798992050921 -92.9134938456118 1.74481243325936 -92.9380186349154 1.79540180816633 -92.9625434242189 1.88158017350116 -92.9870682135224 1.82038671386411 -93.011593002826 1.81289498433722 -93.0361177921295 1.72648300166066 -93.0606425814331 1.76639510680006 -93.0851673707366 1.81381038355551 -93.1096921600401 1.82683312410623 -93.1342169493437 1.82991878110137 -93.1587417386472 1.83447384384819 -93.1832665279508 1.77004406769447 -93.2077913172543 1.7639404554055 -93.2323161065578 1.79747599672737 -93.2568408958614 1.81485021950412 -93.2813656851649 1.82621522990125 -93.3058904744685 1.79124627840164 -93.330415263772 1.79793560335138 -93.3549400530756 1.82546360160009 -93.3794648423791 1.76518387187489 -93.4039896316826 1.77631145090058 -93.4285144209862 1.77318431499533 -93.4530392102897 1.82089161342794 -93.4775639995933 1.78892536110327 -93.5020887888968 1.76508780265908 -93.5266135782003 1.76459982852057 -93.5511383675039 1.76431924893258 -93.5756631568074 1.75096300779461 -93.600187946111 1.77318812902505 -93.6247127354145 1.72569370847912 -93.649237524718 1.77480438026852 -93.6737623140216 1.77625304656515 -93.6982871033251 1.7322569273921 -93.7228118926287 1.85012811810621 -93.7473366819322 1.8407412270543 -93.7718614712358 1.79358030709563 -93.7963862605393 1.76971438113694 -93.8209110498428 1.76612763860768 -93.8454358391464 1.81054951355407 -93.8699606284499 1.80497916834342 -93.8944854177535 1.74459955537269 -93.919010207057 1.74978562372009 -93.9435349963605 1.74626109955599 -93.9680597856641 1.76862925224851 -93.9925845749676 1.71140514269752 -94.0171093642712 1.75444986707833 -94.0416341535747 1.79014422408789 -94.0661589428782 1.76609378775702 -94.0906837321818 1.69882126931576 -94.1152085214853 1.74963496419857 -94.1397333107889 1.76955227952625 -94.1642581000924 1.75649020478871 -94.188782889396 1.74503842254165 -94.2133076786995 1.76624063324882 -94.237832468003 1.76776844333592 -94.2623572573066 1.73858271493364 -94.2868820466101 1.72955173323048 -94.3114068359137 1.70727583572405 -94.3359316252172 1.69177770432372 -94.3604564145207 1.76726735780181 -94.3849812038243 1.70908803942887 -94.4095059931278 1.72021180442484 -94.4340307824314 1.76265389071956 -94.4585555717349 1.75997325004271 -94.4830803610384 1.73449107284055 -94.507605150342 1.74224478722366 -94.5321299396455 1.74890026132273 -94.5566547289491 1.74278735166787 -94.5811795182526 1.73792334181856 -94.6057043075562 1.76255400747403 -94.6302290968597 1.79157215092946 -94.6547538861632 1.79097118214982 -94.6792786754668 1.79890392356894 -94.7038034647703 1.74152700977315 -94.7283282540739 1.76817345965422 -94.7528530433774 1.78163506737389 -94.7773778326809 1.8172257271082 -94.8019026219845 1.77303365547381 -94.826427411288 1.75862446699162 -94.8509522005916 1.79277957182491 -94.8754769898951 1.77006862117925 -94.9000017791986 1.7479903454406 -94.9245265685022 1.74254062293054 -94.9490513578057 1.71334726646881 -94.9735761471093 1.75042044335039 -94.9981009364128 1.73603962238271 -95.0226257257164 1.78022025192793 -95.0471505150199 1.79409831805507 -95.0716753043234 1.76981426438247 -95.096200093627 1.75823637609866 -95.1207248829305 1.76643277168044 -95.1452496722341 1.74608588654969 -95.1697744615376 1.76811124128907 -95.1942992508411 1.76582250553491 -95.2188240401447 1.76702611240064 -95.2433488294482 1.79692032088755 -95.2678736187518 1.76124288930332 -95.2923984080553 1.81627814634569 -95.3169231973588 1.7944280046126 -95.3414479866624 1.76358073202702 -95.3659727759659 1.75734719967158 -95.3904975652695 1.77135518586518 -95.415022354573 1.80507142352951 -95.4395471438766 1.803383656555 -95.4640719331801 1.76920566754338 -95.4885967224836 1.77770171286177 -95.5131215117872 1.81051566270341 -95.5376463010907 1.79349734927542 -95.5621710903943 1.81410240523267 -95.5866958796978 1.82929325883696 -95.6112206690013 1.8453754335916 -95.6357454583049 1.81894567562693 -95.6602702476084 1.81116740775905 -95.684795036912 1.82318797724154 -95.7093198262155 1.77330493769591 -95.733844615519 1.79737611348184 -95.7583694048226 1.780649821731 -95.7828941941261 1.8027073580146 -95.8074189834297 1.75882590278914 -95.8319437727332 1.8318139426264 -95.8564685620368 1.78174806201504 -95.8809933513403 1.81680378536457 -95.9055181406438 1.8016391545515 -95.9300429299474 1.77895442669705 -95.9545677192509 1.79118024600677 -95.9790925085545 1.79413979696517 -96.003617297858 1.79195261376299 -96.0281420871615 1.84355941585706 -96.0526668764651 1.8449289383632 -96.0771916657686 1.86014434545226 -96.1017164550722 1.7823113659143 -96.1262412443757 1.84785630777738 -96.1507660336792 1.82247401382075 -96.1752908229828 1.81522138497176 -96.1998156122863 1.79035710197457 -96.2243404015899 1.77858707525913 -96.2488651908934 1.83057434018673 -96.273389980197 1.84853260631779 -96.2979147695005 1.83343019386987 -96.322439558804 1.82935929123183 -96.3469643481076 1.85977699401434 -96.3714891374111 1.85187262010971 -96.3960139267147 1.81068706167999 -96.4205387160182 1.85161826331294 -96.4450635053217 1.85715857170265 -96.4695882946253 1.84892069721076 -96.4941130839288 1.80925913483842 -96.5186378732324 1.84723841357241 -96.5431626625359 1.89637745287631 -96.5676874518394 1.84714615838632 -96.592212241143 1.79874563598798 -96.6167370304465 1.87517524216914 -96.6412618197501 1.79995687091315 -96.6657866090536 1.80347591174109 -96.6903113983572 1.79446566949298 -96.7148361876607 1.8018689578635 -96.7393609769642 1.82291669501943 -96.7638857662678 1.81527597527747 -96.7884105555713 1.88467894189191 -96.8129353448749 1.88223573258651 -96.8374601341784 1.82041126734888 -96.8619849234819 1.83248642713708 -96.8865097127855 1.88190604602897 -96.911034502089 1.84432796958356 -96.9355592913926 1.84616472677315 -96.9600840806961 1.84276249461607 -96.9846088699996 1.84623624250419 -97.0091336593032 1.84603099267696 -97.0336584486067 1.79081670859857 -97.0581832379103 1.78732603528513 -97.0827080272138 1.7922653748952 -97.1072328165174 1.83616923888215 -97.1317576058209 1.796871213918 -97.1562823951244 1.8451041513695 -97.180807184428 1.82624908075191 -97.2053319737315 1.83788918740088 -97.2298567630351 1.81119196124382 -97.2543815523386 1.8134561435132 -97.2789063416421 1.84775094119569 -97.3034311309457 1.83437396060266 -97.3279559202492 1.80906699640679 -97.3524807095528 1.81537204449328 -97.3770054988563 1.79552624489664 -97.4015302881598 1.84415275657726 -97.4260550774634 1.86103352187934 -97.4505798667669 1.7790166450622 -97.4751046560705 1.79909224797084 -97.499629445374 1.80221938387609 -97.5241542346776 1.76894201338072 -97.5486790239811 1.7777186382871 -97.5732038132846 1.80293334729688 -97.5977286025882 1.79014041005817 -97.6222533918917 1.8820567055505 -97.6467781811953 1.85507508577572 -97.6713029704988 1.85003967694984 -97.6958277598023 1.85688347545082 -97.7203525491059 1.90501652965678 -97.7448773384094 1.80067846239338 -97.769402127713 1.82692537929232 -97.7939269170165 1.87553877957734 -97.81845170632 1.79659444835973 -97.8429764956236 1.7670883307658 -97.8675012849271 1.75983951594652 -97.8920260742307 1.77112156852346 -97.9165508635342 1.84442022476965 -97.9410756528378 1.81314171307455 -97.9656004421413 1.77715914841756 -97.9901252314448 1.82247401382075 -98.0146500207484 1.7908620015384 -98.0391748100519 1.77112156852346 -98.0636995993555 1.74523437500299 -98.088224388659 1.78325131861737 -98.1127491779625 1.79375552010192 -98.1372739672661 1.74242000022995 -98.1617987565696 1.738211549466 -98.1863235458732 1.83373151291292 -98.2108483351767 1.83048208500064 -98.2353731244802 1.83059507964178 -98.2598979137838 1.82401493530347 -98.2844227030873 1.78783093484896 -98.3089474923909 1.78981072350063 -98.3334722816944 1.81879120207568 -98.357997070998 1.76526301566537 -98.3825218603015 1.70854166095493 -98.407046649605 1.8133092980214 -98.4315714389086 1.73945496593539 -98.4560962282121 1.74125954158077 -98.4806210175157 1.77644137096705 -98.5051458068192 1.75100830073443 -98.5296705961227 1.75556884681742 -98.5541953854263 1.75391874472328 -98.5787201747298 1.75943449962822 -98.6032449640334 1.75686303956279 -98.6277697533369 1.84669966315792 -98.6522945426404 1.80735467594751 -98.676819331944 1.76043285666672 -98.7013441212475 1.77419411412299 -98.7258689105511 1.74829929254309 -98.7503936998546 1.76672860738732 -98.7749184891582 1.76527612706098 -98.7994432784617 1.81774373806764 -98.8239680677652 1.79518344694349 -98.8484928570688 1.78961858506901 -98.8730176463723 1.75201047180265 -98.8975424356759 1.73334753961669 -98.9220672249794 1.76084168701474 -98.9465920142829 1.76340933305045 -98.9711168035865 1.73306314599898 -98.99564159289 1.74664156238951 -99.0201663821936 1.73935126866014 -99.0446911714971 1.77798610647948 -99.0692159608006 1.81881194153073 -99.0937407501042 1.79093351726944 -99.1182655394077 1.77154732429681 -99.1427903287113 1.78289159523889 -99.1673151180148 1.77148510593165 -99.1918399073184 1.78827361604764 -99.2163646966219 1.77054515322858 -99.2408894859254 1.7459521524535 -99.265414275229 1.74540577397957 -99.2899390645325 1.80655394067679 -99.3144638538361 1.8494801870803 -99.3389886431396 1.79317529077733 -99.3635134324431 1.77052059974381 -99.3880382217467 1.77046982346782 -99.4125630110502 1.71624459906205 -99.4370878003538 1.75219498217483 -99.4616125896573 1.82301276423524 -99.4861373789608 1.74984784208524 -99.5106621682644 1.8101483112655 -99.5351869575679 1.75908407361563 -99.5597117468715 1.78082884876702 -99.584236536175 1.75557266084714 -99.6087613254786 1.83758953766428 -99.6332861147821 1.81646098741142 -99.6578109040856 1.72525484131016 -99.6823356933892 1.77580655133675 -99.7068604826927 1.75204432265331 -99.7313852719963 1.71645366291901 -99.7559100612998 1.80179362810274 -99.7804348506033 1.74350131508866 -99.8049596399069 1.77819898436616 -99.8294844292104 1.84082037084478 -99.854009218514 1.77032846131219 -99.8785340078175 1.8033006987348 -99.903058797121 1.78248276489087 -99.9275835864246 1.82324256754725 -99.9521083757281 1.83071188831264 -99.9766331650317 1.73555546227392 -100.001157954335 1.77771482425738 -100.025682743639 1.77999807667537 -100.050207532942 1.83369766206226 -100.074732322246 1.85769732211714 -100.099257111549 1.83597710045053 -100.123781900853 1.83869540600775 -100.148306690156 1.78615627927006 -100.17283147946 1.73130553259987 -100.197356268764 1.76067028803817 -100.221881058067 1.7543914627429 -100.246405847371 1.78707930654779 -100.270930636674 1.78481679358485 -100.295455425978 1.74313777768046 -100.319980215281 1.76324723143976 -100.344505004585 1.77407730545213 -100.369029793888 1.77325964475609 -100.393554583192 1.78762187099201 -100.418079372495 1.7180737281922 -100.442604161799 1.76095468165588 -100.467128951102 1.75135109868758 -100.491653740406 1.74037417918341 -100.51617852971 1.81378964410046 -100.540703319013 1.77720444135738 -100.565228108317 1.75682537468241 -100.58975289762 1.75193132801217 -100.614277686924 1.7174389085619 -100.638802476227 1.77748502094537 -100.663327265531 1.80050324938709 -100.687852054834 1.75790668954112 -100.712376844138 1.78431570805074 -100.736901633441 1.79022336787837 -100.761426422745 1.77234043150807 -100.785951212049 1.70854547498466 -100.810476001352 1.78323439319204 -100.835000790656 1.7278394127712 -100.859525579959 1.78336812728823 -100.884050369263 1.73568919637012 -100.908575158566 1.74783968591908 -100.93309994787 1.71027853489899 -100.957624737173 1.71497114577173 -100.982149526477 1.79963099838534 -101.00667431578 1.759918659737 -101.031199105084 1.71230361649049 -101.055723894387 1.79083744805362 -101.080248683691 1.78096258286321 -101.104773472995 1.71965612858502 -101.129298262298 1.72795240741234 -101.153823051602 1.73655763334215 -101.178347840905 1.71835812180992 -101.202872630209 1.75680844925708 -101.227397419512 1.76410255701618 -101.251922208816 1.72454087788938 -101.276446998119 1.71404049043454 -101.300971787423 1.7236931803724 -101.325496576726 1.7271762256264 -101.35002136603 1.76429088141809 -101.374546155334 1.81219413231204 -101.399070944637 1.72920512124761 -101.423595733941 1.77034920076724 -101.448120523244 1.78422345286465 -101.472645312548 1.76215280518545 -101.497170101851 1.73531803090248 -101.521694891155 1.75957204775414 -101.546219680458 1.70389434302202 -101.570744469762 1.77430710876414 -101.595269259065 1.73495067946456 -101.619794048369 1.70660335121337 -101.644318837672 1.69009542068537 -101.668843626976 1.75315019099679 -101.69336841628 1.75587397989018 -101.717893205583 1.78835657386784 -101.742417994887 1.75501698500732 -101.76694278419 1.72424122815277 -101.791467573494 1.7603651549654 -101.815992362797 1.72598573015628 -101.840517152101 1.7528636526558 -101.865041941404 1.80628098914824 -101.889566730708 1.66612031411526 -101.914091520011 1.78312521258062 -101.938616309315 1.69186066214392 -101.963141098619 1.76700918697531 -101.987665887922 1.7846077297279 -102.012190677226 1.7982614758792 -102.036715466529 1.73632783003014 -102.061240255833 1.72062063477286 -102.085765045136 1.74301334095015 -102.11028983444 1.73545939305811 -102.134814623743 1.7128876598448 -102.159339413047 1.7165459181051 -102.18386420235 1.77064503647412 -102.208388991654 1.71552682161155 -102.232913780957 1.72841201403635 -102.257438570261 1.75081234827309 -102.281963359565 1.76153491098048 -102.306488148868 1.7883904247185 -102.331012938172 1.72453324982993 -102.355537727475 1.74797723404499 -102.380062516779 1.70497565788073 -102.404587306082 1.74485009813975 -102.429112095386 1.70806131487587 -102.453636884689 1.71699622736322 -102.478161673993 1.72660362436125 -102.502686463296 1.66506736677105 -102.5272112526 1.7301074090703 -102.551736041903 1.75744708291711 -102.576260831207 1.71236583485564 -102.600785620511 1.74698436034266 -102.625310409814 1.77901283103248 -102.649835199118 1.74994391130106 -102.674359988421 1.7719523406151 -102.698884777725 1.78612791175556 -102.723409567028 1.74597289190855 -102.747934356332 1.69178533238316 -102.772459145635 1.68585693310048 -102.796983934939 1.76554740928308 -102.821508724242 1.77303365547381 -102.846033513546 1.7016356440888 -102.87055830285 1.69354294578227 -102.895083092153 1.78670432705043 -102.919607881457 1.78412738364884 -102.94413267076 1.7613220330938 -102.968657460064 1.78720922661427 -102.993182249367 1.74915080408979 -103.017707038671 1.77307132035419 -103.042231827974 1.73173891643266 -103.066756617278 1.72642078329551 -103.091281406581 1.77245724017894 -103.115806195885 1.73998990232016 -103.140330985188 1.70528460498321 -103.164855774492 1.71194007908229 -103.189380563796 1.70030760049277 -103.213905353099 1.74349750105894 -103.238430142403 1.71493348089135 -103.262954931706 1.69161011937687 -103.28747972101 1.71328886213338 -103.312004510313 1.77887528290656 -103.336529299617 1.71759719614286 -103.36105408892 1.69857072654871 -103.385578878224 1.71113004644569 -103.410103667527 1.73963947630757 -103.434628456831 1.79029869763913 -103.459153246135 1.76863306627823 -103.483678035438 1.76120141039322 -103.508202824742 1.81673226963353 -103.532727614045 1.79725549078125 -103.557252403349 1.7486950114955 -103.581777192652 1.72401523887049 -103.606301981956 1.78477531467475 -103.630826771259 1.72084114071898 -103.655351560563 1.66120171396024 -103.679876349866 1.7640234132257 -103.70440113917 1.82453294626291 -103.728925928473 1.79155903953385 -103.753450717777 1.75765233274434 -103.777975507081 1.81097908335714 -103.802500296384 1.75596075174011 -103.827025085688 1.72924660015772 -103.851549874991 1.69963511598208 -103.876074664295 1.69826559347594 -103.900599453598 1.72908068451731 -103.925124242902 1.75608137444069 -103.949649032205 1.75663323625079 -103.974173821509 1.80287112893173 -103.998698610812 1.78121693965999 -104.023223400116 1.75923854716688 -104.04774818942 1.75859991350685 -104.072272978723 1.84498352866891 -104.096797768027 1.85426505313912 -104.12132255733 1.77139666477528 -104.145847346634 1.78093802937844 -104.170372135937 1.79241984844644 -104.194896925241 1.7648749247724 -104.219421714544 1.7743824385249 -104.243946503848 1.72278326449027 -104.268471293151 1.73927212486965 -104.292996082455 1.7652384621806 -104.317520871758 1.84329743100084 -104.342045661062 1.85210623745144 -104.366570450366 1.82988111622099 -104.391095239669 1.8095964494554 -104.415620028973 1.74796960598555 -104.440144818276 1.80999383771425 -104.46466960758 1.77625304656515 -104.489194396883 1.78010725728679 -104.513719186187 1.810866088716 -104.53824397549 1.82194837480187 -104.562768764794 1.8001943022846 -104.587293554097 1.76197759217916 -104.611818343401 1.85601503847879 -104.636343132704 1.83820194853309 -104.660867922008 1.77490044948434 -104.685392711312 1.77051130237793 -104.709917500615 1.78211159942323 -104.734442289919 1.77477052941787 -104.758967079222 1.80347209771137 -104.783491868526 1.78088343907273 -104.808016657829 1.78946411151776 -104.832541447133 1.86126332519135 -104.857066236436 1.86665297405954 -104.88159102574 1.85364334490443 -104.906115815043 1.89035298437782 -104.930640604347 1.7968995814325 -104.955165393651 1.8490336918519 -104.979690182954 1.84508341191445 -105.004214972258 1.75387345178346 -105.028739761561 1.81304182982902 -105.053264550865 1.80436127413845 -105.077789340168 1.82291669501943 -105.102314129472 1.82937621665716 -105.126838918775 1.82114215619499 -105.151363708079 1.85081204470606 -105.175888497382 1.81523449636736 -105.200413286686 1.87409392731044 -105.224938075989 1.84780171747167 -105.249462865293 1.83915882666149 -105.273987654597 1.86306241750056 -105.2985124439 1.8220482580474 -105.323037233204 1.82513391504255 -105.347562022507 1.83042368066521 -105.372086811811 1.84740981254898 -105.396611601114 1.82747557179597 -105.421136390418 1.80421609795309 -105.445661179721 1.83277844881424 -105.470185969025 1.82393579151298 -105.494710758328 1.85863727482021 -105.519235547632 1.91966314951041 -105.543760336936 1.84554301853845 -105.568285126239 1.86535496728443 -105.592809915543 1.88857844555338 -105.617334704846 1.91574290639389 -105.64185949415 1.87826471319401 -105.666384283453 1.86482932826555 -105.690909072757 1.81586001863178 -105.71543386206 1.8416981051827 -105.739958651364 1.85949641442635 -105.764483440667 1.81481255462374 -105.789008229971 1.83891591195387 -105.813533019274 1.79520966973471 -105.838057808578 1.80364731071766 -105.862582597882 1.83279156020985 -105.887107387185 1.87359284177632 -105.911632176489 1.90757654763304 -105.936156965792 1.88761775339526 -105.960681755096 1.91856109519665 -105.985206544399 1.80175214919264 -106.009731333703 1.82208592292778 -106.034256123006 1.84614780134782 -106.05878091231 1.86497998778707 -106.083305701613 1.9307907285361 -106.107830490917 1.87820249482885 -106.132355280221 1.89345938082802 -106.156880069524 1.83999341278286 -106.181404858828 1.81904174484274 -106.205929648131 1.83867085252298 -106.230454437435 1.83294603376109 -106.254979226738 1.90351327305445 -106.279504016042 1.90493357183657 -106.304028805345 1.92269040217012 -106.328553594649 1.86575998360273 -106.353078383952 1.85451178187646 -106.377603173256 1.86307552889616 -106.402127962559 1.93780974004337 -106.426652751863 1.89130056514033 -106.451177541167 1.8848825224127 -106.47570233047 1.8570040981514 -106.500227119774 1.83178176108218 -106.524751909077 1.84923345834297 -106.549276698381 1.83242802280165 -106.573801487684 1.78477531467475 -106.598326276988 1.85066138518454 -106.622851066291 1.87254156373856 -106.647375855595 1.91654912500077 -106.671900644898 1.89896583836707 -106.696425434202 1.83719596343514 -106.720950223505 1.83919649154187 -106.745475012809 1.79986080169734 -106.769999802113 1.82112904479939 -106.794524591416 1.79254976851291 -106.81904938072 1.76786451255174 -106.843574170023 1.81728413144363 -106.868098959327 1.85514660150676 -106.89262374863 1.81896641508198 -106.917148537934 1.84511344873538 -106.941673327237 1.8768706372031 -106.966198116541 1.84238965984199 -106.990722905844 1.83986897605255 -107.015247695148 1.83526488298054 -107.039772484452 1.85205011509588 -107.064297273755 1.80812909026698 -107.088822063059 1.80549081566593 -107.113346852362 1.84022183264548 -107.137871641666 1.83960839768925 -107.162396430969 1.83740109806562 -107.186921220273 1.80552618077624 -107.211446009576 1.81845576859406 -107.23597079888 1.84597301145518 -107.260495588183 1.82234372840644 -107.285020377487 1.79418329328353 -107.30954516679 1.79517795868886 -107.334069956094 1.79404773035516 -107.358594745398 1.78325703933754 -107.383119534701 1.7947988427197 -107.407644324005 1.78419969767831 -107.432169113308 1.79022493967361 -107.456693902612 1.7893552247376 -107.481218691915 1.81879947010081 -107.505743481219 1.79051325946207 -107.530268270522 1.81893127730945 -107.554793059826 1.79424442433282 -107.579317849129 1.80868937012588 -107.603842638433 1.83566930508535 -107.628367427737 1.78319338141063 -107.65289221704 1.77278022307656 -107.677417006344 1.8224764819698 -107.701941795647 1.83653389876802 -107.726466584951 1.8296231134386 -107.750991374254 1.80453310518657 -107.775516163558 1.8137909819023 -107.800040952861 1.80336982570292 -107.824565742165 1.81056559427995 -107.849090531468 1.80037760916046 -107.873615320772 1.79266151105911 -107.898140110075 1.8059277642564 -107.922664899379 1.79538011034294 -107.947189688683 1.81439157332048 -107.971714477986 1.85339912372666 -107.99623926729 1.82120368327498 -108.020764056593 1.8137264562324 -108.045288845897 1.83023381525557 -108.0698136352 1.82304096818458 -108.094338424504 1.81029192547162 -108.118863213807 1.8310664970934 -108.143388003111 1.85345603925289 -108.167912792414 1.8278479280975 -108.192437581718 1.81341796499846 -108.216962371022 1.83580773602359 -108.241487160325 1.82524035922876 -108.266011949629 1.80509049706872 -108.290536738932 1.8548377491737 -108.315061528236 1.89475707072833 -108.339586317539 1.85279218233859 -108.364111106843 1.78817975183248 -108.388635896146 1.82701152852723 -108.41316068545 1.81381505724938 -108.437685474753 1.83751032562223 -108.462210264057 1.82285211999744 -108.48673505336 1.84649831197895 -108.511259842664 1.83938519560749 -108.535784631968 1.83871466548591 -108.560309421271 1.86483204055888 -108.584834210575 1.85367944094065 -108.609358999878 1.80605874849055 -108.633883789182 1.84173326675223 -108.658408578485 1.81277298802418 -108.682933367789 1.83447997937296 -108.707458157092 1.80502604084338 -108.731982946396 1.80995424900204 -108.756507735699 1.86059454715295 -108.781032525003 1.83817098293832 -108.805557314306 1.84319327446644 -108.83008210361 1.86160738844604 -108.854606892914 1.83877763072536 -108.879131682217 1.85053445868486 -108.903656471521 1.84148405448256 -108.928181260824 1.85137517455441 -108.952706050128 1.8610376035364 -108.977230839431 1.83397789310891 -109.001755628735 1.84338604457561 -109.026280418038 1.84361876501935 -109.050805207342 1.83605509620825 -109.075329996645 1.8659390459052 -109.099854785949 1.8844997703995 -109.124379575253 1.84614958333946 -109.148904364556 1.90017180844062 -109.17342915386 1.8990249334435 -109.197953943163 1.86726876219828 -109.222478732467 1.85467062633243 -109.24700352177 1.85197765617049 -109.271528311074 1.84621596787557 -109.296053100377 1.86990897164015 -109.320577889681 1.83834875220746 -109.345102678984 1.82547111544647 -109.369627468288 1.81802423303709 -109.394152257591 1.83746111241382 -109.418677046895 1.83195015895729 -109.443201836199 1.83443087164994 -109.467726625502 1.82357009845624 -109.492251414806 1.83178646665189 -109.516776204109 1.82802293484216 -109.541300993413 1.82689906784314 -109.565825782716 1.86871655834501 -109.59035057202 1.87057081290427 -109.614875361323 1.88632091780111 -109.639400150627 1.87810601604086 -109.66392493993 1.86994096887793 -109.688449729234 1.82439889453846 -109.712974518538 1.83553775349044 -109.737499307841 1.82997342065453 -109.762024097145 1.85634933811795 -109.786548886448 1.88209198309118 -109.811073675752 1.82727747505087 -109.835598465055 1.85432652561367 -109.860123254359 1.83742634612152 -109.884648043662 1.83422135410113 -109.909172832966 1.85301995633337 -109.933697622269 1.86277433618444 -109.958222411573 1.84574617245033 -109.982747200876 1.85633574770561 -110.00727199018 1.88877547212399 -110.031796779484 1.84167593424473 -110.056321568787 1.82763513472472 -110.080846358091 1.83516703265213 -110.105371147394 1.87015897555263 -110.129895936698 1.8760195568489 -110.154420726001 1.88111975832319 -110.178945515305 1.86791656448596 -110.203470304608 1.86867347095 -110.227995093912 1.82037778000425 -110.252519883215 1.82473508655219 -110.277044672519 1.88911589309761 -110.301569461823 1.87398534714953 -110.326094251126 1.85284039098694 -110.35061904043 1.81678081711187 -110.375143829733 1.84848315834157 -110.399668619037 1.89139402579012 -110.42419340834 1.85162524791144 -110.448718197644 1.8652629707678 -110.473242986947 1.85529304845631 -110.497767776251 1.85643395454955 -110.522292565554 1.85150841008559 -110.546817354858 1.88223704428908 -110.571342144161 1.87330160573517 -110.595866933465 1.8406703713814 -110.620391722769 1.85167704205875 -110.644916512072 1.86573762517703 -110.669441301376 1.85909717211509 -110.693966090679 1.8500872675877 -110.718490879983 1.85382782065898 -110.743015669286 1.8730302576684 -110.76754045859 1.85195475353222 -110.792065247893 1.9135769476703 -110.816590037197 1.86564173241904 -110.8411148265 1.86969046497723 -110.865639615804 1.88879384749218 -110.890164405107 1.89535823616289 -110.914689194411 1.88135460668371 -110.939213983715 1.855523024396 -110.963738773018 1.89209979073498 -110.988263562322 1.87444877445889 -111.012788351625 1.84172844588112 -111.037313140929 1.8338147368326 -111.061837930232 1.82994616360409 -111.086362719536 1.81997254158059 -111.110887508839 1.86204660830041 -111.135412298143 1.85788601569701 -111.159937087446 1.8592434060218 -111.18446187675 1.87813699069816 -111.208986666054 1.85957566292783 -111.233511455357 1.87399040561395 -111.258036244661 1.86609944643824 -111.282561033964 1.83399402854487 -111.307085823268 1.83876080247637 -111.331610612571 1.85309660610577 -111.356135401875 1.83017571578131 -111.380660191178 1.8302171932682 -111.405184980482 1.84372671450733 -111.429709769785 1.84589670476149 -111.454234559089 1.87231000976659 -111.478759348392 1.86248704356006 -111.503284137696 1.88536231697475 -111.527808927 1.84917979277166 -111.552333716303 1.84600476639049 -111.576858505607 1.84951471808156 -111.60138329491 1.81806324172611 -111.625908084214 1.81482259765 -111.650432873517 1.87828004290152 -111.674957662821 1.88341266079248 -111.699482452124 1.88910482761644 -111.724007241428 1.87697520232599 -111.748532030731 1.85712852385179 -111.773056820035 1.84682425024678 -111.797581609339 1.87263607807028 -111.822106398642 1.86932695990533 -111.846631187946 1.82641340603249 -111.871155977249 1.85018682194483 -111.895680766553 1.84922234426316 -111.920205555856 1.82185301937059 -111.94473034516 1.82526302622019 -111.969255134463 1.86688900276819 -111.993779923767 1.86810795091552 -112.01830471307 1.88317506792791 -112.042829502374 1.87636344659685 -112.067354291677 1.89445315611657 -112.091879080981 1.89296997225552 -112.116403870285 1.87862323318082 -112.140928659588 1.85413777832834 -112.165453448892 1.83985041899413 -112.189978238195 1.82195382046468 -112.214503027499 1.87435483456214 -112.239027816802 1.86519436639192 -112.263552606106 1.83676185905754 -112.288077395409 1.85411496773863 -112.312602184713 1.88855273191758 -112.337126974016 1.87383085021988 -112.36165176332 1.84973052959253 -112.386176552624 1.85087861541134 -112.410701341927 1.87664177271114 -112.435226131231 1.89087962052674 -112.459750920534 1.84944416279089 -112.484275709838 1.81405458966394 -112.508800499141 1.83190020345817 -112.533325288445 1.86216842390691 -112.557850077748 1.88257415810707 -112.582374867052 1.89084634522033 -112.606899656355 1.84869103006701 -112.631424445659 1.83832284945141 -112.655949234962 1.84261078271026 -112.680474024266 1.85086177385109 -112.70499881357 1.84850643746226 -112.729523602873 1.86164334088398 -112.754048392177 1.85805181570114 -112.77857318148 1.87988688999607 -112.803097970784 1.87067991042501 -112.827622760087 1.86706122231233 -112.852147549391 1.83828057159406 -112.876672338694 1.83730949027524 -112.901197127998 1.84638236657213 -112.925721917301 1.80685872903834 -112.950246706605 1.84540041247136 -112.974771495908 1.88645638660888 -112.999296285212 1.88264588152295 -113.023821074516 1.8640557002309 -113.048345863819 1.84892593443525 -113.072870653123 1.83059234932803 -113.097395442426 1.85284957763502 -113.12192023173 1.87724780378533 -113.146445021033 1.87738241163132 -113.170969810337 1.85790147163119 -113.19549459964 1.87446635818427 -113.220019388944 1.86812700578549 -113.244544178247 1.85885518879751 -113.269068967551 1.87003206544086 -113.293593756855 1.85249144264899 -113.318118546158 1.88987408912066 -113.342643335462 1.87399058719953 -113.367168124765 1.88212215945005 -113.391692914069 1.87405055219563 -113.416217703372 1.90639556987608 -113.440742492676 1.88076709445042 -113.465267281979 1.88034170260169 -113.489792071283 1.91483361300119 -113.514316860586 1.84777143127404 -113.53884164989 1.8421071692939 -113.563366439193 1.85690187779732 -113.587891228497 1.8585043029816 -113.612416017801 1.8747366703593 -113.636940807104 1.85531902065691 -113.661465596408 1.84736450213291 -113.685990385711 1.89441621921102 -113.710515175015 1.86272284034734 -113.735039964318 1.89510031701535 -113.759564753622 1.88745965340668 -113.784089542925 1.87257276322208 -113.808614332229 1.85900205285391 -113.833139121532 1.89296031029526 -113.857663910836 1.86226867578624 -113.88218870014 1.89971909687811 -113.906713489443 1.89930528198917 -113.931238278747 1.88262451934824 -113.95576306805 1.8603443684361 -113.980287857354 1.857300299541 -114.004812646657 1.87246355356031 -114.029337435961 1.89026560684496 -114.053862225264 1.89657804998118 -114.078387014568 1.91723515569946 -114.102911803871 1.89808320356261 -114.127436593175 1.86964781478153 -114.151961382478 1.86624650331943 -114.176486171782 1.88134831259598 -114.201010961086 1.92072468240336 -114.225535750389 1.88710418224093 -114.250060539693 1.85772219793934 -114.274585328996 1.88421346418918 -114.2991101183 1.89193221267398 -114.323634907603 1.89648030501259 -114.348159696907 1.93011923002088 -114.37268448621 1.93110761477531 -114.397209275514 1.88686100166406 -114.421734064817 1.85633107696282 -114.446258854121 1.86828323129434 -114.470783643425 1.86999959301647 -114.495308432728 1.85703500784309 -114.519833222032 1.87531183891721 -114.544358011335 1.87542978666295 -114.568882800639 1.88369670226896 -114.593407589942 1.89261826804872 -114.617932379246 1.90151570718285 -114.642457168549 1.91630853409046 -114.666981957853 1.90689427483077 -114.691506747156 1.87269754095905 -114.71603153646 1.85713731806906 -114.740556325763 1.84669980893288 -114.765081115067 1.87217334357396 -114.789605904371 1.88495458300513 -114.814130693674 1.8925396273022 -114.838655482978 1.88746034621972 -114.863180272281 1.89368299095409 -114.887705061585 1.89327477721428 -114.912229850888 1.86913268066427 -114.936754640192 1.87475752796218 -114.961279429495 1.86638600148109 -114.985804218799 1.87442011376196 -115.010329008102 1.86599213833917 -115.034853797406 1.90828325194213 -115.059378586709 1.93293017926023 -115.083903376013 1.8850563573276 -115.108428165317 1.92257286257335 -115.13295295462 1.88176741048254 -115.157477743924 1.88425721306554 -115.182002533227 1.91128126465612 -115.206527322531 1.93864852454642 -115.231052111834 1.89228495735611 -115.255576901138 1.87827000194728 -115.280101690441 1.89846427239301 -115.304626479745 1.88738536707233 -115.329151269048 1.88792073529842 -115.353676058352 1.86600042798462 -115.378200847656 1.8671004105149 -115.402725636959 1.88432695881855 -115.427250426263 1.87173872043423 -115.451775215566 1.90341722403574 -115.47630000487 1.90431155022234 -115.500824794173 1.87483604325642 -115.525349583477 1.91919373155128 -115.54987437278 1.89910140163032 -115.574399162084 1.88510411753743 -115.598923951387 1.85890145124024 -115.623448740691 1.90812910280034 -115.647973529994 1.89601906162774 -115.672498319298 1.87846163067932 -115.697023108602 1.90922933428873 -115.721547897905 1.90614870967966 -115.746072687209 1.87438392567717 -115.770597476512 1.90868272094234 -115.795122265816 1.90118629568267 -115.819647055119 1.89548792889158 -115.844171844423 1.89021553933039 -115.868696633726 1.8651642526964 -115.89322142303 1.90305877742826 -115.917746212333 1.89155802306134 -115.942271001637 1.916570244873 -115.966795790941 1.87806863528038 -115.991320580244 1.93028065288326 -116.015845369548 1.89937194015943 -116.040370158851 1.88877559092064 -116.064894948155 1.92649245174318 -116.089419737458 1.93166584306878 -116.113944526762 1.89536894525411 -116.138469316065 1.89817520378379 -116.162994105369 1.92194724072569 -116.187518894672 1.91458670138064 -116.212043683976 1.93647175572516 -116.236568473279 1.93348260001737 -116.261093262583 1.91443649412777 -116.285618051887 1.87087303692414 -116.31014284119 1.86284956428993 -116.334667630494 1.88068425607568 -116.359192419797 1.90674779196596 -116.383717209101 1.89582973354482 -116.408241998404 1.91840274482676 -116.432766787708 1.91729508478029 -116.457291577011 1.88919295539327 -116.481816366315 1.88940604707624 -116.506341155618 1.87732286374207 -116.530865944922 1.91566889587037 -116.555390734226 1.93943266796845 -116.579915523529 1.90227048585569 -116.604440312833 1.92837116478757 -116.628965102136 1.91636163776364 -116.65348989144 1.92555283634223 -116.678014680743 1.9116650534401 -116.702539470047 1.92074593909267 -116.72706425935 1.93710022756226 -116.751589048654 1.91499803416046 -116.776113837957 1.91248879758795 -116.800638627261 1.9132645392235 -116.825163416564 1.92240448621392 -116.849688205868 1.89930327965025 -116.874212995172 1.88944411399413 -116.898737784475 1.9124832771226 -116.923262573779 1.93251578357371 -116.947787363082 1.90735318636042 -116.972312152386 1.89894623656368 -116.996836941689 1.90566353234839 -117.021361730993 1.9180115161199 -117.045886520296 1.90563290742542 -117.0704113096 1.92196895263473 -117.094936098903 1.91657633935474 -117.119460888207 1.90765273312317 -117.143985677511 1.90127565217604 -117.168510466814 1.90417948739998 -117.193035256118 1.91271843044895 -117.217560045421 1.93937139803005 -117.242084834725 1.92897876112695 -117.266609624028 1.92596645840637 -117.291134413332 1.93078748404382 -117.315659202635 1.90841214649796 -117.340183991939 1.90403912827654 -117.364708781242 1.91678378041091 -117.389233570546 1.92183844829635 -117.413758359849 1.92236801118372 -117.438283149153 1.94064611367085 -117.462807938457 1.97247843011664 -117.48733272776 1.95529145786276 -117.511857517064 1.90854217564973 -117.536382306367 1.93775673660224 -117.560907095671 1.92194967360829 -117.585431884974 1.93294802996413 -117.609956674278 1.92572079559481 -117.634481463581 1.94030259374968 -117.659006252885 1.94883715105481 -117.683531042188 1.93091999750232 -117.708055831492 1.91182216471236 -117.732580620795 1.89381036972242 -117.757105410099 1.92635032787413 -117.781630199403 1.93460055882941 -117.806154988706 1.89482135214929 -117.83067977801 1.90632119607675 -117.855204567313 1.94277550542033 -117.879729356617 1.92473841107594 -117.90425414592 1.89659656216644 -117.928778935224 1.91958813530587 -117.953303724527 1.9677601205728 -117.977828513831 1.95972444773591 -118.002353303134 1.93824593663178 -118.026878092438 1.92835643105164 -118.051402881742 1.93576986988028 -118.075927671045 1.95619378662383 -118.100452460349 1.92083925562094 -118.124977249652 1.90988443509862 -118.149502038956 1.9118977112205 -118.174026828259 1.90414987162066 -118.198551617563 1.93045295116472 -118.223076406866 1.95375515011049 -118.24760119617 1.93301499988142 -118.272125985473 1.98521955746896 -118.296650774777 1.9532310270047 -118.32117556408 1.9424683417327 -118.345700353384 1.911052360649 -118.370225142688 1.90651655585248 -118.394749931991 1.91389382267029 -118.419274721295 1.951539184359 -118.443799510598 1.97656416249488 -118.468324299902 1.97045416254174 -118.492849089205 2.00606495550399 -118.517373878509 1.96102572095849 -118.541898667812 1.94401001304079 -118.566423457116 1.973251153614 -118.590948246419 1.95894859395933 -118.615473035723 1.95175528294095 -118.639997825027 1.93054921845867 -118.66452261433 1.96642411697319 -118.689047403634 1.9632446173832 -118.713572192937 1.93855871948895 -118.738096982241 1.93779368028899 -118.762621771544 1.9737126212809 -118.787146560848 1.96157526705013 -118.811671350151 1.97497357628858 -118.836196139455 1.96594392420376 -118.860720928758 1.94588608235736 -118.885245718062 1.96527681808802 -118.909770507365 2.00518243501724 -118.934295296669 1.98235783666279 -118.958820085973 1.96326327969679 -118.983344875276 1.95156505119978 -119.00786966458 1.98521031274112 -119.032394453883 1.99138614123442 -119.056919243187 1.95253037653198 -119.08144403249 1.96232066405249 -119.105968821794 1.94275500904215 -119.130493611097 1.94902757103719 -119.155018400401 1.98499527485253 -119.179543189704 1.98602915520851 -119.204067979008 2.00109702782286 -119.228592768312 1.94006466458761 -119.253117557615 1.97293715993513 -119.277642346919 1.99939140664919 -119.302167136222 1.95024265144934 -119.326691925526 1.94784687804895 -119.351216714829 1.9445985431454 -119.375741504133 1.93223068152944 -119.400266293436 1.963974565107 -119.42479108274 1.98952077629989 -119.449315872043 1.98244328730809 -119.473840661347 1.95950126367457 -119.49836545065 1.98591760691033 -119.522890239954 1.97067565601119 -119.547415029258 1.9576696808159 -119.571939818561 1.98016647745272 -119.596464607865 1.96609031514558 -119.620989397168 1.93886706712462 -119.645514186472 1.97484468851395 -119.670038975775 1.99769553477518 -119.694563765079 1.97497670863984 -119.719088554382 1.99162789348875 -119.743613343686 1.99117598688029 -119.768138132989 1.96950239170662 -119.792662922293 1.93517864004012 -119.817187711596 1.95172825682819 -119.8417125009 1.96053403881984 -119.866237290204 1.93849063299616 -119.890762079507 1.97598265049714 -119.915286868811 1.99684602468836 -119.939811658114 1.98494197157334 -119.964336447418 1.96345671128732 -119.988861236721 1.945894271101 -120.013386026025 1.96612053420092 -120.037910815328 1.99135578982002 -120.062435604632 1.99031096485156 -120.086960393935 1.98357597778403 -120.111485183239 2.0145596137731 -120.136009972543 1.99966704633919 -120.160534761846 1.98496648411973 -120.18505955115 1.95838007380708 -120.209584340453 1.9706683147925 -120.234109129757 1.96489709011517 -120.25863391906 1.93918334355775 -120.283158708364 1.92183594804124 -120.307683497667 1.93663001503018 -120.332208286971 1.96537469987368 -120.356733076274 1.95677443753422 -120.381257865578 1.95274109812128 -120.405782654881 1.98076365830191 -120.430307444185 1.96768813488757 -120.454832233489 1.98195482486009 -120.479357022792 1.96998266986899 -120.503881812096 1.96062988888136 -120.528406601399 2.00257605216857 -120.552931390703 1.98152270863111 -120.577456180006 1.98260681799478 -120.60198096931 1.96936980421997 -120.626505758613 1.97876853797534 -120.651030547917 1.96754014289087 -120.67555533722 1.94846609379342 -120.700080126524 1.99661978437594 -120.724604915828 1.96415259677107 -120.749129705131 1.95949663562826 -120.773654494435 1.95811011607555 -120.798179283738 1.9616830662842 -120.822704073042 1.99566966601983 -120.847228862345 1.98327949392798 -120.871753651649 1.96350102739886 -120.896278440952 1.96954852191587 -120.920803230256 2.0478338540939 -120.945328019559 2.04467762031334 -120.969852808863 1.97265800224427 -120.994377598166 1.96685350006292 -121.01890238747 1.9710884865581 -121.043427176774 1.96487793446898 -121.067951966077 1.97738449594816 -121.092476755381 1.97233642975341 -121.117001544684 1.95912310749697 -121.141526333988 1.96213000176764 -121.166051123291 1.9569909352026 -121.190575912595 2.02140503036569 -121.215100701898 1.97355794760042 -121.239625491202 1.93524658971585 -121.264150280505 1.95564178297354 -121.288675069809 1.99056710581666 -121.313199859113 1.98853179950867 -121.337724648416 1.9983929113704 -121.36224943772 1.96820459184197 -121.386774227023 1.97827280846238 -121.411299016327 1.97868739031807 -121.43582380563 1.99848143157803 -121.460348594934 1.9926559351023 -121.484873384237 1.96850783166111 -121.509398173541 1.9869484357244 -121.533922962844 1.99844355290135 -121.558447752148 1.9380579875193 -121.582972541451 1.94032787313725 -121.607497330755 1.95556758410417 -121.632022120059 1.9707336700925 -121.656546909362 2.00804811127473 -121.681071698666 1.98234508711978 -121.705596487969 1.95936051212033 -121.730121277273 1.93832913598006 -121.754646066576 1.97671589206532 -121.77917085588 1.96719539883675 -121.803695645183 1.98721308603341 -121.828220434487 2.00765414930303 -121.85274522379 1.99947587282689 -121.877270013094 1.97778166222722 -121.901794802397 2.01479227820912 -121.926319591701 1.97440115901592 -121.950844381005 1.99852394495665 -121.975369170308 1.99239346871779 -121.999893959612 1.95493056035226 -122.024418748915 1.95469007480177 -122.048943538219 1.97320230150471 -122.073468327522 1.97935215767183 -122.097993116826 1.97464937350673 -122.122517906129 1.96302014847202 -122.147042695433 1.99252047960578 -122.171567484736 1.97548569226764 -122.19609227404 1.98229779544093 -122.220617063344 1.96513423912481 -122.245141852647 1.95440225292951 -122.269666641951 1.93492827973769 -122.294191431254 1.95565328565402 -122.318716220558 2.02554133483032 -122.343241009861 1.996327740325 -122.367765799165 1.98815881393651 -122.392290588468 1.97388176460709 -122.416815377772 2.01333678640021 -122.441340167075 2.00235667330465 -122.465864956379 1.99731280655899 -122.490389745682 1.96783375886263 -122.514914534986 1.98212495252831 -122.53943932429 2.02031949267948 -122.563964113593 1.9933689672377 -122.588488902897 1.98055044782228 -122.6130136922 1.9857610002099 -122.637538481504 1.99731586281007 -122.662063270807 1.94754888311455 -122.686588060111 1.95334764042292 -122.711112849414 1.9818030076991 -122.735637638718 1.98029182920607 -122.760162428021 1.98586907985932 -122.784687217325 2.0043650542761 -122.809212006629 2.02706851859787 -122.833736795932 2.04414650559762 -122.858261585236 1.96939254536512 -122.882786374539 2.01933089017305 -122.907311163843 2.00963103120406 -122.931835953146 1.99429756650957 -122.95636074245 2.03726545484416 -122.980885531753 2.02474471091584 -123.005410321057 2.02334519286202 -123.02993511036 2.02162764053622 -123.054459899664 1.97897342747784 -123.078984688967 2.00707655464372 -123.103509478271 2.02265765303144 -123.128034267575 2.00780892859449 -123.152559056878 2.02225637464268 -123.177083846182 2.00127360636276 -123.201608635485 2.01750452074187 -123.226133424789 2.02610533907729 -123.250658214092 2.03084714995183 -123.275183003396 2.02653923807231 -123.299707792699 2.02305752789929 -123.324232582003 2.00921230107225 -123.348757371306 1.98367837089139 -123.37328216061 2.04116421108716 -123.397806949914 2.01509900482303 -123.422331739217 1.9873462834517 -123.446856528521 2.03661389683675 -123.471381317824 2.03788179592314 -123.495906107128 2.00853204645264 -123.520430896431 2.02107223755615 -123.544955685735 2.05434976048212 -123.569480475038 2.00107814122722 -123.594005264342 1.9648355892391 -123.618530053645 2.01883000807507 -123.643054842949 1.99790931927119 -123.667579632252 1.9957090044763 -123.692104421556 1.94042688493525 -123.71662921086 1.96047693034323 -123.741154000163 2.00534136911487 -123.765678789467 2.03604428007586 -123.79020357877 2.00063572164907 -123.814728368074 2.00574299058233 -123.839253157377 1.99085194559156 -123.863777946681 2.00807781910261 -123.888302735984 2.00421918812414 -123.912827525288 1.96953604807904 -123.937352314591 2.01875932055092 -123.961877103895 2.01900264472606 -123.986401893198 2.03360183183506 -124.010926682502 2.04984113068346 -124.035451471806 2.04793445680427 -124.059976261109 2.02982515111124 -124.084501050413 2.04687290724296 -124.109025839716 2.03938498362928 -124.13355062902 1.99817678070749 -124.158075418323 1.99224039568196 -124.182600207627 2.01828606961655 -124.20712499693 2.06858556833148 -124.231649786234 2.05558744145082 -124.256174575537 2.03831473263598 -124.280699364841 2.04950180800539 -124.305224154145 2.03395206166155 -124.329748943448 2.04409371258311 -124.354273732752 2.05321595263534 -124.378798522055 2.05797975791887 -124.403323311359 2.0450259583764 -124.427848100662 2.0352991756406 -124.452372889966 2.05111458488213 -124.476897679269 2.04212301205532 -124.501422468573 2.02320333728587 -124.525947257876 2.0212167517118 -124.55047204718 2.03187497254504 -124.574996836483 2.03922698994235 -124.599521625787 2.0502863762908 -124.624046415091 2.05116223342813 -124.648571204394 2.05529564261157 -124.673095993698 2.04791546975472 -124.697620783001 2.03431429222285 -124.722145572305 2.03081621304324 -124.746670361608 2.04953524122178 -124.771195150912 2.05928366730529 -124.795719940215 2.05460506627075 -124.820244729519 2.06695085523854 -124.844769518822 2.04699076622608 -124.869294308126 2.03197267904076 -124.89381909743 2.05657413435339 -124.918343886733 2.06365834975563 -124.942868676037 2.03320111309657 -124.96739346534 2.0358789713616 -124.991918254644 2.05073474760632 -125.016443043947 2.06238198823284 -125.040967833251 2.09188674421539 -125.065492622554 2.09142925641213 -125.090017411858 2.06872012216643 -125.114542201161 2.03354504284945 -125.139066990465 2.04476063602075 -125.163591779768 2.04342652130047 -125.188116569072 2.05060203935564 -125.212641358376 2.06768181836395 -125.237166147679 2.06811355911514 -125.261690936983 2.077875532835 -125.286215726286 2.06775836911821 -125.31074051559 2.08504029847041 -125.335265304893 2.08671975679954 -125.359790094197 2.05399622924727 -125.3843148835 2.05998484519245 -125.408839672804 2.06909263940016 -125.433364462107 2.0543809121707 -125.457889251411 2.07662119379203 -125.482414040715 2.06576972033763 -125.506938830018 2.0694106545843 -125.531463619322 2.08157368778301 -125.555988408625 2.05047697771225 -125.580513197929 2.06167603297056 -125.605037987232 2.06493964667653 -125.629562776536 2.07467967248888 -125.654087565839 2.07272049837997 -125.678612355143 2.03806082356612 -125.703137144446 2.03492307301676 -125.72766193375 2.06627228052832 -125.752186723053 2.11804958497308 -125.776711512357 2.09967489688562 -125.801236301661 2.08284478880754 -125.825761090964 2.07310092540706 -125.850285880268 2.06877501023398 -125.874810669571 2.05240611579136 -125.899335458875 2.06011169463755 -125.923860248178 2.07663797414564 -125.948385037482 2.08027094547532 -125.972909826785 2.06536996188046 -125.997434616089 2.07772492300882 -126.021959405392 2.078563432308 -126.046484194696 2.0693047839875 -126.071008983999 2.07483212675657 -126.095533773303 2.06780581618709 -126.120058562607 2.0793754089233 -126.14458335191 2.0819824706626 -126.169108141214 2.08943225031638 -126.193632930517 2.08387016941794 -126.218157719821 2.06118046022062 -126.242682509124 2.07869495610416 -126.267207298428 2.08435157351556 -126.291732087731 2.09857690551783 -126.316256877035 2.08621769886586 -126.340781666338 2.08736964780278 -126.365306455642 2.10086658888249 -126.389831244946 2.09219483931816 -126.414356034249 2.08872669882876 -126.438880823553 2.1054212002104 -126.463405612856 2.10999918399684 -126.48793040216 2.07957421118991 -126.512455191463 2.10670640943826 -126.536979980767 2.11784151965263 -126.56150477007 2.07867793433549 -126.586029559374 2.0994224694461 -126.610554348677 2.10528457054 -126.635079137981 2.11514561799285 -126.659603927284 2.0822286477992 -126.684128716588 2.09736851565108 -126.708653505892 2.09197715346559 -126.733178295195 2.11618776146386 -126.757703084499 2.09619068844998 -126.782227873802 2.10124514392466 -126.806752663106 2.13170837194929 -126.831277452409 2.10681494234003 -126.855802241713 2.10100546442495 -126.880327031016 2.11903145309069 -126.90485182032 2.10229938188273 -126.929376609623 2.10754351592813 -126.953901398927 2.09421004220201 -126.978426188231 2.10636028646812 -127.002950977534 2.11639266318928 -127.027475766838 2.09240411150878 -127.052000556141 2.06377666338082 -127.076525345445 2.09047007281242 -127.101050134748 2.08978166410866 -127.125574924052 2.10324524458111 -127.150099713355 2.11047127288395 -127.174624502659 2.10278777764052 -127.199149291962 2.08176397060925 -127.223674081266 2.09945289963967 -127.248198870569 2.10412952012723 -127.272723659873 2.08875732487356 -127.297248449177 2.1121952580066 -127.32177323848 2.11978882328446 -127.346298027784 2.1225986205479 -127.370822817087 2.11705264199566 -127.395347606391 2.11928200400356 -127.419872395694 2.11714911463452 -127.444397184998 2.13176523411748 -127.468921974301 2.1324431310254 -127.493446763605 2.12947681493673 -127.517971552908 2.11969674798027 -127.542496342212 2.13534005550736 -127.567021131516 2.13226173288272 -127.591545920819 2.11860960944848 -127.616070710123 2.11483977037371 -127.640595499426 2.10386867322406 -127.66512028873 2.1040507601278 -127.689645078033 2.12022741572255 -127.714169867337 2.12795376681217 -127.73869465664 2.13391186416115 -127.763219445944 2.12293636873917 -127.787744235247 2.12444018781309 -127.812269024551 2.11153287310756 -127.836793813854 2.12530504751152 -127.861318603158 2.12342338404469 -127.885843392462 2.13270673051147 -127.910368181765 2.13521949296275 -127.934892971069 2.14048593377113 -127.959417760372 2.13451281070095 -127.983942549676 2.12700660588155 -128.008467338979 2.10957865429249 -128.032992128283 2.09526425405245 -128.057516917586 2.11335543553719 -128.08204170689 2.11769460489209 -128.106566496193 2.12412113667443 -128.131091285497 2.11260858399583 -128.1556160748 2.11924248178901 -128.180140864104 2.12693438966885 -128.204665653408 2.11372404321831 -128.229190442711 2.14174942789189 -128.253715232015 2.1523524854872 -128.278240021318 2.13558587868184 -128.302764810622 2.1316646250445 -128.327289599925 2.13978846032608 -128.351814389229 2.15414187217273 -128.376339178532 2.15156265175159 -128.400863967836 2.16328822240734 -128.425388757139 2.13992945653529 -128.449913546443 2.12609371724505 -128.474438335747 2.15197463233315 -128.49896312505 2.15260662354828 -128.523487914354 2.12318589661948 -128.548012703657 2.12461634500161 -128.572537492961 2.11740261910927 -128.597062282264 2.11873906373831 -128.621587071568 2.12779555192822 -128.646111860871 2.14049540152523 -128.670636650175 2.15183471409683 -128.695161439478 2.14467888465701 -128.719686228782 2.11300763417715 -128.744211018085 2.11592215834124 -128.768735807389 2.17064517716597 -128.793260596693 2.15739510731678 -128.817785385996 2.13275204689249 -128.8423101753 2.13792944732882 -128.866834964603 2.1188360389858 -128.891359753907 2.14659696517055 -128.91588454321 2.18530998560328 -128.940409332514 2.16840757730511 -128.964934121817 2.15178739501689 -128.989458911121 2.13849503185172 -129.013983700424 2.14432677612301 -129.038508489728 2.14854840400583 -129.063033279032 2.17787221106698 -129.087558068335 2.14188067645906 -129.112082857639 2.12925055678389 -129.136607646942 2.12591902873561 -129.161132436246 2.13179526953195 -129.185657225549 2.13940342853899 -129.210182014853 2.14381512874609 -129.234706804156 2.14633085527849 -129.25923159346 2.16045423640419 -129.283756382763 2.16941365227075 -129.308281172067 2.18130830500418 -129.33280596137 2.13453228395029 -129.357330750674 2.13218759800782 -129.381855539978 2.15789315610206 -129.406380329281 2.15510786426893 -129.430905118585 2.15825047628872 -129.455429907888 2.16949421607051 -129.479954697192 2.16909749579795 -129.504479486495 2.15713329818166 -129.529004275799 2.14416472965774 -129.553529065102 2.14778760090695 -129.578053854406 2.17167336547538 -129.602578643709 2.17742754794416 -129.627103433013 2.1612011434426 -129.651628222317 2.15199201960872 -129.67615301162 2.15158318404504 -129.700677800924 2.16484532611214 -129.725202590227 2.14783676123453 -129.749727379531 2.15396490301035 -129.774252168834 2.1417876110865 -129.798776958138 2.11553908647287 -129.823301747441 2.16208447445488 -129.847826536745 2.17827109844917 -129.872351326048 2.15540201467075 -129.896876115352 2.16716564589433 -129.921400904655 2.1764154513738 -129.945925693959 2.15687707657883 -129.970450483263 2.15546908668283 -129.994975272566 2.14848869217888 -130.01950006187 2.14100533362166 -130.044024851173 2.14333677528622 -130.068549640477 2.15121243712002 -130.09307442978 2.15706582207258 -130.117599219084 2.18100035081232 -130.142124008387 2.20196308507916 -130.166648797691 2.18659331174095 -130.191173586994 2.16049447838078 -130.215698376298 2.16870024962207 -130.240223165601 2.17567686569762 -130.264747954905 2.17867347892176 -130.289272744209 2.18427914125942 -130.313797533512 2.16946633311492 -130.338322322816 2.15069762770105 -130.362847112119 2.17755234224067 -130.387371901423 2.19223599098563 -130.411896690726 2.16661049094727 -130.43642148003 2.14921280839353 -130.460946269333 2.14459124056004 -130.485471058637 2.17114702887569 -130.50999584794 2.16757470453033 -130.534520637244 2.15460931750466 -130.559045426548 2.16837494563125 -130.583570215851 2.18597943292561 -130.608095005155 2.17274375521128 -130.632619794458 2.15957391518337 -130.657144583762 2.17868322032257 -130.681669373065 2.1670757149032 -130.706194162369 2.17895840169526 -130.730718951672 2.1778991801674 -130.755243740976 2.16844400138417 -130.779768530279 2.17055477600297 -130.804293319583 2.17677233517323 -130.828818108886 2.18694305145381 -130.85334289819 2.18208727517944 -130.877867687494 2.18942511797622 -130.902392476797 2.18052244607562 -130.926917266101 2.1972415179662 -130.951442055404 2.17515087353706 -130.975966844708 2.19828954394771 -131.000491634011 2.21023090963025 -131.025016423315 2.16743216005918 -131.049541212618 2.15656828650308 -131.074066001922 2.16224372839931 -131.098590791225 2.19335044065424 -131.123115580529 2.20182369693627 -131.147640369833 2.18607385700871 -131.172165159136 2.17494240318199 -131.19668994844 2.18077311009228 -131.221214737743 2.19337117791168 -131.245739527047 2.19403569138886 -131.27026431635 2.19583280722401 -131.294789105654 2.17041262686766 -131.319313894957 2.17963401786233 -131.343838684261 2.2035738905803 -131.368363473564 2.20515330122388 -131.392888262868 2.19555154895468 -131.417413052171 2.21047496897165 -131.441937841475 2.19480440390019 -131.466462630779 2.19433966171951 -131.490987420082 2.20872419591976 -131.515512209386 2.22501592119997 -131.540036998689 2.20519057191118 -131.564561787993 2.19814366355941 -131.589086577296 2.19354799119008 -131.6136113666 2.1925312403988 -131.638136155903 2.16769702818298 -131.662660945207 2.18083316495189 -131.68718573451 2.17662167020163 -131.711710523814 2.17460644903364 -131.736235313118 2.17876695735756 -131.760760102421 2.19706135666913 -131.785284891725 2.20804949721195 -131.809809681028 2.19581800943918 -131.834334470332 2.18821923362212 -131.858859259635 2.19411597770343 -131.883384048939 2.17838778639786 -131.907908838242 2.1780898567179 -131.932433627546 2.20141441531585 -131.956958416849 2.20452156044867 -131.981483206153 2.19994753673063 -132.006007995456 2.21783417529829 -132.03053278476 2.19826939165904 -132.055057574064 2.19802510729104 -132.079582363367 2.19097083767762 -132.104107152671 2.18724638864785 -132.128631941974 2.18980213266276 -132.153156731278 2.20310108196044 -132.177681520581 2.21105967245563 -132.202206309885 2.20304815420272 -132.226731099188 2.21703928057451 -132.251255888492 2.22006002161925 -132.275780677795 2.21092204221545 -132.300305467099 2.23698113193983 -132.324830256402 2.23325473767903 -132.349355045706 2.21591300759657 -132.37387983501 2.2191129572893 -132.398404624313 2.2169350753896 -132.422929413617 2.18367727781835 -132.44745420292 2.18234913800511 -132.471978992224 2.20714208025217 -132.496503781527 2.22773960780056 -132.521028570831 2.22276893996965 -132.545553360134 2.23180669620504 -132.570078149438 2.23981967862204 -132.594602938741 2.23135131908054 -132.619127728045 2.19973294183722 -132.643652517349 2.21211796324682 -132.668177306652 2.20742559962779 -132.692702095956 2.20553197836795 -132.717226885259 2.21995890520736 -132.741751674563 2.22778796408479 -132.766276463866 2.21819552659803 -132.79080125317 2.19000520111436 -132.815326042473 2.18926907639599 -132.839850831777 2.2315905275561 -132.86437562108 2.22726729807186 -132.888900410384 2.23606435109212 -132.913425199687 2.23621414188787 -132.937949988991 2.22740101847279 -132.962474778295 2.19591311403688 -132.986999567598 2.20711057764919 -133.011524356902 2.21247281735302 -133.036049146205 2.22661545217448 -133.060573935509 2.22752690736631 -133.085098724812 2.2117267406223 -133.109623514116 2.22226570604957 -133.134148303419 2.21647039850414 -133.158673092723 2.21815271559715 -133.183197882026 2.23760192383381 -133.20772267133 2.22532471305037 -133.232247460634 2.21852383585767 -133.256772249937 2.22412549059354 -133.281297039241 2.20376894514686 -133.305821828544 2.19943127769257 -133.330346617848 2.2126413579874 -133.354871407151 2.22280328299632 -133.379396196455 2.21148986725332 -133.403920985758 2.22735234863703 -133.428445775062 2.22359075118491 -133.452970564365 2.20568949835791 -133.477495353669 2.22745609175002 -133.502020142972 2.22155471183257 -133.526544932276 2.23330037796265 -133.55106972158 2.2400296654635 -133.575594510883 2.24471652880246 -133.600119300187 2.22582097492063 -133.62464408949 2.24861011073273 -133.649168878794 2.24422754475592 -133.673693668097 2.25101643639944 -133.698218457401 2.2133557362792 -133.722743246704 2.23102495497547 -133.747268036008 2.22761789653014 -133.771792825311 2.23707378077883 -133.796317614615 2.23504373190791 -133.820842403919 2.24376038875336 -133.845367193222 2.24772941437627 -133.869891982526 2.2434420606101 -133.894416771829 2.2435842309648 -133.918941561133 2.24594150402898 -133.943466350436 2.25050895181907 -133.96799113974 2.25130917365107 -133.992515929043 2.24793309454764 -134.017040718347 2.24071681504997 -134.04156550765 2.23597504772638 -134.066090296954 2.23065996557604 -134.090615086257 2.21498571467019 -134.115139875561 2.23082863424523 -134.139664664865 2.23751680271573 -134.164189454168 2.25039087897322 -134.188714243472 2.24000270083475 -134.213239032775 2.23646787683038 -134.237763822079 2.24083118801455 -134.262288611382 2.25378323546746 -134.286813400686 2.25414518182333 -134.311338189989 2.24748463931007 -134.335862979293 2.25373192255362 -134.360387768596 2.23472818280806 -134.3849125579 2.23821838719559 -134.409437347203 2.23151812180474 -134.433962136507 2.24898704706069 -134.458486925811 2.25705957031002 -134.483011715114 2.2579110695692 -134.507536504418 2.24508188799319 -134.532061293721 2.24678604593367 -134.556586083025 2.23322436640695 -134.581110872328 2.24284260617655 -134.605635661632 2.26820990470847 -134.630160450935 2.25388000024355 -134.654685240239 2.25624281574375 -134.679210029542 2.22852345013875 -134.703734818846 2.2395376932946 -134.72825960815 2.25734129127424 -134.752784397453 2.26397728808036 -134.777309186757 2.25704023506565 -134.80183397606 2.23927496721235 -134.826358765364 2.24059415316884 -134.850883554667 2.25206757222301 -134.875408343971 2.2528711950992 -134.899933133274 2.23872194846438 -134.924457922578 2.23814864756859 -134.948982711881 2.24772388594765 -134.973507501185 2.24314306614421 -134.998032290488 2.25532639855313 -135.022557079792 2.2533954272639 -135.047081869096 2.25059691429722 -135.071606658399 2.26526549626194 -135.096131447703 2.23965230562695 -135.120656237006 2.26131596993995 -135.14518102631 2.27310795877922 -135.169705815613 2.25765650595479 -135.194230604917 2.24817364840156 -135.21875539422 2.26861363685032 -135.243280183524 2.27663746372055 -135.267804972827 2.26710803491772 -135.292329762131 2.26526918912996 -135.316854551435 2.25015069957335 -135.341379340738 2.25634244549946 -135.365904130042 2.27274285904053 -135.390428919345 2.25909099408375 -135.414953708649 2.25484657740407 -135.439478497952 2.25735509868238 -135.464003287256 2.28640275441732 -135.488528076559 2.26427760308204 -135.513052865863 2.26212416618149 -135.537577655166 2.25904806250083 -135.56210244447 2.26027696249126 -135.586627233773 2.27134494631124 -135.611152023077 2.28007000862314 -135.635676812381 2.29322482116158 -135.660201601684 2.28791609165921 -135.684726390988 2.27222881112805 -135.709251180291 2.27618080223488 -135.733775969595 2.28652994313174 -135.758300758898 2.28850092919883 -135.782825548202 2.26605974809808 -135.807350337505 2.26783315599929 -135.831875126809 2.27831225454499 -135.856399916112 2.27364727745808 -135.880924705416 2.27805926214707 -135.90544949472 2.27133762959934 -135.929974284023 2.27897448580479 -135.954499073327 2.2696031705575 -135.97902386263 2.28315463298248 -136.003548651934 2.28475257329831 -136.028073441237 2.27869527783911 -136.052598230541 2.27188048704581 -136.077123019844 2.29076722615109 -136.101647809148 2.2886543460934 -136.126172598451 2.29719050075705 -136.150697387755 2.29064041533046 -136.175222177058 2.27550893282 -136.199746966362 2.28629246369947 -136.224271755666 2.29627715504519 -136.248796544969 2.28664282773938 -136.273321334273 2.28577098558273 -136.297846123576 2.29436548519532 -136.32237091288 2.29242921926521 -136.346895702183 2.29850413755092 -136.371420491487 2.29987821570137 -136.39594528079 2.30222975624561 -136.420470070094 2.30423300024465 -136.444994859397 2.29574644089826 -136.469519648701 2.28738320942424 -136.494044438004 2.30773330229846 -136.518569227308 2.28495672249751 -136.543094016612 2.28012126992176 -136.567618805915 2.27146543481095 -136.592143595219 2.28067153062784 -136.616668384522 2.28195202652728 -136.641193173826 2.28862473576854 -136.665717963129 2.28864853692456 -136.690242752433 2.29074616484454 -136.714767541736 2.29907991536618 -136.73929233104 2.29747010243934 -136.763817120343 2.27976721670199 -136.788341909647 2.28371131033482 -136.812866698951 2.31556781719796 -136.837391488254 2.32891787740113 -136.861916277558 2.29632807982246 -136.886441066861 2.27586067934927 -136.910965856165 2.3044748132923 -136.935490645468 2.30720846137599 -136.960015434772 2.29658678138829 -136.984540224075 2.29345983242661 -137.009065013379 2.29617621214914 -137.033589802682 2.32014870919774 -137.058114591986 2.30478815837792 -137.082639381289 2.30256508878811 -137.107164170593 2.30280731657302 -137.131688959897 2.30819414423548 -137.1562137492 2.32199048453671 -137.180738538504 2.30873592869288 -137.205263327807 2.29625115153748 -137.229788117111 2.290573445708 -137.254312906414 2.30625766567203 -137.278837695718 2.29980062608791 -137.303362485021 2.30831314770334 -137.327887274325 2.29281696803769 -137.352412063628 2.29186391529016 -137.376936852932 2.29714960628447 -137.401461642236 2.28926829194676 -137.425986431539 2.31772661992292 -137.450511220843 2.32417643855372 -137.475036010146 2.3088284428469 -137.49956079945 2.31728064479614 -137.524085588753 2.32692824877712 -137.548610378057 2.31560666628572 -137.57313516736 2.31397811857867 -137.597659956664 2.33795479458807 -137.622184745967 2.32080819070926 -137.646709535271 2.32403502861116 -137.671234324574 2.33555421004066 -137.695759113878 2.32431356141295 -137.720283903182 2.32178440436231 -137.744808692485 2.32048481017845 -137.769333481789 2.33338362253276 -137.793858271092 2.31671001510788 -137.818383060396 2.30524102133819 -137.842907849699 2.30818871231071 -137.867432639003 2.32657849676273 -137.891957428306 2.32596804450172 -137.91648221761 2.31779064065517 -137.941007006913 2.31511448547739 -137.965531796217 2.34007571989486 -137.990056585521 2.36078951371786 -138.014581374824 2.34124809692909 -138.039106164128 2.30021207349302 -138.063630953431 2.34405107039938 -138.088155742735 2.3379274948903 -138.112680532038 2.30751018365226 -138.137205321342 2.30561710955972 -138.161730110645 2.31764978405233 -138.186254899949 2.30663026173444 -138.210779689252 2.32949830182912 -138.235304478556 2.32914575915262 -138.259829267859 2.32378861043787 -138.284354057163 2.34591923153508 -138.308878846467 2.33595261142435 -138.33340363577 2.31510732856059 -138.357928425074 2.31821779818042 -138.382453214377 2.3239934072257 -138.406978003681 2.32109179925062 -138.431502792984 2.32433902713622 -138.456027582288 2.32511034617902 -138.480552371591 2.35082498061283 -138.505077160895 2.34341790095947 -138.529601950198 2.3121378595541 -138.554126739502 2.33590385246108 -138.578651528805 2.34794899439151 -138.603176318109 2.31930028481884 -138.627701107413 2.32555068454443 -138.652225896716 2.34504358563608 -138.67675068602 2.35049289478479 -138.701275475323 2.3340322636423 -138.725800264627 2.34316949392778 -138.75032505393 2.33577943214598 -138.774849843234 2.32668540791566 -138.799374632537 2.32468973927795 -138.823899421841 2.31783570655933 -138.848424211144 2.34192940319773 -138.872949000448 2.35278828220235 -138.897473789752 2.34392760241487 -138.921998579055 2.33830241193725 -138.946523368359 2.32340391080494 -138.971048157662 2.32372567779133 -138.995572946966 2.32533684121793 -139.020097736269 2.33165242014241 -139.044622525573 2.34716096390077 -139.069147314876 2.34198049873903 -139.09367210418 2.35626815932087 -139.118196893483 2.34626549765833 -139.142721682787 2.35834122668549 -139.16724647209 2.34931117931162 -139.191771261394 2.37002219145407 -139.216296050698 2.30989042942989 -139.240820840001 2.30202375044605 -139.265345629305 2.33304049435171 -139.289870418608 2.32763670586007 -139.314395207912 2.3294124072661 -139.338919997215 2.32069084133655 -139.363444786519 2.31623499547457 -139.387969575822 2.33405387473416 -139.412494365126 2.31503848991649 -139.437019154429 2.3123206749237 -139.461543943733 2.32439749089 -139.486068733037 2.35612367803332 -139.51059352234 2.35467530664992 -139.535118311644 2.3319722087425 -139.559643100947 2.35778057912643 -139.584167890251 2.35072179225367 -139.608692679554 2.35169167439405 -139.633217468858 2.36351514737636 -139.657742258161 2.36810891470995 -139.682267047465 2.33848545110023 -139.706791836768 2.33034901652153 -139.731316626072 2.35192095875738 -139.755841415375 2.36073921730489 -139.780366204679 2.35135102550208 -139.804890993983 2.35087434346091 -139.829415783286 2.34040919230172 -139.85394057259 2.33321375205618 -139.878465361893 2.32625464661169 -139.902990151197 2.33794151097608 -139.9275149405 2.33593582032729 -139.952039729804 2.35405918609658 -139.976564519107 2.35634615449893 -140.001089308411 2.33819857704049 -140.025614097714 2.35681685015196 -140.050138887018 2.35460073106677 From b90f56ad83ff9e75aeb55339470d387d6ff76d52 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 3 Feb 2026 09:50:25 -0500 Subject: [PATCH 672/696] fix import bug --- src/diffpy/utils/_deprecator.py | 39 +-------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 560c9530..317c224e 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -96,7 +96,7 @@ def wrapper(*args, **kwargs): return decorator -def build_deprecation_message( +def deprecation_message( old_base, old_name, new_name, removal_version, new_base=None ): """Generate a deprecation message. @@ -131,40 +131,3 @@ def build_deprecation_message( f"version {removal_version}. Please use '{new_base}.{new_name}' " f"instead." ) - - -def generate_deprecation_docstring(new_name, removal_version, new_base=None): - """Generate a docstring for copy-pasting into a deprecated function. - - this function will print the text to the terminal for copy-pasting - - usage: - python - >>> import diffpy.utils._deprecator.generate_deprecation_docstring as gdd - >>> gdd("new_name", "4.0.0") - - The message looks like: - This function has been deprecated and will be removed in version - {removal_version}. Please use {new_base}.{new_name} instead. - - Parameters - ---------- - new_name: str - The name of the new function or class to replace the existing one - removal_version: str - The version when the deprecated item is targeted for removal, - e.g., 4.0.0 - new_base: str Optional. Defaults to old_base. - The new base for importing. The new import statement would look like - "from new_base import new_name" - - Returns - ------- - None - """ - print( - f"This function has been deprecated and will be " - f"removed in version {removal_version}. Please use" - f"{new_base}.{new_name} instead." - ) - return From e4d3c6621b5b3d8b9339419a297b4f63a33c7dbd Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 3 Feb 2026 10:02:00 -0500 Subject: [PATCH 673/696] add deprecator test --- tests/test_deprecator.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/test_deprecator.py diff --git a/tests/test_deprecator.py b/tests/test_deprecator.py new file mode 100644 index 00000000..8743474c --- /dev/null +++ b/tests/test_deprecator.py @@ -0,0 +1,37 @@ +import pytest + +from diffpy.utils._deprecator import deprecated, deprecation_message + +old_base = "diffpy.utils" +old_name = "oldFunction" +new_name = "new_function" +removal_version = "4.0.0" + +dep_msg = deprecation_message(old_base, old_name, new_name, removal_version) + + +@deprecated(dep_msg) +def oldFunction(print_msg): + """This function is deprecated and will be removed in version 4.0.0. + + Please use newFunction instead + """ + return new_function(print_msg) + + +def new_function(print_msg): + print(print_msg) + return + + +def test_deprecated(capsys): + # Case: user deprecates a function with the deprecated decorator + # Expected: DeprecationWarning is raised when the function is called + # and the function executes correctly + expected_print_msg = "Testing deprecated function" + with pytest.deprecated_call(match=dep_msg): + oldFunction(expected_print_msg) + + captured = capsys.readouterr() + actual_print_msg = captured.out.strip() + assert actual_print_msg == expected_print_msg From 28b607423b0252c94755329e87961cd3b4d43f05 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 3 Feb 2026 10:02:38 -0500 Subject: [PATCH 674/696] precommit --- tests/test_deprecator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_deprecator.py b/tests/test_deprecator.py index 8743474c..f6d4c912 100644 --- a/tests/test_deprecator.py +++ b/tests/test_deprecator.py @@ -31,7 +31,6 @@ def test_deprecated(capsys): expected_print_msg = "Testing deprecated function" with pytest.deprecated_call(match=dep_msg): oldFunction(expected_print_msg) - captured = capsys.readouterr() actual_print_msg = captured.out.strip() assert actual_print_msg == expected_print_msg From 1c6bcda4e8964a512ac0cbb12b95f5fa73d7968e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 3 Feb 2026 12:03:45 -0500 Subject: [PATCH 675/696] change function to build_deprecation_message --- news/dep-msg-helper.rst | 2 +- src/diffpy/utils/_deprecator.py | 11 +++++++---- src/diffpy/utils/parsers/loaddata.py | 4 ++-- tests/test_deprecator.py | 6 ++++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/news/dep-msg-helper.rst b/news/dep-msg-helper.rst index 733801e6..620a532a 100644 --- a/news/dep-msg-helper.rst +++ b/news/dep-msg-helper.rst @@ -1,6 +1,6 @@ **Added:** -* Add ``deprecation_message`` helper for printing consistent deprecation messages. +* Add ``build_deprecation_message`` helper for printing consistent deprecation messages. **Changed:** diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 317c224e..6eeb2225 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -20,7 +20,9 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): .. code-block:: python - from diffpy.utils._deprecator import deprecated, deprecation_message + from diffpy.utils._deprecator import ( + deprecated, build_deprecation_message + ) deprecation_warning = build_deprecation_message("diffpy.utils", "old_function", @@ -44,8 +46,9 @@ def new_function(x, y): .. code-block:: python - from diffpy.utils._deprecator import deprecated, deprecation_message - + from diffpy.utils._deprecator import ( + deprecated, build_deprecation_message + ) deprecation_warning = build_deprecation_message("diffpy.utils", "OldAtom", "NewAtom", @@ -96,7 +99,7 @@ def wrapper(*args, **kwargs): return decorator -def deprecation_message( +def build_deprecation_message( old_base, old_name, new_name, removal_version, new_base=None ): """Generate a deprecation message. diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 18601258..58a49a46 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -18,12 +18,12 @@ import numpy from diffpy.utils import validators -from diffpy.utils._deprecator import deprecated, deprecation_message +from diffpy.utils._deprecator import build_deprecation_message, deprecated base = "diffpy.utils.parsers.loaddata" removal_version = "4.0.0" -loaddata_deprecation_msg = deprecation_message( +loaddata_deprecation_msg = build_deprecation_message( base, "loadData", "load_data", diff --git a/tests/test_deprecator.py b/tests/test_deprecator.py index f6d4c912..c4f2d25b 100644 --- a/tests/test_deprecator.py +++ b/tests/test_deprecator.py @@ -1,13 +1,15 @@ import pytest -from diffpy.utils._deprecator import deprecated, deprecation_message +from diffpy.utils._deprecator import build_deprecation_message, deprecated old_base = "diffpy.utils" old_name = "oldFunction" new_name = "new_function" removal_version = "4.0.0" -dep_msg = deprecation_message(old_base, old_name, new_name, removal_version) +dep_msg = build_deprecation_message( + old_base, old_name, new_name, removal_version +) @deprecated(dep_msg) From 2a79ecf3438ef5512196c715d1e14ff02c2e9c0d Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 4 Feb 2026 10:03:07 -0500 Subject: [PATCH 676/696] readd docstring generator with a simple cli tool --- src/diffpy/utils/_deprecator.py | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 6eeb2225..47d2d6ef 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -1,3 +1,4 @@ +import argparse import functools import warnings @@ -134,3 +135,73 @@ def build_deprecation_message( f"version {removal_version}. Please use '{new_base}.{new_name}' " f"instead." ) + + +def generate_deprecation_docstring(new_name, removal_version, new_base=None): + """Generate a docstring for copy-pasting into a deprecated function. + + This function will print the text to the terminal for copy-pasting. + + Usage + ----- + >>> from diffpy.utils._deprecator import generate_deprecation_docstring + >>> generate_deprecation_docstring("new_name", "4.0.0") + + The message looks like: + This function has been deprecated and will be removed in version + {removal_version}. Please use {new_base}.{new_name} instead. + + Parameters + ---------- + new_name : str + The name of the new function or class to replace the existing one. + removal_version : str + The version when the deprecated item is targeted for removal, + e.g., 4.0.0. + new_base : str, optional + The new base for importing. The new import statement would look like + "from new_base import new_name". Defaults to None. + + Returns + ------- + None + """ + print( + f"This function has been deprecated and will be removed in version " + f"{removal_version}.\n" + f"Please use {new_base}.{new_name} instead." + ) + return + + +def main(): + parser = argparse.ArgumentParser( + description="""\ +Print a docstring for copy-pasting into a deprecated function. + +Example usage +------------- + python -m diffpy.utils._deprecator load_data 4.0.0 -n diffpy.utils.parsers +""", + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument("new_name", help="Name of the new function.") + parser.add_argument( + "removal_version", + help="Version when the deprecated item will be removed.", + ) + parser.add_argument( + "-n", "--new-base", default=None, help="New import base." + ) + + args = parser.parse_args() + + generate_deprecation_docstring( + new_name=args.new_name, + removal_version=args.removal_version, + new_base=args.new_base, + ) + + +if __name__ == "__main__": + main() From 106b419417c1306623a194634c691f2163df5c57 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 4 Feb 2026 08:05:08 -0800 Subject: [PATCH 677/696] Update news for Python 3.14 support and changes Added command line interface for generating template docstring for deprecated functions and updated references for loadData. --- news/python-version.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/news/python-version.rst b/news/python-version.rst index a037a121..a5d08f5b 100644 --- a/news/python-version.rst +++ b/news/python-version.rst @@ -1,6 +1,7 @@ **Added:** * Support for Python 3.14 +* Command line interface for generating a template docstring for deprecated functions **Changed:** @@ -16,7 +17,7 @@ **Fixed:** -* +* All references to ``loadData`` changed to ``load_data`` due to that deprecation **Security:** From 77db094c81311bd7cf44bebedf1427bf56a978d8 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 4 Feb 2026 11:08:10 -0500 Subject: [PATCH 678/696] reorder items in docstring for proper rendering --- src/diffpy/utils/_deprecator.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 47d2d6ef..1fcf3f00 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -142,15 +142,6 @@ def generate_deprecation_docstring(new_name, removal_version, new_base=None): This function will print the text to the terminal for copy-pasting. - Usage - ----- - >>> from diffpy.utils._deprecator import generate_deprecation_docstring - >>> generate_deprecation_docstring("new_name", "4.0.0") - - The message looks like: - This function has been deprecated and will be removed in version - {removal_version}. Please use {new_base}.{new_name} instead. - Parameters ---------- new_name : str @@ -162,6 +153,16 @@ def generate_deprecation_docstring(new_name, removal_version, new_base=None): The new base for importing. The new import statement would look like "from new_base import new_name". Defaults to None. + Example + ------- + >>> from diffpy.utils._deprecator import generate_deprecation_docstring + >>> generate_deprecation_docstring("new_name", "4.0.0") + + The message looks like: + This function has been deprecated and will be removed in version + {removal_version}. Please use {new_base}.{new_name} instead. + + Returns ------- None From ce61f514ce4f0368f39fc15d12708f61e138b635 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 4 Feb 2026 11:23:26 -0500 Subject: [PATCH 679/696] rm cli, we will put it in scikit-package --- src/diffpy/utils/_deprecator.py | 34 --------------------------------- 1 file changed, 34 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 1fcf3f00..0d5ce770 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -1,4 +1,3 @@ -import argparse import functools import warnings @@ -173,36 +172,3 @@ def generate_deprecation_docstring(new_name, removal_version, new_base=None): f"Please use {new_base}.{new_name} instead." ) return - - -def main(): - parser = argparse.ArgumentParser( - description="""\ -Print a docstring for copy-pasting into a deprecated function. - -Example usage -------------- - python -m diffpy.utils._deprecator load_data 4.0.0 -n diffpy.utils.parsers -""", - formatter_class=argparse.RawTextHelpFormatter, - ) - parser.add_argument("new_name", help="Name of the new function.") - parser.add_argument( - "removal_version", - help="Version when the deprecated item will be removed.", - ) - parser.add_argument( - "-n", "--new-base", default=None, help="New import base." - ) - - args = parser.parse_args() - - generate_deprecation_docstring( - new_name=args.new_name, - removal_version=args.removal_version, - new_base=args.new_base, - ) - - -if __name__ == "__main__": - main() From efd094d61e35d2d45a511e8950e7c48d442a49b9 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 4 Feb 2026 11:37:36 -0500 Subject: [PATCH 680/696] clean news items to reflect new changes --- news/dep-msg-helper.rst | 2 ++ news/deprecator.rst | 1 - news/python-version.rst | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/news/dep-msg-helper.rst b/news/dep-msg-helper.rst index 620a532a..91496c2b 100644 --- a/news/dep-msg-helper.rst +++ b/news/dep-msg-helper.rst @@ -1,6 +1,8 @@ **Added:** * Add ``build_deprecation_message`` helper for printing consistent deprecation messages. +* Add ``generate_deprecation_docstring`` for generating a template docstring for deprecated functions. + **Changed:** diff --git a/news/deprecator.rst b/news/deprecator.rst index 01b88147..dabbfdf1 100644 --- a/news/deprecator.rst +++ b/news/deprecator.rst @@ -1,6 +1,5 @@ **Added:** -* added a function in _deprecator to generate a deprecation message for copy pasting * Add ``@deprecated`` decorator. **Changed:** diff --git a/news/python-version.rst b/news/python-version.rst index a5d08f5b..03d0f371 100644 --- a/news/python-version.rst +++ b/news/python-version.rst @@ -1,7 +1,6 @@ **Added:** * Support for Python 3.14 -* Command line interface for generating a template docstring for deprecated functions **Changed:** From 38cc8fd65bf94b1de4abd0a5110117992bab0464 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 4 Feb 2026 13:45:23 -0500 Subject: [PATCH 681/696] api docs --- docs/source/api/diffpy.utils.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/source/api/diffpy.utils.rst b/docs/source/api/diffpy.utils.rst index c50dd0cd..a49f55b2 100644 --- a/docs/source/api/diffpy.utils.rst +++ b/docs/source/api/diffpy.utils.rst @@ -20,6 +20,14 @@ Subpackages Submodules ---------- +diffpy.utils.utils_app module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.utils_app + :members: + :undoc-members: + :show-inheritance: + diffpy.utils.transforms module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -44,6 +52,14 @@ diffpy.utils.tools module :undoc-members: :show-inheritance: +diffpy.utils._deprecator module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils._deprecator + :members: + :undoc-members: + :show-inheritance: + diffpy.utils.diffraction_objects module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From b54c6539b70b8ca8d6a5f1a028851398d84aa5d1 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 4 Feb 2026 13:45:52 -0500 Subject: [PATCH 682/696] news --- news/api-docs370.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/api-docs370.rst diff --git a/news/api-docs370.rst b/news/api-docs370.rst new file mode 100644 index 00000000..f511ac4c --- /dev/null +++ b/news/api-docs370.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news needed: add api docs + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 59b9f096c049b44e96bcd0373ca8266af073d988 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 4 Feb 2026 14:50:45 -0500 Subject: [PATCH 683/696] fix print statement to handle None better --- src/diffpy/utils/_deprecator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 0d5ce770..9a0347de 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -166,9 +166,13 @@ def generate_deprecation_docstring(new_name, removal_version, new_base=None): ------- None """ + if new_base is None: + function_location = new_name + else: + function_location = f"{new_base}.{new_name}" print( f"This function has been deprecated and will be removed in version " f"{removal_version}.\n" - f"Please use {new_base}.{new_name} instead." + f"Please use {function_location} instead." ) return From fdca183b3416bcbab80c993c892ee94f55a81cd5 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 4 Feb 2026 14:51:21 -0500 Subject: [PATCH 684/696] news --- news/ds-bug.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/ds-bug.rst diff --git a/news/ds-bug.rst b/news/ds-bug.rst new file mode 100644 index 00000000..e550df81 --- /dev/null +++ b/news/ds-bug.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news needed: small bug fix. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 14f2c1921ea9bdd7f0f1b668d172c9846ea7bc19 Mon Sep 17 00:00:00 2001 From: sbillinge <4254545+sbillinge@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:19:25 +0000 Subject: [PATCH 685/696] update changelog --- CHANGELOG.rst | 30 ++++++++++++++++++++++++++++++ news/api-docs370.rst | 23 ----------------------- news/dep-msg-helper.rst | 25 ------------------------- news/depr-tests.rst | 23 ----------------------- news/deprecated_update.rst | 23 ----------------------- news/deprecator.rst | 23 ----------------------- news/docs-add-data.rst | 23 ----------------------- news/docs-fix.rst | 23 ----------------------- news/docstring-fix.rst | 23 ----------------------- news/ds-bug.rst | 23 ----------------------- news/headerfile.rst | 23 ----------------------- news/html-build.rst | 23 ----------------------- news/mu-docs.rst | 23 ----------------------- news/python-version.rst | 23 ----------------------- news/rm-packing-fraction.rst | 23 ----------------------- news/scikit-package.rst | 23 ----------------------- 16 files changed, 30 insertions(+), 347 deletions(-) delete mode 100644 news/api-docs370.rst delete mode 100644 news/dep-msg-helper.rst delete mode 100644 news/depr-tests.rst delete mode 100644 news/deprecated_update.rst delete mode 100644 news/deprecator.rst delete mode 100644 news/docs-add-data.rst delete mode 100644 news/docs-fix.rst delete mode 100644 news/docstring-fix.rst delete mode 100644 news/ds-bug.rst delete mode 100644 news/headerfile.rst delete mode 100644 news/html-build.rst delete mode 100644 news/mu-docs.rst delete mode 100644 news/python-version.rst delete mode 100644 news/rm-packing-fraction.rst delete mode 100644 news/scikit-package.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 302e6949..d5f1cca8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,36 @@ Release notes .. current developments +3.7.0 +===== + +**Added:** + +* Add ``build_deprecation_message`` helper for printing consistent deprecation messages. +* Add ``generate_deprecation_docstring`` for generating a template docstring for deprecated functions. +* Support for Python 3.14 +* Add ``@deprecated`` decorator. +* Add documentation for calculation mu. + +**Changed:** + +* load_data now takes a Path or a string for the file-path + +**Deprecated:** + +* diffpy.utils.parsers.loaddata.loadData replaced by diffpy.utils.parsers.load_data + +**Fixed:** + +* Normalize paths before assert in `test_serialization/test_load_multiple` to prevent possible Windows short/long name mismatch. +* All references to ``loadData`` changed to ``load_data`` due to that deprecation + +**Removed:** + +* Removed API that calculates mu from packing fraction. +* Support for Python 3.11 + + 3.6.1 ===== diff --git a/news/api-docs370.rst b/news/api-docs370.rst deleted file mode 100644 index f511ac4c..00000000 --- a/news/api-docs370.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news needed: add api docs - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/dep-msg-helper.rst b/news/dep-msg-helper.rst deleted file mode 100644 index 91496c2b..00000000 --- a/news/dep-msg-helper.rst +++ /dev/null @@ -1,25 +0,0 @@ -**Added:** - -* Add ``build_deprecation_message`` helper for printing consistent deprecation messages. -* Add ``generate_deprecation_docstring`` for generating a template docstring for deprecated functions. - - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/depr-tests.rst b/news/depr-tests.rst deleted file mode 100644 index 12559e13..00000000 --- a/news/depr-tests.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* load_data now takes a Path or a string for the file-path - -**Deprecated:** - -* diffpy.utils.parsers.loaddata.loadData replaced by diffpy.utils.parsers.load_data - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/deprecated_update.rst b/news/deprecated_update.rst deleted file mode 100644 index 27fc85d7..00000000 --- a/news/deprecated_update.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news needed. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/deprecator.rst b/news/deprecator.rst deleted file mode 100644 index dabbfdf1..00000000 --- a/news/deprecator.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Add ``@deprecated`` decorator. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docs-add-data.rst b/news/docs-add-data.rst deleted file mode 100644 index ad18e7a5..00000000 --- a/news/docs-add-data.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No News Added: Add real values into example - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docs-fix.rst b/news/docs-fix.rst deleted file mode 100644 index 2c81480c..00000000 --- a/news/docs-fix.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No News Added: Fix import in examples - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docstring-fix.rst b/news/docstring-fix.rst deleted file mode 100644 index fc596a63..00000000 --- a/news/docstring-fix.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No News Added: Fix docstring - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/ds-bug.rst b/news/ds-bug.rst deleted file mode 100644 index e550df81..00000000 --- a/news/ds-bug.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news needed: small bug fix. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/headerfile.rst b/news/headerfile.rst deleted file mode 100644 index 13b35c28..00000000 --- a/news/headerfile.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Normalize paths before assert in `test_serialization/test_load_multiple` to prevent possible Windows short/long name mismatch. - -**Security:** - -* diff --git a/news/html-build.rst b/news/html-build.rst deleted file mode 100644 index cf04edf2..00000000 --- a/news/html-build.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No News Added: Fix format of docs to build html - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/mu-docs.rst b/news/mu-docs.rst deleted file mode 100644 index d11e372c..00000000 --- a/news/mu-docs.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Add documentation for calculation mu. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/python-version.rst b/news/python-version.rst deleted file mode 100644 index 03d0f371..00000000 --- a/news/python-version.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Support for Python 3.14 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* Support for Python 3.11 - -**Fixed:** - -* All references to ``loadData`` changed to ``load_data`` due to that deprecation - -**Security:** - -* diff --git a/news/rm-packing-fraction.rst b/news/rm-packing-fraction.rst deleted file mode 100644 index 70bccad7..00000000 --- a/news/rm-packing-fraction.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* Removed API that calculates mu from packing fraction. - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scikit-package.rst b/news/scikit-package.rst deleted file mode 100644 index 9f7ee0e6..00000000 --- a/news/scikit-package.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No News Added: recut diffpy.utils using latest scikit-package - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From 103f1f457069b4838c60354c88f1a3392a6f759e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 5 Feb 2026 16:37:35 -0500 Subject: [PATCH 686/696] add support back for python 3.11 --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2e3da67d..3fd75830 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ description = "General utilities for analyzing diffraction data" keywords = ['text data parsers', 'wx grid', 'diffraction objects'] readme = "README.rst" -requires-python = ">=3.12, <3.15" +requires-python = ">=3.11, <3.15" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -25,6 +25,7 @@ classifiers = [ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', + 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3.14', From c77bdafd335a4086822b49016258be9e025dbe5b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 5 Feb 2026 16:38:42 -0500 Subject: [PATCH 687/696] news --- news/support-py311.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/support-py311.rst diff --git a/news/support-py311.rst b/news/support-py311.rst new file mode 100644 index 00000000..1cfb1b43 --- /dev/null +++ b/news/support-py311.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add support for python 3.11 for deprecator usage. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From ed1dfb912fdee4bd6b2ca73c2ead4c4995ca1f5c Mon Sep 17 00:00:00 2001 From: sbillinge <4254545+sbillinge@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:47:27 +0000 Subject: [PATCH 688/696] update changelog --- CHANGELOG.rst | 8 ++++++++ news/support-py311.rst | 23 ----------------------- 2 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 news/support-py311.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d5f1cca8..dc11a8c5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,14 @@ Release notes .. current developments +3.7.1 +===== + +**Added:** + +* Add support for python 3.11 for deprecator usage. + + 3.7.0 ===== diff --git a/news/support-py311.rst b/news/support-py311.rst deleted file mode 100644 index 1cfb1b43..00000000 --- a/news/support-py311.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Add support for python 3.11 for deprecator usage. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From d6cc05d70bfe81f5f5e51ebe3109a0fb453aa418 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 17 Mar 2026 09:28:32 -0400 Subject: [PATCH 689/696] readd support for python 3.10 --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3fd75830..ec8ad217 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ description = "General utilities for analyzing diffraction data" keywords = ['text data parsers', 'wx grid', 'diffraction objects'] readme = "README.rst" -requires-python = ">=3.11, <3.15" +requires-python = ">=3.10, <3.15" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -25,6 +25,7 @@ classifiers = [ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', From 0a68debdca2961a666de8601469211d966932795 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 17 Mar 2026 09:29:06 -0400 Subject: [PATCH 690/696] news --- news/py310.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/py310.rst diff --git a/news/py310.rst b/news/py310.rst new file mode 100644 index 00000000..54221a14 --- /dev/null +++ b/news/py310.rst @@ -0,0 +1,23 @@ +**Added:** + +* Added Support for Python 3.10 + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 16d9b8967d6abd462a8cc6fcd3fd286a5af96452 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 17 Mar 2026 09:51:33 -0400 Subject: [PATCH 691/696] run pre-commit autoupdate --- .pre-commit-config.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e4a84d1..f189a945 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: submodules: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - id: check-yaml - id: end-of-file-fixer @@ -21,45 +21,45 @@ repos: - id: check-toml - id: check-added-large-files - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 26.3.1 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.3.0 hooks: - id: flake8 - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: 8.0.1 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/kynan/nbstripout - rev: 0.7.1 + rev: 0.9.1 hooks: - id: nbstripout - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: no-commit-to-branch name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.2 hooks: - id: codespell additional_dependencies: - tomli # prettier - multi formatter for .json, .yml, and .md files - repo: https://github.com/pre-commit/mirrors-prettier - rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 + rev: v4.0.0-alpha.8 hooks: - id: prettier additional_dependencies: - "prettier@^3.2.4" # docformatter - PEP 257 compliant docstring formatter - - repo: https://github.com/s-weigand/docformatter - rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + - repo: https://github.com/PyCQA/docformatter + rev: v1.7.7 hooks: - id: docformatter additional_dependencies: [tomli] From 0d907b44580231823ce25d069ed78e77569f4a1c Mon Sep 17 00:00:00 2001 From: sbillinge <4254545+sbillinge@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:46:41 +0000 Subject: [PATCH 692/696] update changelog --- CHANGELOG.rst | 8 ++++++++ news/py310.rst | 23 ----------------------- 2 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 news/py310.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dc11a8c5..31873450 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,14 @@ Release notes .. current developments +3.7.2 +===== + +**Added:** + +* Added Support for Python 3.10 + + 3.7.1 ===== diff --git a/news/py310.rst b/news/py310.rst deleted file mode 100644 index 54221a14..00000000 --- a/news/py310.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Added Support for Python 3.10 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From 4032a0f163f0641d010249aa2fb4169ec54624cc Mon Sep 17 00:00:00 2001 From: symscae Date: Thu, 28 May 2026 19:42:35 -0700 Subject: [PATCH 693/696] chore: updated dependencies --- .pre-commit-config.yaml | 6 +++--- news/dependency-update.rst | 23 +++++++++++++++++++++++ src/diffpy/__init__.py | 1 - src/diffpy/utils/parsers/serialization.py | 9 ++++----- src/diffpy/utils/resampler.py | 1 - src/diffpy/utils/wx/gridutils.py | 5 +++-- 6 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 news/dependency-update.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f189a945..c424bcf7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: check-toml - id: check-added-large-files - repo: https://github.com/psf/black - rev: 26.3.1 + rev: 26.5.1 hooks: - id: black - repo: https://github.com/pycqa/flake8 @@ -29,7 +29,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pycqa/isort - rev: 8.0.1 + rev: 9.0.0a3 hooks: - id: isort args: ["--profile", "black"] @@ -59,7 +59,7 @@ repos: - "prettier@^3.2.4" # docformatter - PEP 257 compliant docstring formatter - repo: https://github.com/PyCQA/docformatter - rev: v1.7.7 + rev: v1.7.8 hooks: - id: docformatter additional_dependencies: [tomli] diff --git a/news/dependency-update.rst b/news/dependency-update.rst new file mode 100644 index 00000000..23f5b4a4 --- /dev/null +++ b/news/dependency-update.rst @@ -0,0 +1,23 @@ +**Added:** + +No news needed: updating docformatter, isort and black. Not user facing. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 00208e23..99124eeb 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -21,7 +21,6 @@ Blank namespace package. """ - from pkgutil import extend_path __path__ = extend_path(__path__, __name__) diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 5acbbe41..46e29d2e 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -33,9 +33,10 @@ def serialize_data( show_path=True, serial_file=None, ): - """Serialize file data into a dictionary. Can also save dictionary - into a serial language file. Dictionary is formatted as {filename: - data}. + """Serialize file data into a dictionary. + + Can also save dictionary into a serial language file. + Dictionary is formatted as {filename:data}. Requires hdata and data_table (can be generated by load_data). @@ -67,7 +68,6 @@ def serialize_data( dict: Returns the dictionary loaded from/into the updated database file. """ - # compile data_table and hddata together data = {} @@ -191,7 +191,6 @@ def deserialize_data(filename, filetype=None): dict A dictionary read from a serial file. """ - # check if supported type f = pathlib.Path(filename) f_name = f.name diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 354eb37d..1e0f035d 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -141,7 +141,6 @@ def resample(r, s, dr): ------- Returns resampled ``(r, s)``. """ - warnings.warn( ( "The 'resample' function is deprecated and will be removed " diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index d94860ca..f585cc41 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -81,8 +81,9 @@ def getSelectedCells(grid): def limitSelectionToRows(grid, indices): - """Limit selection to the specified row indices. No action for empty - indices. + """Limit selection to the specified row indices. + + No action for empty indices. Parameters ---------- From cfd1c626043972475b1b9daf7cd57b19b2b6e7e9 Mon Sep 17 00:00:00 2001 From: symscae Date: Thu, 28 May 2026 19:53:04 -0700 Subject: [PATCH 694/696] fix news item --- news/dependency-update.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/dependency-update.rst b/news/dependency-update.rst index 23f5b4a4..abd1d67a 100644 --- a/news/dependency-update.rst +++ b/news/dependency-update.rst @@ -1,6 +1,6 @@ **Added:** -No news needed: updating docformatter, isort and black. Not user facing. +* No news needed: updating docformatter, isort and black. Not user facing. **Changed:** From 4b7583ea0a97fc63214dbef775160aa766875a3e Mon Sep 17 00:00:00 2001 From: symscae Date: Sun, 31 May 2026 13:01:14 -0700 Subject: [PATCH 695/696] revert docformatter version --- .pre-commit-config.yaml | 3 ++- news/dependency-update.rst | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c424bcf7..29a53dfb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,8 +59,9 @@ repos: - "prettier@^3.2.4" # docformatter - PEP 257 compliant docstring formatter - repo: https://github.com/PyCQA/docformatter - rev: v1.7.8 + rev: v1.7.7 hooks: - id: docformatter additional_dependencies: [tomli] args: [--in-place, --config, ./pyproject.toml] + language_version: python3.13 diff --git a/news/dependency-update.rst b/news/dependency-update.rst index abd1d67a..fcb87efa 100644 --- a/news/dependency-update.rst +++ b/news/dependency-update.rst @@ -1,6 +1,6 @@ **Added:** -* No news needed: updating docformatter, isort and black. Not user facing. +* No news needed: updating isort and black. Not user facing. **Changed:** From 33e71bc73f3ad67ba477e801c9543f43e1d80c3b Mon Sep 17 00:00:00 2001 From: symscae Date: Mon, 1 Jun 2026 15:39:37 -0700 Subject: [PATCH 696/696] Revert "revert docformatter version" This reverts commit 4b7583ea0a97fc63214dbef775160aa766875a3e. --- .pre-commit-config.yaml | 3 +-- news/dependency-update.rst | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 29a53dfb..c424bcf7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,9 +59,8 @@ repos: - "prettier@^3.2.4" # docformatter - PEP 257 compliant docstring formatter - repo: https://github.com/PyCQA/docformatter - rev: v1.7.7 + rev: v1.7.8 hooks: - id: docformatter additional_dependencies: [tomli] args: [--in-place, --config, ./pyproject.toml] - language_version: python3.13 diff --git a/news/dependency-update.rst b/news/dependency-update.rst index fcb87efa..abd1d67a 100644 --- a/news/dependency-update.rst +++ b/news/dependency-update.rst @@ -1,6 +1,6 @@ **Added:** -* No news needed: updating isort and black. Not user facing. +* No news needed: updating docformatter, isort and black. Not user facing. **Changed:**