diff --git a/.gitea/workflows/build-test.yaml b/.gitea/workflows/build-test.yaml new file mode 100644 index 0000000..1ee983b --- /dev/null +++ b/.gitea/workflows/build-test.yaml @@ -0,0 +1,51 @@ +name: build and test the package + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: check runner environment + run: | + uname -a + lsb_release -a + echo "Runner home: $HOME" + + - name: check out + uses: actions/checkout@v5 + + - name: set up compilers + run: | + sudo apt-get update + sudo apt-get -y install binutils build-essential g++ gcc gfortran libblas-dev liblapack-dev openmpi-bin openmpi-common sqlite3 + + - name: set up python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.18" + enable-cache: true + + - name: lint with ruff + # configuration is in pyproject.toml + run: | + uvx ruff check --extend-exclude=.venv,build pmsco + + - name: install dependencies + run: uv sync --locked --all-extras --dev + + - name: tests + run: | + uv run nosetests diff --git a/.gitea/workflows/deploy-pages.yaml b/.gitea/workflows/deploy-pages.yaml new file mode 100644 index 0000000..508bacb --- /dev/null +++ b/.gitea/workflows/deploy-pages.yaml @@ -0,0 +1,45 @@ +name: build and deploy documentation + +on: + push: + branches: + - master + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + container: + image: gitea.psi.ch/pearl/docs + credentials: + username: ${{ gitea.actor }} + password: ${{ secrets.package_token }} + + steps: + - name: checkout + working-directory: /app + run: | + git clone --branch master --single-branch https://${{ secrets.REPO_TOKEN }}@gitea.psi.ch/${{ github.repository }}.git + + - name: build + working-directory: /app/pmsco/docs + run: | + export REVISION=$(shell git describe --always --tags --dirty --long || echo "unknown, "`date +"%F %T %z"`) + export OUTDIR=/app/build + doxygen config.dox + + - name: configure git + working-directory: /app/pmsco + run: | + git config --global user.name "Gitea Actions" + git config --global user.email "actions@gitea.local" + + - name: push to gitea-pages + working-directory: /app/pmsco + run: | + git checkout --orphan gitea-pages + git reset --hard + cp -r /app/build/html/* . + git add . + git commit -m "Deploy documentation to gitea" + git push -f https://${{ secrets.REPO_TOKEN }}@gitea.psi.ch/${{ github.repository }}.git gitea-pages + diff --git a/.githooks/install-hooks.sh b/.githooks/install-hooks.sh new file mode 100755 index 0000000..96fdc04 --- /dev/null +++ b/.githooks/install-hooks.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +echo "Setting up Git hooks..." +cd "$(dirname "$0")" +cd .. + +# Create symlinks +ln -sf ../../.githooks/pre-commit .git/hooks/pre-commit +ln -sf ../../.githooks/pre-push .git/hooks/pre-push + +chmod +x .git/hooks/* +echo "Git hooks installed successfully!" diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..5d6219c --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,34 @@ +#!/bin/bash +# .git/hooks/pre-commit +# requires uv + +# Track overall status +PASS=true + +# Color output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}Running pre-commit checks...${NC}" + +PY_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$') + +# Python checks +if [ -n "$PY_FILES" ]; then + echo -e "${YELLOW}Checking Python files...${NC}" + + if ! uvx ruff check --extend-exclude=.*,build*; then + PASS=false + fi +fi + +# Final status +if [ "$PASS" = true ]; then + echo -e "${GREEN}All checks passed!${NC}" + exit 0 +else + echo -e "${RED}Some checks failed. Please fix issues before committing.${NC}" + exit 1 +fi diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..3efacdb --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,43 @@ +#!/bin/bash +# .git/hooks/pre-push + +# Track overall status +PASS=true + +# Color output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}Running pre-push checks...${NC}" + +PY_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$') + +# Python checks +echo -e "${YELLOW}Checking Python files...${NC}" + +# Lint +if ! uvx ruff check --extend-exclude=.*,build*; then + PASS=false +fi + +# Compile +uv sync + +# Run different test suites based on changed files +echo -e "${YELLOW}Running Python tests...${NC}" + +if ! uv run nosetests; then + echo -e "Tests failed. Push aborted." + PASS=false +fi + +# Final status +if [ "$PASS" = true ]; then + echo -e "${GREEN}All checks passed!${NC}" + exit 0 +else + echo -e "${RED}Some checks failed. Please fix issues before committing.${NC}" + exit 1 +fi diff --git a/.gitignore b/.gitignore index 3c2fca0..206ea99 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ work/* debug/* lib/* dev/* +build/* +__pycache__/* *.pyc *.o *.so @@ -15,3 +17,4 @@ dev/* .ropeproject/* .fuse* .trash +.wraplock diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index acf059c..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,14 +0,0 @@ -pages: - stage: deploy - script: - - ~/miniconda3/bin/activate pmsco - - make docs - - mv docs/html/ public/ - artifacts: - paths: - - public - only: - - master - tags: - - doxygen - diff --git a/CHANGES.md b/CHANGES.md index fe83899..223ae9f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,59 +1,72 @@ +Release 4.2.0 (2026-01-01) +========================== + +- Switch to Astral-UV package manager +- CI lint, build, test workflow in gitea +- Automated documentation workflow in gitea +- Recommended Python version 3.12 (compatibility 3.10-3.13) +- Multipole expansion +- Table optimization mode +- Integrate phagen scattering amplitude calculator +- Select modulation and R-factor functions in runfile +- Parametric holo scan generator +- Namespace package installation, support for editable installation +- Simplified command line +- Meson build system +- Differential cross section in periodic table +- Configurable reports +- Path resolution +- Database interface for reports +- Runfile based job scheduling + + Release 3.0.0 (2021-02-01) ========================== -| Hash | Date | Description | -| ---- | ---- | ----------- | -| 72a9f38 | 2021-02-06 | introduce run file based job scheduling | -| 42e12d8 | 2021-02-05 | compatibility with recent conda and singularity versions | -| caf9f43 | 2021-02-03 | installation: include plantuml.jar | -| 574c88a | 2021-02-01 | docs: replace doxypy by doxypypy | -| a5cb831 | 2021-02-05 | redefine output_file property | -| 49dbb89 | 2021-01-27 | documentation of run file interface | -| 940d9ae | 2021-01-07 | introduce run file interface | -| 6950f98 | 2021-02-05 | set legacy fortran for compatibility with recent compiler | -| 28d8bc9 | 2021-01-27 | graphics: fixed color range for modulation functions | -| 1382508 | 2021-01-16 | cluster: build_element accepts symbol or number | -| 53508b7 | 2021-01-06 | graphics: swarm plot | -| 4a24163 | 2021-01-05 | graphics: genetic chart | -| 99e9782 | 2020-12-23 | periodic table: use common binding energies in condensed matter XPS | -| fdfcf90 | 2020-12-23 | periodic table: reformat bindingenergy.json, add more import/export functions | -| 13cf90f | 2020-12-21 | hbnni: parameters for xpd demo with two domains | -| 680edb4 | 2020-12-21 | documentation: update documentation of optimizers | -| d909469 | 2020-12-18 | doc: update top components diagram (pmsco module is entry point) | -| 574993e | 2020-12-09 | spectrum: add plot cross section function | +- Compatibility with recent conda and singularity versions +- Installation: include plantuml.jar +- Documentation: replace doxypy by doxypypy +- Redefine output_file property +- Documentation of run file interface +- Introduce runfile interface +- Set legacy Fortran for compatibility with recent compiler +- Graphics: fixed color range for modulation functions +- Cluster: build_element accepts symbol or number +- Graphics: swarm plot +- Graphics: genetic chart +- Periodic table: use common binding energies in condensed matter XPS +- Periodic table: reformat bindingenergy.json, add more import/export functions +- Spectrum: add plot cross section function Release 2.2.0 (2020-09-04) ========================== -| Hash | Date | Description | -| ---- | ---- | ----------- | -| 4bb2331 | 2020-07-30 | demo project for arbitrary molecule (cluster file) | -| f984f64 | 2020-09-03 | bugfix: DATA CORRUPTION in phagen translator (emitter mix-up) | -| 11fb849 | 2020-09-02 | bugfix: load native cluster file: wrong column order | -| d071c97 | 2020-09-01 | bugfix: initial-state command line option not respected | -| 9705eed | 2020-07-28 | photoionization cross sections and spectrum simulator | -| 98312f0 | 2020-06-12 | database: use local lock objects | -| c8fb974 | 2020-04-30 | database: create view on results and models | -| 2cfebcb | 2020-05-14 | REFACTORING: Domain -> ModelSpace, Params -> CalculatorParams | -| d5516ae | 2020-05-14 | REFACTORING: symmetry -> domain | -| b2dd21b | 2020-05-13 | possible conda/mpi4py conflict - changed installation procedure | -| cf5c7fd | 2020-05-12 | cluster: new calc_scattering_angles function | -| 20df82d | 2020-05-07 | include a periodic table of binding energies of the elements | -| 5d560bf | 2020-04-24 | clean up files in the main loop and in the end | -| 6e0ade5 | 2020-04-24 | bugfix: database ingestion overwrites results from previous jobs | -| 263b220 | 2020-04-24 | time out at least 10 minutes before the hard time limit given on the command line | -| 4ec526d | 2020-04-09 | cluster: new get_center function | -| fcdef4f | 2020-04-09 | bugfix: type error in grid optimizer | -| a4d1cf7 | 2020-03-05 | bugfix: file extension in phagen/makefile | -| 9461e46 | 2019-09-11 | dispatch: new algo to distribute processing slots to task levels | -| 30851ea | 2020-03-04 | bugfix: load single-line data files correctly! | -| 71fe0c6 | 2019-10-04 | cluster generator for zincblende crystal | -| 23965e3 | 2020-02-26 | phagen translator: fix phase convention (MAJOR), fix single-energy | -| cf1814f | 2019-09-11 | dispatch: give more priority to mid-level tasks in single mode | -| 58c778d | 2019-09-05 | improve performance of cluster add_bulk, add_layer and rotate | -| 20ef1af | 2019-09-05 | unit test for Cluster.translate, bugfix in translate and relax | -| 0b80850 | 2019-07-17 | fix compatibility with numpy >= 1.14, require numpy >= 1.13 | -| 1d0a542 | 2019-07-16 | database: introduce job-tags | -| 8461d81 | 2019-07-05 | qpmsco: delete code after execution | - +- Demo project for arbitrary molecule (cluster file) +- Bugfix: DATA CORRUPTION in phagen translator (emitter mix-up) +- Bugfix: load native cluster file: wrong column order +- Bugfix: initial-state command line option not respected +- Photoionization cross sections and spectrum simulator +- Database: use local lock objects +- Database: create view on results and models +- REFACTORING: Domain -> ModelSpace, Params -> CalculatorParams +- REFACTORING: symmetry -> domain +- Possible conda/mpi4py conflict - changed installation procedure +- Cluster: new calc_scattering_angles function +- Include a periodic table of binding energies of the elements +- Clean up files in the main loop and in the end +- Bugfix: database ingestion overwrites results from previous jobs +- Time out at least 10 minutes before the hard time limit given on the command line +- Cluster: new get_center function +- Bugfix: type error in grid optimizer +- Bugfix: file extension in phagen/makefile +- Dispatch: new algo to distribute processing slots to task levels +- Bugfix: load single-line data files correctly! +- Cluster generator for zincblende crystal +- Phagen translator: fix phase convention (MAJOR), fix single-energy +- Dispatch: give more priority to mid-level tasks in single mode +- Improve performance of cluster add_bulk, add_layer and rotate +- Unit test for Cluster.translate, bugfix in translate and relax +- Fix compatibility with numpy >= 1.14, require numpy >= 1.13 +- Database: introduce job-tags +- qpmsco: delete code after execution diff --git a/NOTICE.md b/NOTICE.md index 47e6075..9165900 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -5,10 +5,10 @@ List of Contributors Original Author --------------- -Matthias Muntwiler, +- Matthias Muntwiler, Contributors ------------ - +- Frederik Schirdewahn, diff --git a/README.md b/README.md index 74966c8..5185263 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ Introduction ============ -PMSCO stands for PEARL multiple-scattering cluster calculations and structural optimization. -It is a collection of computer programs to calculate photoelectron diffraction patterns, -and to optimize structural models based on measured data. +PMSCO (PSI multiple-scattering cluster calculations and structural optimization) +is a Python-based workflow engine to calculate photoelectron diffraction patterns, +and to optimize structural models based on measured data using machine learning techniques. +PMSCO was developed at the [Paul Scherrer Institut (PSI)](https://www.psi.ch/) +by the team of the [PEARL beamline](https://www.psi.ch/en/sls/pearl). The actual scattering calculation is done by code developed by other parties. PMSCO wraps around those programs and facilitates parameter handling, cluster building, structural optimization and parallel processing. @@ -31,46 +33,40 @@ Highlights Installation ============ -PMSCO is written in Python 3.6. -The code will run in any recent Linux environment on a workstation or virtual machine. -Scientific Linux, CentOS7, [Ubuntu](https://www.ubuntu.com/) -and [Lubuntu](http://lubuntu.net/) (recommended for virtual machine) have been tested. -For optimization jobs, a cluster with 20-50 available processor cores is recommended. +PMSCO is written in Python. The recommended Python version is 3.12. +Further requirements are the GNU compiler collection, BLAS/LAPACK libraries, OpenMPI and a package manager such as uv, pip or conda. +For optimization jobs, a cluster machine with 20-50 available processor cores is recommended. +Smaller jobs run on any recent Linux workstation. The code requires about 2 GB of RAM per process. Detailed installation instructions and dependencies can be found in the documentation (docs/src/installation.dox). -A [Doxygen](http://www.stack.nl/~dimitri/doxygen/index.html) compiler with Doxypypy is required to generate the documentation in HTML format. - -The easiest way to set up an environment with all dependencies and without side-effects on other installed software is to use a [Singularity](https://www.sylabs.io/guides/3.7/user-guide/index.html) container. -A Singularity recipe file is part of the distribution, see the PMSCO documentation for details, Singularity must be installed separately. -Installation in a [virtual box](https://www.virtualbox.org/) on Windows or Mac is straightforward using pre-compiled images with [Vagrant](https://www.vagrantup.com/). -A Vagrant definition file is included in the distribution. - -The public distribution of PMSCO does not contain the [EDAC](http://garciadeabajos-group.icfo.es/widgets/edac/) code. -Please obtain the EDAC source code from the original author, copy it to the pmsco/edac directory, and apply the edac_all.patch patch. License ======= The source code of PMSCO is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). -Please read and respect the license agreement. +This _does not include_ the calculation packages contained in the subprojects folder which are licensed separately. -Please share your extensions of the code with the original author. -The gitlab facility can be used to create forks and to submit pull requests. -Attribution notices for your contributions shall be added to the NOTICE.md file. +- Please read and respect the respective license agreements. +- Please acknowledge the use of the code. +- Please consider sharing your developments with the original author. + +Due to different copyright terms, the third-party calculation programs are not contained in the public software repository. +These programs may not be used without an explicit agreement by the respective original authors. -Author ------- +Authors +------- -Matthias Muntwiler, +- Matthias Muntwiler, +- Frederik Schirdewahn, Copyright --------- -Copyright 2015-2021 by [Paul Scherrer Institut](http://www.psi.ch) +Copyright 2015-2025 by [Paul Scherrer Institut](http://www.psi.ch) Release Notes @@ -78,6 +74,31 @@ Release Notes For a detailed list of changes, see the CHANGES.md file. +4.2.0 (2026-01-01) +------------------ + +- Recommended Python version 3.12 (compatibility 3.10-3.13) +- Build system and package environment + - Switch to Astral-UV package manager + - Meson build system for Fortran, C and C++ extension modules + - Namespace package installation, support for editable installation + - CI lint, build, test workflow in gitea + - Automated documentation workflow in gitea +- User interface + - Simplified command line, all configuration via runfile and/or project class + - Select modulation and R-factor functions in runfile + - Parametric holo scan generator + - Configurable reports + - Path resolution in runfile + - Database interface for reports + - Runfile based job scheduling +- Calculation features + - Multipole expansion + - Table optimization mode + - Integrate phagen scattering amplitude calculator + - Differential cross section in periodic table + + 3.0.0 (2021-02-08) ------------------ diff --git a/docs/config.dox b/docs/config.dox index e1b9d23..2ea0afa 100644 --- a/docs/config.dox +++ b/docs/config.dox @@ -1,4 +1,4 @@ -# Doxyfile 1.8.9.1 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -17,11 +17,11 @@ # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "PMSCO" +PROJECT_NAME = PMSCO # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -44,21 +44,21 @@ PROJECT_NUMBER = $(REVISION) # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = "PEARL multiple scattering calculation and optimization" +PROJECT_BRIEF = "PSI multiple scattering calculation and optimization" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = +OUTPUT_DIRECTORY = $(OUTDIR) # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. @@ -118,7 +126,7 @@ REPEAT_BRIEF = YES # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. -ABBREVIATE_BRIEF = +ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief @@ -152,7 +160,7 @@ FULL_PATH_NAMES = NO # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -161,7 +169,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -179,6 +187,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus @@ -199,6 +217,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = NO + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -226,15 +252,14 @@ TAB_SIZE = 4 # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) -ALIASES = "raise=@exception" - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = +ALIASES = raise=@exception # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -264,28 +289,40 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. -EXTENSION_MAPPING = +EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. +# documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. @@ -293,6 +330,15 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -318,7 +364,7 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. @@ -343,6 +389,13 @@ IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent @@ -397,6 +450,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -417,6 +483,12 @@ EXTRACT_ALL = YES EXTRACT_PRIVATE = YES +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. @@ -454,6 +526,13 @@ EXTRACT_LOCAL_METHODS = YES EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -471,8 +550,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -491,11 +570,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = YES @@ -622,7 +708,7 @@ GENERATE_DEPRECATEDLIST= YES # sections, marked by \if ... \endif and \cond # ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the @@ -677,17 +763,17 @@ FILE_VERSION_FILTER = "git log --pretty=format:\"revision %h, %ad\" --date=is # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = +LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. -CITE_BIB_FILES = +CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages @@ -727,11 +813,21 @@ WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = NO +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated @@ -746,7 +842,7 @@ WARN_FORMAT = "$file:$line: $text" # messages should be written. If left blank the output is written to standard # error (stderr). -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -755,42 +851,49 @@ WARN_LOGFILE = # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with -# spaces. +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = \ -src/introduction.dox \ -src/concepts.dox \ -src/concepts-tasks.dox \ -src/concepts-emitter.dox \ -src/concepts-atomscat.dox \ -src/installation.dox \ -src/project.dox \ -src/execution.dox \ -src/commandline.dox \ -src/runfile.dox \ -src/optimizers.dox \ - ../pmsco \ - ../projects \ - ../tests +INPUT = src/introduction.dox \ + src/concepts.dox \ + src/concepts-tasks.dox \ + src/concepts-emitter.dox \ + src/concepts-atomscat.dox \ + src/installation.dox \ + src/project.dox \ + src/execution.dox \ + src/commandline.dox \ + src/runfile.dox \ + src/optimizers.dox \ + src/reports.dox \ + ../pmsco # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank the -# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, -# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, -# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, -# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, -# *.qsf, *.as and *.js. +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.py \ *.dox @@ -808,10 +911,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = ../pmsco/edac \ - ../pmsco/loess \ - ../pmsco/msc \ - ../pmsco/mufpot +EXCLUDE = ../pmsco/projects # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -839,20 +939,20 @@ EXCLUDE_PATTERNS = */__init__.py \ # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands @@ -881,6 +981,10 @@ IMAGE_PATH = src/images # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. INPUT_FILTER = @@ -890,6 +994,10 @@ INPUT_FILTER = # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. FILTER_PATTERNS = *.py=./py_filter.sh @@ -906,14 +1014,14 @@ FILTER_SOURCE_FILES = NO # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. -FILTER_SOURCE_PATTERNS = +FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -942,7 +1050,7 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. +# entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO @@ -974,12 +1082,12 @@ SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # @@ -1002,23 +1110,42 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. # Note: The availability of this option depends on whether or not doxygen was -# compiled with the --with-libclang option. +# generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. -CLANG_OPTIONS = +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index @@ -1031,20 +1158,13 @@ CLANG_OPTIONS = ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 8 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output @@ -1088,7 +1208,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1098,7 +1218,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1110,7 +1230,7 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1123,7 +1243,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1133,12 +1253,12 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. @@ -1167,12 +1287,24 @@ HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: YES. +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. @@ -1196,13 +1328,14 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1241,8 +1374,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1261,7 +1394,7 @@ GENERATE_HTMLHELP = NO # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_FILE = +CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, @@ -1269,10 +1402,10 @@ CHM_FILE = # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -HHC_LOCATION = +HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1282,7 +1415,7 @@ GENERATE_CHI = NO # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it @@ -1313,11 +1446,12 @@ GENERATE_QHP = NO # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. -QCH_FILE = +QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1325,8 +1459,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1334,33 +1468,33 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_ATTRS = +QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_SECT_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = +QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -1434,6 +1568,17 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1443,7 +1588,7 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # @@ -1454,8 +1599,14 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1467,7 +1618,7 @@ USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1482,8 +1633,8 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest @@ -1493,15 +1644,16 @@ MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_EXTENSIONS = +MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_CODEFILE = +MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and @@ -1525,7 +1677,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1544,7 +1696,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1557,11 +1710,12 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. -SEARCHENGINE_URL = +SEARCHENGINE_URL = # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed # search data is written to a file for indexing by an external tool. With the @@ -1577,7 +1731,7 @@ SEARCHDATA_FILE = searchdata.xml # projects and redirect the results back to the right project. # This tag requires that the tag SEARCHENGINE is set to YES. -EXTERNAL_SEARCH_ID = +EXTERNAL_SEARCH_ID = # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen # projects other than the one defined by this configuration file, but that are @@ -1587,7 +1741,7 @@ EXTERNAL_SEARCH_ID = # EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... # This tag requires that the tag SEARCHENGINE is set to YES. -EXTRA_SEARCH_MAPPINGS = +EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output @@ -1596,7 +1750,7 @@ EXTRA_SEARCH_MAPPINGS = # If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. # The default value is: YES. -GENERATE_LATEX = YES +GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of @@ -1609,21 +1763,35 @@ LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. # -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). # The default file is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. @@ -1642,13 +1810,16 @@ COMPACT_LATEX = NO PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. To get the times font for -# instance you can specify -# EXTRA_PACKAGES=times +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for the # generated LaTeX document. The header should contain everything until the first @@ -1664,7 +1835,7 @@ EXTRA_PACKAGES = # to HTML_HEADER. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_HEADER = +LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the # generated LaTeX document. The footer should contain everything after the last @@ -1675,7 +1846,7 @@ LATEX_HEADER = # Note: Only use a user-defined footer if you know what you are doing! # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_FOOTER = +LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created @@ -1686,7 +1857,7 @@ LATEX_FOOTER = # list). # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_STYLESHEET = # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output @@ -1694,7 +1865,7 @@ LATEX_EXTRA_STYLESHEET = # markers available. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_FILES = +LATEX_EXTRA_FILES = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is # prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will @@ -1705,9 +1876,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1741,12 +1914,28 @@ LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. # The default value is: plain. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- @@ -1786,22 +1975,22 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the # default style sheet that doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = # If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code # with syntax highlighting in the RTF output. @@ -1846,7 +2035,7 @@ MAN_EXTENSION = .3 # MAN_EXTENSION with the initial . removed. # This tag requires that the tag GENERATE_MAN is set to YES. -MAN_SUBDIR = +MAN_SUBDIR = # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it # will generate one additional man file for each entity documented in the real @@ -1884,6 +2073,13 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- @@ -1916,9 +2112,9 @@ DOCBOOK_PROGRAMLISTING = NO #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sf.net) file that captures the -# structure of the code including all documentation. Note that this feature is -# still experimental and incomplete at the moment. +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO @@ -1959,7 +2155,7 @@ PERLMOD_PRETTY = YES # overwrite each other's variables. # This tag requires that the tag GENERATE_PERLMOD is set to YES. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor @@ -2000,7 +2196,7 @@ SEARCH_INCLUDES = YES # preprocessor. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -2008,7 +2204,7 @@ INCLUDE_PATH = # used. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of e.g. @@ -2018,7 +2214,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2027,7 +2223,7 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have @@ -2056,13 +2252,13 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = +GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES, all external class will be listed in # the class index. If set to NO, only the inherited external classes will be @@ -2103,7 +2299,7 @@ CLASS_DIAGRAMS = YES # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. -DIA_PATH = +DIA_PATH = # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. @@ -2152,7 +2348,7 @@ DOT_FONTSIZE = 10 # the path where dot can find it using this tag. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTPATH = +DOT_FONTPATH = # If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for # each documented class showing the direct and indirect inheritance relations. @@ -2195,10 +2391,32 @@ UML_LOOK = YES # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2230,7 +2448,8 @@ INCLUDED_BY_GRAPH = YES # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2241,7 +2460,8 @@ CALL_GRAPH = NO # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2264,13 +2484,17 @@ GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). # Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, # png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, -# gif:cairo:gd, gif:gd, gif:gd:gd and svg. +# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2292,7 +2516,7 @@ INTERACTIVE_SVG = NO # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_PATH = +DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile @@ -2305,13 +2529,13 @@ DOTFILE_DIRS = src # contain msc files that are included in the documentation (see the \mscfile # command). -MSCFILE_DIRS = +MSCFILE_DIRS = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). -DIAFILE_DIRS = +DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the # path where java can find the plantuml.jar file. If left blank, it is assumed @@ -2321,10 +2545,15 @@ DIAFILE_DIRS = PLANTUML_JAR_PATH = $(PLANTUML_JAR_PATH) +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. -PLANTUML_INCLUDE_PATH = +PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes @@ -2379,9 +2608,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/docs/makefile b/docs/makefile index 78fa84c..57025a0 100644 --- a/docs/makefile +++ b/docs/makefile @@ -10,26 +10,25 @@ SHELL=/bin/sh .SUFFIXES: .SUFFIXES: .c .cpp .cxx .exe .f .h .i .o .py .pyf .so .html -.PHONY: all docs clean +.PHONY: all docs html clean DOX=doxygen DOXOPTS= -LATEX_DIR=latex REVISION=$(shell git describe --always --tags --dirty --long || echo "unknown, "`date +"%F %T %z"`) export REVISION +OUTDIR= +export OUTDIR -all: docs +all: html -docs: doxygen pdf +docs: html doxygen: $(DOX) $(DOXOPTS) config.dox -pdf: doxygen - -$(MAKE) -C $(LATEX_DIR) +html: doxygen clean: - -rm -r latex/* -rm -r html/* diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..18082b0 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,28 @@ +To compile the source code documentation in HTML format on Ubuntu, follow the instructions below. + +~~~~~~{.sh} +apt-get update +apt-get install -y --no-install-recommends \ + default-jre \ + doxygen \ + gawk \ + git \ + graphviz \ + pandoc \ + wget + +pip install --no-cache-dir \ + doxypypy \ + meson \ + meson-python \ + ninja \ + pynose + +wget -O plantuml.jar https://sourceforge.net/projects/plantuml/files/plantuml.jar/download +export PLANTUML_JAR_PATH=/app/plantuml.jar + +cd pmsco/docs +doxygen config.dox +~~~~~~ + +Open `pmsco/docs/html/index.html` in your browser. diff --git a/docs/readme.txt b/docs/readme.txt deleted file mode 100644 index 335269d..0000000 --- a/docs/readme.txt +++ /dev/null @@ -1,17 +0,0 @@ -To compile the source code documentation in HTML format, -you need the following packages. -They are available from Linux distributions unless noted otherwise. - -GNU make -doxygen -python -doxypypy (pip) -graphviz -java JRE -plantuml (download from plantuml.com) - -export the location of plantuml.jar in the PLANTUML_JAR_PATH environment variable. - -go to the `docs` directory and execute `make html`. - -open `docs/html/index.html` in your browser. diff --git a/docs/src/commandline.dox b/docs/src/commandline.dox index dcf9e0a..634b6ee 100644 --- a/docs/src/commandline.dox +++ b/docs/src/commandline.dox @@ -1,135 +1,48 @@ /*! @page pag_command Command Line \section sec_command Command Line -This section describes the command line arguments for a direct call of PMSCO from the shell. -For batch job submission to Slurm see @ref sec_slurm. +Assuming that PMSCO has been installed in the active Python environment (@ref pag_install), +the basic command line of PMSCO is as follows: -Since PMSCO is started indirectly by a call of the specific project module, -the syntax of the command line arguments is defined by the project module. -However, to reduce the amount of custom code and documentation and to avoid confusion -it is recommended to adhere to the standard syntax described below. +~~~~~~{.sh} +[mpiexec -np NPROCESSES] python -m pmsco [options] +~~~~~~ -The basic command line is as follows: -@code{.sh} -[mpiexec -np NPROCESSES] python path/to/pmsco path/to/project.py [common args] [project args] -@endcode +The first portion between square brackets is necessary for parallel execution using MPI. +Replace `NPROCESSES` by the number of processes. -Include the first portion between square brackets if you want to run parallel processes. -Specify the number of processes as the @c -np option. -@c path/to/pmsco is the directory where __main.py__ is located. -Do not include the extension .py or a trailing slash. -@c path/to/project.py should be the path and name to your project module. -Common args and project args are described below. +The PMSCO main program has a limited number of `common arguments` that are described below. +Usually, all parameters should be declared in a @ref pag_runfile so that they can be archived with the results. +However, in some cases it may be necessary to override some common parameters, e.g. the job name, on the command line. \subsection sec_command_common Common Arguments -All common arguments are optional and default to more or less reasonable values if omitted. -They can be added to the command line in arbitrary order. +All common arguments can also be set in the project code or the run-file (recommended). +In that case, only the run-file is specified on the command line. +However, there are a number of options that override settings from the run-file. + +The arguments can appear in arbitrary order. The following table is ordered by importance. -| Option | Values | Description | -| --- | --- | --- | -| -h , --help | | Display a command line summary and exit. | -| -m , --mode | single (default), grid, swarm, genetic | Operation mode. | -| -d, --data-dir | file system path | Directory path for experimental data files (if required by project). Default: current working directory. | -| -o, --output-file | file system path | Base path and/or name for intermediate and output files. Default: pmsco0 | -| -t, --time-limit | decimal number | Wall time limit in hours. The optimizers try to finish before the limit. Default: 24.0. | -| -k, --keep-files | list of file categories | Output file categories to keep after the calculation. Multiple values can be specified and must be separated by spaces. By default, cluster and model (simulated data) of a limited number of best models are kept. See @ref sec_file_categories below. | -| --log-level | DEBUG, INFO, WARNING (default), ERROR, CRITICAL | Minimum level of messages that should be added to the log. | -| --log-file | file system path | Name of the main log file. Under MPI, the rank of the process is inserted before the extension. Default: output-file + log, or pmsco.log. | -| --log-disable | | Disable logging. By default, logging is on. | -| --pop-size | integer | Population size (number of particles) in swarm and genetic optimization mode. The default value is the greater of 4 or the number of parallel calculation processes. | -| --seed-file | file system path | Name of the population seed file. Population data of previous optimizations can be used to seed a new optimization. The file must have the same structure as the .pop or .dat files. See @ref pmsco.project.Project.seed_file. | -| --table-file | file system path | Name of the model table file in table scan mode. | +| Option | Values | Description | Run File | +| --- | --- | --- | --- | +| -r, --run-file | file path | JSON-formatted configuration file that defines run-time parameters. The format and content of a run file is described in a section @ref pag_runfile. | no | +| -o, --output-dir | file path | Base path and/or name for intermediate and output files. | see note below | +| -j , --job-name | string | Job name | job-name | +| -m, --module | file path | Project module | __module__ | +| -c, --project-class | string | Project class | __class__ | +| -h, --help | | Display a command line summary and exit. | no | +The job name is used as a prefix of output file names. +It is also registered in the `jobs` table of the results database (if used), +and it is used to identify the job with a job scheduling system. -\subsubsection sec_command_files File Categories - -The following category names can be used with the `--keep-files` option. -Multiple names can be specified and must be separated by spaces. - -| Category | Description | Default Action | -| --- | --- | --- | -| all | shortcut to include all categories | | -| input | raw input files for calculator, including cluster and phase files in custom format | delete | -| output | raw output files from calculator | delete | -| atomic | atomic scattering and emission files in portable format | delete | -| cluster | cluster files in portable XYZ format for report | keep | -| debug | debug files | delete | -| model | output files in ETPAI format: complete simulation (a_-1_-1_-1_-1) | keep | -| scan | output files in ETPAI format: scan (a_b_-1_-1_-1) | keep | -| domain | output files in ETPAI format: domain (a_b_c_-1_-1) | delete | -| emitter | output files in ETPAI format: emitter (a_b_c_d_-1) | delete | -| region | output files in ETPAI format: region (a_b_c_d_e) | delete | -| report| final report of results | keep always | -| population | final state of particle population | keep | -| rfac | files related to models which give bad r-factors, see warning below | delete | - -\note -The `report` category is always kept and cannot be turned off. -The `model` category is always kept in single calculation mode. - -\warning -If you want to specify `rfac` with the `--keep-files` option, -you have to add the file categories that you want to keep, e.g., -`--keep-files rfac cluster model scan population` -(to return the default categories for all calculated models). -Do not specify `rfac` alone as this will effectively not return any file. - - -\subsection sec_command_project_args Project Arguments - -The following table lists a few recommended options that are handled by the project code. -Project options that are not listed here should use the long form to avoid conflicts in future versions. - - -| Option | Values | Description | -| --- | --- | --- | -| -s, --scans | project-dependent | Nick names of scans to use in calculation. The nick name selects the experimental data file and the initial state of the photoelectron. Multiple values can be specified and must be separated by spaces. | - - -\subsection sec_command_scanfile Experimental Scan Files - -The recommended way of specifying experimental scan files is using nick names (dictionary keys) and the @c --scans option. -A dictionary in the module code defines the corresponding file name, chemical species of the emitter and initial state of the photoelectron. -The location of the files is selected using the common @c --data-dir option. -This way, the file names and photoelectron parameters are versioned with the code, -whereas command line arguments may easily get forgotten in the records. - - -\subsection sec_command_example Argument Handling - -To handle command line arguments in a project module, -the module must define a parse_project_args and a set_project_args function. -An example can be found in the twoatom.py demo project. - - -\section sec_slurm Slurm Job Submission - -The command line of the Slurm job submission script for the Ra cluster at PSI is as follows. -This script is specific to the configuration of the Ra cluster but may be adapted to other Slurm-based queues. - -@code{.sh} -qpmsco.sh [NOSUB] DESTDIR JOBNAME NODES TASKS_PER_NODE WALLTIME:HOURS PROJECT MODE [ARGS [ARGS [...]]] -@endcode - -Here, the first few arguments are positional and their order must be strictly adhered to. -After the positional arguments, optional arguments of the PMSCO project command line can be added in arbitrary order. -If you execute the script without arguments, it displays a short summary. -The job script is written to @c $DESTDIR/$JOBNAME which is also the destination of calculation output. - -| Argument | Values | Description | -| --- | --- | --- | -| NOSUB (optional) | NOSUB or omitted | If NOSUB is present as the first argument, create the job script but do not submit it to the queue. Otherwise, submit the job script. | -| DESTDIR | file system path | destination directory. must exist. a sub-dir $JOBNAME is created. | -| JOBNAME | text | Name of job. Use only alphanumeric characters, no spaces. | -| NODES | integer | Number of computing nodes. (1 node = 24 or 32 processors). Do not specify more than 2. | -| TASKS_PER_NODE | 1...24, or 32 | Number of processes per node. 24 or 32 for full-node allocation. 1...23 for shared node allocation. | -| WALLTIME:HOURS | integer | Requested wall time. 1...24 for day partition, 24...192 for week partition, 1...192 for shared partition. This value is also passed on to PMSCO as the @c --time-limit argument. | -| PROJECT | file system path | Python module (file path) that declares the project and starts the calculation. | -| MODE | single, swarm, grid, genetic | PMSCO operation mode. This value is passed on to PMSCO as the @c --mode argument. | -| ARGS (optional) | | Any further arguments are passed on verbatim to PMSCO. You don't need to specify the mode and time limit here. | +\note It is important that the job name be unique within a project. +Specifically, you need to *provide a new job name each time you start pmsco*, otherwise the job may fail. +It may be more natural to specify the job name on the command line using the `-j` argument +than to change the run file every time. +Unfortunately, PMSCO cannot auto-generate, auto-increment or verify the job name. */ diff --git a/docs/src/execution.dox b/docs/src/execution.dox index 1a81eb7..5464152 100644 --- a/docs/src/execution.dox +++ b/docs/src/execution.dox @@ -3,36 +3,35 @@ To run PMSCO you need the PMSCO code and its dependencies (cf. @ref pag_install), a customized code module that contains the project-specific code, -and one or several files containing the scan parameters and experimental data. -Please check the projects folder for examples of project modules. +one or several files containing the scan parameters and experimental data, +and a run-file specifying the calculation parameters. -The run-time arguments can either be passed on the command line -(@ref pag_command - the older and less flexible way) -or in a JSON-formatted run-file -(@ref pag_runfile - the recommended new and flexible way). -For beginners, it's also possible to hard-code all project parameters in the custom project module. +Please check the projects folder for examples of project modules. \subsection sec_run_single Single Process -Run PMSCO from the command prompt: +The following instructions assume that PMSCO was installed as a Python site-package according to @ref pag_install. + +To run PMSCO from the command prompt: @code{.sh} cd work-dir -python pmsco-dir -r run-file +python -m pmsco -j job-name -r run-file @endcode -where work-dir is the destination directory for output files, -pmsco-dir is the directory containing the __main__.py file, -run-file is a json-formatted configuration file that defines run-time parameters. -The format and content of the run-file is described in a separate section. +where: + +| `work-dir` | Destination directory for output files | +| `run-file` | JSON-formatted configuration file that defines run-time parameters. The format and content of a run file is described in a section @ref pag_runfile. | +| `job-name` | (optional) The job name appears mainly as the prefix of all output files but is also used in the database and other places. The job name can also be declared in the run file. | In this form, PMSCO is run in one process which handles all calculations sequentially. Example command line for a single EDAC calculation of the two-atom project: @code{.sh} cd work/twoatom -python ../../pmsco -r twoatom-hemi.json +python -m pmsco -j job0001 -r twoatom-hemi.json @endcode This command line executes the main pmsco module pmsco.py. @@ -54,46 +53,104 @@ The slave processes will run the scattering calculations, while the master coord and optimizes the model parameters (depending on the operation mode). For optimum performance, the number of processes should not exceed the number of available processors. -To start an optimization job with multiple processes on an quad-core workstation with hyperthreading: +To start an optimization job with multiple processes on a quad-core workstation with hyperthreading: @code{.sh} cd work/my_project -mpiexec -np 8 --use-hwthread-cpus python pmsco-dir -r run-file +mpiexec -np 8 --use-hwthread-cpus python -m pmsco -j my_job002 -r my_project.json @endcode -The `--use-hwthread` option may be necessary on certain hyperthreading architectures. +The `--use-hwthread` option is necessary on certain hyperthreading architectures. \subsection sec_run_hpc High-Performance Cluster PMSCO is ready to run with resource managers on cluster machines. -Code for submitting jobs to the slurm queue of the Ra cluster at PSI is included in the pmsco.schedule module -(see also the PEARL wiki pages in the PSI intranet). -The job parameters are entered in a separate section of the run file, cf. @pag_runfile for details. +Code for submitting jobs to Slurm queues is included and can be customized for many machines. +For example, code for the slurm queue of the Ra cluster at PSI is included in the pmsco.schedule module. Other machines can be supported by sub-classing pmsco.schedule.JobSchedule or pmsco.schedule.SlurmSchedule. -If a schedule section is present and enabled in the run file, -the following command will submit a job to the cluster machine -rather than starting a calculation directly: +To have PMSCO submit a job, the arguments for the queue are entered in the schedule section of the run file, +cf. @ref pag_runfile. +Then, the same command as for starting a calculation directly will instead submit a job to the queue: @code{.sh} -cd ~/pmsco -python pmsco -r run-file.json +python -m pmsco -j job-name -r run-file.json @endcode -The command will copy the pmsco and project source trees as well as the run file and job script to a job directory -under the output directory specified in the project section of the run file. -The full path of the job directory is _output-dir/job-name. -The directory must be empty or not existing when you run the above command. +The command creates a separate work directory with copies of the project source, the run-file and the job script. +This job directory will also receive the calculation results. +The full path of the job directory is _output-dir/job-name_. +The directory must not exist when you run the above command to prevent overwriting of previous data. +The job name can be declared in the run file or on the command line. -Be careful to specify correct project file paths. -The output and data directories should be specified as absolute paths. +The command above also loads the project module and scan files. +Many parameter errors are caught this way and can be fixed before the job is submitted to the queue. -The scheduling command will also load the project and scan files. -Many parameter errors can, thus, be caught and fixed before the job is submitted to the queue. -The run file also offers an option to stop just before submitting the job +The run file offers an option to prepare a script file and not to submit the job immediately so that you can inspect the job files and submit the job manually. Be sure to consider the resource allocation policy of the cluster before you decide on the number of processes. Requesting less resources will prolong the run time but might increase the scheduling priority. + +\subsection sec_run_dirs Directories + +Code and data files are typically located in different, possibly machine-specific locations. +This can make it difficult to port a project to another machine and to repeat calculations. +Ideally, a calculation job should be repeatable on different machines +with a minimum of changes to code, input data and parameter files. +Project code (which is under version control) +should never need modifications for porting to another machine. +Run-files (which are considered part of the data) can follow a project-specific or machine-specific directory structure. + +PMSCO provides directory resolution at run-time to facilitate writing of portable code. +This is done by a number of directory aliases that can be included as shell-like placeholders, e.g. `${project}`, in file paths. +Some aliases are preset to system-based defaults, +further aliases can be added by the project code or declared in the run file. +Directory aliases can be used in Project.directories +as well as in other Project attributes that hold a file name. + +The table below shows the aliases defined and/or required by PMSCO. +The paths are stored in Project.directories. +The aliases are resolved before the actual calculations start (in the Project.validate() method). +The resolved paths are printed to the log at warning level. + +| Key | Description | Source | Use | +| --- | --- | --- | --- | +| work | Working directory at program start | PMSCO | | +| home | User's home directory | PMSCO | | +| project | Location of the project module. | PMSCO | Can be used to find auxiliary files that are part of the repository. | +| output | Intermediate and output files. | Must be set by the project or run file | The `output_file` property which serves as the basis of all output files is a concatenation of the `output` directory and `job_name`. | +| report | Directory for graphical output (reports) | Default: `${output}/report` | | +| data (optional) | Location of data (scan) files. | Project or run file | Usage is up to the project. | +| temp | Temporary files | | Reserved. Currently not supported | +| (job tag) | Any job_tags key that maps to a legal directory name can be included in a path | run file | project or run file | +| mode, job_name, project_name | These project attributes can be included in a path if they contain a valid directory name | | | + +\subsection sec_run_stop Stopping a PMSCO job + +A PMSCO optimization job stops on any one of the following events. + +- The model handler is done. + Depending on the run mode, this happens when the optimization has converged or + the planned number of iterations or calculations has been reached. +- The number of calculation tasks exceeds the limit configured in `dispatch.MscoMaster.max_calculations`. + This is meant to prevent excessive and runaway jobs. + The default value is 1000000. It can be adjusted by the project code if necessary. +- The master process receives a SIGTERM, SIGUSR1 or SIGUSR2 from the operating system. + The signal can be sent, e.g., by the `kill` command on Linux. + This doesn´t work on all platforms. +- The time limit configured in `Project.timedelta_limit` is reached. + This is a soft limit and should be set shorter than the job reservation with the resource manager. +- A file named `finish_pmsco` is present in the output directory. + This is an easy way for a user to stop a running optimization. + The file doesn´t need any content. + It can be created by the `touch` command. + +All these stop conditions cause graceful stops. +Running calculation tasks are waited for, but some results on the model level may not be complete. +Final reports of complete models are produced and the output folder is cleaned up. + +Stops caused by resource managers such as Slurm are typically not graceful. +The results are in an undefined state, reports are not generated, and temporary files may be left over. */ diff --git a/docs/src/installation.dox b/docs/src/installation.dox index e6a6ba5..926bff1 100644 --- a/docs/src/installation.dox +++ b/docs/src/installation.dox @@ -3,69 +3,66 @@ \subsection sec_general General Remarks -The PMSCO code is maintained under [Git](https://git-scm.com/). -The central repository for PSI-internal projects is at https://git.psi.ch/pearl/pmsco, -the public repository at https://gitlab.psi.ch/pearl/pmsco. +The central repository for development and PSI-internal projects is at https://gitea.psi.ch/pearl/pmsco, +the public repository at https://gitea.psi.ch/pearl-public/pmsco. For their own developments, users should clone the repository. Changes to common code should be submitted via pull requests. +Scientific projects should be maintained in a separate directory tree, cf. @ref sec_project. -The program code of PMSCO and its external programs is written in Python 3.6, C++ and Fortran. +The program code of PMSCO and its external programs is written in Python, C++ and Fortran. The code will run in any recent Linux environment on a workstation or in a virtual machine. -Scientific Linux, CentOS7, [Ubuntu](https://www.ubuntu.com/) -and [Lubuntu](http://lubuntu.net/) (recommended for virtual machine) have been tested. -For optimization jobs, a workstation with at least 4 processor cores +For optimization jobs with parallel execution, a workstation with at least 4 processor cores or cluster with 20-50 available processor cores is recommended. The program requires about 2 GB of RAM per process. The recommended IDE is [PyCharm (community edition)](https://www.jetbrains.com/pycharm). -[Spyder](https://docs.spyder-ide.org/index.html) is a good alternative with a better focus on scientific data. -The documentation in [Doxygen](http://www.stack.nl/~dimitri/doxygen/index.html) format is part of the source code. -The Doxygen compiler can generate separate documentation in HTML or LaTeX. +[Spyder](https://docs.spyder-ide.org/index.html) is a good alternative with a focus on scientific data. +The documentation in [Doxygen](https://www.doxygen.nl/index.html) format is part of the source code. +The Doxygen compiler can generate documentation in HTML. + +@attention Due to rapidly evolving computing environments +some of the installation instructions on this page may be outdated or incompatible with certain environments. \subsection sec_requirements Requirements Please note that in some environments (particularly shared high-performance machines) -it may be important to choose specific compiler and library versions. -In order to maintain backward compatibility with some of these older machines, +it may be important to choose specific compiler and library versions that are tailored to the hardware platform. +In order to maintain backward compatibility with older installations, code that requires new versions of compilers and libraries should be introduced carefully. -The code depends on the following libraries: +The following basic tools and libraries are required: -- GCC >= 4.8 -- OpenMPI >= 1.10 -- F2PY -- F2C -- SWIG +- GCC (C, C++, Fortran) >= 4.8 - BLAS - LAPACK -- Python 3.6 -- Numpy >= 1.13 -- Python packages listed in the requirements.txt file +- OpenMPI >= 1.10 +- Git Most of these requirements are available from the Linux distribution. -For an easily maintainable Python environment, [Miniconda](https://conda.io/miniconda.html) is recommended. -The Python environment distributed with the OS often contains outdated packages, -and it's difficult to switch between different Python versions. +For the Python environment, +the [uv](https://docs.astral.sh/uv/) package and environment manager is recommended. +It can be installed by non-privileged users. +Other package managers like pip and conda may work as well but are not described here. -On the PSI cluster machines, the environment must be set using the module system and conda (on Ra). -Details are explained in the PEARL Wiki. - -The following tools are required to compile the documentation: +The following tools are required to compile the documentation. +They are not needed in calculations. - doxygen - doxypypy - graphviz -- Java +- Java runtime environment (JRE) - [plantUML](https://plantuml.com) -- LaTeX (optional, generally not recommended) \subsection sec_install_instructions Instructions +Installation instructions are given for Ubuntu 24.04. +On managed HPC clusters use the compilers and libraries recommended by the administrator +(often provided by a module system). + \subsubsection sec_install_ubuntu Installation on Ubuntu The following instructions install the necessary dependencies on Ubuntu, Debian or related distributions. -The Python environment is provided by [Miniconda](https://conda.io/miniconda.html). @code{.sh} sudo apt update @@ -74,7 +71,6 @@ sudo apt install \ binutils \ build-essential \ doxygen \ -f2c \ g++ \ gcc \ gfortran \ @@ -83,57 +79,184 @@ graphviz \ libblas-dev \ liblapack-dev \ libopenmpi-dev \ -make \ nano \ openmpi-bin \ openmpi-common \ +python3 \ +python3-venv \ sqlite3 \ wget @endcode -On systems where the link to libblas is missing (see @ref sec_compile below), -the following lines are necessary. +In addition, download and install [uv](https://docs.astral.sh/uv/). +PSI users should configure uv to use PSI's PyPI package cache (cf. documentation on the intranet). + + +\subsubsection sec_install_extra Additional Applications + +For working with the code and data, some other applications are recommended. +The PyCharm IDE can be installed from the Ubuntu software center. +The following commands install other useful helper applications: @code{.sh} -cd /usr/lib -sudo ln -s /usr/lib/libblas/libblas.so.3 libblas.so +sudo apt install \ +avogadro \ +gitg \ +meld @endcode -Download and install [Miniconda](https://conda.io/), -then configure the Python environment: +To compile the documentation install the following tools. +The basic documentation is in HTML format and can be opened in any internet browser. @code{.sh} -wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh -bash ~/miniconda.sh +sudo apt install \ +doxygen \ +graphviz \ +default-jre -conda create -q --yes -n pmsco python=3.6 -conda activate pmsco -conda install -q --yes -n pmsco \ - pip \ - "numpy>=1.13" \ - scipy \ - ipython \ - matplotlib \ - nose \ - mock \ - future \ - statsmodels \ - swig \ - gitpython -pip install periodictable attrdict commentjson fasteners mpi4py doxypypy +wget -O plantuml.jar https://sourceforge.net/projects/plantuml/files/plantuml.jar/download +sudo mkdir /opt/plantuml/ +sudo mv plantuml.jar /opt/plantuml/ +echo "export PLANTUML_JAR_PATH=/opt/plantuml/plantuml.jar" | sudo tee /etc/profile.d/pmsco-env.sh @endcode -@note `mpi4pi` should be installed via pip, _not_ conda. - conda might install its own MPI libraries, which can cause a conflict with system libraries. - (cf. [mpi4py forum](https://groups.google.com/forum/#!topic/mpi4py/xpPKcOO-H4k)) -\subsubsection sec_install_singularity Installation in Singularity container +\subsection sec_distro Download PMSCO Source Code -A [Singularity](https://sylabs.io/singularity/) container -contains all OS and Python dependencies for running PMSCO. -Besides the Singularity executable, nothing else needs to be installed in the host system. -This may be the fastest way to get PMSCO running. +Clone or download the code from one of these repository addresses: +| Repository | Access | +| --- | --- | +| https://gitea.psi.ch/pearl/pmsco | PSI internal | +| https://gitea.psi.ch/pearl-public/pmsco-public | Public | + +@code{.sh} +cd ~ +git clone {repo-address see above} pmsco +cd pmsco +git checkout master +@endcode + +These instructions download the base package of PMSCO. +The public repository does not contain external programs (EDAC, PHAGEN, LOESS). +You need to obtain the source code for these programs from their respective owners, +copy them to the respective subprojects directories and +apply the patches included in the PMSCO distribution. +Please respect the respective license terms and acknowledge the use of the codes. + + +\subsection sec_install_environment Set up the Python Environment + +The following instructions are for the [uv](https://docs.astral.sh/uv/) package manager. +For other package managers, the pyproject.toml and requirements.txt files list the necessary dependencies. + +\subsubsection sec_install_uv Virtual Environment with uv + +By default, uv creates the virtual environment automatically in a `.venv` folder inside the source directory tree. +In this case, no explicit setup is necessary, and pmsco can be called by: + +~~~~~~{.sh} +uv run pmsco -h +~~~~~~ + +On some platforms, however, it may be necessary to separate the environment from the code, +e.g. because of limited storage space or quota in the home directory. +In this case, create the environment as follows: + +~~~~~~{.sh} +cd ~ +mkdir envs +cd envs +uv venv --clear my_pmsco_env +~~~~~~ + +The `--clear` option resets an existing environment to empty. +To activate this environment, call this command once in every terminal: + +~~~~~~{.sh} +source ~/envs/my_pmsco_env/bin/activate +~~~~~~ + + +\subsubsection sec_normal_install Installing PMSCO + +to install PMSCO and all dependencies into the active environment, +run the following commands in the top-level PMSCO directory (where `pyproject.toml` is located). +The commands compile the Fortran and C++ code of the calculation programs using the +[Meson build system](https://mesonbuild.com/meson-python/index.html) +and install the binaries and Python code in the site-packages folder of the active Python environment. + +~~~~~~{.sh} +uv sync --active +~~~~~~ + +To use the default `.venv` environment, omit the `--active` option (also in the uv commands shown further below). +Now, run the unit tests to check the installation: + +~~~~~~{.sh} +uv run --active nosetests +~~~~~~ + +And check the help page: + +~~~~~~{.sh} +uv run --active pmsco -h +~~~~~~ + +In the explicit environment, these commands can alternatively be called directly: + +~~~~~~{.sh} +nosetests +pmsco -h +~~~~~~ + +The PMSCO packages are now accessible in Python import statements. +Verify it by opening a Python shell and entering: + +~~~~~~{.py} +import pmsco.project +dir(pmsco.project) +~~~~~~ + +Note: By default, uv installs the Python code in editable mode. +Changes in the PMSCO source tree are visible as soon as you start a new Python interpreter. +This does not apply to the subpackages, however. +After modifying the subpackages, you need to clear and re-sync the environment. + + +\subsection sec_test Test project + +Run the twoatom project to check that everything is installed correctly: + +~~~~~~{.py} +cd ~ +mkdir -p work/twoatom +cd work/twoatom +nice python -m pmsco -r {path-to-pmsco}/projects/twoatom/twoatom-hemi.json +~~~~~~ + +You should get a number of result files whose names start with `twoatom0001` in `~/work/twoatom/`, +including a hologram plot of the modulation function. + +To learn more about running PMSCO, see @ref pag_run. + +\subsection sec_install_projects Installing Namespace Packages + +Instructions on how to set up your own projects as namespace packages are given in section \ref sec_project. +To install them into the pmsco namespace, call uv with the `--inexact` option. +Without `--inexact`, uv would remove the previously installed packages (including PMSCO). + +~~~~~~{.sh} +uv sync --active --inexact +~~~~~~ + + +\subsection sec_install_singularity Installation in a Singularity container + +Singularity containers are currently unmaintained. + +The PMSCO source includes an install script for the [Singularity](https://sylabs.io/singularity/) container system +under `extras/singularity`. To get started with Singularity, download it from [sylabs.io](https://www.sylabs.io/singularity/) and install it according to their instructions. On Windows, Singularity can be installed in a virtual machine using the [Vagrant](https://www.vagrantup.com/) @@ -146,7 +269,7 @@ check out PMSCO as explained in the @ref sec_compile section: cd ~ mkdir containers cd containers -git clone git@git.psi.ch:pearl/pmsco.git pmsco +git clone git@gitea.psi.ch:pearl-public/pmsco-public.git pmsco cd pmsco git checkout master git checkout -b my_branch @@ -156,7 +279,7 @@ Then, either copy a pre-built container into `~/containers`, or build one from the definition file included under extras/singularity. You may need to customize the definition file to match the host OS or to install compatible OpenMPI libraries, -cf. cf. [Singularity user guide](https://sylabs.io/guides/3.7/user-guide/mpi.html). +cf. [Singularity user guide](https://sylabs.io/guides/3.7/user-guide/mpi.html). @code{.sh} cd ~/containers @@ -172,8 +295,10 @@ singularity shell pmsco.sif . /opt/miniconda/etc/profile.d/conda.sh conda activate pmsco cd ~/containers/pmsco -make all -nosetests -w tests/ +meson setup build +meson compile -C build +meson install -C build +meson test -C build @endcode Or call PMSCO from outside: @@ -182,107 +307,10 @@ Or call PMSCO from outside: cd ~/containers mkdir output cd output -singularity run -e ../pmsco.sif ~/containers/pmsco/pmsco -r path/to/your-runfile +singularity run -e ../pmsco.sif python -m pmsco -r path/to/your-runfile @endcode For parallel processing, prepend `mpirun -np X` to the singularity command as needed. -Note that this requires "compatible" OpenMPI versions on the host and container to avoid runtime errors. +Note that this requires compatible OpenMPI versions on the host and container to avoid runtime errors. - -\subsubsection sec_install_extra Additional Applications - -For working with the code and data, some other applications are recommended. -The PyCharm IDE (community edition) can be installed from the Ubuntu software center. -The following commands install other useful helper applications: - -@code{.sh} -sudo apt install \ -avogadro \ -gitg \ -meld -@endcode - -To compile the documentation install the following tools. -The basic documentation is in HTML format and can be opened in any internet browser. -If you have a working LaTeX installation, a PDF document can be produced as well. -It is not recommended to install LaTeX just for this documentation, however. - -@code{.sh} -sudo apt install \ -doxygen \ -graphviz \ -default-jre - -conda activate pmsco -conda install -q --yes -n pmsco doxypypy - -wget -O plantuml.jar https://sourceforge.net/projects/plantuml/files/plantuml.jar/download -sudo mkdir /opt/plantuml/ -sudo mv plantuml.jar /opt/plantuml/ -echo "export PLANTUML_JAR_PATH=/opt/plantuml/plantuml.jar" | sudo tee /etc/profile.d/pmsco-env.sh -@endcode - - -\subsection sec_compile Compilation - -Make sure you have access to the PMSCO Git repository and set up your Git environment. -Depending on your setup, location and permissions, one of the following addresses may work. -Private key authentication is usually recommended except on shared computers. - -| Repository | Access | -| --- | --- | -| `git@git.psi.ch:pearl/pmsco.git` | PSI intranet, SSH private key authentication | -| `https://git.psi.ch/pearl/pmsco.git` | PSI intranet, password prompt | -| `git@gitlab.psi.ch:pearl/pmsco.git` | Public repository, SSH private key authentication | -| `https://gitlab.psi.ch/pearl/pmsco.git` | Public repository, password prompt | - -Clone the code repository using one of these repositiory addresses and switch to the desired branch: - -@code{.sh} -git clone git@git.psi.ch:pearl/pmsco.git pmsco -cd pmsco -git checkout master -git checkout -b my_branch -@endcode - -Compile the code and run the unit tests to check that it worked. - -@code{.sh} -make all -nosetests -w tests/ -@endcode - -If the compilation of _loess.so failes due to a missing BLAS library, -try to set a link to the BLAS library as follows (the actual file names may vary due to the actual distribution or version): -@code{.sh} -cd /usr/lib -sudo ln -s /usr/lib/libblas/libblas.so.3 libblas.so -@endcode - - -\subsection sec_test Tests - -Run the unit tests. -They should pass successfully. -Re-check from time to time. - -@code{.sh} -cd ~/pmsco -nosetests -w tests/ -@endcode - -Run the twoatom project to check the compilation of the calculation programs. - -@code{.sh} -cd ~/pmsco -mkdir work -cd work -mkdir twoatom -cd twoatom/ -nice python ~/pmsco/pmsco -r ~/pmsco/projects/twoatom/twoatom-energy.json -@endcode - -Runtime warnings may appear because the twoatom project does not contain experimental data. - -To learn more about running PMSCO, see @ref pag_run. */ diff --git a/docs/src/introduction.dox b/docs/src/introduction.dox index 9037aef..f9d777d 100644 --- a/docs/src/introduction.dox +++ b/docs/src/introduction.dox @@ -1,9 +1,11 @@ /*! @mainpage Introduction \section sec_intro Introduction -PMSCO stands for PEARL multiple-scattering cluster calculations and structural optimization. -It is a collection of computer programs to calculate photoelectron diffraction patterns, -and to optimize structural models based on measured data. +PMSCO (PSI multiple-scattering cluster calculations and structural optimization) +is a Python-based workflow engine to calculate photoelectron diffraction patterns, +and to optimize structural models based on measured data using machine learning techniques. +PMSCO was developed at the [Paul Scherrer Institut (PSI)](https://www.psi.ch/) +by the team of the [PEARL beamline](https://www.psi.ch/en/sls/pearl). The actual scattering calculation is done by code developed by other parties. While the scattering program typically calculates a diffraction pattern based on a set of static parameters and a specific coordinate file in a single process, @@ -12,7 +14,7 @@ PMSCO wraps around that program to facilitate parameter handling, cluster buildi In the current version, PMSCO can make use of the following programs. Other programs may be integrated as well. -- [EDAC](http://garciadeabajos-group.icfo.es/widgets/edac/) +- [EDAC](https://garciadeabajos-group.icfo.es/widgets/edac/) by F. J. García de Abajo, M. A. Van Hove, and C. S. Fadley, [Phys. Rev. B 63 (2001) 075404](http://dx.doi.org/10.1103/PhysRevB.63.075404) - PHAGEN from the [MsSpec package](https://ipr.univ-rennes1.fr/msspec) @@ -29,7 +31,8 @@ Other programs may be integrated as well. - structural optimization algorithms: genetic, particle swarm, grid search. - calculation of the modulation function. - calculation of the weighted R-factor. -- automatic parallel processing using OpenMPI. +- integrated and extensible reporting, database storage of results. +- automatic parallel processing using OpenMPI and job submission to scheduling systems. \section sec_intro_project Optimization Projects @@ -38,11 +41,11 @@ To set up a new optimization project, you need to: - create a new directory under projects. - create a new Python module in this directory, e.g., my_project.py. -- implement a sub-class of project.Project in my_project.py. +- implement a sub-class of pmsco.project.Project in my_project.py. - override the create_cluster, create_params, and create_model_space methods. - optionally, override the combine_domains and combine_scans methods. -- add a global function create_project to my_project.py. -- provide experimental data files (intensity or modulation function). +- add a global function create_project to my_project.py or create a @ref pag_runfile. +- prepare experimental data files (intensity or modulation function). For details, see @ref pag_project, the documentation of the pmsco.project.Project class and the example projects. @@ -59,17 +62,18 @@ For details, see @ref pag_project, the documentation of the pmsco.project.Projec \section sec_license License Information -An open distribution of PMSCO is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) at . +The source code of PMSCO is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). +This _does not include_ the calculation packages contained in the subprojects folder which are licensed separately. - Please read and respect the respective license agreements. - Please acknowledge the use of the code. -- Please share your development of the code with the original author. +- Please consider sharing your developments with the original author. Due to different copyright terms, the third-party calculation programs are not contained in the public software repository. These programs may not be used without an explicit agreement by the respective original authors. \author Matthias Muntwiler, \version This documentation is compiled from version $(REVISION). -\copyright 2015-2021 by [Paul Scherrer Institut](http://www.psi.ch) +\copyright 2015-2025 by [Paul Scherrer Institut](http://www.psi.ch) \copyright Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) */ diff --git a/docs/src/project.dox b/docs/src/project.dox index 8658cc3..dd77dba 100644 --- a/docs/src/project.dox +++ b/docs/src/project.dox @@ -1,5 +1,5 @@ /*! @page pag_project Setting up a new project -\section sec_project Setting Up a New Project +\section sec_project Setting up a new project This topic guides you through the setup of a new project. Be sure to check out the examples in the projects folder @@ -7,13 +7,82 @@ and the code documentation as well. The basic steps are: -1. Create a new folder under `projects`. -2. In the new folder, create a Python module for the project (subsequently called _the project module_). -3. In the project module, define a cluster generator class which derives from pmsco.cluster.ClusterGenerator. -4. In the project module, define a project class which derives from pmsco.project.Project. -5. In the same folder as the project module, create a JSON run-file. +1. Create a new package folder under `pmsco/projects`. + To keep your code and PMSCO separate, you are suggested to start your own pmsco/projects tree + in a convenient location separate from the PMSCO source code. +2. Add the parent directory of your pmsco/projects tree to the Python path. +3. In the new folder, create a Python module for the project (subsequently called _the project module_). +4. In the project module, define a cluster generator class which inherits from @ref pmsco.cluster.ClusterGenerator. +5. In the project module, define a project class which inherits from @ref pmsco.project.Project. +6. Create one or more run files. -\subsection sec_project_module Project Module +The basic steps listed above are recommended and explained in the following. +In previous versions, other mechanisms of project invocation were available. +They are now obsolete. + + +\subsection sec_packages Namespace packages + +[Python namespace packages](https://realpython.com/python-namespace-package/) provide an easy way +to inject project modules into the PMSCO namespace +while their source files are kept separate from the core PMSCO packages. +This way, PMSCO and the project modules can be under separate version control. + +Namespace packages work by extending the Python module search path. +The module loader looks for packages in every entry of the search path +and does not stop at the first match as it would do for a regular package. + +The recommended folder structure is: + +~~~~~~ +pmsco-projects/ ++-- pyproject.toml ++-- pmsco/ + +-- projects/ + +-- project1/ + +-- project1.py + +-- run1.json + +-- ... + +-- project2/ + +-- ... +~~~~~~ + +In place of `pmsco-projects`, `project1`, `project2`, `run1`, you should use distinct names. +The two levels `pmsco` and `projects` should be left as is. +If you now include `pmsco-projects` in the Pyton path, +all of your projects become available within the `pmsco` namespace, i.e., +you can `import pmsco.projects.project1.project1` in Python. +Furthermore, you can call the module in a run-file without specifying a file path. +You may install multiple project packages if needed. + +The recommended way to add `pmsco-projects` to the Python path is by an editable installation. +This will allow you to keep editing your project sources in place. + +1. Place your project files in a directory tree similar to `pmsco-projects/pmsco/projects/project1/`. + The `pmsco/projects` level is mandatory as a part of the path. + Replace `pmsco-projects` and `project1` by your own choice. +2. Be sure not to create any `__init__.py` files in this directory tree. +3. Copy the `pyproject.toml` file from the PMSCO source into your `pmsco-projects` and adjust its contents. + At least give the package a distinct name. +4. Select another build backend if necessary. + The default [uv_build](https://docs.astral.sh/uv/concepts/build-backend/) is recommended for pure Python projects. +5. 'Install' the project locally. + With uv, call `uv sync --active --inexact` + while you are in the directory that contains the `pyproject.toml` file. + In plain pip the corresponding command would be + `pip install --editable .`. +6. Check that you can `import pmsco.projects.project1.project1` (or whatever your project is called) in a Python shell. + +If you encounter problems importing the pmsco modules, check the Python path in a Python shell. +It must contain the `site-packages` directory of your Python environment. +Make sure it does not contain any pmsco or project source directory explicitly. +Also make sure that you don't have any `__init__.py` files in your project tree, +and do not use explicit paths to pmsco or your project anywhere in your source code or shell configuration files. +Be careful not to install packages multiple times in different locations. +In case of trouble, set up a fresh environment. + + +\subsection sec_project_module Project module A skeleton of the project module file (with some common imports) may look like this: @@ -67,26 +136,29 @@ For the project to be useful, some of the methods in the skeleton above need to The individual methods are discussed in the following. Further descriptions can be found in the documentation of the code. -\subsection sec_project_cluster Cluster Generator +\subsection sec_project_cluster Cluster generator The cluster generator is a project-specific Python object that produces a cluster, i.e., a list of atomic coordinates, based on a small number of model parameters whenever PMSCO requires it. -The most important member of a cluster generator is its `create_cluster` method. +The most important method of a cluster generator is `create_cluster`. At least this method must be implemented for a functional cluster generator. A generic `count_emitters` method is implemented in the base class. -It needs to be overridden if you want to use parallel calculation of multiple emitters. +It needs to be overridden if inequivalent emitters should be calculated in parallel. -\subsubsection sec_project_cluster_create Cluster Definition +\subsubsection sec_project_cluster_create Cluster definition The `create_cluster` method takes the model parameters (a dictionary) and the task index (a pmsco.dispatch.CalcID, cf. @ref pag_concepts_tasks) as arguments. -Given these arguments, it must create and fill a pmsco.cluster.Cluster object. -See pmsco.cluster.ClusterGenerator.create_cluster for details on the method contract. +Given these arguments, it creates and fills a @ref pmsco.cluster.Cluster object. +See @ref pmsco.cluster.ClusterGenerator.create_cluster for details on the method contract. -As an example, have a look at the following simplified excerpt from the twoatom demo project. +As an example, have a look at the following simplified excerpt from the `twoatom` demo project. ~~~~~~{.py} +class TwoatomCluster(ClusterGenerator): + # ... + def create_cluster(self, model, index): # access model parameters # dAB - distance between atoms in Angstroms @@ -120,7 +192,7 @@ As an example, have a look at the following simplified excerpt from the twoatom ~~~~~~ In this example, two atoms are added to the cluster. -The pmsco.cluster.Cluster class provides several methods to simplify the task, +The @ref pmsco.cluster.Cluster class provides several methods to simplify the task, such as adding layers or bulk regions, rotation, translation, trim, emitter selection, etc. Please refer to the documentation of its code for details. It may also be instructive to have a look at the demo projects. @@ -132,17 +204,17 @@ For each atom, the following properties are stored: - atom type (chemical element number) - chemical element symbol from periodic table - x coordinate of the atom position -- t coordinate of the atom position +- y coordinate of the atom position - z coordinate of the atom position - emitter flag (0 = scatterer, 1 = emitter, default 0) - charge/ionicity (units of elementary charge, default 0) - scatterer class (default 0) -All of these properties except the scatterer class can be set by the add methods of the cluster. +All of these properties except the scatterer class can be set by the `add_xxxx` methods of the cluster. The scatterer class is used internally by the atomic scattering factor calculators. Whether the charge/ionicity is used, depends on the particular calculators, EDAC does not use it, for instance. -Note: You do not need to take care how many emitters a calculator allows, +\note You do not need to take care how many emitters a calculator allows, or whether the emitter needs to be at the origin or the first place of the array. These technical aspects are handled by PMSCO code transparently. @@ -150,8 +222,8 @@ These technical aspects are handled by PMSCO code transparently. Domains refer to regions of inequivalent structure in the probing region. This may include regions of different orientation, different lattice constant, or even different structure. -The cluster methods can read the selected domain from the `index.domain` argument. -This is an index into the pmsco.project.Project.domains list where each item is a dictionary +The cluster methods read the requested domain from the `index.domain` argument. +This is an index into the @ref pmsco.project.Project.domains list where each item is a dictionary that holds additional, invariable structural parameters. A common case are rotational domains. @@ -177,19 +249,26 @@ and the `create_cluster` method would include additional code to rotate the clus return clu ~~~~~~ -Depending on the complexity of the system, it may, however, be necessary to write a specific sub-routine for each domain. +Depending on the complexity of the system, it is advisable to split the code into a separate method for each domain. -The pmsco.project.Project class includes generic code to add intensities of domains incoherently (cf. pmsco.project.Project.combine_domains). -If the model space contains parameters 'wdom0', 'wdom1', etc., -these parameters are interpreted at weights of domain 0, 1, etc. -One domain must have a fixed weight to avoid correlated parameters. +The @ref pmsco.project.Project class includes generic code to add intensities of domains incoherently +(cf. @ref pmsco.project.Project.combine_domains). +In this case, the model space should contain parameters 'wdom0', 'wdom1', etc., +that define the weights of domain 0, 1, etc. + +To avoid correlations between parameters, one domain must have a fixed weight: Typically, 'wdom0' is left undefined and defaults to 1. -\subsubsection sec_project_cluster_emitters Emitter Configurations +\subsubsection sec_project_cluster_emitters Emitter configurations -If your project has a large cluster and/or many emitters, have a look at @ref pag_concepts_emitter. -In this case, you should override the `count_emitters` method and return the number of emitter configurations. -In the simplest case, this is the number of inequivalent emitters, and the implementation would be: +If a project uses a large cluster and/or many emitters, +it may be more efficient to generate emitter-specific cluster configurations, +for instance to leverage process parallelization, +or to produce small, local clusters around the emitter site. +This concept is called _emitter configurations_ and explained in detail in @ref pag_concepts_emitter. + +To implement emitter configurations, override the `count_emitters` method to return the number of emitter configurations. +In the simplest case, this is the number of inequivalent emitters: ~~~~~~{.py} def count_emitters(self, model, index): @@ -200,8 +279,8 @@ In the simplest case, this is the number of inequivalent emitters, and the imple Next, modify the `create_cluster` method to check the emitter index (`index.emit`). If it is -1, the method must return the full cluster with all inequivalent emitters marked. -If it is positive, only the corresponding emitter must be marked. -The code could be similar to this example: +If it is positive, only the corresponding emitter configuration must be marked. +For example, if each emitting atom represents a separate emitter configuration: ~~~~~~{.py} def create_cluster(self, model, index): @@ -211,36 +290,36 @@ The code could be similar to this example: # select all possible emitters (atoms of a specific element) in a cylindrical volume # idx_emit is an array of atom numbers (0-based atom index) idx_emit = clu.find_index_cylinder(origin, r_xy, r_z, self.project.scans[index.scan].emitter) - # if a specific emitter should be marked, restrict the array index. + + # if PMSCO asks for a specific emitter, restrict the array index: if index.emit >= 0: idx_emit = idx_emit[index.emit] + # mark the selected emitters - # if index.emit was < 0, all emitters are marked clu.data['e'][idx_emit] = 1 return clu ~~~~~~ -Now, the individual emitter configurations will be calculated in separate tasks -which can be run in parallel in a multi-process environment. +Now, the individual emitter configurations are calculated in separate tasks +which can run in parallel in a multi-process environment. Note that the processing time of EDAC scales linearly with the number of emitters. -Thus, parallel execution is beneficial. -Advanced programmers may exploit more of the flexibility of emitter configurations, cf. @ref pag_concepts_emitter. -\subsection sec_project_project Project Class +\subsection sec_project_project Project class Most commonly, a project class overrides the `__init__`, `create_model_space` and `create_params` methods. Most other inherited methods can be overridden optionally, for instance `validate`, `setup`, `calc_modulation`, `rfactor`, as well as the combine methods `combine_rfactors`, `combine_domains`, `combine_emitters`, etc. -Int his introduction, we focus on the most basic three methods. +This introduction shall focus on the three most important methods. -\subsubsection sec_project_project_init Initialization and Defaults -In the `__init__` method, you define and initialize (with default values) additional project properties. -You may also redefine properties of the base class. -The following code is just an example to give you some ideas. +\subsubsection sec_project_project_init Initialization and defaults + +The `__init__` method defines and initializes project properties with default values. +It may also redefine properties of the base class. +The following code is just an example to give some ideas. ~~~~~~{.py} class MyProject(pmsco.project.Project): @@ -259,19 +338,20 @@ class MyProject(pmsco.project.Project): self.domains = [{"zrot": 0.}] def build_scan_dict(self): - self.scan_dict["empty"] = {"filename": "{pmsco}/projects/common/empty-hemiscan.etpi", + self.scan_dict["empty"] = {"filename": "${pmsco}/projects/common/empty-hemiscan.etpi", "emitter": "Si", "initial_state": "2p3/2"} - self.scan_dict["Si2p"] = {"filename": "{data}/xpd-Si2p.etpis", + self.scan_dict["Si2p"] = {"filename": "${data}/xpd-Si2p.etpis", "emitter": "Si", "initial_state": "2p3/2"} ~~~~~~ -The scan dictionary can come in handy if you want to select scans by a shortcut on the command line or in a run file. +A scan dictionary is one way to specify locations and metadata of experimental files centrally in the project code. +The scan can then be selected by the dictionary key rather than copying file locations. -Note that most of the properties can be assigned from a run file. +Note that all public attributes can be assigned from a run file. This happens after the `__init__` method. The values set by `__init__` serve as default values. -\subsubsection sec_project_project_space Model Space +\subsubsection sec_project_project_space Model space The model space defines the keys and value ranges of the model parameters. There are three ways to declare the model space in order of priority: @@ -280,21 +360,21 @@ There are three ways to declare the model space in order of priority: 2. Assign a ModelSpace to the self.model_space property directly in the `__init__` method. 3. Implement the `create_model_space` method. -We begin the third way: +The third way may look like this: ~~~~~~{.py} -# under class MyProject(pmsco.project.Project): +class MyProject(pmsco.project.Project): def create_model_space(self): # create an empty model space spa = pmsco.project.ModelSpace() # add parameters - spa.add_param('dAB', 2.10, 2.00, 2.25, 0.05) - spa.add_param('th', 15.00, 0.00, 30.00, 1.00) + spa.add_param('dAB', 2.05, width=0.25, step=0.05) + spa.add_param('th', 15.00, 0.00, 30.00, 1.00) spa.add_param('ph', 90.00) - spa.add_param('V0', 21.96, 15.00, 25.00, 1.00) + spa.add_param('V0', 21.96, width=10.0, step=1.0) spa.add_param('Zsurf', 1.50) - spa.add_param('wdom1', 0.5, 0.10, 10.00, 0.10) + spa.add_param('wdom1', 0.5, 0.10, 10.00, 0.10) # return the model space return spa @@ -303,14 +383,18 @@ We begin the third way: This code declares six model parameters: `dAB`, `th`, `ph`, `V0`, `Zsurf` and `wdom1`. Three of them are structural parameters (used by the cluster generator above), two are used by the `create_params` method (see below), -and `wdom1` is used in pmsco.project.Project.combine_domains while summing up contributions from different domains. +and `wdom1` is used in @ref pmsco.project.Project.combine_domains +while summing up contributions from different domains. The values in the arguments list correspond to the start value (initial guess), the lower and upper boundaries of the value range, and the step size for optimizers that require it. -If just one value is given, like for `ph` and `Zsurf`, the parameter is held constant during the optimization. +If just one value is given the parameter is held constant during the optimization. +The range can, alternatively, be specified by the `width` argument. -The equivalent declaration in the run-file would look like (parameters after `th` omitted): +A similar declaration in a run-file could look like as follows (some parameters omitted for brevity). +Parameter values can be numeric constants, +or simple Python math expressions in double quotes. ~~~~~~{.py} { @@ -318,9 +402,8 @@ The equivalent declaration in the run-file would look like (parameters after `th // ... "model_space": { "dAB": { - "start": 2.109, - "min": 2.0, - "max": 2.25, + "start": "2.0 / math.cos(math.radians(15.0))", + "width": 0.25, "step": 0.05 }, "th": { @@ -329,22 +412,25 @@ The equivalent declaration in the run-file would look like (parameters after `th "max": 30.0, "step": 1.0 }, + "Zsurf": { + "start": 1.50 + } // ... } } } ~~~~~~ -\subsubsection sec_project_project_params Calculation Parameters +\subsubsection sec_project_project_params Calculation parameters Non-structural parameters that are needed for the input files of the calculators are passed -in a pmsco.project.CalculatorParams object. -This object should be created and filled in the `create_params` method of the project class. +in a @ref pmsco.project.CalculatorParams object. +This object is created and filled in the `create_params` method of the project class. -The following example is from the twoatoms demo project: +The following example is from the `twoatoms` demo project: ~~~~~~{.py} -# under class MyProject(pmsco.project.Project): +class MyProject(pmsco.project.Project): def create_params(self, model, index): params = pmsco.project.CalculatorParams() @@ -381,74 +467,48 @@ The following example is from the twoatoms demo project: Most of the code is generic and can be copied to other projects. Only the experimental and material parameters need to be adjusted. -Other properties can be changed as needed, see the documentation of pmsco.project.CalculatorParams for details. +Other properties can be changed as needed, see @ref pmsco.project.CalculatorParams. -\subsection sec_project_args Passing Runtime Parameters -Runtime parameters can be passed in one of three ways: +\subsection sec_project_args Passing run-time parameters -1. hard-coded in the project module, -2. on the command line, or -3. in a JSON run-file. +The recommended way of passing calculation parameters is via @ref pag_runfile. +Run-files allow for a complete separation of code and data in a generic and flexible way. +Program code can be managed by a version control system, +and run-files can be stored along with the results. +This simplifies the reproduction of previous calculations and documentation of the workflow. -In the first way, all parameters are hard-coded in the `create_project` function of the project module. -This is the simplest way for a quick start to a small project. -However, as the project code grows, it's easy to loose track of revisions. -In programming it is usually best practice to separate code and data. +For testing and simple projects, it is possible to hard-code all parameters in the project class. -The command line is another option for passing parameters to a process. -It requires extra code for parsing the command line and is not very flexible. -It is difficult to pass complex data types. -Using the command line is no longer recommended and may become deprecated in a future version. -The recommended way of passing parameters is via run-files. -Run-files allow for complete separation of code and data in a generic and flexible way. -For example, run-files can be stored along with the results. -However, the semantics of the run-file may look intimidating at first. - -\subsubsection sec_project_args_runfile Setting Up a Run-File +\subsubsection sec_project_args_runfile Setting up a run-file The usage and format of run-files is described in detail under @ref pag_runfile. -\subsubsection sec_project_args_code Hard-Coded Arguments -Hard-coded parameters are usually set in a `create_module` function of the project module. -At the end of the module, this function can easily be found. -The function has two purposes: to create the project object and to set parameters. -The parameters can be any attributes of the project class and its ancestors. -See the parent pmsco.project.Project class for a list of common attributes. +\subsubsection sec_project_args_code Hard-coded arguments -The `create_project` function may look like in the following example. -It must return a project object, i.e. an object instance of a class that inherits from pmsco.project.Project. +Though it's normally recommended to declare all parameters in the run-file, +parameter values can also be hard-coded in the constructor and/or the validate method of the project class. +Which method to use depends on the processing stage. -~~~~~~{.py} -def create_project(): - project = MyProject() +The constructor can set default values for rarely changing parameters. +The declarations in the run-file override the defaults from the constructor. +If some parameters need adjusting _after_ the run-file has been loaded, +this can be done in the validate` method. - project.optimizer_params["pop_size"] = 20 +The call sequence of the methods is as follows. - project_dir = Path(__file__).parent - scan_file = Path(project_dir, "hbnni_e156_int.etpi") - project.add_scan(filename=scan_file, emitter="N", initial_state="1s") - - project.add_domain({"zrot": 0.0}) - project.add_domain({"zrot": 60.0}) - - return project -~~~~~~ - -To have PMSCO call this function, -pass the file path of the containing module as the first command line argument of PMSCO, cf. @ref pag_command. -PMSCO calls this function in absence of a run-file. - - -\subsubsection sec_project_args_cmd Command Line - -Since it is not recommended to pass calculation parameters on the command line, -this mechanism is not described in detail here. -It is, however, still available. -If you really need to use it, -have a look at the code of the pmsco.pmsco.main function -and how it calls the `create_project`, `parse_project_args` and `set_project_args` of the project module. +1. `Project.__init__`: + The constructor is usually overridden by the project. + The constructor must call the superclass before applying its values. +2. `Project.set_properties`: + Sets the parameters from the run-file and resolves class names. + This method can be overridden if additional classes need resolving after loading the run-file. + It must call the superclass. +3. `Project.validate`: Parameters are validated, i.e., checked and made consistent. + Handler classes are resolved. + The `validate` method or its sub-methods can be overridden by the project. + The inherited method should be called. */ diff --git a/docs/src/reports.dox b/docs/src/reports.dox new file mode 100644 index 0000000..b40ee29 --- /dev/null +++ b/docs/src/reports.dox @@ -0,0 +1,135 @@ +/*! @page pag_reports Reports +\section sec_reports Reports + +The main output of PMSCO is the model parameters to R-factor mapping. +By default, it is produced in the form of a text file (.dat) as well as an sqlite3 database file (.db). +Graphical representations of the result data, called _reports_ in PMSCO, can be produced automatically at run-time or +manually after the calculation has ended. + +PMSCO provides a number of pre-defined reports as well as an interface for custom reports. +Essentially, a report is defined by a Python class which derives from `pmsco.reports.base.ProjectReport`. +Instances of reports are added to the project's `reports` list during initialization of the calculation job. +They are called by the calculation handlers whenever a new model-level result is available in the database. +While reports typically produce graphics files for diagnostics, +report classes could basically produce any derived data including data files in different formats. + +By default, no report is produced during a project run. +There are several ways to generate reports: + +- Add instances of reports to the `reports` list of the project object. + This can be done in the project code or in the @ref pag_runfile. + One or multiple reports (of different classes) can be added and configured. +- Some report modules have their own command line interface. + This allows you to produce a report at any time during or after the project run. +- Lastly, all reports are Python classes and can be instantiated and executed in a Python shell. + +The remainder of this page describes some of the pre-defined reports and their configuration parameters (attributes). + +@note Reporting is still under development. +The configuration parameters and behaviour is subject to change, and the documentation may be partially outdated. +Be sure to check the in-line documentation as well as the source code for the latest information. + + +\subsection sec_reports_common Common Parameters + +The reports share some common parameters which may, however, be used differently or ignored by some reports. + +| Key | Values | Description | +| --- | --- | --- | +| `filename_format` | template string using `${key}`-type placeholders | Template string for file names of reports. Possible placeholders are listed below. | +| `title_format` | template string using `${key}`-type placeholders | Template string for graph titles. Possible placeholders are listed below. | +| `canvas` | string. default: `matplotlib.backends.backend_agg.FigureCanvasAgg` (PNG) | A matplotlib figure canvas such as FigureCanvasAgg, FigureCanvasPdf or FigureCanvasSVG. | + +The `filename_format` and `title_format` attributes are template strings which can contain `${key}` type placeholders. +placeholders are replaced according to the following table. +Some of these values may not be available if you call the reports outside of an optimization run +(e.g., from the command line of a report module). + +| Key | Description | +| --- | --- | +| `base` | Base file name. Default: job name | +| `mode` | optimization mode | +| `job_name` | job name | +| `project_name` | project name | +| any directories key | corresponding directories value | +| any job_tags key | corresponding job_tags value | + + +\subsection sec_reports_convergence Convergence Plot + +The convergence plot is a violin plot where each violin represents the R-factor distribution of one generation. +The minimum, maximum and mean values are marked, and the distribution is indicated by the body. +Convergence plots are suitable for genetic or swarm optimizations. + +| Key | Values | Description | +| --- | --- | --- | +| __class_name__ | pmsco.reports.population.ConvergencePlot | | +| filename_format | template string using `${key}`-type placeholders | See common section. | +| title_format | template string using `${key}`-type placeholders | See common section. | + + +\subsection sec_reports_genetic Genetic Chart + +A genetic chart is a pseudo-colour representation of the coordinates of each individual in the model space. +The chart shows the amount of diversity in the population +and - by comparing charts of different generations - the changes due to mutation. +The axes are the model parameters (x) and particle number (y). +The colour is mapped from the relative parameter value within the parameter range. +Genetic charts are suitable for genetic or swarm optimizations. + +| Key | Values | Description | +| --- | --- | --- | +| __class_name__ | pmsco.reports.population.GeneticPlot | | +| filename_format | template string using `${key}`-type placeholders | See common section. | +| title_format | template string using `${key}`-type placeholders | See common section. | +| cmap | string: 'viridis', 'plasma' (default), 'inferno', 'magma', 'cividis' | Name of colour map supported by matplotlib. | +| params | list of model parameter names | | + +In addition to the common template substitutions, +the genetic chart report replaces the following placeholders +of the `filename_format` and `title_format` template strings. + +| Key | Description | +| --- | --- | +| `gen` | Generation index (population reports only) | + + +\subsection sec_reports_swarm Particle Swarm Plot + +The particle swarm plot shows the current positions and velocities of particles projected onto two dimensions. +The plot contains three elements: +- a pseudo-color scatter plot of all R-factors in the background, +- a scatter plot of particle positions. +- a quiver plot indicating the velocities of the particles. + +Particle swarm plots are suitable in particle swarm optimization mode only. + +| Key | Values | Description | +| --- | --- | --- | +| __class_name__ | pmsco.reports.population.SwarmPlot | | +| filename_format | template string using `${key}`-type placeholders | See common section. | +| title_format | template string using `${key}`-type placeholders | See common section. | +| cmap | string: 'viridis', 'plasma' (default), 'inferno', 'magma', 'cividis' | Name of colour map supported by matplotlib. | +| params | nested list of pairs of model parameter names | | + +In addition to the common template substitutions, +the particle swarm plot report replaces the following placeholders +of the `filename_format` and `title_format` template strings. + +| Key | Description | +| --- | --- | +| `gen` | Generation index (population reports only) | +| `param0` | Parameter name 0 (population reports only) | +| `param1` | Parameter name 1 (population reports only) | + + +\subsection sec_reports_misc Miscellaneous + +To make a video from swarm or genetic plots, you may use ffmpeg on Linux: + +~~~~~~{.sh} +ffmpeg -framerate 5 -i basename-%00d.geneticplot.png -c:v libx264 -profile:v high -crf 20 -pix_fmt yuv420p basename.geneticplot.mp4 +~~~~~~ + + +*/ diff --git a/docs/src/runfile.dox b/docs/src/runfile.dox index e180c01..f1fc026 100644 --- a/docs/src/runfile.dox +++ b/docs/src/runfile.dox @@ -2,59 +2,92 @@ \section sec_runfile Run File This section describes the format of a run-file. -Run-files are a new way of passing arguments to a PMSCO process which avoids cluttering up the command line. -It is more flexible than the command line -because run-files can assign a value to any property of the project object in an abstract way. -Moreover, there is no necessity for the project code to parse the command line. +Run-files are a flexible way of passing arguments to a PMSCO process. +The benefits are: + +- contain all essential parameters to repeat a calculation - no need to remember or record the command line +- avoid cluttering up the command line or frequent changes of source code +- can be versioned or stored separately from the code, maintain a single file or multiple files - up to the user +- any property and sub-property of the project object can be assigned in a generic way - even custom properties that are unknown to PMSCO +- no necessity for the project code to parse the command line +- schema validation can help to find syntax errors while editing \subsection sec_runfile_how How It Works -Run-files are text files in [JSON](https://en.wikipedia.org/wiki/JSON) format -which shares most syntax elements with Python. -JSON files contain nested dictionaries, lists, strings and numbers. +Run-files are text files in machine and human readable [JSON](https://en.wikipedia.org/wiki/JSON) format. +In PMSCO, run-files contain dictionaries of parameters to be passed to the project object. +For the calculations, internally, the project object is the main container of calculation parameters, model objects and input data. -In PMSCO, run-files contain a dictionary of parameters for the project object -which is the main container for calculation parameters, model objects and links to data files. -An abstract run-file parser reads the run-file, -constructs the specified project object based on the custom project class -and assigns the attributes of the project object. -It's important to note that the parser does not recognize specific data types or classes. -All specific data handling is done by the instantiated objects, mainly the project class. +Upon launching PMSCO, a generic parser reads the run-file, +constructs the project object from the specified custom project class +and assigns the attributes defined in the run-file. +Run-files are a sort of script that assign data to the project. +The parser does not expect specific data types or classes. +It merely copies data items to the project attributes of the same name. +The validation and interpretation of the data is up to the project object. -The parser can handle the following situations: +The parser handles the following situations: -- Strings, numbers as well as dictionaries and lists of simple objects can be assigned directly to project attributes. -- If the project class defines an attribute as a _property_, - the class can execute custom code to import or validate data. -- The parser can instantiate an object from a class in the namespace of the project module - and assign its properties. +- Strings, numbers as well as dictionaries and lists of simple objects are assigned directly to project attributes. + If the project class declares a setter method for the attribute, the setter is called. + Else, the existing attribute is overwritten. + Setters can execute custom code to validate the data value. +- If specified in the run-file, the parser creates objects from classes in the namespace of the project module + and recursively assigns their properties. + +\note There are no implicit checks of correctness of the assigned data objects! +The author of the run-file must make sure that the run-file is compatible with the project class, +else the calculation process might fail. + +There are three ways to check assigned attributes before the calculations are started. +All have to be implemented explicitly by the project maintainer: + +1. The run-file can be validated against a JSON schema before launching PMSCO (see below). + Schema validation may catch some obvious mistakes + but is not complete in the sense that it cannot guarantee error-free execution of the project code. +2. The classes used with run-files define property setters. + The setters can raise an exception or post an error in the log. + (The latter won't stop the calculation process.) +3. The project class implements a validation method to check and fix important or error-prone attributes. + It can write warnings and errors to the log, or raise an exception if the process should be aborted. \subsection sec_runfile_general General File Format -Run-files must adhere to the [JSON](https://en.wikipedia.org/wiki/JSON) format, -which shares most syntax elements with Python. +Run-files must adhere to the [JSON](https://en.wikipedia.org/wiki/JSON) format. Specifically, a JSON file can declare dictionaries, lists and simple objects such as strings, numbers and `null`. -As one extension to plain JSON, PMSCO ignores line comments starting with a hash `#` or double-slash `//`. -This can be used to temporarily hide a parameter from the parser. +The syntax of these basic elements is similar to Python source code (there are some differences, though). -For example run-files, have a look at the twoatom demo project. +At the top level, a PMSCO run-file contains a dictionary with up to two items: + +1. The _project_ item is the most important, it is described in the following under @ref sec_runfile_project. +2. The _schedule_ item is an optional section for passing the parameters to a job queue of a computing cluster. + See @ref sec_runfile_schedule . + + +\subsection sec_runfile_schema Schema + +The structure of a JSON file can be described in a _schema_ file that can be used to check the syntax and structure programmatically. +The `schema/runfile.schema.json` file of the PMSCO distribution describes the structure of a run-file as well as common properties of the project. +The schema is, however, rather basic and does not cover all parameters, conditional cases or custom project properties. + +A run-file can be easily validated against the schema while editing in the PyCharm IDE. +Alternatively, the jsonschema validator from the Python distribution can be used on the command line. \subsection sec_runfile_project Project Specification - -The following minimum run-file demonstrates how to specify the project at the top level: +The following minimum run-file from the twoatom project demonstrates how to specify the project: ~~~~~~{.py} { "project": { - "__module__": "projects.twoatom.twoatom", + "__module__": "twoatom", "__class__": "TwoatomProject", "mode": "single", - "output_file": "twoatom0001" + "job_name": "twoatom0001" } } ~~~~~~ @@ -67,28 +100,33 @@ Further dictionary items correspond to attributes of the project class. The module name is the same as would be used in a Python import statement. It must be findable on the Python path. +Alternatively, a file path may be specified. PMSCO ensures that the directory containing the `pmsco` and `projects` sub-directories is on the Python path. The class name must be in the namespace of the loaded module. As PMSCO starts, it imports the specified module, constructs an object of the specified project class, and assigns any further items to project attributes. -In the example above, `twoatom0001` is assigned to the `output_file` property. -Any attributes not specified in the run-file will remain at their default values -that were set byt the `__init__` method of the project class. +In the example above, it creates an object of type `TwoatomProject` from the `twoatom` module +and assigns `single` to the `mode` property and `twoatom0001` to the `job_name` property. -Note that parameter names must start with an alphabetic character, else they are ignored. -This provides another way to temporarily ignore an item from the file besides line comments. +Any attributes not specified in the run-file remain at their default values +that were set by the `__init__` constructor of the project class. +Note that parameter names must start with an alphabetic character, else they are ignored +(useful for comments as JSON does not have a syntax for comments). Also note that PMSCO does not spell-check parameter names. The parameter values are just written to the corresponding object attribute. -If a name is misspelled, the value will be written under the wrong name and missed by the code eventually. +If a name is misspelled, the value will be written to the wrong attribute. PMSCO carries out only some most important checks on the given parameter values. Incorrect values may lead to improper operation or exceptions later in the calculations. +The project class can explicitly check and fix important or error-prone attributes, or report errors. + +The following sub-sections describe the most common properties of the project class. -\subsection sec_runfile_common Common Arguments +\subsubsection sec_runfile_common Common Arguments The following table lists some important parameters controlling the calculations. They are declared in the pmsco.projects.Project class. @@ -96,10 +134,10 @@ They are declared in the pmsco.projects.Project class. | Key | Values | Description | | --- | --- | --- | | mode | `single` (default), `grid`, `swarm`, `genetic`, `table`, `test`, `validate` | Operation mode. `validate` can be used to check the syntax of the run-file, the process exits before starting calculations. | -| directories | dictionary | This dictionary lists common file paths used in the project. It contains keys such as `home`, `project`, `output` (see documentation of Project class in pmsco.project). Enclosed in curly braces, the keys can be used as placeholders in filenames. | +| directories | dictionary | This dictionary lists common file paths used in the project. It contains keys such as `home`, `project`, `output` (see documentation of Project class in pmsco.project). | | output_dir | path | Shortcut for directories["output"] | | data_dir | path | Shortcut for directories["data"] | -| job_name | string, must be a valid file name | Base name for all produced output files. It is recommended to set a unique name for each calculation run. Do not include a path. The path can be set in _output_dir_. | +| job_name | string, must be a valid and unique file name (see note below) | Base name for all produced output files. It is recommended to set a unique name for each calculation run. Do not include a path. The path can be set in _output_dir_. | | cluster_generator | dictionary | Class name and attributes of the cluster generator. See below. | | atomic_scattering_factory | string
Default: InternalAtomicCalculator from pmsco.calculators.calculator | Class name of the atomic scattering calculator. This name must be in the namespace of the project module. | | multiple_scattering_factory | string
Default: EdacCalculator from pmsco.calculators.edac | Class name of the multiple scattering calculator. This name must be in the namespace of the project module. | @@ -108,27 +146,71 @@ They are declared in the pmsco.projects.Project class. | scans | list of dictionaries | See @ref sec_runfile_scans below. | | optimizer_params | dictionary | See @ref sec_runfile_optimizer below. | +\note The *job name* parameter appears most visibly as the prefix of output file names. +It is also registered in the `jobs` table of the results database (if used), +and it is used to identify the job with a job scheduling system. +For these reasons, it is important that the job name be unique within the respective subsystem. +Specifically, you need to *provide a new job name each time you start pmsco*, otherwise the job may fail. +It may be more natural to specify the job name on the command line using the `-o` argument +rather than changing the run file every time. +Unfortunately, PMSCO cannot auto-generate, auto-increment or verify the job name. + +File names specified in a runfile can include an explicit path or a placeholder. +Placeholders have the format `${key}` where `key` must be one of the keys of the `directories` dictionary. +The placeholder will then be replaced by the corresponding value before the calculation starts +(as a part of the pmsco.project.Project.validate method). +The `directories` dictionary can be filled by the project class or in the runfile. +In addition, a number of keys are defined by PMSCO and can be used as placeholders in other directories and file paths. + +| Key | Type | Description | +| --- | --- | --- | +| data | absolute | Directory with experimental data. Must be set by user if needed. | +| home | absolute | Home directory of the current user | +| pmsco | absolute | Directory that contains the loaded pmsco.py module. Note: This may be in a site packages directory. | +| output | absolute | Output directory. Must be set by the user. | +| project | absolute | Directory where the project module is located. | +| project_name | relative | Name of the project. By default, the name of the project class. | +| job_name | relative | Name of the calculation job. | +| mode | relative | Calculation mode | +| report | absolute | Report directory. Defaults to `${output}/report`. | +| run | absolute | Directory where the runfile is located (if used). | +| temp | absolute | Directory for temporary files. Currently not used. | +| work | absolute | Current working directory | + +Placeholders of absolute paths must be used at the beginning of a path. +Relative paths can be used at any position in a file path. +Some of the keys may have empty values if PMSCO was loaded in a non-standard way. +For verification of the path resolution, all directories are printed to the log file at WARNING level (default). + The following table lists some common control parameters and metadata that affect the behaviour of the program but do not affect the calculation results. The job metadata is used to identify and describe a job in the results database if requested. | Key | Values | Description | | --- | --- | --- | -| job_tags | list of strings | User-specified job tags (metadata). | +| db_file | new or existing file path or `:memory:` | SQLite3 database file to receive the optimization results. If the database exists, results are inserted under the given job name. If it doesn't exist, a new file is created. If the attribute is `:memory:`, an in-memory database is used internally and flushed at the end of processing. | +| job_tags | dictionary of strings | User-specified job tags in key-value format (metadata). | | description | string | Description of the calculation job (metadata) | | time_limit | decimal number
Default: 24. | Wall time limit in hours. The optimizers try to finish before the limit. This cannot be guaranteed, however. | | keep_files | list of file categories | Output file categories to keep after the calculation. Multiple values can be specified and must be separated by spaces. By default, cluster and model (simulated data) of a limited number of best models are kept. See @ref sec_runfile_files below. | | keep_best | integer number
Default: 10 | number of best models for which result files should be kept. | -| keep_level | integer number
Default: 1 | numeric task level down to which files are kept. 1 = scan level, 2 = domain level, etc. | +| keep_levels | integer number
Default: 1 | numeric task level down to which files are kept. 1 = scan, 2 = domain, 3 = emitter. | | log_level | DEBUG, INFO, WARNING, ERROR, CRITICAL | Minimum level of messages that should be added to the log. Empty string turns off logging. | | log_file | file system path
Default: job_name + ".log". | Name of the main log file. Under MPI, the rank of the process is inserted before the extension. The log name is created in the working directory. | -\subsection sec_runfile_space Model Space +\subsubsection sec_runfile_space Model Space The `model_space` parameter is a dictionary of model parameters. The key is the name of the parameter as used by the cluster and input-formatting code, the value is a dictionary holding the `start`, `min`, `max`, `step` values to be used by the optimizer. +Instead of `min` and `max` you may declare the `width`, which will center the space on the start value. + +All parameter values can be declared as numbers or as simple Python expressions in double quotes. +Expressions are evaluated by the Python `eval` function. +All functions in the namespace of the project module. +Note that you have to import the `math` or `numpy` modules in your project module +if you want to use their functions. ~~~~~~{.py} { @@ -137,15 +219,14 @@ the value is a dictionary holding the `start`, `min`, `max`, `step` values to be "model_space": { "dAB": { "start": 2.109, - "min": 2.0, - "max": 2.25, + "min": "2.109 - 0.1", + "max": "2.109 + 0.1", "step": 0.05 }, "pAB": { - "start": 15.0, - "min": 0.0, - "max": 30.0, - "step": 1.0 + "start": "4 * 3.56 / math.sqrt(3.0)", + "width": 4.0, + "step": 0.5 }, // ... } @@ -153,13 +234,18 @@ the value is a dictionary holding the `start`, `min`, `max`, `step` values to be } ~~~~~~ +Alternatively, the `model_space` can be declared as a `ModelSpace` object. +However, this shall not be described in detail here. -\subsection sec_runfile_domains Domains + +\subsubsection sec_runfile_domains Domains Domains is a list of dictionaries. Each dictionary holds keys describing the domain to the cluster and input-formatting code. The meaning of these keys is up to the project. +An example: + ~~~~~~{.py} { "project": { @@ -175,10 +261,10 @@ The meaning of these keys is up to the project. \subsection sec_runfile_scans Experimental Scan Files -The pmsco.project.Scan objects used in the calculation cannot be instantiated from the run-file directly. -Instead, the scans object is a list of scan creators/loaders which specify what to do to create a Scan object. -The pmsco.project module defines three scan creators: ScanLoader, ScanCreator and ScanKey. -The following code block shows an example of each of the three: +The pmsco.scan.Scan objects used in the calculation cannot be instantiated from the run-file directly. +Instead, the scans object of the run-file is a list of scan creators/loaders which specify how to create a Scan object. +The pmsco.scan module defines four scan creators: `ScanLoader`, `ScanCreator`, `HoloScanCreator` and `ScanKey`. +The following code block shows examples: ~~~~~~{.py} { @@ -186,7 +272,7 @@ The following code block shows an example of each of the three: // ... "scans": [ { - "__class__": "pmsco.project.ScanCreator", + "__class__": "ScanCreator", "filename": "twoatom_energy_alpha.etpai", "emitter": "N", "initial_state": "1s", @@ -198,15 +284,34 @@ The following code block shows an example of each of the three: } }, { - "__class__": "pmsco.project.ScanLoader", - "filename": "{project}/twoatom_hemi_250e.etpi", + "__class__": "ScanLoader", + "filename": "${project}/twoatom_hemi_250e.etpi", "emitter": "N", "initial_state": "1s", "is_modf": false }, { - "__class__": "pmsco_project.ScanKey", - "key": "Ge3s113tp" + "__class__": "HoloScanCreator", + "filename": "${project}/twoatom_scan3.etpi", + "emitter": "N", + "initial_state": "1s", + "generator": "pmsco.data.holo_grid", + "generator_args": { + "theta_start": 90, + "theta_step": 1, + "theta_range": 90, + "phi_start": 0, + "phi_range": 360, + "phi_refinement": 1 + }, + "other_positions": {"e": 250, "a": 0} + }, + { + "__class__": "HoloScanCreator", + "filename": "${project}/twoatom_scan4.etpi", + "emitter": "N", + "initial_state": "1s", + "other_positions": {"e": 250, "a": 0} } ] } @@ -214,7 +319,14 @@ The following code block shows an example of each of the three: ~~~~~~ The class name must be specified as it would be called in the custom project module. -`pmsco.project` must, thus, be imported in the custom project module. +For the example shown above, the following import statements are necessary in the `pmsco.projects.twoatom.py` module. +(Other forms of the import statement can be used accordingly.) + +~~~~~~{.py} +import numpy as np +import pmsco.data +from pmsco.scan import ScanKey, ScanLoader, ScanCreator, HoloScanCreator +~~~~~~ The *ScanCreator* object creates a scan using Numpy array constructors in `positions`. In the example above, a two-dimensional rectangular energy-alpha scan grid is created. @@ -223,6 +335,19 @@ and must return a one-dimensional Numpy `ndarray`. The `emitter` and `initial_state` keys define the probed core level. +The *HoloScanCreator* object creates a /holo scan/, i.e., an angle scan of the theta and phi axes. +The distribution of the grid points is defined by a separate generator function. +Usually, the default pmsco.data.holo_grid function is used +which generates the well-known Osterwalder holo scan +with constant point density in solid angle and equidistant polar steps. + +The `generator` and `generator_args` properties have default values. +The two example holo scans above are equivalent, +as the first one above just uses default values explicitly. +If you want to specify a generator function explicitly, +you must import it into the namespace of your project. +E.g. for `pmsco.data.holo_grid` you have to `import pmsco.data`. + The *ScanLoader* object loads a data file, specified under `filename`. The filename can include a placeholder which is replaced by the corresponding item from Project.directories. Note that some of the directories (including `project`) are pre-set by PMSCO. @@ -232,13 +357,14 @@ The `is_modf` key indicates whether the file contains a modulation function (`tr In the latter case, the modulation function is calculated after loading. The *ScanKey* is the shortest scan specification in the run-file. -It is a shortcut to a complete scan description in `scan_dict` dictionary in the project object. +It should not be used in new projects as it uses hard-coded data links in program code. + +ScanKey is a shortcut to a complete scan dictionary in the project object. The `scan_dict` must be set up in the `__init__` method of the project class. The `key` item specifies which key of `scan_dict` should be used to create the Scan object. - Each item of `scan_dict` holds a dictionary -that in turn holds the attributes for either a `ScanCreator` or a `ScanLoader`. -If it contains a `positions` key, it represents a `ScanCreator`, else a `ScanLoader`. +that holds the attributes for either a `ScanCreator`, `HoloScanCreator` or a `ScanLoader`. +If it contains a `positions` (`other_positions`) key, it represents a `ScanCreator` (`HoloScanCreator`), else a `ScanLoader`. \subsection sec_runfile_optimizer Optimizer Parameters @@ -247,9 +373,11 @@ The `optimizer_params` is a dictionary holding one or more of the following item | Key | Values | Description | | --- | --- | --- | -| pop-size | integer
The default value is the greater of 4 or the number of parallel calculation processes. | Population size (number of particles) in swarm and genetic optimization mode. | -| seed-file | file system path | Name of the population seed file. Population data of previous optimizations can be used to seed a new optimization. The file must have the same structure as the .pop or .dat files. See @ref pmsco.project.Project.seed_file. | -| table-file | file system path | Name of the model table file in table scan mode. | +| pop_size | integer
The default value is the greater of 4 or the number of parallel calculation processes. | Population size (number of particles) in swarm and genetic optimization mode. | +| seed_file | file system path | Name of the population seed file. Population data of previous optimizations can be used to seed a new optimization. The file must have the same structure as the .pop or .dat files. See @ref pmsco.optimizers.population.Population.seed_from_file. | +| seed_limit | integer | Number of seed models to import. | +| recalc_seed | true or false | If true, the seed models are calculated. Otherwise, the R-factor from the seed file is used as result. Use true if the seed file contains no or outdated R-factors. | +| table_source | file system path | Name of the model table file in table scan mode. | \subsubsection sec_runfile_files File Categories @@ -286,6 +414,45 @@ you have to add the file categories that you want to keep, e.g., Do not specify `rfac` alone as this will effectively not return any file. +\subsection sec_runfile_reports Reports + +Run-time graphical reports are configured in the `reports` section. +The section is organized as a list of dictionaries. +Each dictionary sets up a specific report. +For example: + +~~~~~~{.py} +{ + "project": { + // ... + "reports": [ + { + "__class__": "ConvergencePlot", + "filename_format": "${base}.convergence", + "title_format": "my_calc" + }, + { + "__class__": "SwarmPlot", + "filename_format": "${base}-${param0}-${param1}-${gen}.swarmplot", + "title_format": "my_calc ${param0}-${param1} gen ${gen}", + "params": [["A", "B"], ["C", "D"]] + } + ] + } +} +~~~~~~ + +The class name must be specified as it would be called in the custom project module. +For the example above, the import section of the project must include: + +~~~~~~{.py} +from pmsco.reports.convergence import ConvergencePlot +from pmsco.reports.swarm import SwarmPlot +~~~~~~ + +For details on reports and their configuration, see @ref sec_reports. + + \subsection sec_runfile_schedule Job Scheduling To submit a job to a resource manager such as Slurm, add a `schedule` section to the run file @@ -306,7 +473,7 @@ To submit a job to a resource manager such as Slurm, add a `schedule` section to "__module__": "projects.twoatom.twoatom", "__class__": "TwoatomProject", "mode": "single", - "output_file": "{home}/pmsco/twoatom0001", + "output_file": "${home}/pmsco/twoatom0001", ... } } @@ -314,10 +481,16 @@ To submit a job to a resource manager such as Slurm, add a `schedule` section to In the same way as for the project, the `__module__` and `__class__` keys select the class that handles the job submission. In this example, it is pmsco.schedule.PsiRaSchedule which is tied to the Ra cluster at PSI. -For other machines, you can sub-class one of the classes in the pmsco.schedule module and include it in your project module. +For other machines, you can sub-class one of the classes in the pmsco.schedule module and include it in your project package. +The derived job submission class must prepare the code, run file and job script, and submit the job to the queue. +It should copy the code to the calculation directory to avoid version conflicts if the user continues to edit the code. +Compilation of the code can be done before submission or as a part of the job script. + +@note It is difficult to check the run file and code against errors that may abort job execution. +New code and run files should be tested with a modified, fast-running calculation. The parameters of pmsco.schedule.PsiRaSchedule are as follows. -Some of them are also used in other schedule classes or may have different types or ranges. +Information about the computing nodes and partitions can be printed by the `sinfo -Nel` and `sinfo --long` commands. | Key | Values | Description | | --- | --- | --- | @@ -325,9 +498,9 @@ Some of them are also used in other schedule classes or may have different types | tasks_per_node | integer: 1..24, 32 | Number of tasks (CPU cores on Ra) per node. Jobs with less than 24 tasks are assigned to the shared partition. | | wall_time | string: [days-]hours[:minutes[:seconds]]
dict: with any combination of days, hours, minutes, seconds | Maximum run time (wall time) of the job. | | manual | bool | Manual submission (true) or automatic submission (false). Manual submission allows you to inspect the job files before submission. | -| enabled | bool | Enable scheduling (true). Otherwise, the calculation is started directly (false). +| enabled | bool | Enable scheduling (true). Otherwise, the calculation is started directly (false). | @note The calculation job may run in a different working directory than the current one. It is important to specify absolute data and output directories in the run file (project/directories section). - +Placeholders like `${home}` can be used to make run files portable, cf. @ref sec_run_dirs. */ diff --git a/docs/src/uml/database.puml b/docs/src/uml/database.puml index 5d1cad4..4bbe111 100644 --- a/docs/src/uml/database.puml +++ b/docs/src/uml/database.puml @@ -58,6 +58,8 @@ domain emit region rfac +timestamp +secs } class Param << (T,orchid) >> { @@ -74,6 +76,7 @@ param_id model_id .. value +delta } Project "1" *-- "*" Job diff --git a/docs/src/uml/scan-classes.puml b/docs/src/uml/scan-classes.puml new file mode 100644 index 0000000..2dbf13c --- /dev/null +++ b/docs/src/uml/scan-classes.puml @@ -0,0 +1,86 @@ +@startuml +'https://plantuml.com/class-diagram + +class ConfigurableObject + +class Scan { + filename: str + raw_data: numpy.ndarray + dtype: numpy.dtype + modulation: numpy.ndarray + modulation_func: Callable + modulation_args: Dict + rfactor_func: Callable + rfactor_args: Dict + mode: List + emitter: str + initial_state: str + positions: Dict + + __init__() + copy() + load() + define_scan() + import_scan_file() + analyse_raw_data() + generate_holo_scan() +} + +class ScanSpec { + filename: str + emitter: str + initial_state: str + modulation_func: Callable + modulation_args: Dict + rfactor_func: Callable + rfactor_args: Dict + + __init__() + load() +} + +class ScanKey { + project: pmsco.project.Project + key: str + + __init__() + load() +} + +class ScanLoader { + is_modf: bool + patch: Dict + + __init__() + load() +} + +class ScanCreator { + positions: Dict + + __init__() + load() +} + +class HoloScanCreator { + generator: Callable + generator_args: Dict + + __init__() + load() + set_property() +} + +ConfigurableObject <|-- ScanSpec +ConfigurableObject <|-- ScanKey +ScanSpec <|-- ScanCreator +ScanSpec <|-- ScanLoader +ScanSpec <|-- HoloScanCreator +ScanKey --> ScanCreator: creates +ScanKey --> HoloScanCreator: creates +ScanKey --> ScanLoader: creates +ScanLoader --> Scan: creates +ScanCreator --> Scan: creates +HoloScanCreator --> Scan: creates + +@enduml \ No newline at end of file diff --git a/extras/docker-docs/Dockerfile b/extras/docker-docs/Dockerfile new file mode 100644 index 0000000..b4df9b3 --- /dev/null +++ b/extras/docker-docs/Dockerfile @@ -0,0 +1,30 @@ +FROM python:3.12 + +# docker container to build PMSCO documentation + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + default-jre \ + doxygen \ + gawk \ + git \ + graphviz \ + pandoc \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache-dir \ + doxypypy \ + meson \ + meson-python \ + ninja \ + pynose + +RUN wget -O plantuml.jar https://sourceforge.net/projects/plantuml/files/plantuml.jar/download +ENV PLANTUML_JAR_PATH=/app/plantuml.jar + +COPY . . + +CMD ["sh"] + diff --git a/extras/singularity/singularity_python3 b/extras/singularity/singularity_python3 index f364479..77710ad 100644 --- a/extras/singularity/singularity_python3 +++ b/extras/singularity/singularity_python3 @@ -1,5 +1,5 @@ BootStrap: debootstrap -OSVersion: bionic +OSVersion: focal MirrorURL: http://ch.archive.ubuntu.com/ubuntu/ %help @@ -32,7 +32,7 @@ path/to/pmsco must point to the directory that contains the __main__.py file. %labels Maintainer Matthias Muntwiler Maintainer_Email matthias.muntwiler@psi.ch - Python_Version 3 + Python_Version 3.8 %environment export LC_ALL=C @@ -43,7 +43,7 @@ path/to/pmsco must point to the directory that contains the __main__.py file. %post export LC_ALL=C - export PYTHON_VERSION=3 + export PYTHON_VERSION=3.8 export CONDA_ROOT=/opt/miniconda export PLANTUML_ROOT=/opt/plantuml @@ -63,33 +63,44 @@ path/to/pmsco must point to the directory that contains the __main__.py file. libblas-dev \ liblapack-dev \ libopenmpi-dev \ - make \ + nano \ openmpi-bin \ openmpi-common \ sqlite3 \ wget apt-get clean - wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh - bash ~/miniconda.sh -b -p ${CONDA_ROOT} + wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh -O ~/miniforge3.sh + bash ~/miniforge3.sh -b -p ${CONDA_ROOT} - . ${CONDA_ROOT}/bin/activate + . ${CONDA_ROOT}/etc/profile.d/conda.sh + conda activate base conda create -q --yes -n pmsco python=${PYTHON_VERSION} - conda activate pmsco - conda install -q --yes -n pmsco \ - pip \ - "numpy>=1.13" \ - scipy \ - ipython \ - matplotlib \ - nose \ - mock \ + conda install -q --yes -n pmsco -c conda-forge \ + commentjson \ + fasteners \ future \ - statsmodels \ - swig \ gitpython + ipython \ + ipykernel \ + jsonschema \ + h5py \ + matplotlib \ + meson \ + mock \ + pynose \ + "numpy>=1.13" \ + pandas \ + periodictable \ + pip \ + scikit-learn \ + scipy \ + seaborn \ + sqlalchemy \ + statsmodels \ + swig conda clean --all -y - pip install periodictable attrdict commentjson fasteners mpi4py doxypypy + ${CONDA_ROOT}/envs/pmsco/bin/pip install meson-python mpi4py netgraph networkx doxypypy mkdir ${PLANTUML_ROOT} wget -O ${PLANTUML_ROOT}/plantuml.jar https://sourceforge.net/projects/plantuml/files/plantuml.jar/download @@ -111,11 +122,16 @@ path/to/pmsco must point to the directory that contains the __main__.py file. git checkout master git checkout -b ${SINGULAR_BRANCH} - make all - nosetests -w tests/ + meson setup build + cd build + meson compile + meson install + meson test %apprun compile . ${CONDA_ROOT}/etc/profile.d/conda.sh conda activate pmsco - make all - nosetests + cd build + meson compile + meson install + meson test diff --git a/makefile b/makefile deleted file mode 100644 index a70e257..0000000 --- a/makefile +++ /dev/null @@ -1,55 +0,0 @@ -SHELL=/bin/sh - -# makefile for all programs, modules and documentation -# -# required libraries for LOESS module: libblas, liblapack, libf2c -# (you may have to set soft links so that linker finds them) -# -# on shared computing systems (high-performance clusters) -# you may have to switch the environment before running this script. -# -# note: the public distribution does not include third-party code -# (EDAC in particular) because of incompatible license terms. -# please obtain such code from the original authors -# and copy it to the proper directory before compilation. -# -# the MSC and MUFPOT programs are currently not used. -# they are not built by the top-level targets all and bin. -# -# the make system uses the compiler executables of the current environment. -# to override the executables, you may set the following variables. -# to switch between python versions, however, the developers recommend miniconda. -# -# PYTHON = python executable (default: python) -# PYTHONOPTS = python options (default: none) -# CC = C and Fortran compiler executable (default: gcc) -# CCOPTS = C compiler options (default: none) -# CXX = C++ compiler executable (default: g++) -# CXXOPTS = C++ compiler options (default: none) -# -# make all PYTHON=/usr/bin/python2.7 -# -# or: -# -# export PYTHON=/usr/bin/python2.7 -# make all -# - -.PHONY: all bin docs clean edac loess msc mufpot phagen - -PMSCO_DIR = pmsco -DOCS_DIR = docs - -all: edac loess phagen docs - -bin: edac loess phagen - -edac loess msc mufpot phagen: - $(MAKE) -C $(PMSCO_DIR) - -docs: - $(MAKE) -C $(DOCS_DIR) - -clean: - $(MAKE) -C $(PMSCO_DIR) clean - $(MAKE) -C $(DOCS_DIR) clean diff --git a/pmsco/__init__.py b/pmsco/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pmsco/calculators/__init__.py b/pmsco/calculators/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pmsco/calculators/edac.py b/pmsco/calculators/edac.py index 11cc525..d8e84ff 100644 --- a/pmsco/calculators/edac.py +++ b/pmsco/calculators/edac.py @@ -11,23 +11,23 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import logging import numpy as np import os import pmsco.calculators.calculator as calculator -from pmsco.compat import open import pmsco.data as md import pmsco.cluster as mc -import pmsco.edac.edac as edac from pmsco.helpers import BraceMessage as BMsg logger = logging.getLogger(__name__) +try: + import edac +except (ImportError, ModuleNotFoundError) as e: + edac = None + logger.critical("Error importing the edac package.", exc_info=e) + class EdacCalculator(calculator.Calculator): def write_input_file(self, params, scan, filepath): @@ -59,7 +59,7 @@ class EdacCalculator(calculator.Calculator): """ files = {} - with open(filepath, "w") as f: + with open(filepath, "wt", encoding="latin1") as f: f.write("verbose off\n") f.write("cluster input {0}\n".format(params.cluster_file)) f.write("emitters {0:d} l(A)\n".format(len(params.emitters))) @@ -219,8 +219,10 @@ class EdacCalculator(calculator.Calculator): dat_filename = out_filename if params.fixed_cluster: etpi_filename = base_filename + ".etpai" + dtype = md.DTYPE_ETPAI else: etpi_filename = base_filename + ".etpi" + dtype = md.DTYPE_ETPI # fix EDAC particularities params.cluster_file = clu_filename @@ -246,13 +248,10 @@ class EdacCalculator(calculator.Calculator): result_etpi['e'] -= params.work_function if 't' in scan.mode and 'p' in scan.mode: - hemi_tpi = scan.raw_data.copy() - hemi_tpi['i'] = 0.0 - try: - hemi_tpi['s'] = 0.0 - except ValueError: - pass - result_etpi = md.interpolate_hemi_scan(result_etpi, hemi_tpi) + dest_tpi = np.zeros(scan.raw_data.shape, dtype) + dest_tpi['t'] = scan.thetas + dest_tpi['p'] = scan.phis + result_etpi = md.interpolate_hemi_scan(result_etpi, dest_tpi) if params.fixed_cluster: expected_shape = max(scan.energies.shape[0], 1) * max(scan.alphas.shape[0], 1) diff --git a/pmsco/calculators/msc.py b/pmsco/calculators/msc.py index 048d7ed..4bc19cc 100644 --- a/pmsco/calculators/msc.py +++ b/pmsco/calculators/msc.py @@ -18,7 +18,7 @@ from __future__ import division from __future__ import print_function import pmsco.calculators.calculator as calculator import pmsco.data as md -import pmsco.msc.msc as msc +import subprojects.msc.msc as msc import logging logger = logging.getLogger(__name__) @@ -27,36 +27,36 @@ logger = logging.getLogger(__name__) class MscCalculator(calculator.Calculator): def write_input_file(self, params, filepath): with open(filepath, "w") as f: - f.write(" %s\n" % (params.title) ) - f.write(" %s\n" % (params.comment) ) + f.write(" %s\n" % (params.title)) + f.write(" %s\n" % (params.comment)) l_init = "spdf".index(params.initial_state[1]) - f.write(" %4u\n" % (l_init) ) - f.write(" %4u\n" % (params.spherical_order) ) - f.write(" %s\n" % (params.polarization) ) - f.write(" %4u\n" % (params.scattering_level) ) - f.write(" %7.2f%7.2f\n" % (params.fcut, params.cut) ) - f.write(" %12.6f\n" % (params.angular_resolution) ) - f.write(" %12.6f\n" % (params.lattice_constant) ) - f.write(" %12.6f\n" % (params.z_surface) ) - f.write(" %4u\n" % (params.atom_types) ) + f.write(" %4u\n" % (l_init)) + f.write(" %4u\n" % (params.spherical_order)) + f.write(" %s\n" % (params.polarization)) + f.write(" %4u\n" % (params.scattering_level)) + f.write(" %7.2f%7.2f\n" % (params.fcut, params.cut)) + f.write(" %12.6f\n" % (params.angular_resolution)) + f.write(" %12.6f\n" % (params.lattice_constant)) + f.write(" %12.6f\n" % (params.z_surface)) + f.write(" %4u\n" % (params.atom_types)) for iat in range(params.atom_types): f.write(" %4u %s\n" % (params.atomic_number[iat], "...")) f.write(" %4u %s\n" % (params.atomic_number[iat], params.phase_file[iat])) - f.write(" %12.6f\n" % (params.msq_displacement[iat]) ) - f.write(" %12.6f\n" % (params.planewave_attenuation) ) - f.write(" %12.6f\n" % (params.inner_potential) ) - f.write(" %12.6f\n" % (params.symmetry_range) ) - f.write(" %12.6f\n" % (params.polar_incidence_angle) ) - f.write(" %12.6f\n" % (params.azimuthal_incidence_angle) ) - f.write(" %s\n" % (params.vibration_model) ) - f.write(" %12.6f\n" % (params.substrate_atomic_mass) ) - f.write(" %12.6f\n" % (params.experiment_temperature) ) - f.write(" %12.6f\n" % (params.debye_temperature) ) - f.write(" %12.6f\n" % (params.debye_wavevector) ) - f.write(" %12.6f%7.3f\n" % (params.rme_minus_value, params.rme_minus_shift) ) - f.write(" %12.6f%7.3f\n" % (params.rme_plus_value, params.rme_plus_shift) ) - f.write(" %4u\n" % (1) ) - f.write(" %4u %12.6f\n" % (1, 1.0) ) + f.write(" %12.6f\n" % (params.msq_displacement[iat])) + f.write(" %12.6f\n" % (params.planewave_attenuation)) + f.write(" %12.6f\n" % (params.inner_potential)) + f.write(" %12.6f\n" % (params.symmetry_range)) + f.write(" %12.6f\n" % (params.polar_incidence_angle)) + f.write(" %12.6f\n" % (params.azimuthal_incidence_angle)) + f.write(" %s\n" % (params.vibration_model)) + f.write(" %12.6f\n" % (params.substrate_atomic_mass)) + f.write(" %12.6f\n" % (params.experiment_temperature)) + f.write(" %12.6f\n" % (params.debye_temperature)) + f.write(" %12.6f\n" % (params.debye_wavevector)) + f.write(" %12.6f%7.3f\n" % (params.rme_minus_value, params.rme_minus_shift)) + f.write(" %12.6f%7.3f\n" % (params.rme_plus_value, params.rme_plus_shift)) + f.write(" %4u\n" % (1)) + f.write(" %4u %12.6f\n" % (1, 1.0)) def run(self, params, cluster, scan, output_file): """ diff --git a/pmsco/calculators/phagen/__init__.py b/pmsco/calculators/phagen/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pmsco/calculators/phagen/makefile b/pmsco/calculators/phagen/makefile deleted file mode 100644 index 3d26d5e..0000000 --- a/pmsco/calculators/phagen/makefile +++ /dev/null @@ -1,44 +0,0 @@ -SHELL=/bin/sh - -# makefile for PHAGEN program and module -# -# the PHAGEN source code is not included in the public distribution. -# please obtain the PHAGEN code from the original author, -# and copy it to this directory before compilation. -# -# see the top-level makefile for additional information. - -.SUFFIXES: -.SUFFIXES: .c .cpp .cxx .exe .f .h .i .o .py .pyf .so -.PHONY: all clean phagen - -FC?=gfortran -FCOPTS?=-std=legacy -F2PY?=f2py -F2PYOPTS?=--f77flags=-std=legacy --f90flags=-std=legacy -CC?=gcc -CCOPTS?= -SWIG?=swig -SWIGOPTS?= -PYTHON?=python -PYTHONOPTS?= -PYTHONINC?= -PYTHON_CONFIG = ${PYTHON}-config -PYTHON_CFLAGS ?= $(shell ${PYTHON_CONFIG} --cflags) -PYTHON_EXT_SUFFIX ?= $(shell ${PYTHON_CONFIG} --extension-suffix) - -all: phagen - -phagen: phagen.exe phagen$(PYTHON_EXT_SUFFIX) - -phagen.exe: phagen_scf.f msxas3.inc msxasc3.inc - $(FC) $(FCOPTS) -o phagen.exe phagen_scf.f - -phagen.pyf: | phagen_scf.f - $(F2PY) -h phagen.pyf -m phagen phagen_scf.f only: libmain - -phagen$(PYTHON_EXT_SUFFIX): phagen_scf.f phagen.pyf msxas3.inc msxasc3.inc - $(F2PY) -c $(F2PYOPTS) -m phagen phagen.pyf phagen_scf.f - -clean: - rm -f *.so *.o *.exe diff --git a/pmsco/calculators/phagen/phagen_scf.f.patch b/pmsco/calculators/phagen/phagen_scf.f.patch deleted file mode 100644 index 92fb3f7..0000000 --- a/pmsco/calculators/phagen/phagen_scf.f.patch +++ /dev/null @@ -1,102 +0,0 @@ ---- phagen_scf.orig.f 2019-06-05 16:45:52.977855859 +0200 -+++ phagen_scf.f 2019-05-09 16:32:35.790286429 +0200 -@@ -174,6 +174,99 @@ - 1100 format(//,1x,' ** phagen terminated normally ** ',//) - end - -+ -+c----------------------------------------------------------------------- -+ subroutine libmain(infile,outfile,etcfile) -+c main calculation routine -+c entry point for external callers -+c -+c infile: name of parameter input file -+c -+c outfile: base name of output files -+c output files with endings .list, .clu, .pha, .tl, .rad -+c will be created -+c----------------------------------------------------------------------- -+ implicit real*8 (a-h,o-z) -+c -+ include 'msxas3.inc' -+ include 'msxasc3.inc' -+ -+ character*60 infile,outfile,etcfile -+ character*70 listfile,clufile,tlfile,radfile,phafile -+ -+c -+c.. constants -+ antoau = 0.52917715d0 -+ pi = 3.141592653589793d0 -+ ev = 13.6058d0 -+ zero = 0.d0 -+c.. threshold for linearity -+ thresh = 1.d-4 -+c.. fortran io units -+ idat = 5 -+ iwr = 6 -+ iphas = 30 -+ iedl0 = 31 -+ iwf = 32 -+ iof = 17 -+ -+ iii=LnBlnk(outfile)+1 -+ listfile=outfile -+ listfile(iii:)='.list' -+ clufile=outfile -+ clufile(iii:)='.clu' -+ phafile=outfile -+ phafile(iii:)='.pha' -+ tlfile=outfile -+ tlfile(iii:)='.tl' -+ radfile=outfile -+ radfile(iii:)='.rad' -+ -+ open(idat,file=infile,form='formatted',status='old') -+ open(iwr,file=listfile,form='formatted',status='unknown') -+ open(10,file=clufile,form='formatted',status='unknown') -+ open(35,file=tlfile,form='formatted',status='unknown') -+ open(55,file=radfile,form='formatted',status='unknown') -+ open(iphas,file=phafile,form='formatted',status='unknown') -+ -+ open(iedl0,form='unformatted',status='scratch') -+ open(iof,form='unformatted',status='scratch') -+ open(unit=21,form='unformatted',status='scratch') -+ open(60,form='formatted',status='scratch') -+ open(50,form='formatted',status='scratch') -+ open(unit=13,form='formatted',status='scratch') -+ open(unit=14,form='formatted',status='scratch') -+ open(unit=11,status='scratch') -+ open(unit=iwf,status='scratch') -+ open(unit=33,status='scratch') -+ open(unit=66,status='scratch') -+ -+ call inctrl -+ call intit(iof) -+ call incoor -+ call calphas -+ -+ close(idat) -+ close(iwr) -+ close(10) -+ close(35) -+ close(55) -+ close(iphas) -+ close(iedl0) -+ close(iof) -+ close(60) -+ close(50) -+ close(13) -+ close(14) -+ close(11) -+ close(iwf) -+ close(33) -+ close(66) -+ close(21) -+ -+ endsubroutine -+ -+ - subroutine inctrl - implicit real*8 (a-h,o-z) - include 'msxas3.inc' diff --git a/pmsco/calculators/phagen/runner.py b/pmsco/calculators/phagen/runner.py index 1111d67..69593cf 100644 --- a/pmsco/calculators/phagen/runner.py +++ b/pmsco/calculators/phagen/runner.py @@ -2,33 +2,41 @@ @package pmsco.calculators.phagen.runner Natoli/Sebilleau PHAGEN interface -this module runs the PHAGEN program to calculate scattering factors and radial matrix element. +This module runs the PHAGEN program to calculate scattering factors and radial matrix elements. + +Requires PHAGEN version 2.2 from https://git.ipr.univ-rennes.fr/epsi/msspec_python3.git (contained in subprojects). @author Matthias Muntwiler -@copyright (c) 2015-19 by Paul Scherrer Institut @n +@copyright (c) 2015-23 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import logging import os import shutil import tempfile +from pathlib import Path +import sys from pmsco.calculators.calculator import AtomicCalculator -from pmsco.calculators.phagen.phagen import libmain + from pmsco.calculators.phagen.translator import Translator import pmsco.cluster +from pmsco.helpers import stdout_redirected +import pmsco.project logger = logging.getLogger(__name__) +try: + import phagen +except (ImportError, ModuleNotFoundError) as e: + phagen = None + logger.critical("Error importing the phagen package.", exc_info=e) + class PhagenCalculator(AtomicCalculator): """ @@ -37,7 +45,11 @@ class PhagenCalculator(AtomicCalculator): this produces scatterer, radial matrix element and cluster files for EDAC. """ - def run(self, params, cluster, scan, output_file): + def run(self, + params: pmsco.project.CalculatorParams, + cluster: pmsco.cluster.Cluster, + scan: pmsco.project.Scan, + output_file: str): """ create the input file, run PHAGEN, and translate the output to EDAC format. @@ -85,19 +97,16 @@ class PhagenCalculator(AtomicCalculator): phagen_cluster = pmsco.cluster.Cluster() files = {} - prev_wd = os.getcwd() + prev_wd = Path.cwd() try: with tempfile.TemporaryDirectory() as temp_dir: - os.chdir(temp_dir) - os.mkdir("div") - os.mkdir("div/wf") - os.mkdir("plot") - os.mkdir("data") - - # prepare input for phagen - infile = "phagen.in" - outfile = "phagen.out" + temp_path = Path(temp_dir) + in_path = temp_path / "input" + in_path.mkdir(exist_ok=True) + out_path = temp_path / "output" + out_path.mkdir(exist_ok=True) + infile = in_path / "input.ms" try: transl.write_input(infile) report_infile = os.path.join(prev_wd, output_file + ".phagen.in") @@ -106,12 +115,22 @@ class PhagenCalculator(AtomicCalculator): except IOError: logger.warning("error writing phagen input file {fi}.".format(fi=infile)) - # call phagen - libmain(infile, outfile) + report_listfile = os.path.join(prev_wd, output_file + ".phagen.list") + files[report_listfile] = "log" + + # call phagen, redirect stdout (unit 6) + os.chdir(out_path) + with open(report_listfile, "wb") as f: + with stdout_redirected(f): + phagen.phagen() + + phafile = out_path / "div" / "phases.dat" + radfile = out_path / "fort.55" + # tlfile = out_path / "fort.35" + clufile = out_path / "clus" / "clus.out" # collect results try: - phafile = outfile + ".pha" transl.parse_phagen_phase(phafile) report_phafile = os.path.join(prev_wd, output_file + ".phagen.pha") shutil.copy(phafile, report_phafile) @@ -120,7 +139,6 @@ class PhagenCalculator(AtomicCalculator): logger.error("error loading phagen phase file {fi}".format(fi=phafile)) try: - radfile = outfile + ".rad" transl.parse_radial_file(radfile) report_radfile = os.path.join(prev_wd, output_file + ".phagen.rad") shutil.copy(radfile, report_radfile) @@ -129,31 +147,23 @@ class PhagenCalculator(AtomicCalculator): logger.error("error loading phagen radial file {fi}".format(fi=radfile)) try: - clufile = outfile + ".clu" phagen_cluster.load_from_file(clufile, pmsco.cluster.FMT_PHAGEN_OUT) except IOError: logger.error("error loading phagen cluster file {fi}".format(fi=clufile)) - try: - listfile = outfile + ".list" - report_listfile = os.path.join(prev_wd, output_file + ".phagen.list") - shutil.copy(listfile, report_listfile) - files[report_listfile] = "log" - except IOError: - logger.error("error loading phagen list file {fi}".format(fi=listfile)) - finally: os.chdir(prev_wd) # write edac files scatfile = output_file + "_{}.scat" scatfiles = transl.write_edac_scattering(scatfile) - params.phase_files = {c: scatfiles[c] for c in scatfiles} - files.update({scatfiles[c]: "atomic" for c in scatfiles}) + params.phase_files = scatfiles.copy() + files.update({f: "atomic" for f in params.phase_files.values()}) - rmefile = output_file + ".rme" - transl.write_edac_emission(rmefile) - files[rmefile] = "atomic" + rmefile = output_file + "_{}.rme" + rmefiles = transl.write_edac_emission(rmefile) + params.rme_files = rmefiles.copy() + files.update({f: "atomic" for f in params.rme_files.values()}) cluster.update_atoms(phagen_cluster, {'c'}) clufile = output_file + ".pmsco.clu" diff --git a/pmsco/calculators/phagen/translator.py b/pmsco/calculators/phagen/translator.py index dfee383..a4bbe8f 100644 --- a/pmsco/calculators/phagen/translator.py +++ b/pmsco/calculators/phagen/translator.py @@ -13,14 +13,12 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import logging import numpy as np from pmsco.cluster import Cluster -from pmsco.compat import open + +logger = logging.getLogger(__name__) ## rydberg energy in electron volts ERYDBERG = 13.6056923 @@ -59,7 +57,7 @@ class TranslationParams(object): self.initial_state = "1s" self.binding_energy = 0. self.cluster = None - self.kinetic_energies = np.empty(0, dtype=np.float) + self.kinetic_energies = np.empty(0, dtype=float) @property def l_init(self): @@ -287,7 +285,7 @@ class Translator(object): self.write_cluster(f) self.write_ionicity(f) else: - with open(f, "w") as fi: + with open(f, "wt", encoding="latin1") as fi: self.write_input(fi) def parse_phagen_phase(self, f): @@ -392,7 +390,7 @@ class Translator(object): f.write(" 0 0") f.write("\n") else: - with open(f, "w") as fi: + with open(f, "wt", encoding="latin1") as fi: self.write_edac_scattering_file(fi, scat) def write_edac_phase_file(self, f, scat): @@ -426,26 +424,36 @@ class Translator(object): f.write(" 0") f.write("\n") else: - with open(f, "w") as fi: + with open(f, "wt", encoding="latin1") as fi: self.write_edac_phase_file(fi, scat) def parse_radial_file(self, f): """ - parse the radial matrix element output file from phagen. + parse the radial matrix element output file from phagen version 2.2. + + the file contains 7 header lines and one data line per requested energy. + the data line contains real and imaginary parts of the matrix elements. + the first four columns contain the electric dipole transitions Rd(li --> li - 1) and Rd(li --> li + 1), + followed by higher orders that we do not use here. @param f: file or path (any file-like or path-like object that can be passed to numpy.genfromtxt). @return: None + + @raise ValueError if the file is in a wrong format. """ - dt = [('ar', 'f8'), ('ai', 'f8'), ('br', 'f8'), ('bi', 'f8')] - data = np.atleast_1d(np.genfromtxt(f, dtype=dt)) + data = np.atleast_2d(np.genfromtxt(f, skip_header=7)) + if data.shape[0] != self.params.kinetic_energies.shape[0] or data.shape[1] < 4: + raise ValueError(f"Unexpected array size of Phagen radial matrix elements output: " + f"expected ({self.params.kinetic_energies.shape[0]}, >= 4), received {data.shape}") - self.emission = np.resize(self.emission, data.shape) + self.emission = np.resize(self.emission, data.shape[0:1]) emission = self.emission - emission['dw'] = data['ar'] + 1j * data['ai'] - emission['up'] = data['br'] + 1j * data['bi'] + emission['e'] = self.params.kinetic_energies + emission['dw'] = data[:, 0] + 1j * data[:, 1] + emission['up'] = data[:, 2] + 1j * data[:, 3] - def write_edac_emission(self, f): + def write_edac_emission_file(self, f): """ write the radial photoemission matrix element in EDAC format. @@ -472,5 +480,24 @@ class Translator(object): f.write(" {0:.6f} {1:.6f}".format(item['dw'].real, item['dw'].imag)) f.write("\n") else: - with open(f, "w") as of: - self.write_edac_emission(of) + with open(f, "wt", encoding="latin1") as of: + self.write_edac_emission_file(of) + + def write_edac_emission(self, filename_format): + """ + write the radial photoemission matrix element in EDAC format. + + requires self.scattering, self.emission, self.params.kinetic_energies and self.params.initial_state. + + @param filename_format: file name including, optionally, a placeholder {} for the atom class. + since phagen calculates only one emitter, the placeholder is not necessary. + + @return: dictionary that maps atom classes to file names. + since phagen calculates only one emitter, this dictionary will contain just one entry. + """ + scat = self.scattering + atom = scat['a'][0] + f = filename_format.format(atom) + self.write_edac_emission_file(f) + files = {atom: f} + return files diff --git a/pmsco/cluster.py b/pmsco/cluster.py index c63c43a..d7b0f79 100755 --- a/pmsco/cluster.py +++ b/pmsco/cluster.py @@ -71,14 +71,14 @@ FMT_CLUSTER_MSC = ["%5u", "%7.3f", "%7.3f", "%7.3f", "%2u"] FIELDS_CLUSTER_MSC = ['i', 'x', 'y', 'z', 't'] ## numpy.array datatype of cluster for EDAC cluster file input/output -DTYPE_CLUSTER_EDAC= [('i', 'i4'), ('c', 'i4'), ('x', 'f4'), ('y', 'f4'), ('z', 'f4')] +DTYPE_CLUSTER_EDAC = [('i', 'i4'), ('c', 'i4'), ('x', 'f4'), ('y', 'f4'), ('z', 'f4')] ## file format of EDAC cluster file FMT_CLUSTER_EDAC = ["%5u", "%2u", "%7.3f", "%7.3f", "%7.3f"] ## field (column) names of EDAC cluster file FIELDS_CLUSTER_EDAC = ['i', 'c', 'x', 'y', 'z'] ## numpy.array datatype of cluster for XYZ file input/output -DTYPE_CLUSTER_XYZ= [('s', _SYMBOL_TYPE), ('x', 'f4'), ('y', 'f4'), ('z', 'f4')] +DTYPE_CLUSTER_XYZ = [('s', _SYMBOL_TYPE), ('x', 'f4'), ('y', 'f4'), ('z', 'f4')] ## file format of XYZ cluster file FMT_CLUSTER_XYZ = ["%s", "%10.5f", "%10.5f", "%10.5f"] ## field (column) names of XYZ cluster file @@ -306,7 +306,7 @@ class Cluster(object): """ add bulk atoms to the cluster. - the lattice is expanded up to the limits given by + the lattice is expanded up to the limits given by self.rmax (maximum distance from the origin) and z_surf (position of the surface). all atoms are non-emitters. @@ -359,7 +359,7 @@ class Cluster(object): @param tol: tolerance for checking uniqueness. positions of two atoms are considered equal if all coordinates lie within the tolerance interval. - @return: None + @return: None """ assert isinstance(cluster, Cluster) data = self.data.copy() @@ -498,7 +498,7 @@ class Cluster(object): @param matrix: transformation matrix - @return: None + @return: None """ pos = np.empty((3, self.data.shape[0]), np.float32) pos[0, :] = self.data['x'] @@ -1030,7 +1030,7 @@ class Cluster(object): update the index column. if you have modified the order or number of elements in the self.data array directly, - you may need to re-index the atoms if your code uses functions that rely on the index. + you may need to re-index the atoms if your code uses functions that rely on the index. @return None """ diff --git a/pmsco/compat.py b/pmsco/compat.py deleted file mode 100644 index 0d94bd0..0000000 --- a/pmsco/compat.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -@package pmsco.compat -compatibility code - -code bits to provide compatibility for different python versions. -currently supported 2.7 and 3.6. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from io import open as io_open - - -def open(fname, mode='r', encoding='latin1'): - """ - open a data file for read/write/append using the default str type - - this is a drop-in for io.open - where data is exchanged via the built-in str type of python, - whether this is a byte string (python 2) or unicode string (python 3). - - the file is assumed to be a latin-1 encoded binary file. - - @param fname: file name and path - @param mode: 'r', 'w' or 'a' - @param encoding: 'latin1' (default), 'ascii' or 'utf-8' - @return file handle - """ - if isinstance(b'b', str): - # python 2 - mode += 'b' - kwargs = {} - else: - # python 3 - mode += 't' - kwargs = {'encoding': encoding} - - return io_open(fname, mode, **kwargs) diff --git a/pmsco/config.py b/pmsco/config.py index 8bef7d2..efc1eb1 100644 --- a/pmsco/config.py +++ b/pmsco/config.py @@ -4,7 +4,7 @@ infrastructure for configurable objects @author Matthias Muntwiler -@copyright (c) 2021 by Paul Scherrer Institut @n +@copyright (c) 2021-23 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -12,78 +12,114 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n """ import collections.abc -import functools import inspect import logging +import os from pathlib import Path +from string import Template +from typing import Any, Callable, Dict, Generator, Iterable, List, Mapping, Optional, Sequence, Set, Tuple, Union logger = logging.getLogger(__name__) +PathLike = Union[str, os.PathLike] +DataDict = Mapping[str, Union[str, int, float, Iterable, Mapping]] -def resolve_path(path, dirs): + +def resolve_path(path: PathLike, dirs: Mapping[str, Any]): """ - resolve a file path by replacing placeholders + Resolve a file path by replacing placeholders. - placeholders are enclosed in curly braces. - values for all possible placeholders are provided in a dictionary. + Placeholders are enclosed in curly braces. + Values for all possible placeholders are provided in a dictionary. @param path: str, Path or other path-like. - example: '{work}/test/testfile.dat'. - @param dirs: dictionary mapping placeholders to project paths. - the paths can be str, Path or other path-like - example: {'work': '/home/user/work'} + Example: '${work}/test/testfile.dat'. + @param dirs: Dictionary mapping placeholders to project paths. + The paths can be str, Path or other path-like + Example: {'work': '/home/user/work'} @return: pathlib.Path object """ - return Path(*(p.format(**dirs) for p in Path(path).parts)) + + return Path(*(Template(p).substitute(dirs) for p in Path(path).parts)) class ConfigurableObject(object): """ - Parent class for objects that can be configured by a run file + Parent class for objects that can be configured from a runfile - the run file is a JSON file that contains object data in a nested dictionary structure. + The runfile is a JSON file that contains object data in a nested dictionary structure. - in the dictionary structure the keys are property or attribute names of the object to be initialized. - keys starting with a non-alphabetic character (except for some special keys like __class__) are ignored. - these can be used as comments, or they protect private attributes. + In the dictionary structure the keys are property or attribute names of the object to be initialized. + Keys starting with a non-alphabetic character (except for some special keys like __class__) are ignored. + These can be used as comments, or they protect private attributes. - the values can be numeric values, strings, lists or dictionaries. + The values can be numeric values, strings, lists or dictionaries. - simple values are simply assigned using setattr. - this may call a property setter if defined. + Simple values are simply assigned using setattr. + This may call a property setter if defined. - lists are iterated. each item is appended to the attribute. - the attribute must implement an append method in this case. + Lists are iterated. Each item is appended to the attribute. + The attribute must implement an append method in this case. - if an item is a dictionary and contains the special key '__class__', + If an item is a dictionary and contains the special key '__class__', an object of that class is instantiated and recursively initialized with the dictionary elements. - this requires that the class can be found in the module scope passed to the parser methods, + This requires that the class can be found in the module scope passed to the parser methods, and that the class inherits from this class. - cases that can't be covered easily using this mechanism + Cases that can't be covered easily using this mechanism should be implemented in a property setter. - value-checking should also be done in a property setter (or the append method in sequence-like objects). + Value-checking should also be done in a property setter (or the append method in sequence-like objects). + + Attributes + ---------- + + project_symbols: Dictionary of symbols that should be used to resolve class and function names. + This is usually the globals() dictionary of the project module. """ + def __init__(self): - pass + super().__init__() + self.project_symbols: Optional[Mapping[str, Any]] = None + + def set_properties(self, symbols: Optional[Mapping[str, Any]], + data_dict: DataDict, + project: 'ConfigurableObject') -> None: - def set_properties(self, module, data_dict, project): """ - set properties of this class. + Set properties from dictionary. - @param module: module reference that should be used to resolve class names. - this is usually the project module. - @param data_dict: dictionary of properties to set. - see the class description for details. - @param project: reference to the project object. + @param symbols: Dictionary of symbols that should be used to resolve class names. + This is usually the globals() dictionary of the project module. + Classes are resolved using the eval function. + @param data_dict: Dictionary of properties to set. + See the class description for details. + @param project: Reference to the project object. @return: None """ + + self.project_symbols = symbols for key in data_dict: if key[0].isalpha(): - self.set_property(module, key, data_dict[key], project) + self.set_property(symbols, key, data_dict[key], project) - def set_property(self, module, key, value, project): - obj = self.parse_object(module, value, project) + def set_property(self, symbols: Optional[Mapping[str, Any]], + key: str, + value: DataDict, + project: 'ConfigurableObject') -> None: + + """ + Set one property. + + @param symbols: Dictionary of symbols that should be used to resolve class names. + This is usually the globals() dictionary of the project module. + Classes are resolved using the eval function. + @param key: Attribute name to set. + @param value: New value of the attribute. + @param project: Reference to the project object. + @return: None + """ + + obj = self.parse_object(symbols, value, project) if hasattr(self, key): if obj is not None: if isinstance(obj, collections.abc.MutableSequence): @@ -103,18 +139,25 @@ class ConfigurableObject(object): else: logger.warning(f"class {self.__class__.__name__} does not have attribute {key}.") - def parse_object(self, module, value, project): + def parse_object(self, symbols: Optional[Mapping[str, Any]], + value: DataDict, + project: 'ConfigurableObject') -> object: + if isinstance(value, collections.abc.MutableMapping) and "__class__" in value: - cn = value["__class__"].split('.') - c = functools.reduce(getattr, cn, module) + cn = value["__class__"] + try: + c = eval(cn, symbols) + except (AttributeError, KeyError, NameError, ValueError): + logger.critical(f"can't resolve class name {cn}") + raise s = inspect.signature(c) if 'project' in s.parameters: o = c(project=project) else: o = c() - o.set_properties(module, value, project) + o.set_properties(symbols, value, project) elif isinstance(value, collections.abc.MutableSequence): - o = [self.parse_object(module, i, project) for i in value] + o = [self.parse_object(symbols, i, project) for i in value] else: o = value return o diff --git a/pmsco/data.py b/pmsco/data.py index f3e3efa..a0f3385 100644 --- a/pmsco/data.py +++ b/pmsco/data.py @@ -1,32 +1,36 @@ """ @package pmsco.data -import, export, evaluation of msc data. +Import, export, evaluation of msc data. -this module provides common functions for loading/saving and manipulating PED scan data sets. +This module provides common functions for loading/saving and manipulating PED scan data sets. @author Matthias Muntwiler -@copyright (c) 2015-17 by Paul Scherrer Institut @n +@copyright (c) 2015-23 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import logging +import math import numpy as np +import numpy.typing as npt import os +import scipy.special import scipy.optimize as so - -from pmsco.compat import open -import pmsco.loess.loess as loess +from typing import Any, Callable, Dict, Generator, Iterable, List, Mapping, Optional, Sequence, Set, Tuple, Union +import h5py logger = logging.getLogger(__name__) +try: + import loess +except (ModuleNotFoundError, ImportError) as e: + loess = None + logger.critical("Error importing the loess package.", exc_info=e) + ## energy, intensity DTYPE_EI = [('e', 'f4'), ('i', 'f4')] ## energy, theta, phi, intensity @@ -43,9 +47,11 @@ DTYPE_TP = [('t', 'f4'), ('p', 'f4')] DTYPE_TPI = [('t', 'f4'), ('p', 'f4'), ('i', 'f4')] ## theta, phi, intensity, sigma (standard deviation) DTYPE_TPIS = [('t', 'f4'), ('p', 'f4'), ('i', 'f4'), ('s', 'f4')] +## intensity, theta, phi +DTYPE_ITP = [('i', 'f4'), ('t', 'f4'), ('p', 'f4')] DTYPES = {'EI': DTYPE_EI, 'ETPI': DTYPE_ETPI, 'ETPIS': DTYPE_ETPIS, 'ETPAI': DTYPE_ETPAI, 'ETPAIS': DTYPE_ETPAIS, - 'TP': DTYPE_TP, 'TPI': DTYPE_TPI, 'TPIS': DTYPE_TPIS, } + 'TP': DTYPE_TP, 'TPI': DTYPE_TPI, 'TPIS': DTYPE_TPIS, 'ITP': DTYPE_ITP, } DATATYPES = DTYPES.keys ## supportd scan types @@ -55,8 +61,11 @@ DATATYPES = DTYPES.keys # @arg @c 'TP' theta - phi (holo scan) SCANTYPES = ['E', 'EA', 'ET', 'TP'] +GenTextFileLike = Union[str, os.PathLike, Iterable[str], int] +OSFileLike = Union[str, os.PathLike, int] -def create_etpi(shape, sigma_column=True): + +def create_etpi(shape: Tuple[int], sigma_column: bool = True) -> np.ndarray: """ create an ETPI array of a given size. @@ -64,6 +73,7 @@ def create_etpi(shape, sigma_column=True): the array is initialized with zeroes. @param shape (tuple) shape of the array + @param sigma_column: whether the array should include a sigma field (ETPIS type instead of ETPI) """ if sigma_column: data = np.zeros(shape, dtype=DTYPE_ETPIS) @@ -72,7 +82,7 @@ def create_etpi(shape, sigma_column=True): return data -def create_data(shape, datatype='', dtype=None): +def create_data(shape: Tuple[int], datatype: str = '', dtype: Optional[npt.DTypeLike] = None) -> np.ndarray: """ create a data array of a given size and type. @@ -90,7 +100,108 @@ def create_data(shape, datatype='', dtype=None): return data -def load_plt(filename, int_column=-1): +def holo_grid(theta_start: float = 90., theta_step: float = 1., theta_range: float = 90., + phi_start: float = 0., phi_range: float = 360., phi_refinement: float = 1.): + """ + Generator of a holo grid with constant point density in solid angle. + + The generator yields the polar coordinates of a hologram scan in the traditional Osterwalder fashion, + where the grid points are distributed evenly on the hemisphere by varying the azimuthal step size, + while the polar step size is constant. + + The yield are tuples (theta, phi) in degrees. + Theta is the polar, phi the azimuthal coordinate. + + @param theta_start Maximum polar angle in degrees, 0..90. Defaults to 90 (grazing emission). + @param theta_step Polar angle step in degrees, 1..90. Defaults to 1. + @param theta_range Polar angle range in degrees, 1..th_start. Defaults to 90. + @param phi_start Azimuthal start angle in degrees. Defaults to 0. + This azimuth is included at every polar step. + @param phi_range Azimuthal range in degrees. Defaults to 360. + @param phi_refinement Azimuthal refinement/oversampling (scalar). Defaults to 1. + A refinement of 2 yields a factor 2 more grid points in the azimuthal sub-scans. + + @return yield tuples (theta, phi) in degrees + """ + + deg2rad = 0.01745329 + + def calc_phi_step(th): + if th < 0.5 or int(phi_range * math.sin(th * deg2rad) * phi_refinement / theta_step) == 0: + phi_st = 0.0 + else: + phi_st = phi_range / int(th / theta_start * phi_range / theta_step) + if abs(phi_st) < 0.001: + phi_st = 360. + return phi_st + + for theta in np.arange(theta_range, -theta_step, -theta_step): + phi_step = calc_phi_step(theta) + for phi in np.arange(phi_start, phi_range, phi_step): + yield theta, phi + + +def holo_array(generator: Callable[..., Iterable[Tuple[float, float]]], + generator_args: Dict, + datatype: str = 'TP', + dtype: Optional[npt.DTypeLike] = None) -> np.ndarray: + + """ + Create an hologram scan grid in a numpy array. + + A holo data array is a numpy structured array containing at least + a column for theta (polar angle) and phi (azimuthal angle). + The theta and phi columns are filled with angles from the holo_grid (or custom generator) function. + The array can contain further columns for energy, intensity, etc. according to the data type specified. + These columns are initialized with zeroes. + + @param generator Generator that yields tuples (theta, phi) for each grid point, + given the keyword arguments kwargs. + Defaults to holo_grid, the traditional Osterwalder holo scan. + @param generator_args Keyword arguments to be passed to the generator. + For arguments of the traditional holo scan, see the documentation of holo_grid. + @param datatype See DATATYPES. Must contain 'T' and 'P' dimensions. Defaults to 'TP'. + @param dtype See DTYPES. Must contain a 't' and 'p' column. Takes precedence over datatype. + Defaults to None (not specified). + """ + + if not dtype: + dtype = DTYPES[datatype] + + tp = np.fromiter(generator(**generator_args), dtype=DTYPES['TP']) + + result = np.zeros(tp.shape, dtype=dtype) + result['t'] = tp['t'] + result['p'] = tp['p'] + + return result + + +def analyse_holoscan_steps(holoscan: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Find the polar and azimuthal steps in a holoscan. + + @param holoscan: + @return: thetas: unique theta angles. sorted. + dtheta: theta steps for each theta + dphi: phi step for each theta + """ + + thetas, indices, counts = np.unique(holoscan['t'], return_index=True, return_counts=True) + dtheta = np.diff(thetas) + dtheta = np.append(dtheta, dtheta[-1]) + + adjusted_phis = np.append(holoscan['p'], holoscan['p'][-1]) + phis0 = adjusted_phis[indices] + phis1 = adjusted_phis[indices+1] + dphi = phis1 - phis0 + phi_range = counts[-1] * dphi[-1] + dphi[counts <= 1] = phi_range + + return thetas, dtheta, dphi + + +def load_plt(filename: GenTextFileLike, int_column: int = -1) -> np.ndarray: """ loads ETPI data from an MSC output (plt) file @@ -122,7 +233,8 @@ def load_plt(filename, int_column=-1): return data -def load_edac_pd(filename, int_column=-1, energy=0.0, theta=0.0, phi=0.0, fixed_cluster=False): +def load_edac_pd(filename: OSFileLike, int_column: int = -1, + energy: float = 0.0, theta: float = 0.0, phi: float = 0.0, fixed_cluster: bool = False) -> np.ndarray: """ load ETPI or ETPAI data from an EDAC PD output file. @@ -157,7 +269,8 @@ def load_edac_pd(filename, int_column=-1, energy=0.0, theta=0.0, phi=0.0, fixed_ data[i]['i'] = selected intensity column @endverbatim """ - with open(filename, "r") as f: + + with open(filename, "rt", encoding="latin1") as f: header1 = f.readline().strip() header2 = f.readline().strip() if not header1 == '--- scan PD': @@ -218,7 +331,7 @@ def load_edac_pd(filename, int_column=-1, energy=0.0, theta=0.0, phi=0.0, fixed_ return etpi -def load_etpi(filename): +def load_etpi(filename: GenTextFileLike) -> np.ndarray: """ loads ETPI or ETPIS data from a text file @@ -253,7 +366,7 @@ def load_etpi(filename): return data -def load_data(filename, dtype=None): +def load_data(filename: GenTextFileLike, dtype: Optional[npt.DTypeLike] = None): """ load column data (ETPI, and the like) from a text file. @@ -288,7 +401,7 @@ def load_data(filename, dtype=None): return data -def format_extension(data): +def format_extension(data: np.ndarray) -> str: """ format the file extension based on the contents of an array. @@ -299,7 +412,7 @@ def format_extension(data): return "." + "".join(data.dtype.names) -def save_data(filename, data): +def save_data(filename: OSFileLike, data: npt.ArrayLike) -> None: """ save column data (ETPI, and the like) to a text file. @@ -315,7 +428,7 @@ def save_data(filename, data): np.savetxt(filename, data, fmt='%g') -def sort_data(data): +def sort_data(data: np.ndarray) -> None: """ sort scan data (ETPI and the like) in a consistent order. @@ -338,7 +451,8 @@ def sort_data(data): data.sort(kind='mergesort', order=sort_key) -def restructure_data(data, dtype=DTYPE_ETPAIS, defaults=None): +def restructure_data(data: np.ndarray, dtype: Optional[npt.DTypeLike] = None, + defaults: Optional[Mapping] = None) -> np.ndarray: """ restructure the type of a data array by adding or removing columns. @@ -361,6 +475,8 @@ def restructure_data(data, dtype=DTYPE_ETPAIS, defaults=None): @return: re-structured numpy array or @c data if the new and original data types are the same. """ + if dtype is None: + dtype = DTYPE_ETPAIS if data.dtype == dtype: return data else: @@ -378,7 +494,7 @@ def restructure_data(data, dtype=DTYPE_ETPAIS, defaults=None): return new_data -def common_dtype(scans): +def common_dtype(scans: Iterable[Union[npt.ArrayLike, npt.DTypeLike]]) -> npt.DTypeLike: """ determine the common data type for a number of scans. @@ -409,7 +525,7 @@ def common_dtype(scans): return dtype -def detect_scan_mode(data): +def detect_scan_mode(data: np.ndarray) -> Tuple[List[str], Dict[str, np.ndarray]]: """ detect the scan mode and unique scan positions in a data array. @@ -495,7 +611,7 @@ def detect_scan_mode(data): return scan_mode, scan_positions -def filter_tp(data, filter): +def filter_tp(data: np.ndarray, _filter: np.ndarray) -> np.ndarray: """ select data points from an ETPI array that match theta and phi coordinates of another ETPI array. @@ -503,7 +619,7 @@ def filter_tp(data, filter): @param data ETPI-like structured numpy.ndarray (ETPI, ETPIS, ETPAI, ETPAIS). - @param filter ETPI-like structured numpy.ndarray (ETPI, ETPIS, ETPAI, ETPAIS). + @param _filter ETPI-like structured numpy.ndarray (ETPI, ETPIS, ETPAI, ETPAIS). only 't' and 'p' columns are used. @return filtered data (numpy.ndarray) @@ -512,18 +628,19 @@ def filter_tp(data, filter): """ # copy theta,phi into separate structured arrays data_tp = np.zeros_like(data, dtype=[('t', ' np.ndarray: """ interpolate a hemispherical scan from a rectangular angle scan. @@ -555,7 +672,9 @@ def interpolate_hemi_scan(rect_tpi, hemi_tpi): hemi_tpi['i'][sel_theta] = result return hemi_tpi -def reshape_2d(flat_data, axis_columns, return_column='i'): + +def reshape_2d(flat_data: np.ndarray, axis_columns: Sequence[str], return_column: str = 'i') -> \ + Tuple[np.ndarray, np.ndarray, np.ndarray]: """ reshape an ETPI-like array into a two-dimensional array according to the scan axes. @@ -564,7 +683,9 @@ def reshape_2d(flat_data, axis_columns, return_column='i'): the array must be sorted in the order of axis_labels. @param axis_columns list of column names that designate the axes - + + @param return_column: name of field to return in two dimensions + @return the tuple (result_data, axis0, axis1), where @arg result_data (ndarray) new two-dimensional ndarray of the scan @arg axis0 (ndarray) scan positions along the first dimension @@ -579,7 +700,7 @@ def reshape_2d(flat_data, axis_columns, return_column='i'): return data.copy(), axis0, axis1 -def calc_modfunc_mean(data): +def calc_modfunc_mean(data: np.ndarray) -> np.ndarray: """ calculates the modulation function using the mean value of data. this is a simplified calculation method @@ -615,7 +736,7 @@ def calc_modfunc_mean(data): return modf -def calc_modfunc_loess(data, smth=0.4): +def calc_modfunc_loess(data: np.ndarray, smth: float = 0.4) -> np.ndarray: """ calculate the modulation function using LOESS (locally weighted regression) smoothing. @@ -669,20 +790,27 @@ def calc_modfunc_loess(data, smth=0.4): return modf -def rfactor(experiment, theory): +def square_diff_rfactor(experiment: np.ndarray, theory: np.ndarray) -> float: """ - calculate the R-factor of a calculated modulation function. + Calculate the R-factor from the normalized sum of squared differences. - if the sigma column is present in experiment and non-zero, + If the sigma column is present in experiment and non-zero, the R-factor terms are weighted by 1/sigma**2. - the input arrays must have the same shape and the coordinate columns must be identical (they are ignored). - the array elements are compared element-by-element. - terms having NaN intensity are ignored. + The input arrays must have the same shape and the coordinate columns must be identical. + The array elements are compared element-by-element. + The values of the coordinate arrays do not influence the result. + Terms having NaN intensity are ignored. - @param experiment: ETPI, ETPIS, ETPAI or ETPAIS array containing the experimental modulation function. + This function can be specified in the Scan.rfactor_func parameter of the project. - @param theory: ETPI or ETPAI array containing the calculated modulation functions. + @param experiment: (numpy structured array) + ETPI, ETPIS, ETPAI or ETPAIS array containing the experimental modulation function. + If an `s` field is present and non-zero, + the R-factor terms are weighted by 1/sigma**2. + + @param theory: (numpy structured array) + ETPI or ETPAI array containing the theoretical function. @return scalar R-factor in the range from 0.0 to 2.0. @@ -702,7 +830,7 @@ def rfactor(experiment, theory): return sum1 / sum2 -def scaled_rfactor(scale, experiment, weights, theory): +def scaled_rfactor_func(scale: float, experiment: np.ndarray, weights: np.ndarray, theory: np.ndarray) -> float: """ calculate the R-factor of a modulation function against the measurement with scaled amplitude. @@ -732,6 +860,7 @@ def scaled_rfactor(scale, experiment, weights, theory): @raise ValueError if all experiments and theory values or all weights are zero. """ + difs = weights * (scale * experiment - theory) ** 2 sums = weights * (scale ** 2 * experiment ** 2 + theory ** 2) sum1 = difs.sum(dtype=np.float64) @@ -739,7 +868,7 @@ def scaled_rfactor(scale, experiment, weights, theory): return sum1 / sum2 -def optimize_rfactor(experiment, theory): +def optimize_rfactor(experiment: np.ndarray, theory: np.ndarray) -> float: """ calculate the R-factor of a calculated modulation function against the measurement, adjusting their amplitude. @@ -750,13 +879,15 @@ def optimize_rfactor(experiment, theory): this is useful if the amplitudes of the two functions do not match due to systematic effects of the calculation or the measurement. - the optimization is done in a scipy.optimize.least_squares optimization of the scaled_rfactor() function. + the optimization is done in a scipy.optimize.least_squares optimization of the scaled_rfactor_func() function. the initial guess of the scaling factor is 0.7, the constraining boundaries are 1/10 and 10. the input arrays must have the same shape and the coordinate columns must be identical (they are ignored). the array elements are compared element-by-element. terms having NaN intensity are ignored. + This function can be specified in the Scan.rfactor_func parameter of the project. + @param experiment: ETPI, ETPIS, ETPAI or ETPAIS array containing the experimental modulation function. @param theory: ETPI or ETPAI array containing the calculated modulation functions. @@ -773,13 +904,13 @@ def optimize_rfactor(experiment, theory): else: wgts = np.ones_like(experiment['i']) - result = so.least_squares(scaled_rfactor, 0.7, bounds=(0.1, 10.0), args=(experiment['i'], wgts, theory['i'])) - result_r = scaled_rfactor(result.x, experiment['i'], wgts, theory['i']) + result = so.least_squares(scaled_rfactor_func, 0.7, bounds=(0.1, 10.0), args=(experiment['i'], wgts, theory['i'])) + result_r = scaled_rfactor_func(result.x, experiment['i'], wgts, theory['i']) return result_r -def alpha_average(data): +def alpha_average(data: np.ndarray) -> np.ndarray: """ average I(alpha, theta, phi) over alpha. @@ -809,7 +940,7 @@ def alpha_average(data): return result -def phi_average(data): +def phi_average(data: np.ndarray) -> np.ndarray: """ average I(theta, phi) over phi. @@ -827,9 +958,9 @@ def phi_average(data): names = list(data.dtype.names) names.remove('p') dtype = [(name, data.dtype[name].str) for name in names] - result = create_data((nt), dtype=dtype) + result = create_data((nt,), dtype=dtype) - for i,t in enumerate(t_axis): + for i, t in enumerate(t_axis): sel = np.abs(scan_positions['t'] - t) < 0.01 for name in names: result[name][i] = np.mean(data[name][sel], dtype=np.float64) @@ -839,7 +970,7 @@ def phi_average(data): return result -def alpha_mirror_average(data): +def alpha_mirror_average(data: np.ndarray) -> np.ndarray: """ calculate the average of I(alpha, theta, phi) and I(-alpha, theta, phi). @@ -871,3 +1002,14 @@ def alpha_mirror_average(data): logger.warning('asymmetric alpha scan. skipping alpha mirror average.') return result1 + + +if loess is not None: + default_modfunc = calc_modfunc_loess + logger.info("pmsco.data.default_modfunc = pmsco.data.calc_modfunc_loess") +else: + default_modfunc = calc_modfunc_mean + logger.warning("pmsco.data.default_modfunc = pmsco.data.calc_modfunc_mean") + +default_rfactor = square_diff_rfactor +logger.info("pmsco.data.default_rfactor = pmsco.data.square_diff_rfactor") \ No newline at end of file diff --git a/pmsco/database.py b/pmsco/database.py deleted file mode 100644 index 9cae8e9..0000000 --- a/pmsco/database.py +++ /dev/null @@ -1,1657 +0,0 @@ -""" -@package pmsco.database -experimental development of a model database. - -this module maintains an sqlite3 database of calculation results. -the database provides a flexible way to query and analyse results of various calculations -under different filtering and sorting from any client that supports sqlite3. - -currently, the database module is independent of the core PMSCO, -and the database has to be maintained manually. -in the future, calculation results will be added automatically by PMSCO. - -@author Matthias Muntwiler, matthias.muntwiler@psi.ch - -@copyright (c) 2016-18 by Paul Scherrer Institut @n -Licensed under the Apache License, Version 2.0 (the "License"); @n - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import os -import datetime -import logging -import sqlite3 -import fasteners -import numpy as np -import pmsco.dispatch as dispatch -from pmsco.helpers import BraceMessage as BMsg - -logger = logging.getLogger(__name__) - - -# make sure sqlite understands numpy data types -sqlite3.register_adapter(np.float64, float) -sqlite3.register_adapter(np.float32, float) -sqlite3.register_adapter(np.int64, int) -sqlite3.register_adapter(np.int32, int) - - -class _DummyLock(object): - """ - dummy lock used for in memory database. - """ - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - -## mapping of database fields to special parameter names -# -# `_db` parameters are returned by some query methods to identify the database records. -# -DB_SPECIAL_PARAMS = {"job_id": "_db_job", - "model_id": "_db_model", - "result_id": "_db_result", - "model": "_model", - "scan": "_scan", - "domain": "_domain", - "emit": "_emit", - "region": "_region", - "gen": "_gen", - "particle": "_particle", - "rfac": "_rfac"} - - -## numpy data types of special parameters by database field -# -# this dictionary helps to create a numpy array from a database record. -# -DB_SPECIAL_NUMPY_TYPES = {"job_id": "i8", - "model_id": "i8", - "result_id": "i8", - "model": "i8", - "scan": "i8", - "domain": "i8", - "emit": "i8", - "region": "i8", - "gen": "i8", - "particle": "i8", - "rfac": "f8"} - - -def regular_params(d): - """ - filter regular parameters from dictionary - - returns a dictionary containing only the regular parameters (those not prefixed with an underscore). - - @param d: dict or numpy.void or pmsco.dispatch.CalcID. - the param names must have no leading underscore. - the numpy.void type occurs when an element of a structured array is extracted. - the CalcID does not contain a regular parameter and will return an empty dictionary. - it is supported only for compatibility with special_params function. - a tuple or list is interpreted as a sequence of parameter names. - in this case the names representing special parameters are returned with underscore removed. - - @return: dict. leading underscores are removed from key names. - """ - if isinstance(d, np.void): - d = {k: d[k] for k in d.dtype.names if k[0] != "_"} - elif isinstance(d, dispatch.CalcID): - d = {} - elif isinstance(d, tuple): - d = [k for k in d if k[0] != "_"] - d = tuple(d) - elif isinstance(d, dict): - d = {k: v for k, v in d.items() if k[0] != "_"} - else: - d = [k for k in d if k[0] != "_"] - - return d - - -def special_params(d): - """ - filter special parameters from model dictionary, numpy record or sequence. - - special parameters are those prefixed with an underscore. - the underscore is removed from the keys. - fields starting with '_db_' are removed. - - @param d: dict or numpy.void or pmsco.dispatch.CalcID or sequence. - in the case of a dict or numpy.void, - the key names of the special parameters must have a leading underscore. - the numpy.void type occurs when an element of a structured array is extracted. - in the case of a CalcID, the attribute names become the key names. - a tuple or list is interpreted as a sequence of parameter names. - in this case the names representing special parameters are returned with underscore removed. - - @return - the return type depends on the type of input `d`: - @arg in the case of a dict, numpy.void or CalcID it is a dictionary. - @arg in the case of a tuple or list the return type is the same as the input. - """ - if isinstance(d, np.void): - d = {k[1:]: d[k] for k in d.dtype.names if k[0] == "_" and k[0:4] != "_db_"} - elif isinstance(d, dispatch.CalcID): - d = d._asdict() - elif isinstance(d, tuple): - d = [k[1:] for k in d if k[0] == "_" and k[0:4] != "_db_"] - d = tuple(d) - elif isinstance(d, dict): - d = {k[1:]: v for k, v in d.items() if k[0] == "_" and k[0:4] != "_db_"} - else: - d = [k[1:] for k in d if k[0] == "_" and k[0:4] != "_db_"] - - return d - - -def field_to_param(f): - """ - translate database field name to parameter name. - - field names of optimization parameters are unchanged. - special parameters are prefixed by '_' or '_db_'. - - @param f: (str) database field name. - @return: (str) parameter name as used in model dictionaries. - """ - try: - p = DB_SPECIAL_PARAMS[f] - except KeyError: - p = f - return p - - -def field_to_numpy_type(f): - """ - determine the numpy data type string of a database field. - - @param f: (str) database field name. - @return: (str) numpy type description, e.g. 'f8'. - """ - try: - t = DB_SPECIAL_NUMPY_TYPES[f] - except KeyError: - t = 'f8' - return t - - -class ResultsDatabase(object): - """ - interface to a sqlite3 database of calculation results. - - the class maintains a connection to a specified database file. - results can be written to and retrieved from the database. - - to get started in a calculation job, call the following methods: - - connect() - - register_project() - - register_job() - - register_params() - - the class also maintains a local registry of model parameters. - this is basically a dictionary that maps parameter names to parameter ids used in the database. - because the database can only relate a parameter to a project or job after a value has been inserted, - it is important to maintain such a local registry. - - before any access to the ParamValues table, the project parameters must be registered - by register_params() or query_project_params(). - the first method can be used at any time. - it should be used before data is inserted into the database. - if a parameter is already registered in the database, the id is taken from the database - (parameter names are unique in the whole Params table). - the second method can be used only when the database already contains data. - it should be used if the database is read only, or the parameter names are unknown. - """ - sql_create_projects = """CREATE TABLE IF NOT EXISTS `Projects` ( - `id` INTEGER PRIMARY KEY, - `name` TEXT NOT NULL UNIQUE COLLATE NOCASE, - `code` TEXT COLLATE NOCASE - )""" - sql_insert_project = "insert into Projects(name, code) values (:name, :code)" - sql_select_project = "select name, code from Projects where id=:id" - sql_select_project_name = "select id, name, code from Projects where name=:name" - sql_delete_project = """delete from Projects where project_id = :project_id""" - - sql_create_jobs = """CREATE TABLE IF NOT EXISTS `Jobs` ( - `id` INTEGER PRIMARY KEY, - `project_id` INTEGER, - `name` TEXT NOT NULL COLLATE NOCASE, - `mode` TEXT COLLATE NOCASE, - `machine` TEXT COLLATE NOCASE, - `git_hash` TEXT, - `datetime` TEXT, - `description` TEXT, - FOREIGN KEY(project_id) REFERENCES Projects(id) ON DELETE CASCADE - )""" - sql_insert_job = """insert into Jobs(project_id, name, mode, machine, git_hash, datetime, description) - values (:project_id, :name, :mode, :machine, :git_hash, :datetime, :description)""" - sql_select_job = """select id, project_id, name, mode, machine, git_hash, datetime - from Jobs where id = :job_id""" - sql_select_job_name = """select id, project_id, name, mode, machine, git_hash, datetime - from Jobs where project_id=:project_id and name=:name""" - sql_delete_job = """delete from Jobs where id = :job_id""" - - sql_create_models = """CREATE TABLE IF NOT EXISTS `Models` ( - `id` INTEGER PRIMARY KEY, - `job_id` INTEGER, - `model` INTEGER, - `gen` INTEGER, - `particle` INTEGER, - FOREIGN KEY(job_id) REFERENCES Jobs(id) ON DELETE CASCADE - )""" - sql_index_models = """create index if not exists - index_models on Models - ('job_id', 'model')""" - sql_drop_index_models = "drop index if exists index_models" - sql_insert_model = """insert into Models(job_id, model, gen, particle) - values (:job_id, :model, :gen, :particle)""" - sql_update_model = """update Models - set model=:model, gen=:gen, particle=:particle - where id=:model_id""" - sql_select_model = """select id, job_id, model, gen, particle - from Models where id=:id""" - sql_select_model_model = """select id, job_id, model, gen, particle - from Models where job_id=:job_id and model=:model""" - sql_select_model_job = """select id, job_id, model, gen, particle - from Models where job_id=:job_id""" - sql_delete_model = """delete from Models where model_id = :model_id""" - - sql_create_results = """create table if not exists `Results` ( - `id` INTEGER PRIMARY KEY, - `model_id` INTEGER, - `scan` integer, - `domain` integer, - `emit` integer, - `region` integer, - `rfac` REAL, - FOREIGN KEY(model_id) REFERENCES Models(id) ON DELETE CASCADE - )""" - sql_index_results_tasks = """create index if not exists - `index_results_tasks` ON `Results` - (`model_id`, `scan`,`domain`,`emit`,`region`)""" - sql_drop_index_results_tasks = "drop index if exists index_results_tasks" - sql_index_results_models = """create index if not exists - `index_results_models` ON `Results` - (`id`, `model_id`)""" - sql_drop_index_results_models = "drop index if exists index_results_models" - sql_insert_result = """insert into Results(model_id, scan, domain, emit, region, rfac) - values (:model_id, :scan, :domain, :emit, :region, :rfac)""" - sql_update_result = """update Results - set rfac=:rfac - where id=:result_id""" - sql_select_result = """select id, model_id, scan, domain, emit, region, rfac - from Results where id=:id""" - sql_select_result_index = """select id, model_id, scan, domain, emit, region, rfac - from Results where model_id=:model_id and scan=:scan and domain=:domain and emit=:emit and region=:region""" - sql_delete_result = """delete from Results where id = :result_id""" - sql_view_results_models = """create view if not exists `ViewResultsModels` as - select project_id, job_id, model_id, Results.id as result_id, rfac, model, scan, domain, emit, region - from Models - join Results on Results.model_id = Models.id - join Jobs on Jobs.id = Models.job_id - order by project_id, job_id, rfac, model, scan, domain, emit, region - """ - - sql_create_params = """CREATE TABLE IF NOT EXISTS `Params` ( - `id` INTEGER PRIMARY KEY, - `key` TEXT NOT NULL UNIQUE COLLATE NOCASE - )""" - sql_insert_param = "insert into Params(key) values (:key)" - sql_select_param = "select key from Params where id=:id" - sql_select_param_key = "select id, key from Params where key=:key" - sql_select_param_project = """select distinct key, param_id from Models - join Jobs on Models.job_id = Jobs.id - join ParamValues on Models.id = paramValues.model_id - join Params on Params.id = ParamValues.param_id - where Jobs.project_id = :project_id - order by key collate nocase""" - sql_select_param_job = """select distinct key, param_id from Models - join ParamValues on Models.id = paramValues.model_id - join Params on Params.id = ParamValues.param_id - where Models.job_id = :job_id - order by key collate nocase""" - - sql_create_paramvalues = """CREATE TABLE IF NOT EXISTS `ParamValues` ( - `id` INTEGER PRIMARY KEY, - `param_id` INTEGER NOT NULL, - `model_id` INTEGER NOT NULL, - `value` REAL, - FOREIGN KEY(param_id) REFERENCES Params(id) ON DELETE CASCADE, - FOREIGN KEY(model_id) REFERENCES Models(id) ON DELETE CASCADE - )""" - sql_index_paramvalues = """create index if not exists - `index_paramvalues` ON `ParamValues` - (`param_id`, `model_id`)""" - sql_drop_index_paramvalues = "drop index if exists index_paramvalues" - sql_insert_paramvalue = """ - insert into ParamValues(param_id, model_id, value) - values (:param_id, :model_id, :value) - """ - sql_update_paramvalue = """ - update ParamValues set value=:value where id=:paramvalue_id - """ - sql_select_paramvalue_model = """ - select key, value from ParamValues - join Params on ParamValues.param_id = Params.id - where model_id = :model_id - """ - sql_select_paramvalue = """ - select ParamValues.id as id, key, value from ParamValues - join Params on ParamValues.param_id = Params.id - where param_id = :param_id and model_id = :model_id - """ - - sql_create_tags = """CREATE TABLE IF NOT EXISTS `Tags` ( - `id` INTEGER PRIMARY KEY, - `key` TEXT NOT NULL UNIQUE COLLATE NOCASE - )""" - sql_insert_tag = "insert into Tags(key) values (:key)" - sql_select_tag = "select key from Tags where id=:id" - sql_select_tag_key = "select id, key from Tags where key=:key" - sql_select_tag_project = """select distinct key, tag_id from Jobs - join JobTags on Jobs.id = JobTags.job_id - join Tags on Tags.id = JobTags.tag_id - where Jobs.project_id = :project_id - order by key collate nocase""" - sql_select_tag_job = """select distinct key, tag_id from JobTags - join Tags on Tags.id = JobTags.tag_id - where JobTags.job_id = :job_id - order by key collate nocase""" - - sql_create_jobtags = """CREATE TABLE IF NOT EXISTS `JobTags` ( - `id` INTEGER PRIMARY KEY, - `tag_id` INTEGER NOT NULL, - `job_id` INTEGER NOT NULL, - `value` TEXT COLLATE NOCASE, - FOREIGN KEY(tag_id) REFERENCES Tags(id) ON DELETE CASCADE, - FOREIGN KEY(job_id) REFERENCES Jobs(id) ON DELETE CASCADE - )""" - sql_index_jobtags = """create index if not exists - `index_jobtags` ON `JobTags` - (`tag_id`, `job_id`)""" - sql_drop_index_jobtags = "drop index if exists index_jobtags" - sql_insert_jobtag = """ - insert into JobTags(tag_id, job_id, value) - values (:tag_id, :job_id, :value) - """ - sql_update_jobtag = """ - update JobTags set value=:value where id=:jobtag_id - """ - sql_select_jobtag_job = """ - select key, value from JobTags - join Tags on JobTags.tag_id = Tags.id - where job_id = :job_id - """ - sql_select_jobtag = """ - select JobTags.id as id, key, value from JobTags - join Tags on JobTags.tag_id = Tags.id - where tag_id = :tag_id and job_id = :job_id - """ - - # @var _conn (sqlite3.Connection). - # connection interface to the database. - # - # opened and closed by self.connect() and self.disconnect(), respectively. - # the connection can remain open during the whole process. - # - # @note sqlite3.Connection is re-usable but not re-entrant. - # Be careful not to nest contexts when calling other methods from within this class! - - # @var _db_filename (str). - # path and name of the database file or ":memory:" for an in-memory database. - - # @var project_id (int). - # id of the current project in the primary key of the Projects table. - # - # set by self.register_project(). - # new jobs and models are linked to this project id by default. - - # @var job_id (int). - # id of the current job in the primary key of the Jobs table. - # - # set by self.register_job(). - # new models are linked to this job id by default. - - # @var _model_params (dict). - # dictionary of model parameters used in the current project. - # - # set by self.register_params() and self.register_param(). - # read by self.add_model(). - - # @var _lock_filename (str). - # path and name of the lock file or an empty string if no locking is used. - - def __init__(self): - self._conn = None - self._db_filename = "" - self.project_id = 0 - self.job_id = 0 - self._model_params = {} - self._tags = {} - self._lock_filename = "" - - def connect(self, db_filename, lock_filename=""): - """ - connect to a new or existing database file. - - if the file does not exist, or if it is empty, a new database schema is created. - - @param db_filename: name of a file or ":memory:" for an in-memory database. - - @param lock_filename: name of a file that is used to lock the database. - by default, the db_filename with a suffix of ".lock" is used. - for most uses, the default should be fine. - the argument is provided mainly for testing the locking functionality. - - this must be a file that is not used for anything else. - the file does not need to exist. - it's best if the file is in the same directory as the database file. - - @return: None - """ - self._db_filename = db_filename - - if lock_filename: - self._lock_filename = lock_filename - elif db_filename == ":memory:": - self._lock_filename = "" - else: - self._lock_filename = db_filename + ".lock" - - self._conn = sqlite3.connect(self._db_filename) - self._conn.row_factory = sqlite3.Row - with self.lock(): - self._conn.execute("PRAGMA foreign_keys = 1") - self._conn.commit() - c = self._conn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Models'") - l = c.fetchone() - if not l or not l[0]: - self.create_schema() - - def disconnect(self): - """ - close the connnection to the database. - - uncommitted transactions are lost. - data of an in-memory database is lost. - - @return: None - """ - if self._conn is not None: - self._conn.close() - self._conn = None - - def check_connection(self): - """ - check that the database is connected. - - checks that self.connect has been called. - this method is used by most ResultsDatabase methods to check connection before the database is used. - it essentially produces a better understandable exception message if the connect call has been forgotten. - (with out it, python would raise: `AttributeError: __exit__`). - - @return None - - @raise AssertionError if the connection is not valid. - """ - assert self._conn is not None, "database not connected" - - def lock(self): - """ - create a file-lock context manager for the database. - - this is either a fasteners.InterProcessLock object on self._lock_filename - or a _DummyLock object if the database is in memory. - InterprocessLock allows to serialize access to the database by means of a lock file. - this is necessary if multiple pmsco instances require access to the same database. - _DummyLock is used with an in-memory database which does not require locking. - - the lock object can be used as context-manager in a with statement. - """ - if self._lock_filename: - return fasteners.InterProcessLock(self._lock_filename) - else: - return _DummyLock() - - def create_schema(self): - """ - create the database schema (tables and indices). - - the method can safely be applied to an existing schema. - because all create commands include the "if not exists", - only missing objects will be created. - - @return: None - """ - self.check_connection() - with self.lock(), self._conn: - self._conn.execute(self.sql_create_projects) - self._conn.execute(self.sql_create_jobs) - self._conn.execute(self.sql_create_models) - self._conn.execute(self.sql_create_results) - self._conn.execute(self.sql_create_params) - self._conn.execute(self.sql_create_paramvalues) - self._conn.execute(self.sql_create_tags) - self._conn.execute(self.sql_create_jobtags) - self._conn.execute(self.sql_index_results_tasks) - self._conn.execute(self.sql_index_results_models) - self._conn.execute(self.sql_index_paramvalues) - self._conn.execute(self.sql_index_jobtags) - self._conn.execute(self.sql_index_models) - self._conn.execute(self.sql_view_results_models) - - def register_project(self, name, code): - """ - register a project with the database. - - @param name: name of the project. alphanumeric characters only. no spaces or special characters! - if a project of the same name exists in the database, - the id of the existing entry is returned. - the existing entry is not modified. - - @param code: name of the pmsco module that defines the project. - - @return: id value of the project in the database. - """ - self.check_connection() - with self.lock(), self._conn: - c = self._conn.execute(self.sql_select_project_name, {'name': name}) - v = c.fetchone() - if v: - project_id = v[0] - else: - c = self._conn.execute(self.sql_insert_project, {'name': name, 'code': code}) - project_id = c.lastrowid - self.project_id = project_id - - return project_id - - def delete_project(self, project_id): - """ - delete a project from the database. - - this will delete all dependent jobs, models, results and parameter values. - - @param project_id: id value of the project entry in the database. - - @return None - """ - self.check_connection() - with self.lock(), self._conn: - param_dict = {'project_id': project_id} - self._conn.execute(self.sql_delete_project, param_dict) - - def register_job(self, project_id, name, mode, machine, git_hash, _datetime, description=None): - # type: (int, basestring, basestring, basestring, basestring, datetime.datetime, basestring) -> int - """ - register a calculation job with the database. - - @param project_id: identifier of the project. see register_project(). - - @param name: name of the job. alphanumeric characters only. no spaces or special characters! - must be unique within a project. - if a job of the same name and same project exists in the database, - the id of the existing entry is returned. - the existing entry is not modified. - - @param mode: optimization mode string (should be same as command line argument). - - @param machine: name of the machine executing the calculation. up to the user. - - @param git_hash: git revision (hash) of the project code. - - @param _datetime: (datetime.datetime) date and time. - - @param description: meaningful description of the calculation job, up to the user. - - @return: id value of the job in the database. - """ - self.check_connection() - with self.lock(), self._conn: - c = self._conn.execute(self.sql_select_job_name, {'project_id': project_id, 'name': name}) - v = c.fetchone() - if v: - job_id = v[0] - else: - v = {'project_id': project_id, - 'name': name, - 'mode': mode, - 'machine': machine, - 'git_hash': git_hash, - 'datetime': _datetime, - 'description': description} - c = self._conn.execute(self.sql_insert_job, v) - job_id = c.lastrowid - self.job_id = job_id - - return job_id - - def delete_job(self, job_id): - """ - delete a job from the database. - - this will delete all dependent models, results and parameter values. - - @param job_id: id value of the job entry in the database. - - @return None - """ - self.check_connection() - with self.lock(), self._conn: - param_dict = {'job_id': job_id} - self._conn.execute(self.sql_delete_job, param_dict) - - def _query_job_name(self, job_name, project_id=0): - """ - (internal) query a job by name - - this is the internal analog of @ref query_job_name - which asserts an acquired lock and open connection. - - @param job_name: name of the job - - @param project_id: project identifier. - by default, the current project self.project_id is used. - - @return: id value of the job in the database - - @raise DatabaseError if the job can't be found. - """ - if project_id == 0: - project_id = self.project_id - param_dict = {'project_id': project_id, 'name': job_name} - c = self._conn.execute(self.sql_select_job_name, param_dict) - v = c.fetchone() - return v[0] - - def query_job_name(self, job_name, project_id=0): - """ - query a job by name - - @param job_name: name of the job - - @param project_id: project identifier. - by default, the current project self.project_id is used. - - @return: id value of the job in the database - """ - self.check_connection() - with self.lock(), self._conn: - job_id = self._query_job_name(job_name, project_id=project_id) - - return job_id - - def register_param(self, key): - """ - register a parameter key with the database. - - each parameter name must be registered once before a value can be written to the database. - see the class description for an explanation. - - @param key: key (name) of the parameter. - - @return: id value of the parameter in the database. - """ - self.check_connection() - with self.lock(), self._conn: - return self._register_param(key) - - def _register_param(self, key): - """ - register a parameter key with the database. - - @note this is method does not lock the database file and does not commit. - to lock the database and commit the transaction, call the public method register_param(). - - @param key: key (name) of the parameter. - - @return: id value of the parameter in the database. - """ - c = self._conn.execute(self.sql_select_param_key, {'key': key}) - v = c.fetchone() - if v: - param_id = v[0] - else: - c = self._conn.execute(self.sql_insert_param, {'key': key}) - param_id = c.lastrowid - self._model_params[key] = param_id - return param_id - - def register_params(self, model_params): - """ - register the model parameters of this project with the database. - - each parameter name must be registered once before a value can be written to the database. - see the class description for an explanation. - - @param model_params: sequence of model parameter keys, or dictionary of model parameters. - @return: None - """ - self.check_connection() - with self.lock(), self._conn: - for key in model_params: - if key[0] != '_': - self._register_param(key) - - def query_project_params(self, project_id=0, job_id=0, update_registry=False): - """ - query a list of model parameters used in a project or job. - - optionally, the local registry can be updated with the results of the query. - this should be done if the database is read only and the client does not know the parameter names. - see the class description for a description of the registry. - - @note this method returns the parameters that are used with models in the database. - if you have registered additional parameters but not stored models with those parameters, - this method will _not_ list them. - - @param project_id: project identifier. - by default, the current project self.project_id is used. - - @param job_id: job identifier. - by default, all jobs of the selected project are included in the query. - if a job is specified, the project_id parameter is ignored. - - @param update_registry: update the local parameter registry (self._model_params). - with the query results. - - @return: dictionary of project parameters. - the keys are the parameter names, the values are the parameter ids in the database. - """ - if project_id == 0: - project_id = self.project_id - if job_id == 0: - sql = self.sql_select_param_project - args = {'project_id': project_id} - else: - sql = self.sql_select_param_job - args = {'job_id': job_id} - - params = {} - self.check_connection() - with self.lock(), self._conn: - c = self._conn.execute(sql, args) - for row in c: - params[row['key']] = row['param_id'] - - if update_registry: - self._model_params.update(params) - - return params - - def register_tag(self, key): - """ - register a tag with the database. - - tags are a way of structuring a job description. - they can be used to, for instance, distinguish calculations made with different clusters, - different experimental data, etc. - a job tag has a key and a value, and is associated to a job. - the use of tags is up to the user. pmsco does not change or read them. - - each tag name must be registered once before a value can be written to the database. - see the class description for an explanation. - - @param key: key (name) of the tag. - - @return: id value of the tag in the database. - """ - self.check_connection() - with self.lock(), self._conn: - return self._register_tag(key) - - def _register_tag(self, key): - """ - register a tag with the database without committing the transaction. - - @note this method does not lock the database file and does not commit. - to lock the database and commit the transaction, call the public method register_tag(). - - @param key: key (name) of the tag. - - @return: id value of the tag in the database. - """ - c = self._conn.execute(self.sql_select_tag_key, {'key': key}) - v = c.fetchone() - if v: - tag_id = v[0] - else: - c = self._conn.execute(self.sql_insert_tag, {'key': key}) - tag_id = c.lastrowid - self._tags[key] = tag_id - return tag_id - - def register_tags(self, tags): - """ - register the tags of this project with the database. - - each tag name must be registered once before a value can be written to the database. - see the class description for an explanation. - - @param tags: sequence of tag keys, or dictionary of tags. - @return: None - """ - self.check_connection() - with self.lock(), self._conn: - for key in tags: - self._register_tag(key) - - def query_tags(self, project_id=0, job_id=0, update_registry=False): - """ - query a list of tag keys used in a project or job. - - optionally, the local registry can be updated with the results of the query. - this should be done if the database is read only and the client does not know the tag names. - see the class description for a description of the registry. - - @note this method returns the tags that are used with jobs in the database. - if you have registered additional tags but not attached them to jobs, - this method will _not_ list them. - - @param project_id: project identifier. - by default, the current project self.project_id is used. - - @param job_id: job identifier. - by default, all jobs of the selected project are included in the query. - if a job is specified, the project_id parameter is ignored. - - @param update_registry: update the local tags registry (self._tags). - with the query results. - - @return: dictionary of tags. - the keys are the tag names, the values are the tag ids in the database. - """ - if project_id == 0: - project_id = self.project_id - if job_id == 0: - sql = self.sql_select_tag_project - args = {'project_id': project_id} - else: - sql = self.sql_select_tag_job - args = {'job_id': job_id} - - tags = {} - self.check_connection() - with self.lock(), self._conn: - c = self._conn.execute(sql, args) - for row in c: - tags[row['key']] = row['tag_id'] - - if update_registry: - self._tags.update(tags) - - return tags - - def query_job_tags(self, job_id): - """ - query a list of tags (keys and values) associated with a job. - - @param job_id: job identifier. - - @return: dictionary of tags. - the keys are the tag names, the values are the tag values. - """ - sql = self.sql_select_jobtag_job - args = {'job_id': job_id} - - tags = {} - self.check_connection() - with self.lock(), self._conn: - c = self._conn.execute(sql, args) - for row in c: - tags[row['key']] = row['value'] - - return tags - - def insert_jobtags(self, job_id, tags): - """ - add or update job tags in the database. - - the method updates the JobTags table. - - @param job_id: (int) primary key of the job entry in the Jobs table. - the entry must exist. - - @param tags: (dict) dictionary containing the tags. - keys are matched or added to the Tags table, - values are added to the JobTags table and linked to the job and tag key. - - @return: None - """ - self.check_connection() - with self.lock(), self._conn: - for key, value in tags.items(): - try: - tag_id = self._tags[key] - except KeyError: - tag_id = self._register_tag(key) - v = None - else: - jobtag_entry = {'tag_id': tag_id, 'job_id': job_id, 'value': value} - c = self._conn.execute(self.sql_select_jobtag, jobtag_entry) - v = c.fetchone() - - if v: - jobtag_entry = {'jobtag_id': v[0], 'tag_id': tag_id, 'job_id': job_id, 'value': value} - self._conn.execute(self.sql_update_jobtag, jobtag_entry) - else: - jobtag_entry = {'tag_id': tag_id, 'job_id': job_id, 'value': value} - self._conn.execute(self.sql_insert_jobtag, jobtag_entry) - - def create_models_view(self, job_id=0, temporary=False): - """ - create a flat (pivot) view of model parameters of the current project or job. - - the view is an SQL view that can be used like other tables in select statements. - the name of the view is the project or job id prefixed by @c ViewModelsProject or ViewModelsJob, respectively. - the view contains the 'job_id', 'model_id' and 'model' columns of the Models table - as well as columns of the de-normalized ParamValues table - where the column names are the respective parameter names. - the list of parameter names is combined from the private registry and the database. - - the method generates the SQL to create the view and executes it. - a previous view of the same name is dropped. - the view can optionally be made temporary (it is dropped when the database connection is closed). - - depending on the `job_id` argument, a view of the current project - or of the specified job is created. - - @note the view needs to be re-recreated whenever the parameter dictionary has changed. - - @param job_id: job identifier. - by default, all jobs of the selected project are included in the query. - if a job is specified, the project_id parameter is ignored. - - @param temporary: if True, the view is temporary and dropped when the database connection is closed. - otherwise (by default), the view is available on all connections and stored with the database. - - @return sql string that was used to create the view. - """ - project_id = self.project_id - self.check_connection() - params = self.query_project_params(project_id, job_id) - params.update(self._model_params) - param_names = sorted(params, key=lambda s: s.lower()) - with self.lock(), self._conn: - if job_id: - view_name = "ViewModelsJob{0}".format(job_id) - else: - view_name = "ViewModelsProject{0}".format(project_id) - - sql = "drop view if exists {0}".format(view_name) - self._conn.execute(sql) - - sql = "create {1} view {0} as ".format(view_name, "temp" if temporary else "") - sql += "select job_id, Models.id as model_id, model, gen, particle, " - col_fmt = "max(case when key='{0}' then value end) {0}" - cols = [col_fmt.format(param) for param in param_names] - sql += ", ".join(cols) - sql += " from Models " - sql += " join ParamValues on Models.id = ParamValues.model_id " - sql += " join Params on Params.id = ParamValues.param_id " - sql += " join Jobs on Jobs.id = Models.job_id " - if job_id: - sql += " where Models.job_id = {job_id} ".format(job_id=job_id) - else: - sql += " where Jobs.project_id = {project_id} ".format(project_id=project_id) - sql += " group by model_id " - - self._conn.execute(sql) - - return sql - - def insert_model(self, model_params): - """ - add calculated model parameters to the database. - - this adds to the Models and ParamValues tables. - - @param model_params: dictionary containing all model parameters and control variables. - the '_model' value is required. - '_gen' and '_particle' are optional, they are written to the database if provided, or default to None. - the names of the model parameters must have been registered with register_params() beforehand. - - @return: id value of the model entry in the database. - - @raise KeyError if a parameter hasn't been registered. - """ - self.check_connection() - with self.lock(), self._conn: - # insert model record - model_dict = {'job_id': self.job_id, 'gen': None, 'particle': None} - model_dict.update(special_params(model_params)) - c = self._conn.execute(self.sql_insert_model, model_dict) - model_id = c.lastrowid - - # insert parameter value records - for key, value in model_params.items(): - if key[0] != '_': - param_id = self._model_params[key] - param_dict = {'param_id': param_id, 'model_id': model_id, 'value': value} - c.execute(self.sql_insert_paramvalue, param_dict) - - return model_id - - def delete_model(self, model_id): - """ - delete a model from the database. - - this will delete all dependent results and parameter values. - - @param model_id: id value of the model entry in the database. - - @return None - """ - self.check_connection() - with self.lock(), self._conn: - param_dict = {'model_id': model_id} - self._conn.execute(self.sql_delete_model, param_dict) - - def query_model(self, model_id): - """ - retrieve model parameters and control variables from the database. - - @param model_id: id of the model in the database. - @return: dict - """ - self.check_connection() - with self.lock(), self._conn: - c = self._conn.execute(self.sql_select_paramvalue_model, {'model_id': model_id}) - d = {} - for row in c: - d[row['key']] = row['value'] - c = self._conn.execute(self.sql_select_model, {'id': model_id}) - row = dict(c.fetchone()) - for key, value in row.items(): - if key[0] == '_' and value is not None: - d[key] = value - return d - - def insert_model_array(self, array): - """ - add a numpy array of calculated model parameters to the database. - - @param array: numpy structured array as it is used, e.g., in pmsco.optimizers.population.Population. - - @return: - """ - assert isinstance(array, np.ndarray) - - for r in array: - d = {name: r[name] for name in r.dtype.names} - self.insert_model(d) - - def query_model_array(self, filter=[], limit=0): - """ - load a range of models from the database. - - the project id is implicitly given by the object attribute. - it's not possible to combine results from multiple projects. - - @param filter: list of filter expressions. - each expression is a relational expression of the form field operator value, - where field is a unique field name of the Projects, Jobs, Models or Results table, e.g. - `job_id`, `model`, `rfac`, `scan`, `domain`, etc. - operator is one of the relational operators in SQL syntax. - value is a numeric or string constant, the latter including single or double quotes. - if the list is empty, no filtering is applied. - the project should not be specified here. - - @param limit: maximum number of models to read. 0 = return all models that match the filter criteria. - - @return numpy structured array as it is used, e.g., in pmsco.optimizers.population.Population. - the function uses the data type only. data in the array is overwritten. - the array is sorted by model_id. - - @raise unknown exception when the project does not have results. - - @raise OperationalError if the view ViewModelsProject{0} is missing in the database schema. - """ - self.check_connection() - filter += [" project_id = {0} ".format(self.project_id)] - with self.lock(), self._conn: - sql = "select distinct Models.id as model_id, model " - sql += "from Models " - sql += "join Results on Models.id = Results.model_id " - sql += "join Jobs on Models.job_id = Jobs.id " - sql += "join Projects on Jobs.project_id = Projects.id " - if filter: - sql += "where " - sql += " and ".join(filter) - sql += " " - sql += "order by model_id " - if limit: - sql += "limit {0} ".format(limit) - c = self._conn.execute(sql) - models = c.fetchall() - count = len(models) - model_ids = [row['model_id'] for row in models] - - sql = "select * " - sql += "from ViewModelsProject{0} ".format(self.project_id) - sql += "where model_id in ({0}) ".format(",".join("?" * len(model_ids))) - sql += "order by model_id " - c = self._conn.execute(sql, model_ids) - results = c.fetchall() - - names = [desc[0] for desc in c.description] - dt = np.dtype([(field_to_param(n), field_to_numpy_type(n)) for n in sorted(names, key=str.lower)]) - out_array = np.zeros((count,), dtype=dt) - for idx, row in enumerate(results): - for name in names: - try: - out_array[field_to_param(name)][idx] = row[name] - except TypeError: - # can't store None in integer - pass - - return out_array - - def query_best_results(self, filter=[], limit=0): - """ - load a range of results from the database. - - @param filter: list of filter expressions. - each expression is a relational expression of the form field operator value, - where field is a unique field name of the Projects, Jobs, Models or Results table, e.g. - `job_id`, `model`, `rfac`, `scan`, `domain`, etc. - operator is one of the relational operators in SQL syntax. - value is a numeric or string constant, the latter including single or double quotes. - if the list is empty, no filtering is applied. - the project should not be specified here. - - @param limit: maximum number of models to read. 0 = return all models that match the filter criteria. - - @return numpy structured array as it is used, e.g., in pmsco.optimizers.population.Population. - the function uses the data type only. data in the array is overwritten. - the array is sorted by rfac. - """ - self.check_connection() - filter += [" project_id = {0} ".format(self.project_id)] - with self.lock(), self._conn: - sql = "select Results.id as result_id, model_id, job_id, " - sql += "model, scan, domain, emit, region, rfac, gen, particle " - sql += "from Models " - sql += "join Results on Models.id = Results.model_id " - sql += "join Jobs on Models.job_id = Jobs.id " - sql += "join Projects on Jobs.project_id = Projects.id " - if filter: - sql += "where " - sql += " and ".join(filter) - sql += " " - sql += "order by rfac, job_id, model, scan, domain, emit, region " - if limit: - sql += "limit {0} ".format(limit) - c = self._conn.execute(sql) - results = c.fetchall() - count = len(results) - - names = [desc[0] for desc in c.description] - dt = np.dtype([(field_to_param(n), field_to_numpy_type(n)) for n in sorted(names)]) - out_array = np.zeros((count,), dtype=dt) - for idx, row in enumerate(results): - for name in names: - try: - out_array[field_to_param(name)][idx] = row[name] - except TypeError: - # can't store None in integer - pass - - return out_array - - def query_best_models_per_jobs(self, job_ids=None, task_level='model'): - """ - return the best model (by rfac) of each selected job - - the query gathers the R-factors of the selected jobs at the selected task levels - and, for each job, returns the (database) model id where the lowest R-factor is reported - among the gathered results. - - this can be useful if you want to compile a report of the best model per job. - - @param job_ids: iterable of job ids to include in the query. - the job ids must belong to the current project. - if empty or non-specified, all jobs of the current project are included. - - @param task_level: element of or index into @ref pmsco.dispatch.CALC_LEVELS. - deepest task_level to include in the query. - results on deeper levels are not considered. - e.g. if you pass 'scan', R-factors of individual scans are included in the query. - note that including deeper levels will not increase the number of results returned. - - @return sequence of model_id. - the number of results corresponds to the number of jobs in the filter scope. - to find out details of the models, execute another query that filters on these model ids. - - the method produces an SQL query similar to: - @code{.sql} - select Models.id from Models - join Results on Models.id = Results.model_id - join Jobs on Models.job_id = Jobs.id - where scan=-1 - and project_id=1 - and job_id in (1,2,3) - group by Models.job_id - having min(rfac) - order by rfac - @endcode - """ - - try: - level = dispatch.CALC_LEVELS.index(task_level) + 1 - except ValueError: - level = task_level + 1 - try: - level_name = dispatch.CALC_LEVELS[level] - except IndexError: - level_name = dispatch.CALC_LEVELS[4] - - self.check_connection() - with self.lock(), self._conn: - sql = "select Models.id from Models " - sql += "join Results on Models.id = Results.model_id " - sql += "join Jobs on Models.job_id = Jobs.id " - sql += "where project_id = {0} ".format(self.project_id) - sql += "and {0} = -1 ".format(level_name) - if job_ids: - sql += "and Models.job_id in ({0}) ".format(",".join(map(str, job_ids))) - sql += "group by Models.job_id " - sql += "having min(rfac) " - sql += "order by rfac, job_id, model, scan, domain, emit, region " - c = self._conn.execute(sql) - models = [row['id'] for row in c] - - return models - - def query_tasks(self, job_id=0): - """ - query the task index used in a calculation job. - - this query neglects the model index - and returns the unique tuples (-1, scan, domain, emit, region). - - @param job_id: (int) id of the associated Jobs entry. - if 0, self.job_id is used. - - @return list of pmsco.dispatch.CalcID tuples of task indices. - the model attribute is -1 in all elements. - """ - if not job_id: - job_id = self.job_id - - self.check_connection() - with self.lock(), self._conn: - sql = "select scan, domain, emit, region " - sql += "from Models " - sql += "join Results on Models.id = Results.model_id " - sql += "join Jobs on Models.job_id = Jobs.id " - sql += "join Projects on Jobs.project_id = Projects.id " - sql += "where job_id = :job_id" - c = self._conn.execute(sql, {'job_id': job_id}) - results = c.fetchall() - - output = [] - for row in results: - d = dict(row) - d['model'] = -1 - output.append(dispatch.CalcID(**d)) - - return output - - def query_best_task_models(self, level, count, job_id=0): - """ - query N best models per task. - - this query is used by the file tracker to determine the models to keep. - - @param level: level up to which to query. - the level can be specified by level name (str) or numeric index (0..4). - if it is scan (equivalent to 1), the method queries the model and scan levels. - - @param count: number of models to query per task. - - @param job_id: (int) id of the associated Jobs entry. - if 0, self.job_id is used. - - @return set of matching model numbers (Models.model field). - """ - if not job_id: - job_id = self.job_id - - try: - level = int(level) - except ValueError: - level = dispatch.CALC_LEVELS.index(level) - assert 0 <= level < len(dispatch.CALC_LEVELS) - - def _query_models(d): - sql = "select model " - sql += "from Models " - sql += "join Results on Models.id = Results.model_id " - sql += "where Models.job_id = :job_id " - sql += "and scan = :scan " - sql += "and domain = :domain " - sql += "and emit = :emit " - sql += "and region = :region " - sql += "order by rfac " - sql += "limit :limit " - c = self._conn.execute(sql, d) - results = c.fetchall() - return set([row['model'] for row in results]) - - tasks = self.query_tasks(job_id) - models = set([]) - with self.lock(), self._conn: - for task in tasks: - if task.numeric_level <= level: - d = task._asdict() - del d['model'] - d['job_id'] = job_id - d['limit'] = count - q_models = _query_models(d) - models |= q_models - - return models - - def insert_result(self, index, result, job_id=0): - """ - add or update a result in the database. - - the method updates the Models, Results and ParamValues tables. - - the model is identified by job_id and index.model. - the result is identified by job_id and index. - - if the model or result exists in the database, it is updated. - - @param index: (pmsco.dispatch.CalcID or dict) - calculation index. - in case of dict, the keys must be the attribute names of CalcID prefixed with an underscore, i.e., - '_model', '_scan', '_domain', '_emit', '_region'. - extra values in the dictionary are ignored. - undefined indices must be -1. - - @param result: (dict) dictionary containing the parameter values and the '_rfac' result. - may also contain the special values '_gen' and '_particle'. - '_gen' and '_particle' default to None if not present. - - @param job_id: (int) id of the associated Jobs entry. - if 0, self.job_id is used. - - @return: id of the new or updated Results entry. - """ - if not job_id: - job_id = self.job_id - - self.check_connection() - with self.lock(), self._conn: - model_id = self._insert_result_model(job_id, index, result) - result_id = self._insert_result_data(model_id, index, result) - self._insert_result_paramvalues(model_id, result) - - logger.debug(BMsg("database insert result: job {}, model {}, result {}", job_id, model_id, result_id)) - - return result_id - - def _insert_result_model(self, job_id, index, result): - """ - add or update the model entry for a calculation result in the database. - - the method updates the Models table. - the database and transaction must be locked by the caller. - - the model is identified by job_id and index.model. - the result is identified by job_id and index. - - if the model exists in the database, it is updated. - - @param job_id: (int) primary key of the associated Jobs entry. - no default value! passing an invalid job_id raises an exception. - - @param index: (pmsco.dispatch.CalcID or dict) - calculation index. - in case of dict, the keys must be the attribute names of CalcID prefixed with an underscore, i.e., - '_model', '_scan', '_domain', '_emit', '_region'. - extra values in the dictionary are ignored. - undefined indices must be -1. - - @param result: (dict) dictionary containing the parameter values and the '_rfac' result. - may also contain the special values '_gen' and '_particle'. - '_gen' and '_particle' default to None if not present. - - @return: id of the new or updated Results entry. - """ - model_entry = {'gen': None, 'particle': None} - model_entry.update(special_params(result)) - model_entry['job_id'] = job_id - try: - model_entry['model'] = index.model - except AttributeError: - model_entry['model'] = index['_model'] - c = self._conn.execute(self.sql_select_model_model, model_entry) - v = c.fetchone() - if v: - model_id = v[0] - model_entry['model_id'] = model_id - self._conn.execute(self.sql_update_model, model_entry) - else: - c = self._conn.execute(self.sql_insert_model, model_entry) - model_id = c.lastrowid - return model_id - - def _insert_result_data(self, model_id, index, result): - """ - add or update a result in the database. - - the method updates the Results table. - the database and transaction must be locked by the caller. - - the model is identified by model_id. - the result is identified by model_id and index. - - if the result exists in the database, it is updated. - - @param model_id: (int) primary key of the model entry in the Models table. - the entry must exist. - - @param index: (pmsco.dispatch.CalcID or dict) - calculation index. - in case of dict, the keys must be the attribute names of CalcID prefixed with an underscore, i.e., - '_model', '_scan', '_domain', '_emit', '_region'. - extra values in the dictionary are ignored. - undefined indices must be -1. - @param result: (dict) dictionary containing the parameter values and the '_rfac' result. - - @return: id of the new or updated Results entry. - """ - result_entry = special_params(result) - result_entry['model_id'] = model_id - result_entry.update(special_params(index)) - c = self._conn.execute(self.sql_select_result_index, result_entry) - v = c.fetchone() - if v: - result_id = v[0] - result_entry['result_id'] = result_id - self._conn.execute(self.sql_update_result, result_entry) - else: - c = self._conn.execute(self.sql_insert_result, result_entry) - result_id = c.lastrowid - - return result_id - - def _insert_result_paramvalues(self, model_id, result): - """ - add or update parameter values of a model in the database. - - the method updates the ParamValues table. - the database and transaction must be locked by the caller. - - @param model_id: (int) primary key of the model entry in the Models table. - the entry must exist. - - @param result: (dict) dictionary containing the parameter values. - the parameter names must exist in the Params table and in the self._model_params dictionary. - special values (with a leading underscore) are ignored. - extra parameters may raise a KeyError. - - @return: None - - @raise: KeyError if a parameter key is not registered. - """ - for key, value in regular_params(result).items(): - param_id = self._model_params[key] - paramvalue_entry = {'param_id': param_id, 'model_id': model_id, 'value': value} - c = self._conn.execute(self.sql_select_paramvalue, paramvalue_entry) - v = c.fetchone() - if v: - paramvalue_id = v[0] - paramvalue_entry['paramvalue_id'] = paramvalue_id - self._conn.execute(self.sql_update_paramvalue, paramvalue_entry) - else: - self._conn.execute(self.sql_insert_paramvalue, paramvalue_entry) - - def import_results_file(self, filename, job_id=0): - """ - import a results file into the database. - - the results file is a space-delimited, general text file - such as produced by pmsco.optimizers.population.Population.save_array(). - each line contains one result dataset, the columns correspond to the regular and special parameters. - the first row contains the parameter names. - - a job entry with the given id must exist, - but there must be no model entries referencing the job. - it is not possible to update existing models, results or parameter values using this method. - instead, you have to delete the job (which also deletes all dependent entries) - and re-import the results. - - @param filename: path and name of the results file. - - @return: None. - - @raise ValueError if the job already has model entries. - """ - if not job_id: - job_id = self.job_id - - data = np.atleast_1d(np.genfromtxt(filename, names=True)) - self.register_params(data.dtype.names) - try: - unique_models, unique_index = np.unique(data['_model'], True) - except ValueError: - unique_models = np.array([0]) - unique_index = np.array([0]) - unique_data = data[unique_index] - model_ids = {} - - def model_entry_generator(): - for result in unique_data: - model_entry = {'job_id': job_id, - 'model': unique_models[0], - 'gen': None, - 'particle': None} - model_entry.update(special_params(result)) - yield model_entry - - def result_entry_generator(): - for result in data: - try: - model = result['_model'] - except ValueError: - model = unique_models[0] - result_entry = {'model_id': model_ids[model], - 'scan': -1, - 'domain': -1, - 'emit': -1, - 'region': -1, - 'rfac': None} - result_entry.update(special_params(result)) - yield result_entry - - def param_entry_generator(): - for result in unique_data: - try: - model = result['_model'] - except ValueError: - model = unique_models[0] - for key, value in regular_params(result).items(): - param_entry = {'model_id': model_ids[model], - 'param_id': self._model_params[key], - 'value': value} - yield param_entry - - with self.lock(), self._conn: - c = self._conn.execute(self.sql_select_model_job, {'job_id': job_id}) - v = c.fetchone() - if v: - raise ValueError("database already contains model entries for job {0}".format(job_id)) - self._conn.executemany(self.sql_insert_model, model_entry_generator()) - c = self._conn.execute(self.sql_select_model_job, {'job_id': job_id}) - model_ids = {row['model']: row['id'] for row in c} - self._conn.executemany(self.sql_insert_result, result_entry_generator()) - self._conn.executemany(self.sql_insert_paramvalue, param_entry_generator()) - - -def import_job_results(**kwargs): - """ - import results from a calculation job. - - this function contains all steps necessary to import the results from a calculation job into a database. - it registers the project and job, and imports the results data. - the project may exist in the database, the job must not exist (raises an exception). - - arguments can be specified as dict (**d) or in keyword=value form. - - @param kwargs: dictionary of function arguments. - the dictionary contains the following values. - all arguments are required unless noted. - @arg 'workdir' (optional) path to the working directory. - the working directory of the operating system is changed. - this is the root for relative paths of the database and results files. - if not specified, the working directory is unchanged. - @arg 'dbfile' name of the database file. - @arg 'project' name of the project. - @arg 'code' name of the project code. - @arg 'job' name of the calculation job. - @arg 'mode' pmsco optimization mode. - @arg 'machine' name of the machine where the job ran. - @arg 'git' git hash of the code revision. - @arg 'datetime' (datetime.datetime) time stamp (optional). - if not specified, the argument defaults to the time stamp of the results file. - hint: the constructor of a datetime object is - `datetime.datetime(year, month, day, hour, minute, second)`. - @arg 'description' meaningful description of the calculation job, up to the user. - @arg 'resultsfile' name of the .tasks.dat results file. - - @return dict with 'project_id' and 'job_id' - - @raise ValueError if the job already exists in the database. - """ - try: - os.chdir(kwargs['workdir']) - except KeyError: - pass - try: - dt = kwargs['datetime'] - except KeyError: - dt = datetime.datetime.fromtimestamp(os.path.getmtime(kwargs['resultsfile'])) - - db = ResultsDatabase() - db.connect(kwargs['dbfile']) - project_id = db.register_project(kwargs['project'], kwargs['code']) - job_id = db.register_job(project_id, kwargs['job'], kwargs['mode'], kwargs['machine'], kwargs['git'], dt, - kwargs['description']) - db.import_results_file(kwargs['resultsfile'], job_id) - db.create_models_view() - db.disconnect() - - return {'project_id': project_id, 'job_id': job_id} diff --git a/pmsco/database/access.py b/pmsco/database/access.py new file mode 100644 index 0000000..8883995 --- /dev/null +++ b/pmsco/database/access.py @@ -0,0 +1,169 @@ +""" +@package pmsco.database.access +wrapper classes for access to a pmsco database + +the most import class to be used is DatabaseAccess. + +usage: +~~~~~~{.py} +db = DatabaseAccess() +db.connect("file.db") +with db.session(): + # database access here + # ... + # commit transaction + session.commit() + # continue in new transaction + +# at the end of the context +# the session is closed and orm objects are detached from the database. +~~~~~~ + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016-21 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import fasteners +import logging +from pathlib import Path +import pmsco.database.orm as orm + +logger = logging.getLogger(__name__) + + +class _DummyLock(object): + """ + dummy lock used for in memory database. + """ + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + +class LockedSession(object): + """ + database session context manager + + this context manager (to be used in a with statement) + acquires a lock on the database lock file + and provides a database session (orm.Session()). + + the session is closed (and pending transactions committed) on exit. + if an exception occurs, pending transactions are rolled back before the session is closed. + + @note the term _session_ refers to a session in sqlalchemy. + """ + def __init__(self, lock_file=None): + self.lock_file = lock_file + self._session = None + self._lock = None + + def __enter__(self): + self._lock = self.lock() + self._lock.__enter__() + self._session = orm.Session() + return self._session + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + self._session.close() + else: + self._session.rollback() + self._session.close() + self._lock.__exit__(exc_type, exc_val, exc_tb) + self._lock = None + + def lock(self): + """ + create a file-lock context manager for the database. + + this is either a fasteners.InterProcessLock object on self._lock_filename + or a _DummyLock object if the database is in memory. + InterprocessLock allows to serialize access to the database by means of a lock file. + this is necessary if multiple pmsco instances require access to the same database. + _DummyLock is used with an in-memory database which does not require locking. + + the lock object can be used as context-manager in a with statement. + """ + if self.lock_file: + return fasteners.InterProcessLock(self.lock_file) + else: + return _DummyLock() + + +class DatabaseAccess(object): + """ + basic database connection + + this class maintains a database connection and builds session objects. + + a _session_ corresponds to an sqlalchemy session, which defines the lifecycle of mapped objects. + a session can open one or multiple (subsequent) transactions. + + usage: + ~~~~~~{.py} + db = DatabaseAccess() + db.connect("file.db") + with db.session(): + # database access + session.commit() + ~~~~~~ + + the session object is a context handler. + it commits the transaction and closes the session at the end of the context. + if an exception occurs, it rolls back the transaction and closes the session before passing the exception. + """ + def __init__(self): + self.db_file = "" + self.lock_file = "" + + def connect(self, db_file, lock_file=""): + """ + connect to a new or existing database file. + + if the file does not exist, or if it is empty, a new database schema is created. + + @param db_file: name of a file or ":memory:" for an in-memory database. + + @param lock_file: name of a file that is used to lock the database. + by default, the db_filename with a suffix of ".lock" is used. + for most uses, the default should be fine. + the argument is provided mainly for testing the locking functionality. + + this must be a file that is not used for anything else. + the file does not need to exist. + it's best if the file is in the same directory as the database file. + all clients of a database must use the same lock file. + + @return: None + """ + self.db_file = db_file + + if lock_file: + self.lock_file = lock_file + elif db_file == ":memory:": + self.lock_file = "" + else: + self.lock_file = Path(str(db_file) + ".lock") + + orm.connect(orm.sqlite_link(self.db_file)) + + def session(self): + """ + open a database session. + + this function returns a pmsco.database.util.LockedSession object + which is a context handler that provides an sqlalchemy session + that is locked against concurrent access from other DatabaseAccess instances. + see the class description for an example usage pattern. + + @return: pmsco.database.util.LockedSession() object. + """ + return LockedSession(self.lock_file) diff --git a/pmsco/database/common.py b/pmsco/database/common.py new file mode 100644 index 0000000..a203022 --- /dev/null +++ b/pmsco/database/common.py @@ -0,0 +1,329 @@ +""" +@package pmsco.database.common +common database operations + +this module gathers a number of common database operations. +all functions require an open session object from pmsco.database.access.DatabaseAccess. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016-21 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import logging +import sqlalchemy +import pmsco.database.orm as orm + +logger = logging.getLogger(__name__) + + +def filter_project(query, project_or_name_or_id): + """ + filter a query by project + + @param query: sqlalchemy query object + @param project_or_name_or_id: orm.Project object or project name or project id. + @return: modified query + """ + if isinstance(project_or_name_or_id, orm.Project): + query = query.filter(orm.Project == project_or_name_or_id) + elif isinstance(project_or_name_or_id, int): + query = query.filter(orm.Project.id == project_or_name_or_id) + else: + query = query.filter(orm.Project.name == project_or_name_or_id) + return query + + +def filter_job(query, job_or_name_or_id): + """ + filter a query by job + + @param query: sqlalchemy query object + @param job_or_name_or_id: orm.Job object or job name or job id. + @return: modified query + """ + if isinstance(job_or_name_or_id, orm.Job): + query = query.filter(orm.Job == job_or_name_or_id) + elif isinstance(job_or_name_or_id, int): + query = query.filter(orm.Job.id == job_or_name_or_id) + else: + query = query.filter(orm.Job.name == job_or_name_or_id) + return query + + +def query_params(session, project=None, job=None): + """ + query parameter names and their associated objects from the database + + the result is a dictionary of orm.Param objects mapped to their respective keys. + the parameters can be filtered by project and/or job. + if no arguments are given, parameters from all projects are returned. + + @note make sure previous changes have been committed. else the query may not find all records. + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + @param project: orm.Project object or project name or project id. + default: don't filter projects. + @param job: orm.Job object or job name or job id. + default: don't filter jobs + @return: dictionary of parameters + """ + query = session.query(orm.Param).join(orm.ParamValue).join(orm.Model).join(orm.Job).join(orm.Project) + if project is not None: + query = filter_project(query, project) + if job is not None: + query = filter_job(query, job) + params = query.all() + params = {param.key: param for param in params} + return params + + +def query_tags(session, project=None, job=None): + """ + query tag names and their associated objects from the database + + the result is a dictionary of orm.Tag objects mapped to their respective keys. + the tags can be filtered by project and/or job. + if no arguments are given, tags from all projects are returned. + + @note the orm.Job.tags mapping is an alternative way to access job tags. + + @note make sure previous changes have been committed. else the query may not find all records. + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + @param project: orm.Project object or project name or project id. + default: don't filter projects. + @param job: orm.Job object or job name or job id. + default: don't filter jobs + @return: dictionary of tags + """ + query = session.query(orm.Tag).join(orm.JobTag).join(orm.Job).join(orm.Project) + if project is not None: + query = filter_project(query, project) + if job is not None: + query = filter_job(query, job) + tags = query.all() + tags = {tag.key: tag for tag in tags} + return tags + + +def query_job_tags(session, project=None, job=None): + """ + query tags (keys and values) from the database + + the result is a dictionary of tag values (str) mapped to their respective keys (str). + the tags can be filtered by project and/or job. + if no arguments are given, tags from all projects are returned. + + @note for one specific job, this is equivalent to the orm.Job.tags mapping. + + @note make sure previous changes have been committed. else the query may not find all records. + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + @param project: orm.Project object or project name or project id. + default: don't filter projects. + @param job: orm.Job object or job name or job id. + default: don't filter jobs + @return: tags dictionary {key: value} + """ + query = session.query(orm.JobTag).join(orm.Job).join(orm.Project) + if project is not None: + query = filter_project(query, project) + if job is not None: + query = filter_job(query, job) + job_tags = query.all() + job_tags = {jt.tag.key: jt.value for jt in job_tags} + return job_tags + + +def register_project(session, name, code, allow_existing=False): + """ + register (insert or query) a project with the database. + + a new project record with the given parameters is inserted into the database. + if a project of the same name already exists, the existing record is returned. + + @attention the orm.Project.id field is undefined until the session is committed! + it's better to identify a project by name or orm.Project object. + + @note make sure previous changes have been committed. else the query may not find an existing project. + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + the session is committed if a new project entry has been added. + @param name: project name. must be unique within the database. + @param code: name of the project module. + @param allow_existing: selects the behaviour if a project record exists in the database: + return the corresponding orm.Project (True) or raise an exception (False, default). + the exception is ValueError. + @return: orm.Project object. + the object can be used and modified as long as the session is active. + note that the id attribute is invalid until the session is committed! + @raise ValueError if the job exists and allow_existing is False. + """ + query = session.query(orm.Project) + query = query.filter(orm.Project.name == name) + project = query.one_or_none() + + if project is None: + project = orm.Project(name=name, code=code) + session.add(project) + session.commit() + elif not allow_existing: + raise ValueError(f"project {project.name} exists") + + return project + + +def get_project(session, project_or_name_or_id): + """ + resolve a project by name or id. + + this function resolves a project specification to an orm.Project object. + if `project_or_name_or_id` is an orm.Project object, it just returns that object without any checks. + else, the project is looked up in the database. + + @attention if `project_or_name_or_id` is an orm.Project object the function returns it without checks! + that means if the object is detached, you cannot use it to query results from the database. + if you need an object that is valid and in sync with the database, + resolve it by name or id! + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + @param project_or_name_or_id: orm.Project object or project name or project id. + @return: orm.Project object + """ + if isinstance(project_or_name_or_id, orm.Project): + project = project_or_name_or_id + elif isinstance(project_or_name_or_id, int): + project = session.query(orm.Project).get(project_or_name_or_id) + else: + query = session.query(orm.Project) + query = query.filter(orm.Project.name == project_or_name_or_id) + project = query.one() + return project + + +def register_job(session, project, job_name, allow_existing=False, **job_attr): + """ + register (insert or query) a new job with the database. + + a new job record with the given parameters is inserted into the database. + if a job of the same name exists within the given project, the existing record is returned + (without modifications!). + + @note make sure previous changes have been committed. else the query may not find an existing project. + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + the session is committed if a new job entry has been added. + @param project: orm.Project object or project name or project id. + @param job_name: name of job. unique in the project + @param job_attr: optional attributes of the job. + the keywords correspond to attribute names of the pmsco.database.Job object. + @param allow_existing: selects the behaviour if a job record exists in the database: + return the corresponding orm.Job (True) or raise an exception (False, default). + the exception is ValueError. + @return: orm.Job object. + the object can be used and modified as long as the session is active. + note that the id attribute is invalid until the session is committed! + @raise ValueError if the job exists and allow_existing is False. + """ + project = get_project(session, project) + + query = session.query(orm.Job).join(orm.Project) + query = query.filter(orm.Project.name == project.name) + query = query.filter(orm.Job.name == job_name) + job = query.one_or_none() + + if job is None: + job = orm.Job() + job.name = job_name + job.project = project + optional_args = {'mode', 'machine', 'git_hash', 'datetime', 'processes', 'hours', 'description'} + for name, value in job_attr.items(): + if name in optional_args: + setattr(job, name, value) + session.add(job) + session.commit() + elif not allow_existing: + raise ValueError(f"a job {job_name} exists in project {project.name}") + + return job + + +def get_job(session, project_or_name_or_id, job_or_name_or_id): + """ + resolve a job by name or id. + + this function resolves any combination of project and job specification to an orm.Job object. + if `job_or_name_or_id` is an orm.Job object, it just returns that object without any checks. + else, the job is looked up in the database. + + @attention if `job_or_name_or_id` is an orm.Job object the function returns it without checks! + that means if the object is detached, you cannot query results from the database. + if you need an object that is valid and in sync with the database, + query the job by name or id! + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + @param project_or_name_or_id: orm.Project object or project name or project id. + @param job_or_name_or_id: orm.Job object or job name or job id. + @return: orm.Job object + """ + if isinstance(job_or_name_or_id, orm.Job): + job = job_or_name_or_id + elif isinstance(job_or_name_or_id, int): + job = session.query(orm.Job).get(job_or_name_or_id) + else: + project = get_project(session, project_or_name_or_id) + query = session.query(orm.Job).join(orm.Project) + query = query.filter(orm.Project.name == project.name) + query = query.filter(sqlalchemy.or_(orm.Job.id == job_or_name_or_id, + orm.Job.name == job_or_name_or_id)) + job = query.one() + return job + + +def register_job_tags(session, job, tags): + """ + insert or update key-value tags of a job + + this is one of many options to populate the Tag and JobTag tables. + it is not required to use this function. + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + @param job: orm.Job object + @param tags: dictionary of tags + @return: None + """ + for k, v in tags.items(): + job.tags[k] = v + if tags: + session.commit() + + +def register_params(session, params): + """ + register (insert missing) parameter names + + add new parameter names to the global list of parameter names. + + this is one of many options to populate the Param table. + it is not required to use this function. + + this function implies a session flush. + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + the session is committed if new parameters have been added + @param params: sequence of parameter names + param names with leading underscore are ignored. + @return: None + """ + existing_params = query_params(session).keys() + params = [param for param in params if param[0] != '_'] + new_params = set(params) - set(existing_params) + for k in new_params: + session.add(orm.Param(key=k)) + if new_params: + session.commit() diff --git a/pmsco/database/git.py b/pmsco/database/git.py new file mode 100644 index 0000000..1841e56 --- /dev/null +++ b/pmsco/database/git.py @@ -0,0 +1,57 @@ +""" +@package pmsco.database.git +git metadata + +this module retrieves the git hash of the running code for job metadata. +this requires that the code is run from a git repository +and that the gitpython package is installed. +gitpython is loaded on demand. +common errors (missing gitpython or invalid repository) are handled. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2015-21 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import importlib + + +def git(): + """ + import the git module from GitPython + + @return: git module or None if an error occurred + """ + try: + return importlib.import_module('git') + except ImportError: + return None + + +def get_git_hash(repo_path=None): + """ + get the git commit (hash) of the running code (HEAD) + + the method looks for a git repository in the source tree of this module. + if successful, it returns the hash string of the HEAD commit. + + @return: hexadecimal hash string. + empty string if the file is not in a git repository. + """ + if repo_path is None: + repo_path = __file__ + + _git = git() + if _git is not None: + try: + repo = _git.Repo(repo_path, search_parent_directories=True) + except _git.exc.InvalidGitRepositoryError: + return "" + else: + return repo.head.commit.hexsha + else: + return "" diff --git a/pmsco/database/ingest.py b/pmsco/database/ingest.py new file mode 100644 index 0000000..dbb252a --- /dev/null +++ b/pmsco/database/ingest.py @@ -0,0 +1,406 @@ +""" +@package pmsco.database.ingest + +ingest existing data such as flat results files (.dat or .tasks.dat) into a database. + +the results file is a space-delimited, general text file +such as produced by pmsco.optimizers.population.Population.save_array(). +each line contains one result dataset, the columns correspond to the regular and special parameters. +the first row contains the parameter names. + +the main function is ingest_job_results(). +the other functions require an open database session from pmsco.database.access.DatabaseAccess.session(), +and ingest the metadata and the actual results, respectively. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016-21 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import datetime +import logging +import numpy as np +from pathlib import Path +from pmsco.database.access import DatabaseAccess +import pmsco.database.common as common +import pmsco.database.orm as orm +import pmsco.database.util as util + +logger = logging.getLogger(__name__) + + +def insert_result(session, job, index, result, delta=None): + """ + add or update a calculation result including index and model to the database. + + @param session: (sqlalchemy.Session) database session. + when updating an existing model, previous changes must have been committed, + else the model may not be found. + this function does not commit the transaction. + @param job: (orm.Job) job object. + use pmsco.database.common.get_object to retrieve by id or name. + @param index: (pmsco.dispatch.CalcID or dict) + calculation index. + in case of dict, the keys must be the attribute names of CalcID prefixed with an underscore, i.e., + '_model', '_scan', '_domain', '_emit', '_region'. + extra values in the dictionary are ignored. + undefined indices must be -1. + @param result: (dict) dictionary containing the parameter values and the '_rfac' result. + may also contain the special values '_gen', '_particle', '_timestamp'. + '_gen' and '_particle' are integers and default to None. + '_timestamp' can be numeric (seconds since jan 1, 1970) + or an object that implements a timestamp function like datetime.datetime. + it defaults to the current (local) time. + @param delta: (dict) dictionary containing the delta values. + the keys must correspond to model keys in the result dictionary. + this argument is optional. + + @return: (orm.Model, orm.Result) model and result objects + """ + model_obj = store_model(session, job, index, result) + result_obj = store_result_data(session, model_obj, index, result) + store_param_values(session, model_obj, result, delta) + return model_obj, result_obj + + +def store_model(session, job, index, result): + """ + add or update the model entry for a calculation result in the database. + + the method updates the Models table. + the model is identified by job and index.model. + the result is identified by job and index. + if the model exists in the database, it is updated. + + @param session: (sqlalchemy.Session) database session. + when updating an existing model, previous changes must have been committed, + else the model may not be found. + this function does not commit the transaction. + @param job: (orm.Job) job object. + use pmsco.database.common.get_object to retrieve by id or name. + @param index: (pmsco.dispatch.CalcID or dict) + calculation index. + in case of dict, the keys must be the attribute names of CalcID prefixed with an underscore, i.e., + '_model', '_scan', '_domain', '_emit', '_region'. + extra values in the dictionary are ignored. + undefined indices must be -1. + @param result: (dict) dictionary containing the parameter values and the '_rfac' result. + may also contain the special values '_gen' and '_particle'. + '_gen' and '_particle' default to None if not present. + + @return: (orm.Model) updated model object + """ + assert isinstance(job, orm.Job) + + model_dict = {'gen': None, 'particle': None} + model_dict.update(util.special_params(result)) + try: + model_dict['model'] = index.model + except AttributeError: + model_dict['model'] = index['_model'] + + q = session.query(orm.Model) + q = q.filter(orm.Model.job == job) + q = q.filter(orm.Model.model == model_dict['model']) + model_obj = q.one_or_none() + + if model_obj is None: + model_obj = orm.Model() + model_obj.job = job + model_obj.model = model_dict['model'] + session.add(model_obj) + + model_obj.gen = model_dict['gen'] + model_obj.particle = model_dict['particle'] + + return model_obj + + +def store_result_data(session, model_obj, index, result): + """ + add or update a result in the database. + + the method updates the Results table. + the model is identified by model_id. + the result is identified by model_id and index. + if the result exists in the database, it is updated. + + @param session: (sqlalchemy.Session) database session. + when updating an existing model, previous changes must have been committed, + else the result entry may not be found. + this function does not commit the transaction. + @param model_obj: (orm.Model) model object that is already part of the session. + @param index: (pmsco.dispatch.CalcID or dict) + calculation index. + in case of dict, the keys must be the attribute names of CalcID prefixed with an underscore, i.e., + '_model', '_scan', '_domain', '_emit', '_region'. + extra values in the dictionary are ignored. + undefined indices must be -1. + @param result: (dict) dictionary containing the parameter values and the '_rfac' result. + may also contain the special values '_gen', '_particle', '_timestamp'. + '_gen' and '_particle' are integers and default to None. + '_timestamp' can be numeric (seconds since jan 1, 1970) + or an object that implements a timestamp function like datetime.datetime. + it defaults to the current (local) time. + + @return: (orm.Result) updated Results object. + """ + assert isinstance(model_obj, orm.Model) + + result_dict = util.special_params(result) + result_dict.update(util.special_params(index)) + + q = session.query(orm.Result) + q = q.filter(orm.Result.model == model_obj) + q = q.filter(orm.Result.scan == result_dict['scan']) + q = q.filter(orm.Result.domain == result_dict['domain']) + q = q.filter(orm.Result.emit == result_dict['emit']) + q = q.filter(orm.Result.region == result_dict['region']) + + result_obj = q.one_or_none() + if result_obj is None: + result_obj = orm.Result() + result_obj.model = model_obj + result_obj.scan = result_dict['scan'] + result_obj.domain = result_dict['domain'] + result_obj.emit = result_dict['emit'] + result_obj.region = result_dict['region'] + session.add(result_obj) + + result_obj.rfac = result_dict['rfac'] + try: + result_obj.timestamp = result_dict['timestamp'].timestamp() + except KeyError: + result_obj.timestamp = datetime.datetime.now().timestamp() + except AttributeError: + result_obj.timestamp = result_dict['timestamp'] + try: + result_obj.secs = result_dict['secs'] + except KeyError: + pass + + return result_obj + + +def store_param_values(session, model_obj, result, delta=None): + """ + add or update parameter values of a model in the database. + + the method updates the ParamValues table. + + @param session: (sqlalchemy.Session) database session. + when updating an existing model, previous changes must have been committed, + else the result entry may not be found. + this function flushes the session at the end. + it does not commit the transaction. + @param model_obj: (orm.Model) model object that is already part of the session. + @param result: (dict) dictionary containing the parameter values. + the parameter names must exist in the Params table and in the self._model_params dictionary. + special values (with a leading underscore) are ignored. + extra parameters may raise a KeyError. + @param delta: (dict) dictionary containing the delta values. + the keys must correspond to model keys in the result dictionary. + this argument is optional. + + @return: None + + @raise: KeyError if a parameter key is not registered. + """ + assert isinstance(model_obj, orm.Model) + + for key in util.regular_params(result).keys(): + pv = orm.ParamValue() + pv.model = model_obj + pv.param_key = key + pv.value = result[key] + try: + pv.delta = delta[key] + except (TypeError, KeyError): + pass + session.add(pv) + session.flush() + + +def ingest_results_file(session, project, job, filename): + """ + import a results file into the database. + + this is a sub-method used by ingest(). + + a job entry with the given id must exist, + but there must be no model entries referencing the job. + it is not possible to update existing models, results or parameter values using this method. + instead, you have to delete the job (which also deletes all dependent entries) + and re-import the results. + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + the session is flushed but not committed at the end of this function. + @param project: orm.Project object or project name or project id. + @param job: orm.Job object or job name or job id. + @param filename: path and name of the results file. + + @return: None. + + @raise ValueError if the job already has model entries. + """ + job = common.get_job(session, project, job) + assert isinstance(job, orm.Job) + + data = np.atleast_1d(np.genfromtxt(filename, names=True)) + + try: + unique_models, unique_index = np.unique(data['_model'], True) + except ValueError: + unique_models = np.array([0]) + unique_index = np.array([0]) + unique_data = data[unique_index] + + special_params = util.special_params(data.dtype.names) + + model_objs = {} + # iterate on models + for _data in unique_data: + try: + _model = _data['_model'] + except ValueError: + _model = unique_models[0] + model = orm.Model(job=job, model=_model) + if 'gen' in special_params: + model.gen = _data['_gen'] + if 'particle' in special_params: + model.particle = _data['_particle'] + session.add(model) + model_objs[_model] = model + for key, value in util.regular_params(_data).items(): + model.values[key] = value + session.flush() + + # iterate on results + for _data in data: + try: + _model = _data['_model'] + except ValueError: + _model = unique_models[0] + result_entry = {'model': None, + 'scan': -1, + 'domain': -1, + 'emit': -1, + 'region': -1, + 'rfac': None} + result_entry.update(util.special_params(_data)) + result_entry['model'] = model_objs[_model] + result = orm.Result() + for key, value in result_entry.items(): + setattr(result, key, value) + session.add(result) + + session.flush() + + +def ingest_job_metadata(session, **kwargs): + """ + ingest job metadata + + @param session: (sqlalchemy.Session) database session created by pmsco.database.access.DatabaseAccess.session() + the session is flushed but not committed at the end of this function. + + @param kwargs: dictionary of function arguments. + the dictionary contains the following values. + all arguments are required unless noted. + @arg 'resultsfile' (required) name of the .tasks.dat results file. + @arg 'project' (required) unique name of the project. + @arg 'code' (optional) name of the project code. + @arg 'job' (required) name of the calculation job. job name must not exist for the project yet. + @arg 'mode' (required) pmsco optimization mode. + @arg 'machine' (optional) name of the machine where the job ran. + @arg 'processes' (optional) number of processes. + @arg 'hours' (optional) run time in hours (wall time). + @arg 'git_hash' (optional) git hash of the code revision. + @arg 'datetime' (datetime.datetime) time stamp (optional). + if not specified, the argument defaults to the time stamp of the results file. + hint: the constructor of a datetime object is + `datetime.datetime(year, month, day, hour, minute, second)`. + @arg 'description' (optional) meaningful description of the calculation job, up to the user. + @arg 'jobtags' (dict, optional) key=value tags to be associated with the job + + @return (orm.Project, orm.Job) orm objects of the inserted records. + + @raise sqlalchemy.exc.IntegrityError if the job already exists in the database. + + """ + + if 'datetime' not in kwargs: + rf = Path(kwargs['resultsfile']) + kwargs['datetime'] = datetime.datetime.fromtimestamp(rf.stat().st_mtime) + + project = common.register_project(session, kwargs['project'], kwargs['code']) + job = common.register_job(session, project, kwargs['job'], **kwargs) + try: + common.register_job_tags(session, job, kwargs['jobtags']) + except KeyError: + pass + + session.flush() + return project, job + + +def ingest_job_results(**kwargs): + """ + import results from a calculation job. + + this function contains all steps necessary to import the results (tasks.dat) + from a calculation job into a database. + it registers the project and job, and imports the results data. + the project may exist in the database, the job must not exist (raises an exception). + + arguments can be specified as dict (**d) or in keyword=value form. + + @param kwargs: dictionary of function arguments. + the dictionary contains the following values. + all arguments are required unless noted. + @arg 'workdir' (optional) path to the working directory. + the working directory of the operating system is changed. + this is the root for relative paths of the database and results files. + if not specified, the working directory is unchanged. + @arg 'dbfile' (required) name of the database file. + @arg 'project' (required) unique name of the project. + @arg 'code' (optional) name of the project code. + @arg 'job' (required) name of the calculation job. job name must not exist for the project yet. + @arg 'mode' (required) pmsco optimization mode. + @arg 'machine' (optional) name of the machine where the job ran. + @arg 'processes' (optional) number of processes. + @arg 'hours' (optional) run time in hours (wall time). + @arg 'git_hash' (optional) git hash of the code revision. + @arg 'datetime' (datetime.datetime) time stamp (optional). + if not specified, the argument defaults to the time stamp of the results file. + hint: the constructor of a datetime object is + `datetime.datetime(year, month, day, hour, minute, second)`. + @arg 'description' (optional) meaningful description of the calculation job, up to the user. + @arg 'jobtags' (dict, optional) key=value tags to be associated with the job + @arg 'resultsfile' (required) name of the .tasks.dat results file. + + @return dict with 'project_id' and 'job_id'. + these are the database ids of the project and job records. + + @raise sqlalchemy.exc.IntegrityError if the job already exists in the database. + """ + try: + wd = Path(kwargs['workdir']) + except KeyError: + pass + else: + wd.cwd() + + dba = DatabaseAccess() + dba.connect(kwargs['dbfile']) + with dba.session() as session: + project, job = ingest_job_metadata(session, **kwargs) + ingest_results_file(session, project, job, kwargs['resultsfile']) + session.commit() + ref = {'project_id': project.id, 'job_id': job.id} + + return ref diff --git a/pmsco/database/orm.py b/pmsco/database/orm.py new file mode 100644 index 0000000..8620451 --- /dev/null +++ b/pmsco/database/orm.py @@ -0,0 +1,746 @@ +""" +@package pmsco.database.orm +pmsco results database object-relational mapper + +this module declares the database schema and object mapping. +the object-relational mapping uses +the [sqlalchemy framework](https://docs.sqlalchemy.org/en/13/orm/tutorial.html). +the database backend is sqlite3. + +for examples how to use the database, see the ingest module and the unit tests. + +@author Matthias Muntwiler + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + +""" +import datetime + +from sqlalchemy import create_engine +from sqlalchemy import event +from sqlalchemy import Column, Sequence, ForeignKey +from sqlalchemy import Boolean, Integer, Float, String, DateTime +from sqlalchemy.engine import Engine +from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import object_session +from sqlalchemy.orm import relationship +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import validates +from sqlalchemy.orm.collections import attribute_mapped_collection +from sqlalchemy.orm.exc import NoResultFound + +import numpy as np +import sqlite3 +from pmsco.dispatch import CalcID +import pmsco.database.util as db_util + +# make sure sqlite understands numpy data types +sqlite3.register_adapter(np.float64, float) +sqlite3.register_adapter(np.float32, float) +sqlite3.register_adapter(np.int64, int) +sqlite3.register_adapter(np.int32, int) + + +Base = declarative_base() +engine = None +Session = sessionmaker() + + +class Project(Base): + """ + database object representing a project + + @note there is an implicit constructor with keyword arguments that correspond to the attributes. + """ + ## @var id + # (int, primary key) database id of the project + + ## @var name + # project name, should be short, must be unique within a project + + ## @var jobs + # collection of related jobs + # + # defines the relationship between Project and Job objects. + # the instance attribute maps job names (str) to Job objects. + + __tablename__ = "Projects" + id = Column(Integer, Sequence('project_id_seq'), primary_key=True) + name = Column(String(50, collation='NOCASE'), nullable=False, unique=True) + code = Column(String(50, collation='NOCASE')) + jobs = relationship('Job', backref='project', + collection_class=attribute_mapped_collection('name'), + cascade="all, delete, delete-orphan", lazy='joined') + + def __repr__(self): + return f'Project({repr(self.name), repr(self.code)})' + + +class Job(Base): + """ + database object representing a calculation job + + a job object holds several descriptive values of a calculation job. + it also refers to a project. + + tags are key-value pairs that describe the job in standardized terms. + they can provide a consistent classification scheme across jobs and projects. + for example, they can store special project arguments that may be important + to distinguish calculations in different stages or contexts. + + the class also defines mapping and proxy objects that simplify the use of tags and models. + explicit creation of Tag and JobTag objects is then not necessary. + + @attention after modifying the mapped collections job_tags, tags or models + make sure to call flush() or commit() on the session + before accessing those mappings in other objects + else integrity errors may occur! + """ + + ## @var id + # (int, primary key) database id of the job + + ## @var project_id + # (int, foreign key) database id of the related project + + ## @var name + # job name, should be short, must be unique within a project + + ## @var mode + # pmsco calculation mode + + ## @var machine + # name of the computing facility + + ## @var git_hash + # git hash of the used code if under version control + + ## @var datetime + # start date and time of the job, ISO format (yyyy-mm-dd hh:mm:ss) + + ## @var processes + # number of processes + + ## @var hours + # job run time (wall time) in hours + + ## @var description + # up to the user + + ## @var job_tags + # collection of related job tags + # + # defines the relationship between Job and JobTag objects. + # the instance attribute maps tag keys (str) to JobTag objects. + + ## @var tags + # collection of tags + # + # maps tag keys (str) to tag values (str). + # this is an association proxy of job_tags. + + ## @var models + # collection of related models + # + # defines the relationship between Job and Model objects. + # the instance attribute maps model numbers to Model objects + + __tablename__ = "Jobs" + id = Column(Integer, Sequence('job_id_seq'), primary_key=True) + project_id = Column(Integer, ForeignKey('Projects.id'), index=True) + name = Column(String(50, collation='NOCASE'), nullable=False) + mode = Column(String(20, collation='NOCASE')) + machine = Column(String(50, collation='NOCASE')) + git_hash = Column(String(50, collation='NOCASE')) + datetime = Column(String(50)) + processes = Column(Integer) + hours = Column(Float) + description = Column(String(200, collation='NOCASE')) + + job_tags = relationship('JobTag', back_populates='job', + collection_class=attribute_mapped_collection('tag_key'), + cascade="all, delete, delete-orphan") + # mapping tag_key -> tag_value + tags = association_proxy('job_tags', 'value', creator=lambda k, v: JobTag(key=k, value=v)) + + models = relationship('Model', back_populates='job', + collection_class=attribute_mapped_collection('model'), + cascade="all, delete, delete-orphan") + + def __repr__(self): + try: + project_name = repr(self.project.name) + except AttributeError: + project_name = None + try: + job_name = repr(self.name) + except AttributeError: + job_name = None + return f'Job({project_name}, {job_name}, {repr(self.mode)})' + + +class Tag(Base): + """ + database object representing a tag name + """ + + ## @var id + # (int, primary key) database id of the tag name + + ## @var key + # tag name/key, should be short, must be unique + + ## @var tag_jobs + # collection of related JobTag objects + # + # defines the relationship between Tag and JobTag objects. + + __tablename__ = "Tags" + id = Column(Integer, Sequence('tag_id_seq'), primary_key=True) + key = Column(String(20, collation='NOCASE'), nullable=False, unique=True) + + tag_jobs = relationship('JobTag', back_populates='tag', cascade="all, delete, delete-orphan") + + def __init__(self, key): + self.key = key + + def __repr__(self): + return f'Tag({repr(self.key)})' + + +class JobTag(Base): + """ + association object class for job tags + + Job - Tag is a many-to-many relationship built using this association class. + by using the dictionary-like Job.tags proxy, explicit creation of association objects can be avoided. + + the class applies the + [UniqueObjectValidateOnPending pattern](https://github.com/sqlalchemy/sqlalchemy/wiki/UniqueObjectValidatedOnPending) + to look up existing tags in the database when a Tag object is needed and only the key is given. + """ + ## @var id + # (int, primary key) database id of the job tag + + ## @var tag_id + # (int, foreign key) database id of the related tag name + + ## @var job_id + # (int, foreign key) database id of the related job + + ## @var value + # value (str) of the job tag + + ## @var tag + # associated Tag object + # + # defines the relationship between JobTag and Tag objects + + ## @var job + # associated Job object + # + # defines the relationship between JobTag and Job objects + + ## @var tag_key + # key (name) of the asscoiated Tag object + # + # this is an association proxy that provides direct access to tag.key + # or links to or creates a Tag object behind the scenes. + + __tablename__ = "JobTags" + id = Column(Integer, Sequence('jobtag_id_seq'), primary_key=True) + tag_id = Column(Integer, ForeignKey('Tags.id'), index=True) + job_id = Column(Integer, ForeignKey('Jobs.id'), index=True) + value = Column(String(200, collation='NOCASE')) + + tag = relationship("Tag", back_populates="tag_jobs") + job = relationship("Job", back_populates="job_tags") + tag_key = association_proxy("tag", "key") + + def __init__(self, key=None, value=None): + if key is not None: + self.tag_key = key + self.value = value + + @validates("tag") + def _validate_tag(self, key, value): + """ + receive the event that occurs when `jobtag.tag` is set. + + if the object is present in a Session, then make sure it's the Tag + object that we looked up from the database. + + otherwise, do nothing and we'll fix it later when the object is + put into a Session. + + @param key: attribute name, i.e., 'tag' + @param value: a JobTag object + """ + sess = object_session(self) + if sess is not None: + return _setup_tag(sess, value) + else: + return value + + +@event.listens_for(Session, "transient_to_pending") +def _validate_tag(session, object_): + """ + receive a JobTag object when it gets attached to a Session to correct its unique Tag relationship. + """ + if isinstance(object_, JobTag): + if object_.tag is not None and object_.tag.id is None: + old_tag = object_.tag + new_tag = _setup_tag(session, object_.tag) + if new_tag is not old_tag: + if old_tag in session: + session.expunge(old_tag) + object_.tag = new_tag + + +def _setup_tag(session, tag_object): + """ + given a Session and a Tag object, return the correct Tag object from the database. + """ + with session.no_autoflush: + try: + return session.query(Tag).filter_by(key=tag_object.key).one() + except NoResultFound: + return tag_object + + +class Model(Base): + """ + database object representing a model + + the object holds the model number (which is unique within the context of a single job only), + the diagnostic generation and particle values, and refers to the job where the model is used. + + the class also defines relationship properties that simplify access to referenced objects. + for instance, parameter values can be accessed via the values['param_key'] mapping proxy. + + examples: + ~~~~~~{.py} + model = Model(model=10, gen=5, particle=2) + model.job = job1_object + + model.values['dA'] = 25.6 + model.deltas['dA'] = 0.1 + + pv = ParamValue(value=39.0, delta=-0.3) + model.param_values['dB'] = pv + + result = Result(calc_id=calc_id, rfac=0.77) + model.results.append(result) + ~~~~~~ + + @attention after modifying the mapped collections param_values, values or deltas, + make sure to call flush() or commit() on the session + before accessing those mappings in another model + else integrity errors may occur! + """ + + ## @var id + # (int, primary key) database id of the model + + ## @var job_id + # (int, foreign key) database id of the related job + + ## @var model + # (int) model number as used in the task index of pmsco + # + # @note the model number is not unique in the database as multiple jobs can produce same task indices. + # the unique number, self.id is not used in pmsco code. + + ## @var gen + # (int) generation number assigned by some optimizers. defaults to None. + + ## @var particle + # (int) particle number assigned by some optimizers. defaults to None. + + ## @var job + # associated Job + # + # defines the relationship between Model and Job objects. + + ## @var results + # collection of Result objects + # + # defines the relationship between Model and Result objects. + + ## @var param_values + # collection of ParamValue objects + # + # defines the relationship between Model and ParamValue objects. + # the instance attribute maps parameter keys to ParamValue objects. + + ## @var values + # collection of parameter values + # + # this is an association proxy that maps parameter keys to parameter values (ParamValue.value). + # ParamValue objects are accessed and created behind the scene. + + ## @var deltas + # collection of delta values + # + # this is an association proxy that maps parameter keys to parameter deltas (ParamValue.delta. + # ParamValue objects are accessed and created behind the scene. + + __tablename__ = "Models" + id = Column(Integer, Sequence('model_id_seq'), primary_key=True) + job_id = Column(Integer, ForeignKey('Jobs.id'), index=True) + model = Column(Integer, index=True) + gen = Column(Integer) + particle = Column(Integer) + + job = relationship("Job", back_populates="models") + results = relationship('Result', back_populates='model', cascade="all, delete, delete-orphan") + # mapping param_key -> ParamValue object + param_values = relationship('ParamValue', back_populates='model', + collection_class=attribute_mapped_collection('param_key'), + cascade="all, delete, delete-orphan") + + # mapping param_key -> param_value + values = association_proxy('param_values', 'value', creator=lambda k, v: ParamValue(key=k, value=v)) + deltas = association_proxy('param_values', 'delta', creator=lambda k, v: ParamValue(key=k, delta=v)) + + def __repr__(self): + return f'Model(id={repr(self.id)}, job_id={repr(self.job_id)}, model={repr(self.model)})' + + def as_dict(self): + """ + object properties in a dictionary. + + the dictionary keys correspond to the column names of numpy arrays. + the mapping db_field -> column name is declared in pmsco.database.util.DB_SPECIAL_PARAMS + + @return: (dict) + """ + d = {'_db_model_id': self.id} + for attr, key in db_util.DB_SPECIAL_PARAMS.items(): + try: + d[key] = getattr(self, attr) + except AttributeError: + pass + return d + + +class Result(Base): + """ + database object representing a calculation result + + the result object holds the calculated R-factor per job and calculation index. + + the calculation index (CalcID) is not unique in the database because it may contain results from multiple jobs. + thus, the object links to a Model object which is unique. + the calc_id property can be used to reconstruct a CalcID. + """ + + ## @var id + # (int, primary key) database id of the result + + ## @var model_id + # (int, foreign key) database id of the related model + + ## @var model + # associated Model object + # + # defines the relationship between Result and Model objects. + # + # @attention do not confuse the Result.model and Model.model attributes of same name! + # to obtain the model number to which a result belongs, use Result.model.model. + + ## @var scan + # (int) scan index as used in the calculations + + ## @var domain + # (int) domain index as used in the calculations + + ## @var emit + # (int) emitter index as used in the calculations + + ## @var region + # (int) region index as used in the calculations + + ## @var rfac + # (float) calculated R-factor + + ## @var timestamp + # (float) end date and time of this calculation task + # + # the float value represents seconds since jan 1, 1970 (datetime.datetime.timestamp). + # the datetime proxy converts to and from python datetime.datetime. + + ## @var datetime + # (datetime.datetime) end date and time of this calculation task + # + # this is a conversion proxy for timestamp. + + ## @var secs + # (float) total duration of the calculation task in seconds + # + # total cpu time necessary to get this result (including child tasks) in seconds. + + ## @var calc_id + # (CalcID) calculation task index + # + # conversion proxy for the task index components. + # + # on assignment, the scan, domain, emit and region attributes are updated. + # it does not update the model index as it is not stored by this object! + # the model index must be set separately in the linked Model object. + + __tablename__ = "Results" + id = Column(Integer, Sequence('result_id_seq'), primary_key=True) + model_id = Column(Integer, ForeignKey('Models.id'), index=True) + scan = Column(Integer, index=True) + domain = Column(Integer, index=True) + emit = Column(Integer, index=True) + region = Column(Integer, index=True) + rfac = Column(Float) + timestamp = Column(Float) + secs = Column(Float) + + model = relationship("Model", back_populates="results") + + def __init__(self, calc_id=None, scan=None, domain=None, emit=None, region=None, + rfac=None, timestamp=None, secs=None): + if calc_id is not None: + self.calc_id = calc_id + else: + self.scan = scan + self.domain = domain + self.emit = emit + self.region = region + self.rfac = rfac + self.timestamp = timestamp + self.secs = secs + + def __repr__(self): + return f'Result(model_id={repr(self.model_id)}, calc_id={repr(self.calc_id)}, rfac={repr(self.rfac)})' + + @property + def calc_id(self): + return CalcID(self.model.model, self.scan, self.domain, self.emit, self.region) + + @calc_id.setter + def calc_id(self, calc_id): + self.scan = calc_id.scan + self.domain = calc_id.domain + self.emit = calc_id.emit + self.region = calc_id.region + + @property + def datetime(self): + return datetime.datetime.fromtimestamp(self.timestamp) + + @datetime.setter + def datetime(self, value): + self.timestamp = value.timestamp() + + def as_dict(self): + """ + object properties in a dictionary. + + the dictionary keys correspond to the column names of numpy arrays. + the mapping db_field -> column name is declared in pmsco.database.util.D.B_SPECIAL_PARAMS + + @return: (dict) + """ + d = {'_db_result_id': self.id} + for attr, key in db_util.DB_SPECIAL_PARAMS.items(): + try: + d[key] = getattr(self, attr) + except AttributeError: + pass + return d + + +class Param(Base): + """ + database object representing a parameter + + the parameter object holds the name (or key) of a calculation parameter. + + explicit creation of parameter objects can be avoided by using the mappings of the Model class. + """ + ## @var id + # (int, primary key) database id of the parameter name + + ## @var key + # parameter name/key as used in calculations, should be very short, must be unique + + ## @var param_values + # collection of related ParamValue objects + # + # defines the relationship between Param and ParamValue objects. + + __tablename__ = "Params" + id = Column(Integer, Sequence('param_id_seq'), primary_key=True) + key = Column(String(20, collation='NOCASE'), nullable=False, unique=True) + + param_values = relationship('ParamValue', back_populates='param', cascade="all, delete, delete-orphan") + + def __init__(self, key): + self.key = key + + def __repr__(self): + return f'Param({repr(self.key)})' + + +class ParamValue(Base): + """ + association object class for parameter values + + Model - Param is a many-to-many relationship built using this association class. + by using the dictionary-like Model.values and Model.deltas proxies, + explicit creation of association objects can be avoided. + + the class applies the + [UniqueObjectValidateOnPending pattern](https://github.com/sqlalchemy/sqlalchemy/wiki/UniqueObjectValidatedOnPending) + to look up existing params in the database when a Param object is needed and only the key is given. + """ + ## @var id + # (int, primary key) database id of the parameter value + + ## @var param_id + # (int, foreign key) database id of the related parameter name + + ## @var model_id + # (int, foreign key) database id of the related model + + ## @var value + # (float) numeric value of the parameter + + ## @var delta + # (float) numeric delta value of the parameter (reported by some optimizers) + + ## @var param + # associated Param object + # + # defines the relationship between ParamValue and Param objects + + ## @var model + # associated Model object + # + # defines the relationship between ParamValue and Model objects + + ## @var param_key + # key (name) of the asscoiated Param object + # + # this is an association proxy that provides direct access to param.key. + # it accesses or creates Param objects behind the scenes. + + __tablename__ = "ParamValues" + id = Column(Integer, Sequence('paramvalue_id_seq'), primary_key=True) + param_id = Column(Integer, ForeignKey('Params.id'), index=True) + model_id = Column(Integer, ForeignKey('Models.id'), index=True) + value = Column(Float) + delta = Column(Float) + + param = relationship("Param", back_populates="param_values") + model = relationship("Model", back_populates="param_values") + + param_key = association_proxy('param', 'key') + + def __init__(self, model=None, param=None, key=None, value=None, delta=None): + if model is not None: + self.model = model + if param is not None: + self.param = param + elif key is not None: + self.param_key = key + self.value = value + self.delta = delta + + @validates("param") + def _validate_param(self, key, value): + """ + receive the event that occurs when `paramvalue.param` is set. + + if the object is present in a Session, then make sure it's the Param + object that we looked up from the database. + + otherwise, do nothing and we'll fix it later when the object is put into a Session. + """ + sess = object_session(self) + if sess is not None: + return _setup_param(sess, value) + else: + return value + + +@event.listens_for(Session, "transient_to_pending") +def _validate_param(session, object_): + """ + receive a ParamValue object when it gets attached to a Session to correct its unique Param relationship. + """ + if isinstance(object_, ParamValue): + if object_.param is not None and object_.param.id is None: + old_param = object_.param + new_param = _setup_param(session, object_.param) + if new_param is not old_param: + if old_param in session: + session.expunge(old_param) + object_.param = new_param + + +def _setup_param(session, param_object): + """ + given a Session and a Tag object, return the correct Tag object from the database. + """ + with session.no_autoflush: + try: + return session.query(Param).filter_by(key=param_object.key).one() + except NoResultFound: + return param_object + + +@event.listens_for(Engine, "connect") +def set_sqlite_pragma(dbapi_connection, connection_record): + """ + set sqlite pragmas. + + make sure sqlite enforces relational integrity. + + @param dbapi_connection: + @param connection_record: + @return: + """ + cursor = dbapi_connection.cursor() + cursor.execute("PRAGMA foreign_keys=ON") + cursor.close() + + +def sqlite_link(path=None): + """ + format the sqlalchemy link to an sqlite3 database. + + @param path: file path. if empty, an in-memory database is created. + @return: (str) database link for the sqlalchemy engine. + """ + if not path: + path = ':memory:' + return f'sqlite:///{path}' + + +def connect(db_link): + """ + connect to the database. + + create the sqlalchemy engine and bind the session maker. + the database engine and session maker are global. + this function should be called only once in a process. + + @param db_link: (str) database link expected by the sqlalchemy engine + @return: None + """ + global engine + engine = create_engine(db_link, echo=False) + Base.metadata.create_all(engine) + Session.configure(bind=engine) diff --git a/pmsco/database/project.py b/pmsco/database/project.py new file mode 100644 index 0000000..6974a3f --- /dev/null +++ b/pmsco/database/project.py @@ -0,0 +1,158 @@ +""" +@package pmsco.database.project +wrapper class for project-specific database operations + + +usage: +~~~~~~{.py} +db = DatabaseAccess() +db.connect("file.db") +with db.session(): + # database access here + # ... + # commit transaction + session.commit() + # continue in new transaction + # ... + +# at the end of the context +# the session is closed and orm objects are detached from the database. +~~~~~~ + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016-21 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import datetime +import logging +import socket +from pmsco.database.access import DatabaseAccess +import pmsco.database.common as db_common +import pmsco.database.ingest as db_ingest +import pmsco.database.query as db_query +from pmsco.dispatch import mpi_size + +logger = logging.getLogger(__name__) + + +class ProjectDatabase(DatabaseAccess): + """ + wrapper class for project specific database operations + + the purpose of this class is to bundle all specific code and run-time information + for database access of a running calculation job. + + after calling ingest_project_metadata(), + the class object stores the persistent project and job identifiers. + the other methods provide convenient wrappers so that database code can be kept minimal in the project. + + usage: + ~~~~~~{.py} + db = ProjectDatabase() + db.connect('file.db') + db.ingest_project_metadata(...) + for result in results: + db.ingest_result(result...) + ~~~~~~ + """ + + def __init__(self): + super().__init__() + self.db_project_id = None + self.db_job_id = None + + def ingest_project_metadata(self, project): + """ + ingest project metadata into the database + + @param project: pmsco.project.Project object + + @return: None + """ + with self.session() as session: + db_project = db_common.register_project(session=session, + name=project.project_name, + code=project.__module__, + allow_existing=True) + + db_job = db_common.register_job(session=session, + project=db_project, + job_name=project.job_name, + allow_existing=False, + mode=project.mode, + machine=socket.gethostname(), + git_hash=project.git_hash, + datetime=datetime.datetime.now(), + processes=mpi_size, + hours=project.timedelta_limit.total_seconds() / 3600., + description=project.description) + + db_common.register_job_tags(session, db_job, project.job_tags) + db_common.register_params(session, project.model_space.start.keys()) + session.commit() + + self.db_project_id = db_project.id + self.db_job_id = db_job.id + + def ingest_result(self, index, result, delta): + """ + add or update a result in the database. + + the method updates the Models, Results and ParamValues tables. + + the model is identified by self.job_id and index.model. + the result is identified by self.job_id and index. + if the model or result exists in the database, it is updated. + + @param index: (pmsco.dispatch.CalcID or dict) + calculation index. + in case of dict, the keys must be the attribute names of CalcID prefixed with an underscore, i.e., + '_model', '_scan', '_domain', '_emit', '_region'. + extra values in the dictionary are ignored. + undefined indices must be -1. + + @param result: (dict) dictionary containing the parameter values and the '_rfac' result. + may also contain the special values '_gen', '_particle', '_timestamp'. + '_gen' and '_particle' are integers and default to None. + '_timestamp' can be numeric (seconds since jan 1, 1970) + or an object that implements a timestamp function like datetime.datetime. + it defaults to the current (local) time. + + @param delta: (dict) dictionary containing the delta values. + the keys must correspond to model keys in the result dictionary. + this argument is optional. + """ + assert self.db_project_id is not None + assert self.db_job_id is not None + with self.session() as session: + job_obj = db_common.get_job(session, self.db_project_id, self.db_job_id) + model_obj = db_ingest.store_model(session, job_obj, index, result) + db_ingest.store_result_data(session, model_obj, index, result) + db_ingest.store_param_values(session, model_obj, result, delta) + session.commit() + + def query_best_task_models(self, level, count): + """ + query N best models per task. + + this is a wrapper for pmsco.database.query.query_best_task_models(). + in addition to the wrapped function, it opens a session and uses the registered db_job_id. + + this query is used by the file tracker to determine the models to keep. + + @param level: level up to which to query. + the level can be specified by level name (str) or numeric index (0..4). + if it is scan (equivalent to 1), the method queries the model and scan levels. + @param count: number of models to query per task. + + @return set of matching model numbers (model index, Models.model field). + """ + with self.session() as session: + models = db_query.query_best_task_models(session, self.db_job_id, level, count) + + return models diff --git a/pmsco/database/query.py b/pmsco/database/query.py new file mode 100644 index 0000000..b08ab0f --- /dev/null +++ b/pmsco/database/query.py @@ -0,0 +1,470 @@ +""" +@package pmsco.database.query +specialized query functions for the pmsco database + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016-21 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import logging +import numpy as np +from sqlalchemy import func +import pmsco.database.orm as orm +import pmsco.database.util as util +import pmsco.dispatch as dispatch + +logger = logging.getLogger(__name__) + + +def query_newest_job(session): + """ + retrieve the entry of the newest job + + the newest entry is determined by the datetime field. + + @param session: + + @return: pmsco.database.orm.Job object + """ + q = session.query(orm.Job) + q = q.order_by(orm.Job.datetime.desc(), orm.Job.id.desc()) + job = q.first() + return job + + +def query_model(session, job_id=None, model_id=None, model=None): + """ + retrieve model parameters and control variables from the database. + + @param model_id: id of the model in the database. + + @return: (dict, dict) value dictionary and delta dictionary. + dictionary keys are parameter values. + the special value '_model' is included. + """ + query = session.query(orm.ParamValue) + if job_id is not None: + query = query.filter(orm.Job.id == job_id) + if model_id is not None: + query = query.filter(orm.Model.id == model_id) + if model is not None: + query = query.filter(orm.Model.model == model) + result = query.all() + + param_value = {} + param_delta = {} + model_obj = None + for pv in result: + if model_obj is None: + model_obj = pv.model + param_value[pv.param.key] = pv.value + param_delta[pv.param.key] = pv.delta + + param_value['_model_id'] = model_obj.id + param_value['_model'] = model_obj.model + param_value['_gen'] = model_obj.gen + param_value['_particle'] = model_obj.particle + param_delta['_model_id'] = model_obj.id + param_delta['_model'] = model_obj.model + param_delta['_gen'] = model_obj.gen + param_delta['_particle'] = model_obj.particle + + return param_value, param_delta + + +def query_results(session, job_id): + query = session.query(orm.Result) + query = query.join(orm.Model) + query = query.filter(orm.Job == job_id) + return None + + +def query_tasks(session, job_id): + """ + query the task index used in a calculation job. + + this query neglects the model index + and returns the unique tuples (-1, scan, domain, emit, region). + + @param job_id: (int) id of the associated Jobs entry. + + @return list of pmsco.dispatch.CalcID tuples of task indices. + the model attribute is -1 in all elements. + """ + query = session.query(orm.Result.scan, orm.Result.domain, orm.Result.emit, orm.Result.region) + query = query.join(orm.Model) + query = query.filter(orm.Model.job_id == job_id) + query = query.distinct() + query = query.order_by(orm.Result.scan, orm.Result.domain, orm.Result.emit, orm.Result.region) + results = query.all() + + output = [] + for row in results: + d = row._asdict() + d['model'] = -1 + output.append(dispatch.CalcID(**d)) + + return output + + +def query_best_task_models(session, job_id, level, count): + """ + query N best models per task. + + this query is used by the file tracker to determine the models to keep. + + @param job_id: (int) id of the associated Jobs entry. + @param level: level up to which to query. + the level can be specified by level name (str) or numeric index (0..4). + if it is scan (equivalent to 1), the method queries the model and scan levels. + @param count: number of models to query per task. + + @return set of matching model numbers (Models.model field). + """ + + try: + level = int(level) + except ValueError: + level = dispatch.CALC_LEVELS.index(level) + assert 0 <= level < len(dispatch.CALC_LEVELS) + + def _query_models(t): + query = session.query(orm.Model.model).join(orm.Job).join(orm.Result) + query = query.filter(orm.Job.id == job_id) + query = query.filter(orm.Result.scan == t.scan) + query = query.filter(orm.Result.domain == t.domain) + query = query.filter(orm.Result.emit == t.emit) + query = query.filter(orm.Result.region == t.region) + query = query.order_by(orm.Result.rfac) + results = query[0:count] + return set((row.model for row in results)) + + tasks = query_tasks(session, job_id) + models = set() + for task in tasks: + if task.numeric_level <= level: + q_models = _query_models(task) + models |= q_models + + return models + + +def query_model_params_array(session, jobs=None, models=None, order=None, limit=None): + """ + query parameter values and return them in a numpy array + + the models table can be filtered by job and/or model. + else, the whole database is returned (which might be huge!). + + @param session: + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + @param models: filter by model. + the argument can be a singleton or sequence of orm.Model objects or their id. + @param order: ordering of results. this can be a sequence of orm.Model attributes. + the default order is by job_id and model. + @param limit: maximum number of models to return + @return: dict['values']: numpy values array, dict['deltas']: numpy deltas array + """ + count_query = session.query(orm.Model) + pn_query = session.query(orm.Param.key) + pv_query = session.query(orm.ParamValue) + + if jobs: + try: + jobs = [int(jobs)] + except TypeError: + pass + job_ids = [j if isinstance(j, int) else j.id for j in jobs] + count_query = count_query.filter(orm.Model.job_id.in_(job_ids)) + pn_query = pn_query.filter(orm.Model.job_id.in_(job_ids)) + pv_query = pv_query.filter(orm.Model.job_id.in_(job_ids)) + + if models: + try: + models = [int(models)] + except TypeError: + pass + model_ids = [m if isinstance(m, int) else m.id for m in models] + count_query = count_query.filter(orm.ParamValue.model_id.in_(model_ids)) + pn_query = pn_query.filter(orm.ParamValue.model_id.in_(model_ids)) + pv_query = pv_query.filter(orm.ParamValue.model_id.in_(model_ids)) + + if order is not None: + pv_query = pv_query.order_by(*order) + else: + pv_query = pv_query.order_by(orm.Model.job_id, orm.Model.model) + if limit: + pv_query = pv_query[0:limit] + + n_models = count_query.count() + param_names = pn_query.all() + param_values = pv_query.all() + + special_names = orm.Model().as_dict().keys() + dt_names = special_names + param_names + dt = np.dtype([(n, util.field_to_numpy_type(n)) for n in sorted(dt_names, key=str.lower)]) + values = np.zeros((n_models,), dtype=dt) + deltas = np.zeros((n_models,), dtype=dt) + + for i, pv in enumerate(param_values): + for k, v in pv.model.as_dict(): + values[i][k] = deltas[i][k] = v + values[i][pv.param_key] = pv.value + deltas[i][pv.param_key] = pv.delta + + return {'values': values, 'deltas': deltas} + + +calc_id_props = {'model': orm.Model.model, + 'scan': orm.Result.scan, + 'domain': orm.Result.domain, + 'emit': orm.Result.emit, + 'region': orm.Result.region} + + +def query_model_results_array(session, jobs=None, models=None, order=None, limit=None, + query_hook=None, hook_data=None, include_params=False, **index): + """ + query a results table with flexible filtering options + + the function returns a structured numpy array of the results and, optionally, parameter values. + the database is fully flattened, row of the array represents one result. + + the jobs and models arguments filter for specific jobs and/or models. + + custom filters can be added in a query hook function. + the hook function receives an sqlalchemy Query object of the Result table, + joined with the Model and Job tables. + other joins must be added explicitly. + the hook function can add more filters and return the modified query. + + the hook function is called after the filters from the other function arguments + (job, models, index) have been applied, + and before the ordering and limit are applied. + + @param session: + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + @param models: filter by model. + the argument can be a singleton or sequence of orm.Model objects or their id. + @param order: ordering of results. this can be a sequence of orm.Result attributes. + the default order is by `orm.Result.rfac`. + to override the default ascending order, append a modifier, e.g., `orm.Result.rfac.desc()`. + @param limit: maximum number of models to return + @param query_hook: hook function that modifies an sqlalchemy.orm.Query object. + the function receives the query as first argument, and any data from hook_data as keyword arguments. + it must return the modified query object. + @param hook_data: (dict) keyword arguments to be passed to the query_hook function. + @param include_params: include parameter values of each model in the result. + by default, only data from the Model and Result records is included. + @param index: filters the results list by scan, domain, emit, and/or region index. + for example, to get only the final results per model, specify `scan=-1`. + @return: numpy values array + """ + results_query = session.query(orm.Result).join(orm.Model).join(orm.Job) + + if jobs: + results_query = filter_objects(results_query, orm.Job, jobs) + + if models: + results_query = filter_objects(results_query, orm.Model, models) + + for k, v in index.items(): + results_query = results_query.filter(calc_id_props[k] == v) + + if query_hook is not None: + results_query = query_hook(results_query, **hook_data) + + if order is not None: + results_query = results_query.order_by(*order) + if limit: + results = results_query[0:limit] + else: + results = results_query.all() + n_results = len(results) + logger.debug(f"query_model_results_array: {results_query.statement} ({n_results} rows)") + + dt_names = [n for n in util.DB_SPECIAL_PARAMS.values()] + if include_params: + model_ids = {r.model_id for r in results} + pn_query = session.query(orm.Param.key).join(orm.ParamValue) + pn_query = pn_query.filter(orm.ParamValue.model_id.in_(model_ids)) + pn_query = pn_query.distinct() + pn_query = pn_query.order_by(orm.Param.key) + p_names = [r.key for r in pn_query.all()] + dt_names.extend(p_names) + logger.debug(f"query_model_results_array: {pn_query.statement} ({len(p_names)} rows)") + + dt = [] + v0 = [] + for n in dt_names: + ft = util.field_to_numpy_type(n) + dt.append((n, ft)) + v0.append(np.nan if ft[0] == 'f' else 0) + dt = np.dtype(dt) + v0 = np.array([tuple(v0)], dtype=dt) + values_array = np.full((n_results,), v0, dtype=dt) + deltas_array = np.full((n_results,), v0, dtype=dt) + + for i, r in enumerate(results): + d = {**r.as_dict(), **r.model.as_dict()} + for k, v in d.items(): + try: + values_array[i][k] = v + except TypeError: + values_array[i][k] = 0 + deltas_array[i] = values_array[i] + if include_params: + for k, v in r.model.values.items(): + values_array[i][k] = v + for k, v in r.model.deltas.items(): + deltas_array[i][k] = v + + return values_array, deltas_array + + +def query_best_models_per_job(session, projects=None, jobs=None, task_level='model', order=None, limit=None): + """ + return the best model (by rfac) of each selected job + + the query gathers the R-factors of the selected jobs at the selected task levels + and, for each job, returns the (database) model id where the lowest R-factor is reported + among the gathered results. + + this can be useful if you want to compile a report of the best model per job. + + @param session: + @param projects: filter by project. + the argument can be a singleton or sequence of orm.Project objects or numeric id. + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + @param task_level: element of or index into @ref pmsco.dispatch.CALC_LEVELS. + deepest task_level to include in the query. + results on deeper levels are not considered. + e.g. if you pass 'scan', R-factors of individual scans are included in the query. + note that including deeper levels will not increase the number of results returned. + the lowest level that can be specified is `emit`. + @param order: ordering of results. this can be a sequence of orm.Result attributes. + the default order is by `orm.Result.rfac`. + @param limit: maximum number of models to return + + @return sequence of (orm.Model, orm.Result) tuples. + the number of results corresponds to the number of jobs in the filter scope. + to find out details of the models, execute another query that filters on these model ids. + + the method produces an SQL query similar to: + @code{.sql} + select Models.id from Models + join Results on Models.id = Results.model_id + join Jobs on Models.job_id = Jobs.id + where scan=-1 + and project_id=1 + and job_id in (1,2,3) + group by Models.job_id + having min(rfac) + order by rfac + @endcode + """ + + try: + level = dispatch.CALC_LEVELS.index(task_level) + 1 + except ValueError: + level = task_level + 1 + try: + level_name = dispatch.CALC_LEVELS[level] + except IndexError: + level_name = dispatch.CALC_LEVELS[4] + + query = session.query(orm.Model, orm.Result).join(orm.Result) + + if projects: + query = filter_objects(query, orm.Project, projects) + + if jobs: + query = filter_objects(query, orm.Job, jobs) + + query = query.filter(getattr(orm.Result, level_name) == -1) + query = query.group_by(orm.Model.job_id) + query = query.having(func.min(orm.Result.rfac)) + + if order is not None: + query = query.order_by(*order) + else: + query = query.order_by(orm.Result.rfac) + if limit: + query = query[0:limit] + else: + query = query.all() + + return query + + +def filter_objects(query, entity, objects): + """ + filter a query for the given objects + + apply a simple object filter to a database query. + the criteria can be a single object or a sequence of objects. + the objects can be specified either by their object representation or numeric id. + the query is filtered by id. + thus, in the first case, the objects must have a valid id. + + @param query: sqlalchemy.orm.Query object that queries a table that is linked to the entity table. + the function joins the entity table. + a table with a direct foreign key relationship to the entity table must already be in the query. + @param entity: orm entity class, e.g. pmsco.database.orm.Project. + @param objects: singleton or sequence of orm objects or their numeric ids. + + @return: modified query + """ + # avoid duplicate joins + if str(query.statement).find(entity.__tablename__) < 0: + query = query.join(entity) + try: + objects = [p if isinstance(p, int) else p.id for p in objects] + query = query.filter(entity.id.in_(objects)) + except TypeError: + object = objects if isinstance(objects, int) else objects.id + query = query.filter(entity.id == object) + return query + + +def filter_task_levels(query, level='model', include_parents=False): + """ + refine a query by filtering by task level. + + @param query: sqlalchemy.orm.Query object that queries the Result table + (possibly joined with others). + @param level: element of or index into @ref pmsco.dispatch.CALC_LEVELS. + deepest task_level to include in the query. + results on deeper levels are not considered. + e.g. if you pass 'scan', R-factors of individual scans are included in the query. + the lowest level that can be specified is `emit`. + @param include_parents: by default, the query will return only results from the given level. + if True, combined results (parents) will be returned as well. + """ + + try: + level = dispatch.CALC_LEVELS.index(level) + except ValueError: + level = int(level) + child_level = level + 1 + + try: + child_level_name = dispatch.CALC_LEVELS[child_level] + level_name = dispatch.CALC_LEVELS[level] + except IndexError: + child_level_name = dispatch.CALC_LEVELS[4] + level_name = dispatch.CALC_LEVELS[3] + + query = query.filter(getattr(orm.Result, child_level_name) == -1) + if not include_parents: + query = query.filter(getattr(orm.Result, level_name) >= 0) + + return query diff --git a/pmsco/database/util.py b/pmsco/database/util.py new file mode 100644 index 0000000..68c4fd8 --- /dev/null +++ b/pmsco/database/util.py @@ -0,0 +1,161 @@ +import logging +import numpy as np +from pathlib import Path +import pmsco.dispatch as dispatch + +logger = logging.getLogger(__name__) + + +## mapping of database fields to special parameter names +# +# `_db` parameters are returned by some query methods to identify the database records. +# +DB_SPECIAL_PARAMS = {"project_id": "_db_project_id", + "job_id": "_db_job_id", + "model_id": "_db_model_id", + "result_id": "_db_result_id", + "model": "_model", + "scan": "_scan", + "domain": "_domain", + "emit": "_emit", + "region": "_region", + "gen": "_gen", + "particle": "_particle", + "rfac": "_rfac", + "secs": "_secs", + "timestamp": "_timestamp"} + + +## numpy data types of special parameters by database field +# +# this dictionary helps to create a numpy array from a database record. +# +DB_SPECIAL_NUMPY_TYPES = {"_db_project_id": "i8", + "_db_job_id": "i8", + "_db_model_id": "i8", + "_db_result_id": "i8", + "_model": "i8", + "_scan": "i8", + "_domain": "i8", + "_emit": "i8", + "_region": "i8", + "_gen": "i8", + "_particle": "i8", + "_rfac": "f8", + "_secs": "f8", + "_timestamp": "f8"} + + +def regular_params(d): + """ + filter regular parameters from dictionary + + returns a dictionary containing only the regular parameters (those not prefixed with an underscore). + + @param d: dict or numpy.void or pmsco.dispatch.CalcID. + the param names must have no leading underscore. + the numpy.void type occurs when an element of a structured array is extracted. + the CalcID does not contain a regular parameter and will return an empty dictionary. + it is supported only for compatibility with special_params function. + a tuple or list is interpreted as a sequence of parameter names. + in this case the names representing special parameters are returned with underscore removed. + + @return: dict for mapping types (numpy and dict) containing the regular key: value pairs of the original object. + list (tuple) of parameter names for sequence (tuple) types. + leading underscores are removed from key names. + """ + if isinstance(d, np.void): + d = {k: d[k] for k in d.dtype.names if k[0] != "_"} + elif isinstance(d, dispatch.CalcID): + d = {} + elif isinstance(d, tuple): + d = [k for k in d if k[0] != "_"] + d = tuple(d) + elif isinstance(d, dict): + d = {k: v for k, v in d.items() if k[0] != "_"} + else: + d = [k for k in d if k[0] != "_"] + + return d + + +def special_params(d): + """ + filter special parameters from model dictionary, numpy record or sequence. + + special parameters are those prefixed with an underscore. + the underscore is removed from the keys. + fields starting with '_db_' are removed. + + @param d: dict or numpy.void or pmsco.dispatch.CalcID or sequence. + in the case of a dict or numpy.void, + the key names of the special parameters must have a leading underscore. + the numpy.void type occurs when an element of a structured array is extracted. + in the case of a CalcID, the attribute names become the key names. + a tuple or list is interpreted as a sequence of parameter names. + in this case the names representing special parameters are returned with underscore removed. + + @return + the return type depends on the type of input `d`: + @arg in the case of a dict, numpy.void or CalcID it is a dictionary. + @arg in the case of a tuple or list the return type is the same as the input. + """ + if isinstance(d, np.void): + d = {k[1:]: d[k] for k in d.dtype.names if k[0] == "_" and k[0:4] != "_db_"} + elif isinstance(d, dispatch.CalcID): + d = d._asdict() + elif isinstance(d, tuple): + d = [k[1:] for k in d if k[0] == "_" and k[0:4] != "_db_"] + d = tuple(d) + elif isinstance(d, dict): + d = {k[1:]: v for k, v in d.items() if k[0] == "_" and k[0:4] != "_db_"} + else: + d = [k[1:] for k in d if k[0] == "_" and k[0:4] != "_db_"] + + return d + + +def field_to_param(f): + """ + translate database field name to parameter name. + + field names of optimization parameters are unchanged. + special parameters are prefixed by '_' or '_db_'. + + @param f: (str) database field name. + @return: (str) parameter name as used in model dictionaries. + """ + try: + p = DB_SPECIAL_PARAMS[f] + except KeyError: + p = f + return p + + +def field_to_numpy_type(f): + """ + determine the numpy data type string of a database field. + + @param f: (str) database field name. + @return: (str) numpy type description, e.g. 'f8'. + """ + try: + t = DB_SPECIAL_NUMPY_TYPES[f] + except KeyError: + t = 'f8' + return t + + +def is_sqlite3_file(path_like): + """ + test whether a file is an sqlite3 database file. + + @param path_like: file path (str or pathlib.Path). + @return: (bool) + """ + try: + with Path(path_like).open("rb") as f: + s = f.read(16) + return s == b"SQLite format 3\000" + except OSError: + return False diff --git a/pmsco/dispatch.py b/pmsco/dispatch.py index bfaea77..e570cc0 100644 --- a/pmsco/dispatch.py +++ b/pmsco/dispatch.py @@ -19,8 +19,6 @@ import collections import copy import logging -from attrdict import AttrDict - try: from mpi4py import MPI mpi_comm = MPI.COMM_WORLD @@ -190,6 +188,15 @@ class CalculationTask(object): # this is typically initialized to the parameters of the parent task, # and varied at the level where the task ID was produced. + ## @var delta (dict) + # dictionary containing a delta vector of the model parameters. + # + # this is a diagnostic value of the optimizer, it is not used by calculators. + # if defined, it is entered into the results database (ParamValue.delta field). + # + # the exact meaning depends on the optimizer. + # in particle swarm, e.g., it is the current velocity of the particle. + ## @var file_root (string) # file name without extension and index. @@ -258,6 +265,7 @@ class CalculationTask(object): self.id = CalcID(-1, -1, -1, -1, -1) self.parent_id = self.id self.model = {} + self.delta = {} self.file_root = "" self.file_ext = "" self.result_filename = "" @@ -500,6 +508,16 @@ class CachedCalculationMethod(object): del self._cache[index] +class AttrDict(collections.UserDict): + def __getattr__(self, key): + return self.__getitem__(key) + + def __setattr__(self, key, value): + if key == "data": + return super().__setattr__(key, value) + return self.__setitem__(key, value) + + class MscoProcess(object): """ code shared by MscoMaster and MscoSlave. diff --git a/pmsco/edac/.gitignore b/pmsco/edac/.gitignore deleted file mode 100644 index 669e543..0000000 --- a/pmsco/edac/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -edac_all_wrap.* -edac.py diff --git a/pmsco/edac/__init__.py b/pmsco/edac/__init__.py deleted file mode 100644 index 1233160..0000000 --- a/pmsco/edac/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'muntwiler_m' diff --git a/pmsco/edac/makefile b/pmsco/edac/makefile deleted file mode 100644 index 148f05a..0000000 --- a/pmsco/edac/makefile +++ /dev/null @@ -1,47 +0,0 @@ -SHELL=/bin/sh - -# makefile for EDAC program and module -# -# the EDAC source code is not included in the public distribution. -# please obtain it from the original author, -# copy it to this directory, -# and apply the edac_all.patch patch before compilation. -# -# see the top-level makefile for additional information. - -.SUFFIXES: -.SUFFIXES: .c .cpp .cxx .exe .f .h .i .o .py .pyf .so -.PHONY: all clean edac - -FC?=gfortran -FCCOPTS?= -F2PY?=f2py -F2PYOPTS?= -CXX?=g++ -CXXOPTS?=-Wno-write-strings -PYTHON?=python -PYTHONOPTS?= - -all: edac - -edac: edac.exe _edac.so edac.py - -edac.exe: edac_all.cpp - $(CXX) $(CXXOPTS) -o edac.exe edac_all.cpp - -edac.py _edac.so: edac_all.cpp edac_all.i setup.py - $(PYTHON) $(PYTHONOPTS) setup.py build_ext --inplace - -revision.py: _edac.so - git log --pretty=format:"code_rev = 'Code revision %h, %ad'" --date=iso -1 > $@ || echo "code_rev = 'Code revision unknown, "`date +"%F %T %z"`"'" > $@ - echo "" >> revision.py - -revision.txt: _edac.so edac.exe - git log --pretty=format:"Code revision %h, %ad" --date=iso -1 > $@ || echo "Code revision unknown, "`date +"%F %T %z"` > $@ - echo "" >> revision.txt - -clean: - rm -f *.so *.o *.exe *.pyc - rm -f edac.py edac_all_wrap.* - rm -f revision.* - diff --git a/pmsco/edac/setup.py b/pmsco/edac/setup.py deleted file mode 100644 index 74e2fe4..0000000 --- a/pmsco/edac/setup.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python - -""" -setup.py file for EDAC -""" - -from distutils.core import setup, Extension - - -edac_module = Extension('_edac', - sources=['edac_all.cpp', 'edac_all.i'], - swig_opts=['-c++'] - ) - -setup (name = 'edac', - version = '0.1', - author = "Matthias Muntwiler", - description = """EDAC module in Python""", - ext_modules = [edac_module], - py_modules = ["edac"], - requires=['numpy'] - ) - diff --git a/pmsco/elements/__init__.py b/pmsco/elements/__init__.py deleted file mode 100644 index 43a2718..0000000 --- a/pmsco/elements/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -@package pmsco.elements -extended properties of the elements - -this package extends the element table of the `periodictable` package -(https://periodictable.readthedocs.io/en/latest/index.html) -by additional attributes like the electron binding energies. - -the package requires the periodictable package (https://pypi.python.org/pypi/periodictable). - - -@author Matthias Muntwiler - -@copyright (c) 2020 by Paul Scherrer Institut @n -Licensed under the Apache License, Version 2.0 (the "License"); @n - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -""" - -import periodictable.core - - -def _load_binding_energy(): - """ - delayed loading of the binding energy table. - """ - from . import bindingenergy - bindingenergy.init(periodictable.core.default_table()) - - -def _load_photoionization(): - """ - delayed loading of the binding energy table. - """ - from . import photoionization - photoionization.init(periodictable.core.default_table()) - - -periodictable.core.delayed_load(['binding_energy'], _load_binding_energy) -periodictable.core.delayed_load(['photoionization'], _load_photoionization) diff --git a/pmsco/elements/bindingenergy.py b/pmsco/elements/bindingenergy.py index 6899690..6d0c866 100644 --- a/pmsco/elements/bindingenergy.py +++ b/pmsco/elements/bindingenergy.py @@ -1,22 +1,22 @@ """ @package pmsco.elements.bindingenergy -electron binding energies of the elements +Electron binding energies of the elements -extends the element table of the `periodictable` package +Extends the element table of the `periodictable` package (https://periodictable.readthedocs.io/en/latest/index.html) by the electron binding energies. -the binding energies are compiled from Gwyn Williams' web page +The binding energies are compiled from Gwyn Williams' web page (https://userweb.jlab.org/~gwyn/ebindene.html). -please refer to the original web page or the x-ray data booklet +Please refer to the original web page or the x-ray data booklet for original sources, definitions and remarks. -binding energies of gases are replaced by respective values of a common compound -from the 'handbook of x-ray photoelectron spectroscopy' (physical electronics, inc., 1995). +Binding energies of gases are replaced by respective values of a common compound +from the 'handbook of x-ray photoelectron spectroscopy' (Physical Electronics, Inc., 1995). -usage +Usage ----- -this module requires the periodictable package (https://pypi.python.org/pypi/periodictable). +This module requires the periodictable package (https://pypi.python.org/pypi/periodictable). ~~~~~~{.py} import periodictable as pt @@ -29,15 +29,16 @@ print(pt.elements.name('gold').binding_energy['4f7/2']) print(pt.elements[79].binding_energy['4f7/2']) ~~~~~~ -note that attributes are writable. -you may assign refined values in your instance of the database. +The database is loaded from the accompanying bindingenergy.json file on first demand. +Attributes are writable, you may update the values in your run-time instance of the database. -the query_binding_energy() function queries all terms with a particular binding energy. +Normally, the user will not need to call any functions in this module directly. +The query_binding_energy() function queries all terms with a particular binding energy. @author Matthias Muntwiler -@copyright (c) 2020 by Paul Scherrer Institut @n +@copyright (c) 2020-23 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -46,15 +47,15 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n import json import numpy as np -import os +from pathlib import Path import periodictable as pt -from pmsco.compat import open - +import periodictable.core index_energy = np.zeros(0) index_number = np.zeros(0) index_term = [] -default_data_path = os.path.join(os.path.dirname(__file__), "bindingenergy.json") + +default_data_path = Path(Path(__file__).parent, "bindingenergy.json") def load_data(data_path=None): @@ -63,13 +64,13 @@ def load_data(data_path=None): the data file must be in the same format as generated by save_data. - @param file path of the data file. default: "bindingenergy.json" next to this module file + @param data_path file path of the data file. default: "bindingenergy.json" next to this module file @return dictionary """ if data_path is None: data_path = default_data_path - with open(data_path) as fp: + with open(data_path, "rt", encoding="utf8") as fp: data = json.load(fp) return data @@ -78,7 +79,7 @@ def save_data(data_path=None): """ save binding energy data to json file - @param file path of the data file. default: "bindingenergy.json" next to this module file + @param data_path file path of the data file. default: "bindingenergy.json" next to this module file @return None """ @@ -91,7 +92,7 @@ def save_data(data_path=None): element_data[term] = energy if element_data: data[element.number] = element_data - with open(data_path, 'w', 'utf8') as fp: + with open(data_path, "w", encoding="utf8") as fp: json.dump(data, fp, sort_keys=True, indent='\t') @@ -120,6 +121,7 @@ def build_index(): @return None """ + global index_energy global index_number global index_term @@ -210,3 +212,14 @@ def import_flat_text(f): data = np.atleast_1d(np.genfromtxt(f, names=True, dtype=None, encoding="utf8")) for d in data: pt.elements[d['number']].binding_energy[d['term']] = d['energy'] + + +def _load_binding_energy(): + """ + delayed loading of the binding energy table. + """ + + init(periodictable.core.default_table()) + + +periodictable.core.delayed_load(['binding_energy'], _load_binding_energy) diff --git a/pmsco/elements/cross-sections.dat b/pmsco/elements/cross-sections.dat index b12e106..d40e5d5 100644 Binary files a/pmsco/elements/cross-sections.dat and b/pmsco/elements/cross-sections.dat differ diff --git a/pmsco/elements/photoionization.py b/pmsco/elements/photoionization.py index 3a5742b..616305b 100644 --- a/pmsco/elements/photoionization.py +++ b/pmsco/elements/photoionization.py @@ -1,63 +1,258 @@ """ @package pmsco.elements.photoionization -photoionization cross-sections of the elements +Photoionization cross-sections of the elements -extends the element table of the `periodictable` package +Extends the element table of the `periodictable` package (https://periodictable.readthedocs.io/en/latest/index.html) -by a table of photoionization cross-sections. +by a table of photoionization cross-sections and asymmetry parameters. -the data is available from (https://vuo.elettra.eu/services/elements/) +The data is available from (https://vuo.elettra.eu/services/elements/) or (https://figshare.com/articles/dataset/Digitisation_of_Yeh_and_Lindau_Photoionisation_Cross_Section_Tabulated_Data/12389750). -both sources are based on the original atomic data tables by Yeh and Lindau (1985). -the Elettra data includes interpolation at finer steps, -whereas the Kalha data contains only the original data points by Yeh and Lindau +Both sources are based on the original atomic data tables by Yeh and Lindau (1985). +The Elettra data includes the cross section and asymmetry parameter and is interpolated at finer steps, +whereas the Kalha data contains only the cross sections at the photon energies calculated by Yeh and Lindau plus an additional point at 8 keV. -the tables go up to 1500 eV photon energy and do not resolve spin-orbit splitting. +The tables go up to 1500 eV photon energy and do not resolve spin-orbit splitting. -usage +Usage ----- -this module requires python 3.6, numpy and the periodictable package (https://pypi.python.org/pypi/periodictable). +This module adds the photoionization attribute to the elements database of the periodictable package (https://pypi.python.org/pypi/periodictable). +Python >= 3.6, numpy >= 1.15 and the periodictable package are required. ~~~~~~{.py} import numpy as np import periodictable as pt import pmsco.elements.photoionization -# read any periodictable's element interfaces as follows. -# eph and cs are numpy arrays of identical shape that hold the photon energies and cross sections. -eph, cs = pt.gold.photoionization.cross_section['4f'] -eph, cs = pt.elements.symbol('Au').photoionization.cross_section['4f'] -eph, cs = pt.elements.name('gold').photoionization.cross_section['4f'] -eph, cs = pt.elements[79].photoionization.cross_section['4f'] +# get a SubShellPhotoIonization object from any of periodictable's element interface: +sspi = pt.gold.photoionization['4f'] +sspi = pt.elements.symbol('Au').photoionization['4f'] +sspi = pt.elements.name('gold').photoionization['4f'] +sspi = pt.elements[79].photoionization['4f'] -# interpolate for specific photon energy -print(np.interp(photon_energy, eph, cs) +# get the cross section, asymmetry parameter or differential cross section at 800 eV photon energy: +sspi.cross_section(800) +sspi.asymmetry_parameter(800) +sspi.diff_cross_section(800, gamma=30) + +# with the j quantum number, the cross-section is weighted based on a full sub-shell: +sspi = pt.gold.photoionization['4f7/2'] +print(sspi.weight) +print(pt.gold.photoionization['4f7/2'].cross_section(800) / pt.gold.photoionization['4f'].cross_section(800)) + +# the original data is contained in the data array (which is a numpy.recarray): +sspi.data.eph, sspi.data.cs, sspi.data.ap ~~~~~~ -the data is loaded from the cross-sections.dat file which is a python-pickled data file. -to switch between data sources, use one of the load functions defined here -and dump the data to the cross-sections.dat file. +The data is loaded on demand from the cross-sections.dat file when the photoionization record is first accessed. +Normally, the user will not need to call any functions in this module directly. + +The load_elettra_data()/load_kalha_data() and save_pickled_data() functions are provided +to import data from one of the sources referenced above and +to create the cross-sections.dat file. @author Matthias Muntwiler -@copyright (c) 2020 by Paul Scherrer Institut @n +@copyright (c) 2020-23 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 """ +import copy import numpy as np from pathlib import Path import periodictable as pt import pickle import urllib.request import urllib.error -from . import bindingenergy +import periodictable.core + + +class PhotoIonization(dict): + """ + photo-ionization parameters of an element + + this class provides the photo-ionization cross-section and asymmetry parameter of the sub-shells of an element. + it is, essentially, a dictionary, mapping 'nl' and 'nlj' terms to the corresponding SubShellPhotoIonization object. + + examples of 'nl' and 'nlj' terms: '4f' and '4f7/2' + + @note the dictionary actually contains raw data for 'nl' terms only. + for 'nlj' terms, the corresponding 'nl' object is copied, + and a weight according to the spin-orbit multiplicity is set. + + @note 'nlj' terms are not considered by any methods or properties + except the bracket notation or __getitem__ method! + in particular, iteration or the keys() method will yield 'nl' terms only. + """ + + def __init__(self, *args, **kwargs): + """ + dictionary constructor + + the class accepts the same arguments as the Python built-in dict constructor. + keys are 'nl' terms, e.g. '4f', and values must be SubShellPhotoIonization() objects. + + @param args: + @param kwargs: + """ + super().__init__(*args, **kwargs) + self.cross_section_units = "Mb" + + def __getitem__(self, k): + """ + get sub-shell photo-ionization data by 'nl' or 'nlj' term. + + @param k: dictionary key. + if this is an 'nl' term, the original object is returned. + if this is an 'nlj' term, a proxy of the corresponding 'nl' object + with shared data but weight based on j-branching is returned. + + @return: SubShellPhotoIonization() object + + @note whether the original or a proxy object is returned, + its data attribute always refers to the original data. + any modification will affect the original data (process memory). + """ + spi = super().__getitem__(k[0:2]) + if len(k) > 2: + spi = copy.copy(spi) + spi.set_spin_orbit(k[1:5]) + return spi + + +class SubShellPhotoIonization(object): + """ + Sub-shell photo-ionization parameters versus photon energy. + + this class provides the photo-ionization cross-section and asymmetry parameter of one sub-shell. + it contains a three-column record array of photon energy, cross section and asymmetry parameter in self.data. + accessory functions provide high-level access to specific views and interpolated data. + + a weighting factor self.weight is multiplied to the method results. + it is normally used to weight the spin-orbit peaks by calling set_spin_orbit(). + """ + SPIN_ORBIT_WEIGHTS = {"p1/2": 1. / 3., + "p3/2": 2. / 3., + "d3/2": 2. / 5., + "d5/2": 3. / 5., + "f5/2": 3. / 7., + "f7/2": 4. / 7.} + + def __init__(self, photon_energy, cross_section, asymmetry_parameter): + """ + initialize a new object instance. + + all arrays must have the same length. + + @param photon_energy: (array-like) photon energies + @param cross_section: (array-like) cross-section values + @param asymmetry_parameter: (array-like) asymmetry parameter values + """ + super().__init__() + self.data = np.rec.fromarrays([photon_energy, cross_section, asymmetry_parameter], names='eph, cs, ap') + self.weight = 1. + + def cross_section(self, photon_energy): + """ + interpolated sub-shell cross-section at a specific energy. + + the weighting factor self.weight (e.g. spin-orbit) is included in the result. + + @param photon_energy: photon energy in eV. + can be scalar or numpy array. + @return: cross-section in Mb. + numpy.nan where photon_energy is off range. + """ + cs = np.interp(photon_energy, self.data.eph, self.data.cs, left=np.nan, right=np.nan) * self.weight + return cs + + def asymmetry_parameter(self, photon_energy): + """ + interpolated asymmetry parameter at a specific energy. + + @param photon_energy: photon energy in eV. + can be scalar or numpy array. + @return: asymmetry parameter (0..2). + numpy.nan where photon_energy is off range. + """ + ap = np.interp(photon_energy, self.data.eph, self.data.ap, left=np.nan, right=np.nan) + return ap + + def diff_cross_section(self, photon_energy, gamma): + """ + differential cross-section for linear polarization. + + the weighting factor self.weight (e.g. spin-orbit) is included in the result. + + @param photon_energy: photon energy in eV. + @param gamma: angle between polarization vector and electron propagation direction in degrees. + @return: differential cross-section in Mb. + """ + p2 = (3 * np.cos(gamma) ** 2 - 1) / 2 + cs = self.cross_section(photon_energy) + ap = self.asymmetry_parameter(photon_energy) + dcs = cs / 4 / np.pi * (1 + ap * p2) + return dcs + + def photon_energy_array(self): + """ + photon energy array. + + the weighting factor self.weight (e.g. spin-orbit) is included in the result. + + @return: + """ + return self.data.eph + + def cross_section_array(self): + """ + sub-shell cross-section versus photon energy. + + the weighting factor self.weight (e.g. spin-orbit) is included in the result. + + @return: numpy.ndarray + """ + return self.data.cs * self.weight + + def asymmetry_parameter_array(self): + """ + sub-shell asymmetry parameter versus photon energy. + + the weighting factor self.weight (e.g. spin-orbit) is included in the result. + + @return: numpy.ndarray + """ + return self.data.ap + + def diff_cross_section_array(self, gamma): + """ + differential cross-section for linear polarization (full array). + + @param gamma: angle between polarization vector and electron propagation direction in degrees. + @return: (np.ndarray) differential cross-section in Mb. + """ + p2 = (3 * np.cos(gamma) ** 2 - 1) / 2 + dcs = self.data.cs / 4 / np.pi * (1 + self.data.ap * p2) * self.weight + return dcs + + def set_spin_orbit(self, lj): + """ + set the weight according to the spin-orbit quantum number (based on full sub-shell). + + the weight is stored in the self.weight attribute. + it is applied to the results of the cross-section methods, but not to the raw data in self.data! + + @param lj: (str) 4-character lj term notation, e.g. 'f7/2' + @return: None + """ + self.weight = self.SPIN_ORBIT_WEIGHTS.get(lj, 1.) def load_kalha_data(): @@ -98,7 +293,7 @@ def load_kalha_file(path): for l in 'spdf': col = f"{n}{l}" try: - data[col] = (eph, a[col].copy()) + data[col] = SubShellPhotoIonization(eph, a[col].copy(), np.zeros_like(eph)) except ValueError: pass return data @@ -138,24 +333,24 @@ def load_elettra_file(symbol, nl): @param symbol: (str) element symbol @param nl: (str) nl term, e.g. '2p' (no spin-orbit) - @return: (photon_energy, cross_section) tuple of 1-dimensional numpy arrays. + @return: PhotoIonizationData(photon_energy, cross_section, asymmetry_parameter) + named tuple of 1-dimensional numpy arrays. """ + spi = None + url = f"https://vuo.elettra.eu/services/elements/data/{symbol.lower()}{nl}.txt" try: data = urllib.request.urlopen(url) except urllib.error.HTTPError: - eph = None - cs = None + pass else: a = np.genfromtxt(data) try: - eph = a[:, 0] - cs = a[:, 1] + spi = SubShellPhotoIonization(a[:, 0], a[:, 1], a[:, 4]) except IndexError: - eph = None - cs = None + pass - return eph, cs + return spi def load_elettra_data(): @@ -171,9 +366,9 @@ def load_elettra_data(): nl = nlj[0:2] eb = element.binding_energy[nlj] if nl not in element_data and eb <= 2000: - eph, cs = load_elettra_file(element.symbol, nl) - if eph is not None and cs is not None: - element_data[nl] = (eph, cs) + spi = load_elettra_file(element.symbol, nl) + if spi is not None: + element_data[nl] = spi if len(element_data): data[element.symbol] = element_data @@ -212,15 +407,9 @@ def load_pickled_data(path): return data -class Photoionization(object): - def __init__(self): - self.cross_section = {} - self.cross_section_units = "Mb" - - def init(table, reload=False): """ - loads cross section data into the periodic table. + loads cross-section data into the periodic table. this function is called by the periodictable to load the data on demand. @@ -233,16 +422,25 @@ def init(table, reload=False): table.properties.append('photoionization') # default value - pt.core.Element.photoionization = Photoionization() + pt.core.Element.photoionization = PhotoIonization() p = Path(Path(__file__).parent, "cross-sections.dat") data = load_pickled_data(p) for el_key, el_data in data.items(): + # el_data is dict('nl': PhotoIonizationData) try: el = table[int(el_key)] except ValueError: el = table.symbol(el_key) - pi = Photoionization() - pi.cross_section = el_data - pi.cross_section_units = "Mb" - el.photoionization = pi + el.photoionization = PhotoIonization(el_data) + + +def _load_photoionization(): + """ + delayed loading of the binding energy table. + """ + + init(periodictable.core.default_table()) + + +periodictable.core.delayed_load(['photoionization'], _load_photoionization) diff --git a/pmsco/elements/spectrum.py b/pmsco/elements/spectrum.py index 1d770c1..b64da96 100644 --- a/pmsco/elements/spectrum.py +++ b/pmsco/elements/spectrum.py @@ -77,9 +77,9 @@ def get_binding_energy(photon_energy, element, nlj): return np.nan -def get_cross_section(photon_energy, element, nlj): +def get_cross_section(photon_energy, element, nlj, gamma=None): """ - look up the photoionization cross section. + look up the photo-ionization cross-section. since the Yeh/Lindau tables do not resolve the spin-orbit splitting, this function applies the normal relative weights of a full sub-shell. @@ -89,31 +89,28 @@ def get_cross_section(photon_energy, element, nlj): @param photon_energy: photon energy in eV. @param element: Element object of the periodic table. @param nlj: (str) spectroscopic term, e.g. '4f7/2'. - @return: (float) cross section in Mb. + the j-value can be left out, in which case the sum over all j-states is returned. + @param gamma: (float) angle in degrees between linear polarization vector and photoelectron emission direction. + By default (None), unpolarized light or magic angle (54.7 deg) geometry is assumed. + @return: (float) total (gamma=None) or differential (gamma not None) cross section in Mb. """ - nl = nlj[0:2] + if not hasattr(element, "photoionization"): element = get_element(element) try: - pet, cst = element.photoionization.cross_section[nl] + pi = element.photoionization[nlj] except KeyError: return np.nan - # weights of spin-orbit peaks - d_wso = {"p1/2": 1./3., - "p3/2": 2./3., - "d3/2": 2./5., - "d5/2": 3./5., - "f5/2": 3./7., - "f7/2": 4./7.} - wso = d_wso.get(nlj[1:], 1.) - cst = cst * wso + if gamma is None: + cs = pi.cross_section(photon_energy) + else: + cs = pi.diff_cross_section(photon_energy, gamma) - # todo: consider spline - return np.interp(photon_energy, pet, cst) + return cs -def build_spectrum(photon_energy, elements, binding_energy=False, work_function=4.5): +def build_spectrum(photon_energy, elements, binding_energy=False, work_function=4.5, gamma=None): """ calculate the positions and amplitudes of core-level photoemission lines. @@ -126,6 +123,8 @@ def build_spectrum(photon_energy, elements, binding_energy=False, work_function= if a dictionary is given, the (float) values are stoichiometric weights of the elements. @param binding_energy: (bool) return binding energies (True) rather than kinetic energies (False, default). @param work_function: (float) work function of the instrument in eV. + @param gamma: (float) angle in degrees between linear polarization vector and photoelectron emission direction. + By default (None), unpolarized light or magic angle (54.7 deg) geometry is assumed. @return: tuple (labels, positions, intensities) of 1-dimensional numpy arrays representing the spectrum. labels are in the format {Symbol}{n}{l}{j}. """ @@ -141,7 +140,7 @@ def build_spectrum(photon_energy, elements, binding_energy=False, work_function= for j in ['', '1/2', '3/2', '5/2', '7/2']: nlj = f"{n}{l}{j}" eb = get_binding_energy(photon_energy, el, nlj) - cs = get_cross_section(photon_energy, el, nlj) + cs = get_cross_section(photon_energy, el, nlj, gamma=gamma) try: cs = cs * elements[element] except (KeyError, TypeError): @@ -163,7 +162,7 @@ def build_spectrum(photon_energy, elements, binding_energy=False, work_function= return labels, ekin, intens -def plot_spectrum(photon_energy, elements, binding_energy=False, work_function=4.5, show_labels=True): +def plot_spectrum(photon_energy, elements, binding_energy=False, work_function=4.5, gamma=None, show_labels=True): """ plot a simple spectrum representation of a material. @@ -178,11 +177,13 @@ def plot_spectrum(photon_energy, elements, binding_energy=False, work_function=4 if a dictionary is given, the (float) values are stoichiometric weights of the elements. @param binding_energy: (bool) return binding energies (True) rather than kinetic energies (False, default). @param work_function: (float) work function of the instrument in eV. + @param gamma: (float) angle in degrees between linear polarization vector and photoelectron emission direction. + By default (None), unpolarized light or magic angle (54.7 deg) geometry is assumed. @param show_labels: (bool) show peak labels (True, default) or not (False). @return: (figure, axes) """ labels, energy, intensity = build_spectrum(photon_energy, elements, binding_energy=binding_energy, - work_function=work_function) + work_function=work_function, gamma=gamma) fig, ax = plt.subplots() ax.stem(energy, intensity, basefmt=' ', use_line_collection=True) diff --git a/pmsco/graphics/__init__.py b/pmsco/graphics/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pmsco/graphics/cluster.py b/pmsco/graphics/cluster.py new file mode 100755 index 0000000..e8300f5 --- /dev/null +++ b/pmsco/graphics/cluster.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python + +""" +@package pmsco.graphics.cluster +graphics rendering module for clusters. + +this module is experimental. +interface and implementation may change without notice. + +at the moment we are evaluating rendering solutions. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2017 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import sys +import os +import numpy as np +import argparse +import logging + +logger = logging.getLogger(__name__) + +try: + import pymol2 +except ImportError: + logger.warning("error importing pymol2. cluster rendering using pymol2 disabled.") + pymol2 = None + +try: + from mpl_toolkits.mplot3d import Axes3D + from matplotlib.figure import Figure + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas + # from matplotlib.backends.backend_pdf import FigureCanvasPdf + # from matplotlib.backends.backend_svg import FigureCanvasSVG +except ImportError: + Axes3D = None + Figure = None + FigureCanvas = None + logger.warning("error importing matplotlib. cluster rendering using matplotlib disabled.") + + +def render_file(spath, view): + sname = "cluster" + opath = spath + ".png" + + pm = pymol2.PyMOL() + cmd = pm.cmd + pm.start() + try: + cmd.reinitialize() + cmd.load(spath, sname) + cmd.disable("all") + cmd.enable(sname) + + cmd.set("orthoscopic", 1) + cmd.bg_color("white") + cmd.show_as("spheres") + cmd.alter("all", "vdw=0.8") + #cmd.show("sticks") + + #zoom selection-expression # selection to fill the viewer + #orient selection-expression # largest dim horizontal, second-largest vertical + + #cmd.orient() --- should stick to fixed orientation + #cmd.turn("x", -90) + #cmd.turn("x", 0) + #cmd.turn("y", 0) + + #cmd.clip("slab", 5.0) + cmd.viewport(640, 640) + cmd.zoom(complete=1) + #pymol.cmd.rebuild() #--- necessary? + + cmd.png(opath) + finally: + pm.stop() + + +def render_cluster(clu): + pass + + +def set_axes_equal(ax): + """ + Make axes of 3D plot have equal scale so that spheres appear as spheres, + cubes as cubes, etc.. This is one possible solution to Matplotlib's + ax.set_aspect('equal') and ax.axis('equal') not working for 3D. + + @author https://stackoverflow.com/a/31364297 + + @param ax: a matplotlib axis, e.g., as output from plt.gca(). + """ + + x_limits = ax.get_xlim3d() + y_limits = ax.get_ylim3d() + z_limits = ax.get_zlim3d() + + x_range = abs(x_limits[1] - x_limits[0]) + x_middle = np.mean(x_limits) + y_range = abs(y_limits[1] - y_limits[0]) + y_middle = np.mean(y_limits) + z_range = abs(z_limits[1] - z_limits[0]) + z_middle = np.mean(z_limits) + + # The plot bounding box is a sphere in the sense of the infinity + # norm, hence I call half the max range the plot radius. + plot_radius = 0.5*max([x_range, y_range, z_range]) + + ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius]) + ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius]) + ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius]) + + +def render_xyz_matplotlib(filename, data, canvas=None): + """ + produce a graphics file from an array of 3d coordinates in the matplotlib scatter style. + + the default file format is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param filename: path and name of the scan file. + this is used to derive the output file path by adding the extension of the graphics file format. + @param data: numpy array of shape (N,3). + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + + @return (str) path and name of the generated graphics file. + empty string if an error occurred. + + @raise TypeError if matplotlib is not available. + """ + + if canvas is None: + canvas = FigureCanvas + fig = Figure() + canvas(fig) + + ax = fig.add_subplot(111, projection='3d') + # ax.set_aspect('equal') + try: + # method available in matplotlib 2.1 and later + ax.set_proj_type('ortho') + except AttributeError: + pass + ax.scatter(data[:, 0], data[:, 1], data[:, 2], c='r', marker='o') + ax.set_xlabel('x') + ax.set_ylabel('y') + ax.set_zlabel('z') + set_axes_equal(ax) + + out_filename = "{0}.{1}".format(filename, canvas.get_default_filetype()) + fig.savefig(out_filename) + return out_filename + + +def exec_cli(): + parser = argparse.ArgumentParser() + parser.add_argument('-v', '--view', default='z') + parser.add_argument(dest='files', nargs='+') + args = parser.parse_args() + for fil in args.files: + render_file(fil, args.view) + + +if __name__ == '__main__': + exec_cli() + sys.exit(0) diff --git a/pmsco/graphics/population.py b/pmsco/graphics/population.py deleted file mode 100644 index ffe2450..0000000 --- a/pmsco/graphics/population.py +++ /dev/null @@ -1,443 +0,0 @@ -""" -@package pmsco.graphics.population -graphics rendering module for population dynamics. - -the main function is render_genetic_chart(). - -this module is experimental. -interface and implementation are subject to change. - -@author Matthias Muntwiler, matthias.muntwiler@psi.ch - -@copyright (c) 2021 by Paul Scherrer Institut @n -Licensed under the Apache License, Version 2.0 (the "License"); @n - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -""" - -import logging -import numpy as np -import os -from pmsco.database import regular_params, special_params - -logger = logging.getLogger(__name__) - -try: - from matplotlib.figure import Figure - from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas - # from matplotlib.backends.backend_pdf import FigureCanvasPdf - # from matplotlib.backends.backend_svg import FigureCanvasSVG -except ImportError: - Figure = None - FigureCanvas = None - logger.warning("error importing matplotlib. graphics rendering disabled.") - - -def _default_range(pos): - """ - determine a default range from actual values. - - @param pos: (numpy.ndarray) 1-dimensional structured array of parameter values. - @return: range_min, range_max are dictionaries of the minimum and maximum values of each parameter. - """ - names = regular_params(pos.dtype.names) - range_min = {} - range_max = {} - for name in names: - range_min[name] = pos[name].min() - range_max[name] = pos[name].max() - return range_min, range_max - - -def _prune_constant_params(pnames, range_min, range_max): - """ - remove constant parameters from the list and range - - @param pnames: (list) - @param range_min: (dict) - @param range_max: (dict) - @return: - """ - del_names = [name for name in pnames if range_max[name] <= range_min[name]] - for name in del_names: - pnames.remove(name) - del range_min[name] - del range_max[name] - - -def render_genetic_chart(output_file, input_data_or_file, model_space=None, generations=None, title=None, cmap=None, - canvas=None): - """ - produce a genetic chart from a given population. - - a genetic chart is a pseudo-colour representation of the coordinates of each individual in the model space. - the axes are the particle number and the model parameter. - the colour is mapped from the relative position of a parameter value within the parameter range. - - the chart should illustrate the diversity in the population. - converged parameters will show similar colours. - by comparing charts of different generations, the effect of the optimization algorithm can be examined. - though the chart type is designed for the genetic algorithm, it may be useful for other algorithms as well. - - the function requires input in one of the following forms: - - a result (.dat) file or numpy structured array. - the array must contain regular parameters, as well as the _particle and _gen columns. - the function generates one chart per generation unless the generation argument is specified. - - a population (.pop) file or numpy structured array. - the array must contain regular parameters, as well as the _particle columns. - - a pmsco.optimizers.population.Population object with valid data. - - the graphics file format can be changed by providing a specific canvas. default is PNG. - - this function requires the matplotlib module. - if it is not available, the function raises an error. - - @param output_file: path and base name of the output file without extension. - a generation index and the file extension according to the file format are appended. - @param input_data_or_file: a numpy structured ndarray of a population or result list from an optimization run. - alternatively, the file path of a result file (.dat) or population file (.pop) can be given. - file can be any object that numpy.genfromtxt() can handle. - @param model_space: model space can be a pmsco.project.ModelSpace object, - any object that contains the same min and max attributes as pmsco.project.ModelSpace, - or a dictionary with to keys 'min' and 'max' that provides the corresponding ModelSpace dictionaries. - by default, the model space boundaries are derived from the input data. - if a model_space is specified, only the parameters listed in it are plotted. - @param generations: (int or sequence) generation index or list of indices. - this index is used in the output file name and for filtering input data by generation. - if the input data does not contain the generation, no filtering is applied. - by default, no filtering is applied, and one graph for each generation is produced. - @param title: (str) title of the chart. - the title is a {}-style format string, where {base} is the output file name and {gen} is the generation. - default: derived from file name. - @param cmap: (str) name of colour map supported by matplotlib. - default is 'jet'. - other good-looking options are 'PiYG', 'RdBu', 'RdYlGn', 'coolwarm'. - @param canvas: a FigureCanvas class reference from a matplotlib backend. - if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. - some other options are: - matplotlib.backends.backend_pdf.FigureCanvasPdf or - matplotlib.backends.backend_svg.FigureCanvasSVG. - - @return (str) path and name of the generated graphics file. - empty string if an error occurred. - - @raise TypeError if matplotlib is not available. - """ - - try: - pos = np.copy(input_data_or_file.pos) - range_min = input_data_or_file.model_min - range_max = input_data_or_file.model_max - generations = [input_data_or_file.generation] - except AttributeError: - try: - pos = np.atleast_1d(np.genfromtxt(input_data_or_file, names=True)) - except TypeError: - pos = np.copy(input_data_or_file) - range_min, range_max = _default_range(pos) - pnames = regular_params(pos.dtype.names) - - if model_space is not None: - try: - # a ModelSpace-like object - range_min = model_space.min - range_max = model_space.max - except AttributeError: - # a dictionary-like object - range_min = model_space['min'] - range_max = model_space['max'] - try: - pnames = range_min.keys() - except AttributeError: - pnames = range_min.dtype.names - - pnames = list(pnames) - _prune_constant_params(pnames, range_min, range_max) - - if generations is None: - try: - generations = np.unique(pos['_gen']) - except ValueError: - pass - - files = [] - path, base = os.path.split(output_file) - if generations is not None and len(generations): - if title is None: - title = "{base} gen {gen}" - - for generation in generations: - idx = np.where(pos['_gen'] == generation) - gpos = pos[idx] - gtitle = title.format(base=base, gen=int(generation)) - out_filename = "{base}-{gen}".format(base=os.fspath(output_file), gen=int(generation)) - out_filename = _render_genetic_chart_2(out_filename, gpos, pnames, range_min, range_max, - gtitle, cmap, canvas) - files.append(out_filename) - else: - if title is None: - title = "{base}" - gtitle = title.format(base=base, gen="") - out_filename = "{base}".format(base=os.fspath(output_file)) - out_filename = _render_genetic_chart_2(out_filename, pos, pnames, range_min, range_max, gtitle, cmap, canvas) - files.append(out_filename) - - return files - - -def _render_genetic_chart_2(out_filename, pos, pnames, range_min, range_max, title, cmap, canvas): - """ - internal part of render_genetic_chart() - - this function calculates the relative position in the model space, - sorts the positions array by particle index, - and calls plot_genetic_chart(). - - @param out_filename: - @param pos: - @param pnames: - @param range_max: - @param range_min: - @param cmap: - @param canvas: - @return: out_filename - """ - spos = np.sort(pos, order='_particle') - rpos2d = np.zeros((spos.shape[0], len(pnames))) - for index, pname in enumerate(pnames): - rpos2d[:, index] = (spos[pname] - range_min[pname]) / (range_max[pname] - range_min[pname]) - out_filename = plot_genetic_chart(out_filename, rpos2d, pnames, title=title, cmap=cmap, canvas=canvas) - return out_filename - - -def plot_genetic_chart(filename, rpos2d, param_labels, title=None, cmap=None, canvas=None): - """ - produce a genetic chart from the given data. - - a genetic chart is a pseudo-colour representation of the coordinates of each individual in the model space. - the chart should highlight the amount of diversity in the population - and - by comparing charts of different generations - the changes due to mutation. - the axes are the model parameter (x) and particle number (y). - the colour is mapped from the relative position of a parameter value within the parameter range. - - in contrast to render_genetic_chart() this function contains only the drawing code. - it requires input in the final form and does not do any checks, conversion or processing. - - the graphics file format can be changed by providing a specific canvas. default is PNG. - - this function requires the matplotlib module. - if it is not available, the function raises an error. - - @param filename: path and name of the output file without extension. - @param rpos2d: (two-dimensional numpy array of numeric type) - relative positions of the particles in the model space. - dimension 0 (y-axis) is the particle index, - dimension 1 (x-axis) is the parameter index (in the order given by param_labels). - all values must be between 0 and 1. - @param param_labels: (sequence) list or tuple of parameter names. - @param title: (str) string to be printed as chart title. default is 'genetic chart'. - @param cmap: (str) name of colour map supported by matplotlib. - default is 'jet'. - other good-looking options are 'PiYG', 'RdBu', 'RdYlGn', 'coolwarm'. - @param canvas: a FigureCanvas class reference from a matplotlib backend. - if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. - some other options are: - matplotlib.backends.backend_pdf.FigureCanvasPdf or - matplotlib.backends.backend_svg.FigureCanvasSVG. - - @raise TypeError if matplotlib is not available. - """ - if canvas is None: - canvas = FigureCanvas - if cmap is None: - cmap = 'jet' - if title is None: - title = 'genetic chart' - - fig = Figure() - canvas(fig) - ax = fig.add_subplot(111) - im = ax.imshow(rpos2d, aspect='auto', cmap=cmap, origin='lower') - im.set_clim((0.0, 1.0)) - ax.set_xticks(np.arange(len(param_labels))) - ax.set_xticklabels(param_labels, rotation=45, ha="right", rotation_mode="anchor") - ax.set_ylabel('particle') - ax.set_title(title) - cb = ax.figure.colorbar(im, ax=ax) - cb.ax.set_ylabel("relative value", rotation=-90, va="bottom") - - out_filename = "{base}.{ext}".format(base=filename, ext=canvas.get_default_filetype()) - fig.savefig(out_filename) - return out_filename - - -def render_swarm(output_file, input_data, model_space=None, title=None, cmap=None, canvas=None): - """ - render a two-dimensional particle swarm population. - - this function generates a schematic rendering of a particle swarm in two dimensions. - particles are represented by their position and velocity, indicated by an arrow. - the model space is projected on the first two (or selected two) variable parameters. - in the background, a scatter plot of results (dots with pseudocolor representing the R-factor) can be plotted. - the chart type is designed for the particle swarm optimization algorithm. - - the function requires input in one of the following forms: - - position (.pos), velocity (.vel) and result (.dat) files or the respective numpy structured arrays. - the arrays must contain regular parameters, as well as the `_particle` column. - the result file must also contain an `_rfac` column. - - a pmsco.optimizers.population.Population object with valid data. - - the graphics file format can be changed by providing a specific canvas. default is PNG. - - this function requires the matplotlib module. - if it is not available, the function raises an error. - - @param output_file: path and base name of the output file without extension. - a generation index and the file extension according to the file format are appended. - @param input_data: a pmsco.optimizers.population.Population object with valid data, - or a sequence of position, velocity and result arrays. - the arrays must be structured ndarrays corresponding to the respective Population members. - alternatively, the arrays can be referenced as file paths - in any format that numpy.genfromtxt() can handle. - @param model_space: model space can be a pmsco.project.ModelSpace object, - any object that contains the same min and max attributes as pmsco.project.ModelSpace, - or a dictionary with to keys 'min' and 'max' that provides the corresponding ModelSpace dictionaries. - by default, the model space boundaries are derived from the input data. - if a model_space is specified, only the parameters listed in it are plotted. - @param title: (str) title of the chart. - the title is a {}-style format string, where {base} is the output file name and {gen} is the generation. - default: derived from file name. - @param cmap: (str) name of colour map supported by matplotlib. - default is 'plasma'. - other good-looking options are 'viridis', 'plasma', 'inferno', 'magma', 'cividis'. - @param canvas: a FigureCanvas class reference from a matplotlib backend. - if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. - some other options are: - matplotlib.backends.backend_pdf.FigureCanvasPdf or - matplotlib.backends.backend_svg.FigureCanvasSVG. - - @return (str) path and name of the generated graphics file. - empty string if an error occurred. - - @raise TypeError if matplotlib is not available. - """ - try: - range_min = input_data.model_min - range_max = input_data.model_max - pos = np.copy(input_data.pos) - vel = np.copy(input_data.vel) - rfac = np.copy(input_data.results) - generation = input_data.generation - except AttributeError: - try: - pos = np.atleast_1d(np.genfromtxt(input_data[0], names=True)) - vel = np.atleast_1d(np.genfromtxt(input_data[1], names=True)) - rfac = np.atleast_1d(np.genfromtxt(input_data[2], names=True)) - except TypeError: - pos = np.copy(input_data[0]) - vel = np.copy(input_data[1]) - rfac = np.copy(input_data[2]) - range_min, range_max = _default_range(rfac) - pnames = regular_params(pos.dtype.names) - - if model_space is not None: - try: - # a ModelSpace-like object - range_min = model_space.min - range_max = model_space.max - except AttributeError: - # a dictionary-like object - range_min = model_space['min'] - range_max = model_space['max'] - try: - pnames = range_min.keys() - except AttributeError: - pnames = range_min.dtype.names - - pnames = list(pnames) - _prune_constant_params(pnames, range_min, range_max) - pnames = pnames[0:2] - files = [] - if len(pnames) == 2: - params = {pnames[0]: [range_min[pnames[0]], range_max[pnames[0]]], - pnames[1]: [range_min[pnames[1]], range_max[pnames[1]]]} - out_filename = plot_swarm(output_file, pos, vel, rfac, params, title=title, cmap=cmap, canvas=canvas) - files.append(out_filename) - else: - logging.warning("model space must be two-dimensional and non-degenerate.") - - return files - - -def plot_swarm(filename, pos, vel, rfac, params, title=None, cmap=None, canvas=None): - """ - plot a two-dimensional particle swarm population. - - this is a sub-function of render_swarm() containing just the plotting commands. - - the graphics file format can be changed by providing a specific canvas. default is PNG. - - this function requires the matplotlib module. - if it is not available, the function raises an error. - - @param filename: path and base name of the output file without extension. - a generation index and the file extension according to the file format are appended. - @param pos: structured ndarray containing the positions of the particles. - @param vel: structured ndarray containing the velocities of the particles. - @param rfac: structured ndarray containing positions and R-factor values. - this array is independent of pos and vel. - it can also be set to None if results should be suppressed. - @param params: dictionary of two parameters to be plotted. - the keys correspond to columns of the pos, vel and rfac arrays. - the values are lists [minimum, maximum] that define the axis range. - @param title: (str) title of the chart. - the title is a {}-style format string, where {base} is the output file name and {gen} is the generation. - default: derived from file name. - @param cmap: (str) name of colour map supported by matplotlib. - default is 'plasma'. - other good-looking options are 'viridis', 'plasma', 'inferno', 'magma', 'cividis'. - @param canvas: a FigureCanvas class reference from a matplotlib backend. - if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. - some other options are: - matplotlib.backends.backend_pdf.FigureCanvasPdf or - matplotlib.backends.backend_svg.FigureCanvasSVG. - - @return (str) path and name of the generated graphics file. - empty string if an error occurred. - - @raise TypeError if matplotlib is not available. - """ - if canvas is None: - canvas = FigureCanvas - if cmap is None: - cmap = 'plasma' - if title is None: - title = 'swarm map' - - pnames = list(params.keys()) - fig = Figure() - canvas(fig) - ax = fig.add_subplot(111) - - if rfac is not None: - try: - s = ax.scatter(rfac[params[0]], rfac[params[1]], s=5, c=rfac['_rfac'], cmap=cmap, vmin=0, vmax=1) - except ValueError: - # _rfac column missing - pass - else: - cb = ax.figure.colorbar(s, ax=ax) - cb.ax.set_ylabel("R-factor", rotation=-90, va="bottom") - - p = ax.plot(pos[pnames[0]], pos[pnames[1]], 'co') - q = ax.quiver(pos[pnames[0]], pos[pnames[1]], vel[pnames[0]], vel[pnames[1]], color='c') - ax.set_xlim(params[pnames[0]]) - ax.set_ylim(params[pnames[1]]) - ax.set_xlabel(pnames[0]) - ax.set_ylabel(pnames[1]) - ax.set_title(title) - - out_filename = "{base}.{ext}".format(base=filename, ext=canvas.get_default_filetype()) - fig.savefig(out_filename) - return out_filename diff --git a/pmsco/graphics/scan.py b/pmsco/graphics/scan.py index a183316..fcb8233 100644 --- a/pmsco/graphics/scan.py +++ b/pmsco/graphics/scan.py @@ -202,14 +202,16 @@ def render_tp_scan(filename, data, canvas=None, is_modf=False): cb = fig.colorbar(pc, shrink=0.4, pad=0.1) - dlo = np.nanpercentile(data['i'], 2) - dhi = np.nanpercentile(data['i'], 98) + clip = 2 + dlo = np.nanpercentile(data['i'], clip) + dhi = np.nanpercentile(data['i'], 100 - clip) + if is_modf: pc.set_cmap("RdBu_r") # im.set_cmap("coolwarm") dhi = max(abs(dlo), abs(dhi)) dlo = -dhi - pc.set_clim((-1., 1.)) + pc.set_clim((dlo, dhi)) try: ti = cb.get_ticks() ti = [min(ti), 0., max(ti)] diff --git a/pmsco/graphics/scattering.py b/pmsco/graphics/scattering.py new file mode 100644 index 0000000..63fa87c --- /dev/null +++ b/pmsco/graphics/scattering.py @@ -0,0 +1,210 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import logging +import math +import numpy as np +import scipy.interpolate +import scipy.special + +logger = logging.getLogger(__name__) + +try: + from matplotlib.figure import Figure + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas +except ImportError: + Figure = None + FigureCanvas = None + logger.warning("error importing matplotlib. graphics rendering disabled.") + + +class TMatrix(object): + def __init__(self): + """ + self.en.shape = (n_e,) + self.tl.shape = (n_e, n_l) + """ + self.en = None + self.tl = None + + def load_test_data(self): + self.en = np.array([100.]) + raw = [-0.052845, -0.003238, 0.478705, 0.672581, 0.137932, 0.981700, 0.323890, 0.805299, 0.291814, 0.776792, + 0.369416, 0.351845, 0.199775, 0.113314, 0.062479, 0.025691, 0.013699, 0.005283] + re_tl = np.array(raw[0::2]) + im_tl = np.array(raw[1::2]) + self.tl = re_tl + 1j * im_tl + + def load_edac_scattering(self, f, energy=math.nan): + """ + load T matrix from EDAC scattering file + + currently, only the 'tl' format is supported. + + @param f: file path + @param energy: kinetic energy in eV if none is defined in the file + @return: None + """ + with open(f, "r") as fi: + h = fi.readline().rstrip().split(' ') + + ne = int(h[0]) + if ne > 1: + assert h[1] == 'E(eV)' + del h[1] + lmax = int(h[1]) + assert h[2] == 'regular' + assert h[3] == 'tl' + + self.load_edac_tl(f, ne, lmax, energy=energy) + + def load_edac_tl(self, f, ne, lmax, energy=math.nan): + """ + load T matrix from EDAC scattering file in 'tl' format + + @param f: file path + @param ne: number of energies (rows) + @param lmax: maximum l number (columns = 2 * (lmax + 1)) + @param energy: kinetic energy in eV if none is defined in the file + @return: None + """ + if ne > 1: + self.en = np.atleast_1d(np.genfromtxt(f, skip_header=1, usecols=[0])) + start_col = 1 + else: + self.en = np.asarray(energy) + start_col = 0 + + re_cols = range(start_col, start_col + (lmax + 1) * 2, 2) + im_cols = range(start_col + 1, start_col + (lmax + 1) * 2, 2) + re_tl = np.atleast_1d(np.genfromtxt(f, skip_header=1, usecols=re_cols)) + im_tl = np.atleast_1d(np.genfromtxt(f, skip_header=1, usecols=im_cols)) + self.tl = re_tl + 1j * im_tl + assert self.tl.shape == (ne, lmax + 1), "array shape mismatch" + + def planewave_amplitude(self, energy, angle): + """ + total, complex plane wave scattering amplitude for given energy and angle + + @param energy: kinetic energy in eV. + this can be a numeric value, a 1-dimensional numpy.ndarray, + or any value accepted by the numpy.asarray function. + @param angle: scattering angle in degrees (0..180). + this can be a numeric value, a 1-dimensional numpy.ndarray, + or any value accepted by the numpy.asarray function. + @return: 3 numpy arrays (amp, magnitude, phase) representing the scattering amplitude + versus energy and angle. + the shape of the three arrays is (n_energies, n_angles). + @arg amp: complex scattering amplitude. + @arg magnitude: magnitude (absolute value) of the scattering amplitude. + @arg phase: phase angle in radians of the scattering amplitude. + """ + if not isinstance(energy, np.ndarray): + energy = np.atleast_1d(np.asarray(energy)) + ne = len(energy) + if not isinstance(angle, np.ndarray): + angle = np.atleast_1d(np.array(angle)) + na = len(angle) + + kinv = 1. / (0.513019932 * np.sqrt(energy)) + f_tl = scipy.interpolate.interp1d(self.en, self.tl, axis=0, copy=False) + tl = f_tl(energy) + + cos_angle = np.cos(np.radians(angle)) + lmax = self.tl.shape[1] - 1 + l = np.arange(0, lmax + 1) + + amp = np.zeros((ne, na), dtype=complex) + for ia, ca in enumerate(cos_angle): + lpmn, __ = scipy.special.lpmn(0, lmax, ca) + fpart = np.outer(kinv, (2 * l + 1) * lpmn[0]) * tl + ftot = np.sum(fpart, axis=-1) + amp[:, ia] = ftot + + mag = np.abs(amp) + pha = np.angle(amp) + + return amp, mag, pha + + +def render_scattering_1d(filename, tmatrix, energy=None): + if energy is None: + en = tmatrix.en[0] + else: + en = energy + an = np.arange(0, 181, 2) + __, mag, pha = tmatrix.planewave_amplitude(en, an) + pha = pha / math.pi + + canvas = FigureCanvas + fig = Figure() + canvas(fig) + + ax = fig.add_subplot(211) + ax.plot(an, mag[0]) + ax.set_xlabel('th (deg)') + ax.set_ylabel('mag (arb)') + + ax = fig.add_subplot(212) + ax.plot(an, pha[0]) + ax.set_xlabel('th (deg)') + ax.set_ylabel('pha (1/pi)') + + out_filename = "{0}.{1}".format(filename, canvas.get_default_filetype()) + fig.savefig(out_filename) + return out_filename + + +def render_scattering_2d(filename, tmatrix): + en = tmatrix.en + an = np.arange(0, 181, 2) + __, mag, pha = tmatrix.planewave_amplitude(en, an) + pha = pha / math.pi + + canvas = FigureCanvas + fig = Figure() + canvas(fig) + + ax = fig.add_subplot(211) + im = ax.imshow(mag, origin='lower', aspect='auto', interpolation='none') + im.set_extent((an[0], an[-1], en[0], en[-1])) + im.set_cmap("magma") + ax.set_xlabel('th (deg)') + ax.set_ylabel('E (eV)') + # cb = ax.colorbar(im, shrink=0.4, pad=0.1) + # ti = cb.get_ticks() + # ti = [0., max(ti)] + # cb.set_ticks(ti) + + ax = fig.add_subplot(212) + im = ax.imshow(pha, origin='lower', aspect='auto', interpolation='none') + im.set_extent((an[0], an[-1], en[0], en[-1])) + im.set_cmap("RdBu_r") + ax.set_xlabel('th (deg)') + ax.set_ylabel('E (eV)') + # cb = ax.colorbar(im, shrink=0.4, pad=0.1) + + dlo = np.nanpercentile(mag, 2) + dhi = np.nanpercentile(mag, 98) + dhi = max(abs(dlo), abs(dhi)) + dlo = -dhi + im.set_clim((dlo, dhi)) + # ti = cb.get_ticks() + # ti = [min(ti), 0., max(ti)] + # cb.set_ticks(ti) + + out_filename = "{0}.{1}".format(filename, canvas.get_default_filetype()) + fig.savefig(out_filename) + return out_filename + + +def render_scattering_map(filename, energy): + tmatrix = TMatrix() + tmatrix.load_edac_scattering(filename, energy) + + if tmatrix.tl.shape[0] == 1: + out_filename = render_scattering_1d(filename, tmatrix) + else: + out_filename = render_scattering_2d(filename, tmatrix) + + return out_filename diff --git a/pmsco/handlers.py b/pmsco/handlers.py index a14dad8..f96cc44 100644 --- a/pmsco/handlers.py +++ b/pmsco/handlers.py @@ -55,7 +55,6 @@ import numpy as np import os from pathlib import Path -from pmsco.compat import open import pmsco.data as md import pmsco.dispatch as dispatch import pmsco.graphics.scan as mgs @@ -375,7 +374,7 @@ class SingleModelHandler(ModelHandler): keys.sort(key=lambda t: t[0].lower()) vals = (str(self.result[key]) for key in keys) filename = Path(self._project.output_file).with_suffix(".dat") - with open(filename, "w") as outfile: + with open(filename, "wt", encoding="latin1") as outfile: outfile.write("# ") outfile.write(" ".join(keys)) outfile.write("\n") @@ -1002,27 +1001,3 @@ class EnergyRegionHandler(RegionHandler): logger.error("no region tasks generated. this is probably a bug.") return out_tasks - - -def choose_region_handler_class(project): - """ - choose a suitable region handler for the project. - - the function returns the EnergyRegionHandler class - if the project includes an energy scan with at least 10 steps. - Otherwise, it returns the SingleRegionHandler. - - angle scans do not benefit from region splitting in EDAC. - - @param project: Project instance. - @return: SingleRegionHandler or EnergyRegionHandler class. - """ - energy_scans = 0 - for scan in project.scans: - if scan.energies.shape[0] >= 10: - energy_scans += 1 - - if energy_scans >= 1: - return EnergyRegionHandler - else: - return SingleRegionHandler diff --git a/pmsco/helpers.py b/pmsco/helpers.py index 525c9d7..8976b3b 100644 --- a/pmsco/helpers.py +++ b/pmsco/helpers.py @@ -6,6 +6,13 @@ a collection of small and generic code bits mostly collected from the www. """ +import contextlib +import ctypes +import io +import os +import sys +from typing import BinaryIO + class BraceMessage(object): """ @@ -22,3 +29,40 @@ class BraceMessage(object): def __str__(self): return self.fmt.format(*self.args, **self.kwargs) + + +libc = ctypes.CDLL(None) +c_stdout = ctypes.c_void_p.in_dll(libc, 'stdout') + + +@contextlib.contextmanager +def stdout_redirected(dest_file: BinaryIO): + """ + A context manager to temporarily redirect stdout to a file. + + Redirects all standard output from Python and the C library to the specified file. + This can be used, e.g., to capture output from Fortran code. + + credit: https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ + + @param dest_file: binary file open for writing ('wb' mode). + This function requires just the fileno function. + @return: None + """ + + original_stdout_fd = sys.stdout.fileno() + + def _redirect_stdout(to_fd): + """Redirect stdout to the given file descriptor.""" + libc.fflush(c_stdout) + sys.stdout.close() + os.dup2(to_fd, original_stdout_fd) + sys.stdout = io.TextIOWrapper(os.fdopen(original_stdout_fd, 'wb')) + + saved_stdout_fd = os.dup(original_stdout_fd) + try: + _redirect_stdout(dest_file.fileno()) + yield + _redirect_stdout(saved_stdout_fd) + finally: + os.close(saved_stdout_fd) diff --git a/pmsco/igor.py b/pmsco/igor.py index fd1009f..01e39a8 100644 --- a/pmsco/igor.py +++ b/pmsco/igor.py @@ -6,21 +6,15 @@ this module provides functions for loading/saving pmsco data in igor pro. @author Matthias Muntwiler -@copyright (c) 2019 by Paul Scherrer Institut @n +@copyright (c) 2019-23 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import numpy as np -from pmsco.compat import open - def _escape_igor_string(s): s = s.replace('\\', '\\\\') @@ -91,7 +85,7 @@ class IgorExport(object): """ write to igor file. """ - with open(filename, 'w') as f: + with open(filename, 'wt', encoding="utf8") as f: self._write_header(f) self._write_data(f) diff --git a/pmsco/loess/__init__.py b/pmsco/loess/__init__.py deleted file mode 100644 index 1f99b1f..0000000 --- a/pmsco/loess/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'matthias muntwiler' diff --git a/pmsco/loess/makefile b/pmsco/loess/makefile deleted file mode 100644 index 95af5a6..0000000 --- a/pmsco/loess/makefile +++ /dev/null @@ -1,74 +0,0 @@ -SHELL=/bin/sh - -# makefile for the LOESS module -# -# required libraries: libblas, liblapack, libf2c -# (you may have to set soft links so that linker finds them) -# -# the makefile calls python-config to get the compilation flags and include path. -# you may override the corresponding variables on the command line or by environment variables: -# -# PYTHON_INC: specify additional include directories. each dir must start with -I prefix. -# PYTHON_CFLAGS: specify the C compiler flags. -# -# see the top-level makefile for additional information. - -.SUFFIXES: -.SUFFIXES: .c .cpp .cxx .exe .f .h .i .o .py .pyf .so .x -.PHONY: all loess test gas madeup ethanol air galaxy - -OBJ=loessc.o loess.o predict.o misc.o loessf.o dqrsl.o dsvdc.o fix_main.o - -FFLAGS?=-O -LIB=-lblas -lm -lf2c -LIBPATH?= -CC?=gcc -CCOPTS?= -SWIG?=swig -SWIGOPTS?= -PYTHON?=python -PYTHONOPTS?= -PYTHON_CONFIG = ${PYTHON}-config -#PYTHON_LIB ?= $(shell ${PYTHON_CONFIG} --libs) -#PYTHON_INC ?= $(shell ${PYTHON_CONFIG} --includes) -PYTHON_INC ?= -PYTHON_CFLAGS ?= $(shell ${PYTHON_CONFIG} --cflags) -#PYTHON_LDFLAGS ?= $(shell ${PYTHON_CONFIG} --ldflags) - -all: loess - -loess: _loess.so - -loess.py _loess.so: loess.c loess.i - $(PYTHON) $(PYTHONOPTS) setup.py build_ext --inplace - -examples: gas madeup ethanol air galaxy - -gas: gas.x - -gas.x: gas.o $(OBJ) - $(CC) -o gas.x gas.o $(OBJ) $(LIB) - -madeup: madeup.x - -madeup.x: madeup.o $(OBJ) - $(CC) -o madeup.x madeup.o $(OBJ) $(LIB) - -ethanol: ethanol.x - -ethanol.x: ethanol.o $(OBJ) - $(CC) -o ethanol.x ethanol.o $(OBJ) $(LIB) - -air: air.x - -air.x: air.o $(OBJ) - $(CC) -o air.x air.o $(OBJ) $(LIB) - -galaxy: galaxy.x - -galaxy.x: galaxy.o $(OBJ) - $(CC) -o galaxy.x galaxy.o $(OBJ) $(LIB) - -clean: - rm -f *.o *.so *.x core *.pyc - rm -f loess.py loess_wrap.c diff --git a/pmsco/loess/setup.py b/pmsco/loess/setup.py deleted file mode 100644 index d627c1d..0000000 --- a/pmsco/loess/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python - -""" -@package loess.setup -setup.py file for LOESS - -the LOESS code included here was developed at Bell Labs by -William S. Cleveland, Eric Grosse, Ming-Jen Shyu, -and is dated 18 August 1992. -the code is available in the public domain -from http://www.netlib.org/a/dloess. -see the README file for details. - -the Python wrapper was set up by M. Muntwiler -with the help of the SWIG toolkit -and other incredible goodies available in the Linux world. - -@bug numpy.distutils.build_src in python 2.7 treats all Fortran files with f2py -so that they are compiled via both f2py and swig. -this produces extra object files which cause the linker to fail. -to fix this issue, this module hacks the build_src class. -this hack does not work with python 3. perhaps it's even unnecessary. - -@author Matthias Muntwiler - -@copyright (c) 2015-18 by Paul Scherrer Institut @n -Licensed under the Apache License, Version 2.0 (the "License"); @n - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -""" - -import numpy -try: - numpy_include = numpy.get_include() -except AttributeError: - numpy_include = numpy.get_numpy_include() - -def configuration(parent_package='', top_path=None): - from numpy.distutils.misc_util import Configuration - config = Configuration('loess', parent_package, top_path) - lib = ['blas', 'm', 'f2c'] - src = ['loess.c', 'loessc.c', 'predict.c', 'misc.c', 'loessf.f', 'dqrsl.f', 'dsvdc.f', 'fix_main.c', 'loess.i'] - inc_dir = [numpy_include] - config.add_extension('_loess', - sources=src, - libraries=lib, - include_dirs=inc_dir - ) - return config - -def ignore_sources(self, sources, extension): - return sources - -if __name__ == '__main__': - try: - from numpy.distutils.core import numpy_cmdclass - numpy_cmdclass['build_src'].f2py_sources = ignore_sources - except ImportError: - pass - from numpy.distutils.core import setup - setup(**configuration(top_path='').todict()) - diff --git a/pmsco/makefile b/pmsco/makefile deleted file mode 100644 index 3f18d64..0000000 --- a/pmsco/makefile +++ /dev/null @@ -1,38 +0,0 @@ -SHELL=/bin/sh - -# makefile for external programs and modules -# -# see the top-level makefile for additional information. - -.PHONY: all clean edac loess msc mufpot phagen - -EDAC_DIR = edac -MSC_DIR = msc -MUFPOT_DIR = mufpot -LOESS_DIR = loess -PHAGEN_DIR = calculators/phagen - -all: edac loess phagen - -edac: - $(MAKE) -C $(EDAC_DIR) - -loess: - $(MAKE) -C $(LOESS_DIR) - -msc: - $(MAKE) -C $(MSC_DIR) - -mufpot: - $(MAKE) -C $(MUFPOT_DIR) - -phagen: - $(MAKE) -C $(PHAGEN_DIR) - -clean: - $(MAKE) -C $(EDAC_DIR) clean - $(MAKE) -C $(LOESS_DIR) clean - $(MAKE) -C $(MSC_DIR) clean - $(MAKE) -C $(MUFPOT_DIR) clean - $(MAKE) -C $(PHAGEN_DIR) clean - rm -f *.pyc diff --git a/pmsco/msc/.gitignore b/pmsco/msc/.gitignore deleted file mode 100644 index a04ad0b..0000000 --- a/pmsco/msc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -revision.f diff --git a/pmsco/msc/__init__.py b/pmsco/msc/__init__.py deleted file mode 100644 index 1233160..0000000 --- a/pmsco/msc/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'muntwiler_m' diff --git a/pmsco/msc/makefile b/pmsco/msc/makefile deleted file mode 100644 index bfc2771..0000000 --- a/pmsco/msc/makefile +++ /dev/null @@ -1,50 +0,0 @@ -SHELL=/bin/sh - -# makefile for MSC program and module -# -# the MSC source code is not included in the public distribution. -# please obtain the MSC code from the original author, -# and copy it to this directory before compilation. -# -# see the top-level makefile for additional information. - -.SUFFIXES: -.SUFFIXES: .c .cpp .cxx .exe .f .h .i .o .py .pyf .so -.PHONY: all clean edac msc mufpot - -FC?=gfortran -FCCOPTS?= -F2PY?=f2py -F2PYOPTS?= -CC?=gcc -CCOPTS?= -SWIG?=swig -SWIGOPTS?= -PYTHON?=python -PYTHONOPTS?= -PYTHONINC?= - -all: msc - -msc: msc.exe msc.so - -msc.exe: msc.f param.f common.f phases.f angles.f revision.f - $(FC) $(FCOPTS) -o msc.exe msc.f phases.f angles.f - -#msc.pyf currently needs a manual edit before compiling. -#this target should execute only if it doesn't exist. -msc.pyf: | msc.f phases.f angles.f - $(F2PY) -h msc.pyf -m msc msc.f phases.f angles.f only: mscmain anglesarray anglesfile ps - $(error msc.pyf auto-generated - must be edited manually before build can continue!) - -msc.so: msc.f param.f common.f phases.f angles.f revision.f msc.pyf - $(F2PY) -c $(F2PYOPTS) msc.pyf msc.f phases.f angles.f -m msc - -revision.f: msc.f - echo " character*50 coderev" > revision.f - echo " parameter(coderev=" >> revision.f - git log --pretty=format:" ='Code revision %h, %ad')" --date=iso -1 $< >> $@ || echo " ='Code revision unknown, "`date +"%F %T %z"`"')" >> $@ - -clean: - rm -f *.so *.o *.exe - rm -f revision.f diff --git a/pmsco/mufpot/__init__.py b/pmsco/mufpot/__init__.py deleted file mode 100644 index 1233160..0000000 --- a/pmsco/mufpot/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'muntwiler_m' diff --git a/pmsco/mufpot/makefile b/pmsco/mufpot/makefile deleted file mode 100644 index 25696b1..0000000 --- a/pmsco/mufpot/makefile +++ /dev/null @@ -1,46 +0,0 @@ -SHELL=/bin/sh - -# makefile for MUFPOT program and module -# -# the MUFPOT source code is not included in the public distribution. -# please obtain the MUFPOT code from the original author, -# and copy it to this directory before compilation. -# -# see the top-level makefile for additional information. - -.SUFFIXES: -.SUFFIXES: .c .cpp .cxx .exe .f .h .i .o .py .pyf .so -.PHONY: all clean edac msc mufpot - -FC=gfortran -FCCOPTS= -F2PY=f2py -F2PYOPTS= -CC=gcc -CCOPTS= -SWIG=swig -SWIGOPTS= -PYTHON=python2 -PYTHONOPTS= - -all: mufpot - -mufpot: mufpot.exe mufpot.so - -mufpot.exe: mufpot.f - $(FC) $(FCOPTS) -o mufpot.exe mufpot.f - -mufpot.pyf: | mufpot.f - $(F2PY) -h mufpot.pyf -m mufpot mufpot.f only: mufpot - -mufpot.so: mufpot.f mufpot.pyf - $(F2PY) -c $(F2PYOPTS) mufpot.pyf mufpot.f -m mufpot - -revision.f: msc.f - echo " character*50 coderev" > revision.f - echo " parameter(coderev=" >> revision.f - git log --pretty=format:" ='Code revision %h, %ad')" --date=iso -1 $< >> $@ || echo " ='Code revision unknown, "`date +"%F %T %z"`"')" >> $@ - -clean: - rm -f *.so *.o *.exe - rm -f revision.f diff --git a/pmsco/optimizers/__init__.py b/pmsco/optimizers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pmsco/optimizers/genetic.py b/pmsco/optimizers/genetic.py index c7ea0ec..85a6106 100644 --- a/pmsco/optimizers/genetic.py +++ b/pmsco/optimizers/genetic.py @@ -13,21 +13,17 @@ and R-factor based selection. @author Matthias Muntwiler, matthias.muntwiler@psi.ch -@copyright (c) 2018 by Paul Scherrer Institut @n +@copyright (c) 2018-21 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function import logging import numpy as np import random import pmsco.optimizers.population as population -from pmsco.helpers import BraceMessage as BMsg logger = logging.getLogger(__name__) @@ -112,7 +108,7 @@ class GeneticPopulation(population.Population): def setup(self, size, model_space, **kwargs): """ - @copydoc Population.setup() + @copydoc pmsco.optimizers.population.Population.setup() in addition to the inherited behaviour, this method initializes self.mutation_step. mutation_step of a parameter is set to its model_space.step if non-zero. diff --git a/pmsco/optimizers/gradient.py b/pmsco/optimizers/gradient.py index 07b34c3..93b735d 100644 --- a/pmsco/optimizers/gradient.py +++ b/pmsco/optimizers/gradient.py @@ -47,6 +47,7 @@ TAG_NEW_RESULT = 1 # currently not used TAG_FINISHED = 2 + class MscProcess(object): """ Code shared by MscoMaster and MscoSlave @@ -79,7 +80,6 @@ class MscProcess(object): all other calculation results are discarded. """ - rev = "rank %u, iteration %u" % (self.comm.rank, self.iteration) # create parameter and cluster structures clu = self.project.create_cluster(pars) @@ -101,6 +101,7 @@ class MscProcess(object): return pars + class MscMaster(MscProcess): def __init__(self, comm): super(MscMaster, self).__init__(comm) @@ -235,6 +236,7 @@ class MscMaster(MscProcess): self.comm.send(None, dest=rank, tag=TAG_FINISH) super(MscMaster, self).cleanup() + class MscSlave(MscProcess): def run(self): @@ -258,6 +260,7 @@ class MscSlave(MscProcess): self.comm.send(result, dest=0, tag=TAG_NEW_RESULT) self.iteration += 1 + def optimize(project): """ main entry point for optimization diff --git a/pmsco/optimizers/grid.py b/pmsco/optimizers/grid.py index 74277b3..fc0246a 100644 --- a/pmsco/optimizers/grid.py +++ b/pmsco/optimizers/grid.py @@ -6,23 +6,19 @@ the module starts multiple MSC calculations and varies parameters on a fixed coo @author Matthias Muntwiler, matthias.muntwiler@psi.ch -@copyright (c) 2015 by Paul Scherrer Institut @n +@copyright (c) 2015-21 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import datetime import math import numpy as np +from pathlib import Path import logging -from pmsco.compat import open import pmsco.handlers as handlers import pmsco.graphics as graphics from pmsco.helpers import BraceMessage as BMsg @@ -287,6 +283,7 @@ class GridSearchHandler(handlers.ModelHandler): self._timeout = False self._invalid_limit = 10 self._next_model = 0 + self._results_path = None def setup(self, project, slots): """ @@ -307,10 +304,13 @@ class GridSearchHandler(handlers.ModelHandler): self._pop.setup(self._project.model_space) self._invalid_limit = max(slots, self._invalid_limit) - self._outfile = open(self._project.output_file + ".dat", "w") - self._outfile.write("# ") - self._outfile.write(" ".join(self._pop.positions.dtype.names)) - self._outfile.write("\n") + of = Path(self._project.output_file) + self._results_path = of.with_suffix(".dat") + + with open(self._results_path, "wt", encoding="latin1") as outfile: + outfile.write("# ") + outfile.write(" ".join(self._pop.positions.dtype.names)) + outfile.write("\n") return self._pop.model_count @@ -388,11 +388,10 @@ class GridSearchHandler(handlers.ModelHandler): task.model['_rfac'] = task.rfac self._pop.add_result(task.model, task.rfac) - if self._outfile: + with open(self._results_path, "at", encoding="latin1") as outfile: s = (str(task.model[name]) for name in self._pop.positions.dtype.names) - self._outfile.write(" ".join(s)) - self._outfile.write("\n") - self._outfile.flush() + outfile.write(" ".join(s)) + outfile.write("\n") self._project.files.update_model_rfac(task.id.model, task.rfac) self._project.files.set_model_complete(task.id.model, True) @@ -422,6 +421,6 @@ class GridSearchHandler(handlers.ModelHandler): """ super(GridSearchHandler, self).save_report(root_task) - files = graphics.rfactor.render_results(self._project.output_file + ".dat", self._pop.positions) + files = graphics.rfactor.render_results(self._results_path, self._pop.positions) for f in files: self._project.files.add_file(f, root_task.id.model, "report") diff --git a/pmsco/optimizers/population.py b/pmsco/optimizers/population.py index 8a62444..281a853 100644 --- a/pmsco/optimizers/population.py +++ b/pmsco/optimizers/population.py @@ -21,18 +21,14 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import datetime import logging import math import numpy as np +from pathlib import Path import os import time -from pmsco.compat import open import pmsco.handlers as handlers import pmsco.graphics.rfactor as grfactor from pmsco.helpers import BraceMessage as BMsg @@ -761,7 +757,9 @@ class Population(object): self.pos_import = np.delete(self.pos_import, range(first, last)) self.size_act = last - first self.update_particle_info() - return last - first + else: + self.size_act = 0 + return self.size_act def update_particle_info(self, index=None, inc_model=True): """ @@ -1083,9 +1081,9 @@ class Population(object): the file name extensions are .pos, .vel, and .best """ - self.save_array(base_filename + ".pos", self.pos) - self.save_array(base_filename + ".vel", self.vel) - self.save_array(base_filename + ".best", self.best) + self.save_array(Path(base_filename).with_suffix(".pos"), self.pos) + self.save_array(Path(base_filename).with_suffix(".vel"), self.vel) + self.save_array(Path(base_filename).with_suffix(".best"), self.best) def load_population(self, base_filename): """ @@ -1096,9 +1094,9 @@ class Population(object): the files must have the same format as produced by save_population. the files must have the same number of rows. """ - self.pos = self.load_array(base_filename + ".pos", self.pos) - self.vel = self.load_array(base_filename + ".vel", self.vel) - self.best = self.load_array(base_filename + ".best", self.best) + self.pos = self.load_array(Path(base_filename).with_suffix(".pos"), self.pos) + self.vel = self.load_array(Path(base_filename).with_suffix(".vel"), self.vel) + self.best = self.load_array(Path(base_filename).with_suffix(".best"), self.best) self.size_act = self.pos.shape[0] def save_results(self, filename): @@ -1107,6 +1105,9 @@ class Population(object): """ self.save_array(filename, self.results) + def render_population(self, base_filename): + pass + class PopulationHandler(handlers.ModelHandler): """ @@ -1167,6 +1168,8 @@ class PopulationHandler(handlers.ModelHandler): self._invalid_limit = 10 self.patch_file = "pmsco_patch.pop" self._patch_last_mtime = 0 + self._diag_path = None + self._results_path = None def setup(self, project, slots): """ @@ -1200,8 +1203,13 @@ class PopulationHandler(handlers.ModelHandler): self._pop_size = _req_size if _req_size >= _min_size else _def_size self.setup_population() self._invalid_limit = self._pop_size * 10 + of = Path(self._project.output_file) + dp = of.parent / "diag" + dp.mkdir(exist_ok=True) + self._diag_path = dp / of.name + self._results_path = of.with_suffix(".dat") - with open(self._project.output_file + ".dat", "w") as outfile: + with open(self._results_path, "wt", encoding="latin1") as outfile: outfile.write("# ") outfile.write(" ".join(self._pop.results.dtype.names)) outfile.write("\n") @@ -1256,7 +1264,7 @@ class PopulationHandler(handlers.ModelHandler): self._check_patch_file() self._pop.advance_population() - for pos in self._pop.pos_gen(): + for pos, vel in zip(self._pop.pos_gen(), self._pop.vel_gen()): time_pending += self._model_time if time_pending > time_avail: self._timeout = True @@ -1268,6 +1276,7 @@ class PopulationHandler(handlers.ModelHandler): new_task = parent_task.copy() new_task.parent_id = parent_id new_task.model = pos + new_task.delta = vel new_task.change_id(model=pos['_model']) new_tasks.append(new_task) @@ -1322,9 +1331,8 @@ class PopulationHandler(handlers.ModelHandler): assert not math.isnan(task.rfac) task.model['_rfac'] = task.rfac self._pop.add_result(task.model, task.rfac) - self._pop.save_population(self._project.output_file + ".pop") - with open(self._project.output_file + ".dat", "a") as outfile: + with open(self._results_path, "at", encoding="latin1") as outfile: s = (str(task.model[name]) for name in self._pop.results.dtype.names) outfile.write(" ".join(s)) outfile.write("\n") @@ -1364,6 +1372,6 @@ class PopulationHandler(handlers.ModelHandler): """ super(PopulationHandler, self).save_report(root_task) - files = grfactor.render_results(self._project.output_file + ".dat", self._pop.results) + files = grfactor.render_results(Path(self._project.output_file).with_suffix(".dat"), self._pop.results) for f in files: self._project.files.add_file(f, root_task.id.model, "report") diff --git a/pmsco/optimizers/swarm.py b/pmsco/optimizers/swarm.py index 8e91ae4..5da5a28 100644 --- a/pmsco/optimizers/swarm.py +++ b/pmsco/optimizers/swarm.py @@ -10,20 +10,16 @@ D. A. Duncan et al., Surface Science 606, 278 (2012) @author Matthias Muntwiler, matthias.muntwiler@psi.ch -@copyright (c) 2015-18 by Paul Scherrer Institut @n +@copyright (c) 2015-21 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function import logging import numpy as np import pmsco.optimizers.population as population -from pmsco.helpers import BraceMessage as BMsg logger = logging.getLogger(__name__) diff --git a/pmsco/optimizers/table.py b/pmsco/optimizers/table.py index b9e7171..a3b3937 100644 --- a/pmsco/optimizers/table.py +++ b/pmsco/optimizers/table.py @@ -130,6 +130,8 @@ class TablePopulation(population.Population): """ super(TablePopulation, self).setup(size, model_space, **kwargs) self.table_source = kwargs['table_source'] + self.size_act = 0 + self.model_count = 0 def advance_population(self): """ @@ -141,6 +143,7 @@ class TablePopulation(population.Population): @return: None """ self.import_positions(self.table_source) + self.generation += 1 self.advance_from_import() super(TablePopulation, self).advance_population() diff --git a/pmsco/pmsco.py b/pmsco/pmsco.py old mode 100755 new mode 100644 index 567fa14..7a4051b --- a/pmsco/pmsco.py +++ b/pmsco/pmsco.py @@ -1,31 +1,35 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ @package pmsco.pmsco -PEARL Multiple-Scattering Calculation and Structural Optimization +PSI Multiple-Scattering Calculation and Structural Optimization -this is the top-level interface of the PMSCO package. -all calculations (any mode, any project) start by calling the run_project() function of this module. -the module also provides a command line and a run-file/run-dict interface. +This is the top-level interface of the PMSCO package. +All calculations (any mode, any project) start by calling the run_project function of this module. +The module also provides a command line, a run-file, and a run-dict interface. +They all, in one way or another, set up an instance of a Project class and call the run_project function. -for parallel execution, prefix the command line with mpi_exec -np NN, where NN is the number of processes to use. -note that in parallel mode, one process takes the role of the coordinator (master). -the master does not run calculations and is idle most of the time. -to benefit from parallel execution on a work station, NN should be the number of processors. -on a cluster, the number of processes is chosen according to the available resources. +For parallel execution, prefix the command line with mpi_exec -np NN, where NN is the number of processes to use. +Note that in parallel mode, one process takes the role of the coordinator (master). +The master does not run calculations and is idle most of the time. +To benefit from parallel execution on a work station, NN should be the number of processors. +On a cluster, the number of processes should be chosen according to the available resources. -all calculations can also be run in a single process. +All calculations can also be run in a single process. PMSCO serializes the calculations automatically. -the code of the main module is independent of a particular calculation project. -all project-specific code must be in a separate python module. -the project module must implement a class derived from pmsco.project.Project, -and call run_project() with an instance of the project class. -refer to the projects folder for examples. +The code of the main module is independent of a particular calculation project. +All project-specific code must be in a separate python module. +The project module must implement a class derived from pmsco.project.Project. +The project module and class must be referenced in the run-file, or passed to the suitable run-function. + +While they are not strictly necessary, run-files help to separate code and data. +Code is usually version-controlled, run-files contain metadata of calculations and should be kept with the results. +A git hash can be used to refer to the code used to execute the calculation. @author Matthias Muntwiler, matthias.muntwiler@psi.ch -@copyright (c) 2015-21 by Paul Scherrer Institut @n +@copyright (c) 2015-23 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -33,12 +37,16 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n """ import argparse -from builtins import range -import logging +from collections.abc import Mapping import importlib -import commentjson as json +import importlib.util +import json +import jsonschema +import logging +import os from pathlib import Path import sys +import typing try: from mpi4py import MPI @@ -55,10 +63,9 @@ pmsco_root = Path(__file__).resolve().parent.parent if str(pmsco_root) not in sys.path: sys.path.insert(0, str(pmsco_root)) +from pmsco.database.git import get_git_hash import pmsco.dispatch as dispatch -import pmsco.files as files -import pmsco.handlers as handlers -from pmsco.optimizers import genetic, swarm, grid, table +from pmsco.project import Project # the module-level logger logger = logging.getLogger(__name__) @@ -94,6 +101,7 @@ def setup_logging(enable=False, filename="pmsco.log", level="WARNING"): numeric_level = getattr(logging, level.upper(), logging.WARNING) root_logger = logging.getLogger() root_logger.setLevel(numeric_level) + logging.getLogger('matplotlib').setLevel(logging.WARNING) if enable: if mpi_size > 1: @@ -112,49 +120,6 @@ def setup_logging(enable=False, filename="pmsco.log", level="WARNING"): root_logger.addHandler(handler) -def set_common_args(project, args): - """ - set common project arguments from parsed command line. - - this function translates and distributes the common arguments from the command line parser - to the respective destinations. - as of this writing, there are two destinations: the global logger and the project instance. - - note that run_project() is called with the project instance as the only argument. - all project-related arguments from the command line must therefore be copied to the project object. - - @param args: a namespace object containing the necessary parameters. - this can be an instance of Args, or the return value of parse_cli(), - or any object which has the same attributes as the Args class. - - @return: None - """ - - if args.data_dir: - project.data_dir = args.data_dir - if args.output_file: - project.output_file = args.output_file - if args.db_file: - project.db_file = args.db_file - if args.log_file: - project.log_file = args.log_file - if args.log_level: - project.log_level = args.log_level - if not args.log_enable: - project.log_file = "" - project.log_level = "" - if args.mode: - project.mode = args.mode.lower() - if args.time_limit: - project.time_limit = args.time_limit - if args.keep_files: - project.keep_files = args.keep_files - if args.keep_levels: - project.keep_levels = max(args.keep_levels, project.keep_levels) - if args.keep_best: - project.keep_best = max(args.keep_best, project.keep_best) - - def run_project(project): """ run a calculation project. @@ -179,36 +144,18 @@ def run_project(project): if mpi_rank == 0: project.log_project_args() + if not project.git_hash: + project.git_hash = get_git_hash() + project.validate() - optimizer_class = None - if project.mode == 'single': - optimizer_class = handlers.SingleModelHandler - elif project.mode == 'grid': - optimizer_class = grid.GridSearchHandler - elif project.mode == 'swarm': - optimizer_class = swarm.ParticleSwarmHandler - elif project.mode == 'genetic': - optimizer_class = genetic.GeneticOptimizationHandler - elif project.mode == 'gradient': - logger.error("gradient search not implemented") - # TODO: implement gradient search - # optimizer_class = gradient.GradientSearchHandler - elif project.mode == 'table': - optimizer_class = table.TableModelHandler - else: - logger.error("invalid optimization mode '%s'.", project.mode) - project.handler_classes['model'] = optimizer_class - - project.handler_classes['region'] = handlers.choose_region_handler_class(project) - - if project and optimizer_class: + if project: logger.info("starting calculations") try: dispatch.run_calculations(project) except (SystemExit, KeyboardInterrupt): raise - except Exception as __: + except Exception: logger.exception("unhandled exception during calculations.") raise else: @@ -223,6 +170,8 @@ def schedule_project(project, run_dict): the function validates the project and submits a job to the scheduler. + placeholders in run-file's directories dict are resolved. + @param project: fully initialized project object. the validate method is called as part of this function. @@ -234,117 +183,234 @@ def schedule_project(project, run_dict): setup_logging(enable=False) project.validate() + try: + dirs = run_dict['project']['directories'] + for k in dirs: + dirs[k] = str(project.directories[k]) + except KeyError: + pass + if project.git_hash: + run_dict['project']['git_hash'] = project.git_hash + elif hsh := get_git_hash(): + run_dict['project']['git_hash'] = hsh + if project.db_file: + run_dict['project']['db_file'] = str(project.db_file) + if sf := project.optimizer_params['seed_file']: + run_dict['project']['optimizer_params']['seed_file'] = str(sf) schedule_dict = run_dict['schedule'] - module = importlib.import_module(schedule_dict['__module__']) + module = _load_module(schedule_dict['__module__']) schedule_class = getattr(module, schedule_dict['__class__']) schedule = schedule_class(project) - schedule.set_properties(module, schedule_dict, project) + schedule.set_properties(vars(module), schedule_dict, project) schedule.run_dict = run_dict schedule.validate() schedule.submit() -class Args(object): +def _load_runfile(runfile: typing.Union[typing.Dict, str, bytes, os.PathLike, typing.TextIO]) -> typing.Mapping: """ - arguments of the main function. - - this class can be used to set up an arguments object for the main - function as an alternative to the __main__ function which parses - command line arguments. - - the constructor initializes the attributes with the same default - values as the command line parser. + Load a runfile + + The function loads a runfile from a dictionary, an open json file object, or a json file specified by a file path. + If the source is a file, the directory is added to the project directories under the `run` key. + + @param runfile: Dictionary with contents of a runfile, an open file object, or a path-like. + @return: Dictionary with the contents of the runfile. """ - def __init__(self): - """ - constructor. - - the parameters are the same as for the command line interface. - project and mode are mandatory. - other parameters may be required depending on the project - and/or the calculation mode. - """ - self.data_dir = "" - self.output_file = "" - self.db_file = "" - self.time_limit = 24.0 - self.keep_files = files.FILE_CATEGORIES_TO_KEEP - self.keep_best = 10 - self.keep_levels = 1 - self.log_level = "WARNING" - self.log_file = "" - self.log_enable = True + def set_run_dir(fileobj): + try: + p = Path(fileobj.name).parent.resolve(True) + rf['project']['directories']['run'] = p + except (AttributeError, FileNotFoundError): + pass + + if isinstance(runfile, Mapping): + rf = runfile + elif hasattr(runfile, 'read'): + rf = json.load(runfile) + set_run_dir(runfile) + else: + with open(runfile, 'r') as f: + rf = json.load(f) + set_run_dir(f) + + schema_dir = Path(__file__).parent / "schema" + schema_file = schema_dir / "runfile.schema.json" + schema_url = f"file://{schema_dir}/" + with open(schema_file) as f: + schema = json.load(f) + + resolver = jsonschema.RefResolver(schema_url, None) + jsonschema.validate(rf, schema, resolver=resolver) + + return rf + + +def _load_module(name_or_path: typing.Union[str, bytes, os.PathLike]): + """ + Load a Python module + + @param name_or_path: Module name or file path of the module. + If a module name is given, the module must be in the Python module search path. + @return: module + @raise ValueError if the module is not found + """ + + try: + return importlib.import_module(name_or_path) + except ImportError: + p = Path(name_or_path) + module_name = p.stem + spec = importlib.util.spec_from_file_location(module_name, name_or_path) + try: + module = importlib.util.module_from_spec(spec) + except AttributeError: + msg = f"Can't find module {name_or_path}" + print(msg, sys.stderr) + print("sys.path:", sys.path, sys.stderr) + raise ValueError(msg) + + sys.modules[module_name] = module + spec.loader.exec_module(module) + return module + + +def main_project(symbols: typing.Optional[typing.Dict[str, typing.Any]] = None, + project: typing.Optional[Project] = None, + project_module: typing.Optional[typing.Union[str, os.PathLike]] = None, + project_class: typing.Optional[typing.Union[str, typing.Type[Project]]] = None, + runfile: typing.Union[typing.Dict, str, bytes, os.PathLike, typing.TextIO] = None): + + """ + Main function with optional arguments. + + This function starts the whole process based on function arguments. + The arguments can be a an existing project instance, a project class, and/or a runfile. + + The function carries out the following steps: + + 1. Load a runfile - if specified. + 2. Create a project object. + 3. Apply the runfile to the project. + 4. Run or schedule the project. + + The project instance is produced from the first match of the following conditions: + + 1. `project` argument is a Project instance. + 2. `project_class` is a Project class. + 3. `__class__` entry from runfile. + The class must be listed in symbols, + or the runfile must also contain a `__module__` entry + with the name or file path of the project module that declares the class. + + The project is scheduled rather than executed if the corresponding section in the runfile is present. + + @param symbols: Namespace of the project module, which contains project, cluster and calculator classes. + This is the basis for class resolution from runfiles. + If called by the project module, it should pass vars(). + @param project: project instance. + @param project_class: project class or name of a project class defined in `symbols`. + @param project_module: name or file path of the project module. + This is required if symobls is not defined + and the project class is given as a string (project_class argument or runfile value). + @param runfile: A file-like, path-like or dict with runfile contents. + Runfiles must be in json-format. + @return: None + """ + + if runfile is not None: + rf = _load_runfile(runfile) + rfp = rf['project'] + else: + rf = None + rfp = None + + if project is None: + if project_class is None or not issubclass(project_class, Project): + project_classname = project_class + if not project_classname: + project_classname = rfp['__class__'] + + if not symbols: + if project_module: + module = _load_module(project_module) + symbols = vars(module) + else: + module = _load_module(rfp['__module__']) + symbols = vars(module) + + project_class = symbols[project_classname] + + project = project_class() + + project.directories['pmsco'] = Path(__file__).parent + try: + project.directories['project'] = Path(module.__file__).parent + except AttributeError: + pass + + if rfp: + project.set_properties(symbols, rfp, project) + + try: + schedule_enabled = rf['schedule']['enabled'] + except KeyError: + schedule_enabled = False + if schedule_enabled: + schedule_project(project, rf) + else: + run_project(project) def get_cli_parser(): - KEEP_FILES_CHOICES = files.FILE_CATEGORIES | {'all'} - parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=""" - multiple-scattering calculations and optimization + PSI multiple-scattering calculations and optimization (PMSCO) + + This is the main command line entry point for PMSCO calculation jobs. + Alternative entry points can be provided by project modules. + The command line requires at least a run-file to define the project parameters. - you must call pmsco.py from a project file which defines the calculation project. - the project file must be a regular Python module and define: + The command can run a calculation job directly or submit it to a job queue + via the `schedule` section in the run-file. + The program detects whether it runs in a single-process or OpenMPI multi-process environment + and coordinates parallel processes automatically. - 1) a project class derived from pmsco.project.Project. - the class implements/overrides all necessary methods of the calculation project, - in particular create_model_space, create_cluster, and create_params. - - 2) a global function named create_project. - the function accepts a namespace object from the argument parser. - it may evaluate extra, project-specific arguments. - it does not need to evaluate the common parameters described below. - the function must return an instance of the project class described above. - - 3) main code that parses the command line and calls pmsco.pmsco.main_pmsco(). - (see the projects folder for examples). + All arguments should preferably be declared in the run-file. + A small number of options can be passed on the command line + to override the corresponding parameter of the run-file. + + Please see the documentation that is compiled in docs/html/index.html + for instructions how to set up a project module and run-files. + See also the projects folder for examples. """) - # the required argument list may depend on the calculation mode. - # for simplicity, the parser does not check these requirements. - # all parameters are optional and accepted regardless of mode. - # errors may occur if implicit requirements are not met. - parser.add_argument('project_module', nargs='?', - help="path to custom module that defines the calculation project") + parser.add_argument('-r', '--run-file', - help="path to run-time parameters file which contains all program arguments. " + - "must be in JSON format.") - parser.add_argument('-m', '--mode', - choices=['single', 'grid', 'swarm', 'genetic', 'table'], - help='calculation mode') - parser.add_argument('-d', '--data-dir', - help='directory path for experimental data files (if required by project). ' + - 'default: working directory') - parser.add_argument('-o', '--output-file', - help='base path for intermediate and output files.') - parser.add_argument('-b', '--db-file', - help='name of an sqlite3 database file where the results should be stored.') - parser.add_argument('-k', '--keep-files', nargs='*', - choices=KEEP_FILES_CHOICES, - help='output file categories to keep after the calculation. ' - 'by default, cluster and model (simulated data) ' - 'of a limited number of best models are kept.') - parser.add_argument('--keep-best', type=int, - help='number of best models for which to keep result files ' - '(at each node from root down to keep-levels).') - parser.add_argument('--keep-levels', type=int, choices=range(5), - help='task level down to which result files of best models are kept. ' - '0 = model, 1 = scan, 2 = domain, 3 = emitter, 4 = region.') - parser.add_argument('-t', '--time-limit', type=float, - help='wall time limit in hours. the optimizers try to finish before the limit.') - parser.add_argument('--log-file', - help='name of the main log file. ' + - 'under MPI, the rank of the process is inserted before the extension.') - parser.add_argument('--log-level', - help='minimum level of log messages. DEBUG, INFO, WARNING, ERROR, CRITICAL.') - feature_parser = parser.add_mutually_exclusive_group(required=False) - feature_parser.add_argument('--log-enable', dest='log_enable', action="store_true", - help="enable logging. by default, logging is on.") - feature_parser.add_argument('--log-disable', dest='log_enable', action='store_false', - help="disable logging. by default, logging is on.") - parser.set_defaults(log_enable=True) + help="Path to a run-file in JSON format which contains all calculation parameters. " + "This argument is mandatory. " + ) + parser.add_argument('-m', '--module', + help="File name of the custom project module. " + "The module must declare the project class and other project-specific classes. " + "This optional argument overrides the __module__ entry of the run-file. " + ) + parser.add_argument('-c', '--project-class', + help="Project class. Requires --module to be specified. " + "The project class is resolved in the namespace of the module. " + "This optional argument corresponds to the __class__ entry of the run-file. " + ) + parser.add_argument('-o', '--output-dir', + help="Output directory. " + "This optional argument overrides the directories['output'] entry of the run-file." + ) + parser.add_argument('-j', '--job-name', + help="Job name. Should be short and valid as a part of directory and file names. " + "If a persistent database is used, it must not exist in the database yet. " + "This optional argument overrides the job_name of the run-file." + ) return parser @@ -362,129 +428,52 @@ def parse_cli(): return args, unknown_args -def import_module(module_name): +def main(symbols: typing.Optional[typing.Dict[str, typing.Any]] = None): """ - import a custom module by name. + Main function with command line parsing - import a module given its file path or module name (like in an import statement). + This function starts the whole process with parameters from the command line. - preferably, the module name should be given as in an import statement. - as the top-level pmsco directory is on the python path, - the module name will begin with `projects` for a custom project module or `pmsco` for a core pmsco module. - in this case, the function just calls importlib.import_module. + If the command line contains a run-file parameter, it determines the project class and the project parameters. - if a file path is given, i.e., `module_name` links to an existing file and has a `.py` extension, - the function extracts the directory path, - inserts it into the python path, - and calls importlib.import_module on the stem of the file name. - - @note the file path remains in the python path. - this option should be used carefully to avoid breaking file name resolution. - - @param module_name: file path or module name. - file path is interpreted relative to the working directory. - - @return: the loaded module as a python object - """ - p = Path(module_name) - if p.is_file() and p.suffix == ".py": - path = p.parent.resolve() - module_name = p.stem - if path not in sys.path: - sys.path.insert(0, path) - - module = importlib.import_module(module_name) - return module - - -def main_dict(run_params): - """ - main function with dictionary run-time parameters - - this starts the whole process with all direct parameters. - the command line is not parsed. - no run-file is loaded (just the project module). - - @param run_params: dictionary with the same structure as the JSON run-file. + The project class can be specified either in the run-file, on the command line or the function arguments. + If the run-file specifies a class name, that class is instantiated. @return: None """ - project_params = run_params['project'] - module = importlib.import_module(project_params['__module__']) - try: - project_class = getattr(module, project_params['__class__']) - except KeyError: - project = module.create_project() - else: - project = project_class() - - project._module = module - project.directories['pmsco'] = Path(__file__).parent - project.directories['project'] = Path(module.__file__).parent - project.set_properties(module, project_params, project) - run_project(project) - - -def main(): - """ - main function with command line parsing - - this function starts the whole process with parameters from the command line. - - if the command line contains a run-file parameter, it determines the module to load and the project parameters. - otherwise, the command line parameters apply. - - the project class can be specified either in the run-file or the project module. - if the run-file specifies a class name, that class is looked up in the project module and instantiated. - otherwise, the module's create_project is called. - - @return: None - """ args, unknown_args = parse_cli() try: - with open(args.run_file, 'r') as f: - rf = json.load(f) + rf = _load_runfile(args.run_file) except AttributeError: - rfp = {'__module__': args.project_module} - else: - rfp = rf['project'] - - module = import_module(rfp['__module__']) - try: - project_args = module.parse_project_args(unknown_args) - except AttributeError: - project_args = None + rf = {'project': {}} try: - project_class = getattr(module, rfp['__class__']) - except (AttributeError, KeyError): - project = module.create_project() - else: - project = project_class() - project_args = None - - project._module = module - project.directories['pmsco'] = Path(__file__).parent - project.directories['project'] = Path(module.__file__).parent - project.set_properties(module, rfp, project) - - set_common_args(project, args) - try: - if project_args: - module.set_project_args(project, project_args) + if args.module: + rf['project']['__module__'] = args.module except AttributeError: pass try: - schedule_enabled = rf['schedule']['enabled'] - except KeyError: - schedule_enabled = False - if schedule_enabled: - schedule_project(project, rf) - else: - run_project(project) + if args.project_class: + rf['project']['__class__'] = args.project_class + except AttributeError: + pass + + try: + if args.output_dir: + rf['project']['directories']['output'] = args.output_dir + except (AttributeError, KeyError): + pass + + try: + if args.job_name: + rf['project']['job_name'] = args.job_name + except (AttributeError, KeyError): + pass + + main_project(symbols=symbols, runfile=rf) if __name__ == '__main__': diff --git a/pmsco/project.py b/pmsco/project.py index 95bb995..8747e1c 100644 --- a/pmsco/project.py +++ b/pmsco/project.py @@ -19,7 +19,7 @@ the ModelSpace and CalculatorParams classes are typically used unchanged. @author Matthias Muntwiler, matthias.muntwiler@psi.ch -@copyright (c) 2015-21 by Paul Scherrer Institut @n +@copyright (c) 2015-25 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -27,173 +27,271 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n """ import collections -import copy import datetime -import git import logging +import os import numpy as np +import numpy.typing as npt from pathlib import Path -import socket +import re +from typing import Any, Callable, Dict, Generator, Iterable, List, Mapping, Optional, Sequence, Set, Tuple, Union -from pmsco.calculators.calculator import InternalAtomicCalculator +from pmsco.calculators.calculator import Calculator, InternalAtomicCalculator from pmsco.calculators.edac import EdacCalculator -import pmsco.cluster +from pmsco.cluster import Cluster import pmsco.config as config -from pmsco.compat import open -import pmsco.data as md -import pmsco.database -import pmsco.dispatch -import pmsco.files -import pmsco.handlers +import pmsco.data +import pmsco.database.project as db_project +from pmsco.dispatch import CalcID, CalculationTask +from pmsco.files import FileTracker, FILE_CATEGORIES_TO_KEEP +from pmsco.handlers import (DomainHandler, EmitterHandler, EnergyRegionHandler, ScanHandler, + SingleModelHandler, SingleRegionHandler, TaskHandler) from pmsco.helpers import BraceMessage as BMsg +from pmsco.optimizers.genetic import GeneticOptimizationHandler +from pmsco.optimizers.swarm import ParticleSwarmHandler +from pmsco.optimizers.grid import GridSearchHandler +from pmsco.optimizers.table import TableModelHandler +from pmsco.reports.base import ProjectReport +from pmsco.scan import Scan, ScanLoader, ScanCreator logger = logging.getLogger(__name__) ParamSpace = collections.namedtuple('ParamSpace', ['start', 'min', 'max', 'step']) +Numeric = Union[int, float, np.number] +PathLike = Union[str, os.PathLike] -class ModelSpace(object): + +class ModelSpace(config.ConfigurableObject): """ Domain of model parameters. - Each member contains a dictionary of model parameter names and their values. - Parameter names can be defined almost freely by the project, - except that they should contain only alphanumeric and underscore characters. - furthermore, names starting with an underscore are reserved for the optimizers. + The model space declares the model parameters and defines their domain. + A dimension can have a finite range or fixed value. + In the case of a range, start and step values can be given. + + Parameter names can be defined almost freely by the project. + They must contain only alphanumeric and underscore characters. + Names starting with an underscore are reserved for the optimizers. + + The object storage is organized by start, min, max and step dictionaries containing the parameters. + The `add_param` and `get_param` give access to a `ParamSpace` structure by parameter name. """ ## @var start (dict) - # dictionary of start values for each model parameter. + # Dictionary of start values for each model parameter. # - # the start value can be the initial guess for an optimization run, + # The start value can be the initial guess for an optimization run, # or the actual value for a single calculation. # - # there must be one item for each model parameter, + # There must be one item for each model parameter, # where the key is the name of the parameter, and the value its physical value. ## @var min (dict) - # dictionary of minimum values for each model parameter. + # Dictionary of minimum values for each model parameter. # - # the minimum defines the lower bound of the allowed interval for a model parameter. + # The minimum defines the lower bound of the allowed interval for a model parameter. # - # there must be one item for each model parameter, + # There must be one item for each model parameter, # where the key is the name of the parameter, and the value its physical value. ## @var max (dict) - # dictionary of maximum values for each model parameter. + # Dictionary of maximum values for each model parameter. # - # the maximum defines the upper bound of the allowed interval for a model parameter. + # The maximum defines the upper bound of the allowed interval for a model parameter. # - # there must be one item for each model parameter, + # There must be one item for each model parameter, # where the key is the name of the parameter, and the value its physical value. ## @var step (dict) - # dictionary of step sizes for each model parameter. + # Dictionary of step sizes for each model parameter. # - # depending on the optimization mode, the step is a guess of how fast values should vary, + # Depending on the optimization mode, the step is a guess of how fast values should vary, # e.g. step size, gradient, velocity, ... # - # there must be one item for each model parameter, + # There must be one item for each model parameter, # where the key is the name of the parameter, and the value its physical value. def __init__(self): """ initialize the domain object with empty dictionaries. """ - self.start = {} - self.min = {} - self.max = {} - self.step = {} + super().__init__() + self.start: Dict[str, Numeric] = {} + self.min: Dict[str, Numeric] = {} + self.max: Dict[str, Numeric] = {} + self.step: Dict[str, Numeric] = {} - def add_param(self, name, start, min=None, max=None, step=None, width=None): + def _eval_param_value(self, expr: Optional[Union[Numeric, str]]) -> Numeric: """ - set the domain of one parameter with all necessary values at once. + Evaluate a parameter expression - the exact meaning of the arguments depends on the calculation mode. + If the expression has a numeric type, it is cast to numpy.float64. + Else, it is evaluated using Python's `eval` function. + The expression may use the symbols from `self.project_symbols`. + These normally include built-in functions as well as the `math` and `numpy` modules. - @param name (string) name of the parameter (alphanumeric and underscore characters only). - it is recommended to use short but distinctive names. + This function is used to parse expressions from a runfile. - @param start (float) start value. - - @param min (float) lower bound of the parameter interval. - must be lower or equal to start. - if None, the field is set to start. - - @param max (float) upper bound of the parameter interval. - must be greater or equal to start. - if None, the field is set to start. - - @param width (float) width of the parameter interval. - instead of min and max, the interval can be set centered around the start value. - this is equivalent to min = start - width/2, max = start + width/2. - this argument overrides min and max. don't use both arguments. - - @param step (float) step size. - must be greater or equal to zero. - if None, the field is set to zero. + @param expr: Numeric value or string expression. + numpy.nan or None result in numpy.nan. + Empty string results in a ValueError. + @return: numpy.float64, numpy.nan if expr has a wrong type (e.g. NoneType) + @raise Exceptions that occur during evaluation of a string expression are passed on. """ + + try: + value = np.float64(expr) + except ValueError: + if expr: + value = np.float64(eval(expr, self.project_symbols)) + else: + raise + except TypeError: + value = np.nan + + return value + + def add_param(self, + name: str, + start: Numeric, + min: Optional[Numeric] = None, + max: Optional[Numeric] = None, + step: Optional[Numeric] = None, + width: Optional[Numeric] = None) -> None: + + """ + Set the domain of one parameter with all necessary values at once. + + The exact meaning of the arguments depends on the calculation mode. + + The parameters can be given as floats or expressions that evaluate to scalar values. + Expressions may use built-in functions and the math or numpy module. + + @param name Name of the parameter (alphanumeric and underscore characters only). + It is recommended to use short but distinctive names. + + @param start Start value. + + @param min lower bound of the parameter interval. + Must be lower or equal to start. + If None, the field is set to start. + + @param max Upper bound of the parameter interval. + Must be greater or equal to start. + If None, the field is set to start. + + @param width Width of the parameter interval. + Instead of min and max, the interval can be set centered around the start value. + This is equivalent to min = start - width/2, max = start + width/2. + This argument overrides min and max. Don't use both arguments. + + @param step Step size. + Must be greater or equal to zero. + If None, the field is set to zero. + + @raise ValueError if invalid values of required arguments are given. + """ + + if not name: + raise ValueError("Parameter name cannot be empty") + + try: + start = self._eval_param_value(start) + except (AttributeError, KeyError, TypeError, ValueError, SyntaxError): + raise ValueError(f"Invalid start value of parameter {name}: {start}") + if np.isnan(start): + raise ValueError(f"Missing start value of parameter {name}") + + try: + min = self._eval_param_value(min) if min is not None else np.nan + except (AttributeError, KeyError, NameError, TypeError, ValueError, SyntaxError): + raise ValueError(f"Invalid min value of parameter {name}: {min}") + + try: + max = self._eval_param_value(max) if max is not None else np.nan + except (AttributeError, KeyError, NameError, TypeError, ValueError, SyntaxError): + raise ValueError(f"Invalid max value of parameter {name}: {max}") + + try: + width = self._eval_param_value(width) if width is not None else np.nan + except (AttributeError, KeyError, NameError, TypeError, ValueError, SyntaxError): + raise ValueError(f"Invalid width value of parameter {name}: {width}") + + try: + step = self._eval_param_value(step) if step is not None else np.nan + except (AttributeError, KeyError, NameError, TypeError, ValueError, SyntaxError): + raise ValueError(f"Invalid step value of parameter {name}: {step}") + else: + if step < 0: + raise ValueError(f"Invalid step value of parameter {name}: {step}") + self.start[name] = start - self.min[name] = min if min is not None else start - self.max[name] = max if max is not None else start - if width is not None: + self.min[name] = min if not np.isnan(min) else start + self.max[name] = max if not np.isnan(max) else start + if not np.isnan(width): self.min[name] = start - width / 2. self.max[name] = start + width / 2. - self.step[name] = step if step is not None else 0.0 + self.step[name] = step if not np.isnan(step) else 0.0 - def get_param(self, name): + def get_param(self, name: str) -> ParamSpace: """ - get all values of a model parameter in a tuple. + Get all values of a model parameter in a named tuple. - @param name (string) name of the parameter. + @param name Name of the parameter. - @return named tuple ParamSpace(start, min, max, step) of the parameter. + @return named tuple `ParamSpace(start, min, max, step)` of the parameter. @raise IndexError if the parameter is not defined. """ + return ParamSpace(self.start[name], self.min[name], self.max[name], self.step[name]) - def set_param_dict(self, d): + def set_param_dict(self, d: Dict[str, Dict[str, Numeric]]) -> None: """ - initialize model space from dictionary. + Initialize model space from dictionary. - @param d: dictionary with two levels: - the top level are parameter names, + @param d: Dictionary with two levels: + The top level are parameter names, the second level the space descriptors 'start', 'min', 'max', 'step' and 'width'. - see add_param() for possible combinations. + The values can be numeric values or expressions that evaluate to scalar values. + See add_param() for possible combinations and accepted values. @return: None """ + self.__init__() for k, v in d.items(): self.add_param(k, **v) - def get_param_dict(self): + def get_param_dict(self) -> Dict[str, Dict[str, Numeric]]: """ - return model space parameters in dictionary form + Return model space parameters in dictionary form. - the top level are parameter names, + The top level are parameter names, the second level the space descriptors 'start', 'min', 'max' and 'step'. @return: dict """ + d = {} for name in self.start: - d[name] = {self.start[name], self.min[name], self.max[name], self.step[name]} + d[name] = {'start': self.start[name], 'min': self.min[name], 'max': self.max[name], 'step': self.step[name]} + return d -class CalculatorParams(object): +class CalculatorParams: """ - calculation parameters for a single scattering calculation job. + Calculation parameters for a single scattering calculation job. - this class holds all the calculation parameters that are passed via input file to the calculation program. + This class holds all the calculation parameters that are passed via input file to the calculation program. - the class can hold parameters for both the MSC and EDAC codes. - some parameters are used by both codes, others are used just by one of them. - newer features such as multiple emitters, multiple domains, and others are supported in EDAC mode only. + The class can hold parameters for both the MSC and EDAC codes. + Some parameters are used by both codes, others are used just by one of them. + Newer features such as multiple emitters, multiple domains, and others are supported in EDAC mode only. MSC mode is currently not maintained. - objects of this class are created by the implementation of the create_params() method + Objects of this class are created by the implementation of the create_params() method of the actual project class. """ @@ -276,49 +374,49 @@ class CalculatorParams(object): # def __init__(self): - self.title = "default parameters" - self.comment = "set by project.CalculatorParams()" - self.cluster_file = "" - self.output_file = "" - self.scan_file = "" - self.initial_state = "1s" - self.binding_energy = 0.0 - self.polarization = "H" - self.angular_resolution = 1.0 - self.z_surface = 0.0 - self.inner_potential = 10.0 - self.work_function = 0.0 - self.symmetry_range = 360.0 - self.polar_incidence_angle = 60.0 - self.azimuthal_incidence_angle = 0.0 - self.experiment_temperature = 300.0 - self.debye_temperature = 400.0 - self.debye_wavevector = 1.0 - self.phase_files = {} - self.rme_files = {} - self.rme_minus_value = 0.1 - self.rme_minus_shift = 0.0 - self.rme_plus_value = 1.0 - self.rme_plus_shift = 0.0 + self.title: str = "default parameters" + self.comment: str = "set by project.CalculatorParams()" + self.cluster_file: PathLike = "" + self.output_file: PathLike = "" + self.scan_file: PathLike = "" + self.initial_state: str = "1s" + self.binding_energy: Numeric = 0.0 + self.polarization: str = "H" + self.angular_resolution: Numeric = 1.0 + self.z_surface: Numeric = 0.0 + self.inner_potential: Numeric = 10.0 + self.work_function: Numeric = 0.0 + self.symmetry_range: Numeric = 360.0 + self.polar_incidence_angle: Numeric = 60.0 + self.azimuthal_incidence_angle: Numeric = 0.0 + self.experiment_temperature: Numeric = 300.0 + self.debye_temperature: Numeric = 400.0 + self.debye_wavevector: Numeric = 1.0 + self.phase_files: Dict[int, PathLike] = {} + self.rme_files: Dict[int, PathLike] = {} + self.rme_minus_value: Numeric = 0.1 + self.rme_minus_shift: Numeric = 0.0 + self.rme_plus_value: Numeric = 1.0 + self.rme_plus_shift: Numeric = 0.0 # used by MSC only - self.spherical_order = 2 - self.scattering_level = 5 - self.fcut = 15.0 - self.cut = 15.0 - self.lattice_constant = 1.0 - self.msq_displacement = {} + self.spherical_order: int = 2 + self.scattering_level: int = 5 + self.fcut: Numeric = 15.0 + self.cut: Numeric = 15.0 + self.lattice_constant: Numeric = 1.0 + self.msq_displacement: Dict[int, Numeric] = {} self.planewave_attenuation = 1.0 self.vibration_model = "N" self.substrate_atomic_mass = 1.0 # used by EDAC only - self.emitters = [(0.0, 0.0, 0.0, 0)] - self.lmax = 15 - self.dmax = 5.0 - self.orders = [20] - self.phase_output_classes = None + self.emitters: Iterable[Tuple[Numeric, Numeric, Numeric, int]] = [(0.0, 0.0, 0.0, 0)] + self.lmax: int = 15 + self.dmax: Numeric = 5.0 + self.orders: Iterable[int] = [20] + self.phase_output_classes: Optional[Union[int, Iterable[int]]] = None @property - def l_init(self): + def l_init(self) -> int: """ initial state l quantum number. @@ -329,445 +427,155 @@ class CalculatorParams(object): return "spdf".index(self.initial_state[1]) -class Scan(object): +class ProjectDirectories(collections.UserDict): """ - class to describe the scanning scheme or store the experimental data set. + Dictionary of project directories + + This class encapsulates a mapping of keys to directory paths and methods to resolve placeholders. + Placeholders have the format `${identifier}` and resolve to the correspondingly named items from: + - this dictionary itself + - the project attributes listed in project_attr + - the project tags + - extra mapping provided to the resolve method + + The resolve_directories() method resolves all items at once, + The resolve_path() method resolves just one item. + + The dictionary values can be strings or pathlib.Path objects. + + Initial values can be passed to the constructor as a dictionary or keyword arguments. """ - ## @var filename (string) - # file name from which a scan was loaded + def __init__(self, project_: 'Project', *args, **kwargs): + args = [self, *args] + super().__init__(*args, **kwargs) + self.project = project_ + self.project_attr = ['project_name', 'job_name', 'mode'] - ## @var raw_data (numpy.ndarray) - # original scan data (ETPAIS array) - - ## @var dtype (dict) - # data type of self.raw_data. - # - # one of the data.DTYPE_Xxxx constants. - - ## @var modulation (numpy.ndarray) - # modulation function calculated from original scan (ETPAIS array) - - ## @var mode (list of characters) - # list of ETPAI column names which are scanned in self.raw_data. - # - # example: ['t','p'] - - ## @var emitter (string) - # chemical symbol and, optionally following, further specification (chemical state, environment, ...) - # of photo-emitting atoms. - # the interpretation of this string is up to the project and its cluster generator. - # it should, however, always start with a chemical element symbol. - # - # examples: 'Ca' (calcium), 'CA' (carbon A), 'C a' (carbon a), 'C 1' (carbon one), 'N=O', 'FeIII'. - - ## @var initial_state (string) - # nl term of initial state - # - # in the form expected by EDAC, for example: '1s' - - ## @var energies (numpy.ndarray) - # kinetic energy referenced to Fermi level. - # - # one-dimensional array. - - ## @var thetas (numpy.ndarray) - # polar angle referenced to normal emission - # - # one-dimensional array. - # - # note: in the case of a hemispherical scan, the values in this array will not be unique. - - ## @var phis (numpy.ndarray) - # azimuthal angle referenced to arbitrary origin - # - # one-dimensional array. - # - # note: in the case of a hemispherical scan, the values in this array will not be unique, and not monotonic. - - ## @var alphas (numpy.ndarray) - # polar angle referenced to normal emission - # - # one-dimensional array. - - def __init__(self): - self.filename = "" - self.raw_data = None - self.dtype = None - self.modulation = None - self.mode = [] - self.emitter = "" - self.initial_state = "1s" - self.positions = { - 'e': np.empty(0), - 't': np.empty(0), - 'p': np.empty(0), - 'a': np.empty(0), - } - - @property - def energies(self): - return self.positions['e'] - - @energies.setter - def energies(self, value): - self.positions['e'] = value - - @property - def thetas(self): - return self.positions['t'] - - @thetas.setter - def thetas(self, value): - self.positions['t'] = value - - @property - def phis(self): - return self.positions['p'] - - @phis.setter - def phis(self, value): - self.positions['p'] = value - - @property - def alphas(self): - return self.positions['a'] - - @alphas.setter - def alphas(self, value): - self.positions['a'] = value - - def copy(self): - """ - create a copy of the scan. - - @return: new independent scan object with the same attributes as the original one. - """ - return copy.deepcopy(self) - - def import_scan_file(self, filename, emitter, initial_state): - """ - import the reference experiment. - - the extension must be one of msc_data.DATATYPES (case insensitive) - corresponding to the meaning of the columns in the file. - - this method does not calculate the modulation function. - - @attention EDAC can only calculate equidistant, rectangular scans. - holo scans are transparently mapped to rectangular scans by pmsco. - this method accepts the following scans: - - * intensity vs energy at fixed theta, phi - * intensity vs analyser angle vs energy at normal emission (theta = 0, constant phi) - * intensity vs theta, phi, or alpha - * holo scan (theta,phi) - - @param filename: (string) file name of the experimental data, possibly including a path. - - @param emitter: (string) chemical symbol of the photo-emitting atom, e.g. "Cu". - - @param initial_state: (string) nl term of the initial state of the atom, e.g. "2p". + def _get_path_dict(self, extra_mapping: Optional[Dict[str, PathLike]] = None, + include_absolute_paths: bool = True) -> Dict[str, PathLike]: """ - self.filename = filename - self.emitter = emitter - self.initial_state = initial_state + Auxiliary function for resolve_path - if self.filename: - self.raw_data = md.load_data(self.filename) - self.dtype = self.raw_data.dtype - self.mode, self.positions = md.detect_scan_mode(self.raw_data) + Compile a dictionary for path resolution. See @ref resolve_path. - if 'e' not in self.mode: + @param extra_mapping: Custom placeholders to substitute + @param include_absolute_paths: Include items that represent absolute paths. + If False, absolute paths from self.dictionaries are not included in the result. + @return: dictionary + """ + + d = {k: v for k, v in self.data.items() if v and (include_absolute_paths or not Path(v).is_absolute())} + for attr in self.project_attr: + d[attr] = getattr(self.project, attr) + d.update(self.project.job_tags) + if extra_mapping: + d.update(extra_mapping) + return d + + def resolve_directories(self, check: bool = True) -> None: + """ + Resolve the paths of the directories property + + @param check: Check that no placeholders remain in the resolved paths. + Else, raise a ValueError. + @return: None + """ + + pattern = r"\$\{(\w*)\}" + for i in range(len(self.data)): + self.data = {k: Path(self.resolve_path(v)) for k, v in self.data.items()} + if check: + unresolved = [path for path in self.data.values() if re.search(pattern, str(path)) is not None] + if len(unresolved) == 0: + break + else: + break + else: + raise ValueError('Cannot resolve directory placeholders') + + def resolve_path(self, path: PathLike, extra_mapping: Optional[Dict[str, PathLike]] = None, + allow_absolute: bool = True) -> PathLike: + + """ + Resolve a path or file name by template substitution + + Replace placeholders of the form `${identifier}` + by values from project-related dictionaries. + Placeholders are looked up in (in order of precedence): + - `extra_mapping` + - `project.job_tags` + - `project.mode`, `project.job_name`, `project.project_name` + - `self` + + @note Placeholders that can't be resolved are not replaced! + + @param path: (str or pathlib.Path) Template string containing placeholders in the form `${identifier}`, + or `Path` object containing one or more placeholders. + + @param extra_mapping: Custom placeholders to substitute. + + @param allow_absolute: Allow absolute paths for substitutes. + Used for internal recursive calls to prevent insertion of an absolute path. + + @return: Resolved path or string + """ + + pattern = r"\$\{(\w*)\}" + if isinstance(path, Path): + if path.is_absolute(): + allow_absolute = False + parts = [] + for p in path.parts: + parts.append(self.resolve_path(p, extra_mapping=extra_mapping, allow_absolute=allow_absolute)) + allow_absolute = False + r = Path(*parts) + elif path: + d = self._get_path_dict(extra_mapping=extra_mapping, include_absolute_paths=allow_absolute) + + def replacement(mo): try: - self.energies = np.asarray((self.raw_data['e'][0], )) - except ValueError: - logger.error("missing energy in scan file %s", self.filename) - raise + return str(d[mo.group(1)]) + except KeyError: + return mo.group(0) - if 't' not in self.mode: - try: - self.thetas = np.asarray((self.raw_data['t'][0], )) - except ValueError: - logger.info("missing theta in scan file %s, defaulting to 0.0", self.filename) - self.thetas = np.zeros(1) - - if 'p' not in self.mode: - try: - self.phis = np.asarray((self.raw_data['p'][0], )) - except ValueError: - logger.info("missing phi in scan file %s, defaulting to 0.0", self.filename) - self.phis = np.zeros(1) - - if 'a' not in self.mode: - try: - self.alphas = np.asarray((self.raw_data['a'][0], )) - except ValueError: - logger.info("missing alpha in scan file %s, defaulting to 0.0", self.filename) - self.alphas = np.zeros(1) - - def define_scan(self, positions, emitter, initial_state): - """ - define a cartesian (rectangular/grid) scan. - - this method initializes the scan with a one- or two-dimensional cartesian scan - of the four possible scan dimensions. - the scan range is given as arguments, the intensity values are initialized as 1. - the file name and modulation functions are reset to empty and None, respectively. - - the method can create the following scan schemes: - - * intensity vs energy at fixed theta, phi - * intensity vs analyser angle vs energy at normal emission (theta = 0, constant phi) - * intensity vs theta, phi, or alpha - * intensity vs theta and phi (rectangular holo scan) - - @param positions: (dictionary of numpy arrays) - the dictionary must contain a one-dimensional array for each scan dimension 'e', 't', 'p' and 'a'. - these array must contain unique, equidistant positions. - constant dimensions must contain exactly one value. - missing angle dimensions default to 0, - a missing energy dimension results in a KeyError. - - @param emitter: (string) chemical symbol of the photo-emitting atom, e.g. "Cu". - - @param initial_state: (string) nl term of the initial state of the atom, e.g. "2p". - - """ - self.filename = "" - self.emitter = emitter - self.initial_state = initial_state - self.mode = [] - shape = 1 - - try: - self.energies = np.copy(positions['e']) - except KeyError: - logger.error("missing energy in define_scan arguments") - raise + r = re.sub(pattern, replacement, str(path)) else: - if self.energies.shape[0] > 1: - self.mode.append('e') - shape *= self.energies.shape[0] + r = path - try: - self.thetas = np.copy(positions['t']) - except KeyError: - logger.info("missing theta in define_scan arguments, defaulting to 0.0") - self.thetas = np.zeros(1) - else: - if self.thetas.shape[0] > 1: - self.mode.append('t') - shape *= self.thetas.shape[0] - - try: - self.phis = np.copy(positions['p']) - except KeyError: - logger.info("missing phi in define_scan arguments, defaulting to 0.0") - self.phis = np.zeros(1) - else: - if self.phis.shape[0] > 1: - self.mode.append('p') - shape *= self.phis.shape[0] - - try: - self.alphas = np.copy(positions['a']) - except KeyError: - logger.info("missing alpha in define_scan arguments, defaulting to 0.0") - self.alphas = np.zeros(1) - else: - if self.alphas.shape[0] > 1: - self.mode.append('a') - shape *= self.alphas.shape[0] - - assert 0 < len(self.mode) <= 2, "unacceptable number of dimensions in define_scan" - assert not ('t' in self.mode and 'a' in self.mode), "unacceptable combination of dimensions in define_scan" - - self.dtype = md.DTYPE_ETPAI - self.raw_data = np.zeros(shape, self.dtype) - dimensions = [self.positions[dim] for dim in ['e', 't', 'p', 'a']] - grid = np.meshgrid(*dimensions) - for i, dim in enumerate(['e', 't', 'p', 'a']): - self.raw_data[dim] = grid[i].reshape(-1) - self.raw_data['i'] = 1 - - def load(self): - return self + return r -class ScanKey(config.ConfigurableObject): - """ - create a Scan object based on a project-supplied dictionary - - this class can be used in a run file to create a scan object based on the scan_dict attribute of the project. - this may be convenient if you're project should selectively use scans out of a long list of data files - and you don't want to clutter up the run file with parameters that don't change. - - to do so, set the key property to match an item of scan_dict. - the load method will look up the corresponding scan_dict item and construct the final Scan object. - """ - def __init__(self, project=None): - super().__init__() - self.key = "" - self.project = project - - def load(self, dirs=None): - """ - load the selected scan as specified in the project's scan dictionary - - the method uses ScanLoader or ScanCreator as an intermediate. - - @return a new Scan object which contains the loaded data. - """ - scan_spec = self.project.scan_dict[self.key] - if hasattr(scan_spec, 'positions'): - loader = ScanCreator() - else: - loader = ScanLoader() - for k, v in scan_spec.items(): - setattr(loader, k, v) - scan = loader.load(dirs=dirs) - return scan - - -class ScanLoader(config.ConfigurableObject): - """ - create a Scan object from a data file reference - - this class can be used in a run file to create a scan object from an experimental data file. - to do so, fill the properties with values as documented. - the load() method is called when the project is run. - """ - - ## @var filename (string) - # file name from which the scan should be loaded. - # the file name can contain a format specifier like {project} to include the base path. - - ## @var emitter (string) - # chemical symbol and, optionally following, further specification (chemical state, environment, ...) - # of photo-emitting atoms. - # the interpretation of this string is up to the project and its cluster generator. - # it should, however, always start with a chemical element symbol. - # - # examples: 'Ca' (calcium), 'CA' (carbon A), 'C a' (carbon a), 'C 1' (carbon one), 'N=O', 'FeIII'. - - ## @var initial_state (string) - # nl term of initial state - # - # in the form expected by EDAC, for example: '2p1/2' - - ## @var is_modf (bool) - # declares whether the data file contains the modulation function rather than intensity values - # - # if false, the project will calculate a modulation function from the raw data - - def __init__(self): - super().__init__() - self.filename = "" - self.emitter = "" - self.initial_state = "1s" - self.is_modf = False - - def load(self, dirs=None): - """ - load the scan according to specification - - create a new Scan object and load the file by calling Scan.import_scan_file(). - - @return a new Scan object which contains the loaded data file. - """ - scan = Scan() - filename = config.resolve_path(self.filename, dirs) - scan.import_scan_file(filename, self.emitter, self.initial_state) - if self.is_modf: - scan.modulation = scan.raw_data - return scan - - -class ScanCreator(config.ConfigurableObject): - """ - create a Scan object from string expressions - - this class can be used in a run file to create a scan object from python expressions, - such as lists, ranges or numpy functions. - to do so, fill the properties with values as documented. - the load() method is called when the project is run. - - @note the raw_data property of the scan cannot be filled this way. - thus, the class is useful in `single` calculation mode only. - """ - - ## @var filename (string) - # name of the file which should receive the scan data. - # the file name can contain a format specifier like {project} to include the base path. - - ## @var positions (dict) - # dictionary specifying the scan positions - # - # the dictionary must contain four keys: 'e', 't', 'p', 'a' representing the four scan axes. - # each key holds a string that contains a python expression. - # the string is evaluated using python's built-in eval() function. - # the expression must evaluate to an iterable object or numpy ndarray of the scan positions. - # the `np` namespace can be used to access numpy functions. - # - # example: - # the following dictionary generates a hemispherical scan - # self.position = {'e': '100', 't': 'np.linspace(0, 90, 91)', 'p': 'range(0, 360, 2)', 'a': '0'} - - ## @var emitter (string) - # chemical symbol and, optionally following, further specification (chemical state, environment, ...) - # of photo-emitting atoms. - # the interpretation of this string is up to the project and its cluster generator. - # it should, however, always start with a chemical element symbol. - # - # examples: 'Ca' (calcium), 'CA' (carbon A), 'C a' (carbon a), 'C 1' (carbon one), 'N=O', 'FeIII'. - - ## @var initial_state (string) - # nl term of initial state - # - # in the form expected by EDAC, for example: '2p1/2' - - def __init__(self): - super().__init__() - self.filename = "" - self.positions = {'e': None, 't': None, 'p': None, 'a': None} - self.emitter = "" - self.initial_state = "1s" - - def load(self, dirs=None): - """ - create the scan according to specification - - @return a new Scan object which contains the created scan array. - """ - scan = Scan() - positions = {} - for axis in self.positions.keys(): - positions[axis] = np.atleast_1d(np.asarray(eval(self.positions[axis]))) - scan.define_scan(positions, self.emitter, self.initial_state) - scan.filename = config.resolve_path(self.filename, dirs) - return scan +# placeholder if default handler should be chosen. +# any other value means that the default is overridden by the user. +# default handlers are resolved in Project.validate. +DefaultHandler = None # noinspection PyMethodMayBeStatic class Project(config.ConfigurableObject): """ - base class of a calculation project. + Base class of a calculation project. - a 'calculation project' is a coded set of prescriptions - on how to get from a set of model parameters to simulated data - which correspond to provided experimental data. - the results include a measure of the quality of the simulated data compared to experimental data. - - each calculation project must derive from this class. - it must implement the create_model_space(), create_cluster(), and create_params() methods. - - the other methods and attributes of this class - are for passing command line parameters to the calculation modules. - the attributes should be populated in the constructor of the derived class, - or (recommended) in the create_project() function of the module. - it is essential that the attributes are set correctly before calculation. + Each calculation project must derive from this class. + It contains all parameters and data necessary to run the calculation. + It contains or references code for certain tasks like cluster generation, calculation parameters, + calculation of modulation functions and R-factors. + + The attributes should be populated in the constructor of the derived class or (recommended) via runfile. + It is essential that the attributes are set correctly before calculation. + + The call sequence of project methods is as follows: + 1. Constructor `__init__`. + 2. `set_properties` inherited from `ConfigurableObject` assigns values from the runfile. + 3. `validate` resolves directories, instantiates objects, loads scan data, + and checks validity of important attribute values. + 4. `setup` prepares task handlers and reports for the calculation. + 5. During the calculations, the various `calc`, `combine`, `evaluate` functions are called as necessary. + 6. After the calculations, `cleanup` can do some final processing. + The code must not rely on cleanup being called, though. + A resource manager may kill the process at any time. """ ## @var features (dictionary) @@ -847,11 +655,21 @@ class Project(config.ConfigurableObject): # dictionary for various directory paths. # # home: user's home directory. + # work: working directory at job start. # data: where to load experimental data (scan files) from. # project: directory of the project module. # output: where to write output and intermediate files. + # report: directory for graphical reports. # temp: for temporary files. # + # the paths should be pathlib.Path objects. + # strings are accepted as well. + # + # directly after initialization (__init__ constructor or runfile configuration), + # the paths can contain ${identifier}-style placeholders + # that refer to other directories items, job tags and some other project attributes. + # they are resolved to final paths by the validate method. + # # output_dir and output_file are set at once by @ref set_output. ## @var output_file (Path) @@ -888,6 +706,22 @@ class Project(config.ConfigurableObject): # # files.categories_to_delete determines which files can be deleted. + ## @var git_hash + # git hash of the running code + # + # the attribute is normally set by the main pmsco module but can be overwritten by the run file. + # it is part of the job metadata and stored in the job record of the database. + + ## @var handler_classes + # Classes of the task handlers used in the calculation process + # + # Normally, PMSCO chooses the appropriate task handlers automatically based on arguments. + # This happens in the `Project.validate` methods. + # The default behavior can be overridden in one of the following ways: + # - Specify an explicit class in the constructor of the subclass. + # - Specify an explicit class in the runfile. + # - Override a `validate_xxxx_handler` method. + ## @var keep_best # number of best models for which result files should be kept. # @@ -919,93 +753,121 @@ class Project(config.ConfigurableObject): # example: pmsco.calculators.edac.EdacCalculator # + ## @var reports + # list of reports + # + # reports are a configurable way of generating extra graphics or data files + # during an optimization job. + # + # the objects must in inherit from ProjectReport. + # the reports are called each time the calculation of a model finishes. + # + # the reports list can be configured in the runfile or project constructor. + # the Project.setup() method resolves symbolic file paths and calls setup on each report. + def __init__(self): super().__init__() - self._module = None - self.mode = "single" - self.job_name = "pmsco0" - self.job_tags = {} - self.git_hash = "" - self.description = "" - self.features = {} - self.cluster_format = pmsco.cluster.FMT_EDAC - self.cluster_generator = pmsco.cluster.LegacyClusterGenerator(self) - self._model_space = None - self.scans = [] - self.domains = [] - self.optimizer_params = { + self.mode: str = "single" + self.project_name: str = self.__class__.__name__ + self.job_name: str = "pmsco0" + self.job_tags: Dict[str, Union[str, Numeric]] = {} + self.git_hash: str = "" + self.description: str = "" + self.features: Dict[str, Union[str, Numeric]] = {} + self.cluster_format: int = pmsco.cluster.FMT_EDAC + self.cluster_generator: pmsco.cluster.ClusterGenerator = pmsco.cluster.LegacyClusterGenerator(self) + self._model_space: Optional[ModelSpace] = None + self.scans: List[Scan] = [] + self.domains: List[Dict[str, Any]] = [] + self.optimizer_params: Dict[str, Any] = { 'pop_size': 0, 'seed_file': "", 'seed_limit': 0, 'recalc_seed': True, 'table_file': "" } - self.directories = { - "home": Path.home(), - "work": Path.cwd(), - "data": "", - "project": "", - "output": "", - "temp": ""} - self.log_file = "" - self.log_level = "WARNING" - self.db_file = ':memory:' - self.timedelta_limit = datetime.timedelta(days=1) - self.combined_scan = None - self.combined_modf = None - self.files = pmsco.files.FileTracker() - self.keep_files = list(pmsco.files.FILE_CATEGORIES_TO_KEEP) - self.keep_levels = 1 - self.keep_best = 10 - self.handler_classes = { - 'model': pmsco.handlers.SingleModelHandler, - 'scan': pmsco.handlers.ScanHandler, - 'domain': pmsco.handlers.DomainHandler, - 'emit': pmsco.handlers.EmitterHandler, - 'region': pmsco.handlers.SingleRegionHandler + self.directories: ProjectDirectories = ProjectDirectories(self, + home=Path.home(), + work=Path.cwd(), + data="", + project="", + output="", + report=Path("${output}", "report"), + temp="") + self.log_file: PathLike = "" + self.log_level: str = "WARNING" + self.db_file: PathLike = ':memory:' + self.timedelta_limit: datetime.timedelta = datetime.timedelta(days=1) + self.combined_scan: Optional[npt.ArrayLike] = None + self.combined_modf: Optional[npt.ArrayLike] = None + self.files: FileTracker = FileTracker() + self.keep_files: Iterable[str] = list(FILE_CATEGORIES_TO_KEEP) + self.keep_levels: int = 1 + self.keep_best: int = 10 + self.handler_classes: Dict[str, Optional[type[TaskHandler]]] = { + 'model': DefaultHandler, + 'scan': DefaultHandler, + 'domain': DefaultHandler, + 'emit': DefaultHandler, + 'region': DefaultHandler } - self.atomic_scattering_factory = InternalAtomicCalculator - self.multiple_scattering_factory = EdacCalculator + self.atomic_scattering_factory: type[Calculator] = InternalAtomicCalculator + self.multiple_scattering_factory: type[Calculator] = EdacCalculator + self.reports: List[ProjectReport] = [] self._tasks_fields = [] - self._db = pmsco.database.ResultsDatabase() + self._db = db_project.ProjectDatabase() + + def set_properties(self, symbols: Optional[Mapping[str, Any]], + data_dict: config.DataDict, + project: config.ConfigurableObject) -> None: + + """ + Set configurable properties. + + Inherits from `ConfigurableObject` and resolves calculator class names. + + @param symbols: + @param data_dict: + @param project: + @return: + """ + + super().set_properties(symbols, data_dict, project) + if isinstance(self.atomic_scattering_factory, str): + self.atomic_scattering_factory = eval(self.atomic_scattering_factory, symbols) + if isinstance(self.multiple_scattering_factory, str): + self.multiple_scattering_factory = eval(self.multiple_scattering_factory, symbols) def validate(self): """ - validate the project parameters before starting the calculations + Validate the project parameters before starting calculations - the method checks and fixes attributes that may cause trouble or go unnoticed if they are wrong. - in addition, it fixes attributes which may be incomplete after loading a run-file. - failed critical checks raise an exception (AssertionError, AttributeError, KeyError, ValueError). - checks that cause an attribute do revert to default, are logged as warning. + - Check and fix attributes that may cause trouble or go unnoticed if they are wrong. + - Fix attributes which may be incomplete after loading a run-file. + - Look up scattering factories that are declared as string. + - Resolve placeholders in the directories. + - Resolve placeholders in the output_file. + - Make output_file and output_dir consistent (so that output_file includes output_dir). + - Call `create_model_space` if the `model_space` attribute is undefined. + - Load scan data. - the following attributes are fixed silently: - - scattering factories that are declared as string are looked up in the project module. - - place holders in the directories attribute are resolved. - - place holders in the output_file attribute are resolved. - - output_file and output_dir are made consistent (so that output_file includes output_dir). - - the create_model_space() method is called if the model_space attribute is undefined. - - scan data are loaded. + Failed critical checks raise an exception (AssertionError, AttributeError, KeyError, ValueError). + Checks that cause an attribute do revert to default, are logged as warning. - @note to check the syntax of a run-file, set the calculation mode to 'validate' and run pmsco. - this will pass the validate method but will stop execution before calculations are started. + @note To check the syntax of a run-file, set the calculation mode to 'validate' and run pmsco. + This will pass the validate method but will stop execution before calculations are started. @raise AssertionError if a parameter is not correct. @raise AttributeError if a class name cannot be resolved. """ + assert self.mode in {"single", "swarm", "genetic", "grid", "table", "test", "validate"} + assert self.job_name - if isinstance(self.atomic_scattering_factory, str): - self.atomic_scattering_factory = getattr(self._module, self.atomic_scattering_factory) - if isinstance(self.multiple_scattering_factory, str): - self.multiple_scattering_factory = getattr(self._module, self.multiple_scattering_factory) - - self.directories = {k: config.resolve_path(Path(v), self.directories) for k, v in self.directories.items()} - - assert len(str(self.output_file)) - d = config.resolve_path(self.directories['output'], self.directories) - f = config.resolve_path(self.output_file, self.directories) - self.output_file = Path(d, f) - self.directories['output'] = self.output_file.parent + self.directories.resolve_directories(check=True) + self.directories['output'].mkdir(parents=True, exist_ok=True) + self.db_file = self.directories.resolve_path(self.db_file) + self.optimizer_params['seed_file'] = self.directories.resolve_path(self.optimizer_params['seed_file']) if self._model_space is None or not self._model_space.start: logger.warning("undefined model_space attribute, trying project's create_model_space") @@ -1013,28 +875,161 @@ class Project(config.ConfigurableObject): self.load_scans() + self.validate_model_handler() + self.validate_scan_handler() + self.validate_domain_handler() + self.validate_emitter_handler() + self.validate_region_handler() + + for report in self.reports: + report.validate(self) + + def validate_model_handler(self): + """ + Validate the model handler. + + Check that `self.handler_classes['model']` contains a valid TaskHandler. + If none is set, choose the appropriate handler corresponding to the `mode` attribute. + + The default behavior can be overridden by specifying an explicit class in the runfile + or by overriding this method. + The class must derive from TaskHandler, else an AssertionError is raised. + """ + + handler_class = self.handler_classes.get('model') + + if handler_class is None: + if self.mode == 'single': + handler_class = SingleModelHandler + elif self.mode == 'grid': + handler_class = GridSearchHandler + elif self.mode == 'swarm': + handler_class = ParticleSwarmHandler + elif self.mode == 'genetic': + handler_class = GeneticOptimizationHandler + elif self.mode == 'table': + handler_class = TableModelHandler + else: + handler_class = SingleModelHandler + logger.error(f"invalid optimization mode {self.mode}, defaulting to single") + + assert issubclass(handler_class, TaskHandler) + self.handler_classes['model'] = handler_class + + def validate_scan_handler(self): + """ + Validate the scan handler. + + Check that `self.handler_classes['scan']` contains a valid TaskHandler. + If none is set, choose the default `ScanHandler`. + + The default behavior can be overridden by specifying an explicit class in the runfile + or by overriding this method. + The class must derive from TaskHandler, else an AssertionError is raised. + """ + + handler_class = self.handler_classes.get('scan') + + if handler_class is None: + handler_class = ScanHandler + + assert issubclass(handler_class, TaskHandler) + self.handler_classes['scan'] = handler_class + + def validate_domain_handler(self): + """ + Validate the domain handler. + + Check that `self.handler_classes['domain']` contains a valid TaskHandler. + If none is set, choose the default `DomainHandler`. + + The default behavior can be overridden by specifying an explicit class in the runfile + or by overriding this method. + The class must derive from TaskHandler, else an AssertionError is raised. + """ + + handler_class = self.handler_classes.get('domain') + + if handler_class is None: + handler_class = DomainHandler + + assert issubclass(handler_class, TaskHandler) + self.handler_classes['domain'] = handler_class + + def validate_emitter_handler(self): + """ + Validate the emitter handler. + + Check that `self.handler_classes['emit']` contains a valid TaskHandler. + If none is set, choose the default `EmitterHandler`. + + The default behavior can be overridden by specifying an explicit class in the runfile + or by overriding this method. + The class must derive from TaskHandler, else an AssertionError is raised. + """ + + handler_class = self.handler_classes.get('emit') + + if handler_class is None: + handler_class = EmitterHandler + + assert issubclass(handler_class, TaskHandler) + self.handler_classes['emit'] = handler_class + + def validate_region_handler(self): + """ + Validate the emitter handler. + + Check that `self.handler_classes['region']` contains a valid TaskHandler. + If none is set, choose one of `EnergyRegionHandler` + (if the project includes an energy scan with at least 10 steps) + or `SingleRegionHandler`. + + angle scans do not benefit from region splitting in EDAC. + + The default behavior can be overridden by specifying an explicit class in the runfile + or by overriding this method. + The class must derive from TaskHandler, else an AssertionError is raised. + """ + + handler_class = self.handler_classes.get('region') + + if handler_class is None: + energy_scans = 0 + for scan in self.scans: + if scan.energies.shape[0] >= 10: + energy_scans += 1 + + if energy_scans >= 1: + handler_class = EnergyRegionHandler + else: + handler_class = SingleRegionHandler + + assert issubclass(handler_class, TaskHandler) + self.handler_classes['region'] = handler_class + @property - def data_dir(self): + def data_dir(self) -> PathLike: return self.directories['data'] @data_dir.setter - def data_dir(self, path): + def data_dir(self, path: PathLike): self.directories['data'] = Path(path) @property - def output_dir(self): + def output_dir(self) -> PathLike: return self.directories['output'] @output_dir.setter - def output_dir(self, path): + def output_dir(self, path: PathLike): self.directories['output'] = Path(path) @property - def output_file(self): + def output_file(self) -> PathLike: return Path(self.directories['output'], self.job_name) @output_file.setter - def output_file(self, filename): + def output_file(self, filename: PathLike) -> None: """ set path and base name of output file. @@ -1054,14 +1049,20 @@ class Project(config.ConfigurableObject): raise ValueError("invalid output file name") @property - def time_limit(self): + def time_limit(self) -> float: + """ + Wall time limit in hours + + @return: hours + """ + return self.timedelta_limit.total_seconds() / 3600 / 24 @time_limit.setter - def time_limit(self, hours): + def time_limit(self, hours: float) -> None: self.timedelta_limit = datetime.timedelta(hours=hours) - def create_model_space(self): + def create_model_space(self) -> Optional[ModelSpace]: """ create a project.ModelSpace object which defines the allowed range for model parameters. @@ -1079,7 +1080,7 @@ class Project(config.ConfigurableObject): return None @property - def model_space(self): + def model_space(self) -> ModelSpace: """ ModelSpace object that defines the allowed range for model parameters. @@ -1095,7 +1096,7 @@ class Project(config.ConfigurableObject): return self._model_space @model_space.setter - def model_space(self, value): + def model_space(self, value: ModelSpace) -> None: if isinstance(value, ModelSpace): self._model_space = value elif hasattr(value, 'items'): @@ -1104,7 +1105,7 @@ class Project(config.ConfigurableObject): else: raise ValueError("incompatible object type") - def create_params(self, model, index): + def create_params(self, model: Dict[str, Numeric], index: CalcID) -> None: """ create a CalculatorParams object given the model parameters and calculation index. @@ -1129,7 +1130,9 @@ class Project(config.ConfigurableObject): self.combined_scan = None self.combined_modf = None - def add_scan(self, filename, emitter, initial_state, is_modf=False, positions=None): + def add_scan(self, filename: PathLike, emitter: str, initial_state: str, is_modf: bool = False, + positions: Optional[Dict[str, npt.NDArray]] = None) -> Union[ScanLoader, ScanCreator]: + """ add a scan specification to the scans list. @@ -1168,6 +1171,7 @@ class Project(config.ConfigurableObject): @return (Scan) the new scan object (which is also a member of self.scans). """ + if positions is not None: scan = ScanCreator() scan.positions = positions @@ -1196,30 +1200,30 @@ class Project(config.ConfigurableObject): has_mod_func = True loaded_scans = [] - for idx, scan in enumerate(self.scans): - scan = scan.load(dirs=self.directories) + for scan_proto in self.scans: + scan = scan_proto.load(dirs=self.directories) loaded_scans.append(scan) if scan.modulation is None: try: - scan.modulation = self.calc_modulation(scan.raw_data, self.model_space.start) + scan.modulation = self.calc_modulation(scan, scan.raw_data) except ValueError: - logger.error(f"error calculating the modulation function of scan {idx}.") + logger.error(f"error calculating the modulation function of scan {scan_proto}.") has_raw_data = has_raw_data and scan.raw_data is not None has_mod_func = has_mod_func and scan.modulation is not None self.scans = loaded_scans if has_raw_data: stack1 = [scan.raw_data for scan in self.scans] - dtype = md.common_dtype(stack1) - stack2 = [md.restructure_data(data, dtype) for data in stack1] + dtype = pmsco.data.common_dtype(stack1) + stack2 = [pmsco.data.restructure_data(data, dtype) for data in stack1] self.combined_scan = np.hstack(tuple(stack2)) else: self.combined_scan = None if has_mod_func: stack1 = [scan.modulation for scan in self.scans] - dtype = md.common_dtype(stack1) - stack2 = [md.restructure_data(data, dtype) for data in stack1] + dtype = pmsco.data.common_dtype(stack1) + stack2 = [pmsco.data.restructure_data(data, dtype) for data in stack1] self.combined_modf = np.hstack(tuple(stack2)) else: self.combined_modf = None @@ -1234,7 +1238,7 @@ class Project(config.ConfigurableObject): """ self.domains = [] - def add_domain(self, domain): + def add_domain(self, domain: Dict[str, Any]): """ add a domain to the list of domains. @@ -1287,14 +1291,14 @@ class Project(config.ConfigurableObject): logger.warning("intermediate files to keep: {0}".format(", ".join(_files_to_keep))) for idx, scan in enumerate(self.scans): - logger.warning(f"scan {idx}: {scan.filename} ({scan.emitter} {scan.initial_state})") + logger.warning(f"scan {idx}: {scan}") for idx, dom in enumerate(self.domains): logger.warning(f"domain {idx}: {dom}") except AttributeError: logger.warning("AttributeError in log_project_args") - def combine_domains(self, parent_task, child_tasks): + def combine_domains(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]): """ combine results of different domain into one result and calculate the modulation function. @@ -1329,7 +1333,7 @@ class Project(config.ConfigurableObject): result_data = None sum_weights = 0. for task in child_tasks: - data = md.load_data(task.result_filename) + data = pmsco.data.load_data(task.result_filename) if result_data is None: result_data = data.copy() result_data['i'] = 0. @@ -1341,16 +1345,15 @@ class Project(config.ConfigurableObject): sum_weights += weight result_data['i'] /= sum_weights - md.save_data(parent_task.result_filename, result_data) + pmsco.data.save_data(parent_task.result_filename, result_data) - # todo : the handling of missing modulation functions may need some cleanup if self.scans[parent_task.id.scan].modulation is not None: - result_modf = self.calc_modulation(result_data, parent_task.model) - md.save_data(parent_task.modf_filename, result_modf) + result_modf = self.calc_modulation(parent_task.id.scan, result_data) + pmsco.data.save_data(parent_task.modf_filename, result_modf) else: parent_task.modf_filename = "" - def combine_emitters(self, parent_task, child_tasks): + def combine_emitters(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]): """ combine results of different emitters into one result. calculate the modulation function. @@ -1383,22 +1386,21 @@ class Project(config.ConfigurableObject): result_data = None for task in child_tasks: - data = md.load_data(task.result_filename) + data = pmsco.data.load_data(task.result_filename) if result_data is not None: result_data['i'] += data['i'] else: result_data = data - md.save_data(parent_task.result_filename, result_data) + pmsco.data.save_data(parent_task.result_filename, result_data) - # todo : the handling of missing modulation functions may need some cleanup if self.scans[parent_task.id.scan].modulation is not None: - result_modf = self.calc_modulation(result_data, parent_task.model) - md.save_data(parent_task.modf_filename, result_modf) + result_modf = self.calc_modulation(parent_task.id.scan, result_data) + pmsco.data.save_data(parent_task.modf_filename, result_modf) else: parent_task.modf_filename = "" - def combine_scans(self, parent_task, child_tasks): + def combine_scans(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]): """ combine results of different scans into one result, for intensity and modulation. @@ -1424,27 +1426,27 @@ class Project(config.ConfigurableObject): # intensity try: - stack1 = [md.load_data(task.result_filename) for task in child_tasks] + stack1 = [pmsco.data.load_data(task.result_filename) for task in child_tasks] except IOError: parent_task.result_filename = "" else: - dtype = md.common_dtype(stack1) - stack2 = [md.restructure_data(data, dtype) for data in stack1] + dtype = pmsco.data.common_dtype(stack1) + stack2 = [pmsco.data.restructure_data(data, dtype) for data in stack1] result_data = np.hstack(tuple(stack2)) - md.save_data(parent_task.result_filename, result_data) + pmsco.data.save_data(parent_task.result_filename, result_data) # modulation try: - stack1 = [md.load_data(task.modf_filename) for task in child_tasks] + stack1 = [pmsco.data.load_data(task.modf_filename) for task in child_tasks] except IOError: parent_task.modf_filename = "" else: - dtype = md.common_dtype(stack1) - stack2 = [md.restructure_data(data, dtype) for data in stack1] + dtype = pmsco.data.common_dtype(stack1) + stack2 = [pmsco.data.restructure_data(data, dtype) for data in stack1] result_modf = np.hstack(tuple(stack2)) - md.save_data(parent_task.modf_filename, result_modf) + pmsco.data.save_data(parent_task.modf_filename, result_modf) - def combine_regions(self, parent_task, child_tasks): + def combine_regions(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]): """ combine results from different regions into one result, for intensity and modulation. @@ -1472,16 +1474,16 @@ class Project(config.ConfigurableObject): """ # intensity try: - stack1 = [md.load_data(task.result_filename) for task in child_tasks] + stack1 = [pmsco.data.load_data(task.result_filename) for task in child_tasks] except IOError: parent_task.result_valid = False parent_task.result_filename = "" else: - dtype = md.common_dtype(stack1) - stack2 = [md.restructure_data(data, dtype) for data in stack1] + dtype = pmsco.data.common_dtype(stack1) + stack2 = [pmsco.data.restructure_data(data, dtype) for data in stack1] result_data = np.hstack(tuple(stack2)) - md.sort_data(result_data) - md.save_data(parent_task.result_filename, result_data) + pmsco.data.sort_data(result_data) + pmsco.data.save_data(parent_task.result_filename, result_data) scan = self.scans[parent_task.id.scan] if result_data.shape[0] != scan.raw_data.shape[0]: @@ -1490,31 +1492,14 @@ class Project(config.ConfigurableObject): # modulation try: - data = md.load_data(parent_task.result_filename) - modf = self.calc_modulation(data, parent_task.model) + data = pmsco.data.load_data(parent_task.result_filename) + modf = self.calc_modulation(parent_task.id.scan, data) except IOError: parent_task.modf_filename = "" else: - md.save_data(parent_task.modf_filename, modf) + pmsco.data.save_data(parent_task.modf_filename, modf) - def get_git_hash(self): - """ - get the git commit (hash) of the running code (HEAD) - - the method looks for a git repository in the source tree of this module. - if successful, it returns the hash string of the HEAD commit. - - @return: hexadecimal hash string. - empty string if the file is not in a git repository. - """ - try: - repo = git.Repo(__file__, search_parent_directories=True) - except git.exc.InvalidGitRepositoryError: - return "" - else: - return repo.head.commit.hexsha - - def setup(self, handlers): + def setup(self, handlers: Dict[str, pmsco.handlers.TaskHandler]): """ prepare for calculations. @@ -1534,9 +1519,9 @@ class Project(config.ConfigurableObject): @return: None """ - self.git_hash = self.get_git_hash() + fields = ["rfac"] - fields.extend(pmsco.dispatch.CalcID._fields) + fields.extend(CalcID._fields) fields.append("secs") fields = ["_" + f for f in fields] model_fields = list(self.model_space.start.keys()) @@ -1555,29 +1540,18 @@ class Project(config.ConfigurableObject): Path(self.output_file).parent.mkdir(parents=True, exist_ok=True) tasks_file = Path(self.output_file).with_suffix(".tasks.dat") - with open(tasks_file, "w") as outfile: + with open(tasks_file, "wt", encoding="latin1") as outfile: outfile.write("# ") outfile.write(" ".join(fields)) outfile.write("\n") self._db.connect(self.db_file) - project_name = self.__class__.__name__ - project_module = self.__class__.__module__ - project_id = self._db.register_project(project_name, project_module) - job_id = self._db.register_job(project_id, - self.job_name, - self.mode, - socket.gethostname(), - self.git_hash, - datetime.datetime.now(), - self.description) - logger.debug(BMsg("database {db_file}, project {proj}, job {job}", - db_file=self.db_file, proj=project_id, job=job_id)) - self._db.insert_jobtags(job_id, self.job_tags) - self._db.register_params(model_fields) - self._db.create_models_view() + self._db.ingest_project_metadata(self) - def evaluate_result(self, parent_task, child_tasks): + for report in self.reports: + report.set_database(self._db) + + def evaluate_result(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]): """ evaluate the result of a calculation task. @@ -1603,75 +1577,96 @@ class Project(config.ConfigurableObject): except ValueError: parent_task.result_valid = False logger.warning(BMsg("calculation {0} resulted in an undefined R-factor.", parent_task.id)) - else: - values_dict = parent_task.id._asdict() - values_dict = {"_" + k: v for k, v in values_dict.items()} - values_dict.update(parent_task.model) - values_dict['_rfac'] = parent_task.rfac - values_dict['_secs'] = parent_task.time.total_seconds() - values_list = [values_dict[field] for field in self._tasks_fields] - tasks_file = Path(self.output_file).with_suffix(".tasks.dat") - with open(tasks_file, "a") as outfile: - outfile.write(" ".join(format(value) for value in values_list) + "\n") + return None - db_id = self._db.insert_result(parent_task.id, values_dict) - logger.debug(BMsg("model {model}, database result {db_id}", model=parent_task.id.model, db_id=db_id)) + values_dict = parent_task.id._asdict() + values_dict = {"_" + k: v for k, v in values_dict.items()} + values_dict.update(parent_task.model) + values_dict['_rfac'] = parent_task.rfac + values_dict['_secs'] = parent_task.time.total_seconds() + values_list = [values_dict[field] for field in self._tasks_fields] + + tasks_file = Path(self.output_file).with_suffix(".tasks.dat") + with open(tasks_file, "at", encoding="latin1") as outfile: + outfile.write(" ".join(format(value) for value in values_list) + "\n") + if parent_task.delta: + delta_dict = parent_task.delta + else: + delta_dict = None + + self._db.ingest_result(parent_task.id, values_dict, delta_dict) + + if parent_task.result_valid and parent_task.id.level == 'model': + for report in self.reports: + if report.enabled and 'model' in report.trigger_levels: + logger.info(f"calling report {report.__class__.__name__} on model {parent_task.id.model}") + report.select_data(jobs=self._db.db_job_id, calcs=parent_task.id) + report.create_report() return None # noinspection PyUnusedLocal - def calc_modulation(self, data, model): + def calc_modulation(self, scan: Union[int, Scan], data: npt.NDArray) -> npt.NDArray: """ - calculate the project-dependent modulation function. + Calculate the modulation function configured for a scan. - the modulation function of I(x) is (I(x) - S(x)) / S(x) - where S(x) is a smooth copy of I(x). + This method identifies the modulation function calculator of the scan and calls it on the given data. + If the scan does not define the type of modulation function, the default is pmsco.data.default_modfunc. - by default, the modulation function is calculated by data.calc_modfunc_loess(). - override this method in your project to use a different modulation function. + @param scan: Scan object or scan index that defines the modulation function. + Can alternatively be any object that defines the modulation_func and modulation_args attributes + like in the Scan class. - @param data structured numpy.ndarray in EI, ETPI, or ETPAI format. - can contain a one- or multi-dimensional scan. - the scan coordinates must be on a rectangular or hemisperical grid. - for maximum compatibility, the array should be sorted, - though for the default calc_modfunc_loess() function this is not required. - - if data contains a hemispherical scan, the phi dimension is ignored, - i.e. the modulation function is calcualted on a phi-average. - - @param model: (dict) model parameters of the calculation task. - can be used to pass parameters from the project. - this argument is a dictionary of the model parameters. + @param data: Structured numpy.ndarray in EI, ETPI, or ETPAI format. + Can contain a one- or multi-dimensional scan. + The scan coordinates must be on a rectangular or hemispherical grid. + For maximum compatibility, the array should be sorted, + though for the default calc_modfunc_loess function this is not required. @return copy of the data array with the modulation function in the 'i' column. """ - return md.calc_modfunc_loess(data) + try: + modfunc = scan.modulation_func + modargs = scan.modulation_args + except AttributeError: + try: + modfunc = self.scans[scan].modulation_func + modargs = self.scans[scan].modulation_args + except (IndexError, TypeError): + logger.error(f"Unknown scan {scan} in Project.calc_modulation. " + f"Defaulting to pmsco.data.default_modfunc.") + modfunc = pmsco.data.default_modfunc + modargs = {} - def calc_rfactor(self, parent_task, child_tasks): + return modfunc(data, **modargs) + + def calc_rfactor(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]) -> float: """ - calculate the r-factor of a task. + Calculate the r-factor of a task. - the r-factor is calculated on the experimental and simulated modulation functions. - the algorithm differs for the model level and the lower task levels. - at the model level, the calculation is delegated to Project.combine_rfactors. - at all other levels, the r-factor is calculated by Project.rfactor, - where the simulated is loaded from the file specified by parent_task.modf_filename + The R-factor is calculated on the experimental and simulated modulation functions. + The algorithm differs for the model level and the lower task levels. + At the model level, the calculation is delegated to Project.combine_rfactors. + At all other levels, the calculation is delegated to Project.calc_scan_rfactor + where the simulated data is loaded from the file specified by parent_task and the experimental data from Project.scan. - this method is called by the task handlers. - all child tasks belonging to the parent task must be complete. + This method is called by the task handlers. + All child tasks belonging to the parent task must be complete. - to select or implement a specific R-factor algorithm, - the project sub-class should override Project.rfactor. - to combine scan r-factors, it should override or patch Project.combine_rfactors. + To select a specific R-factor algorithm, + the R-factor function should be set in the Scan.rfactor_func attribute + which can be specified in the runfile. - @version in earlier versions, + In special (rare) cases, the project sub-class can override Project.calc_scan_rfactor + and/or Project.combine_rfactors. + + @version In earlier versions, projects had to override this method to implement their algorithm. - this has lead to duplication of common code. - the r-factor algorithm is now distributed over several methods, - and the method signature has changed. - new projects should override Project.rfactor and/or Project.combine_rfactors. + This has lead to duplication of common code. + The r-factor algorithm is now distributed over several methods + and can be specified in the runfile. @param parent_task: (CalculationTask) a calculation task. @@ -1682,72 +1677,70 @@ class Project(config.ConfigurableObject): @raise ValueError if the function fails (e.g. division by zero or all elements non-finite). """ + if parent_task.id.scan >= 0: - task_data = md.load_data(parent_task.modf_filename) - exp_data = self.scans[parent_task.id.scan].modulation - result_r = self.rfactor(exp_data, task_data) + result_r = self.calc_scan_rfactor(parent_task) else: result_r = self.combine_rfactors(parent_task, child_tasks) return result_r - def rfactor(self, exp_data, theo_data): + def calc_scan_rfactor(self, task: CalculationTask) -> float: """ - calculate the r-factor of simulated diffraction data. + Calculate the R-factor of simulated diffraction data at the scan level. - in this class, the method calls the data.rfactor function to calculate the r-factor. - override this method in your project to use a different R-factor algorithm. + The method calls the rfactor_func function of the scan referred by the task + to calculate the R-factor on the modulation functions referred to by the task. - the input arrays must have the same shape, - and the coordinate columns must be identical (they are ignored, however). - the array elements are compared element-by-element. - terms having NaN intensity are ignored. + Override this method in your project if you want to calculate the R-factor on other data, + e.g. unnormalized intensity. - if the sigma column is present in experiment and non-zero, - the R-factor terms are weighted. + The R-factor function should be selected in the scan object. - @param exp_data: (numpy structured array) - ETPI, ETPIS, ETPAI or ETPAIS array containing the experimental modulation function. - if an @c s field is present and non-zero, - the R-factor terms are weighted by 1/sigma**2. - - @param theo_data: (numpy structured array) - ETPI or ETPAI array containing the calculated modulation functions. + @param task: (CalculationTask) a calculation task at the scan level (task index must be >= 0). + task.modf_filename must point to a valid data file. @return: (float) scalar R-factor @raise ValueError if the function fails (e.g. division by zero or all elements non-finite). """ - return md.rfactor(exp_data, theo_data) - def opt_rfactor(self, exp_data, theo_data): + scan = self.scans[task.id.scan] + exp_data = scan.modulation + task_data = pmsco.data.load_data(task.modf_filename) + return scan.rfactor_func(exp_data, task_data, **scan.rfactor_args) + + def combine_rfactors(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]) -> float: """ - calculate the r-factor of simulated diffraction data, adjusting their amplitude. + combine r-factors of child tasks. - this is an alternative r-factor calculation algorithm - using the pmsco.data.optimize_rfactor() function. + the r-factors are taken from the rfac attribute of the child_tasks. + the result is an average of the child r-rfactors. - to activate this method (replacing the default one), assign it to Project.rfactor - in the overriding __init__ or setup method: - @code{.py} - self.rfactor = self.opt_rfactor - @endcode + to produce a balanced result, every child dataset must contain a similar amount of information. + if this is not the case, the child r-factors must be weighted. + weighting is currently not implemented but may be introduced in a future version. - @param exp_data: (numpy structured array) - ETPI, ETPIS, ETPAI or ETPAIS array containing the experimental modulation function. - if an @c s field is present and non-zero, - the R-factor terms are weighted by 1/sigma**2. + the method is intended to be used at the model level (children are scans). + though it can technically be used at any level where child r-factors are available. - @param theo_data: (numpy structured array) - ETPI or ETPAI array containing the calculated modulation functions. + @param parent_task: (CalculationTask) parent task for which the r-factor is calculated, + i.e. a model task. - @return: (float) scalar R-factor + @param child_tasks: (sequence of CalculationTask) child tasks of parent_tasks + that may be consulted for calculating the r-factor. - @raise ValueError if the function fails (e.g. division by zero or all elements non-finite). + @return: (float) r-factor, NaN if parent task is invalid + + @raise ValueError or IndexError if child_tasks is empty. """ - return md.optimize_rfactor(exp_data, theo_data) - def combine_rfactors(self, parent_task, child_tasks): + if parent_task.result_valid: + return self.combine_rfactors_average(parent_task, child_tasks) + else: + return float('nan') + + def combine_rfactors_average(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]) -> float: """ combine r-factors of child tasks. @@ -1773,13 +1766,15 @@ class Project(config.ConfigurableObject): """ if parent_task.result_valid: rsum = 0. + count = 0 for task in child_tasks: rsum += task.rfac - return rsum / len(child_tasks) + count += 1 + return rsum / count else: return float('nan') - def alt_combine_rfactors(self, parent_task, child_tasks): + def combine_rfactors_datastack(self, parent_task: CalculationTask, child_tasks: Iterable[CalculationTask]) -> float: """ combine r-factors of child tasks by explicit calculation on the combined result. @@ -1807,13 +1802,13 @@ class Project(config.ConfigurableObject): @return: (float) r-factor, NaN if parent task is invalid """ if parent_task.result_valid: - task_data = md.load_data(parent_task.modf_filename) + task_data = pmsco.data.load_data(parent_task.modf_filename) exp_data = self.combined_modf - return self.rfactor(exp_data, task_data) + return pmsco.data.default_rfactor(exp_data, task_data) else: return float('nan') - def export_cluster(self, index, filename, cluster): + def export_cluster(self, index: CalcID, filename: PathLike, cluster: Cluster) -> Dict[str, str]: """ export the cluster of a calculation task in XYZ format for diagnostics and reporting. @@ -1857,7 +1852,9 @@ class Project(config.ConfigurableObject): return _files - def before_atomic_scattering(self, task, par, clu): + def before_atomic_scattering(self, task: CalculationTask, par: CalculatorParams, clu: Cluster) -> \ + Tuple[Optional[CalculatorParams], Optional[Cluster]]: + """ project hook before atomic scattering factors are calculated. @@ -1888,12 +1885,15 @@ class Project(config.ConfigurableObject): or copies of the original arguments. if atomic scattering factors should not be calculated, the return values should be None. """ + if task.id.model >= 0: return par, clu else: return None, None - def after_atomic_scattering(self, task, par, clu): + def after_atomic_scattering(self, task: CalculationTask, par: CalculatorParams, clu: Cluster) -> \ + Tuple[Optional[CalculatorParams], Optional[Cluster]]: + """ project hook after atomic scattering factors are calculated. @@ -1929,14 +1929,24 @@ class Project(config.ConfigurableObject): def cleanup(self): """ - delete unwanted files at the end of a project and close the database. + wrap up the calculation job. + + - call final reports + - delete unwanted files at the end of a project + - close the database. @return: None """ - self.cleanup_files(incomplete_models=True) - self._db.disconnect() + for report in self.reports: + if report.enabled and 'end' in report.trigger_levels: + logger.info(f"calling report {report.__class__.__name__} at end") + report.select_data(jobs=self._db.db_job_id) + report.create_report() - def cleanup_files(self, keep=0, incomplete_models=False): + self.cleanup_files(incomplete_models=True) + self._db = None + + def cleanup_files(self, keep: int = 0, incomplete_models: bool = False) -> None: """ delete uninteresting files (any time). diff --git a/projects/common/clusters/crystals.py b/pmsco/projects/common/clusters/crystals.py similarity index 100% rename from projects/common/clusters/crystals.py rename to pmsco/projects/common/clusters/crystals.py diff --git a/projects/common/empty-hemiscan.etpi b/pmsco/projects/common/empty-hemiscan.etpi similarity index 100% rename from projects/common/empty-hemiscan.etpi rename to pmsco/projects/common/empty-hemiscan.etpi diff --git a/pmsco/projects/demo/cu111-single.json b/pmsco/projects/demo/cu111-single.json new file mode 100644 index 0000000..9934513 --- /dev/null +++ b/pmsco/projects/demo/cu111-single.json @@ -0,0 +1,46 @@ +{ + "project": { + "__module__": "pmsco.projects.demo.fcc", + "__class__": "FCC111Project", + "job_name": "cu111-0002", + "job_tags": {}, + "description": "edac phases, ${mode}", + "directories": { + "output": "${project}/../../work/demo/${job_name}" + }, + "mode": "single", + "time_limit": 24, + "log_level": "WARNING", + "keep_files": [ + "cluster", + "output", + "atomic" + ], + "element": "Cu", + "atomic_scattering_factory": "InternalAtomicCalculator", + "multiple_scattering_factory": "EdacCalculator", + "domains": [ + { + "default": 0.0 + } + ], + "scans": [ + { + "__class__": "ScanLoader", + "filename": "${project}/demo_holo_scan.etpi", + "is_modf": true, + "emitter": "Cu", + "initial_state": "3s", + "patch": {"e": "26."} + } + ], + "model_space": { + "rmax": {"start": 5.0}, + "dlat": {"start": 3.6149}, + "dl1l2": {"start": "3.6149 / math.sqrt(3.0)"}, + "phi": {"start": 0.0}, + "V0": {"start": 10.0}, + "Zsurf": {"start": 1.0} + } + } +} diff --git a/projects/demo/demo_alpha_scan.etpai b/pmsco/projects/demo/demo_alpha_scan.etpai similarity index 100% rename from projects/demo/demo_alpha_scan.etpai rename to pmsco/projects/demo/demo_alpha_scan.etpai diff --git a/projects/demo/demo_holo_scan.etpi b/pmsco/projects/demo/demo_holo_scan.etpi similarity index 100% rename from projects/demo/demo_holo_scan.etpi rename to pmsco/projects/demo/demo_holo_scan.etpi diff --git a/projects/demo/fcc.py b/pmsco/projects/demo/fcc.py similarity index 55% rename from projects/demo/fcc.py rename to pmsco/projects/demo/fcc.py index c103d7a..23bdd74 100644 --- a/projects/demo/fcc.py +++ b/pmsco/projects/demo/fcc.py @@ -4,39 +4,55 @@ scattering calculation project for the (111) surface of an arbitrary face-center @author Matthias Muntwiler, matthias.muntwiler@psi.ch -@copyright (c) 2015-19 by Paul Scherrer Institut @n +@copyright (c) 2015-22 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import logging import math import numpy as np -import os.path import periodictable as pt -import argparse -import logging -import pmsco.cluster as mc -import pmsco.project as mp +# noinspection PyUnresolvedReferences +from pmsco.calculators.calculator import InternalAtomicCalculator +# noinspection PyUnresolvedReferences +from pmsco.calculators.edac import EdacCalculator +# noinspection PyUnresolvedReferences +from pmsco.calculators.phagen.runner import PhagenCalculator +# noinspection PyUnresolvedReferences +from pmsco.cluster import Cluster, ClusterGenerator +# noinspection PyUnresolvedReferences +import pmsco.elements.bindingenergy +from pmsco.graphics.scan import render_scan +from pmsco.project import Project, ModelSpace, CalculatorParams +# noinspection PyUnresolvedReferences +from pmsco.scan import ScanKey, ScanLoader, ScanCreator +# noinspection PyUnresolvedReferences +from pmsco.dispatch import CalcID from pmsco.helpers import BraceMessage as BMsg logger = logging.getLogger(__name__) -class FCC111Project(mp.Project): +class FCC111Project(Project): def __init__(self): """ initialize a project instance + + the element attribute must be set directly after creation, e.g. via run-file. + + unlike previous versions, the current version of this class does not define a scan_dict. + the scans should be declared in the run-file using the ScanLoader and ScanCreator classes. + the demo_holo_scan.etpi and demo_alpha_scan.etpai files can be used as templates. """ super(FCC111Project, self).__init__() - self.scan_dict = {} self.element = "Ni" + self.scan_dict = {} + self.phase_files = {} + self.rme_files = {} def create_cluster(self, model, index): """ @@ -48,7 +64,7 @@ class FCC111Project(mp.Project): @arg model['rmax'] cluster radius @arg model['phi'] azimuthal rotation angle in degrees """ - clu = mc.Cluster() + clu = Cluster() clu.comment = "{0} {1}".format(self.__class__, index) clu.set_rmax(model['rmax']) # fcc lattice constant @@ -91,45 +107,46 @@ class FCC111Project(mp.Project): par['V0'] = inner potential par['Zsurf'] = position of surface """ - params = mp.CalculatorParams() + params = CalculatorParams() params.title = "fcc(111)" params.comment = "{0} {1}".format(self.__class__, index) params.cluster_file = "" params.output_file = "" - params.initial_state = self.scans[index.scan].initial_state - params.spherical_order = 2 + initial_state = self.scans[index.scan].initial_state + params.initial_state = initial_state + emitter = self.scans[index.scan].emitter + params.binding_energy = pt.elements.symbol(emitter).binding_energy[initial_state] params.polarization = "H" - params.scattering_level = 5 - params.fcut = 15.0 - params.cut = 15.0 - params.angular_resolution = 0.0 - params.lattice_constant = 1.0 params.z_surface = model['Zsurf'] - params.atom_types = 3 - params.atomic_number = [pt.elements.symbol(self.element).number] - params.phase_file = [] - params.msq_displacement = [0.00] - params.planewave_attenuation = 1.0 params.inner_potential = model['V0'] params.work_function = 4.5 - params.symmetry_range = 360.0 params.polar_incidence_angle = 60.0 params.azimuthal_incidence_angle = 0.0 - params.vibration_model = "P" - params.substrate_atomic_mass = pt.elements.symbol(self.element).mass + params.angular_resolution = 5.0 params.experiment_temperature = 300.0 params.debye_temperature = 400.0 - params.debye_wavevector = 1.7558 - params.rme_minus_value = 0.0 + + if self.phase_files: + state = emitter + initial_state + try: + params.phase_files = self.phase_files[state] + except KeyError: + params.phase_files = {} + logger.warning("no phase files found for {} - using default calculator".format(state)) + + params.rme_files = {} + params.rme_minus_value = 0.1 params.rme_minus_shift = 0.0 params.rme_plus_value = 1.0 params.rme_plus_shift = 0.0 - # used by EDAC only + + # edac_interface only params.emitters = [] params.lmax = 15 params.dmax = 5.0 params.orders = [25] + # params.phase_output_classes = self.cluster_generator.create_cluster(model, index).get_atom_count() return params @@ -137,7 +154,7 @@ class FCC111Project(mp.Project): """ define the model space of the optimization parameters. """ - dom = mp.ModelSpace() + dom = ModelSpace() if self.mode == "single": dom.add_param('rmax', 5.00, 5.00, 15.00, 2.50) @@ -169,92 +186,3 @@ class FCC111Project(mp.Project): dom.add_param('Zsurf', 1.00, 0.00, 2.00, 0.50) return dom - - -def create_project(): - """ - create an FCC111Project calculation project. - """ - - project = FCC111Project() - - project_dir = os.path.dirname(os.path.abspath(__file__)) - project.data_dir = project_dir - - # scan dictionary - # to select any number of scans, add their dictionary keys as scans option on the command line - project.scan_dict['default'] = {'filename': os.path.join(project_dir, "demo_holo_scan.etp"), - 'emitter': "Ni", 'initial_state': "3s"} - project.scan_dict['holo'] = {'filename': os.path.join(project_dir, "demo_holo_scan.etp"), - 'emitter': "Ni", 'initial_state': "3s"} - project.scan_dict['alpha'] = {'filename': os.path.join(project_dir, "demo_alpha_scan.etp"), - 'emitter': "Ni", 'initial_state': "3s"} - - project.add_domain({'default': 0.0}) - - return project - - -def set_project_args(project, project_args): - """ - set the project arguments of a MnGeTeProject calculation project. - - @param project: project instance - - @param project_args: (Namespace object) project arguments. - """ - - scans = ['default'] - try: - if project_args.scans: - scans = project_args.scans - else: - logger.warning(BMsg("missing scan argument, using {0}", scans[0])) - except AttributeError: - logger.warning(BMsg("missing scan argument, using {0}", scans[0])) - - for scan_key in scans: - scan_spec = project.scan_dict[scan_key] - project.add_scan(**scan_spec) - logger.info(BMsg("add scan {filename} ({emitter} {initial_state})", **scan_spec)) - - try: - if project_args.element: - project.element = project_args.element - for scan in project.scans: - scan.emitter = project_args.element - logger.warning(BMsg("override emitters to {0}", project.emitter)) - except AttributeError: - pass - - try: - if project_args.initial_state: - for scan in project.scans: - scan.initial_state = project_args.initial_state - logger.warning(f"override initial states of all scans to {project_args.initial_state}") - except AttributeError: - pass - - try: - if project_args.energy: - for scan in project.scans: - scan.energies = np.asarray((project_args.energy, )) - logger.warning(BMsg("override scan energy, set to {0}", project_args.energy)) - except AttributeError: - pass - - -def parse_project_args(_args): - parser = argparse.ArgumentParser() - - # main arguments - parser.add_argument('-e', '--element', help="chemical element symbol") - parser.add_argument('-s', '--scans', nargs="*", default=['default'], - help="nick names of scans to use in calculation (see create_project function)") - parser.add_argument('-i', '--initial-state', - help="inital state of photoelectron") - parser.add_argument('--energy', type=float, - help="kinetic energy of photoelectron (override scan file)") - - parsed_args = parser.parse_args(_args) - return parsed_args diff --git a/pmsco/projects/demo/molecule.json b/pmsco/projects/demo/molecule.json new file mode 100644 index 0000000..5c9f229 --- /dev/null +++ b/pmsco/projects/demo/molecule.json @@ -0,0 +1,85 @@ +{ + "#description": "template runfile for angle scans of molecules (i.e., clusters defined in a molecule file)", + "project": { + "__module__": "pmsco.projects.demo.molecule", + "__class__": "MoleculeProject", + "job_name": "molecule0001", + "job_tags": [], + "description": "", + "mode": "single", + "directories": { + "data": "", + "output": "" + }, + "keep_files": [ + "cluster", + "model", + "scan", + "report", + "population" + ], + "keep_best": 10, + "keep_levels": 1, + "time_limit": 24, + "log_file": "", + "log_level": "WARNING", + "cluster_generator": { + "__class__": "MoleculeFileCluster", + "atom_types": { + "A": "N", + "B": "Ni" + }, + "model_dict": { + "dAB": "dNNi", + "th": "pNNi", + "ph": "aNNi" + } + }, + "cluster_file": "TODO", + "emitter_file": "TODO", + "atomic_scattering_factory": "InternalAtomicCalculator", + "multiple_scattering_factory": "EdacCalculator", + "model_space": { + "zsurf": { + "start": 1.5, + "min": 0.5, + "max": 2.0, + "step": 0.25 + }, + "Texp": {"start": 300.0}, + "Tdeb": {"start": 100.0}, + "V0": {"start": 10.0}, + "rmax": {"start": 50.0}, + "ares": {"start": 5.0}, + "distm": {"start": 5.0}, + "wdom1": {"start": 1.0}, + "wdom2": {"start": 1.0} + }, + "domains": [ + {"xrot": 0.0, "yrot": 0.0, "zrot": 0.0}, + {"xrot": 0.0, "yrot": 0.0, "zrot": 120.0}, + {"xrot": 0.0, "yrot": 0.0, "zrot": 240.0} + ], + "scans": [ + { + "__class__": "HoloScanCreator", + "filename": "${project}/molecule.etpi", + "emitter": "N", + "initial_state": "1s", + "generator": "pmsco.data.holo_grid", + "generator_args": { + "theta_start": 90, + "theta_step": 1 + }, + "other_positions": {"e": 250, "a": 0} + } + ], + "optimizer_params": { + "pop_size": 0, + "seed_file": "", + "seed_limit": 0, + "recalc_seed": true, + "table_file": "" + } + } +} diff --git a/projects/demo/molecule.py b/pmsco/projects/demo/molecule.py similarity index 54% rename from projects/demo/molecule.py rename to pmsco/projects/demo/molecule.py index cdbf02c..50e9f12 100644 --- a/projects/demo/molecule.py +++ b/pmsco/projects/demo/molecule.py @@ -1,11 +1,33 @@ """ @package pmsco.projects.demo.molecule -scattering calculation project for single molecules +scattering calculation project for single molecules or coordinate files from other programs -the atomic positions are read from a molecule file. -cluster file, emitter (by chemical symbol), initial state and kinetic energy are specified on the command line. +the atomic positions are read from a molecule (.xyz) file. +emitters are selected by chemical element symbol or by an additional molecule file (emitter file) +that contains only those atoms of the cluster file which are inequivalent emitters. + +cluster, emitters, initial state and kinetic energy are specified on the command line. there are no structural parameters. +example 1: molecule from XYZ file +--------------------------------- + +the cluster file contains all atomic positions necessary for calculating the diffraction pattern. +emitters are selected by chemical element symbol. +the cluster is not trimmed. +normal emission is along the z-axis. + + +example 2: periodic structure from external program (e.g. Vesta) +---------------------------------------------------------------- + +the cluster file contains the unit cells spanned by at least 3 unit vectors in the surface plane. +the emitter file contains only one unit cell as a sub-set of the cluster file. +emitters can be narrowed down further by chemical element symbol. +an rmax parameter can be specified to trim the cluster. +normal emission is along the z-axis. + + @author Matthias Muntwiler, matthias.muntwiler@psi.ch @copyright (c) 2015-20 by Paul Scherrer Institut @n @@ -17,10 +39,8 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n import math import numpy as np -import os.path from pathlib import Path import periodictable as pt -import argparse import logging # noinspection PyUnresolvedReferences @@ -30,11 +50,11 @@ from pmsco.calculators.edac import EdacCalculator # noinspection PyUnresolvedReferences from pmsco.calculators.phagen.runner import PhagenCalculator import pmsco.cluster as cluster -from pmsco.data import calc_modfunc_loess +import pmsco.data # noinspection PyUnresolvedReferences import pmsco.elements.bindingenergy from pmsco.helpers import BraceMessage as BMsg -import pmsco.project as project +import pmsco.project logger = logging.getLogger(__name__) @@ -48,10 +68,11 @@ class MoleculeFileCluster(cluster.ClusterGenerator): def __init__(self, project): super(MoleculeFileCluster, self).__init__(project) self.base_cluster = None + self.emitter_cluster = None - def load_base_cluster(self): + def get_base_cluster(self): """ - load and cache the project-defined coordinate file. + return the project-defined atom coordinates, load from file if necessary. the file path is set in self.project.cluster_file. the file must be in XYZ (.xyz) or PMSCO cluster (.clu) format (cf. pmsco.cluster module). @@ -60,7 +81,7 @@ class MoleculeFileCluster(cluster.ClusterGenerator): """ if self.base_cluster is None: clu = cluster.Cluster() - clu.set_rmax(120.0) + clu.set_rmax(np.inf) p = Path(self.project.cluster_file) ext = p.suffix if ext == ".xyz": @@ -74,6 +95,36 @@ class MoleculeFileCluster(cluster.ClusterGenerator): return self.base_cluster + def get_emitter_cluster(self): + """ + return the project-defined emitter coordinates, load from file if necessary. + + the file path is set in self.project.emitter_file. + if the file path is None, the method loads the base cluster self.project.cluster_file. + the file must be in XYZ (.xyz) or PMSCO cluster (.clu) format (cf. pmsco.cluster module). + + @return: Cluster object (also referenced by self.emitter_cluster). + None if no emitter file was specified. + """ + if self.emitter_cluster is None: + clu = cluster.Cluster() + clu.set_rmax(np.inf) + try: + p = Path(self.project.emitter_file) + except TypeError: + p = Path(self.project.cluster_file) + ext = p.suffix + if ext == ".xyz": + fmt = cluster.FMT_XYZ + elif ext == ".clu": + fmt = cluster.FMT_PMSCO + else: + raise ValueError(f"unknown cluster file extension {ext}") + clu.load_from_file(self.project.emitter_file, fmt=fmt) + self.emitter_cluster = clu + + return self.emitter_cluster + def count_emitters(self, model, index): """ count the number of emitter configurations. @@ -86,8 +137,14 @@ class MoleculeFileCluster(cluster.ClusterGenerator): or the number of emitters in the specified configuration (>= 0). @return: number of emitter configurations. """ - clu = self.create_cluster(model, index) - return clu.get_emitter_count() + if index.emit == -1: + clu = self.get_emitter_cluster() + sel_emit = clu.data['s'] == self.project.scans[index.scan].emitter + return np.sum(sel_emit) + elif index.emit >= 0: + return 1 + else: + raise ValueError(f"invalid emitter index {index.emit}") def create_cluster(self, model, index): """ @@ -107,45 +164,55 @@ class MoleculeFileCluster(cluster.ClusterGenerator): @return pmsco.cluster.Cluster object """ - self.load_base_cluster() clu = cluster.Cluster() - clu.copy_from(self.base_cluster) + clu.copy_from(self.get_base_cluster()) clu.comment = f"{self.__class__}, {index}" dom = self.project.domains[index.domain] - # trim - clu.set_rmax(model['rmax']) - clu.trim_sphere(clu.rmax) - # emitter selection - idx_emit = np.where(clu.data['s'] == self.project.scans[index.scan].emitter) + ems = cluster.Cluster() + ems.copy_from(self.get_emitter_cluster()) + ems.set_rmax(model['rmax'] + 0.1) + ems.trim_cylinder(clu.rmax, clu.rmax) + + idx_emit = np.where(ems.data['s'] == self.project.scans[index.scan].emitter) assert isinstance(idx_emit, tuple) idx_emit = idx_emit[0] if index.emit >= 0: idx_emit = idx_emit[index.emit] - clu.data['e'][idx_emit] = 1 + origin = ems.get_position(idx_emit) + clu.translate(-origin) + clu.data['e'] = 0 + clu.set_emitter(pos=np.array((0.0, 0.0, 0.0))) + else: + for idx in idx_emit: + clu.set_emitter(pos=ems.get_position(idx)) # rotation if 'xrot' in model: - clu.rotate_z(model['xrot']) + clu.rotate_x(model['xrot']) elif 'xrot' in dom: - clu.rotate_z(dom['xrot']) + clu.rotate_x(dom['xrot']) if 'yrot' in model: - clu.rotate_z(model['yrot']) + clu.rotate_y(model['yrot']) elif 'yrot' in dom: - clu.rotate_z(dom['yrot']) + clu.rotate_y(dom['yrot']) if 'zrot' in model: clu.rotate_z(model['zrot']) elif 'zrot' in dom: clu.rotate_z(dom['zrot']) + # trim + clu.set_rmax(model['rmax'] + 0.1) + clu.trim_paraboloid(clu.rmax, -clu.rmax) + logger.info(f"cluster for calculation {index}: " f"{clu.get_atom_count()} atoms, {clu.get_emitter_count()} emitters") return clu -class MoleculeProject(project.Project): +class MoleculeProject(pmsco.project.Project): """ general molecule project. @@ -176,15 +243,29 @@ class MoleculeProject(project.Project): initialize a project instance """ super(MoleculeProject, self).__init__() - self.model_space = project.ModelSpace() - self.scan_dict = {} + self.model_space = pmsco.project.ModelSpace() self.cluster_file = "demo-cluster.xyz" + self.emitter_file = None self.cluster_generator = MoleculeFileCluster(self) self.atomic_scattering_factory = PhagenCalculator self.multiple_scattering_factory = EdacCalculator self.phase_files = {} self.rme_files = {} - self.modf_smth_ei = 0.5 + + def validate(self): + """ + Validate project parameters + + Resolve paths of cluster and emitter files after calling the inherited method. + + @return: None + """ + + super().validate() + self.cluster_file = self.directories.resolve_path(self.cluster_file) + self.emitter_file = self.directories.resolve_path(self.emitter_file) + logger.warning(f"cluster_file: {self.cluster_file}") + logger.warning(f"emitter_file: {self.emitter_file}") def create_params(self, model, index): """ @@ -196,7 +277,7 @@ class MoleculeProject(project.Project): @param index (named tuple CalcID) calculation index. this method formats the index into the comment line. """ - params = project.CalculatorParams() + params = pmsco.project.CalculatorParams() params.title = "molecule demo" params.comment = f"{self.__class__} {index}" @@ -215,8 +296,21 @@ class MoleculeProject(project.Project): params.angular_resolution = model['ares'] params.experiment_temperature = model['Texp'] params.debye_temperature = model['Tdeb'] - params.phase_files = self.phase_files - params.rme_files = self.rme_files + + if self.phase_files: + state = emitter + initial_state + try: + params.phase_files = self.phase_files[state] + except KeyError: + params.phase_files = {} + logger.warning("no phase files found for {} - using default calculator".format(state)) + + params.rme_files = {} + params.rme_minus_value = 0.1 + params.rme_minus_shift = 0.0 + params.rme_plus_value = 1.0 + params.rme_plus_shift = 0.0 + # edac_interface only params.emitters = [] params.lmax = 15 @@ -233,152 +327,3 @@ class MoleculeProject(project.Project): """ return self.model_space - - # noinspection PyUnusedLocal - def calc_modulation(self, data, model): - """ - calculate the modulation function with project-specific smoothing factor - - see @ref pmsco.pmsco.project.calc_modulation. - - @param data: (numpy.ndarray) experimental data in ETPI, or ETPAI format. - - @param model: (dict) model parameters of the calculation task. not used. - - @return copy of the data array with the modulation function in the 'i' column. - """ - return calc_modfunc_loess(data, smth=self.modf_smth_ei) - - -def create_model_space(mode): - """ - define the model space. - """ - dom = project.ModelSpace() - - if mode == "single": - dom.add_param('zsurf', 1.20) - dom.add_param('Texp', 300.00) - dom.add_param('Tdeb', 100.00) - dom.add_param('V0', 10.00) - dom.add_param('rmax', 50.00) - dom.add_param('ares', 5.00) - dom.add_param('distm', 5.00) - dom.add_param('wdom1', 1.0) - dom.add_param('wdom2', 1.0) - dom.add_param('wdom3', 1.0) - dom.add_param('wdom4', 1.0) - dom.add_param('wdom5', 1.0) - else: - raise ValueError(f"undefined model space for {mode} optimization") - - return dom - - -def create_project(): - """ - create the project instance. - """ - - proj = MoleculeProject() - proj_dir = os.path.dirname(os.path.abspath(__file__)) - proj.project_dir = proj_dir - - # scan dictionary - # to select any number of scans, add their dictionary keys as scans option on the command line - proj.scan_dict['empty'] = {'filename': os.path.join(proj_dir, "../common/empty-hemiscan.etpi"), - 'emitter': "N", 'initial_state': "1s"} - - proj.mode = 'single' - proj.model_space = create_model_space(proj.mode) - proj.job_name = 'molecule0000' - proj.description = 'molecule demo' - - return proj - - -def set_project_args(project, project_args): - """ - set the project arguments. - - @param project: project instance - - @param project_args: (Namespace object) project arguments. - """ - - scans = [] - try: - if project_args.scans: - scans = project_args.scans - else: - logger.error("missing scan argument") - exit(1) - except AttributeError: - logger.error("missing scan argument") - exit(1) - - for scan_key in scans: - scan_spec = project.scan_dict[scan_key] - project.add_scan(**scan_spec) - - try: - project.cluster_file = os.path.abspath(project_args.cluster_file) - project.cluster_generator = MoleculeFileCluster(project) - except (AttributeError, TypeError): - logger.error("missing cluster-file argument") - exit(1) - - try: - if project_args.emitter: - for scan in project.scans: - scan.emitter = project_args.emitter - logger.warning(f"override emitters of all scans to {project_args.emitter}") - except AttributeError: - pass - - try: - if project_args.initial_state: - for scan in project.scans: - scan.initial_state = project_args.initial_state - logger.warning(f"override initial states of all scans to {project_args.initial_state}") - except AttributeError: - pass - - try: - if project_args.energy: - for scan in project.scans: - scan.energies = np.asarray((project_args.energy, )) - logger.warning(f"override scan energy of all scans to {project_args.energy}") - except AttributeError: - pass - - try: - if project_args.symmetry: - for angle in np.linspace(0, 360, num=project_args.symmetry, endpoint=False): - project.add_domain({'xrot': 0., 'yrot': 0., 'zrot': angle}) - logger.warning(f"override rotation symmetry to {project_args.symmetry}") - except AttributeError: - pass - - -def parse_project_args(_args): - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - - # main arguments - parser.add_argument('--scans', nargs="*", - help="nick names of scans to use in calculation (see create_project function)") - parser.add_argument('--cluster-file', - help="path name of molecule file (xyz format).") - - # conditional arguments - parser.add_argument('--emitter', - help="emitter: chemical symbol") - parser.add_argument('--initial-state', - help="initial state term: e.g. 2p1/2") - parser.add_argument('--energy', type=float, - help="kinetic energy (eV)") - parser.add_argument('--symmetry', type=int, default=1, - help="n-fold rotational symmetry") - - parsed_args = parser.parse_args(_args) - return parsed_args diff --git a/projects/twoatom/twoatom-energy.json b/pmsco/projects/twoatom/twoatom-energy.json similarity index 82% rename from projects/twoatom/twoatom-energy.json rename to pmsco/projects/twoatom/twoatom-energy.json index ad8e3e6..f769479 100644 --- a/projects/twoatom/twoatom-energy.json +++ b/pmsco/projects/twoatom/twoatom-energy.json @@ -1,15 +1,24 @@ { - // line comments using // or # prefix are allowed as an extension of JSON syntax + "schedule": { + "__module__": "pmsco.schedule", + "__class__": "PsiRaSchedule", + "tasks": 4, + "nodes": 1, + "wall_time": "1:00", + "manual": true, + "enabled": true, + "overwrite_job_dir": true + }, "project": { - "__module__": "projects.twoatom.twoatom", + "__module__": "pmsco.projects.twoatom.twoatom", "__class__": "TwoatomProject", "job_name": "twoatom0002", - "job_tags": [], + "job_tags": {}, "description": "", "mode": "single", "directories": { "data": "", - "output": "" + "output": "${work}/${job_name}" }, "keep_files": [ "cluster", @@ -70,7 +79,7 @@ ], "scans": [ { - "__class__": "mp.ScanCreator", + "__class__": "ScanCreator", "filename": "twoatom_energy_alpha.etpai", "emitter": "N", "initial_state": "1s", diff --git a/projects/twoatom/twoatom-hemi.json b/pmsco/projects/twoatom/twoatom-hemi.json similarity index 61% rename from projects/twoatom/twoatom-hemi.json rename to pmsco/projects/twoatom/twoatom-hemi.json index 7b3d5ff..7b8a3a9 100644 --- a/projects/twoatom/twoatom-hemi.json +++ b/pmsco/projects/twoatom/twoatom-hemi.json @@ -1,17 +1,28 @@ { - // line comments using // or # prefix are allowed as an extension of JSON syntax + "#comment": "keys starting with a non-alphabetic character are treated as a comment", + "schedule": { + "__module__": "pmsco.schedule", + "__class__": "PsiRaSchedule", + "tasks": 1, + "nodes": 1, + "wall_time": "1:00", + "manual": true, + "enabled": true, + "overwrite_job_dir": true + }, "project": { - "__module__": "projects.twoatom.twoatom", + "__module__": "pmsco.projects.twoatom.twoatom", "__class__": "TwoatomProject", "job_name": "twoatom0001", - "job_tags": [], + "job_tags": {}, "description": "", "mode": "single", "directories": { "data": "", - "output": "" + "output": "${work}/${job_name}" }, "keep_files": [ + "all", "cluster", "model", "scan", @@ -70,13 +81,23 @@ ], "scans": [ { - // class name as it would be used in the project module - "__class__": "mp.ScanLoader", - // any placeholder key from project.directories can be used - "filename": "{project}/twoatom_hemi_250e.etpi", + "__class__": "HoloScanCreator", + "filename": "${project}/twoatom_demo.etpi", "emitter": "N", "initial_state": "1s", - "is_modf": false + "generator": "pmsco.data.holo_grid", + "generator_args": { + "theta_start": 90, + "theta_step": 1, + "theta_range": 90, + "phi_start": 0, + "phi_range": 360, + "phi_refinement": 1 + }, + "other_positions": {"e": 250, "a": 0}, + "modulation_func": "pmsco.data.calc_modfunc_loess", + "modulation_args": {"smth": 0.5}, + "rfactor_func": "pmsco.data.square_diff_rfactor" } ], "optimizer_params": { diff --git a/projects/twoatom/twoatom.py b/pmsco/projects/twoatom/twoatom.py old mode 100644 new mode 100755 similarity index 72% rename from projects/twoatom/twoatom.py rename to pmsco/projects/twoatom/twoatom.py index c3cafa5..c412686 --- a/projects/twoatom/twoatom.py +++ b/pmsco/projects/twoatom/twoatom.py @@ -1,33 +1,32 @@ """ @package projects.twoatom Two-atom demo scattering calculation project - -this file is specific to the project and the state of the data analysis, -as it contains particular parameter values. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import argparse import logging import math import numpy as np -import os.path import periodictable as pt +# noinspection PyUnresolvedReferences +from pmsco.pmsco import main from pmsco.calculators.calculator import InternalAtomicCalculator from pmsco.calculators.edac import EdacCalculator from pmsco.calculators.phagen.runner import PhagenCalculator -import pmsco.cluster as mc -import pmsco.project as mp +from pmsco.cluster import Cluster, ClusterGenerator +from pmsco.project import CalculatorParams, ModelSpace, Project from pmsco.helpers import BraceMessage as BMsg +# the following imports are used in run files +# noinspection PyUnresolvedReferences +import pmsco.data +# noinspection PyUnresolvedReferences +from pmsco.scan import ScanKey, ScanLoader, ScanCreator, HoloScanCreator + logger = logging.getLogger(__name__) -class TwoatomCluster(mc.ClusterGenerator): +class TwoatomCluster(ClusterGenerator): """ cluster of two atoms. @@ -119,7 +118,7 @@ class TwoatomCluster(mc.ClusterGenerator): dy = r * math.sin(th) * math.sin(ph) dz = r * math.cos(th) - clu = mc.Cluster() + clu = Cluster() clu.comment = "{0} {1}".format(self.__class__, index) clu.set_rmax(r * 2.0) @@ -132,7 +131,7 @@ class TwoatomCluster(mc.ClusterGenerator): return clu -class TwoatomProject(mp.Project): +class TwoatomProject(Project): """ two-atom calculation project class. @@ -146,9 +145,9 @@ class TwoatomProject(mp.Project): @arg @c model['V0'] : inner potential @arg @c model['Zsurf'] : position of surface """ + def __init__(self): super(TwoatomProject, self).__init__() - self.scan_dict = {} self.cluster_generator = TwoatomCluster(self) self.cluster_generator.set_atom_type('A', 'N') self.cluster_generator.set_atom_type('B', 'Ni') @@ -173,13 +172,12 @@ class TwoatomProject(mp.Project): @param model: (dict) optimizable parameters """ - params = mp.CalculatorParams() + params = CalculatorParams() params.title = "two-atom demo" params.comment = "{0} {1}".format(self.__class__, index) params.cluster_file = "" params.output_file = "" - params.initial_state = self.scans[index.scan].initial_state initial_state = self.scans[index.scan].initial_state params.initial_state = initial_state emitter = self.scans[index.scan].emitter @@ -219,7 +217,7 @@ class TwoatomProject(mp.Project): """ define the domain of the optimization parameters. """ - dom = mp.ModelSpace() + dom = ModelSpace() if self.mode == "single": dom.add_param('dNNi', 2.109, 2.000, 2.250, 0.050) @@ -264,80 +262,3 @@ def example_intensity(e, t, p, a): np.cos(np.radians(p)) ** 2 * \ np.sin(e / 1000. * np.pi * 0.1 / np.sqrt(e)) ** 2 return i - - -def create_project(): - """ - create a new TwoatomProject calculation project. - - the default experimental data file is @c twoatom_hemi_scan_250e.etpi - in the same directory as this Python module. - it defines a classic hemispherical angle scan grid - but does not include measured data for optimization. - - @return project instance. - """ - - project = TwoatomProject() - - project_dir = os.path.dirname(os.path.abspath(__file__)) - project.data_dir = project_dir - - # scan dictionary - # to select any number of scans, add their dictionary keys as scans option on the command line - project.scan_dict['ea'] = {'filename': os.path.join(project_dir, "twoatom_energy_alpha.etpai"), - 'emitter': "N", 'initial_state': "1s"} - project.scan_dict['et0p'] = {'filename': os.path.join(project_dir, "twoatom_energy_theta_0p.etpi"), - 'emitter': "N", 'initial_state': "1s"} - project.scan_dict['et180p'] = {'filename': os.path.join(project_dir, "twoatom_energy_theta_180p.etpi"), - 'emitter': "N", 'initial_state': "1s"} - project.scan_dict['tp215e'] = {'filename': os.path.join(project_dir, "twoatom_hemi_215e.etpi"), - 'emitter': "N", 'initial_state': "1s"} - project.scan_dict['tp250e'] = {'filename': os.path.join(project_dir, "twoatom_hemi_250e.etpi"), - 'emitter': "N", 'initial_state': "1s"} - - return project - - -def set_project_args(project, project_args): - """ - set the project-specific arguments. - - @param project: project instance - - @param project_args: (Namespace object) project arguments. - """ - - scans = [] - try: - if project_args.scans: - scans = project_args.scans - except AttributeError: - pass - - for scan_key in scans: - scan_spec = project.scan_dict[scan_key] - project.add_scan(**scan_spec) - logger.info(BMsg("add scan {filename} ({emitter} {initial_state})", **scan_spec)) - - project.add_domain({'default': 0.0}) - - -def parse_project_args(_args): - """ - parse project-specific command line arguments. - - @param _args: list of project-specific arguments from the command line. - this is typically the unknown_args return value from argparse.ArgumentParser.parse_known_args(). - - @return: namespace object containing the specified arguments as attributes. - """ - parser = argparse.ArgumentParser() - - # main arguments - parser.add_argument('-s', '--scans', nargs="*", - help="nick names of scans to use in calculation (see create_project function)") - - parsed_args = parser.parse_args(_args) - - return parsed_args diff --git a/projects/twoatom/twoatom_energy_alpha.etpai b/pmsco/projects/twoatom/twoatom_energy_alpha.etpai similarity index 100% rename from projects/twoatom/twoatom_energy_alpha.etpai rename to pmsco/projects/twoatom/twoatom_energy_alpha.etpai diff --git a/projects/twoatom/twoatom_energy_theta_0p.etpi b/pmsco/projects/twoatom/twoatom_energy_theta_0p.etpi similarity index 100% rename from projects/twoatom/twoatom_energy_theta_0p.etpi rename to pmsco/projects/twoatom/twoatom_energy_theta_0p.etpi diff --git a/projects/twoatom/twoatom_energy_theta_180p.etpi b/pmsco/projects/twoatom/twoatom_energy_theta_180p.etpi similarity index 100% rename from projects/twoatom/twoatom_energy_theta_180p.etpi rename to pmsco/projects/twoatom/twoatom_energy_theta_180p.etpi diff --git a/projects/twoatom/twoatom_hemi_215e.etpi b/pmsco/projects/twoatom/twoatom_hemi_215e.etpi similarity index 100% rename from projects/twoatom/twoatom_hemi_215e.etpi rename to pmsco/projects/twoatom/twoatom_hemi_215e.etpi diff --git a/projects/twoatom/twoatom_hemi_250e.etpi b/pmsco/projects/twoatom/twoatom_hemi_250e.etpi similarity index 100% rename from projects/twoatom/twoatom_hemi_250e.etpi rename to pmsco/projects/twoatom/twoatom_hemi_250e.etpi diff --git a/pmsco/reports/base.py b/pmsco/reports/base.py new file mode 100644 index 0000000..805fcd6 --- /dev/null +++ b/pmsco/reports/base.py @@ -0,0 +1,229 @@ +""" +@package pmsco.reports.base +base class of project reports + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import logging +from pathlib import Path +from string import Template +import pmsco.config as config + +logger = logging.getLogger(__name__) + + +class ProjectReport(config.ConfigurableObject): + """ + base class of project reports + + what do we need to know from project? + - directories to resolve path names + - database session factory + - database job id + - calculation id + + usage: + 1. assign public attributes as necessary, leave defaults at None. + 2. call validate() if necessary. + 3. call set_database() if necessary. + 4. load data into result_data by calling select_data() or by modifying result_data directly. + 5. call create_report() + + implementations of reports should not need to access any project members directly! + + this class is under development + """ + + ## @var _project + # project object reference + # + + ## @var _dba + # database access (session factory) + # + + ## @var _modes + # compatible project modes + # + # set of modes in which the report can be used. + # if an incompatible project is assigned, the enabled property is set to False. + + ## @var canvas + # matplotlib canvas for graphical reports + # + # a FigureCanvas class reference of a matplotlib backend. + # the default is matplotlib.backends.backend_agg.FigureCanvasAgg + # which produces a bitmap file in PNG format. + # some other options are + # matplotlib.backends.backend_pdf.FigureCanvasPdf or + # matplotlib.backends.backend_svg.FigureCanvasSVG. + + ## @var enabled + # enable/disable the report + # + # the flag allows to temporarily enable or disable a report. + # + # the flag does not change the behaviour of the class. + # it is up to the caller to respect it. + # + # the validate method can set the flag to False if the project is not compatible. + + ## @var report_dir + # destination directory for report files + # + # this should be a Path or PathLike object. + # by default, the project's directories['report'] entry is used. + + ## @var base_filename + # base name of output files + # + # this value gets copied into the {base} placeholder of filename_format. + # + # by default, this is the stem of the output filename from the project settings. + + ## @var filename_format (str) + # format of the output file name + # + # the format method of the string will be used to produce individual names. + # possible fields are: + # base, job_id, job_name, mode, model, gen, particle, param0, param1 + # where base corresponds to the stem of the output files produced by the calculators. + # + # a file extension according to the file format is appended. + + ## @var title_format (str) + # title of the plot + # + # the format method of the string will be used to produce individual titles. + # possible fields are: + # base, job_id, job_name, mode, model, gen, particle, param0, param1 + + ## @var trigger_levels (set of str) + # events that may trigger creation of a report + # + # this attribute selects when a report is created. + # the following values are currently recognized. + # any other value disables the report. + # further modes may be implemented in the future. + # + # - `model` - every time a new model has been calculated. + # - `end` (default) - only once at the end of an optimization job. + # + # it is up to the calling code to respect this attribute. + # it does not affect the behaviour of the class. + + def __init__(self): + super().__init__() + self._project = None + self._dba = None + self._modes = set() + + self.enabled = True + self.trigger_levels = {'end'} + self.canvas = None + self.report_dir = None + self.base_filename = None + self.filename_format = "${base}" + self.title_format = "" + + def get_session(self): + """ + get a new database session context handler + + @return: context handler which provides an sqlalchemy database session, + e.g. a pmsco.database.access.LockedSession() object. + """ + return self._dba.session() + + def validate(self, project): + """ + validate the configuration (object properties). + + @param project: pmsco.project.Project object, + or any object that contains equivalent directories and output_file attributes. + + @return: None + """ + self._project = project + if self._project: + if self.report_dir is None: + self.report_dir = self._project.directories['report'] + if self.base_filename is None: + self.base_filename = self._project.output_file.name + + if self.enabled and self._project.mode not in self._modes: + self.enabled = False + logger.warning(f"project mode {self._project.mode} incompatible with {self.__class__.__name__}") + + if self.report_dir: + Path(self.report_dir).mkdir(exist_ok=True) + + def set_database(self, database_access): + """ + preparation steps for the report. + + @param database_access: fully initialized pmsco.database.project.ProjectDatabase object + which provides database sessions. + + @return: None + """ + self._dba = database_access + + def resolve_template(self, template, mapping): + """ + resolve placeholders in template string + + the function first tries to resolve using the project's resolve_path function + and reverts to plain python Template strings if no project is set. + + @param template: template string using ${name}-style place holders. + name must be declared in project.directories or mapping. + non-string objects (e.g. Path) are converted to a string using the str function. + @param mapping: dictionary of name-value pairs for placeholders. + @return: resolved string + """ + try: + r = self._project.directories.resolve_path(template, mapping) + except AttributeError: + if template: + r = Template(str(template)).substitute(mapping) + else: + r = "" + return r + + def select_data(self, jobs=-1, calcs=None): + """ + query data from the database + + this method must be implemented by the sub-class. + + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + if None, results from all jobs are loaded. + if -1 (default), results from the most recent job (by datetime field) are loaded. + + @param calcs: filter by result. + the argument can be a singleton or sequence of CalcID objects. + if None (default), all results are loaded. + if -1, the most recent result is loaded. + + @return: None + """ + pass + + def create_report(self): + """ + generate or update the report from stored data. + + this method must be implemented by the sub-class. + + @return: None + """ + pass diff --git a/pmsco/reports/convergence.py b/pmsco/reports/convergence.py new file mode 100755 index 0000000..a7995e3 --- /dev/null +++ b/pmsco/reports/convergence.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python +""" +@package pmsco.reports.convergence +graphics rendering module to show convergence of a population. + +the module can be used in several different ways: + +1. via the command line on a pmsco database or .dat results file. + this is the most simple but least flexible way. +2. via python functions on given population arrays or database queries. + this is the most flexible way but requires understanding of the required data formats. +3. as a listener on calculation events. (to be implemented) + this will be configurable in the run file. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import argparse +import logging +import numpy as np +from pathlib import Path +import sys + +if __name__ == "__main__": + pmsco_root = Path(__file__).resolve().parent.parent.parent + if str(pmsco_root) not in sys.path: + sys.path.insert(0, str(pmsco_root)) + +import pmsco.reports.results as rp_results +import pmsco.database.util as db_util +import pmsco.database.query as db_query +from pmsco.reports.base import ProjectReport + +logger = logging.getLogger(__name__) + +try: + from matplotlib.figure import Figure + from matplotlib.ticker import MaxNLocator + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas + # from matplotlib.backends.backend_pdf import FigureCanvasPdf + # from matplotlib.backends.backend_svg import FigureCanvasSVG +except ImportError: + Figure = None + FigureCanvas = None + MaxNLocator = None + logger.warning("error importing matplotlib. graphics rendering disabled.") + + +def plot_convergence(filename, data, title=None, canvas=None, num_gen=10): + """ + violin plot showing the convergence of a population by generation. + + the plot is a violin plot where each violin represents one generation. + the minimum, maximum and mean values are marked, + and the distribution is indicated by the body. + + if no generation index is available, the function can divide the data into a selected number of segments. + + this is a low-level function containing just the plotting commands from numpy arrays. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param filename: path and base name of the output file without extension. + a generation index and the file extension according to the file format are appended. + @param data: structured ndarray containing generation numbers and R-factor values. + the '_rfac' column is required and must contain R-factor values. + the '_gen' column is optional. if present it must contain the generation index. + if the '_gen' column is missing or if the array is a simple 1D array, + the array is divided into num_gen segments. + other columns may be present and are ignored. + @param num_gen: number of generations if the '_gen' column is missing in data. + @param title: (str) title of the chart. + default: derived from parameter names. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (str) path and name of the generated graphics file. + None if no file was generated due to an error. + """ + if canvas is None: + canvas = FigureCanvas + if canvas is None or Figure is None: + return None + if title is None: + title = 'convergence' + + fig = Figure() + canvas(fig) + ax = fig.add_subplot(111) + + rfactors = [] + generations = [] + try: + generations = np.unique(data['_gen']) + for gen in generations: + idx = np.where(data['_gen'] == gen) + rfactors.append(data['_rfac'][idx]) + except IndexError: + for gen, arr in enumerate(np.array_split(data, num_gen)): + generations.append(gen) + rfactors.append(arr) + except ValueError: + for gen, arr in enumerate(np.array_split(data['_rfac'], num_gen)): + generations.append(gen) + rfactors.append(arr) + + # the following may raise a VisibleDeprecationWarning. + # this is a bug in matplotlib and has been resolved as of matplotlib 3.3.0. + # BUG: VisibleDeprecationWarning in boxplot #16353 + # https://github.com/matplotlib/matplotlib/issues/16353 + ax.violinplot(rfactors, generations, showmeans=True, showextrema=True, showmedians=False, widths=0.8) + ax.set_ylim([0., 1.]) + ax.set_xlabel('generation') + ax.set_ylabel('R-factor') + ax.set_title(title) + + out_filename = "{base}.{ext}".format(base=filename, ext=canvas.get_default_filetype()) + try: + fig.savefig(out_filename) + except OSError: + logger.exception(f"exception while saving figure {out_filename}") + out_filename = None + + return out_filename + + +class ConvergencePlot(ProjectReport): + """ + violin plot showing the convergence of a population by generation. + + this class collects and validates all parameters and data for generating a convergence plot. + the convergence plot can be used to monitor the overall progress of an optimization job. + + the convergence plot is a violin plot which shows the evolution of R-factors in a population over generations. + each violin represents one generation. + the minimum, maximum and mean values are marked, + and the distribution is indicated by the body. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + """ + + def __init__(self): + super().__init__() + self._modes = ['genetic', 'swarm'] + self.result_data = rp_results.ResultData() + self.filename_format = "${base}-convergence" + self.title_format = "convergence" + + def select_data(self, jobs=-1, calcs=None): + """ + query data from the database + + this method must be implemented by the sub-class. + + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + if None, results from all jobs are loaded. + if -1 (default), results from the most recent job (by datetime field) are loaded. + + @param calcs: the calcs argument is ignored. + + @return: None + """ + + with self.get_session() as session: + if jobs == -1: + jobs = db_query.query_newest_job(session) + self.result_data.reset_filters() + self.result_data.levels = {'scan': -1} + self.result_data.load_from_db(session, jobs=jobs, include_params=False) + + def create_report(self): + # check that result data is compatible with convergence plots + if self.result_data.generations is None or len(self.result_data.generations) < 1: + logger.warning("result data must specify at least 1 generation") + return [] + if self.result_data.particles is None or len(self.result_data.particles) < 5: + logger.warning("result data must specify at least 3 particles") + return [] + + kwargs = {} + if self.canvas is not None: + kwargs['canvas'] = self.canvas + + files = [] + fdict = {'base': self.base_filename} + filename = Path(self.report_dir, self.filename_format) + filename = Path(self.resolve_template(filename, fdict)) + kwargs['title'] = self.resolve_template(self.title_format, fdict) + of = plot_convergence(filename, self.result_data.values, **kwargs) + if of: + files.append(of) + + return files + + +def render_convergence(output_file, values, generations=None, title=None, canvas=None): + """ + produce a convergence plot of a population-based optimization job. + + see ConvergencePlot and plot_convergence for details. + + the function requires input in one of the following forms: + - a result (.dat) file or numpy structured array. + the array must contain the _gen and _rfac columns. + other columns are ignored. + the function generates one plot. + - a pmsco.optimizers.population.Population object with valid data. + the generation is taken from the respective attribute and overrides the function argument. + - an open pmsco database session. the most recent job results are loaded. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param output_file: path and base name of the output file without extension. + @param values: a numpy structured ndarray of a population or result list from an optimization run. + alternatively, the file path of a result file (.dat) or population file (.pop) can be given. + file can be any object that numpy.genfromtxt() can handle. + array or file must be wrapped in a sequence. + @param generations: (sequence of int) list of generation indices to filter. + if the input data does not contain the generation, no filtering is applied. + by default, no filtering is applied, and all generations are included in the plot. + @param title: (str) title of the chart. + the title is a {}-style format string, where {base} is the output file name. + default: derived from file name. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (list of str) paths of the generated graphics files. + empty if an error occurred. + """ + + data = rp_results.ResultData() + if isinstance(generations, int): + generations = (generations,) + data.generations = generations + data.levels = {'scan': -1} + data.load_any(values) + + plot = ConvergencePlot() + plot.canvas = canvas + if title: + plot.title_format = title + else: + plot.title_format = "" + plot.report_dir = Path(output_file).parent + plot.filename_format = Path(output_file).name + plot.validate(None) + plot.result_data = data + files = plot.create_report() + + return files + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description=""" + population convergence plot for multiple-scattering optimization results + + this module operates on results or database files and produces one graphics file per generation. + + database files contain the complete information for all plot types. + data from the most recent job stored in the database is used. + + .dat results files contain all necessary data. + .tasks.dat files lack the generation and particle identification and should not be used. + """) + parser.add_argument('results_file', + help="path to results file (.dat) or sqlite3 database file.") + parser.add_argument('output_file', + help="base name of output file. generation and extension will be appended.") + parser.add_argument('-t', '--title', default=None, + help='graph title. may contain {gen} as a placeholder for the generation number.') + + args, unknown_args = parser.parse_known_args() + + kwargs = {} + if args.title is not None: + kwargs['title'] = args.title + + render_func = render_convergence + + if db_util.is_sqlite3_file(args.results_file): + import pmsco.database.access as db_access + db = db_access.DatabaseAccess() + db.connect(args.results_file) + with db.session() as session: + render_func(args.output_file, session, **kwargs) + else: + render_func(args.output_file, args.results_file, **kwargs) + + +if __name__ == '__main__': + main() + sys.exit(0) diff --git a/pmsco/reports/genetic.py b/pmsco/reports/genetic.py new file mode 100755 index 0000000..b1646b1 --- /dev/null +++ b/pmsco/reports/genetic.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python +""" +@package pmsco.reports.genetic +graphics rendering module for population genetics. + +the module can be used in several different ways: + +1. via the command line on a pmsco database or .dat results file. + this is the most simple but least flexible way. +2. via python functions on given population arrays or database queries. + this is the most flexible way but requires understanding of the required data formats. +3. as a listener on calculation events. (to be implemented) + this will be configurable in the run file. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import argparse +import logging +import numpy as np +from pathlib import Path +import sys + +if __name__ == "__main__": + pmsco_root = Path(__file__).resolve().parent.parent.parent + if str(pmsco_root) not in sys.path: + sys.path.insert(0, str(pmsco_root)) + +import pmsco.reports.results as rp_results +import pmsco.database.util as db_util +import pmsco.database.query as db_query +from pmsco.reports.base import ProjectReport +from pmsco.reports.population import GenerationTracker + +logger = logging.getLogger(__name__) + +try: + from matplotlib.figure import Figure + from matplotlib.ticker import MaxNLocator + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas + # from matplotlib.backends.backend_pdf import FigureCanvasPdf + # from matplotlib.backends.backend_svg import FigureCanvasSVG +except ImportError: + Figure = None + FigureCanvas = None + MaxNLocator = None + logger.warning("error importing matplotlib. graphics rendering disabled.") + + +def plot_genetic(filename, rpos2d, param_labels, title=None, cmap=None, canvas=None): + """ + produce a genetic chart from the given data. + + a genetic chart is a pseudo-colour representation of the coordinates of each individual in the model space. + the chart should highlight the amount of diversity in the population + and - by comparing charts of different generations - the changes due to mutation. + the axes are the model parameter (x) and particle number (y). + the colour is mapped from the relative position of a parameter value within the parameter range. + + in contrast to render_genetic_chart() this function contains only the drawing code. + it requires input in the final form and does not do any checks, conversion or processing. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param filename: path and name of the output file without extension. + @param rpos2d: (two-dimensional numpy array of numeric type) + relative positions of the particles in the model space. + dimension 0 (y-axis) is the particle index, + dimension 1 (x-axis) is the parameter index (in the order given by param_labels). + all values must be between 0 and 1. + @param param_labels: (sequence) list or tuple of parameter names. + @param title: (str) string to be printed as chart title. default is 'genetic chart'. + @param cmap: (str) name of colour map supported by matplotlib. + default is 'jet'. + other good-looking options are 'PiYG', 'RdBu', 'RdYlGn', 'coolwarm'. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (str) path and name of the generated graphics file. + None if no file was generated due to an error. + """ + if canvas is None: + canvas = FigureCanvas + if canvas is None or Figure is None: + return None + if cmap is None: + cmap = 'jet' + if title is None: + title = 'genetic chart' + + fig = Figure() + canvas(fig) + ax = fig.add_subplot(111) + im = ax.imshow(rpos2d, aspect='auto', cmap=cmap, origin='lower') + im.set_clim((0.0, 1.0)) + ax.set_xticks(np.arange(len(param_labels))) + ax.set_xticklabels(param_labels, rotation=45, ha="right", rotation_mode="anchor") + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + ax.set_ylabel('particle') + ax.set_title(title) + cb = ax.figure.colorbar(im, ax=ax) + cb.ax.set_ylabel("relative value", rotation=-90, va="bottom") + + out_filename = "{base}.{ext}".format(base=filename, ext=canvas.get_default_filetype()) + try: + fig.savefig(out_filename) + except OSError: + logger.exception(f"exception while saving figure {out_filename}") + out_filename = None + + return out_filename + + +class GeneticPlot(ProjectReport, GenerationTracker): + """ + produce two-dimensional genetic population charts + + this class collects and validates all parameters and data for generating a series of genetic charts. + it iterates over generations and calls plot_genetic() for each. + + a genetic chart is a pseudo-colour representation of the coordinates of each individual in the model space. + the axes are the particle number and the model parameter. + the colour is mapped from the relative position of a parameter value within the parameter range. + + the chart should illustrate the diversity in the population. + converged parameters will show similar colours. + by comparing charts of different generations, the effect of the optimization algorithm can be examined. + though the chart type is designed for the genetic algorithm, it may be useful for other algorithms as well. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + """ + + def __init__(self): + super().__init__() + self._modes = ['genetic', 'swarm'] + self.result_data = rp_results.ResultData() + self.filename_format = "${base}-genetic-${gen}" + self.title_format = "generation ${gen}" + self.cmap = None + self.params = None + + def select_data(self, jobs=-1, calcs=None): + """ + query data from the database + + this method must be implemented by the sub-class. + + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + if None, results from all jobs are loaded. + if -1 (default), results from the most recent job (by datetime field) are loaded. + + @param calcs: the calcs argument is ignored. + + @return: None + """ + + with self.get_session() as session: + if jobs == -1: + jobs = db_query.query_newest_job(session) + changed_gens = self.changed_generations(session, jobs) + self.result_data.reset_filters() + self.result_data.generations = changed_gens + self.result_data.levels = {'scan': -1} + self.result_data.load_from_db(session, jobs=jobs) + if self._project: + self.result_data.set_model_space(self._project.model_space) + + def create_report(self): + """ + generate the plots based on the stored attributes. + + this method essentially loops over generations and parameter combinations, + and compiles the input for plot_genetic. + + @return: list of created files + """ + # check that result data is compatible with genetic plots + if self.result_data.params is None or len(self.result_data.params) < 2: + logger.warning("result data must contain at least 2 parameters") + return [] + if self.result_data.generations is None or len(self.result_data.generations) < 1: + logger.warning("result data must specify at least 1 generation") + return [] + if self.result_data.particles is None or len(self.result_data.particles) < 5: + logger.warning("result data must specify at least 1 particle") + return [] + + vmin = self.result_data.model_space.min + vmax = self.result_data.model_space.max + pnames = self.result_data.non_degenerate_params() + pnames = sorted(list(pnames), key=str.lower) + + kwargs = {} + if self.cmap is not None: + kwargs['cmap'] = self.cmap + if self.canvas is not None: + kwargs['canvas'] = self.canvas + + files = [] + fdict = {'base': self.base_filename} + + for rd in self.result_data.iterate_generations(): + fdict['gen'] = int(rd.generations[0]) + filename = Path(self.report_dir, self.filename_format) + filename = Path(self.resolve_template(filename, fdict)) + kwargs['title'] = self.resolve_template(self.title_format, fdict) + sorted_values = np.sort(rd.values, order='_particle') + values_2d = np.zeros((sorted_values.shape[0], len(pnames))) + for index, pname in enumerate(pnames): + values_2d[:, index] = (sorted_values[pname] - vmin[pname]) / (vmax[pname] - vmin[pname]) + of = plot_genetic(filename, values_2d, pnames, **kwargs) + if of: + files.append(of) + + return files + + +def render_genetic(output_file, values, model_space=None, generations=None, title=None, cmap=None, + canvas=None): + """ + produce a genetic chart from a given population. + + a genetic chart is a pseudo-colour representation of the coordinates of each individual in the model space. + the axes are the particle number and the model parameter. + the colour is mapped from the relative position of a parameter value within the parameter range. + + the chart should illustrate the diversity in the population. + converged parameters will show similar colours. + by comparing charts of different generations, the effect of the optimization algorithm can be examined. + though the chart type is designed for the genetic algorithm, it may be useful for other algorithms as well. + + the function requires input in one of the following forms: + - a result (.dat) file or + numpy structured array. + the array must contain regular parameters, as well as the _particle and _gen columns. + other columns are ignored. + the function generates one chart per generation unless the generation argument is specified. + - a file (file name or file object) or numpy structured array. + the array must be wrapped in a sequence (tuple or list) for compatibility with other functions. + the array must essentially be in the same format as the corresponding member of the Population class. + the array must contain regular parameters, as well as the _particle columns. + files are loaded by numpy.genfromtxt. + - a pmsco.optimizers.population.Population object with valid data. + the generation is taken from the respective attribute and overrides the function argument. + - an open pmsco database session. the most recent job results are loaded. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param output_file: path and base name of the output file without extension. + a generation index and the file extension according to the file format are appended. + @param values: a numpy structured ndarray of a population or result list from an optimization run. + alternatively, the file path of a result file (.dat) or population file (.pop) can be given. + file can be any object that numpy.genfromtxt() can handle. + array or file must be wrapped in a sequence. + @param model_space: model space can be a pmsco.project.ModelSpace object, + any object that contains the same min and max attributes as pmsco.project.ModelSpace, + or a dictionary with to keys 'min' and 'max' that provides the corresponding ModelSpace dictionaries. + by default, the model space boundaries are derived from the input data. + if a model_space is specified, only the parameters listed in it are plotted. + @param generations: (int or sequence) generation index or list of indices. + this index is used in the output file name and for filtering input data by generation. + if the input data does not contain the generation, no filtering is applied. + by default, no filtering is applied, and one graph for each generation is produced. + @param title: (str) title of the chart. + the title is a {}-style format string, where {base} is the output file name and {gen} is the generation. + default: derived from file name. + @param cmap: (str) name of colour map supported by matplotlib. + default is 'jet'. + other good-looking options are 'PiYG', 'RdBu', 'RdYlGn', 'coolwarm'. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (list of str) paths of the generated graphics files. + empty if an error occurred. + + @raise TypeError if matplotlib is not available. + """ + + data = rp_results.ResultData() + if isinstance(generations, int): + generations = (generations,) + data.generations = generations + data.levels = {'scan': -1} + data.load_any(values) + if model_space is not None: + data.set_model_space(model_space) + + plot = GeneticPlot() + plot.canvas = canvas + plot.cmap = cmap + if title: + plot.title_format = title + else: + plot.title_format = "${gen}" + plot.report_dir = Path(output_file).parent + plot.filename_format = Path(output_file).name + "-${gen}" + plot.validate(None) + plot.result_data = data + files = plot.create_report() + + return files + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description=""" + population genetics plot for multiple-scattering optimization results + + this module operates on results or database files and produces one graphics file per generation. + + database files contain the complete information for all plot types. + data from the most recent job stored in the database is used. + + .dat results files contain all data shown in genetic plots. + .tasks.dat files lack the generation and particle identification and should not be used. + + note that the plot type is independent of the optimization mode. + it's possible to generate genetic plots from a particle swarm optimization and vice versa. + """) + parser.add_argument('results_file', + help="path to results file (.dat) or sqlite3 database file.") + parser.add_argument('output_file', + help="base name of output file. generation and extension will be appended.") + parser.add_argument('-t', '--title', default=None, + help='graph title. may contain {gen} as a placeholder for the generation number.') + + args, unknown_args = parser.parse_known_args() + + kwargs = {} + if args.title is not None: + kwargs['title'] = args.title + + render_func = render_genetic + + if db_util.is_sqlite3_file(args.results_file): + import pmsco.database.access as db_access + db = db_access.DatabaseAccess() + db.connect(args.results_file) + with db.session() as session: + render_func(args.output_file, session, **kwargs) + else: + render_func(args.output_file, args.results_file, **kwargs) + + +if __name__ == '__main__': + main() + sys.exit(0) diff --git a/pmsco/reports/population.py b/pmsco/reports/population.py new file mode 100644 index 0000000..9b367d4 --- /dev/null +++ b/pmsco/reports/population.py @@ -0,0 +1,81 @@ +""" +@package pmsco.reports.population +common code for plotting population dynamics. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import logging +import numpy as np +from sqlalchemy import func as sql_func +import pmsco.database.util as db_util +import pmsco.database.orm as db_orm +import pmsco.database.query as db_query + + +logger = logging.getLogger(__name__) + + +class GenerationTracker(object): + """ + mixin for generation-based reports + + the class keeps track of generations and identifies which generations have changed. + """ + + ## @var gens_parts + # generations and particle counts tracking + # + # this is a dictionary generation -> number of particles in generation + # which holds results from the last query of results. + + def __init__(self): + super().__init__() + self.gens_parts = {} + + def reset(self): + """ + reset tracking data + + @return: None + """ + self.gens_parts = {} + + def changed_generations(self, session, jobs): + """ + determine which generations have new results + + the function queries the database for all generations (of the specified jobs) and their particle counts. + it returns the set of generations that has increased particle counts compared to the last call. + this indicates in which generations new results are available. + + @param session: database session + @param jobs: job objects or ids to include in the query. + though the function accepts a sequence of jobs, + normally, this should be just one job id and the function should always be called with the same id. + @return: set of generation numbers that have new results since the last call. + """ + q = session.query(db_orm.Model.gen, sql_func.count(db_orm.Model.particle)) + if jobs: + q = db_query.filter_objects(q, db_orm.Job, jobs) + q = q.join(db_orm.Result) + q = q.filter(db_orm.Result.scan == -1) + q = q.group_by(db_orm.Model.gen) + q_gens = q.all() + # logger.debug(f"changed_generations: {q.statement} ({len(q_gens)} rows)") + + new_gens_parts = {g: p for g, p in q_gens} + changed = {g for g, p in q_gens if g not in self.gens_parts or self.gens_parts[g] < p} + # max_gen = max(new_gens_parts.keys()) + # max_part = max(new_gens_parts.values()) + logger.debug(f"changed generations: {changed}") + + self.gens_parts = new_gens_parts + + return changed diff --git a/pmsco/reports/results.py b/pmsco/reports/results.py new file mode 100644 index 0000000..6aeeec7 --- /dev/null +++ b/pmsco/reports/results.py @@ -0,0 +1,442 @@ +""" +@package pmsco.reports.results +query and filter result data for reports + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import logging +import numpy as np +import pmsco.database.orm as db_orm +import pmsco.database.query as db_query +import pmsco.database.util as db_util +from pmsco.project import ModelSpace + +logger = logging.getLogger(__name__) + + +def array_remove_columns(a, cols): + """ + return a copy of a structured array with some columns removed. + + @param a: numpy structured array + @param cols: sequence of column names to be removed + @return: new array + """ + dtb = [dt for dt in a.dtype if dt[0] not in cols] + b = np.empty(a.shape, dtype=dtb) + for col in b.dtype.names(): + b[col] = a[col] + return b + + +def array_range(a): + """ + determine a default range from actual values. + + @param a: (numpy.ndarray) 1-dimensional structured array of parameter values. + @return: range_min, range_max are dictionaries of the minimum and maximum values of each parameter. + """ + names = db_util.regular_params(a.dtype.names) + range_min = {} + range_max = {} + for name in names: + range_min[name] = a[name].min() + range_max[name] = a[name].max() + return range_min, range_max + + +class ResultData(object): + """ + data structure for results + + the data is stored in the values and deltas arrays. + the arrays are numpy structured arrays + and contain a flat list of results (Result table of database) + along with parameter and system variables + in the format returned by pmsco.database.query.query_model_results_array(). + + values and deltas must have the same data type (same fields). + deltas can be None if they are not available. + + the other attributes serve two purposes: + some of them define filter rules that are applied by some of the data loading methods or the apply_filters() method. + after loading, the update_collections() method updates them to describe actually loaded data. + + @attention if you want to reuse the same instance for multiple loads, + check or reset the filter attributes before each loading. + """ + + ## @var generations + # sequence of generation numbers loaded + # + # on loading, data is filtered by the generation numbers in this sequence (`in` operator, see apply_filters() method). + # by default (None), all generations are loaded. + # after loading, the sequence contains the loaded generation numbers (update_collections() method). + + ## @var particles + # sequence of particle numbers loaded + # + # on loading, data is filtered by the particle numbers in this sequence (`in` operator, see apply_filters() method). + # by default (None), all particles are loaded. + # after loading, the sequence contains the loaded particle numbers (update_collections() method). + + ## @var levels + # dictionary of level indices loaded + # + # the dictionary is organized by task level. + # allowed keys are: 'scan', 'domain', 'emit' (one or more). + # the values are sequences of index numbers. + # + # on loading, data is filtered by level numbers (`in` operator, see apply_filters() method). + # by default (None), all levels are loaded. + # after loading, the sequence contains the loaded indices (update_collections() method). + + ## @var filters + # extra database filters + # + # filter expressions that are not covered by the generations, particles and levels attributes can be entered here. + # this must be a sequence of sqlalchemy expressions that are passed to the Query.filter methods. + # these filters take effect in load_from_db() only. + + ## @var order + # sort order in database query + # + # this must be a sequence of pmsco.database.orm.Model and pmsco.database.orm.Result attributes. + # this sort order takes effect in load_from_db() only. + + ## @var model_space + # parameter range + # + # pmsco.project.ModelSpace object, only min and max are used. + # + # @attention this may be a reference to the project's model space object rather than an independent copy. + # do not modify the object directly! instead, copy the old one and modify the copy! + + ## @var values + # value data + # + + ## @var deltas + # delta data (if loaded) + # + + def __init__(self): + self.generations = None + self.params = None + self.particles = None + self.levels = None + self.filters = None + self.order = None + self.values = None + self.deltas = None + self.model_space = None + + def load_any(self, values, deltas=None): + """ + load data of any accepted type. + + the method tries to detect the type of input data and calls the specialized load method. + the data can be one of the types accepted by + load_from_population(), load_from_arrays(), load_from_text(), or load_from_db(). + + @param values: value data + @param deltas: delta data (optional) + @return: None + """ + if isinstance(values, np.ndarray): + self.load_from_arrays(values, deltas) + elif hasattr(values, 'pos') and hasattr(values, 'vel'): + self.load_from_population(values) + elif hasattr(values, 'query'): + self.load_from_db(values) + else: + self.load_from_text(values, deltas) + + def load_from_population(self, pop): + """ + load data from a population object. + + the object should be of pmsco.optimizer.population.Population type + or have the same pos, vel, results, generation, model_min and model_max attributes. + + loaded data is filtered by generations, particles, and/or levels by the apply_filters() method. + + @param pop: Population-like object + @return: None + """ + # the _rfac field of pop.pos is undefined + pos = np.copy(pop.pos) + pos['_rfac'] = np.nan + self.values = np.concatenate([pop.results, pos]) + self.deltas = np.copy(pop.vel) + self.apply_filters() + self.update_collections() + self.generations = np.array((pop.generation,)) + self.model_space = ModelSpace() + self.model_space.min = pop.model_min + self.model_space.max = pop.model_max + + def load_from_arrays(self, values, deltas=None): + """ + load data from numpy arrays. + + data type must be the same as used by pmsco.optimizer.population.Population. + + loaded data is filtered by generations, particles, and/or levels by the apply_filters() method. + + @param values: path-like or open file + @param deltas: path-like or open file + @return: None + @raise OSError if file can't be loaded. + """ + self.values = values + self.deltas = deltas + self.apply_filters() + self.update_collections() + + def load_from_text(self, values_file, deltas_file=None): + """ + load data from results file (.dat or .tasks.dat) + + loaded data is filtered by generations, particles, and/or levels by the apply_filters() method. + + @param values_file: path-like or open file + @param deltas_file: path-like or open file + @return: None + @raise OSError if file can't be loaded. + """ + self.values = np.atleast_1d(np.genfromtxt(values_file, names=True)) + if deltas_file is not None: + self.deltas = np.atleast_1d(np.genfromtxt(deltas_file, names=True)) + self.apply_filters() + self.update_collections() + + def load_from_db(self, session, jobs=-1, include_params=True): + """ + load data from the database. + + data is filtered on the SQL level by self.generations, self.particles, self.levels and self.filters. + data is ordered on the SQL level by self.order, + which defaults to generation, particle, scan, domain, emit. + + @param session: database session, from pmsco.database.access.DatabaseAccess.session(). + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + if None, results from all jobs are loaded. + if -1 (default), results from the most recent job (by datetime field) are loaded. + @param include_params: include parameter values of each model in the result (True, default). + if you're just interested in the R-factor, set this to False and parameter values are not retrieved. + @return: None + """ + if jobs == -1: + jobs = db_query.query_newest_job(session) + + filters = self.filters if self.filters is not None else [] + if self.generations is not None: + filters.append(db_orm.Model.gen.in_(self.generations)) + if self.particles is not None: + filters.append(db_orm.Model.particle.in_(self.particles)) + if self.levels is not None: + for k, v in self.levels.items(): + if k[0] == '_': + k = k[1:] + if hasattr(type(v), '__iter__'): + filters.append(getattr(db_orm.Result, k).in_(v)) + else: + filters.append(getattr(db_orm.Result, k) == v) + + if self.order is None: + self.order = [db_orm.Model.gen, db_orm.Model.particle, + db_orm.Result.scan, db_orm.Result.domain, db_orm.Result.emit] + + hook_data = {'filters': filters} + self.values, self.deltas = db_query.query_model_results_array(session, + jobs=jobs, + query_hook=self._filters_hook, + hook_data=hook_data, + include_params=include_params, + order=self.order) + + self.update_collections() + + @staticmethod + def _filters_hook(query, filters): + """ + hook function used in ResultData.load_from_db + + the function adds a sequence of conditions to a database query. + + @param query: sqlalchemy query object + @param filters: sequence of filter expressions to be passed to query.filter. + example: db_orm.Model.gen.in_([1,2,3]]) + @return: modified query + """ + for f in filters: + query = query.filter(f) + return query + + def load_from_project(self, project): + """ + define model space from project + + @note we copy the reference. the object must not be modified! + + @param project: + @return: + """ + self.model_space = project.model_space + + def reset_filters(self): + """ + reset all filter attributes to default values + + this function resets all instance attributes that modify the query statement to their default values. + in particular: generations, params, particles, levels, filters and order. + it does not affect the values and deltas arrays. + + @return: None + """ + self.generations = None + self.params = None + self.particles = None + self.levels = None + self.filters = None + self.order = None + + def apply_filters(self): + """ + apply generation, particle and level filters to loaded arrays. + + this method acts on the loaded values and deltas arrays and replaces with views of the original arrays. + + @return: None + """ + filters = {} + if self.generations is not None: + filters['_gen'] = list(self.generations) + if self.particles is not None: + filters['_particle'] = list(self.particles) + if self.levels is not None: + for k, v in self.levels.items(): + if k[0] != '_': + k = '_' + k + if k in self.values.dtype.names: + filters[k] = list(v) + + for k, v in filters.items(): + idx = np.where(np.isin(self.values[k], v)) + self.values = self.values[idx] + if self.deltas is not None: + idx = np.where(np.isin(self.deltas[k], v)) + self.deltas = self.deltas[idx] + + def update_collections(self): + """ + update attributes that depend on the values and deltas arrays. + + namely: params, generations, particles, model_space + + this method is called by the load methods after loading data. + + @return: None + """ + self.params = db_util.regular_params(self.values.dtype.names) + + try: + self.generations = np.unique(self.values['_gen']) + except (KeyError, ValueError): + pass + + try: + self.particles = np.unique(self.values['_particle']) + except (KeyError, ValueError): + pass + + self.model_space = ModelSpace() + self.model_space.min, self.model_space.max = array_range(self.values) + + def debug_log(self): + logger.debug(f"params = {self.params}") + logger.debug(f"generations = {self.generations}") + logger.debug(f"particles = {self.particles}") + logger.debug(f"levels = {self.levels}") + logger.debug(f"model_space.min = {self.model_space.min}") + logger.debug(f"model_space.max = {self.model_space.max}") + logger.debug(f"values.shape = {self.values.shape}") + logger.debug(f"values.dtype = {self.values.dtype}") + + def set_model_space(self, model_space): + """ + set the model space (parameter value range) + + @note the model space is updated by the load methods and update_collections(). + + @param model_space: model space can be a pmsco.project.ModelSpace object, + any object that contains the same min and max attributes as pmsco.project.ModelSpace, + or a dictionary with two keys 'min' and 'max' that provides the corresponding ModelSpace dictionaries. + + @return: None + """ + if isinstance(model_space, ModelSpace): + self.model_space = model_space + else: + self.model_space = ModelSpace() + try: + self.model_space.min, self.model_space.max = model_space.min, model_space.max + except AttributeError: + self.model_space.min, self.model_space.max = model_space['min'], model_space['max'] + + def non_degenerate_params(self): + """ + get the names of non-degenerate parameters + + the result set contains the names of all parameters + where the upper range limit of the model space is strictly greater than the lower. + + the result is based on the params and model_space attributes. + + @return: set of strings (not ordered). + """ + pn = set(self.params) + rmn = set(self.model_space.min.keys()) + rmx = set(self.model_space.max.keys()) + names = pn.intersection(rmn).intersection(rmx) + names = {name for name in names if self.model_space.max[name] > self.model_space.min[name]} + return names + + def iterate_generations(self): + """ + iterate over generations. + + this is a generator function. + it yields a @ref ResultData object for each generation, + where the data is filtered by generation. + + the @ref ResultData object is a shallow copy of `self`. + the attributes are references to the original objects. + the `values` and `deltas` are views of the original arrays + showing just the elements belonging to one generation. + `generations` contains just one element: the generation number. + + @return: one @ref ResultData for each generation + """ + for gen in self.generations: + rd = ResultData() + rd.generations = (gen,) + rd.params = self.params + rd.particles = self.particles + rd.levels = self.levels + idx = np.where(self.values['_gen'] == gen) + rd.values = self.values[idx] + if self.deltas is not None: + idx = np.where(self.deltas['_gen'] == gen) + rd.deltas = self.deltas[idx] + rd.model_space = self.model_space + yield rd diff --git a/pmsco/reports/rfactor.py b/pmsco/reports/rfactor.py new file mode 100755 index 0000000..2905798 --- /dev/null +++ b/pmsco/reports/rfactor.py @@ -0,0 +1,772 @@ +#!/usr/bin/env python +""" +@package pmsco.reports.rfactor +graphics rendering module to show R-factor plots. + +the module can be used in several different ways: + +1. via the command line on a pmsco database or .dat results file. + this is the most simple but least flexible way. +2. via python functions on given population arrays or database queries. + this is the most flexible way but requires understanding of the required data formats. +3. as a _report_ function during an optimization. + reporting is configurable in the run file. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import argparse +import itertools +import logging +import numpy as np +from pathlib import Path +from scipy.interpolate import griddata +import sys + +if __name__ == "__main__": + pmsco_root = Path(__file__).resolve().parent.parent.parent + if str(pmsco_root) not in sys.path: + sys.path.insert(0, str(pmsco_root)) + +import pmsco.reports.results as rp_results +import pmsco.database.util as db_util +import pmsco.database.query as db_query +from pmsco.reports.base import ProjectReport + +logger = logging.getLogger(__name__) + +try: + from matplotlib.figure import Figure + from matplotlib.ticker import MaxNLocator + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas + # from matplotlib.backends.backend_pdf import FigureCanvasPdf + # from matplotlib.backends.backend_svg import FigureCanvasSVG +except ImportError: + Figure = None + FigureCanvas = None + MaxNLocator = None + logger.warning("error importing matplotlib. graphics rendering disabled.") + + +def plot_rfactor_1d(filename, data, params, title=None, canvas=None): + """ + one-dimensional R-factor versus parameter plot + + this is a low-level function containing just the plotting commands from numpy arrays. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param filename: path and base name of the output file without extension. + a generation index and the file extension according to the file format are appended. + @param data: structured ndarray containing parameter and R-factor values. + the '_rfac' column is required and must contain R-factor values. + the column specified by `param_name` is required and must contain the parameter values. + other columns may be present and are ignored. + @param params: dictionary of parameter and range. + the key corresponds to the parameter column of the data array. + the value is a list [minimum, maximum] that defines the axis range. + if the value is None, the axes are auto-scaled. + the dictionary should contain one parameter exactly. + only the first one is plotted. + @param title: (str) title of the chart. + default: derived from parameter names. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (str) path and name of the generated graphics file. + None if no file was generated due to an error. + """ + pname = list(params.keys())[0] + if canvas is None: + canvas = FigureCanvas + if canvas is None or Figure is None: + return None + if title is None: + title = f'{pname} R-factor map' + + fig = Figure() + canvas(fig) + ax = fig.add_subplot(111) + + ax.scatter(data[pname], data['_rfac'], c='b', marker='o', s=4.0) + # ax.grid(True) + rng = params[pname] + if rng is not None: + ax.set_xlim(rng) + ax.set_ylim([0., 1.]) + ax.set_xlabel(pname) + ax.set_ylabel('R-factor') + ax.set_title(title) + + out_filename = "{base}.{ext}".format(base=filename, ext=canvas.get_default_filetype()) + try: + fig.savefig(out_filename) + except OSError: + logger.exception(f"exception while saving figure {out_filename}") + out_filename = None + + return out_filename + + +def plot_rfactor_2d(filename, data, params, title=None, cmap=None, canvas=None): + """ + plot a two-dimensional R-factor scatter plot. + + the plot consists of a pseudo-color scatter plot of R-factors projected on two parameter dimensions. + + this is a low-level function containing just the plotting commands from numpy arrays. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param filename: path and base name of the output file without extension. + a generation index and the file extension according to the file format are appended. + @param data: structured ndarray containing parameter and R-factor values. + @param params: dictionary of two parameters to be plotted. + the keys correspond to columns of the data array. + the values are lists [minimum, maximum] that define the axis range. + if the values are None, the axes are auto-scaled. + the dictionary must contain at least two parameters and should not contain more than two. + only the first two are plotted. + @param title: (str) title of the chart. + default: derived from parameter names. + @param cmap: (str) name of colour map supported by matplotlib. + default is 'plasma'. + other good-looking options are 'viridis', 'plasma', 'inferno', 'magma', 'cividis'. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (str) path and name of the generated graphics file. + None if no file was generated due to an error. + """ + pnames = list(params.keys()) + if canvas is None: + canvas = FigureCanvas + if canvas is None or Figure is None: + return None + if cmap is None: + cmap = 'plasma' + if title is None: + title = f'{pnames[0]} - {pnames[1]} R-factor map' + + fig = Figure() + canvas(fig) + ax = fig.add_subplot(111) + + s = ax.scatter(data[pnames[0]], data[pnames[1]], s=5, c=data['_rfac'], cmap=cmap, vmin=0, vmax=1) + cb = ax.figure.colorbar(s, ax=ax) + cb.ax.set_ylabel("R-factor", rotation=-90, va="bottom") + rng = params[pnames[0]] + if rng is not None: + ax.set_xlim(rng) + rng = params[pnames[1]] + if rng is not None: + ax.set_ylim(rng) + ax.set_xlabel(pnames[0]) + ax.set_ylabel(pnames[1]) + ax.set_title(title) + + out_filename = "{base}.{ext}".format(base=filename, ext=canvas.get_default_filetype()) + try: + fig.savefig(out_filename) + except OSError: + logger.exception(f"exception while saving figure {out_filename}") + out_filename = None + + return out_filename + + +def triplet_to_grid(x, y, z): + """ + reshape and interpolate three flat arrays to a two-dimensional grid array. + + input is two coordinate arrays and one value array. + the coordinates arrays must contain coordinates of a rectangular grid with (per dimension) equidistant steps. + some defects (missing values, imprecise coordinate values) are accepted. + + the function currently has a very basic implementation which relies on the following strong assumptions: + - the x and y arrays contain coordinates of a two-dimensional, rectangular grid + - jitter up to one third of step size is accepted. + - missing data points are interpolated (nearest neighbour interpolation). + a warning message is sent to the logger in such a case. + + @param x: 1D array of x-coordinates. x, y and z must have the same shape. + @param y: 1D array of y-coordinates. x, y and z must have the same shape. + @param z: 1D array of values. x, y and z must have the same shape. + @return: three 2D arrays of same shape containing: x-coordinates, y-coordinates, values. + """ + x_sort = np.sort(x) + x_diff = np.diff(x_sort) + x_big_step = np.amax(x_diff) + x_steps = x_diff[np.where(x_diff > x_big_step / 3)] + x_step = np.amin(x_steps) + x_min = x_sort[0] + x_max = x_sort[-1] + nx = round((x_max - x_min) / x_step + 1) + + y_sort = np.sort(y) + y_diff = np.diff(y_sort) + y_big_step = np.amax(y_diff) + y_steps = y_diff[np.where(y_diff > y_big_step / 3)] + y_step = np.amin(y_steps) + y_min = y_sort[0] + y_max = y_sort[-1] + ny = round((y_max - y_min) / y_step + 1) + + if nx * ny != len(z): + logger.warning(f"grid plot: input array size ({len(z)}) does not match grid size ({nx}*{ny}). " + f"some array elements may be interpolated!") + + grid_x, grid_y = np.mgrid[x_min:x_max:nx * 1j, y_min:y_max:ny * 1j] + grid_z = griddata((x, y), z, (grid_x, grid_y), method='nearest') + + return grid_x, grid_y, grid_z + + +def plot_rfactor_grid(filename, data, params, title=None, cmap=None, canvas=None): + """ + plot a two-dimensional R-factor map. + + the plot consists of a pseudo-color R-factor map on a Cartesian grid. + + this is a low-level function containing just the plotting commands from numpy arrays. + the input data can be provided in a matrix or structured array. + in the former case, the coordinate scales are defined by the params argument. + in the latter case, each row of the array contains coordinates and value, + and the coordinate columns to be used are defined by the params argument. + the array is reshaped to a matrix internally. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param filename: path and base name of the output file without extension. + a generation index and the file extension according to the file format are appended. + @param data: R-factor data. + this can be a two-dimensional numpy array of R-factor values + or a one-dimensional numpy structured array containing parameter R-factor values. + in the first case, for an array of shape (M, N), + the M (N) index corresponds to params[0] (params[1]), respectively. + M/params[0] is plotted on the horizontal axis. + in the latter case, the `_rfac` column must be a flat version of a rectangular array + with axes `params[0]` (horizontal) and `params[1]` (vertical). + @param params: dictionary of two parameters to be plotted. + the keys contain the parameter names, + the values are lists [minimum, maximum] that define the axis range. + if the values are None, the axes are auto-scaled. + the dictionary must contain at least two parameters and should not contain more than two. + only the first two are plotted. + params.keys()[0] runs along the horizontal axis. + @param title: (str) title of the chart. + default: derived from parameter names. + @param cmap: (str) name of colour map supported by matplotlib. + default is 'plasma'. + other good-looking options are 'viridis', 'plasma', 'inferno', 'magma', 'cividis'. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (str) path and name of the generated graphics file. + None if no file was generated due to an error. + """ + pnames = list(params.keys()) + if canvas is None: + canvas = FigureCanvas + if canvas is None or Figure is None: + return None + if cmap is None: + cmap = 'plasma' + if title is None: + title = f'{pnames[0]} - {pnames[1]} R-factor map' + + try: + scales = [params[pnames[0]][0], params[pnames[0]][1], params[pnames[1]][0], params[pnames[1]][1]] + extent = tuple(scales) + except IndexError: + scales = None + extent = None + + if len(data.shape) == 1: + try: + grid_x, grid_y, data = triplet_to_grid(data[pnames[0]], data[pnames[1]], data['_rfac']) + except ValueError: + logger.exception("error reshaping data array") + return None + extent = (grid_x.min(), grid_x.max(), grid_y.min(), grid_y.max()) + scales = None + else: + scales = None + + nx, ny = data.shape + if extent is None: + extent = (0, nx - 1, 0, ny - 1) + xstep = (extent[1] - extent[0]) / (nx - 1) + ystep = (extent[3] - extent[2]) / (ny - 1) + extent = (extent[0] - xstep / 2, extent[1] + xstep / 2, extent[2] - ystep / 2, extent[3] + ystep / 2) + + fig = Figure() + canvas(fig) + ax = fig.add_subplot(111) + im = ax.imshow(data.T, aspect='auto', cmap=cmap, vmin=0.0, vmax=1.0, extent=extent, origin='lower') + + if scales is not None: + ax.set_xlim(scales[0:2]) + if scales is not None: + ax.set_ylim(scales[2:4]) + ax.set_xlabel(pnames[0]) + ax.set_ylabel(pnames[1]) + ax.set_title(title) + + cb = ax.figure.colorbar(im, ax=ax) + cb.ax.set_ylabel("R-factor", rotation=-90, va="bottom") + + out_filename = "{base}.{ext}".format(base=filename, ext=canvas.get_default_filetype()) + try: + fig.savefig(out_filename) + except OSError: + logger.exception(f"exception while saving figure {out_filename}") + out_filename = None + + return out_filename + + +class Rfactor2DPlot(ProjectReport): + """ + produce two-dimensional pseudo-colour R-factor scatter plots + + this class collects and validates all parameters and data for generating a 2D R-factor plot. + + the plots consist of a pseudo-colour scatter or image plot of R-factors versus two parameters. + scatter plots can contain an arbitrary number of results from any optimization algorithm. + image plots require results from a rectangular grid and should be used in 2D grid optimization mode only. + + see the render_rfactor_2d function code for an example of how to use this class. + """ + + ## @var params + # parameter that should be plotted + # + # this should be a list of pairs (sequence or tuple of two strings) of parameter names. + # for each pair, a plot is generated. + # by default, all non-degenerate parameters are plotted in all combinations. + + ## @var result_data + # R-factor data for scatter plot + # + # pmsco.reports.results.ResultData object holding the data or filter criteria + # for the scatter plot in the background. + # the scatter plot shows a map of the model space in terms of R-factor. + + ## @var plot_type + # type of the generated plot (scatter or image) + # + # - 'auto': (default) choose automatically based on project mode and number of parameters. + # 'auto' will choose 'image' if the data fit on a rectangular grid and 'scatter' otherwise. + # - 'scatter': most general plot, suitable in any situation. + # - 'image': works in two-dimensional grid mode only. + + def __init__(self): + super().__init__() + self._modes = ['genetic', 'grid', 'swarm'] + self.result_data = rp_results.ResultData() + self.filename_format = "${base}-rfactor2d-${param0}-${param1}" + self.title_format = "${param0} ${param1} R-factor" + self.cmap = None + self.params = [] + self.plot_type = 'auto' + + def select_data(self, jobs=-1, calcs=None): + """ + query data from the database + + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + if None, results from all jobs are loaded. + if -1 (default), results from the most recent job (by datetime field) are loaded. + + @param calcs: the calcs argument is ignored. + + @return: None + """ + + with self.get_session() as session: + if jobs == -1: + jobs = db_query.query_newest_job(session) + + self.result_data.reset_filters() + self.result_data.levels = {'scan': -1} + self.result_data.load_from_db(session, jobs=jobs) + + if self._project: + self.result_data.set_model_space(self._project.model_space) + + def create_report(self): + """ + generate the plots based on the stored attributes. + + this method essentially loops over parameter combinations, + and compiles the input for plot_rfactor_1d. + + combinations of parameter names are either set by the user in self.params + or constructed from all non-degenerate (not constant) parameters. + + @return: list of created files + """ + + vmin = self.result_data.model_space.min + vmax = self.result_data.model_space.max + nd_params = self.result_data.non_degenerate_params() + + if self.params: + ppairs = [p for p in self.params if len(p) == 2 and p[0] in nd_params and p[1] in nd_params] + else: + pnames = sorted(list(nd_params), key=str.lower) + ppairs = [p for p in itertools.combinations(pnames, 2)] + + plot_type = self.plot_type + if plot_type == 'auto': + if self._project and self._project.mode == 'grid' and len(nd_params) == 2: + plot_type = 'image' + else: + plot_type = 'scatter' + + kwargs = {} + if self.cmap is not None: + kwargs['cmap'] = self.cmap + if self.canvas is not None: + kwargs['canvas'] = self.canvas + + files = [] + fdict = {'base': self.base_filename} + + for ppair in ppairs: + fdict['param0'] = ppair[0] + fdict['param1'] = ppair[1] + filename = Path(self.report_dir, self.filename_format) + filename = Path(self.resolve_template(filename, fdict)) + kwargs['title'] = self.resolve_template(self.title_format, fdict) + params = {ppair[0]: [vmin[ppair[0]], vmax[ppair[0]]], + ppair[1]: [vmin[ppair[1]], vmax[ppair[1]]]} + if plot_type == 'image': + of = plot_rfactor_grid(filename, self.result_data.values, params, **kwargs) + else: + of = plot_rfactor_2d(filename, self.result_data.values, params, **kwargs) + if of: + files.append(of) + + return files + + +class Rfactor1DPlot(ProjectReport): + """ + produce one-dimensional R-factor plots + + this class collects and validates all parameters and data for generating 1D R-factor plots. + + the plots consist of scatter plots of R-factor versus one model parameter. + this can contain an arbitrary number of results. + + see the render_rfactor_1d function code for an example of how to use this class. + """ + + ## @var params + # parameter that should be plotted + # + # this should be a list of parameter names. + # for each parameter, a plot is generated. + # by default, all non-degenerate parameters are plotted. + + ## @var result_data + # R-factor data for scatter plot + # + # pmsco.reports.results.ResultData object holding the data or filter criteria. + + def __init__(self): + super().__init__() + self._modes = ['bayesian', 'genetic', 'grid', 'swarm'] + self.result_data = rp_results.ResultData() + self.filename_format = "${base}-rfactor1d-${param}" + self.title_format = "${param} R-factor" + self.cmap = None + self.params = [] + + def select_data(self, jobs=-1, calcs=None): + """ + query data from the database + + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + if None, results from all jobs are loaded. + if -1 (default), results from the most recent job (by datetime field) are loaded. + + @param calcs: the calcs argument is ignored. + + @return: None + """ + + with self.get_session() as session: + if jobs == -1: + jobs = db_query.query_newest_job(session) + + self.result_data.reset_filters() + self.result_data.levels = {'scan': -1} + self.result_data.load_from_db(session, jobs=jobs) + + if self._project: + self.result_data.set_model_space(self._project.model_space) + + def create_report(self): + """ + generate the plots based on the stored attributes. + + this method essentially loops over parameter combinations, + and compiles the input for plot_rfactor_1d. + + the list of parameter names is either set by the user in self.params + or defaults to all non-degenerate (not constant) parameters. + + @return: list of created files + """ + + vmin = self.result_data.model_space.min + vmax = self.result_data.model_space.max + nd_params = self.result_data.non_degenerate_params() + + if self.params: + pnames = self.params + else: + pnames = sorted(list(nd_params), key=str.lower) + + kwargs = {} + if self.cmap is not None: + kwargs['cmap'] = self.cmap + if self.canvas is not None: + kwargs['canvas'] = self.canvas + + files = [] + fdict = {'base': self.base_filename} + + for param in pnames: + fdict['param'] = param + filename = Path(self.report_dir, self.filename_format) + filename = Path(self.resolve_template(filename, fdict)) + kwargs['title'] = self.resolve_template(self.title_format, fdict) + params = {param: [vmin[param], vmax[param]]} + of = plot_rfactor_1d(filename, self.result_data.values, params, **kwargs) + if of: + files.append(of) + + return files + + +def render_rfactor_2d(output_file, values, model_space=None, title=None, plot_type=None, cmap=None, canvas=None): + """ + render two-dimensional R-factor scatter plots from database or result file. + + the function generates one plot for each combination of non-degenerate parameters. + + the function accepts input in one of the following forms: + - an open pmsco database session. the most recent job results are loaded. + - a result (.dat) file or numpy structured array. + the array must contain columns of regular model parameters and the _rfac column. + - a pmsco.optimizers.population.Population object with valid data. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this is a high-level function with minimal input requirements. + some assumptions are made about which data to plot. + more fine grained control is provided by the Rfactor2DPlot class or the plot_rfactor_2d function. + the function code shows how to use the Rfactor2DPlot class. + + @param output_file: path and base name of the output file without extension. + parameter names and the file extension according to the file format are appended. + @param values: result data to plot. + the input can be specified in several ways - see the main description for details. + @param model_space: model space can be a pmsco.project.ModelSpace object, + any object that contains the same min and max attributes as pmsco.project.ModelSpace, + or a dictionary with to keys 'min' and 'max' that provides the corresponding ModelSpace dictionaries. + by default, the model space boundaries are derived from the input data. + if a model_space is specified, only the parameters listed in it are plotted. + @param title: (str) title of the chart. + the title is a {}-style format string, where {base} is the output file name and {gen} is the generation. + default: derived from file name. + @param plot_type: (str) override plot type: 'scatter' or 'image'. + by default, @ref Rfactor2DPlot chooses a scatter plot, which works in all situations. + for data on a rectangular grid, an image type may be suitable and can be forced using this option. + @param cmap: (str) name of colour map supported by matplotlib. + default is 'plasma'. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (list of str) paths of the generated graphics files. + empty if an error occurred. + + @raise TypeError if matplotlib is not available. + """ + data = rp_results.ResultData() + data.levels = {'scan': -1} + data.load_any(values) + if model_space is not None: + data.set_model_space(model_space) + + plot = Rfactor2DPlot() + if plot_type: + plot.plot_type = plot_type + plot.canvas = canvas + plot.cmap = cmap + if title: + plot.title_format = title + else: + plot.title_format = "R-factor" + plot.report_dir = Path(output_file).parent + plot.filename_format = Path(output_file).name + "-${param0}-${param1}" + plot.validate(None) + plot.result_data = data + files = plot.create_report() + + return files + + +def render_rfactor_1d(output_file, values, model_space=None, title=None, cmap=None, canvas=None): + """ + render one-dimensional R-factor scatter plots from database or result file. + + the function generates one plot for each non-degenerate parameter. + + the function accepts input in one of the following forms: + - an open pmsco database session. the most recent job results are loaded. + - a result (.dat) file or numpy structured array. + the array must contain columns of regular model parameters and the _rfac column. + - a pmsco.optimizers.population.Population object with valid data. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this is a high-level function with minimal input requirements. + some assumptions are made about which data to plot. + more fine grained control is provided by the Rfactor1DPlot class or the plot_rfactor_1d function. + the function code shows how to use the Rfactor1DPlot class. + + @param output_file: path and base name of the output file without extension. + parameter name and the file extension according to the file format are appended. + @param values: result data to plot. + the input can be specified in several ways - see the main description for details. + @param model_space: model space can be a pmsco.project.ModelSpace object, + any object that contains the same min and max attributes as pmsco.project.ModelSpace, + or a dictionary with to keys 'min' and 'max' that provides the corresponding ModelSpace dictionaries. + by default, the model space boundaries are derived from the input data. + if a model_space is specified, only the parameters listed in it are plotted. + @param title: (str) title of the chart. + the title is a {}-style format string, where {base} is the output file name and {gen} is the generation. + default: derived from file name. + @param cmap: (str) name of colour map supported by matplotlib. + default is 'plasma'. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (list of str) paths of the generated graphics files. + empty if an error occurred. + + @raise TypeError if matplotlib is not available. + """ + data = rp_results.ResultData() + data.levels = {'scan': -1} + data.load_any(values) + if model_space is not None: + data.set_model_space(model_space) + + plot = Rfactor1DPlot() + plot.canvas = canvas + plot.cmap = cmap + if title: + plot.title_format = title + else: + plot.title_format = "R-factor" + plot.report_dir = Path(output_file).parent + plot.filename_format = Path(output_file).name + "-${param}" + plot.validate(None) + plot.result_data = data + files = plot.create_report() + + return files + + +def main(args=None): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description=""" + R-factor result plots for multiple-scattering optimization results + + this module operates on result or database files and produces graphics files. + + database files contain the complete information for all plot types. + data from the most recent job stored in the database is used. + + the plot type is selected by the mode argument. + """) + parser.add_argument('results_file', + help="path to results file (.dat) or sqlite3 database file.") + parser.add_argument('output_file', + help="base name of output file. generation and extension will be appended.") + parser.add_argument('-m', '--mode', choices=['1d', '2d', 'grid'], default='1d', + help="plot type. " + "1d: R-factor versus one parameter; " + "2d: pseudo-color scatter plot versus two parameters; " + "grid: pseudo-color image plot (2d grid data only)") + parser.add_argument('-t', '--title', default=None, + help='graph title. may contain {gen} as a placeholder for the generation number.') + + args, unknown_args = parser.parse_known_args(args=args) + + kwargs = {} + if args.title is not None: + kwargs['title'] = args.title + + if args.mode == '1d': + render_func = render_rfactor_1d + elif args.mode == '2d': + render_func = render_rfactor_2d + elif args.mode == 'grid': + render_func = render_rfactor_2d + kwargs['plot_type'] = 'image' + else: + raise ValueError(f"unknown graph mode {args.mode}") + + if db_util.is_sqlite3_file(args.results_file): + import pmsco.database.access as db_access + db = db_access.DatabaseAccess() + db.connect(args.results_file) + with db.session() as session: + render_func(args.output_file, session, **kwargs) + else: + render_func(args.output_file, args.results_file, **kwargs) + + +if __name__ == '__main__': + main() + sys.exit(0) diff --git a/pmsco/reports/swarm.py b/pmsco/reports/swarm.py new file mode 100755 index 0000000..17a23d8 --- /dev/null +++ b/pmsco/reports/swarm.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python +""" +@package pmsco.reports.swarm +graphics rendering module for swarm dynamics. + +the module can be used in several different ways: + +1. via the command line on a pmsco database or .dat results file. + this is the most simple but least flexible way. +2. via python functions on given population arrays or database queries. + this is the most flexible way but requires understanding of the required data formats. +3. as a listener on calculation events. (to be implemented) + this will be configurable in the run file. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import argparse +import copy +import itertools +import logging +import numpy as np +from pathlib import Path +import sys + +if __name__ == "__main__": + pmsco_root = Path(__file__).resolve().parent.parent.parent + if str(pmsco_root) not in sys.path: + sys.path.insert(0, str(pmsco_root)) + +import pmsco.reports.results as rp_results +import pmsco.database.util as db_util +import pmsco.database.query as db_query +from pmsco.reports.base import ProjectReport +from pmsco.reports.population import GenerationTracker + +logger = logging.getLogger(__name__) + +try: + from matplotlib.figure import Figure + from matplotlib.ticker import MaxNLocator + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas + # from matplotlib.backends.backend_pdf import FigureCanvasPdf + # from matplotlib.backends.backend_svg import FigureCanvasSVG +except ImportError: + Figure = None + FigureCanvas = None + MaxNLocator = None + logger.warning("error importing matplotlib. graphics rendering disabled.") + + +def plot_swarm(filename, pos, vel, rfac, params, title=None, cmap=None, canvas=None): + """ + plot a two-dimensional particle swarm population. + + the plot consists of three elements: + - a pseudo-color scatter plot of R-factors in the background, + - a scatter plot of particle positions, optionally colorized by R-factor. + - a quiver plot indicating the velocities of the particles. + + @note the color of the particles is mapped according to the _rfac column of the positions array, + while the background plot uses R-factors from the _rfac column of the rfac array. + + this is a low-level function containing just the plotting commands from numpy arrays. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @param filename: path and base name of the output file without extension. + a generation index and the file extension according to the file format are appended. + @param pos: structured ndarray containing the positions of the particles. + if the array contains an _rfac column, the dot is colorized. + @param vel: structured ndarray containing the velocities of the particles. + @param rfac: structured ndarray containing positions and R-factor values. + this array is independent of pos and vel. + it can also be set to None if results should be suppressed. + @param params: dictionary of two parameters to be plotted. + the keys correspond to columns of the pos, vel and rfac arrays. + the values are lists [minimum, maximum] that define the axis range. + the dictionary must contain at least two parameters and should not contain more than two. + only the first two are plotted. + @param title: (str) title of the chart. + default: derived from parameter names. + @param cmap: (str) name of colour map supported by matplotlib. + default is 'plasma'. + other good-looking options are 'viridis', 'plasma', 'inferno', 'magma', 'cividis'. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (str) path and name of the generated graphics file. + None if no file was generated due to an error. + """ + pnames = list(params.keys()) + if canvas is None: + canvas = FigureCanvas + if canvas is None or Figure is None: + return None + if cmap is None: + cmap = 'plasma' + if title is None: + title = f'{pnames[0]} - {pnames[1]} swarm map' + + fig = Figure() + canvas(fig) + ax = fig.add_subplot(111) + + s = None + if rfac is not None: + try: + s = ax.scatter(rfac[pnames[0]], rfac[pnames[1]], s=5, c=rfac['_rfac'], cmap=cmap, vmin=0, vmax=1) + except ValueError: + # _rfac column missing + pass + + ax.plot(pos[pnames[0]], pos[pnames[1]], 'co') + if '_rfac' in pos.dtype.names: + s = ax.scatter(pos[pnames[0]], pos[pnames[1]], s=5, c=pos['_rfac'], cmap=cmap, vmin=0, vmax=1) + if vel is not None: + ax.quiver(pos[pnames[0]], pos[pnames[1]], vel[pnames[0]], vel[pnames[1]], angles='xy', units='xy', + scale_units='xy', scale=1, color='c') + if s is not None: + cb = ax.figure.colorbar(s, ax=ax) + cb.ax.set_ylabel("R-factor", rotation=-90, va="bottom") + ax.set_xlim(params[pnames[0]]) + ax.set_ylim(params[pnames[1]]) + ax.set_xlabel(pnames[0]) + ax.set_ylabel(pnames[1]) + ax.set_title(title) + + out_filename = "{base}.{ext}".format(base=filename, ext=canvas.get_default_filetype()) + try: + fig.savefig(out_filename) + except OSError: + logger.exception(f"exception while saving figure {out_filename}") + out_filename = None + + return out_filename + + +class SwarmPlot(ProjectReport, GenerationTracker): + """ + produce two-dimensional particle swarm population maps + + this class collects and validates all parameters and data for generating a series of swarm plots. + it iterates over generations and parameter pairs and calls plot_swarm() for each combination. + + the plots consist of two sub-plots on the same axes: + 1. a scatter plot in the background maps the R-factor across the model space. + this can contain an arbitrary number of results from as many generations as requested. + 2. a quiver plot shows the positions and velocities of the particles in one generation. + + the two plots each have their independent data source. + it is up to the owner to select corresponding and meaningful data. + + usage: + 1. assign public attributes as necessary, leave defaults at None. + 2. load data into result_data and swarm_data arrays by calling the appropriate load methods. + 3. call validate() + 4. call plot() + """ + + ## @var params + # parameter that should be plotted + # + # this should be a list of pairs (sequence or tuple of two strings) of parameter names. + # for each pair, a plot is generated. + # by default, all non-degenerate parameters are plotted in all combinations. + + ## @var result_data + # R-factor data for scatter plot (in the background) + # + # pmsco.reports.results.ResultData object holding the data or filter criteria + # for the scatter plot in the background. + # the scatter plot shows a map of the model space in terms of R-factor. + + ## @var swarm_data + # particle vectors for swarm (quiver) plot + # + # pmsco.reports.results.ResultData object holding the data or filter criteria + # for the quiver plot in the foreground. + # the quiver plot shows the positions and velocities of the particles. + + def __init__(self): + super().__init__() + self._modes = ['swarm'] + self.result_data = rp_results.ResultData() + self.swarm_data = rp_results.ResultData() + self.filename_format = "${base}-swarm-${param0}-${param1}-${gen}" + self.title_format = "${param0} ${param1} gen ${gen}" + self.cmap = None + self.params = [] + + def select_data(self, jobs=-1, calcs=None): + """ + query data from the database + + this method must be implemented by the sub-class. + + @param jobs: filter by job. + the argument can be a singleton or sequence of orm.Job objects or numeric id. + if None, results from all jobs are loaded. + if -1 (default), results from the most recent job (by datetime field) are loaded. + + @param calcs: the calcs argument is ignored. + + @return: None + """ + + with self.get_session() as session: + if jobs == -1: + jobs = db_query.query_newest_job(session) + changed_gens = self.changed_generations(session, jobs) + + self.result_data.reset_filters() + self.result_data.levels = {'scan': -1} + self.result_data.load_from_db(session, jobs=jobs) + + self.swarm_data = copy.copy(self.result_data) + self.swarm_data.reset_filters() + self.swarm_data.generations = changed_gens + self.swarm_data.apply_filters() + self.swarm_data.update_collections() + + if self._project: + self.result_data.set_model_space(self._project.model_space) + self.swarm_data.set_model_space(self._project.model_space) + + def create_report(self): + """ + generate the plots based on the stored attributes. + + this method essentially loops over generations and parameter combinations, + and compiles the input for plot_swarm. + + combinations of parameter names are either set by the user in self.params + or constructed from all non-degenerate (not constant) parameters. + + @return: list of created files + """ + + # check that result data is compatible with swarm plots + if self.swarm_data.params is None or len(self.swarm_data.params) < 2: + logger.warning("result data must contain at least 2 parameters") + return [] + if self.swarm_data.generations is None or len(self.swarm_data.generations) < 1: + logger.warning("result data must specify at least 1 generation") + return [] + if self.swarm_data.particles is None or len(self.swarm_data.particles) < 5: + logger.warning("result data must specify at least 2 particles") + return [] + + vmin = self.swarm_data.model_space.min + vmax = self.swarm_data.model_space.max + nd_params = self.swarm_data.non_degenerate_params() + + if self.params: + ppairs = [p for p in self.params if len(p) == 2 and p[0] in nd_params and p[1] in nd_params] + else: + pnames = sorted(list(nd_params), key=str.lower) + ppairs = [p for p in itertools.combinations(pnames, 2)] + + kwargs = {} + if self.cmap is not None: + kwargs['cmap'] = self.cmap + if self.canvas is not None: + kwargs['canvas'] = self.canvas + + files = [] + fdict = {'base': self.base_filename} + + for rd in self.swarm_data.iterate_generations(): + fdict['gen'] = int(rd.generations[0]) + for ppair in ppairs: + fdict['param0'] = ppair[0] + fdict['param1'] = ppair[1] + filename = Path(self.report_dir, self.filename_format) + filename = Path(self.resolve_template(filename, fdict)) + kwargs['title'] = self.resolve_template(self.title_format, fdict) + params = {ppair[0]: [vmin[ppair[0]], vmax[ppair[0]]], + ppair[1]: [vmin[ppair[1]], vmax[ppair[1]]]} + of = plot_swarm(filename, rd.values, rd.deltas, self.result_data.values, params, **kwargs) + if of: + files.append(of) + + return files + + +def render_swarm(output_file, values, deltas=None, model_space=None, generations=None, title=None, + cmap=None, canvas=None): + """ + render a two-dimensional particle swarm population. + + this function generates a schematic rendering of a particle swarm in two dimensions. + particles are represented by their position and velocity, indicated by an arrow. + the model space is projected on the first two (or selected two) variable parameters. + in the background, a scatter plot of results (dots with pseudocolor representing the R-factor) can be plotted. + the chart type is designed for the particle swarm optimization algorithm. + + the function requires input in one of the following forms: + - an open pmsco database session. the most recent job results are loaded. + - a result (.dat) file or numpy structured array. + the array must contain regular parameters, as well as the _particle and _gen columns, + and optionally, the _rfac column. + the function generates one chart per generation unless the generation argument is specified. + velocities are not plotted. + - position (.pos) and velocity (.vel) files or the respective numpy structured arrays. + the arrays must contain regular parameters, as well as the `_particle` column. + the result file must also contain an `_rfac` column. + files are loaded by numpy.genfromtxt. + - a pmsco.optimizers.population.Population object with valid data. + the generation is taken from the respective attribute and overrides the function argument. + + the graphics file format can be changed by providing a specific canvas. default is PNG. + + this function requires the matplotlib module. + if it is not available, the function raises an error. + + @note velocities are not stored in result files and can't be plotted if result files are used as input. + to plot velocities, provide the database, .pos/.vel files or Population object as input. + + @param output_file: path and base name of the output file without extension. + a generation index and the file extension according to the file format are appended. + @param values: position data to plot. + the input can be specified in several ways - see the main description for details. + @param deltas: velocity data to plot (optional). + the input can be specified in several ways - see the main description for details. + @param model_space: model space can be a pmsco.project.ModelSpace object, + any object that contains the same min and max attributes as pmsco.project.ModelSpace, + or a dictionary with to keys 'min' and 'max' that provides the corresponding ModelSpace dictionaries. + by default, the model space boundaries are derived from the input data. + if a model_space is specified, only the parameters listed in it are plotted. + @param title: (str) title of the chart. + the title is a {}-style format string, where {base} is the output file name and {gen} is the generation. + default: derived from file name. + @param generations: (int or sequence) generation index or list of indices. + this index is used in the output file name and for filtering input data by generation. + if the input data does not contain the generation, no filtering is applied. + by default, no filtering is applied, and one graph for each generation is produced. + @param cmap: (str) name of colour map supported by matplotlib. + default is 'plasma'. + other good-looking options are 'viridis', 'plasma', 'inferno', 'magma', 'cividis'. + @param canvas: a FigureCanvas class reference from a matplotlib backend. + if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format. + some other options are: + matplotlib.backends.backend_pdf.FigureCanvasPdf or + matplotlib.backends.backend_svg.FigureCanvasSVG. + + @return (list of str) paths of the generated graphics files. + empty if an error occurred. + + @raise TypeError if matplotlib is not available. + """ + data = rp_results.ResultData() + if isinstance(generations, int): + generations = (generations,) + data.generations = generations + data.levels = {'scan': -1} + data.load_any(values, deltas) + if model_space is not None: + data.set_model_space(model_space) + + plot = SwarmPlot() + plot.canvas = canvas + plot.cmap = cmap + if title: + plot.title_format = title + else: + plot.title_format = "${gen}" + plot.report_dir = Path(output_file).parent + plot.filename_format = Path(output_file).name + "-${param0}-${param1}-${gen}" + plot.validate(None) + plot.result_data = data + plot.swarm_data = data + files = plot.create_report() + + return files + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description=""" + swarm dynamics plot for multiple-scattering optimization results + + this module operates on results or database files and produces one graphics file per generation. + + database files contain the complete information for all plot types. + data from the most recent job stored in the database is used. + + while .dat results files contain all data shown in genetic plots, + they lack the velocity information for swarm plots. + only particle positions are plotted in this case. + .tasks.dat files lack the generation and particle identification and should not be used. + + note that the plot type is independent of the optimization mode. + it's possible to generate genetic plots from a particle swarm optimization and vice versa. + """) + parser.add_argument('results_file', + help="path to results file (.dat) or sqlite3 database file.") + parser.add_argument('output_file', + help="base name of output file. generation and extension will be appended.") + parser.add_argument('-t', '--title', default=None, + help='graph title. may contain {gen} as a placeholder for the generation number.') + + args, unknown_args = parser.parse_known_args() + + kwargs = {} + if args.title is not None: + kwargs['title'] = args.title + + render_func = render_swarm + + if db_util.is_sqlite3_file(args.results_file): + import pmsco.database.access as db_access + db = db_access.DatabaseAccess() + db.connect(args.results_file) + with db.session() as session: + render_func(args.output_file, session, **kwargs) + else: + render_func(args.output_file, args.results_file, **kwargs) + + +if __name__ == '__main__': + main() + sys.exit(0) diff --git a/pmsco/scan.py b/pmsco/scan.py new file mode 100644 index 0000000..2a79230 --- /dev/null +++ b/pmsco/scan.py @@ -0,0 +1,670 @@ +""" +@package pmsco.scan +scan classes and factories. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2015-21 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import copy +import logging +import os +from typing import Any, Callable, Dict, Generator, Iterable, List, Mapping, Optional, Sequence, Set, Tuple, Union + +import numpy as np + +import pmsco.config as config +import pmsco.data as md + +logger = logging.getLogger(__name__) + + +class Scan(object): + """ + class to describe the scanning scheme or store the experimental data set. + """ + + ## @var filename (string) + # file name from which a scan was loaded + + ## @var raw_data (numpy.ndarray) + # original scan data (ETPAIS array) + + ## @var dtype (dict) + # data type of self.raw_data. + # + # one of the data.DTYPE_Xxxx constants. + + ## @var modulation (numpy.ndarray) + # modulation function calculated from original scan (ETPAIS array) + + ## @var mode (list of characters) + # list of ETPAI column names which are scanned in self.raw_data. + # + # example: ['t','p'] + + ## @var emitter (string) + # chemical symbol and, optionally following, further specification (chemical state, environment, ...) + # of photo-emitting atoms. + # the interpretation of this string is up to the project and its cluster generator. + # it should, however, always start with a chemical element symbol. + # + # examples: 'Ca' (calcium), 'CA' (carbon A), 'C a' (carbon a), 'C 1' (carbon one), 'N=O', 'FeIII'. + + ## @var initial_state (string) + # nl term of initial state + # + # in the form expected by EDAC, for example: '1s' + + ## @var energies (numpy.ndarray) + # kinetic energy referenced to Fermi level. + # + # one-dimensional array. + + ## @var thetas (numpy.ndarray) + # polar angle referenced to normal emission + # + # one-dimensional array. + # + # note: in the case of a hemispherical scan, the values in this array will not be unique. + + ## @var phis (numpy.ndarray) + # azimuthal angle referenced to arbitrary origin + # + # one-dimensional array. + # + # note: in the case of a hemispherical scan, the values in this array will not be unique, and not monotonic. + + ## @var alphas (numpy.ndarray) + # polar angle referenced to normal emission + # + # one-dimensional array. + + def __init__(self): + self.filename = "" + self.raw_data = None + self.dtype = None + self.modulation = None + self.modulation_func = md.default_modfunc + self.modulation_args = {"smth": 0.4} + self.rfactor_func = md.default_rfactor + self.rfactor_args = {} + self.mode = [] + self.emitter = "" + self.initial_state = "1s" + self.positions = { + 'e': np.empty(0), + 't': np.empty(0), + 'p': np.empty(0), + 'a': np.empty(0), + } + + def __str__(self): + return f"({self.__class__.__name__}) {self.filename} ({self.emitter} {self.initial_state})" + + @property + def energies(self): + return self.positions['e'] + + @energies.setter + def energies(self, value): + self.positions['e'] = value + + @property + def thetas(self): + return self.positions['t'] + + @thetas.setter + def thetas(self, value): + self.positions['t'] = value + + @property + def phis(self): + return self.positions['p'] + + @phis.setter + def phis(self, value): + self.positions['p'] = value + + @property + def alphas(self): + return self.positions['a'] + + @alphas.setter + def alphas(self, value): + self.positions['a'] = value + + def copy(self): + """ + create a copy of the scan. + + @return: new independent scan object with the same attributes as the original one. + """ + return copy.deepcopy(self) + + def import_scan_file(self, filename, emitter, initial_state): + """ + import the reference experiment. + + the extension must be one of msc_data.DATATYPES (case insensitive) + corresponding to the meaning of the columns in the file. + + this method does not calculate the modulation function. + + @attention EDAC can only calculate equidistant, rectangular scans. + holo scans are transparently mapped to rectangular scans by pmsco. + this method accepts the following scans: + + * intensity vs energy at fixed theta, phi + * intensity vs analyser angle vs energy at normal emission (theta = 0, constant phi) + * intensity vs theta, phi, or alpha + * holo scan (theta,phi) + + @param filename: (string) file name of the experimental data, possibly including a path. + + @param emitter: (string) chemical symbol of the photo-emitting atom, e.g. "Cu". + + @param initial_state: (string) nl term of the initial state of the atom, e.g. "2p". + + """ + self.filename = filename + self.emitter = emitter + self.initial_state = initial_state + + if self.filename: + self.raw_data = md.load_data(self.filename) + self.analyse_raw_data() + else: + logger.error("empty file name in Scan.import_scan_file.") + + def analyse_raw_data(self): + """ + analyse raw data and update dependant properties + + must be called after loading or modifying self.raw_data. + part of import_scan_file. + + @return: None + """ + self.dtype = self.raw_data.dtype + self.mode, self.positions = md.detect_scan_mode(self.raw_data) + if 'e' not in self.mode: + try: + self.energies = np.asarray((self.raw_data['e'][0],)) + except ValueError: + logger.error("missing energy in scan file %s", self.filename) + raise + if 't' not in self.mode: + try: + self.thetas = np.asarray((self.raw_data['t'][0],)) + except ValueError: + logger.info("missing theta in scan file %s, defaulting to 0.0", self.filename) + self.thetas = np.zeros(1) + if 'p' not in self.mode: + try: + self.phis = np.asarray((self.raw_data['p'][0],)) + except ValueError: + logger.info("missing phi in scan file %s, defaulting to 0.0", self.filename) + self.phis = np.zeros(1) + if 'a' not in self.mode: + try: + self.alphas = np.asarray((self.raw_data['a'][0],)) + except ValueError: + logger.info("missing alpha in scan file %s, defaulting to 0.0", self.filename) + self.alphas = np.zeros(1) + + def define_scan(self, positions, emitter, initial_state): + """ + define a cartesian (rectangular/grid) scan. + + this method initializes the scan with a one- or two-dimensional cartesian scan + of the four possible scan dimensions. + the scan range is given as arguments, the intensity values are initialized as 1. + the file name and modulation functions are reset to empty and None, respectively. + + the method can create the following scan schemes: + + * intensity vs energy at fixed theta, phi + * intensity vs analyser angle vs energy at normal emission (theta = 0, constant phi) + * intensity vs theta, phi, or alpha + * intensity vs theta and phi (rectangular holo scan) + + @param positions: (dictionary of numpy arrays) + the dictionary must contain a one-dimensional array for each scan dimension 'e', 't', 'p' and 'a'. + these array must contain unique, equidistant positions. + constant dimensions must contain exactly one value. + missing angle dimensions default to 0, + a missing energy dimension results in a KeyError. + + @param emitter: (string) chemical symbol of the photo-emitting atom, e.g. "Cu". + + @param initial_state: (string) nl term of the initial state of the atom, e.g. "2p". + + """ + self.filename = "" + self.emitter = emitter + self.initial_state = initial_state + self.mode = [] + shape = 1 + + try: + self.energies = np.copy(positions['e']) + except KeyError: + logger.error("missing energy in define_scan arguments") + raise + else: + if self.energies.shape[0] > 1: + self.mode.append('e') + shape *= self.energies.shape[0] + + try: + self.thetas = np.copy(positions['t']) + except KeyError: + logger.info("missing theta in define_scan arguments, defaulting to 0.0") + self.thetas = np.zeros(1) + else: + if self.thetas.shape[0] > 1: + self.mode.append('t') + shape *= self.thetas.shape[0] + + try: + self.phis = np.copy(positions['p']) + except KeyError: + logger.info("missing phi in define_scan arguments, defaulting to 0.0") + self.phis = np.zeros(1) + else: + if self.phis.shape[0] > 1: + self.mode.append('p') + shape *= self.phis.shape[0] + + try: + self.alphas = np.copy(positions['a']) + except KeyError: + logger.info("missing alpha in define_scan arguments, defaulting to 0.0") + self.alphas = np.zeros(1) + else: + if self.alphas.shape[0] > 1: + self.mode.append('a') + shape *= self.alphas.shape[0] + + assert 0 < len(self.mode) <= 2, "unacceptable number of dimensions in define_scan" + assert not ('t' in self.mode and 'a' in self.mode), "unacceptable combination of dimensions in define_scan" + + self.dtype = md.DTYPE_ETPAI + self.raw_data = np.zeros(shape, self.dtype) + dimensions = [self.positions[dim] for dim in ['e', 't', 'p', 'a']] + grid = np.meshgrid(*dimensions) + for i, dim in enumerate(['e', 't', 'p', 'a']): + self.raw_data[dim] = grid[i].reshape(-1) + self.raw_data['i'] = 1 + + def generate_holo_scan(self, + generator: Callable[..., Iterable[Tuple[float, float]]], + generator_args: Dict, + other_positions: Dict, + emitter: Optional[str] = None, + initial_state: Optional[str] = None): + """ + Generate a hologram (theta-phi) scan. + + The grid algorithm must be specified as the generator function, e.g. pmsco.data.holo_grid. + + @param generator Generator function that yields tuples (theta, phi) for each grid point, + e.g., pmsco.data.holo_grid. + @param generator_args Arguments to be passed to the generator. + See the documentation of the respective generator function. + @param other_positions: + Coordinates of the other dimensions ('e' and 'a') in dictionary format. + As of this version, they must be constant and contain exactly one value. + @param emitter: + Chemical symbol of the photo-emitting atom, e.g. "Cu". + Optional if the corresponding Scan attribute is set elsewhere. + @param initial_state: + nlj term of the initial state of the atom, e.g. "2p1/2". + Optional if the corresponding Scan attribute is set elsewhere. + """ + + if emitter: + self.emitter = emitter + if initial_state: + self.initial_state = initial_state + + self.mode = ["t", "p"] + self.positions = {'t': None, 'p': None} + + for dim, val in other_positions.items(): + self.positions[dim] = np.atleast_1d(np.asarray(val)) + assert self.positions[dim].shape[0] == 1, f"other_positions[{dim}] must have scalar value" + + tp = np.fromiter(generator(**generator_args), dtype=md.DTYPES['TP']) + self.positions['t'] = tp['t'] + self.positions['p'] = tp['p'] + + fields = set(self.positions.keys()) + fields.add('i') + self.dtype = [dt for dt in md.DTYPE_ETPAIS if dt[0] in fields] + self.raw_data = np.zeros(tp.shape, self.dtype) + self.raw_data['i'] = np.random.default_rng().normal(1, 0.1, self.raw_data.shape) + for dim in self.positions.keys(): + self.raw_data[dim] = self.positions[dim] + + def load(self): + return self + + +class ScanKey(config.ConfigurableObject): + """ + create a Scan object based on a project-supplied dictionary + + this class can be used in a run file to create a scan object based on the scan_dict attribute of the project. + this may be convenient if you're project should selectively use scans out of a long list of data files + and you don't want to clutter up the run file with parameters that don't change. + + to do so, set the key property to match an item of scan_dict. + the load method will look up the corresponding scan_dict item and construct the final Scan object. + """ + def __init__(self, project=None): + super().__init__() + self.key = "" + self.project = project + + def __str__(self): + return f"({self.__class__.__name__}) {self.key}" + + def load(self, dirs: Optional[Mapping] = None) -> Scan: + """ + load the selected scan as specified in the project's scan dictionary + + the method uses ScanLoader or ScanCreator as an intermediate. + + @return a new Scan object which contains the loaded data. + """ + scan_spec = self.project.scan_dict[self.key] + if hasattr(scan_spec, 'positions'): + loader = ScanCreator() + elif hasattr(scan_spec, "generator"): + loader = HoloScanCreator() + else: + loader = ScanLoader() + for k, v in scan_spec.items(): + setattr(loader, k, v) + scan = loader.load(dirs=dirs) + return scan + + +class BaseScanSpec(config.ConfigurableObject): + """ + Basic scan specification + + Declares and manages common scan attributes of the scan creators and loaders. + """ + + ## @var filename (string) + # Name of the file from which to load or to which to save the scan data. + # The file name can contain a format specifier like `${project}`. + + ## @var modulation_func (string or callable) + # Function to calculate the modulation function. + # Must have the same signature as pmsco.data.default_modfunc. + # The function name can be given as a string relative to the global namespace of the project module, + # e.g. `pmsco.data.calc_modfunc_loess` if `pmsco.data` has been imported. + + ## @var emitter (string) + # Chemical symbol and, optionally following, further specification (chemical state, environment, ...) + # of photo-emitting atoms. + # The interpretation of this string is up to the project and its cluster generator. + # It should, however, always start with a chemical element symbol. + # + # Examples: 'Ca' (calcium), 'CA' (carbon A), 'C a' (carbon a), 'C 1' (carbon one), 'N=O', 'FeIII'. + + ## @var initial_state (string) + # Term symbol of the initial state + # + # nlj term of the initial state in the form expected by EDAC, for example: '2p1/2' + + def __init__(self): + super().__init__() + self.filename: Union[str, os.PathLike] = "" + self.emitter: str = "" + self.initial_state: str = "1s" + self.modulation_func: Union[str, Callable] = md.default_modfunc + self.modulation_args: Dict = {"smth": 0.4} + self.rfactor_func: Union[str, Callable] = md.default_rfactor + self.rfactor_args: Dict = {} + + def __str__(self): + return f"({self.__class__.__name__}) {self.filename} ({self.emitter} {self.initial_state})" + + def load(self, dirs: Optional[Mapping] = None) -> Scan: + """ + Load the scan according to specification + + Create a new Scan object and initialize the attributes defined in this class. + This method is extended by derived classes. + In this class, the method returns a partially initialized object. + + @return a new Scan object which contains the loaded data file. + """ + + scan = Scan() + scan.filename = self.filename + scan.emitter = self.emitter + scan.initial_state = self.initial_state + + if callable(self.modulation_func): + scan.modulation_func = self.modulation_func + else: + scan.modulation_func = eval(self.modulation_func, self.project_symbols) + scan.modulation_args = self.modulation_args + + if callable(self.rfactor_func): + scan.rfactor_func = self.rfactor_func + else: + scan.rfactor_func = eval(self.rfactor_func, self.project_symbols) + scan.rfactor_args = self.rfactor_args + + return scan + + +class ScanLoader(BaseScanSpec): + """ + create a Scan object from a data file reference + + this class can be used in a run file to create a scan object from an experimental data file. + to do so, fill the properties with values as documented. + the load() method is called when the project is run. + """ + + ## @var filename (string) + # file name from which the scan should be loaded. + # the file name can contain a format specifier like ${project} to include the base path. + + ## @var patch (dict) + # patching instructions for raw_data array + # + # this attribute lets you modify parts of the raw_data array after loading the scan file. + # this is useful, for instance, to change the energy in an angle scan. + # + # in the most common case, a fixed axis is changed to another value. + # more complex patches can be declared using arbitrary numpy functions. + # the resulting array must be compatible with broadcasting into the raw_data array, + # i.e., its length must be 1 or equal to the one loaded from the file. + # + # the dictionary can contain any of the four keys: 'e', 't', 'p', 'a', 'i' + # representing the four position and one data columns. + # each key holds a string that contains a python expression. + # the string is evaluated using python's built-in eval() function. + # the expression must evaluate to an iterable object or numpy ndarray. + # the namespaces of the project module can be used. + # the `raw_data` object contains the loaded file. + # + # examples: + # override the energy of a loaded scan with a constant value: + # ~~~~~~{.py} + # self.patch = {'e': '100.'} + # ~~~~~~ + # subtract a constant offset from the theta angle: + # ~~~~~~{.py} + # self.patch = {'t': "raw_data['t'] - 3.4"} + # ~~~~~~ + + ## @var is_modf (bool) + # declares whether the data file contains the modulation function rather than intensity values + # + # if false, the project will calculate a modulation function from the raw data + + def __init__(self): + super().__init__() + self.patch = {} + self.is_modf = False + + def __str__(self): + return f"({self.__class__.__name__}) {self.filename} ({self.emitter} {self.initial_state})" + + def load(self, dirs: Optional[Mapping] = None) -> Scan: + """ + load the scan according to specification + + create a new Scan object and load the file by calling Scan.import_scan_file(). + + @return a new Scan object which contains the loaded data file. + """ + + scan = super().load() + filename = config.resolve_path(self.filename, dirs) + scan.import_scan_file(filename, self.emitter, self.initial_state) + + if self.patch: + patch_arrays = {} + locals = {'raw_data': scan.raw_data} + for axis in self.patch.keys(): + patch_arrays[axis] = np.atleast_1d(np.asarray(eval(self.patch[axis], self.project_symbols, locals))) + for axis in patch_arrays.keys(): + scan.raw_data[axis][:] = patch_arrays[axis] + logger.warning(f"scan file {filename.name}, overriding axis '{axis}'") + scan.analyse_raw_data() + + if self.is_modf: + scan.modulation = scan.raw_data + + return scan + + +class ScanCreator(BaseScanSpec): + """ + Create a linear or rectangular scan object from string expressions + + This class can be used in a run file to create a scan object from python expressions, + such as lists, ranges or numpy functions. + To do so, fill the properties with values as documented. + The load() method is called when the project is run. + + @note The raw_data property of the scan cannot be filled this way. + Thus, the class is useful in `single` calculation mode only. + """ + + ## @var positions (dict) + # Dictionary specifying the scan positions. + # + # The dictionary must contain four keys: 'e', 't', 'p', 'a' representing the four scan axes. + # Each value must contain a Python expression that evaluates to a one-dimensional coordinate array. + # The coordinates are meshed into a one or multidimensional rectangular scan grid. + # Non-scanning dimensions must be set to a constant scalar value. + # + # The values are evaluated using python's built-in eval() function. + # The expression must evaluate to an iterable object, numpy ndarray or scalar of the scan positions. + # Functions from the project namespace can be used to build the scan. + # + # Example + # ------- + # + # Generate a rectangular energy-alpha scan at fixed angle: + # + # ~~~~~~{.py} + # self.positions = {"e": "numpy.linspace(100, 200, 5)", "t": "0", "p": "60", "a": "numpy.linspace(-10, 10, 51)"} + # ~~~~~~ + # + # In the run-file: + # + # ~~~~~~{.json} + # "positions": {"e": "numpy.linspace(100, 200, 5)", "t": "0", "p": "60", "a": "numpy.linspace(-10, 10, 51)"} + # ~~~~~~ + # + + def __init__(self): + super().__init__() + self.positions = {'e': None, 't': None, 'p': None, 'a': None} + + def __str__(self): + return f"({self.__class__.__name__}) {self.filename} ({self.emitter} {self.initial_state})" + + def load(self, dirs: Optional[Mapping] = None) -> Scan: + """ + Create the scan according to specification + + @return a new Scan object which contains the created scan array. + """ + + scan = super().load() + positions = {} + for axis in self.positions.keys(): + positions[axis] = np.atleast_1d(np.asarray(eval(str(self.positions[axis]), self.project_symbols))) + scan.define_scan(positions, self.emitter, self.initial_state) + scan.filename = config.resolve_path(self.filename, dirs) + return scan + + +class HoloScanCreator(BaseScanSpec): + """ + Generate a hologram scan + + This scan generator can be used to generate a holo scan. + The (theta, phi) coordinates are obtained from a generator function, e.g. pmsco.data.holo_grid. + The remaining coordinates are specified in the other_positions dictionary. + As of this version, they must be scalar values. + + Example + ------- + + ~~~~~~{.py} + sg = HoloScanCreator() + sg.generator = pmsco.data.holo_grid + sg.generator_args = {"theta_start": 85, "theta_step": 1) + sg.other_positions = {"e": "250", "a": "0"} + sg.load() + ~~~~~~ + """ + + def __init__(self): + super().__init__() + self.generator: Union[str, Callable[..., Iterable[Tuple[float, float]]]] = md.holo_grid + self.generator_args: Dict[str, Union[str, int, float]] = {} + self.other_positions = {} + + def __str__(self): + return f"({self.__class__.__name__}) {self.filename} ({self.emitter} {self.initial_state})" + + def load(self, dirs: Optional[Mapping] = None) -> Scan: + """ + Generate the scan according to specification + + @return a new Scan object which contains the created scan array. + """ + + scan = super().load() + positions = {} + + for axis in self.other_positions.keys(): + positions[axis] = np.atleast_1d(np.asarray(eval(str(self.other_positions[axis]), self.project_symbols))) + + generator = self.generator if callable(self.generator) else eval(self.generator, self.project_symbols) + + scan.generate_holo_scan(generator=generator, generator_args=self.generator_args, other_positions=positions) + scan.filename = config.resolve_path(self.filename, dirs) + + return scan diff --git a/pmsco/schedule.py b/pmsco/schedule.py index ddaabfe..7480887 100644 --- a/pmsco/schedule.py +++ b/pmsco/schedule.py @@ -1,21 +1,21 @@ """ @package pmsco.schedule -job schedule interface +Job schedule interface -this module defines common infrastructure to submit a pmsco calculation job to a job scheduler such as slurm. +This module defines common infrastructure to submit a PMSCO calculation job to a job scheduler such as Slurm. -the schedule can be defined as part of the run-file (see pmsco module). -users may derive sub-classes in a separate module to adapt to their own computing cluster. +The schedule can be defined as part of the run-file (see pmsco module). +Users may derive sub-classes in a separate module to adapt to their own computing cluster. -the basic call sequence is: -1. create a schedule object. -2. initialize its properties with job parameters. -3. validate() -4. submit() +The basic call sequence is: +1. Create a schedule object. +2. Initialize its properties with job parameters. +3. Validate() +4. Submit() @author Matthias Muntwiler, matthias.muntwiler@psi.ch -@copyright (c) 2015-21 by Paul Scherrer Institut @n +@copyright (c) 2015-23 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -23,12 +23,20 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n """ import collections.abc -import commentjson as json import datetime import logging +import os from pathlib import Path import shutil import subprocess +import sys +from typing import Any, Callable, Dict, Generator, Iterable, Iterator, List, Mapping, Optional, Sequence, Set, Tuple, Union + +try: + import commentjson as json +except ImportError: + import json + import pmsco.config logger = logging.getLogger(__name__) @@ -36,37 +44,82 @@ logger = logging.getLogger(__name__) class JobSchedule(pmsco.config.ConfigurableObject): """ - base class for job schedule + Base class for job schedule - this class defines the abstract interface and some utilities. - derived classes may override any method, but should call the inherited method. + Usage: + 1. Create object, assigning a project instance. + 2. Set attributes. + 3. Assign run_dict. + 4. Call validate. + 5. Call submit. - usage: - 1. create object, assigning a project instance. - 2. assign run_file. - 3. call validate. - 4. call submit. + An example can be seen in pmsco.schedule_project. - this class' properties should not be listed in the run file - they will be overwritten. + This class defines the abstract interface, common actions and some utilities. + Derived classes may override any method, but should call the inherited method. """ ## @var enabled (bool) # - # this parameter signals whether pmsco should schedule a job or run the calculation. - # it is not directly used by the schedule classes but by the pmsco module. - # it must be defined in the run file and set to true to submit the job to a scheduler. - # it is set to false in the run file copied to the job directory so that the job script starts the calculation. + # This parameter signals whether pmsco should schedule a job or run the calculation. + # It is not directly used by the schedule classes but by the pmsco module. + # It must be defined in the run file and set to true to submit the job to a scheduler. + # It is set to false in the run file copied to the job directory so that the job script starts the calculation. def __init__(self, project): super(JobSchedule, self).__init__() self.project = project + + # Specifies whether a job script is created (True) or not (False, default). self.enabled = False + + # Specifies whether the job script is submitted manually by the user (True, default) + # or as a part of this class' execution (False). + self.manual = True + + # Specifies whether an existing job directory is overwritten (True). + # Otherwise, an exception is raised if it exists (False, default). + self.overwrite_job_dir = False + + # Content of the run-file. + # This must be set by the caller before the object is executed. self.run_dict = {} - self.job_dir = Path() - self.job_file = Path() + + # Path to the destination run-file. + # This value is set by self.validate. self.run_file = Path() - # directory that contains the pmsco and projects directories - self.pmsco_root = Path(__file__).parent.parent + + # Job work directory. + # This value is set by self.validate. + self.job_dir = Path() + + # Dest path of the shell script to be submitted to the queue. + # This value is set by self.validate. + self.job_file = Path() + + # Directory that contains the pmsco code. + # This value is set by self.validate. + self.pmsco_root_dir = Path(__file__).parent.parent + + # Directory that contains the project module + # This value is set by self.validate. + self.project_dir = Path() + + # Project files to be copied to the job_dir. + # Paths should be absolute or relative to project_dir. + # The project module is appended by self.validate. + # Other files have to be added by the caller. + self.project_files = [] + + # Name of the conda environment to activate (`source activate` command). + # Either conda_env or virtual_env should be specified. + # If both are empty, the environment is detected from the environment of the current process. + self.conda_env = "" + + # Path of the virtual environment to activate (must contain the `activate` command). + # Either conda_env or virtual_env should be specified. + # If both are empty, the environment is detected from the environment of the current process. + self.virtual_env = "" def validate(self): """ @@ -76,20 +129,35 @@ class JobSchedule(pmsco.config.ConfigurableObject): @return: None """ - self.pmsco_root = Path(self.project.directories['pmsco']).parent + + assert self.run_dict, "run_dict not set" + + self.pmsco_root_dir = Path(self.project.directories['pmsco']).parent + self.project_dir = Path(self.project.directories['project']) output_dir = Path(self.project.directories['output']) - assert self.pmsco_root.is_dir() - assert (self.pmsco_root / "pmsco").is_dir() - assert (self.pmsco_root / "projects").is_dir() - assert output_dir.is_dir() - assert self.project.job_name + assert self.pmsco_root_dir.is_dir() + assert (self.pmsco_root_dir / "pmsco").is_dir(), "can't find pmsco directory (source code)" + assert self.project_dir.is_dir(), "can't find project directory" + assert output_dir.is_dir(), "can't find output directory" + assert output_dir.is_absolute(), "output directory must be an absolute path" + assert self.project.job_name, "job_name is undefined" + + if output_dir.name == self.project.job_name: + self.job_dir = output_dir + else: + self.job_dir = output_dir / self.project.job_name + try: + self.job_dir.mkdir(parents=True, exist_ok=self.overwrite_job_dir) + except FileExistsError: + logger.error("job directory exists - check job name or clean up manually") + raise - self.job_dir = output_dir / self.project.job_name - self.job_dir.mkdir(parents=True, exist_ok=True) self.job_file = (self.job_dir / self.project.job_name).with_suffix(".sh") self.run_file = (self.job_dir / self.project.job_name).with_suffix(".json") + self.project_files.append(sys.modules[self.project.__module__].__file__) + def submit(self): """ submit the job to the scheduler. @@ -111,24 +179,16 @@ class JobSchedule(pmsco.config.ConfigurableObject): """ copy the source files to the job directory. - the source_dir and job_dir attributes must be correct. - the job_dir directory must not exist and will be created. - - this is a utility method used internally by derived classes. - - job_dir/pmsco/pmsco/** - job_dir/pmsco/projects/** - job_dir/job.sh - job_dir/job.json + the files to copy must be listed explicitly in the project_files attribute. + the files are copied to the job_dir directory. + the directory must exist. @return: None """ - source = self.pmsco_root - dest = self.job_dir / "pmsco" - ignore = shutil.ignore_patterns(".*", "~*", "*~") - shutil.copytree(source / "pmsco", dest / "pmsco", ignore=ignore) - shutil.copytree(source / "projects", dest / "projects", ignore=ignore) + files = set((self.project_dir.joinpath(pf) for pf in self.project_files)) + for f in files: + shutil.copy2(f, self.job_dir) def _fix_run_file(self): """ @@ -144,6 +204,7 @@ class JobSchedule(pmsco.config.ConfigurableObject): """ self.run_dict['schedule']['enabled'] = False self.run_dict['project']['directories']['output'] = str(self.job_dir) + self.run_dict['project']['job_name'] = self.project.job_name self.run_dict['project']['log_file'] = str((self.job_dir / self.project.job_name).with_suffix(".log")) def _write_run_file(self): @@ -169,6 +230,31 @@ class JobSchedule(pmsco.config.ConfigurableObject): """ pass + @staticmethod + def detect_env() -> Dict[str, os.PathLike]: + """ + detect the python environment + + determines the current python environment. + + examples: + - /das/work/p17/p17274/conda/envs/pmsco310/bin + - /home/user/envs/pmsco-uv + + @return: dictionary type -> path containing one or zero items. + type is either 'conda', 'venv' or 'system'; + path is the bin directory containing python. + """ + + pp = Path(sys.executable).parent + if (pp / 'activate').is_file(): + return {"venv": pp} + for parent in pp.parents: + if (parent / "condabin").is_dir(): + return {"conda": pp} + else: + return {"system": pp} + class SlurmSchedule(JobSchedule): """ @@ -189,10 +275,9 @@ class SlurmSchedule(JobSchedule): super(SlurmSchedule, self).__init__(project) self.host = "" self.nodes = 1 - self.tasks_per_node = 8 + self.tasks = 8 self.wall_time = datetime.timedelta(hours=1) self.signal_time = 600 - self.manual = True @staticmethod def parse_timedelta(td): @@ -221,13 +306,21 @@ class SlurmSchedule(JobSchedule): pass td = datetime.timedelta(**dt) elif isinstance(td, collections.abc.Mapping): + td = {k: float(v) for k, v in td.items()} td = datetime.timedelta(**td) return td + @property + def tasks_per_node(self): + return self.tasks // self.nodes + + @tasks_per_node.setter + def tasks_per_node(self, value): + self.tasks = value * self.nodes + def validate(self): super(SlurmSchedule, self).validate() self.wall_time = self.parse_timedelta(self.wall_time) - assert self.job_dir.is_absolute() def submit(self): """ @@ -252,8 +345,31 @@ class PsiRaSchedule(SlurmSchedule): job shedule for the Ra cluster at PSI. this class selects specific features of the Ra cluster, - such as the partition and node type (24 or 32 cores). + such as the partition and node type. it also implements the _write_job_file method. + + for information about the Ra cluster, see + https://www.psi.ch/en/photon-science-data-services/offline-computing-facility-for-sls-and-swissfel-data-analysis + + COMPUTE NODES + + | NodeName | Weight | CPUs | RealMemory | MemSpecLimit | Sockets | CoresPerSocket | ThreadsPerCore | + | --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | + | ra-c-[033-048] | 100 | 36 | 256000 | 21502 | 2 | 18 | 1 | + | ra-c-[049-072] | 100 | 40 | 386000 | 21502 | 2 | 20 | 1 | + | ra-c-[073-084] | | 52 | | | 2 | 26 | 1 | + | ra-c-[085-096] | | 56 | | | 2 | 28 | 1 | + + PARTITIONS + + | PartitionName | Nodes | MaxTime | DefaultTime | Priority | + | --- | :---: | :---: | :---: | :---: | :---: | + | hour | | 0-01:00:00 | 0-01:00:00 | 4 | + | day | | 1-00:00:00 | 0-08:00:00 | 2 | + | week | | 8-00:00:00 | 2-00:00:00 | 1 | + + The option --ntasks-per-node is meant to be used with the --nodes option. + (For the --ntasks option, the default is one task per node, use the --cpus-per-task option to change this default.) """ ## @var partition (str) @@ -261,24 +377,64 @@ class PsiRaSchedule(SlurmSchedule): # the partition is selected based on wall time and number of tasks by the validate() method. # it should not be listed in the run file. + ## @var modules (list of str) + # + # names of the software modules to load (`module load` command) + def __init__(self, project): super(PsiRaSchedule, self).__init__(project) - self.partition = "shared" + self.partition = "" + self.modules = [] + self.default_env = {} def validate(self): + """ + check validity of parameters and detect the environment + + the validity is not checked in detail - just intercept some common or severe mistakes. + + detect whether we are in a conda or virtual environment + so that we can add the necessary activation to the job script. + + detect which modules are active. depending on the python environment, LOADEDMODULES is: + - venv: openssl/3.4.1:TclTk/8.6.16:xz/5.8.0:Python/3.12.9:gcc/9.3.0:libfabric/1.18.0:cuda/11.1.0:openmpi/4.0.5_slurm + - conda: miniforge/2025-03-25:gcc/9.3.0:libfabric/1.18.0:cuda/11.1.0:openmpi/4.0.5_slurm + + other potentially useful but currently unused environment variables are: + - PMODULES_LOADED_COMPILER: openmpi/4.0.5_slurm + - PMODULES_LOADED_TOOLS: openssl/3.4.1:xz/5.8.0 + - PMODULES_LOADED_LIBRARIES: libfabric/1.18.0 + - PMODULES_LOADED_PROGRAMMING: TclTk/8.6.16:Python/3.12.9:gcc/9.3.0:cuda/11.1.0 + - PMODULES_LOADED_TOMCAT: miniforge/2025-03-25 + + note: openssl, TclTk, xz and cuda were not requested explicitly. + """ + super(PsiRaSchedule, self).validate() + + # check that the submission is sane - the values are not firm assert self.nodes <= 2 - assert self.tasks_per_node <= 24 or self.tasks_per_node == 32 - assert self.wall_time.total_seconds() >= 60 + assert self.tasks <= 64 + assert 30 * 60 <= self.wall_time.total_seconds() <= 8 * 24 * 60 * 60 + if self.wall_time.total_seconds() > 24 * 60 * 60: self.partition = "week" - elif self.tasks_per_node < 24: - self.partition = "shared" else: self.partition = "day" - assert self.partition in ["day", "week", "shared"] + + assert self.partition in ["day", "week"] + + self.default_env = self.detect_env() + + if len(self.modules) == 0: + self.modules = os.environ["LOADEDMODULES"].split(":") def _write_job_file(self): + """ + write a job file for the ra cluster + + @return: None + """ lines = [] lines.append('#!/bin/bash') @@ -288,21 +444,38 @@ class PsiRaSchedule(SlurmSchedule): lines.append(f'#SBATCH --time={int(self.wall_time.total_seconds() / 60)}') lines.append(f'#SBATCH --nodes={self.nodes}') lines.append(f'#SBATCH --ntasks-per-node={self.tasks_per_node}') - if self.tasks_per_node > 24: - lines.append('#SBATCH --cores-per-socket=16') # 0 - 65535 seconds # currently, PMSCO does not react to signals properly # lines.append(f'#SBATCH --signal=TERM@{self.signal_time}') lines.append(f'#SBATCH --output="{self.project.job_name}.o.%j"') lines.append(f'#SBATCH --error="{self.project.job_name}.e.%j"') - lines.append('module load psi-python36/4.4.0') - lines.append('module load gcc/4.8.5') - lines.append('module load openmpi/3.1.3') - lines.append('source activate pmsco') + + # environment + if self.modules: + lines.append('module use unstable || true') + lines.append('module use Libraries || true') + for module in self.modules: + lines.append(f'module load {module}') + + conda_env = self.conda_env or self.default_env.get("conda") + virtual_env = self.virtual_env or self.default_env.get("venv") + if conda_env: + lines.append(f'source activate {conda_env}') + elif virtual_env: + activate_script = Path(virtual_env) / "activate" + if activate_script.is_file(): + lines.append(f'source {activate_script}') + + lines.append('env') + lines.append('') + + # run lines.append(f'cd "{self.job_dir}"') - lines.append(f'mpirun python pmsco/pmsco -r {self.run_file.name}') + lines.append(f'mpirun python -m pmsco -r {self.run_file}') + lines.append('') + + # clean up lines.append(f'cd "{self.job_dir}"') - lines.append('rm -rf pmsco') lines.append('exit 0') self.job_file.write_text("\n".join(lines)) diff --git a/pmsco/schema/project.schema.json b/pmsco/schema/project.schema.json new file mode 100644 index 0000000..3a7c681 --- /dev/null +++ b/pmsco/schema/project.schema.json @@ -0,0 +1,139 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:project.schema.json", + "title": "PMSCO Project", + "description": "PMSCO Project", + "type": "object", + "properties": { + "__module__": { + "type": "string", + "title": "name of project module", + "examples": ["projects.twoatom.twoatom"] + }, + "__class__": { + "type": "string", + "title": "name of project class", + "examples": ["TwoatomProject"] + }, + "job_tags": { + "type": "object" + }, + "description": {"type": "string"}, + "mode": { + "type": "string", + "enum": ["genetic", "grid", "single", "swarm", "table", "test", "validate"] + }, + "directories": {"type": "object"}, + "db_file": {"type": "string"}, + "keep_files": { + "type": "array", + "items": { + "type": "string", + "enum": ["all", "cluster", "atomic", "input", "output", "report", + "region", "emitter", "scan", "domain", "model", "log", "debug", "population", "rfac"] + }, + "uniqueItems": true + }, + "keep_best": {"type": "integer"}, + "keep_levels": {"type": "integer"}, + "time_limit": { + "type": "number", + "title": "time limit in hours", + "minimum": 0 + }, + "log_level": { + "type": "string", + "enum": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + }, + "cluster_generator": { + "type": "object", + "description": "class name and initial values of cluster generator attributes", + "examples": ["TwoatomCluster"], + "properties": { + "__class__": { + "type": "string", + "description": "class name of cluster generator" + } + }, + "required": ["__class__"] + }, + "atomic_scattering_factory": { + "type": "string", + "enum": ["InternalAtomicCalculator", "PhagenCalculator"] + }, + "multiple_scattering_factory": { + "type": "string", + "enum": ["EdacCalculator", "TestCalculator"] + }, + "model_space": { + "type": "object" + }, + "domains": {"type": "array", + "items": {"type": "object"}, + "minItems": 1, + "uniqueItems": true + }, + "scans": {"type": "array", + "items": { + "type": "object", + "properties": { + "__class__": { + "type": "string", + "enum": ["ScanCreator", "ScanKey", "ScanLoader", "HoloScanCreator"] + }, + "key": {"type": "string"}, + "filename": {"type": "string"}, + "emitter": {"type": "string"}, + "initial_state": {"type": "string"}, + "positions": { + "type": "object", + "properties": { + "e": {"type": ["number", "string"]}, + "t": {"type": ["number", "string"]}, + "p": {"type": ["number", "string"]}, + "a": {"type": ["number", "string"]} + } + }, + "patch": { + "type": "object", + "properties": { + "e": {"type": ["number", "string"]}, + "t": {"type": ["number", "string"]}, + "p": {"type": ["number", "string"]}, + "a": {"type": ["number", "string"]} + } + }, + "generator": {"type": "string"}, + "generator_params": {"type": "object"}, + "other_positions": { + "type": "object", + "properties": { + "e": {"type": ["number", "string"]}, + "t": {"type": ["number", "string"]}, + "p": {"type": ["number", "string"]}, + "a": {"type": ["number", "string"]} + } + }, + "is_modf": {"type": "boolean"} + }, + "required": ["__class__"] + }, + "minItems": 1, + "uniqueItems": true + }, + "optimizer_params": { + "type": "object", + "properties": { + "pop_size": {"type": "integer"}, + "seed_file": {"type": "string"}, + "seed_limit": {"type": "integer"}, + "recalc_seed": {"type": "boolean"}, + "table_source": {"type": "string"} + } + }, + "reports": { + "$ref": "file:reports.schema.json" + } + }, + "required": ["__class__", "__module__"] +} diff --git a/pmsco/schema/reports.schema.json b/pmsco/schema/reports.schema.json new file mode 100644 index 0000000..4b390a7 --- /dev/null +++ b/pmsco/schema/reports.schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:reports.schema.json", + "title": "PMSCO Reports", + "description": "PMSCO Reports", + "type": "array", + "items": { + "type": "object", + "properties": { + "__class__": { + "type": "string", + "enum": [ + "ConvergencePlot", + "GeneticPlot", + "Rfactor1DPlot", + "Rfactor2DPlot", + "SwarmPlot" + ] + }, + "enabled": { + "type": "boolean", + "default": true, + "title": "enable the report", + "description": "disabled reports will not execute at run time" + }, + "filename_format": { + "type": "string", + "examples": [ + "${base}.convergence", + "${base}-${gen}.geneticplot", + "${base}-${param0}-${param1}-${gen}.swarmplot" + ] + }, + "titlename_format": { + "type": "string", + "examples": [ + "${job_name}", + "${job_name} gen ${gen}", + "${job_name} ${param0}-${param1} gen ${gen}" + ] + }, + "params": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 2, + "maxItems": 2, + "uniqueItems": true + } + } + }, + "required": [ + "__class__" + ] + }, + "minItems": 1 +} diff --git a/pmsco/schema/runfile.schema.json b/pmsco/schema/runfile.schema.json new file mode 100644 index 0000000..ee14fc0 --- /dev/null +++ b/pmsco/schema/runfile.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:runfile.schema.json", + "title": "PMSCO Runfile", + "description": "PMSCO Runfile", + "type": "object", + "properties": { + "schedule": { + "$ref": "file:schedule.schema.json" + }, + "project": { + "$ref": "file:project.schema.json" + } + }, + "required": ["project"] +} diff --git a/pmsco/schema/schedule.schema.json b/pmsco/schema/schedule.schema.json new file mode 100644 index 0000000..db02d81 --- /dev/null +++ b/pmsco/schema/schedule.schema.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:schedule.schema.json", + "title": "PMSCO Job Schedule", + "description": "PMSCO Job Schedule", + "type": "object", + "properties": { + "__module__": { + "type": "string", + "examples": ["pmsco.schedule"] + }, + "__class__": { + "type": "string", + "examples": ["PsiRaSchedule"] + }, + "nodes": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "tasks": { + "type": "integer", + "minimum": 1, + "default": 8 + }, + "tasks_per_node": { + "type": "integer", + "minimum": 1, + "default": 8 + }, + "wall_time": { + "type": "string", + "description": "[days-]hours[:minutes[:seconds]]", + "pattern": "^([0-9]+-)?[0-9]+(:[0-9]+)?(:[0-9]+)?$", + "default": "1", + "examples": [ + "2:00", + "5-4:3:21" + ] + }, + "partition": { + "type": "string", + "description": "name of slurm partition", + "default": "" + }, + "modules": { + "type": "array", + "description": "names of system modules to load", + "items": {"type": "string"}, + "minItems": 0, + "uniqueItems": true + }, + "conda_env": { + "type": "string", + "description": "name or path of conda environment", + "default": "" + }, + "manual": { + "type": "boolean", + "default": true, + "title": "manual submission", + "description": "produce a job file that is submitted separately." + }, + "enabled": { + "type": "boolean", + "title": "enable schedule", + "default": false, + "description": "true: schedule the job, false: run it directly." + }, + "overwrite_job_dir": { + "type": "boolean", + "default": false, + "title": "overwrite job directory", + "description": "true: overwrite contents in output directory (dangerous), false (default): raise error if directory exists." + } + }, + "required": ["__class__", "__module__"] +} diff --git a/pmsco/transforms/multipoles.py b/pmsco/transforms/multipoles.py new file mode 100644 index 0000000..6d75c64 --- /dev/null +++ b/pmsco/transforms/multipoles.py @@ -0,0 +1,371 @@ +""" +@package pmsco.transforms.multipoles +Transform holoscan data to multipoles expansion. + +@author Matthias Muntwiler + +@copyright (c) 2015-24 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import logging +import numpy as np +import numpy.typing as npt +import os +import scipy.special +from typing import Any, Callable, Dict, Generator, Iterable, List, Mapping, Optional, Sequence, Set, Tuple, Union +import h5py + +from pmsco.data import analyse_holoscan_steps, create_data, DATATYPES, DTYPE_TPI + +logger = logging.getLogger(__name__) + + +class MultipoleExpansion: + """ + Multipole expansion (spherical harmonics) of hologram scans + + This class provides functions to calculate the multipole expansion of hologram scans. + + Usage + ----- + + 1. Instantiate the object + 2. Assign a scan to `holoscan`. + If you will use the `generate` method, the scan must include an intensity column. + Else, the theta and phi coordinates are sufficient. + 3. Set `lmax`. + 4. Call `generate` to calculate the alm coefficients, or assign the coefficients directly. + 5. Call `expand` to calculate the filtered pattern. + + The object keeps intermediate calculation data, e.g. spherical harmonics, to speed up further calculations. + You have to keep the object alive in order to benefit from these performance improvements. + Reuse of the spherical harmonics depends on the same theta-phi scan grid and same lmax. + The class re-calculates the internal data automatically if these conditions are not met. + + Attributes + ---------- + + _sph_harm dimensions: l, m, tp + _d_omege dimensions: l, m, tp + _alm dimensions: l, m + + """ + + def __init__(self) -> None: + """ + Initialize the object + + The scan and arrays are set to shape 0. + The default lmax is 30 and the alm array is initialized with zeros to the corresponding shape. + The spherical harmonics and volume element are not initialized. + """ + + super().__init__() + self._scan: np.ndarray = create_data((0,), datatype='TP') + self._original: np.ndarray = np.zeros(self._scan.shape) + self._expansion: np.ndarray = np.zeros(self._scan.shape) + self._lmax: int = 60 + self._alm: np.ndarray = np.zeros((self._lmax // 2 + 1, 2 * self._lmax + 1)) + self._sph_harm: Optional[np.ndarray] = None + self._d_omega: Optional[np.ndarray] = None + + @property + def holoscan(self) -> np.ndarray: + """ + Holoscan grid and original intensity pattern. + + The holoscan array can be created, e.g., from holo_grid or is taken from measured data. + 't' and 'p' contain the polar and azimuthal coordinates of the scan. + The 'i' column contains the intensity value. + + The array must contain at least the 't' and 'p' columns if you want to expand the pattern from alm. + If you want to generate the coefficients, the 'i' (intensity) column must be set as well. + + @attention: The property returns a copy of internal data. + Do not change individual values or columns directly on the array. + """ + + tpi = np.zeros(self._scan.shape, dtype=DTYPE_TPI) + tpi['t'] = self._scan['t'] + tpi['p'] = self._scan['p'] + tpi['i'] = self._original + + return tpi + + @holoscan.setter + def holoscan(self, arr: np.ndarray): + if arr.shape != self._scan.shape or \ + self._original.shape[0] == 0 or \ + np.all(np.abs(arr['i'] - self._original) < 0.01): + self._scan = create_data(arr.shape, datatype='TP') + self._discard_basis() + + self._original = arr['i'] + self._scan['t'] = arr['t'] + self._scan['p'] = arr['p'] + + @property + def expansion(self) -> np.ndarray: + """ + Return the expansion in TPI format. + + The expansion must have been calculated using the `expand` method. + + @return: Structured numpy array in TPI format (dtype=DTYPE_TPI). + """ + + tpi = create_data(self._expansion.shape, dtype=DTYPE_TPI) + tpi['t'] = self._scan['t'] + tpi['p'] = self._scan['p'] + tpi['i'] = self._expansion + + return tpi + + def get_expansion(self, datatype: Optional[str] = None, dtype: Optional[npt.DTypeLike] = None) -> np.ndarray: + """ + Return the expansion in the requested format. + + The expansion must have been calculated using the `expand` method. + Either datatype or dtype must be specified, dtype takes precedence. + + @param datatype see DATATYPES. + @param dtype see DTYPES. + Any structured array type will work. + The dtype must specify at least the `i`, `t` and `p` columns. + + @return: structured numpy array + """ + + if not datatype or datatype not in DATATYPES: + datatype = "TPI" + + tpi = create_data(self._expansion.shape, datatype=datatype, dtype=dtype) + tpi['t'] = self._scan['t'] + tpi['p'] = self._scan['p'] + tpi['i'] = self._expansion + + return tpi + + @property + def lmax(self): + """ + Maximum l angular number used + + Determines the size of the alm array and number of coefficients calculated by `generate`. + + The useful range for XPD scans is from 30 to 90. + The default is 60, which is sufficient to reproduce fine backscattering details. + """ + + return self._lmax + + @lmax.setter + def lmax(self, value): + if value != self._lmax: + self._lmax = value + self._alm = np.zeros((self._lmax // 2 + 1, 2 * self._lmax + 1)) + self._sph_harm = None + self._d_omega = None + + @property + def alm(self): + """ + Multipole coefficients a_lm + + Multipole coefficients a_lm are stored in a two-dimensional ndarray. + The shape is (lmax / 2 + 1, 2 * lmax + 1), + where the first dimension is l (0...lmax, step 2), + and the second dimension is m (-lmax...+lmax, step 1). + + There are two ways to modify this array: + by assignment of a whole array or modification of individual values. + + On assignment of a whole array, lmax is calculated from the shape of the array. + The array is copied, NaNs are converted to zeros. + Items |m| > l are ignored and should be zero. + """ + + return self._alm + + @alm.setter + def alm(self, value): + lmax = (value.shape[0] - 1) * 2 + assert value.shape == (lmax // 2 + 1, 2 * lmax + 1) + if lmax != self._lmax: + self._lmax = lmax + self._sph_harm = None + self._d_omega = None + self._alm = np.nan_to_num(value, copy=True, nan=0.0) + + def generate(self): + """ + Calculate the multipole coefficients for the stored holoscan + + Requires: lmax and holoscan + Generates: alm + + @return alm: Multipole coefficients in a two-dimensional ndarray. + The shape is (lmax / 2 + 1, 2 * lmax + 1), + where the first dimension is l (0...lmax, step 2), + and the second dimension is m (-lmax...+lmax, step 1). + Items |m| > l are 0. + """ + + if not self._check_basis(): + self._calc_basis() + + self._alm = np.sum(self._original * np.conj(self._sph_harm) * self._d_omega, axis=-1) + + return self._alm + + def expand(self, lmin: Optional[int] = None, lmax: Optional[int] = None) -> np.ndarray: + """ + Calculate the expanded pattern from coefficients + + The alm property and the `t` and `p` columns of `holoscan` must be valid. + The result is available from `expansion` and as the function result. + + The l range used in the expansion can be constricted by the lmin and lmax options. + By default, the full alm matrix is used. + + @param lmin: minimum l to include in expansion. + Allowed range: 0...self.lmax. + @param lmax: maximum l to include in expansion. + Allowed range: 0...self.lmax. + By default, all coefficients are included. + @return: intensity array versus `holoscan`. + This array contains just the intensity. + The `expansion` property provides the full scan object in TPI structure. + Other structures can be retrieved from the `get_expansion` method. + """ + + if not self._check_basis(): + self._calc_basis() + + if lmax or lmin: + alm = self._alm.copy() + if lmin is not None and 0 <= lmin <= self._lmax: + alm[0:lmin, :] = 0 + if lmax is not None and 0 <= lmax <= self._lmax: + alm[lmax+1:self._lmax+1, :] = 0 + else: + alm = self._alm + + self._expansion = np.real(np.sum(np.sum(alm * self._sph_harm.transpose((2, 0, 1)), axis=-1), axis=-1)) + + return self._expansion + + def export_alm_h5(self, filename: os.PathLike): + """ + Export the alm coefficients to an HDF5 file. + + The HDF5 file contains 3 datasets: alm, l and m. + The alm matrix is written directly as a 2D complex array + (compound type with members r and i containing the real and imaginary parts, respectively). + l and m ranges are linked as dimension scales. + + @param filename: path-like + @return: None + """ + + range_l = np.arange(0, self._lmax + 1, 2) + range_m = np.arange(-self._lmax, self._lmax + 1, 1) + with h5py.File(filename, "w") as h5: + h5["l"] = range_l + h5["m"] = range_m + h5["l"].make_scale() + h5["m"].make_scale() + h5["alm"] = self._alm + h5["alm"].dims[0].label = "l" + h5["alm"].dims[1].label = "m" + h5["alm"].dims[0].attach_scale(h5["l"]) + h5["alm"].dims[1].attach_scale(h5["m"]) + + def import_alm_h5(self, filename: os.PathLike): + """ + Import the alm coefficients from an HDF5 file. + + The file must use the same structure as created by `export_alm_h5`. + Alternatively, the real and imaginary parts can be in separate datasets "alm_r" and "alm_i", respectively. + + @param filename: path-like + @return: + """ + + with h5py.File(filename, "r") as h5: + try: + self.alm = h5["alm"][()] + except KeyError: + self.alm = complex(h5["alm_r"][()], h5["alm_i"][()]) + + def _check_basis(self): + """ + Check whether the internal basis functions are consistent with scan data. + + # sph dimensions: l, m, tp + + @return: + """ + + nl = self._lmax // 2 + 1 + nm = 2 * self._lmax + 1 + if self._sph_harm is None or self._d_omega is None: + return False + if self._sph_harm.shape[0] != nl or self._d_omega.shape[0] != nl: + return False + if self._sph_harm.shape[1] != nm or self._d_omega.shape[1] != nm: + return False + if self._sph_harm.shape[2] != self._scan.shape[0] or self._d_omega.shape[2] != self._scan.shape[0]: + return False + return True + + def _discard_basis(self): + """ + Discard the basis functions (spherical harmonics and volume element). + + This is necessary when the scan positions, lmax or shape of alm coefficients have changed. + It is not necessary after changes to the intensity column or alm values. + The basis functions will be recalculated automatically. + + @return: None + """ + + self._sph_harm = None + self._d_omega = None + + def _calc_basis(self): + """ + Calculate the basis functions. + + The basis functions include the spherical harmonics and the volume element. + They depend on the scan grid and lmax. + For better performance, the values of the basis functions are evaluated on a particular scan grid + and stored for further use. + + They have to be re-calculated whenever lmax or the scan angles have changed. + It is not necessary after changes to the intensity column or alm values. + + @return: None + """ + + range_l = np.arange(0, self._lmax + 1, 2) + range_m = np.arange(-self._lmax, self._lmax + 1, 1) + tpi = np.arange(self._scan.shape[0]) + # order of dimensions is l, m, tp + mesh_m, mesh_l, mesh_tpi = np.meshgrid(range_m, range_l, tpi) + mesh_theta = np.deg2rad(self._scan['t'][mesh_tpi]) + mesh_phi = np.deg2rad(self._scan['p'][mesh_tpi]) + + # sph_harm returns nan for bad l/m combinations + self._sph_harm = scipy.special.sph_harm(mesh_m, mesh_l, mesh_phi, mesh_theta) + self._sph_harm = np.nan_to_num(self._sph_harm, copy=False, nan=0.0) + + unique_theta, d_theta, d_phi = analyse_holoscan_steps(self._scan) + mesh_sin = np.interp(mesh_theta, unique_theta, np.sin(np.deg2rad(unique_theta))) + mesh_dtheta = np.interp(mesh_theta, unique_theta, np.deg2rad(d_theta)) + mesh_dphi = np.interp(mesh_theta, unique_theta, np.deg2rad(d_phi)) + self._d_omega = mesh_sin * mesh_dtheta * mesh_dphi diff --git a/projects/__init__.py b/projects/__init__.py deleted file mode 100644 index 1233160..0000000 --- a/projects/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'muntwiler_m' diff --git a/projects/demo/__init__.py b/projects/demo/__init__.py deleted file mode 100644 index 1233160..0000000 --- a/projects/demo/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'muntwiler_m' diff --git a/projects/twoatom/__init__.py b/projects/twoatom/__init__.py deleted file mode 100644 index 1233160..0000000 --- a/projects/twoatom/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'muntwiler_m' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f32a5b0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,92 @@ +[build-system] +build-backend = "uv_build" +requires = [ + "uv_build>=0.9.0,<0.10.0", +] + +[project] +name = "pmsco" +version = "4.2.0" +description = "PSI Multiple Scattering Cluster Optimization" +classifiers = ["Private :: Do Not Upload"] +authors = [ + { name = "Matthias Muntwiler", email = "matthias.muntwiler@psi.ch" }, + { name = "Frederik Schirdewahn", email = "frederik.schirdewahn@psi.ch" }, +] +maintainers = [ + { name = "Matthias Muntwiler", email = "matthias.muntwiler@psi.ch" }, +] +license = "Apache-2.0" +license-files = ["LICEN[CS]E*", "NOTICE*"] +requires-python = ">=3.10, <3.14" +dependencies = [ + "ase", + "fasteners", + "future", + "h5py", + "ipython", + "ipykernel", + "jsonschema", + "matplotlib", + "mpi4py", + "networkx", + "numpy >= 1.22, < 2", + "periodictable", + "scikit-learn", + "scipy", + "sqlalchemy == 1.4", + "statsmodels", + "edac", + "loess", + "phagen", +] +readme = "README.md" + +[project.optional-dependencies] +boss = [ + "aalto-boss", +] + +[project.scripts] +pmsco = "pmsco.pmsco:main" + +[dependency-groups] +dev = [ + "gitpython", + "mock", + "pynose", + "ruff>=0.14.10", +] + +[tool.uv] +required-version = ">=0.9.0" + +[tool.uv.sources] +edac = { workspace = true, editable = false } +loess = { workspace = true, editable = false } +phagen = { workspace = true, editable = false } + +[tool.uv.workspace] +members = [ + "subprojects/edac", + "subprojects/loess", + "subprojects/phagen", +] + +[tool.uv.build-backend] +module-name = "pmsco" +module-root = "" +namespace = true + +[tool.ruff.lint] +# ignore the following violations: +# E26 - block comment +# E4 - import not at top +# E5 - long lines +# E741 - l variables +# F401 - unused import +# F841 - unused local variable +# W2 - trailing whitespace +# https://docs.astral.sh/ruff/rules/ +select = ["E", "F", "W"] +ignore = ["E26","E4","E5","E741","F401","F841","W2"] diff --git a/requirements.txt b/requirements.txt index 7d2f91e..3436f87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,175 @@ -python >= 3.6 -attrdict -fasteners -numpy >= 1.13 -periodictable -statsmodels -mpi4py -nose -mock -scipy -matplotlib -future -swig -gitpython -commentjson +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml -o requirements.txt +ase==3.26.0 + # via pmsco (pyproject.toml) +asttokens==3.0.1 + # via stack-data +attrs==25.4.0 + # via + # jsonschema + # referencing +comm==0.2.3 + # via ipykernel +contourpy==1.3.3 + # via matplotlib +cycler==0.12.1 + # via matplotlib +debugpy==1.8.19 + # via ipykernel +decorator==5.2.1 + # via ipython +executing==2.2.1 + # via stack-data +fasteners==0.20 + # via pmsco (pyproject.toml) +fonttools==4.61.1 + # via matplotlib +future==1.0.0 + # via pmsco (pyproject.toml) +greenlet==3.3.0 + # via sqlalchemy +h5py==3.15.1 + # via pmsco (pyproject.toml) +ipykernel==7.1.0 + # via pmsco (pyproject.toml) +ipython==9.8.0 + # via + # pmsco (pyproject.toml) + # ipykernel +ipython-pygments-lexers==1.1.1 + # via ipython +jedi==0.19.2 + # via ipython +joblib==1.5.3 + # via scikit-learn +jsonschema==4.25.1 + # via pmsco (pyproject.toml) +jsonschema-specifications==2025.9.1 + # via jsonschema +jupyter-client==8.7.0 + # via ipykernel +jupyter-core==5.9.1 + # via + # ipykernel + # jupyter-client +kiwisolver==1.4.9 + # via matplotlib +matplotlib==3.8.4 + # via + # pmsco (pyproject.toml) + # ase +matplotlib-inline==0.2.1 + # via + # ipykernel + # ipython +mpi4py==4.1.1 + # via pmsco (pyproject.toml) +nest-asyncio==1.6.0 + # via ipykernel +networkx==3.6.1 + # via pmsco (pyproject.toml) +numpy==1.26.4 + # via + # pmsco (pyproject.toml) + # ase + # contourpy + # h5py + # loess + # matplotlib + # pandas + # patsy + # periodictable + # scikit-learn + # scipy + # statsmodels +packaging==25.0 + # via + # ipykernel + # matplotlib + # statsmodels +pandas==2.3.3 + # via statsmodels +parso==0.8.5 + # via jedi +patsy==1.0.2 + # via statsmodels +periodictable==2.0.2 + # via pmsco (pyproject.toml) +pexpect==4.9.0 + # via ipython +pillow==12.0.0 + # via matplotlib +platformdirs==4.5.1 + # via jupyter-core +prompt-toolkit==3.0.52 + # via ipython +psutil==7.1.3 + # via ipykernel +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data +pygments==2.19.2 + # via + # ipython + # ipython-pygments-lexers +pyparsing==3.2.5 + # via + # matplotlib + # periodictable +python-dateutil==2.9.0.post0 + # via + # jupyter-client + # matplotlib + # pandas +pytz==2025.2 + # via pandas +pyzmq==27.1.0 + # via + # ipykernel + # jupyter-client +referencing==0.37.0 + # via + # jsonschema + # jsonschema-specifications +rpds-py==0.30.0 + # via + # jsonschema + # referencing +scikit-learn==1.8.0 + # via pmsco (pyproject.toml) +scipy==1.12.0 + # via + # pmsco (pyproject.toml) + # ase + # scikit-learn + # statsmodels +six==1.17.0 + # via python-dateutil +sqlalchemy==1.4.0 + # via pmsco (pyproject.toml) +stack-data==0.6.3 + # via ipython +statsmodels==0.14.6 + # via pmsco (pyproject.toml) +threadpoolctl==3.6.0 + # via scikit-learn +tornado==6.5.4 + # via + # ipykernel + # jupyter-client +traitlets==5.14.3 + # via + # ipykernel + # ipython + # jupyter-client + # jupyter-core + # matplotlib-inline +typing-extensions==4.15.0 + # via + # ipython + # referencing +tzdata==2025.3 + # via pandas +wcwidth==0.2.14 + # via prompt-toolkit diff --git a/setup.py b/setup.py deleted file mode 100644 index a63386d..0000000 --- a/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python - -""" -@file package distribution information. - -preliminary - not tested. -""" - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - -config = { - 'name': "pmsco", - 'description': "PEARL Multiple-Scattering Cluster Calculation and Structural Optimization", - 'url': "https://git.psi.ch/pearl/pmsco", - 'author': "Matthias Muntwiler", - 'author_email': "matthias.muntwiler@psi.ch", - 'version': '0.1', - 'packages': ['pmsco'], - 'scripts': [], - 'install_requires': ['numpy','periodictable','statsmodels','mpi4py','nose', 'scipy'] -} - -setup(**config) diff --git a/sql/db-list-jobs.sh b/sql/db-list-jobs.sh new file mode 100755 index 0000000..99b3475 --- /dev/null +++ b/sql/db-list-jobs.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +# simple script to list projects and jobs from a pmsco database +# +# usage: +# ./db-list-jobs.sh database_path + +printf "%s\n%s;\n" ".mode column" "select projects.name as project, jobs.name as job, jobs.datetime as datetime from projects join jobs on projects.id = jobs.project_id order by project, job, datetime" | sqlite3 "$1" diff --git a/subprojects/edac/.gitignore b/subprojects/edac/.gitignore new file mode 100644 index 0000000..6ed36b5 --- /dev/null +++ b/subprojects/edac/.gitignore @@ -0,0 +1,2 @@ +*_wrap.* +build/edac.py diff --git a/subprojects/edac/meson.build b/subprojects/edac/meson.build new file mode 100644 index 0000000..f89b310 --- /dev/null +++ b/subprojects/edac/meson.build @@ -0,0 +1,59 @@ +project('edac extension module', + 'cpp', + meson_version: '>= 1.3', + default_options: [ + 'c_std=c99', + 'cpp_std=c++14', + 'fortran_std=legacy', + 'python.install_env=auto', + 'warning_level=2', + ] +) + +py_mod = import('python') +py3 = py_mod.find_installation('python3', pure: false) +py3_dep = py3.dependency() + +cpp = meson.get_compiler('cpp') + +# silence warnings in edac code +_global_cpp_args = cpp.get_supported_arguments( + '-Wno-maybe-uninitialized', + '-Wno-unused-result', + '-Wno-unused-variable', + '-Wno-unused-function', + '-Wno-write-strings', + '-Wno-dangling-else', + '-Wno-misleading-indentation', + '-Wno-format-extra-args', + '-Wno-int-in-bool-context', +) +add_project_arguments(_global_cpp_args, language : 'cpp') + +# swig wrapper + +swig = find_program('swig') +edac_module = custom_target( + input: ['src/edac.i'], + output: ['@BASENAME@_wrap.cpp', '@BASENAME@.py'], + command: [swig, '-c++', '-python', '-py3', '-o', '@OUTPUT0@', '@INPUT@'], + install: true, + install_dir: [false, py3.get_install_dir()], + ) + +# shared library + +edac_lib = py3.extension_module('_edac', + ['src/edac.cpp', edac_module], + include_directories: ['src'], + dependencies : py3_dep, + install: true, +) + +edac_dep = declare_dependency( + sources : [edac_module[1]], + dependencies : py3_dep, + link_with : edac_lib, +) + +test('edac', py3, args : ['-c', 'from edac import run_script; exit(0)']) diff --git a/subprojects/edac/pyproject.toml b/subprojects/edac/pyproject.toml new file mode 100644 index 0000000..1e08be1 --- /dev/null +++ b/subprojects/edac/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +build-backend = 'mesonpy' +requires = [ + "charset-normalizer", + "meson>=1.6.0", + "meson-python>=0.18", + "ninja", + "numpy >= 1.22, < 2", + "setuptools", + "swig == 4.2", +] + +[project] +name = "edac" +version = "0.1.0" +description = "EDAC - electron diffraction in atomic clusters" +classifiers = ["Private :: Do Not Upload"] +authors = [ + { name = "F. J. García de Abajo" }, +] +maintainers = [ + { name = "Matthias Muntwiler", email = "matthias.muntwiler@psi.ch" } +] +requires-python = ">=3.10, <3.14" +dependencies = [ +] diff --git a/pmsco/edac/edac_all.cpp b/subprojects/edac/src/edac.cpp similarity index 100% rename from pmsco/edac/edac_all.cpp rename to subprojects/edac/src/edac.cpp diff --git a/pmsco/edac/edac_all.i b/subprojects/edac/src/edac.i similarity index 100% rename from pmsco/edac/edac_all.i rename to subprojects/edac/src/edac.i diff --git a/pmsco/edac/edac_all.patch b/subprojects/edac/src/edac.patch similarity index 100% rename from pmsco/edac/edac_all.patch rename to subprojects/edac/src/edac.patch diff --git a/pmsco/loess/.gitignore b/subprojects/loess/.gitignore similarity index 100% rename from pmsco/loess/.gitignore rename to subprojects/loess/.gitignore diff --git a/subprojects/loess/meson.build b/subprojects/loess/meson.build new file mode 100644 index 0000000..82ae1eb --- /dev/null +++ b/subprojects/loess/meson.build @@ -0,0 +1,89 @@ +# the loess python module is built using swig +# +# Compiling this module requires a matching numpy.i include file from +# https://github.com/numpy/numpy/blob/main/tools/swig/numpy.i + +project('loess', 'c', 'fortran', + version : '0.3', + meson_version: '>= 1.3', + default_options: [ + 'c_std=c99', + 'cpp_std=c++14', + 'fortran_std=legacy', + 'python.install_env=auto', + 'warning_level=2', + ] + ) + +py_mod = import('python') +py3 = py_mod.find_installation('python3') +py3_dep = py3.dependency() + +ff = meson.get_compiler('fortran') +blas_dep = ff.find_library('blas', required: true) + +# silence fortran warnings +_global_ff_args = ff.get_supported_arguments( + '-fallow-argument-mismatch', + '-Wno-conversion', + '-Wno-unused-dummy-argument', + '-Wno-unused-variable', + '-Wno-unused-parameter', + '-Wno-compare-reals', + '-Wno-intrinsic-shadow', + '-Wno-return-type', + '-Wno-do-subscript', + '-Wno-argument-mismatch', +) +add_project_arguments(_global_ff_args, language : 'fortran') + +# silence c warnings +cc = meson.get_compiler('c') +_global_cc_args = cc.get_supported_arguments( + '-Wno-argument-mismatch', + '-Wno-dangling-else', + '-Wno-format-extra-args', + '-Wno-implicit-function-declaration', + '-Wno-implicit-int', + '-Wno-int-in-bool-context', + '-Wno-maybe-uninitialized', + '-Wno-misleading-indentation', + '-Wno-return-type', + '-Wno-unused-result', + '-Wno-unused-variable', + '-Wno-unused-function', + '-Wno-unused-parameter', + '-Wno-write-strings', +) +add_project_arguments(_global_cc_args, language : 'c') + +# swig wrapper + +swig = find_program('swig') +loess_module = custom_target( + input: ['src/loess.i'], + output: ['@BASENAME@_wrap.c', '@BASENAME@.py'], + command: [swig, '-python', '-py3', '-o', '@OUTPUT0@', '@INPUT@'], + install: true, + install_dir: [false, py3.get_install_dir()], + ) + +# shared library +loess_src = ['src/loess.c', 'src/loessc.c', 'src/predict.c', 'src/misc.c', 'src/loessf.f', 'src/dqrsl.f', 'src/dsvdc.f', 'src/fix_main.c', loess_module] + +numpy_dep = dependency('numpy') + +loess_lib = py3.extension_module('_loess', + loess_src, + include_directories: ['src'], + dependencies : [py3_dep, blas_dep, numpy_dep], + install: true, +) + +loess_dep = declare_dependency( + sources : [loess_module[1]], + dependencies : [py3_dep, blas_dep], + link_with : loess_lib, +) + +test('loess', py3, args : ['-c', 'from loess import loess; exit(0)']) diff --git a/subprojects/loess/pyproject.toml b/subprojects/loess/pyproject.toml new file mode 100644 index 0000000..eb3ee87 --- /dev/null +++ b/subprojects/loess/pyproject.toml @@ -0,0 +1,31 @@ +[build-system] +build-backend = 'mesonpy' +requires = [ + "charset-normalizer", + "meson>=1.6.0", + "meson-python>=0.18", + "ninja", + "numpy >= 1.22, < 2", + "setuptools", + "swig == 4.2", +] + +[project] +name = "loess" +version = "0.1.0" +description = "LOESS - locally-weighted regression (AT&T netlib)" +classifiers = ["Private :: Do Not Upload"] +authors = [ + { name = "William S. Cleveland" }, + { name = "Eric Grosse" }, + { name = "Ming-Jen Shyu" }, + ] +maintainers = [ + { name = "Matthias Muntwiler", email = "matthias.muntwiler@psi.ch" } +] +readme = {file = "src/README", content-type = "text/plain"} +license-files = ["src/README"] +requires-python = ">=3.10, <3.14" +dependencies = [ + "numpy >= 1.22, < 2", +] diff --git a/pmsco/loess/README b/subprojects/loess/src/README similarity index 100% rename from pmsco/loess/README rename to subprojects/loess/src/README diff --git a/pmsco/loess/S.h b/subprojects/loess/src/S.h similarity index 100% rename from pmsco/loess/S.h rename to subprojects/loess/src/S.h diff --git a/pmsco/loess/air.c b/subprojects/loess/src/air.c similarity index 100% rename from pmsco/loess/air.c rename to subprojects/loess/src/air.c diff --git a/pmsco/loess/changes b/subprojects/loess/src/changes similarity index 100% rename from pmsco/loess/changes rename to subprojects/loess/src/changes diff --git a/pmsco/loess/cloess.ps b/subprojects/loess/src/cloess.ps similarity index 100% rename from pmsco/loess/cloess.ps rename to subprojects/loess/src/cloess.ps diff --git a/pmsco/loess/depend.ps b/subprojects/loess/src/depend.ps similarity index 100% rename from pmsco/loess/depend.ps rename to subprojects/loess/src/depend.ps diff --git a/pmsco/loess/dqrsl.f b/subprojects/loess/src/dqrsl.f similarity index 100% rename from pmsco/loess/dqrsl.f rename to subprojects/loess/src/dqrsl.f diff --git a/pmsco/loess/dsvdc.f b/subprojects/loess/src/dsvdc.f similarity index 100% rename from pmsco/loess/dsvdc.f rename to subprojects/loess/src/dsvdc.f diff --git a/pmsco/loess/ethanol.c b/subprojects/loess/src/ethanol.c similarity index 100% rename from pmsco/loess/ethanol.c rename to subprojects/loess/src/ethanol.c diff --git a/pmsco/loess/fix_main.c b/subprojects/loess/src/fix_main.c similarity index 100% rename from pmsco/loess/fix_main.c rename to subprojects/loess/src/fix_main.c diff --git a/pmsco/loess/galaxy.c b/subprojects/loess/src/galaxy.c similarity index 100% rename from pmsco/loess/galaxy.c rename to subprojects/loess/src/galaxy.c diff --git a/pmsco/loess/gas.c b/subprojects/loess/src/gas.c similarity index 100% rename from pmsco/loess/gas.c rename to subprojects/loess/src/gas.c diff --git a/pmsco/loess/loess.c b/subprojects/loess/src/loess.c similarity index 100% rename from pmsco/loess/loess.c rename to subprojects/loess/src/loess.c diff --git a/pmsco/loess/loess.h b/subprojects/loess/src/loess.h similarity index 100% rename from pmsco/loess/loess.h rename to subprojects/loess/src/loess.h diff --git a/pmsco/loess/loess.i b/subprojects/loess/src/loess.i similarity index 100% rename from pmsco/loess/loess.i rename to subprojects/loess/src/loess.i diff --git a/pmsco/loess/loess.m b/subprojects/loess/src/loess.m similarity index 100% rename from pmsco/loess/loess.m rename to subprojects/loess/src/loess.m diff --git a/pmsco/loess/loessc.c b/subprojects/loess/src/loessc.c similarity index 100% rename from pmsco/loess/loessc.c rename to subprojects/loess/src/loessc.c diff --git a/pmsco/loess/loessf.f b/subprojects/loess/src/loessf.f similarity index 100% rename from pmsco/loess/loessf.f rename to subprojects/loess/src/loessf.f diff --git a/pmsco/loess/loessf.m b/subprojects/loess/src/loessf.m similarity index 100% rename from pmsco/loess/loessf.m rename to subprojects/loess/src/loessf.m diff --git a/pmsco/loess/madeup.c b/subprojects/loess/src/madeup.c similarity index 100% rename from pmsco/loess/madeup.c rename to subprojects/loess/src/madeup.c diff --git a/pmsco/loess/misc.c b/subprojects/loess/src/misc.c similarity index 100% rename from pmsco/loess/misc.c rename to subprojects/loess/src/misc.c diff --git a/pmsco/loess/numpy.i b/subprojects/loess/src/numpy.i similarity index 90% rename from pmsco/loess/numpy.i rename to subprojects/loess/src/numpy.i index b6a588c..7474466 100644 --- a/pmsco/loess/numpy.i +++ b/subprojects/loess/src/numpy.i @@ -48,7 +48,7 @@ %fragment("NumPy_Backward_Compatibility", "header") { -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION %#define NPY_ARRAY_DEFAULT NPY_DEFAULT %#define NPY_ARRAY_FARRAY NPY_FARRAY %#define NPY_FORTRANORDER NPY_FORTRAN @@ -69,7 +69,7 @@ { /* Macros to extract array attributes. */ -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION %#define is_array(a) ((a) && PyArray_Check((PyArrayObject*)a)) %#define array_type(a) (int)(PyArray_TYPE((PyArrayObject*)a)) %#define array_numdims(a) (((PyArrayObject*)a)->nd) @@ -80,7 +80,9 @@ %#define array_data(a) (((PyArrayObject*)a)->data) %#define array_descr(a) (((PyArrayObject*)a)->descr) %#define array_flags(a) (((PyArrayObject*)a)->flags) +%#define array_clearflags(a,f) (((PyArrayObject*)a)->flags) &= ~f %#define array_enableflags(a,f) (((PyArrayObject*)a)->flags) = f +%#define array_is_fortran(a) (PyArray_ISFORTRAN((PyArrayObject*)a)) %#else %#define is_array(a) ((a) && PyArray_Check(a)) %#define array_type(a) PyArray_TYPE((PyArrayObject*)a) @@ -93,10 +95,11 @@ %#define array_descr(a) PyArray_DESCR((PyArrayObject*)a) %#define array_flags(a) PyArray_FLAGS((PyArrayObject*)a) %#define array_enableflags(a,f) PyArray_ENABLEFLAGS((PyArrayObject*)a,f) +%#define array_clearflags(a,f) PyArray_CLEARFLAGS((PyArrayObject*)a,f) +%#define array_is_fortran(a) (PyArray_IS_F_CONTIGUOUS((PyArrayObject*)a)) %#endif %#define array_is_contiguous(a) (PyArray_ISCONTIGUOUS((PyArrayObject*)a)) %#define array_is_native(a) (PyArray_ISNOTSWAPPED((PyArrayObject*)a)) -%#define array_is_fortran(a) (PyArray_ISFORTRAN((PyArrayObject*)a)) } /**********************************************************************/ @@ -111,19 +114,14 @@ if (py_obj == NULL ) return "C NULL value"; if (py_obj == Py_None ) return "Python None" ; if (PyCallable_Check(py_obj)) return "callable" ; - if (PyString_Check( py_obj)) return "string" ; - if (PyInt_Check( py_obj)) return "int" ; + if (PyBytes_Check( py_obj)) return "string" ; + if (PyLong_Check( py_obj)) return "int" ; if (PyFloat_Check( py_obj)) return "float" ; if (PyDict_Check( py_obj)) return "dict" ; if (PyList_Check( py_obj)) return "list" ; if (PyTuple_Check( py_obj)) return "tuple" ; -%#if PY_MAJOR_VERSION < 3 - if (PyFile_Check( py_obj)) return "file" ; - if (PyModule_Check( py_obj)) return "module" ; - if (PyInstance_Check(py_obj)) return "instance" ; -%#endif - return "unkown type"; + return "unknown type"; } /* Given a NumPy typecode, return a string describing the type. @@ -167,13 +165,11 @@ return PyArray_EquivTypenums(actual_type, desired_type); } -%#ifdef SWIGPY_USE_CAPSULE - void free_cap(PyObject * cap) +void free_cap(PyObject * cap) { void* array = (void*) PyCapsule_GetPointer(cap,SWIGPY_CAPSULE_NAME); if (array != NULL) free(array); } -%#endif } @@ -295,7 +291,11 @@ Py_INCREF(array_descr(ary)); result = (PyArrayObject*) PyArray_FromArray(ary, array_descr(ary), +%#if NPY_API_VERSION < NPY_1_7_API_VERSION NPY_FORTRANORDER); +%#else + NPY_ARRAY_F_CONTIGUOUS); +%#endif *is_new_object = 1; } return result; @@ -480,7 +480,7 @@ { int i; int success = 1; - int len; + size_t len; char desired_dims[255] = "["; char s[255]; char actual_dims[255] = "["; @@ -522,7 +522,7 @@ return success; } - /* Require the given PyArrayObject to to be Fortran ordered. If the + /* Require the given PyArrayObject to be Fortran ordered. If the * the PyArrayObject is already Fortran ordered, do nothing. Else, * set the Fortran ordering flag and recompute the strides. */ @@ -533,7 +533,13 @@ int i; npy_intp * strides = array_strides(ary); if (array_is_fortran(ary)) return success; + int n_non_one = 0; /* Set the Fortran ordered flag */ + const npy_intp *dims = array_dimensions(ary); + for (i=0; i < nd; ++i) + n_non_one += (dims[i] != 1) ? 1 : 0; + if (n_non_one > 1) + array_clearflags(ary,NPY_ARRAY_CARRAY); array_enableflags(ary,NPY_ARRAY_FARRAY); /* Recompute the strides */ strides[0] = strides[nd-1]; @@ -1983,7 +1989,7 @@ %typemap(argout) (DATA_TYPE ARGOUT_ARRAY1[ANY]) { - $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum); + $result = SWIG_AppendOutput($result,(PyObject*)array$argnum); } /* Typemap suite for (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1) @@ -1994,7 +2000,7 @@ (PyObject* array = NULL) { npy_intp dims[1]; - if (!PyInt_Check($input)) + if (!PyLong_Check($input)) { const char* typestring = pytype_string($input); PyErr_Format(PyExc_TypeError, @@ -2002,7 +2008,8 @@ typestring); SWIG_fail; } - $2 = (DIM_TYPE) PyInt_AsLong($input); + $2 = (DIM_TYPE) PyLong_AsSsize_t($input); + if ($2 == -1 && PyErr_Occurred()) SWIG_fail; dims[0] = (npy_intp) $2; array = PyArray_SimpleNew(1, dims, DATA_TYPECODE); if (!array) SWIG_fail; @@ -2011,7 +2018,7 @@ %typemap(argout) (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1) { - $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum); + $result = SWIG_AppendOutput($result,(PyObject*)array$argnum); } /* Typemap suite for (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1) @@ -2022,7 +2029,7 @@ (PyObject* array = NULL) { npy_intp dims[1]; - if (!PyInt_Check($input)) + if (!PyLong_Check($input)) { const char* typestring = pytype_string($input); PyErr_Format(PyExc_TypeError, @@ -2030,7 +2037,8 @@ typestring); SWIG_fail; } - $1 = (DIM_TYPE) PyInt_AsLong($input); + $1 = (DIM_TYPE) PyLong_AsSsize_t($input); + if ($1 == -1 && PyErr_Occurred()) SWIG_fail; dims[0] = (npy_intp) $1; array = PyArray_SimpleNew(1, dims, DATA_TYPECODE); if (!array) SWIG_fail; @@ -2039,7 +2047,7 @@ %typemap(argout) (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1) { - $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum); + $result = SWIG_AppendOutput($result,(PyObject*)array$argnum); } /* Typemap suite for (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY]) @@ -2057,7 +2065,7 @@ %typemap(argout) (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY]) { - $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum); + $result = SWIG_AppendOutput($result,(PyObject*)array$argnum); } /* Typemap suite for (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY]) @@ -2075,7 +2083,7 @@ %typemap(argout) (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY]) { - $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum); + $result = SWIG_AppendOutput($result,(PyObject*)array$argnum); } /* Typemap suite for (DATA_TYPE ARGOUT_ARRAY4[ANY][ANY][ANY][ANY]) @@ -2093,7 +2101,7 @@ %typemap(argout) (DATA_TYPE ARGOUT_ARRAY4[ANY][ANY][ANY][ANY]) { - $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum); + $result = SWIG_AppendOutput($result,(PyObject*)array$argnum); } /*****************************/ @@ -2118,7 +2126,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEW_ARRAY1) @@ -2139,7 +2147,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) @@ -2161,7 +2169,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_ARRAY2) @@ -2183,7 +2191,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) @@ -2205,7 +2213,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array || !require_fortran(array)) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_FARRAY2) @@ -2227,7 +2235,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array || !require_fortran(array)) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, @@ -2251,7 +2259,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, @@ -2275,7 +2283,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, @@ -2299,7 +2307,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array || !require_fortran(array)) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, @@ -2323,7 +2331,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array || !require_fortran(array)) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, @@ -2348,7 +2356,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, @@ -2373,7 +2381,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEW_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, @@ -2398,7 +2406,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array || !require_fortran(array)) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, @@ -2423,7 +2431,7 @@ PyArrayObject* array = (PyArrayObject*) obj; if (!array || !require_fortran(array)) SWIG_fail; - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /*************************************/ @@ -2449,19 +2457,15 @@ if (!array) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEWM_ARRAY1) @@ -2483,19 +2487,15 @@ if (!array) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$2), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) @@ -2518,19 +2518,15 @@ if (!array) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_ARRAY2) @@ -2553,19 +2549,15 @@ if (!array) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$3), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) @@ -2588,19 +2580,15 @@ if (!array || !require_fortran(array)) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_FARRAY2) @@ -2623,19 +2611,15 @@ if (!array || !require_fortran(array)) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$3), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, @@ -2660,19 +2644,15 @@ if (!array) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, @@ -2697,19 +2677,15 @@ if (!array) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$4), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, @@ -2734,19 +2710,15 @@ if (!array || !require_fortran(array)) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, @@ -2771,19 +2743,15 @@ if (!array || !require_fortran(array)) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$4), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, @@ -2809,19 +2777,15 @@ if (!array) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, @@ -2847,171 +2811,15 @@ if (!array) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$5), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); -} - -/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, - DIM_TYPE* DIM3, DIM_TYPE* DIM4) - */ -%typemap(in,numinputs=0) - (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1 , DIM_TYPE* DIM2 , DIM_TYPE* DIM3 , DIM_TYPE* DIM4 ) - (DATA_TYPE* data_temp = NULL , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp) -{ - $1 = &data_temp; - $2 = &dim1_temp; - $3 = &dim2_temp; - $4 = &dim3_temp; - $5 = &dim4_temp; -} -%typemap(argout, - fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements,NumPy_Utilities") - (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3) -{ - npy_intp dims[4] = { *$2, *$3, *$4 , *$5 }; - PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$1)); - PyArrayObject* array = (PyArrayObject*) obj; - - if (!array || !require_fortran(array)) SWIG_fail; - -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif - -%#if NPY_API_VERSION < 0x00000007 - PyArray_BASE(array) = cap; -%#else - PyArray_SetBaseObject(array,cap); -%#endif - - $result = SWIG_Python_AppendOutput($result,obj); -} - -/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, - DATA_TYPE** ARGOUTVIEWM_FARRAY4) - */ -%typemap(in,numinputs=0) - (DIM_TYPE* DIM1 , DIM_TYPE* DIM2 , DIM_TYPE* DIM3 , DIM_TYPE* DIM4 , DATA_TYPE** ARGOUTVIEWM_FARRAY4) - (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp, DATA_TYPE* data_temp = NULL ) -{ - $1 = &dim1_temp; - $2 = &dim2_temp; - $3 = &dim3_temp; - $4 = &dim4_temp; - $5 = &data_temp; -} -%typemap(argout, - fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements,NumPy_Utilities") - (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEWM_FARRAY4) -{ - npy_intp dims[4] = { *$1, *$2, *$3 , *$4 }; - PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$5)); - PyArrayObject* array = (PyArrayObject*) obj; - - if (!array || !require_fortran(array)) SWIG_fail; - -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif - -%#if NPY_API_VERSION < 0x00000007 - PyArray_BASE(array) = cap; -%#else - PyArray_SetBaseObject(array,cap); -%#endif - - $result = SWIG_Python_AppendOutput($result,obj); -} - -/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, - DIM_TYPE* DIM3, DIM_TYPE* DIM4) - */ -%typemap(in,numinputs=0) - (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1 , DIM_TYPE* DIM2 , DIM_TYPE* DIM3 , DIM_TYPE* DIM4 ) - (DATA_TYPE* data_temp = NULL , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp) -{ - $1 = &data_temp; - $2 = &dim1_temp; - $3 = &dim2_temp; - $4 = &dim3_temp; - $5 = &dim4_temp; -} -%typemap(argout, - fragment="NumPy_Backward_Compatibility,NumPy_Utilities") - (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4) -{ - npy_intp dims[4] = { *$2, *$3, *$4 , *$5 }; - PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$1)); - PyArrayObject* array = (PyArrayObject*) obj; - - if (!array) SWIG_fail; - -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif - -%#if NPY_API_VERSION < 0x00000007 - PyArray_BASE(array) = cap; -%#else - PyArray_SetBaseObject(array,cap); -%#endif - - $result = SWIG_Python_AppendOutput($result,obj); -} - -/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, - DATA_TYPE** ARGOUTVIEWM_ARRAY4) - */ -%typemap(in,numinputs=0) - (DIM_TYPE* DIM1 , DIM_TYPE* DIM2 , DIM_TYPE* DIM3 , DIM_TYPE* DIM4 , DATA_TYPE** ARGOUTVIEWM_ARRAY4) - (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp, DATA_TYPE* data_temp = NULL ) -{ - $1 = &dim1_temp; - $2 = &dim2_temp; - $3 = &dim3_temp; - $4 = &dim4_temp; - $5 = &data_temp; -} -%typemap(argout, - fragment="NumPy_Backward_Compatibility,NumPy_Utilities") - (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEWM_ARRAY4) -{ - npy_intp dims[4] = { *$1, *$2, *$3 , *$4 }; - PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$5)); - PyArrayObject* array = (PyArrayObject*) obj; - - if (!array) SWIG_fail; - -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif - -%#if NPY_API_VERSION < 0x00000007 - PyArray_BASE(array) = cap; -%#else - PyArray_SetBaseObject(array,cap); -%#endif - - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, @@ -3037,19 +2845,15 @@ if (!array || !require_fortran(array)) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, @@ -3075,19 +2879,15 @@ if (!array || !require_fortran(array)) SWIG_fail; -%#ifdef SWIGPY_USE_CAPSULE - PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap); -%#else - PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free); -%#endif +PyObject* cap = PyCapsule_New((void*)(*$5), SWIGPY_CAPSULE_NAME, free_cap); -%#if NPY_API_VERSION < 0x00000007 +%#if NPY_API_VERSION < NPY_1_7_API_VERSION PyArray_BASE(array) = cap; %#else PyArray_SetBaseObject(array,cap); %#endif - $result = SWIG_Python_AppendOutput($result,obj); + $result = SWIG_AppendOutput($result,obj); } /**************************************/ @@ -3134,6 +2934,15 @@ %numpy_typemaps(unsigned long long, NPY_ULONGLONG, int) %numpy_typemaps(float , NPY_FLOAT , int) %numpy_typemaps(double , NPY_DOUBLE , int) +%numpy_typemaps(int8_t , NPY_INT8 , int) +%numpy_typemaps(int16_t , NPY_INT16 , int) +%numpy_typemaps(int32_t , NPY_INT32 , int) +%numpy_typemaps(int64_t , NPY_INT64 , int) +%numpy_typemaps(uint8_t , NPY_UINT8 , int) +%numpy_typemaps(uint16_t , NPY_UINT16 , int) +%numpy_typemaps(uint32_t , NPY_UINT32 , int) +%numpy_typemaps(uint64_t , NPY_UINT64 , int) + /* *************************************************************** * The follow macro expansion does not work, because C++ bool is 4 diff --git a/pmsco/loess/predict.c b/subprojects/loess/src/predict.c similarity index 100% rename from pmsco/loess/predict.c rename to subprojects/loess/src/predict.c diff --git a/pmsco/loess/predict.m b/subprojects/loess/src/predict.m similarity index 100% rename from pmsco/loess/predict.m rename to subprojects/loess/src/predict.m diff --git a/pmsco/loess/struct.m b/subprojects/loess/src/struct.m similarity index 100% rename from pmsco/loess/struct.m rename to subprojects/loess/src/struct.m diff --git a/pmsco/loess/supp.f b/subprojects/loess/src/supp.f similarity index 100% rename from pmsco/loess/supp.f rename to subprojects/loess/src/supp.f diff --git a/subprojects/phagen/meson.build b/subprojects/phagen/meson.build new file mode 100644 index 0000000..fb660eb --- /dev/null +++ b/subprojects/phagen/meson.build @@ -0,0 +1,129 @@ +# meson build file based on https://numpy.org/doc/stable/f2py/buildtools/meson.html +# the phagen python module is built using numpy.f2py + +# note when importing a new phagen version: +# in the phagen_scf.f file, replace "program phagen" with "subroutine phagen" + +project('phagen', 'c', 'fortran', + version : '2.3', + meson_version: '>= 1.3', + default_options: [ + 'c_std=c99', + 'cpp_std=c++14', + 'fortran_std=legacy', + 'python.install_env=auto', + 'warning_level=2' + ] +) + +fs_mod = import('fs') +py_mod = import('python') +py3 = py_mod.find_installation('python3') +py3_dep = py3.dependency() +message(py3.full_path()) +message(py3.get_install_dir()) + +# silence fortran warnings +ff = meson.get_compiler('fortran') +_global_ff_args = ff.get_supported_arguments( + '-fallow-argument-mismatch', + '-Wno-argument-mismatch', + '-Wno-compare-reals', + '-Wno-conversion', + '-Wno-do-subscript', + '-Wno-intrinsic-shadow', + '-Wno-line-truncation', + '-Wno-maybe-uninitialized', + '-Wno-return-type', + '-Wno-tabs', + '-Wno-unused-dummy-argument', + '-Wno-unused-label', + '-Wno-unused-parameter', + '-Wno-unused-variable', +) +add_project_arguments(_global_ff_args, language : 'fortran') + +# silence c warnings +cc = meson.get_compiler('c') +_global_cc_args = cc.get_supported_arguments( + '-Wno-cast-function-type', + '-Wno-dangling-else', + '-Wno-format-extra-args', + '-Wno-implicit-function-declaration', + '-Wno-implicit-int', + '-Wno-int-in-bool-context', + '-Wno-maybe-uninitialized', + '-Wno-misleading-indentation', + '-Wno-missing-field-initializers', + '-Wno-return-type', + '-Wno-unused-result', + '-Wno-unused-variable', + '-Wno-unused-function', + '-Wno-unused-parameter', + '-Wno-write-strings', +) +add_project_arguments(_global_cc_args, language : 'c') + +# work is in progress in the meson project to simplify dependency detection. +# as of november 2025, it seems to work for numpy but not for numpy.f2py. +# np_dep = dependency('numpy') +# np_dep = dependency('numpy', modules: 'f2py') +# cf. https://github.com/mesonbuild/meson/issues/9598 + +# the following is adapted from scipy +# https://github.com/scipy/scipy/blob/beb6b2fbe656eae47b117fa8c416cdd88c27c98e/scipy/meson.build + +_incdir_numpy_abs = run_command(py3, + ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'], + check : true +).stdout().strip() +incdir_numpy = fs_mod.relative_to(_incdir_numpy_abs, '.') +inc_np = include_directories(incdir_numpy) +numpy_nodepr_api = ['-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION'] +np_dep = declare_dependency(include_directories: inc_np, compile_args: numpy_nodepr_api) + +incdir_f2py = run_command(py3, + ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'], + check : true +).stdout().strip() +incdir_f2py = fs_mod.relative_to(incdir_f2py, '.') +inc_f2py = include_directories(incdir_f2py) +fortranobject_c = incdir_f2py / 'fortranobject.c' + +fortranobject_lib = static_library('_fortranobject', + fortranobject_c, + c_args: numpy_nodepr_api, + dependencies: py3_dep, + include_directories: [inc_np, inc_f2py], + gnu_symbol_visibility: 'hidden', +) +fortranobject_dep = declare_dependency( + link_with: fortranobject_lib, + include_directories: [inc_np, inc_f2py], +) + +f2py = find_program('f2py') + +wrapper = custom_target('phagenmodule.c', + input : ['src/phagen_scf.f'], + depend_files : ['src/msxas3.inc', 'src/msxasc3.inc'], + output : ['phagenmodule.c', 'phagen-f2pywrappers.f'], + command : [py3, '-m', 'numpy.f2py', '@INPUT@', '--verbose', '-m', 'phagen', '--build-dir', '@OUTDIR@', 'only:', 'phagen'] +) + +source = ['src/phagen_scf.f', wrapper] + +phagen_lib = py3.extension_module('phagen', + source, + include_directories: ['src'], + dependencies : [py3_dep, np_dep, fortranobject_dep], + link_language: 'fortran', + install : true +) + +phagen_dep = declare_dependency( + dependencies : py3_dep, + link_with : phagen_lib, +) + +test('phagen', py3, args : ['-c', 'from phagen import phagen; exit(0)']) diff --git a/subprojects/phagen/pyproject.toml b/subprojects/phagen/pyproject.toml new file mode 100644 index 0000000..2b5a5ff --- /dev/null +++ b/subprojects/phagen/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +build-backend = 'mesonpy' +requires = [ + "charset-normalizer", + "meson>=1.6.0", + "meson-python>=0.18", + "ninja", + "numpy >= 1.22, < 2", + "setuptools", + "swig == 4.2", +] + +[project] +name = "phagen" +version = "0.1.0" +description = "PHAGEN - electron scattering phase calculator" +classifiers = ["Private :: Do Not Upload"] +authors = [ + { name = "C. R. Natoli" }, + { name = "D. Sébilleau" }, + ] +maintainers = [ + { name = "Matthias Muntwiler", email = "matthias.muntwiler@psi.ch" } +] +requires-python = ">=3.10, <3.14" +dependencies = [ +] diff --git a/subprojects/phagen/src/msxas3.inc b/subprojects/phagen/src/msxas3.inc new file mode 100644 index 0000000..2151d02 --- /dev/null +++ b/subprojects/phagen/src/msxas3.inc @@ -0,0 +1,69 @@ +c.. dimensions for the program + integer ua_ + parameter ( nat_ = 4000, + $ ua_ = 4000, + $ neq_ = 48 ) +C +C where : +c +c nat_ maximum number of atoms expected in any +c molecule of interest (including an outer +c sphere. an even number is suggested). +c +c ua_ maximum number of nda's (unique, or +c symmetry-distinct atoms) expected in any +c molecule (including an outer sphere). +c +c neq_ maximum number of atoms expected in +c any symmetry-equivalent set (including +c the nda of the set) +c +c Warning: This version of msxas3.inc with program +c phagen_scf_2.2_dp.f +c +c................................................................... +c dimensioning cont and cont_sub source program +c................................................................... +c + integer fl_, rdx_ +c + parameter ( rdx_ = 1600, + $ lmax_ = 50, + $ npss = lmax_ + 2, + $ fl_ = 2*npss + 1, + $ nef_ = 10, + $ lexp_ = 10, + $ nep_ = 500 ) +c +c where : +c +c rdx_ number of points of the linear-log mesh +c +c lmax_ the maximum l-value used on any sphere +c (suggested value 5 or less if running valence dos section of +c phagen, 60 when calculating atomic t_l) +c +c nef_ effective number of atoms used in the transition +c matrix elements of eels. Put = 1 if not doing a eels +c calculation (suggested value 12) +c +c lexp_ lmax in the expansion of coulomb interaction plus one! temporary +c +c nep_ the maximum number of energy points for which phase +c shifts will be computed. +c +c....................................................................... +c multiple scattering paths, xn programs dimensioning +c....................................................................... +c +c + parameter (natoms=nat_) +c +c +c where: +c +c natoms = number of centers in the system +c +c +c................................................................... + diff --git a/subprojects/phagen/src/msxasc3.inc b/subprojects/phagen/src/msxasc3.inc new file mode 100644 index 0000000..95f5dc0 --- /dev/null +++ b/subprojects/phagen/src/msxasc3.inc @@ -0,0 +1,27 @@ + logical vinput, nosym, tdl + character*5 potype + character*1 optrsh + character*2 edge,charelx,edge1,edge2,potgen,relc + character*3 calctype,expmode,eikappr,enunit + character*4 coor + character*6 norman + character*7 ionzst + integer absorber,hole,l2h,hole1,hole2 + dimension nz(natoms) + dimension c(natoms,3), rad(natoms), redf(natoms) + dimension neqat(natoms) + dimension nk0(0:lmax_) +c.....Warning: when reordering common/options/, reorder also the same common in +c.....subroutine inpot + common/options/rsh,ovlpfac,vc0,rs0,vinput,absorber,hole,mode, + & ionzst,potype,norman,coor,charelx,edge,potgen,lmax_mode, + & lmaxt,relc,eikappr,optrsh,nosym,tdl + common/atoms/c,rad,redf,charge_ion(100),nat,nz,neqat +c common/azimuth/lin,lmax + common/auger/calctype,expmode,edge1,edge2 + common/auger1/lin1,lin2,hole1,hole2,l2h + common/funit/idat,iwr,iphas,iedl0,iwf + common/constant/antoau,ev,pi,pi4,pif,zero,thresh,nk0 +c.................................................................... +c rpot = if real potential is to be used +c..................................................................... diff --git a/subprojects/phagen/src/phagen_scf.f b/subprojects/phagen/src/phagen_scf.f new file mode 100644 index 0000000..e738a66 --- /dev/null +++ b/subprojects/phagen/src/phagen_scf.f @@ -0,0 +1,23867 @@ +C +CST ==> Phagen to python shared object modifications +CST Phagen becomes a subroutine in a library +CST PROGRAM PHAGEN + subroutine phagen() +CST Phagen to python shared object modifications <== +C +C .................................... +C .. .. +C .. Generates atomic phase shifts .. +C .. for inequivalent atoms in a .. +C .. given cluster. Prototypical .. +C .. atoms selected automatically. .. +C .. Muffin-tin radii and type of .. +C .. final state potential selected .. +C .. via input option .. +C .. .. +C .. By C.R. Natoli 15/10/93 .. +C .. .. +C .. This version can handle ES .. +C .. ES = Empty Spheres 28/09/2007 .. +C .. .. +C .. Scalar-relativistic version .. +C .. with spin-orbit selection .. +C .. by C.R. Natoli 9 june 2011 .. +C .. .. +C .................................... +C .................................... +C +C .. INCOMING WAVE BOUNDARY CONDITIONS +C +C .................................... +C +C Version history: +C +C bug corrected in subroutine +C GET_CORE_STATE +C (FDP 18th May 2006) +C +C bug corrected in subroutine +C ALPHA0 (DS : 7th May 2007) +C 2nd dimension r: 150 ---> UA_ +C +C LEED case (calctype = 'led') +C added (DS : 30th May 2007). +C +C bug corrected in subroutine +C SETEQS (DS+CRN 30th May 2007) : +C z_shift=5.0 and i_z_shift=5 +C instead of 0.0 and 0. +C +C bug corrected in subroutines +C MOLDAT,GRPNEI,WRIDAT : +C NEIMAX set to nat_ instead +C of 350 in PARAMETER statement +C (FDP+DS 4th June 2007) +C +C all error output redirected to +C unit 6 (DS 4th March 2008). +C +C modified to handle high Z elements +C (CRN : september 2008) +C +C cleaned : DS 17th November 2008 +C +C modified to impose lmaxt externally +C (CRN : july 2009) +C +C modified to include quadrupole +C radial matrix elements +C (CRN : june 2012) +C +C File formats for radial integrals +C modified (DS 8th january 2013) +C +C modified to introduce t-matrix +C calculation in the eikonal approximation +C (CRN : march 2013) +C +C bug corrected in routine linlogmesh: rhon ---> r_sub +C (CRN : april 2013) +C +C modified to calculate tmatrix, radial integrals +C and atomic cross sections on linearlog mesh +C (CRN: september 2012 and april 2013) +C +C bug corrected in routine pgenll2: complex*16 dnm. +C v potential converted to complex*16 in routines +C pgenll1m and pgenll2 +C (CRN: april 2013) +C +C bug corrected in the calculation of the total mfp = amfpt +C (CRN: april 2014) +C +C modified to calculate eels regular radial matrix elements +C (CRN: november 2014) +C +C modified to convert energy input data in data3.ms to Ryd +C (CRN: november 2014) +C +C modified to calculate eels and xas/rexs irregular radial matrix elements +C (CRN: juin 2015) +C +C modified to calculate e2e regular radial matrix elements +C (CRN: december 2015) modification in subroutine smtxllm +C statement 13824 +C +C modified to to include magnetic dipole and electric octupole +C radial integrals (CRN: may 2016) +C +C extended to energies less than the intertistial potential to calculate +C valence bound states in MsSpec. Gamma set by default to 0.0001. +C (CRN: february 2018) +C +C modified to be consistent with MsSpec-1.1 +C And MsSpec-2.0 + cleaned (DS: Jan 2019) +C +C rewritten in double precision + simplification of the subroutines +C (DS: Mar 2019) +C +C calculation of phase derivatives added in subroutine cont +C phase shifts listed for each a.m. l at all energies in the chosen range +C (CRN: Sept. 2020) +C +C .................................... + + IMPLICIT REAL*8 (A-H,O-Z) +C + INCLUDE 'msxas3.inc' + INCLUDE 'msxasc3.inc' +C +C.. Constants +C + ANTOAU = 0.52917721067D0 + PI = 3.14159265358979323846264338D0 + EV = 13.605693009D0 + ZERO = 0.0D0 +C +C.. Threshold for linearity +C + THRESH = 1.0D-4 +C +C.. Fortran I/O units +C + IDAT = 5 + IWR = 6 + IWF=32 + IPHAS = 30 + IEDL0 = 31 + IOF = 17 +C +C... Starting to write in the check file IWR +C + WRITE(IWR,1000) +CST ==> Phagen to python shared object modifications +CST Create output folders and open input file + CALL SYSTEM('mkdir -p div/wf') + CALL SYSTEM('mkdir -p plot') + CALL SYSTEM('mkdir -p tl') + CALL SYSTEM('mkdir -p clus') + OPEN(idat, FILE='../input/input.ms', STATUS='old') +CST Phagen to python shared object modifications <== +C +C... Opening the Fortran files +C +C.....Do not use units 40 and 41. They are used in subroutine CONT +C +C... Scratch files +C + OPEN(IEDL0,FILE='div/exdl0.dat',FORM='UNFORMATTED', + 1 STATUS='UNKNOWN') + OPEN(UNIT=21,FORM='UNFORMATTED',STATUS='SCRATCH') + OPEN(IOF,FILE='div/inf.xas',FORM='UNFORMATTED',STATUS='UNKNOWN') +C +C... scfdat log file +C + OPEN(UNIT=11,FILE='div/fort.11',STATUS='UNKNOWN') +C +C... non relativistic overlapped Matheiss potential +C + OPEN(UNIT=13,FILE='div/filepot.dat',STATUS='UNKNOWN') +C +C... relativistic overlapped potential +C + OPEN(UNIT=10,FILE ='div/vrel.dat',STATUS='UNKNOWN') +C +C... table of prototypical/equivalent atoms +C + OPEN(UNIT=14,FILE='div/filesym.dat',STATUS='UNKNOWN') +C +C... PED/XAS radial matrix elements +C + OPEN(UNIT=50,FILE='div/filerme.dat',STATUS='UNKNOWN') +C +C... EELS/(E,2E) radial matrix elements +C + OPEN(UNIT=56,FILE='div/eelsrme.dat',STATUS='UNKNOWN') +C +C... Output wave functions files: +C +C 1) isolated absorber +C + OPEN(UNIT=37,FILE='div/wf/wf_abs_orbitals.dat',STATUS='UNKNOWN') ! occupied WF orbitals GS + OPEN(UNIT=32,FILE='div/wf/wf_abs_excited.dat',STATUS='UNKNOWN') ! WF orbitals excited state + OPEN(UNIT=33,FILE='div/wf/wf_core_hole_hs.dat',STATUS='UNKNOWN') ! core WF HS mesh + OPEN(UNIT=34,FILE='div/wf/wf_core_hole_ll.dat',STATUS='UNKNOWN') ! core WF LL mesh +C +C 2) absorber surrounded by first neighbours +C + OPEN(UNIT=12,FILE ='div/wf/wf_abs_orb_nei.dat',STATUS='UNKNOWN') ! non-relativistic + OPEN(UNIT=15,FILE ='div/wf/wf_abs_orb_nei_rel.dat', + 1 STATUS = 'UNKNOWN') ! relativistic +C +C.....Atomic charge density +C + OPEN(UNIT=66,FILE='div/rho_tot_at.dat',STATUS='UNKNOWN') +C +C.....T-matrix files (subroutine SMTXLLM): ! linear Log mesh +C + OPEN(UNIT=45,FILE='tl/tbmat.dat',STATUS='UNKNOWN') ! eikonal approximation +C + OPEN(UNIT=70,FILE='div/tl-nr.dat',STATUS='UNKNOWN') ! non-relativistic t_l + OPEN(UNIT=80,FILE='div/tl-sr.dat',STATUS='UNKNOWN') ! scalar-relativistic t_l + OPEN(UNIT=90,FILE='div/tl-so.dat',STATUS='UNKNOWN') ! spin-orbit t_l +C +C EELS/(E,2E) T-matrix files +C + OPEN(UNIT=85,FILE='tl/tl-sr_in.dat',STATUS='UNKNOWN') ! incoming beam + OPEN(UNIT=86,FILE='tl/tl-sr_sc.dat',STATUS='UNKNOWN') ! scattered beam + OPEN(UNIT=87,FILE='tl/tl-sr_ex.dat',STATUS='UNKNOWN') ! excited beam +C +C.....Imaginary part of t_l ! for external +C ! potential + OPEN(UNIT=46,FILE='div/imagt_l.dat',STATUS='UNKNOWN') ! input +C +C.....Phase shifts files +C + OPEN(UNIT=71,FILE='div/phases-nr.dat',STATUS='UNKNOWN') !\ + OPEN(UNIT=81,FILE='div/phases-sr.dat',STATUS='UNKNOWN') ! | linear Log mesh + OPEN(UNIT=91,FILE='div/phases-so.dat',STATUS='UNKNOWN') !/ + OPEN(IPHAS,FILE='div/phases.dat',STATUS='UNKNOWN') ! Herman-Skillman mesh +C +C.....Derivatives of phase shifts with respect to energy + OPEN(UNIT=74,FILE='div/pha-derv-nr.dat',STATUS='UNKNOWN') + OPEN(UNIT=84,FILE='div/pha-derv-sr.dat',STATUS='UNKNOWN') +C.....phase shifts as a function of energy for each individual am l + OPEN(UNIT=78,FILE='div/lphases-nr.dat',STATUS='UNKNOWN') + OPEN(UNIT=88,FILE='div/lphases-sr.dat',STATUS='UNKNOWN') +C +C Storage of old t_l calculation (subroutine SMTX) ! Herman-Skillman mesh +C + OPEN(UNIT=95,FILE='div/tl_ref.dat',STATUS='UNKNOWN') +C +C Control files for linlogmesh +C + OPEN(UNIT=98,FILE='div/cshsm.dat',STATUS='UNKNOWN') + OPEN(UNIT=99,FILE='div/csllm.dat',STATUS='UNKNOWN') +C + REWIND IDAT + REWIND IWF + REWIND IPHAS + REWIND IEDL0 + REWIND IOF +C +C Read control cards +C + CALL INCTRL_V2 +C +C Read title cards +C + CALL INTIT(IOF) +C +C Read atomic coordinates cards (internal or cartesian) +C + CALL INCOOR +C +C Compute atomic phase shifts if required +C + CALL CALPHAS +C +C Normal end +C + WRITE(IWR,1100) +C +C Closing the Fortran files +C + CLOSE(70) + CLOSE(71) + CLOSE(74) + CLOSE(80) + CLOSE(81) + CLOSE(84) + CLOSE(85) + CLOSE(86) + CLOSE(87) + CLOSE(90) + CLOSE(91) + CLOSE(21) + CLOSE(60) + CLOSE( 7) + CLOSE(10) + CLOSE(12) + CLOSE(13) + CLOSE(14) + CLOSE(15) + CLOSE(50) + CLOSE(56) + CLOSE(37) + CLOSE(33) + CLOSE(34) + CLOSE(35) + CLOSE(45) + CLOSE(46) + CLOSE(IWF) + CLOSE(IPHAS) +CST ==> Phagen to python shared object modifications <== +CST explicitely close fort.55 + CLOSE(55) +CST Phagen to python shared object modifications <== +C +C Formats: +C + 1000 FORMAT(1X,65('_'),//,31X,'PHAGEN',/,1X,65('_'),/) + 1100 FORMAT(//,15X,' ** phagen terminated normally ** ',//) +C + END + +c + subroutine inctrl_v2 +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + include 'msxasc3.inc' +C +C Shells and orbitals of the primary core hole, +C and the of the two holes in the final state: +C + character*1 shell,shell1,shell2,orbital1,orbital,orbital2 +C + character*3 version +C + real*8 lambda +C + complex*16 eelsme,p1,p2,p3,ramfsr1,ramfsr2,ramfsr3 + complex*16 p3irreg,p2irreg +C +CST ==> Phagen to python shared object modifications + logical noproto +CST Phagen to python shared object modifications <== +C +C..................................................................... +C + common /continuum/ emin,emax,delta,cip,gamma,eftri,iexcpot,db + common /eels/ einc,esct,scangl,qt,lambda, + 1 eelsme(npss,npss,npss), + 2 p1(rdx_,npss,nef_),p2(rdx_,npss,nef_), + 3 p3(rdx_,npss,nef_),ramfsr1(npss,nef_), + 4 ramfsr2(npss,nef_),ramfsr3(npss,nef_), + 5 lmxels(3,ua_),p3irreg(rdx_,7),p2irreg(rdx_,7) + common /typot/ ipot + common /v_type/ version +CST ==> Phagen to python shared object modifications + common /misc/ noproto +CST Phagen to python shared object modifications <== +C +C..................................................................... +C + namelist /job/ edge,edge1,edge2,l2h,potype,norman,absorber,coor, + 1 emin,emax,delta,gamma,eftri,cip,vc0,rs0,vinput, + 2 eikappr,rsh,db,lmaxt,ovlpfac,ionzst,charelx, + 3 calctype,potgen,lmax_mode,relc,einc,esct,scangl, + 4 optrsh,enunit,lambda,expmode,nosym,version,tdl +CST ==> Phagen to python shared object modifications +CST I added an option to control the number of prototypical atoms + 5 ,noproto +CST Phagen to python shared object modifications <== +C +C..................................................................... +C +C Initialize namelist: +C + vinput = .false. + nosym = .true. + tdl = .false. + potype='hedin' ! type of exchange and correlation potential + potgen='in' ! potential internally generated or read + cip=0.0 ! ionization potential given or computed + relc='nr' ! relativistic/non relativistic + eikappr=' no' ! spherical waves vs impact parameter + coor='angs' ! unit for coordinates of input atoms + edge='k' ! core hole excited + edge1='k' ! core hole in the final state + edge2='k' ! (for Auger type spectroscopies) + lmax_mode=2 ! control of the number of basis functions used + lmaxt=60 ! impose l_max for lmax_mode = 0 + l2h=0 ! for Auger only (see user's guide) + absorber = 1 ! index of absorbing atom + charelx = 'ex' ! ground state vs excited state + norman = 'stdcrm' ! criterium for calculatuon of MT radii + ovlpfac=0.d0 ! overlap factor + ionzst='neutral' ! amount of charge transfer among between atoms + calctype='xpd' ! type of spectroscopy + expmode='cis' ! type of scan for electrons + optrsh='n' ! choice for MT radius of hydrogen + enunit='Ryd' ! energy unit +C + version='2.0' ! MsSpec version number +CST ==> Phagen to python shared object modifications + noproto = .false. +CST Phagen to python shared object modifications <== +C + vc0 = -0.7d0 + rs0 = 3.d0 +C + emin = 0.5 ! initial energy + emax = 40.0 ! final energy + delta= 0.05 ! energy step + gamma= 0.0001 ! accounts for the core hole lifetime + eftri= 0.0 + rsh = 0.0d0 ! used as a flag; set below to default in au + db = 0.01 +C +C Data initialization for calctype = 'els' or 'e2e' +C + einc= 1200.0 ! initial energy + esct= 1000.0 ! final energy + scangl= 7.0/180.0*3.1415926 + lambda = 0.d0 ! used as a flag; set below to default in au +C +C.....Definition of lmax_mode: +C +C..... lmax_mode = 0: lmaxn(na)=lmax_, independent of energy and atom number +C..... lmax_mode = 1: lmaxn(na)= km*rs(na)+1, where km=(emax)^{1/2} +C..... lmax_mode = 2: lmaxn(na)= ke*rs(na)+1, where ke=(e)^{1/2}, where +C..... e is the running energy +C +C.. Read control cards in namelist &job +C + read(idat,job) + read(idat,*) +C +C.....Convert lengths in au if coor='angs'. Coordinates will be converted +C in subroutine incoor +C + if(coor.eq.'angs'.and.lambda.ne.0.d0) then + lambda = lambda/antoau + else + lambda = 20.d0 ! in au corresponding to kappa = 0.05 + endif ! (see subroutine cont) +C + if(coor.eq.'angs'.and.rsh.ne.0) then + rsh = rsh/antoau + else + rsh = 1.0d0 ! in au + endif +C +C.....Convert all energies to Ryd (when they are inputed in eV) +C + if(enunit.eq.' ev') then + cip = cip/ev + emin = emin/ev + emax = emax/ev + delta= delta/ev + gamma= gamma/ev + eftri= eftri/ev + einc= einc/ev + esct= esct/ev + endif +C +C.....Tests for inconsistencies in the input data +C + if(lmax_mode.gt.2) then + write(iwr,*) 'lmax_mode should be less than 3' + call exit + endif +C + if(calctype.eq.'els') then + lmax_mode = 2 + einl = einc - esct - cip + if(cip.ne.0.0.and.einl.lt.0.0d0) then + write(6,*)' unable to excite chosen edge:', + 1 ' einc - esct - cip less than zero =', einl + call exit + endif + endif +C +C.....Write the information of the input data file +C..... into the check file (unit iwr) +C + if(calctype.eq.'led'.or.calctype.eq.'dos') then + charelx = 'gs' + endif +C + if(tdl.eqv..true.) then + calctype = 'xpd' +c potype = 'xalph' +c gamma = 0.d0 + write(iwr,*) ' calculating derivatives of phase shifts', + 1 ' setting calctype to xpd, ' +c write(iwr,*) ' potype to xalpha and gamma to zero' + endif + write(iwr,1001) +C + if((calctype.eq.'xpd').or.(calctype.eq.'led').or. + 1 (calctype.eq.'els')) then + write(iwr,1000) calctype + write(iwr,1001) + if(calctype.eq.'xpd'.or.calctype.eq.'xas'.or. + 1 calctype.eq.'rex'.or.calctype.eq.'els') + 2 write(iwr,1005)edge + write(iwr,1010)potype,norman,absorber + write(iwr,1015)coor,emin,emax + write(iwr,1020)delta,gamma,eftri + write(iwr,1025)cip,lmaxt,charelx + write(iwr,1038) ionzst + write(iwr,*) ' relativistic corrections of type: ',relc + if (potgen.eq.'in') write(iwr,1036) + if (potgen.eq.'ex') write(iwr,1037) + else + write(iwr,10001) calctype + write(iwr,10011) + write(iwr,10051)edge,edge1,edge2 + write(iwr,10101)potype,norman,absorber + write(iwr,10151)coor,emin,emax + write(iwr,10201)delta,gamma,eftri + write(iwr,10251)cip,lmaxt,charelx + write(iwr,10381) ionzst + write(iwr,*) ' relativistic corrections of type: ',relc + endif +C +C......Check number of energy points +C + kxe = nint((emax-emin)/delta + 1.) + if(kxe.gt.nep_)then + write(6,731) kxe + call exit + endif +C +C......Set other options and seek for errors +C + ierror=0 +C +C......Set up the exchange and correlation potential index +C +C potgen determines whether the potential is generated internally +C by the present program or read in externally +C potype determines which which kind of exchange-correlation potential +C is used +C mode is 0 if the potential is to be computed and 1 if the +C potential is to be read +C iexcpot is defined after the potential type according to +C the values found below +C ipot 0 for real potential and 1 for complex potential +C + mode = 0 + if (potgen.eq.'ex') then + mode=1 + endif +C + iexcpot = 0 + ipot = 0 +C + if(potype.eq.'xalph')then + iexcpot=1 + elseif(potype.eq.'hedin')then + ipot = 1 + iexcpot=5 + elseif(potype.eq.'dhrel')then + iexcpot=2 + elseif(potype.eq.'dhcmp')then + ipot = 1 + iexcpot=4 + elseif(potype.eq.'hdrel')then + iexcpot=3 + elseif(potype.eq.' lmto')then + iexcpot=6 + elseif(potype.eq.'spkkr')then + iexcpot=6 + elseif(potype.eq.' msf')then + iexcpot=6 + else + ierror=1 + endif +C +C Index of core hole for the initial state excitation +C +C if(charelx.eq.'ex') then + call core_hole_index(edge,shell,orbital,lin,hole,ierror) +C endif +C +C Auger case: indices for final state core holes +C + if(calctype.eq.'aed') then + call core_hole_index(edge1,shell1,orbital1,lin1,hole1,ierror) + call core_hole_index(edge2,shell2,orbital2,lin2,hole2,ierror) + endif +C +C.. Stop if errors occurred +C + if(ierror.eq.0)goto 10 +C + write(iwr,*) ' ' + write(iwr,*) ' ' + write(iwr,*)' ** error in inctrl **' + write(iwr,*)' -> check namelist values' + write(iwr,*) ' ' + write(iwr,*) ' ' +C + stop +C + 10 continue +C +C.. Check dimensions for lmax +C + if(lmaxt.gt.lmax_) then + write(iwr,*) ' ' + write(iwr,*) ' ' + write(iwr,*)' ** error in inctrl **' + write(iwr,*)' -> check dimensions for lmax_' + write(iwr,*) ' ' + write(iwr,*) ' ' + stop + endif +C +C Formats: +C + 731 FORMAT(//, + 1 ' increase the dummy dimensioning variable, nep_. ', + 2 /,' it should be at least equal to: ', i5,/) +C + 1000 FORMAT(' parameters for this ',a3,' calculation:') + 1001 FORMAT(1x,65('-')) + 1005 FORMAT(2x,'edge= ',a2) + 1010 FORMAT(2x,'potype= ',a5,5x,'norman= ',a6,4x,'absorber= ',i2) + 1015 FORMAT(2x,'coor= ',a4,8x,'emin= ',f7.2,' Ry',2x,'emax= ', + 1 f7.2,' Ry') + 1020 FORMAT(2x,'delta= ',f6.3,' Ry',2x,'gamma= ',f5.2, + 2 2x,'Ry',2x,'eftri= ',f6.3,2x,'Ry') + 1025 FORMAT(2x,'cip= ',f7.2,2x,'Ry',2x,'lmaxt= ',i2,9x,'charelx: ',a2) + 1036 FORMAT(2x,'final state potential generated internally') + 1037 FORMAT(2x,'final state potential read in from extnl file') + 1038 FORMAT(2x,'ionization state : ',a7) +C +10001 FORMAT(' parameters for this 'a3,' calculation:') +10011 FORMAT(52('-')) +10051 FORMAT(2x,'edge= ',a2,2x,'edge1= ',a2,2x,'edge2= ',a2) +10101 FORMAT(2x,'potype= ',a5,5x,'norman= ',a6,4x,'absorber= ',i2) +10151 FORMAT(2x,'coor= ',a4,8x,'emin= ',f7.2,' Ry',2x,'emax= ', + 1 f7.2,' Ry') +10201 FORMAT(2x,'delta= ',f6.3,' Ry',2x,'gamma= ',f5.2, + 1 2x,'Ry',2x,'eftri= ',f6.3,2x,'Ry') +10251 FORMAT(2x,'cip= ',f7.2,2x,'Ry',2x,'lmax= ',i2,9x,'charelx: ',a2) +10381 FORMAT(2x,'ionization state :',a7) +C + end ! of subroutine inctrl +C + subroutine core_hole_index(edge,shell,orbital,lin,hole,ierror) +C + implicit none +C + character*1 shell,orbital + character*2 edge +C + integer lin,hole,ierror +C +C Index of hole for the (initial) core state +C + shell=edge(1:1) + orbital=edge(2:2) +C +C K shell core hole +C + if(shell.eq.'k')then + lin=0 + hole=1 +C +C L shell core hole +C + elseif(shell.eq.'l')then + if(orbital.eq.'1') then + lin=0 + hole=2 + elseif(orbital.eq.'2')then + lin=1 + hole=3 + elseif(orbital.eq.'3')then + lin=1 + hole=4 + else + ierror=1 + endif +C +C M shell core hole +C + elseif(shell.eq.'m')then + if(orbital.eq.'1')then + lin=0 + hole=5 + elseif(orbital.eq.'2')then + lin=1 + hole=6 + elseif(orbital.eq.'3')then + lin=1 + hole=7 + elseif(orbital.eq.'4')then + lin= 2 + hole=8 + elseif(orbital.eq.'5')then + lin=2 + hole=9 + else + ierror=1 + endif +C +C N shell core hole +C + elseif(shell.eq.'n')then + if(orbital.eq.'1')then + lin=0 + hole=10 + elseif(orbital.eq.'2')then + lin=1 + hole=11 + elseif(orbital.eq.'3')then + lin=1 + hole=12 + elseif(orbital.eq.'4')then + lin= 2 + hole=13 + elseif(orbital.eq.'5')then + lin=2 + hole=14 + elseif(orbital.eq.'6')then + lin=3 + hole=15 + elseif(orbital.eq.'7')then + lin=3 + hole=16 + else + ierror=1 + endif +C +C O shell core hole +C + elseif(shell.eq.'o')then + if(orbital.eq.'1')then + lin=0 + hole=17 + elseif(orbital.eq.'2')then + lin=1 + hole=18 + elseif(orbital.eq.'3')then + lin=1 + hole=19 + elseif(orbital.eq.'4')then + lin= 2 + hole=20 + elseif(orbital.eq.'5')then + lin=2 + hole=21 + elseif(orbital.eq.'6')then + lin=3 + hole=22 + elseif(orbital.eq.'7')then + lin=3 + hole=23 + else + ierror=1 + endif + endif +C + return +C + end +c +c + subroutine intit(iof) +C +c... read title cards until a blank card is encountered +C + implicit real*8 (a-h,o-z) + include 'msxas3.inc' +c + include 'msxasc3.inc' +c + logical blank + logical line1 + character*1 card(80) +c + write(iwr,1001) + + line1=.true. +c + 1 call incard (idat,card,ierr) + if(ierr.eq.0) goto 3 + if(ierr.eq.1) then + + write(iwr,2000) + + if(ierr.eq.2) then + + write(iwr,2001) + + endif + endif + 2000 format(//,' ** intit : end input -> stop **',//) + 2001 format(//,' ** intit : input error -> stop **',//) + stop + 3 continue +c +c.. write the 1st line of title into iof +c + if (line1) write(iof) (card(j),j=1,79) + line1=.false. + if ( blank(card) ) goto 2 + write(iwr,1000) (card(j),j=1,79) + goto 1 + 2 continue + write(iwr,1001) +1000 format(1x,80a1) +1001 format(/) + end +c + subroutine incard (idat,card,ierr) +c + character*1 card(80) + ierr=0 + do 2 i=1,80 + 2 card(i)=' ' + read(idat,1000,end=9,err=10) (card(i),i=1,80) + return + 9 ierr=1 + return + 10 ierr=2 + return + 1000 format(80a1) + end +c + logical function blank(card) + character*1 card(80) + data iasc/32/ +c +c iasc is the ascii code for ' ' (32) +c here a blank card is a card with ascii codes < 32 +c i.e., control characters are ignored +c + blank=.true. + do 1 i=1,80 + if (ichar(card(i)).gt.iasc) then + blank=.false. + return + endif + 1 continue + end +c + subroutine incoor +c + implicit real*8 (a-h,o-z) + include 'msxas3.inc' +c + include 'msxasc3.inc' +c + common/lmto/ rdsymbl,tag(nat_) + character*2 tag,tagi + logical rdsymbl +c + if( coor.eq.'au ') write(iwr,2000) + if( coor.eq.'angs') write(iwr,2001) + write(iwr,2002) + i=1 + 1 continue +c + rdsymbl=.false. + read (idat,*,iostat=ios) tagi,nzi + backspace(idat) + if (ios.eq.0) rdsymbl=.true. +c + if (rdsymbl) then +c + if (norman.eq.'stdcrm') then + radi = 0.0d0 + redfi = 0.0d0 + read (idat,*,err=2) tagi,nzi,ci1,ci2,ci3 + endif +c + if (norman.eq.'stdfac') then + radi = 0.d0 + redfi = 0.8d0 + read (idat,*,err=2) tagi,nzi,ci1,ci2,ci3 + endif +c + if (norman.eq.'scaled') then + radi = 0.0d0 + read (idat,*,err=2) tagi,nzi,ci1,ci2,ci3,redfi + endif +c + if (norman.eq.'extrad') then + redfi = 0.0d0 + read (idat,*,err=2) tagi,nzi,ci1,ci2,ci3,radi + endif +c + else +c + if (norman.eq.'stdcrm') then + radi = 0.0d0 + redfi = 0.0d0 + read (idat,*,err=2) nzi,ci1,ci2,ci3 + endif +c + if (norman.eq.'stdfac') then + radi = 0.d0 + redfi = 0.8d0 + read (idat,*,err=2) nzi,ci1,ci2,ci3 + endif +c + if (norman.eq.'scaled') then + radi = 0.0d0 + read (idat,*,err=2) nzi,ci1,ci2,ci3,redfi + endif +c + if (norman.eq.'extrad') then + redfi = 0.0d0 + read (idat,*,err=2) nzi,ci1,ci2,ci3,radi + endif +c + endif +c + if (nzi.lt.0) goto 2 +c + if (i.gt.natoms) then + write(iwr,*) ' ' + write(iwr,*) ' ' + write(iwr,*)' ** error in incoor **' + write(iwr,*)' -> too many atoms, ', + 1 'check dimensions' + write(iwr,*) ' ' + write(iwr,*) ' ' + stop + endif +c + nz(i) = nzi + c(i,1) = ci1 + c(i,2) = ci2 + c(i,3) = ci3 + rad(i) = radi + redf(i) = redfi + tag(i) = tagi + if(rdsymbl) then + write (iwr,101) tag(i),nz(i),c(i,1),c(i,2),c(i,3),rad(i),redf(i) + else + write (iwr,100) nz(i),c(i,1),c(i,2),c(i,3),rad(i),redf(i) + endif + 100 format(2x,i3,3f10.4,3x,2f7.4) + 101 format(2x,a2,3x,i3,3f10.4,3x,2f7.4) + i=i+1 + goto 1 + 2 nat = i-1 +C print *, 'nat =', nat + write(iwr,2002) + write(iwr,2003) + if(ionzst.eq.' ionic') then + 10 read(idat,*) nzat + if(nzat.lt.0) goto 20 + backspace(idat) + read(idat,*) ndummy,charge_ion(nzat) + goto 10 + endif + 20 continue +c +c.. default units are angtroms, convert to a.u. if necessary +c + if (coor.eq.'au ') return + if (coor.eq.'angs') then + do 3 i=1,nat + if (norman.eq.'extrad') + & rad(i) = rad(i)/antoau + do 3 iz=1,3 + c(i,iz)= c(i,iz) / antoau + 3 continue + return + endif +c + write(iwr,*) ' ' + write(iwr,*) ' ' + write(iwr,*)' ** incoor: unit type unknown -> ', + 1 'stop ** ' + write(iwr,*) ' ' + write(iwr,*) ' ' +c + 2000 format(' coordinates in a.u. ',25x,'Radii') + 2001 format(' coordinates in angstroms',25x,'Radii') + 2002 format(1x,65('-')) + 2003 format(/) + stop + end +c + subroutine calphas +c + implicit real*8 (a-h,o-z) + include 'msxas3.inc' +c + include 'msxasc3.inc' +c +c + common/continuum/emin,emax,delta,cip,gamma,eftri,iexcpot,db + common/eels/einc,esct,scangl,qt,lambda,eelsme(npss,npss,npss), + & p1(rdx_,npss,nef_),p2(rdx_,npss,nef_), + & p3(rdx_,npss,nef_),ramfsr1(npss,nef_), + & ramfsr2(npss,nef_),ramfsr3(npss,nef_), + & lmxels(3,ua_),p3irreg(rdx_,7),p2irreg(rdx_,7) + complex*16 eelsme,p1,p2,p3,ramfsr1,ramfsr2,ramfsr3,p3irreg, + & p2irreg + real*8 lambda +c + character*8 nsymbl +c +c ######## Modified to introduce the two state wave functions for the +c Auger decay +c ######## let's introduce i_absorber_hole1 and i_absorber_hole2 +c + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode + common/dimens/nats,ndat,nout,lmaxx,irreps +c + common/aparms/xv(natoms),yv(natoms),zv(natoms),z(natoms), + u nsymbl(natoms),nzeq(natoms),neq(natoms),ncores(natoms), + u lmaxat(natoms), ktau(ua_),natau(neq_,ua_) +c + common/aparms_extra/rs_(natoms),redf_(natoms),ovlf +c +c + write(iwr,*) ' ** enter calphas **' +c + if(cip.eq.0.0) then +c +c calculate edge ionization potential +c + call calc_edge(cip) + write(6,*) ' calculated ionization potential (ryd) =',cip + else + write(6,*) ' given ionization potential (ryd) =',cip + endif + write(6,*) ' ---' +c +c check consistency of input data in case of calctype = 'els' +c + if(calctype.eq.'els') then + einl = einc - esct - cip + if(einl.lt.0.0d0) then + write(6,*)' unable to excite chosen edge:', + & ' einc - esct - cip less than zero =', einl + call exit + endif + endif +c +c phase shifts computation +c initializes some variables for symmetry+potential programs +c nat is the total number of physical atoms as read in in +c subroutine incoor and is listed in common/atoms/ +c + nats=nat + i_absorber = absorber + i_absorber_hole = hole +c +c ################## Modified to introduce the two state wave functions +c for the Auger decay +c ################## hole1 is the electron that will go down to fill +c the primary core hole +c + i_absorber_hole1 = hole1 + + + i_absorber_hole2 = hole2 + + + + + + + i_norman = 1 +c if (norman.eq.'extrad') i_norman = 0 + i_mode = mode + do 100 i=2,nat+1 + + nzeq(i) = nz(i-1) + xv(i) = c(i-1,1) + yv(i) = c(i-1,2) + zv(i) = c(i-1,3) + rs_(i)=rad(i-1) + redf_(i)=redf(i-1) + 100 continue + ovlf = ovlpfac +c + write(iwr,*) ' ' + write(iwr,*) ' ' + write(iwr,*) ' symmetrizing coordinates... ' + open (7,file='div/sym.out',status='unknown') + + call xasymfn_sub + + +c +c.....Warning: in subroutine xasymfn_sub nats has been assigned +c.....the value (nat+1) to take into account the outer sphere. +c +c create equivalence table neqat +c i=1 is the outer sphere in xasym programs +c + do 200 i=1,nat + if (neq(i+1).eq.0) then + neqat(i)=i + else + neqat(i)=neq(i+1)-1 + endif + 200 continue +c +c.....Write out atomic coordinates in symmetry-program order: +c each prototypical atom is followed by its sym-equivalent atoms +c +CST ==> Phagen to python shared object modifications +CST The clus.out file needs to be opened, so I uncommented the +CSR line below + open (10,file='clus/clus.out',status='unknown') +CST Phagen to python shared object modifications <== + if( coor.eq.'au ') then + ipha=1 + coef=1.d0 + endif + if( coor.eq.'angs') then + ipha=2 + coef=0.529177d0 + endif +CST ==> Phagen to python shared object modifications <== +CST I uncommented the line below + write(10,888) ipha +CST Phagen to python shared object modifications <== + 888 format(30x,i1) + write(7,10) (neqat(i),i=1,nat) + 10 format (/,16i5,//) +c +c write(7,10) nat, ndat-1 +c + x0 = xv(2) + y0 = yv(2) + z0 = zv(2) +c + no = 0 + do na = 1, ndat-1 + do k = 2, nat+1 + if (neqat(k-1).eq.na) then + no = no + 1 + write(7,20) no,nsymbl(k),nzeq(k),xv(k)-x0, + & yv(k)-y0,zv(k)-z0,neqat(k-1) +CST ==> Phagen to python shared object modifications +CST I changed the unit to 10 +CST write(7,20) no,nsymbl(k),nzeq(k),(xv(k)-x0)*coef, +CST & (yv(k)-y0)*coef,(zv(k)-z0)*coef,neqat(k-1) + write(10,20) no,nsymbl(k),nzeq(k),(xv(k)-x0)*coef, + & (yv(k)-y0)*coef,(zv(k)-z0)*coef,neqat(k-1) +CST Phagen to python shared object modifications <== + endif + continue + enddo + enddo +c +CST ==> Phagen to python shared object modifications +CST I uncommented the line below + close(10) +CST Phagen to python shared object modifications <== +c + 20 format (i5,6x,a4,i5,3f10.4,i5) +c + write(iwr,*) + write(iwr,*)' computing muffin tin potential and phase shifts' + call cont_sub(potype,potgen,lmax_mode,lmaxt,relc,eikappr,db, + & calctype,nosym,tdl) +c +ctn write(iwr,*)'calphas: neq', (neq(i),i=1,nat+1) +ctn write(iwr,*)'calphas: neqat', (neqat(i),i=1,nat) +c tstop=cputim() +c elapsed=tstop-tstart +c write(iwr,2000)elapsed +c 2000 format(' ** end calphas ** elapsed time ',f10.3,' seconds') + return + end +c +c + subroutine exit +c + write(6,*) ' ' + write(6,*) ' ' + write(6,*)' ** stop via call exit **' + write(6,*) ' ' + write(6,*) ' ' + stop + end +c + subroutine xasymfn_sub +c +c*********************************************************************** +c +c xasymfn: xalpha symmetry function program (version 3, 11 feb 1981) +c +c written by m. cook, 1981. +c +c calls: input(at input,outpot),seteqs,symops,closur,ctable,basfns +c +c*********************************************************************** +c + + implicit real*8 (a-h,o-z) +c include 'mscalc.inc' + include 'msxas3.inc' + integer op_,ord_,two_npr_ + parameter (natm2_=nat_-2,npr_=24,op_=48,ntax_=250, + 1 ir_=14,ib_=28,ord_=8,l_=3,lp1_=4, + 2 nms_=7,nfac_=9,nbf_=nat_*4,ncs_=24) + parameter(two_npr_=2*npr_,npr_p1_=npr_+1) +c + common/maxdim/natmx,ndatmx,neqsmx,nprmx,nopmx,nimp1, + u nordmx,nirpmx,nibmx,lbasmx,nbfmx,ncsmx,ntaxmx +c +c !flag for reformatted output + common/sym_out/isym_format + + +c +c----- define maximum array dimensions --------------------------------- +c warning : natmx est dans le common +cman data natmx,ndatmx,neqsmx,nprmx,nopmx,nimp1, +cman u nordmx,nirpmx,nibmx,lbasmx,nbfmx,ncsmx,ntaxmx +cman u /nat_,ua_,neq_,npr_,two_npr_,npr_p1_, +cman u ord_,ir_,ib_,l_,nbf_,ncs_,ntax_/ +c + data natm2m,nopmax,lp1mx,nmsmx,mxfct + u /natm2_,op_,lp1_,nms_,nfac_/ +cman + natmx = nat_ + ndatmx = ua_ + neqsmx = neq_ + nprmx = npr_ + nopmx = two_npr_ + nimp1 = npr_p1_ + nordmx = ord_ + nirpmx = ir_ + nibmx = ib_ + lbasmx = l_ + nbfmx = nbf_ + ncsmx = ncs_ + ntaxmx = ntax_ + +c +c + if (natm2m.lt.natmx-2) go to 10 + if (nopmax.ne.2*nprmx) go to 20 + if (lp1mx.ne.lbasmx+1) go to 30 + if (nmsmx.ne.2*lbasmx+1) go to 40 + if (mxfct.lt.2*lbasmx+1) go to 50 + if (nordmx.lt.3) go to 60 +c +c----- call major calculational subroutines ---------------------------- +c + + call input_xasymfn + + + call seteqs + call outpot_xasymfn +c + return +c +c----- error prints and stops ------------------------------------------ +c + 10 write (6,500) natm2m + stop + 20 write (6,510) nopmax + stop + 30 write (6,520) lp1mx + stop + 40 write (6,530) nmsmx + stop + 50 write (6,540) mxfct + stop + 60 write (6,550) nordmx + stop +c + 500 format (//,' error stop: natm2m =',i6,' is less than', + u ' natmx-2 : redimension',//) + 510 format (//,' error stop: nopmax =',i6,' is not equal to', + u ' 2*nprmx : redimension',//) + 520 format (//,' error stop: lp1mx =',i6,' is not equal to', + u ' lbasmx+1 : redimension',//) + 530 format (//,' error stop: nmsmx =',i6,' is not equal to', + u ' 2*lbasmx+1 : redimension',//) + 540 format (//,' error stop: mxfct =',i6,' is less than', + u ' 2*lbasmx+1 : redimension',//) + 550 format (//,' error stop: nordmx =',i6,' : must be', + u ' redimensioned to 3 or greater',//) + end +c +c + subroutine input_xasymfn +c +c*********************************************************************** +c +c reads in the molecular geometry information, desired +c l-values, and mode control variables. modes of operation: +c +c iprt=0, rot'n matrices not printed +c iprt=1, rot'n matrices will be printed out from ctable +c +c mdin=0, geometry, nz, neq data all read from card input +c mdin=1, non-sym data read from a molec stpot; sym data from cards +c +c mdou=0, only 1st col of degenerate irreps output to ktape +c mdou=1, all columns of degenerate irreps will be written +c +c mdco=0, single-atom core functions will be generated +c mdco=1, symmetry-adapted core functions will be generated +c +c mdeq=0, calc'd symmetry-eq list (neq) overrides any input neq +c mdeq=1, input list of symmetry-equivalences will be used +c +c if mdin=1, mdeq=1 is automatically enforced by this program +c because the form of the stpot depends on the list of sym-eq ats. +c +c called by: main (at input,outpot) +c +c*********************************************************************** +c + implicit real*8(a-h,o-z) +c include 'mscalc.inc' + include 'msxas3.inc' +c + logical cmplxc,frezeq,inpot,nonint,onecol,symcor + character*8 nsymbl,nsymbl2 + common/aparms_extra/rs(nat_),redf(nat_) + common/aparms/xv(nat_),yv(nat_),zv(nat_),z(nat_), + u nsymbl(nat_),nz(nat_),neq(nat_),ncores(nat_),lmax(nat_), + u ktau(ua_),natau(neq_,ua_) + common/aparms2/xv2(nat_),yv2(nat_),zv2(nat_),rs2(nat_), + u alpha2(nat_),redf2(nat_),z2(nat_),q2(nat_),qspnt2(2), + u qint2(2), + u watfac(nat_),alpha02,volint2,ovout2,rmxout2,nsymbl2(nat_), + u nz2(nat_),neq2(nat_),kmax2(nat_),kplace2(nat_),ktau2(ua_) + common/lparam/lmax2(nat_),l0i + common/coords/s(3,nat_) + dimension s2(3,nat_) + common/dimens/nat,ndat,nout,lmaxx,irreps + common/dimens2/nat2,ndat2 + common/logicl/cmplxc,iprt,frezeq,inpot,nonint,onecol,symcor + common/maxdim/natmx,ndatmx,neqsmx,nprmx,nopmx,nimp1, + u nordmx,nirpmx,nibmx,lbasmx,nbfmx,ncsmx,ntaxmx +c !flag for reformatted output + common/sym_out/isym_format +c + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode + +c !generate potential file + common/out_ascii/iout_ascii +c + common/charge_center/cc_dif(3,1),z_shift,i_z_shift,shift_cc + logical shift_cc +c + common/lmto/ rdsymbl,tag(nat_) + character*2 tag + logical rdsymbl + + character*2 nameat + dimension nameat(100) +c + DATA NAMEAT/' H','He','Li','Be',' B',' C',' N',' O',' F','Ne', + 1 'Na','Mg','Al','Si',' P',' S','Cl','Ar',' K','Ca', + 1 'Sc','Ti',' V','Cr','Mn','Fe','Co','Ni','Cu','Zn', + 1 'Ga','Ge','As','Se','Br','Kr','Rb','Sr',' Y','Zr', + 1 'Nb','Mo','Tc','Ru','Rh','Pd','Ag','Cd','In','Sn', + 1 'Sb','Te',' I','Xe','Cs','Ba','La','Ce','Pr','Nd', + 1 'Pm','Sm','Eu','Gd','Tb','Dy','Ho','Er','Tm','Yb', + 1 'Lu','Hf','Ta',' W','Re','Os','Ir','Pt','Au','Hg', + 1 'Tl','Pb','Bi','Po','At','Rn','Fr','Ra','Ac','Th', + 1 'Pa',' U','Np','Pu','Am','Cm','Bk','Cf','Es','Fm'/ +c + data thr/0.001d0/ + data zero/0.d0/ + data lunout,lunout2/7,60/ + +c + iprt=0 + mdou=0 + mdco=0 + mdeq=0 + isym_format=0 + +c !nout defined + nout=1 +c !same as nout but global + i_outer_sphere=1 +c + frezeq=.false. + symcor=.false. + onecol=.true. + if (mdeq.eq.1) frezeq=.true. + if (mdco.eq.1) symcor=.true. + if (mdou.eq.1) onecol=.false. +c +c----------------------------------------------------------------------- +c mdin = 0 : only geometry & atomic # data, from card input +c----------------------------------------------------------------------- +c + inpot=.false. +c !nout defined + nout=1 +ctn +ctn Values passed through the subroutines parameters +ctn read (lunin,*) nat,i_absorber,i_absorber_hole,i_norman, +ctn &i_mode +c + nat=nat+i_outer_sphere + if (nout.eq.0) write (lunout,570) nat + if (nout.ne.0) write (lunout,580) nat + if (nat.gt.natmx) go to 140 + write (lunout,530) + + +c + r_sphere=0.0d0 + + + + do 10 na=2,nat + + +ctn read (lunin,*) nsymbl(na),nz(na),xv(na),yv(na),zv(na), +ctn u rs(na),redf(na) +ctn modifs : + + +c nsymbl(na)=nameat(nz(na)) +c......modification for Empty Spheres +c + if(rdsymbl) then + nsymbl(na)=tag(na-1) + else + if(nz(na).eq.0) then + nsymbl(na)='ES' + else + nsymbl(na)=nameat(nz(na)) + endif + endif + z(na)=dfloat(nz(na)) + neq(na)=0 +c !needed to determine point group + lmax(na)=3 + ncores(na)=0 + + + write (lunout,550) na,nsymbl(na),nz(na),xv(na),yv(na),zv(na), + u neq(na),lmax(na),ncores(na) + 10 continue +c +c define outer sphere parameters (i. e. atomic center) +c + na=1 + nsymbl(na)='osph' + nz(na)=0 + z(na)=0.0d0 + neq(na)=0 + rs(na)=0.0d0 + redf(na)=0.0d0 +c !needed to determine point group + lmax(na)=3 + ncores(na)=0 +c +c define outer sphere coordinates at center of charge +c + xo=zero + yo=zero + zo=zero + wt=zero + do 910 na1=2,nat + xo=xo+z(na1)*xv(na1) + yo=yo+z(na1)*yv(na1) + zo=zo+z(na1)*zv(na1) + wt=wt+z(na1) + 910 continue + xo=xo/wt + yo=yo/wt + zo=zo/wt + if (dabs(xo).lt.thr) xo=zero + if (dabs(yo).lt.thr) yo=zero + if (dabs(zo).lt.thr) zo=zero + xv(na)=xo + yv(na)=yo + zv(na)=zo +c + if(i_norman.ne.1)then + do 15 na1=2,nat + r_sphere_temp=sqrt((xv(na1)-xv(1))**2+ + u (yv(na1)-yv(1))**2+ + u (zv(na1)-zv(1))**2)+rs(na1) + if(r_sphere.lt.r_sphere_temp)then + r_sphere=r_sphere_temp + end if +15 continue + rs(1)=r_sphere + end if + write (lunout,550) na,nsymbl(na),nz(na),xv(na),yv(na),zv(na), + u neq(na),lmax(na),ncores(na) + write (lunout,560) +c +c*** check coordinates of atoms +c + do 1150 na1=1,nat + do 1140 na2=1,na1 + dist =dsqrt((xv(na1)-xv(na2))**2 + u +(yv(na1)-yv(na2))**2 + (zv(na1)-zv(na2))**2 ) + if((na2.gt.1).and.(na1.ne.na2)) then + if(dist.lt.thr)then + write(6,562)na1,na2 + call exit + end if + end if + 1140 continue + 1150 continue +c + return +c +c+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +c entry outpot_xasymfn +c+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +c +c----- molecule will usually have been rotated: +c print the new atomic coordinates in standard orientation ------ +c + entry outpot_xasymfn + write (lunout,590) + print 595 + write (lunout,530) + print 535 + nashf=1 +c + + nat2=nat + ndat2=ndat + i_absorber_real=i_absorber+i_outer_sphere +c +c set z on absorbing atom back to original value +c + z(i_absorber_real)=z(i_absorber_real)-z_shift + nz(i_absorber_real)=nz(i_absorber_real)-i_z_shift +c !symmetry distinct atoms + do 70 nda=1,ndat + if(shift_cc)then +c !go back to real cente + s2(1,nashf)=s(1,nashf)-cc_dif(1,1) +c !of charge + s2(2,nashf)=s(2,nashf)-cc_dif(2,1) + s2(3,nashf)=s(3,nashf)-cc_dif(3,1) + if (dabs(s2(1,nashf)).lt.thr) s2(1,nashf)=zero + if (dabs(s2(2,nashf)).lt.thr) s2(2,nashf)=zero + if (dabs(s2(3,nashf)).lt.thr) s2(3,nashf)=zero + else + s2(1,nashf)=s(1,nashf) + s2(2,nashf)=s(2,nashf) + s2(3,nashf)=s(3,nashf) + endif + write (lunout,550) nda,nsymbl(nda),nz(nda), + u s2(1,nashf),s2(2,nashf),s2(3,nashf),neq(nda), + u lmax(nda),ncores(nda) + print 555, nda,nsymbl(nda),nz(nda), + u s2(1,nashf),s2(2,nashf),s2(3,nashf),neq(nda) + if(nda.ne.1)write (lunout2,552) s2(1,nashf),s2(2,nashf), + u s2(3,nashf),nsymbl(nda) +c + rs2(nda)=rs(nda) + redf2(nda)=redf(nda) + nsymbl2(nda)=nsymbl(nda) + xv2(nda)=s2(1,nashf) + yv2(nda)=s2(2,nashf) + zv2(nda)=s2(3,nashf) + nz2(nda)=nz(nda) + z2(nda)=z(nda) + neq2(nda)=neq(nda) + ktau2(nda)=ktau(nda) + nashf=nashf+ktau(nda) + 70 continue + nashf=0 + do 90 nda=1,ndat + nashf=nashf+1 + neqs=ktau(nda) + if (neqs.eq.1) go to 90 + do 80 ne=2,neqs +c !equivalent sets + nashf=nashf+1 + na=natau(ne,nda) + if(shift_cc)then +c !go back to real cente + s2(1,nashf)=s(1,nashf)-cc_dif(1,1) +c !of charge + s2(2,nashf)=s(2,nashf)-cc_dif(2,1) + s2(3,nashf)=s(3,nashf)-cc_dif(3,1) + if (dabs(s2(1,nashf)).lt.thr) s2(1,nashf)=zero + if (dabs(s2(2,nashf)).lt.thr) s2(2,nashf)=zero + if (dabs(s2(3,nashf)).lt.thr) s2(3,nashf)=zero + else + s2(1,nashf)=s(1,nashf) + s2(2,nashf)=s(2,nashf) + s2(3,nashf)=s(3,nashf) + endif + write (lunout,550) na,nsymbl(na),nz(na), + u s2(1,nashf),s2(2,nashf),s2(3,nashf),neq(na),lmax(na),ncores(na) + print 555, na,nsymbl(na),nz(na), + u s2(1,nashf),s2(2,nashf),s2(3,nashf),neq(na) + write (lunout2,552) s2(1,nashf),s2(2,nashf),s2(3,nashf), + u nsymbl(na) + rs2(na)=rs(na) + redf2(na)=redf(na) + nsymbl2(na)=nsymbl(na) + xv2(na)=s2(1,nashf) + yv2(na)=s2(2,nashf) + zv2(na)=s2(3,nashf) + nz2(na)=nz(na) + z2(na)=z(na) + neq2(na)=neq(na) + 80 continue + 90 continue + if(nout.eq.1) then + + + z2(1)=1.0d0 + nz2(1)=1 + end if + write (lunout,560) + + return +c +c----- error prints and stops ------------------------------------------ +c + 140 write (6,600) natmx,nat + stop +c + 530 format (t53,'position'/30x,'atom no.',4x,'x',9x,'y',9x,'z',8x, + u 'eq',5x,'lmax',5x,'#cores'/) + 535 format (t35,'position'/12x,'atom no.',4x,'x',9x,'y',9x,'z',8x, + u 'eq'/) + 550 format (26x,i4,2x,a4,i6,3f10.4,i6,i8,i9) + 552 format (3(2x,f10.3),2x,a4) + 555 format (8x,i4,2x,a4,i6,3f10.4,i6) + 560 format (/46x,6('*****')/) + 562 format (//,'error: check coordinates of atoms # ',i4, + & ' and # ',i4,//) + 570 format (//38x,'number of centers=',i5,' no outer sphere'/) + 580 format (//38x,'number of centers=',i5,' outer sphere at ' + u ,'center 1'/) + 590 format (///38x,'molecular orientation for basis fn projection:'/) + 595 format (//14x,' symmetrized atomic coordinates of cluster '/) + 600 format (//' error stop: variable nat is .gt.',i6, + u ' : redimension natmx to',i6,//) + end +c + subroutine seteqs +c +c*********************************************************************** +c +c translates the molecule to the center of nuclear charge +c and tentatively identifies symmetry-equivalent sets of atoms +c on the basis of interatomic distances. +c checks that the atoms are arranged in correct order for +c xascf: nda's first and eq atoms following. if input is from +c a molec starting pot, error stop if order is not correct. if +c input is not from a pot, the atoms will be shuffled into +c the appropriate xascf order at output time. +c note that during the execution of the symmetry program, the +c atoms are not kept in the scf order: they are in sym-program +c order, each nda followed immediately by its sym-eq partners. +c +c called by: main +c +c*********************************************************************** +c + implicit real*8 (a-h,o-z) +c include 'mscalc.inc' + include 'msxas3.inc' + parameter (natm2_=nat_-2) +c + character*8 nsymbl + logical doshuf,equiv,found,match,frezeq + logical cmplxc,inpot,nonint,onecol,symcor + dimension neqt(nat_) + dimension found(natm2_),nbrz(natm2_,nat_),dnbr(natm2_,nat_) + integer trans(nat_) + common/aparms_extra/rs(nat_),redf(nat_) + common/aparms/xv(nat_),yv(nat_),zv(nat_),z(nat_), + u nsymbl(nat_),nz(nat_),neq(nat_),ncores(nat_),lmax(nat_), + u ktau(ua_),natau(neq_,ua_) + common/coords/s(3,nat_) + common/dimens/nat,ndat,nout,lmaxx,irreps + common/logicl/cmplxc,iprt,frezeq,inpot,nonint,onecol,symcor + common/maxdim/natmx,ndatmx,neqsmx,nprmx,nopmx,nimp1, + u nordmx,nirpmx,nibmx,lbasmx,nbfmx,ncsmx,ntaxmx +c + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode + +c + common/charge_center/cc_dif(3,1),z_shift,i_z_shift,shift_cc + common/transform/trans + logical shift_cc +CST ==> Phagen to python shared object modifications + common /misc/ noproto + logical noproto +CST Phagen to python shared object modifications <== +c +CST ==> Phagen to python shared object modifications +CST data zero,thr/0.0d0,0.001d0/ !if thr is negative, all cluster atoms are considered prototypical + data zero/0.0d0/ +CST Phagen to python shared object modifications <== +c + data jtape/21/ + data lunout/7/ +CST +CST ==> Phagen to python shared object modifications + thr=0.001d0 + if (noproto) thr=-1*thr +CST Phagen to python shared object modifications <== +c +c----------------------------------------------------------------------- +c find the center of charge of the nuclear framework and +c translate the molecule to that origin +c----------------------------------------------------------------------- +c !define nuclear charge shift + z_shift=5.0d0 + i_z_shift=5 + shift_cc=.true. +c + xo=zero + yo=zero + zo=zero + wt=zero + nastrt=nout+1 +c !set up to make absorbing atom unique by addin + cc_dif(1,1)=zero +c !z_shift units of charge to its nucleus + cc_dif(2,1)=zero + cc_dif(3,1)=zero + wt_real=zero + + do 5 na=nastrt,nat + cc_dif(1,1)=cc_dif(1,1)+z(na)*xv(na) + cc_dif(2,1)=cc_dif(2,1)+z(na)*yv(na) + cc_dif(3,1)=cc_dif(3,1)+z(na)*zv(na) + wt_real=wt_real+z(na) + 5 continue + cc_dif(1,1)=cc_dif(1,1)/wt_real + cc_dif(2,1)=cc_dif(2,1)/wt_real + cc_dif(3,1)=cc_dif(3,1)/wt_real +c + i_absorber_real=i_absorber+i_outer_sphere +c increase z value of absorbing atom + z(i_absorber_real)=z(i_absorber_real)+z_shift + nz(i_absorber_real)=nz(i_absorber_real)+i_z_shift +c + do 10 na=nastrt,nat + xo=xo+z(na)*xv(na) + yo=yo+z(na)*yv(na) + zo=zo+z(na)*zv(na) + wt=wt+z(na) + 10 continue + xo=xo/wt + yo=yo/wt + zo=zo/wt + if (dabs(xo).lt.thr) xo=zero + if (dabs(yo).lt.thr) yo=zero + if (dabs(zo).lt.thr) zo=zero +c !cc_dif is difference between + cc_dif(1,1)=cc_dif(1,1)-xo +c !real and shifted centers of + cc_dif(2,1)=cc_dif(2,1)-yo +c !charge + cc_dif(3,1)=cc_dif(3,1)-zo + if (dabs(cc_dif(1,1)).lt.thr) cc_dif(1,1)=zero + if (dabs(cc_dif(2,1)).lt.thr) cc_dif(2,1)=zero + if (dabs(cc_dif(3,1)).lt.thr) cc_dif(3,1)=zero + r_dif_cc=sqrt( cc_dif(1,1)*cc_dif(1,1)+cc_dif(2,1)* + u cc_dif(2,1)+cc_dif(3,1)*cc_dif(3,1) )/dsqrt(3.0d0) + if(r_dif_cc.lt.thr)shift_cc=.false. + do 20 na=1,nat + xv(na)=xv(na)-xo + yv(na)=yv(na)-yo + zv(na)=zv(na)-zo + if (dabs(xv(na)).lt.thr) xv(na)=zero + if (dabs(yv(na)).lt.thr) yv(na)=zero + if (dabs(zv(na)).lt.thr) zv(na)=zero + 20 continue +c +c----------------------------------------------------------------------- +c classify sym-eq sets of atoms: two atoms are eqiv +c if they have same number of neighbors of same nz at same distances +c----------------------------------------------------------------------- +c +c----- calculate the distances of each atom from the others ------------ +c + neqt(1)=0 + do 40 na1=nastrt,nat + nabor=0 + neqt(na1)=0 + do 30 na2=nastrt,nat + if (na1.eq.na2) go to 30 + nabor=nabor+1 + nbrz(nabor,na1)=nz(na2) + rab=dsqrt((xv(na1)-xv(na2))**2 + u +(yv(na1)-yv(na2))**2 + (zv(na1)-zv(na2))**2 ) + dnbr(nabor,na1)=rab + 30 continue + 40 continue +c +c----- compare the neighbor charges and distances ---------------------- +c + nabors=nat-(nout+1) + do 90 na1=nastrt,nat + na1p1=na1+1 + if (na1p1.gt.nat) go to 90 + do 80 na2=na1p1,nat + if (nz(na1).ne.nz(na2)) go to 80 + if (neqt(na2).ne.0) go to 80 + do 50 nabor=1,nabors + 50 found(nabor)=.false. + equiv=.true. +c +c----- try to match the neighbors of na1 & na2 one-to-one -------------- +c + do 70 nabor1=1,nabors + nzt= nbrz(nabor1,na1) + rabt=dnbr(nabor1,na1) + match=.false. + do 60 nabor2=1,nabors + if (found(nabor2)) go to 60 + if (nbrz(nabor2,na2).ne.nzt) go to 60 + if (dabs(dnbr(nabor2,na2)-rabt).gt.thr) go to 60 + found(nabor2)=.true. + match=.true. + go to 65 + 60 continue + 65 if (match) go to 70 + equiv=.false. + go to 75 + 70 continue +c +c----- if all nabor2 found and each nabor1 had match=.true., +c na1 and na2 have equivalent sets of neighbors ----------------- +c + 75 if (equiv) neqt(na2)=na1 + 80 continue + 90 continue +c +c----------------------------------------------------------------------- +c compare the calculated and input neq arrays +c----------------------------------------------------------------------- +c + write (lunout,500) + write (lunout,510) (na,neqt(na),na=1,nat) + equiv=.true. + do 100 na=1,nat + if (neqt(na).ne.neq(na)) equiv=.false. + if (.not.frezeq) neq(na)=neqt(na) + 100 continue + if (equiv) write (lunout,520) + if (.not.equiv.and.frezeq) write (lunout,530) + if (.not.equiv.and..not.frezeq) write (lunout,540) +c +c----------------------------------------------------------------------- +c check that the atoms are arranged in the correct scf order: +c all nda's first, then the sym-eq atoms for each nda in same order +c----------------------------------------------------------------------- +c + doshuf=.false. + do 110 na=nastrt,nat + if (neq(na).eq.0.and.neq(na-1).ne.0) doshuf=.true. + if (neq(na).lt.neq(na-1)) doshuf=.true. + 110 continue + if (inpot.and.doshuf) go to 230 +c +c----- if not running from a molecular starting pot, +c shuffle the atoms into xascf order ---------------------------- +c + rewind jtape + nda=0 + do 130 na=1,nat + if (neq(na).gt.0) go to 130 + nda=nda+1 + write (jtape) nsymbl(na),neq(na),nz(na),xv(na),yv(na),zv(na) + write (jtape) lmax(na),ncores(na),rs(na),redf(na),z(na) + do 120 na2=1,nat + if (neq(na2).eq.na) neq(na2)=nda + 120 continue + 130 continue + ndat=nda + if (ndat.gt.ndatmx) go to 240 + do 150 nda=1,ndat + do 140 na=1,nat + if (neq(na).ne.nda) go to 140 + write (jtape) nsymbl(na),neq(na),nz(na),xv(na),yv(na),zv(na) + write (jtape) lmax(na),ncores(na),rs(na),redf(na),z(na) + 140 continue + 150 continue + + nda=0 + do 310 i=2,nat + if (neq(i).eq.0) then + nda=nda+1 + trans(i-1)=nda + endif + 310 continue + + + do 320 na=2,ndat + do 325 i=2,nat + if (neq(i).eq.na) then + nda=nda+1 + trans(i-1)=nda + endif + 325 continue + 320 continue + + +c +c----- read the shuffled atomic parameters back in --------------------- +c + rewind jtape + do 160 na=1,nat + read (jtape) nsymbl(na),neq(na),nz(na),xv(na),yv(na),zv(na) + read (jtape) lmax(na),ncores(na),rs(na),redf(na),z(na) + 160 continue + rewind jtape +c +c----------------------------------------------------------------------- +c calculate the final symmetry-equivalence list ( natau ) +c----------------------------------------------------------------------- +c + do 200 nda=1,ndat + neqs=1 + natau(1,nda)=nda + do 190 na=1,nat + if (neq(na).ne.nda) go to 190 + neqs=neqs+1 + if (neqs.gt.neqsmx) go to 250 + natau(neqs,nda)=na + 190 continue + ktau(nda)=neqs + 200 continue + +c +c----------------------------------------------------------------------- +c arrange the atomic x,y,z coords in symmetry-program order: +c each nda is followed immediately by its sym-equivalent atoms +c----------------------------------------------------------------------- +c + nashuf=0 + do 220 nda=1,ndat + neqs=ktau(nda) + do 210 ne=1,neqs + na=natau(ne,nda) + nashuf=nashuf+1 + s(1,nashuf)=xv(na) + s(2,nashuf)=yv(na) + s(3,nashuf)=zv(na) + 210 continue + 220 continue + + return +c +c----- error prints and stops ------------------------------------------ +c + 230 write (6,550) + stop + 240 write (6,560) ndatmx,ndat + stop + 250 write (6,570) neqsmx + stop +c + 500 format (//25x,'calculated atomic symmetry equivalences,'/ + u 30x,'based on interatomic distance matrix:',7x,'na', + u 4x,'neq(na)'/) + 510 format (69x,i7,i8) + 520 format (/t35,'the calculated symmetry-eq sets agree with', + u ' the input'/) + 530 format (/t25,'calculated & input symmetry-eq sets do not', + u ' agree: input sets will be used'/) + 540 format (/t22,'calculated & input symmetry-eq sets do not', + u ' agree: calculated sets will be used'/) + 550 format (//t25,'input molecular pot does not have distinct', + u ' & sym-eq atoms in correct order for input to xascf',//) + 560 format (//' error stop: variable ndat is .gt.',i6, + u ' : redimension ndatmx to',i6,//) + 570 format (//' error stop: variable neqs is .gt.',i6, + u ' : redimension neqsmx',//) + end +c +c + subroutine vgen +c + implicit real*8 (a-h,o-z) +c write(6,*) 'check1' + call rhoat +c write(6,*) 'check2' + call molpot +c write(6,*) 'check3' + call inpot +c write(6,*) 'check4' + return + end +c +C*********************************************************************** + SUBROUTINE RHOAT +C*********************************************************************** +C +C MAY-92 +C +C GENERATES ATOMIC CHARGE DENSITY FOR PROTOTYPICAL ATOMS +C +C DICTIONARY : +C NDAT Number of prototypical atoms +C INV Logical unit on which to write the output [8] +C ZAT Atomic number +C MESH Number of radial mesh points [441] +C +C************************************************ + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + include 'msxasc3.inc' +c + common/dimens/nats,ndat +c + character*8 nsymbl +c.. + + +c common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1 +c *i_absorber_hole2,i_norman,i_alpha, +c 1i_outer_sphere,i_exc_pot,i_mode + + + + COMMON/POT_TYPE/I_ABSORBER,I_ABSORBER_HOLE,I_ABSORBER_HOLE1, + * I_ABSORBER_HOLE2,I_NORMAN,I_ALPHA, + 1 I_OUTERSPHERE,I_EXC_POT,I_MODE + + + + +C COMMON/APARMS/XV(NATOMS),YV(NATOMS),ZV(NATOMS),Z(NATOMS), +C u NSYMBOL(NATOMS),NZEQ(NATOMS),NEQ(NATOMS),NCORES(NATOMS), +C . LMAXAT(NATOMS) + +C COMMON/APARMS_EXTRA/RS_(NATOMS),REDF_(NATOMS),OVLF + + + common/aparms/xv(natoms),yv(natoms),zv(natoms),z(natoms), + u nsymbl(natoms),nzeq(natoms),neq(natoms),ncores(natoms), + u lmaxat(natoms),ktau(ua_),natau(neq_,ua_) +C + COMMON/CRHOAT/RO(441,UA_,1) +c + DIMENSION X(441),RMESH(441) +C + DIMENSION XC(NAT_),YC(NAT_),ZC(NAT_) +C + DIMENSION NPAC(100) +C + LOGICAL OK +C + OK = .TRUE. +C +C* * * Initialize variables for subroutine molpot * * * +C + MESH = 441 +C +C Prepare coordinate vectors to input subroutine moldat +C + DO 10 I=1,NAT + XC(I) = XV(I+1) + YC(I) = YV(I+1) +10 ZC(I) = ZV(I+1) +C Initialize to zero the vector indicating for which atom the density +C has already been calculated + DO N = 1, 100 + NPAC(N) = 0 + ENDDO +C +C compute x and r mesh (441 points) +C + NBLOCK=11 + I=1 + X(I)=0.0D0 + RMESH(I)=0.0D0 + DELTAX=0.0025D0 + DO 120 J=1,NBLOCK + DO 121 K=1,40 + I=I+1 + X(I)=X(I-1)+DELTAX +121 CONTINUE +C +C For each new block, double the increment +C + DELTAX=DELTAX+DELTAX +120 CONTINUE +C +C Loop over prototypical atoms excluding outer sphere +C + NDAT1 = NDAT-1 + + DO 100 M=2,NDAT + DO NR = 1, 441 + RO(NR,M,1) = 0.D0 + ENDDO + IHOLE = 0 + IF (M.EQ.2.AND.CHARELX.EQ.'ex') IHOLE=HOLE + NZAT = NZEQ(M) + IF(NZAT.NE.0) CION=CHARGE_ION(NZAT) + ZAT = Z(M) +C +C.....CHANGE FOR EMPTY SPHERES; CHS=0.88534138D0/ZAT**(1.D0/3.D0) +C + IF(ZAT.NE.0.D0) THEN + CHS=0.88534138D0/ZAT**(1.D0/3.D0) + ELSE + CHS=0.88534138D0 + ENDIF +C +C Factor CHS is to go from X values to R values +C (the latter in atomic units; See Herman-Skillman p.5-3) +C + DO 130 I=2,MESH + RMESH(I)=CHS*X(I) +130 CONTINUE +C + IF(NZAT.EQ.0) GO TO 100 + IF(NPAC(NZAT).EQ.0) THEN + CALL atom_sub(NZAT,IHOLE,RMESH(1),RO(1,M,1),0,0,CION) + IF(M.NE.2) NPAC(NZAT) = M + GO TO 100 + ELSE + DO I = 1, 441 + RO(I,M,1) = RO(I,NPAC(NZAT),1) + ENDDO + ENDIF +C +100 CONTINUE +C +C* * * * Generate input structural parameters for subroutine molpot * * +C +C + CALL MOLDAT(XC,YC,ZC,NZEQ(2),NEQAT(1),NAT,NDAT1,OK) +C + RETURN +C + END +C +C******************************* +C + subroutine atom_sub(iz,ihole,r_hs,rho0_hs,i_mode_atom, + $ i_radial,xion) +c +c i_mode_atom = 1 pass_back P_nK corresponding to neutr +c atom. i_radial designates radial function +c which is passed back in array rho0_hs re +c to mesh r_hs. +c I_radial has same label convention +c as ihole (1 = 1s1/2 ...). +c = all else pass back charge density in rho0_hs. +c +c + implicit real*8(a-h,o-z) +c + parameter ( mp = 251, ms = 30 ) +c + character*40 title +c + + common dgc(mp,ms),dpc(mp,ms),bidon(630),IDUMMY +c +c common /pass/ passd, passvt(251), passvc(251), passc(251) +c rho0 not renormalized +c common /rho/rho0(251) +c dgc contains large component radial functions +c common /deux/ dvn(251), dvf(251), d(251), dc(251), dgc(251,30) +c passc and rho0 contain 4*pi*r^2*rho(r) +c +CST ==> Phagen to python shared object modifications +CST dimension r(mp),r_hs(440),rho0_hs(440) + dimension r(mp),r_hs(*),rho0_hs(*) +CST Phagen to python shared object modifications <== +C + dimension dum1(mp), dum2(mp) + dimension vcoul(mp), rho0(mp), enp(ms) +c + title = ' ' +c + ifr=1 + iprint=0 +C + amass=0.0d0 + beta=0.0d0 +c +c There are no nodes in relativistic radial charge density +c + small=1.0d-11 +c !Hence a lower limit on rho(r) can be used. + dpas=0.05d0 + dr1=dexp(-8.8d0) + dex=exp(dpas) + r_max=44.447d0 +c +c compute relativistic Hartree-Fock charge density (on log mesh) +C and core state orbital wave function +c open(unit=543,file='atom_.dat',status='unknown') +c + + call scfdat (title, ifr, iz, ihole, xion, amass, beta, iprint, + 1 vcoul, rho0, dum1, dum2, enp, eatom) + + +c +c compute radial log mesh (see subroutine phase in J.J. Rehr's progr +c FEFF.FOR) +c + ddex=dr1 + do 10 i=1,251 + r(i)=ddex + ddex=ddex*dex +10 continue +C + DO JMP=1,MP + WRITE(66,*) R(JMP),RHO0(JMP) + ENDDO +c + do 15 i=1,440 + rho0_hs(i)=0.0d0 +15 continue + +c +cman if(i_mode_atom.eq.1)goto 30 +c + if(i_mode_atom.eq.1)goto 31 +c +c using mesh form xainpot (r=0 not included) +c + do 30 i=1,440 + if(r_hs(i).gt.r_max) goto 30 +c +c find nearest points +c initialize hunting parameter (subroututine nearest) +c + jlo=1 + call nearest(r,251,r_hs(i), + 1 i_point_1,i_point_2,i_point_3,jlo) + if(abs(rho0(i_point_3)).lt.small) goto 30 +c interpolate charge density + call interp_quad( r(i_point_1),rho0(i_point_1), + 1 r(i_point_2),rho0(i_point_2), + 1 r(i_point_3),rho0(i_point_3), + 1 r_hs(i),rho0_hs(i),dm1,dm2 ) +c +c branch point +c +30 continue +31 continue +c +c + if(i_mode_atom.ne.1)goto 50 +c +c wave function generation +c using mesh form xainpot (r=0 not included) +c + do 40 i=1,440 + if(r_hs(i).gt.r_max) goto 50 +c +c find nearest points +c initialize hunting parameter (subroututine nearest) +c + jlo=1 + call nearest(r,251,r_hs(i), + 1 i_point_1,i_point_2,i_point_3,jlo) +c interpolate wavefunction + call interp_quad( + 1 r(i_point_1),dgc(i_point_1,i_radial), + 1 r(i_point_2),dgc(i_point_2,i_radial), + 1 r(i_point_3),dgc(i_point_3,i_radial), + 1 r_hs(i),rho0_hs(i),dm1,dm2 + 1 ) +40 continue +c +c branch point +c +50 continue +c + return + end +C + SUBROUTINE NEAREST(XX,N,X,I_POINT_1,I_POINT_2,I_POINT_3,JLO) +C +C FIND NEAREST THREE POINTS IN ARRAY XX(N), TO VALUE X +C AND RETURN INDICES AS I_POINT_1,I_POINT_2 AND I_POINT_3 +C This subroutine was taken from Numerical Recipes, +C W. H. Press, B. F. Flanney, S. A. Teukolsky and W. T. +C Vetterling, page 91. Originally called HUNT +c + IMPLICIT REAL*8(A-H,O-Z) + +C + DIMENSION XX(N) + LOGICAL ASCND + ASCND=XX(N).GT.XX(1) +C +C EXTRAPOLATE BELOW LOWEST POINT +C + IF(X.LE.XX(1))THEN + I_POINT_1=1 + I_POINT_2=2 + I_POINT_3=3 + RETURN + END IF +C +C EXTRAPOLATE BEYOND HIGHEST POINT +C + IF(X.GE.XX(N))THEN + I_POINT_1=N-2 + I_POINT_2=N-1 + I_POINT_3=N + RETURN + END IF + IF(JLO.LE.0.OR.JLO.GT.N)THEN + JLO=0 + JHI=N+1 + GO TO 3 + ENDIF + INC=1 + IF(X.GE.XX(JLO).EQV.ASCND)THEN +1 JHI=JLO+INC + IF(JHI.GT.N)THEN + JHI=N+1 + ELSE IF(X.GE.XX(JHI).EQV.ASCND)THEN + JLO=JHI + INC=INC+INC + GO TO 1 + ENDIF + ELSE + JHI=JLO +2 JLO=JHI-INC + IF(JLO.LT.1)THEN + JLO=0 + ELSE IF(X.LT.XX(JLO).EQV.ASCND)THEN + JHI=JLO + INC=INC+INC + GO TO 2 + ENDIF + ENDIF +3 IF(JHI-JLO.EQ.1)THEN + IF((JLO+1).EQ.N)THEN + I_POINT_1=JLO-1 + I_POINT_2=JLO + I_POINT_3=JLO+1 + ELSE + I_POINT_1=JLO + I_POINT_2=JLO+1 + I_POINT_3=JLO+2 + END IF + RETURN + END IF + JM=(JHI+JLO)/2 + IF(X.GT.XX(JM).EQV.ASCND)THEN + JLO=JM + ELSE + JHI=JM + ENDIF + GO TO 3 + END +C +C + SUBROUTINE INTERP_QUAD(X1,Y1,X2,Y2,X3,Y3,X4,Y4,DY4,D2Y4) +C +c Quadratic interpolation based on the polinomial y = ax^2+bx+c. +c Finds y4=f(x4) given x1,y1,x2,y2,x3,y3 and x4 as input parameters. +c Returns also the derivatives at the point y4. +c This subroutine for real funtion y. +C + IMPLICIT REAL*8(A-H,O-Z) +C + TOP = (Y2-Y1)*(X3*X3-X2*X2)- (Y3-Y2)*(X2*X2-X1*X1) + BOTTOM = (X2-X1)*(X3*X3-X2*X2)- (X3-X2)*(X2*X2-X1*X1) + B = TOP/BOTTOM + A = ( (Y2-Y1)- B*(X2-X1) )/(X2*X2-X1*X1) + C = Y3 - A*X3*X3 - B*X3 + Y4 = A*X4*X4 + B*X4 + C + DY4 = 2.0*A*X4 + B + D2Y4 = 2.0*A +C + RETURN + END +C +C*********************************************************************** +C + SUBROUTINE MOLDAT(XCOORD,YCOORD,ZCOORD,ZNUMBE,GROUPN,NATOMSM, + 1 NTYPES,OK) +C +C 8-dec-86 C.Brouder +C This subroutine builds the file containing the additional input +C required for MOLPOT once CLEM has been run. +C 15-dec-86 If program CONTINUUM is to be run with complex +C potential, set all alpha parametres to zero. +C If program MOLPOT is to be run with an outer sphere, +C write corresponding parametres. +C +C Arguments description : +C XCOORD,YCOORD,ZCOORD Array of the coordinates of the atoms +C ZNUMBE Array of the atomic numbers of the atoms +C GROUPN Array of the number of the group to which the +C atoms belong. (A group is a class of atoms equivalent +C by the symmetry operations of the symmetry group) +C NATOMSM Number of atoms +C NTYPES Number of groups (prototypical atoms) +C +C DATA description (Value of data is [value]) : +C NRUNS Number of cluster for which potential is computed [1] +C INV Logical unit from which output from CLEM is read [8] +C +C NOUT 0 No outer sphere, 1 an outer sphere [0] +C NWR1 Punched output to be punched [PCH] +C NWR2 Print charge densities, charge, potential [PRT] +C 1NSPINS 1 spin restricted potential, 2 spin polarized potential [1] +C EXAFCO Slater alpha parameter for exchange for the interstitial regi +C OVLF Overlap factor of neighbouring spheres [.10] +C CHPERC The charge radius of the atom, is defined as the radius +C for which the integrated density of charge is Z*(1+CHPER +C This is used to compute the muffin-tin radii [0.005] +C NCUT A control number intended to change the mesh size for high +C energy calculations [0] (= no change) +C +C NSYMBL 4 character description of the atom (Symbol + number) +C NEQ 0 for prototypical atoms +C NTYPE of the prototypical atom for atoms equivalent to N +C NGBR The number of neighbours surrounding the atom. +C NTYPE Type of the atom (Group number) +C XV,YV,ZV Coordinates in atomic units +C EXFACT Slater alpha parameter +C +C ALPHAP Alpha Parameter of elements, from Schwarz, (Phys.Rev.B 5(7) +C 2466 (1972)) up to Z=41 (Nb), some possible "interpolation" +C for the other elements. +C NAMEAT Name of atoms +C OUTER Logical. .TRUE. if MOLPOT is to be run with an outer sphere +C BOHRAD Bohr radius in Angstrom +C +C*********************************************************************** +C + implicit real*8 (a-h,o-z) +C + INCLUDE 'msxas3.inc' +C + COMMON/CONTINUUM/EMIN,EMAX,DELTA,CIP,GAMMA,EFTRI,IEXCPOT +C +C +C + COMMON/MOLINP/ + 1 EXAFCOM,EXFCTM(NAT_),OVLFM,CHPERCM,IITYPE,IIATOM, + 1 NGBRM(NAT_),NTYPEM(NAT_),NATAN(NAT_,UA_), + 1 NAM(NAT_,UA_),NAT1(NAT_,UA_),NWR1,NWR2 + +C + PARAMETER (NEIMAX=nat_) + REAL*8 XCOORD(NATOMS),YCOORD(NATOMS),ZCOORD(NATOMS) + INTEGER ZNUMBE(NATOMS-1),ZNBRE,GROUPN(NATOMS) + INTEGER NEIGHB(NEIMAX),NUMNEI(NEIMAX) + LOGICAL OK,OUTER,PROTO,DEUX + CHARACTER*5 NWR1,NWR2 + DIMENSION ALPHAP(100) + DATA NRUNS/1/,INV/8/ + DATA NOUT/0/,NSPINS/1/ + DATA OVLF/0.0/,CHPERC/0.005/,NCUT/1/ +C DATA BOHRAD/.529177/ + DATA BOHRAD/1.0/ +C H-Ne,Na-Ca,Sc-Zn,Ga-Zr,Nb-Sn,Sb-Nd,Pm-Yb + DATA ALPHAP/.978,.773,.781,.768,.765,.759,.752,.744,.737,.731, + 1 .731,.729,.728,.727,.726,.725,.723,.722,.721,.720, + 1 .718,.717,.716,.714,.713,.712,.710,.709,.707,.707, + 1 .707,.707,.707,.706,.706,.706,.706,.705,.705,.704, + 1 .704,.704,.704,.704,.704,.704,.704,.704,.704,.704, + 1 .703,.703,.703,.703,.703,.703,.703,.703,.703,.703, + 1 .702,.702,.702,.702,.702,.702,.702,.702,.702,.702, + 1 30*.702/ + NWR1=' PCH' + NWR2=' PRT' +C +C Check whether complex potential will be used +C + IF (IEXCPOT.EQ.4.OR.IEXCPOT.EQ.5) THEN + DO 100 I=1,100 + ALPHAP(I)=0. +100 CONTINUE + END IF +C +C Ask whether an outer sphere is to be used. +C 13-APR-87 In this new version, the file is always generated with an o +C sphere. +C + OUTER=.TRUE. +C +C* * * * Open file and write header * * * * * * * +C + OPEN(UNIT=2,FILE='div/STRPARM.DAT',STATUS='UNKNOWN', + & FORM='FORMATTED') +C +C Write first line +C + WRITE(2,2000) NRUNS,INV +2000 FORMAT(2I5) +C +C Compute EXAFCO (EXAFCO is taken as the average of all alpha parametr +C and write second line. +C +C Correction for the presence of empty spheres: 27th Sept 2007 +C + NPA = 0 + EXAFCO=0. + DO 200 I=1,NATOMSM + NZAT = ZNUMBE(I) + IF(NZAT.NE.0) THEN + NPA = NPA + 1 + EXAFCO=EXAFCO+ALPHAP(NZAT) + ENDIF +200 CONTINUE + EXAFCO=EXAFCO/NPA + IF (OUTER) THEN + IITYPE=NTYPES+1 + IIATOM=NATOMSM+1 + NOUT=1 + ELSE + IITYPE=NTYPES + IIATOM=NATOMSM + NOUT=0 + END IF + WRITE(2,2010) IITYPE,IIATOM,NOUT,NWR1,NWR2,NSPINS,EXAFCO,OVLF, + 1 CHPERC,NCUT +2010 FORMAT(3I5,2A5,I5,3F10.5,I5) +C + EXAFCOM=EXAFCO + OVLFM=OVLF + CHPERCM=CHPERC +C +C* * * * * * Write outer sphere description if any * * * * +C + IF (OUTER) THEN + XV=0. + YV=0. + ZV=0. + ITYPE=0 + CALL GRPNEI(ITYPE,XCOORD,YCOORD,ZCOORD,GROUPN,NATOMSM, + 1 NGBR,NEIGHB,NUMNEI,OK) + IF (.NOT.OK) THEN + CLOSE(UNIT=2) + RETURN + END IF + EXFACT=EXAFCO + ZNBRE=0 + PROTO=.TRUE. + N = 1 + CALL WRIDAT(XV,YV,ZV,ITYPE,ZNBRE,NGBR,EXFACT,GROUPN, + 1 NUMNEI,NEIGHB,NATOMSM,OUTER,PROTO,N) + END IF +C +C* * * * * * Write prototypical atom description * * * * * +C + DO 300 NTYPE=1,NTYPES + XV=XCOORD(NTYPE)/BOHRAD + YV=YCOORD(NTYPE)/BOHRAD + ZV=ZCOORD(NTYPE)/BOHRAD +C +C + CALL GRPNEI(NTYPE,XCOORD,YCOORD,ZCOORD,GROUPN,NATOMSM, + 1 NGBR,NEIGHB,NUMNEI,OK) + IF (.NOT.OK) THEN + CLOSE(UNIT=2) + RETURN + END IF + ZNBRE=ZNUMBE(NTYPE) +C +C.......CHANGE FOR ES +C + IF(ZNBRE.EQ.0.D0) THEN + EXFACT=EXAFCO + ELSE + EXFACT=ALPHAP(ZNBRE) + ENDIF + PROTO=.TRUE. + N=NTYPE+1 + CALL WRIDAT(XV,YV,ZV,NTYPE,ZNBRE,NGBR,EXFACT,GROUPN, + 1 NUMNEI,NEIGHB,NATOMSM,OUTER,PROTO,N) +300 CONTINUE +C +C* * * * * Write non prototypical atom description * * * * * * +C + IF (NATOMSM.GT.NTYPES) THEN + DO 400 I=NTYPES+1,NATOMSM + XV=XCOORD(I)/BOHRAD + YV=YCOORD(I)/BOHRAD + ZV=ZCOORD(I)/BOHRAD + ZNBRE=ZNUMBE(I) +C +C.......CHANGE FOR ES +C + IF(ZNBRE.EQ.0.D0) THEN + EXFACT=EXAFCO + ELSE + EXFACT=ALPHAP(ZNBRE) + ENDIF + CALL GRPNEI(I,XCOORD,YCOORD,ZCOORD,GROUPN,NATOMSM, + 1 NGBR,NEIGHB,NUMNEI,OK) + IF (.NOT.OK) THEN +C CLOSE(UNIT=2) + RETURN + END IF + PROTO=.FALSE. + N = I + 1 + CALL WRIDAT(XV,YV,ZV,I,ZNBRE,NGBR,EXFACT,GROUPN, + 1 NUMNEI,NEIGHB,NATOMSM,OUTER,PROTO,N) +400 CONTINUE + END IF +C CLOSE (UNIT=2) +C +C * * * * * * * Create MOLSYM.COO * * * * * * * * +C +C Now we create a file called MOLSYM.COO which lists the coordinates +C and the number of each atom in the cluster, according to the +C FORMAT required by MOLSYM. This file will be used later on to +C make the input file of MOLSYM. In this file, the atoms must be +C ordered according to their group (all equivalent atoms must follow +C each other), and numbered according to the way their are declared +C in the input of MOLPOT. If an outer sphere is to be used, it must +C be declared to be atom number 1. +C According to the FORMAT required by MOLSYM, the atoms must +C be written in pairs. The logical variable DEUX is here to say +C that two atoms are available and it is time to write them. +C + OPEN(UNIT=2,FILE='div/molsym.coo',STATUS='unknown') +C*************************************************** +C*************************************************** + DEUX=.TRUE. +C**** IF (OUTER) THEN +C**** XX1=0. +C**** YY1=0. +C** ZZ1=0. +C** NN1=1 +C** DEUX=.FALSE. +C** END IF +C + X0 = XCOORD(1) + Y0 = YCOORD(1) + Z0 = ZCOORD(1) +C + DO 500 ITYPE=1,NTYPES + DO 500 I=1,NATOMSM +C +C Order atoms according to their groups +C + IF (GROUPN(I).EQ.ITYPE) THEN + IF (DEUX) THEN + XX1=XCOORD(I)/BOHRAD - X0 + YY1=YCOORD(I)/BOHRAD - Y0 + ZZ1=ZCOORD(I)/BOHRAD - Z0 +C*** IF (OUTER) THEN +C*** NN1=I+1 +C*** ELSE + NN1=I +C*** END IF + DEUX=.FALSE. + ELSE + XX2=XCOORD(I)/BOHRAD - X0 + YY2=YCOORD(I)/BOHRAD - Y0 + ZZ2=ZCOORD(I)/BOHRAD - Z0 +C*** IF (OUTER) THEN +C*** NN2=I+1 +C*** ELSE + NN2=I +C*** END IF + WRITE (2,3000) XX1,YY1,ZZ1,NN1,XX2,YY2,ZZ2,NN2 +3000 FORMAT(2(3F10.6,I5,5X)) + DEUX=.TRUE. + END IF + END IF +500 CONTINUE +C +C If the number of atoms written in the file (including possibly +C the outer sphere) is not even, there is an atom that is left +C to be written, so write it. In any case, close the file. +C + IF (.NOT.DEUX) THEN + WRITE (2,3010) XX1,YY1,ZZ1,NN1 +3010 FORMAT(3F10.6,I5,5X) + END IF + CLOSE (UNIT=2) + RETURN + END +C +C*********************************************************************** +C + SUBROUTINE GRPNEI(ITYPE,XCOORD,YCOORD,ZCOORD,GROUPN,NATOMSM, + 1 NGBR,NEIGHB,NUMNEI,OK) +C +C 9-dec-86 C.Brouder +C This subroutine finds the groups of neighbours of atom number ITYPE +C A group of neighbours of atom ITYPE is a set of all atoms +C at the same distance from atom ITYPE and belonging to the same group +C (i.e. equivalent to the same prototypical atom, i.e.having the same +C group number GROUPN). +C At the end, the groups of neigbours are sorted according to increasi +C distances. +C +C Arguments description : +C ITYPE # of atom (0 if outer sphere) whose neighbours +C are to be determined. +C XCOORD,YCOORD,ZCOORD Array of the coordinates of the atoms. +C GROUPN Array of the number of the group to which the +C atoms belong. (A group is a class of atoms equivalent +C by the symmetry operations of the symmetry group). +C NATOMSM Number of atoms +C NGBR Number of groups of neighbours +C NEIGHB # of an atom in the group of neigbours +C NUMNEI Number of atoms in the group of neighbours +C NEIMAX Maximum number of groups of neighbours. +C +C DISTAN Array of distances of neigbours +C EPSILO If the distances are smaller than EPSILO, they are +C supposed to be identical. +C +C********************************************************************* +C + implicit real*8 (a-h,o-z) +C + INCLUDE 'msxas3.inc' +C + PARAMETER (NEIMAX=nat_) + DIMENSION XCOORD(NATOMS),YCOORD(NATOMS),ZCOORD(NATOMS) + DIMENSION DISTAN(NEIMAX) + INTEGER GROUPN(NATOMS),NEIGHB(NEIMAX),NUMNEI(NEIMAX) + LOGICAL OK,NEW + DATA EPSILO/1.E-5/ + NGBR=1 +C +C Initialize arrays +C + DO 100 I=1,NATOMSM + NEIGHB(I)=0 + NUMNEI(I)=0 +100 CONTINUE + IF (ITYPE.EQ.0) THEN + X0=0. + Y0=0. + Z0=0. + ELSE + X0=XCOORD(ITYPE) + Y0=YCOORD(ITYPE) + Z0=ZCOORD(ITYPE) + END IF +C +C Scan all other atoms +C + DO 200 I=1,NATOMSM + IF (I.NE.ITYPE) THEN +C +C Compute distance +C + NEW=.TRUE. + DISTAN(NGBR)=(XCOORD(I)-X0)*(XCOORD(I)-X0) + DISTAN(NGBR)=DISTAN(NGBR)+(YCOORD(I)-Y0)*(YCOORD(I)-Y0) + DISTAN(NGBR)=DISTAN(NGBR)+(ZCOORD(I)-Z0)*(ZCOORD(I)-Z0) + DISTAN(NGBR)=SQRT(DISTAN(NGBR)) + IF (NGBR.NE.1) THEN +C +C Check whether this distance already exists and the corresponding +C atom belongs to the same group. +C + DO 210 I2=1,NGBR-1 + IF ((ABS(DISTAN(I2)-DISTAN(NGBR)).LT.EPSILO).AND. + 1 (GROUPN(NEIGHB(I2)).EQ.GROUPN(I))) THEN + NEW=.FALSE. + NUMNEI(I2)=NUMNEI(I2)+1 + END IF +210 CONTINUE + END IF +C +C If it does not, this is a new group +C + IF (NEW) THEN + NUMNEI(NGBR)=1 + NEIGHB(NGBR)=I + NGBR=NGBR+1 + IF (NGBR.GT.NEIMAX) THEN + PRINT 4000 +4000 FORMAT(' Too many neighbours, increase NEIMAX in', + 1 ' subroutines GRPNEI and MOLDAT') + OK=.FALSE. + RETURN + END IF + END IF + END IF +200 CONTINUE + NGBR=NGBR-1 +C +C Order groups of neighbours according to increasing distances +C + DO 300 I=1,NGBR +C +C Look for the smallest remaining distance +C + DISMIN=1.E20 + IDISMI=I + DO 310 J=I,NGBR + IF (DISTAN(J).LT.DISMIN) THEN + DISMIN=DISTAN(J) + IDISMI=J + END IF +310 CONTINUE +C +C Transpose values +C + IF (IDISMI.NE.I) THEN + N1TEMP=NEIGHB(I) + N2TEMP=NUMNEI(I) + DTEMPO=DISTAN(I) + NEIGHB(I)=NEIGHB(IDISMI) + NUMNEI(I)=NUMNEI(IDISMI) + DISTAN(I)=DISTAN(IDISMI) + NEIGHB(IDISMI)=N1TEMP + NUMNEI(IDISMI)=N2TEMP + DISTAN(IDISMI)=DTEMPO + END IF +300 CONTINUE + RETURN + END +C +C*********************************************************************** +C + SUBROUTINE WRIDAT(XV,YV,ZV,ITYPE,ZNBRE,NGBR,EXFACT,GROUPN, + 1 NUMNEI,NEIGHB,NATOMSM,OUTER,PROTO,N) +C +C This subroutine writes on file 2 the data collected by MOLDAT, +C for each atom. There are many cases to consider : the outer sphere +C (ITYPE=0), prototypical atoms (PROTO=.TRUE.), non prototypical atoms +C (PROTO=.FALSE.) and in the latter cases, the outputs are different +C if there is an outer sphere (OUTER=.TRUE.) or not. +C Variable description +C XV,YV,ZV Position +C ITYPE # of atom whose data are involved +C ZNBRE Z number of atom +C NGBR Number of neighbours +C EXFACT Alpha parametre +C GROUPN Group numbers +C NUMNEI Number of neighbours +C NEIGHB Example of neighbour +C NATOMSM Number of atoms +C OUTER .TRUE. if there is an outer sphere +C PROTO .TRUE. if this is a prototypical atom +C +C NSYMBL Symbol +C +C******************************************************************** +C + implicit real*8 (a-h,o-z) +C + INCLUDE 'msxas3.inc' +C + REAL*8 EXAFCOM,EXFCTM,OVLFM,CHPERCM +C + COMMON/MOLINP/ + 1 EXAFCOM,EXFCTM(NAT_),OVLFM,CHPERCM,IITYPE,IIATOM, + 1 NGBRM(NAT_),NTYPEM(NAT_),NATAN(NAT_,UA_), + 1 NA(NAT_,UA_),NAT1(NAT_,UA_),NWR1,NWR2 +C + PARAMETER (NEIMAX=nat_) + INTEGER GROUPN(NATOMS),ZNBRE + INTEGER NEIGHB(NEIMAX),NUMNEI(NEIMAX) + LOGICAL PROTO,OUTER + CHARACTER*5 NWR1,NWR2 +C +C* * * * * * Initialize data * * * * * * * +C +C +C NEQ (0 if prototypical atom, NTYPE of prototypical atom otherwise +C + IF (PROTO) THEN + NEQ=0 + ELSE + IF (OUTER) THEN + NEQ=GROUPN(ITYPE)+1 + ELSE + NEQ=GROUPN(ITYPE) + END IF + END IF +C +C NTYPE (if outer sphere, outer sphere is number 1, so add 1 to +C all group numbers) +C + IF (PROTO) THEN + IF (OUTER) THEN + NTYPE=ITYPE+1 + ELSE + NTYPE=ITYPE + END IF + ELSE + NTYPE=NEQ + END IF +C +C* * * Initialize variables for subroutine molpot * * * +C + NGBRM(N)=NGBR + NTYPEM(N)=NTYPE + EXFCTM(N)=EXFACT +C +C* * * Initialize variables for subroutine molpot * * * +C + IF (PROTO) THEN + DO 300 K=1,NGBR + IF (OUTER) THEN + NATAN(K,N) = GROUPN(NEIGHB(K)) + 1 + NAT1(K,N) = NEIGHB(K) + 1 + ELSE + NATAN(K,N) = GROUPN(NEIGHB(K)) + NAT1(K,N) = NEIGHB(K) + ENDIF +300 NA(K,N) = NUMNEI(K) + ENDIF +C + RETURN + END +C +C*********************************************************************** +C + SUBROUTINE MOLPOT +C +C SPIN-RESTRICTED MOLECULAR POTENTIAL PROGRAM +C GENERATES SUPERPOSED-ATOM POTENTIAL USED TO START SCF CALCULATION +C + implicit real*8 (a-h,o-z) + include 'msxas3.inc' +c + include 'msxasc3.inc' +c + character*8 nsymbl +c.. +c common/dimens/nats,ndat,nout,lmaxx,irreps + common/aparms/xv(natoms),yv(natoms),zv(natoms),z(natoms), + u nsymbl(natoms),nzeq(natoms),neq(natoms),ncores(natoms), + u lmaxat(natoms) + common/aparms_extra/rs_(natoms),redf_(natoms),ovlf +c + integer trans + common/transform/trans(natoms) +C + COMMON/MOLINP/ + * EXFAC0,EXFACT(NAT_),OVLFM,CHPERC,NTYPES,NATOMSM, + * NGBR(NAT_),NTYPE(NAT_),NATAN(NAT_,UA_), + * NA(NAT_,UA_),NAT1(NAT_,UA_),NWR1,NWR2 +C + COMMON/CRHOAT/ RO(441,UA_,1) +C + COMMON/MPARMS/ RADION,QION,NCUT,NOUT,MOUT,NSAT +C + COMMON/MTRAD/ RS(NAT_) +C + COMMON/STRUCT/NTNABS(NAT_),NGBRABS +C + DIMENSION R(441,UA_),V(441,1),RV(441,UA_),Q(441),ALPHA(441), + 1 BETA(441),GAMMA(441,1),SNLO(441),XI(441),XJ(441), + 2 ZPALPH(441),ROTOTL(441,1),ROT(441) +C + DIMENSION ZM(NAT_),NZM(NAT_),NIMAX(NAT_),AN(NAT_,NAT_), + * FAC2(NAT_),RSC(NAT_) +C + CHARACTER*5 NWR1,NWR2 +C +c DATA PI/3.14159265358979/ +c DATA PI4/12.56637061435916/,THIRD/.333333333333333/ +C + LOGICAL SKIP + PI=3.14159265358979D0 + PI4=12.56637061435916D0 + THIRD=.333333333333333D0 + NRUNS = 1 + DO 999 IRUNS=1,NRUNS +1002 FORMAT(15I5) + SKIP=.FALSE. +C +C.....MOUT: CONTROLS THE OUTPUT OF PROGRAM INPOT. IF MOUT=1 THIS +C..... OUTPUT WILL CONTAIN THE OUTER SPHERE. IF MOUT=0 IT +C..... WILL NOT. THIS VERSION INITIALIZED TO MOUT=0 +C.....0VLF: THIS IS THE OVERLAP FACTOR FOR THE MUFFIN-TIN RADII +C..... DEFAULT=0.1 IN SUBROUTINE MOLDAT +C.....CHPERC: THIS IS THE PERCENTAGE OF ATOMIC CHARGE INSIDE THE +C..... ATOMIC SPHERES WHEN APPLYING NORMAN CRITERIUM +C..... DEFAULT=0.005 IN SUBROUTINE MOLDAT +C + MOUT=0 + NOUT=1 + NSPINS=1 + NSAT=1 + NCUT=1 + FAC1=NSPINS + NDAT=NATOMSM + OPEN (UNIT=7,FILE='div/molinpot3.out',STATUS='unknown') + DO 43 N=1,NATOMSM +C READ(5,1001) NSYMBL(N),NEQ(N),NGBR(N),NTYPE(N),XV(N),YV(N),ZV(N), +C 1 EXFACT(N) + 1001 FORMAT(1X,A8,3I5,4F10.6) + WRITE(7,1001) NSYMBL(N),NEQ(N),NGBR(N),NTYPE(N),XV(N),YV(N),ZV(N), + 1 EXFACT(N) + FAC2(N)=6.D0*EXFACT(N)*(FAC1*3.D0/(32.D0*PI*PI))**THIRD + IF(NEQ(N).NE.0) GO TO 443 + NGBRS=NGBR(N) +C READ(5,1002) (NATAN(I,N),NA(I,N),NAT1(I,N),I=1,NGBRS) +C NATAN=TYPE OF NEIGHBOR NA=NUMBER OF ATOMS IN GROUP NAT1=LABEL OF +C ONE OF THE NEIGHBORS +C + WRITE(7,1002) (NATAN(I,N),NA(I,N),NAT1(I,N),I=1,NGBRS) + IF(SKIP) GO TO 4511 + GO TO 43 + 4511 WRITE(7,1045) + 1045 FORMAT(' DIFFERENT ATOMS MUST COME FIRST') + SKIP=.FALSE. + GO TO 43 + 443 IF(SKIP) GO TO 43 + SKIP=.TRUE. + NDAT=N-1 + 43 CONTINUE +C +C AN(I,N): DISTANCE OF PROTOTYPICAL ATOM N FROM NEIGHBORS OF TYPE I +C + WRITE(7,*) + WRITE(7,*) 'DIST. OF PROTOTYPICAL ATOM N FROM NEIGHBORS OF TYPE I' + ANMAX = 0.0D0 + DO 44 N=1,NDAT + ANPR=0.0D0 + NGBRS=NGBR(N) + IF(N.EQ.2) NGBRABS=NGBRS + DO 44 I=1,NGBRS + NT = NATAN(I,N) + IF(N.EQ.2) NTNABS(I)=NT-1 +C write(6,*) i,nt,ntnabs(i),ngbrabs + NB=NAT1(I,N) + AN(I,N)=DSQRT((XV(NB)-XV(N))**2+(YV(NB)-YV(N))**2+(ZV(NB)-ZV(N))** + 1 2) + WRITE(7,*) N, NT, AN(I,N) + IF(I.EQ.1) THEN + ANPR=AN(I,N) + GO TO 440 + ENDIF + IF(AN(I,N).LT.ANPR) THEN + WRITE(7,30) I,N + 30 FORMAT(' **WARNING** : NEIGHBOR OF TYPE',I3,' TO ATOM',I3, + * ' NOT ARRANGED IN ASCENDING ORDER OF DISTANCE') +C +C CALL EXIT +C + ENDIF + 440 IF(N.NE.1) GO TO 44 + IF(AN(I,N).GT.ANMAX) ANMAX = AN(I,N) + 44 CONTINUE + SKIP=NOUT.NE.0 + WRITE(7,104) NATOMSM,NDAT,FAC1 + 104 FORMAT(30X,I3,7H ATOMS,,I3,17H DIFFERENT, FAC1=,F11.7) + WRITE(7,105) (NSYMBL(N),NEQ(N),XV(N),YV(N),ZV(N),EXFACT(N),N=1, + 1 NATOMSM) + 105 FORMAT(//28X,6HSYMBOL,4X,2HEQ,5X,1HX,11X,1HY,11X,1HZ,7X,6HEXFACT + 1 /(30X,A5,I6,4F11.7)) + DO 1 N=1,NTYPES + IF(SKIP) GO TO 89 + WRITE(7,2002) NZEQ(N),NSAT + 2002 FORMAT(6I4) + KMAX=441 + ZM(N)=NZEQ(N) + NZM(N)=NZEQ(N) + TZ=2.D0*ZM(N) + GO TO 90 + 89 DELTAR=.88534138D0*.0025D0 + NZM(1)=1 + GO TO 91 + 90 IF(ZM(N).EQ.0.D0) THEN + DELTAR=.88534138D0*.0025D0 + ELSE + DELTAR=.88534138D0*.0025D0/ZM(N)**THIRD + ENDIF + 91 I=1 + R(1,N)=0.D0 + DO 87 J=1,11 + DO 88 K=1,40 + I=I+1 + 88 R(I,N)=R(I-1,N)+DELTAR + 87 DELTAR=2.0D0*DELTAR + IF(SKIP) GO TO 49 + DO 52 K=1,441 + 52 ROT(K)=RO(K,N,1) + CALL MINTEGR(ROT,XI,R(1,N),441) + Q(1)=0.D0 + DO 10 I=2,441 + 10 Q(I)=ROT(I)/R(I,N) + CALL MINTEGR(Q,XJ,R(1,N),441) +C +C RV=R*( COULOMB POTENTIAL ) +C + DO 12 I=1,441 + 12 RV(I,N)=-TZ+2.D0*(XI(I)+R(I,N)*(XJ(441)-XJ(I))) + IF(NSPINS.EQ.1.AND.ZM(N).NE.0) + 1 WRITE(7,101) N,(I,R(I,N),RV(I,N),ROT(I),XI(I),I=1,KMAX) + 101 FORMAT(1H1,40X,22HATOMIC DATA FOR CENTER,I3,4X,/, + & 2(9X,1HR,15X,2HRV, + 1 14X,3HRHO,11X,6HCHARGE,3X),/,2(I4,1P4E15.6)) + GO TO 1 + 49 DO 50 J=1,441 + 50 RV(J,N)=0.D0 + 1 SKIP=.FALSE. + IF(NWR1.NE.' PCH') GO TO 1041 + OPEN (UNIT=4,FORM='UNFORMATTED',STATUS='unknown') + REWIND(4) + WRITE(4) NATOMSM,NDAT,NOUT,EXFAC0,NSPINS + KC=2 + 1041 DO 1000 M=1,NDAT + N=NTYPE(M) + NZM(M)=NZM(N) + NIMAX(M)=441 + IF(M.EQ.1.AND.NOUT.NE.0) GO TO 450 + DO 1043 J=1,441 + IF(R(J,N).LT.AN(1,M)) GO TO 1043 + NIMAX(M)=J + GO TO 450 + 1043 CONTINUE + 450 NBRS=NGBR(M) + IMAX=NIMAX(M) + DO 600 I=1,441 + ZPALPH(I)=0.D0 + BETA(I)=0.D0 + DO 600 ISPIN=1,NSPINS + ROTOTL(I,ISPIN)=0.D0 + 600 GAMMA(I,ISPIN)=0.D0 + DO 45 I=1,NBRS + MVAL=NATAN(I,M) + IF(NOUT.NE.0.AND.MVAL.EQ.1) GO TO 45 +C +C ITH SET OF NEIGHBORS TO CENTER M +C N IS TYPE OF CENTER M +C MVAL IS THE TYPE OF ITH SET OF NEIGHBORS TO CENTER M +C + IF(AN(I,M).GT..00001D0) GO TO 650 +C +C FOR A CENTER COINCIDING WITH THE MOLECULAR CENTER +C AVERAGE VALUES ARE EQUAL TO THE VALUES AT THE POINT +C + DO 652 J=2,IMAX + CALL MINTERP(R(J,N),RV(1,MVAL),XVAL,R(1,MVAL)) + ZPALPH(J)=ZPALPH(J)+NA(I,M)*XVAL + BETA(J)=BETA(J)-0.5D0*XVAL*NA(I,M)*R(J,N)**2 + DO 652 ISPIN=1,NSPINS + CALL MINTERP(R(J,N),RO(1,MVAL,ISPIN),XVAL,R(1,MVAL)) + ROTOTL(J,ISPIN)=ROTOTL(J,ISPIN)+NA(I,M)*XVAL/R(J,N) + 652 GAMMA(J,ISPIN)=GAMMA(J,ISPIN)-0.5D0*XVAL*NA(I,M)*R(J,N) + DO 451 ISPIN=1,NSPINS + CALL MINTEGR(RO(1,MVAL,ISPIN),SNLO,R(1,MVAL),441) + DO 451 J=1,441 + CALL MINTERP(R(J,N),SNLO,XVAL,R(1,MVAL)) + XJ(J)=R(J,MVAL)*RV(J,MVAL) + 451 GAMMA(J,ISPIN)=GAMMA(J,ISPIN)+NA(I,M)*XVAL + CALL MINTEGR(XJ,SNLO,R(1,MVAL),441) + DO 452 J=1,441 + CALL MINTERP(R(J,N),SNLO,XVAL,R(1,MVAL)) + 452 BETA(J)=BETA(J)+NA(I,M)*XVAL + GO TO 45 +C +C FOR SEPARATED CENTERS CALCULATE SPHERICAL AVERAGES AROUND CENTER M +C + 650 CALL MINTEGR(RV(1,MVAL),SNLO,R(1,MVAL),441) + CALL ALPHA0(AN(I,M),SNLO,ALPHA,R,IMAX,N,MVAL) + DO 65 J=2,IMAX + 65 ZPALPH(J)=NA(I,M)*ALPHA(J)+ZPALPH(J) + Q(1)=0.D0 +C +C SPHERICAL AVERAGE CHARGE DENSITY +C + DO 95 ISPIN=1,NSPINS + DO 901 J=2,441 + 901 Q(J)=RO(J,MVAL,ISPIN)/R(J,MVAL) + CALL MINTEGR(Q,SNLO,R(1,MVAL),441) + CALL ALPHA0(AN(I,M),SNLO,ALPHA,R,IMAX,N,MVAL) + DO 95 J=2,IMAX + 95 ROTOTL(J,ISPIN)=ROTOTL(J,ISPIN)+NA(I,M)*ALPHA(J) + IF(N.NE.1.OR.NOUT.EQ.0) GO TO 45 + XJ(1)=0.D0 +C +C TOTAL CHARGE FOR OUTER SPHERE +C + DO 37 ISPIN=1,NSPINS + DO 36 J=2,441 + 36 XJ(J)=-RO(J,MVAL,ISPIN)*(R(J,MVAL)-AN(I,M))**2/R(J,MVAL) + CALL MINTEGR(XJ,SNLO,R(1,MVAL),441) + CALL ALPHA0(AN(I,M),SNLO,Q,R,441,N,MVAL) + CALL MINTEGR(RO(1,MVAL,ISPIN),XJ,R(1,MVAL),441) + DO 37 J=2,441 + CALL MINTERP(R(J,N)-AN(I,M),XJ,XVAL,R(1,MVAL)) + 37 GAMMA(J,ISPIN)=GAMMA(J,ISPIN)+NA(I,M)*(XVAL+0.5D0*Q(J)) +C +C INTEGRATED POTENTIAL FOR OUTER SPHERE +C + XI(1)=0.D0 + XJ(1)=-RV(1,MVAL)*AN(I,M)**2 + DO 46 J=2,441 + XI(J)=RV(J,MVAL)*R(J,MVAL) + 46 XJ(J)=-RV(J,MVAL)*(R(J,MVAL)-AN(I,M))**2 + CALL MINTEGR(XI,Q,R(1,MVAL),441) + CALL MINTEGR(XJ,SNLO,R(1,MVAL),441) + CALL ALPHA0(AN(I,M),SNLO,ALPHA,R,441,N,MVAL) + DO 47 J=2,441 + CALL MINTERP(R(J,N)-AN(I,M),Q,XVAL,R(1,MVAL)) + 47 BETA(J)=BETA(J)+NA(I,M)*(XVAL+0.5D0*ALPHA(J)) + 45 CONTINUE + IF(N.NE.1.OR.NOUT.EQ.0) GO TO 2003 + DO 2005 J=1,IMAX + BETA(J)=(BETA(J)+0.5D0*ZPALPH(J)*R(J,N)**2)*PI4 + DO 2005 ISPIN=1,NSPINS + ROTOTL(J,ISPIN)=ROTOTL(J,ISPIN)*R(J,N) + 2005 GAMMA(J,ISPIN)=GAMMA(J,ISPIN)+0.5D0*ROTOTL(J,ISPIN)*R(J,N) + GO TO 112 +C +C INTEGRATED POTENTIAL AND TOTAL CHARGE FOR MUFFIN-TIN SPHERE +C GAMMA(I,ISPIN) IS TOTAL INTEGRATED CHARGE, BETA(I) IS INTEGRATED +C POTENTIAL, ZPALPH(I) IS R*VCOULOMB CALCULATED WITH PROJECTED +C DENSITY +C + 2003 DO 2001 J=1,IMAX + ZPALPH(J)=ZPALPH(J)+RV(J,N) + Q(J)=PI4*R(J,N)*ZPALPH(J) + DO 2001 ISPIN=1,NSPINS + 2001 ROTOTL(J,ISPIN)=ROTOTL(J,ISPIN)*R(J,N)+RO(J,N,ISPIN) + DO 2004 ISPIN=1,NSPINS + 2004 CALL MINTEGR(ROTOTL(1,ISPIN),GAMMA(1,ISPIN),R(1,N),IMAX) + CALL MINTEGR(Q,BETA,R(1,N),IMAX) + 112 DO 111 ISPIN=1,NSPINS + V(1,ISPIN)=0 + DO 111 J=2,IMAX +C +C VC(J) = ZPALPH(J)/R(J,N) +C + 111 V(J,ISPIN)=(ZPALPH(J)-FAC2(M)*(R(J,N)*DABS(ROTOTL(J,ISPIN)))**THIR + 1D)/R(J,N) +C +C...FIND RADIUS CONTAINING THE ATOMIC NUMBER OF ELECTRONS WITHIN CHPERC +C + RSC(M) = AN(1,M)/2.D0 + IF(M.EQ.1.AND.NOUT.EQ.1) GO TO 14 + IF(NZM(M).EQ.0) GO TO 14 + DO 13 I=1,IMAX +C IF(M.EQ.1.AND.NOUT.EQ.1) GO TO 13 + CHPCI=(ZM(M)-GAMMA(I,1))/ZM(M) + IF(CHPCI.GT.CHPERC)GO TO 13 + RSC(M) = R(I,M) + GO TO 14 + 13 CONTINUE + 14 IF(NWR2.NE.' PRT') GO TO 1032 + WRITE(7,6)M + 6 FORMAT(1H1,35X,11HATOM NUMBER,I6) + WRITE(7,7) (NA(I,M),NATAN(I,M),AN(I,M),I=1,NBRS) + 7 FORMAT(/ 23H NO. OF CENTERS TYPE,7X,8HDISTANCE/(5X,I4,10X,I + 1 4,F17.8)) + IF(NSPINS.EQ.1) WRITE(7,9)(J,R(J,N),ZPALPH(J),BETA(J),GAMMA(J,1),V + 1 (J,1),ROTOTL(J,1),J=1,IMAX) + 9 FORMAT(16X,1HR,16X,6HZPALPH,5X,20HINTEGRATED POTENTIAL,7X,12HTOTAL + 1 CHARGE,13X,1HV,18X,3HRHO/(I4,6E20.8)) + 1032 IF(NWR1.NE.' PCH') GO TO 1000 + NIMAX(M)=NIMAX(M)-1 + WRITE(4) NSYMBL(M),NEQ(M),NZM(M),NIMAX(M),XV(M),YV(M), + 1 ZV(M),EXFACT(M),KC + KC=KC+1 + DO 1014 ISPIN=1,NSPINS + DO 1014 K=2,IMAX,5 + KCARD=MIN0(IMAX,K+4) + WRITE(4) KC,( V(I,ISPIN),I=K,KCARD) + 1014 KC=KC+1 +C DO 1020 K=2,IMAX,5 +C KCARD=MIN0(IMAX,K+4) +C WRITE(4,1015) KC,( VC(I),I=K,KCARD) +C 1020 KC=KC+1 + DO 2214 ISPIN=1,NSPINS + DO 2214 K=2,IMAX,5 + KCARD=MIN0(IMAX,K+4) + WRITE(4) KC,(ROTOTL(I,ISPIN) ,I=K,KCARD) + 2214 KC=KC+1 + DO 1016 K=2,IMAX,5 + KCARD=MIN0(IMAX,K+4) + WRITE(4) KC,(BETA(I),I=K,KCARD) + 1016 KC=KC+1 + DO 1019 ISPIN=1,NSPINS + DO 1019 K=2,IMAX,5 + KCARD=MIN0(IMAX,K+4) + WRITE(4) KC,(GAMMA(I,ISPIN) ,I=K,KCARD) + 1019 KC=KC+1 + 1000 CONTINUE +C + WRITE(7,*) 'CHECKING MUFFIN-TIN RADII' + IF(OPTRSH.EQ.'y') THEN + WRITE(6,*) ' MT radii for Hydrogen atoms set to rsh' + WRITE(7,*) ' MT radii for Hydrogen atoms set to rsh =', RSH + ELSE + WRITE(6,*) ' MT radii for Hydrogen atoms determined by stdcrm', + & ' unless other options are specified' + WRITE(7,*) ' MT radii for Hydrogen atoms determined by stdcrm', + & ' unless other options are specified' + ENDIF + WRITE(7,*) ' M, Z(M), MN, Z(MN), AN(MN,M),', + & ' RSC(M), RSC(MN), RS(M), RS(MN)' +C +C FIND MUFFIN-TIN RADIUS FOR PAIR IJ ACCORDING TO NORMAN CRITERIUM (STDCRM) +C + DO 18 M=1,NDAT + IF(M.EQ.1.AND.NOUT.EQ.1) GO TO 18 +C IF(NOUT.EQ.1.AND.NDAT.EQ.2) GO TO 18 !if only the absorber is present 17/03/2019 + NBRS=NGBR(M) + IF(NZM(M).NE.0) THEN + DO NG = 1, NBRS + MN=NATAN(NG,M) + IF(NZM(MN).NE.0) GO TO 191 + ENDDO +191 RS(M)=AN(NG,M)*(1.D0+OVLF)/(1.D0+RSC(MN)/RSC(M)) +C +C IF OPTRSH='y' MT RADIUS FOR H ATOMs SET TO RSH IN INPUT ! Added 16 Jul 2013 +C + IF(NZM(M).EQ.1.AND.OPTRSH.EQ.'y') THEN + WRITE(6,*) ' MT radius', RS(M),' for H atom', M, + & ' set to', RSH + RS(M) = RSH + ENDIF + WRITE(7,190) M, NZM(M), MN, NZM(MN), AN(NG,M), + & RSC(M), RSC(MN), RS(M), RS(MN) + GO TO 18 + ENDIF + MN = NATAN(1,M) + IF (NZM(MN).EQ.0.D0) THEN + RS(M) = AN(1,M)*(1.D0+OVLF)/2.D0 + ELSE + RS(M) = (AN(1,M)-RS(MN))*(1.D0+OVLF) + ENDIF + WRITE(7,190) M, NZM(M), MN, NZM(MN), AN(1,M), + & RSC(M), RSC(MN), RS(M), RS(MN) +190 FORMAT(4I5, 5F10.5) + IF(NORMAN.EQ.'stdfac'.OR.NORMAN.EQ.'scaled') + *RS(M)=REDF_(M)*RSC(M) + 18 CONTINUE + IF(NOUT.EQ.1) RS(1) = ANMAX + RS(NDAT) + IF(NDAT.EQ.NATOMSM) GO TO 5001 + NDAT1=NDAT+1 + DO 221 M=NDAT1,NATOMSM + NZM(M)= NZM(NEQ(M)) + RS(M)= RS(NEQ(M)) + NIMAX(M)=0 + WRITE(4) NSYMBL(M),NEQ(M),NZM(M),NIMAX(M),XV(M),YV(M), + 1 ZV(M),EXFACT(M),KC + 221 KC=KC+1 + 5001 CONTINUE + IF (NORMAN.EQ.'extrad') THEN + RS(1) = ANMAX + RS_(NDAT) + DO 5002 M=2,NATOMSM + 5002 RS(M)=RS_(M) + END IF + IF (NORMAN.NE.'extrad') THEN + WRITE(6,*) + WRITE(6,5003) + 5003 FORMAT(1X,65('-')) + WRITE(6,*) ' i rs(i) i=1,natoms ' + WRITE(6,5004) (I, RS(I), I=1,NATOMSM) + WRITE(6,*) ' N.B.: Order of atoms as reshuffled by', + * ' symmetry routines ' + 5004 FORMAT(8(I5,1X,F7.2)) + WRITE(6,5003) + WRITE(6,*) + ELSE + WRITE(6,5003) + WRITE(6,*) ' External radii read in as: ' + WRITE(6,*) ' i rs(i) i=1,natoms ' + WRITE(6,5004) (I, RS(I), I=1,NATOMSM) + END IF + IF(NWR1.NE.' PCH') GO TO 999 + WRITE(7,*) + WRITE(7,*) ' Radion, qion, ncut, rs(i), i=1,nat' + WRITE(7,19) RADION,QION,NCUT,(RS(M),M=1,NATOMSM) + 19 FORMAT(/,1X,2F10.5,I5/(8F10.5),//) + 999 CONTINUE +C + REWIND(4) +C + RETURN + END +C +CLAGRNG + SUBROUTINE LAGRNG(F,LPLACE,B,RES) + IMPLICIT REAL*8(A-H,O-Z) + DIMENSION F(4),B(4) + RES=0.D0 + DO 5 N=1,4 + M=LPLACE-2+N + 5 RES=RES+B(N)*F(M) + RETURN + END +CBSET + SUBROUTINE BSET(PINTRP,B) + IMPLICIT REAL*8(A-H,O-Z) + DIMENSION B(4) + PM=PINTRP*(PINTRP**2-1.D0)*(PINTRP-2.D0) + B(1)=-PM/(6.D0*(PINTRP+1.D0)) + B(2)= PM/(2.D0*PINTRP) + B(3)=-PM/(2.D0*(PINTRP-1.D0)) + B(4)= PM/(6.D0*(PINTRP-2.D0)) + RETURN + END +CINTERP +C L.F. MATTHEISS SUBROUTINE INTERP(B,X1,M2,D,R) +C B IS THE RADIAL DISTANCE +C X1 IS THE INTEGRATED FUNCTION +C D IS THE INTERPOLATED VALUE OF THE INTEGRAL FROM 0 TO B. +C R IS THE RADIAL MESH +C + SUBROUTINE MINTERP(B,X1,D,R) + IMPLICIT REAL*8(A-H,O-Z) + DIMENSION X1(441),R(441),B1(4),C(4) + IF(B-R(2 ))10,11,12 + 10 D=0.0D0 + GOTO 100 + 11 D=X1(2) + GOTO 100 + 12 IF(B-R(440 ))15,14,13 + 13 D=X1(441) + GOTO 100 + 14 D=X1(440) + GOTO 100 + 15 DO 22 I=1,441 + L=441+1-I + IF(R(L)-B) 23,24,22 + 22 CONTINUE + 23 LPLACE=L + DO 29 N=1,11 + ISCALE=41+40*(N-1)-LPLACE + IF(ISCALE)25,46,25 + 25 IF(ISCALE-1)29,48,29 + 29 CONTINUE + B1(1)=X1(LPLACE-1) + B1(2)=X1(LPLACE) + B1(3)=X1(LPLACE+1) + B1(4)=X1(LPLACE+2) + H=R(LPLACE+1 )-R(LPLACE ) + 50 PINTRP=(B-R(LPLACE ))/H + 51 CALL BSET(PINTRP,C) + CALL LAGRNG(B1,2,C,D) + 100 RETURN + 24 D=X1(L) + RETURN + 46 B1(1)=X1(LPLACE-2) + B1(2)=X1(LPLACE) + B1(3)=X1(LPLACE+1) + B1(4)=X1(LPLACE+2) + H=R(LPLACE+1 )-R(LPLACE ) + GOTO 50 + 48 B1(1)=X1(LPLACE-3) + B1(2)=X1(LPLACE-1) + B1(3)=X1(LPLACE+1) + B1(4)=X1(LPLACE+2) + H=R(LPLACE+2 )-R(LPLACE+1 ) + PINTRP=(B-R(LPLACE-1 ))/H + GO TO 51 + END +CINTEGR +C SIMPSON'S RULE INTEGRATION +C + SUBROUTINE MINTEGR(X,Y,R,M2) + IMPLICIT REAL*8(A-H,O-Z) + DIMENSION X(441),Y(441),R(441) + H=R(2) + Y(1)=0.D0 + Y(2)=H*(5.D0*X(1 )+8.D0*X(2 )-X(3 ))/12.D0 + DO 20 J=1,11 + DO 10 K=1,40 + I=40*(J-1)+K + IF(I.GT.M2) RETURN + IF(I-440) 5,10,10 + 5 Y(I+2)=Y(I)+H*(X(I )+4.D0*X(I+1 )+X(I+2 ))/3.D0 + 10 CONTINUE + H=H+H + IF (I-440) 15,20,15 + 15 Y(I+2)=Y(I+1)+H*(5.D0*X(I+1 )+8.D0*X(I+2 )-X(I+3 ))/12.D0 + 20 CONTINUE + RETURN + END +CALPHAO +C L.F. MATTHEISS SUBROUTINE ALPHA0(AP,ZINT,ALPHA,R,IMAX,M1,M2) +C AP IS THE DISTANCE OF THE NEIGHBORING ATOM +C ZINT IS THE INDEFINITE INTEGRAL +C ALPHA IS A TABLE OF THE DESIRED ALPHA FUNCTIONS +C R IS THE RADIAL DISTANCE +C IMAX IS THE NUMBER OF ALPHA FUNCTIONS TO BE COMPUTED +C M1 IS THE ATOM NO. AT THE ORIGIN +C M2 IS THE ATOM NO. AT AP +C + SUBROUTINE ALPHA0(AP,ZINT,ALPHA,R,IMAX,M1,M2) +C + IMPLICIT REAL*8(A-H,O-Z) +C + include 'msxas3.inc' +C + DIMENSION ZINT(441),ALPHA(441),R(441,UA_) + DO 100 I=2,IMAX + APLUSR=AP+R(I,M1) + AMINSR=DABS(AP-R(I,M1)) + CALL MINTERP(APLUSR,ZINT,XVAL1,R(1,M2)) + CALL MINTERP(AMINSR,ZINT,XVAL2,R(1,M2)) + ALPHA(I)=(XVAL1-XVAL2)/(2.0D0*AP) + 100 CONTINUE + RETURN + END +C + SUBROUTINE INPOT +C + IMPLICIT REAL*8 (A-H,O-Z) +C + INCLUDE 'msxas3.inc' +C + character*2 potgen + character*4 coor + character*5 potype + character*7 ionzst + character*2 edge,charelx + character*6 norman + integer absorber,hole + logical*4 vinput + + + common/options/rsh,ovlpfac,vc0,rs0,vinput,absorber,hole,mode, + & ionzst,potype,norman,coor,charelx,edge,potgen + +C +C**** CONT_SUB DIMENSIONING VARIABLES +C + INTEGER AT_,D_,RD_,SD_ + PARAMETER ( AT_=NAT_-1,D_=UA_-1,RD_=440,SD_=UA_-1) +C +C**** +C + COMMON/MPARMS/ RADION,QION,NCUT,NOUT,MOUT,NSAT +C + COMMON/MTRAD/ RS(NAT_) +C + DIMENSION XV(NAT_),YV(NAT_),ZV(NAT_),Z(NAT_),NEQ1(NAT_), + 1EXFACT(NAT_),NZ(NAT_),NSYMBL(NAT_),NEQ(NAT_),H(NAT_), + 2VCONS(2),R(441,UA_),V(441,UA_),ICHG(10,UA_),KPLACE(NAT_), + 3KMAX(NAT_),VINT(UA_),CHARGE(UA_,2),ROCON(2),RHO(441,UA_) +C 4,VC(441,UA_) +C + DIMENSION RTEMP(440),VTEMP(441,2),GAMMA(440,2),DENSTEMP(441,2) + EQUIVALENCE (VTEMP(1,1),BETA(1)),(ROTEMP(1,1),GAMMA(1,1)) + DIMENSION BETA(440),ROTEMP(440,2) +C DIMENSION VCTEMP(441) +C +C +CC**** CONT_SUB COMMON BLOCKS +C + COMMON /DENS/ IRHO2,RHOTOT2(RD_,SD_),RHOINT2(2), + $ vcoul(rd_,sd_),vcoulint(2) +C + COMMON /FCNR/KXE2, H2(D_),VCONS2(2),R2(RD_,D_),V2(2,RD_,SD_), + $ ICHG2(10,D_),KPLACE2(AT_),KMAX2(AT_) + complex*16 VCONS2 +C + COMMON /FLAG/ INMSH,INV,INRHO,INSYM,IOVRHO,IOSYM, + 1 IMVHL,NEDHLP +C + CHARACTER*8 NAME0 ,NSYMBL2 +C + complex*16 VCON2,XE2,EV2 + COMMON/PARAM/EFTR2,GAMMA2,VCON2,XE2,EV2,E2,IOUT2,NAT2, + 1 NDAT2,NSPINS2,NAS2,RS2(AT_),XV2(AT_),YV2(AT_),ZV2(AT_), + 2 EXFACT2(AT_),Z2(AT_),LMAXX2(AT_),NZ2(AT_),NSYMBL2(AT_), + 4 NEQ2(AT_),NAME0,CIP,EMAX,EMIN,DE,RS_OS +C +C ############MODIFIED TO INCLUDE THE TWO CORE STATE WAVE FUNCTIONS +c ############FOR THE AUGER CALCULATION +c + common/pot_type/i_absorber,i_absorber_hole, + 1 i_absorber_hole1,i_absorber_hole2, + 2 i_norman,i_alpha,i_outer_sphere, + 3 i_exc_pot,i_mode + + + + + + +C +C***** +C +C + CHARACTER*8 NSYMBL +C + DATA PI/3.14159265358979D0/,THIRD/.333333333333333D0/ +C +C FORMAT FOR ALL FUNCTIONS OF RADIAL MESH POINTS +C FORMAT FOR ERROR MESSAGE IF INPUT CARD IS OUT OF ORDER +C + 400 FORMAT(' CARD',I5,' OUT OF SEQUENCE') + LOGICAL OUTER + READ(4) NAT,NDAT,NOUT,EXFAC0,NSPINS +C READ(10,8853)RADION,QION,NCUT,MOUT + + + IF(NCUT.EQ.0) NCUT=2 +C READ(10,8854)(RS(I),I=1,NAT) + IF (NAT.EQ.0) STOP 4602 + FAC1=NSPINS + IF(NOUT.EQ.0) WRITE(7,110) NAT + ROCON(2)=0 + ROCON(1)=0 + VCON=0.0D0 + IN = 0 +C +C IN=1 SECTION. INPUT DATA FROM MOLECULAR POTENTIAL PROGRAM +C + IF (IN.GT.1) GO TO 4300 + NC0=1 + 113 FORMAT(1H1,30X,18HNUMBER OF CENTERS=,I5,26H OUTER SPHERE AT CENTE + *R 1 ) + 110 FORMAT(1H1,30X,18HNUMBER OF CENTERS=,I5,17H NO OUTER SPHERE) + IF(NOUT.NE.0) WRITE(7,113)NAT + WRITE(7,8852)NCUT,RADION,QION +8852 FORMAT(30X,'NCUT=',I3,' RADION=',F7.3,' QION=', F7.1) + VOLUME=0.0D0 + DO 422 N=1,NAT + OUTER=NOUT.NE.0.AND.N.EQ.1 + READ(4) NSYMBL(N),NEQ(N),NZ(N),KMAX(N),XV(N),YV(N), + U ZV(N),EXFACT(N),NC + IF(NC.EQ.NC0+1) GO TO 423 + WRITE(7,400) NC + 423 NC0=NC + Z(N)=NZ(N) + IF(NEQ(N).NE.0) GO TO 439 + KMAXN=KMAX(N) + KMAXL=KMAXN +C +C CALCULATE RADIAL MESH FOR INPUT DATA +C + ZINO=Z(N) + IF(NZ(N) .EQ. 0) ZINO=1.D0 + HH=.0025D0*.88534138D0/ZINO**THIRD + RTEMP(1)=HH + KK=1 + K0=2 + DO 4285 I=1,11 + DO 4286 K=K0,40 + KK=KK+1 + IF(KK.GT.KMAXN) GO TO 1014 + 4286 RTEMP(KK)=RTEMP(KK-1)+HH + K0=1 + 4285 HH=2.0D0*HH + 1014 DO 1020 ISPIN=1,NSPINS +C +C READ STARTING POTENTIAL +C + DO 1019 K=1,KMAXN,5 + KCARD=MIN0(K+4,KMAXN) + READ(4) NC,( VTEMP(I,ISPIN),I=K,KCARD) + IF(NC.EQ.NC0+1) GO TO 1019 + WRITE(7,400) NC + 1019 NC0=NC + 1020 CONTINUE +C DO 1200 K=1,KMAXN,5 +C KCARD=MIN0(K+4,KMAXN) +C READ(4,1015) NC,( VCTEMP(I),I=K,KCARD) +C IF(NC.EQ.NC0+1) GO TO 1200 +C WRITE(7,400) NC +C ERROR=.TRUE. +C 1200 NC0=NC + DO 2720 ISPIN=1,NSPINS +C +C READ STARTING CHARGE DENSITY +C + DO 2723 K=1,KMAXN,5 + KCARD=MIN0(K+4,KMAXN) + READ(4) NC,(DENSTEMP(I,ISPIN),I=K,KCARD) + IF(NC.EQ.NC0+1) GO TO 2723 + WRITE(7,400) NC + 2723 NC0=NC + 2720 CONTINUE +C +C CONVERT INPUT DATA TO FORM FOR MOLECULAR CALCULATION +C + KMIN=1 + 428 KPL=(KMAXN+KMIN)/2 + IF(RTEMP(KPL)-RS(N)) 424,434,426 + 424 KMIN=KPL + IF(KMAXN-KMIN-1) 427,427,428 + 426 KMAXN=KPL + IF(KMAXN-KMIN-1) 427,427,428 + 427 KPL=KMIN + 434 KPL0=KPL + N40=40/NCUT + KPL=KPL/NCUT + IF(RTEMP(KPL*NCUT+NCUT)+RTEMP(KPL*NCUT)-2.D0*RS(N)) 429,430,430 + 429 KPL=KPL+1 + 430 IF(OUTER) GO TO 433 + KMAX(N)=KPL+3 + KMAXN=KMAX(N) + NMOD=MOD(KMAXN,N40) + IF(NMOD.GE.5.OR.NMOD.EQ.0) GO TO 431 + KMAXN=KMAXN-NMOD + 431 ICHGN=KMAXN + DO 432 K=1,KMAXN + KN=NCUT*K + R(K,N)=RTEMP(KN) + NS=N + DO 4320 IS=1,NSPINS + V(K,NS)=VTEMP(KN,IS) +C VC(K,NS)=VCTEMP(KN) + RHO(K,NS)=DENSTEMP(KN,IS) + 4320 NS=NS+NDAT + 432 CONTINUE + IF(KMAXN.EQ.KMAX(N)) GO TO 441 + KX1=KMAXN+1 + KMAXN=KMAX(N)+1 + IF(NCUT.EQ.1) GO TO 435 + DO 436 K=KX1,KMAXN + KN=(KX1+K-1)*NCUT/2 + R(K,N)=RTEMP(KN) + NS=N + DO 4360 IS=1,NSPINS + V(K,NS)=VTEMP(KN,IS) +C VC(K,NS)=VCTEMP(KN) + RHO(K,NS)=DENSTEMP(KN,IS) + 4360 NS=NS+NDAT + 436 CONTINUE + GO TO 440 + 435 DO 437 K=KX1,KMAXN + KN=(KX1+K-1)/2 + IF(2*((K-KX1+1)/2).EQ.(K-KX1+1)) GO TO 438 + R(K,N)=.5D0*(RTEMP(KN)+RTEMP(KN+1)) + NS=N + DO 4310 IS=1,NSPINS + CALL DINTERP(RTEMP(KN-3),VTEMP(KN-3 ,IS),7,R(K,N),V(K,NS),DUMMY, + 1 .FALSE.) +C CALL DINTERP(RTEMP(KN-3),VCTEMP(KN-3 ),7,R(K,N),VC(K,NS),DUMMY, +C 1 .FALSE.) + CALL DINTERP(RTEMP(KN-3),DENSTEMP(KN-3 ,IS),7,R(K,N), + 1 RHO(K,NS),DUMMY,.FALSE.) + 4310 NS=NS+NDAT + GO TO 437 + 438 R(K,N)=RTEMP(KN) + NS=N + DO 4311 IS=1,NSPINS + V(K,NS)=VTEMP(KN,IS) +C VC(K,NS)=VCTEMP(KN) + RHO(K,NS)=DENSTEMP(KN,IS) + 4311 NS=NS+NDAT + 437 CONTINUE + 440 IF( ABS(R(KPL,N)-RS(N)).LE. ABS(R(KPL+1,N)-RS(N))) GO TO 441 + KPL=KPL+1 + KMAX(N)=KMAX(N)+1 + 441 KPLACE(N)=KPL + ICHG(1,N)=N40 + DO 443 K=2,10 + ICHG(K,N)=ICHG(K-1,N)+N40 + IF(ICHG(K,N).GE.ICHGN) ICHG(K,N)=400/NCUT + 443 CONTINUE + GO TO 448 +C +C.....FOR OUTER REGION +C + 433 KMIN=(KPL-3)*NCUT + KMAX(N)=MIN0((440/NCUT-KPL+4),200) + ICHG(1,N)=(40-MOD(KMIN,40))/NCUT+1 + ICHGN=1 + IF(ICHG(1,N).GT.4) GO TO 444 + ICHGN=ICHG(1,N)-1 + DO 445 K=1,ICHGN + KN=KMIN+NCUT*(2*K-ICHG(1,N)-1) + R(K,N)=RTEMP(KN) + NS=N + DO 445 IS=1,NSPINS + V(K,NS)=VTEMP(KN,IS) +C VC(K,NS)=VCTEMP(KN) + RHO(K,NS)=DENSTEMP(KN,IS) + 445 NS=NS+NDAT + ICHG(1,N)=ICHG(1,N)+N40 + ICHGN=ICHGN+1 + 444 KMAXN=KMAX(N) + DO 446 K=ICHGN,KMAXN + KN=KMIN+(K-1)*NCUT + R(K,N)=RTEMP(KN) + NS=N + DO 446 IS=1,NSPINS + V(K,NS)=VTEMP(KN,IS) +C VC(K,NS)=VCTEMP(KN) + RHO(K,NS)=DENSTEMP(KN,IS) + 446 NS=NS+NDAT + DO 447 K=2,10 + 447 ICHG(K,N)=ICHG(K-1,N)+N40 + KPLACE(N)=4 +C +C.....FOR ATOMIC SPHERES +C + 448 NQ=N + K=KPL0 + IF(RTEMP(K+1)+RTEMP(K)-2.D0*RS(N).LT.0.0D0 ) K=KPL0+1 +C +C READ INTEGRATED POTENTIAL AND INTERPOLATE FOR VALUE ON BOUNDARY +C + DO 1016 KK=1,KMAXL,5 + KCARD=MIN0(KK+4,KMAXL) + READ(4) NC,(BETA(I),I=KK,KCARD) + IF(NC.EQ.NC0+1) GO TO 1016 + WRITE(7,400) NC + 1016 NC0=NC + CALL DINTERP(RTEMP(K-3), BETA(K-3),7,RS(N), VINT(N),DUMMY,.FALSE.) +C +C READ TOTAL CHARGE AND INTERPOLATE FOR VALUE ON BOUNDARY +C + DO 1022 ISPIN=1,NSPINS + DO 1021 KK=1,KMAXL,5 + KCARD=MIN0(KK+4,KMAXL) + READ(4) NC, (GAMMA(I,ISPIN),I=KK,KCARD) + IF(NC.EQ.NC0+1) GO TO 1021 + WRITE(7,400) NC + 1021 NC0=NC + 1022 CALL DINTERP(RTEMP(K-3),GAMMA(K-3,ISPIN),7,RS(N),CHARGE(N,ISPIN), + 1 DUMMY,.FALSE.) + GO TO 4281 +C +C.....FOR EQUIVALENT ATOMS +C + 439 NQ=NEQ(N) + KPLACE(N)=KPLACE(NQ) + 4281 IF(OUTER) GO TO 4280 + VOLUME=VOLUME-RS(N)**3 + VCON=VCON-VINT(NQ) + DO 455 IS=1,NSPINS + 455 ROCON(IS)=ROCON(IS)-CHARGE(NQ,IS) + IF(NEQ(N).NE.0) GO TO 422 + GO TO 4221 + 4280 VCON=VCON+VINT(NQ) + VOLUME=VOLUME+RS(N)**3 + DO 456 IS=1,NSPINS + 456 ROCON(IS)=ROCON(IS)+CHARGE(NQ,IS) + 4221 H(N)=R(2,N)-R(1,N) + 422 CONTINUE + VOLUME=1.3333333333333D0*PI*VOLUME + VCON=VCON/VOLUME + VCONC=VCON + IF (RADION.NE.0) THEN + DVSPH = -2.D0*QION/RADION + VCONC = VCONC + DVSPH + ENDIF + NS=1 + RH0 = 3.D0 / (NSPINS*4.D0*PI*RS0**3) +c write (*,*) ' vc0 =', vc0, ' rs0 =',rs0 + DO 453 IS=1,NSPINS + ROCON(IS)=ROCON(IS)/VOLUME + VCONS(IS)=VCON-6*EXFAC0*(3*FAC1*ROCON(IS)/(8*PI))**THIRD + VC0X = VC0 - 6*EXFAC0*(3*FAC1*RH0/(8*PI))**THIRD + IF(RADION.EQ.0) GO TO 453 + VCONS(IS)=VCONS(IS)+DVSPH + KX=KMAX(1) + DO 451 K=1,KX + IF(R(K,1).LT.RADION) GO TO 452 + V(K,NS)=V(K,NS)-2.D0*QION/R(K,1) +C VC(K,NS)=VC(K,NS)-2.*QION/R(K,1) + GO TO 451 + 452 V(K,NS)=V(K,NS)+DVSPH +C VC(K,NS)=VC(K,NS)+DVSPH + 451 CONTINUE + NS=NS+1 + DO 454 N=2,NDAT + KX=KMAX(N) + DO 450 K=1,KX +C VC(K,NS)=VC(K,NS)+DVSPH + 450 V(K,NS)=V(K,NS)+DVSPH + 454 NS=NS+1 + 453 CONTINUE + GO TO 4220 + 4300 WRITE(7,105) + 105 FORMAT(' IN IS EQUAL 2') +C +C OUTPUT AND CHECK FOR CONSISTENCY OF INPUT DATA +C + 4220 WRITE(7,111) + 111 FORMAT(30X,10HATOM NO.,12X,8HPOSITION,14X,13HRADIUS EQ ) + WRITE(7,112) (I,NSYMBL(I),NZ(I),XV(I),YV(I),ZV(I),RS(I),NEQ(I), + 1 I=1,NAT) + 112 FORMAT(26X,I3,A6,I6,4F10.4,I6) +C IF(NOUT.NE.0.AND.NOUT.NE.1) GO TO 205 +C GO TO 1130 +C 205 WRITE(7,200) I,J +C ERROR=.TRUE. + DO 211 I=1,NAT + IF(RS(I).LT.0.0D0) GO TO 213 + IF(NEQ(I).EQ.0)GO TO 210 + IF(NEQ(I).GE.I) GO TO 213 + 210 I1=I+1 + IF(NOUT.EQ.0) GO TO 212 + IF(NEQ(I).EQ.1) GO TO 213 + 212 IF(I1.GT.NAT) GO TO 216 + GO TO 2135 + 213 CONTINUE +C WRITE(6,200) I,J + 2135 DO 211 J=I1,NAT + RIJ = SQRT((XV(J)-XV(I))**2+(YV(J)-YV(I))**2+(ZV(J)-ZV(I))**2) + IF(NOUT.EQ.1.AND.I.EQ.1) GO TO 214 + RSUM = RS(I)+RS(J) + IF (RSUM.GT.RIJ) GO TO 215 + GO TO 211 + 214 RSUM = RIJ+RS(J) + IF (RSUM.GT.RS(1)) GO TO 215 + GO TO 211 + 215 CONTINUE +C WRITE (6,200) I,J,RSUM,RIJ,RDIF + 211 CONTINUE + 216 IF(RADION.EQ.0.0D0) GO TO 217 + IF(RADION.EQ.RS(1)) GO TO 217 + KX=KMAX(1) + DO 219 K=1,KX + IF(RADION.GT.R(K,1)) GO TO 219 + 219 CONTINUE + 217 CONTINUE + NDUMMY = 0 +C +C SHIFT BACK ORIGIN TO PHOTOABSORBER +C + X0=XV(2) + Y0=YV(2) + Z0=ZV(2) +C + DO 150 N=1,NAT + XV(N)=XV(N)-X0 + YV(N)=YV(N)-Y0 + ZV(N)=ZV(N)-Z0 + NEQ1(N)=0 + IF(NEQ(N).NE.0) NEQ1(N)=NEQ(N)-1 + 150 CONTINUE +C +C WRITE OUT POTENTIAL AND DENSITY FILES +C + IF (potype.EQ.'xalph') THEN + OPEN (19, FILE = 'div/XALPHA.POT', STATUS = 'unknown') + ELSE + OPEN (20, FILE = 'div/COUL.POT', STATUS = 'unknown') + OPEN (9, FILE = 'div/RHO.DENS', STATUS = 'unknown') + ENDIF +C + INV = 20 + IF (potype.EQ.'xalph') INV = 19 + INRHO= 9 + NST=2 + NC=2 + DO 4401 N=NST,NAT + WRITE(INV,311) NSYMBL(N),NEQ1(N),NZ(N),NDUMMY,KMAX(N),KPLACE(N), + 1 XV(N),YV(N),ZV(N),RS(N),EXFACT(N),NC + 311 FORMAT(A5,3I2,2I4,5F11.6,T76,I5) + NC=NC+1 + IF(NEQ(N).NE.0) GO TO 4401 + WRITE(INV,308) (ICHG(I,N),I= 1,10),NC + 308 FORMAT(10I5,T76,I5) + NC=NC+1 + WRITE(INV,319) NC,(R(I,N),I=1,5) + 319 FORMAT(T76,I5,T2,1P5E14.7) + NS=N + NC=NC+1 + KX=KMAX(N) + NS = N + DO 142 ISPIN=1,NSPINS + DO 141 K=1,KX,5 + KCARD=MIN0(KX,K+4) + WRITE(INV,319) NC,(V(I,NS),I=K,KCARD) + 141 NC=NC+1 + 142 NS=NS+NDAT + NS=N + IF (potype.NE.'xalph') THEN + DO 555 ISPIN=1,NSPINS + DO 551 K=1,KX,5 + KCARD=MIN0(KX,K+4) + WRITE(INRHO,319) NC,(RHO(I,NS),I=K,KCARD) + 551 NC=NC+1 + 555 NS=NS+NDAT + ENDIF + 4401 CONTINUE +C + IF(INV.EQ.19) WRITE( INV,319) NC,(VCONS(IS),IS=1,NSPINS) +C + IF (INV.EQ.20) THEN + WRITE(INV,319) NC, VCONC + + WRITE( INRHO,319) NC,(ROCON(IS),IS=1,NSPINS) + ENDIF +C +c CLOSE (4) + IF(potype.EQ.'xalph') THEN + CLOSE (UNIT=19) + ELSE + CLOSE (UNIT=20) + CLOSE (UNIT=9) + ENDIF +C +C CLOSE (UNIT=7) +C +C----------------------------------------------------------------------- +C +C PASS POTENTIAL AND/OR CHARGE DENSITY TO CONT_SUB. +C +C990 IF(IOUT_ASCII.NE.2) GO TO 999 +C +C----------------------------------------------------------------------- + NAT2=NAT-NOUT + NDAT2=NDAT-NOUT + NSPINS2=NSPINS +c +c A.Kuzmin 10.06.93 +c Correction of the atomic coordinates due to the outer +c sphere non central position +c + xv0=0.D0 + yv0=0.D0 + zv0=0.D0 +c if(nout.eq.1)then +c xv0=xv(1) +c yv0=yv(1) +c zv0=zv(1) +c endif +c +c End of correction +c + RS_OS = RS(1) ! pass outer sphere radius to cont_sub in common /param/ +c + DO 780 I=1,NAT2 +C +C SKIP OUTER SPHERE +C + J=I+NOUT + NSYMBL2(I)=NSYMBL(J) + NZ2(I)=NZ(J) + + + IF(NEQ(J).EQ.0)THEN + NEQ2(I)=0 + ELSE + NEQ2(I)=NEQ(J)-NOUT + END IF + XV2(I)=XV(J)-xv0 + YV2(I)=YV(J)-yv0 + ZV2(I)=ZV(J)-zv0 + Z2(I)=Z(J) + RS2(I)=RS(J) + EXFACT2(I)=EXFACT(J) + KMAX2(I)=KMAX(J) + KPLACE2(I)=KPLACE(J) + IF(NEQ(J).NE.0)GOTO 780 + DO 735 K=1,10 + ICHG2(K,I)=ICHG(K,J) +735 CONTINUE + H2(I)=R(2,J)-R(1,J) + ISDA=I + JSDA=J + DO 745 IS=1,NSPINS + DO 740 K=1,KMAX(J) + IF(IS.EQ.1)R2(K,ISDA)=R(K,JSDA) + RHOTOT2(K,ISDA)=RHO(K,JSDA) + V2(1,K,ISDA)=V(K,JSDA) + V2(2,K,ISDA)=0.0 +740 CONTINUE + ISDA=ISDA+NDAT2 + JSDA=JSDA+NDAT +745 CONTINUE +780 CONTINUE +C + RHKM1 = RHOTOT2(KMAX2(1),1)/ + 1 (4.D0*PI*R2(KMAX2(1),1)**2) + RHKM2 = RHOTOT2(KMAX2(2),2)/ + 1 (4.D0*PI*R2(KMAX2(2),2)**2) + RHKM = ( RHKM1 + RHKM2 ) / 2.D0 + RSKM = (3.D0 / ( 4.D0 * PI * RHKM * NSPINS ) ) ** THIRD + VCKM = (V2(1,KMAX2(1),1)+V2(1,KMAX2(2),2))/2.D0 + + WRITE(*,*) ' input value for coulomb interst. potential =', + 1 vc0 + WRITE(*,*) ' and interstitial rs =', rs0 + WRITE(*,*) ' lower bound for coulomb interst. potential =', + 1 vckm + WRITE(*,*) ' and for interst. rs =',rskm + + DO 790 M=1,NSPINS + IF (VINPUT) THEN + VCONS2(M) = DCMPLX(VC0X) + RHOINT2(M) = RH0 + ELSE + VCONS2(M)=DCMPLX(VCONS(M)) + RHOINT2(M)=ROCON(M) + ENDIF + 790 CONTINUE +C +C +C BRANCH POINT +C + RETURN + END +C + SUBROUTINE DINTERP(R,P,N,RS,PS,DPS,DERIV) + IMPLICIT REAL*8 (A-H,O-Z) + LOGICAL DERIV,NODRIV + DIMENSION R(N),P(N) + NODRIV=.NOT.DERIV + DPS=0.0D0 + PS=0.0D0 + DO 1 J=1,N + TERM=1.0D0 + DENOM=1.0D0 + DTERM=0.0D0 + DO 2 I=1,N + IF(I.EQ.J) GO TO 2 + DENOM=DENOM*(R(J)-R(I)) + TERM=TERM*(RS-R(I)) + IF(NODRIV) GO TO 2 + DTERM1=1.0D0 + DO 3 K=1,N + IF(K.EQ.J.OR.K.EQ.I) GO TO 3 + DTERM1=DTERM1*(RS-R(K)) + 3 CONTINUE + DTERM=DTERM+DTERM1 + 2 CONTINUE + IF(NODRIV) GO TO 1 + DPS=DPS+DTERM*P(J)/DENOM + 1 PS=PS+TERM*P(J)/DENOM + RETURN + END +c----------------------------------------------------------------------- +C + SUBROUTINE CSBF(X0,Y0,MAX,SBF,DSBF) + IMPLICIT REAL*8(A-H,O-Z) + REAL*8 XF1 +C + COMPLEX*16 X0,Y0 + COMPLEX*16 X,Y,RAT,DSBF1,Z,SBFJ,B,A + COMPLEX*16 SBFK,SBF1,SBF2 + COMPLEX*16 SBF,DSBF + INTEGER MAX,K,JMIN,KMAX + DIMENSION SBF(MAX), DSBF(MAX) +C +C +C GENERATES SPHERICAL BESSEL FUNCTIONS OF ORDER 0 - MAX-1 AND THEIR +C FIRST DERIVATIVES WITH RESPECT TO R. X=ARGUMENT= Y*R. +C IF Y=0, NO DERIVATIVES ARE CALCULATED. MAX MUST BE AT LEAST 3. +C OSBF GENERATES ORDINARY SPHERICAL BESSEL FUNCTIONS. MSBF - MODI- +C FIED SPHERICAL BESSEL FUNCTIONS; OSNF - ORD. SPH. NEUMANN FCNS; +C MSNF - MOD. SPH. NEUMANN FCNS; MSHF - MOD. SPH HANKEL FCNS +C +C +C + X=DCMPLX(X0) + Y=DCMPLX(Y0) + + IF (MAX.LT.1.OR.MAX.GT.2000) GO TO 99 + IF(ABS(X).LT.0.50D0 ) GO TO 18 +C +C BESSEL FUNCTIONS BY DOWNWARD RECURSION +C + SBF2=(0.0D0,0.0D0) + SBF1=1.0D-25*(0.5D0,0.5D0) + IF(ABS(X).LT.2.0D0) SBF1=1.0D-38*(0.5D0,0.5D0) + JMIN=10+INT(ABS(X)) + KMAX=MAX+JMIN-1 + K=MAX + XF1=2*KMAX+1 + DO 10 J=1,KMAX + SBFK=XF1*SBF1/X-SBF2 + SBF2=SBF1 + SBF1=SBFK + XF1=XF1-2.0D0 + IF (J.LT.JMIN) GO TO 10 + SBF(K)=SBFK + K=K-1 +10 CONTINUE + RAT=SIN(X)/(X*SBF(1)) + DO 17 K=1,MAX + 17 SBF(K)=RAT*SBF(K) + DSBF1=-SBF(2) + GO TO 26 +C +C SMALL ARGUMENTS +C + 18 Z=-(X*X*0.50D0) + A=(1.0D0,0.0D0) + MMX=MAX + IF (MAX.EQ.1.AND.Y.NE.(0.0D0,0.0D0)) MMX=2 + DO 30 J=1,MMX + SBFJ=A + B=A + DO 31 I=1,20 + B=B*Z/(I*(2*(J+I)-1)) + SBFJ=SBFJ+B + IF (ABS(B).LE.1.0D-07*ABS(SBFJ)) GO TO 29 + 31 CONTINUE +29 IF (J.EQ.2) DSBF1=-SBFJ + IF (J.LE.MAX) SBF(J)=SBFJ + 30 A=A*X/DCMPLX(FLOAT(2*J+1)) +C +C +26 IF (Y.EQ.(0.0D0,0.0D0)) RETURN + DSBF(1)=Y*DSBF1 + IF (MAX.EQ.1) RETURN + DO 9 I=2,MAX + 9 DSBF(I)=Y*(SBF(I-1)- DCMPLX(FLOAT(I))*SBF(I)/X) + RETURN +99 WRITE(6,100) MAX +100 FORMAT (' SPHERICAL BESSEL FUNCTION ROUTINE - MAX=',I8) + STOP + END +C +c + subroutine cshf2(x0,y0,max,sbf,dsbf) + implicit real*8(a-h,o-z) + real*8 xf1 +C complex*8 x0,y0 + complex*16 x0,y0 + complex*16 x,y,rat,z,sbfj,b,a + complex*16 sbfk,sbf1,sbf2,cplu + complex*16 sbf,dsbf + integer max,k,jmin,kmax + dimension sbf(max), dsbf(max) +c +c cshf2 - May 1992 +c generates spherical hankel functions of type 2 of order 0 - max-1. +c max must be at least 3. cshf2 is calculated as csbf - i*csnf, wher +c csbf(csnf) are spherical Bessel(Neuman) functions. csbf(csnf) are +c calculated using downward(upward) recurrence realations. +c ***** This subroutine returns i*cshf2 = csnf + i*csbf and its +c derivative if y0 ne. 0. In this case dsbf = i*y0*(cshf")'*** +c +c + cplu = (0.d0,1.d0) +c + x=dcmplx(x0) + y=dcmplx(y0) + + if (max.lt.1.or.max.gt.2000) go to 99 + if(abs(x).lt.0.50D0 ) go to 18 +c +c bessel functions sbf by downward recursion +c + sbf2=(0.0D0,0.0D0) + sbf1=1.0D-25*(0.5D0,0.5D0) + if(abs(x).lt.2.0D0) sbf1=1.0d-38*(0.5D0,0.5D0) + jmin=10+int(abs(x)) + kmax=max+jmin-1 + k=max + xf1=2*kmax+1 + do 10 j=1,kmax + sbfk=xf1*sbf1/x-sbf2 + sbf2=sbf1 + sbf1=sbfk + xf1=xf1-2.0d0 + if (j.lt.jmin) go to 10 + sbf(k)=sbfk + k=k-1 +10 continue + rat=sin(x)/(x*sbf(1)) + do 17 k=1,max + 17 sbf(k)=rat*sbf(k) + go to 2 +c +c sbf for small arguments +c + 18 z=-(x*x*0.50D0) + a=(1.0D0,0.0D0) + mmx=max + if (max.eq.1.and.y.ne.(0.0D0,0.0D0)) mmx=2 + do 30 j=1,mmx + sbfj=a + b=a + do 31 i=1,20 + b=b*z/(i*(2*(j+i)-1)) + sbfj=sbfj+b + if (abs(b).le.1.0d-07*abs(sbfj)) go to 29 + 31 continue + 29 if (j.le.max) sbf(j)=sbfj + 30 a=a*x/ dcmplx(float(2*j+1)) +c +c spherical neumann functions snf by upward recursion +c damped in dsbf +c + 2 sbf2=-cos(x)/x + sbf1=(sbf2-sin(x))/x + dsbf(1)=sbf2 + if (max.eq.1) go to 26 + dsbf(2)=sbf1 + if (max.eq.2) go to 26 + xf1=3.0d0 + do 22 i=3,max + sbfk=xf1*sbf1/x-sbf2 + dsbf(i)=sbfk + sbf2=sbf1 + sbf1=sbfk +22 xf1=xf1+2.0d0 +c +c hankel functions h^+ = sbf + i*snf, h^- = sbf - i*snf. This subroutine +c returns i*h^- = snf + i*sbf +c + do 3 i=1,max + 3 sbf(i) = cplu*sbf(i) + dsbf(i) + +26 if (y.eq.(0.0D0,0.0D0)) return +c +c calculate derivative of shf +c + dsbf(1) = -y*sbf(2) + if (max.eq.1) return + do 9 i=2,max + 9 dsbf(i)=y*(sbf(i-1)- dcmplx(float(i))*sbf(i)/x) + return +99 write(6,100) max +100 format (' spherical bessel function routine - max=',i8) + stop + end +c + SUBROUTINE DEFINT(F,R,KMAX,ICHG,A,ID) + implicit real*8 (a-h,o-z) + DIMENSION F(KMAX),R(KMAX),ICHG(10) + complex*16 F,A,F0 +C + DATA S720,S251,S646,S264 /720.,251.,646.,264./ +C + DATA S106,S19,S346,S456,S74,S11/106.0,19.0,346.0,456.0,74.0,11.0/ +C + H=R(2)-R(1) + A0=0.0 + K0=0 + IF (ID.NE.1) GO TO 11 + F0=(0.0,0.0) + GO TO 12 + 11 F0=5.0*F(1)-10.0*F(2)+10.0*F(3)-5.0*F(4)+F(5) +12 KX=KMAX + N=1 + A=A0+H*(S251*F0+S646*F(K0+1)-S264*F(K0+2)+S106*F(K0+3)-S19* + 1 F(K0+4))/S720 + A=A+H*(-S19*F0+S346*F(K0+1)+S456*F(K0+2)-S74*F(K0+3)+S11* + 1 F(K0+4))/S720 + A=A+H*(S11*F0-S74*F(K0+1)+S456*F(K0+2)+S346*F(K0+3)-S19* + 1 F(K0+4))/S720 + K0=K0+4 + DO 50 K=K0,KX + KICH=K-ICHG(N) + IF (KICH.EQ.1) GO TO 30 + IF (KICH.EQ.2) GO TO 40 + A=A+H*( 9.0*F(K)+19.0*F(K-1)- 5.0*F(K-2)+ F(K-3))/24.0 + GO TO 50 +30 H=H+H + A=A+H*( 2.0*F(K)+ 7.0*F(K-1)- 4.0*F(K-2)+ F(K-3))/ 6.0 + GO TO 50 +40 N=N+1 + A=A+H*(11.0*F(K)+25.0*F(K-1)-10.0*F(K-2)+4.0*F(K-3))/30.0 +50 CONTINUE + RETURN + END +C +C +C + SUBROUTINE defint0(F,DX,KMAX,A,ID) + implicit real*8 (a-h,o-z) + complex*16 F, A, A0, F0 + DIMENSION F(KMAX) +C + DATA S720,S251,S646,S264 /720.,251.,646.,264./ +C + DATA S106,S19,S346,S456,S74,S11/106.0,19.0,346.0,456.0,74.0,11.0/ +C + H=DX + A0=0.0 + K0=0 + IF (ID.NE.1) GO TO 11 + F0=(0.0,0.0) + GO TO 12 + 11 F0=5.0*F(1)-10.0*F(2)+10.0*F(3)-5.0*F(4)+F(5) +c 11 F0 = F(1) +c K0 = 1 +c write(6,*) 'defint', f0 +12 KX=KMAX + N=1 + A=A0+H*(S251*F0+S646*F(K0+1)-S264*F(K0+2)+S106*F(K0+3)-S19* + 1 F(K0+4))/S720 + A=A+H*(-S19*F0+S346*F(K0+1)+S456*F(K0+2)-S74*F(K0+3)+S11* + 1 F(K0+4))/S720 + A=A+H*(S11*F0-S74*F(K0+1)+S456*F(K0+2)+S346*F(K0+3)-S19* + 1 F(K0+4))/S720 + K0=K0+4 + DO 50 K=K0,KX + A=A+H*( 9.0*F(K)+19.0*F(K-1)- 5.0*F(K-2)+ F(K-3))/24.0 +50 CONTINUE + RETURN +C + END +C +C + SUBROUTINE defint1(F,DX,KMAX,A,ID) + implicit real*8 (a-h,o-z) + complex*16 F, A, A0, F0 + DIMENSION F(KMAX) +C + DATA S720,S251,S646,S264 /720.,251.,646.,264./ +C + DATA S106,S19,S346,S456,S74,S11/106.0,19.0,346.0,456.0,74.0,11.0/ +C + H=DX + A0=0.0 + K0=0 + IF (ID.NE.1) GO TO 11 + F0=(0.0,0.0) + GO TO 12 +c 11 F0=5.0*F(1)-10.0*F(2)+10.0*F(3)-5.0*F(4)+F(5) + 11 F0 = F(1) + K0 = 1 +12 KX=KMAX + N=1 + A=A0+H*(S251*F0+S646*F(K0+1)-S264*F(K0+2)+S106*F(K0+3)-S19* + 1 F(K0+4))/S720 + A=A+H*(-S19*F0+S346*F(K0+1)+S456*F(K0+2)-S74*F(K0+3)+S11* + 1 F(K0+4))/S720 + A=A+H*(S11*F0-S74*F(K0+1)+S456*F(K0+2)+S346*F(K0+3)-S19* + 1 F(K0+4))/S720 + K0=K0+4 + DO 50 K=K0,KX + A=A+H*( 9.0*F(K)+19.0*F(K-1)- 5.0*F(K-2)+ F(K-3))/24.0 +50 CONTINUE + RETURN +C + END +C +C + SUBROUTINE defintr(F,DX,KMAX,A,ID) + implicit double precision (a-h,o-z) + DIMENSION F(KMAX) +C + DATA S720,S251,S646,S264 /720.,251.,646.,264./ +C + DATA S106,S19,S346,S456,S74,S11/106.0,19.0,346.0,456.0,74.0,11.0/ +C + H=DX + A0=0.0 + K0=0 + IF (ID.NE.1) GO TO 11 + F0=0.0 + GO TO 12 +c 11 F0=5.0*F(1)-10.0*F(2)+10.0*F(3)-5.0*F(4)+F(5) + 11 F0 = F(1) + K0 = 1 +12 KX=KMAX + N=1 + A=A0+H*(S251*F0+S646*F(K0+1)-S264*F(K0+2)+S106*F(K0+3)-S19* + 1 F(K0+4))/S720 + A=A+H*(-S19*F0+S346*F(K0+1)+S456*F(K0+2)-S74*F(K0+3)+S11* + 1 F(K0+4))/S720 + A=A+H*(S11*F0-S74*F(K0+1)+S456*F(K0+2)+S346*F(K0+3)-S19* + 1 F(K0+4))/S720 + K0=K0+4 + DO 50 K=K0,KX + A=A+H*( 9.0*F(K)+19.0*F(K-1)- 5.0*F(K-2)+ F(K-3))/24.0 +50 CONTINUE + RETURN +C + END +C +C + SUBROUTINE INTEGR(F,R,KMAX,ICHG,A,ID) + implicit real*8 (a-h,o-z) + DIMENSION F(KMAX),R(KMAX),ICHG(10),A(KMAX) +C + DATA S720,S251,S646,S264 /720.,251.,646.,264./ +C + DATA S106,S19,S346,S456,S74,S11/106.0,19.0,346.0,456.0,74.0,11.0/ +C + H=R(2)-R(1) + A0=0.0 + IF (ID.NE.1) GO TO 11 + K0=0 + F0=0.0 + GO TO 12 + 11 K0=1 + A(1)=0.0 + F0=F(1) +12 KX=KMAX + N=1 + A(K0+1)=A0+H*(S251*F0+S646*F(K0+1)-S264*F(K0+2)+S106*F(K0+3)-S19*F + 1 (K0+4))/S720 + A(K0+2)=A(K0+1)+H*(-S19*F0+S346*F(K0+1)+S456*F(K0+2)-S74*F(K0+3)+S + 1 11*F(K0+4))/S720 + A(K0+3)=A(K0+2)+H*(S11*F0-S74*F(K0+1)+S456*F(K0+2)+S346*F(K0+3)-S1 + 1 9*F(K0+4))/S720 + K0=K0+4 + DO 50 K=K0,KX + KICH=K-ICHG(N) + IF (KICH.EQ.1) GO TO 30 + IF (KICH.EQ.2) GO TO 40 + A(K)=A(K-1)+H*( 9.0*F(K)+19.0*F(K-1)- 5.0*F(K-2)+ F(K-3))/24.0 + GO TO 50 +30 H=H+H + A(K)=A(K-1)+H*( 2.0*F(K)+ 7.0*F(K-1)- 4.0*F(K-2)+ F(K-3))/ 6.0 + GO TO 50 +40 N=N+1 + A(K)=A(K-1)+H*(11.0*F(K)+25.0*F(K-1)-10.0*F(K-2)+4.0*F(K-3))/30.0 +50 CONTINUE + IF (MOD(ID,2).NE.0) RETURN + DO 150 K=1,KMAX +150 A(K)=A(KMAX)-A(K) + RETURN +C # + END +C + SUBROUTINE CINTEGR(F,R,KMAX,ICHG,A,ID) + implicit real*8 (a-h,o-z) + complex*16 F,A,F0 + DIMENSION F(KMAX),R(KMAX),ICHG(10),A(KMAX) +C + DATA S720,S251,S646,S264 /720.,251.,646.,264./ +C + DATA S106,S19,S346,S456,S74,S11/106.0,19.0,346.0,456.0,74.0,11.0/ +C + H=R(2)-R(1) + A0=0.0 + IF (ID.NE.1) GO TO 11 + K0=0 + F0=(0.0,0.0) + GO TO 12 + 11 K0=1 + A(1)=(0.0,0.0) + F0=F(1) +12 KX=KMAX + N=1 + A(K0+1)=A0+H*(S251*F0+S646*F(K0+1)-S264*F(K0+2)+S106*F(K0+3)-S19*F + 1 (K0+4))/S720 + A(K0+2)=A(K0+1)+H*(-S19*F0+S346*F(K0+1)+S456*F(K0+2)-S74*F(K0+3)+S + 1 11*F(K0+4))/S720 + A(K0+3)=A(K0+2)+H*(S11*F0-S74*F(K0+1)+S456*F(K0+2)+S346*F(K0+3)-S1 + 1 9*F(K0+4))/S720 + K0=K0+4 + DO 50 K=K0,KX + KICH=K-ICHG(N) + IF (KICH.EQ.1) GO TO 30 + IF (KICH.EQ.2) GO TO 40 + A(K)=A(K-1)+H*( 9.0*F(K)+19.0*F(K-1)- 5.0*F(K-2)+ F(K-3))/24.0 + GO TO 50 +30 H=H+H + A(K)=A(K-1)+H*( 2.0*F(K)+ 7.0*F(K-1)- 4.0*F(K-2)+ F(K-3))/ 6.0 + GO TO 50 +40 N=N+1 + A(K)=A(K-1)+H*(11.0*F(K)+25.0*F(K-1)-10.0*F(K-2)+4.0*F(K-3))/30.0 +50 CONTINUE + IF (MOD(ID,2).NE.0) RETURN + DO 150 K=1,KMAX +150 A(K)=A(KMAX)-A(K) + RETURN +C # + END +C +C + SUBROUTINE INTEGRCM(F,DX,KMAX,A,ID) + implicit real*8 (a-h,o-z) + COMPLEX*16 F,A,F0 +C + DIMENSION F(KMAX),A(KMAX) +C + DATA S720,S251,S646,S264 /720.D0,251.D0,646.,264.D0/ +C + DATA S106,S19,S346,S456,S74,S11 /106.0D0,19.0D0,346.0D0,456.0D0, + 1 74.0D0,11.0D0/ +C + H=DX + A0=0.0D0 + IF (ID.NE.1) GO TO 11 + K0=0 + F0=(0.0D0,0.0D0) + GO TO 12 + 11 K0=1 + A(1)=(0.0D0,0.0D0) + F0=F(1) +12 KX=KMAX + A(K0+1)=A0+H*(S251*F0+S646*F(K0+1)-S264*F(K0+2)+ + 1 S106*F(K0+3)-S19*F(K0+4))/S720 + A(K0+2)=A(K0+1)+H*(-S19*F0+S346*F(K0+1)+S456*F(K0+2)- + 1 S74*F(K0+3)+S11*F(K0+4))/S720 + A(K0+3)=A(K0+2)+H*(S11*F0-S74*F(K0+1)+S456*F(K0+2)+ + 1 S346*F(K0+3)-S19*F(K0+4))/S720 + K0=K0+4 + DO 50 K=K0,KX + A(K)=A(K-1)+H*( 9.0D0*F(K)+19.0D0*F(K-1)-5.0D0*F(K-2)+ + 1 F(K-3))/24.0D0 +50 CONTINUE + IF (MOD(ID,2).NE.0) RETURN + DO 150 K=1,KMAX +150 A(K)=A(KMAX)-A(K) + RETURN +C # + END +C +C + SUBROUTINE INTERP(R,P,N,RS,PS,DPS,DERIV) + IMPLICIT REAL*8 (A-H,O-Z) + LOGICAL DERIV,NODRIV + DIMENSION R(N),P(N) + COMPLEX*16 P,PS,DPS + NODRIV=.NOT.DERIV + DPS=(0.0,0.0) + PS=(0.0,0.0) + DO 1 J=1,N + TERM=1.0 + DENOM=1.0 + DTERM=0.0 + DO 2 I=1,N + IF(I.EQ.J) GO TO 2 + DENOM=DENOM*(R(J)-R(I)) + TERM=TERM*(RS-R(I)) + IF(NODRIV) GO TO 2 + DTERM1=1.0 + DO 3 K=1,N + IF(K.EQ.J.OR.K.EQ.I) GO TO 3 + DTERM1=DTERM1*(RS-R(K)) + 3 CONTINUE + DTERM=DTERM+DTERM1 + 2 CONTINUE + IF(NODRIV) GO TO 1 + DPS=DPS+DTERM*P(J)/DENOM + 1 PS=PS+TERM *P(J)/DENOM + RETURN +C + END +C +C + SUBROUTINE SORT(NINI,VALIN,NFIN,VALFIN) +C +C Given a set of **real** numbers VALINI, this routine orders them and +C suppresses the values appearing more than once. The remaining +C values are stored in VALFIN. +C +C VALINI(K+1).GT.VALINI(K) : decreasing order +C VALINI(K+1).LT.VALINI(K) : increasing order +C +C + IMPLICIT REAL*8 (A-H,O-Z) +C + DIMENSION VALIN(NINI),VALINI(NINI),VALFIN(NINI) +C + LOGICAL BUBBLE +C + DATA SMALL /0.00001/ +C +C.....STORE INPUT ARRAY +C + DO I=1,NINI + VALINI(I)=VALIN(I) + ENDDO +C + DO J=1,NINI-1 + K=J + BUBBLE=.TRUE. +150 IF(K.GE.1.AND.BUBBLE) THEN + IF(VALINI(K+1).LT.VALINI(K)) THEN + R1=VALINI(K) + VALINI(K)=VALINI(K+1) + VALINI(K+1)=R1 + ELSE + BUBBLE=.FALSE. + END IF + K=K-1 + GOTO 150 + ENDIF + ENDDO +C + JFIN=1 + VALFIN(1)=VALINI(1) + DO J=1,NINI-1 + IF(ABS(VALFIN(JFIN)-VALINI(J+1)).GT.SMALL) THEN + JFIN=JFIN+1 + VALFIN(JFIN)=VALINI(J+1) + ENDIF + ENDDO + NFIN=JFIN +C + RETURN +C + END +C +C + SUBROUTINE STARTP(ZZ0,L,E,R,V,KMAX,KI,P) +C + IMPLICIT COMPLEX*16 (A-B) +C + REAL*8 ZZ0,E,R + REAL*8 XL,Z0,H,RC +C + COMPLEX*16 V + COMPLEX*16 P,Z +C + DIMENSION R(KMAX),V(KMAX),Z(300),P(KMAX) +C 1,ZA(150) +C + Z0=(ZZ0) !+ (0.D0,1.D0)*(GAMMA) + RC = 1.0D0 +C IF(L.GT.10) RC = 0.01/R(1) + KM=KI/4 + IF(KI.EQ.1) KM=1 + KI1=KI+2 + DO 1 K=1,KI1 + 1 Z(K)=DCMPLX(R(K)*V(K)) + XL=DFLOAT(L) + H=DBLE(KM)*R(1) + B1=-2.0D0*Z0 + B2=(22.D0*Z0+18.D0*Z(KM)-9.D0*Z(2*KM)+2.D0*Z(3*KM))/(6.D0*H)- E + B3=(-12.D0*Z0-15.D0*Z(KM)+12.D0*Z(2*KM)-3.D0*Z(3*KM))/(6.D0*H*H) + B4=(2.D0*Z0+3.D0*Z(KM)-3.D0*Z(2*KM)+Z(3*KM))/(6.D0*H**3) + A1=-Z0/(XL+1.0D0) + A2=(B1*A1+B2)/(4.0D0*XL+6.0D0) + A3=(B1*A2+B2*A1+B3)/(6.0D0*XL+12.0D0) + A4=(B1*A3+B2*A2+B3*A1+B4)/(8.0D0*XL+20.0D0) + A5=(B1*A4+B2*A3+B3*A2+B4*A1)/(10.D0*XL+30.D0) + A6=(B1*A5+B2*A4+B3*A3+B4*A2)/(12.D0*XL+42.D0) + A7=(B1*A6+B2*A5+B3*A4+B4*A3)/(14.D0*XL+56.D0) + DO 4 K=1,KI1 + 4 P(K)=DCMPLX((1.0D0+R(K)*(A1+R(K)*(A2+R(K)* + 1 (A3+R(K)*(A4+R(K)*(A5+R(K)* + 2 (A6+R(K)*A7)))))))*(R(K)*RC)**(L+1)) +C DO 2 K=1,KI1 +C 2 ZA(K)=B1+R(K)*(B2+(R(K)*(B3+R(K)*B4))) +C WRITE(6,3) (I,(R(I+J-1),Z(I+J-1),ZA(I+J-1),J=1,2),I=1,KI1,2) + RETURN + END +C +C + subroutine rhl(erl,eim,pi) +c +c +c this is a new hl subroutine, using interpolation for the +c real part while calculating the imaginary part is calculated +c analitically. +c it uses hl to calculate values at the mesh points for the inter +c polation of the real part. the imaginary part is calculated +c using subroutine imhl. +c +c written by jose mustre +c polynomial in rs has a 3/2 power term. j.m. +c + implicit double precision (a-h,o-z) + common /corr/ rs,blt,xk1,vii,index2 + common /hlin/ xk + common /cusp/ icusp +c +c for the right branch the interpolation has the form: +c hl(rs,x) = e/x + f/x**2 + g/x**3 +c where e is known and +c f = sum (i=1,3) ff(i) rs**(i+1)/2 +c g = sum (i=1,3) gg(i) rs**(i+1)/2 +c +c +c lrs=number of rs panels, in this case one has 4 panels +c nrs=number of standard rs values, also order of rs expansion +c if you change nrs you need to change the expansion of hl +c in powers of rs that only has 3 terms! +c nleft=number of coefficients for xx0 +c + parameter (lrs=4,nrs=3,nleft=4,nright=2) + dimension rcfl(lrs,nrs,nleft),rcfr(lrs,nrs,nright) + dimension cleft(nleft),cright(nright) + data conv /1.9191583/ + data rcfr/-0.173963d+00,-0.173678d+00,-0.142040d+00,-0.101030d+00, + 1 -0.838843d-01,-0.807046d-01,-0.135577d+00,-0.177556d+00, + 2 -0.645803d-01,-0.731172d-01,-0.498823d-01,-0.393108d-01, + 3 -0.116431d+00,-0.909300d-01,-0.886979d-01,-0.702319d-01, + 4 0.791051d-01,-0.359401d-01,-0.379584d-01,-0.419807d-01, + 5 -0.628162d-01, 0.669257d-01, 0.667119d-01, 0.648175d-01/ + data rcfl/ 0.590195d+02, 0.478860d+01, 0.812813d+00, 0.191145d+00, + 1 -0.291180d+03,-0.926539d+01,-0.858348d+00,-0.246947d+00, + 2 0.363830d+03, 0.460433d+01, 0.173067d+00, 0.239738d-01, + 3 -0.181726d+03,-0.169709d+02,-0.409425d+01,-0.173077d+01, + 4 0.886023d+03, 0.301808d+02, 0.305836d+01, 0.743167d+00, + 5 -0.110486d+04,-0.149086d+02,-0.662794d+00,-0.100106d+00, + 6 0.184417d+03, 0.180204d+02, 0.450425d+01, 0.184349d+01, + 7 -0.895807d+03,-0.318696d+02,-0.345827d+01,-0.855367d+00, + 8 0.111549d+04, 0.156448d+02, 0.749582d+00, 0.117680d+00, + 9 -0.620411d+02,-0.616427d+01,-0.153874d+01,-0.609114d+00, + 1 0.300946d+03, 0.109158d+02, 0.120028d+01, 0.290985d+00, + 2 -0.374494d+03,-0.535127d+01,-0.261260d+00,-0.405337d-01/ + +c +c calcualte hl using interplation coefficients +c + rkf=conv/rs + ef=rkf*rkf*0.5D0 + wp=sqrt(3.0D0/rs**3) + call imhl (erl,eim,pi) + eim=eim +c +c eim already has a factor of ef in it j.m. +c eim also gives the position of the cusp +c + xx=xk1/rkf +c +c calculate right hand side coefficients +c + if (rs .lt. 0.2D0) then + mrs=1 + go to 209 + endif + if (rs .ge. 0.2D0 .and. rs .lt. 1.0D0) then + mrs=2 + go to 209 + endif + if (rs .ge. 1.0D0 .and. rs .lt. 5.0D0) then + mrs=3 + go to 209 + endif + if (rs .ge. 5.0D0) mrs=4 + 209 do 210 j=1,nright + cright(j)=rcfr(mrs,1,j)*rs+rcfr(mrs,2,j)*rs*sqrt(rs) + 1 +rcfr(mrs,3,j)*rs*rs +c +c jm written this way to calculate powers of rs quicker. +c cright(j)=0.0 +c do 205 k=1,nrs +c 205 cright(j)=cright(j)+rcfr(mrs,k,j)*rs**((k+1.)/2.) + 210 continue + eee=-pi*wp/(4.0D0*rkf*ef) +c + if (icusp .ne. 1) then + do 230 j=1,nleft + cleft(j)=rcfl(mrs,1,j)*rs+rcfl(mrs,2,j)*rs*sqrt(rs) + 1 +rcfl(mrs,3,j)*rs*rs +c cleft(j)=0.0 +c do 225 k=1,nrs +c 225 cleft(j)=cleft(j)+rcfl(mrs,k,j)*rs**((k+1.)/2.) + 230 continue +c + erl=cleft(1) + do 250 j=2,nleft + 250 erl=erl+cleft(j)*xx**(j-1) +c + else +c +c right branch +c + erl=eee/xx + do 280 j=1,nright + 280 erl=erl+cright(j)/xx**(j+1) + endif +c + erl=erl*ef + return + end +c +c +c + subroutine imhl(erl,eim,pi) +C +c********************************************************************** +c********************************************************************** +C +c writen by j. mustre march 1988 based on analytical expression derived +c by john rehr. +c it leaves the real part unchanged. +C +c********************************************************************** +c********************************************************************** + implicit double precision (a-h,o-z) + common /corr/rs,blt,xk1,vii,index2 + common/hlin/xk + common /cusp/ icusp + common/inter/wp,alph,ef,xf + common/cube/a0,a1,a2 + external ffq + icusp=0 + fa=1.9191583D0 + xf=fa/rs + ef=xf*xf/2.0D0 + xk=xk1 + xk=xk/xf +c +c wp is given in units of the fermi energy in the formula below. +c + wp=sqrt(3.0D0/(rs*rs*rs))/ef + alph=4.0D0/3.0D0 +c write(*,225) +c 225 format(1x'xk,wp') +c write(*,*)xk,wp + xs=wp*wp-(xk*xk-1.0D0)**2 +c write (*,*)xs + if (xs .ge. 0.D0) go to 10 + q2=sqrt((sqrt(alph*alph-4.0D0*xs)-alph)/2.0D0) + qu=min(q2,(1.0D0+xk)) + d1=qu-(xk-1.0D0) + if(d1.gt.0.D0) goto 11 + 10 eim=0.0D0 + go to 20 + 11 eim=ffq(qu)-ffq((xk-1.0D0)) + +c write(*,223) +c 223 format(1x'xk,eim,d1') +c write(*,*)xk,eim,d1 + 20 call cubic (rad,qplus,qminus) +c write(*,224) +c 224 format(1x'xk,rad,qplus,qminus') +c write(*,*)xk,rad,qplus,qminus + if (rad.gt. 0.0D0) goto 32 + d2=qplus-(xk+1.0D0) + if(d2.gt.0.D0)go to 21 + eim=eim + go to 30 + 21 eim=eim+ffq(qplus)-ffq((xk+1.0D0)) +c write(*,221) +c 221 format(1x'xk,eim,d2') +c write (*,*)xk,eim,d2 + 30 d3=(xk-1.0D0)-qminus + if(d3.gt.0.D0)go to 31 + return + 31 eim=eim+ffq((xk-1.0D0))-ffq(qminus) +c +c beginning of the imaginary part and position of the cusp x0 +c + icusp=1 +c write(*,222) +c 222 format(1x'xk,eim,d3') +c write (*,*)xk,eim,d3 + 32 return + end +c +c +c + subroutine cubic ( rad,qplus,qminus) + implicit double precision (a-h, o-z) + complex*16 s1,s13 + common/hlin/xk + common/inter/wp,alph,ef,xf + common/cube/a0,a1,a2 +c +c this subroutine finds the roots of the equation +c 4xk*q^3+(alph-4xk^2)q^2+wp^2=0. +c see abramowitz and stegun for formulae. + + a2=(alph/(4.0D0*xk*xk)-1.0D0)*xk + a0=wp*wp/(4.0D0*xk) + a1=0.0D0 + q=a1/3.0D0-a2**2/9.0D0 + r=(a1*a2-3.0D0*a0)/6.0D0-a2**3/27.0D0 + rad=q**3+r**2 + if (rad .gt. 0.0D0) then + qplus=0.0D0 + qminus=0.0D0 + return + endif + s13=dcmplx(r,sqrt(-rad)) + s1=s13**(1.0D0/3.0D0) + qz1=2.0D0*dreal(s1)-a2/3.0D0 + qz3=-(dreal(s1)-dsqrt(3.0D0)*dimag(s1)+a2/3.0D0) + qplus=qz1 + qminus=qz3 + return + end +c +c +c + double precision function ffq(q) + implicit double precision (a-h,o-z) + common /corr/rs,blt,xk1,vii,index2 + common /hlin/xk + common /inter/wp,alph,ef,xf + wq=sqrt(wp*wp+alph*q*q+q*q*q*q) + ffq=(wp+wq)/(q*q)+alph/(2.0D0*wp) +c +c check prefactor (wp/4xk) to see if units are correct. +c + ffq=(ef*wp/(4.0D0*xk1))*log(ffq) + return + end + + subroutine cont_sub(potype,potgen,lmax_mode,lmaxt,relc, + & eikappr,db,calctype,nosym,tdl) +c + implicit real*8 (a-h,o-z) +c +c.... continuum program version for phase shift calculation: +c.... february 1990 +c + include 'msxas3.inc' +c + + + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $ n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + common /dens/ irho,rhotot(rd_,sd_),rhoint(2), + $ vcoul(rd_,sd_),vcoulint(2) +c + dimension rs_abs(rd_) +c + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode + + common /fcnr/kxe, h(d_),vcons(2),r(rd_,d_),v(rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) + complex*16 v,vcons + dimension rtmp(rd_),zr(rd_) + double precision rtmp, zr, estryd +c + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +c + DOUBLE PRECISION RXD(RDX_), DX +c + COMMON /LLM/ ALPHA, BETA +c + common /flag/ inmsh,inv,inrho,insym,iovrho,iosym, + 1 imvhl,nedhlp +c + character*8 name0 ,nsymbl + CHARACTER*3 VERSION +C + COMMON /V_TYPE/ VERSION +c + common /param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os +c + complex*16 cgamma !cgamma = i*gamma + + complex*16 vcon,xe,ev +c + common /pdq/ p(rd_,fl_),ps(n_),dps(n_), + * ramf(n_),pss(6),dpss(6) + complex*16 p,ps,dps,ramf,pss,dpss +c +c ##############common /pdqi/ modified to include the two wavefuncti +c ############### for the final two holes state in the Auger decay r +c + common /pdqi/rpi(rd_),rpi1(rd_),rpi2(rd_) +c + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) +c + common/lparam/lmax2(nat_),l0i +c + character*2 potgen,relc + character*3 eikappr,calctype + character*5 potype +c + logical do_r_in,nosym,asa_ape,tdl +c + real*8 enp + dimension lorb(29), ic_occ(29), iabs_occ(29), enp(30) + character*5 orb(29), orbtp +c +c write(6,11) jat,jd,jf,jlmax,jn,jrd,jsd,j1d +c +c 11 format('0 final state parameters:' +c $ /'0 jat =',i6,2x,'number of centers (tb)' +c $ /'0 jd =',i6,2x,'number of inequivalent centers (nun)' +c $ /'0 jf =',i6,2x,'storage location for radial functions:=10' +c $ /'0jlmax =',i6,2x,'maximum l-value on any atomic sphere' +c $ /'0 jn =',i6,2x,'number of basis functions on all atoms' +c $ /'0 jrd =',i6,2x,'maximum number of radial mesh points (npt)' +c $ /'0 jsd =',i6,2x,'nspins*jd (for spin restriction)' +c $ /'0 j1d =',i6,2x,'is jd+1') +c +c +c + asa_ape = calctype.ne.'asa'.or.calctype.ne.'ape' +c +C WARNING: COMMONS /FCNR/ AND /PARAM/ ARE AVAILABLE ONLY AFTER SUBROUTINE +C INPUT_CONT IS CALLED +c +c do not change in this version! + nns=1 +c +c*********************************************************************** +c get initial state radial function +c*********************************************************************** +c + print 660 +660 format( 1x,' generating core state wavefunction ') +c + call get_core_state +c +c +c*********************************************************************** +c compute parameters for final state and call subroutine cont +c*********************************************************************** +c + id=1 +c + + call input_cont(id,potype,potgen,lmax_mode,lmaxt) + + if(asa_ape) call output_cont(id) +c + call setup +c + vcon=vcons(nns) +c write(6,*) 'vcon = vcons(nns)', vcon +c + write(6,10) eftr + 10 format(/,1x,' fermi level =', f10.5,/) +c + emmef=emin-eftr + if(emmef.lt.0.0) write(6,556) emin,eftr + 556 format(/,' ***warning***: emin=',f10.5,' less than the fermi ', + * 'level eftr=',f10.5,/ + * 'a stop is caused in the case ', + * 'of hedin-lundqvist potential') + write(6,*) + if(emmef.lt.0.0.and.irho.ne.0) then + print 780 +780 format (//,1x, 'emin less than the Fermi level; see file: ', + * ' results.dat',//) + stop + endif +c + print 770 +770 format( 1x,' generating t_l (for030) and', + &' atomic cross section (for050)') +c +c construct log-linear x mesh if calctype.ne.'asa' or calctype.ne.'ape'. +c in this latter case the x mesh is calculated in subroutine input_cont +c + if(asa_ape) call llmesh +c +c and generate core state wavefunction on log-linear x-mesh +c + call corewf(nas,nz(nas),i_absorber_hole,est) +c +c write out atomic orbitals of photoabsorber +c + kmxn = kmx(nas) + do i = 1, kmxn + rxd(i) = rx(i,nas) + enddo +c + dx = hx(nas) + ityhole = 0 + call get_atomic_orbitals(nz(nas),ityhole,rxd,dx,kmxn, + & lorb,ic_occ,iabs_occ,enp,orb) +c +c.....writing atomic orbital energies +c + write(6,*) 'writing atomic orbital energies' +c + do io = 1, 29 + if(iabs_occ(io).eq.0) cycle + enev = 2.0*enp(io)*13.605 + write(6,*) ' orbital energy (Ryd eV) ', orb(io), + & 2.0D0*enp(io), enev !, lorb(io) + enddo +c +c.....use overlapped potential to search for core state of photoabsorber +c + write(6,*) + write(6,*)'using overlapped potential to search for core ', + & 'states of photoabsorber' +c + if(irho.ne.0) then + anns = nns + ot = 1./3. + do k=1,kmax(nas) + rs_abs(k)=((3.*(r(k,nas)**2))/(rhotot(k,nas)*anns))**ot + enddo +c rsint_abs=(3./(pi*4.*rhoint(1)*anns))**ot + endif +c +c + kxhs = kmax(nas) + kmxn = kmx(nas) + do k = 1, kxhs + rtmp(k) = r(k,nas) + if(irho.eq.0) then + zr(k) = -rtmp(k)*dble(v(k,nas)) + else + zr(k) = + & -rtmp(k)*(dble(v(k,nas) + 1.06*vxc_gs(rs_abs(k)))) +c write(6,*) rtmp(k), zr(k) + endif + enddo + write(6,*) +c.....calculate non relativistic core states of photoabsorber + write(6,*)' calculating non relativistic core states' + + do io = 1, 29 + if(ic_occ(io).eq.0) cycle + estryd = 2.d0*enp(io) + l = lorb(io) + orbtp = orb(io) + call search_corewf(nz(nas),i_absorber_hole,zr,rtmp,kxhs, + & rxd,dx,kmxn,estryd,l,orbtp) + enddo +c.....calculate relativistic core states of photoabsorber + write(6,*) + write(6,*)' calculating relativistic core states' + do io = 1, 29 + if(ic_occ(io).eq.0) cycle + estryd = 2.d0*enp(io) + l = lorb(io) + orbtp = orb(io) + call search_corewf_rel(nz(nas),i_absorber_hole,zr,rtmp,kxhs, + & rxd,dx,kmxn,estryd,l,orbtp) + enddo +c +c.....calculate plasmon energy corresponding to all valence electrons of the cluster +c + call val_plasmon +c + if(irho.eq.0.and.calctype.eq.'dos') + & call valence_dos(nosym,calctype) +c +c if(tdl.eqv..true.) gamma = 0.d0 +c + if(irho.eq.0) then + write(6,*)'adding i*gamma to real potential ' + cgamma = (0.0,1.0)*gamma + vcon = vcon + cgamma + do na = 1, ndat + do k = 1, kmax(na) + v(k,na) = v(k,na) + cgamma + enddo + enddo + endif +c + call cont(potype,potgen,lmax_mode,lmaxt,relc,eikappr,db,tdl, + 1 lorb,iabs_occ) +c +c + return + end +c +c + subroutine cont(potype,potgen,lmax_mode,lmaxt,relc,eikappr,db, + 1 tdl,lorb,iabs_occ) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c +c + common/bessel/sbf(ltot_),dsbf(ltot_),snf(ltot_),dsnf(ltot_) + complex*16 sbf,dsbf,snf,dsnf +c + common /dens/ irho,rhotot(rd_,sd_),rhoint(2), + $ vcoul(rd_,sd_),vcoulint(2) +c + common /fcnr/kxe, h(d_),vcons(2),r(rd_,d_),v(rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) + complex*16 vcons,v +c + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +C + COMMON /LLM/ ALPHA, BETA +c + COMMON /PDQX/PX(RDX_,fl_), PX0(RDX_,fl_), PPX(RDX_,fl_), + & PAX(RDX_,fl_), RAMFNR(N_), RAMFSR(N_), RAMFSOP(N_), + & RAMFSOA(N_) + complex*16 PX, PX0, PPX, PAX, RAMFNR, RAMFSR, RAMFSOP, RAMFSOA +c + common /seculrx/ atmnr(n_), atmsr(n_), atmsop(n_), atmsoa(n_) + complex*16 atmnr, atmsr, atmsop, atmsoa +c + common /flag/ inmsh,inv,inrho,insym,iovrho,iosym, + 1 imvhl,nedhlp +c + common/mtxele/ nstart,nlast,dmx(2),dmx1(2),qmx(3),qmx1(3), + $ dxdir,dxexc,nfis,nfis1,nfis2 + real*8 nfis,nfis2,nfis1 + complex*16 dmx,dmx1,qmx,qmx1,dxdir,dxexc +c + common/mtxelex/ dmxx(2),dmxx1(2),dmxxa(2),dmxxa1(2), + & qmxx(3),qmxx1(3),qmxxa(3),qmxxa1(3), + & dxxdir,dxxexc,mdxx,mdxx1,mdxxa,mdxxa1, + & omxx(4),omxx1(4),omxxa(4),omxxa1(4), + & dqxx1(2,3),dmmx1(2),dqxxa1(2,3),dmmxa1(2) + complex*16 dmxx,dmxx1,dmxxa,dmxxa1,qmxx,qmxx1,qmxxa,qmxxa1, + & dxxdir,dxxexc,mdxx,mdxx1,mdxxa,mdxxa1, + & omxx,omxx1,omxxa,omxxa1,dqxx1,dmmx1,dqxxa1,dmmxa1 +c + character*8 name0 ,nsymbl + CHARACTER*3 VERSION +C + COMMON /V_TYPE/ VERSION +c + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,xe,ev +c + common/eels/einc,esct,scangl,qt,lambda,eelsme(npss,npss,npss), + & p1(rdx_,npss,nef_),p2(rdx_,npss,nef_), + & p3(rdx_,npss,nef_),ramfsr1(npss,nef_), + & ramfsr2(npss,nef_),ramfsr3(npss,nef_), + & lmxels(3,ua_),p3irreg(rdx_,7),p2irreg(rdx_,7) + complex*16 eelsme,p1,p2,p3,ramfsr1,ramfsr2,ramfsr3,argc,yc, + & p3irreg,p2irreg + real*8 lambda +c + common/msbhf/ il(rdx_,lexp_,d_), kl(rdx_,lexp_,d_), kappa + dimension msbfi(lexp_), mshfk(lexp_), ylc(lexp_*(lexp_+1)) + dimension dmsbfi(lexp_), dmshfk(lexp_) + real*8 kappa, arg, y, msbfi, mshfk, il, kl, dmsbfi, dmshfk +c + common/struct/ntnabs(nat_),ngbrabs +c +c ############# I include the common auger to take into account also the +c ############# to make the auger calculation +c + + common/auger/calctype,expmode,edge1,edge2 + + character*3 calctype, expmode + character*2 edge1,edge2 + + common /pdq/ p(rd_,fl_),ps(n_),dps(n_), + * ramf(n_),pss(6),dpss(6) + complex*16 p,ps,dps,ramf,pss,dpss + +c ###################common /pdqi/ modified to include the two core hole +c ##################of the electrons which interacts and give rise +c + common /pdqi/rpi(rd_),rpi1(rd_),rpi2(rd_) +c + common /seculr/ atm(n_) + complex*16 atm +c + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) +c + common/lparam/lmax2(nat_),l0i +c + common/typot/ ipot +c + complex*16 amem,amem1,pamel,pamel0,cofct,vrr,qcofct,ocofct, + 1 rexsrme,rexssme +c + dimension es(nep_),xkrn(rd_),xkri(rd_),xkrs(d_),cofct(nep_,2) + dimension qcofct(nep_,3),ocofct(nep_,4) +c + common/phase/phexp_nr(nep_,ltot_),phexp_sr(nep_,ltot_), + 1 phase_nr(nep_,ltot_,ua_),phase_sr(nep_,ltot_,ua_) + complex*16 phexp_nr, phexp_sr, vl, dvl + logical tdl + dimension lorb(29),iabs_occ(29) +c + logical*4 doit, do_r_in + logical*4 xasxpd +c +c fortran units +c + common/funit/idat,iwr,iphas,iedl0,iwf + +c + complex*16 atmd +c + dimension distin(d_), distor(d_), ntnabs1(nat_) + character*20 correction + character*9 reg_type,irr_type + character*5 potype + character*4 spectro + character*2 potgen,relc + character*8 filename + character*3 eikappr +c + data facts/8.067/,ot/.3333333/,pai/3.1415927/ + data fsc,fscs4 /7.29735e-3,1.331283e-5/ +c +c.....facts=4.*(pi)**2/137*(0.529)**2*100.0 if cross section is expresse +c..... in megabarns = 10.e-18 cm**2 +c +c +c start energy do loop: +c +c 67 if( irho .eq. 0 ) write(6,40) vcon +c 40 format(//,' interstitial potential vcon = (',E12.6,E12.6,')',//) +c + reg_type='regular ' + irr_type='irregular' +c + if(relc.eq.'nr') then + correction='non relativistic ' + elseif(relc.eq.'sr') then + correction='scalar relativistic ' + elseif(relc.eq.'so') then + correction='spin-orbit ' + else + correction=' ' + endif +c + if (calctype.eq.'xpd') then + spectro='PED ' + elseif (calctype.eq.'xas') then + spectro='XAS ' + elseif (calctype.eq.'aed') then + spectro='AED ' + elseif (calctype.eq.'led') then + spectro='LEED' + elseif (calctype.eq.'rex') then + spectro='REXS' + elseif (calctype.eq.'els') then + spectro='EELS' + elseif (calctype.eq.'e2e') then + spectro='E,2E' + endif +c + if (emin.lt.dble(vcon)) then + write(6,45) +c stop + endif +c + 45 format(//,' emin less than the interstitial potential vcon',//) +c + xasxpd = (calctype.eq.'xpd'.or.calctype.eq.'xas') +c + if(irho.eq.0) go to 68 + ot = 1./3. + rsint = (3./(4.*pai*rhoint(1)))**ot + write(6,41) gamma,rsint + 41 format(/,1x,' gamma =',f10.6,' rsint =',f10.6,/) + 68 doit = .true. + if(calctype.eq.'xas') then + write(50,803) + elseif(calctype.eq.'rex') then + write(50,804) + elseif(calctype.eq.'xpd') then + write(50,807) + endif +c + 803 format(2x,' e vcon mfp ', + $ ' sigma0 regrme singrme ') +c + 804 format(2x,' e vcon mfp ', + $ ' rexsrme rexssme ') +c + 807 format(2x,' e vcon mfp ', + $ ' sigma0 regrme ') +c +c +c de = alog(emax - emin + 1.)/(kxe - 1.) +c con = 27.2116/7.62 +c wvb = sqrt(con*emin) +c wve = sqrt(con*emax) +c kxe = nint((wve-wvb)/0.05 + 1.) + kxe = nint((emax-emin)/de + 1.) +c + nval=1 + do jat=1,nuatom + nval=max0(nval,nterms(jat)) + enddo + write(35,111) nuatom,kxe,1,ipot,lmax_mode + write(85,111) nuatom,kxe,1,ipot,lmax_mode + write(86,111) nuatom,kxe,1,ipot,lmax_mode + write(87,111) nuatom,kxe,1,ipot,lmax_mode + write(95,111) nuatom,kxe,1,ipot,lmax_mode + write(70,111) nuatom,kxe,1,ipot,lmax_mode + write(80,111) nuatom,kxe,1,ipot,lmax_mode + write(90,111) nuatom,kxe,1,ipot,lmax_mode + 111 format(5(5x,i4)) +c + if(potgen.eq.'in') then + write(6,*) ' check in subroutine cont' +c + write(6,*) ' order of neighb. -- symb. -- dist. from absorber' + write(6,*) ' ' +c +c.....check with molpot data: ok (14/12/2007) +c + do i=1,ngbrabs + nb=ntnabs(i) + dist=sqrt((xv(nb)-xv(1))**2+(yv(nb)-yv(1))**2+(zv(nb)-zv(1))**2) + write(6,*) nb, nsymbl(nb), dist + enddo +c + endif +c + write(6,*) ' ---------------------------------------------------', + 1 '--------------' +c + do nb=1,ndat + dist=sqrt((xv(nb)-xv(1))**2+(yv(nb)-yv(1))**2+(zv(nb)-zv(1))**2) + distin(nb) = dist + enddo +c +c endif +c +c.....Order prototypical atoms in order of increased distance from absor +c + call sort(ndat,distin,ndiff,distor) + small=0.00001 +c nbrs=ngbrabs + nbrs = ndiff +c nbrs=8 +c + do i=1,nbrs + do j=1,ndat + if(abs(distin(j)-distor(i)).lt.small) then + ntnabs1(i)=j + write(6,12) j, nsymbl(j), distin(j) + endif + enddo + enddo + 12 format(5X,I4,12X,A2,10X,F10.6) +c +c do i=2,nbrs +c write(6,*) ntnabs1(i), ntnabs(i-1) +c enddo +c + +c +c write(6,*) 'irho =', irho +c write(6,*) '----------------------------------' + nunit=40 + nunit1=nunit+1 +c +c.....write out potential and density file for first neighbors to absorber +c +100 format(1x,a5,a5,a6,f10.5,a10,3f10.5) +c + if(irho.ne.0) then +c + open(unit=nunit,file='plot/plot_vc.dat',status='unknown') + open(unit=nunit1,file='plot/plot_dens.dat',status='unknown') +c + do i=1,nbrs +c + j = ntnabs1(i) + write(6,12) j, nsymbl(j), distin(j) + write(nunit,100) 'atom ',nsymbl(j), 'dist =',distin(j), + & ' coord = ', xv(j), yv(j), zv(j) + write(nunit1,100) 'atom ',nsymbl(j), 'dist =',distin(j), + & ' coord ', xv(j), yv(j), zv(j) + do k=1,kmax(j) + write(nunit,*) r(k,j), vcoul(k,j) +c +c do ith=0,nthe +c theta = dthe*float(ith) +c do iph=0,nphi +c phi = dphi*float(iph) +c write(nunit1,*) r(k,j), theta, phi, rhotot(k,j) + write(nunit1,*) r(k,j), rhotot(k,j) +c enddo +c enddo +c + enddo +c close(nunit) +c close(nunit1) +c nunit=nunit+2 +c nunit1=nunit1+2 + enddo +c + else +c + open(unit=nunit,file='plot/plot_v.dat',status='unknown') + open(unit=nunit1,file='plot/plot_dens.dat',status='unknown') + do i=1,nbrs +c + j = ntnabs1(i) + write(6,12) j, nsymbl(j), distin(j) + write(nunit,100) 'atom ',nsymbl(j), 'dist =',distin(j), + & ' coord = ', xv(j), yv(j), zv(j) + write(nunit1,100) 'atom ',nsymbl(j), 'dist =',distin(j), + & ' coord ', xv(j), yv(j), zv(j) + do k=1,kmax(j) + write(nunit,*) r(k,j), dble(v(k,j)) +c +c do ith=0,nthe +c theta = dthe*float(ith) +c do iph=0,nphi +c phi = dphi*float(iph) +c write(nunit1,*) r(k,j), theta, phi, rhotot(k,j) + write(nunit1,*) r(k,j), rhotot(k,j) +c enddo +c enddo +c + + enddo +c close(nunit) +c close(nunit1) +c nunit=nunit+2 +c nunit1=nunit1+2 + enddo +c +c + endif +c + close(nunit) + close(nunit1) +c +c endif +c write(6,*) '----------------------------------' +c do i=1,ndat +c write(6,*) i, nsymbl(i),distin(i),distor(i) +c enddo +C +c......l0i set in subroutine setup +c + cl = (l0i + 1.5)**2 + nid = 1 +c write(6,*) 'in sub cont l0i =', l0i + write(6,*) ' ' +c +c nels = 1 + if(calctype.eq.'els'.or.calctype.eq.'e2e') then +c nels = 3 +c +c calculate cluster size for effective integration of eels tme +c + kappa = 1.d0/lambda ! to account for thomas-fermi screening + ! length = 2.9*0.529/(r_s)^(1/2) + ! default = 1/20 = 0.05 (au)^{-1} +c + do i = 1, ndat + rcut = distor(i) + scrcoul = exp(-kappa*rcut)/rcut + if(scrcoul.le.0.05) go to 11 + enddo + 11 neff = i - 1 + write(6,*)' neff =', neff + if(neff.gt.nef_) then + write(6,*)' increase dimension of nef_ ',nef_, + & ' should be at least equal to neff ',neff + call exit + endif +c + ltc = lexp_ + y = 0.0d0 + do na = 1, ndat + do k = 1, kmx(na) + arg = kappa*rx(k,na) + call msbf(arg,y,ltc,msbfi,dmsbfi) + call mshf(arg,y,ltc,mshfk,dmshfk) + do l = 1, ltc + il(k,l,na) = msbfi(l) + kl(k,l,na) = mshfk(l)*(-1)**(l-1)*kappa !correction 15 march 2014 + enddo + enddo + enddo +c + scangl = scangl/180.0*pai + qt2 = einc + esct - 2.0*sqrt(einc*esct)*cos(scangl) + qt = sqrt(qt2) + write(6,*) ' ' + write(6,*)' Calculating eels in DWBA. einc =',einc, + & ' esct =', esct,' einl =', einc - esct - cip + write(6,*)' Momentum transfer qt =', qt, ' au^{-1}' + write(6,*)' Scattering angle', scangl, 'radians' + write(6,*)' Scattering angle', scangl*180.0/pai, 'degrees' + write(6,*) ' ' + write(6,*) ' Coulomb screening inverse length kappa =', kappa + write(6,*) ' ' +c + endif +c +c.....Calculation of tl and rme for xpd, xas and rexs +c +c + if (calctype.eq.'xpd'.or.calctype.eq.'xas'.or. + 1 calctype.eq.'rex' .or. calctype.eq.'aed'.or. + 2 calctype.eq.'led') then +c + nks = 1 !ficticious: in this section only for writing purposes +c +c writing the headers of the rme file +c + IF(VERSION.EQ.'1.1') THEN + write(55,721) + write(55,722) spectro,correction + write(55,721) + ELSEIF(VERSION.EQ.'2.0') THEN + write(55,821) + write(55,822) spectro,correction + write(55,840) + ENDIF +C + if(calctype.eq.'xpd'.or.calctype.eq.'xas'.or. + 1 calctype.eq.'rex') then + IF(VERSION.EQ.'1.1') THEN + write(55,730) + write(55,740) + write(55,750) + write(55,740) + ELSEIF(VERSION.EQ.'2.0') THEN + write(55,830) + write(55,840) + write(55,850) + write(55,840) + ENDIF + endif +c + do 9 ne=1,kxe + es(ne) = emin + float(ne-1)*de + e=es(ne) + ev=e-vcon +c +c calculate energy dependent potential: +c + if( irho .ne. 0 ) then + if(ne.eq.1) write(6,*) ' irho =', irho, + & ' entering vxc to calculate energy', + & ' dependent exchange' + call vxc ( doit ) + ev=e-vcon + write(6,*) ' energy dependent vcon = ', vcon,' at energy', e + else + if(ne.eq.1.and.nks.eq.1) then + write(6,*) ' irho =', irho, ' energy independent potential' + write(6,*)' constant interstitial potential vcon =', vcon + endif + endif + +C +C CONSTRUCT RELATIVISTIC POTENTIAL ON LINEAR-LOG MESH +C + CALL VREL +C + xe=sqrt(ev) +C + 113 FORMAT('++++++++++++++ KINETIC ENERGY POINT ',I4,' : ',F8.2, + 1 ' eV ++++++++++++++') +c +c.....write out potential ans rs files for first neighbors to +c.....absorber for the first energy point +c + nunit=40 + nunit1=nunit+1 + open(unit=nunit,file='plot/plot_v(e).dat',status='unknown') + open(unit=nunit1,file='plot/plot_rs.dat',status='unknown') +c + if(ne.eq.1) then +c + do i=1,nbrs +c + j = ntnabs1(i) + +c write(6,*) j, nsymbl(j), distin(j) + write(nunit,100) 'atom ',nsymbl(j), 'dist =',distin(j), + & ' coord = ', xv(j), yv(j), zv(j) + write(nunit1,100) 'atom ',nsymbl(j), 'dist =',distin(j), + & ' coord ', xv(j), yv(j), zv(j) + do k=1,kmax(j) + write(nunit,*) r(k,j), dble(v(k,j)) + write(nunit1,*) r(k,j), rhotot(k,j) + enddo +c close(nunit) +c close(nunit1) +c nunit=nunit+2 +c nunit1=nunit1+2 + enddo +c + endif +c + close(nunit) + close(nunit1) +c +c calculate maximum l-value lmxne(n,ne) for each prototipical atom +c at the energy e=es(ne) +c +c if(lmax_mode.eq.2.or.calctype.eq.'els'.or.calctype.eq.'e2e') then + if(lmax_mode.eq.2) then + do n=1,nuatom + lmxne(n,ne) = nint(sqrt(e)*rs(n))+2 + if(lmxne(n,ne).lt.l0i+1) lmxne(n,ne)=l0i+2 +c lmxels(nks,n) = lmxne(n,ne) +c write(6,*) nks, n, e, rs(n), lmxne(n,ne) + enddo + endif +c + NBL1=NUATOM/4 + XNBL1=FLOAT(NBL1)+0.0001 + XNBL2=FLOAT(NUATOM)/4. + IF(XNBL1.LT.XNBL2) NBL1=NBL1+1 + 112 FORMAT(4(7X,I2)) + IF(EIKAPPR.NE.'yes') THEN + IF(VERSION.NE.'1.1') THEN + WRITE(35,113) NE,dble(EV*13.605693) + ENDIF + if (lmax_mode.eq.2) then + DO JL=1,NBL1 + JLN=4*(JL-1)+1 + write(35,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(85,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(86,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(87,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(95,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(70,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(80,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(90,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + ENDDO + else if (lmax_mode.eq.1) then + DO JL=1,NBL1 + JLN=4*(JL-1)+1 + write(35,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(85,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(86,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(87,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(95,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(70,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(80,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(90,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + ENDDO + else + DO JL=1,NBL1 + JLN=4*(JL-1)+1 + write(35,112) lmaxt,lmaxt,lmaxt,lmaxt + write(85,112) lmaxt,lmaxt,lmaxt,lmaxt + write(86,112) lmaxt,lmaxt,lmaxt,lmaxt + write(87,112) lmaxt,lmaxt,lmaxt,lmaxt + write(95,112) lmaxt,lmaxt,lmaxt,lmaxt + write(70,112) lmaxt,lmaxt,lmaxt,lmaxt + write(80,112) lmaxt,lmaxt,lmaxt,lmaxt + write(90,112) lmaxt,lmaxt,lmaxt,lmaxt + ENDDO + endif + ENDIF +c +c energy dependent factors for dipole and quadrupole absoprtion; +c factor 1/3 for unpolarized absorption +c + if(ne.eq.1) + & write(6,*) ' check ionization potential:', cip + edfct= facts*(cip+e)*2./3.0 + edfctq = 2.0/5.0*3.0/16.0*edfct*((cip+e)*fsc)**2 + edfcto = 2.0/5.0*3.0/16.0*edfct*((cip+e)*fsc)**3/2.0 + dafsfct = (cip+e)**4 * pai**2 +c + write(6,*) ' ' + write(6,*) ' ' + write(6,*) ' value of the mean free path:' + write(6,44) + 44 format(' --------------------------------------------------', + 1 '---------------') + if(gamma.ne.0.0.and.ne.eq.1.and.nks.eq.1) then + amfph = 0.529/gamma/2 + write(6,43) amfph,e + 43 format(' average mean free path due to finite gamma: mfp =' + * ,f10.5,' angstrom at energy ', f10.5 ,/) + endif +c + if(irho.eq.0.and.imvhl.eq.0.and.nks.eq.1) then + write(6,*)' infinite cluster mfp for real potential' + go to 802 + endif +ctn write(6,40) vcon,eftr + xeim = -aimag(xe) +c +c calculate average mean free path (= amfp). define r-dependent +c wave vector xkr and its indefinite integral xkri +c + amfpi = 0.0 + do 20 n = 1,ndat + kxn = kmax(n) + do 30 k = 1,kxn + vrr = v(k,n) + cl/r(k,n)**2 + if ((e-dble(vrr)).lt.0.0) then + xkrn(k) = 0.0 + go to 30 + endif + xkrn(k) = -imag(sqrt(e-vrr)) + 30 continue +c +c calculate integral of xkr +c + call integr (xkrn(1),r(1,n),kxn,ichg(1,n),xkri,nid) + call dinterp (r(kplace(n)-3,n),xkri(kplace(n)-3),7,rs(n), + * xkrs(n),dummy,.false.) + xkrs(n) = xkrs(n)/rs(n) + 20 amfpi = amfpi + xkrs(n) +c +c it is assumed that the average interstitial path is 2/3 of the total +c + amfpi = 1./3.*amfpi/ndat + 2.0*xeim/3. + if (amfpi.ne.0.0) then + amfp = 0.529/amfpi/2. + write(6,42) amfp, e + 42 format(' average mean free path in the cluster : mfp =' + * ,f10.5,' angstrom at energy ', f10.5 ,/) + endif + 802 continue + if(gamma.ne.0.0.and.ne.eq.1) then + amfpt = 0.529/(amfpi + gamma)/2.0 + write(6,46) amfpt, e + endif + 46 format(' total mean free path due to Im V and gamma: mfp =' + * ,f10.5,' angstrom at energy ', f10.5) + if(ne.eq.1.and.amfpt.eq.0.0.and.nks.eq.1) write(6,*) + & ' infinite mean free path for gamma: mfp = 0.0 and Im V = 0.0 ' + write(6,44) + write(6,*) ' ' +c +c calculate atomic t-matrix elements atm(n) +C +c if(ne.eq.1.and.nks.eq.1) write(6,*) + if(ne.eq.1) write(6,*) + & ' calculating atomic t-matrix elements atm(n)' +c +c + if(eikappr.eq.'yes') then + neik = 1 + if(ne.eq.1) then + write(6,*)' ' + write(6,*)' calculating phases in the eikonal approximation' + endif + call eikonal(nuatom,xe,z,rs,db,neik) +c + else !calculate tl in normal way +c + call smtx(ne,lmax_mode) +c +c calculate the radial integrals of transition matrix elements: +c + if(calctype.ne.'led') then + call radial(doit,imvhl) + endif + +c +c calculate atomic t-matrix with relativistic corrections +c + call smtxllm(ne,lmax_mode,relc,nks,px,px0,ppx,pax, + & ramfnr,ramfsr,ramfsop,ramfsoa,tdl) +c +c and corresponding radial integrals of transition matrix elements: +c + call radialx(ne,relc,eikappr) +c +c modified to write the continuum radial wavefunction for eels +c + lxp = lmxne(nas,ne) + if(lxp.gt.fl_) lxp=fl_ - 1 + call writewf(lxp) +c +c.....calculate dipole cross section and atomic matrix elements +c + write(50,*)' ------------------------- ' + write(50,*)' &&&&&&&&&&&&&&&&&&&&&&&&& ' + write(50,*)' ------------------------- ' +c + if (xasxpd) then + write(50,*) ' dipole atomic cross section' + else + write(50,*) ' dipole rexs matrix elements' + endif +c + sigmasum = 0.0 +c + do 800 i=1,2 + if((l0i.eq.0).and.(i.eq.1)) goto 800 + np= l0i + (-1)**i + amem = dmx(i) + amem1 = dmx1(i) + pamel = amem1*cmplx(atm(nstart+np))*edfct +c write(50,*)'nr ', amem1*xe/pai/(l0i - 1 + i) + cofct(ne,i) = amem*cmplx(atm(nstart+np))**2*edfct*xe/pai + pamel0 = cofct(ne,i)/cmplx(atm(nstart+np)) + sigma0 = -aimag(pamel) + sigmasum = sigmasum + sigma0 + sigma0r = -aimag(pamel0) + rexsrme = dmx(i)*xe/pai/(l0i-1+i) + rexssme = dmx1(i)/(l0i-1+i) +c cofct(ne,i) = cofct(ne,i)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif +c + if(i.eq.2) write(98,*) e*13.6, sigma0 + 800 continue +c + do i=1,2 + cofct(ne,i) = cofct(ne,i)/sigmasum + enddo +c +c.....calculate quadrupole atomic matrix elements for cross section (temp) +c + if (xasxpd) then + write(50,*) ' quadrupole atomic cross section ' + else + write(50,*) ' quadrupole rexs matrix elements ' + endif +c + n = 0 + sigmasum = 0.0 + do 900 i=-2,2,2 + n = n + 1 + lf = l0i + i + if(lf.le.0) go to 900 + np = l0i + i + amem = qmx(n) + amem1 = qmx1(n) + pamel = amem1*cmplx(atm(nstart+np))*edfctq + qcofct(ne,n) = amem*cmplx(atm(nstart+np))**2*edfctq*xe/pai + pamel0 = qcofct(ne,n)/cmplx(atm(nstart+np)) + sigma0 = -aimag(pamel) + sigmasum = sigmasum + sigma0 + sigma0r = -aimag(pamel0) + rexsrme = qmx(n)*xe/pai + rexssme = qmx1(n) +c qcofct(ne,i) = qcofct(ne,n)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif + 900 continue +c + if (xasxpd) then + write(50,*)' ------------------------- ' + write(50,*)'electric and magnetic dipole and quadrupole cross', + & ' section with relativistic corrections of type: ', relc + write(50,*)' ------------------------- ' + else + write(50,*)' ------------------------- ' + write(50,*) 'electric and magnetic dipole and quadrupole rexs', + & ' matrix elements with relativistic corrections of type: ', relc + write(50,*)' ------------------------- ' + endif +c +c + if (xasxpd) then + write(50,*)'electric dipole atomic cross section with', + & ' rel. corr.s' + else + write(50,*)'electric dipole rexs matrix elements with', + & ' rel. corr.s' + endif +c + sigmasum = 0.0 +c + do 910 i=1,2 + if((l0i.eq.0).and.(i.eq.1)) goto 910 + np= l0i + (-1)**i + amem = dmxx(i) + amem1 = dmxx1(i) + if(relc.eq.'nr') then + atmd = atmnr(nstart+np) + else if (relc.eq.'sr') then + atmd = atmsr(nstart+np) + else + atmd = atmsop(nstart+np) + endif + pamel = amem1*atmd*edfct +c write(50,*)'nr-rc ', amem1*xe/pai/(l0i - 1 + i) + cofct(ne,i) = amem*atmd**2*edfct*xe/pai + pamel0 = cofct(ne,i)/atmd + sigma0 = -aimag(pamel) + sigmasum = sigmasum + sigma0 + sigma0r = -aimag(pamel0) + rexsrme = dmxx(i)*xe/pai/(l0i-1+i) + rexssme = dmxx1(i)/(l0i-1+i) +c cofct(ne,i) = cofct(ne,i)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif +c + if(i.eq.2) write(99,*) e*13.6, sigma0 + 910 continue +c +c + do i=1,2 + cofct(ne,i) = cofct(ne,i)/sigmasum + enddo +c +c + if (xasxpd) then + write(50,*)'magnetic dipole atomic cross section with', + & ' rel. corr.s' + else + write(50,*)'magnetic dipole rexs matrix elements with', + & ' rel. corr.s' + endif +c + i = 1 + fctmd = fsc**2/4.0 +c + np= l0i + amem = mdxx + amem1 = mdxx1 + if(relc.eq.'nr') then + atmd = atmnr(nstart+np) + else if (relc.eq.'sr') then + atmd = atmsr(nstart+np) + else + atmd = atmsop(nstart+np) + endif + pamel = amem1*atmd*edfct*fctmd +c write(50,*)'nr-rc ', amem1*xe/pai/(l0i - 1 + i) + cofct(ne,i) = amem*atmd**2*edfct*xe/pai*fctmd + pamel0 = cofct(ne,i)/atmd + sigma0 = -aimag(pamel) + sigma0r = -aimag(pamel0) + rexsrme = mdxx*xe/pai*fctmd + rexssme = mdxx1*fctmd +c cofct(ne,i) = cofct(ne,i)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif +c + write(99,*) e*13.6, sigma0 +c +c +c.....calculate quadrupole atomic matrix elements for cross section (temp) +c + if (xasxpd) then + write(50,*) ' quadrupole atomic cross section with rel. corr.s' + else + write(50,*) ' quadrupole rexs matrix elements with rel. corr.s' + endif +c + n = 0 + sigmasum = 0.0 + do 920 i=-2,2,2 + n = n + 1 + lf = l0i + i + if(lf.le.0) go to 920 + np = l0i + i + amem = qmxx(n) + amem1 = qmxx1(n) + if(relc.eq.'nr') then + atmd = atmnr(nstart+np) + else if (relc.eq.'sr') then + atmd = atmsr(nstart+np) + else + atmd = atmsop(nstart+np) + endif + pamel = amem1*atmd*edfctq + qcofct(ne,n) = amem*atmd**2*edfctq*xe/pai + pamel0 = qcofct(ne,n)/atmd + sigma0 = -aimag(pamel) + sigmasum = sigmasum + sigma0 + sigma0r = -aimag(pamel0) + rexsrme = qmxx(n)*xe/pai + rexssme = qmxx1(n) +c qcofct(ne,i) = qcofct(ne,n)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif +c + 920 continue +c +c +c +c.....calculate octupole atomic matrix elements for cross section (temp) +c + if (xasxpd) then + write(50,*) ' octupole atomic cross section with rel. corr.s' + else + write(50,*) ' octupole rexs matrix elements with rel. corr.s' + endif +c + n = 0 + sigmasum = 0.0 + do 921 i=-3,3,2 + n = n + 1 + lf = l0i + i + if(lf.le.0) go to 921 + np = l0i + i + amem = omxx(n) + amem1 = omxx1(n) + if(relc.eq.'nr') then + atmd = atmnr(nstart+np) + else if (relc.eq.'sr') then + atmd = atmsr(nstart+np) + else + atmd = atmsop(nstart+np) + endif + pamel = amem1*atmd*edfctq + ocofct(ne,n) = amem*atmd**2*edfcto*xe/pai + pamel0 = ocofct(ne,n)/atmd + sigma0 = -aimag(pamel) + sigmasum = sigmasum + sigma0 + sigma0r = -aimag(pamel0) + rexsrme = omxx(n)*xe/pai + rexssme = omxx1(n) +c qcofct(ne,i) = qcofct(ne,n)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif +c + 921 continue +c +c + if(relc.eq.'so') then +c + if (xasxpd) then + write(50,*)' dipole atomic cross section for second so component' + else + write(50,*)' dipole rexs matrix elements for second so component' + endif +c + do 930 i=1,2 + if((l0i.eq.0).and.(i.eq.1)) goto 930 + np= l0i + (-1)**i + amem = dmxxa(i) + amem1 = dmxxa1(i) + atmd = atmsoa(nstart+np) + pamel = amem1*atmd*edfct + cofct(ne,i) = amem*atmd**2*edfct*xe/pai + pamel0 = cofct(ne,i)/atmd + sigma0 = -aimag(pamel) + sigmasum = sigmasum + sigma0 + sigma0r = -aimag(pamel0) + rexsrme = dmxxa(i)*xe/pai/(l0i-1+i) + rexssme = dmxxa1(i)/(l0i-1+i) +c cofct(ne,i) = cofct(ne,i)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif +c + 930 continue +c + do i=1,2 + cofct(ne,i) = cofct(ne,i)/sigmasum + enddo +c +c.....calculate quadrupole atomic matrix elements for cross section +c + if (xasxpd) then + write(50,*)'quadrupole atomic cross section for second so ', + & 'component' + else + write(50,*)'quadrupole rexs matrix elements for second so ', + & 'component' + endif +c + n = 0 + sigmasum = 0.0 + do 940 i=-2,2,2 + n = n + 1 + lf = l0i + i + if(lf.le.0) go to 940 + np = l0i + i + amem = qmxxa(n) + amem1 = qmxxa1(n) + atmd = atmsoa(nstart+np) + pamel = amem1*atmd*edfctq + qcofct(ne,n) = amem*atmd**2*edfctq*xe/pai + pamel0 = qcofct(ne,n)/atmd + sigma0 = -aimag(pamel) + sigmasum = sigmasum + sigma0 + sigma0r = -aimag(pamel0) + rexsrme = qmxxa(n)*xe/pai + rexssme = qmxxa1(n) +c qcofct(ne,i) = qcofct(ne,n)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif +c + 940 continue +c +c... endif +C +c +c.....calculate octupole atomic matrix elements for cross section +c + if (xasxpd) then + write(50,*)'octupole atomic cross section for second so ', + & 'component' + else + write(50,*)'octupole rexs matrix elements for second so ', + & 'component' + endif +c + n = 0 + sigmasum = 0.0 + do 950 i=-3,3,2 + n = n + 1 + lf = l0i + i + if(lf.le.0) go to 950 + np = l0i + i + amem = omxxa(n) + amem1 = omxxa1(n) + atmd = atmsoa(nstart+np) + pamel = amem1*atmd*edfctq + ocofct(ne,n) = amem*atmd**2*edfcto*xe/pai + pamel0 = ocofct(ne,n)/atmd + sigma0 = -aimag(pamel) + sigmasum = sigmasum + sigma0 + sigma0r = -aimag(pamel0) + rexsrme = omxxa(n)*xe/pai + rexssme = omxxa1(n) +c qcofct(ne,i) = qcofct(ne,n)/sigma0 +c write(6,*) sigma0,sigma0r + if (calctype.eq.'xas') then + write(50,805) e,vcon,amfpt,sigma0,rexsrme,rexssme + else + write(50,806) e,vcon,amfpt,rexsrme,rexssme + endif +c + 950 continue +c + endif +C +C Writing the radial integrals in unit 55 +C eliminated division of dmx (qmx) by nfis: 29-3-2013 due to reorganization +C of normalization of initial core state +C + if(calctype.eq.'xpd'.or.calctype.eq.'xas'.or. + 1 calctype.eq.'rex') then +C + IF(VERSION.EQ.'1.1') THEN + if(l0i.eq.0) then +C + write(55,760) 0.0,0.0, + 1 sqrt(dmxx(2)*xe/pai), + 2 0.0,0.0, + 3 0.0,0.0, + 4 sqrt(qmxx(3)*xe/pai),reg_type +C + elseif(l0i.eq.1) then +C + write(55,760) sqrt(dmxx(1)*xe/pai/l0i), + 1 sqrt(dmxx(2)*xe/pai/(l0i+1)), + 2 0.0,0.0, + 3 sqrt(qmxx(2)*xe/pai), + 4 sqrt(qmxx(3)*xe/pai),reg_type +C + else +C + write(55,760) sqrt(dmxx(1)*xe/pai/l0i), + 1 sqrt(dmxx(2)*xe/pai/(l0i+1)), + 2 sqrt(qmxx(1)*xe/pai), + 3 sqrt(qmxx(2)*xe/pai), + 4 sqrt(qmxx(3)*xe/pai),reg_type +C + endif + ELSEIF(VERSION.EQ.'2.0') THEN + if(l0i.eq.0) then +c + write(55,860) (0.0,0.0), + 1 sqrt(dmxx(2)*xe/pai/(l0i+1)), + 2 sqrt(qmxx(1)*xe/pai), + 3 sqrt(qmxx(2)*xe/pai), + 4 sqrt(qmxx(3)*xe/pai), + 5 sqrt(mdxx*xe/pai), + 6 sqrt(omxx(1)*xe/pai), + 7 sqrt(omxx(2)*xe/pai), + 8 sqrt(omxx(3)*xe/pai), + 9 sqrt(omxx(4)*xe/pai),reg_type + else +c + write(55,860) sqrt(dmxx(1)*xe/pai/l0i), + 1 sqrt(dmxx(2)*xe/pai/(l0i+1)), + 2 sqrt(qmxx(1)*xe/pai), + 3 sqrt(qmxx(2)*xe/pai), + 4 sqrt(qmxx(3)*xe/pai), + 5 sqrt(mdxx*xe/pai), + 6 sqrt(omxx(1)*xe/pai), + 7 sqrt(omxx(2)*xe/pai), + 8 sqrt(omxx(3)*xe/pai), + 9 sqrt(omxx(4)*xe/pai),reg_type + endif + ENDIF +C +c + if(relc.eq.'so') then + write(55,*) ' second component of so matrix element ' +C + IF(VERSION.EQ.'1.1') THEN + if(l0i.eq.0) then +C + write(55,760) 0.0,0.0, + 1 sqrt(dmxxa(2)*xe/pai), + 2 0.0,0.0, + 3 0.0,0.0, + 4 sqrt(qmxxa(3)*xe/pai) +C + elseif(l0i.eq.1) then +C + write(55,760) sqrt(dmxxa(1)*xe/pai/l0i), + 1 sqrt(dmxxa(2)*xe/pai/(l0i+1)), + 2 0.0,0.0, + 3 sqrt(qmxxa(2)*xe/pai), + 4 sqrt(qmxxa(3)*xe/pai) +C + else +C + write(55,760) sqrt(dmxxa(1)*xe/pai/l0i), + 1 sqrt(dmxxa(2)*xe/pai/(l0i+1)), + 2 sqrt(qmxxa(1)*xe/pai), + 3 sqrt(qmxxa(2)*xe/pai), + 4 sqrt(qmxxa(3)*xe/pai) +C + endif + ELSEIF(VERSION.EQ.'2.0') THEN + if(l0i.eq.0) then +c + write(55,860) (0.0,0.0), + 1 sqrt(dmxxa(2)*xe/pai/(l0i+1)), + 2 sqrt(qmxxa(1)*xe/pai), + 3 sqrt(qmxxa(2)*xe/pai), + 4 sqrt(qmxxa(3)*xe/pai), + 5 sqrt(mdxxa*xe/pai), + 6 sqrt(omxxa(1)*xe/pai), + 7 sqrt(omxxa(2)*xe/pai), + 8 sqrt(omxxa(3)*xe/pai), + 9 sqrt(omxxa(4)*xe/pai),reg_type +C + else +c + write(55,860) sqrt(dmxxa(1)*xe/pai/l0i), + 1 sqrt(dmxxa(2)*xe/pai/(l0i+1)), + 2 sqrt(qmxxa(1)*xe/pai), + 3 sqrt(qmxxa(2)*xe/pai), + 4 sqrt(qmxxa(3)*xe/pai), + 5 sqrt(mdxxa*xe/pai), + 6 sqrt(omxxa(1)*xe/pai), + 7 sqrt(omxxa(2)*xe/pai), + 8 sqrt(omxxa(3)*xe/pai), + 9 sqrt(omxxa(4)*xe/pai),reg_type + endif + ENDIF + endif +c + endif +c +c + if(calctype.eq.'xas'.or.calctype.eq.'rex') then +C + IF(VERSION.EQ.'1.1') THEN + if(l0i.eq.0) then +c write(55,*) '========dq irregular me: hs mesh===============' +C +c write(55,760) 0.0,0.0, +c 1 dmx1(2)/(l0i+1), +c 2 qmx1(1), +c 3 qmx1(2), +c 4 qmx1(3) +C +c write(55,*) '========dq irregular me: ll mesh===============' +C + write(55,760) 0.0,0.0, + 1 dmxx1(2)/(l0i+1), + 2 qmxx1(1), + 3 qmxx1(2), + 4 qmxx1(3),irr_type + else +c write(55,*) '========dq irregular me: hs mesh===============' +C +c write(55,760) dmx1(1)/l0i, +c 1 dmx1(2)/(l0i+1), +c 2 qmx1(1), +c 3 qmx1(2), +c 4 qmx1(3) +C +c write(55,*) '========dq irregular me: ll mesh===============' +C + write(55,760) dmxx1(1)/l0i, + 1 dmxx1(2)/(l0i+1), + 2 qmxx1(1), + 3 qmxx1(2), + 4 qmxx1(3),irr_type + endif + ELSEIF(VERSION.EQ.'2.0') THEN + if(l0i.eq.0) then +c write(55,*) '========dq irregular me: hs mesh===============' +C +c write(55,860) 0.0,0.0, +c 1 dmx1(2)/(l0i+1), +c 2 qmx1(1), +c 3 qmx1(2), +c 4 qmx1(3) +C +c write(55,*) '========dq irregular me: ll mesh===============' +C + write(55,860) (0.0,0.0), + 1 dmxx1(2)/(l0i+1), + 2 qmxx1(1), + 3 qmxx1(2), + 4 qmxx1(3), + 5 mdxx1, + 6 omxx1(1), + 7 omxx1(2), + 8 omxx1(3), + 9 omxx1(4),irr_type + else +c write(55,*) '========dq irregular me: hs mesh===============' +C +c write(55,860) dmx1(1)/l0i, +c 1 dmx1(2)/(l0i+1), +c 2 qmx1(1), +c 3 qmx1(2), +c 4 qmx1(3) +C +c write(55,*) '========dq irregular me: ll mesh===============' +C + write(55,860) dmxx1(1)/l0i, + 1 dmxx1(2)/(l0i+1), + 2 qmxx1(1), + 3 qmxx1(2), + 4 qmxx1(3), + 5 mdxx1, + 6 omxx1(1), + 7 omxx1(2), + 8 omxx1(3), + 9 omxx1(4),irr_type + endif + ENDIF +C + WRITE(55,114) + WRITE(55,115) + WRITE(55,114) + WRITE(55,116) + WRITE(55,114) +C + 114 FORMAT(29X,'++++++++++++++++++++++++++++++++++++++++++++++++++', + 1 '+++') + 115 FORMAT(29X,'+ off-diagonal irregular E1-E2 interference terms', + 1 ' +') + 116 FORMAT(29X,'+ l_E1 + l_E2 + E1-E2 ', + 1 ' +') + 117 FORMAT(29X,'+',3X,I3,4X,'+',3X,I3,4X,'+',2X,E12.5,1X,E12.5, + 1 2X,'+') +C + DO I=1,2 + LD = L0I + (-1)**I + M = 0 + DO J=-2,2,2 + M = M + 1 + LQ = L0I + J + WRITE(55,117) LD, LQ, DQXX1(I,M) + ENDDO + ENDDO +C +c + WRITE(55,114) +c + endif +c +C + endif !end if clause for eikonal approximation +c +c 810 format(29x,2f8.5,4x,2f8.5) +c + doit = .false. +c + 9 continue !end energy loop +c + write(iedl0) ((cofct(ne,i),ne=1,kxe),i=1,2) +c +c if(tdl.eqv..true.) calculate energy derivatives of phase-shifts +c + fct = 658.21/13.605 ! (= 48.38 - time delay will be in as (attoseconds)) + write(74,*) ' energy - phase derivative in as' + write(84,*) ' energy - phase derivative in as' +c + nlx = 5 + if(tdl.eqv..true.) then + nr = 6 + ni = 3 + do nl = 1, nlx + write(74,*)' l =', nl-1 + do ne = 1, kxe-nr + ei = es(ne+ni) + call interp(es(ne),phexp_nr(ne,nl),nr,ei,vl,dvl,.true.) + dphs = dvl*conjg(vl)*(0.d0,1.d0) + write(74,120) ei, fct*dphs !, vl, dvl + call interp(es(ne),phexp_sr(ne,nl),nr,ei,vl,dvl,.true.) + dphs = dvl*conjg(vl)*(0.d0,1.d0) + write(84,120) ei, fct*dphs !, vl, dvl + enddo + ne = kxe-nr+1 + do ki = 0, ni-1 + ei = es(ne + ni + ki) + call interp(es(ne),phexp_nr(ne,nl),nr,ei,vl,dvl,.true.) + dphs = dvl*conjg(vl)*(0.d0,1.d0) + write(74,120) ei, fct*dphs + call interp(es(ne),phexp_sr(ne,nl),nr,ei,vl,dvl,.true.) + dphs = dvl*conjg(vl)*(0.d0,1.d0) + write(84,120) ei, fct*dphs + enddo + enddo + endif + 120 format(7f12.4) +c +c.....calculate occupancy of given orbital symmetry for Levinson theorem +c + ns = 0 + np = 0 + nd = 0 + nf = 0 + ng = 0 + do io = 1, 29 + if(iabs_occ(io).eq.0) cycle + if(lorb(io).eq.0) ns = ns + 1 + if(lorb(io).eq.1) np = np + 1 + if(lorb(io).eq.2) nd = nd + 1 + if(lorb(io).eq.3) nf = nf + 1 + if(lorb(io).eq.4) ng = ng + 1 + enddo +c +c write(6,'(5i5)') ns, np, nd, nf, ng +c eliminate phase jumps due to log function range (-pi:pi) +c + do nl = 1, nlx + l = nl - 1 +c write(96,*)' l =', l + do ne = 1, kxe + if(ne.eq.1) then + phasep = phase_nr(ne,nl,nas) +c write(96,'(2f12.4)') es(ne), phase_nr(ne,nl,nas) + cycle + endif + phasef = phase_nr(ne,nl,nas) + if(abs(phasef - phasep).gt.1.98d0*pai) then +c write(96,*)'sign of phasep', sign(1.d0,phasep) + phasef = phasef + sign(1.d0,phasep)*2.d0*pai + endif + phase_nr(ne,nl,nas) = phasef + phasep = phasef +c write(96,'(2f12.4)') es(ne), phase_nr(ne,nl,nas) + enddo + enddo +c + do nl = 1, nlx + l = nl - 1 +c write(97,*)' l =', l + do ne = 1, kxe + if(ne.eq.1) then + phasep = phase_sr(ne,nl,nas) +c write(97,'(2f12.4)') es(ne), phase_sr(ne,nl,nas) + cycle + endif + phasef = phase_sr(ne,nl,nas) + if(abs(phasef - phasep).gt.1.98d0*pai) then +c write(96,*)'sign of phasep', sign(1.d0,phasep) + phasef = phasef + sign(1.d0,phasep)*2.d0*pai + endif + phase_sr(ne,nl,nas) = phasef + phasep = phasef +c write(97,'(2f12.4)') es(ne), phase_sr(ne,nl,nas) + enddo + enddo +c + write(78,*) ' energy - continuous phase in rad for absorber' + write(88,*) ' energy - continuous phase in rad for absorber' + do nl = 1, nlx + l = nl - 1 + write(78,*)' l =', l + write(88,*)' l =', l + do ne = 1, kxe + if(l.eq.0) then + write(78,120) es(ne), phase_nr(ne,nl,nas) + ns*pai + write(88,120) es(ne), phase_sr(ne,nl,nas) + ns*pai + elseif(l.eq.1) then + write(78,120) es(ne), phase_nr(ne,nl,nas) + np*pai + write(88,120) es(ne), phase_sr(ne,nl,nas) + np*pai + elseif(l.eq.2) then + write(78,120) es(ne), phase_nr(ne,nl,nas) + nd*pai + write(88,120) es(ne), phase_sr(ne,nl,nas) + nd*pai + elseif(l.eq.3) then + write(78,120) es(ne), phase_nr(ne,nl,nas) + nf*pai + write(88,120) es(ne), phase_sr(ne,nl,nas) + nf*pai + elseif(l.eq.4) then + write(78,120) es(ne), phase_nr(ne,nl,nas) + ng*pai + write(88,120) es(ne), phase_sr(ne,nl,nas) + ng*pai + endif + enddo + enddo +c +c + else !perform eels or e2e calculation +c + write(6,*)' calculating eels radial matrix elements' + write(6,*)' n. of prototypical atoms in the effective cluster', + & ' chosen for eels (e2e) radial matrix elements',neff + write(6,*) ' ' + write(6,*) ' ' +C + IF(VERSION.EQ.'1.1') THEN + write(55,721) + write(55,722) spectro,correction + write(55,740) + ELSEIF(VERSION.EQ.'2.0') THEN + write(55,821) + write(55,822) spectro,correction + write(55,840) + ENDIF +c +c +c +c +c write(55,815) +c +c 815 format(2x,'single and two-site eels (e2e) radial matrix elements') +c + do ne = 1, kxe + deltae = float(ne-1)*de + write(6,*) ' ---> start of calculation of eels (e2e) rme at', + 1 ' energy point ',ne +c +c nks: loop on the 3 electrons involved: +c = 1 : incoming electron +c = 2 : scattered electron +c = 3 : excited electron +c + do 10 nks = 1, 3 + if(expmode.eq.'cis') then + if(nks.eq.1) e = einc + if(nks.eq.2) e = einc - cip - emin - deltae + if(nks.eq.3) e = emin + deltae + elseif(expmode.eq.'cfs') then + if(nks.eq.1) e = esct + cip + emin + deltae + if(nks.eq.2) e = esct + if(nks.eq.3) e = emin + deltae + elseif(expmode.eq.'cel') then + if(nks.eq.1) e = einc + deltae + if(nks.eq.2) e = einc - cip - emin + deltae + if(nks.eq.3) e = emin + endif +c + ev=e-vcon +c + if(nks.eq.1) write(6,*)' einc =',e,' Ryd' + if(nks.eq.2) write(6,*)' esct =',e,' Ryd' + if(nks.eq.3) write(6,*)' eloss =',e,' Ryd', + 1 ' (excluding the ion. pot.)' +c +c calculate energy dependent potential: +c + if( irho .ne. 0 ) then + if(ne.eq.1) write(6,*) ' irho =', irho, + & ' entering vxc to calculate energy', + & ' dependent exchange' + call vxc ( doit ) + else + if(ne.eq.1.and.nks.eq.1) then + write(6,*) ' irho =', irho, ' energy independent', + 1 ' potential' + write(6,*)' constant interstitial potential vcon =', + 1 vcon + endif + endif + ev=e-vcon + if( irho .ne. 0 ) + & write(6,*) ' energy dependent vcon = ', vcon, + 1 ' at energy', e,' Ryd' + +C +C CONSTRUCT RELATIVISTIC POTENTIAL ON LINEAR-LOG MESH +C + CALL VREL +C + xe=sqrt(ev) +c +c.....write out potential ans rs files for first neighbors to +c.....absorber for the first energy point +c + nunit=40 + nunit1=nunit+1 + open(unit=nunit,file='plot/plot_v(e).dat',status='unknown') + open(unit=nunit1,file='plot/plot_rs.dat',status='unknown') +c + if(ne.eq.1) then +c + do i=1,nbrs +c + j = ntnabs1(i) + +c write(6,*) j, nsymbl(j), distin(j) + write(nunit,100) 'atom ',nsymbl(j), 'dist =',distin(j), + & ' coord = ', xv(j), yv(j), zv(j) + write(nunit1,100) 'atom ',nsymbl(j), 'dist =',distin(j), + & ' coord ', xv(j), yv(j), zv(j) + do k=1,kmax(j) + write(nunit,*) r(k,j), dble(v(k,j)) + write(nunit1,*) r(k,j), rhotot(k,j) + enddo +c close(nunit) +c close(nunit1) +c nunit=nunit+2 +c nunit1=nunit1+2 + enddo +c + endif +c + close(nunit) + close(nunit1) +c +c calculate maximum l-value lmxne(n,ne) for each prototipical atom +c at the energy e=es(ne) +c + if(lmax_mode.eq.2) then + do n=1,nuatom + lmxne(n,ne) = nint(sqrt(e)*rs(n))+2 + lmxels(nks,n) = lmxne(n,ne) + if(lmxne(n,ne).lt.l0i+1) lmxne(n,ne)=l0i+2 + write(6,*) nks, n, e, rs(n), lmxne(n,ne) + enddo + endif +c + NBL1=NUATOM/4 + XNBL1=FLOAT(NBL1)+0.0001 + XNBL2=FLOAT(NUATOM)/4. + IF(XNBL1.LT.XNBL2) NBL1=NBL1+1 +c 112 FORMAT(4(7X,I2)) + IF(EIKAPPR.NE.'yes') THEN + IF(VERSION.NE.'1.1') THEN + WRITE(35,113) NE,dble(EV*13.605693) + ENDIF + if(nks.eq.1) WRITE(85,113) NE,dble(EV*13.605693) + if(nks.eq.2) WRITE(86,113) NE,dble(EV*13.605693) + if(nks.eq.3) WRITE(87,113) NE,dble(EV*13.605693) + if (lmax_mode.eq.2) then + DO JL=1,NBL1 + JLN=4*(JL-1)+1 + write(35,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + if(nks.eq.1) write(85,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + if(nks.eq.2) write(86,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + if(nks.eq.3) write(87,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(95,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(70,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(80,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + write(90,112) lmxne(jln,ne),lmxne(jln+1,ne), + & lmxne(jln+2,ne),lmxne(jln+3,ne) + ENDDO + else if (lmax_mode.eq.1) then + DO JL=1,NBL1 + JLN=4*(JL-1)+1 + write(35,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + if(nks.eq.1) write(85,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + if(nks.eq.2) write(86,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + if(nks.eq.2) write(87,112) lmax2(jln),lmax2(jln+1), + & (jln+2),lmax2(jln+3) + write(95,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(70,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(80,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + write(90,112) lmax2(jln),lmax2(jln+1), + & lmax2(jln+2),lmax2(jln+3) + ENDDO + else + DO JL=1,NBL1 + JLN=4*(JL-1)+1 + write(35,112) lmaxt,lmaxt,lmaxt,lmaxt + if(nks.eq.1) write(85,112) lmaxt,lmaxt,lmaxt,lmaxt + if(nks.eq.2) write(86,112) lmaxt,lmaxt,lmaxt,lmaxt + if(nks.eq.3) write(87,112) lmaxt,lmaxt,lmaxt,lmaxt + write(95,112) lmaxt,lmaxt,lmaxt,lmaxt + write(70,112) lmaxt,lmaxt,lmaxt,lmaxt + write(80,112) lmaxt,lmaxt,lmaxt,lmaxt + write(90,112) lmaxt,lmaxt,lmaxt,lmaxt + ENDDO + endif + ENDIF +c +c +c calculate atomic t-matrix with relativistic corrections +c + call smtxllm(ne,lmax_mode,relc,nks,px,px0,ppx,pax, + & ramfnr,ramfsr,ramfsop,ramfsoa,tdl) +c + if(eikappr.eq.'yes') then + neik = 0 + if(ne.eq.1) then + write(6,*)' ' + write(6,*)' calculating phases in the eikonal approximation' + endif + call eikonal(nuatom,xe,z,rs,db,neik) + endif +c +c and corresponding radial integrals of transition matrix elements: +c + if(nks.eq.3) then + write(55,823) ne ! energy point + call radialx_eels(neff) + call writeelswf + endif +c +c + doit = .false. +c + 10 continue !end loop for eels +c + write(6,*) ' ---> end of calculation of eels (e2e) rme', + 1 ' at energy point ',ne + write(6,*) ' ' +c + enddo !end energy do loop +c +c + endif !end of if clause beginning at line 5606 +c +C +C Version 1.1 formats +C + 721 FORMAT(138('-')) + 722 FORMAT(35x,'matrix elements of ',a4,' with corrections of type: ', + 1 a20) + 730 FORMAT(' electric dipole radial integrals +', + 1 ' electric quadrupole radial ', + 2 'integrals') + 740 FORMAT('------------------------------------------------------', + 1 '-+----------------------------------------------------', + 2 '------------------------------') + 750 FORMAT(' R(li --> li - 1) R(li --> li + 1) +', + 1 ' R(li --> li - 2) R(li --> li) ', + 2 ' R(li --> li + 2)') + 760 FORMAT(1X,e12.5,1X,e12.5,2X,e12.5,1X,e12.5,4X,e12.5,1X,e12.5, + 1 2X,e12.5,1X,e12.5,2X,e12.5,1X,e12.5,4x,a9) +C +C Common formats +C + 801 format(1x,f10.5,2x,2f10.5,2x,f10.5,2x,f10.5,2x,2f10.5) + 805 format(1x,f10.5,2x,2f10.5,2x,f10.5,2x,f10.5,2x,2e15.6,2x,2e15.6) + 806 format(1x,f10.5,2x,2f10.5,2x,f10.5,2x,2e15.6,2x,2e15.6) + 810 FORMAT(29X,F8.5,1X,F8.5,4X,F8.5,1X,F8.5) + 820 FORMAT(29X,f8.5,1X,f8.5,4X,f8.5,1X,f8.5,4X,f8.5,1X,f8.5) + 823 FORMAT(50x,'---> energy point number ',i5,' <---') +C +C Version 2.0 formats +C + 821 FORMAT(278('-')) + 822 FORMAT(63x,'radial integrals of ',a4, + 1 ' with corrections of type: ',a20,143(' '),'+') + 830 FORMAT(' electric dipole ', + 1 ' + electric quadrupole ', + 2 ' + magnetic dipole ', + 3 ' + ', + 4 'electric octupole ',45(' '),'+ Type') + 840 FORMAT('------------------------------------------------------', + 1 '-+----------------------------------------------------', + 2 '------------------------------+-----------------------', + 3 '-----+------------------------------------------------', + 4 '------------------------------------------------------', + 5 '-------+') + 850 FORMAT(' Rd(li --> li - 1) Rd(li --> li + 1) +', + 1 ' Rq(li --> li - 2) Rq(li --> li) ', + 2 ' Rq(li --> li + 2) + Rmd(li --> li) ', + 3 ' + Ro(li --> li - 3) Ro(li --> li - 1)', + 4 ' Ro(li --> li + 1) Ro(li --> li + 3)', + 5 ' +') + 860 FORMAT(1X,e12.5,1X,e12.5,2X,e12.5,1X,e12.5,2X,'+',1X,e12.5,1X, + 1 e12.5,2X,e12.5,1X,e12.5,2X,e12.5,1X,e12.5,2X,'+',1X,e12.5, + 2 1X,e12.5,2X,'+',1X,e12.5,1X,e12.5,2X,e12.5,1X,e12.5,2X, + 3 e12.5,1X,e12.5,2X,e12.5,1X,e12.5,2X,'+',2X,a9) +c +c ######### the auger matrix elements are written in the output file +c radaed.dat directly from the subroutine radial, since they m +c for each interaction momentum lk + + +c + return +c + end +c +c +c + subroutine output_cont(iq) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,d_,rd_,sd_ + parameter (at_=nat_-1,d_=ua_-1,rd_=440,sd_=ua_-1) +c +c modified output subroutine for complex potentials +c + common /dens/ irho,rhotot(rd_,sd_),rhoint(2), + $ vcoul(rd_,sd_),vcoulint(2) +c + common /fcnr/kxe, h(d_),vcons(2),r(rd_,d_),v(2,rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) + complex*16 vcons +c + common /flag/ inmsh,inv,inrho,insym,iovrho,iosym, + 1 imvhl,nedhlp +c + character*8 name0 ,nsymbl + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 ev,xe,vcon +c +c + character*4 label(2) + logical pott,rhoo + data label/'down',' up '/ +c + pott=(irho .ne. 1) + rhoo=(irho .ne. 0) +c + write (6,5) iovrho + 5 format(1x,' starting potentials and/or charge densities', + x ' written to file',i3) +ctn if(radion.ne.0.0. and . nout.eq.1) write(6,10) radion,qion + 15 format(7x,'constant potential=(',1pe14.6,' , ',1pe14.6,')') + 20 format(7x,'interstitial charge=',1pe14.6) +c +c + do 300 ispin=1,nspins + if(nspins.eq.2) write(6,25) label(ispin) + 25 format(///40x,'spin ',a4,' potential') + if( pott ) write (iovrho,15) vcons(ispin) + if( rhoo ) write (iovrho,20) rhoint(ispin) + do 200 n=1,nat + if(neq(n).eq.0) goto 35 + write(iovrho,30) n,neq(n) + 30 format(' mesh and potential for',i4,' same as for',i4) + goto 200 + 35 write(iovrho,40) n,h(n),(ichg(i,n),i=1,10),kplace(n),exfact(n) + 40 format(///i8,' h=',f10.4,' change points:',10i4,' kplace=' + 1 ,i4,' exchange=',f8.6) + kmaxn=kmax(n) + m=n+(ispin-1)*ndat + if( rhoo ) goto 55 + write(iovrho,45) + 45 format(72x/12x,4('r',11x,'real(v)',11x)) + write(iovrho,50) (i,(r(i+j-1,n),v(1,i+j-1,m),j=1,4),i=1,kmaxn,4) + 50 format(1x,i3,8e15.7) + goto 200 + 55 if( pott ) goto 65 + write(iovrho,60) + 60 format(72x/12x,4('r',13x,'rho',13x)) + write(iovrho,50) (i,(r(i+j-1,n),rhotot(i+j-1,m),j=1,4), + x i=1,kmaxn,4) + goto 200 + 65 write(iovrho,70) + 70 format(72x/27x,2('r',11x,'real(v)',10x,'rho',13x)) + write(iovrho,75) (i,(r(i+j-1,n),v(1,i+j-1,m),rhotot(i+j-1,m), + x j=1,2),i=1,kmaxn,2) + 75 format(16x,i3,6e15.7) + goto 200 +c 80 if( rhoo ) goto 90 +c write(iovrho,85) +c 85 format(72x/27x,2('r',11x,'real(v)',9x,'lcore',12x)) +c write(iovrho,75) (i,(r(i+j-1,n),v(1,i+j-1,m), +c x j=1,2),i=1,kmaxn,2) +c goto 200 +c 90 if( pott ) goto 100 +c write(iovrho,95) +c 95 format(72x/27x,2('r',13x,'rho',11x,'lcore',12x)) +c write(iovrho,75) (i,(r(i+j-1,n),rhotot(i+j-1,m), +c x j=1,2),i=1,kmaxn,2) +c goto 200 +c 100 write(iovrho,105) +c 105 format(72x/27x,2('r',11x,'real(v)',10x,'rho', +c x 10x)) +c write(iovrho,50) (i,(r(i+j-1,n),v(1,i+j-1,m), +c x rhotot(i+j-1,m),j=1,2),i=1,kmaxn,2) + 200 continue + 300 continue +c +c + return +c + end +c +c + subroutine radial(doit,imvhl) +c + implicit real*8 (a-h,o-z) +c include 'mscalc.inc' + include 'msxas3.inc' + + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c +c +c.....this subroutine calculates the radial matrix elements d(i) +c.....(i=1,2) for lfin=l0i-1 (i=1) and lfin=l0i+1 (i=2) both for +c.....the regular (dmx) and irregular solution (dmx1) +c + common /fcnr/kxe, h(d_),vcons(2,2),r(rd_,d_),v(2,rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) +c + common/mtxele/ nstart,nlast,dmx(2),dmx1(2),qmx(3),qmx1(3), + $ dxdir,dxexc,nfis,nfis1,nfis2 + real*8 nfis,nfis2,nfis1 + complex*16 dmx,dmx1,qmx,qmx1,dxdir,dxexc +c +c ######### I introduce a new common with the orbital momentum of +c ######### the two electrons which interacts and give rise to +c ######### to the auger decay; these two momentum are necessary +c ######### to do the loop over the interaction momentum when I perf +c the integrals +c + common/l2holes/l01i,l02i + integer l01i,l02i + + character*8 name0 ,nsymbl +c + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,ev,xe +c + common /pdq/ p(rd_,fl_),ps(n_),dps(n_),ramf(n_),pss(6),dpss(6) + complex*16 p,ps,dps,ramf,pss,dpss +c +c ########## common pdqi modified to include also the Auger two +c wavefunctions + common/pdqi/rpi(rd_),rpi1(rd_),rpi2(rd_) +c + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) + +c +c ######### common pottype modified to consider also the Auger calcu +c + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode + + + + + + common/auger/calctype,expmode,edge1,edge2 + + character*3 calctype, expmode + character*2 edge1,edge2 + integer nct,l2hmin,l2hmax + + data pai/3.1415927/ +c + common /lparam/lmax2(nat_),l0i +c +c +c + dimension rid(rd_),rid0(rd_),riq0(rd_),cri(rd_),cri1(rd_) + dimension rid2(rd_),cri2(rd_) + complex*16 rid,cri,cri1,dx,qx,dx1,dx2,dx3,dx4 + + + +c + logical*4 doit +c + integer nchannel,lkmaxdir1,lkmaxdir2,lkminexc2 + integer lkmindir1,lkmindir2,lkmaxexc1,lkmaxexc2,lkminexc1 + integer lamin,lamax,lkmin,lkmin1,lkmax,lkmax1,lkm,lkmn + + + +c +c iout = 5 + + + id=1 + n = nas +c +c kx = kmax(n) ! value used in older versions (contains the 3 points +C outside the muffin-tin radius that were used for interpolation) +c + kx = kmax(n) - 3 +c +c ################# Modified the subsequent "if" to take into account +c also the possibility to make an auger calcula +c + if(.not.doit) go to 21 + +c go to 20 + +c +c*********************************************************************** +c find normalization factor for initial state: nfis +c*********************************************************************** +c +c + +c if (calctype.eq.'xpd') then + if (calctype.eq.'xpd'.or.calctype.eq.'xas'.or. + & calctype.eq.'rex') then +c n=nas +c kx=kmax(n) + do 156 k=1,kx + 156 rid(k)=rpi(k)**2 + call defint(rid,r(1,n),kx,ichg(1,n),dx,id) + nfis=sqrt(dble(dx)) + if(iout .eq. 5) write(6,*) (i, r(i,n), rpi(i)/nfis, i=1,kx) + + + + +c WRITE(33,*) CIP + write(33,*) 'core wf on HS mesh for l =', l0i + do i=1,kx + write(33,*) r(i,n), rpi(i)/(nfis*r(i,n)) + enddo + nfis = nfis**2 + + + else +c +c ######## normalization of primary core hole wave function +c +c n=nas +c kx=kmax(n) + do 1560 k=1,kx + 1560 rid(k)=rpi(k)**2 + +c + call defint(rid,r(1,n),kx,ichg(1,n),dx,id) +c + nfis=sqrt(dble(dx)) + if(iout .eq. 5) write(6,*) (i, r(i,n), rpi(i)/nfis, i=1,kx) + + + + +c WRITE(33,*) CIP + write(33,*) 'core wf on HS mesh for l =', l0i + do i=1,kx + write(33,*) r(i,n), rpi(i)/(nfis*r(i,n)) + enddo + + + + +c +c ######### Auger normalization +c + rid(k)=rpi1(k)**2 + call defint(rid,r(1,n),kx,ichg(1,n),dx1,id) + rid(k)=rpi2(k)**2 + call defint(rid,r(1,n),kx,ichg(1,n),dx2,id) +c + nfis1=sqrt(dble(dx1)) + nfis2=sqrt(dble(dx2)) + + end if + + +c +c*********************************************************************** +c note that for the initial state rpi(k) = r*pi(k) +c*********************************************************************** +c +c ################ I introduce an if condition to take into account +c ################ also the possibility to make an Auger calculation +c +c 21 if(calctype.eq.'xpd') then + 21 if (calctype.eq.'xpd'.or.calctype.eq.'xas'.or. + & calctype.eq.'rex') then +C + do 30 k=1,kx + rid0(k) = r(k,n)**2*rpi(k) + 30 riq0(k) = r(k,n)*rid0(k) +c +c.....calculate regular and irregular dipole matrix elements +c + do 100 i=1,2 + dmx(i)=(0.,0.) + dmx1(i)=(0.,0.) + if((l0i.eq.0).and.(i.eq.1))goto 100 + np = l0i + (-1)**i + do 110 k=1,kx + 110 rid(k) = rid0(k)*p(k,np+1) + call cintegr(rid,r(1,n),kx,ichg(1,n),cri,id) + dmx(i) = (cri(kx)/ramf(nstart+np))**2*(l0i-1+i)/nfis + do 120 k=1,kx + 120 rid(k) = rid0(k)*p(k,np+1+npss) + call cintegr(rid,r(1,n),kx,ichg(1,n),cri1,id) + do 130 k=1,kx + 130 rid(k) = rid(k)*cri(k) + call defint(rid,r(1,n),kx,ichg(1,n),dx,id) + do 140 k=1,kx + 140 rid(k) = rid0(k)*p(k,np+1)*(cri1(kx)-cri1(k)) + call defint(rid,r(1,n),kx,ichg(1,n),dx1,id) + dmx1(i) = (dx+dx1)*(l0i-1+i)/ramf(nstart+np)/nfis + 100 continue +C +c write(6,*) 'radial matrix elements from shell li = ', l0i +c write(6,*) (dble(dmx(l)),aimag(dmx(l)),l=1,2) +c write(6,*) (dble(dmx1(l)),aimag(dmx1(l)),l=1,2) +c.....calculate regular and irregular quadrupole matrix elements +c + m = 0 + do 10 i=-2,2,2 + m = m + 1 + qmx(m)=(0.,0.) + qmx1(m)=(0.,0.) + lf = l0i + i + if(lf.le.0) go to 10 + np = l0i + i + do 11 k=1,kx + 11 rid(k) = riq0(k)*p(k,np+1) + call cintegr(rid,r(1,n),kx,ichg(1,n),cri,id) + qmx(m) = (cri(kx)/ramf(nstart+np))**2/nfis + do 12 k=1,kx + 12 rid(k) = riq0(k)*p(k,np+1+npss) + call cintegr(rid,r(1,n),kx,ichg(1,n),cri1,id) + do 13 k=1,kx + 13 rid(k) = rid(k)*cri(k) + call defint(rid,r(1,n),kx,ichg(1,n),dx,id) + do 14 k=1,kx + 14 rid(k) = riq0(k)*p(k,np+1)*(cri1(kx)-cri1(k)) + call defint(rid,r(1,n),kx,ichg(1,n),dx1,id) + qmx1(m) = (dx+dx1)/ramf(nstart+np)/nfis + 10 continue +C + else +c +c ######## start the auger part; first write +c ######## the orbital momentum of the electrons involved +c + write(55,8110)l0i,l01i,l02i +8110 format(5x,i2,5x,i2,5x,i2) + +c +c ######### Start calculation of auger matrix elements +C ######### rpi is the wavefunction of the primary core hole +C ######### rpi1 and rpi2 are the wavefunction for the two holes in t +c ######### nchannel is the number of channels allowed for +c ######### the Auger continuum electron; +c ######### l2h is the orbital angular momentum given by the coupling +c ######### two orbital momentum of the two final holes +c ######### lk is the 'angular momentum' of the interaction-transferr +c ######### here we count the u_er and lower bound for l of the cont +c + + + l2hmin=abs(l01i-l02i) + l2hmax=l01i+l02i + lamin=abs(l0i-l2hmin) + lamax=l0i+l2hmax +c +c here we count the number of the channels for the continuum auger e +c + nchannel=0 + do 101 np=lamin,lamax + nchannel=nchannel+1 +101 continue + + write(55,8120) lamin,nchannel + 8120 format(12x,i2,5x,i2) +c +c loop over the number of continuum channels +c + nct=0 + do 1 i=1,nchannel + np=lamin+(i-1) + + +c +c ###### establish the range for the interaction momentum for +c ###### the direct integral +c ###### from the selection rules we have: +c ###### abs(np-l01i)r +c + do 1040 k=1,kx +1040 rid2(k)=rpi(k)*rpi2(k)*(r(k,n)**lk) + call integr(rid2,r(1,n),kx,ichg(1,n),cri2,id) + + + do 1050 k=1,kx +1050 rid(k)=r(k,n)*rpi1(k)*p(k,np+1)*cri2(k)/(r(k,n)**(lk+1)) + call defint(rid,r(1,n),kx,ichg(1,n),dx1,id) + dxdir=(dx+dx1)*2* + * sqrt(xe/pai)/(nfis*nfis1*nfis2*ramf(nstart+np)) + + + end if +c +c ###### now the exchange integral +c + + lsum3=np+lk+l02i + lsum4=l0i+lk+l01i + + if((lk.lt.lkmin1).or.(lk.gt.lkmax1).or. + * (((lsum3/2)*2).ne.lsum3).or.(((lsum4/2)*2).ne.lsum4)) then + dxexc=(0.,0.) + + else + + do 1060 k=1,kx +1060 rid(k)=r(k,n)*rpi1(k)*p(k,np+1)*(r(k,n)**lk) + call cintegr (rid,r(1,n),kx,ichg(1,n),cri,id) + + + do 1070 k=1,kx + +1070 rid(k)=rpi(k)*rpi1(k)*cri(k)/(r(k,n)**(lk+1)) + + call defint(rid,r(1,n),kx,ichg(1,n),dx3,id) + +c +c ####### now the other region where r'>r +c + do 1788 k=1,kx +1788 rid2(k)=rpi(k)*rpi1(k)*(r(k,n)**lk) + call integr(rid2,r(1,n),kx,ichg(1,n),cri2,id) + + + + do 1799 k=1,kx +1799 rid(k)=r(k,n)*rpi2(k)*p(k,np+1)*cri2(k)/(r(k,n)**(lk+1)) + + call defint(rid,r(1,n),kx,ichg(1,n),dx4,id) + + + dxexc=(dx3+dx4)*2* + * sqrt(xe/pai)/(nfis1*nfis2*nfis*ramf(nstart+np)) + + end if +c +c ############## Write the auger matrix elements +c + +c write(55,8111) 'L =',np,'LB =',lk,dxdir,dxexc +c8111 format(2x,a3,i2,4x,a4,3x,i2,8x,f8.5,1x,f8.5,4x,f8.5,1x,f8.5) + write(55,8111) 'LB =',lk,dxdir,dxexc +8111 format(12x,a4,3x,i2,8x,f8.5,1x,f8.5,4x,f8.5,1x,f8.5) + + + + +2 continue + +1 continue + +c write(55,*) 'nct=',nct + + end if + + return + end +c + subroutine radialx_eels(neff) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' +c + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +C +c.....this subroutine calculates the radial matrix elements +c.....necessary for eels cross-section +c.....using a linear-log mesh +c +CST ==> Phagen to Python shared object modifications +CST I replaced the line below +CST common/mtxele/ nstart,nlast + common/mtxele/ nstart,nlast,dmx(2),dmx1(2),qmx(3),qmx1(3), + $ dxdir,dxexc,nfis,nfis1,nfis2 + real*8 nfis,nfis2,nfis1 + complex*16 dmx,dmx1,qmx,qmx1,dxdir,dxexc +CST Phagen to Python shared object modifications <== +c + common/mtxelex/ dmxx(2),dmxx1(2),dmxxa(2),dmxxa1(2), + & qmxx(3),qmxx1(3),qmxxa(3),qmxxa1(3), + & dxxdir,dxxexc,mdxx,mdxx1,mdxxa,mdxxa1, + & omxx(4),omxx1(4),omxxa(4),omxxa1(4), + & dqxx1(2,3),dmmx1(2),dqxxa1(2,3),dmmxa1(2) + complex*16 dmxx,dmxx1,dmxxa,dmxxa1,qmxx,qmxx1,qmxxa,qmxxa1, + & dxxdir,dxxexc,mdxx,mdxx1,mdxxa,mdxxa1, + & omxx,omxx1,omxxa,omxxa1,dqxx1,dmmx1,dqxxa1,dmmxa1 +c + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,ev,xe + character*8 nsymbl,name0 +c + common/bessel/sbf(ltot_),dsbf(ltot_),shf(ltot_),dshf(ltot_) + complex*16 sbf,dsbf,shf,dshf +C + COMMON /LLM/ ALPHA, BETA +C + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +C +C +C +c + COMMON /PDQX/PX(RDX_,fl_), PX0(RDX_,fl_), PPX(RDX_,fl_), + & PAX(RDX_,fl_), RAMFNR(N_), RAMFSR(N_), RAMFSOP(N_), + & RAMFSOA(N_) + complex*16 PX, PX0, PPX, PAX, RAMFNR, RAMFSR, RAMFSOP, RAMFSOA +c +C + COMMON/PDQIX/RPIX(RDX_), FNISX + complex*16 RPIX +C + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) +C +c ######### common pottype modified to consider also the Auger calcu +c + + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode +c + common/auger/calctype,expmode,edge1,edge2 +c + common/eels/einc,esct,scangl,qt,lambda,eelsme(npss,npss,npss), + & p1(rdx_,npss,nef_),p2(rdx_,npss,nef_), + & p3(rdx_,npss,nef_),ramfsr1(npss,nef_), + & ramfsr2(npss,nef_),ramfsr3(npss,nef_), + & lmxels(3,ua_),p3irreg(rdx_,7),p2irreg(rdx_,7) + complex*16 eelsme,p1,p2,p3,ramfsr1,ramfsr2,ramfsr3,ramfprd, + & ramfprx,p3irreg,p2irreg,trop1(rdx_) + complex*16 trop(rdx_) + real*8 lambda + complex*16 qtc, arg, ydf, scprod +c + common/msbhf/ il(rdx_,lexp_,d_), kl(rdx_,lexp_,d_), kappa + double precision kappa, il, kl +c + character*3 calctype, expmode, eikappr + character*2 edge1,edge2 +C + common /lparam/lmax2(nat_),l0i +c + DIMENSION RID(RDX_),CRI(RDX_),CRI1(RDX_) + DIMENSION RID1(RDX_),RID2(RDX_),RID3(RDX_),RID4(RDX_) + complex*16 RID,RID1,RID2,RID3,RID4 + complex*16 VC,VCX,VCD,VCDX,VCDR,VCDXR +C + CHARACTER*2 RELC +C +C +c*************************************************************************** +c note that here rpix(k) = r**3*pi(k). +c wf rpix(k) is already normalized +c (see subroutine corewf) +c*************************************************************************** +c + pi = 3.1415926 +c + id = 1 + na = nas +c +c.....calculate direct and exchange Coulomb integral on absorber and different +c.....spheres +c + nt0a=n0(na) + ntxa=nt0a+nterms(na)-1 + dxa = hx(na) + nstart = nt0a + nlast = ntxa +c write(6,*) 'in radialx_eels', nt0a, ntxa +c + write(6,*) ' ' + write(6,*)' writing eels (e2e) regular direct terms' + write(55,100) + write(55,821) +c + do 20 n1 = nt0a, ntxa + l=ln(n1) + if(l.gt.lmxels(3,na)) goto 20 + do k = 1, kmx(na) + rid1(k) = rpix(k)*p3(k,l+1,na)/(alpha*rx(k,na) + beta) + enddo +c + do 30 nat2 = 1, neff + nb = nat2 + if(neq(nat2).ne.0) nb = neq(nat2) + nt0b=n0(nb) + ntxb=nt0b+nterms(nb)-1 + dxb = hx(nb) + do 40 n2 = nt0b, ntxb + lp = ln(n2) + if(lp.gt.lmxels(1,nb)) goto 40 + do 50 n3 = nt0b, ntxb + ls = ln(n3) + if(ls.gt.lmxels(2,nb)) goto 50 + do k = 1, kmx(nb) + rid2(k) = p1(k,lp+1,nb)*p2(k,ls+1,nb)*rx(k,nb)**3 + & /(alpha*rx(k,nb) + beta) + enddo +c + ramfprd = ramfsr3(l+1,na)*ramfsr1(lp+1,nb)*ramfsr2(ls+1,nb) + lc_min=max(abs(l-l0i), abs(lp-ls)) + lc_max=min(l+l0i, lp+ls) +c + if(na.eq.nb) then + do lc = lc_min, lc_max, 2 + l1 = lc + 1 + if(l1.gt.lexp_) cycle + call coulss(rid1,rid2,il(1,l1,na), + & kl(1,l1,na),kmx(na),dxa,pi,vc) + write(55,10) na, l, lp, ls, lc, vc/ramfprd !, vc + enddo + endif +c + 50 continue +c + 40 continue +c + 30 continue + + 20 continue +c + write(55,821) + write(55,104) + write(55,821) +c + do 120 n1 = nt0a, ntxa + l=ln(n1) + if(l.gt.lmxels(3,na)) goto 120 + do k = 1, kmx(na) + rid1(k) = rpix(k)*p3(k,l+1,na)/(alpha*rx(k,na) + beta) + enddo +c + do 130 nat2 = 1, neff + nb = nat2 + if(neq(nat2).ne.0) nb = neq(nat2) + nt0b=n0(nb) + ntxb=nt0b+nterms(nb)-1 + dxb = hx(nb) + do 140 n2 = nt0b, ntxb + lp = ln(n2) + if(lp.gt.lmxels(1,nb)) goto 140 + do 150 n3 = nt0b, ntxb + ls = ln(n3) + if(ls.gt.lmxels(2,nb)) goto 150 + do k = 1, kmx(nb) + rid2(k) = p1(k,lp+1,nb)*p2(k,ls+1,nb)*rx(k,nb)**3 + & /(alpha*rx(k,nb) + beta) + enddo +c + ramfprd = ramfsr3(l+1,na)*ramfsr1(lp+1,nb)*ramfsr2(ls+1,nb) + lc_min=max(abs(l-l0i), abs(lp-ls)) + lc_max=min(l+l0i, lp+ls) +c + if(na.ne.nb) then + do lc=abs(l-l0i), l+l0i, 2 + l1 = lc + 1 + if(l1.gt.lexp_) cycle + do lcp=abs(lp-ls), lp+ls, 2 + l1p = lcp + 1 + if(l1p.gt.lexp_) cycle + call coulds(rid1,rid2,dxa,dxb,il(1,l1,na), + & il(1,l1p,nb),kmx(na),kmx(nb),pi,vcd) + vcdr = vcd/ramfprd +c if(abs(vcdr).lt.1.d-9) cycle + write(55,11) na, nb, l, lp, ls, lc, lcp, vcdr + enddo + enddo + endif +c + 150 continue +c + 140 continue +c + 130 continue + + 120 continue +c + write(6,*)' writing eels (e2e) regular exchange terms' + write(55,821) + write(55,102) + write(55,821) +c + do 21 n1 = nt0a, ntxa + l=ln(n1) + if(l.gt.lmxels(2,na)) goto 21 + do k = 1, kmx(na) + rid3(k) = rpix(k)*p2(k,l+1,na)/(alpha*rx(k,na) + beta) + enddo +c + do 31 nat2 = 1, neff + nb = nat2 + if(neq(nat2).ne.0) nb = neq(nat2) + nt0b=n0(nb) + ntxb=nt0b+nterms(nb)-1 + dxb = hx(nb) + do 41 n2 = nt0b, ntxb + lp = ln(n2) + if(lp.gt.lmxels(1,nb)) goto 41 + do 51 n3 = nt0b, ntxb + ls = ln(n3) + if(ls.gt.lmxels(3,nb)) goto 51 + do k = 1, kmx(nb) + rid4(k) = p1(k,lp+1,nb)*p3(k,ls+1,nb)*rx(k,nb)**3 + & /(alpha*rx(k,nb) + beta) + enddo +c + ramfprx = ramfsr3(ls+1,nb)*ramfsr1(lp+1,nb)*ramfsr2(l+1,na) + lc_min=max(abs(l-l0i), abs(lp-ls)) + lc_max=min(l+l0i, lp+ls) +c + if(na.eq.nb) then + do lc = lc_min, lc_max, 2 + l1 = lc + 1 + if(l1.gt.lexp_) cycle + call coulss(rid3,rid4,il(1,l1,na), + & kl(1,l1,na),kmx(na),dxa,pi,vcx) + write(55,10) na, l, lp, ls, lc, vcx/ramfprx + enddo + endif +c + 51 continue +c + 41 continue +c + 31 continue + + 21 continue +c + write(55,821) + write(55,106) + write(55,821) +C + do 121 n1 = nt0a, ntxa + l=ln(n1) + if(l.gt.lmxels(2,na)) goto 121 + do k = 1, kmx(na) + rid3(k) = rpix(k)*p2(k,l+1,na)/(alpha*rx(k,na) + beta) + enddo +c + do 131 nat2 = 1, neff + nb = nat2 + if(neq(nat2).ne.0) nb = neq(nat2) + nt0b=n0(nb) + ntxb=nt0b+nterms(nb)-1 + dxb = hx(nb) + do 141 n2 = nt0b, ntxb + lp = ln(n2) + if(lp.gt.lmxels(1,nb)) goto 141 + do 151 n3 = nt0b, ntxb + ls = ln(n3) + if(ls.gt.lmxels(3,nb)) goto 151 + do k = 1, kmx(nb) + rid4(k) = p1(k,lp+1,nb)*p3(k,ls+1,nb)*rx(k,nb)**3 + & /(alpha*rx(k,nb) + beta) + enddo +c + ramfprx = ramfsr3(ls+1,nb)*ramfsr1(lp+1,nb)*ramfsr2(l+1,na) + lc_min=max(abs(l-l0i), abs(lp-ls)) + lc_max=min(l+l0i, lp+ls) +c + if(na.ne.nb) then + do lc=abs(l-l0i), l+l0i, 2 + l1 = lc + 1 + if(l1.gt.lexp_) cycle + do lcp=abs(lp-ls), lp+ls, 2 + l1p = lcp + 1 + if(l1p.gt.lexp_) cycle + call coulds(rid3,rid4,dxa,dxb,il(1,l1,na), + & il(1,l1p,nb),kmx(na),kmx(nb),pi,vcdx) + vcdxr = vcdx/ramfprx +c if(abs(vcdxr).lt.1.d-9) cycle + write(55,11) na, nb, l, lp, ls, lc, lcp, vcdxr + enddo + enddo + endif +c + 151 continue +c + 141 continue +c + 131 continue + + 121 continue +c + 10 format(5i5,4e15.7) + 11 format(7i5,4e15.7) +c +c write(6,*) alpha, beta +c + if(calctype.eq.'els') then + write(6,*) ' ' + write(6,*)' writing eels irregular direct terms' + write(55,821) + write(55,101) + write(55,821) +c + do 22 n1 = nt0a, ntxa + l=ln(n1) + if(l.gt.lmxels(3,na)) goto 22 + do k = 1, kmx(na) + rid1(k) = rpix(k)*p3(k,l+1,na)/(alpha*rx(k,na) + beta) + if(l.le.5) then + rid(k) = rpix(k)*p3irreg(k,l+1)/(alpha*rx(k,na) + beta) + else + rid(k) = (0.0,0.0) + endif + enddo +c + do 32 nat2 = 1, neff + nb = nat2 + if(neq(nat2).ne.0) nb = neq(nat2) + nt0b=n0(nb) + ntxb=nt0b+nterms(nb)-1 + dxb = hx(nb) + do 42 n2 = nt0b, ntxb + lp = ln(n2) + if(lp.gt.lmxels(1,nb)) goto 42 + do 52 n3 = nt0b, ntxb + ls = ln(n3) + if(ls.gt.lmxels(2,nb)) goto 52 +c + do k = 1, kmx(nb) + rid2(k) = p1(k,lp+1,nb)*p2(k,ls+1,nb)*rx(k,nb)**3 + & /(alpha*rx(k,nb) + beta) + & /ramfsr1(lp+1,nb)/ramfsr2(ls+1,nb) + enddo +c +c ramfprd = ramfsr3(l+1,na)*ramfsr1(lp+1,nb)*ramfsr2(ls+1,nb) +c + lc_min=max(abs(l-l0i), abs(lp-ls)) + lc_max=min(l+l0i, lp+ls) +c + if(na.eq.nb) then + do lc = lc_min, lc_max, 2 + l1 = lc + 1 + if(l1.gt.lexp_) cycle + call sstrop(rid2,il(1,l1,na), + & kl(1,l1,na),kmx(na),dxa,pi,trop) + do k = 1, kmx(na) + rid4(k) = rid1(k)*trop(k) + rid3(k) = rid(k)*trop(k) + enddo + call irregint1(rid3,rid4,kmx(na),dxa,vc) +c if(abs(vc/ramfsr3(l+1,na)).lt.1.d-10) cycle + write(55,10) na, l, lp, ls, lc, vc/ramfsr3(l+1,na) + enddo + else + do lc=abs(l-l0i), l+l0i, 2 + l1 = lc + 1 + if(l1.gt.lexp_) cycle + do lcp=abs(lp-ls), lp+ls, 2 + l1p = lcp + 1 + if(l1p.gt.lexp_) cycle + call dstrop(rid2,dx2,il(1,l1,na), + & il(1,l1p,nb),kmx(na),kmx(nb),pi,trop1) + do k = 1, kmx(na) + rid4(k) = rid1(k)*trop1(k) + rid3(k) = rid(k)*trop1(k) + enddo + call irregint1(rid3,rid4,kmx(na),dxa,vcd) + vcdr = vcd/ramfsr3(l+1,na) +c if(abs(vcdr).lt.1.d-10) cycle + write(55,11) na, nb, l, lp, ls, lc, lcp, vcdr + enddo + enddo + endif +c + 52 continue +c + 42 continue +c + 32 continue + + 22 continue +c +c + write(6,*)' writing eels irregular exchange terms' + write(55,821) + write(55,103) + write(55,821) +c + do 23 n1 = nt0a, ntxa + l=ln(n1) + if(l.gt.lmxels(2,na)) goto 23 + do k = 1, kmx(na) + rid1(k) = rpix(k)*p2(k,l+1,na)/(alpha*rx(k,na) + beta) + if(l.le.5) then + rid(k) = rpix(k)*p2irreg(k,l+1)/(alpha*rx(k,na) + beta) + else + rid(k) = (0.0,0.0) + endif + enddo +c + do 33 nat2 = 1, neff + nb = nat2 + if(neq(nat2).ne.0) nb = neq(nat2) + nt0b=n0(nb) + ntxb=nt0b+nterms(nb)-1 + dxb = hx(nb) + do 43 n2 = nt0b, ntxb + lp = ln(n2) + if(lp.gt.lmxels(1,nb)) goto 43 + do 53 n3 = nt0b, ntxb + ls = ln(n3) + if(ls.gt.lmxels(3,nb)) goto 53 +c + do k = 1, kmx(nb) + rid2(k) = p1(k,lp+1,nb)*p3(k,ls+1,nb)*rx(k,nb)**3 + & /(alpha*rx(k,nb) + beta) + & /ramfsr1(lp+1,nb)/ramfsr3(ls+1,nb) + enddo +c +c ramfprd = ramfsr3(l+1,na)*ramfsr1(lp+1,nb)*ramfsr2(ls+1,nb) +c + lc_min=max(abs(l-l0i), abs(lp-ls)) + lc_max=min(l+l0i, lp+ls) +c + if(na.eq.nb) then + do lc = lc_min, lc_max, 2 + l1 = lc + 1 + if(l1.gt.lexp_) cycle + call sstrop(rid2,il(1,l1,na), + & kl(1,l1,na),kmx(na),dxa,pi,trop) + do k = 1, kmx(na) + rid4(k) = rid1(k)*trop(k) + rid3(k) = rid(k)*trop(k) + enddo + call irregint1(rid3,rid4,kmx(na),dxa,vc) +c if(abs(vc/ramfsr2(l+1,na)).lt.1.d-10) cycle + write(55,10) na, l, lp, ls, lc, vc/ramfsr2(l+1,na) + enddo + else + do lc=abs(l-l0i), l+l0i, 2 + l1 = lc + 1 + if(l1.gt.lexp_) cycle + do lcp=abs(lp-ls), lp+ls, 2 + l1p = lcp + 1 + if(l1p.gt.lexp_) cycle + call dstrop(rid2,dx2,il(1,l1,na), + & il(1,l1p,nb),kmx(na),kmx(nb),pi,trop1) + do k = 1, kmx(na) + rid4(k) = rid1(k)*trop1(k) + rid3(k) = rid(k)*trop1(k) + enddo + call irregint1(rid3,rid4,kmx(na),dxa,vcd) + vcdr = vcd/ramfsr2(l+1,na) +c if(abs(vcdr).lt.1.d-10) cycle + write(55,11) na, nb, l, lp, ls, lc, lcp, vcdr + enddo + enddo + endif +c + 53 continue +c + 43 continue +c + 33 continue + + 23 continue +c + endif !end of if clause to write irregular terms in case of calctype = els +c + write(55,821) +c + 100 format(10x,'single site regular direct terms:') + 101 format(10x,'irregular direct terms:') + 102 format(10x,'single site regular exchange terms:') + 103 format(10x,'irregular exchange terms') + 104 format(10x,'two-site regular direct terms:') + 106 format(10x,'two-site regular exchange terms:') + 821 FORMAT(138('-')) +c + return + end +c +C + SUBROUTINE COULDS(RHO1,RHO2,DX1,DX2,ILA,ILB, + & KMX1,KMX2,PI,VC) +C + INCLUDE 'msxas3.inc' +C + DIMENSION RHO1(KMX1), RHO2(KMX2), ILA(KMX1), ILB(KMX2) + DIMENSION A1(RDX_), A2(RDX_), RID(RDX_) + COMPLEX*16 RHO1, RHO2, A1, A2, RID, VC1, VC2, VC + REAL*8 ILA, ILB, DX1, DX2, PI +C + ID = 1 + DO K = 1, KMX1 + RID(K) = RHO1(K)*ILA(K) + ENDDO + CALL INTEGRCM(RID,DX1,KMX1,A1,ID) +C + VC1 = A1(KMX1) +C + ID = 1 + DO K = 1, KMX2 + RID(K) = RHO2(K)*ILB(K) + ENDDO + CALL INTEGRCM(RID,DX2,KMX2,A2,ID) +C + VC2 = A2(KMX2) +C + VC = VC1*VC2*8.0D0*PI + RETURN + END +C +C + SUBROUTINE DSTROP(RHO2,DX2,ILA,ILB,KMX1,KMX2,PI,RID) +C + INCLUDE 'msxas3.inc' +C + DIMENSION RHO2(KMX2), ILA(KMX1), ILB(KMX2) + DIMENSION A2(RDX_), RID(RDX_) + COMPLEX*16 RHO2, A2, RID + REAL*8 ILA, ILB, DX2, PI +C + ID = 1 + DO K = 1, KMX2 + RID(K) = RHO2(K)*ILB(K) + ENDDO + CALL INTEGRCM(RID,DX2,KMX2,A2,ID) +C + DO K = 1, KMX1 + RID(K) = ILA(K)*A2(KMX2)*8.0D0*PI + ENDDO +C + RETURN + END +C +C + SUBROUTINE IRREGINT1(RHO1,RHO2,KMX,DX,VC) +C +C Calculate the integral \int rho1(r_<) rho2(_>) dr1 dr2, +C where r_< (r_>) is the lesser (the greater) of r1 and r2. +C + IMPLICIT REAL*8(A-H,O-Z) +C + INCLUDE 'msxas3.inc' +C + DIMENSION RHO1(KMX), RHO2(KMX) + DIMENSION RID(RDX_), A(RDX_), P(RDX_) +C + COMPLEX*16 RHO1, RHO2, VC, VC1, VC2 + COMPLEX*16 RID, A, P +C + REAL*8 DX +C + ID = 1 + DO K = 1, KMX + RID(K) = RHO2(K) + ENDDO + CALL INTEGRCM(RID,DX,KMX,A,ID) + DO K = 1, KMX + RID(K) = RHO1(K) + ENDDO + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + DO K = 1, KMX + RID(K) = (P(KMX)-P(K))*RHO2(K) + ENDDO + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + VC1 = P(KMX) + DO K = 1, KMX + RID(K) = A(K)*RHO1(K) + ENDDO + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + VC2 = P(KMX) +C + VC = (VC1 + VC2) +C + RETURN +C + END +C +C + SUBROUTINE IRREGINT(RHO1,RHO2,RL,HL,KMX,DX,VC) +C +C This subroutine is never called. Check: 19 march 2019 +C + IMPLICIT REAL*8(A-H,O-Z) +C + INCLUDE 'msxas3.inc' +C + DIMENSION RHO1(KMX), RHO2(KMX), RL(KMX), HL(KMX) + DIMENSION RID(RDX_), A(RDX_), P(RDX_) +C + COMPLEX*16 RHO1, RHO2, VC, VC1, VC2 + COMPLEX*16 RID, A, P, RL, HL +C + REAL*8 DX +C + ID = 1 +C + DO K = 1, KMX + RID(K) = RL(K)*RHO2(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,A,ID) +C + DO K = 1, KMX + RID(K) = HL(K)*RHO2(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + DO K = 1, KMX + RID(K) = (P(KMX)-P(K))*RL(K)*RHO1(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + VC1 = P(KMX) +C + DO K = 1, KMX + RID(K) = A(K)*HL(K)*RHO1(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + VC2 = P(KMX) +C + VC = (VC1 + VC2) +C + RETURN +C + END +C +C + SUBROUTINE COULSS(RHO1,RHO2,IL,KL,KMX,DX,PI,VC) +C +C Calculate the integral \int rho1(r1) il(r_<) kl(r_>) rho2(2) dr1 dr2, +C where r_< (r_>) is the lesser (the greater) of r1 and r2. +C + IMPLICIT REAL*8(A-H,O-Z) +C + INCLUDE 'msxas3.inc' +C + COMPLEX*16 RHO1, RHO2, VC, VC1, VC2 + COMPLEX*16 RID, A, P +C + REAL*8 IL, KL, DX, PI +C + DIMENSION RHO1(KMX), RHO2(KMX), IL(KMX), KL(KMX) + DIMENSION RID(RDX_), A(RDX_), P(RDX_) + +C + ID = 1 + DO K = 1, KMX + RID(K) = IL(K)*RHO2(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,A,ID) +C + DO K = 1, KMX + RID(K) = KL(K)*RHO2(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + DO K = 1, KMX + RID(K) = (P(KMX)-P(K))*IL(K)*RHO1(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + VC1 = P(KMX) +C + DO K = 1, KMX + RID(K) = A(K)*KL(K)*RHO1(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + VC2 = P(KMX) +C + VC = (VC1 + VC2)*8.0D0*PI +C + RETURN + END +C +C + SUBROUTINE SSTROP(RHO2,IL,KL,KMX,DX,PI,TROP) +C +C Calculate the eels transition operator TROP(r1) as integral: +C \int rho2(r2) il(r_<) kl(r_>) dr2, +C where r_< (r_>) is the lesser (the greater) of r1 and r2. +C + IMPLICIT REAL*8(A-H,O-Z) +C + INCLUDE 'msxas3.inc' +C + COMPLEX*16 RHO2 + COMPLEX*16 RID, A, P, TROP +C + REAL*8 IL, KL, DX, PI +C + DIMENSION RHO2(KMX), IL(KMX), KL(KMX), TROP(KMX) + DIMENSION RID(RDX_), A(RDX_), P(RDX_) +C + ID = 1 + DO K = 1, KMX + RID(K) = IL(K)*RHO2(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,A,ID) +C + DO K = 1, KMX + RID(K) = KL(K)*RHO2(K) + ENDDO +C + CALL INTEGRCM(RID,DX,KMX,P,ID) +C + DO K = 1, KMX + RID(K) = (P(KMX)-P(K))*IL(K) + ENDDO +C + DO K = 1, KMX + TROP(K) = (RID(K) + A(K)*KL(K))*8.0D0*PI + ENDDO +C + RETURN + END +C +C +c + subroutine setup +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,ltot_ + parameter ( at_=nat_-1,ltot_=lmax_+1,n_=ltot_*ua_) +c + common /flag/ inmsh,inv,inrho,insym,iovrho,iosym, + 1 imvhl,nedhlp +c + common/funit/idat,iwr,iphas,iedl0,iwf +c + character*8 name0, name0i, nsymbl +c + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,xe,ev +c + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) +c + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode + + + + common/auger/calctype,expmode,edge1,edge2 + + + character*3 calctype, expmode + character*2 edge1,edge2 + + common/lparam/lmax2(nat_),l0i +c +c ########## I introduce a common/l2holes to take into account the +c ########## the orbital momentum of the two electrons which interac +c ########## and give rise to the Auger decay; the two orbital momen +c ########## are necessary in subroutine radial to do the loop over +c ########## the interaction momentum +c + common/l2holes/l01i,l02i + + integer l01i,l02i +c + character*8 core_basis_name(25) + integer core_basis_l(25) + character*8 exc_basis_name + integer exc_basis_l(lmax_+1),exc_basis_dim + integer exc_basis_ndg +c + data core_basis_name/'1s1/2','2s1/2','2p1/2','2p3/2', + 1'3s1/2','3p1/2','3p3/2','3d3/2','3d5/2','4s1/2','4p1/2', + 2 '4p3/2','4d3/2','4d5/2','4f5/2','4f7/2','5s1/2','5p1/2', + 3 '5p3/2','5d3/2','5d5/2','5f5/2','5f7/2','5g7/2','5g9/2'/ +c + data core_basis_l/0,0,1,1,0,1,1,2,2,0,1,1,2,2,3,3,0, + 1 1,1,2,2,3,3,4,4/ +c + data exc_basis_name/'no sym'/ + data lmaximum/lmax_/ + + data exc_basis_ndg/1/ +c + do 7001 i=1,lmaximum+1 + exc_basis_l(i)=i-1 +7001 continue + exc_basis_dim=0 + do 7002 i=1,ndat + exc_basis_dim=exc_basis_dim+lmax2(i)+1 +7002 continue +c + + do 59 n=1,nat + lmaxx(n)=0 + n0(n)=0 + n0l(n)=0 + lmaxn(n)=0 + nterms(n)=0 + 59 nls(n)=0 + nuatom=0 + write (6,327)iosym + 327 format(1x,' symmetry information generated internally'/, + x 1x,' symmetry information written to file',i3) +c + name0i=core_basis_name(i_absorber_hole) + write(iwr,120) name0i + write(iosym,120) name0i + + + 120 format(1x,//,' core initial state of type: ',a5) +c + ndim=exc_basis_dim + ndg=exc_basis_ndg + name0=exc_basis_name +c + write (iosym,103) ndim,ndg,name0 + 103 format(' # basis function including o.s. =',i4,' degeneracy=', + 1 i3,5x,a6) + i_l=1 + i_atom=1 + + + + + l0i = core_basis_l(i_absorber_hole) +c +c ############## Modified to consider also the Auger part +c + if (calctype.eq.'aed') then + l01i = core_basis_l(i_absorber_hole1) + l02i = core_basis_l(i_absorber_hole2) + end if +c +c + do 125 n=1,ndim + + ln(n)=exc_basis_l(i_l) + write (iosym,104) n, ln(n) +104 format ( 1x,'basis function no.',i5,' l=',i3) + natom(n)=i_atom + i_l=i_l+1 + if(i_l.gt.(lmax2(i_atom)+1))then + i_l=1 + i_atom=i_atom+1 + endif +c + write(iosym,106) natom(n) + 106 format (30x, ' atom no.=',i3) +c + na=natom(n) + lmaxn(na)=max0(lmaxn(na),ln(n)) + nuatom=max0(nuatom,na) + nterms(na)=nterms(na)+1 + nls(na)=nls(na)+1 + 125 continue +ctn write(6,1099) ndim + write(iosym,112) nuatom, name0 + 112 format(' number of inequivalent atoms =',i4, + * ' for representation:',a6) + if (nuatom.ne.ndat) then + write(6,122) nuatom, ndat + stop + endif + 122 format(//,' fatal error: nuatom not equal ndat',2i5,//) +c + n0(1)=1 + n0l(1)=1 + lmaxx(1)=max0(lmaxx(1),lmaxn(1)) + if(nuatom.eq.1) go to 127 + do 124 na=2,nuatom + n0(na)=n0(na-1)+nterms(na-1) + n0l(na)=n0l(na-1)+nls(na-1) + 124 lmaxx(na)=max0(lmaxn(na),lmaxx(na)) +c branch point + 127 continue + return +c + end +c +c + subroutine smtx(ne,lmax_mode) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + common/bessel/sbf(ltot_),dsbf(ltot_),shf(ltot_),dshf(ltot_) + complex*16 sbf,dsbf,shf,dshf + complex*16 sbfrs(ltot_),dsbfrs(ltot_) +c + common /fcnr/kxe, h(d_),vcons(2),r(rd_,d_),v(rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) + complex*16 vcons,v +c + common /flag/ inmsh,inv,inrho,insym,iovrho,iosym, + 1 imvhl,nedhlp +c + common /pdq/ p(rd_,fl_),ps(n_),dps(n_),ramf(n_),pss(6),dpss(6) + complex*16 p,ps,dps,ramf,pss,dpss +c + character*8 name0 ,nsymbl +c + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,ev,xe +c + common /seculr/ atm(n_) + complex*16 atm,stmat +c + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) +c + common/mtxele/ nstart,nlast,dmx(2),dmx1(2),qmx(3),qmx1(3), + $ dxdir,dxexc,nfis,nfis1,nfis2 + real*8 nfis,nfis2,nfis1 + complex*16 dmx,dmx1,qmx,qmx1,dxdir,dxexc +c + complex*16 csqrt,arg,ramf0 + complex*16 phexp, cphase +c + common/auger/calctype,expmode,edge1,edge2 + character*3 calctype, expmode + character*2 edge1,edge2 +c + common/funit/idat,iwr,iphas,iedl0,iwf +c + write(46,*)'--------------' +c + xe = sqrt(ev) + ns=(nns-1)*ndat +c write(6,*)'check in smtx: ev, xe =', ev, xe +c + do 5 j=1,ndim + 5 atm(j)=(0.0D0,0.0D0) +c +c calculate t-matrix elements: +c stmat: inverse t-m elements (atomic spheres) +c ramf: for normalization of ps(k) functions +c + do 60 na=1,nuatom + WRITE(95,77) NA,NZ(NA) + ns=ns+1 + mout=1 + nt0a=n0(na) + ntxa=nt0a+nterms(na)-1 + if (na.eq.nas) then + nstart=nt0a + nlast=ntxa + endif + l=-1 + nlat=-1 + arg=xe*rs(na) + ml=lmaxn(na)+1 + call csbf(arg,xe,ml,sbf,dsbf) + call cshf2(arg,xe,ml,shf,dshf) + npabs=0 + do 45 nn=nt0a,ntxa + l=ln(nn) + nlat=nlat+1 + npabs=npabs+1 + if(na.ne.nas.or.npabs.gt.npss-1) npabs=npss + if(lmax_mode.eq.2.and.l.gt.lmxne(na,ne)) goto 45 + call tmat(l,rs(na),kmax(na),z(na),h(na),r(1,na),v(1,ns), + 1 ichg(1,na),mout,kplace(na),p(1,npabs),stmat,ps(nn), + 2 dps(nn),ramf0) +c + atm(nn)=stmat + ramf(nn)=ramf0 + IF(LMAX_MODE.EQ.0) THEN + write(95,1001)xe/0.52917715,stmat + ELSE + write(95,1002)xe/0.52917715,stmat + ENDIF +c +C definition of stmat as exp(-i*delta)*sin(delta) +c + phexp = stmat/abs(stmat) + phase = (0.d0,1.d0)*log(phexp) + write(iphas,1000)e,xe,na,nlat,stmat,phase + write(46,1000)e,xe,na,nlat,stmat,-dimag(stmat) +c write(*,*)e,xe,na,nlat,stmat + 1000 format(2x,f10.5,2x,2f10.5,2x,i3,2x,i3,2x,2e16.6,2f12.5) + 1001 format(3x,f9.4,1x,f9.4,5x,e12.6,5x,e12.6) + 1002 format(3x,f9.4,1x,f9.4,5x,f12.9,5x,f12.9) + 45 continue + 60 continue +C + 77 FORMAT('-------------------- ATOM ',I3,' ---> Z = ',I2, + 1 ' -----------------') +c +c calculate singular solution inside muffin tin sphere for the absorbing +c atom, matching to sbf in interstitial region +c + nl=0 + lmsing=5 + mout=4 + kp=kplace(nas) + kpx=kmax(nas) + do 92 k=kp-3,kpx + if(r(k,nas)-rs(nas)) 92,93,93 + 92 continue +c +c define points (first) kp1 and kp2 outside the absorbing sphere +c and use them to start computation of singular solution (s_l) +c + 93 kp1=k+1 + kpl=kp1-3 + nst=n0(nas) + nlst=n0(nas)+nterms(nas)-1 + l=-1 + ml=lmaxn(nas)+1 + arg=xe*r(kp1,nas) + call cshf2(arg,xe,ml,sbf,dsbf) + arg=xe*r(kp1-1,nas) + call cshf2(arg,xe,ml,shf,dshf) + arg=xe*rs(nas) + call cshf2(arg,xe,ml,sbfrs,dsbfrs) + do 95 n=nst,nlst + l=ln(n) +c +c skip high and divergent l-values of +c singular solution h_l +c + if(l.gt.lmsing)go to 95 + nl=nl+1 + np=npss+nl + np1=nl +c + call tmat(l,rs(nas),kp1,z(nas),h(nas),r(1,nas),v(1,nas), + $ichg(1,nas),mout,kpl,p(1,np),stmat,pss(np1),dpss(np1),ramf0) +c +c shfp = shf(l+1)*xepi +c dshfp = dshf(l+1)*xepi +c print *, ps(np),dps(np),shfp,dshfp +c do 96 k=1,kpx +c if(k.lt.kp2)then +c p(k,np)=p(k,np)*(sbfrs(l+1)/pss(np1))*xepi !rescale h_l +c else ! to match h_l at rs +c p(k,np)=(0.,0.) +c end if +c 96 continue + 95 continue +c + return + end +c + subroutine tmat(l,rs,kmax,z,delh,r,v,ichg,mout,kplace,p,stmat, + 1 ps,dps,ramf) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer ltot_, rd_ + parameter (ltot_=lmax_+1, rd_=440) +c +c +c +c t-matrix calculation - integrates radial schrodinger equation +c using numerov procedure - does outward and inward integration +c for atomic spheres - gives inverse of t-matrix and log deriva- +c tive at sphere surface. +c +c modified for complex potentials +c +c calculates : +c +c mout=4 solution matching to (0.,1.)*hf2 at r=rs +c +c +c mout=1 atomic spheres t-matrix elements +c returns: +c stmat=[sbfc,ps]/[shfc,ps] (@rs atomic sphere +c ramf=[sbfc,ps]*xe*rs**2 (@rc atomic sphere +c +c +c + common/bessel/sbfc(ltot_),dsbfc(ltot_),shfc(ltot_), + 1 dshfc(ltot_) + complex*16 sbfc,shfc,dsbfc,dshfc +c + common/param/eftr,gamma,vcon,xe,ev,e,iout + complex*16 vcon,xe,ev +c +c + dimension v(kmax),p(kmax),r(kmax),ichg(10) + complex*16 v,p,ps,dps,ramf + complex*16 stmat,x,ramff + complex*16 pk,pk1,pkm,dkm,dk1,dk,gk,gk1,gkm + complex*16 pn(rd_) + data pi/3.141592653589793d0/ +c +c +c + kstop=1 + a=l*(l+1) + if(mout.eq.4) go to 60 +c +c outward integration for atomic spheres +c + ki=1 + if(l.ge.5) ki=ichg(1) + call startp(z,l,e,r,v,kmax,ki,pn) + h=r(ki+1)-r(ki) + hsq=h**2 + pkm=pn(ki) + pk1=pn(ki+1) + dkm=-dcmplx((e-v(ki)-a/r(ki)**2)*hsq)*pn(ki)/12.d0 + dk1=-dcmplx((e-v(ki+1)-a/r(ki+1)**2)*hsq)*pn(ki+1)/12.d0 + kis=ki+2 + n=1 + if(ki.eq.ichg(1)) n=2 + do 34 k=kis,kmax + gk=dcmplx((e-v(k)-a/r(k)**2)*hsq)/12.d0 + pk=dcmplx((2.d0*(pk1+5.d0*dk1)-(pkm-dkm))/(1.d0+gk)) + pn(k)=pk + if(k.lt.ichg(n)) go to 30 + n=n+1 + hsq=4.*hsq + dkm=4.d0*dkm + dk1=-4.d0*gk*pk + pk1=pk + go to 34 + 30 pkm=pk1 + dkm=dk1 + dk1=-gk*pk + pk1=pk + 34 continue +c + go to 78 +c +c inward integration to find solution matching to (0.,1.)*hf2 at r=rs +c + 60 n=11 + 61 n=n-1 + if(n.eq.0) go to 66 + kn=ichg(n) + if(kn.ge.kmax) go to 61 +c + 66 kn=kmax + pkm=sbfc(l+1)*dcmplx(xe/pi*r(kn)) + pk1=shfc(l+1)*dcmplx(xe/pi*r(kn-1)) + hsq=delh**2*4**n + pn(kn)=pkm + pn(kn-1)=pk1 + dkm=-dcmplx((e-a/r(kn)**2-vcon))*pkm*hsq/12.d0 + dk1=-dcmplx((e-a/r(kn-1)**2-vcon))*pk1*hsq/12.d0 + k=kn+1 + if(k.gt.kmax) go to 79 + do 76 i=k,kmax + 76 pn(i)=(0.0d0,0.0d0) + 79 k=kn-1 + 73 k=k-1 + 74 gk=dcmplx((e-v(k)-a/r(k)**2))*hsq/12.d0 + pk=dcmplx((2.d0*(pk1+5.d0*dk1)-pkm+dkm)/(1.d0+gk)) + pn(k)=pk + if(k.eq.kstop) go to 78 + if(n.eq.0) go to 69 + if(k.gt.ichg(n)) go to 69 + if(k.le.2) go to 75 + n=n-1 + dk=-pk*gk + gk1=dcmplx((e-v(k-2)-a/r(k-2)**2))*hsq/12.d0 + pk1=dcmplx((2.d0*(pk+5.d0*dk)-pk1+dk1)/(1.d0+gk1)) + dk1=-pk1*gk1/4.d0 + hsq=hsq/4. + gkm=dcmplx((e-v(k-1)-a/r(k-1)**2))*hsq/12.d0 + dk=dk/4.d0 + pkm=0.5d0*((pk-dk)+(pk1-dk1))/(1.d0-5.d0*gkm) + dkm=-pkm*gkm + k=k-3 +c +c keller modification subroutine tmat +c + pn(k+2)=pkm + if(k+1.lt.kstop) go to 78 + pn(k+1) = pk1 + if(k+1.eq.kstop) go to 78 + go to 74 + 69 pkm=pk1 + dkm=dk1 + dk1=-pk*gk + pk1=pk + go to 73 + 75 write(6,103) + stop + 103 format(//,18h error stop - tmat,//) +c +c + 78 continue + do 77 k=1,kmax + 77 p(k)=dcmplx(pn(k)/r(k)) + call interp(r(kplace-3),p(kplace-3),7,rs,ps,dps,.true.) + if(mout.eq.4) return + x=dcmplx(dps/ps) + ramff=sbfc(l+1)*x-dsbfc(l+1) + + stmat=ramff/(shfc(l+1)*x-dshfc(l+1)) + ramf=dcmplx(ramff)*ps*rs*rs*xe + return +c + end +c +c + subroutine eikonal(nuatom,xe,z,rs,db,neik) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' +c + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + dimension z(at_), rs(at_) +c + common /fcnr/kxe, h(d_),vcons(2),r(rd_,d_),v(rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) + complex*16 vcons,v +c + complex*16 xe +c +c open(unit=45, file='tl/tbmat.dat',status='unknown') +c + write(45,*) 'electron wave vector kappa =', dble(xe) + write(35,333) dble(xe)/0.52917715,aimag(xe)/0.52917715 + write(6,*) 'electron wave vector kappa =', dble(xe) +C + 333 FORMAT('---> ELECTRON WAVE VECTOR K = (', + 1 f9.4,1x,',',f9.4,')') + +C + DO NA=1,NUATOM,4 + nb0 = nint(rs(na)/db)-1 + nb1 = nint(rs(na+1)/db)-1 + nb2 = nint(rs(na+2)/db)-1 + nb3 = nint(rs(na+3)/db)-1 + write(35,112) nb0,nb1,nb2,nb3 + ENDDO +c + do na=1,nuatom + write(45,*)'atom number ', na,'(z =', z(na),')' + write(35,77) na,int(z(na)) +c write(6,*)' atom number ', na,'(z =', z(na),')' + z0 = z(na) + call tbmat(db,rs(na),kplace(na),z0,r(1,na),v(1,na),dble(xe),neik) + enddo +c + 77 FORMAT('-------------------- ATOM ',I3,' ---> Z = ',I2, + 1 ' -----------------') + 112 FORMAT(4(7X,I4)) +c close(45) +c +c write(6,*) ' normal exit in subroutine eikonal ' +c stop +c + return + end +c +c + subroutine tbmat(db,rs,kmax,z0,r,v,xer,neik) +c + implicit real*8 (a-h,o-z) +c + integer rd_ + parameter (rd_=440, nt_=1500) +c + dimension v(kmax),r(kmax), z(rd_) + complex*16 v, z +c + dimension x(nt_), rx(nt_), rid(nt_), rid1(nt_) +c + complex*16 cu, tb, zb, z1, zx, dzx, d2zx, rid, rid1, dbf, dbs +c + data pi/3.1415926/ +c + + do i = 1, kmax + z(i) = r(i)*v(i) +c write(45,*) r(i), z(i) + enddo +c + id = 1 !for subroutine defint + idr = 0 !for subroutine defint + cu = (0.d0,1.d0) +c write(6,*) + twz = -2.d0*z0 +c write(6,*) ' twz =', twz +c +c db = 0.01 +c b0 = -5.3 +c nb = (-b0 + log(rs))/db +c do ib = 1, nb +c b = exp((ib-1)*db + b0) + nb = nint(rs/db) +c write(35,*) '2 : ',rs,db +c write(6,*) 'nb =', nb + do ib = 1, nb - 1 + b = (ib-1)*db + db +c + dx = 0.005d0 + nx = nint(rs/dx) + rmx = nx*dx + t = rmx/b + rt = log(t + sqrt(t**2-1.0)) +c + nt = nint(rt/dx) +c write(6,*) 'nt =', nt,' for ib =', ib + if(nt.gt.nt_) then + write(6,*) ' ' + write(6,*) ' ' + write(6,*) ' stop in subroutine tbmat ' + write(6,*) ' increase dimension nt_; ', + & ' it should be greater than nt =', nt + write(6,*) ' ' + write(6,*) ' ' + call exit + endif + if(nt.le.4) cycle + x(1) = dx + rx(1) = b*(exp(dx) + exp(-dx))/2.0 +c write(2,*) x(1), rx(1) + do i = 2, nt + x(i) = x(i-1) + dx + rx(i) = b*(exp(x(i)) + exp(-x(i)))/2.0 +c write(2,*) x(i), rx(i) + enddo +c + do i = 1, nt + jlo = 1 + call nearest(r, kmax, rx(i), ip1, ip2, ip3, jlo) +c + call cinterp_quad( r(ip1), z(ip1), r(ip2), z(ip2), + & r(ip3),z(ip3),rx(i),zx,dzx,d2zx) + rid(i) = zx - twz + rid1(i) = zx + enddo +c + call defint0(rid,dx,nt,zb,id) + call defint0(rid1,dx,nt,z1,idr) +c + zbc = twz*rt + dbf = zb + zbc +c write(6,*) ' coulomb eikonal phase zbc =', zbc +c write(6,*) ' eikonal phase zb =', zb +c write(6,*) ' total eikonal phase dbf =', dbf +c +c write(6,*) ' integrated zx =', z1 +c + dbs = -dbf/xer/2.0 + tb = cu/pi*(exp(2.d0*cu*dbs) - 1.d0) +c +c write(6,*) ' eikonal t(b) =', tb,' at b =', b +c + write(45,'(3e15.7)') b, tb + if(neik.eq.1) write(35,'(3e15.7)') b, tb +c + enddo +c +c + return + end +c +c +c + double precision function vxc_gs(rs) +c + implicit none + real*8 rs, mup, pi, alpha, beta, c1, c2 + data pi/3.1415926535898d0/, alpha/0.521065158d0/ + data c1/0.0545d0/, c2/11.4d0/ +c + mup = -2.0/(pi*alpha*rs) + beta = 1.0 + c1*rs*log(1.0 + c2/rs) + vxc_gs = mup*beta + return + end +c +c + subroutine vxc ( doit ) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,d_,rd_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,rd_=440,sd_=ua_-1) +c +c calculation of ex-correlation h-l potential +c +c +c + common /dens/ irho,rs(rd_,sd_),rsint(2), + $ vcoul(rd_,sd_),vcoulint(2) + + common /fcnr/kxe, h(d_),vcons(2,2),r(rd_,d_),v(2,rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) +c + common /flag/ inmsh,inv,inrho,insym,iovrho,iosym, + 1 imvhl,nedhlp +c + common /hedin/ wp2,xk,e,eta2,pi,ot,kdens +c +c x_k_0 not divided by k_f +c + common/corr/r_s,blt,x_k_0 +c + character*8 name0 ,nsymbl + common/param/eftr,gamma,vcon(2),xe,ev,ekn,iout,nat,ndat, + 1 nspins,nas,rmuftin(at_),xv(at_),yv(at_),zv(at_),exfact(at_), + 3 z(at_),lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + + complex*16 xe,ev + external f1,f2,f3 + real*8 f1, f2, f3 + + real*8 r_s,blt,x_k_0,im_vxc,re_vxc + + + logical doit, iskip + + nout = 0 + anns=float(nspins) + eps=1.e-3 + eta=1.e-3 + eta2=eta*eta + ot=1./3. + ts2=27.*27. + t2=32. + sqr3=sqrt(3.) + pi = 3.14159265358979323846264338D0 + a=(4./(9.*pi))**ot + eken=ekn-eftr + +c +c do na = 1, ndat +c print *, ' atom number =', na +c do k = 1 , kmax(na) +c print *, k, r(k,na), rs(k,na) +c enddo +c enddo +c +c calculate rs from charge density first time through subroutine: +c remember that rhotot read in input is actually 4*pi*rho*r**2 +c +c print *, nspins, ndat, kmax(1), 'check point' + if( .not. doit ) goto 100 + do 50 isp=1,nspins + do 40 nb=1,ndat + ns=nb+(isp-1)*ndat + do 30 k=1,kmax(nb) + rs(k,ns)=((3.*(r(k,nb)**2))/(rs(k,ns)*anns))**ot +c if(ns.eq.1) +c & print *, 'r, rs(k,1) =', r(k,1), rs(k,1) + 30 continue + 40 continue + rsint(isp)=(3./(pi*4.*rsint(isp)*anns))**ot + 50 continue +c +c +c calculate self-energy +c + 100 do 300 isp=1,nspins + iskip=.false. + do 280 nb=1,ndat+1 + ns=nb+(isp-1)*ndat + if(.not.iskip)then +c +c compute vxc for atomic and outer spheres +c + km=kmax(nb) + else +c +c compute vxc for interstitial region +c + km=1 + endif + do 260 k=1,km + if(.not.iskip)then + rsp=rs(k,ns) + else + rsp=rsint(isp) + endif + ef=1./(a*rsp)**2 + xk=sqrt(1.0+eken/ef) + if(eken.lt.0.0) xk=1.0 + wp2=4.*a*rsp/(3.*pi) + wp=sqrt(wp2) + xk2=xk*xk + e=.5*xk2 + xkp=xk+1. + xkm=xk-1. + xkpi=1./xkp + if(nedhlp.eq.2)then +c +c define variables used by rehr's subroutine rhl +c + x_k_0=(xk/(a*rsp)) + r_s=(rsp) + call rhl(re_vxc,im_vxc,pi) +c +c conversion to ryd +c + re_vxc = 2.d0*re_vxc + im_vxc = 2.d0*im_vxc +c + if (iskip) goto 1200 + v(1,k,ns)=vcoul(k,ns) + re_vxc + if(imvhl.ne.0)v(2,k,ns)=-im_vxc + gamma + goto 1210 +1200 vcons(1,isp)=vcoulint(isp) + re_vxc + if(imvhl.ne.0)vcons(2,isp)=-im_vxc + gamma +1210 continue + if(imvhl.ne.0)goto 260 + goto 210 + end if +c + flg=log((xkp+eta2)/(xkm+eta2)) + edxc=(1.-xk2)/xk*.5*flg + vedx=1.5*wp2*(1.+edxc) + vsex = 0.0 + vch = 0.0 + if(nedhlp.ne.0) go to 199 + if(nb.eq.1.and.nout.eq.1) go to 199 + vsex=.75*wp2**2/xk*gauss(f2,xkm,xkp,eps) + vch1=gauss(f3,0.d0,xkp,eps) + vch2=gauss(f1,0.d0,xkpi,eps) + vch=.75*wp2**2/xk*(vch1+vch2) + 199 continue + if (iskip) goto 200 + v(1,k,ns)=vcoul(k,ns) - ef*(vedx+vsex+vch) + goto 210 + 200 vcons(1,isp)=vcoulint(isp) - ef*(vedx+vsex+vch) + 210 continue +c +c calculate vim, imaginary part of self energy: +c + if(imvhl.eq.0) goto 260 + rfct = 1.0 ! renormalizes the imaginary part +c if((icplxv.eq.1).and.(.not.iskip)) go to 260 + if(wp2.ge.t2/ts2) go to 215 + c1=ts2*wp2/16. + phi=acos(1.-c1) + phit=phi*ot + xkl=1.+2./9.*(-1.+cos(phit)+sqr3*sin(phit)) + goto 216 + 215 q=(16.-ts2*wp2)/54. + del=(ts2*wp2-t2)*wp2/4. + srdel=sqrt(del) + v2=-q-srdel + v2m=abs(-q-srdel) + xkl=7./9.+ot*((-q+srdel)**ot+sign(1.d0,v2)*v2m**ot) + 216 xkl2m=xkl**2-1. + xkmm=1.+sqrt(-2./3.+sqrt(4./9.-4.*wp2+xkl2m**2)) + if(abs(xkl-xkmm).gt.1.e-4) + x write(iovrho,221) xkl,xkmm,nb,k,rsp + 221 format(' xkl(=',e14.6,') not equal to xkmm(=',e14.6,') for ', + x ' nb,k,rs=',2i10,e20.6) + xmm=sqrt(1.+2.*wp) + if(xkl.lt.xmm) write(iovrho,222) xkl,xmm,nb,k,rsp + 222 format(' xkl(=',e14.6,') less than xmm(=',e14.6,') for ', + x 'nb,k,rs=',2i10,e20.6) + if(.not.iskip) v(2,k,ns)=gamma + if(iskip) vcons(2,isp)=gamma + if(xk.le.xkl) go to 260 + del1=27.*xk2*wp2-4.*(xk2-ot)**3 + if(del1.ge.0.) write(iovrho,223) nb,k,rsp + 223 format(' discriminant del1 positive for nb,k,rs=',2i10,e20.6) + xm2=-2*ot+sqrt(4./9.-4.*wp2+(xk2-1.)**2) + c1=27.*xk2*wp2/(2.*(xk2-ot)**3) + if(c1.gt.2.) write(iovrho,224) c1,nb,k,rsp + 224 format(' c1(=',e14.6,') gt 2. for nb,k,rs=',2i10,e20.6) + phi=acos(1.-c1) + phit=ot*phi + xk1=(1.-cos(phit)+sqr3*sin(phit))*(xk2-ot)/(3.*xk) + xk12=xk1*xk1 + an=xm2*(xk12*(1.-3.*wp)+6.*wp*(wp+xk*xk1)) + ad=xk12*(xm2+3.*wp*(xk2-1.+2.*wp)) + if (iskip) goto 258 + v(2,k,ns)= rfct*ef*(3.*pi/8.*wp**3/xk*log(an/ad))+gamma + goto 260 + 258 vcons(2,isp)= rfct*ef*(3.*pi/8.*wp**3/xk*log(an/ad))+gamma + 260 continue + if(nb.eq.ndat)iskip=.true. + 280 continue + 300 continue +c +c transfer constant for interstitial potential +c + vcon(1)=vcons(1,1) + vcon(2)=vcons(2,1) +c + return + end +c +C + DOUBLE PRECISION FUNCTION F1(X) +C + IMPLICIT REAL*8 (A-H,O-Z) +C + COMMON /HEDIN/ WP2,XK,E,ETA2,PI,OT +C + YI=1.0D0/X + YI2=YI*YI + WQ=DSQRT(WP2+OT*YI2+(0.50D0*YI2)**2) + T1=0.50D0*(XK+YI)**2-E+WQ + T2=0.50D0*(XK-YI)**2-E+WQ + R=(T1*T1+ETA2)/(T2*T2+ETA2) + F1=0.50D0*DLOG(R)*YI/WQ +C + RETURN +C + END +C +C + DOUBLE PRECISION FUNCTION F2(X) +C + IMPLICIT REAL*8 (A-H,O-Z) +C + COMMON /HEDIN/ WP2,XK,E,ETA2,PI,OT +C + X2=X*X + WQ=DSQRT(WP2+OT*X2+(0.50D0*X2)**2) + T1=0.50D0-E-WQ + T2=0.50D0*(XK-X)**2-E-WQ + T3=T2+2.0D0*WQ + T4=0.50D0-E+WQ + R=(T1*T1+ETA2)*(T3*T3+ETA2)/((T2*T2+ETA2)*(T4*T4+ETA2)) + F2=0.50D0*DLOG(R)/(WQ*X) +C + RETURN +C + END +C +C + DOUBLE PRECISION FUNCTION F3(X) +C + IMPLICIT REAL*8 (A-H,O-Z) +C + COMMON /HEDIN/ WP2,XK,E,ETA2,PI,OT +C + X2=X*X + WQ=DSQRT(WP2+OT*X2+(0.50D0*X2)**2) + T1=0.50D0*(XK+X)**2-E+WQ + T2=0.50D0*(XK-X)**2-E+WQ + R=(T1*T1+ETA2)/(T2*T2+ETA2) + F3=0.50D0*DLOG(R)/(WQ*X) +C + RETURN +C + END +C +C + DOUBLE PRECISION FUNCTION GAUSS(F,A,B,EPS) +C +C Original code by K. S. Kölbig: CERN MATHLIB library +C +C This is based on a 16-point formula +C +C F : Name of a user-supplied FUNCTION, declared EXTERNAL in the calling program. +C This subprogram must set F(X) = f(X) +C A : Start_point of the interval +C B : End_point of the interval +C EPS : Accuracy parameter +C + IMPLICIT REAL*8 (A-H,O-Z) +C + LOGICAL MFLAG,RFLAG +C + EXTERNAL F +C + DIMENSION W(12),X(12) +C +C ****************************************************************** +C +C Adaptive gaussian quadrature. +C +C GAUSS is set equal to the approximate value of the integral of +c the function F over the interval (A,B), with accuracy parameter +C EPS. +C +C ****************************************************************** +C + DATA W /0.10122853629037626D0, 0.22238103445337447D0, + 1 0.31370664587788729D0, 0.36268378337836198D0, + 2 0.02715245941175409D0, 0.06225352393864789D0, + 3 0.09515851168249278D0, 0.12462897125553387D0, + 4 0.14959598881657673D0, 0.16915651939500254D0, + 5 0.18260341504492359D0, 0.18945061045506850D0/ + + DATA X /0.96028985649753623D0, 0.79666647741362674D0, + 1 0.52553240991632899D0, 0.18343464249564980D0, + 2 0.98940093499164993D0, 0.94457502307323258D0, + 3 0.86563120238783174D0, 0.75540440835500303D0, + 4 0.61787624440264375D0, 0.45801677765722739D0, + 5 0.28160355077925891D0, 0.09501250983763744D0/ +C +C ****************************************************************** +C +C Start +C + GAUSS=0.0D0 + IF(B.EQ.A) RETURN + CONST=0.005D0/(B-A) + BB=A +C +C Computational loop +C + 1 AA=BB + BB=B + 2 C1=0.50D0*(BB+AA) + C2=0.50D0*(BB-AA) + S8=0.0D0 +C + DO I=1,4 + U=C2*X(I) + S8=S8+W(I)*(F(C1+U)+F(C1-U)) + ENDDO +C + S8=C2*S8 + S16=0.0D0 +C + DO I=5,12 + U=C2*X(I) + S16=S16+W(I)*(F(C1+U)+F(C1-U)) + ENDDO +C + S16=C2*S16 + IF(DABS(S16-S8).LE.EPS*(1.0D0+DABS(S16))) GOTO 5 + BB=C1 + IF(1.0D0+DABS(CONST*C2).NE.1.0D0) GOTO 2 + GAUSS=0.0D0 + CALL KERMTR('D103.1',LGFILE,MFLAG,RFLAG) + IF(MFLAG) THEN + IF(LGFILE.EQ.0) THEN + WRITE(*,6) + ELSE + WRITE(LGFILE,6) + ENDIF + ENDIF + IF(.NOT. RFLAG) CALL ABEND + RETURN + 5 GAUSS=GAUSS+S16 + IF(BB.NE.B) GOTO 1 +C +C Format: +C + 6 FORMAT( 4X, 'FUNCTION GAUSS ... TOO HIGH ACCURACY REQUIRED') +C + RETURN +C + END +C + SUBROUTINE KERSET(ERCODE,LGFILE,LIMITM,LIMITR) + PARAMETER(KOUNTE = 28) + CHARACTER*6 ERCODE, CODE(KOUNTE) + LOGICAL MFLAG, RFLAG + INTEGER KNTM(KOUNTE), KNTR(KOUNTE) + DATA LOGF / 0 / + DATA CODE(1), KNTM(1), KNTR(1) / 'C204.1', 100, 100 / + DATA CODE(2), KNTM(2), KNTR(2) / 'C204.2', 100, 100 / + DATA CODE(3), KNTM(3), KNTR(3) / 'C204.3', 100, 100 / + DATA CODE(4), KNTM(4), KNTR(4) / 'C205.1', 100, 100 / + DATA CODE(5), KNTM(5), KNTR(5) / 'C205.2', 100, 100 / + DATA CODE(6), KNTM(6), KNTR(6) / 'C205.3', 100, 100 / + DATA CODE(7), KNTM(7), KNTR(7) / 'C305.1', 100, 100 / + DATA CODE(8), KNTM(8), KNTR(8) / 'C308.1', 100, 100 / + DATA CODE(9), KNTM(9), KNTR(9) / 'C312.1', 100, 100 / + DATA CODE(10),KNTM(10),KNTR(10) / 'C313.1', 100, 100 / + DATA CODE(11),KNTM(11),KNTR(11) / 'C336.1', 100, 100 / + DATA CODE(12),KNTM(12),KNTR(12) / 'C337.1', 100, 100 / + DATA CODE(13),KNTM(13),KNTR(13) / 'C341.1', 100, 100 / + DATA CODE(14),KNTM(14),KNTR(14) / 'D103.1', 100, 100 / + DATA CODE(15),KNTM(15),KNTR(15) / 'D106.1', 100, 100 / + DATA CODE(16),KNTM(16),KNTR(16) / 'D209.1', 100, 100 / + DATA CODE(17),KNTM(17),KNTR(17) / 'D509.1', 100, 100 / + DATA CODE(18),KNTM(18),KNTR(18) / 'E100.1', 100, 100 / + DATA CODE(19),KNTM(19),KNTR(19) / 'E104.1', 100, 100 / + DATA CODE(20),KNTM(20),KNTR(20) / 'E105.1', 100, 100 / + DATA CODE(21),KNTM(21),KNTR(21) / 'E208.1', 100, 100 / + DATA CODE(22),KNTM(22),KNTR(22) / 'E208.2', 100, 100 / + DATA CODE(23),KNTM(23),KNTR(23) / 'F010.1', 100, 0 / + DATA CODE(24),KNTM(24),KNTR(24) / 'F011.1', 100, 0 / + DATA CODE(25),KNTM(25),KNTR(25) / 'F012.1', 100, 0 / + DATA CODE(26),KNTM(26),KNTR(26) / 'F406.1', 100, 0 / + DATA CODE(27),KNTM(27),KNTR(27) / 'G100.1', 100, 100 / + DATA CODE(28),KNTM(28),KNTR(28) / 'G100.2', 100, 100 / + LOGF = LGFILE + IF(ERCODE .EQ. ' ') THEN + L = 0 + ELSE + DO 10 L = 1, 6 + IF(ERCODE(1:L) .EQ. ERCODE) GOTO 12 + 10 CONTINUE + 12 CONTINUE + ENDIF + DO 14 I = 1, KOUNTE + IF(L .EQ. 0) GOTO 13 + IF(CODE(I)(1:L) .NE. ERCODE(1:L)) GOTO 14 + 13 KNTM(I) = LIMITM + KNTR(I) = LIMITR + 14 CONTINUE + RETURN + ENTRY KERMTR(ERCODE,LOG,MFLAG,RFLAG) + LOG = LOGF + DO 20 I = 1, KOUNTE + IF(ERCODE .EQ. CODE(I)) GOTO 21 + 20 CONTINUE + WRITE(*,1000) ERCODE + CALL ABEND + RETURN + 21 RFLAG = KNTR(I) .GE. 1 + IF(RFLAG .AND. (KNTR(I) .LT. 100)) KNTR(I) = KNTR(I) - 1 + MFLAG = KNTM(I) .GE. 1 + IF(MFLAG .AND. (KNTM(I) .LT. 100)) KNTM(I) = KNTM(I) - 1 + IF(.NOT. RFLAG) THEN + IF(LOGF .LT. 1) THEN + WRITE(*,1001) CODE(I) + ELSE + WRITE(LOGF,1001) CODE(I) + ENDIF + ENDIF + IF(MFLAG .AND. RFLAG) THEN + IF(LOGF .LT. 1) THEN + WRITE(*,1002) CODE(I) + ELSE + WRITE(LOGF,1002) CODE(I) + ENDIF + ENDIF + RETURN +1000 FORMAT(' KERNLIB LIBRARY ERROR. ' / + + ' ERROR CODE ',A6,' NOT RECOGNIZED BY KERMTR', + + ' ERROR MONITOR. RUN ABORTED.') +1001 FORMAT(/' ***** RUN TERMINATED BY CERN LIBRARY ERROR ', + + 'CONDITION ',A6) +1002 FORMAT(/' ***** CERN LIBRARY ERROR CONDITION ',A6) + END +C + SUBROUTINE ABEND +C +C CERN PROGLIB# Z035 ABEND .VERSION KERNVAX 1.10 811126 + + STOP '*** ABEND ***' + END +C +C==================================================================== +C + SUBROUTINE GET_CORE_STATE +C + IMPLICIT REAL*8(A-H,O-Z) +C + include 'msxas3.inc' +c +c ############ I include the file msxasc3.inc +c + include 'msxasc3.inc' + +cman + integer rd_ + PARAMETER(rd_=440) +C + + + + + + COMMON/APARMS2/XV2(NAT_),YV2(NAT_),ZV2(NAT_),RS2(NAT_), + U ALPHA2(NAT_),REDF2(NAT_),Z2(NAT_),Q2(NAT_),QSPNT2(2), + U QINT2(2), + U WATFAC(NAT_),ALPHA02,VOLINT2,OVOUT2,RMXOUT2,NSYMBL2(NAT_), + U NZ2(NAT_) + + CHARACTER*8 NSYMBL2 + +C + +c #############common/pot_type modified to include the core states +c #############to the two hole in the final state of Auger decay i_ +c ##############common /pdqi modified to consider also the two auger wav +C +C common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, +C * i_absorber_hole2,i_norman,i_alpha, +C 1 i_outer_sphere,i_exc_pot,i_mode +C + + COMMON/POT_TYPE/I_ABSORBER,I_ABSORBER_HOLE,I_ABSORBER_HOLE1, + * I_ABSORBER_HOLE2,I_NORMAN,I_ALPHA, + 1 I_OUTER_SPHERE,I_EXC_POT,I_MODE + + + + +C + + COMMON/PDQI/RPI(RD_),RPI1(RD_),RPI2(RD_) +c + INTEGER I_HOLE +C + DIMENSION R(440),P_NK(440),P_NK1(440),P_NK2(440),ICHG(12) +C + DATA THIRD,XINCR,CTFD + &/0.3333333333333333D0,0.0025D0,0.885341377000114D0/ +C + DATA KMX,MESH/RD_,440/ +C + IZ=NZ2(I_ABSORBER+I_OUTER_SPHERE) +c open(unit=697,file='get1.dat',status='unknown') + if(iz.eq.0) then + iz=1 ! in case an empty sphere is the first atom + write(6,*) ' warning check! empty sphere is the first atom ' + endif + + I_RADIAL=I_ABSORBER_HOLE +C +C ######### Modified to consider also the Auger calculation +C + I_RADIAL1=I_ABSORBER_HOLE1 + I_RADIAL2=I_ABSORBER_HOLE2 + I_HOLE=0 + NCUT=1 +C +C SET-UP HERMAN-SKILLMAN MESH FOR Z OF ABSORBING ATOM +C + MESH=MESH/NCUT + H=XINCR*CTFD/(DFLOAT(IZ)**THIRD)*NCUT + R(1)=H + DO 10 N=1,12 +10 ICHG(N)=(40/NCUT)*N + N=1 + DO 20 K=2,MESH + R(K)=R(K-1)+H + IF (K.LT.ICHG(N)) GO TO 20 + H=H+H + N=N+1 +20 CONTINUE +C +C*** COMPUTE FUNCTION P_NK ON RADIAL MESH R +C + CALL ATOM_SUB(IZ,I_HOLE,R,P_NK,1,I_RADIAL,0.d0) +C + + +C +C*** PASS VIA COMMON BLOCK THE FIRST KMX POINTS. NOTE THAT +C P_NK IS NOT NORMALIZED SINCE Q_NK MUST ALSO BE CONSIDERED. +C ALSO NOTE THE RELATION TO THE SCHRODINGER RADIAL FUNCTION +C R*R_L = P_NK. THIS RELATION HOLDS IN THE LIMIT C --> INFINITY. +C +c write(6,*)'core state on h-s mesh' + DO 30 I=1,KMX + RPI(I)=P_NK(I) +c write(6,*) r(i), rpi(i) +30 CONTINUE +c +c WRITE(33,*) 'core wf on hs mesh for hole =', i_radial +c +c do i=1,kmx +c write(33,*) r(i), rpi(i)/r(i) +c enddo +c write(33,*) '------------' +c +c ############# modified to make the calculations also for the two +c ############# wave functions necessary for the auger decay calcula +c ############# these two wavefunction are calculated with Z+1 appro +c ############# with one hole=to the deeper first core hole (hole) +c + IF (calctype.EQ.'aed') THEN + + + I_HOLE=HOLE2 + + + CALL ATOM_SUB(IZ,I_HOLE,R,P_NK1,1,I_RADIAL1,0.d0) + CALL ATOM_SUB(IZ,I_HOLE,R,P_NK2,1,I_RADIAL2,0.d0) + DO 3011 I=1,KMX + RPI1(I)=P_NK1(I) + RPI2(I)=P_NK2(I) + + + + +3011 CONTINUE + + END IF +C + + RETURN + END +c +C + SUBROUTINE COREWF(NAS,IZC,HOLE,EST) +C + implicit real*8 (a-h,o-z) +C + INCLUDE 'msxas3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +C +C + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +C + COMMON /LLM/ ALPHA, BETA +C + COMMON/PDQIX/RPIX(RDX_), FNISX + complex*16 RPIX +C + DOUBLE PRECISION CWFX(RDX_),RXD(RDX_),XION,ES + complex*16 RIDX(RDX_),DX +C + INTEGER HOLE +C + DATA THIRD,XINCR,CTFD + &/0.3333333333333333D0,0.0025D0,0.885341377000114D0/ +C +C + IZ=IZC + ITYRADIAL=HOLE +C + XION=0 + ITYHOLE=0 +C + KMXN = KMX(NAS) + DO I = 1, KMXN + RXD(I) = RX(I,NAS) + ENDDO +c write(6,*) ' corewf: kmx = ', kmxn +C +C*** COMPUTE FUNCTION P_NK ON RADIAL MESH RD AND LL MESH RX +C + XION = 0.D0 + CALL GET_INTRP_CORE(IZ,ITYHOLE,ITYRADIAL,XION,CWFX,RXD,KMXN,ES) +C +C*** NOTE THAT CWFX=P_NK (UPPER COMPONENT OF DIRAC EQU.) IS NOT NORMALIZED +C SINCE ALSO Q_NK (LOWER COMPONENT) MUST BE CONSIDERED. +C ALSO NOTE THE RELATION TO THE SCHRODINGER RADIAL FUNCTION R*R_L = P_NK. +C THIS RELATION HOLDS IN THE LIMIT C --> INFINITY. +c +c.....Find normalization constant in ll-mesh. +c + do i = 1, kmxn + xi = cwfx(i) + rpix(i)=cmplx(xi) +c write(6,*) rx(i,nas), xi + enddo +c + est = es +c dh = x(2,n) - x(1,n) +c write(6,*) ' dh ', dh, hx(n), alpha, beta + n = nas + id = 1 + do k = 1,kmxn + ridx(k)=rpix(k)**2*rx(k,n)/(alpha*rx(k,n) + beta) + enddo + call defint0(ridx,hx(n),kmxn,dx,id) + fnisx=sqrt(dble(dx)) +c + write(6,*) 'corewf: fnisx = ', fnisx +c +c write(6,*) 'alpha, beta, hx(nas) =',alpha, beta, hx(n) +c + do k=1,kmxn + rpix(k)=rx(k,n)**2*rpix(k)/fnisx + enddo +c + write(34,*) ' core wf on lin-log mesh for hole ', hole + do i=1,kmxn + write(34,1) rx(i,n), dble(rpix(i)/(fnisx*rx(i,n)**3)) + enddo +c + 1 format(2e13.6) +c + RETURN + END +C +C +C*********************************************************************** +C + subroutine get_intrp_core(iz,ihole,i_radial,xion,cwfx,rx,kmxn,es) +c +c + implicit real*8(a-h,o-z) +c +c + parameter ( mp = 251, ms = 30 ) +c + character*40 title +c +c + common dgc(mp,ms),dpc(mp,ms),bidon(630),idummy +c +c For interpolation on rx mesh +c + dimension rx(kmxn), cwfx(kmxn) + dimension p(0:mp), rat(0:mp), r(mp) +c +c + dimension dum1(mp), dum2(mp) + dimension vcoul(mp), rho0(mp), enp(ms) +c + title = ' ' +c + ifr=1 + iprint=0 +C + amass=0.0d0 + beta=0.0d0 +c +c There are no nodes in relativistic radial charge density +c + small=1.0d-11 +c !Hence a lower limit on rho(r) can be used. + dpas=0.05d0 + dr1=dexp(-8.8d0) + dex=exp(dpas) + r_max=44.447d0 +c + radius=10.0d0 +c + xion=0.d0 +c +c compute relativistic Hartrer-Fock-Slater charge density (on log mesh) +c + call scfdat (title, ifr, iz, ihole, xion, amass, beta, iprint, + 1 vcoul, rho0, dum1, dum2, enp, eatom) +c + es = enp(i_radial) +c +c compute radial log mesh (see subroutine phase in J.J. Rehr's program +c FEFF.FOR) +c + ddex=dr1 + do 10 i=1,251 + r(i)=ddex + ddex=ddex*dex +10 continue +c +c write(6,*) ' interpolating on rx mesh ' +c Dump upper componen of Dirac wf into p +c + p(0) = 0.d-8 + rat(0) = 0.d-8 + do i = 1, 251 + p(i) = dgc(i,i_radial) + rat(i) = r(i) +c write(6,*) rat(i), p(i) + enddo +c + do i=1,kmxn + if(rx(i).gt.r_max) goto 60 +c find nearest points +c initialize hunting parameter (subroututine nearest) +c + jlo=1 + call nearest(rat,252,rx(i), + 1 i_point_1,i_point_2,i_point_3,jlo) +c + i_point_1 = i_point_1 -1 + i_point_2 = i_point_2 -1 + i_point_3 = i_point_3 -1 +c +c interpolate wavefunction +c + call interp_quad( rat(i_point_1),p(i_point_1), + 1 rat(i_point_2),p(i_point_2), + 1 rat(i_point_3),p(i_point_3), + 1 rx(i),cwfx(i),dm1,dm2 ) + enddo +c +60 continue +c + return + end +C +C +C*********************************************************************** +c + subroutine input_cont(id,potype,potgen,lmax_mode,lmaxt) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c +c modified input subroutine for (optionally) complex potentials +c + common /dens/ irho,rhotot(rd_,sd_),rhoconi(2), + $ vcoul(rd_,sd_),vcoulint(2) + + common/auger/calctype,expmode,edge1,edge2 +c + common /fcnr/kxe, h(d_),vcons(2),r(rd_,d_),v(2,rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) + complex*16 vcons +c + common /flag/ inmsh,inv,inrho,insym,iovrho,iosym, + 1 imvhl,nedhlp +c + character*8 name0 ,nsymbl + character*3 calctype, expmode + character*5 potype + character*2 potgen + character*2 edge1,edge2 +c +ctn common block from msxas3c.inc +c .... redundant variables with param.... +c + common/continuum/xemin,xemax,xdelta,xcip,xgamma,xeftri,iexcpot +c + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,xe,ev +c + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) +c + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode +c !pass pots and rhos to this sub + common/out_ascii/iout_ascii +c + common/lparam/lmax2(nat_),l0i +c + logical check +c + character*65 exc_pot_label(5) + character*65 exc_pot_label_extnl(6) + data exc_pot_label/ + &'generating final potential (x_alpha exchange)', + &'generating final potential (real dirac-hara exchange)', + &'generating final potential (real hedin-lundqvist exchange)', + &'generating final potential (complex dirac-hara exchange)', + &'generating final potential (complex hedin-lundqvist exchange)' + &/ + data exc_pot_label_extnl/ + &'potential from extnl file (x_alpha exchange)', + &'potential from extnl file (real dirac-hara exchange)', + &'potential from extnl file (real hedin-lundqvist exchange)', + &'potential form extnl file (complex dirac-hara exchange)', + &'potential form extnl file (complex hedin-lundqvist exchange)', + &'potential form extnl file (potential from extnl calculation)' + &/ +c + data lunout/7/, ot/.333333333d0/, pi/3.1415926d0/ +c +c**** definitions for this version of continuum +c + iout=2 + nspins=1 + iout_ascii=2 +c !output check files + iovrho=13 + iosym=14 +c +c*** define state dependent parameters +c read cip (core ionization potential),emin,emax and deltae +c in order to check array sizes. +ctn read(5,*) cip,emin_exc,emax_exc,de_exc +ctn read(5,*) i_exc_pot,gamma,eftri +ctn initializes from common continuum +c + emin_exc=xemin + emax_exc=xemax + de_exc=xdelta + cip=xcip + gamma=xgamma + eftri=xeftri + i_exc_pot=iexcpot +ctn write(*,*)'dans inpot_cont:' +ctn write(*,*) cip,emin_exc,emax_exc,de_exc +ctn write(*,*) i_exc_pot,gamma,eftri +c +c de_exc = 0.05 +c con = 27.2116/7.62 +c wvb = sqrt(con*emin_exc) +c wve = sqrt(con*emax_exc) +c kxe = nint((wve-wvb)/0.05 + 1.) +c kxe = nint(alog(emax_exc - emin_exc + 1.)/de_exc + 1.) + kxe = nint((xemax-xemin)/xdelta + 1.) + if(kxe.gt.nep_)then +c write(lunout,730) kxe + write(6,730) kxe +730 format(//, + & ' increase the dummy dimensioning variable, nep_. ', + & /,'it should be at least equal to: ', i5,/) + write(6,'(3f10.5)') xemax, xemin, xdelta + call exit + end if +c !define absorbing atom + nas=i_absorber +c + emin=emin_exc + emax=emax_exc + de=de_exc + if(i_exc_pot.eq.1)then +c !define exchange potential types + nedhlp=0 + irho=0 + imvhl=0 + if(i_mode.eq.1)then + + print 745,exc_pot_label_extnl(1) + else + print 745,exc_pot_label(1) + end if +745 format(2x,a65) + else if(i_exc_pot.eq.2)then + nedhlp=1 + irho=2 + imvhl=0 + if(i_mode.eq.1)then + print 745,exc_pot_label_extnl(2) + else + print 745,exc_pot_label(2) + end if + else if(i_exc_pot.eq.3)then +c +c nedhlp=2 !use rehr's approximation to re(vxc) +c + nedhlp=0 !use exact integral expression for re(vxc) + irho=2 + imvhl=0 + if(i_mode.eq.1)then + print 745,exc_pot_label_extnl(3) + else + print 745,exc_pot_label(3) + end if + else if(i_exc_pot.eq.4)then + nedhlp=1 + irho=2 + imvhl=1 + if(i_mode.eq.1)then + print 745,exc_pot_label_extnl(4) + else + print 745,exc_pot_label(4) + end if + else if(i_exc_pot.eq.5) then +c +c nedhlp=2 !use rehr's approximation to re(vxc) and im(vxc) +c + nedhlp=0 !use exact integral expression for vxc +c + irho=2 + imvhl=1 + if(i_mode.eq.1)then + print 745,exc_pot_label_extnl(5) + else + print 745,exc_pot_label(5) + end if + else if(i_exc_pot.eq.6) then + irho = 0 + print 745, exc_pot_label_extnl(6) +c + end if +c + + if(irho.ne.0)then + i_alpha=0 + else + i_alpha=1 + end if + if (i_mode.eq.1)then + if(potype.eq.' msf') print 745, 'extnl pot of msf type' + if(potype.eq.' lmto') print 745, 'extnl pot of lmto type' + if(potype.eq.'spkkr') print 745, 'extnl pot of spkkr type' + if(potype.eq.' lmto'.or.potype.eq.'spkkr') + & call get_ext_pot(potype) + if(potype.eq.' msf') call get_ext_pot_msf + elseif(calctype.eq.'asa'.or.calctype.eq.'ape') then + call asa_ape + else + call vgen + end if +c +c... calculate fermi level eftr = vcint + kf**2 - .72*3./2.*kf/pi*2. +c + if (irho.eq.0) then + eftr = dble(vcons(1))/2. + else + fmkf = (3.*pi**2*rhoconi(1))**ot + eftr = dble(vcons(1)) + fmkf*(fmkf - 2.16/pi) + endif +c + if (eftri.ne.0.0) eftr = eftri +c + if (lmax_mode.eq.0) then +c write(lunout,741) + write(6,741) lmaxt +741 format(/,1x,' lmax constant on each atom equal to: ', i5) +c + else if (lmax_mode.eq.1) then +c write(lunout,741) + write(6,742) emax +742 format(/,1x,' lmax assignment based on', + & ' lmax = r_mt * k_max + 2',/, + & ' at energy emax =',f12.6) +c + else +c write(lunout,741) + write(6,743) +743 format(/,1x,' lmax assignment based on', + & ' l_max = r_mt * k_e + 2',/, + & ' where e is the running energy') +c + endif + +c ###### problem: for low energy continuum auger electron it can happen +c that lmax2 is less than the higher value of the orbital mom +c allowed for the continuum auger electron; thus I set the lm +c value equal to the lmax_ value given in the include file +c msxas3.inc +c + l_max = 0 +c + if ((calctype.eq.'xpd').or.(calctype.eq.'xas').or. + & (calctype.eq.'rex').or.(calctype.eq.'led')) then +c +c !assign lmax values and check max(lm) +c + if (lmax_mode.eq.0) then + do i=1,ndat + lmax2(i) = lmaxt +c write(lunout,842) lmax2(i),i + write(6,842) lmax2(i),i +842 format(10x,' lmax =', i3, ' on center =', i3) + enddo +c + else if (lmax_mode.eq.1) then + do i=1,ndat + lmax2(i) = nint(rs(i)*sqrt(emax)) + 2 + if(l_max.lt.lmax2(i)) l_max=lmax2(i) +c write(lunout,843) lmax2(i),i + write(6,843) lmax2(i),i +843 format(10x,' optimal lmax =', i3, ' on center =', i3) + enddo +c + else + do i=1,ndat + lmax2(i) = nint(rs(i)*sqrt(emax)) + 2 + if(l_max.lt.lmax2(i)) l_max=lmax2(i) + if(i.eq.ndat) then +c write(lunout,844) + write(6,844) + endif +844 format(1x,' optimal lmax chosen according to the running', + & ' energy e for each atom') + enddo +c + endif +c +c...give warning for insufficient lmax dimensions +c + check = .false. + if(lmax_mode.ne.0) then + if(l_max.gt.lmax_) then +c manolo + check=.true. +c write(lunout,746)l_max + write(6,746)l_max +746 format(///, + & ' increase the dummy dimensioning variable, lmax_. ', + & /,' it should be at least equal to: ', i5) + call exit + endif + else + if(lmaxt.gt.lmax_) then +c manolo + check=.true. +c write(lunout,746)lmaxt + write(6,746)lmaxt + call exit + endif + endif +c +c + else +c +c ##### auger part: +c + do i=1,ndat + lmax2(i)=lmax_ + l_max=lmax_ + enddo + + end if +c +c...set lmax equal on any atom if check='true' +c + if ((calctype.eq.'xpd').or.(calctype.eq.'xas').or. + & (calctype.eq.'rex').or.(calctype.eq.'led')) then + if(check) then + do i=1,ndat + lmax2(i) = l_max + write(6,7422)lmax2(i),i +7422 format(10x,' lmax =', i3, ' on center =', i3) + enddo +c + write(6,*) ' ' + write(6,*)' ** input_cont warning **' + write(6,*)' -> estimated l_max is greater than lmax_' + write(6,*)' computation proceeds with l_max=lmax_' + write(6,*)' but convergence is not guaranteed' +c + endif +c + else +c do i=1,ndat +c lmax2(i) = l_max +c write(6,7422)lmax2(i),i +c enddo + endif +c + write(6,*) + +c +c + write (iovrho,408) nedhlp,irho,imvhl,eftr,gamma + 408 format(' nedhlp=',i5,' irho=',i5,' imvhl=',i5, + x /,' eftr = ',f10.6,' gamma =',f10.6) + write (iovrho,409) nat,ndat,nspins, + 1 inmsh,inv,inrho,insym,iovrho,iosym + 409 format(9i5) +c + write(iovrho,110) nat + if (iovrho .ne. 6 ) write(6,110) nat + 110 format(/,2x,18hnumber of centers=,i5,/) +c +c store coulomb potential if energy dependent exchange is to be used +c + if(irho.ne.0)then + do 4304 isp=1,nspins + do 4303 nb=1,ndat + ns=nb+(isp-1)*ndat + do 4302 k=1,kmax(nb) + vcoul(k,ns)=v(1,k,ns) +4302 continue +4303 continue + vcoulint(isp)=dble(vcons(isp)) +4304 continue + end if +c +c check for consistency of input data: +c + write(iovrho,111) + 111 format(30x,10hatom no.,12x,8hposition,14x,13hradius eq ) + write(iovrho,112) (i,nsymbl(i),nz(i),xv(i),yv(i),zv(i),rs(i), + 1 neq(i),i=1,nat) + write (iovrho,112) + 112 format(26x,i3,2x,a4,i6,4f10.4,i6) + do 211 i=1,nat + if(rs(i).lt.0.0) then + write(iovrho,201) i, rs(i) + write(6,201) i, rs(i) + call exit + endif + if(neq(i).eq.0)go to 210 + if(neq(i).ge.i) go to 213 + 210 i1=i+1 + if(i1.gt.nat) go to 5000 + go to 2135 + 213 write(iovrho,202) neq(i), i + write(6,202) neq(i), i + call exit + 2135 do 211 j=i1,nat + rij = sqrt((xv(j)-xv(i))**2+(yv(j)-yv(i))**2+(zv(j)-zv(i))**2) + rsum = rs(i)+rs(j) + rdif = rsum-rij + if (rsum.gt.rij) go to 215 + go to 211 + 215 write (iovrho,200) i,j,rsum,rij,rdif + 200 format(' spheres',2i5,' overlap ',3f12.6) + 201 format(' sphere',i5,' has negative rs', f12.6) + 202 format(' neq(i)',i5,' for atom i=', i5,' is inconsistent' ) + 211 continue +c + 5000 return + end +c +C + SUBROUTINE GET_EXT_POT_MSF !EXTERNAL POTENTIAL IN MS FORMAT +C + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + INTEGER AT_,D_,RD_,SD_ + PARAMETER ( AT_=NAT_-1,D_=UA_-1,rd_=440,SD_=UA_-1) + + COMMON /DENS/ IRHO,RHOTOT(RD_,SD_),RHOCONI(2), + $ VCOUL(RD_,SD_),VCOULINT(2) +C + COMMON /FCNR/KXE, H(D_),VCONS(2),R(RD_,D_),V(2,RD_,SD_), + $ ICHG(10,D_),KPLACE(AT_),KMAX(AT_) + complex*16 VCONS +C + COMMON /FLAG/ INMSH,INV,INRHO,INSYM,IOVRHO,IOSYM, + 1 IMVHL,NEDHLP +C + CHARACTER*8 NAME0 ,NSYMBL +C + COMMON/PARAM/EFTR,GAMMA,VCON,XE,EV,E,IOUT,NAT,NDAT,NSPINS, + 1 NAS,RS(AT_),XV(AT_),YV(AT_),ZV(AT_),EXFACT(AT_),Z(AT_), + 3 LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),NAME0,CIP,EMAX,EMIN,DE,RS_OS + complex*16 VCON,XE,EV +C + COMMON/DIMENS2/NAT2,NDAT2 +C +cman DATA INV,INRHO/2,3/ + inv=2 + inrho=3 +C + NAT = NAT2 - 1 + NDAT = NDAT2 - 1 +C + OPEN(INV, status='unknown') + DO 4444 N=1,NAT + READ (INV,311) NSYMBL(N),NEQ(N), NZ(N),IDUMMY,KMAX(N), + 1 KPLACE(N),XV(N),YV(N),ZV(N),RS(N),EXFACT(N),NC +311 FORMAT (1X,A4,3I2,2I4,5F11.6,T76,I5) + Z(N)=NZ(N) + IF(NEQ(N).NE.0) GO TO 4444 +C +C RECONSTRUCT RADIAL MESH +C + READ (INV,308) (ICHG(I,N),I=1,10),NC + 308 FORMAT(10I5,T76,I5) + KX=KMAX(N) + READ (INV,319) NC,(R(I,N),I=1,5) + H(N)=R(2,N)-R(1,N) + HH=H(N) + ICH=1 + KICH=ICHG(ICH,N) + DO 133 K=3,KX + R(K,N)=R(K-1,N)+HH + IF (K.LT.KICH) GO TO 133 + ICH=ICH+1 + KICH=ICHG(ICH,N) + HH=HH+HH +133 CONTINUE + 319 FORMAT(T76,I5,T2,1P5E14.7) + H(N)=R(2,N)-R(1,N) + NS=N +C + DO 142 ISPIN=1,NSPINS + DO 141 K=1,KX,5 + KCARD=MIN0(KX,K+4) + READ (INV,319) NC,(V(1,I,NS),I=K,KCARD) + DO 7474 KKK=K,KCARD + 7474 V(2,KKK,NS) = 0.000 + 141 CONTINUE + 142 NS=NS+NDAT +C + IF(IRHO.EQ.0) GOTO 4444 + OPEN(INRHO, status='unknown') + DO 423 ISPIN=1,NSPINS + NS=N+(ISPIN-1)*NDAT + DO 424 K=1,KX,5 + KCARD=MIN0(KX,K+4) + READ(INRHO,319) NC,(RHOTOT(I,NS),I=K,KCARD) + 424 CONTINUE + 423 CONTINUE + 4444 CONTINUE +C +C READ INTERSTITIAL V AND RHO +C + READ (INV,319) NC,(VCONS(ISPIN),ISPIN=1,NSPINS) + IF(IRHO.NE.0)READ (INRHO,319) NC,(RHOCONI(ISPIN),ISPIN=1,NSPINS) +C + WRITE(6,120) INV + 120 FORMAT (' STARTING POTENTIAL READ IN FROM FILE',I4) + IF( IRHO .NE. 0) WRITE(6,121) INRHO + 121 FORMAT (' STARTING CHARGE DENSITY READ IN FROM FILE',I4) +C + REWIND(INV) + REWIND(INRHO) +C + RETURN + END +C + SUBROUTINE GET_EXT_POT(potype) +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' +C + INTEGER AT_,D_,RD_,SD_ + PARAMETER ( AT_=NAT_-1,D_=UA_-1,rd_=440,SD_=UA_-1) +C + PARAMETER (MRP = 900) +C + COMMON /DENS/ IRHO,RHOTOT(RD_,SD_),RHOCONI(2), + $ VCOUL(RD_,SD_),VCOULINT(2) +C + COMMON /FCNR/KXE, H(D_),VCONS(2),R(RD_,D_),V(2,RD_,SD_), + $ ICHG(10,D_),KPLACE(AT_),KMAX(AT_) + complex*16 VCONS +C + COMMON /FLAG/ INMSH,INV,INRHO,INSYM,IOVRHO,IOSYM, + 1 IMVHL,NEDHLP +C + CHARACTER*8 NAME0 ,NSYMBL +C + COMMON/PARAM/EFTR,GAMMA,VCON,XE,EV,E,IOUT,NAT,NDAT,NSPINS, + 1 NAS,RS(AT_),XV(AT_),YV(AT_),ZV(AT_),EXFACT(AT_),Z(AT_), + 3 LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),NAME0,CIP,EMAX,EMIN,DE,RS_OS + complex*16 VCON,XE,EV +C + COMMON/DIMENS2/NAT2,NDAT2 +C + common/aparms/xa(natoms),ya(natoms),za(natoms),zat(natoms), + & nsymbla(natoms),nzeq(natoms),neqa(natoms),ncores(natoms), + & lmaxat(natoms) +C + REAL*8 xa,ya,za,zat + CHARACTER*8 nsymbla +C + DIMENSION RL(MRP,D_), VCL(MRP,SD_), RHOL(MRP,SD_), HL(D_), + & VLMTO(MRP,SD_), KMXP(SD_), KPLP(SD_), RSL(SD_), + & NPAC(-10:100), NZL(D_), KMX(SD_), ICHGL(SD_,D_) +C + DIMENSION RHS(MRP,D_), VHS(MRP,SD_), RHOHS(MRP,SD_) +C + REAL*8 RL, VCL, RHOL, HL, VLMTO, RSL, RHS, VHS, RHOHS, + & HR, VINT, RHOINT, DVT, DVTRHOINT +C + EXTERNAL NEAREST +C + CHARACTER*5 POTYPE + CHARACTER*5 CHECK +C + DATA THIRD,XINCR,CTFD + &/0.33333333,0.0025E0,0.88534137E0/ +C + INP=2 +C + NDUMMY = 0 + NSPINS = 1 + NAT = NAT2 - 1 + NDAT = NDAT2 - 1 +C +C OPEN(INP, file='data/inpot.ext',status='unknown') to be specifed in procfase +C +C Initialize to zero the vector indicating for which atomic species +C the external potential data have been already interpolated. Positions from 1 to +C 100 indicates physical atoms, from 0 to -1010 empty inequivalent +C spheres +C + DO N = -10, 100 + NPAC(N) = 0 + ENDDO +C +C VCOULINT : interstitial Coulomb potential in Ry +C RHOCONI : interstitial charge density in Ry +C VCLMTO : intsrstitial LMTO potential in Ry +C + READ(INP,*) VCOULINT(1), RHOCONI(1), VCLMTO +C + NES=1 +C + DO N=1,NDAT +C + READ(INP,*,END=50) NZL(N), KMX(N), RSL(N) + WRITE(6,*) 'N=',N,'ZATL(N)=', NZL(N),'KMX(N)=',KMX(N), + & 'RS(N)=',RSL(N) + IF (KMX(N).GT.MRP) THEN + WRITE(6,*) ' ' + WRITE(6,*) ' ' + WRITE(6,*)' MRP =', MRP,' TOO SMALL, INCREASE UP TO ', KMX(N) + WRITE(6,*) ' ' + WRITE(6,*) ' ' + CALL EXIT + ENDIF +C + IF(NZL(N).NE.0) THEN + NPAC(NZL(N)) = N +C WRITE(6,*) 'N, NZL(N), NPAC(NZL(N))', N, NZL(N) , NPAC(NZL(N)) + ELSE + NES=NES-1 + NPAC(NES)=N +C WRITE(6,*) 'N, NZL(N), NES, NPAC(NES)', N,NZL(N),NES,NPAC(NES) + ENDIF +C +C NOTE: COULOMB AND LMTO (SPKKR) POTENTIALS ARE MULTIPLIED BY RL +C + DO K = 1, KMX(N) + READ(INP,*) RL(K,N), VCL(K,N), RHOL(K,N), VLMTO(K,N) +C WRITE(6,*) K, RL(K,N), VCL(K,N), RHOL(K,N), VLMTO(K,N) + ENDDO + +C +C SET-UP HERMAN-SKILLMAN MESH FOR ATOM OF ATOMIC NUMBER Z +C + MESH=400 + NCUT=1 + MESH=MESH/NCUT + IF(NZL(N).EQ.0) THEN + HL(N)=XINCR*CTFD*DBLE(NCUT) + ELSE + HL(N)=XINCR*CTFD/(DBLE(NZL(N))**THIRD)*DBLE(NCUT) + ENDIF + HR = HL(N) + RHS(1,N)=HR + DO 10 K=1,12 +10 ICHGL(K,N)=(40/NCUT)*K + I=1 + DO 20 K=2,MESH + RHS(K,N)=RHS(K-1,N)+HR + IF (K.LT.ICHGL(I,N)) GO TO 20 + HR=HR+HR + I=I+1 +20 CONTINUE +C +C FIND KMAX(N) IN THE H-S MESH ACCORDING TO RS(N) +C + KMXP(N) = 0 + KPLP(N) = 0 + DO K = 1, MESH + IF (RHS(K,N).GT.RSL(N)) GO TO 40 + ENDDO + 40 KPLP(N) = K - 1 + KMXP(N) = K + 2 +C + WRITE(6,*) 'ATOMIC SPECIES, HS KPLACE AND KMAX' + WRITE(6,*) 'N=',N, 'KPLP(N)= ',KPLP(N), ' KMXP(N)= ', KMXP(N) +C WRITE(6,*) 'RHSMAX=', RHS(400,N), 'RSL(N) =', RSL(N) +C + DO I=1,KMXP(N) +C FIND NEAREST POINTS +C INITIALIZE HUNTING PARAMETER (SUBROUTUTINE NEAREST) +C + JLO = 1 + CALL NEAREST(RL(1,N), KMX(N), RHS(I,N), IP1, IP2, IP3, JLO) +C + IF(IRHO.NE.0) THEN +C +C INTERPOLATE COULOMB POTENTIAL +C + CALL INTERP_QUAD( RL(IP1,N),VCL(IP1,N),RL(IP2,N),VCL(IP2,N), + & RL(IP3,N),VCL(IP3,N),RHS(I,N),VHS(I,N), + & DM1,DM2) +C +C INTERPOLATE CHARGE DENSITY +C + CALL INTERP_QUAD( RL(IP1,N),RHOL(IP1,N),RL(IP2,N), + & RHOL(IP2,N),RL(IP3,N),RHOL(IP3,N), + & RHS(I,N),RHOHS(I,N),DM1,DM2) + ELSE +C +C INTERPOLATE EXTERNAL POTENTIAL +C + CALL INTERP_QUAD( RL(IP1,N),VLMTO(IP1,N), + & RL(IP2,N),VLMTO(IP2,N), + & RL(IP3,N),VLMTO(IP3,N),RHS(I,N),VHS(I,N), + & DM1,DM2) + ENDIF + ENDDO +C + WRITE(6,*) 'INTERPOLATED VALUES ON HS MESH' +C + DO I = 1, KMXP(N) +C WRITE(6,*) I, RHS(I,N), VHS(I,N), RHOHS(I,N) + IF(RHOHS(I,N).LT.0.D0) THEN + WRITE(6,*) ' WARNING: DENSITY INTERPOLATED TO NEGATIVE', + & ' VALUES AT RHS =', RHS(I,N),' FOR ATOM', + & ' NUMBER N =', N + CALL EXIT + ENDIF + ENDDO +C +C......TEST LAST THREE INTERPOLATED VALUES +C + SMALL=0.005 +C + DO I = KPLP(N) + 1, KMXP(N) + KP = KMX(N) +C + IF(IRHO.NE.0) THEN + CALL DINTERP(RL(KP-5,N),VCL(KP-5,N),5,RHS(I,N),VINT,DVT, + & .TRUE.) + CALL DINTERP(RL(KP-5,N),RHOL(KP-5,N),5,RHS(I,N),RHOINT, + & DVTRHOINT,.TRUE.) + IF(DABS(VHS(I,N)-VINT).LT.SMALL) THEN + CHECK='OK' + WRITE(6,*) 'CHECK ON THE INTERPOLATED VALUE AT I =',I, + & 'FOR VC ', CHECK + ELSE + CHECK='NOTOK' + WRITE(6,*) 'CHECK ON THE INTERPOLATED VALUE AT I =',I, + & 'FOR VC ', CHECK + WRITE(6,*) I, RHS(I,N), VINT, VHS(I,N) + ENDIF +C + IF(DABS(RHOHS(I,N)-RHOINT).LT.SMALL) THEN + CHECK='OK' + WRITE(6,*) 'CHECK ON THE INTERPOLATED VALUE AT I =',I, + & 'FOR RHO ', CHECK + ELSE + CHECK='NOTOK' + WRITE(6,*) 'CHECK ON THE INTERPOLATED VALUE AT I =',I, + & 'FOR DENSITY RHO ', CHECK + WRITE(6,*) I, RHS(I,N), RHOINT, RHOHS(I,N) + ENDIF +C + ELSE +C + CALL DINTERP(RL(KP-5,N),VLMTO(KP-5,N),5,RHS(I,N),VINT,DVT, + & .TRUE.) + IF(DABS(VHS(I,N)-VINT).LT.SMALL) THEN + CHECK='OK' + WRITE(6,*) 'CHECK ON THE INTERPOLATED VALUE AT I =',I, + & 'FOR VLMTO ', CHECK + ELSE + CHECK='NOTOK' + WRITE(6,*) 'CHECK ON THE INTERPOLATED VALUE AT I =',I, + & 'FOR VLMTO ', CHECK + WRITE(6,*) I, RHS(I,N), VINT, VHS(I,N) + ENDIF +C + ENDIF +C + ENDDO +C +C + ENDDO +C + 50 CONTINUE +C + CLOSE(2) +C +C write(6,*) npac(22), npac(8), npac(0), npac(-1) + DO 60 I=1,NAT + XV(I) = XA(I+1) - XA(2) + YV(I) = YA(I+1) - YA(2) + ZV(I) = ZA(I+1) - ZA(2) + NSYMBL(I) = NSYMBLA(I+1) + NEQ(I) = NEQA(I+1) +c write(6,*) NEQ(I), NSYMBL(I) + IF(NEQ(I).NE.0) NEQ(I) = NEQ(I) - 1 + NZ(I) = NZEQ(I+1) +C N = NPAC(NZ(I)) + IF(NZ(I).NE.0) THEN +C + N = NPAC(NZ(I)) +C WRITE(6,*) 'N, NZ(I), NPAC(NZ(I))', N, NZ(I), NPAC(NZ(I)) +C + ELSE +C + IF(NSYMBL(I).EQ.'ES') THEN + N=NPAC(0) + ELSE + NES=ICHAR('0')-ICHAR(NSYMBL(I)(2:2)) + N=NPAC(NES) +C WRITE(6,*) ICHAR('0'),ICHAR(NSYMBL(I)(2:2)) +C WRITE(6,*) ' NES = ',NES, ' N = ', N + ENDIF +C + ENDIF + KPLACE(I) = KPLP(N) + KMAX(I) = KMXP(N) + RS(I) = RSL(N) + EXFACT(I) = 0.0 +C + IF(NEQ(I).NE.0) GO TO 60 +C + H(I) = HL(N) + DO K = 1,10 + ICHG(K,I) = ICHGL(K,N) + ENDDO + DO K = 1, KMAX(I) + R(K,I) = RHS(K,N) + V(2,K,I) = 0.0 + IF(IRHO.NE.0) THEN + V(1,K,I) = VHS(K,N)/RHS(K,N) + RHOTOT(K,I) = RHOHS(K,N) + ELSE + V(1,K,I) = VHS(K,N)/RHS(K,N) + ENDIF + ENDDO + IF(IRHO.NE.0) THEN + VCONS(1) = DCMPLX(VCOULINT(1)) + ELSE + VCONS(1) = DCMPLX(VCLMTO) + ENDIF + 60 CONTINUE +C +C.....WRITE OUT POTENTIAL AND DENSITY FILES +C + IF (potype.EQ.' lmto') THEN + OPEN (19, FILE = 'div/LMTO.POT', STATUS = 'unknown') + ELSEIF (potype.EQ.'spkkr') THEN + OPEN (19, FILE = 'div/SPKKR.POT', STATUS = 'unknown') + ELSE + OPEN (20, FILE = 'div/COUL.POT', STATUS = 'unknown') + OPEN (9, FILE = 'div/RHO.DENS', STATUS = 'unknown') + ENDIF +C + INV = 20 + IF (potype.EQ.' lmto'.OR.potype.EQ.'spkkr') INV = 19 + INRHO= 9 + NST=1 + NC=2 + DO 4401 N=NST,NAT + WRITE(INV,311) NSYMBL(N),NEQ(N),NZ(N),NDUMMY,KMAX(N),KPLACE(N), + 1 XV(N),YV(N),ZV(N),RS(N),EXFACT(N),NC + 311 FORMAT(A5,3I2,2I4,5F11.6,T76,I5) + NC=NC+1 + IF(NEQ(N).NE.0) GO TO 4401 + WRITE(INV,308) (ICHG(I,N),I= 1,10),NC + 308 FORMAT(10I5,T76,I5) + NC=NC+1 + WRITE(INV,319) NC,(R(I,N),I=1,5) + 319 FORMAT(T76,I5,T2,1P5E14.7) + NS=N + NC=NC+1 + KX=KMAX(N) + NS = N + DO 142 ISPIN=1,NSPINS + DO 141 K=1,KX,5 + KCARD=MIN0(KX,K+4) + WRITE(INV,319) NC,(V(1,I,NS),I=K,KCARD) + 141 NC=NC+1 + 142 NS=NS+NDAT + NS=N + IF (potype.NE.' lmto'.AND.potype.NE.'spkkr') THEN + DO 555 ISPIN=1,NSPINS + DO 551 K=1,KX,5 + KCARD=MIN0(KX,K+4) + WRITE(INRHO,319) NC,(RHOTOT(I,NS),I=K,KCARD) + 551 NC=NC+1 + 555 NS=NS+NDAT + ENDIF + 4401 CONTINUE +C + IF(INV.EQ.19) WRITE( INV,319) NC,(VCONS(IS),IS=1,NSPINS) +C + IF (INV.EQ.20) THEN + WRITE(INV,319) NC, dble(VCONS(1)) + + WRITE( INRHO,319) NC,(RHOCONI(IS),IS=1,NSPINS) + ENDIF +C + IF (potype.EQ.' lmto'.OR.potype.EQ.'spkkr') THEN + CLOSE (UNIT=19) + ELSE + CLOSE (UNIT=20) + CLOSE (UNIT=9) + ENDIF +C +C STOP + RETURN + END +C +C + subroutine asa_ape +c + implicit double precision (a-h,o-z) +c + include 'msxas3.inc' +c + integer at_, d_, sd_ + parameter ( mp = 251, ms = 30) + parameter ( at_=nat_-1,d_=ua_-1,sd_=ua_-1) +c +c + common dgc(mp,ms),dpc(mp,ms),bidon(630),idummy +c +c dgc, dpc contains large (small) component of radial functions +c + common/ratom1/xnel(ms),en(ms),scc(ms),scw(ms),sce(ms), + 1 nq(ms),kap(ms),nmax(ms) +c + dimension r(mp), vcoul(mp), rho(mp), vtot(mp), rsw(mp), enp(ms) +c + dimension vxcgs(mp), zx(rdx_), zcx(rdx_), zrsx(rdx_), rswx(rdx_) + dimension z(0:mp), zc(0:mp), zrs(0:mp), r_at(0:mp) + dimension dum1(mp), dum2(mp) +c +c.....variables from msxasc3.inc +c + common/atoms/c,rad,redf,charge_ion(100),nat,nz,neqat + dimension c(natoms,3), rad(natoms), redf(natoms), nz(natoms) + dimension neqat(natoms) +c +c + common/lmto/ rdsymbl,tag(natoms) + character*2 tag,tagi + logical rdsymbl +c +c + common /param/eftr,gamma,vcon,xe,ev,e,iout,nat1,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),za(at_), + 3 lmaxx(at_),nza(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,xe,ev + character*8 name0, nsymbl +c + common /fcnrlm/x(rdx_,d_), rx(rdx_,d_), hx(d_), vx(rdx_,sd_), + & vxr(rdx_,sd_), dvx(rdx_,sd_), bx(rdx_,sd_), + & vxso(rdx_,sd_), kmx(at_), kplx(at_) + complex*16 vx, vxr, dvx, bx, vxso +c + dimension vtotx(rdx_) +c + common /llm/ alpha, beta +c +c + logical do_r_in +c + character*40 title + character*2 symbl +c +c +c + data zero,one,two/0.d0,1.d0,2.d0/ + data pi/3.14159265358979d0/,srt2/1.414213562d0/ +c + data fsc,fscs4 /7.29735d-3,1.331283d-5/ +c +c + title = ' ' +c +c read(5,*) symbl, iz + symbl = tag(1) + iz = nz(1) +c + ifr=1 + iprint=0 +C + amass=0.0d0 + betan=0.0d0 +c + ihole = 0 + xion = 0.d0 +c +c There are no nodes in relativistic radial charge density. +c Hence a lower limit on rho(r) can be used. +c +c compute radial log mesh in au (see subroutine phase in J.J. Rehr's progr +c FEFF.FOR) + + small=1.0d-11 +c ! + dpas=0.05d0 + dr1=dexp(-8.8d0) + dex=exp(dpas) + r_max=44.447d0 +c + ddex=dr1 + do 10 i = 1,mp + r(i)=ddex + ddex=ddex*dex +10 continue +c +c compute relativistic Hartree-Fock charge density rho(r) (on log mesh), +c total coulomb potential vcoul(r) and large and small components of +c occupied orbitals dgc(mp,ms),dpc(mp,ms) +c energies in Hartrees + + call scfdat (title, ifr, iz, ihole, xion, amass, betan, iprint, + & vcoul, rho, dum1, dum2, enp, eatom) +c + anns = 1.d0 + ot = 1.d0/3.d0 + do k = 1, mp + rsw(k)=((3.*(r(k)**2))/(rho(k)*anns))**ot + vxcgs(k) = 1.06d0*vxc_gs(rsw(k)) + vcoul(k) = 2.0d0*vcoul(k) !conversion to ryd energy units + vtot(k) = vcoul(k) + vxcgs(k) + enddo +c + open(unit=60,file ='tasa/vc_rho_at.dat',status='unknown') +c + write(60,*)' r, vc, vt, rho, rsw, vxcgs' + do j = 1,mp + write(60,30) r(j), vcoul(j), vtot(j), rho(j), rsw(j), vxcgs(j) + enddo +30 format(6e14.6) +c +c determine atomic radius rmt +c + do j=1,mp + if(abs(vtot(j)) .lt. 5.d-2) goto 20 + enddo +20 rmt = r(j) + write(6,*)'rmt =', rmt +c + do j = 1, iz + write(6,*) 'occupation for orbital j =',j,'is =', xnel(j), + & 'with energy =',en(j)*2.0d0,' Ryd' + enddo +c +c construct linear-log mesh +c + do_r_in = .false. +c + zat = dble(iz) + if(zat.eq.0.0) then + x0 = 9.0d0 +c x0 = 10d0.0 + else + x0 = 9.0d0 + log(zat) +c x0 = 10.0d0 + log(zat) + endif + rkmx = rmt + dpas = 0.1d0/rkmx + if(dpas.gt. 0.02d0) dpas = 0.02d0 + alpha = 0.5d0 + beta = 1.0d0 + rho_1 = -beta*x0 + r_sub = rmt + xmax = alpha*r_sub + beta*log(r_sub) + kmx(1) = nint ( (xmax + x0 + dpas) / dpas ) + if(kmx(1).gt.rdx_) then + write(6,*) + & 'increase parameter rdx_. it should be at least ', kmx(1) + call exit + endif + nr = kmx(1) + kplx(1) = kmx(1)-3 +c + call linlogmesh ( i_end, hx(1), x(1,1), rx(1,1), do_r_in, + & kmx(1), kplx(1), nr, rho_1, r_sub, r_in, + & alpha, beta ) +c +c +c write(6,*)'kmx and kplx for log-linear mesh', kmx, kplx +c +c interpolate vcoul, vtot and rho on linear-log mesh +c + write(6,*) 'interpolating on rx mesh for calctype eq. asa or ape' +c + open(unit=61,file ='tasa/vtot_xm.dat',status='unknown') + open(unit=62,file ='tasa/vxc_xm.dat',status='unknown') + open(unit=63,file ='tasa/twopot.dat',status='unknown') +c +c + z(0) = -2.0d0*dble(iz) + zc(0) = z(0) + zrs(0) = 0.d0 + r_at(0) = 1.0d-8 + do i = 1, mp + z(i) = r(i)*vtot(i) + zc(i) = r(i)*vcoul(i) + zrs(i) = r(i)*rsw(i) + r_at(i) = r(i) +c write(6,*) rat(i), p(i) + enddo +c + do i=1,kmx(1) +c + if(rx(i,1).gt.rmt) goto 60 +c find nearest points +c initialize hunting parameter (subroututine nearest) +c + jlo=1 + call nearest(r,mp,rx(i,1),i_point_1,i_point_2,i_point_3,jlo) +c + i_point_1 = i_point_1 - 1 + i_point_2 = i_point_2 - 1 + i_point_3 = i_point_3 - 1 +c +c interpolate wavefunction +c + call interp_quad( r_at(i_point_1),z(i_point_1), + 1 r_at(i_point_2),z(i_point_2), + 1 r_at(i_point_3),z(i_point_3), + 1 rx(i,1),zx(i),dm1,dm2 ) +c + call interp_quad( r_at(i_point_1),zc(i_point_1), + 1 r_at(i_point_2),zc(i_point_2), + 1 r_at(i_point_3),zc(i_point_3), + 1 rx(i,1),zcx(i),dm1,dm2 ) +c +c + call interp_quad( r_at(i_point_1),zrs(i_point_1), + 1 r_at(i_point_2),zrs(i_point_2), + 1 r_at(i_point_3),zrs(i_point_3), + 1 rx(i,1),zrsx(i),dm1,dm2 ) +c + vx(i,1) = dcmplx(zx(i)/rx(i,1)) !xalpha-potential on lin-log mesh + vxr(i,1) = dcmplx(zcx(i)/rx(i,1)) !coulomb potential on lin-log mesh + rswx(i) = zrsx(i)/rx(i,1) !Wigner parameter rsw on lin-log mesh +c + write(61,*) rx(i,1), zx(i)/rx(i,1), zcx(i)/rx(i,1), zx(i), + & zcx(i), zrsx(i) +c + enddo +c +60 continue + kmxc = i - 1 + kplxc = kmxc - 3 + write(6,*)'cut values for rx mesh',kmxc,kplxc, + & rx(kmxc,1),rx(kplxc,1) +c +c.....pass atom parameter to common /param/ +c +c common /param/eftr,gamma,vcon,xe,ev,e,iout,nat1,ndat,nspins, +c 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),za(at_), +c 3 lmaxx(at_),nza(at_),nsymbl(at_), +c 4 neq(at_),name0,cip,emax,emin,de +c + nat1 = nat + ndat = 1 + nspins = 1 + nas = 1 + rs(1) = rx(kplxc,1) + rs_input = rad(1) + vcon = vxr(kmxc,1) + za(1) = dble(iz) + nza(1) = iz + nsymbl(1) = tag(1) + neq(1) = 0 + xv(1) = c(1,1) + yv(1) = c(1,2) + zv(1) = c(1,3) +c + write(6,*) nat1, rs(1), rs_input, vcon, za(1), tag(1), xv(1) +c +c + e = 53.d0 + write(62,*) ' rx rswx vxcrl vxcim vxcgs for e =', e + do i = 1, kmxc + bx(i,1) = fscs4/(1.0d0 + fscs4*(e - vx(i,1))) + call hlvxc(e,rswx(i),vxcrl,vxcim) + write(62,30) rx(i,1), rswx(i), vxcrl, vxcim, vxc_gs(rswx(i)) +c vxr(i,1) = vxr(i,1) + dcmplx(vxcrl,vxcim) +c write(63,*) rx(i,1), dble(vx(i,1)), dble(vxr(i,1)) + enddo +c +c + stop +c +c.....Define bd for non relativistic calculation +c +c do i = 1, rdx_ +c bd(i) = dcmplx(fscs4,0.d0) +c enddo +c +c +c write(6,*)'csbf' +c write(6,*) (sbf(l+1),l = 0, lmax) +c write(6,*)'cshf' +c write(6,*) (shf(l+1),l = 0, lmax) +c +c write(62,*) ' rx rswx vxcrl vxcim vxcgs for e =', e +c do i = 1, kmxc +c bx(i) = bd(i)/(1.0 + fscs4*(e - vx(i))) +c call hlvxc(e,rswx(i),vxcrl,vxcim) +c write(62,30) rx(i), rswx(i), vxcrl, vxcim, vxc_gs(rswx(i)) +c vxr(i) = vxr(i) + dcmplx(vxcrl,vxcim) +c write(63,*) rx(i), dble(vx(i)), dble(vxr(i)) +c enddo +c + return +c + end +c +c +c + subroutine hlvxc(eken,rsw,vxcrl,vxcim) +c +c calculation of exchange-correlation h-l potential +c + implicit double precision (a-h,o-z) +c + common /hedin/ wp2,xk,e,eta2,pi,ot +c +c + external f1,f2,f3 +c +c + anns=float(nspins) + eps=1.d-3 + eta=1.d-5 + eta2=eta*eta + ot=1.d0/3.d0 + ts2=27.d0*27.d0 + t2=32.d0 + sqr3=sqrt(3.d0) + pi=3.1415926d0 + a=(4.d0/(9.d0*pi))**ot +c +c calculate rel part of self-energy +c + rsp=rsw +c + ef=1.d0/(a*rsp)**2 + xk=sqrt(1.d0+eken/ef) + wp2=4.d0*a*rsp/(3.d0*pi) + wp=sqrt(wp2) + twp=2.d0*wp + xk2=xk*xk + e=0.5d0*xk2 + xkp=xk+1.d0 + xkm=xk-1.d0 + xkpi=1.d0/xkp +c + flg=log((xkp+eta2)/(xkm+eta2)) + edxc=(1.d0-xk2)/xk*0.5d0*flg + vedx=1.5d0*wp2*(1.d0+edxc) + vsex = 0.d0 + vch = 0.d0 + vsex=0.75d0*wp2**2/xk*gauss(f2,xkm,xkp,eps) + vch1=gauss(f3,0.d0,xkp,eps) + vch2=gauss(f1,0.d0,xkpi,eps) + vch=0.75d0*wp2**2/xk*(vch1+vch2) +c + vxcrl = - ef*(vedx+vsex+vch) +c +c calculate vim, imaginary part of self energy +c + vxcim = 0.0d0 + if(wp2.ge.t2/ts2) go to 215 + c1=ts2*wp2/16.d0 + phi=acos(1.-c1) + phit=phi*ot + xkl=1.d0+2.d0/9.d0*(-1.d0+cos(phit)+sqr3*sin(phit)) + goto 216 + 215 q=(16.d0-ts2*wp2)/54.d0 + del=(ts2*wp2-t2)*wp2/4.d0 + srdel=sqrt(del) + v2=-q-srdel + v2m=abs(-q-srdel) + xkl=7.d0/9.d0+ot*((-q+srdel)**ot+sign(1.d0,v2)*v2m**ot) + 216 xkl2m=xkl**2-1.d0 + xkmm=1.+sqrt(-2.d0/3.d0+sqrt(4.d0/9.d0-4.d0*wp2+xkl2m**2)) + if(abs(xkl-xkmm).gt.1.d-4) + x write(6,221) xkl,xkmm,nb,k,rsp + 221 format(' xkl(=',e14.6,') not equal to xkmm(=',e14.6,') for ', + x ' nb,k,rs=',2i10,e20.6) + xmm=sqrt(1.d0+2.d0*wp) + if(xkl.lt.xmm) write(6,222) xkl,xmm,nb,k,rsp + 222 format(' xkl(=',e14.6,') less than xmm(=',e14.6,') for ', + x 'nb,k,rs=',2i10,e20.6) + if(xk.le.xkl) go to 260 + del1=27.d0*xk2*wp2-4.d0*(xk2-ot)**3 + if(del1.ge.0.) write(6,223) nb,k,rsp + 223 format(' discriminant del1 positive for nb,k,rs=',2i10,e20.6) + xm2=-2.d0*ot+sqrt(4.d0/9.d0-4.d0*wp2+(xk2-1.d0)**2) + c1=27.d0*xk2*wp2/(2.d0*(xk2-ot)**3) + if(c1.gt.2.) write(6,224) c1,nb,k,rsp + 224 format(' c1(=',e14.6,') gt 2. for nb,k,rs=',2i10,e20.6) + phi=acos(1.d0-c1) + phit=ot*phi + xk1=(1.d0-cos(phit)+sqr3*sin(phit))*(xk2-ot)/(3.d0*xk) + xk12=xk1*xk1 + an=xm2*(xk12*(1.d0-3.d0*wp)+6.d0*wp*(wp+xk*xk1)) + ad=xk12*(xm2+3.d0*wp*(xk2-1.d0+2.d0*wp)) +c + vxcim = ef*(3.d0*pi/8.d0*wp**3/xk*log(an/ad)) +c +c + 260 continue +c + return + end +c +c +c +C-------------------------------------------------------------- + + subroutine writewf(lxp) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + COMMON/PARAM/EFTR,GAMMA,VCON,XE,EV,E,IOUT,NAT,NDAT,NSPINS, + 1 NAS,RS(AT_),XV(AT_),YV(AT_),ZV(AT_),EXFACT(AT_),Z(AT_), + 3 LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),NAME0,CIP,EMAX,EMIN,DE,RS_OS + complex*16 VCON,XE,EV + CHARACTER*8 NSYMBL,NAME0 +c + common /pdq/ p(rd_,fl_),ps(n_),dps(n_), + * ramf(n_),pss(6),dpss(6) + complex*16 p,ps,dps,ramf,pss,dpss +c + common /fcnr/kxe, h(d_),vcons(2),r(rd_,d_),v(rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) + complex*16 vcons,v +c + common/funit/idat,iwr,iphas,iedl0,iwf + common/mtxele/ nstart,nlast,dmx(2),dmx1(2),qmx(3),qmx1(3), + $ dxdir,dxexc,nfis,nfis1,nfis2 + real*8 nfis,nfis2,nfis1 + complex*16 dmx,dmx1,qmx,qmx1,dxdir,dxexc +c + nlastl = nstart + lxp +c +c write(6,*) 'iwf,iwr,iphas,iedl0,iwf', idat,iwr,iphas,iedl0,iwf + write(iwf,*) 'energy -- xe (complex wv) -- vcon (real part ip)' + write(iwf,*) e, xe, dble(vcon) +c +c write(iwf,*) lxp, kmax(nas), (ichg(i,1),i=1,10) +c + write(iwf,*) + write(iwf,*) ' -- absorber excited regular wf for all l -- ' + write(iwf,*) +c + do 1 i=nstart,nlastl + write(iwf,*) ' l= ', i-1 + do 2 j=1,kmax(nas) + write(iwf,*) r(j,1),p(j,i)/ramf(i) +2 continue +1 continue +c + write(iwf,*) + write(iwf,*) ' -- absorber irregular wf for l less than 6 -- ' + write(iwf,*) ' radial coor --- wf ' + write(iwf,*) +c + do 3 i= 1, 6 + write(iwf,*) ' l= ', i-1 + do 4 j=1,kmax(nas) + write(iwf,*) r(j,1),p(j,i+npss) + 4 continue + 3 continue +c + return + end +c +c +C-------------------------------------------------------------- + + subroutine writeelswf +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + COMMON/PARAM/EFTR,GAMMA,VCON,XE,EV,E,IOUT,NAT,NDAT,NSPINS, + 1 NAS,RS(AT_),XV(AT_),YV(AT_),ZV(AT_),EXFACT(AT_),Z(AT_), + 3 LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),NAME0,CIP,EMAX,EMIN,DE,RS_OS + complex*16 VCON,XE,EV + CHARACTER*8 NSYMBL,NAME0 +C + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +C +c + common/eels/einc,esct,scangl,qt,lambda,eelsme(npss,npss,npss), + & p1(rdx_,npss,nef_),p2(rdx_,npss,nef_), + & p3(rdx_,npss,nef_),ramfsr1(npss,nef_), + & ramfsr2(npss,nef_),ramfsr3(npss,nef_), + & lmxels(3,ua_),p3irreg(rdx_,7),p2irreg(rdx_,7) + complex*16 eelsme,p1,p2,p3,ramfsr1,ramfsr2,ramfsr3,p3irreg, + & p2irreg + real*8 lambda +c +c + common/funit/idat,iwr,iphas,iedl0,iwf +c +c write(6,*) 'iwf,iwr,iphas,iedl0,iwf', idat,iwr,iphas,iedl0,iwf + write(iwf,*) 'energy -- xe (complex wv) -- vcon (real part ip)' + write(iwf,*) e, xe, dble(vcon) +c +c write(iwf,*) lxp, kmax(nas), (ichg(i,1),i=1,10) +c + write(iwf,*) + write(iwf,*) ' -- absorber excited regular wf for all l -- ' + write(iwf,*) +c + do i=1,lmxels(1,nas) + write(iwf,*) ' inc l= ', i-1 + do j=1,kmx(nas) + write(iwf,10) rx(j,1),p1(j,i,nas)/ramfsr1(i,nas) + enddo + enddo +c +c + do i=1,lmxels(2,nas) + write(iwf,*) ' sct l= ', i-1 + do j=1,kmx(nas) + write(iwf,10) rx(j,1),p2(j,i,nas)/ramfsr2(i,nas) + enddo + enddo +c +c + do i=1,lmxels(3,nas) + write(iwf,*) ' exc l= ', i-1 + do j=1,kmx(nas) + write(iwf,10) rx(j,1),p3(j,i,nas)/ramfsr3(i,nas) + enddo + enddo +c +c + 10 format(7e15.7) +c + write(iwf,*) + write(iwf,*) ' -- absorber irregular wf for l less than 6 -- ' + write(iwf,*) ' radial coor --- wf ' + write(iwf,*) +c + do 3 i= 1, 6 + write(iwf,*) ' l= ', i-1 + do 4 j=1,kmx(nas) + write(iwf,10) rx(j,1),p3irreg(j,i) + 4 continue + 3 continue +c + return + end +c +c +c********************************************************************** +c + subroutine scfdat (title, ifr, iz, ihole, xion,amass, beta,iprint, + 1 vcoul, srho, dgc0, dpc0, enp, eatom) +c +c single configuration dirac-fock atom code +c +c input: +c title - any name that will be written into output files. +c ifr - specify aadditional output file atom(ifr).dat +c iz - atomic number +c ihole - remove one electron from orbital #ihole. +c complete list is in subroutine getorb. +c xion - ionicity (iz-number of electrons) +c amass - mass of nucleus; 0. - for point nucleus. +c beta - thickness parameter for nuclear charge distribution +c beta=0. for uniform distribution +c iprint - if iprint>0 additional output is written into atom(ifr).dat +c output: +c vcoul - total coulomb potential (hartrees) +c srho - total charge density (bohr**-3) +c dgc0 - upper components of dirac spinors +c dpc0 - lower components of dirac spinors +c enp - energy eigenvalues (hartrees) +c eatom - total atomic energy (hartrees) + +c written by a. ankudinov, univ. of washington +c +c programming language fortran 77 +c +c based on modifications of the code ACRV of J.P. Desclaux +c [Comp Phys Comm. 9, 31 (1975)] and some subroutines from +c the FEFF code, J.J. Rehr, J. Mustre de Leon, S.I. Zabinsky +c and R.C. Albers, [J. Am. Chem. Soc 113,5135(1991) +c +c version 1 (5-22-96) +c +c********************************************************************** + + implicit double precision (a-h,o-z) + parameter ( mp = 251, ms = 30 ) +c +c save central atom dirac components, see comments below. +c + dimension dgc0(mp), dpc0(mp) + dimension vcoul(mp), srho(mp), enp(ms) + + character*(*) title + character*40 ttl + character*512 slog + common /charact/ ttl + + character*30 fname +c +c this programm uses cofcon cofdat dsordf ictime iowrdf +c lagdat messer nucdev ortdat potrdf soldir + common cg(mp,ms),cp(mp,ms),bg(10,ms),bp(10,ms),fl(ms),ibgp +c cg (cp) large (small) components +c bg (bp) development coefficients at the origin of large +c (small) component +c fl power of the first term of development limits. +c ibgp first dimension of the arrays bg and bp +c +c gg,gp are the output from soldir +c + common/comdir/cl,dz,gg(mp),ag(10),gp(mp),ap(10),bid(3*mp+30) + common/itescf/testy,rap(2),teste,nz,norb,norbsc + common/mulabk/afgk + common/inelma/nem + dimension afgk( 30, 30, 0:3) + common/messag/dlabpr,numerr + character*8 dprlab, dlabpr + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + common/scrhf1/eps(435),nre(30),ipl + common/snoyau/dvn(251),anoy(10),nuc + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim + data dprlab/' scfdat'/ + + +c +c *** copy input parameters to common blocks +c + ttl = title + lttl = istrln(title) + if (lttl.le.0) ttl='atomic data' + nz=iz + dz=nz +c +c *** desclaux standard opinion. be careful when changing. +c + nuc=11 +c +c nuc - number of points inside nucleus (suggested value 11) +c + nes=50 +c +c nes number of attempts in program soldir +c differ from desclaux nes=40 +c + niter=30 +c +c equivalent to desclaux niter=1130 +c niter =1000*n1+100*n2+n3 +c n3 is the number of iterations per orbital +c + testy=1.d-5 +c +c testy precision for the wave functions +c + hx=5.d-2 + dr(1)=exp(-8.8D0)*iz +c +c dr(1)=exp(-8.8) +c hx exponential step +c dr1 first tabulation point multiplied by nz +c desclaux dr1=0.01 correspond to iz=66 +c + teste=5.d-6 + rap(1)=1.d2 + rap(2)=1.d1 +c +c teste precision for the one-electron energies +c rap tests of precision for soldir +c + ido=1 +c +c equivalent to ido=ndep=1 +c calculate initial orbitals using thomas-fermi model ido=1 +c option to read from cards(ido=2) destroyed +c nmax=251 - set in subroutine inmuat +c scc=0.3 - set in subroutine inmuat +c *** end of desclaux standard opinion on parameters +c + if (iprint .ge. 1) then +c +c prepare file for atom output +c + write(fname,14) ifr + 14 format('atom', i2.2, '.dat') + open (unit=16, file=fname, status='unknown') +c call chopen (ios, fname, 'atom') +c call head (16) + write(16,*) ' free atom ', ifr + lttl = istrln(ttl) + if (iprint .ge. 1) write(16,40) ttl(1:lttl) + 40 format (1h1,40x,a) + endif +c +c initialize the rest of the data and calculate initial w.f. +c + jfail = 0 + ibgp = 10 + numerr = 0 + nz = iz + call inmuat (ihole, xion) +c +c iholep is the index for core hole orbital in all arrays +c for 90% of atoms iholep=ihole +c + a = - xion - 1 + call wfirdf ( en, a, nq, kap, nmax, ido, amass, beta) + + j = 1 + ind = 1 + nter = 0 + do 41 i=1, norb + 41 scw(i) = 0.D0 + test1 = testy / rap(1) + test2 = testy / rap(2) + netir = abs(niter) * norb + if (iprint .ge. 1) then + write(16,210) niter, teste, testy + 210 format (5x,'number of iterations',i4,//, + 1 5x,'precision of the energies',1pe9.2,//, + 2 23x,'wave functions ',1pe9.2,/) + write(16,220) idim, dr(1), hx + 220 format (' the integration is made on ', i3, + 1 ' points-the first is equal to ' ,f7.4,/, + 2 ' and the step-size pas = ',f7.4,/) + write(16,230) test1, nes + 230 format ('matching of w.f. with precision', 1pe9.2, + 2 ' in ',i3,' attempts ',/) + if (nuc.gt.1) write(16,250) + 250 format (1h0,30x,'finite nucleus case used'/) + endif +c +c muatco - programm to calculate angular coefficients +c + call muatco + if (numerr .ne. 0) go to 711 +c +c iteration over the number of cycles +c + 101 iort = 0 + nter = nter + 1 + if (niter .ge. 0) go to 105 +c +c orthogonalization by schmidt procedure +c + 104 call ortdat (j) + 105 method = 1 +c +c calculate lagrange parameters +c + if (nre(j).gt.0 .and. ipl.ne.0) call lagdat (j,1) +c +c calculate electron potential +c + call potrdf (j) + e = en(j) + np = idim +c +c resolution of the dirac equation +c + ifail = 0 + ainf = cg(nmax(j),j) + call soldir (en(j), fl(j), bg(1,j), bp(1,j), ainf, + 1 nq(j), kap(j), nmax(j), ifail) + if (ifail .ne. 0 .and. jfail .eq. 0) jfail = j + if (jfail .eq. j .and. ifail .eq.0 ) jfail = 0 + if (numerr.eq.0) go to 111 + if (iort.ne.0 .or. niter.lt.0) go to 711 + iort = 1 + go to 104 + + 111 sce(j) = abs((e-en(j)) / en(j)) +c +c variation of the wave function using two iterations +c + k = nmax(j) + pr = 0.D0 + do 121 i = 1, k + w = cg(i,j) - gg(i) + if (abs(w).le.abs(pr)) go to 115 + pr = w + a = cg(i,j) + b = gg(i) + 115 w = cp(i,j) - gp(i) + if (abs(w).le.abs(pr)) go to 121 + pr = w + a = cp(i,j) + b = gp(i) + 121 continue + write(slog,'(i4,i3,2(1pe11.2),2(1pd16.6),4x,a,i2)') + 1 nter, j, sce(j), pr, a, b, 'method', method + call wlog(slog,0) +c +c acceleration of the convergence +c + b = scc(j) + call cofcon (a, b, pr, scw(j)) + scc(j) = b + do 151 i = 1,k + gg(i) = b*gg(i) + a*cg(i,j) + 151 gp(i) = b*gp(i) + a*cp(i,j) + do 155 i=1,ndor + ag(i) = b*ag(i) + a*bg(i,j) + 155 ap(i) = b*ap(i) + a*bp(i,j) +c +c normalization of the wave function +c + a = dsordf (j,k,0,4,fl(j)) + a = sqrt(a) + do 171 i=1, np + cg(i,j) = gg(i) / a + 171 cp(i,j) = gp(i) / a + do 175 i=1, ndor + bg(i,j) = ag(i) / a + 175 bp(i,j) = ap(i) / a +c +c determination of the next orbital to calculate +c + if (nter.lt.norbsc .or. (ind.lt.0 .and. j.lt.norbsc) ) then + j = j+1 + go to 451 + endif + j = j+1 + pr=0.D0 + do 301 i=1, norbsc + w = abs(scw(i)) + if (w.gt.pr) then + pr = w + j = i + endif + 301 continue + if (j.gt.norbsc) j = 1 + if (pr.gt.testy) go to 421 + pr = 0.D0 + do 321 i=1, norbsc + w = abs(sce(i)) + if (w.gt.pr) then + pr = w + j = i + endif + 321 continue + if (pr.ge.teste) go to 421 + if (ind.lt.0) go to 999 + ind = -1 + j = 1 + go to 451 + + 421 ind = 1 + 451 if (nter.le.netir) go to 101 + numerr = 192011 +c +c **** number of iterations exceeded the limit +c + dlabpr = dprlab + 711 call messer + stop + 999 if (numerr .eq. 0) then + if (jfail.ne.0) then + call wlog( + 1 'failed to match lower component, results are meaningless',1) + stop + endif +c +c tabulation of the results +c + if (iprint .ge. 1) call tabrat + call etotal( kap, xnel, en, iprint, eatom) +c +c return coulomb potential +c + do 800 i=1, idim + 800 srho(i) = 0.0D0 + do 830 j=1, norb + do 830 i=1, nmax(j) + 830 srho(i) = srho(i) + xnel(j) * (cg(i,j)**2 + cp(i,j)**2) + call potslw( vcoul, srho, dr, hx, idim) + do 810 i=1, 251 + 810 vcoul(i) = vcoul(i) - nz/dr(i) +c +c return srho as density instead of 4*pi*density*r**2 +c do 860 i = 1, 251 +c srho(i) = srho(i) / (dr(i)**2) / 4. / pi +c srho(i) = srho(i) / 4. / pi +c 860 continue +c + do 870 ispinr = 1, 30 + do 852 i = 1, 251 + dgc0(i) = cg( i, ispinr) + dpc0(i) = cp( i, ispinr) + 852 continue + enp(ispinr) = en(ispinr) + 870 continue + endif + if (iprint .ge. 1) close(unit=16) + + return + end + double precision function akeato (i,j,k) +c angular coefficient by the direct coulomb integral fk +c for orbitals i and j + + implicit double precision (a-h,o-z) + common/mulabk/afgk + dimension afgk(30,30,0:3) +c +c afgk angular coefficients by integrales fk and gk +c coefficient of integral fk(i;j) is in afgk(min,max) +c and that of integral gk(i;j) is in afgk(max,min) +c max=max(i,j) min=min(i,j) +c + if (i .le. j) then + akeato=afgk(i,j,k/2) + else + akeato=afgk(j,i,k/2) + endif + return + + entry bkeato (i,j,k) +c +c angular coefficient at the exchange coulomb integral gk +c + bkeato=0.0d 00 + if (i .lt. j) then + bkeato=afgk(j,i,k/2) + elseif (i.gt.j) then + bkeato=afgk(i,j,k/2) + endif + return + end + double precision function aprdev (a,b,l) +c +c the result of this function is the coefficient of the term of +c power for the product of two polynomes, whose coefficients are +c in rows a and b +c + implicit double precision (a-h,o-z) + dimension a(10),b(10) + + aprdev=0.0d 00 + do 11 m=1,l + 11 aprdev=aprdev+a(m)*b(l+1-m) + return + end + subroutine bkmrdf (i,j,k) +c +c angular coefficients for the breit term +c i and j are the numbers of orbitals +c k is the value of k in uk(1,2) +c this programm uses cwig3j +c coefficients for magnetic interaction are in cmag +c and those for retarded term are in cret +c the order correspond to -1 0 and +1 +c + implicit double precision (a-h,o-z) + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + common/tabre/cmag(3),cret(3) + + do 12 l=1,3 + cmag(l)=0.0d 00 + 12 cret(l)=0.0d 00 + ji=2* abs(kap(i))-1 + jj=2* abs(kap(j))-1 + kam=kap(j)-kap(i) + l=k-1 + do 51 m=1,3 + if (l.lt.0) go to 51 + a=cwig3j(ji,jj,l+l,-1,1,2)**2 + if (a.eq.0.0d 00) go to 51 + c=l+l+1 + if (m-2) 14,16,17 + 14 cm=(kam+k)**2 + cz=kam*kam-k*k + cp=(k-kam)**2 + n=k + 15 l1=l+1 + am=(kam-l)*(kam+l1)/c + az=(kam*kam+l*l1)/c + ap=(l+kam)*(kam-l1)/c + d=n*(k+k+1) + go to 31 + + 16 d=k*(k+1) + cm=(kap(i)+kap(j))**2 + cz=cm + cp=cm + go to 41 + + 17 cm=(kam-l)**2 + cz=kam*kam-l*l + cp=(kam+l)**2 + n=l + c=-c + go to 15 + + 31 c= abs(c)*d + if (c.ne.0.0d 00) c=n/c + cret(1)=cret(1)+a*(am-c*cm) + cret(2)=cret(2)+(a+a)*(az-c*cz) + cret(3)=cret(3)+a*(ap-c*cp) + 41 if (d.eq.0.0d 00) go to 51 + a=a/d + cmag(1)=cmag(1)+cm*a + cmag(2)=cmag(2)+cz*(a+a) + cmag(3)=cmag(3)+cp*a + 51 l=l+1 + return + end + subroutine cofcon (a,b,p,q) +c +c acceleration of the convergence in the iterative process +c b is the part of final iteration n is a function of the error (p) +c (p) at iteration n and the error (q) at the iteration n-1. +c if the product p*q is positive b is increased by 0.1 +c zero b is unchanged +c negative b is decreased by 0.1 +c b is between 0.1 and 0.9 +c a = 1. - b +c ** at the end makes q=p +c + implicit double precision (a-h,o-z) + + if (p*q) 11,31,21 + 11 if (b .ge. 0.2D0) b = b - 0.1D0 + go to 31 + + 21 if (b .le. 0.8D0) b = b + 0.1D0 + + 31 a = 1.0D0 - b + q=p + return + end + double precision function cwig3j (j1,j2,j3,m1,m2,ient) +c +c wigner 3j coefficient for integers (ient=1) +c or semiintegers (ient=2) +c other arguments should be multiplied by ient +c + implicit double precision (a-h,o-z) + save + character*512 slog + dimension al(32),m(12) + data ini/1/,idim/31/ +c +c idim-1 is the largest argument of factorial in calculations +c + m3=-m1-m2 + if (ini) 1,21,1 +c +c initialisation of the log's of the factorials +c + 1 ini=0 + al(1)=0.0d 00 + do 11 i=1,idim + b=i + 11 al(i+1)=al(i)+ log(b) + 21 cwig3j=0.0d 00 + if (((ient-1)*(ient-2)).ne.0) go to 101 + ii=ient+ient +c +c test triangular inequalities, parity and maximum values of m +c + if (( abs(m1)+ abs(m2)).eq.0.and.mod(j1+j2+j3,ii).ne.0) go to 99 + m(1)=j1+j2-j3 + m(2)=j2+j3-j1 + m(3)=j3+j1-j2 + m(4)=j1+m1 + m(5)=j1-m1 + m(6)=j2+m2 + m(7)=j2-m2 + m(8)=j3+m3 + m(9)=j3-m3 + m(10)=j1+j2+j3+ient + m(11)=j2-j3-m1 + m(12)=j1-j3+m2 + do 41 i=1,12 + if (i.gt.10) go to 31 + if (m(i).lt.0) go to 99 + 31 if (mod(m(i),ient).ne.0) go to 101 + m(i)=m(i)/ient + if (m(i).gt.idim) go to 101 + 41 continue +c +c calculate 3j coefficient +c + max0= max(m(11),m(12),0)+1 + min0= min(m(1),m(5),m(6))+1 + isig=1 + if (mod(max0-1,2).ne.0) isig=-isig + c=-al(m(10)+1) + do 61 i=1,9 + 61 c=c+al(m(i)+1) + c=c/2.0d 00 + do 71 i=max0,min0 + j=2-i + b=al(i)+al(j+m(1))+al(j+m(5))+al(j+m(6))+al(i-m(11))+al(i-m(12)) + cwig3j=cwig3j+isig* exp(c-b) + 71 isig=-isig + if (mod(j1-j2-m3,ii).ne.0) cwig3j=-cwig3j + 99 return + 101 write(slog,'(a,6i5)') 'error in cwig3j ',j1,j2,j3,m1,m2,ient + call wlog(slog,1) + stop + end + double precision function dentfa (dr,dz,ch) +c +c analitical approximation of potential is created for electrons in +c thomas-fermi model for atom or free ion. dr distance from nucleus +c with charge dz +c ch=ionicity = number of electrons-dz-1 +c + implicit double precision (a-h,o-z) + + dentfa=0.0d 00 + if ((dz+ch).lt.1.0d-04) return + w=dr*(dz+ch)**(1.D0/3.D0) + w=sqrt(w/0.8853D0) + t=w*(0.60112D0*w+1.81061D0)+1.D0 + w=w*(w*(w*(w*(0.04793D0*w+0.21465D0)+0.77112D0)+1.39515D0)+ + 1 1.81061D0)+1D0 + dentfa=(dz+ch)*(1.0d 00-(t/w)**2)/dr + return + end + double precision function dsordf (i,j,n,jnd,a) +c +c * calculation of diff. integrals* +c integration by simpson method of the hg*(r**n) +c hg(l)=cg(l,i)*cg(l,j)+cp(l,i)*cp(l,j) if jnd=1 +c hg=expression above multiplied by dg if jnd=-1 +c hg(l)=cg(l,i)*cp(l,j) if jnd=2 +c hg=expression above multiplied by dg if jnd=-2 +c hg(l)=dg(l)*cg(l,i)+dp(l)*cp(l,j) if jnd=3 +c hg(l)=dg(l)*dg(l)+dp(l)*dp(l) if jnd=4 +c hg is constructed by calling program if jnd>=5 +c cg(l,i) large component of the orbital i +c cp(l,j) small component of the orbital j +c a is such that dg,dp or hg following the case +c behave at the origin as cte*r**a +c the integration is made as far as dr(j) for jnd>3 +c +c the development limits at the origin (used for calculation +c of integral form 0 to dr(1) ) of functions dg,dp and hg are +c supposed to be in blocks ag,ap and chg respectively +c this program utilises aprdev +c + implicit double precision (a-h,o-z) + common cg(251,30),cp(251,30),bg(10,30),bp(10,30),fl(30),ibgp + common/comdir/cl,dz,dg(251),ag(10),dp(251),ap(10),bidcom(783) + dimension hg(251),chg(10) + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim + dimension bgi(10),bgj(10),bpi(10),bpj(10) +c +c construction of the array hg +c + if (jnd.le.3) go to 11 + max0=j + b=a + go to 101 + + 11 max0= min(nmax(i),nmax(j)) + do 15 l= 1,ibgp + bgi(l) = bg(l,i) + bgj(l) = bg(l,j) + bpi(l) = bp(l,i) + 15 bpj(l) = bp(l,j) + if ( abs(jnd)-2) 21,55,101 + 21 do 31 l=1,max0 + 31 hg(l)=cg(l,i)*cg(l,j)+cp(l,i)*cp(l,j) + do 45 l=1,ndor + 45 chg(l)=aprdev(bgi,bgj,l)+aprdev(bpi,bpj,l) + go to 81 + + 55 do 61 l=1,max0 + 61 hg(l)=cg(l,i)*cp(l,j) + do 71 l=1,ndor + 71 chg(l)=aprdev(bgi,bpj,l) + 81 b=fl(i)+fl(j) + if (jnd.gt.0) go to 301 + + do 85 l=1,max0 + 85 hg(l)=hg(l)*dg(l) + do 87 l=1,ndor + 87 ap(l)=chg(l) + b=b+a + do 95 l=1,ndor + 95 chg(l)=aprdev(ap,ag,l) + go to 301 + + 101 if (jnd-4) 201,111,301 + 111 do 121 l=1,max0 + 121 hg(l)=dg(l)*dg(l)+dp(l)*dp(l) + b=b+b + do 131 l=1,ndor + 131 chg(l)=aprdev(ag,ag,l)+aprdev(ap,ap,l) + go to 301 + + 201 do 221 l=1,max0 + 221 hg(l)=dg(l)*cg(l,i)+dp(l)*cp(l,j) + b=a+fl(i) + do 241 l=1,ndor + 241 chg(l)=aprdev(bgi,ag,l)+aprdev(bpj,ap,l) +c +c integration of the hg +c + 301 dsordf=0.0d 00 + io=n+1 + do 305 l=1,max0 + 305 hg(l)=hg(l)*(dr(l)**io) + do 311 l=2,max0,2 + 311 dsordf=dsordf+hg(l)+hg(l)+hg(l+1) + dsordf=hx*(dsordf+dsordf+hg(1)-hg(max0))/3.0d 00 +c +c integral from 0 to dr(1) +c + b=b+n + do 331 l=1,ndor + b=b+1.0d 00 + 331 dsordf=dsordf+chg(l)*(dr(1)**b)/b + return + end + subroutine etotal (kap,xnel,en,iprint,eatom) +c +c combined from original subroutines tabfgk,tabbre,tabrat. +c kap quantique number "kappa" +c xnel occupation of orbitales (can be fractional) +c en one-electron energies +c fdrirk function calculating radial integrals rk +c akeato angular coefficient for integrals fk, for the +c integrals fk(i;i) gives angular coefficients multiplied by 2 +c bkeato angular coefficient for integrals gk +c coul ener(1) direct coulomb interaction +c ech ener(2) exchange coulomb interaction +c * average value of the breit hamiltonian * +c fdrocc function of the orbitals' occupations. +c bkmrdf is a programm to calculate angular coefficients +c ema ener(3) magnetic energy +c ere ener(4) retardation term +c sous programmes utilises akeato,bkeato +c fdrocc fdrirk bkmrdf +c + implicit double precision (a-h,o-z) + dimension kap(30),xnel(30),en(30) + common/itescf/testy,rap(2),teste,nz,norb,norbsc + dimension ener(4) + dimension cer(17) + common/tabre/cmag(3),cret(3) + common/inelma/nem + character*4 iner(4) + character*512 slog + data iner/'coul','ech.','mag.','ret.'/ + + do 10 i = 1,4 + 10 ener(i)=0.0d 00 + iv=0 +c +c fk integrales +c + do 40 i=1,norb + l= abs(kap(i))-1 + do 40 j=1,i + a=1.0d 00 + if (j.eq.i) a=a+a + m= abs(kap(j))-1 + kmi=2* min(l,m) + k=0 + 20 iv=iv+1 + cer(iv)=fdrirk(i,i,j,j,k) + ener(1)=ener(1)+cer(iv)*akeato(i,j,k)/a + if (iv.lt.3) go to 30 + iv=0 + 30 k=k+2 + if (k.le.kmi) go to 20 + 40 continue + iv=0 + if (norb.gt.1) then +c +c gk integrales +c + do 70 i=2,norb + i1=i-1 + do 70 j=1,i1 + l= abs(kap(i)) + m= abs(kap(j)) + k= abs(l-m) + if ((kap(i)*kap(j)).lt.0) k=k+1 + kmi=l+m-1 + 50 iv=iv+1 + cer(iv)=fdrirk(i,j,i,j,k) + ener(2) = ener(2) -cer(iv)*bkeato(i,j,k) + if (iv.lt.3) go to 60 + iv=0 + 60 k=k+2 + if (k.le.kmi) go to 50 + 70 continue + endif +c + nem=1 +c +c direct integrales +c + ik=0 + do 140 j=1,norb + jj=2* abs(kap(j))-1 + do 140 i=1,j + ji=2* abs(kap(i))-1 + k=1 + kma= min(ji,jj) + 110 ik=ik+1 + cer(ik)=fdrirk(j,j,i,i,k) + if (i.ne.j) go to 120 + call bkmrdf (j,j,k) + ener(3)=ener(3)+(cmag(1)+cmag(2)+cmag(3))*cer(ik)* + 1 fdmocc(j,j)/2.0d 00 + 120 if (ik.lt.3) go to 130 + ik=0 + 130 k=k+2 + if (k.le.kma) go to 110 + 140 continue + if (norb.gt.1) then +c +c exchange integrales +c + do 201 j=2,norb + lj= abs(kap(j)) + na=-1 + if (kap(j).gt.0) go to 121 + na=-na + lj=lj-1 + 121 jp=j-1 + do 201 l=1,jp + ll= abs(kap(l)) + nb=-1 + if (kap(l).gt.0) go to 131 + nb=-nb + ll=ll-1 + 131 b=fdmocc(j,l) + nm1= abs(lj+na-ll) + nmp1=ll+lj+nb + nmm1=ll+lj+na + np1= abs(ll+nb-lj) + k= min(nm1,np1) + kma=max(nmp1,nmm1) + if (mod(k+ll+lj,2).eq.0) k=k+1 + nb= abs(kap(j))+ abs(kap(l)) + 141 call bkmrdf (j,l,k) + do 151 i=1,3 + 151 cer(i)=0.0d 00 + if (nb.le.k.and.kap(l).lt.0.and.kap(j).gt.0) go to 161 + cer(1)=fdrirk(l,j,l,j,k) + cer(2)=fdrirk(0,0,j,l,k) + 161 if (nb.le.k.and.kap(l).gt.0.and.kap(j).lt.0) go to 171 + cer(3)=fdrirk(j,l,j,l,k) + if (cer(2).ne.0.0d 00) go to 171 + cer(2)=fdrirk(0,0,l,j,k) + 171 do 185 i=1,3 + ener(3) =ener(3) +cmag(i)*cer(i)*b + 185 ener(4) =ener(4) +cret(i)*cer(i)*b + k=k+2 + if (k.le.kma) go to 141 + 201 continue + endif +c +c total energy +c + eatom = -(ener(1)+ener(2))+ener(3)+ener(4) + do 212 j=1,norb + 212 eatom = eatom + en(j)*xnel(j) + if (iprint .ge. 1) write(16,'(a,1pd18.7)') 'etot',eatom + write(slog,'(a,1pd18.7)') 'etot',eatom + call wlog(slog,0) + do 215 i=1,4 + if (iprint .ge. 1) write(16,'(a4,1pd18.7)') iner(i),ener(i) + write(slog,'(a4,1pd18.7)') iner(i),ener(i) + 215 call wlog(slog,0) + return + end +c + double precision function fdmocc (i,j) +c +c product of the occupation numbers of the orbitals i and j +c + implicit double precision (a-h,o-z) + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + + if (j.eq.i) then + fdmocc=xnel(i)*(xnel(j)-1) + a=2* abs(kap(i)) + fdmocc=fdmocc*a/(a-1.0D0) + else + fdmocc=xnel(i)*xnel(j) + endif + return + end +c + double precision function fdrirk (i,j,l,m,k) +c +c * calculate radial integrales rk * +c rk = integral of f(r) * uk(r,s) * g(s) +c uk(r,s) = rinf**k / rsup**(k+1) rinf=min(r,s) rsup=max(r,s) +c if nem=0 f(.)=cg(.,i)*cg(.,j)+cp(.,i)*cp(.,j) +c g(.)=cg(.,l)*cg(.,m)+cp(.,l)*cp(.,m) +c if nem non zero f(.)=cg(.,i)*cp(.,j) +c g(.)=cg(.,l)*cp(.,m) +c cg (cp) large (small) componenents of the orbitales +c moreover if nem > or =0 the integration is made from 0 to infinity, +c and otherwise from 0 to r. +c this programm uses yzkrdf and dsordf +c + implicit double precision (a-h,o-z) + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + common/comdir/cl,dz,dg(251),ag(10),dp(251),ap(10),bidcom(783) +c +c comdir is used just to exchange variables between dsordf,yzkrdf,fdrirk +c + dimension hg(251) + common/inelma/nem + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim + save + + fdrirk=0.0d 00 + if (i.le.0.or.j.le.0) go to 201 + call yzkrdf (i,j,k) + nn= abs(kap(i))+ abs(kap(j)) + nn=max(nn-k,1) + a=k+1 + do 21 n=1,ndor + 21 hg(n)=0.0d 00 + do 31 n=1,ndor + if (nn.gt.ndor) go to 31 + hg(nn)=-ag(n) + 31 nn=nn+1 + do 41 n=1,ndor + 41 ag(n)=hg(n) + ag(1)=ag(1)+ap(1) + + 201 if (l.le.0.or.m.le.0) return + n=-1 + if (nem.ne.0) n=-2 + fdrirk=dsordf(l,m,-1,n,a) + return + end +c + subroutine getorb (iz, ihole, xion, norb, norbco, + 1 iholep, den, nqn, nk, xnel, xnval) +c +c Gets orbital data for chosen element. Input is iz, atomic number +c of desired element, other arguments are output. +c Feel free to change occupation numbers for element of interest. +c ival(i) is necessary only for partly nonlocal exchange model. +c iocc(i) and ival(i) can be fractional +c But you have to keep the sum of iocc(i) equal to nuclear charge. +c Also ival(i) should be equal to iocc(i) or zero. +c Otherwise you have to change this subroutine or contact authors +c for help. +c + implicit double precision (a-h, o-z) +c +c Written by Steven Zabinsky, July 1989 +c modified (20 aug 1989) table increased to at no 97 +c Recipe for final state configuration is changed. Valence +c electron occupations are added. ala 17.1.1996 + +c Table for each element has occupation of the various levels. +c The order of the levels in each array is: + +c element level principal qn (nqn), kappa qn (nk) +c 1 1s 1 -1 +c 2 2s 2 -1 +c 3 2p1/2 2 1 +c 4 2p3/2 2 -2 +c 5 3s 3 -1 +c 6 3p1/2 3 1 +c 7 3p3/2 3 -2 +c 8 3d3/2 3 2 +c 9 3d5/2 3 -3 +c 10 4s 4 -1 +c 11 4p1/2 4 1 +c 12 4p3/2 4 -2 +c 13 4d3/2 4 2 +c 14 4d5/2 4 -3 +c 15 4f5/2 4 3 +c 16 4f7/2 4 -4 +c 17 5s 5 -1 +c 18 5p1/2 5 1 +c 19 5p3/2 5 -2 +c 20 5d3/2 5 2 +c 21 5d5/2 5 -3 +c 22 5f5/2 5 3 +c 23 5f7/2 5 -4 +c 24 6s 6 -1 +c 25 6p1/2 6 1 +c 26 6p3/2 6 -2 +c 27 6d3/2 6 2 +c 28 6d5/2 6 -3 +c 29 7s 7 -1 +c + dimension den(30), nqn(30), nk(30), xnel(30), xnval(30) + dimension kappa (29) + real*8 iocc, ival + dimension iocc (97, 29), ival (97, 29) + dimension nnum (29) + character*512 slog +c +c kappa quantum number for each orbital +c k = - (j + 1/2) if l = j - 1/2 +c k = + (j + 1/2) if l = j + 1/2 +c + data kappa /-1,-1, 1,-2,-1, 1,-2, 2,-3,-1, 1,-2, 2,-3, 3, + 1 -4,-1, 1,-2, 2, -3, 3,-4,-1, 1, -2, 2,-3,-1/ +c +c principal quantum number (energy eigenvalue) +c + data nnum /1,2,2,2,3, 3,3,3,3,4, 4,4,4,4,4, + 1 4,5,5,5,5, 5,5,5,6,6, 6,6,6,7/ +c +c occupation of each level for z = 1, 97 +c + data (iocc( 1,i),i=1,29) /1,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 1,i),i=1,29) /1,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 2,i),i=1,29) /2,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 2,i),i=1,29) /2,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 3,i),i=1,29) /2,1,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 3,i),i=1,29) /0,1,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 4,i),i=1,29) /2,2,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 4,i),i=1,29) /0,2,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 5,i),i=1,29) /2,2,1,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 5,i),i=1,29) /0,2,1,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 6,i),i=1,29) /2,2,2,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 6,i),i=1,29) /0,2,2,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 7,i),i=1,29) /2,2,2,1,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 7,i),i=1,29) /0,2,2,1,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 8,i),i=1,29) /2,2,2,2,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 8,i),i=1,29) /0,2,2,2,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 9,i),i=1,29) /2,2,2,3,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 9,i),i=1,29) /0,2,2,3,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(10,i),i=1,29) /2,2,2,4,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(10,i),i=1,29) /0,2,2,4,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(11,i),i=1,29) /2,2,2,4,1, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(11,i),i=1,29) /0,0,0,0,1, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(12,i),i=1,29) /2,2,2,4,2, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(12,i),i=1,29) /0,0,0,0,2, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(13,i),i=1,29) /2,2,2,4,2, 1,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(13,i),i=1,29) /0,0,0,0,2, 1,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(14,i),i=1,29) /2,2,2,4,2, 2,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(14,i),i=1,29) /0,0,0,0,2, 2,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(15,i),i=1,29) /2,2,2,4,2, 2,1,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(15,i),i=1,29) /0,0,0,0,2, 2,1,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(16,i),i=1,29) /2,2,2,4,2, 2,2,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(16,i),i=1,29) /0,0,0,0,2, 2,2,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(17,i),i=1,29) /2,2,2,4,2, 2,3,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(17,i),i=1,29) /0,0,0,0,2, 2,3,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(18,i),i=1,29) /2,2,2,4,2, 2,4,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(18,i),i=1,29) /0,0,0,0,2, 2,4,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(19,i),i=1,29) /2,2,2,4,2, 2,4,0,0,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(19,i),i=1,29) /0,0,0,0,0, 0,0,0,0,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(20,i),i=1,29) /2,2,2,4,2, 2,4,0,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(20,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(21,i),i=1,29) /2,2,2,4,2, 2,4,1,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(21,i),i=1,29) /0,0,0,0,0, 0,0,1,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(22,i),i=1,29) /2,2,2,4,2, 2,4,2,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(22,i),i=1,29) /0,0,0,0,0, 0,0,2,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(23,i),i=1,29) /2,2,2,4,2, 2,4,3,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(23,i),i=1,29) /0,0,0,0,0, 0,0,3,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(24,i),i=1,29) /2,2,2,4,2, 2,4,4,1,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(24,i),i=1,29) /0,0,0,0,0, 0,0,4,1,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(25,i),i=1,29) /2,2,2,4,2, 2,4,4,1,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(25,i),i=1,29) /0,0,0,0,0, 0,0,4,1,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(26,i),i=1,29) /2,2,2,4,2, 2,4,4,2,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(26,i),i=1,29) /0,0,0,0,0, 0,0,4,2,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(27,i),i=1,29) /2,2,2,4,2, 2,4,4,3,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(27,i),i=1,29) /0,0,0,0,0, 0,0,4,3,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(28,i),i=1,29) /2,2,2,4,2, 2,4,4,4,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(28,i),i=1,29) /0,0,0,0,0, 0,0,4,4,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(29,i),i=1,29) /2,2,2,4,2, 2,4,4,6,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(29,i),i=1,29) /0,0,0,0,0, 0,0,4,6,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(30,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(30,i),i=1,29) /0,0,0,0,0, 0,0,4,6,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(31,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 1,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(31,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 1,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(32,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(32,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(33,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,1,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(33,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,1,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(34,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,2,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(34,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,2,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(35,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,3,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(35,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,3,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(36,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(36,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,4,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(37,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,0,0,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(37,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(38,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,0,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(38,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(39,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,1,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(39,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,1,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(40,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,2,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(40,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,2,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(41,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,0,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(41,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,0,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(42,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,1,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(42,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,1,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(43,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,1,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(43,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,1,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(44,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,3,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(44,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,3,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(45,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,4,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(45,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,4,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(46,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(46,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,6,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(47,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(47,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,6,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(48,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(48,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,6,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(49,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,1,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(49,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,1,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(50,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(50,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(51,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,1,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(51,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,1,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(52,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,2,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(52,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,2,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(53,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,3,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(53,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,3,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(54,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,4,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(54,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,4,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(55,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,4,0, 0,0,0,1,0, 0,0,0,0/ + data (ival(55,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,1,0, 0,0,0,0/ + data (iocc(56,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(56,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(57,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,4,1, 0,0,0,2,0, 0,0,0,0/ + data (ival(57,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,1, 0,0,0,2,0, 0,0,0,0/ + data (iocc(58,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,2, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(58,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,2, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(59,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,3, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(59,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,3, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(60,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,4, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(60,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,4, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(61,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,5, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(61,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,5, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(62,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(62,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(63,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 1,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(63,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 1,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(64,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 1,2,2,4,1, 0,0,0,2,0, 0,0,0,0/ + data (ival(64,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 1,0,0,0,1, 0,0,0,2,0, 0,0,0,0/ + data (iocc(65,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 3,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(65,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 3,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(66,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 4,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(66,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 4,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(67,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 5,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(67,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 5,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(68,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 6,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(68,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 6,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(69,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 7,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(69,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 7,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(70,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(70,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 8,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(71,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,1, 0,0,0,2,0, 0,0,0,0/ + data (ival(71,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,1, 0,0,0,2,0, 0,0,0,0/ + data (iocc(72,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,2, 0,0,0,2,0, 0,0,0,0/ + data (ival(72,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,2, 0,0,0,2,0, 0,0,0,0/ + data (iocc(73,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,3, 0,0,0,2,0, 0,0,0,0/ + data (ival(73,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,3, 0,0,0,2,0, 0,0,0,0/ + data (iocc(74,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 0,0,0,2,0, 0,0,0,0/ + data (ival(74,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 0,0,0,2,0, 0,0,0,0/ + data (iocc(75,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 1,0,0,2,0, 0,0,0,0/ + data (ival(75,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 1,0,0,2,0, 0,0,0,0/ + data (iocc(76,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 2,0,0,2,0, 0,0,0,0/ + data (ival(76,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 2,0,0,2,0, 0,0,0,0/ + data (iocc(77,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 3,0,0,2,0, 0,0,0,0/ + data (ival(77,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 3,0,0,2,0, 0,0,0,0/ + data (iocc(78,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 5,0,0,1,0, 0,0,0,0/ + data (ival(78,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 5,0,0,1,0, 0,0,0,0/ + data (iocc(79,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,1,0, 0,0,0,0/ + data (ival(79,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 6,0,0,1,0, 0,0,0,0/ + data (iocc(80,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,0, 0,0,0,0/ + data (ival(80,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 6,0,0,2,0, 0,0,0,0/ + data (iocc(81,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,1, 0,0,0,0/ + data (ival(81,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,1, 0,0,0,0/ + data (iocc(82,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 0,0,0,0/ + data (ival(82,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 0,0,0,0/ + data (iocc(83,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 1,0,0,0/ + data (ival(83,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 1,0,0,0/ + data (iocc(84,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 2,0,0,0/ + data (ival(84,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 2,0,0,0/ + data (iocc(85,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 3,0,0,0/ + data (ival(85,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 3,0,0,0/ + data (iocc(86,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,0,0,0/ + data (ival(86,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 4,0,0,0/ + data (iocc(87,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,0,0,1/ + data (ival(87,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,1/ + data (iocc(88,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,0,0,2/ + data (ival(88,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,2/ + data (iocc(89,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,1,0,2/ + data (ival(89,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,1,0,2/ + data (iocc(90,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,2,0,2/ + data (ival(90,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,2,0,2/ + data (iocc(91,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,2,0,2,2, 4,1,0,2/ + data (ival(91,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,2,0,0,0, 0,1,0,2/ + data (iocc(92,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,3,0,2,2, 4,1,0,2/ + data (ival(92,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,3,0,0,0, 0,1,0,2/ + data (iocc(93,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,4,0,2,2, 4,1,0,2/ + data (ival(93,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,4,0,0,0, 0,1,0,2/ + data (iocc(94,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,6,0,2,2, 4,0,0,2/ + data (ival(94,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,6,0,0,0, 0,0,0,2/ + data (iocc(95,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,6,1,2,2, 4,0,0,2/ + data (ival(95,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,6,1,0,0, 0,0,0,2/ + data (iocc(96,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,6,2,2,2, 4,0,0,2/ + data (ival(96,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,6,2,0,0, 0,0,0,2/ + data (iocc(97,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,6,3,2,2, 4,0,0,2/ + data (ival(97,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,6,3,0,0, 0,0,0,2/ + + if (iz .lt. 1 .or. iz .ge. 97) then + 8 format(' Atomic number ', i5, ' not available.') + write(slog,8) iz + call wlog(slog,1) + stop + endif + + ion = nint(xion) + delion=xion-ion + + + index = iz - ion + ilast = 0 + iscr = 0 + iion = 0 + iholep = ihole +c +c find last occupied orbital (ilast) and iion for delion.ge.0 +c + do 30 i=29,1,-1 + if (iion.eq.0 .and. dble(iocc(index,i)).gt.delion) iion=i + if (ilast.eq.0 .and. iocc(index,i).gt.0) ilast=i + 30 continue +c open(unit=91,file='getorbtuo.dat',status='unknown') +c iz=29 + if (ihole.eq.0) go to 11 + if (ihole.gt.0 .and. iocc(index,ihole) .lt. 1 .or. + 1 (ihole.eq.ilast .and. iocc(index,ihole)-dble(delion).lt.1) ) then +c call wlog(' Cannot remove an electron from this level',1) + write(6,*)' Cannot remove an electron from level =', ihole + write(6,*) ' stop in getorb ' + stop 'GETORB-1' + endif + 11 continue +c +c the recipe for final state atomic configuration is changed +c from iz+1 prescription, since sometimes it changed occupation +c numbers in more than two orbitals. This could be consistent +c only with s02=0.0. New recipe remedy this deficiency. +c +c find where to put screening electron +c + index1 = index + 1 + do 10 i = 1, 29 + 10 if (iscr.eq.0 .and. (iocc(index1,i)-iocc(index,i)).gt.0.5) iscr=i +c +c special case of hydrogen like ion +c if (index.eq.1) iscr=2 +c +c find where to add or subtract charge delion (iion). +c if (delion .ge. 0) then +c removal of electron charge +c iion is already found +c + if (delion .lt. 0) then +c +c addition of electron charge +c + iion = iscr +c +c except special cases +c + if (ihole.ne.0 .and. + 1 iocc(index,iscr)+1-dble(delion).gt.2*abs(kappa(iscr))) then + iion = ilast + if (ilast.eq.iscr .or. iocc(index,ilast)-dble(delion).gt. + 1 2*abs(kappa(ilast)) ) iion = ilast + 1 + endif + endif + + norb = 0 + do 20 i = 1, 29 + if (iocc(index,i).gt.0 .or. (i.eq.iscr .and. ihole.gt.0) + 1 .or. (i.eq.iion .and. iocc(index,i)-dble(delion).gt.0)) then + if (i.ne.ihole .or. iocc(index,i).ge.1) then + norb = norb + 1 + nqn(norb) = nnum(i) + nk(norb) = kappa(i) + xnel(norb) = dble(iocc(index,i)) + if (i.eq.ihole) then + xnel(norb) = xnel(norb) - 1 + iholep = norb + endif + if (i.eq.iscr .and. ihole.gt.0) xnel(norb)=xnel(norb)+1 + xnval(norb)= dble(ival(index,i)) + if (i.eq.ihole .and. xnval(norb).ge.1) + 1 xnval(norb) = xnval(norb) - 1 + if (i.eq.iscr .and. ihole.gt.0) + 1 xnval(norb) = xnval(norb) + 1 + if (i.eq.iion) xnel(norb) = xnel(norb) - delion + if (i.eq.iion) xnval(norb) = xnval(norb) - delion + den(norb) = 0.0D0 + endif + endif + 20 continue + norbco = norb +c +c check that all occupation numbers are within limits +c + do 50 i = 1, norb + if ( xnel(i).lt.0 .or. xnel(i).gt.2*abs(nk(i)) .or. + 1 xnval(i).lt.0 .or. xnval(i).gt.2*abs(nk(i)) ) then + write (slog,55) i + 55 format(' error in getorb.f. Check occupation number for ', + 1 i3, '-th orbital. May be a problem with ionicity.') + call wlog(slog,1) + stop + endif + 50 continue +c do 60 i=1,norb +c60 xnval(i) = 0.0d0 +c60 xnval(i) = xnel(i) + + return + end + + subroutine inmuat (ihole, xionin) + implicit double precision (a-h,o-z) + common/itescf/testy,rap(2),teste,nz,norb,norbsc +c the meaning of common variables is described below + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) +c + dimension xnval(30) +c +c en one-electron energies +c scc factors for acceleration of convergence +c scw precisions of wave functions +c sce precisions of one-electron energies +c nmax number of tabulation points for orbitals +c + common/scrhf1/eps(435),nre(30),ipl +c +c eps non diagonal lagrange parameters +c nre distingue: - the shell is closed (nre <0) +c the shell is open (nre>0) +c - the orbitals in the integral rk if abs(nre) > or =2 +c ipl define the existence of lagrange parameters (ipl>0) +c + common/snoyau/dvn(251),anoy(10),nuc +c +c dvn nuclear potential +c anoy development coefficients at the origin of nuclear potential +c this development is supposed to be written anoy(i)*r**(i-1) +c nuc index of nuclear radius (nuc=1 for point charge) +c + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim + data ideps/435/ + + ndor=10 + + + + call getorb( nz, ihole, xionin, norb, norbsc, + 1 iholep, en, nq, kap, xnel, xnval) + xk=0 + do 411 i=1,norb + 411 xk=xk+xnel(i) + if ( abs(nz-xionin-xk) .gt. 0.001D0) then + call wlog('check number of electrons in getorb.f',1) + stop + endif + norbsc=norb +c +c nz atomic number noi ionicity (nz-number of electrons) +c norb number of orbitals +c xnel(i) number of electrons on orbital i. +c first norbsc orbitals will be determined selfconsistently, +c the rest of orbitals are orthogonolized if iorth is non null, +c and their energies are those on cards if iene is non null +c or otherwise are the values obtained from solving dirac equation +c nes number of attempts in program soldir +c nuc number of points inside nucleus (11 by default) +c + do 171 i=1,ideps + 171 eps(i)=0.0d 00 + + idim = 251 + if (mod(idim,2) .eq. 0) idim=idim-1 + + ipl=0 +c +c ipl=0 means no orbitals with the same kappa and no +c orthogonalization needed. Thus it will remain zero only +c for hydrogen atom. +c + do 401 i=1,norb + nre(i)=-1 + llq= abs(kap(i)) + l=llq+llq + if (kap(i).lt.0) llq=llq-1 + if (llq.lt.0.or.llq.ge.nq(i).or.llq.gt.3) then + call wlog('kappa out of range, check getorb.f',1) + stop + endif + nmax(i)=idim + scc(i)=0.3d0 + if (xnel(i) .lt. l) nre(i)=1 + do 385 j=1,i-1 + if (kap(j).ne.kap(i)) go to 385 + if (nre(j).gt.0.or.nre(i).gt.0) ipl=ipl+1 + 385 continue + 401 continue + return + end +c + subroutine intdir(gg,gp,ag,ap,ggmat,gpmat,en,fl,agi,api,ainf,max0) +c +c solution of the inhomogenios dirac equation +c gg gp initially exchage terms, at the time of return - wave functions +c ag and ap development coefficients of gg and gp +c ggmat gpmat values at the matching point for the inward integration +c en one-electron energy +c fl power of the first development term at the origin +c agi (api) initial values of the first development coefficients +c at the origin of a large (small) component +c ainf initial value for large component at point dr(max0) +c - at the end of tabulation of gg gp +c + implicit double precision (a-h,o-z) + save + common/comdir/cl,dz,bid1(522),dv(251),av(10),bid2(522) + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim + common/subdir/ell,fk,ccl,imm,nd,node,mat + common/messag/dlabpr,numerr + character*8 dlabpr + dimension gg(251),gp(251),ag(10),ap(10),coc(5),cop(5),dg(5),dp(5) + data cop/2.51d+02,-1.274d+03,2.616d+03,-2.774d+03,1.901d+03/, + 1coc/-1.9d+01,1.06d+02,-2.64d+02,6.46d+02,2.51d+02/, + 2cmixn/4.73d+02/,cmixd/5.02d+02/,hxd/7.2d+02/,npi/5/,icall/0/ +c +c numerical method is a 5-point predictor-corrector method +c predicted value p(n) = y(n-1) + c * somme de i=1,5 cop(i)*y'(n-i) +c corrected value c(n) = y(n-1) + c * somme de i=1,4 coc(i)*y'(n-i) +c + coc(5)*p'(n) +c final value y(n) = cmix*c(n) + (1.-cmix)*p(n) +c cmix=cmixn/cmixd +c + if (icall.eq.0) then + icall=1 + c=cmixn/cmixd + a=1.0d 00-c + cmc=c*coc(5) + f=coc(1) + do 1 j=2,npi + g=coc(j) + coc(j)=c*f+a*cop(j) + 1 f=g + coc(1)=c*cop(1) + endif + c=hx/hxd + ec=en/cl + ag(1)=agi + ap(1)=api + if (imm) 81,15,26 +c +c search for the second sign change point +c + 15 mat=npi + j=1 + 16 mat=mat+2 + if (mat.ge.np) then +c +c i had trouble with screened k-hole for la, for f-electrons. +c below i still define matching point if one electron energy is +c not less than -1ev. ala, january 1995 +c + if (ec .gt. -0.0003D0) then + mat = np - 12 + go to 25 + endif + numerr=56011 +c +c * fail to find matching point +c if you got this error with fractional ionicity, try +c slightly different.(xion=xion+0.01) +c + return + endif + f=dv(mat)+ell/(dr(mat)*dr(mat)) + f=(f-ec)*j + if (f) 25,25,16 + 25 j=-j + if (j.lt.0) go to 16 + if (mat .ge. np-npi) mat=np-12 +c +c initial values for the outward integration +c + 26 do 35 j=2,ndor + k=j-1 + a=fl+fk+k + b=fl-fk+k + ep=a*b+av(1)*av(1) + f=(ec+ccl)*ap(k)+ap(j) + g=ec*ag(k)+ag(j) + do 31 i=1,k + f=f-av(i+1)*ap(j-i) + 31 g=g-av(i+1)*ag(j-i) + + ag(j)=(b*f+av(1)*g)/ep + 35 ap(j)=(av(1)*f-a*g)/ep + do 41 i=1,npi + gg(i)=0.0d 00 + gp(i)=0.0d 00 + dg(i)=0.0d 00 + dp(i)=0.0d 00 + do 41 j=1,ndor + a=fl+j-1 + b=dr(i)**a + a=a*b*c + gg(i)=gg(i)+b*ag(j) + gp(i)=gp(i)+b*ap(j) + dg(i)=dg(i)+a*ag(j) + 41 dp(i)=dp(i)+a*ap(j) + i=npi + k=1 + ggmat=gg(mat) + gpmat=gp(mat) +c +c integration of the inhomogenious system +c + 51 cmcc=cmc*c + + 55 continue + a=gg(i)+dg(1)*cop(1) + b=gp(i)+dp(1)*cop(1) + i=i+k + ep=gp(i) + eg=gg(i) + gg(i)=a-dg(1)*coc(1) + gp(i)=b-dp(1)*coc(1) + do 61 j=2,npi + a=a+dg(j)*cop(j) + b=b+dp(j)*cop(j) + gg(i)=gg(i)+dg(j)*coc(j) + gp(i)=gp(i)+dp(j)*coc(j) + dg(j-1)=dg(j) + 61 dp(j-1)=dp(j) + f=(ec-dv(i))*dr(i) + g=f+ccl*dr(i) + gg(i)=gg(i)+cmcc*(g*b-fk*a+ep) + gp(i)=gp(i)+cmcc*(fk*b-f*a-eg) + dg(npi)=c*(g*gp(i)-fk*gg(i)+ep) + dp(npi)=c*(fk*gp(i)-f*gg(i)-eg) + if (i.ne.mat) go to 55 + + if (k.lt.0) go to 999 + a=ggmat + ggmat=gg(mat) + gg(mat)=a + a=gpmat + gpmat=gp(mat) + gp(mat)=a + if (imm.ne.0) go to 81 +c +c initial values for inward integration +c + a=test1* abs(ggmat) + if (ainf.gt.a) ainf=a + max0=np+2 + 73 a=7.0d+02/cl + 75 max0=max0-2 + if ((max0+1).le.(mat+npi)) then + numerr=138021 +c +c *the last tabulation point is too close to the matching point +c + return + endif + if (((dv(max0)-ec)*dr(max0)*dr(max0)).gt.a) go to 75 + + 81 c=-c + a=- sqrt(-ec*(ccl+ec)) + if ((a*dr(max0)).lt.-1.7d+02) go to 73 + b=a/(ccl+ec) + f=ainf/ exp(a*dr(max0)) + if (f.eq.0.0d 00) f=1.0d 00 + do 91 i=1,npi + j=max0+1-i + gg(j)=f* exp(a*dr(j)) + gp(j)=b*gg(j) + dg(i)=a*dr(j)*gg(j)*c + 91 dp(i)=b*dg(i) + i=max0-npi+1 + k=-1 + go to 51 + + 999 return + end +c + subroutine lagdat (ia,iex) +c +c * non diagonal lagrange parameteres * +c lagrange parameters involving orbital ia if ia is positive +c all lagrange parameters are calculated if ia is negative or zero +c contribution of the exchange terms is omitted if iex=0 +c this program uses akeato(bkeato) fdrirk multrk +c + implicit double precision (a-h,o-z) + common/itescf/testy,rap(2),teste,nz,norb,norbsc + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1 nq(30),kap(30),nmax(30) + common/scrhf1/eps(435),nre(30),ipl + + i1= max(ia,1) + idep=1 + if (ia.gt.0) go to 15 + 11 idep=i1+1 + 15 ji1=2* abs(kap(i1))-1 + do 201 i2=idep,norbsc + if (i2.eq.i1.or.kap(i2).ne.kap(i1)) go to 201 + if (nre(i1).lt.0.and.nre(i2).lt.0) go to 201 +c +c the following line was included to handle the case of single +c electron in 2 s-shells +c probably need to use schmidt orthogonalization in this case +c + if (xnel(i1).eq.xnel(i2)) go to 201 + d=0.0d 00 + do 101 l=1,norbsc + k=0 + jjl=2* abs(kap(l))-1 + kma= min(ji1,jjl) + 41 a=akeato(l,i1,k)/xnel(i1) + b=a-akeato(l,i2,k)/xnel(i2) + c=b + if (a.ne.0.0d 00) c=c/a + if ( abs(c).lt.1.0d-07) go to 51 + d=d+b*fdrirk(l,l,i1,i2,k) + 51 k=k+2 + if (k.le.kma) go to 41 + if (iex.eq.0) go to 101 + kma=(ji1+jjl)/2 + k= abs(jjl-kma) + if ((kap(i1)*kap(l)).lt.0) k=k+1 + 61 a=bkeato(l,i2,k)/xnel(i2) + b=a-bkeato(l,i1,k)/xnel(i1) + c=b + if (a.ne.0.0d 00) c=c/a + if ( abs(c).lt.1.0d-07) go to 71 + d=d+b*fdrirk(i1,l,i2,l,k) + 71 k=k+2 + if (k.le.kma) go to 61 + 101 continue + i= min(i1,i2) + j= max(i1,i2) + eps(i+((j-1)*(j-2))/2)=d/(xnel(i2)-xnel(i1)) + 201 continue + if (ia.gt.0) go to 999 + i1=i1+1 + if (i1.lt.norbsc) go to 11 + 999 return + end +c + subroutine messer +c +c prints error message on the output device +c + implicit double precision (a-h,o-z) + common/messag/dlabpr,numerr + character*8 dlabpr + character*512 slog + + ilig=numerr/1000 + ier=numerr-1000*ilig + write(slog,'(a,i6,a,i6,a,a8)') 'error number ',ier, + 1 ' detected on a line ',ilig,'in the program',dlabpr + call wlog(slog,1) + return + end +c + subroutine muatco +c +c * angular coefficients * +c sous programmes utilises cwig3j +c + implicit double precision (a-h,o-z) + common/itescf/testy,rap(2),teste,nz,norb,norbsc + common/mulabk/afgk + dimension afgk(30,30,0:3) + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + + do 511 i=1,30 + do 511 j=1,30 + do 511 k=0,3 + 511 afgk(i,j,k)=0.0d 00 + do 701 i=1,norb + li= abs(kap(i))*2-1 + do 701 j=1,i + lj= abs(kap(j))*2-1 + kmax=(li+lj)/2 + kmin= abs(li-lj)/2 + if ((kap(i)*kap(j)).lt.0) kmin=kmin+1 +c +c calculate a_k(i,j) +c + m=0 + if (j.eq.i) m=1 + afgk(j,i,0)=afgk(j,i,0)+xnel(i)*(xnel(j)-m) +c +c calculate b_k(i,j) +c + b=afgk(j,i,0) + if (j.eq.i) then + a=li + b=-b*(a+1.0d 00)/a + kmin = kmin+2 + endif + do 675 k = kmin, kmax,2 + afgk(i,j,k/2)=b*(cwig3j(li,k*2,lj,1,0,2)**2) + 675 continue + 701 continue + return + end +c + subroutine nucdev (a,epai,av,dr,dv,dz,hx,nuc,np,ndor,dr1) +c +c * construction of nuclear potential * +c a atomic mass (negative or null for the point charge) +c epai parameter of the fermi density distribution +c (negative or null for uniform distribution), which is +c cte / (1. + exp((r-rn)/epai) ) +c with nuclear radius rn= 2.2677e-05 * (a**(1/3)) +c av coefficients of the development at the origin of nuclear potential +c dr tabulation points +c dv nuclear potential +c dz nuclear charge +c hx exponential step +c nuc index of the nuclear radius +c np number of tabulation points +c ndor number of the coefficients for development at the origin +c the declared below arguments are saved, dr1 is the first +c + implicit double precision (a-h,o-z) + dimension av(10),dr(251),dv(251),at(251) +c +c calculate radial mesh +c + if (a.le.1.0d-01) then + nuc=1 + else +c dr(nuc)=nuclear radius +c + a=dz*(a**(1.D0/3.D0))*2.2677d-05 + b=a/ exp(hx*(nuc-1)) + if (b.le.dr1) then + dr1=b + else +c +c increase value of nuc +c + b=log(a/dr1)/hx + nuc=3+2*int(b/2.0D0) + if (nuc.ge.np) stop 'dr1 too small' +c +c index of atomic radius larger than dimension of dr +c + dr1=a*exp(-(nuc-1)*hx) + endif + endif + + dr(1)=dr1/dz + do 181 l=2,np + 181 dr(l)=dr(1)* exp(hx*(l-1)) + + if (ndor.lt.5) then +c +c * there should be at least 5 development coefficients +c + call wlog('stopped in programm nucdev, ndor should be > 4.',1) + stop + endif +c +c calculate nuclear potential on calculated radial mesh +c + do 11 i=1,ndor + 11 av(i)=0.0d 00 + if (epai.le.0.0D0) then + do 15 i=1,np + 15 dv(i)=-dz/dr(i) + if (nuc.le.1) then + av(1)=-dz + else + av(2)=-3.0d 00*dz/(dr(nuc)+dr(nuc)) + av(4)=-av(2)/(3.0d 00*dr(nuc)*dr(nuc)) + l=nuc-1 + do 25 i=1,l + 25 dv(i)=av(2)+av(4)*dr(i)*dr(i) + endif + else + b= exp(-dr(nuc)/epai) + b=1.0d 00/(1.0d 00+b) + av(4)=b + av(5)=epai*b*(b-1.0d 00) + if (ndor.le.5) go to 45 + at(1)=1.0d 00 + at(2)=1.0d 00 + nf=1 + do 41 i=6,ndor + n=i-4 + nf=n*nf + dv(1)=n*at(1) + n1=n+1 + dv(n1)=1.0d 00 + do 35 j=2,n + 35 dv(j)=(n-j+2)*at(j-1)+(n-j+1)*at(j) + do 37 j=1,n1 + m=n+1-j + l=1 + if (mod(j,2).eq.0) l=-l + av(i)=av(i)+l*dv(j)*(b**m) + 37 at(j)=dv(j) + 41 av(i)=b*av(i)*(epai**n)/nf + 45 do 47 i=1,np + b=1.0d 00+ exp((dr(i)-dr(nuc))/epai) + if ((b*av(4)).gt.1.0d+15) go to 51 + dv(i)=dr(i)*dr(i)*dr(i)/b + 47 l=i + 51 if (l.ge.(np-1)) l=np-2 + k=l+1 + do 55 i=k,np + 55 dv(i)=0.0d 00 + at(1)=0.0d 00 + at(2)=0.0d 00 + k=2 + do 61 i=4,ndor + k=k+1 + do 58 j=1,2 + 58 at(j)=at(j)+av(i)*(dr(j)**k)/k + av(i)=av(i)/(k*(k-1)) + 61 av(2)=av(2)+av(i)*(dr(1)**k) + a=hx/2.4d+01 + b=a*1.3d+01 + k=l+1 + do 71 i=3,k + 71 at(i)=at(i-1)+b*(dv(i-1)+dv(i))-a*(dv(i-2)+dv(i+1)) + dv(l)=at(l) + do 75 i=k,np + 75 dv(i)=dv(l) + e= exp(hx) + c=1.0d 00/(e*e) + i=l-1 + 83 dv(i)=dv(i+1)/e+b*(at(i+1)/e+at(i))-a*(at(i+2)*c+at(i-1)*e) + i=i-1 + if (i-1) 85,85,83 + 85 dv(1)=dv(3)*c+hx*(at(1)+4.0d 00*at(2)/e+at(3)*c)/3.0d 00 + av(2)=(av(2)+dv(1))/dr(1) + a=-dz/dv(l) + do 95 i=4,ndor + 95 av(i)=-a*av(i) + av(2)=a*av(2) + do 97 i=1,np + 97 dv(i)=a*dv(i)/dr(i) + endif + + return + end +c + subroutine ortdat (ia) +c +c * orthogonalization by the schmidt procedure* +c the ia orbital is orthogonalized toa all orbitals of the same +c symmetry if ia is positive, otherwise all orbitals of the same +c symmetry are orthogonalized +c this program uses dsordf +c + implicit double precision (a-h,o-z) + common cg(251,30),cp(251,30),bg(10,30),bp(10,30),fl(30),ibgp + common/comdir/cl,dz,dg(251),ag(10),dp(251),ap(10),bidcom(783) +c dg,ag,dp,ap are used to exchange data only with dsordf + common/itescf/testy,rap(2),teste,nz,norb,norbsc + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim + + m=norb + l= max(ia,1) + if (ia.gt.0) go to 11 + 5 m=l + l=l+1 + if (l.gt.norb) go to 999 + 11 do 15 i=1,idim + dg(i)=0.0d 00 + 15 dp(i)=0.0d 00 + maxl=nmax(l) + do 21 i=1,maxl + dg(i)=cg(i,l) + 21 dp(i)=cp(i,l) + do 25 i=1,ndor + ag(i)=bg(i,l) + 25 ap(i)=bp(i,l) + do 51 j=1,m + if (j.eq.l.or.kap(j).ne.kap(l)) go to 51 + max0=nmax(j) + a=dsordf (j,j,0,3,fl(l)) + do 41 i=1,max0 + dg(i)=dg(i)-a*cg(i,j) + 41 dp(i)=dp(i)-a*cp(i,j) + do 45 i=1,ndor + ag(i)=ag(i)-a*bg(i,j) + 45 ap(i)=ap(i)-a*bp(i,j) + maxl= max(maxl,max0) + 51 continue + max0= maxl + nmax(l)=max0 + a=dsordf (l,max0,0,4,fl(l)) + a= sqrt(a) + do 71 i=1,max0 + cg(i,l)=dg(i)/a + 71 cp(i,l)=dp(i)/a + do 75 i=1,ndor + bg(i,l)=ag(i)/a + 75 bp(i,l)=ap(i)/a + if (ia.le.0) go to 5 + 999 return + end +c + subroutine potrdf (ia) +c +c this programm uses akeato(bkeato),aprdev,multrk,yzkrdf +c + implicit double precision (a-h,o-z) + common cg(251,30),cp(251,30),bg(10,30),bp(10,30),fl(30),ibgp + common/comdir/cl,dz,dg(251),ag(10),dp(251),ap(10),dv(251),av(10), + 2 eg(251),ceg(10),ep(251),cep(10) +c dg,dp to get data from yzkrdf, dv,eg,ep -output for soldir + dimension at(251),bt(251) + common/itescf/testy,rap(2),teste,nz,norb,norbsc + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + common/scrhf1/eps(435),nre(30),ipl + common/snoyau/dvn(251),anoy(10),nuc + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim + dimension bgj(10),bpj(10) + + do 9 i=1,ndor + cep(i)=0.0d 00 + ceg(i)=0.0d 00 + 9 av(i)=anoy(i) + do 11 i=1,idim + at(i)=0.0d 00 + bt(i)=0.0d 00 + ep(i)=0.0d 00 + eg(i)=0.0d 00 + 11 dv(i)=0.0d 00 +c +c coulomb terms +c + jia=2* abs(kap(ia))-1 + k=0 + 21 do 25 i=1,idim + 25 dg(i)=0.0d 00 + do 31 i=1,ndor + 31 ag(i)=0.0d 00 + max0=0 + do 51 j=1,norb + do 33 i = 1,10 + bgj(i) = bg(i,j) + 33 bpj(i) = bp(i,j) + m=2* abs(kap(j))-1 + if (k.gt.m) go to 51 + a=akeato(ia,j,k)/xnel(ia) + if (a.eq.0.0d 00) go to 51 + m=nmax(j) + do 35 i=1,m + 35 dg(i)=dg(i)+a*(cg(i,j)*cg(i,j)+cp(i,j)*cp(i,j)) + n=2* abs(kap(j))-k + l=ndor+2-n + if (l.le.0) go to 51 + do 41 i=1,l + m=n-2+i + 41 ag(m)=ag(m)+a*(aprdev(bgj,bgj,i)+ + 1 aprdev(bpj,bpj,i)) + 51 max0= max(max0,nmax(j)) + call yzkrdf (0,max0,k) + do 61 i=1,ndor + l=k+i+3 + if (l.gt.ndor) go to 61 + av(l)=av(l)-ag(i) + 61 continue + do 81 i=1,idim + 81 dv(i)=dv(i)+dg(i) + k=k+2 + if (k.le.ndor) av(k)=av(k)+ap(1) + if (k.lt.jia) go to 21 +c +c exchange terms +c + if (method.eq.0) go to 411 + do 201 j=1,norb + if (j-ia) 105,201,105 + 105 max0=nmax(j) + jj=2* abs(kap(j))-1 + kma=(jj+jia)/2 + k= abs(jj-kma) + if ((kap(j)*kap(ia)).lt.0) k=k+1 + + 111 a=bkeato(j,ia,k)/xnel(ia) + if (a.eq.0.0d 00) go to 151 + call yzkrdf (j,ia,k) + do 121 i=1,max0 + eg(i)=eg(i)+a*dg(i)*cg(i,j) + 121 ep(i)=ep(i)+a*dg(i)*cp(i,j) + n=k+1+ abs(kap(j))- abs(kap(ia)) + if (n.gt.ndor) go to 141 + do 135 i=n,ndor + ceg(i)=ceg(i)+bg(i+1-n,j)*a*ap(1) + 135 cep(i)=cep(i)+bp(i+1-n,j)*a*ap(1) + 141 i=2* abs(kap(j))+1 + if (i.gt.ndor) go to 151 + do 143 i = 1,10 + bgj(i) = bg(i,j) + 143 bpj(i) = bp(i,j) + do 145 n=i,ndor + ceg(n)=ceg(n)-a*aprdev(ag,bgj,n+1-i) + 145 cep(n)=cep(n)-a*aprdev(ag,bpj,n+1-i) + 151 k=k+2 + if (k.le.kma) go to 111 + 201 continue + 411 if (ipl.eq.0) go to 511 + do 481 j=1,norbsc + if (kap(j).ne.kap(ia).or.j.eq.ia) go to 481 + if (nre(j).lt.0.and.nre(ia).lt.0) go to 481 + m= max(j,ia) + i= min(j,ia)+((m-1)*(m-2))/2 + a=eps(i)*xnel(j) + max0=nmax(j) + do 461 i=1,max0 + at(i)=at(i)+a*cg(i,j) + 461 bt(i)=bt(i)+a*cp(i,j) + do 471 i=1,ndor + ceg(i)=ceg(i)+bg(i,j)*a + 471 cep(i)=cep(i)+bp(i,j)*a + 481 continue +c +c addition of nuclear potential and division of potentials and +c their development limits by speed of light +c + 511 do 527 i=1,ndor + av(i)=av(i)/cl + cep(i)=cep(i)/cl + 527 ceg(i)=ceg(i)/cl + do 531 i=1,idim + dv(i)=(dv(i)/dr(i)+dvn(i))/cl + ep(i)=(ep(i)+bt(i)*dr(i))/cl + 531 eg(i)=(eg(i)+at(i)*dr(i))/cl + return + end +c + subroutine potslw (dv,d,dr,dpas,np) +c +c coulomb potential uses a 4-point integration method +c dv=potential; d=density; dp=bloc de travail; dr=radial mesh +c dpas=exponential step; +c np=number of points +c ********************************************************************** +c + implicit double precision (a-h,o-z) + save + dimension dv(251), d(251), dp(251), dr(251) + das=dpas/24.0D0 + do 10 i=1,np + 10 dv(i)=d(i)*dr(i) + dlo=exp(dpas) + dlo2=dlo*dlo + dp(2)=dr(1)*(d(2)-d(1)*dlo2)/(12.0D0*(dlo-1.0D0)) + dp(1)=dv(1)/3.0D0-dp(2)/dlo2 + dp(2)=dv(2)/3.0D0-dp(2)*dlo2 + j=np-1 + do 20 i=3,j + 20 dp(i)=dp(i-1)+das*(13.0D0*(dv(i)+dv(i-1))-(dv(i-2)+dv(i+1))) + dp(np)=dp(j) + dv(j)=dp(j) + dv(np)=dp(j) + do 30 i=3,j + k=np+1-i + 30 dv(k)=dv(k+1)/dlo+das*(13.0D0*(dp(k+1)/dlo+dp(k))-(dp(k+2)/dlo2+dp + 1 (k-1)*dlo)) + dv(1)=dv(3)/dlo2+dpas*(dp(1)+4.0D0*dp(2)/dlo+dp(3)/dlo2)/3.0D0 + do 40 i=1,np + 40 dv(i)=dv(i)/dr(i) + return + end +c + subroutine soldir (en,fl,agi,api,ainf,nq,kap,max0,ifail) +c +c resolution of the dirac equation +c p' - kap*p/r = - ( en/cl-v )*g - eg/r +c g' + kap*g/r = ( 2*cl+en/cl-v )*p + ep/r +c at the origin v approximately is -z/(r*cl) due to the point nucleus +c en one-electron energy in atomic units and negative +c fl power of the first term in development at the origin +c agi (api) initial values of the first development coefficient +c at the origin of the large(small)component +c ainf initial value for the large component at the point dr(max0) +c nq principal quantum number kap quantum number kappa +c max0 the last point of tabulation of the wave function +c this programm uses intdir +c + implicit double precision (a-h,o-z) + save + common/comdir/cl,dz,gg(251),ag(10),gp(251),ap(10),dv(251),av(10), + 2eg(251),ceg(10),ep(251),cep(10) +c +c gg,gp -output, dv,eg,ep - input +c + dimension hg(251),agh(10), + 1hp(251),aph(10),bg(251),bgh(10),bp(251),bph(10) +c +c cl speed of light (approximately 137.037 in atomic units) +c dz nuclear charge +c gg (gp) large (small) component +c hg,hp,bg et bp working space +c dv direct potential (v) eg and ep exchange potentials +c ag,ap,agh,aph,bgh,bph,av,ceg and cep are respectively the +c development coefficients for gg,gp,hg,hp,bg,bp,dv,eg et ep +c + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim +c +c hx exponential step +c dr radial mesh +c test1 precision for the matching the small component if method=1 +c test2 precision for the normalisation if method=2 +c ndor number of terms for the developments at the origin +c np maximum number of the tabulation points +c nes maximum number of attempts to ajust the small component +c method at the initial time distinguish the homoginious (method=0) +c from inhomoginious system. at the end is the index of method used. +c idim dimension of the block dr +c + common/subdir/ell,fk,ccl,imm,nd,node,mat +c +c ell fk*(fk+1)/ccl fk=kap ccl=cl+cl +c imm a flag for the determination of matching point +c nd number of nodes found node number of nodes to be found +c mat index of the matching point +c + common/messag/dlabpr,numerr + character*8 dprlab,dlabpr, drplab +c +c at the time of return numerr should be zero if integration is correct, +c otherwise numerr contains the number of instruction, which +c indicate the sourse and reason for abnornal return. +c + character*512 slog +c + data dprlab/' soldir'/,drplab/' intdir'/ + dlabpr=dprlab + enav=1.0d 00 + ainf= abs(ainf) + ccl=cl+cl + iex=method + if (method.le.0) method=1 +c +c notice that below iex=0,1 and method=1,2 only. +c this was used to simplify block structure of program. ala 11/22/94 +c + fk=kap + if (av(1).lt.0.0d 00.and.kap.gt.0) api=-agi*(fk+fl)/av(1) + if (av(1).lt.0.0d 00.and.kap.lt.0) api=-agi*av(1)/(fk-fl) + ell=fk*(fk+1.0d 00)/ccl + node=nq- abs(kap) + if (kap.lt.0) node=node+1 + emin=0.0D0 + do 91 i=1,np + a=(ell/(dr(i)*dr(i))+dv(i))*cl + if (a.lt.emin) emin=a + 91 continue + if (emin .ge. 0.0D0) then + numerr=75011 +c +c *potential is apparently positive +c + return + endif + if (en.lt.emin) en=emin*0.9d 00 + edep=en + + 101 numerr=0 + test=test1 + if (method.gt.1) test=test2 + einf=1.0d 00 + esup=emin + en=edep + ies=0 + nd=0 + 105 jes=0 + 106 modmat=0 + imm=0 + if ( abs((enav-en)/en).lt.1.0d-01) imm=1 + enav=en +c +c integration of the inhomogenious system +c + 107 do 111 i=1,idim + gg(i)=eg(i) + 111 gp(i)=ep(i) + do 115 i=2,ndor + ag(i)=ceg(i-1) + 115 ap(i)=cep(i-1) + call intdir (gg,gp,ag,ap,ggmat,gpmat,en,fl,agi,api,ainf,max0) + if (numerr.ne.0) then + dlabpr=drplab + return + endif + if (iex.ne.0) go to 141 +c +c match large component for the homogenios system(method=0) +c + a=ggmat/gg(mat) + do 135 i=mat,max0 + gg(i)=a*gg(i) + 135 gp(i)=a*gp(i) + j=mat + go to 215 +c +c integration of the homogenios system +c + 141 do 151 i=1,idim + hg(i)=0.0d 00 + 151 hp(i)=0.0d 00 + do 155 i=1,ndor + agh(i)=0.0d 00 + 155 aph(i)=0.0d 00 + imm=1 + if (method.eq.1) imm=-1 + call intdir (hg,hp,agh,aph,hgmat,hpmat,en,fl,agi,api,ainf,max0) +c +c match the large component for inhomogenious system(method=1) +c + a=gg(mat)-ggmat + if (method.lt.2) then + b=-a/hg(mat) + else + b=gp(mat)-gpmat + ah=hpmat*hg(mat)-hgmat*hp(mat) + if (ah.eq.0.0d 00) go to 263 + c=(b*hg(mat)-a*hp(mat))/ah + b=(b*hgmat-a*hpmat)/ah + do 165 i=1,ndor + ag(i)=ag(i)+c*agh(i) + 165 ap(i)=ap(i)+c*aph(i) + j=mat-1 + do 168 i=1,j + gg(i)=gg(i)+c*hg(i) + 168 gp(i)=gp(i)+c*hp(i) + endif + do 173 i=mat,max0 + gg(i)=gg(i)+b*hg(i) + 173 gp(i)=gp(i)+b*hp(i) + + if (method.ge.2) then +c +c integration of the system derived from disagreement in energy +c + do 175 i=2,ndor + bgh(i)=ag(i-1)/cl + 175 bph(i)=ap(i-1)/cl + do 177 i=1,max0 + bg(i)=gg(i)*dr(i)/cl + 177 bp(i)=gp(i)*dr(i)/cl + call intdir (bg,bp,bgh,bph,bgmat,bpmat,en,fl,agi,api,ainf,max0) +c +c match both components for inhomogenious system (method=2) +c + f=bg(mat)-bgmat + g=bp(mat)-bpmat + a=(g*hg(mat)-f*hp(mat))/ah + g=(g*hgmat-f*hpmat)/ah + do 181 i=1,j + bg(i)=bg(i)+a*hg(i) + 181 bp(i)=bp(i)+a*hp(i) + do 182 i=1,ndor + bgh(i)=bgh(i)+a*agh(i) + 182 bph(i)=bph(i)+a*aph(i) + do 183 i=mat,max0 + bg(i)=bg(i)+g*hg(i) + 183 bp(i)=bp(i)+g*hp(i) +c +c calculate the norm +c + call norm(b,hp,dr,gg,gp,ag,ap,method,hx,ndor, + 1 gpmat,fl,max0,mat) +c +c correction to the energy (method=2) +c + do 186 i=1,max0 + 186 hg(i)=(gg(i)*bg(i)+gp(i)*bp(i))*dr(i) + ah=0.0d 00 + c=0.0d 00 + do 187 i=2,max0,2 + 187 ah=ah+hg(i)+hg(i)+hg(i+1) + ah=hx*(ah+ah+hg(1)-hg(max0))/3.0d 00+hg(1)/(fl+fl+1.0d 00) + f=(1.0d 00-b)/(ah+ah) + c=1.0d 00-b + do 191 i=1,max0 + gg(i)=gg(i)+f*bg(i) + 191 gp(i)=gp(i)+f*bp(i) + do 195 i=1,ndor + ag(i)=ag(i)+f*bgh(i) + 195 ap(i)=ap(i)+f*bph(i) + endif +c +c search for the maximum of the modulus of large component +c + a=0.0d 00 + bgh(1)=b + bph(1)=ah + do 211 i=1,max0 + g=gg(i)*gg(i) + if (g.le.a) go to 211 + a=g + j=i + 211 continue + if (j.gt.mat .and. modmat.eq.0) then + modmat=1 + mat=j + if (mod(mat,2).eq.0) mat=mat+1 + imm=1 + if (mat.lt.(max0-10)) go to 107 + + mat=max0-12 + j=mat + if (mod(mat,2).eq.0) mat=mat+1 + write(slog,'(a,i4,a,i4)') ' warning mat=',mat,' max0=',max0 + call wlog(slog,1) + endif +c +c this case can happen due to bad starting point in scf procedure. +c ignore this warning unless you are getting it at final norb calls of +c soldir. redirected by ala 11/21/94. +c numerr=220021 +c * impossible matching point +c go to 899 + +c compute number of nodes +c + 215 nd=1 + j= max(j,mat) + do 231 i=2,j + if (gg(i-1).eq.0.0d 00) go to 231 + if ((gg(i)/gg(i-1)).le.0.0d 00) nd=nd+1 + 231 continue + + if (nd-node) 251,305,261 + 251 esup=en + if (einf.lt.0.0d 00) go to 271 + en=en*8.0d-01 + if ( abs(en).gt.test1) go to 285 + numerr=238031 +c *zero energy + go to 899 + + 261 einf=en + if (esup.gt.emin) go to 271 + 263 en=en*1.2d 00 + if (en.gt.emin) go to 285 + numerr=245041 +c +c *energy is lower than the minimum of apparent potential +c + go to 899 + + 271 if ( abs(einf-esup).gt.test1) go to 281 + numerr=249051 +c +c *the upper and lower limits of energy are identical +c + go to 899 + + 281 en=(einf+esup)/2.0d 00 + + 285 jes=jes+1 + if (jes.le.nes) go to 106 +c +c *number of attempts to find good number of nodes is over the limit +c this case can happen due to bad starting point in scf procedure. +c ignore this warning unless you are getting it at final norb calls of +c soldir +c + call wlog('warning jes>nes',1) + ifail=1 +c +c *redirected by ala 11/21/94. +c numerr=255061 +c go to 899 +c +c calculation of the norm +c + 305 call norm(b,hp,dr,gg,gp,ag,ap,method,hx,ndor, + 1 gpmat,fl,max0,mat) + if (method.eq.1) then +c +c correction to the energy (method=1) +c + c=gpmat-gp(mat) + f=gg(mat)*c*cl/b + if (gpmat.ne.0.0d 00) c=c/gpmat + endif + + en=en+f + g= abs(f/(en-f)) + 371 if ((en.ge.0 .or. g.gt.2.0d-01) .or. + 1 (abs(c).gt.test .and. (en.lt.esup.or.en.gt.einf))) then +c +c try smaller step in enrgy under above conditions +c + f=f/2.0d 00 + g=g/2.0d 00 + en=en-f + if (g.gt.test1) go to 371 + numerr=29071 +c +c *zero energy +c + go to 899 + endif + + if ( abs(c).gt.test) then + ies=ies+1 + if (ies.le.nes) go to 105 + ifail=1 + call wlog('warning: iteration stopped because ies=nes',1) +c +c everything is fine unless you are getting this message +c on the latest stage selfconsistent process. +c just stopped trying to match lower component +c because number of trials exceeded limit. +c lines below were commented out. ala 11/18/94 +c + endif +c +c numerr=298081 +c *number of attempts to match the lower component is over the limit +c go to 899 +c +c divide by a square root of the norm, and test the sign of w.f. +c + b= sqrt(b) + c=b + if ((ag(1)*agi).lt.0.0d 00.or.(ap(1)*api).lt.0.0d 00) c=-c + do 711 i=1,ndor + ag(i)=ag(i)/c + 711 ap(i)=ap(i)/c + if ((gg(1)*agi).lt.0.0d 00.or.(gp(1)*api).lt.0.0d 00) b=-b + do 721 i=1,max0 + gg(i)=gg(i)/b + 721 gp(i)=gp(i)/b + if (max0.ge.np) return + j=max0+1 + do 741 i=j,np + gg(i)=0.0d 00 + 741 gp(i)=0.0d 00 +c +c if everything o'k , exit is here. +c + return +c +c abnormal exit is here, if method.ne.1 +c + 899 if (iex.eq.0 .or. method.eq.2) go to 999 + method=method+1 + go to 101 + + 999 return + end +c + subroutine norm(b,hp,dr,gg,gp,ag,ap,method,hx,ndor, + 1 gpmat,fl,max0,mat) +c +c calculate norm b. this part of original code was used twice, +c causing difficult block structure. so it was rearranged into +c separate subroutine. ala +c + implicit double precision (a-h, o-z) + dimension hp(251),dr(251),gg(251),gp(251),ag(10),ap(10) + + b=0.0d 00 + do 311 i=1,max0 + 311 hp(i)=dr(i)*(gg(i)*gg(i)+gp(i)*gp(i)) + if (method.ne.1) go to 315 + hp(mat)=hp(mat)+dr(mat)*(gpmat**2-gp(mat)**2)/2.0d 00 + 315 do 321 i=2,max0,2 + 321 b=b+hp(i)+hp(i)+hp(i+1) + b=hx*(b+b+hp(1)-hp(max0))/3.0d 00 + do 325 i=1,ndor + g=fl+fl+i + g=(dr(1)**g)/g + do 325 j=1,i + 325 b=b+ag(j)*g*ag(i+1-j)+ap(j)*g*ap(i+1-j) + return + end + +C FUNCTION ISTRLN (STRING) Returns index of last non-blank +C character. Returns zero if string is +C null or all blank. + + FUNCTION ISTRLN (STRING) + CHARACTER*(*) STRING + CHARACTER BLANK, TAB + PARAMETER (BLANK = ' ', TAB = ' ') + +C there is a tab character here ^ + +C -- If null string or blank string, return length zero. + + ISTRLN = 0 + IF (STRING (1:1) .EQ. CHAR(0)) RETURN + IF (STRING .EQ. ' ') RETURN + +C -- Find rightmost non-blank character. + + ILEN = LEN (STRING) + DO 20 I = ILEN, 1, -1 + IF (STRING(I:I).NE.BLANK .AND. STRING(I:I).NE.TAB) GOTO 30 + 20 CONTINUE + 30 ISTRLN = I + + RETURN + END + + subroutine tabrat +c +c tabulation of the results +c do identifications of orbitals +c nmax number of tabulation points for wave function +c this programm uses dsordf +c + implicit double precision (a-h,o-z) + common/itescf/testy,rap(2),teste,nz,norb,norbsc + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + common /charact/ ttl + character*40 ttl + character*2 titre(30) + character*2 ttire(9) + dimension at(8),mbi(8) + parameter (zero=0) + data ttire /'s ', 'p*', 'p ', 'd*', 'd ', 'f*', 'f ','g*', 'g '/ +c + do 110 i=1,norb + if (kap(i) .gt. 0) then + j=2*kap(i) + else + j=-2*kap(i)-1 + endif + titre(i)=ttire(j) + 110 continue +c +c tabulation of number of points and of average values of +c r**n (n=6,4,2,1,-1,-2,-3) +c + do 201 i=2,8 + 201 mbi(i)=8-i-i/3-i/4+i/8 + lttl = istrln(ttl) + write(16,11) ttl(1:lttl) + 11 format (10x,a) + write(16,*) + 1'number of electrons nel and average values of r**n in a.u.' + write(16,2061) (mbi(k),k=2,8) + 2061 format (4x,'nel',' n=',7(i2,8x)) + do 251 i=1,norb + llq= abs(kap(i))-1 + j=8 + if (llq.le.0) j=7 + do 241 k=2,j + 241 at(k)=dsordf(i,i,mbi(k),1, zero) + 251 write(16,2071) nq(i),titre(i),xnel(i),(at(k),k=2,j) + 2071 format(i2,a2,f7.3,7(1pe10.3)) +c +c overlap integrals +c + if (norb.le.1) return + write(16,11) ttl(1:lttl) + write(16,321) + 321 format(10x,'overlap integrals') + do 351 i=1,norb-1 + do 331 j=i+1,norb + if (kap(j).ne.kap(i)) go to 331 + at(1)=dsordf(i,j,0,1, zero) + write(16,2091) nq(i),titre(i),nq(j),titre(j),at(1) + 331 continue + 351 continue + 2091 format (4x,i3,a2,i3,a2,f14.7) + return + end +c + subroutine wfirdf (en,ch,nq,kap,nmax,ido,amass,beta) +c +c calculate initial orbiatls from integration of dirac equation +c cg (cp) large (small) radial components +c bg (bp) development coefficients at the origin of cg (cp) +c en one-electron energies +c fl power of the first term of development at the origin +c ch ionicity (nuclear charge - number of electrons) +c nq principal quantum number +c kap quantum number "kappa" +c nmax number of tabulation points for the orbitals +c ibgp first dimension of the arrays bg and bp +c this programmes utilises nucdev,dentfa,soldir et messer +c + implicit double precision (a-h,o-z) + common cg(251,30),cp(251,30),bg(10,30),bp(10,30),fl(30),ibgp + dimension en(30),nq(30),kap(30),nmax(30) + common/comdir/cl,dz,dg(251),ag(10),dp(251),ap(10), + 1dv(251),av(10),eg(251),ceg(10),ep(251),cep(10) + common/itescf/testy,rap(2),teste,nz,norb,norbsc + common/inelma/nem + common/messag/dlabpr,numerr + character*8 dlabpr + character*512 slog + common/snoyau/dvn(251),anoy(10),nuc + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim +c +c speed of light in atomic units +c + cl=1.370373d+02 +c +c make r-mesh and calculate nuclear potential +c hx exponential step +c dr1 first tabulation point multiplied by nz +c + dr1=dr(1) + call nucdev (amass, beta,anoy,dr,dvn,dz,hx,nuc,idim,ndor,dr1) +c +c notice that here nuc=1, +c unless you specified nonzero nuclear mass in nucdev.f +c + a=(dz/cl)**2 + if (nuc.gt.1) a=0.0d 00 + do 11 j=1,norb + b=kap(j)*kap(j)-a + 11 fl(j)= sqrt(b) +c +c calculate potential from thomas-fermi model +c + do 21 i=1,idim + 21 dv(i)=(dentfa(dr(i),dz,ch)+dvn(i))/cl + if (numerr.ne.0) return + do 51 i=1,idim + eg(i)=0.0d 00 + 51 ep(i)=0.0d 00 + do 61 i=1,ibgp + ceg(i)=0.0d 00 + cep(i)=0.0d 00 + 61 av(i)=anoy(i)/cl + av(2)=av(2)+dentfa(dr(nuc),dz,ch)/cl + test1=testy/rap(1) + b=test1 +c +c resolution of the dirac equation to get initial orbitals +c + if (ido.ne.1) then + call wlog('only option ido=1 left',1) + ido = 1 + endif +c +c here was a piece to read orbitals from cards +c + do 281 j=1,norb + bg(1,j)=1.0d 00 + i=nq(j)- abs(kap(j)) + if (kap(j).lt.0) i=i-1 + if (mod(i,2).eq.0) bg(1,j)=-bg(1,j) + if (kap(j).lt.0) go to 201 + bp(1,j)=bg(1,j)*cl*(kap(j)+fl(j))/dz + if (nuc.gt.1) bg(1,j)=0.0d 00 + go to 211 + + 201 bp(1,j)=bg(1,j)*dz/(cl*(kap(j)-fl(j))) + if (nuc.gt.1) bp(1,j)=0.0d 00 + 211 np=idim + en(j)=-dz*dz/nq(j)*nq(j) + method=0 + call soldir + 1 (en(j),fl(j),bg(1,j),bp(1,j),b,nq(j),kap(j),nmax(j),0) + + if (numerr.eq.0) go to 251 + call messer + write(slog,'(a,2i3)') + 1 'soldir failed in wfirdf for orbital nq,kappa ',nq(j),kap(j) + call wlog(slog,1) + go to 281 + + 251 do 261 i=1,ibgp + bg(i,j)=ag(i) + 261 bp(i,j)=ap(i) + do 271 i=1,np + cg(i,j)=dg(i) + 271 cp(i,j)=dp(i) + 281 continue + nem=0 + return + end +c + subroutine wlog (string,iprint) + character*(*) string +c +c This output routine is used to replace the PRINT statement +c for output that "goes to the terminal", or to the log file. +c If you use a window based system, you can modify this routine +c to handle the running output elegantly. +c Handle carriage control in the string you pass to wlog. +c +c The log file is also written here, hard coded here. +c +c The log file is unit 11. The log file is opened in the +c main program, program feff. +c +c make sure not to write trailing blanks +c + + 10 format (a) + + il = istrln (string) + if (il .eq. 0) then + if(iprint.eq.1) print 10 + write(11,10) + else + if(iprint.eq.1) print 10, string(1:il) + write(11,10) string(1:il) + endif + return + end +c + subroutine yzkrdf (i,j,k) +c +c * calculate function yk * +c yk = r * integral of f(s)*uk(r,s) +c uk(r,s) = rinf**k/rsup**(k+1) rinf=min(r,s) rsup=max(r,s) +c f(s)=cg(s,i)*cg(s,j)+cp(s,i)*cp(s,j) if nem=0 +c f(s)=cg(s,i)*cp(s,j) if nem is non zero +c f(s) is constructed by the calling programm if i < or =0 +c in the last case a function f (lies in the block dg) is supposedly +c tabulated untill point dr(j), and its' devlopment coefficients +c at the origin are in ag and the power in r of the first term is k+2 + +c the output functions yk and zk are in the blocks dp and dg. +c at the origin yk = cte * r**(k+1) - developement limit, +c cte lies in ap(1) and development coefficients in ag. +c this programm uses aprdev and yzkteg +c + implicit double precision (a-h,o-z) + common cg(251,30),cp(251,30),bg(10,30),bp(10,30),fl(30),ibgp + common/comdir/cl,dz,dg(251),ag(10),dp(251),ap(10),bidcom(783) + dimension chg(10) + common/ratom1/xnel(30),en(30),scc(30),scw(30),sce(30), + 1nq(30),kap(30),nmax(30) + common/tabtes/hx,dr(251),test1,test2,ndor,np,nes,method,idim + common/inelma/nem + dimension bgi(10),bgj(10),bpi(10),bpj(10) +c + if (i.le.0) go to 51 +c +c construction of the function f +c + do 5 l= 1,ibgp + bgi(l) = bg(l,i) + bgj(l) = bg(l,j) + bpi(l) = bp(l,i) + 5 bpj(l) = bp(l,j) + id= min(nmax(i),nmax(j)) + ap(1)=fl(i)+fl(j) + if (nem.ne.0) go to 31 + do 11 l=1,id + 11 dg(l)=cg(l,i)*cg(l,j)+cp(l,i)*cp(l,j) + do 21 l=1,ndor + 21 ag(l)=aprdev(bgi,bgj,l)+aprdev(bpi,bpj,l) + go to 55 + + 31 do 35 l=1,id + 35 dg(l)=cg(l,i)*cp(l,j) + do 41 l=1,ndor + 41 ag(l)=aprdev(bgi,bpj,l) + go to 55 +c + 51 ap(1)=k+2 + id=j + 55 call yzkteg (dg,ag,dp,chg,dr,ap(1),hx,k,ndor,id,idim) + return + end +c + subroutine yzkteg (f,af,g,ag,dr,ap,h,k,nd,np,idim) +c +c calculation of yk(r)=zk(r)+ r**(k+1) * integral from r to +c infinity of f(u) * u**(-k-1) +c zk(r) = r**(-k) * integral from 0 to r of f(u) * u**k + +c at the origin f(r)=sum from i=1 to nd of af(i)*r**(ap+i-1) +c dr tabulation points h exponential step +c np number of tabulation points for f +c idim dimension of the blocks f,g and dr + +c at the origin yk=cte*r**(k+1)-developement limit +c the constant for yk lies in ap +c output functions yk and zk lie in f and g, and their +c development coefficients at the origin in af and ag. + +c integration from point to point by a 4 points method. +c integral from r to r+h = h*(-f(r-h)+13*f(r)+13*f(r+h)-f(r+h+h))/24 +c + implicit double precision (a-h,o-z) + dimension f(251),af(10),g(251),ag(10),dr(251) +c +c initialisation and development coefficients of yk +c + np= min(np,idim-2) + b=ap + ap=0.0d 00 + g(1)=0.0d 00 + g(2)=0.0d 00 + do 15 i=1,nd + b=b+1.0d 00 + ag(i)=af(i)/(b+k) + if (af(i).ne.0.0d 00) then + c=dr(1)**b + g(1)=g(1)+ag(i)*c + g(2)=g(2)+ag(i)*(dr(2)**b) + af(i)=(k+k+1)*ag(i)/(b-k-1) + ap=ap+af(i)*c + endif + 15 continue + do 21 i=1,np + 21 f(i)=f(i)*dr(i) + np1=np+1 + f(np1)=0.0d 00 + f(np1+1)=0.0d 00 +c +c calcualation of zk +c + eh= exp(h) + e=eh**(-k) + b=h/2.4d+01 + c=1.3d+01*b + ee=e*e*b + b=b/e + do 51 i=3,np1 + 51 g(i)=g(i-1)*e+(c*(f(i)+f(i-1)*e)-(f(i-2)*ee+f(i+1)*b)) +c +c calcualation of yk +c + f(np)=g(np) + do 61 i=np1,idim + 61 f(i)=f(i-1)*e + i=k+k+1 + b=i*b*eh + ee=i*ee/(eh*eh) + e=e/eh + c=i*c + do 71 i=np-1,2,-1 + 71 f(i)=f(i+1)*e+(c*(g(i)+g(i+1)*e)-(g(i+2)*ee+g(i-1)*b)) + ee=e*e + c=8.0d 00*c/1.3d+01 + f(1)=f(3)*ee+c*(g(3)*ee+4.0d 00*e*g(2)+g(1)) + ap=(ap+f(1))/(dr(1)**(k+1)) + return + end +c + subroutine llmesh +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' +c include 'msxasc3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $ n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + common /fcnr/kxe, h(d_),vcons(2),r(rd_,d_),v(rd_,sd_), + $ ichg(10,d_),kplace(at_),kmax(at_) + complex*16 v,vcons +c + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +C + COMMON /LLM/ ALPHA, BETA +c + character*8 name0 ,nsymbl !added 29/3/2013 +c + common /param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + + complex*16 vcon,xe,ev +c + logical do_r_in +c +c-------------------------------------------------------- +c +c write(69,*) ' in sub cont_sub nat = ', nat +C +C CONSTRUCT LINEAR-LOG MESH +C + DO_R_IN = .FALSE. +C + DO N = 1, NDAT +C + ZAT = FLOAT(NZ(N)) + IF(ZAT.EQ.0.0) THEN + X0 = 9.0 +C X0 = 10.0 + ELSE + X0 = 9.0 + LOG(ZAT) +C X0 = 10.0 + LOG(ZAT) + ENDIF + RKMX = R(KMAX(N),N) + DPAS = 0.1/RKMX + IF(DPAS.GT.0.02) DPAS = 0.02 + ALPHA = 0.5 + BETA = 1.0 + RHO_1 = -BETA*X0 + R_SUB = RS(N) + XMAX = ALPHA*R_SUB + BETA*LOG(R_SUB) + KMX(N) = NINT ( (XMAX + X0 + DPAS) / DPAS ) + IF(KMX(N).GT.RDX_) THEN + WRITE(6,*) + & 'INCREASE PARAMETER RDX_. IT SHOULD BE AT LEAST ', KMX(N) + CALL EXIT + ENDIF + NR = KMX(N) + KPLX(N) = KMX(N)-3 +C +C CHECK IN LLMESH +c write(6,'(2i5,4e15.6)') n,kmx(n),rkmx,r_sub,xmax,rho_1 +c flush(6) +C + CALL LINLOGMESH ( I_END, HX(N), X(1,N), RX(1,N), DO_R_IN, + & KMX(N), KPLX(N), NR, RHO_1, R_SUB, R_IN, + & ALPHA, BETA ) +c +c if(n.eq.ndat) then + +c if(n.eq.ndat) write(6,*) (x(i,n), rx(i,n), i=1,kmx(n)) +c endif +C +c print *, ' inside llmesh loop ', kmx(n) +c do i = 1, kmx(n) +c write(69,*) x(i,n), rx(i,n) +c print *, x(i,n), rx(i,n) +c enddo +c + ENDDO +c +c---------------------------------------------------------- +c + return + end +c + subroutine linlogmesh ( i_end, drho, rho, r_real, do_r_in, + & kmax, kplace, nr, rho_1, r_sub, r_in, + & alpha, beta ) +! +! Set up log + linear radial mesh. +! +! rho = alpha * r_real + beta * log ( r_real ) +! +! rho_i = rho_{i-1} + drho +! +! +! i_end : point at inscribed sphere, for outersphere not used always 0. +! drho : constant step in loglinear space +! rho : log + linear mesh with constant step. +! r_real : real radial mesh correponding to the step of loglinear mesh +! do_r_in : option for outer sphere +! kmax : three points after kplace +! kplace : point on the bounding sphere where the Wronskian is estimated. +! nr : number of radial mesh points +! rho_1 : the first point in loglinear space +! r_sub : radius of bounding sphere in loglinear space, r_sub => rho(kplace) +! r_in : +! alpha : parameter for linear part +! beta : parameter for log part + +c implicit double precision (a-h,o-z) + +!...input +! logical, intent ( in ) :: do_r_in +! integer, intent ( in ) :: nr, kmax, kplace +! real ( kind = double ), intent ( in ) :: rho_1, r_sub, r_in, alpha, beta + +!...output +! integer, intent ( out ) :: i_end +! real ( kind = double ), intent ( out ) :: drho +! real ( kind = double ), intent ( out ), dimension ( : ) :: rho, r_real + +!...local +! logical :: check +! integer :: i, k +! real ( kind = double ) :: rn, rhon, epsilon +c + implicit real*8 (a-h,o-z) +c + dimension rho(kmax), r_real(kmax) +c + logical do_r_in, check + + myrank = 0 + dzero = 0.0 + check = .false. +c check = .true. + + rho ( kplace ) = alpha * r_sub + beta * log ( r_sub ) + + rho ( 1 ) = rho_1 + drho = ( rho ( kplace ) - rho ( 1 ) ) / real ( kmax - 4 ) + + rho ( kmax ) = rho ( kplace ) + 3.00 * drho +! +! write(6,*) rho(1), rho(kmax), drho +! write(6,*) ' ** ' + +! if ( myrank .eq. 0 ) then +! write ( unit = 6, fmt = * ) " alpha =", alpha, " beta ", beta +! write ( unit = 6, fmt = * ) "rho_1 =", rho ( 1 ), & +! & " rho ( kplace ) =", rho ( kplace ), " rho ( kmax ) = ", rho ( kmax ) +! write ( unit = 6, fmt = * ) "drho =", drho, " nr =", nr +! end if + +! + do i = 2, nr + + rho ( i ) = rho ( i - 1 ) + drho + + end do +! +!.....Solve non-linear equation by Newton method +! + rhon = rho ( kplace ) + r_real ( kplace ) = r_sub +! rn = ( rhon - beta * log ( rhon ) ) / alpha ! correction 2nd April 2013 + rn = ( rhon - beta * log ( r_sub ) ) / alpha +! + do i = kplace - 1, 1, - 1 + + k = 0 +! + do +! +! MPI +! + if ( check .and. myrank .eq. 0 ) then + + write ( unit = 98, fmt = * ) i, rn + + end if +! +! MPI + +! + if ( rn .eq. dzero ) then +! +! MPI +! + if ( myrank .eq. 0 ) then + + write ( unit = 6, fmt = * ) "Error occurred at radialmesh!", + & "rn = 0" + + end if +! +! MPI +! + stop + + end if +! + + epsilon = ( alpha * rn + beta * log ( rn ) - rho ( i ) ) / + & ( alpha * rn + beta ) +! +! MPI +! + if ( check .and. myrank .eq. 0 ) then + + write ( unit = 98, fmt = * ) i, rn, epsilon + + end if +! +! MPI +! + + rn = rn * ( 1.00 - epsilon ) +! + if ( rn .lt. 0.0 ) then + + rn = r_real ( i + 1 ) * 0.100 ** k + k = k + 1 + + end if +! +! + if ( abs ( epsilon ) .le. 1.0e-6 ) then + + exit + + end if +! + end do +! + r_real ( i ) = rn + +! write(6,*) i, r_real ( i ) + + end do +! + + rhon = rho ( kplace ) +! rn = ( rhon - beta * log ( rhon ) ) / alpha ! correction 2nd April 2013 + rn = ( rhon - beta * log ( r_sub ) ) / alpha + +! + do i = kmax - 2, nr + + k = 0 +! + do +! +! MPI +! + if ( check .and. myrank .eq. 0 ) then + + write ( unit = 98, fmt = * ) i, rn + + end if +! +! MPI +! + + epsilon = ( alpha * rn + beta * log ( rn ) - rho ( i ) ) / + & ( alpha * rn + beta ) +! +! MPI +! + if ( check .and. myrank .eq. 0 ) then + + write ( unit = 98, fmt = * ) i, rn, epsilon + + end if +! +! MPI +! + rn = rn * ( 1.00 - epsilon ) +! + if ( rn .lt. 0.0 ) then + + rn = r_real ( i - 1 ) * 10.00 ** k + k = k + 1 + + end if +! + if ( abs ( epsilon ) .le. 1.0e-6 ) then + + exit + + end if +! + end do +! + r_real ( i ) = rn + + end do +! +! MPI +! + if ( check .and. myrank .eq. 0 ) then + + write ( unit = 99, fmt = * ) '# i rho r rho ( r )', + & ' dr' + i = 1 + write ( unit = 99, fmt = "( i4, 4es20.10 )" ) i, rho ( i ), + & r_real ( i ), + & alpha * r_real ( i ) + beta * log ( r_real ( i ) ) +! + do i = 2, nr + + write ( unit = 99, fmt = "( i4, 4es20.10 )" ) i,rho ( i ), + & r_real ( i ), + & alpha * r_real ( i ) + beta * log ( r_real ( i ) ), + & r_real ( i ) - r_real ( i - 1 ) + + end do +! + end if +! +! MPI +! + if ( .not. do_r_in ) then +! if ( do_r_in ) then + + i = 1 +! + do +! + if ( r_real ( i ) > r_in ) then + + exit + + end if +! + i = i + 1 + + end do +! + i_end = i + + else + + i_end = 0 + + end if +! + +! if ( myrank .eq. 0 ) then + +! write ( unit = 6, fmt = * ) +! write ( unit = 6, fmt = "( a7, i5, a20, f12.7 )" ) & +! & "kplace = ", kplace, ", r_real ( kplace ) = ", r_real ( kplace ) +! write ( unit = 6, fmt = "( a7, i5, a20, f12.7, a10, f12.7 )" ) & +! & "kmax = ", kmax, ", r_real ( kmax ) = ", r_real ( kmax ), & +! & ", r_sub = ", r_sub +! write ( unit = 6, fmt = * ) +! write ( unit = 6, fmt = * ) "**** r_in = r_real (",i_end,")= ", & +! & r_real ( i_end ) + +! end if + + end subroutine linlogmesh +C +C + SUBROUTINE VREL +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c +C + COMMON /FCNR/KXE,H(D_),VCONS(2), + 1 R(RD_,D_),V(RD_,SD_),ICHG(10,D_),KPLACE(AT_),KMAX(AT_) + complex*16 VCONS,V +C + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +C +c + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,xe,ev + character*8 nsymbl,name0 +c + + complex*16 ZTMP(0:RD_), ZX, DZX, D2ZX +c + DIMENSION RTMP(0:RD_) + DATA FSC,FSCS4 /7.29735E-3,1.331283E-5/ +C +C INTERPOLATE POTENTIAL ON THE LOG-LINEAR MESH +C AND ADD RELATIVISTIC CORRECTIONS, INCLUDING SPIN-ORBIT INTERACTION +C +C WRITE(7,*) ' I RX(I), VX(I), VXSR(I), VXSO(I), BX(I) ' +C + RTMP(0) = 0.0 +C + DO N = 1, NDAT +C + ZAT = FLOAT(NZ(N)) + ZTMP(0) = CMPLX(2.0*ZAT,0.0) +C + DO I = 1, KMAX(N) + RTMP(I) = R(I,N) + ENDDO +C + NS = N + DO IS=1,NSPINS + DO I = 1, KMAX(N) + ZTMP(I) = -V(I,NS) * RTMP(I) + ENDDO +C + DO I=1,KMX(N) +C +C FIND NEAREST POINTS - INITIALIZE HUNTING PARAMETER (SUBROUTINE NEAREST) +C + JLO=1 + CALL NEAREST(RTMP(0), KMAX(N)+1, RX(I,N), + 1 IP1, IP2, IP3, JLO) + IP1 = IP1 - 1 + IP2 = IP2 - 1 + IP3 = IP3 - 1 +C +C INTERPOLATE ZTMP(I) +C + CALL CINTERP_QUAD( RTMP(IP1),ZTMP(IP1), + 1 RTMP(IP2),ZTMP(IP2), + 2 RTMP(IP3),ZTMP(IP3), + 3 RX(I,N),ZX,DZX,D2ZX ) + VX(I,NS) = -ZX/RX(I,N) + BX(I,NS) = FSCS4/(1.0 + FSCS4*(E - VX(I,NS))) + DVX(I,NS) = -(DZX/RX(I,N) - ZX/RX(I,N)**2) + VXR(I,NS) = VX(I,NS) - FSCS4*(E - VX(I,NS))**2 + + 1 0.5*BX(I,NS)*( -D2ZX/RX(I,N) + + 2 1.5*BX(I,NS)*(DVX(I,NS))**2 ) + VXSO(I,NS) = BX(I,NS)*DVX(I,NS)/RX(I,N) +C + ENDDO +C + NS=NS+NDAT +! C + ENDDO +C + ENDDO +C + 1 FORMAT(I5,9E15.6) +C + RETURN +C + END +C +C + SUBROUTINE CINTERP_QUAD(X1,Y1,X2,Y2,X3,Y3,X4,Y4,DY4,D2Y4) +C +c Quadratic interpolation based on the polinomial y = ax^2+bx+c. +c Finds y4=f(x4) given x1,y1,x2,y2,x3,y3 and x4 as input parameters. +c Returns also the derivatives at the point y4. +c This subroutine for complex funtion y. +C + implicit real*8 (a-h,o-z) +C + complex*16 Y1, Y2, Y3, Y4, DY4, D2Y4 + complex*16 TOP, A, B, C +C + TOP = (Y2-Y1)*(X3*X3-X2*X2)- (Y3-Y2)*(X2*X2-X1*X1) + BOTTOM = (X2-X1)*(X3*X3-X2*X2)- (X3-X2)*(X2*X2-X1*X1) + B = TOP/BOTTOM + A = ( (Y2-Y1)- B*(X2-X1) )/(X2*X2-X1*X1) + C = Y3 - A*X3*X3 - B*X3 + Y4 = A*X4*X4 + B*X4 + C + DY4 = 2.0*A*X4 + B + D2Y4 = 2.0*A +C + RETURN + END +C +C + subroutine smtxllm(ne,lmax_mode,relc,nks,px,px0,ppx,pax, + & ramfnr,ramfsr,ramfsop,ramfsoa,tdl) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +C +C + COMMON/BESSEL/SBF(LTOT_),DSBF(LTOT_),SHF(LTOT_),DSHF(LTOT_) + COMPLEX*16 SBF,DSBF,SHF,DSHF + COMPLEX*16 SBFX(LTOT_),DSBFX(LTOT_),SHFX(LTOT_),DSHFX(LTOT_) +C + COMPLEX*16 Y0(0:LMAX_), Y1(0:LMAX_) + DOUBLE PRECISION RX1, RX2, EXPR +C + COMMON /FCNR/KXE, H(D_),VCONS(2), + 1 R(RD_,D_),V(RD_,SD_),ICHG(10,D_),KPLACE(AT_),KMAX(AT_) + complex*16 VCONS,V +C + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +C + complex*16 VXP(RDX_), VXA(RDX_), BD(RDX_) +C + complex*16 PX(RDX_,fl_), PX0(RDX_,fl_), PPX(RDX_,fl_), + & PAX(RDX_,fl_) + complex*16 PSX(N_), DPSX(N_), STMAT, RAMFX(N_) + complex*16 PS0(N_), DPS0(N_), STMAT0, RAMF0(N_) + complex*16 PS1(N_), DPS1(N_), STMAT1, RAMF1(N_) + complex*16 PS2(N_), DPS2(N_), STMAT2, RAMF2(N_) + complex*16 RAMF00, RAMF01, RAMF02 +C + complex*16 PKMX, PKMX1 +C + COMMON /LLM/ ALPHA, BETA +c + common /flag/ inmsh,inv,inrho,insym,iovrho,iosym, + 1 imvhl,nedhlp +c + complex*16 pss(6),dpss(6), + & ramfnr(n_), ramfsr(n_), ramfsop(n_), ramfsoa(n_) +c + character*8 name0 ,nsymbl !added 29/3/2013 + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,ev,xe +c + common /seculrx/ atmnr(n_), atmsr(n_), atmsop(n_), atmsoa(n_) + complex*16 atmnr, atmsr, atmsop, atmsoa +c + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) +c + common/eels/einc,esct,scangl,qt,lambda,eelsme(npss,npss,npss), + & p1(rdx_,npss,nef_),p2(rdx_,npss,nef_), + & p3(rdx_,npss,nef_),ramfsr1(npss,nef_), + & ramfsr2(npss,nef_),ramfsr3(npss,nef_), + & lmxels(3,ua_),p3irreg(rdx_,7),p2irreg(rdx_,7) + complex*16 eelsme,p1,p2,p3,ramfsr1,ramfsr2,ramfsr3,p3irreg, + & p2irreg + real*8 lambda + COMMON /V_TYPE/ VERSION +C + CHARACTER*3 VERSION +c + common/auger/calctype,expmode,edge1,edge2 + character*3 calctype, expmode + character*2 edge1,edge2 +c + complex*16 csqrt,arg,arg1 + complex*16 onec +c + character*2 relc +c + common/phase/phexp_nr(nep_,ltot_),phexp_sr(nep_,ltot_), + 1 phase_nr(nep_,ltot_,ua_),phase_sr(nep_,ltot_,ua_) + complex*16 phexp_nr, phexp_sr, phexp, cphase, cphase1, cphase2 + logical tdl +c + data zero,one,two/0.0,1.0,2.0/ + data pi/3.14159265358979d0/,srt2/1.414213562d0/ +c + data fsc,fscs4 /7.29735d-3,1.331283d-5/ +c +c.....Define bd for non relativistic calculation +c + do i = 1, rdx_ + bd(i) = cmplx(fscs4,0.0) + enddo + +C + onec = (1.0,0.0) + if(e.eq.0.0) e = 1.0e-8 + ns=(nns-1)*ndat +C + do 5 j=1,ndim + atmnr(j)=(0.00,0.00) + atmsr(j)=(0.00,0.00) + atmsop(j)=(0.00,0.00) + 5 atmsoa(j)=(0.00,0.00) +c +c write(70,*) ' non relativistic stmat and phase shifts ' +c write(80,*) ' scalar relativistic stmat and phase shifts ' +c write(90,*) ' spin-orbit stmat and phase shifts ' +c +c calculate t-matrix elements: +c stmat: inverse t-m elements (atomic spheres) +c ramf: for normalization of ps(k) functions +c +c write(19,18) e, xe + write(81,*) ' e, vcon, xe, relc =', e, dble(vcon), + & dble(xe), relc +c write(84,*) ' e, vcon, xe =', e, vcon, xe +c 18 FORMAT(' E =', F10.5,5X,' XE =',2F10.5,' GAMMA =',F10.5) +c + do 60 na=1,nuatom + IF(VERSION.EQ.'1.1') THEN + write(35,78) na + ELSEIF(VERSION.EQ.'2.0') THEN + write(35,77) na,nz(na) + ENDIF + if(nks.eq.1) write(85,77) na,nz(na) + if(nks.eq.2) write(86,77) na,nz(na) + if(nks.eq.3) write(87,77) na,nz(na) + write(70,77) na + write(80,77) na + write(90,77) na + ns=ns+1 + 25 nt0a=n0(na) + ntxa=nt0a+nterms(na)-1 + if (na.eq.nas) then + nstart=nt0a + nlast=ntxa + endif + l=-1 + nlat=-1 + arg=xe*rs(na) + ml=lmaxn(na)+1 + if (ml.lt.3) ml = 3 + call csbf(arg,xe,ml,sbf,dsbf) + call cshf2(arg,xe,ml,shf,dshf) + npabs = 0 +C + 43 do 45 nn=nt0a,ntxa + + l=ln(nn) + nlat=nlat+1 + npabs=npabs+1 + if(na.ne.nas.or.npabs.gt.npss-1) npabs=npss + if(lmax_mode.eq.2.and.l.gt.lmxne(na,ne)) goto 45 + np=npabs +C +c if(relc.eq.'nr') then +c + rx1 = rx(1,na) + rx2 = rx(2,na) + y0(l) = dcmplx(rx1**(l+1),0.d0) + y1(l) = dcmplx(rx2**(l+1),0.d0) +c + call pgenll1m(l, e, hx(na), rx(1,na), vx(1,ns), bd, + & kmx(na), kplx(na), rs(na), px(1,np), psx(nn), + & dpsx(nn), ramf00, stmat, y0(l),y1(l)) +c + atmnr(nn)=stmat + ramfx(nn)=ramf00 + ramfnr(nn) = ramf00 +c +c definition of stmat as exp(-i*delta)*sin(delta) +c + phexp = stmat/abs(stmat) + phase = (0.d0,1.d0)*log(phexp) + phase_nr(ne,nlat+1,na) = phase + write(70,1000) xe/0.52917715, stmat, phase + if(relc.eq.'nr') write(35,1000) xe/0.52917715, stmat + write(71,1001)e,xe,na,nlat,stmat,phase + 1001 format(2x,f10.5,2x,2f10.5,2x,i3,2x,i3, + & 2x,2e13.6,2x,2e13.6,f10.5) + 1000 format(3x,f9.4,1x,f9.4,5x,e13.6,5x,e13.6,5x,e13.6,5x,e13.6) !18/03/2019 format e12.6 changed to e13.6 +c 1000 format(3x,f9.4,1x,f9.4,5x,f12.9,5x,f12.9,5x,f12.9,5x,f12.9) + if(na.eq.nas.and.tdl.eqv..true.) then + phexp_nr(ne,nlat+1) = phexp + endif +c +c elseif(relc.eq.'sr') then +c + rx1 = rx(1,na) + rx2 = rx(2,na) + expr = 0.5d0 + sqrt( dble(l*(l+1)) +1 - (fsc*z(na))**2 ) + y0(l) = dcmplx(rx1**expr,0.d0) + y1(l) = dcmplx(rx2**expr,0.d0) + call pgenll1m(l, e, hx(na), rx(1,na), vxr(1,ns), bx(1,ns), + & kmx(na), kplx(na), rs(na), px0(1,np), ps0(nn), + & dps0(nn), ramf00, stmat0, y0(l),y1(l)) +c + if(calctype.eq.'els'.or.calctype.eq.'e2e') then + do k = 1, kmx(na) + if(nks.eq.1) p1(k,l+1,na) = px0(k,np) !npabs = np + if(nks.eq.2) p2(k,l+1,na) = px0(k,np) + if(nks.eq.3) p3(k,l+1,na) = px0(k,np) + enddo + if(nks.eq.1) ramfsr1(l+1,na) = ramf00 + if(nks.eq.2) ramfsr2(l+1,na) = ramf00 + if(nks.eq.3) ramfsr3(l+1,na) = ramf00 +c + if(nks.eq.1) write(85,1000) xe/0.52917715, stmat0 + if(nks.eq.2) write(86,1000) xe/0.52917715, stmat0 + if(nks.eq.3) write(87,1000) xe/0.52917715, stmat0 + endif +c + atmsr(nn)=stmat0 + ramfsr(nn)=ramf00 +c +c definition of stmat0 as exp(-i*delta)*sin(delta) +c + phexp = stmat0/abs(stmat0) + phase = (0.d0,1.d0)*log(phexp) + phase_sr(ne,nlat+1,na) = phase + write(80,1000) xe/0.52917715, stmat0, phase + write(81,1001)e,xe,na,nlat,stmat0,phase + if(relc.eq.'sr') write(35,1000) xe/0.52917715, stmat0 ! +c + if(na.eq.nas.and.tdl.eqv..true.) then + phexp_sr(ne,nlat+1) = phexp + endif +c +c elseif(relc.eq.'so') then +c + ilm = 2 + if(l.eq.0) ilm = 1 + do il = 1, ilm +c + if(il.eq.1) then + do i = 1, kmx(na) + vxp(i) = vxr(i,ns) + float(l)*vxso(i,ns) + enddo + rx1 = (rx(1,na)) + rx2 = (rx(2,na)) + expr = 0.5d0 + sqrt( dfloat(l+1)**2 -(fsc*z(na))**2 ) + y0(l) = dcmplx(rx1**expr,0.d0) + y1(l) = dcmplx(rx2**expr,0.d0) + call pgenll1m(l, e, hx(na), rx(1,na), vxp, bx(1,ns), + & kmx(na), kplx(na), rs(na), ppx(1,np), + & ps1(nn), dps1(nn), ramf01, stmat1, + & y0(l),y1(l)) + if(na.eq.nas) + & write(81,1) 'rp', na, l, dble(stmat1), 1.0/stmat1, + & dble(ramf01), e + else + do i = 1, kmx(na) + vxa(i) = vxr(i,ns) - float(l+1)*vxso(i,ns) + enddo + rx1 = rx(1,na) + rx2 = rx(2,na) + expr = 0.5d0 + sqrt( dfloat(l)**2 - (fsc*z(na))**2 ) + if(l.eq.0) expr = 0.5d0 +sqrt( 1.0d0 -(fsc*z(na))**2) + y0(l) = dcmplx(rx1**expr,0.d0) + y1(l) = dcmplx(rx2**expr,0.d0) + call pgenll1m(l, e, hx(na), rx(1,na), vxa, bx(1,ns), + & kmx(na), kplx(na), rs(na), pax(1,np), + & ps2(nn), dps2(nn), ramf02, stmat2, + & y0(l),y1(l)) +c + endif +c + enddo +c +c + atmsop(nn)=stmat1 + ramfsop(nn)=ramf01 + atmsoa(nn)=stmat2 + ramfsoa(nn)=ramf02 +C +c definition of stmat as exp(-i*delta)*sin(delta) +c + phexp = stmat1/abs(stmat1) + cphase1 = (0.d0,1.d0)*log(phexp) + phexp = stmat2/abs(stmat2) + cphase2 = (0.d0,1.d0)*log(phexp) + write(90,1000) xe/0.52917715, stmat1, stmat2, cphase1, cphase2 + if(relc.eq.'so') write(35,1000) + 1 xe/0.52917715, stmat1, stmat2 !,cphase1, cphase2 +c + write(91,1001)e,xe,na,nlat,stmat1,cphase1,stmat2,cphase2 +c + +c endif +1 format(a3,2i5,10e13.5) +30 format(5i3,8e13.5) +c +c + 45 continue + 60 continue +c + 77 FORMAT('-------------------- ATOM ',I3,' ---> Z = ',I2, + 1 ' -----------------') + 78 FORMAT('-------------------------- ATOM ',I3, + 1 ' -----------------------') +c +c +c calculate singular solution inside muffin tin sphere for the absorbing +c atom, matching to shf in interstitial region +c + if(calctype.eq.'els'.and.nks.eq.3) + & write(6,*)' store irregular solution' + 90 nl=0 + lmsing=5 + mout=4 + nst=n0(nas) + nlst=n0(nas)+nterms(nas)-1 +c if(nks.eq.3) write(6,*)' nst =',nst,' nlst =',nlst + l=-1 + ml=lmaxn(nas)+1 + if (ml.lt.3) ml = 3 + kpp = kmx(nas) -2 + arg=xe*rx(kpp,nas) + call cshf2(arg,xe,ml,sbfx,dsbfx) + arg1=xe*rx(kpp-1,nas) + call cshf2(arg1,xe,ml,shfx,dshfx) +c + do n=nst,nlst + l=ln(n) + if(l.gt.lmsing) cycle + nl=nl+1 + np=npss+nl + np1=nl +c + pkmx = dcmplx(sbfx(l+1))*arg/pi + pkmx1 = dcmplx(shfx(l+1))*arg1/pi +c + call pgenll2( l, e, hx(nas), rx(1,nas), vx(1,nas), bd, + & kpp, px(1,np), pkmx, pkmx1 ) + + call pgenll2( l, e, hx(nas), rx(1,nas), vxr(1,nas), + & bx(1,nas), kpp, px0(1,np), pkmx, pkmx1 ) + + ilm = 2 + if(l.eq.0) ilm = 1 +c + do i = 1, kmx(nas) + vxp(i) = vxr(i,nas) + dfloat(l)*vxso(i,nas) + vxa(i) = vxr(i,nas) - dfloat(l+1)*vxso(i,nas) + enddo +c + do il = 1, ilm + if(il.eq.1) + & call pgenll2( l, e, hx(nas), rx(1,nas), vxp, + & bx(1,nas), kpp, ppx(1,np), pkmx, pkmx1 ) + if(il.eq.2) + & call pgenll2( l, e, hx(nas), rx(1,nas), vxa, + & bx(1,nas), kpp, pax(1,np), pkmx, pkmx1 ) + enddo +c + if(calctype.eq.'els') then + if(nks.eq.2) then + do k = 1, kmx(nas) + p2irreg(k,l+1) = px0(k,np) +c write(6,*) l, rx(k,nas), px0(k,np) + enddo + elseif(nks.eq.3) then + do k = 1, kmx(nas) + p3irreg(k,l+1) = px0(k,np) +c write(6,*) l, rx(k,nas), px0(k,np) + enddo + endif + endif +c + enddo +c +c + return +c + end +c +c + + subroutine pgenll1m(l, en, h, rx, v, b, kmax, kplx, rs, + & p, ps, dps, ramf, stmat, y0, y1 ) +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + common/bessel/sbf(ltot_),dsbf(ltot_),shf(ltot_),dshf(ltot_) + complex*16 sbf,dsbf,shf,dshf +c + common/param/eftr,gamma,vcon,xe,ev,e,iout + complex*16 vcon,xe,ev +c + common /llm/ alpha, beta +c + complex*16 v(kmax), p(kmax), b(kmax), ps, dps, + & ramff, ramf, stmat, x + complex*16 y0, y1 +c + dimension rx(kmax) +c +c double precision dfl, a, hd, hsq12, rxi, den, arb2, +c & alpha, beta, rlv, amv + complex*16 vi +c + complex*16 um(0:kmax), vm(0:kmax), + & am(0:kmax), bm(0:kmax) +c +c + data pi/3.141592653589793d0/, fsc/7.29735E-3/ +c +c calculate coefficients um(m) and vm(m). +c inv = .true. : y0 first starting point; y1 last starting point +c inv = .false. : y0, y1 first two starting points at rx(1) and rx(2) +c In this particular case um=/0. +c + + vm(1) = (0.d0,0.d0) + um(1) = (1.d0,0.d0) + am(0) = (0.d0,0.d0) + bm(0) = (0.d0,0.d0) +c + dfl = dble(l) + a = (dfl + 1)*dfl + hsq12 = h*h/12.d0 +c + do i = 1, kmax + rxi = rx(i) + arb2 = (alpha*rxi + beta)**2 + vi = v(i) + am(i) = 1.d0 + 1.d0/arb2 * ( rxi**2 * (en-vi) - a - + & beta*(alpha*rxi + beta/4.d0)/arb2 )*hsq12 + bm(i) = 2.d0*(6.d0 - 5.d0*am(i)) + enddo + + do i = 2, kmax-1 + vm(i) = am(i+1) / ( bm(i) - am(i-1)*vm(i-1) ) + enddo + + do i = 2, kmax + um(i) = um(i-1)*am(i-1) / ( bm(i) - am(i-1)*vm(i-1) ) + enddo +c + p(1) = y0 * sqrt( alpha + beta/rx(1) ) + p(2) = y1 * sqrt( alpha + beta/rx(2) ) + do i = 2, kmax - 1 + p(i+1) = (p(i) - um(i)*p(1))/vm(i) + enddo +c + do i = 1, kmax + p(i) = p(i)*sqrt(rx(i)/(alpha*rx(i)+beta) ) * + & fsc/2.0D0 /sqrt(b(i))/ rx(i) + enddo +c + kplx3 = kplx - 3 + call interp(rx(kplx3),p(kplx3),7,rs,ps,dps,.true.) +c + x=dps/ps + ramff=cmplx(sbf(l+1))*x-cmplx(dsbf(l+1)) +c stmat=(shf(l+1)*x-dshf(l+1))/ramff + stmat=ramff/(cmplx(shf(l+1))*x-cmplx(dshf(l+1))) + ramf=ramff*ps*rs*rs*pi + ramf=ramf*xe/pi +c +c + return + end +c +c + subroutine pgenll2( l, en, h, rx, v, b, kmax, p, pkmx, pkmx1 ) +c +c This subroutine for inward integration toward the origin +c + implicit real*8 (a-h,o-z) +c + common /llm/ alpha, beta +c + complex*16 v(kmax), p(kmax), b(kmax), pkmx, pkmx1 + dimension rx(kmax) +c +c double precision dfl, a, hd, hsq12, rxi, den, arb2, +c & alpha, beta +c + complex*16 um(0:kmax), vm(0:kmax), am(0:kmax), bm(0:kmax) + complex*16 vi, dnm +c + data pi/3.14159265/, fsc/7.29735E-3/ +c +c calculate coefficients um(m) and vm(m). +c + + vm(kmax) = (0.d0,0.d0) + um(kmax) = dcmplx(pkmx*sqrt( alpha + beta/rx(kmax) )) + +c + dfl = dble(l) + a = (dfl + 1)*dfl +c + hsq12 = h*h/12.d0 +c + do i = 1, kmax + rxi = rx(i) + arb2 = (alpha*rxi + beta)**2 + vi = v(i) + am(i) = 1.d0 + 1.d0/arb2 * ( rxi**2 * (en-vi) - a - + & beta*(alpha*rxi + beta/4.d0)/arb2 )*hsq12 + bm(i) = 2.d0*(6.d0 - 5.d0*am(i)) + enddo + + do i = kmax-1, 2, -1 + dnm = ( bm(i) - am(i+1)*vm(i+1) ) + vm(i) = am(i-1) / dnm + um(i) = am(i+1) * um(i+1) / dnm +c write(6,*) vm(i), um(i) + enddo + + + p(kmax) = pkmx * sqrt( alpha + beta/rx(kmax) ) + p(kmax-1) = pkmx1 * sqrt( alpha + beta/rx(kmax-1) ) + + do i = kmax-1, 2, -1 + p(i-1) = ( p(i) - um(i)) / vm(i) + enddo + + do i = 1, kmax + p(i) = p(i) * sqrt( rx(i)/(alpha*rx(i) + beta) ) * + & fsc/2.0 /sqrt(b(i))/ rx(i) + enddo + + return + end +c +C + subroutine get_edge_gap(iz,ihole,i_radial,xion,eatom) +c +c + implicit real*8(a-h,o-z) +c +c + parameter ( mp = 251, ms = 30 ) +c + character*40 title +c + common dgc(mp,ms),dpc(mp,ms),bidon(630),idummy +c + dimension dum1(mp), dum2(mp) + dimension vcoul(mp), rho0(mp), enp(ms) +c + title = ' ' +c + ifr=1 + iprint=0 +C + amass=0.0d0 + beta=0.0d0 +c + call scfdat (title, ifr, iz, ihole, xion, amass, beta, iprint, + 1 vcoul, rho0, dum1, dum2, enp, eatom) +c + return + end +C +C + subroutine calc_edge(cip) + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' + include 'msxasc3.inc' +c + dimension etot(2) +c +c.....Find out ionization potential for chosen edge +c + xion=0.0d0 !corrected 23 june 2017 + iz = nz(1) + ihole1 = 0 +c + if(edge.eq.'k ') ihole2 = 1 + if(edge.eq.'l1') ihole2 = 2 + if(edge.eq.'l2') ihole2 = 3 + if(edge.eq.'l3') ihole2 = 4 + if(edge.eq.'m1') ihole2 = 5 + if(edge.eq.'m2') ihole2 = 6 + if(edge.eq.'m3') ihole2 = 7 + if(edge.eq.'m4') ihole2 = 8 + if(edge.eq.'m5') ihole2 = 9 + if(edge.eq.'n2') ihole2 = 11 + if(edge.eq.'n3') ihole2 = 12 + if(edge.eq.'n4') ihole2 = 13 + if(edge.eq.'n5') ihole2 = 14 + if(edge.eq.'n6') ihole2 = 15 + if(edge.eq.'n7') ihole2 = 16 +c + write(6,*) ' ---' + do i = 1, 2 +c + ityhole = ihole1 +c if(i.eq.2) ityhole = ihole2 ----- corrected 23th June 2017 + if(i.eq.2) then + ityhole = ihole2 + xion = 1.0d0 + endif +c + if(i.eq.1) write(6,*) ' total energy for atom in ground state ' + if(i.eq.2) write(6,*) ' total energy for atom with a hole in ', + & edge, ' edge' +c + + call get_edge_gap(iz,ityhole,ityhole,xion,etot(i)) +c + enddo +c + cip = (etot(2) - etot(1))*2.0 + cip = sign(cip,1.d0) + write(6,*) ' calculated ionization energy for edge ', edge, + & ' = ', cip*13.6, ' eV' +c +c.....Find out energy distance between edges and construct two edge +c dipole cross section +c + xion=1.0d0 +c + if(edge.eq.'k '.or.edge.eq.'l1'.or.edge.eq.'m1'.or.edge.eq.'n1') + & go to 15 + if(edge.eq.'l2'.or.edge.eq.'l3') then + ihole1 = 3 + ihole2 = 4 + else if(edge.eq.'m2'.or.edge.eq.'m3') then + ihole1 = 6 + ihole2 = 7 + else if(edge.eq.'m4'.or.edge.eq.'m5') then + ihole1 = 8 + ihole2 = 9 + else if(edge.eq.'n2'.or.edge.eq.'n3') then + ihole1 = 11 + ihole2 = 12 + else if(edge.eq.'n4'.or.edge.eq.'n5') then + ihole1 = 13 + ihole2 = 14 + else if(edge.eq.'n6'.or.edge.eq.'n7') then + ihole1 = 15 + ihole2 = 16 + endif +c + do i = 1, 2 + + ityhole = ihole1 + if(i.eq.2) ityhole = ihole2 +c + call get_edge_gap(iz,ityhole,ityhole,xion,etot(i)) +c + enddo +c + detot = (etot(1) - etot(2))*2.0d0 + detot = sign(detot,1.0d0) + if(edge.eq.'l2'.or.edge.eq.'l3') then + write(6,*) ' energy distance between edges l2 and l3 = ', + & ( etot(1) - etot(2) )* 27.2, 'eV' + elseif(edge.eq.'m2'.or.edge.eq.'m3') then + write(6,*) ' energy distance between edges m2 and m3 = ', + & ( etot(1) - etot(2) )* 27.2, 'eV' + elseif(edge.eq.'m4'.or.edge.eq.'m5') then + write(6,*) ' energy distance between edges m4 and m5 = ', + & ( etot(1) - etot(2) )* 27.2, 'eV' + endif +c +15 continue +c + write(6,*) ' ---' +c + end +C +C + subroutine search_corewf(iz,i_hole,zr,r_hs,kmax, + & rx,hx,kmx,estart,l,orb) +c +c This subroutine calculates eigenvalues and eigenfunctions of the +c absorber overlapped potential. Adapted from HF program mcms-new/scs.f of +c Mon Jun 20 2011; revised: March 16 2016 +c +c core energy search +c + implicit double precision (a-h,o-z) +c + include 'msxas3.inc' +c +c + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1,l_max = ltot_, + $ n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + dimension zr(kmax),r_hs(kmax),ztmp(0:kmax),rtmp(0:kmax),rx(kmx) +c + dimension vx(rdx_), dvx(rdx_), bx(rdx_), vxso(rdx_), vxr(rdx_) + dimension bd(rdx_) +c +c + dimension pllm(rdx_,0:l_max), pinwll(rdx_,0:l_max), + & pall(rdx_,0:l_max), ppll(rdx_,0:l_max), + & fpotw(rdx_,0:l_max), fpinw(rdx_,0:l_max), + & fpinw1(rdx_,0:l_max), fp(rdx_), ap(rdx_), + & pllmwr(rdx_,0:l_max), ap1(rdx_), xq(rdx_) +c + dimension pllme(rdx_,0:l_max), pinwlle(rdx_,0:l_max), + & pllp(rdx_,0:l_max) +c + dimension nsymbl(nat_), neq(nat_), h(nat_) + +c + dimension pow(0:l_max), dpow(0:l_max), + & piw(0:l_max), dpiw(0:l_max), + & ddp(0:l_max), eddp(0:l_max), + & ainw(0:l_max), aotw(0:l_max), djump(0:l_max) +c + common /llm/ alpha1, beta1 +c +c +c + common /ccs/ccwf(rdx_) +c + integer pqn + character*5 orb +c + data fsc,fscs4/7.29735d-3,1.331282926d-5/ + data pi/3.141592653589793d0/ +c + alpha = alpha1 + beta = beta1 +c + e = estart +c +c interpolate zr(k) on hs mesh onto zx(k) on lin_log mesh +c + rtmp(0) = 0.0 +c + zat = float(iz) + ztmp(0) = 2.0*zat +c + do i = 1, kmax + rtmp(i) = r_hs(i) + enddo +c + do i = 1, kmax + ztmp(i) = zr(i) + enddo + +c + do i=1,kmx +c +c find nearest points - initialize hunting parameter (subroutine nearest) +c + jlo=1 + call nearest(rtmp(0), kmax+1, rx(i), + & ip1, ip2, ip3, jlo) + ip1 = ip1 - 1 + ip2 = ip2 - 1 + ip3 = ip3 - 1 +c +c interpolate zr(i) and rhotot(i) +c + call interp_quad( rtmp(ip1),ztmp(ip1), + & rtmp(ip2),ztmp(ip2), + & rtmp(ip3),ztmp(ip3), + & rx(i),zx,dzx,d2zx ) + vx(i) = -zx/rx(i) + bx(i) = fscs4/(1.0 + fscs4*(e - vx(i))) + dvx(i) = -(dzx/rx(i) - zx/rx(i)**2) + vxr(i) = vx(i) - fscs4*(e - vx(i))**2 + + & 0.5*bx(i)*( -d2zx/rx(i) + + & 1.5*bx(i)*(dvx(i))**2 ) + vxso(i) = bx(i)*dvx(i)/rx(i) +c write(15,1) i, rx(i), vx(i), vxr(i), +c & vxso(i), bx(i) + enddo +c +c +c write(6,*) ' i, rx(i), vx(i), vxr(i), vxso(i), bx(i) ' +c write(6,*) hx + do i = 1, kmx +c write(6,1) i, rx(i), vx(i), vxr(i), vxso(i), bx(i) +c & , -rx(i)*vx(i) + enddo +1 format(i4,6e16.8) +c + rx1 = rx(1) + rx2 = rx(2) +c + write(6,*) '------------------------------' +c write(6,*) 'vxrm =', vxr(kmx), 'rmx =', rx(kmx), +c & 'vx =', vx(kmx) +c +10 e = estart + a = float(l*(l+1)) + nctp = 0 +c Search for second classical turning point + evp = (vx(1) + a/rx(1)**2 - e) + do i = 2, kmx + ev = (vx(i) + a/rx(i)**2 - e) +c write(6,*) ' i, ev = ', i, ev, vx(i), e + if(l.eq.0) then + if(evp*ev.lt.0.0) then + ictp = i + go to 2 + else + continue + endif + else + if(evp*ev.lt.0.0) nctp = nctp + 1 + if(evp*ev.gt.0.0.and.nctp.eq.2) then + ictp = i +c write(6,*) 'l =', l,' ictp = ', ictp + go to 2 + endif + endif + evp = ev + enddo +c +2 kctp = ictp +c write(6,*) ' l, kmx, kctp, estart = ', l, kmx, kctp, e + if(kctp.eq.0) then + write(6,*) ' ev does not change sign. Decrease energy ' + stop + endif +c +c write(6,*) 'hx =',hx +c +c Begin energy loop and find solution for radial equations +c + niter = 0 +3 niter = niter + 1 +c write(6,*)'niter =', niter + if(niter.gt.100) then + write(6,*) 'niter gt 100 in search_corewf for orbital =', orb + stop + endif +c +c rx1 = rx(1) +c rx2 = rx(2) +c + exp0 = (vx(kmx) - e)*rx(kmx)**2 + a + exp1 = (vx(kmx-1) - e)*rx(kmx-1)**2 + a +c write(6,*)'exp0, exp1', exp0,exp1 + if(exp0.lt.0) then + estart = estart + 0.3 !1.0 + go to 10 + endif +c +c write(6,*)' estart =', estart + pkmx = exp(-sqrt(exp0)) + pkmx1 = exp(-sqrt(exp1)) +c write(6,*) 'in. val. inw ', rx(kmx), pkmx, rx(kmx-1), pkmx1 +c.....Outward integration with log mesh +c + do i = 1, kmx + xq(i) = 0.0 + bd(i) = fsc**2/4.0 + enddo +c + y0 = rx1**(l+1) + y1 = rx2**(l+1) + + call pgenllout( l, e, hx, rx, vx, xq, kctp+3, alpha, beta, + & pllm(1,l), pow(l), dpow(l), y0, y1, bd) + +c +c.....Inward integration with log mesh +c + do i = kctp - 3, kmx + xq(i) = 0.0 + bd(i) = fsc**2/4.0 + enddo +c + call pgenllinw( l, e, hx, rx, vx, xq, kmx, kctp, alpha, beta, + & pinwll(1,l), piw(l), dpiw(l), pkmx, pkmx1, bd ) +c +c + ddp(l) = dpow(l)/pow(l) - dpiw(l)/piw(l) +c write(6,*) ' l, ddp(l) ', l, ddp(l) +c +c +c........calculate energy derivative of ddp +c + do i = 1, kctp + fpotw(i,l) = pllm(i,l)**2/(alpha + beta/rx(i)) +c write(15,*) rx(i), fpotw(i,l) + enddo +c + do i = kmx, kctp, -1 + fpinw(kmx-i+1,l) = pinwll(i,l)**2/(alpha + beta/rx(i)) +c write(15,*) rx(i), fpinw(kmx-i+1,l) + enddo +c +c dx = x(2) - x(1) + id = 1 + call defintr(fpotw(1,l),hx,kctp,aotw(l),id) + id = 0 + call defintr(fpinw(1,l),hx,kmx-kctp+1,ainw(l),id) +c + eddp(l) = - aotw(l)/pow(l)**2 - ainw(l)/piw(l)**2 +c write(6,*) ' a/p ', aotw(l)/pow(l)**2, ainw(l)/piw(l)**2 +c write(6,*) ' a/p ', aotw(l)/pow(l)**2, dde +c write(6,*) ' aiw/piw ', ainw(l)/piw(l)**2, ddiwe + djump(l) = ddp(l)/eddp(l) +c write(6,*)'djump(l)', l, djump(l) + +c + enew = e - djump(l) +c write(6,*) ' enew, e, l, ddp(l) ', enew, e, l, ddp(l) +c + if(abs(enew-e).lt. 1.d-3.and.ddp(l).lt. 1.d-3) then + go to 4 + else + e = enew + go to 3 + endif +c +4 write(6,*) ' energy of core state = ', enew, ' for orbital =', + & orb +c write(6,*) ' number of energy iterations = ', niter +c +c reconstruct core state wave function +c + do i = kctp, kmx + pllm(i,l) = pow(l)/piw(l)*pinwll(i,l) + enddo +c +c find normalization +c + do i = 1, kmx + fp(i) = pllm(i,l)**2*rx(i)/(alpha*rx(i) + beta) + enddo +c + id = 0 + call defintr(fp,hx,kmx,anorm,id) +c + write(12,*) ' core state for z = ',zat,' orb = ',orb, 'at e = ',e + sra = sqrt(anorm) + do i = 1, kmx + pllmwr(i,l) = pllm(i,l)/rx(i)/sra + pllm(i,l) = pllm(i,l)/sra + ccwf(i) = pllm(i,l)*rx(i)**2 +c write(12,*) rx(i), pllm(i,l), pllmwr(i,l) + write(12,*) rx(i), pllmwr(i,l) + enddo +c +c.....Check number of nodes of solution +c + nzero = 0 + prev = sign(1.d00,pllmwr(1,l)) + do i = 2, kmx -3 + prod = sign(1.d0,prev*pllmwr(i,l)) + if(prod.lt.0.0) nzero = nzero + 1 + prev = sign(1.d0,pllmwr(i,l)) + enddo +c npqn = nint(sqrt(-1.0/enew)*iz) +c write(6,*) ' orbital ', orb + read (orb(1:1),'(I1)') pqn + write(6,*)' n. of zeros found:', nzero, + & ' expected: ', pqn - l - 1 +c +c + end +c +C + subroutine search_corewf_rel(iz,i_hole,zr,r_hs,kmax, + & rx,hx,kmx,estart,l,orb) +c +c This subroutine calculates eigenvalues and eigenfunctions of the +c absorber overlapped potential. Adapted from HF program mcms-new/scs.f of +c Mon Jun 20 2011; revised: March 16 2016 +c + implicit double precision (a-h,o-z) +c core energy search +c +c + include 'msxas3.inc' +c +c + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1,l_max = ltot_, + & n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + dimension zr(kmax),r_hs(kmax),ztmp(0:kmax),rtmp(0:kmax),rx(kmx) +c + dimension vx(rdx_), dvx(rdx_), bx(rdx_), vxso(rdx_), vxr(rdx_) + dimension vxp(rdx_), vxa(rdx_) +c +c + dimension pllm(rdx_,0:l_max), pinwll(rdx_,0:l_max), + & pall(rdx_,0:l_max), ppll(rdx_,0:l_max), + & fpotw(rdx_,0:l_max), fpinw(rdx_,0:l_max), + & fpinw1(rdx_,0:l_max), fp(rdx_), ap(rdx_), + & pllmwr(rdx_,0:l_max), ap1(rdx_), xq(rdx_) +c + dimension pllme(rdx_,0:l_max), pinwlle(rdx_,0:l_max), + & pllp(rdx_,0:l_max) +c + dimension nsymbl(nat_), neq(nat_), h(nat_) + +c + dimension pow(0:l_max), dpow(0:l_max), + & piw(0:l_max), dpiw(0:l_max), + & ddp(0:l_max), eddp(0:l_max), + & ainw(0:l_max), aotw(0:l_max), djump(0:l_max) +c + common /llm/ alpha1, beta1 +c +c common/mesh_param/jlo +c + common /ccs/ccwf(rdx_) +c + integer pqn + character*5 orb +c + data fsc,fscs4/7.29735d-3,1.331282926d-5/ + data pi/3.141592653589793d0/ +c +c + read (orb(3:3),'(i1)') iso +c write(6,*)' iso =', iso +c +c + alpha = alpha1 + beta = beta1 +c + e = estart +c +c interpolate zr(k) on hs mesh onto zx(k) on lin_log mesh +c +c + rtmp(0) = 0.0 +c + zat = float(iz) + ztmp(0) = 2.0*zat +c + do i = 1, kmax + rtmp(i) = r_hs(i) + enddo +c + do i = 1, kmax + ztmp(i) = zr(i) + enddo + +c + do i=1,kmx +c +c find nearest points - initialize hunting parameter (subroutine nearest) +c + jlo=1 + call nearest(rtmp(0), kmax+1, rx(i), + & ip1, ip2, ip3, jlo) + ip1 = ip1 - 1 + ip2 = ip2 - 1 + ip3 = ip3 - 1 +c +c interpolate zr(i) and rhotot(i) +c + call interp_quad( rtmp(ip1),ztmp(ip1), + & rtmp(ip2),ztmp(ip2), + & rtmp(ip3),ztmp(ip3), + & rx(i),zx,dzx,d2zx ) + vx(i) = -zx/rx(i) + bx(i) = fscs4/(1.0 + fscs4*(e - vx(i))) + dvx(i) = -(dzx/rx(i) - zx/rx(i)**2) + vxr(i) = vx(i) - fscs4*(e - vx(i))**2 + + & 0.5*bx(i)*( -d2zx/rx(i) + + & 1.5*bx(i)*(dvx(i))**2 ) + vxso(i) = bx(i)*dvx(i)/rx(i) + vxp(i) = vxr(i) + float(l)*vxso(i) + vxa(i) = vxr(i) - float(l+1)*vxso(i) +c write(15,1) i, rx(i), vx(i), vxr(i), +c & vxso(i), bx(i) + enddo +c +c +c write(6,*) ' i, rx(i), vx(i), vxr(i), vxso(i), bx(i) ' +c write(6,*) hx + do i = 1, kmx +c write(6,1) i, rx(i), vx(i), vxr(i), vxso(i), bx(i) +c & , -rx(i)*vx(i) + enddo +1 format(i4,6e16.8) +c + rx1 = rx(1) + rx2 = rx(2) +c +c write(6,*) '------------------------------' +c write(6,*) 'vxrm =', vxr(kmx), 'rmx =', rx(kmx), +c & 'vx =', vx(kmx) +c +10 e = estart + a = float(l*(l+1)) + nctp = 0 +c Search for second classical turning point +c evp = (vxr(1) + a/rx(1)**2 - e) + evp = (vx(1) + a/rx(1)**2 - e) + do i = 2, kmx +c ev = (vxr(i) + a/rx(i)**2 - e) + ev = (vx(i) + a/rx(i)**2 - e) +c write(6,*) ' i, ev = ', i, ev, vx(i), e + if(l.eq.0) then + if(evp*ev.lt.0.0) then + ictp = i + go to 2 + else + continue + endif + else + if(evp*ev.lt.0.0) nctp = nctp + 1 + if(evp*ev.gt.0.0.and.nctp.eq.2) then + ictp = i +c write(6,*) 'l =', l,' ictp = ', ictp + go to 2 + endif + endif + evp = ev + enddo +c +2 kctp = ictp +c write(6,*) ' l, kmx, kctp, estart = ', l, kmx, kctp, e + if(kctp.eq.0) then + write(6,*) ' ev does not change sign. Decrease energy ' + stop + endif +c +c write(6,*) 'hx =',hx +c +c Begin energy loop and find solution for radial equations +c + niter = 0 +3 niter = niter + 1 +c write(6,*)'niter =', niter + if(niter.gt.100) then + write(6,*) 'niter gt 100 in search_corewf_rel' + stop + endif +c +c rx1 = rx(1) +c rx2 = rx(2) +c + exp0 = (vxr(kmx) - e)*rx(kmx)**2 + a + exp1 = (vxr(kmx-1) - e)*rx(kmx-1)**2 + a +c if(exp0.lt.0) then +c exp0 = abs(e)*rx(kmx)**2 + a +c exp1 = abs(e)*rx(kmx-1)**2 + a +c endif + if(exp0.lt.0) then +c write(6,*)'estart, e =', estart, e + estart = estart + 0.5 + go to 10 + endif +c +c write(6,*)' estart =', estart + pkmx = exp(-sqrt(exp0)) + pkmx1 = exp(-sqrt(exp1)) +c write(6,*) 'in. val. inw ', rx(kmx), pkmx, rx(kmx-1), pkmx1 +c.....Outward integration with log mesh +c + do i = 1, kmx + xq(i) = 0.0 + enddo +c +c y0 = rx1**(l+1) +c y1 = rx2**(l+1) + + if(l.eq.0.or.((2*l+1).eq.iso)) then + expr = 0.50 + sqrt(float(l+1)**2 -(fsc*zat)**2 ) + y0 = rx1**expr + y1 = rx2**expr + + call pgenllout( l, e, hx, rx, vxp, xq, kctp+3, alpha, beta, + & pllm(1,l), pow(l), dpow(l), y0, y1,bx) + else + expr = 0.50 + sqrt(float(l)**2 -(fsc*zat)**2 ) + y0 = rx1**expr + y1 = rx2**expr + + call pgenllout( l, e, hx, rx, vxa, xq, kctp+3, alpha, beta, + & pllm(1,l), pow(l), dpow(l), y0, y1,bx) + endif +c +c.....Inward integration with log mesh +c + do i = kctp - 3, kmx + xq(i) = 0.0 + enddo +c + if(l.eq.0.or.((2*l+1).eq.iso)) then + call pgenllinw( l, e, hx, rx, vxp, xq, kmx, kctp, alpha, beta, + & pinwll(1,l), piw(l), dpiw(l), pkmx, pkmx1, bx ) + else + call pgenllinw( l, e, hx, rx, vxa, xq, kmx, kctp, alpha, beta, + & pinwll(1,l), piw(l), dpiw(l), pkmx, pkmx1, bx ) + endif +c +c + ddp(l) = dpow(l)/pow(l) - dpiw(l)/piw(l) +c write(6,*) ' l, ddp(l) ', l, ddp(l) +c +c +c........calculate energy derivative of ddp +c + do i = 1, kctp + fpotw(i,l) = pllm(i,l)**2/(alpha + beta/rx(i)) +c write(15,*) rx(i), fpotw(i,l) + enddo +c + do i = kmx, kctp, -1 + fpinw(kmx-i+1,l) = pinwll(i,l)**2/(alpha + beta/rx(i)) +c write(15,*) rx(i), fpinw(kmx-i+1,l) + enddo +c +c dx = x(2) - x(1) + id = 1 + call defintr(fpotw(1,l),hx,kctp,aotw(l),id) + id = 0 + call defintr(fpinw(1,l),hx,kmx-kctp+1,ainw(l),id) +c + eddp(l) = - aotw(l)/pow(l)**2 - ainw(l)/piw(l)**2 +c write(6,*) ' a/p ', aotw(l)/pow(l)**2, ainw(l)/piw(l)**2 +c write(6,*) ' a/p ', aotw(l)/pow(l)**2, dde +c write(6,*) ' aiw/piw ', ainw(l)/piw(l)**2, ddiwe + djump(l) = ddp(l)/eddp(l) + +c + enew = e - djump(l) +c write(6,*) ' enew, e, l, ddp(l) ', enew, e, l, ddp(l) +c + if(abs(enew-e).lt. 2.d-3.and.ddp(l).lt.2.d-3) then + go to 4 + else + e = enew + go to 3 + endif +c +4 write(6,*) ' energy of core state = ', enew, ' for orb =', orb +c write(6,*) ' number of energy iterations = ', niter +c +c enddo ! end of energy loop +c +c reconstruct core state wave function +c + do i = kctp, kmx + pllm(i,l) = pow(l)/piw(l)*pinwll(i,l) + enddo +c +c find normalization +c + do i = 1, kmx + fp(i) = pllm(i,l)**2*rx(i)/(alpha*rx(i) + beta) + enddo +c + id = 0 + call defintr(fp,hx,kmx,anorm,id) +c + write(15,*) ' core state for z = ',zat,' orb = ',orb, 'at e = ',e + sra = sqrt(anorm) + do i = 1, kmx + pllmwr(i,l) = pllm(i,l)/rx(i)/sra + pllm(i,l) = pllm(i,l)/sra + ccwf(i) = pllm(i,l)*rx(i)**2 +c write(12,*) rx(i), pllm(i,l), pllmwr(i,l) + write(15,*) rx(i), pllmwr(i,l) + enddo +c +c.....Check number of nodes of solution +c + nzero = 0 + prev = sign(1.d0,pllmwr(1,l)) + do i = 2, kmx -3 + prod = sign(1.d0,prev*pllmwr(i,l)) + if(prod.lt.0.0) nzero = nzero + 1 + prev = sign(1.d0,pllmwr(i,l)) + enddo + read (orb(1:1),'(i1)') pqn + write(6,*)' n. of zeros found:', nzero, + & ' expected: ', pqn - l - 1 +c +c + end +c +c +c +c + subroutine pgenllout( l, en, h, rx, v, q, kmax, alpha, beta, p, + & ps, dps, y0, y1, bd ) +c + implicit double precision (a-h,o-z) +c + logical inw +c + dimension v(kmax), rx(kmax), p(kmax), pp(kmax), q(kmax), bd(kmax) +c + dimension um(0:kmax), vm(0:kmax), am(0:kmax), bm(0:kmax), + & omq(0:kmax+1), omq1(kmax) +c double precision um, vm, am, bm, omq, omq1 + + data fsc/7.29735d-3/ +c calculate coefficients um(m) and vm(m). In this particular case um=/0. + + vm(1) = 0.d0 + um(1) = 1.d0 + am(0) = 0.d0 + bm(0) = 0.d0 +c + omq(0) = 0.d0 + omq(kmax+1) = 0.d0 + + dfl = dfloat(l) + a = (dfl + 1)*dfl + hsq12 = h*h/12.d0 + + do i = 1, kmax + arb = (alpha*rx(i) + beta) + arb2 = arb**2 + am(i) = 1.d0 + 1.d0/arb2 * ( rx(i)**2 * (en-v(i)) - a + & - beta*(alpha*rx(i) + beta/4.d0)/arb2) *hsq12 + bm(i) = 2.d0*(6.d0 - 5.d0*am(i)) + omq(i) = hsq12*(rx(i)/arb)*sqrt(rx(i)/arb)*q(i) + enddo + + do i = 1, kmax + omq1(i) = omq(i+1) + 10.d0*omq(i) + omq(i-1) + enddo + + do i = 2, kmax-1 + vm(i) = am(i+1) / ( bm(i) - am(i-1)*vm(i-1) ) + enddo + + p(1) = y0 * sqrt( alpha + beta/rx(1) ) + p(2) = y1 * sqrt( alpha + beta/rx(2) ) + + um(1) = p(1) + + do i = 2, kmax + um(i) = ( um(i-1)*am(i-1) + omq1(i) ) / + & ( bm(i) - am(i-1)*vm(i-1) ) + enddo +c + do i = 2, kmax - 1 +c p(i+1) = (p(i) - um(i)*p(1))/vm(i) + p(i+1) = (p(i) - um(i))/vm(i) + enddo +c +c write(15,*) ' inside pgenll1m ' + do i = 1, kmax +c p(i) = p(i) * sqrt( rx(i)/(alpha*rx(i) + beta) ) / rx(i) + p(i) = p(i) * sqrt( rx(i)/(alpha*rx(i) + beta) ) + & * fsc/2.d0 /sqrt(bd(i)) +c write(15,*) rx(i), p(i) + enddo +c + kplx3 = kmax - 6 + rs = rx(kmax-3) + call dinterp(rx(kplx3),p(kplx3),7,rs,ps,dps,.true.) +c +c write(15,*) ' kplx3, kmax = kctp + 3 ', kplx3, kmax +c write(15,*) ' rs = ', rs +c write(15,*) ' p, dps = ', ps, dps +c + return + end +c +c + subroutine pgenllinw( l, en, h, rx, v, q, kmax, kctp, + & alpha, beta, p, ps, dps, pkmx, pkmx1, bd ) + +c This subroutine for inward integration toward the origin +c + implicit double precision (a-h,o-z) +c + dimension v(kmax), rx(kmax), p(kmax), q(kmax), omq(kmax), + & omq1(kmax), bd(kmax) + + dimension um(0:kmax), vm(0:kmax), am(0:kmax), bm(0:kmax) +c +c + data fsc/7.29735d-3/ +c calculate coefficients um(m) and vm(m). + + dfl = dble(l) + a = (dfl + 1)*dfl + hsq12 = h*h/12.0 + + vm(kmax) = 0.d0 + um(kmax) = pkmx*sqrt( alpha + beta/rx(kmax) ) + + + do i = 1, kmax + arb = (alpha*rx(i) + beta) + arb2 = arb**2 + am(i) = 1.d0 + 1.d0/arb2 * ( rx(i)**2 * (en-v(i)) - a + & - beta*(alpha*rx(i) + beta/4.0)/arb2) *hsq12 + bm(i) = 2.d0*(6.d0 - 5.d0*am(i)) + omq(i) = hsq12*(rx(i)/arb)*sqrt(rx(i)/arb)*q(i) +c write(6,*) am(i), bm(i) + enddo + + do i = kmax-1, kctp-2, -1 + omq1(i) = omq(i+1) + 10.d0*omq(i) + omq(i-1) + enddo + +c write(2,*) '--------------- inside pgenll2 ------------' + + do i = kmax-1, kctp-2, -1 + dnm = bm(i) - am(i+1)*vm(i+1) + vm(i) = am(i-1) / dnm + um(i) = ( am(i+1) * um(i+1) + omq1(i) )/ dnm +c write(2,*) i, bm(i), am(i), um(i), vm(i) +c write(6,*) vm(i), um(i) + enddo + + p(kmax) = pkmx * sqrt( alpha + beta/rx(kmax) ) + p(kmax-1) = pkmx1 * sqrt( alpha + beta/rx(kmax-1) ) + + do i = kmax-1, kctp-2, -1 + p(i-1) = ( p(i) - um(i)) / vm(i) + enddo + +c write(15,*) ' inside pgenll2 ' + do i = kctp-3, kmax +c p(i) = p(i) * sqrt( rx(i)/(alpha*rx(i) + beta) ) / rx(i) + p(i) = p(i) * sqrt( rx(i)/(alpha*rx(i) + beta) ) + & * fsc/2.0 /sqrt(bd(i)) +c write(15,*) i, rx(i), p(i) + enddo + + kplx3 = kctp - 3 + rs = rx(kctp) + call dinterp(rx(kplx3),p(kplx3),7,rs,ps,dps,.true.) +c +c write(15,*) ' kplx3, kmax = ', kplx3, kmax +c write(15,*) ' rs = ', rs +c write(15,*) ' p, dps = ', ps, dps +c + return + end +c +c +c +c + subroutine get_atomic_orbitals(iz,ihole,rxd,dx,kmxn,lorb,ic_occ, + & iabs_occ,enp,orbtp) +c +c + implicit real*8(a-h,o-z) +c + parameter ( mp = 251, ms = 30 ) +c + dimension rxd(kmxn), r(mp) + dimension abs_occ(29), abs_val(29), lorb(29), + & ic_occ(29), iabs_occ(29) + dimension rpx(kmxn), rid(kmxn) +c + character*40 title +c + character*5 orb(29), orbtp(29) +c + common dgc(mp,ms),dpc(mp,ms),bidon(630),idummy +c + dimension dum1(mp), dum2(mp) + dimension vcoul(mp), rho0(mp), enp(ms) +c +c common/mesh_param/jlo +c + common /llm/ alpha, beta +c +c + title = ' ' +c +c element level principal qn (nqn), kappa qn (nk) +c 1 1s 1 -1 +c 2 2s 2 -1 +c 3 2p1/2 2 1 +c 4 2p3/2 2 -2 +c 5 3s 3 -1 +c 6 3p1/2 3 1 +c 7 3p3/2 3 -2 +c 8 3d3/2 3 2 +c 9 3d5/2 3 -3 +c 10 4s 4 -1 +c 11 4p1/2 4 1 +c 12 4p3/2 4 -2 +c 13 4d3/2 4 2 +c 14 4d5/2 4 -3 +c 15 4f5/2 4 3 +c 16 4f7/2 4 -4 +c 17 5s 5 -1 +c 18 5p1/2 5 1 +c 19 5p3/2 5 -2 +c 20 5d3/2 5 2 +c 21 5d5/2 5 -3 +c 22 5f5/2 5 3 +c 23 5f7/2 5 -4 +c 24 6s 6 -1 +c 25 6p1/2 6 1 +c 26 6p3/2 6 -2 +c 27 6d3/2 6 2 +c 28 6d5/2 6 -3 +c 29 7s 7 -1 +c +c + data orb /'1s','2s','2p1/2','2p3/2','3s','3p1/2','3p3/2','3d3/2', + & '3d5/2','4s','4p1/2','4p3/2','4d3/2','4d5/2','4f5/2', + & '4f7/2','5s','5p1/2','5p3/2','5d3/2','5d5/2','5f5/2', + & '5f7/2','6s','6p1/2','6p3/2','6d3/2','6d5/2','7s'/ +c + do i = 1, 29 + orbtp(i) = orb(i) + enddo +c + ifr=1 + iprint=0 +C + amass=0.0d0 + betas=0.0d0 !changed to avoid resetting beta parameter in common /llm/ + xion = 0.0d0 +c + call scfdat (title, ifr, iz, ihole, xion, amass, betas, iprint, + 1 vcoul, rho0, dum1, dum2, enp, eatom) +c + dpas=0.05d0 + dr1=exp(-8.8d0) + dex=exp(dpas) + r_max=44.447d0 +c + radius=10.0d0 +c +c compute radial log mesh (see subroutine phase in J.J. Rehr's program +c FEFF.FOR) +c + ddex=dr1 + do 10 i=1,251 + r(i)=ddex + ddex=ddex*dex +10 continue +c +c atomic orbital generation + call occupancy(iz, abs_occ, abs_val, lorb) +c +c write(6,*) 'alpha, beta, dx =',alpha, beta, dx +c + do io = 1, 29 +c + ic_occ(io) = int(abs_occ(io) - abs_val(io)) + iabs_occ(io) = int(abs_occ(io)) +c + if(abs_occ(io).ne.0.d0) then + i_radial = io + write(37,*)'energy for orbital ', orb(io), 2*enp(io),' Ryd' +c + do 40 i=1,kmxn + if(rxd(i).gt.r_max) goto 50 +c find nearest points +c initialize hunting parameter jlo (subroutine nearest) +c + jlo = 1 + call nearest(r,251,rxd(i), + 1 i_point_1,i_point_2,i_point_3,jlo) +c interpolate wavefunction + call interp_quad( + 1 r(i_point_1),dgc(i_point_1,i_radial), + 1 r(i_point_2),dgc(i_point_2,i_radial), + 1 r(i_point_3),dgc(i_point_3,i_radial), + 1 rxd(i),rpx(i),dm1,dm2 ) + 40 continue + 50 continue +c +c Normalize for neglecting lower Dirac component +c + id = 1 + do k = 1, kmxn + rid(k) = rpx(k)**2*rxd(k)/(alpha*rxd(k) + + & beta) + enddo + call defintr(rid,dx,kmxn,ax,id) +c + fnis = sqrt(ax) + write(37,*) 'normalization factor for orbital ', + & orb(io),' =',fnis + do k = 1, kmxn + write(37,*) rxd(k), rpx(k)/fnis/rxd(k) + enddo +c + endif +c + enddo +c + return + end +C +C + subroutine occupancy(iz, atom_occ, atom_val, lorb) +c + implicit real*8(a-h,o-z) +c +c Table for each element has occupation of the various levels. +c The order of the levels in each array is: + +c element level principal qn (nqn), kappa qn (nk) +c 1 1s 1 -1 +c 2 2s 2 -1 +c 3 2p1/2 2 1 +c 4 2p3/2 2 -2 +c 5 3s 3 -1 +c 6 3p1/2 3 1 +c 7 3p3/2 3 -2 +c 8 3d3/2 3 2 +c 9 3d5/2 3 -3 +c 10 4s 4 -1 +c 11 4p1/2 4 1 +c 12 4p3/2 4 -2 +c 13 4d3/2 4 2 +c 14 4d5/2 4 -3 +c 15 4f5/2 4 3 +c 16 4f7/2 4 -4 +c 17 5s 5 -1 +c 18 5p1/2 5 1 +c 19 5p3/2 5 -2 +c 20 5d3/2 5 2 +c 21 5d5/2 5 -3 +c 22 5f5/2 5 3 +c 23 5f7/2 5 -4 +c 24 6s 6 -1 +c 25 6p1/2 6 1 +c 26 6p3/2 6 -2 +c 27 6d3/2 6 2 +c 28 6d5/2 6 -3 +c 29 7s 7 -1 + +c dimension den(30), nqn(30), nk(30), xnel(30), xnval(30) + dimension kappa (29), lorb(29) + real*8 iocc, ival + dimension iocc (97, 29), ival (97, 29) + dimension nnum (29) + dimension atom_occ(29) + dimension atom_val(29) + +c kappa quantum number for each orbital +c k = - (j + 1/2) if l = j - 1/2 +c k = + (j + 1/2) if l = j + 1/2 + data kappa /-1,-1, 1,-2,-1, 1,-2, 2,-3,-1, 1,-2, 2,-3, 3, + 1 -4,-1, 1,-2, 2, -3, 3,-4,-1, 1, -2, 2,-3,-1/ + +c principal quantum number (energy eigenvalue) + data nnum /1,2,2,2,3, 3,3,3,3,4, 4,4,4,4,4, + 1 4,5,5,5,5, 5,5,5,6,6, 6,6,6,7/ + +c occupation of each level for z = 1, 97 + data (iocc( 1,i),i=1,29) /1,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 1,i),i=1,29) /1,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 2,i),i=1,29) /2,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 2,i),i=1,29) /2,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 3,i),i=1,29) /2,1,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 3,i),i=1,29) /0,1,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 4,i),i=1,29) /2,2,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 4,i),i=1,29) /0,2,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 5,i),i=1,29) /2,2,1,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 5,i),i=1,29) /0,2,1,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 6,i),i=1,29) /2,2,2,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 6,i),i=1,29) /0,2,2,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 7,i),i=1,29) /2,2,2,1,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 7,i),i=1,29) /0,2,2,1,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 8,i),i=1,29) /2,2,2,2,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 8,i),i=1,29) /0,2,2,2,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc( 9,i),i=1,29) /2,2,2,3,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival( 9,i),i=1,29) /0,2,2,3,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(10,i),i=1,29) /2,2,2,4,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(10,i),i=1,29) /0,2,2,4,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(11,i),i=1,29) /2,2,2,4,1, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(11,i),i=1,29) /0,0,0,0,1, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(12,i),i=1,29) /2,2,2,4,2, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(12,i),i=1,29) /0,0,0,0,2, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(13,i),i=1,29) /2,2,2,4,2, 1,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(13,i),i=1,29) /0,0,0,0,2, 1,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(14,i),i=1,29) /2,2,2,4,2, 2,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(14,i),i=1,29) /0,0,0,0,2, 2,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(15,i),i=1,29) /2,2,2,4,2, 2,1,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(15,i),i=1,29) /0,0,0,0,2, 2,1,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(16,i),i=1,29) /2,2,2,4,2, 2,2,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(16,i),i=1,29) /0,0,0,0,2, 2,2,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(17,i),i=1,29) /2,2,2,4,2, 2,3,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(17,i),i=1,29) /0,0,0,0,2, 2,3,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(18,i),i=1,29) /2,2,2,4,2, 2,4,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(18,i),i=1,29) /0,0,0,0,2, 2,4,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(19,i),i=1,29) /2,2,2,4,2, 2,4,0,0,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(19,i),i=1,29) /0,0,0,0,0, 0,0,0,0,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(20,i),i=1,29) /2,2,2,4,2, 2,4,0,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(20,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(21,i),i=1,29) /2,2,2,4,2, 2,4,1,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(21,i),i=1,29) /0,0,0,0,0, 0,0,1,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(22,i),i=1,29) /2,2,2,4,2, 2,4,2,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(22,i),i=1,29) /0,0,0,0,0, 0,0,2,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(23,i),i=1,29) /2,2,2,4,2, 2,4,3,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(23,i),i=1,29) /0,0,0,0,0, 0,0,3,0,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(24,i),i=1,29) /2,2,2,4,2, 2,4,4,1,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(24,i),i=1,29) /0,0,0,0,0, 0,0,4,1,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(25,i),i=1,29) /2,2,2,4,2, 2,4,4,1,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(25,i),i=1,29) /0,0,0,0,0, 0,0,4,1,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(26,i),i=1,29) /2,2,2,4,2, 2,4,4,2,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(26,i),i=1,29) /0,0,0,0,0, 0,0,4,2,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(27,i),i=1,29) /2,2,2,4,2, 2,4,4,3,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(27,i),i=1,29) /0,0,0,0,0, 0,0,4,3,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(28,i),i=1,29) /2,2,2,4,2, 2,4,4,4,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(28,i),i=1,29) /0,0,0,0,0, 0,0,4,4,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(29,i),i=1,29) /2,2,2,4,2, 2,4,4,6,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(29,i),i=1,29) /0,0,0,0,0, 0,0,4,6,1, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(30,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(30,i),i=1,29) /0,0,0,0,0, 0,0,4,6,2, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(31,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 1,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(31,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 1,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(32,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(32,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(33,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,1,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(33,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,1,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(34,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,2,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(34,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,2,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(35,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,3,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(35,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,3,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(36,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(36,i),i=1,29) /0,0,0,0,0, 0,0,0,0,2, 2,4,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(37,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,0,0,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(37,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(38,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,0,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(38,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(39,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,1,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(39,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,1,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(40,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,2,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(40,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,2,0,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(41,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,0,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(41,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,0,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(42,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,1,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(42,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,1,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(43,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,1,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(43,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,1,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(44,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,3,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(44,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,3,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(45,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,4,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(45,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,4,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(46,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(46,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,6,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(47,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(47,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,6,0, + 1 0,1,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(48,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(48,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,4,6,0, + 1 0,2,0,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(49,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,1,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(49,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,1,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(50,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,0,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(50,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,0,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(51,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,1,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(51,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,1,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(52,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,2,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(52,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,2,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(53,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,3,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(53,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,3,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(54,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,4,0, 0,0,0,0,0, 0,0,0,0/ + data (ival(54,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,2,2,4,0, 0,0,0,0,0, 0,0,0,0/ + data (iocc(55,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,4,0, 0,0,0,1,0, 0,0,0,0/ + data (ival(55,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,1,0, 0,0,0,0/ + data (iocc(56,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(56,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(57,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,0, + 1 0,2,2,4,1, 0,0,0,2,0, 0,0,0,0/ + data (ival(57,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,1, 0,0,0,2,0, 0,0,0,0/ + data (iocc(58,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,2, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(58,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,2, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(59,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,3, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(59,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,3, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(60,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,4, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(60,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,4, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(61,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,5, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(61,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,5, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(62,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 0,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(62,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(63,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 1,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(63,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 1,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(64,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 1,2,2,4,1, 0,0,0,2,0, 0,0,0,0/ + data (ival(64,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 1,0,0,0,1, 0,0,0,2,0, 0,0,0,0/ + data (iocc(65,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 3,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(65,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 3,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(66,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 4,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(66,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 4,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(67,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 5,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(67,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 5,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(68,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 6,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(68,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 6,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(69,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 7,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(69,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 7,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(70,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,0, 0,0,0,2,0, 0,0,0,0/ + data (ival(70,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, + 1 8,0,0,0,0, 0,0,0,2,0, 0,0,0,0/ + data (iocc(71,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,1, 0,0,0,2,0, 0,0,0,0/ + data (ival(71,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,1, 0,0,0,2,0, 0,0,0,0/ + data (iocc(72,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,2, 0,0,0,2,0, 0,0,0,0/ + data (ival(72,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,2, 0,0,0,2,0, 0,0,0,0/ + data (iocc(73,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,3, 0,0,0,2,0, 0,0,0,0/ + data (ival(73,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,3, 0,0,0,2,0, 0,0,0,0/ + data (iocc(74,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 0,0,0,2,0, 0,0,0,0/ + data (ival(74,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 0,0,0,2,0, 0,0,0,0/ + data (iocc(75,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 1,0,0,2,0, 0,0,0,0/ + data (ival(75,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 1,0,0,2,0, 0,0,0,0/ + data (iocc(76,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 2,0,0,2,0, 0,0,0,0/ + data (ival(76,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 2,0,0,2,0, 0,0,0,0/ + data (iocc(77,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 3,0,0,2,0, 0,0,0,0/ + data (ival(77,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 3,0,0,2,0, 0,0,0,0/ + data (iocc(78,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 5,0,0,1,0, 0,0,0,0/ + data (ival(78,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 5,0,0,1,0, 0,0,0,0/ + data (iocc(79,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,1,0, 0,0,0,0/ + data (ival(79,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 6,0,0,1,0, 0,0,0,0/ + data (iocc(80,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,0, 0,0,0,0/ + data (ival(80,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,4, 6,0,0,2,0, 0,0,0,0/ + data (iocc(81,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,1, 0,0,0,0/ + data (ival(81,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,1, 0,0,0,0/ + data (iocc(82,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 0,0,0,0/ + data (ival(82,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 0,0,0,0/ + data (iocc(83,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 1,0,0,0/ + data (ival(83,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 1,0,0,0/ + data (iocc(84,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 2,0,0,0/ + data (ival(84,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 2,0,0,0/ + data (iocc(85,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 3,0,0,0/ + data (ival(85,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 3,0,0,0/ + data (iocc(86,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,0,0,0/ + data (ival(86,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,2,2, 4,0,0,0/ + data (iocc(87,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,0,0,1/ + data (ival(87,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,1/ + data (iocc(88,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,0,0,2/ + data (ival(88,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,0,0,2/ + data (iocc(89,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,1,0,2/ + data (ival(89,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,1,0,2/ + data (iocc(90,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,0,0,2,2, 4,2,0,2/ + data (ival(90,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,0,0,0,0, 0,2,0,2/ + data (iocc(91,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,2,0,2,2, 4,1,0,2/ + data (ival(91,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,2,0,0,0, 0,1,0,2/ + data (iocc(92,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,3,0,2,2, 4,1,0,2/ + data (ival(92,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,3,0,0,0, 0,1,0,2/ + data (iocc(93,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,4,0,2,2, 4,1,0,2/ + data (ival(93,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,4,0,0,0, 0,1,0,2/ + data (iocc(94,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,6,0,2,2, 4,0,0,2/ + data (ival(94,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,6,0,0,0, 0,0,0,2/ + data (iocc(95,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,6,1,2,2, 4,0,0,2/ + data (ival(95,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,6,1,0,0, 0,0,0,2/ + data (iocc(96,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,6,2,2,2, 4,0,0,2/ + data (ival(96,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,6,2,0,0, 0,0,0,2/ + data (iocc(97,i),i=1,29) /2,2,2,4,2, 2,4,4,6,2, 2,4,4,6,6, + 1 8,2,2,4,4, 6,6,3,2,2, 4,0,0,2/ + data (ival(97,i),i=1,29) /0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, + 1 0,0,0,0,0, 0,6,3,0,0, 0,0,0,2/ + +c + do i = 1, 29 + atom_occ(i) = iocc(iz,i) + atom_val(i) = ival(iz,i) + if(kappa(i).gt.0) lorb(i) = kappa(i) + if(kappa(i).lt.0) lorb(i) = - (kappa(i) + 1) + enddo +c + return + end +c +c + subroutine val_plasmon +c + implicit real*8 (a-h,o-z) +c + include 'msxas3.inc' +c + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $ n_=ltot_*ua_,rd_=440,sd_=ua_-1) +c + character*8 name0 ,nsymbl +c + common /param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,xe,ev +c + dimension atom_occ(29), atom_val(29), lorb(29) +c + character*40 title +c +c pi = 3.14159265358979323846264338D0 + pi = 3.141592653589793d0 +c +c + write(6,*)'-------------------------------' +c + sumv = 0.d0 +c + do na = 1, nat + iz = nz(na) +c write(6,*)' na, nz(na) =', na, iz +c +c calculate atomic orbital occupancy +c + call occupancy(iz, atom_occ, atom_val, lorb) +c write(6,*)' na, iz, atom_val =', na, iz, atom_val +c + do i = 1, 29 +c + if(atom_val(i).ne.0.d0) then + sumv = sumv + atom_val(i) + endif +c + enddo +c + enddo +c + os_vol = 4.d0*pi*rs_os**3/3.d0 + rho_pl = sumv/os_vol + ot = 1./3. + rs_v = (3.d0/(4.d0*pi*rho_pl))**ot + omega_p = 41.7d0 / (rs_v)**1.5 + write(6,*) ' density of the valence charge (au^{-3}', rho_pl + write(6,*) ' rs_v corresponding to valence density (au)', rs_v + write(6,*) ' valence plasmon energy (in eV) ', omega_p +c + return + end +C +C + SUBROUTINE RADIALX(NE,RELC,EIKAPPR) +c + implicit real*8 (a-h,o-z) +c + INCLUDE 'msxas3.inc' + integer at_,d_,rd_,ltot_,sd_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=lmax_+1, + $n_=ltot_*ua_,rd_=440,sd_=ua_-1) +C +c.....this subroutine calculates the radial matrix elements d(i) +c.....(i=1,2) for lfin=l0i-1 (i=1) and lfin=l0i+1 (i=2) both for +c.....the regular (dmxx) and irregular solution (dmxx1) using a +c.....linear-log mesh +c +CST ==> Phagen to Python shared object modifications +CST I replaced the line below +CST common/mtxele/ nstart,nlast + common/mtxele/ nstart,nlast,dmx(2),dmx1(2),qmx(3),qmx1(3), + $ dxdir,dxexc,nfis,nfis1,nfis2 + real*8 nfis,nfis2,nfis1 + complex*16 dmx,dmx1,qmx,qmx1,dxdir,dxexc +CST Phagen to Python shared object modifications <== +c + common/mtxelex/ dmxx(2),dmxx1(2),dmxxa(2),dmxxa1(2), + & qmxx(3),qmxx1(3),qmxxa(3),qmxxa1(3), + & dxxdir,dxxexc,mdxx,mdxx1,mdxxa,mdxxa1, + & omxx(4),omxx1(4),omxxa(4),omxxa1(4), + & dqxx1(2,3),dmmx1(2),dqxxa1(2,3),dmmxa1(2) + complex*16 dmxx,dmxx1,dmxxa,dmxxa1,qmxx,qmxx1,qmxxa,qmxxa1, + & dxxdir,dxxexc,mdxx,mdxx1,mdxxa,mdxxa1, + & omxx,omxx1,omxxa,omxxa1,dqxx1,dmmx1,dqxxa1,dmmxa1 +c + common/param/eftr,gamma,vcon,xe,ev,e,iout,nat,ndat,nspins, + 1 nas,rs(at_),xv(at_),yv(at_),zv(at_),exfact(at_),z(at_), + 3 lmaxx(at_),nz(at_),nsymbl(at_), + 4 neq(at_),name0,cip,emax,emin,de,rs_os + complex*16 vcon,ev,xe + character*8 nsymbl,name0 +c + common/bessel/sbf(ltot_),dsbf(ltot_),shf(ltot_),dshf(ltot_) + complex*16 sbf,dsbf,shf,dshf +C + COMMON /LLM/ ALPHA, BETA +C + COMMON /FCNRLM/X(RDX_,D_), RX(RDX_,D_), HX(D_), VX(RDX_,SD_), + & VXR(RDX_,SD_), DVX(RDX_,SD_), BX(RDX_,SD_), + & VXSO(RDX_,SD_), KMX(AT_), KPLX(AT_) + complex*16 VX, VXR, DVX, BX, VXSO +C + COMMON /PDQX/PX(RDX_,fl_), PX0(RDX_,fl_), PPX(RDX_,fl_), + & PAX(RDX_,fl_), RAMFNR(N_), RAMFSR(N_), RAMFSOP(N_), + & RAMFSOA(N_) + complex*16 PX, PX0, PPX, PAX, RAMFNR, RAMFSR, RAMFSOP, RAMFSOA + complex*16 RAMFC +c +C + COMMON/PDQIX/RPIX(RDX_), FNISX + complex*16 RPIX +C +c + common /ccs/ccwf(rdx_) +c + common /state/ natom(n_),ln(n_),nleq(at_), + 1 nns,nuatom,ndg,nls(at_),n0l(at_),n0(at_), + 2 nterms(at_),lmaxn(at_),ndim,lmxne(at_,nep_) +C +c ######### common pottype modified to consider also the Auger calcu +c + common/pot_type/i_absorber,i_absorber_hole,i_absorber_hole1, + * i_absorber_hole2,i_norman,i_alpha, + 1 i_outer_sphere,i_exc_pot,i_mode +c + common/auger/calctype,expmode,edge1,edge2 +c + common/eels/einc,esct,scangl,qt,lambda,eelsme(npss,npss,npss), + & p1(rdx_,npss,nef_),p2(rdx_,npss,nef_), + & p3(rdx_,npss,nef_),ramfsr1(npss,nef_), + & ramfsr2(npss,nef_),ramfsr3(npss,nef_), + & lmxels(3,ua_),p3irreg(rdx_,7),p2irreg(rdx_,7) + complex*16 eelsme,p1,p2,p3,ramfsr1,ramfsr2,ramfsr3,p3irreg, + & p2irreg + real*8 lambda + complex*16 qtc, arg, ydf, scprod +c + character*3 calctype, expmode, eikappr + character*2 edge1,edge2 +C + common /lparam/lmax2(nat_),l0i +c + DIMENSION RID(RDX_),CRI(RDX_),CRI1(RDX_) + complex*16 RID,CRI,CRI1,DX,DX1,SMX0,SMX1 +C + CHARACTER*2 RELC +C +C +c*************************************************************************** +c note that here rpix(k) = r**3*pi(k). +c wf rpix(k) is already normalized +c (see subroutine corewf) +c*************************************************************************** +c + pi = 3.1415926 +c + id = 1 + nq = nas + kx = kmx(nq) - 3 + dh = hx(nq) +c + write(6,*)' check orthogonality between core and continuum', + & ' state' + np = l0i + 1 + do k = 1, kx + if(relc.eq.'nr') + & rid(k)=rpix(k)*px(k,np+1)/(alpha*rx(k,nq) + beta) + if(relc.eq.'sr') + & rid(k)=rpix(k)*px0(k,np+1)/(alpha*rx(k,nq) + beta) + enddo + if(relc.eq.'nr') ramfc = ramfnr(nstart+np) + if(relc.eq.'sr') ramfc = ramfsr(nstart+np) + call defint0(rid,dh,kx,scprod,id) + write(6,*)' scalar product between core and continuum', + & ' state =', scprod/ramfc !*sqrt(xe/pi) +c + write(6,*) ' --- sqrt(xe/pi) =', sqrt(xe/pi) +c +c write(6,*)' check orthogonality between calculated core ', +c & ' and continuum state' +c do k = 1, kx +c if(relc.eq.'nr') +c & rid(k)=ccwf(k)*px(k,np+1)/(alpha*rx(k,nq) + beta) +c if(relc.eq.'sr') +c & rid(k)=ccwf(k)*px0(k,np+1)/(alpha*rx(k,nq) + beta) +c enddo +c call defint1(rid,dh,kx,scprod,id) +c if(relc.eq.'nr') ramfc = ramfnr(nstart+np) +c if(relc.eq.'sr') ramfc = ramfsr(nstart+np) +c write(6,*)' scalar product between calc core and continuum', +c & ' state =', scprod/ramfc !*sqrt(xe/pi) +c + if((calctype.eq.'els'.or.calctype.eq.'e2e') + & .and.eikappr.eq.'yes') then + ydf=(0.0,0.0) + qtc = cmplx(qt,0.0) + ml=lmxne(nq,ne)+1 + if (ml.lt.3) ml = 3 + do np = 0, ml-1 + do k = 1, kx + arg=qtc*rx(k,nq) + call csbf(arg,ydf,ml,sbf,dsbf) + if(relc.eq.'nr') + & rid(k)=rpix(k)*px(k,np+1)*cmplx(sbf(np+1))/ + 1 (alpha*rx(k,nq) + beta) + if(relc.eq.'sr') + & rid(k)=rpix(k)*px0(k,np+1)*cmplx(sbf(np+1))/ + 1 (alpha*rx(k,nq) + beta) + enddo +c call defint1(rid,dh,kx,eelsme(np+1),id) +c eelsme(np+1) = (eelsme(np+1)/ramfsr(nstart+np))**2*xe/pi +c write(6,*) 'l =',np,'eelsme =', eelsme(np+1) +c write(6,*) 'l =',np,'sqrt(eelsme) =', sqrt(eelsme(np+1)) + enddo +c + endif +c +c 21 if(calctype.eq.'xpd'.or.eikappr.eq.' no') then + 21 if (calctype.eq.'xpd'.or.calctype.eq.'xas'.or. + & calctype.eq.'rex'.or.eikappr.eq.' no') then +c +C.....CALCULATE RADIAL DIPOLE TRANSITION MATRIX ELEMENT +c + do 100 i=1,2 + dmxx(i)=(0.,0.) + dmxx1(i)=(0.,0.) + if((l0i.eq.0).and.(i.eq.1))goto 100 + np = l0i + (-1)**i +C + if(relc.eq.'nr') then +c + DO 116 K=1,KX + 116 RID(K)=RPIX(K)*PX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DMXX(I) = (CRI(KX)/RAMFNR(NSTART+NP))**2*(L0I-1+I) +c dmx(i) = (cri(kx)/ramf(nstart+np))**2*(l0i-1+i) + DO 117 K=1,KX + 117 RID(K)=RPIX(K)*PX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 118 K=1,KX + 118 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 119 K=1,KX + 119 RID(K)=RPIX(K)*PX(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DMXX1(I) = (SMX0 + SMX1)*(L0I-1+I)/RAMFNR(NSTART+NP) +c + else if(relc.eq.'sr') then + DO K=1,KX + RID(K)=RPIX(K)*PX0(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DMXX(I) = (CRI(KX)/RAMFSR(NSTART+NP))**2*(L0I-1+I) + DO 120 K=1,KX + 120 RID(K)=RPIX(K)*PX0(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 121 K=1,KX + 121 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 122 K=1,KX + 122 RID(K)=RPIX(K)*PX0(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DMXX1(I) = (SMX0 + SMX1)*(L0I-1+I)/RAMFSR(NSTART+NP) +c + else if(relc.eq.'so') then + DO K=1,KX + RID(K)=RPIX(K)*PPX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DMXX(I) = (CRI(KX)/RAMFSOP(NSTART+NP))**2*(L0I-1+I) + DO 123 K=1,KX + 123 RID(K)=RPIX(K)*PPX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 124 K=1,KX + 124 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 125 K=1,KX + 125 RID(K)=RPIX(K)*PPX(K,NP)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DMXX1(I) = (SMX0 + SMX1)*(L0I-1+I)/RAMFSOP(NSTART+NP) +C + DO K=1,KX + RID(K)=RPIX(K)*PAX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DMXXA(I) = (CRI(KX)/RAMFSOA(NSTART+NP))**2*(L0I-1+I) + DO 126 K=1,KX + 126 RID(K)=RPIX(K)*PAX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 127 K=1,KX + 127 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,DX,ID) + DO 128 K=1,KX + 128 RID(K)=RPIX(K)*PAX(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,DX1,ID) + DMXXA1(I) = (DX + DX1)*(L0I-1+I)/RAMFSOA(NSTART+NP) +c + endif + + 100 continue +c +c +C.....CALCULATE RADIAL MAGNETIC DIPOLE TRANSITION MATRIX ELEMENT +c + MDXX = (0.,0.) + MDXX1 = (0.,0.) +c + np = l0i +C + if(relc.eq.'nr') then +c + DO 316 K=1,KX + 316 RID(K)=RPIX(K)*PX(K,NP+1)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + MDXX = (CRI(KX)/RAMFNR(NSTART+NP))**2 + DO 317 K=1,KX + 317 RID(K)=RPIX(K)*PX(K,NP+1+NPSS)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 318 K=1,KX + 318 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 319 K=1,KX + 319 RID(K)=RPIX(K)*PX(K,NP+1)*(CRI1(KX) - CRI1(K)) + & /(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + MDXX1 = (SMX0 + SMX1)/RAMFNR(NSTART+NP) +c + else if(relc.eq.'sr') then + DO K=1,KX + RID(K)=RPIX(K)*PX0(K,NP+1)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + MDXX = (CRI(KX)/RAMFSR(NSTART+NP))**2 + DO 320 K=1,KX + 320 RID(K)=RPIX(K)*PX0(K,NP+1+NPSS)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 321 K=1,KX + 321 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 322 K=1,KX + 322 RID(K)=RPIX(K)*PX0(K,NP+1)*(CRI1(KX) - CRI1(K)) + & /(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + MDXX1 = (SMX0 + SMX1)/RAMFSR(NSTART+NP) +c + else if(relc.eq.'so') then + DO K=1,KX + RID(K)=RPIX(K)*PPX(K,NP+1)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + MDXX = (CRI(KX)/RAMFSOP(NSTART+NP))**2 + DO 323 K=1,KX + 323 RID(K)=RPIX(K)*PPX(K,NP+1+NPSS)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 324 K=1,KX + 324 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 325 K=1,KX + 325 RID(K)=RPIX(K)*PPX(K,NP)*(CRI1(KX) - CRI1(K)) + & /(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + MDXX1 = (SMX0 + SMX1)/RAMFSOP(NSTART+NP) +C + DO K=1,KX + RID(K)=RPIX(K)*PAX(K,NP+1)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + MDXXA = (CRI(KX)/RAMFSOA(NSTART+NP))**2 + DO 326 K=1,KX + 326 RID(K)=RPIX(K)*PAX(K,NP+1+NPSS)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 327 K=1,KX + 327 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,DX,ID) + DO 328 K=1,KX + 328 RID(K)=RPIX(K)*PAX(K,NP+1)*(CRI1(KX) - CRI1(K)) + & /(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,DX1,ID) + MDXXA1 = (DX + DX1)/RAMFSOA(NSTART+NP) +c + endif +c +C +c write(6,*) ' radialx matrix elements from shell li = ', l0i +c write(6,*) (dble(dmxx(l)),aimag(dmxx(l)),l=1,2) +c write(6,*) (dble(dmxx1(l)),aimag(dmxx1(l)),l=1,2) +C +C.....CALCULATE RADIAL QUADRUPOLE TRANSITION MATRIX ELEMENT +C + DO K = 1, KX + RPIX(K) = RPIX(K) * RX(K,NQ) + ENDDO +C + M = 0 + DO 200 I=-2,2,2 + M = M + 1 + QMXX(M)=(0.,0.) + QMXX1(M)=(0.,0.) + LF = L0I + I + IF(LF.LT.0) GO TO 200 + NP = L0I + I +C + if(relc.eq.'nr') then +c + DO 216 K=1,KX + 216 RID(K)=RPIX(K)*PX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + QMXX(M) = (CRI(KX)/RAMFNR(NSTART+NP))**2 +c dmx(i) = (cri(kx)/ramf(nstart+np))**2*(l0i-1+i) + DO 217 K=1,KX + 217 RID(K)=RPIX(K)*PX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 218 K=1,KX + 218 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 219 K=1,KX + 219 RID(K)=RPIX(K)*PX(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + QMXX1(M) = (SMX0 + SMX1)/RAMFNR(NSTART+NP) +c dmx1(i) = (dx+dx1)*(l0i-1+i)/ramf(nstart+np) +c + else if(relc.eq.'sr') then + DO K=1,KX + RID(K)=RPIX(K)*PX0(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + QMXX(M) = (CRI(KX)/RAMFSR(NSTART+NP))**2 + DO 220 K=1,KX + 220 RID(K)=RPIX(K)*PX0(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 221 K=1,KX + 221 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 222 K=1,KX + 222 RID(K)=RPIX(K)*PX0(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + QMXX1(M) = (SMX0 + SMX1)/RAMFSR(NSTART+NP) +c + else if(relc.eq.'so') then + DO K=1,KX + RID(K)=RPIX(K)*PPX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + QMXX(M) = (CRI(KX)/RAMFSOP(NSTART+NP))**2 + DO 223 K=1,KX + 223 RID(K)=RPIX(K)*PPX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 224 K=1,KX + 224 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 225 K=1,KX + 225 RID(K)=RPIX(K)*PPX(K,NP)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + QMXX1(M) = (SMX0 + SMX1)/RAMFSOP(NSTART+NP) +C + DO K=1,KX + RID(K)=RPIX(K)*PAX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + QMXXA(M) = (CRI(KX)/RAMFSOA(NSTART+NP))**2 + DO 226 K=1,KX + 226 RID(K)=RPIX(K)*PAX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 227 K=1,KX + 227 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,DX,ID) + DO 228 K=1,KX + 228 RID(K)=RPIX(K)*PAX(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,DX1,ID) + QMXXA1(M) = (DX + DX1)/RAMFSOA(NSTART+NP) +c + endif +C + 200 CONTINUE +C +C +C.....CALCULATE RADIAL OCTUPOLE TRANSITION MATRIX ELEMENT +C + DO K = 1, KX + RPIX(K) = RPIX(K) * RX(K,NQ) + ENDDO +C + M = 0 + DO 400 I=-3,3,2 + M = M + 1 + OMXX(M)=(0.,0.) + OMXX1(M)=(0.,0.) + LF = L0I + I + IF(LF.LT.0) GO TO 400 + NP = L0I + I +C + if(relc.eq.'nr') then +c + DO 416 K=1,KX + 416 RID(K)=RPIX(K)*PX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + OMXX(M) = (CRI(KX)/RAMFNR(NSTART+NP))**2 +c dmx(i) = (cri(kx)/ramf(nstart+np))**2*(l0i-1+i) + DO 417 K=1,KX + 417 RID(K)=RPIX(K)*PX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 418 K=1,KX + 418 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 419 K=1,KX + 419 RID(K)=RPIX(K)*PX(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + OMXX1(M) = (SMX0 + SMX1)/RAMFNR(NSTART+NP) +c dmx1(i) = (dx+dx1)*(l0i-1+i)/ramf(nstart+np) +c + else if(relc.eq.'sr') then + DO K=1,KX + RID(K)=RPIX(K)*PX0(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + OMXX(M) = (CRI(KX)/RAMFSR(NSTART+NP))**2 + DO 420 K=1,KX + 420 RID(K)=RPIX(K)*PX0(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 421 K=1,KX + 421 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 422 K=1,KX + 422 RID(K)=RPIX(K)*PX0(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + OMXX1(M) = (SMX0 + SMX1)/RAMFSR(NSTART+NP) +c + else if(relc.eq.'so') then + DO K=1,KX + RID(K)=RPIX(K)*PPX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + OMXX(M) = (CRI(KX)/RAMFSOP(NSTART+NP))**2 + DO 423 K=1,KX + 423 RID(K)=RPIX(K)*PPX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 424 K=1,KX + 424 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 425 K=1,KX + 425 RID(K)=RPIX(K)*PPX(K,NP)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + OMXX1(M) = (SMX0 + SMX1)/RAMFSOP(NSTART+NP) +C + DO K=1,KX + RID(K)=RPIX(K)*PAX(K,NP+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + ENDDO + CALL INTEGRCM(RID,DH,KX,CRI,ID) + OMXXA(M) = (CRI(KX)/RAMFSOA(NSTART+NP))**2 + DO 426 K=1,KX + 426 RID(K)=RPIX(K)*PAX(K,NP+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 427 K=1,KX + 427 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,DX,ID) + DO 428 K=1,KX + 428 RID(K)=RPIX(K)*PAX(K,NP+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL DEFINT1(RID,DH,KX,DX1,ID) + OMXXA1(M) = (DX + DX1)/RAMFSOA(NSTART+NP) +c + endif +C + 400 CONTINUE + +C +C.....RESET RPI(K) TO INITIAL VALUE +C + DO K = 1, KX +C RPIX(K) = RPIX(K) / RX(K,NQ) + RPIX(K) = RPIX(K) / (RX(K,NQ))**2 + ENDDO +C +C.....CALCULATE DIPOLE-QUADRUPOLE IRREGULAR INTEFERENCE TERM +C + DO I=1,2 + DMMX1(I)=(0.0,0.0) + M = 0 + DO J=-2,2,2 + M = M + 1 + DQXX1(I,M)=(0.0,0.0) + ENDDO + ENDDO +C + DO 500 I=1,2 + IF((L0I.EQ.0).AND.(I.EQ.1)) GOTO 500 + NPD = L0I + (-1)**I +C + M = 0 + DO 501 J=-2,2,2 + M = M + 1 + LF = L0I + J + IF(LF.LE.0) GO TO 501 + NPQ = L0I + J +C + if(relc.eq.'nr') then +c + DO 516 K=1,KX + 516 RID(K)=RPIX(K)*PX(K,NPQ+1)*RX(K,NQ)**2/(ALPHA*RX(K,NQ) + BETA) + & /RAMFNR(NSTART+NPQ) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DO 517 K=1,KX + 517 RID(K)=RPIX(K)*PX(K,NPD+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 518 K=1,KX + 518 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 519 K=1,KX + 519 RID(K)=RPIX(K)*PX(K,NPQ+1+NPSS)*RX(K,NQ)**2 + & /(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 520 K=1,KX + 520 RID(K)=RPIX(K)*PX(K,NPD+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA)/RAMFNR(NSTART+NPD) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DQXX1(I,M) = (SMX0 + SMX1) +c + else if(relc.eq.'sr') then +c + DO 526 K=1,KX + 526 RID(K)=RPIX(K)*PX0(K,NPQ+1)*RX(K,NQ)**2/(ALPHA*RX(K,NQ) + BETA) + & /RAMFSR(NSTART+NPQ) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DO 527 K=1,KX + 527 RID(K)=RPIX(K)*PX0(K,NPD+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 528 K=1,KX + 528 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 529 K=1,KX + 529 RID(K)=RPIX(K)*PX0(K,NPQ+1+NPSS)*RX(K,NQ)**2 + & /(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 530 K=1,KX + 530 RID(K)=RPIX(K)*PX0(K,NPD+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA)/RAMFSR(NSTART+NPD) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DQXX1(I,M) = (SMX0 + SMX1) +C + else if(relc.eq.'so') then +C + DO 531 K=1,KX + 531 RID(K)=RPIX(K)*PPX(K,NPQ+1)*RX(K,NQ)**2/(ALPHA*RX(K,NQ) + BETA) + & /RAMFSOP(NSTART+NPQ) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DO 532 K=1,KX + 532 RID(K)=RPIX(K)*PPX(K,NPD+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 533 K=1,KX + 533 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 534 K=1,KX + 534 RID(K)=RPIX(K)*PPX(K,NPQ+1+NPSS)*RX(K,NQ)**2 + & /(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 535 K=1,KX + 535 RID(K)=RPIX(K)*PPX(K,NPD+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA)/RAMFSOP(NSTART+NPD) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DQXX1(I,M) = (SMX0 + SMX1) +c + DO 536 K=1,KX + 536 RID(K)=RPIX(K)*PAX(K,NPQ+1)*RX(K,NQ)**2/(ALPHA*RX(K,NQ) + BETA) + & /RAMFSOA(NSTART+NPQ) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DO 537 K=1,KX + 537 RID(K)=RPIX(K)*PAX(K,NPD+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 538 K=1,KX + 538 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 539 K=1,KX + 539 RID(K)=RPIX(K)*PAX(K,NPQ+1+NPSS)*RX(K,NQ)**2 + & /(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 540 K=1,KX + 540 RID(K)=RPIX(K)*PAX(K,NPD+1)*(CRI1(KX) - CRI1(K))* + & RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA)/RAMFSOA(NSTART+NPD) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DQXXA1(I,M) = (SMX0 + SMX1) +C + ENDIF +C + 501 CONTINUE +C + 500 CONTINUE +C +C.....CALCULATE MAGNETIC DIPOLE-ELECTRIC DIPOLE IRREGULAR INTEFERENCE TERM +C + DO 600 I=1,2 + IF((L0I.EQ.0).AND.(I.EQ.1)) GOTO 600 + NPD = L0I + (-1)**I + NPM = L0I +C + if(relc.eq.'nr') then +c + DO 611 K=1,KX + 611 RID(K)=RPIX(K)*PX(K,NPD+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + & /RAMFNR(NSTART+NPD) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DO 612 K=1,KX + 612 RID(K)=RPIX(K)*PX(K,NPM+1+NPSS)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 613 K=1,KX + 613 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 614 K=1,KX + 614 RID(K)=RPIX(K)*PX(K,NPD+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 615 K=1,KX + 615 RID(K)=RPIX(K)*PX(K,NPM+1)*(CRI1(KX) - CRI1(K)) + & /(ALPHA*RX(K,NQ) + BETA)/RAMFNR(NSTART+NPM) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DMMX1(I) = (SMX0 + SMX1) +C + elseif(relc.eq.'sr') then +c + DO 616 K=1,KX + 616 RID(K)=RPIX(K)*PX0(K,NPD+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + & /RAMFSR(NSTART+NPD) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DO 617 K=1,KX + 617 RID(K)=RPIX(K)*PX0(K,NPM+1+NPSS)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 618 K=1,KX + 618 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 619 K=1,KX + 619 RID(K)=RPIX(K)*PX0(K,NPD+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 620 K=1,KX + 620 RID(K)=RPIX(K)*PX0(K,NPM+1)*(CRI1(KX) - CRI1(K)) + & /(ALPHA*RX(K,NQ) + BETA)/RAMFSR(NSTART+NPM) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DMMX1(I) = (SMX0 + SMX1) +c + elseif(relc.eq.'so') then +c + DO 621 K=1,KX + 621 RID(K)=RPIX(K)*PPX(K,NPD+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + & /RAMFSOP(NSTART+NPD) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DO 622 K=1,KX + 622 RID(K)=RPIX(K)*PPX(K,NPM+1+NPSS)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 623 K=1,KX + 623 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 624 K=1,KX + 624 RID(K)=RPIX(K)*PPX(K,NPD+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 625 K=1,KX + 625 RID(K)=RPIX(K)*PPX(K,NPM+1)*(CRI1(KX) - CRI1(K)) + & /(ALPHA*RX(K,NQ) + BETA)/RAMFSOP(NSTART+NPM) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DMMX1(I) = (SMX0 + SMX1) +C + DO 626 K=1,KX + 626 RID(K)=RPIX(K)*PAX(K,NPD+1)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + & /RAMFSOA(NSTART+NPD) + CALL INTEGRCM(RID,DH,KX,CRI,ID) + DO 627 K=1,KX + 627 RID(K)=RPIX(K)*PAX(K,NPM+1+NPSS)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 628 K=1,KX + 628 RID(K)=RID(K)*CRI(K) + CALL DEFINT1(RID,DH,KX,SMX0,ID) + DO 629 K=1,KX + 629 RID(K)=RPIX(K)*PAX(K,NPD+1+NPSS)*RX(K,NQ)/(ALPHA*RX(K,NQ) + BETA) + CALL INTEGRCM(RID,DH,KX,CRI1,ID) + DO 630 K=1,KX + 630 RID(K)=RPIX(K)*PAX(K,NPM+1)*(CRI1(KX) - CRI1(K)) + & /(ALPHA*RX(K,NQ) + BETA)/RAMFSOA(NSTART+NPM) + CALL DEFINT1(RID,DH,KX,SMX1,ID) + DMMXA1(I) = (SMX0 + SMX1) +C + ENDIF +C + 600 CONTINUE +C + else !PUT AUGER PART HERE +C + endif +C + RETURN + END +C +C + SUBROUTINE OSBF(X,Y,MAX,SBF,DSBF) +C REAL*8 SBFK,SBF1,SBF2,XF1,PSUM + IMPLICIT DOUBLE PRECISION (A-H,O-Z) +C +C GENERATES SPHERICAL BESSEL FUNCTIONS OF ORDER 0 - MAX-1 AND THEIR +C FIRST DERIVATIVES WITH RESPECT TO R. X=ARGUMENT= Y*R. +C IF Y=0, NO DERIVATIVES ARE CALCULATED. MAX MUST BE AT LEAST 3. +C OSBF GENERATES ORDINARY SPHERICAL BESSEL FUNCTIONS. MSBF - MODI- +C FIED SPHERICAL BESSEL FUNCTIONS; OSNF - ORD. SPH. NEUMANN FCNS; +C MSNF - MOD. SPH. NEUMANN FCNS; MSHF - MOD. SPH HANKEL FCNS +C + DIMENSION SBF(MAX), DSBF(MAX) + LOGICAL ORD + ORD=.TRUE. + GO TO 1 + ENTRY MSBF(X,Y,MAX,SBF,DSBF) + ORD=.FALSE. +1 IF (MAX.LT.1.OR.MAX.GT.2000) GO TO 99 + IF( ABS(X).LT.0.50D0 ) GO TO 18 +C +C BESSEL FUNCTIONS BY DOWNWARD RECURSION +C + SBF2=0.0D0 + SBF1=1.0D-25 + IF( ABS(X).LT.2.0D0) SBF1=1.0D-38 + JMIN=INT(10+X) + KMAX=MAX+JMIN-1 + K=MAX + XF1=2*KMAX+1 + IF (ORD) GO TO 11 + DO 10 J=1,KMAX + SBFK=XF1*SBF1/X+SBF2 + SBF2=SBF1 + SBF1=SBFK + IF (J.LT.JMIN) GO TO 10 + SBF(K)=SBFK + K=K-1 +10 XF1=XF1-2.0D0 + RAT=SINH(X)/(X*SBF(1)) + DSBF1=SBF2*RAT + GO TO 16 +11 CONTINUE + DO 12 J=1,KMAX + SBFK=XF1*SBF1/X-SBF2 + SBF2=SBF1 + SBF1=SBFK + XF1=XF1-2.0D0 + IF (J.LT.JMIN) GO TO 12 + SBF(K)=SBFK + K=K-1 +12 CONTINUE + 15 RAT=SIN(X)/(X*SBF(1)) + DSBF1=-SBF2*RAT + 16 DO 17 K=1,MAX + 17 SBF(K)=RAT*SBF(K) + GO TO 26 +C +C SMALL ARGUMENTS +C + 18 Z=X*X*0.50D0 + IF(ORD) Z=-Z + A=1.0D0 + MMX=MAX + IF (MAX.EQ.1.AND.Y.NE.0.0D0) MMX=2 + DO 30 J=1,MMX + SBFJ=A + B=A + DO 31 I=1,20 + B=B*Z/(I*(2*(J+1)-1)) + SBFJ=SBFJ+B + IF ( ABS(B).LE.1.0D-07* ABS(SBFJ )) GO TO 29 + 31 CONTINUE +29 IF (J.EQ.2) DSBF1=SBFJ + IF (J.LE.MAX) SBF(J)=SBFJ + 30 A=A*X/ DFLOAT(2*J+1) + IF (ORD) DSBF1=-DSBF1 + GO TO 26 + ENTRY OSNF(X,Y,MAX,SBF,DSBF) + ORD=.TRUE. + SBF2=-COS(X)/X + IF (MAX.EQ.1 .AND. Y.EQ.0.0D0) GO TO 2 + SBF1=(SBF2-SIN(X))/X + DSBF1=-SBF1 + GO TO 2 + ENTRY MSNF(X,Y,MAX,SBF,DSBF) + ORD=.FALSE. + SBF2=COSH(X)/X + IF (MAX.EQ.1 .AND. Y.EQ.0.0D0) GO TO 2 + SBF1=(SINH(X)-SBF2)/X + DSBF1= SBF1 + GO TO 2 + ENTRY MSHF(X,Y,MAX,SBF,DSBF) + ORD=.FALSE. + SBF2=EXP(-X)/X + SBF1=-SBF2/X-SBF2 + DSBF1= SBF1 +2 SBF(1)=SBF2 + IF (MAX.LT.1.OR.MAX.GT.2000) GO TO 99 + IF (MAX.EQ.1) GO TO 26 + SBF(2)=SBF1 + IF (MAX.EQ.2) GO TO 26 + XF1=3.0D0 + IF (ORD) GO TO 21 + DO 8 I=3,MAX + SBFK=SBF2-XF1*SBF1/X + SBF(I)=SBFK + SBF2=SBF1 + SBF1=SBFK +8 XF1=XF1+2.0D0 + GO TO 26 +21 DO 22 I=3,MAX + SBFK=XF1*SBF1/X-SBF2 + SBF(I)=SBFK + SBF2=SBF1 + SBF1=SBFK +22 XF1=XF1+2.0D0 +26 IF (Y.EQ.0.0D0) RETURN + DSBF(1)=Y*DSBF1 + IF (MAX.EQ.1) RETURN + DO 9 I=2,MAX + 9 DSBF(I)=Y*(SBF(I-1)- DFLOAT(I)*SBF(I)/X) + RETURN +99 WRITE(6,100) MAX +100 FORMAT (' SPHERICAL BESSEL FUNCTION ROUTINE - MAX=',I8) + + STOP 2013 +C + END +C +C + SUBROUTINE VALENCE_DOS(NOSYM,CALCTYPE) +C +C LOCATES POLES OF TAU, SCATTERING PAT OPERATOR +C USES A GREEN'S FUNCTION METHOD AND ANALYTIC CONTINUATION TO NEGATIVE +C ENERGIES BELOW THE CONSTANT INTERSTITIAL POTENTIAL +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + integer at_,d_,rd_,sd_,b_,f_,zz_,yl_,xx_,ww_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=2*lmax_+1, + &rd_=440,sd_=ua_-1,f_=(lmax_+1)*ua_,b_=24) + +C +C + COMMON /DENS/ IRHO,RHOTOT(RD_,SD_),RCONI(2),VC(RD_,SD_),VCI(2) +C + COMMON /FCNR/KX1, H(D_),VCONS(2), + 1 R(RD_,D_),V(RD_,SD_),ICHG(10,D_),KPLACE(AT_),KMAX(AT_) + complex*16 V,VCONS +C + COMMON /FLAG/ INMSH,INV,INRHO,INSYM,IOVRHO,IOSYM,PREV,NCOEF + LOGICAL*4 PREV +C + COMMON/PARAM_DOS/EFTR,GAMMA,VCON,XE,EV,E,IOUT,NOUT,NAT,NDAT, + 1 NSPINS,NACORE,RADION,QION,EXFAC0,ZEFF,NAS,NT1,NTB, + 2 RS(AT_),XV(AT_),YV(AT_),ZV(AT_),Z(AT_), + 3 EXFACT(AT_),LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),LCORE(AT_),KION,NAME(2),MLEQ(AT_) + complex*16 VCON,XE,EV,E +c + common /param/eftr1,gamma1,vcon1,xe1,ev1,re,iout1,nat1,ndat1, + & nspins1,nas1,rs1(at_),xv1(at_),yv1(at_),zv1(at_),exfact1(at_), + & z1(at_),lmaxx1(at_),nz1(at_),nsymbl1(at_),neq1(at_) +c ,name0,cip,emax,emin,de + complex*16 vcon1,xe1,ev1 + character*8 nsymbl1 +c +C + COMMON /PDQ_DOS/ P(RD_,F_),PH(RD_,F_),PS(F_),DPS(F_) +C RAMF(N_), QL(N_),QLS(N_) + complex*16 P,PH,PS,DPS +C RAMF,QL,QLS +C + COMMON /STATE_DOS/ + 3 NLEQ(AT_),KTAU(AT_),NNS,ICORE, + 4 NUATOM,NDG,NLS(AT_),N0L(AT_),N0(AT_), + 5 NTERMS(AT_),LMAXN(AT_),NDIM,NDIMTR +C + INTEGER*2, DIMENSION(:,:), ALLOCATABLE :: MN,IN,NATOM,IMIN,IMAX + INTEGER*4, DIMENSION(:), ALLOCATABLE :: LN, NMS + REAL*8, DIMENSION(:,:), ALLOCATABLE :: CN +C + DIMENSION LM(AT_) +C +C COMMON/GAUNT/AI(WW_),INDEX(XX_),YL(YL_),RAB(ZZ_) +C + real*8, dimension(:), allocatable :: ai, yl, rab + integer, dimension(:), allocatable :: index +C + DATA PAI/3.1415926/ +C +C + REAL*8 DATE(5),CARD(10) +C + LOGICAL*4 OUTER, TNABS, EVMN, NOSYM + CHARACTER*3 CALCTYPE +C +C INTEGER*4 JAT/AT_/,JB/B_/,JD/D_/,JXX/XX_/ +C INTEGER*4 JF/F_/,JLTOT/LTOT_/,JYL/YL_/,JRD/RD_/ +C INTEGER*4 JN/N_/,JNTR/N_/,JZZ/ZZ_/,JSD/SD_/,JWW/WW_/ +C INTEGER*4 JNB/NB_/ +C +C + OPEN(22,FILE='sym_ns.dat') +C OPEN(3,FILE='pot.dat') +C OPEN(10,FILE='rho.dens') +C OPEN(5,FILE='scp1.dat') +C OPEN(6,FILE='log1.out') +C OPEN(7,FILE='rhoa.out') +C OPEN(10,FILE='tdos.out') +C OPEN(13,FILE='pot13.out') +C OPEN(14,FILE='sym14.out') + OPEN(72,FILE='dos.out') + OPEN(73,FILE='dos_ord.out') +C +C +C + NOUT = 0 + IOUT = 3 + NT1 = 0 + NAT = NAT1 + NDAT = NDAT1 + NAS = NAS1 + VCON = VCON1 + DO K = 1, NAT + RS(K) = RS1(K) + XV(K) = XV1(K) + YV(K) = YV1(K) + ZV(K) = ZV1(K) + Z(K) = Z1(K) + NZ(K) = NZ1(k) + NEQ(K) = NEQ1(K) +C WRITE(6,'(5F10.5,2I5)') RS(K),XV(K),YV(K),ZV(K),Z(K),NZ(K), +C & NEQ(K) + ENDDO +C + EVMN = .TRUE. +C +C****DO NOT CHANGE NNS=1. TEMPORARILY THIS VERSION USES SPIN INDEPENDENT +C POTENTIALS + NNS=1 +C +C----FOR LATTER TAIL, WHEN USED + ZEFF=-0.01 +C +C....DEFINE GMAX AND ENERGY INTERVAL WHERE TO LOOK FOR VALENCE STATE POLES +C + EMIN = -2.0 + EMAX = -0.01 + GMAX = 0.005 +C + LMX = LMAX_ + IF(LMAX_.GT.5) LMX = 5 + NDM = (LMX+1)**2*NAT +C + ALLOCATE (CN(B_,NDM),MN(B_,NDM), + &IN(B_,NDM),NATOM(B_,NDM),LN(NDM), + &NMS(NDM),IMIN(NDM,AT_),IMAX(NDM,AT_)) +C + IF(.NOT.NOSYM) GOTO 15 +C +C.....DEFINE ANGULAR MOMENTUM BASIS IN CASE OF NO SYMMETRY +C + NDIM=0 + DO I = 1, NAT + LM(I) = LMX + ENDDO + DO I=1,NAT + LMXP1=LM(I)+1 + DO LP1=1,LMXP1 + L=LP1-1 + MLP1=2*L+1 + DO IM=1,MLP1 + NDIM=NDIM+1 + NMS(NDIM)=1 + LN(NDIM)=L + IN(1,NDIM)=1 + CN(1,NDIM)=1. + NATOM(1,NDIM)=I + MN(1,NDIM)=IM/2 + IF(IM.EQ.1) CYCLE + IF(IM-2*MN(1,NDIM).EQ.1) IN(1,NDIM)=-1 + ENDDO + ENDDO + ENDDO +C + IF(NDIM.NE.NDM) THEN + WRITE(6,*)'Discrepancy between ndim and ndm' + CALL EXIT + ENDIF +C +C IF(NDIM.GT.N_) THEN +C WRITE(6,*)'NDIM GREATER THAN N_ ** INCREASE UA_' +C CALL EXIT +C ENDIF +C + NDG=2 + NONE=0 + WRITE(22,101) NDIM,NDG + DO N=1,NDIM + WRITE(22,101) LN(N),NMS(N),NONE + 101 FORMAT(3I5) + WRITE(22,106) MN(1,N),IN(1,N),NATOM(1,N),CN(1,N) + 106 FORMAT(3I5,F15.10) + ENDDO +C + CLOSE(22) +C + 15 CONTINUE +c OPEN(23,FILE='sym_*.dat') to be input in procfase3 + INSYM = 23 + IOSYM = 14 +C + ltot=2*lmx+1 + zz_=nat*(nat-1)/2 + yl_=ltot*(ltot+1)*zz_ + xx_ = (lmx+1)*(lmx+2)*(lmx+3)*(3*lmx+4)/24 + ww_ = (lmx+3)*(lmx+2)**4/5 +C + allocate(ai(ww_),yl(yl_),rab(zz_),index(xx_)) +C + PREV = .FALSE. + CALL SETUP_DOS(AI,YL,RAB,INDEX,WW_,YL_,ZZ_,XX_, + & CN,MN,IN,NATOM,LN,NMS,IMIN,IMAX,NDM) +C + NAS=1 + IF (NOUT.EQ.1) NAS=2 +C + KTHX = 240 +C + WRITE (6,*) 'SEARCH INTERVAL FOR VALENCE STATES EMIN-EMAX' + WRITE (6,*) ' EMIN EMAX GMAX KTHX' + WRITE (6,16) EMIN,EMAX,GMAX,KTHX + + 16 FORMAT(3F10.3,I5) +C + IF(EMAX.LT.EMIN) THEN + WRITE(6,*)'EMAX SHOUD BE LT EMIN ' + CALL EXIT + ENDIF +C + CALL CONT_DOS(EMAX,EMIN,EVMN,GMAX,KTHX, + & AI,YL,RAB,INDEX,WW_,YL_,ZZ_,XX_, + & CN,MN,IN,NATOM,LN,NMS,IMIN,IMAX,NDM) + +C + WRITE (6,160) + 160 FORMAT(1X,'END OF VALENCE STATES CALCULATION ') +C + IF(CALCTYPE.EQ.'dos') THEN + CLOSE (23) + CLOSE (3) + CLOSE (10) + CLOSE (5) + CLOSE (6) + CLOSE(7) + CLOSE (13) + CLOSE (14) + CLOSE(72) +C + STOP + ELSE + CLOSE (23) + CLOSE (72) + CLOSE(73) + RETURN + ENDIF +C + END +C +C + SUBROUTINE SETUP_DOS(AI,YL,RAB,INDEX,WW_,YL_,ZZ_,XX_, + & CN,MN,IN,NATOM,LN,NMS,IMIN,IMAX,NDM) +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + integer at_,d_,rd_,sd_,b_,f_,zz_,yl_,xx_,ww_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=2*lmax_+1, + &rd_=440,sd_=ua_-1,f_=(lmax_+1)*ua_,b_=24) + +C +C + COMMON /FLAG/ INMSH,INV,INRHO,INSYM,IOVRHO,IOSYM,PREV,NCOEF + LOGICAL*4 PREV +C +C COMMON/GAUNT/AI(WW_),INDEX(XX_),YL(YL_),RAB(ZZ_) + DIMENSION AI(WW_),INDEX(XX_),YL(YL_),RAB(ZZ_) +C + COMMON/PARAM_DOS/EFTR,GAMMA,VCON,XE,EV,E,IOUT,NOUT,NAT,NDAT, + 1 NSPINS,NACORE,RADION,QION,EXFAC0,ZEFF,NAS,NT1,NTB, + 2 RS(AT_),XV(AT_),YV(AT_),ZV(AT_),Z(AT_), + 3 EXFACT(AT_),LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),LCORE(AT_),KION,NAME(2),MLEQ(AT_) + complex*16 VCON,XE,EV,E +C + COMMON /STATE_DOS/ + 3 NLEQ(AT_),KTAU(AT_),NNS,ICORE, + 4 NUATOM,NDG,NLS(AT_),N0L(AT_),N0(AT_), + 5 NTERMS(AT_),LMAXN(AT_),NDIM,NDIMTR +C + INTEGER*2 MN,IN,NATOM,IMIN,IMAX + DIMENSION CN(B_,NDM),MN(B_,NDM), + 1 IN(B_,NDM),NATOM(B_,NDM),LN(NDM), + 2 NMS(NDM),IMIN(NDM,AT_),IMAX(NDM,AT_) +C +C + LOGICAL*4 DOALL + DATA SMALL,ZERO,ONE/1.E-5,0.0,1.0/ + DATA PI/3.14159265358979/,PI4/12.56637061435916/ +C INDEXX SHOULD BE THE SAME AS THE DIMENSION OF INDEX +C INTEGER*4 INDEXX/XX_/,JWW/WW_/,JYL/YL_/,NYL/0/ +C +C write(6,*) 'xx_, ww_, yl_ ', xx_, ww_, yl_ + INDEXX = XX_ + JWW = WW_ + JYL = YL_ + NYL = 0 +C +C + DOALL=.TRUE. + GO TO 121 +C + ENTRY SYMM + DOALL=.FALSE. + 121 IF(PREV ) GO TO 122 + DO 62 I=1,INDEXX + 62 INDEX(I)=0 + MAXSUB=0 + NCOEF=0 + MNYL = 0 + PREV=.TRUE. + 122 DO 59 N=1,NAT + LMAXX(N)=0 + NLEQ(N)=0 + N0(N)=0 + N0L(N)=0 + LMAXN(N)=0 + NTERMS(N)=0 + 59 NLS(N)=0 + NUATOM=0 + WRITE (6,327) INSYM,IOSYM + 327 FORMAT(' SYMMETRY INFORMATION READ IN FROM FILE',I3,/, + X ' SYMMETRY INFORMATION WRITTEN TO FILE',I3) + 60 CONTINUE + READ (INSYM,1010) NDIM,NDG,NAME +1010 FORMAT (2I5,10X,10A4) + WRITE (6,1011) NDG, NAME +1011 FORMAT (' DEGENERACY =',I3,' REPRESENTATION =',10A4) + 101 FORMAT(16I5) +C IF(NDIM.GT.N_) THEN +C WRITE(6,*) ' INCREASE N_ ', N_, ' IN THE SCP.INC FILE.', +C & ' IT SHOULD BE AT LEAST EQUAL TO NDIM = ', NDIM +C CALL EXIT +C ENDIF + IF (IOUT.GE.2) WRITE (IOSYM,103) NDIM,NDG,NAME + 103 FORMAT(' # BASIS FUNCTION INCLUDING O.S. =',I4,' DEGENERACY=', + 1 I3,5X,2A4) + WRITE(6,*) '******SYMMETRY INFORMATION********' + DO 125 N=1,NDIM + DO 131 NA=1,NAT + IMIN(N,NA)=1 + 131 IMAX(N,NA)=0 + READ (INSYM,101) LN(N),NMS(N),NONE + IF (IOUT.GE.2) WRITE (IOSYM,104) N,LN(N),NMS(N),NONE +104 FORMAT ( 1X,'BASIS FUNCTION NO.',I3,' L=',I3,' NO. OF TERMS=', + 1 I3,10X,I3) + NMN=NMS(N) + IF (NONE.NE.0) + $ READ (INSYM,1050) (MN(I,N),IN(I,N),NATOM(I,N),CN(I,N),I=1,NMN) +1050 FORMAT (4(2I2,I4,F12.9)) + DO 55 I=1,NMN + IF (NONE.EQ.0) + $ READ (INSYM,105) MN(I,N),IN(I,N),NATOM(I,N),CN(I,N) + 105 FORMAT(3I5,F15.10) + IF (IOUT.GE.2) + * WRITE(IOSYM,106) MN(I,N),IN(I,N),NATOM(I,N),CN(I,N) + 106 FORMAT(30X,'M=',I3,' I=',I3,' ATOM NO.=',I3,' CN=',F15.10) + ININ=IN(I,N) + IF (IABS(ININ).NE.1 .OR. MN(I,N).LT.0) WRITE(6,109) + IF(MN(I,N).EQ.0.AND.IN(I,N).EQ.-1) WRITE(6,109) + 109 FORMAT(' WRONG VALUES FOR M AND I') + NA=NATOM(I,N) + IF(NLEQ(NA).EQ.0) NLEQ(NA)=NATOM(1,N) + IF(NLEQ(NA).NE.NATOM(1,N)) WRITE(6,110) + 110 FORMAT( ' INCONSISTENT NUMBERING OF ATOMS') + IF(I.EQ.1) GO TO 58 + IF(NA.EQ.NATOM(I-1,N)) GO TO 56 + IF(NA.GT.NATOM(I-1,N)) GO TO 58 + WRITE(6,107) + 107 FORMAT(' SYMMETRY CARD OUT OF SEQUENCE') + STOP + 58 IMIN(N,NA)=I + I0=I + 56 IMAX(N,NA)=I + IF(I0.EQ.I) GO TO 55 + I1=I-1 + DO 126 J=I0,I1 + 126 IF(MN(I,N).EQ.MN(J,N).AND.IN(I,N).EQ.IN(J,N)) WRITE(6,111) + 111 FORMAT(' DUPLICATE TERMS IN BASIS FUNCTION ') + 55 LMAXN(NA)=MAX0(LMAXN(NA),LN(N)) + NUATOM=MAX0(NUATOM,NLEQ(NA)) + NA=NATOM(1,N) + NTERMS(NA)=NTERMS(NA)+1 + IF(N.EQ.1) GO TO 128 + IF(NA.LT.NATOM(1,N-1)) WRITE(6,107) + IF(NA.NE.NATOM(1,N-1)) GO TO 128 + IF(LN(N).EQ.LN(N-1)) GO TO 125 + IF(LN(N).LT.LN(N-1)) WRITE(6,107) + 128 NLS(NA)=NLS(NA)+1 + 125 CONTINUE + NDIMTR=NDIM-NTERMS(1) + IF(NOUT.EQ.0) NDIMTR=NDIM + WRITE(6,999) NDIMTR + IF (IOUT.GE.2) WRITE(IOSYM,999) NDIMTR + 999 FORMAT(' TRUE DIMENSION OF SECULAR MATRIX TO SOLVE =',I4) + WRITE(6,112) NUATOM, NAME + WRITE(IOSYM,112) NUATOM, NAME + 112 FORMAT(' NUMBER OF INEQUIVALENT ATOMS =',I4, + * ' FOR REPRESENTATION:',10A4) + N0(1)=1 + N0L(1)=1 + LMAXX(1)=MAX0(LMAXX(1),LMAXN(1)) + IF(NUATOM.EQ.1) GO TO 127 + DO 124 NA=2,NUATOM + N0(NA)=N0(NA-1)+NTERMS(NA-1) + N0L(NA)=N0L(NA-1)+NLS(NA-1) + 124 LMAXX(NA)=MAX0(LMAXN(NA),LMAXX(NA)) +C + 127 DO 61 NN=1,NDIM + NMN=NMS(NN) + L=LN(NN) + DO 63 I=1,NMN + M=MN(I,NN) + DO 64 NM=1,NDIM + NMM=NMS(NM) + LP=LN(NM) + IF(L.LT.LP) GO TO 64 + LX=L+LP + DO 65 J=1,NMM + MP=MN(J,NM) + 7 ISUB=(L *(L +1)*(L +2)*(3*L +1))/24+((L +1)*(L +2)*M+LP*(LP+1))/2 + 1 +MP+1 + IF (ISUB.LE.INDEXX) GO TO 113 + WRITE (6,102) ISUB + WRITE (6,100) JWW,NCOEF,INDEXX,MAXSUB + CALL MERR(151374) + 102 FORMAT('-JXX',I10,' CAN NOT CONTINUE'/'-INCOMPLETE') + 113 CONTINUE + IF(INDEX(ISUB).NE.0) GO TO 65 + 68 II2=1 + IF(MP.NE.0) II2=2 + INDEX(ISUB)=NCOEF+1 + MAXSUB=MAX0(MAXSUB,ISUB) + DO 67 II=1,II2 + LMIN=MAX0(L-LP,IABS(M-MP)) + IF(MOD(LX-LMIN,2).NE.0) LMIN=LMIN+1 + IF(NCOEF+(LX-LMIN)/2 .LT. JWW) GO TO 3 + NCOEF = NCOEF + (LX-LMIN)/2+1 + GO TO 67 + 3 DO 4 LL=LMIN,LX,2 + NCOEF=NCOEF+1 + ARG=(2*LL+1)*(2*L+1)/(PI4*(2*LP+1)) + 4 AI(NCOEF)=CGC(LL,L,LP,0,0)*CGC(LL,L,LP,MP-M,M)*SQRT(ARG) +C MODIFIED FOR DOUBLE PRECISION COMPILATION JUNE 27 1975 + 67 MP=-MP + 65 CONTINUE + 64 CONTINUE + 63 CONTINUE + 61 CONTINUE + WRITE (6,100) JWW,NCOEF,INDEXX,MAXSUB + IF (IOUT.GE.2) WRITE (IOSYM,100) JWW,NCOEF,INDEXX,MAXSUB + 100 FORMAT(' DIMENSION JWW =',I6,' COULD BE',I6/ + 1 ' DIMENSION JXX =',I6,' COULD BE',I6) + IF (NCOEF.GT.JWW .OR. MAXSUB.GT.INDEXX) CALL MERR(151580) + IF(.NOT.DOALL) RETURN +C + ENTRY STRUCT_DOS +C COMPUTE NUMBER OF ATOMS EQUIVALENT TO EACH DIFFERENT ATOM + DO 130 NA=2,NUATOM + 130 KTAU(NA)=0 + KTAU(1)=1 + NYL = 1 + MLEQ(1) = NLEQ(1) + DO 129 NA=2,NAT + MLEQ(NA) = NLEQ(NA) + IF(NLEQ(NA).EQ.0) GO TO 129 + KTAU(NLEQ(NA))=KTAU(NLEQ(NA))+1 + NA1=NA-1 + DO 415 NB=1,NA1 + IF(NLEQ(NB).EQ.0) GO TO 415 + NLAB=LMAXX(NLEQ(NA))+LMAXX(NLEQ(NB)) + NAB=((NA-1)*(NA-2))/2+NB + MYL = (NLAB+1)*(NLAB+2)/2 + PHI=ZERO + ZMU=ONE + RAB(NAB)= (XV(NA)-XV(NB))**2+(YV(NA)-YV(NB))**2+(ZV(NA)-ZV(NB))**2 + IF(RAB(NAB).GT.SMALL) GO TO 42 + RAB(NAB)=ZERO + GO TO 41 + 42 RAB(NAB)=SQRT(RAB(NAB)) + ZMU=(ZV(NB)-ZV(NA))/RAB(NAB) + RXY= (XV(NA)-XV(NB))**2+(YV(NA)-YV(NB))**2 + IF(RXY.LT.SMALL) GO TO 41 + RXY=SQRT(RXY) + ARGXY=(YV(NB)-YV(NA))/RXY + PHI=ASIN(ARGXY) + IF(XV(NB).GE.XV(NA)) GO TO 41 + PHI=PI-PHI + 41 IF((NYL+2*MYL-1).LE.JYL) CALL YLM1(NLAB,ZMU,PHI,YL(NYL),MYL) + NYL = NYL+2*MYL + 415 CONTINUE + 129 CONTINUE + NYL = NYL-1 + IF (MNYL.LT.NYL) MNYL=NYL + WRITE (6,108) JYL,NYL + IF (IOUT.GE.2) WRITE (IOSYM,108) JYL,NYL + 108 FORMAT(' DIMENSION JYL =',I8,' COULD BE',I8/1X) + IF (NYL.GT.JYL) CALL MERR(152000) + RETURN + ENTRY INIT + PREV=.FALSE. + RETURN +C + END +C +C +C + SUBROUTINE CONT_DOS(EMAX,EMIN,EVMN,GMAX,KTHX, + & AI,YL,RAB,INDEX,WW_,YL_,ZZ_,XX_, + & CN,MN,IN,NATOM,LN,NMS,IMIN,IMAX,NDM) +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + integer at_,d_,rd_,sd_,b_,f_,zz_,yl_,xx_,ww_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=2*lmax_+1,np_=500, + &rd_=440,sd_=ua_-1,f_=(lmax_+1)*ua_,b_=24, + &nb_=(lmax_+1)**2 ) +C +C + COMMON/BESSEL_DOS/SBF(LTOT_),DSBF(LTOT_),SHF(LTOT_),DSHF(LTOT_) + COMPLEX*16 SBF,DSBF,SHF,DSHF +C + COMMON/COULMB/EK,FC(LTOT_),FCP(LTOT_),GC(LTOT_),GCP(LTOT_), + & FC1(LTOT_),FCP1(LTOT_),GC1(LTOT_),GCP1(LTOT_) + complex*16 EK,RHO,FC,FCP,GC,GCP,FC1,FCP1,GC1,GCP1 + REAL*8 ETA,ACCUR,STEP +C + COMMON /DENS/ IRHO,RHOTOT(RD_,SD_),QITOT(2) +C + COMMON /FCNR/KX1,H(D_),VCONS(2), + 1 R(RD_,D_),V(RD_,SD_),ICHG(10,D_),KPLACE(AT_),KMAX(AT_) + complex*16 VCONS,V +C + COMMON /FLAG/ INMSH,INV,INRHO,INSYM,IOVRHO,IOSYM,PREV, + 1 NCOEF + LOGICAL*4 PREV +C + COMMON/PARAM_DOS/EFTR,GAMMA,VCON,XE,EV,E,IOUT,NOUT,NAT,NDAT, + 1 NSPINS,NACORE,RADION,QION,EXFAC0,ZEFF,NAS,NT1,NTB, + 2 RS(AT_),XV(AT_),YV(AT_),ZV(AT_),Z(AT_), + 3 EXFACT(AT_),LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),LCORE(AT_),KION,NAME(2),MLEQ(AT_) + complex*16 VCON,XE,EV,E +C + COMMON /PDQ_DOS/ P(RD_,F_),PH(RD_,F_),PS(F_),DPS(F_) +C RAMF(N_),QL(N_),QLS(N_) + complex*16 P,PH,PS,DPS +C RAMF,QL,QLS + complex*16, DIMENSION(:), ALLOCATABLE :: RAMF, QL, QLS, ATTM +C +c COMMON /SECULR/ A(N_,N_),B(N_,N_) +c COMPLEX*16 A,B + complex*16, dimension (:,:), allocatable :: A, B +C + COMMON /STATE_DOS/ + 3 NLEQ(AT_),KTAU(AT_),NNS,ICORE, + 4 NUATOM,NDG,NLS(AT_),N0L(AT_),N0(AT_), + 5 NTERMS(AT_),LMAXN(AT_),NDIM,NDIMTR +C + INTEGER*2 MN,IN,NATOM,IMIN,IMAX + DIMENSION CN(B_,NDM),MN(B_,NDM), + 1 IN(B_,NDM),NATOM(B_,NDM),LN(NDM), + 2 NMS(NDM),IMIN(NDM,AT_),IMAX(NDM,AT_) +C + COMMON/OS/RAMFTZL(NB_),TZEROL(NB_) !,JL(N_,NB_) + complex*16 TZEROL, RAMFTZL, BOS(NB_,NB_) + complex*16, DIMENSION(:,:), ALLOCATABLE :: JL +C +C +C COMMON/GAUNT/AI(WW_),INDEX(XX_),YL(YL_),RAB(ZZ_) + DIMENSION AI(WW_),INDEX(XX_),YL(YL_),RAB(ZZ_) +C +c integer, dimension(:), allocatable :: index +C + REAL*8 WKAREA + complex*16 SUMCA(2),SUMCT(2),SUMCA1(2),CNTRIT,CNTRIA,CNTRIA1, + * SUM,SUMS,SUM1,SUMOS,CU,DE,XEOPI,SUMOSTZ +C +C COMPLEX ATTM(N_),RHOA(NP_,AT_),RHOS(NP_,AT_),RHOS1(NP_,AT_) +C COMPLEX ATTM(N_) + complex*16 RHOA(NP_,AT_),RHOS(NP_,AT_),RHOS1(NP_,AT_) + complex*16 RHOAOS(NP_,AT_),ETH(NP_),DETH(NP_) +C + REAL*8 REALE(NP_),DUMMY(30) + DIMENSION IDUMMY(30) +C + LOGICAL*4 OUTER,EVMN +C +C +C.... DECLARATIONS FOR NAG SUBROUTINES +C + CHARACTER UPLO +C INTEGER IPIV(N_) +C COMPLEX*16 WORK(LWORK) + complex*16, dimension(:), allocatable :: work + integer, dimension(:), allocatable :: ipiv +c + real*8, dimension(:), allocatable :: val_spectrum + real*8, dimension(:), allocatable :: temp_spectrum +c + DATA UPLO/'U'/ +C +C + DATA PAI/3.1415927/,ALPHA/7.3E-3/ +C +C FOR SUBROUTINE RADIAL_DOS: + IR0=0 +C + CU = (0.0,1.0) + PAI4=4.*PAI + SVC=PAI4/3. + NTAS=NTERMS(NAS) + NTB = NTAS + NTB1 = NTAS + IF(NOUT.EQ.1) NTB1 = NTERMS(1) + NT1 = NTERMS(1) + IF(NOUT.EQ.0) NT1 = 0 + NSTART=N0(NAS) + NLAST=N0(NAS)+NTERMS(NAS)-1 +C + KX1=KMAX(1) +C + 67 WRITE(6,40) VCON + 40 FORMAT(//,' INTERSTITIAL VCON = (',F10.6,',',F10.6,')') +C + allocate(a(ndim,ndim),b(ndim,ndim)) + allocate ( work(1:ndim) , ipiv(1:ndim), stat=istat ) + if(istat.ne.0) then + write(6,*)'problem in allocating matrices work, ipiv' + call exit + endif + ALLOCATE(RAMF(NDM), QL(NDM), QLS(NDM), ATTM(NDM)) + ALLOCATE(JL(NDM,NB_)) + lwork=ndim +C KTHX=PAI/DT+1.9 + IF(KTHX.LT.60) KTHX = 60 + DT = PAI/(FLOAT(KTHX)-1) + HSUM=(EMAX+EMIN)/2.0 + HDIF=(EMAX-EMIN)/2.0 +C + allocate(val_spectrum(kthx)) + allocate(temp_spectrum(kthx)) +C START COMPLEX ENERGY LOOP: +C +C IF(NOUT.EQ.1) WRITE(6,*) 'KMAX FOR OUTER SPHERE = ', KLP(1) +C + DO 9 NTH = 1, KTHX +C +C + THETA = DT*(NTH-1) + E = DCMPLX(HSUM-HDIF*COS(THETA),GMAX*SIN(THETA)) + DE = DCMPLX(HDIF*SIN(THETA),GMAX*COS(THETA)) + ETH(NTH) = E + DETH(NTH) = DE + REALE(NTH)=DBLE(E) + IF(NTH.EQ.KTHX) E=DCMPLX(REALE(NTH),0.D0) + AIMAGE=DIMAG(E) + GAMMAT=-DIMAG(E) + IF(AIMAGE.EQ.0.0) GAMMAT = -SIGN(1.D0,GMAX)*0.00001 + IF (NOUT.EQ.0) GOTO 123 + EK=SQRT(E) +C + 123 EV=E-VCON +C + XE=SQRT(EV) + XEOPI=XE/PAI +C + IF(NOUT.EQ.0) THEN +C WRITE(6,*) 'NTH= ',NTH,' E= ',E,' XE= ',XE + ELSE +C WRITE(6,*) 'NTH= ',NTH,' E= ',E,' XE= ',XE,' EK= ',EK + ENDIF +C +C*** IF OUTER SPHERE IS USED, CALCULATE INITIAL COULWF VALUES FOR +C*** INWARD INTEGRATION ON OUTER SPHERE: +C + IF( NOUT .EQ. 0 ) GOTO 5 +C KMAX(1)=KLP(1) + KN=KMAX(1) + RHO=EK*R(KN,1) +C RHO=XE*R(KN,1) + ML=LMAXN(1)+1 +C CALL CSBF(RHO,EK,ML,FC,FCP) +C IF(GAMMAT.GE.0.0) CALL CSHF2(RHO,EK,ML,GC,GCP) +C IF(GAMMAT.LT.0.0) CALL CSHF1(RHO,EK,ML,GC,GCP) +C CALL RCWFN(RHO,ETA,0,ML,FC,FCP,GC,GCP,ACCUR,STEP) + RHO=EK*R(KN-1,1) +C RHO=XE*R(KN-1,1) +C CALL CSBF(RHO,EK,ML,FC1,FCP1) +C IF(GAMMAT.GE.0.0) CALL CSHF2(RHO,EK,ML,GC1,GCP1) +C IF(GAMMAT.LT.0.0) CALL CSHF1(RHO,EK,ML,GC1,GCP1) +C CALL RCWFN(RHO,ETA,0,ML,FC1,FCP1,GC1,GCP1,ACCUR,STEP) +C +C CONSTRUCT SCATTERING MATRIX A(N,N) + 5 CALL SMTX_DOS(P,PH,RAMF,PS,DPS,ATTM,GAMMAT,EK,A,B, + & AI,YL,RAB,INDEX,WW_,YL_,ZZ_,XX_,JL, + & CN,MN,IN,NATOM,LN,NMS,IMIN,IMAX,NDM) +C + IF(.FALSE..AND.DBLE(E).EQ.EMIN) THEN +c + DO I=1,NDIMTR + DO J=I,NDIMTR + WRITE(6,*) LN(I), MN(1,I), LN(J), MN(1,J), A(J,I) + ENDDO + ENDDO +C + ENDIF +C +C +C INTEGRATE UNNORMALIZED ATOMIC CHARGE DENSITIES +C + CALL RADIAL_DOS(IR0,RAMF,QL,QLS,LN,NDM) +C +C WRITE(6,*) 'IN CONT_DOS:' + DO N=NSTART,NLAST +C WRITE(6,*) 'E =',DBLE(E),'N=',N,' QL=',QL(N),' QLS=',QLS(N) + ENDDO +C +C CALL ZSYTRF(UPLO,NDIM,A,NDIM,IPIV,WORK,LWORK,INFO) + CALL ZSYTRF(UPLO,NDIM,A,NDIM,IPIV,WORK,NDIM,INFO) +C WRITE(6,60) WORK(1),INFO + 60 FORMAT(5X,' OPTIMAL LWORK = (',D14.7,',',D14.7,')','INFO=',I5) + IF (INFO.NE.0) THEN + WRITE(6,61) + 61 FORMAT (' THE FACTOR D IS SINGULAR') + STOP + ENDIF +C +C COMPUTE SOLUTION +C + 72 CALL ZSYTRS(UPLO,NDIM,NDIM,A,NDIM,IPIV,B,NDIM,INFO) +C +C***WRITE OUT INVERSE OF MS MATRIX B(NDIMTR,NTB) IF IOUT=5 +C + IF(IOUT.EQ.5) THEN + DO NN=NSTART,NLAST + DO NM=1,NDIMTR + WRITE (6,54) NM,NN,B(NM,NN) + ENDDO + ENDDO +C + DO M=NSTART,NLAST +C WRITE(6,53) M, RAMF(M), ATTM(M) + ENDDO +C + ENDIF +C + 54 FORMAT(' NM=',I3,' NN=',I3,' B(NM,NN):',2E14.6) + 53 FORMAT(' MD=',I3,' RAMF(MD)=',2E14.6,' ATTM(MD)=',2E14.6) +C +C CALCULATE OS GLOBAL T-MATRIX JTAUJ +C + IF(NOUT.EQ.1) THEN + DO J=1,NT1 + DO I=1,NT1 + BOS(I,J)=(0.0,0.0) + ENDDO + ENDDO + + + DO M=1,NT1 +C LM=LN(M) + DO N=1,NT1 + LNN=LN(N) + DO J=1,NDIMTR + LJ=LN(J+NT1) + DO I=1,NDIMTR +C LI=LN(I+NT1) + BOS(M,N)=BOS(M,N) - JL(I,M)*JL(J,N)*B(I,J) + & *(-1)**(LNN+LJ) + ENDDO + ENDDO + ENDDO + ENDDO +C +C WRITE OUT JL QUANTITIES +C + IF(DBLE(E).EQ.EMIN.OR.DBLE(E).EQ.EMAX) THEN +C IF(.FALSE.) THEN +C + WRITE(20,*) 'E = ',E + DO M=1,NDIMTR + NA = NATOM(1,M + NT1) + NAB = (NA-1)*(NA-2)/2 + 1 + R1A = RAB(NAB) + WRITE(20,*) 'NA = ', NA, XE, R1A, XE*R1A + DO N=1,NT1 + WRITE(20,*) LN(M+NT1), MN(1,M+NT1), IN(1,M+NT1), + & LN(N), MN(1,N), IN(1,N), JL(M,N) + ENDDO + ENDDO +C + ENDIF +C +C + ENDIF +C + IF(NTH.EQ.KTHX) THEN + WRITE(6,*) 'Atom number na ', + & 'number of equvalent atoms to atom na *' + WRITE(6,10) (NA, KTAU(NA),' * ', NA = 1, NDAT) + ENDIF +10 FORMAT(20(2I3,A5)) +C +C CALCULATE ATOMIC CHARGE DENSITIES WEIGHTED BY DENSITY OF STATES +C AT ENERGY E +C + DO NA=1,NUATOM + NT0A=N0(NA) + NTXA=NT0A+NTERMS(NA)-1 + SUM=(0.0,0.0) + SUM1=(0.0,0.0) + SUMS=(0.0,0.0) + SUMOS=(0.0,0.0) + SUMOSTZ=(0.0,0.0) +C + L = -1 +C WRITE(6,*) ' ATOM NUMBER = ', NA + IF(NOUT.EQ.1.AND.NA.EQ.1) THEN + DO N=NT0A, NTXA + IF(LN(N).EQ.L ) GO TO 35 + L=LN(N) +C WRITE(6,*) ' L= ',LN(N),' T0L(L)^(-1)= ',1.0/TZEROL(N) +C WRITE(6,*) ' BOS(L)= ',BOS(N,N),' T0L(L)= ',TZEROL(N) +C WRITE(6,*) ' RAMFOS= ',RAMFTZL(N) +C WRITE(6,*) ' QL= ',QL(N) +C WRITE(6,*) '-----' + 35 CONTINUE + ENDDO +c + DO N=NT0A,NTXA + SUMOS=SUMOS+BOS(N,N)*QL(N) + SUMOSTZ=SUMOSTZ+TZEROL(N)*QL(N) + ENDDO + + ELSE + DO N=NT0A, NTXA + IF(LN(N).EQ.L ) GO TO 36 + L=LN(N) +C WRITE(6,*) ' L= ',LN(N),' ATI(L)= ',1.0/ATTM(N) +C WRITE(6,*) ' B(L)= ',B(N-NT1,N-NT1),' ATTM(L)= ',ATTM(N) +C WRITE(6,*) ' RAMF= ',RAMF(N) +C WRITE(6,*) ' QL= ',QL(N),' QLS= ',QLS(N) +C WRITE(6,*) '-----' + 36 CONTINUE + ENDDO +c + DO N=NT0A,NTXA +C SUM=SUM+(B(N-NT1,N-NT1)-ATTM(N)*XEOPI)*QL(N) + SUM=SUM+B(N-NT1,N-NT1)*QL(N) + SUM1=SUM1+ATTM(N)*QL(N) + SUMS=SUMS+ATTM(N)*QLS(N) + ENDDO +C + ENDIF +C + IF(NOUT.EQ.1.AND.NA.EQ.1) THEN + RHOA(NTH,NA) = SUMOS*DE*(EK/XE)/(XEOPI)**2/PAI +C......SEE DEFINITION OF RAMF IN TMAT FOR OUTER SPHERE (MOUT=2) + RHOS1(NTH,NA) = SUMOSTZ*DE*XEOPI*EK/XE/PAI + ELSE + RHOA(NTH,NA) = SUM*DE*KTAU(NA)/PAI + RHOS1(NTH,NA) = SUM1*DE*XEOPI*KTAU(NA)/PAI + RHOS(NTH,NA) = SUMS*DE*KTAU(NA)/PAI +C WRITE(6,*) REALE(NTH), NA, SUM +c IF(NTH.EQ.KTHX) WRITE(6,*) NA, KTAU(NA) + ENDIF +C +C + 50 CONTINUE + ENDDO +C +C.....ENDING COMPLEX ENERGY DO LOOP +C WRITE(15,*) REALE(NTH)*13.605, DBLE(RHOA(NTH,1)/DE) +C + 9 CONTINUE +c +C +C +C.....INTEGRATE GREEN'S FUNCTION OVER CONTOUR IN COMPLEX ENERGY PLANE +C.....TO FIND OUT ATOMIC CHARGE DENSITIES +C +C + WRITE(6,*) ' ============= ' + ID = 0 + KL = 1 + SUMR=0.0 + SUMGF=0.0 + DO NA=1,NUATOM +C + CALL DEFINT0(RHOA(1,NA),DT,KTHX,SUMCT(KL),ID) + CALL DEFINT0(RHOS(1,NA),DT,KTHX,SUMCA(KL),ID) + CALL DEFINT0(RHOS1(1,NA),DT,KTHX,SUMCA1(KL),ID) +C + WRITE(6,*) ' -- CLUSTER INTEGRAL FROM ',EMIN,' TO ',EMAX, + * ' -- FOR AT. NUM NA=',NA + WRITE(6,*) SUMCT(KL) +C + WRITE(6,*) ' -- SINGULAR INTEGRAL FROM ',EMIN,' TO ',EMAX, + * ' -- FOR AT. NUM NA=',NA + WRITE(6,*) SUMCA(KL) +C + WRITE(6,*) ' -- ATOMIC INTEGRAL FROM ',EMIN,' TO ',EMAX, + * ' -- FOR AT. NUM NA=',NA + WRITE(6,*) SUMCA1(KL) + WRITE(6,*) '--------' +C + SUMR = SUMR + AIMAG(SUMCT(1)) + SUMGF = SUMGF + AIMAG(SUMCT(1)-SUMCA1(1)+SUMCA(1)) + +C + ENDDO +C + WRITE(6,*) ' ============= ' +C +C + WRITE(6,*) ' -- TOTAL NUMBER OF STATES SUMR = ', SUMR +C +C.....WRITE OUT INTEGRAND RHOA(NTH,NA) AS A FUNCTION OF REAL(ETH) AND +C.....ATOM NUMBER NA +C +C DO NA=1,NUATOM +C WRITE(7,*) ' ATOM NUMBER =', NA +C DO NTH=1,KTHX +C WRITE(7,45) DBLE(ETH(NTH)), AIMAG(RHOA(NTH,NA)/DETH(NTH)), +C & AIMAG(RHOS1(NTH,NA)/DETH(NTH)), +C & AIMAG(RHOS(NTH,NA)/DETH(NTH)), +C & AIMAG((RHOA(NTH,NA)-RHOS1(NTH,NA)+RHOS(NTH,NA))/DETH(NTH)) +C ENDDO +C ENDDO + 45 FORMAT(1X, 5F12.5) +C + WRITE(6,*) ' -- TOTAL NUMBER OF STATES SUMGF = ', SUMGF +C +C.....WRITE ALSO TOTAL DOS (28 AUG 2012) +C + WRITE(7,*) ' TOTAL DENSITY OF STATES' + DO NTH=1,KTHX + SUMT = 0.D0 + DO NA = 1, NUATOM + SUMT = SUMT + DIMAG(RHOA(NTH,NA)/DETH(NTH)) + ENDDO +C WRITE(7,45) DBLE(ETH(NTH)), SUMT + VAL_SPECTRUM(NTH) = SUMT + TEMP_SPECTRUM(NTH)= SUMT + WRITE(72,45) REALE(NTH), SUMT + ENDDO +C + CALL SORT2(REALE,VAL_SPECTRUM,KTHX) +C + WRITE(73,*)'# Energy points ordered by decreasing peak values' + DO NTH = 1, KTHX + WRITE(73,45) REALE(NTH), VAL_SPECTRUM(NTH) + ENDDO +C + NUMST = NINT(SUMR+1) +C +C READING THE VALENCE EIGENVALUES FROM THE REORDERED SPECTRUM +C + WRITE(6,*)'---------------------------' + WRITE(6,*)'Listing Valence Eigenvalues. ', + & 'Check with output file dos.out (unit 72).' + WRITE(6,*)'---------------------------' + WRITE(6,*)'IMAGINARY PART OF POTENTIAL, GMAX = ',GMAX +C + NSCUT = 3*NUMST + WRITE(6,*)'NSCUT =', NSCUT + IF(NSCUT.GT.30) THEN + WRITE(6,*)'NSCUT more than 30. Increase dimensions ', + & 'of array "idummy" in sub cont_dos' + CALL EXIT + ENDIF + DO I = 1, NSCUT + IDUMMY(I) = 0 + ENDDO +C + DO I = 1, NSCUT - 1 + DO J = I + 1, NSCUT + CF = 2.1*(1.0 + 0.9/ABS(REALE(J))) + IF(ABS(REALE(J)).LT.0.2) CF = 2.1*(1.0 + 0.9/0.2) +c write(6,*) i, j, ABS(REALE(J) - REALE(I)), cf*gamma + IF(REALE(J).NE.0.0.AND.ABS(REALE(J) - REALE(I)).LT.CF*GMAX) + & THEN + IDUMMY(J) = J +c WRITE(6,*) J, IDUMMY(J) + ENDIF + ENDDO + ENDDO +C + NP = 0 + DO I = 1, NSCUT + IF(IDUMMY(I).EQ.0) THEN + NP = NP + 1 + DUMMY(NP) = REALE(I) + ENDIF + ENDDO +c + write(6,*)'Number of poles = ', NP +c + CALL SORT1(DUMMY,NP) +C + WRITE(6,*)'-------------------' + DO I = 1, NP + WRITE(6,*) DUMMY(I) + ENDDO +C + RETURN +C + END +C +C + SUBROUTINE SORT1(ARRV,N) !Sorting in ascending order +c + implicit real*8 (a-h,o-z) +c + dimension arrv(n) +c + do i = 1, n-1 +c Find the maximum value in arr(i) through arr(n) + iptr = i + do j = i+1, n + if(arrv(j).lt.arrv(iptr)) then + iptr = j + endif + enddo +c iptr now points to the minimum value, so swap arr(iptr) with arr(i) +c if(i.ne.iptr) + if(i.ne.iptr) then + temp = arrv(i) + arrv(i) = arrv(iptr) + arrv(iptr) = temp + endif + enddo +c + END SUBROUTINE +C +C + SUBROUTINE SORT2(ARRE,ARRV,N) !Sorting in discending order +c + implicit real*8 (a-h,o-z) +c + dimension arre(n), arrv(n) +c + do i = 1, n-1 +c Find the maximum value in arr(i) through arr(n) + iptr = i + do j = i+1, n + if(arrv(j).gt.arrv(iptr)) then + iptr = j + endif + enddo +c iptr now points to the minimum value, so swap arr(iptr) with arr(i) +c if(i.ne.iptr) + if(i.ne.iptr) then + temp = arrv(i) + tempe = arre(i) + arrv(i) = arrv(iptr) + arre(i) = arre(iptr) + arrv(iptr) = temp + arre(iptr) = tempe + endif + enddo +c + END SUBROUTINE +C +C + complex*16 FUNCTION + & GMAT(L1,M1,L2,M2,YL,SBF,I,AI,INDEX,WW_,XX_,YL_) +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + integer at_,d_,rd_,sd_,b_,f_,zz_,yl_,xx_,ww_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=2*lmax_+1, + &n_=(lmax_+1)**2*ua_,rd_=440,sd_=ua_-1,f_=(lmax_+1)*ua_,b_=24 ) +C +C +C G-MATRIX FOR POLYATOMIC MOLECULES USING REAL SPHERICAL HARMONICS +C +C +C COMMON/GAUNT/AI(WW_),INDEX(XX_) + DIMENSION AI(WW_),INDEX(XX_) +C + COMMON/PARAM_DOS/EFTR,GAMMA,VCON,XE,EV,E + complex*16 XE,EV,VCON,E +C + COMPLEX*16 GMATP + COMPLEX*16 SBF + DIMENSION SBF(LTOT_),YL(YL_) +C TRUE DIMENSION YL(MYL), MYL IS VARIABLE DEFINED IN SMTX + LOGICAL MPHASE + LOGICAL ENEG + DATA SQR2 /1.414213562373d0/ + DATA PI4/12.56637061435916d0/ + DATA ZERO/0.0/ +C +C + ENEG=.FALSE. + MM=IABS(M2-M1) + LMIN=MAX0(IABS(L2-L1),MM) + IF(MOD(LMIN+L2+L1,2).NE.0) LMIN=LMIN+1 + LMAX=L2+L1 + NP=MM+1+(LMIN*(LMIN+1))/2 + LD =2*LMIN+3 + IF(L2.GT.L1) GO TO 5 + MPHASE=.FALSE. + LL=L1 + M=M1 + LP=L2 + MP=M2 + GO TO 6 + 5 LL=L2 + M=M2 + LP=L1 + MP=M1 + MPHASE=.TRUE. + 6 IF(M.GE.0) GO TO 7 + M=-M + MP=-MP + 7 ISUB=(LL*(LL+1)*(LL+2)*(3*LL+1))/24+((LL+1)*(LL+2)*M+LP*(LP+1))/2 + 1 +IABS(MP)+1 + N=INDEX(ISUB) + IF(MP.LT.0) N=N+MIN0(LP,(LL+LP-IABS(M+MP))/2)+1 + GMATP = (0.0D0,0.0D0) + NSGN=1 + LMIN1=LMIN+1 + LMAX1=LMAX+1 + DO 1 LP1=LMIN1,LMAX1,2 + L=LP1-1 + CLM=AI(N) + N=N+1 + IF(ENEG) GO TO 2 + IF(NSGN.GT.0) GO TO 2 + CLM=-CLM + 2 GMATP = GMATP+SBF(L+1)*YL(NP)*CLM + GMAT = GMATP + NP=NP+LD + LD=LD+4 + 1 NSGN=-NSGN + IF(MPHASE.AND.MOD(M+MP,2).NE.0) GMAT=-GMAT + GMAT=GMAT*PI4 + IF(ENEG) GO TO 3 + IF(MOD(LMIN+L2-L1,4).NE.0) GMAT=-GMAT + GO TO 4 + 3 IF(MOD(L1,2).EQ.0) GMAT=-GMAT +4 IF (M1.EQ.0.OR.M2.EQ.0) GO TO 13 +11 IF (M2.EQ.M1) GO TO 15 + GMAT=GMAT/SQR2 + GO TO 15 + 13 GMAT=GMAT/(2.0,0.0) + 15 IF(M2.GE.M1) RETURN + IF(MOD(MM,2).NE.0) GMAT=-GMAT + IF(I.EQ.-1) GMAT=-GMAT + RETURN +C + END +C +C + SUBROUTINE RADIAL_DOS(IR0,RAMF,QL,QLS,LN,NDM) +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + integer at_,d_,rd_,sd_,b_,f_,zz_,yl_,xx_,ww_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=2*lmax_+1, + &rd_=440,sd_=ua_-1,f_=(lmax_+1)*ua_,b_=24 ) +C +C + COMMON /FCNR/KX1,H(D_),VCONS(2), + 1 R(RD_,D_),V(RD_,SD_),ICHG(10,D_),KPLACE(AT_),KMAX(AT_) + complex*16 VCONS,V +C + COMMON/PARAM_DOS/EFTR,GAMMA,V000,XE,EV,E,IOUT,NOUT,NAT,NDAT, + 1 NSPINS,NACORE,RADION,QION,EXFAC0,ZEFF,NAS,NT1,NTB, + 2 RS(AT_),XV(AT_),YV(AT_),ZV(AT_),Z(AT_), + 3 EXFACT(AT_),LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),LCORE(AT_),KION,NAME(2) + complex*16 V000,EV,XE,E +C + COMMON /PDQ_DOS/ P(RD_,F_),PH(RD_,F_),PS(F_),DPS(F_) + + complex*16 P,PH,PS,DPS,RAMF,QL,QLS + DIMENSION RAMF(NDM),QL(NDM),QLS(NDM) +C + COMMON/STATE_DOS/ + 3 NLEQ(AT_),KTAU(AT_),NNS,ICORE, + 4 NUATOM,NDG,NLS(AT_),N0L(AT_),N0(AT_), + 5 NTERMS(AT_),LMAXN(AT_),NDIM,NDIMTR +C INTEGER*2 MN,IN,NATOM,IMIN,IMAX + DIMENSION LN(NDM) +C +C + DIMENSION RID(RD_),CRI(RD_),CRI1(RD_) + complex*16 RID,CRI,CRI1,DX,DX1 +C +C + IF (IR0.EQ.1) GO TO 90 + I=1 + IF (NOUT.EQ.1) I=2 + NSTART=N0(I) + 80 NLAST=N0(I)+NTERMS(I)-1 +C WRITE(6,31) NSTART,NLAST + 31 FORMAT(' NSTART=',I5,' NLAST=',I5, 'FOR CENTRAL ATOM') + NTAS=NLAST-NSTART+1 + NR=NSTART-NT1 + IF(NR.EQ.1) GO TO 10 + WRITE(6,15) NR + 15 FORMAT(1X,'**BEWARE**: THE ABSORBING ATOM MUST BE THE FIRST', + * ' AFTER THE OUTER SPHERE, IF ANY. NR=',I5) + CALL EXIT + 10 IF(NTERMS(NAS).EQ.NTAS.AND.I.EQ.NAS) GO TO 40 + WRITE(6,45) I, NAS, NTERMS(NAS), NTAS + 45 FORMAT(' ERROR DETECTED IN SUBROUTINE RADIAL_DOS. EITHER THE', + * ' ABSORBING ATOM NUMBER I=',I2,' AND NAS=',I2,' OR', + * ' THE BASIS FUNCTION NUMBER ON THE ABSORBING ATOM', + * ' NTERMS(NAS)=',I2,' AND NTAS=',I2,' DISAGREE') + CALL EXIT +C + 40 IR0=1 +C + 90 CONTINUE +C + NL=0 +C + DO 100 NA=1,NUATOM +C KX=KMAX(NA) + KX = KPLACE(NA) + IF(NA.EQ.1.AND.NOUT.EQ.1) KX=KMAX(NA) + NT0A=N0(NA) + NTXA=NT0A+NTERMS(NA)-1 + IF(NEQ(NA).NE.0) GO TO 101 + L=-1 + NLP=-1 + NPS=0 + ID=1 + IF(NA.EQ.1.AND.NOUT.EQ.1) ID=3 +C + DO 110 NN=NT0A,NTXA + IF(LN(NN).EQ.L) GO TO 114 + L=LN(NN) + NL=NL+1 + NP=NL + DO K=1,KX + RID(K)=(P(K,NP)*R(K,NA))**2 + ENDDO + CALL CINTEGR(RID,R(1,NA),KX,ICHG(1,NA),CRI,ID) + DX = CRI(KX)/RAMF(NN)/RAMF(NN) + DO K=1,KX + RID(K)=PH(K,NP)*P(K,NP)*R(K,NA)**2 + ENDDO + CALL CINTEGR(RID,R(1,NA),KX,ICHG(1,NA),CRI1,ID) + DX1=CRI1(KX)/RAMF(NN) + 114 CONTINUE + QL(NN)= DX + QLS(NN)=DX1 +C WRITE(6,*) 'IN SUB RADIAL_DOS - NA, NP =', NA, NP +C WRITE(6,*)'NN= ',NN, 'QL= ',QL(NN), 'QLS= ',QLS(NN) + 110 CONTINUE +C + GO TO 100 +C + 101 NN0=N0(NEQ(NA)) + DO K=NT0A,NTXA + QL(K)=QL(NN0) + QLS(K)=QLS(NN0) + ENDDO +C + 100 CONTINUE +C +C WRITE(6,*) 'IN RADIAL_DOS:' + DO N=NSTART,NLAST +C WRITE(6,*) 'E =',DBLE(E),'N=',N,' QL=',QL(N),' QLS=',QLS(N) + ENDDO +C + RETURN + END +C +C + SUBROUTINE SMTX_DOS(P,PH,RAMF,PS,DPS,ATTM,GAMMAT,EK,A,B, + & AI,YL,RAB,INDEX,WW_,YL_,ZZ_,XX_,JL, + & CN,MN,IN,NATOM,LN,NMS,IMIN,IMAX,NDM) +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + integer at_,d_,rd_,sd_,b_,f_,zz_,yl_,xx_,ww_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=2*lmax_+1, + &n_=(lmax_+1)**2*ua_,rd_=440,sd_=ua_-1,f_=(lmax_+1)*ua_,b_=24, + &nb_=(lmax_+1)**2 ) +C +C + COMMON/BESSEL_DOS/SBF(LTOT_),DSBF(LTOT_),SHF(LTOT_),DSHF(LTOT_) + COMPLEX*16 SBF,DSBF,SHF,DSHF +C + COMMON /FCNR/KX1, H(D_),VCONS(2), + 1 R(RD_,D_),V(RD_,SD_),ICHG(10,D_),KPLACE(AT_),KMAX(AT_) + complex*16 VCONS,V +C + COMMON /FLAG/ INMSH,INV,INRHO,INSYM,IOVRHO,IOSYM,PREV, + 1 NCOEF + LOGICAL*4 PREV +C +C COMMON/GAUNT/AI(WW_),INDEX(XX_),YL(YL_),RAB(ZZ_) + DIMENSION AI(WW_),INDEX(XX_),YL(YL_),RAB(ZZ_) +C + COMMON/PARAM_DOS/EFTR,GAMMA,VCON,XE,EV,E,IOUT,NOUT,NAT,NDAT, + 1 NSPINS,NACORE,RADION,QION,EXFAC0,ZEFF,NAS,NT1,NTB, + 2 RS(AT_),XV(AT_),YV(AT_),ZV(AT_),Z(AT_), + 3 EXFACT(AT_),LMAXX(AT_),NZ(AT_),NSYMBL(AT_), + 4 NEQ(AT_),LCORE(AT_),KION,NAME(2),MLEQ(AT_) + complex*16 VCON,EV,XE,E +C +c COMMON/SECULR/A(N_,N_),B(N_,N_) + COMPLEX*16 A,STMAT,B + +C + COMMON /STATE_DOS/ + 3 NLEQ(AT_),KTAU(AT_),NNS,ICORE, + 4 NUATOM,NDG,NLS(AT_),N0L(AT_),N0(AT_), + 5 NTERMS(AT_),LMAXN(AT_),NDIM,NDIMTR +C + INTEGER*2 MN,IN,NATOM,IMIN,IMAX + DIMENSION CN(B_,NDM),MN(B_,NDM), + 1 IN(B_,NDM),NATOM(B_,NDM),LN(NDM), + 2 NMS(NDM),IMIN(NDM,AT_),IMAX(NDM,AT_) +C + COMMON/OS/RAMFTZL(NB_),TZEROL(NB_) !,JL(N_,NB_) + complex*16 TZEROL, RAMFTZL, JL + DIMENSION JL(NDM,NB_) +C + complex*16 CSQRT,ARG,RAMF0,GM,GM1,GMAT, + 1 XEPI,EKPI,EK,OSCOR +C + dimension a(ndim,ndim),b(ndim,ndim) +c + DIMENSION P(RD_,F_),PH(RD_,F_),PS(F_),DPS(F_) + DIMENSION RAMF(NDM), ATTM(NDM) + complex*16 P,PH,PS,DPS,RAMF,ATTM +C + complex*16 SHFP,DSHFP +C +C + DATA ZERO,ONE,TWO/0.0,1.0,2.0/ + DATA PI/3.14159265358979/ +C +C + XEPI=XE/PI + EKPI=EK/PI + NL=0 + NS=(NNS-1)*NDAT +C +C INITIALIZE A TO ZERO AND B TO RHS UNIT MATRIX +C + DO I=1,NDIM !TR + DO J=1,NDIM !TR + B(I,J)=(0.D0,0.D0) + A(J,I)=(0.0D0,0.0D0) + ENDDO + ENDDO +C + DO I=1,NDIM !TR + B(I,I)=(1.D0,0.D0)*XEPI + ENDDO +C +C +C CALCULATE T-MATRIX ELEMENTS: +C STMAT: DIAGONAL ELEMENTS (ATOMIC SPHERES) +C RAMF: FOR CONSTRUCTION OF PSI(I) COEFFICIENTS +C IF OUTER SPHERE IS USED: +C TZEROL: FOR CONSTRUCTION OF SUM(JTJ) TERMS +C RAMFTZL:FOR CONSTRUCTION OF B-VECTORS (AX=B) +C + IF(IOUT.GE.3) WRITE(19,18) E, XE, GAMMAT, AIMAG(VCON) + 18 FORMAT(' E =',2F10.5,5X,' XE =',2F10.5,' GAMMAT =',F10.5) + DO 60 NA=1,NUATOM + STMATP=0.E0 + NS=NS+1 + IF(NLEQ(NA).EQ.0) GO TO 60 + MOUT=1 + IF(NA.EQ.1.AND.NOUT.EQ.1) MOUT=2 + 25 NT0A=N0(NA) + NTXA=NT0A+NTERMS(NA)-1 + IF(NEQ(NA).NE.0) GO TO 50 + L=-1 + NLP=-1 + ARG=XE*RS(NA) + ML=LMAXN(NA)+1 + IF (ML.LT.3) ML = 3 + CALL CSBF(ARG,XE,ML,SBF,DSBF) + IF(GAMMAT.GE.0.0) THEN + CALL CSHF2(ARG,XE,ML,SHF,DSHF) + ELSE + CALL CSHF1(ARG,XE,ML,SHF,DSHF) + ENDIF + 43 DO 45 NN=NT0A,NTXA + NNN=NN-NT1 + IF(LN(NN).EQ.L ) GO TO 35 + L=LN(NN) + NL=NL+1 + NP=NL + CALL TMAT1(L,RS(NA),KMAX(NA),Z(NA),H(NA),R(1,NA),V(1,NS), + 1 ICHG(1,NA),MOUT,KPLACE(NA),P(1,NP),STMAT,PS(NP), + 2 DPS(NP),RAMF0) + 35 IF(MOUT.EQ.2) GO TO 40 + A(NNN,NNN)=STMAT + ATTM(NN)=(1.D0,0.D0)/STMAT + RAMF(NN)=RAMF0 + GOTO 44 + 40 TZEROL(NN)=STMAT + RAMFTZL(NN)=RAMF0 + ATTM(NN)=STMAT + RAMF(NN)=RAMF0 + 44 CONTINUE + STMATR=DBLE(STMAT) + IF(LN(NN).EQ.NLP ) GO TO 21 + NLP = LN(NN) + IF (IOUT.EQ.5) THEN + WRITE(19,19) NA, L, RAMF(NNN), ATTM(NNN) +C WRITE(19,19) NA, L, SHF(L+1), DSHF(L+1) +C WRITE(19,19) NA, L, PS(NP), DPS(NP) + ENDIF + 19 FORMAT(2I5,4E15.6) + 21 CONTINUE + IF(STMATP.EQ.STMATR)GO TO 601 +C***** WRITE OUT STMAT IF NEEDED ***** + 601 CONTINUE + STMATP=STMATR + 45 CONTINUE + GOTO 60 + 50 NN0=N0(NEQ(NA))-NT1 + DO 55 NN=NT0A,NTXA + NMN=NN-NT1 + A(NMN,NMN)=A(NN0,NN0) + ATTM(NMN)=A(NN0,NN0) + RAMF(NMN)=RAMF(NN0) + 55 NN0=NN0+1 + 60 CONTINUE +C + IF(IOUT.EQ.5) THEN + DO 70 N=1,NDIMTR + WRITE(6,1002) N, A(N,N), RAMF(N) + 1002 FORMAT(' NN=',I3,' A(NN,NN) =',1P2E16.6,' RAMF(NN) =', + X 1P2E16.6) + 70 CONTINUE + ENDIF +C +C CALCULATE SINGULAR SOLUTION INSIDE MUFFIN TIN SPHERE FOR ALL ATOMS, +C MATCHING TO SHF2 IN INTERSTITIAL REGION +C + 90 NL=0 + IF(NOUT.EQ.1) NL=LMAXN(1)+1 + MOUT=4 + NS=0 +C + DO 600 NA=1,NUATOM + NS=NS+1 + IF(NOUT.EQ.1.AND.NA.EQ.1) GO TO 600 + IF(NLEQ(NA).EQ.0) GO TO 600 +C + KP=KPLACE(NA) + KPX=KMAX(NA) +C + DO 92 K=KP-3,KPX + IF(R(K,NA)-RS(NA)) 92,93,93 + 92 CONTINUE + 93 KP1=K+1 + KP2=KP1+1 + KPL=KP1-4 +C + NST=N0(NA) + NLST=N0(NA)+NTERMS(NA)-1 + L=-1 + ML=LMAXN(NA)+1 + IF (ML.LT.3) ML = 3 + ARG=XE*R(KP1,NA) + IF(GAMMAT.GE.0.0) THEN + CALL CSHF2(ARG,XE,ML,SBF,DSBF) + ELSE + CALL CSHF1(ARG,XE,ML,SBF,DSBF) + ENDIF + ARG=XE*R(KP1-1,NA) + IF(GAMMAT.GE.0.0) THEN + CALL CSHF2(ARG,XE,ML,SHF,DSHF) + ELSE + CALL CSHF1(ARG,XE,ML,SHF,DSHF) + ENDIF +C + DO 95 N=NST,NLST + IF(LN(N).EQ.L) GO TO 95 + L=LN(N) + NL=NL+1 + NP=NL + CALL TMAT1(L,RS(NA),KP1,Z(NA),H(NA),R(1,NA),V(1,NS), + 1 ICHG(1,NA),MOUT,KPL,PH(1,NP),STMAT,PS(NP),DPS(NP),RAMF0) + IF (IOUT.EQ.5) WRITE(6,1003) NP,PS(NP),DPS(NP) + 1003 FORMAT(' NP =',I5,' PS(NP) =',1P2E16.6,' DPS(NP) =',1P2E16.6) + DO 96 K=KP2,KPX + 96 PH(K,NP)=(0.,0.) + 95 CONTINUE +C + 600 CONTINUE +C +C..... +C + IF (IOUT.EQ.5) WRITE (6,100) (MLEQ(I),I=1,NAT) + 100 FORMAT(1H0,'SMTX.MLEQ ',30I4) + IF (IOUT.EQ.5) WRITE (6,101) (LMAXX(I),I=1,NAT) + 101 FORMAT (1X,'SMTX.LMAXX',30I4) + IF (IOUT.EQ.5) WRITE (6,102) (NLEQ(I),I=1,NAT) + 102 FORMAT (1X,'SMTX.NLEQ ',30I4) + IF (IOUT.EQ.5) WRITE (6,103) (LMAXN(I),I=1,NAT) + 103 FORMAT (1X,'SMTX.LMAXN',30I4) + IF (IOUT.EQ.5) WRITE (6,104) (NTERMS(I),I=1,NAT) + 104 FORMAT (1X,'SMTXNTERMS',30I4) + IF (IOUT.EQ.5) WRITE (6,105) (N0(I),I=1,NAT) + 105 FORMAT (1X,'SMTX.N0 ',30I4) +C +C +C CALCULATE 'OFF DIAGONAL' BLOCKS OF G-MATS: +C FOR ATOMIC SPHERES: +C GMAT:ARE CORRECTLY SYMMETRIZED THEN ADDED TO A +C WHEN OUTER SPHERE IS USED: +C JL:STORED FOR CONSTRUCTION OF OS CONTRIBUTION +C TO A (AX=B) +C + NYLC=1 + DO 340 NA=2,NAT + IF (MLEQ(NA).EQ.0) GO TO 340 + NT0A=N0(NLEQ(NA)) + NTXA=NT0A+NTERMS(NLEQ(NA))-1 + NA1=NA-1 + DO 330 NB=1,NA1 + IF (MLEQ(NB).EQ.0) GO TO 330 + NLAB = LMAXX(MLEQ(NA))+LMAXX(MLEQ(NB)) + MYL = (NLAB+1)*(NLAB+2)/2 + IF (NLEQ(NA)*NLEQ(NB).EQ.0) GO TO 320 + MOUT=1 + IF(NB.EQ.1.AND.NOUT.EQ.1) MOUT=2 + NAB=((NA-1)*(NA-2))/2+NB + NT0B=N0(NLEQ(NB)) + NTXB=NT0B+NTERMS(NLEQ(NB))-1 + NYLS = NYLC+MYL + ARG=XE*RAB(NAB) + MLAB=LMAXN(NA)+LMAXN(NB)+1 + IF (MLAB.LT.3) MLAB = 3 + IF(MOUT.EQ.2) GOTO 160 + IF(GAMMAT.GE.0.0) THEN + CALL CSHF2(ARG,(0.D0,0.D0),MLAB,SBF,DSBF) + ELSE + CALL CSHF1(ARG,(0.D0,0.D0),MLAB,SBF,DSBF) + ENDIF + GOTO 165 + 160 CALL CSBF(ARG,(0.D0,0.D0),MLAB,SBF,DSBF) + 165 CONTINUE +C + IF (IOUT.EQ.5) WRITE (6,180) NA,NB,( SBF(I),I=1,MLAB) + 180 FORMAT (1H0,'SMTX. SHF ',2I4,(1P8E12.4)) + IF (IOUT.EQ.5) WRITE (6,185) NA,NB,(DSBF(I),I=1,MLAB) + 185 FORMAT ( 1X,'SMTX.DSHF ',2I4,(1P8E12.4)) +C + DO 310 NN=NT0A,NTXA + NNT1=NN-NT1 + LA=LN(NN) + IMINA=IMIN(NN,NA) + IMAXA=IMAX(NN,NA) + IF(IMINA.GT.IMAXA) GO TO 310 + DO 300 NM=NT0B,NTXB + NMT1=NM-NT1 + LB=LN(NM) + IMINB=IMIN(NM,NB) + IMAXB=IMAX(NM,NB) + IF(IMINB.GT.IMAXB) GO TO 300 + DO 260 I=IMINA,IMAXA + MA=MN(I,NN) + IA=IN(I,NN) + DO 260 J=IMINB,IMAXB + IF(ABS(CN(I,NN)).LT.1.E-5 .OR. ABS(CN(J,NM)).LT.1.E-5) GOTO 260 + MB=MN(J,NM) + IB=IN(J,NM) + IF(IA.NE.IB) GOTO 200 + GM = GMAT(LA,MA,LB,MB,YL(NYLC),SBF,1,AI,INDEX,WW_,XX_,YL_) + IF(MA.EQ.0.OR.MB.EQ.0) GO TO 220 + GM1 = GMAT(LA,MA,LB,-MB,YL(NYLC),SBF,1,AI,INDEX,WW_,XX_,YL_) + IF(IA.EQ.-1) GM1=-GM1 + GOTO 215 + 200 IF(MA.NE.MB) GOTO 205 + GM=(0.0,0.0) + GOTO 210 + 205 GM = GMAT(LA,MA,LB,MB,YL(NYLS),SBF,-1,AI,INDEX,WW_,XX_,YL_) + IF(IA.EQ.-1) GM=-GM + IF(MA.EQ.0.OR.MB.EQ.0) GO TO 220 + 210 GM1 = -GMAT(LA,MA,LB,-MB,YL(NYLS),SBF,-1,AI,INDEX,WW_,XX_,YL_) + 215 IF(MOD(MB,2).NE.0) GM1=-GM1 + GOTO 225 + 220 GM1=GM + 225 GM=GM+GM1 + IF(MOUT.EQ.2) GOTO 235 + IF(NM.GT.NN) GOTO 230 + IF(NM.EQ.NN) GM=(2.0,0.0)*GM + A(NNT1,NMT1)=A(NNT1,NMT1) + GM*CN(I,NN)*CN(J,NM) + GOTO 260 + 230 A(NMT1,NNT1)=A(NMT1,NNT1) + GM*CN(I,NN)*CN(J,NM) + GOTO 260 + 235 CONTINUE + IF(NM.LT.NN) GOTO 255 + WRITE(6,245) + 245 FORMAT(' SMTX EXIT: ERROR IN STORAGE FOR GMAT ELEMENTS') + CALL EXIT + 255 JL(NNT1,NM) = JL(NNT1,NM)+ GM*CN(I,NN)*CN(J,NM) + 260 CONTINUE + 300 CONTINUE + 310 CONTINUE + 320 NYLC = NYLC+2*MYL + 330 CONTINUE + 340 CONTINUE +C +C + IF(IOUT.EQ.5) THEN + DO 5739 NM=2,NDIMTR + NA1=NM-1 + NMP1=NM+NT1 + DO 5739 NN=1,NA1 + NNP1=NN+NT1 + WRITE (6,282) NMP1,NNP1,A(NM,NN) + 282 FORMAT(' NMP1=',I3,' NNP1=',I3,' A(NM,NN):',2E14.6) + 5739 CONTINUE + ENDIF +C +C IF OUTER SPHERE IS USED, DUMP A INTO AP AND CALCULATE O.S. TERMS IN A +C + IF(NOUT.EQ.0) GOTO 400 +C + DO I=1,NDIMTR + LI=LN(I+NT1) + DO J=I,NDIMTR + DO IJ=1,NT1 + LIJ=LN(IJ) + OSCOR = JL(I,IJ)*JL(J,IJ)*TZEROL(IJ)*(-1)**(LI+LIJ) +C OSCOR = (0.0,0.0) + A(J,I)=A(J,I) - OSCOR + ENDDO + ENDDO + ENDDO +C + 400 CONTINUE +C +C FILL IN REST OF SCATTERING MATRIX A + DO 490 NN=2,NDIMTR + NA1=NN-1 + DO 490 NM=1,NA1 + 490 A(NM,NN)=A(NN,NM) + RETURN +C + END +C +C +C + SUBROUTINE TMAT1(L,RS,KMAX,Z,DELH,R,V,ICHG,MOUT,KPLACE,P,STMAT, + 1 PS,DPS,RAMF) +C + implicit real*8 (a-h,o-z) +C + include 'msxas3.inc' + integer at_,d_,rd_,sd_,b_,f_,zz_,yl_,xx_,ww_ + parameter ( at_=nat_-1,d_=ua_-1,ltot_=2*lmax_+1, + &n_=(lmax_+1)**2*ua_,rd_=440,sd_=ua_-1,f_=(lmax_+1)*ua_,b_=24 ) +C +C +C +C +C T-MATRIX CALCULATION FOR MULTIPLE-SCATTERING MODEL FOR POLYATOMIC +C MOLECULES. INTEGRATES RADIAL SCHRODINGER EQUATION USING NUMEROV +C DOES OUTWARD INTEGRATION FOR ATOMIC SPHERES, INWARD FOR OUTER +C SPHERES. GIVES INVERSE OF T-MATRIX AND LOG DERIVATIVE AT SPHERE +C SURFACE. +C +C MODIFIED FOR COMPLEX POTENTIALS.THIS VERSION NOT FOR COMPLEX +C POTENTIAL IN PRESENCE OF OUTER SPHERE. +C FOR T-MATRIX NORMALIZATION AND FOR CONTRACTED SECULAR MATRIX. +C +C CALCULATES : +C +C MOUT=4 SOLUTION MATCHING TO SBF AT R=RS +C +C MOUT=3 ATOMIC CORE STATES +C +C MOUT=2 OUTER SPHERE T-MATRIX ELEMENTS.(ONLY WHEN O.S. IS INCLUDED) +C RETURNS: +C STMAT=[SHFC,GAMMA]/[SBFC,GAMMA] (@RS OUTER SPHERE) +C RAMF=[SBFC,GAMMA]*PI*RS**2 (@RS OUTER SPHERE) +C NOTE: STMAT(IN TMAT)==TZEROL(IN SMTX) +C RAMF(IN TMAT)==RAMFTZL(IN SMTX) +C +C MOUT=1 ATOMIC SPHERES T-MATRIX ELEMENTS +C RETURNS: +C STMAT=[SHFC,PS]/[SBFC,PS] (@RS ATOMIC SPHERE) +C RAMF=[SBFC,PS]*XE*RS**2 (@RC ATOMIC SPHERE) +C +C +C + COMMON /BESSEL_DOS/ SBFC(LTOT_),DSBFC(LTOT_),SHFC(LTOT_), + 1 DSHFC(LTOT_) +C + COMMON /COULMB/ EK,FC(LTOT_),FCP(LTOT_),GC(LTOT_),GCP(LTOT_), + 1 FC1(LTOT_),FCP1(LTOT_),GC1(LTOT_),GCP1(LTOT_) +C + COMMON /PARAM_DOS/ EFTR,GAMMA,VCON,XE,EV,E,IOUT +C + DIMENSION V(KMAX),P(KMAX),R(KMAX),ICHG(10) +C + REAL*8 ASNORM,PI +C + COMPLEX*16 EK,RHO,FC,FCP,GC,GCP,FC1,FCP1,GC1,GCP1 + COMPLEX*16 VCON,XE,EV,E + COMPLEX*16 V,P,PS,DPS,RAMF,DPS1 + COMPLEX*16 ESP,ESP1,RATIO +C + COMPLEX*16 SBFC,SHFC,DSBFC,DSHFC + COMPLEX*16 CDSQRT + COMPLEX*16 STMAT,X,RAMFF + COMPLEX*16 PK,PK1,PKM,DKM,DK1,DK,GK,GK1,GKM +C + LOGICAL IGCTP,ALLOW +C + DATA PI/3.141592653589793D0/ +C +C +c INTERFACE +c SUBROUTINE STARTP_DOS(Z,L,E,R,V,KMAX,KI,P) +c COMPLEX E, V(:), P(:) +c REAL Z, R(:) +c END SUBROUTINE +c END INTERFACE +C +C +C + ALLOW=.FALSE. + KSTOP=1 + A=L*(L+1) + IGCTP=MOUT.EQ.4 + IF(MOUT.EQ.4) GO TO 60 + IGCTP=MOUT.NE.3.AND.IOUT.EQ.0 + IF(MOUT.EQ.2) GO TO 60 +C +C OUTWARD INTEGRATION FOR ATOMIC SPHERES +C + KI=1 + IF(L.GE.6) KI=ICHG(1) + CALL STARTP_DOS(Z,L,E,R,V,KMAX,KI,P) + IF (IOUT.EQ.5) THEN + WRITE(6,3333)L,Z,P(KI),P(KI+1),P(KI+2) + ENDIF + H=R(KI+1)-R(KI) + HSQ=H**2 + PKM=P(KI) + PK1=P(KI+1) + DKM=-(E-V(KI)-A/R(KI)**2)*HSQ*P(KI)/12.D0 + DK1=-(E-V(KI+1)-A/R(KI+1)**2)*HSQ*P(KI+1)/12.D0 + KIS=KI+2 + N=1 + IF(KI.EQ.ICHG(1)) N=2 + DO 34 K=KIS,KMAX + GK=(E-V(K)-A/R(K)**2)*HSQ/12.D0 + PK=(2.D0*(PK1+5.D0*DK1)-(PKM-DKM))/(1.D0+GK) + P(K)=PK + IF(IGCTP) GO TO 50 + IF(ALLOW) GO TO 51 + IF(DREAL(GK).LT.0.D0) GO TO 53 + ALLOW=.TRUE. + GO TO 53 + 51 IF(DREAL(GK).GE.0.D0) GO TO 53 + IGCTP=.TRUE. + IF(MOUT.EQ.3) GO TO 54 + GO TO 53 + 54 KSTOP=K+3 + IF(KSTOP.EQ.ICHG(N)-1) KSTOP=ICHG(N) + GO TO 53 + 50 IF (K.EQ.KSTOP) GO TO 52 + 53 IF(K.LT.ICHG(N)) GO TO 30 + N=N+1 + HSQ=4.*HSQ + DKM=4.D0*DKM + DK1=-4.D0*GK*PK + PK1=PK + GO TO 34 + 30 PKM=PK1 + DKM=DK1 + DK1=-GK*PK + PK1=PK + 34 CONTINUE + IF(MOUT.NE.3) GO TO 78 + WRITE(6,104) E + STOP + 52 DO 40 K=1,KSTOP + 40 P(K)=P(K)/R(K) + KSTOP=KSTOP-6 + CALL INTERP(R(KSTOP),P(KSTOP),7,R(KSTOP+3),PS,DPS,.TRUE.) + PS=P(KSTOP+3) +C +C INWARD INTEGRATION FOR OUTER SPHERE AND ATOMIC CORE STATES. +C TO FIND SOLUTION MATCHING TO SBF AT R=RS, ENTER AT THIS POINT +C WITH IGCTP=.TRUE. AND KSTOP=1. +C + 60 N=11 + 61 N=N-1 + IF(N.EQ.0) GO TO 66 + KN=ICHG(N) + IF(KN.GE.KMAX) GO TO 61 + IF(KN.LE.0) GO TO 61 +C + KN=KMAX +C***** ADDED 29/09/2008 + IF(MOUT.EQ.2) GO TO 67 +C***** + GO TO 62 + 64 KN=ICHG(N) + N=N-1 + IF (N.EQ.0) GO TO 66 + 62 ESP=(V(KN)-E)*R(KN)**2+A + IF (DBLE(ESP)-2400.D0) 65,65,64 + 66 KN=KMAX + IF (KN.GT.6) GO TO 65 + N=1 + KN=ICHG(2) + 65 IF(KN.NE.KMAX) WRITE(6,100) KN,KMAX + IF(MOUT.NE.3.AND.KN.NE.KMAX) CALL EXIT + IF(MOUT.NE.4) GO TO 67 + PKM=SBFC(L+1)*XE/PI*R(KN) + PK1=SHFC(L+1)*XE/PI*R(KN-1) + GO TO 63 + 67 IF (DBLE(E).LT.0.AND.MOUT.EQ.3) GO TO 68 +C ASNORM= CSQRT(PI*EK) + ASNORM=1.0D0 + PKM=GC(L+1)*R(KN)/ASNORM + PK1=GC1(L+1)*R(KN-1)/ASNORM + GO TO 63 + 68 PKM=EXP(-SQRT(ESP)) + ESP1=(V(KN-1)-E)*R(KN-1)**2+A + PK1=EXP(-SQRT(ESP1)) + 63 IF (IOUT.EQ.5) WRITE(6,3334) L,Z,PK1,PKM + HSQ=DELH**2*4**N + P(KN)=PKM + P(KN-1)=PK1 + IF(MOUT.NE.4) GO TO 70 + DKM=-(E-A/R(KN)**2-VCON)*PKM*HSQ/12.D0 + DK1=-(E-A/R(KN-1)**2-VCON)*PK1*HSQ/12.D0 + GO TO 80 + 70 DKM=-(E-V(KN)-A/R(KN)**2)*PKM*HSQ/12.D0 + DK1=-(E-V(KN-1)-A/R(KN-1)**2)*PK1*HSQ/12.D0 + 80 K=KN+1 + IF(K.GT.KMAX) GO TO 79 + DO 76 I=K,KMAX + 76 P(I)=(0.0,0.0) + 79 K=KN-1 + 73 K=K-1 + 74 GK=(E-V(K)-A/R(K)**2)*HSQ/12.D0 + PK=(2.D0*(PK1+5.D0*DK1)-PKM+DKM)/(1.D0+GK) + P(K)=PK + IF(IGCTP) GO TO 71 + IF(ALLOW) GO TO 56 + IF(DREAL(GK).LT.0.D0) GO TO 71 + IF(L.EQ.0) GO TO 59 + ALLOW=.TRUE. + GO TO 71 + 56 IF(DREAL(GK).GE.0.D0) GO TO 71 + 59 IGCTP=.TRUE. + GO TO 71 + 71 IF(K.EQ.KSTOP) GO TO 78 + IF(N.EQ.0) GO TO 69 + IF(K.GT.ICHG(N)) GO TO 69 + IF(K.LE.2) GO TO 75 + N=N-1 + DK=-PK*GK + GK1=(E-V(K-2)-A/R(K-2)**2)*HSQ/12.D0 + PK1=(2.D0*(PK+5.D0*DK)-PK1+DK1)/(1.D0+GK1) + DK1=-PK1*GK1/4.D0 + HSQ=HSQ/4. + GKM=(E-V(K-1)-A/R(K-1)**2)*HSQ/12.D0 + DK=DK/4.D0 + PKM=0.5D0*((PK-DK)+(PK1-DK1))/(1.0D0-5.0D0*GKM) + DKM=-PKM*GKM + K=K-3 +C +C KELLER MODIFICATION SUBROUTINE TMAT +C + P(K+2)=PKM + IF(K+1.LT.KSTOP) GO TO 78 + P(K+1) = PK1 + IF(K+1.EQ.KSTOP) GO TO 78 + GO TO 74 + 69 PKM=PK1 + DKM=DK1 + DK1=-PK*GK + PK1=PK + GO TO 73 + 75 WRITE(6,103) + STOP +C + 78 IF(MOUT.EQ.3) GO TO 57 +C + DO K=1,KMAX + P(K)=P(K)/R(K) + ENDDO +C + CALL INTERP(R(KPLACE-3),P(KPLACE-3),7,RS,PS,DPS,.TRUE.) +C + IF(MOUT.EQ.4) RETURN +C + X=DPS/PS + RAMFF=SBFC(L+1)*X-DSBFC(L+1) + STMAT=(SHFC(L+1)*X-DSHFC(L+1))/RAMFF + RAMF=RAMFF*PS*RS*RS*PI +C + IF(IOUT.EQ.5) THEN + WRITE(6,4444) L,Z,DSBFC(L+1),SBFC(L+1), + 1 DSHFC(L+1),SHFC(L+1),DPS,PS, + 2 RAMF*XE/PI,STMAT + ENDIF +C + IF(MOUT.EQ.2.AND.IOUT.EQ.5) + 1 WRITE(6,6234) L,E,EK,XE,ASNORM,PS,DPS,X,SBFC(L+1),DSBFC(L+1) + 2 ,RS,RAMFF,RAMF +C + IF(MOUT.NE.2) RAMF=RAMF*XE/PI +C +C Formats: +C + 100 FORMAT(1X,' ** WARNING ** : KN =',I3, + 1 ' DIFFERENT FROM KMAX =',I3) + 103 FORMAT(18H ERROR STOP - TMAT) + 104 FORMAT(/' ERROR - LEVEL E=',E14.7,' SHOULD NOT BE A CORE LEVEL'/) + 3333 FORMAT(' L,Z:',I3,E14.6,3(/,' STARTP:',2E18.6)) + 3334 FORMAT(' L,Z:',I3,F10.3,' PK1,PKM:',4E14.6) + 4444 FORMAT(' L,Z:',I3,F10.3,/,' (D)SBF:',4E14.6,/, + 1 ' (D)SHF:',4E14.6,/, + 2 ' (D)PS:',4E14.6,/, + 3 ' RAMF,STMAT:',2E14.6,2D14.6) + 6234 FORMAT(' TMAT:L,E,EK,XE,ASNORM:',I5,5E15.6,/,' PS,DPS,X:', + 1 6E16.7,/,' SBF DSBF:',4E16.7,/,' RS,RAMFF,RAMF:',5E19.7) +C + RETURN +C +C ATOMIC CORE STATES: +C + 57 RATIO=PS* R(KSTOP+3)/P(KSTOP+3) +C + DO K=KSTOP,KMAX + P(K)=P(K)*RATIO/R(K) + ENDDO +C + CALL INTERP(R(KSTOP),P(KSTOP),7,R(KSTOP+3),PS,DPS1,.TRUE.) +C + RAMF=(1.0,0.0) + STMAT=DPS1-DPS + RETURN +C + END +C +C +C*********************************************************************** +C + SUBROUTINE MERR(ISEQ) + WRITE(6,1) ISEQ + 1 FORMAT(' STOP CAUSED BY MERR AT SEQUENCY NUMBER',I6) + STOP + END + DOUBLE PRECISION FUNCTION CGC(L1,L2,L3,M1,M2) +C + IMPLICIT REAL*8(A-H,O-Z) +C +C CLEBSCH-GORDAN COEFFICIENT EQ. 3.18, ROSE +C + DIMENSION NUM(5),ND(5) +C + NFF=0 + M3=M1+M2 +C +C ARGUMENTS OF FACTORIALS +C + NUM(1)=L3+L1-L2 + NUM(2)=L3-L1+L2 + NUM(3)=L1+L2-L3 + NUM(4)=L3+M3 + NUM(5)=L3-M3 + ND(1)=L1+L2+L3+1 + ND(2)=L1-M1 + ND(3)=L1+M1 + ND(4)=L2-M2 + ND(5)=L2+M2 +C +C CHECK TRIANGLE AND PROJECTION CONDITIONS +C + DO 12 I=1,5 + IF(NUM(I)) 99,11,11 + 11 IF(ND(I)) 99,12,12 + 12 CONTINUE + FF=1.D0 +C +C TWO SETS OF FACTORIAL PRODUCTS +C + N=5 + DO 120 NFAC=1,2 + N1=N-1 +C +C ARRANGE ARGUMENTS IN DESCENDING ORDER +C + DO 13 I=1,N1 + INUM=I + ID=I + I1=I+1 + DO 14 J=I1,N + IF(NUM(J).LE.NUM(INUM)) GO TO 15 + INUM=J + 15 IF(ND(J).LE.ND(ID)) GO TO 14 + ID=J + 14 CONTINUE + NTEMP=NUM(I) + NUM(I)=NUM(INUM) + NUM(INUM)=NTEMP + NTEMP=ND(I) + ND(I)=ND(ID) + 13 ND(ID)=NTEMP +C +C COMPUTE FACTORIAL RATIOS +C + DO 16 I=1,N + IF(NUM(I)-ND(I)) 17,16,18 + 17 JM=ND(I) + IF(JM.EQ.1) GO TO 16 + J0=NUM(I)+1 + IF(NUM(I).EQ.0) J0=2 + DO 19 J=J0,JM + IF(DABS(FF).GT.1.D-20) GO TO 19 + FF=FF*1.D20 + NFF=NFF-2 + 19 FF=FF/DFLOAT(J) + GO TO 16 + 18 JM=NUM(I) + IF(JM.EQ.1) GO TO 16 + J0=ND(I)+1 + IF(ND(I).EQ.0) J0=2 + DO 20 J=J0,JM + IF(DABS(FF).LT.1.D 20) GO TO 20 + FF=FF/1.D20 + NFF=NFF+2 + 20 FF=FF*DFLOAT(J) + 16 CONTINUE + IF(NFAC.EQ.2) GO TO 21 + NFF=NFF/2 + FF=DSQRT((2*L3+1)*FF) +C +C SECOND SET OF FACTORIAL ARGUMENTS +C + NMIN=MAX0(0,L2+M3-L1) + NUM(1)=L2+L3+M1-NMIN + NUM(2)=L1-M1+NMIN + NUM(3)=0 + ND(1)=NMIN + IF(NMIN.EQ.0) ND(1)=L1-L2-M3 + ND(2)=L3-L1+L2-NMIN + ND(3)=L3+M3-NMIN + 120 N=3 + 21 IF(MOD(NMIN+L2+M2,2).EQ.0) GO TO 22 + FF=-FF + 22 FF=FF*1.D10**NFF + CGCP = FF + NMAX=MIN0(L3-L1+L2,L3+M3) + CGC = CGCP + IF(NMIN.GE.NMAX) RETURN + NMIN=NMIN+1 + DO 23 NU=NMIN,NMAX + FF= -(((L1-M1+NU)*(L3-L1+L2-NU+1)*(L3+M3-NU+1))/DFLOAT(NU*(NU+L1-L + 1 2-M3)*(L2+L3+M1-NU+1)))*FF + 23 CGCP = CGCP+FF + CGC = CGCP + RETURN + 99 CGC=0.0 + RETURN + END +C + SUBROUTINE CSBF_DOS(X,Y,MAX,SBF,DSBF) +C + implicit real*8 (a-h,o-z) +C + INTEGER MAX,K,JMIN,KMAX +C + REAL*8 XF1 +C + complex*16 X,Y,RAT,DSBF1,CSIN,Z,SBFJ,B,A,CCOS +C + COMPLEX*16 SBFK,SBF1,SBF2,CDEXP + COMPLEX*16 SBF(MAX), DSBF(MAX) +C +C +C GENERATES SPHERICAL BESSEL FUNCTIONS OF ORDER 0 - MAX-1 AND THEIR +C FIRST DERIVATIVES WITH RESPECT TO R. X=ARGUMENT= Y*R. +C IF Y=0, NO DERIVATIVES ARE CALCULATED. MAX MUST BE AT LEAST 3. +C OSBF GENERATES ORDINARY SPHERICAL BESSEL FUNCTIONS. MSBF - MODI- +C FIED SPHERICAL BESSEL FUNCTIONS; OSNF - ORD. SPH. NEUMANN FCNS; +C MSNF - MOD. SPH. NEUMANN FCNS; MSHF - MOD. SPH HANKEL FCNS +C +C +C + 1 IF (MAX.LT.1.OR.MAX.GT.2000) GO TO 99 + ABSX = ABS(X) + IF(ABSX.LT.0.50 ) GO TO 18 +C +C BESSEL FUNCTIONS BY DOWNWARD RECURSION +C + SBF2=(0.0D0,0.0D0) + SBF1=1.0D-25*(0.5D0,0.5D0) + IF(ABSX.LT.2.0) SBF1=1.0D-37*(0.5D0,0.5D0) + JMIN=10+INT(ABS(X)) + KMAX=MAX+JMIN-1 + K=MAX + XF1=2*KMAX+1 + DO 10 J=1,KMAX + SBFK=XF1*SBF1/X-SBF2 + SBF2=SBF1 + SBF1=SBFK + XF1=XF1-2.0D0 + IF (J.LT.JMIN) GO TO 10 + SBF(K)=SBFK + K=K-1 +10 CONTINUE + RAT=SIN(X)/(X*SBF(1)) + 16 DO 17 K=1,MAX + 17 SBF(K)=RAT*SBF(K) + DSBF1=-SBF(2) + GO TO 26 +C +C SMALL ARGUMENTS +C + 18 Z=-(X*X*0.50) + A=(1.0,0.0) + MMX=MAX + IF (MAX.EQ.1.AND.Y.NE.(0.0,0.0)) MMX=2 + DO 30 J=1,MMX + SBFJ=A + B=A + DO 31 I=1,20 + B=B*Z/(I*(2*(J+I)-1)) + SBFJ=SBFJ+B + ABSB = ABS(B) + ABSJ = ABS(SBFJ) + IF (ABSB.LE.1.0E-07*ABSJ) GO TO 29 + 31 CONTINUE +29 IF (J.EQ.2) DSBF1=-SBFJ + IF (J.LE.MAX) SBF(J)=SBFJ + 30 A=A*X/ DBLE(2*J+1) + GO TO 26 +C +C ENTRY TO CALCULATE SPHERICAL NEUMANN FUNCTIONS +C + ENTRY CSNF_DOS(X,Y,MAX,SBF,DSBF) +C + SBF2=-COS(X)/X + IF (MAX.EQ.1 .AND. Y.EQ.(0.0,0.0)) GO TO 2 + SBF1=(SBF2-SIN(X))/X + DSBF1=-SBF1 + GO TO 2 +C +C ENTRY TO CALCULATE SPHERICAL HANKEL FUNCTIONS OF FIRST TYPE ('OUTGOING') +C************ NOTE : RETURNS I [-(0.0,1.0)] TIMES HL1 ****************** +C + ENTRY CSHF1(X,Y,MAX,SBF,DSBF) +C + SBF2=(0.0D0,1.0D0)*X + SBF2=-(0.0D0,1.0D0)*EXP(SBF2)/SBF2 + IF (MAX.EQ.1 .AND. Y.EQ.(0.0,0.0)) GO TO 2 + SBF1=SBF2*(1.0D0/X-(0.0D0,1.0D0)) + DSBF1=-SBF1 + GOTO 2 +C +C ENTRY TO CALCULATE SPHERICAL HANKEL FUNCTIONS OF SECOND TYPE ('INGOING') +C************ NOTE : RETURNS I [(0.0,1.0)] TIMES HL2 ******************* +C + ENTRY CSHF2_DOS(X,Y,MAX,SBF,DSBF) +C + SBF2=-(0.0D0,1.0D0)*X + SBF2=(0.0D0,1.0D0)*EXP(SBF2)/SBF2 + SBF1=SBF2*(1.0D0/X+(0.0D0,1.0D0)) + DSBF1=-SBF1 +2 SBF(1)=SBF2 + IF (MAX.LT.1.OR.MAX.GT.2000) GO TO 99 + IF (MAX.EQ.1) GO TO 26 + SBF(2)=SBF1 + IF (MAX.EQ.2) GO TO 26 + XF1=3.0D0 +21 DO 22 I=3,MAX + SBFK=XF1*SBF1/X-SBF2 + SBF(I)=SBFK + SBF2=SBF1 + SBF1=SBFK +22 XF1=XF1+2.0D0 +26 IF (Y.EQ.(0.0,0.0)) RETURN + DSBF(1)=Y*DSBF1 + IF (MAX.EQ.1) RETURN + DO 9 I=2,MAX + 9 DSBF(I)=Y*(SBF(I-1)- DBLE(I)*SBF(I)/X) + RETURN +99 WRITE(6,100) MAX +100 FORMAT (' SPHERICAL BESSEL FUNCTION ROUTINE - MAX=',I8) + + STOP + END +C + SUBROUTINE STARTP_DOS(Z0,L,E,R,V,KMAX,KI,P) +C + IMPLICIT COMPLEX*16 (A-B) +C + REAL*8 Z0,R +C + REAL*8 Z1,H,XL,RK +C + COMPLEX*16 V,P,E +C + COMPLEX*16 Z(300) +C + DIMENSION R(KMAX),V(KMAX),P(KMAX) +C + KM=KI/4 + IF(KI.EQ.1) KM=1 + KI1=KI+2 +C + DO K=1,KI1 + Z(K)=R(K)*V(K) + ENDDO +C + XL=DBLE(L) + H=DBLE(KM)*R(1) + Z1=Z0 +C + B1=-2.0D0*Z1 + B2=(22.D0*Z1+18.D0*Z(KM)-9.D0*Z(2*KM)+2.D0*Z(3*KM))/ + 1 (6.D0*H)-DCMPLX(E) + B3=(-12.D0*Z1-15.D0*Z(KM)+12.D0*Z(2*KM)-3.D0*Z(3*KM))/(6.D0*H*H) + B4=(2.D0*Z1+3.D0*Z(KM)-3.D0*Z(2*KM)+Z(3*KM))/(6.D0*H**3) +C + A1=-Z1/(XL+1.0D0) + A2=(B1*A1+B2)/(4.0D0*XL+6.0D0) + A3=(B1*A2+B2*A1+B3)/(6.0D0*XL+12.0D0) + A4=(B1*A3+B2*A2+B3*A1+B4)/(8.0D0*XL+20.0D0) + A5=(B1*A4+B2*A3+B3*A2+B4*A1)/(10.D0*XL+30.D0) + A6=(B1*A5+B2*A4+B3*A3+B4*A2)/(12.D0*XL+42.D0) + A7=(B1*A6+B2*A5+B3*A4+B4*A3)/(14.D0*XL+56.D0) +C + DO K=1,KI1 + RK=R(K) + P(K)=(1.0D0+RK*(A1+RK*(A2+RK*(A3+RK*(A4+RK*(A5+RK* + 1 (A6+RK*A7)))))))*RK**(L+1) + ENDDO +C + RETURN +C + END +C + SUBROUTINE YLM1(LMAX,Z,PHI,YL,MYL) +C +C GENERATES REAL SPHERICAL HARMONICS, L = 0 TO LMAX; M = 0 TO L. +C ARRANGED WITH M VARYING MOST RAPIDLY. YL (I,1)=EVEN SPHERICAL +C HARMONIC; YL(I,2)=ODD SPHERICAL HARMONIC. +C + IMPLICIT REAL*8(A-H,O-Z) +C + REAL*8 PI4/12.56637061435917D0/,PI2/6.283185307179586D0/ + REAL*8 Y0/.282094791773878D0/,ZERO/0.0D0/,ONE/1.0D0/ +C + REAL*8 PH +C +C + DIMENSION YL(MYL,2) +C + PH=PHI +C + YL(1,2)=ZERO + YL(1,1)=Y0 +C + IF(LMAX.EQ.0) RETURN +C + X=DSQRT(ONE-(Z*Z)) + SINPHI= DSIN(PH) + COSPHI= DCOS(PH) + SINMP=ZERO + COSMP=ONE + PMM=ONE + FAC=ONE + ISUB=1 + MFAC2=0 + MFAC=1 +C +C P(M,M) BY RECURSION FORMULA +C + LP1=LMAX+1 + DO 1 MP1=1,LP1 + M=MP1-1 + IF(M.EQ.0) GO TO 10 + FAC=FAC/(MFAC*MFAC2) + PMM=-PMM *MFAC*X + COSTP=COSMP*COSPHI-SINMP*SINPHI + SINMP=SINMP*COSPHI+COSMP*SINPHI + COSMP=COSTP + FAC1=FAC/PI2 + MFAC=MFAC+2 + YL1=PMM*DSQRT(FAC1*MFAC) + YL(ISUB,1)=YL1*COSMP + YL(ISUB,2)=YL1*SINMP + IF(M.EQ.LMAX) RETURN + GO TO 11 + 10 FAC1=FAC/PI4 + 11 ISUB1=ISUB+M+1 + ISUB=ISUB1+1 + LFAC=MFAC + PLM=PMM + MFAC2=MFAC2+2 + M1=M+1 +C +C RECURSION FOR P(L,M), L = M+1 TO LMAX. + + DO 2 L=M1,LMAX + LM=L-M + LP=L+M + PLM1=PLM*Z*LFAC + LFAC=LFAC+2 + IF(L.EQ.M1) GO TO 20 + PLM1=PLM1-(LP-1)*PLM0 + 20 PLM1=PLM1/LM + IF(M.EQ.0) GO TO 21 + FAC1=LM*FAC1/LP + 21 YL1=DSQRT(LFAC*FAC1)*PLM1 + YL(ISUB1,1)=YL1*COSMP + YL(ISUB1,2)=YL1*SINMP + PLM0=PLM + PLM=PLM1 + 2 ISUB1=ISUB1+L+1 + 1 CONTINUE + RETURN +C # + END +C + SUBROUTINE F06AAZ ( SRNAME, INFO ) +C +C MARK 12 RELEASE. NAG COPYRIGHT 1986. +C MARK 15 REVISED. IER-915 (APR 1991). +C .. Scalar Arguments .. + INTEGER INFO +CST ==> Phagen to Python shared object modifications +CST I changed the dimension to automatic +CST CHARACTER*13 SRNAME + CHARACTER(*) SRNAME +CST Phagen to Python shared object modifications <== +C .. +C +C Purpose +C ======= +C +C F06AAZ is an error handler for the Level 2 BLAS routines. +C +C It is called by the Level 2 BLAS routines if an input parameter is +C invalid. +C +C Parameters +C ========== +C +C SRNAME - CHARACTER*13. +C On entry, SRNAME specifies the name of the routine which +C called F06AAZ. +C +C INFO - INTEGER. +C On entry, INFO specifies the position of the invalid +C parameter in the parameter-list of the calling routine. +C +C +C Auxiliary routine for Level 2 Blas. +C +C Written on 20-July-1986. +C +C .. Local Scalars .. + INTEGER IERR, IFAIL + CHARACTER*4 VARBNM +C .. Local Arrays .. + CHARACTER*80 REC (1) +C .. External Functions .. + INTEGER P01ACF + EXTERNAL P01ACF +C .. +C .. Executable Statements .. + WRITE (REC (1),99999) SRNAME, INFO + IF (SRNAME(1:3).EQ.'F06') THEN + IERR = -1 + VARBNM = ' ' + ELSE + IERR = -INFO + VARBNM = 'INFO' + END IF + IFAIL = 0 + IFAIL = P01ACF (IFAIL, IERR, SRNAME(1:6), VARBNM, 1, REC) +C + RETURN +C +99999 FORMAT ( ' ** On entry to ', A13, ' parameter number ', I2, + $ ' had an illegal value' ) +C +C End of F06AAZ. +C + END +C + SUBROUTINE F07NRF(UPLO,N,A,LDA,IPIV,WORK,LWORK,INFO) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C .. Entry Points .. + ENTRY ZSYTRF(UPLO,N,A,LDA,IPIV,WORK,LWORK,INFO) +C +C Purpose +C ======= +C +C ZSYTRF computes the factorization of a complex symmetric matrix A +C using the Bunch-Kaufman diagonal pivoting method: +C +C A = U*D*U' or A = L*D*L' +C +C where U (or L) is a product of permutation and unit upper (lower) +C triangular matrices, U' is the transpose of U, and D is symmetric and +C block diagonal with 1-by-1 and 2-by-2 diagonal blocks. +C +C This is the blocked version of the algorithm, calling Level 3 BLAS. +C +C Arguments +C ========= +C +C UPLO (input) CHARACTER*1 +C Specifies whether the upper or lower triangular part of the +C symmetric matrix A is stored: +C = 'U': Upper triangular +C = 'L': Lower triangular +C +C N (input) INTEGER +C The order of the matrix A. N >= 0. +C +C A (input/output) COMPLEX array, dimension (LDA,N) +C On entry, the symmetric matrix A. If UPLO = 'U', the leading +C n-by-n upper triangular part of A contains the upper +C triangular part of the matrix A, and the strictly lower +C triangular part of A is not referenced. If UPLO = 'L', the +C leading n-by-n lower triangular part of A contains the lower +C triangular part of the matrix A, and the strictly upper +C triangular part of A is not referenced. +C +C On exit, the block diagonal matrix D and the multipliers used +C to obtain the factor U or L (see below for further details). +C +C LDA (input) INTEGER +C The leading dimension of the array A. LDA >= max(1,N). +C +C IPIV (output) INTEGER array, dimension (N) +C Details of the interchanges and the block structure of D. +C If IPIV(k) > 0, then rows and columns k and IPIV(k) were +C interchanged and D(k,k) is a 1-by-1 diagonal block. +C If UPLO = 'U' and IPIV(k) = IPIV(k-1) < 0, then rows and +C columns k-1 and -IPIV(k) were interchanged and D(k-1:k,k-1:k) +C is a 2-by-2 diagonal block. If UPLO = 'L' and IPIV(k) = +C IPIV(k+1) < 0, then rows and columns k+1 and -IPIV(k) were +C interchanged and D(k:k+1,k:k+1) is a 2-by-2 diagonal block. +C +C WORK (workspace) COMPLEX array, dimension (LWORK) +C If INFO returns 0, then WORK(1) returns the minimum +C value of LWORK required for optimal performance. +C +C LWORK (input) INTEGER +C The length of WORK. LWORK >= 1. +C For optimal performance LWORK should be at least N*NB, +C where NB is the optimal blocksize returned by F07ZAZ. +C +C INFO (output) INTEGER +C = 0: successful exit +C < 0: if INFO = -k, the k-th argument had an illegal value +C > 0: if INFO = k, D(k,k) is exactly zero. The factorization +C has been completed, but the block diagonal matrix D is +C exactly singular, and division by zero will occur if it +C is used to solve a system of equations. +C +C Further Details +C =============== +C +C If UPLO = 'U', then A = U*D*U', where +C U = P(n)*U(n)* ... *P(k)U(k)* ..., +C i.e., U is a product of terms P(k)*U(k), where k decreases from n to +C 1 in steps of 1 or 2, and D is a block diagonal matrix with 1-by-1 +C and 2-by-2 diagonal blocks D(k). P(k) is a permutation matrix as +C defined by IPIV(k), and U(k) is a unit upper triangular matrix, such +C that if the diagonal block D(k) is of order s (s = 1 or 2), then +C +C ( I v 0 ) k-s +C U(k) = ( 0 I 0 ) s +C ( 0 0 I ) n-k +C k-s s n-k +C +C If s = 1, D(k) overwrites A(k,k), and v overwrites A(1:k-1,k). +C If s = 2, the upper triangle of D(k) overwrites A(k-1,k-1), A(k-1,k), +C and A(k,k), and v overwrites A(1:k-2,k-1:k). +C +C If UPLO = 'L', then A = L*D*L', where +C L = P(1)*L(1)* ... *P(k)*L(k)* ..., +C i.e., L is a product of terms P(k)*L(k), where k increases from 1 to +C n in steps of 1 or 2, and D is a block diagonal matrix with 1-by-1 +C and 2-by-2 diagonal blocks D(k). P(k) is a permutation matrix as +C defined by IPIV(k), and L(k) is a unit lower triangular matrix, such +C that if the diagonal block D(k) is of order s (s = 1 or 2), then +C +C ( I 0 0 ) k-1 +C L(k) = ( 0 I 0 ) s +C ( 0 v I ) n-k-s+1 +C k-1 s n-k-s+1 +C +C If s = 1, D(k) overwrites A(k,k), and v overwrites A(k+1:n,k). +C If s = 2, the lower triangle of D(k) overwrites A(k,k), A(k+1,k), +C and A(k+1,k+1), and v overwrites A(k+2:n,k:k+1). +C +C -- LAPACK routine (adapted for NAG Library) +C Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., +C Courant Institute, Argonne National Lab, and Rice University +C +C ===================================================================== +C +C .. Scalar Arguments .. + INTEGER INFO, LDA, LWORK, N + CHARACTER UPLO +C .. Array Arguments .. + COMPLEX*16 A(LDA,*), WORK(LWORK) + INTEGER IPIV(*) +C .. Local Scalars .. + INTEGER IINFO, IWS, J, K, KB, LDWORK, NB, NBMIN + LOGICAL UPPER +C .. External Subroutines .. + EXTERNAL F06AAZ, F07NRY, F07NRZ, F07ZAZ +C .. Intrinsic Functions .. + INTRINSIC MAX +C .. Executable Statements .. +C +C Test the input parameters. +C + INFO = 0 + UPPER = (UPLO.EQ.'U' .OR. UPLO.EQ.'u') + IF ( .NOT. UPPER .AND. .NOT. (UPLO.EQ.'L' .OR. UPLO.EQ.'l')) THEN + INFO = -1 + ELSE IF (N.LT.0) THEN + INFO = -2 + ELSE IF (LDA.LT.MAX(1,N)) THEN + INFO = -4 + ELSE IF (LWORK.LT.1) THEN + INFO = -7 + END IF + IF (INFO.NE.0) THEN + CALL F06AAZ('F07NRF/ZSYTRF',-INFO) + RETURN + END IF +C +C Determine the block size +C + CALL F07ZAZ(1,'F07NRF',NB,0) + IF (NB.LE.1) NB = N +C + IF (NB.LT.N) THEN + LDWORK = N +C +C Determine if workspace is large enough for blocked code +C + IWS = N*NB + IF (LWORK.LT.IWS) THEN +C +C Not enough workspace has been supplied to use the optimal +C value of NB: determine the minimum value of NB, and reduce +C NB or force use of unblocked code +C + CALL F07ZAZ(2,'F07NRF',NBMIN,0) + NBMIN = MAX(2,NBMIN) +C + IF (LWORK.GE.N*NBMIN) THEN + NB = LWORK/N + ELSE + NB = N + END IF + END IF + ELSE + IWS = 1 + END IF +C + IF (UPPER) THEN +C +C Factorize A as U*D*U' using the upper triangle of A +C +C K is the main loop index, decreasing from N to 1 in steps of +C KB, where KB is the number of columns factorized by F07NRY; +C KB is either NB or NB-1, or K for the last block +C + K = N + 20 CONTINUE +C +C If K < 1, exit from loop +C + IF (K.LT.1) GO TO 80 +C + IF (K.GT.NB) THEN +C +C Factorize columns k-kb+1:k of A and use blocked code to +C update columns 1:k-kb +C + CALL F07NRY(UPLO,K,NB,KB,A,LDA,IPIV,WORK,LDWORK,IINFO) + ELSE +C +C Use unblocked code to factorize columns 1:k of A +C + CALL F07NRZ(UPLO,K,A,LDA,IPIV,IINFO) + KB = K + END IF +C +C Set INFO on the first occurrence of a zero pivot +C + IF (INFO.EQ.0 .AND. IINFO.GT.0) INFO = IINFO +C +C Decrease K and return to the start of the main loop +C + K = K - KB + GO TO 20 +C + ELSE +C +C Factorize A as L*D*L' using the lower triangle of A +C +C K is the main loop index, increasing from 1 to N in steps of +C KB, where KB is the number of columns factorized by F07NRY; +C KB is either NB or NB-1, or N-K+1 for the last block +C + K = 1 + 40 CONTINUE +C +C If K > N, exit from loop +C + IF (K.GT.N) GO TO 80 +C + IF (K.LE.N-NB) THEN +C +C Factorize columns k:k+kb-1 of A and use blocked code to +C update columns k+kb:n +C + CALL F07NRY(UPLO,N-K+1,NB,KB,A(K,K),LDA,IPIV(K),WORK,LDWORK, + * IINFO) + ELSE +C +C Use unblocked code to factorize columns k:n of A +C + CALL F07NRZ(UPLO,N-K+1,A(K,K),LDA,IPIV(K),IINFO) + KB = N - K + 1 + END IF +C +C Set INFO on the first occurrence of a zero pivot +C + IF (INFO.EQ.0 .AND. IINFO.GT.0) INFO = IINFO + K - 1 +C +C Adjust IPIV +C + DO 60 J = K, K + KB - 1 + IF (IPIV(J).GT.0) THEN + IPIV(J) = IPIV(J) + K - 1 + ELSE + IPIV(J) = IPIV(J) - K + 1 + END IF + 60 CONTINUE +C +C Increase K and return to the start of the main loop +C + K = K + KB + GO TO 40 +C + END IF +C + 80 CONTINUE + WORK(1) = IWS + RETURN +C +C End of F07NRF (ZSYTRF) +C + END +C + SUBROUTINE F07NRV(UPLO,N,ALPHA,X,INCX,A,LDA) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C ENTRY ZSYR(UPLO,N,ALPHA,X,INCX,A,LDA) +C +C Purpose +C ======= +C +C ZSYR performs the symmetric rank 1 operation +C +C A := alpha*x*( x' ) + A, +C +C where alpha is a real scalar, x is an n element vector and A is an +C n by n symmetric matrix. +C +C Arguments +C ========== +C +C UPLO - CHARACTER*1 +C On entry, UPLO specifies whether the upper or lower +C triangular part of the array A is to be referenced as +C follows: +C +C UPLO = 'U' or 'u' Only the upper triangular part of A +C is to be referenced. +C +C UPLO = 'L' or 'l' Only the lower triangular part of A +C is to be referenced. +C +C Unchanged on exit. +C +C N - INTEGER +C On entry, N specifies the order of the matrix A. +C N must be at least zero. +C Unchanged on exit. +C +C ALPHA - COMPLEX +C On entry, ALPHA specifies the scalar alpha. +C Unchanged on exit. +C +C X - COMPLEX array, dimension at least +C ( 1 + ( N - 1 )*abs( INCX ) ). +C Before entry, the incremented array X must contain the N- +C element vector x. +C Unchanged on exit. +C +C INCX - INTEGER +C On entry, INCX specifies the increment for the elements of +C X. INCX must not be zero. +C Unchanged on exit. +C +C A - COMPLEX array, dimension( LDA, N ) +C Before entry with UPLO = 'U' or 'u', the leading n by n +C upper triangular part of the array A must contain the upper +C triangular part of the symmetric matrix and the strictly +C lower triangular part of A is not referenced. On exit, the +C upper triangular part of the array A is overwritten by the +C upper triangular part of the updated matrix. +C Before entry with UPLO = 'L' or 'l', the leading n by n +C lower triangular part of the array A must contain the lower +C triangular part of the symmetric matrix and the strictly +C upper triangular part of A is not referenced. On exit, the +C lower triangular part of the array A is overwritten by the +C lower triangular part of the updated matrix. +C +C LDA - INTEGER +C On entry, LDA specifies the first dimension of A as declared +C in the calling (sub) program. LDA must be at least +C max( 1, N ). +C Unchanged on exit. +C +C +C -- LAPACK auxiliary routine (adapted for NAG Library) +C Univ. of Tennessee, Oak Ridge National Lab, Argonne National Lab, +C Courant Institute, NAG Ltd., and Rice University +C +C .. Parameters .. + COMPLEX*16 ZERO + PARAMETER (ZERO=(0.0D+0,0.0D+0)) +C .. Scalar Arguments .. + COMPLEX*16 ALPHA + INTEGER INCX, LDA, N + CHARACTER UPLO +C .. Array Arguments .. + COMPLEX*16 A(LDA,*), X(*) +C .. Local Scalars .. + COMPLEX*16 TEMP + INTEGER I, INFO, IX, J, JX, KX +C .. External Subroutines .. + EXTERNAL F06AAZ +C .. Intrinsic Functions .. + INTRINSIC MAX +C .. Executable Statements .. +C +C Test the input parameters. +C + INFO = 0 + IF ( .NOT. (UPLO.EQ.'U' .OR. UPLO.EQ.'u') + * .AND. .NOT. (UPLO.EQ.'L' .OR. UPLO.EQ.'l')) THEN + INFO = 1 + ELSE IF (N.LT.0) THEN + INFO = 2 + ELSE IF (INCX.EQ.0) THEN + INFO = 5 + ELSE IF (LDA.LT.MAX(1,N)) THEN + INFO = 7 + END IF + IF (INFO.NE.0) THEN + CALL F06AAZ('F07NRV/ZSYR',INFO) + RETURN + END IF +C +C Quick return if possible. +C + IF ((N.EQ.0) .OR. (ALPHA.EQ.ZERO)) RETURN +C +C Set the start point in X if the increment is not unity. +C + IF (INCX.LE.0) THEN + KX = 1 - (N-1)*INCX + ELSE IF (INCX.NE.1) THEN + KX = 1 + END IF +C +C Start the operations. In this version the elements of A are +C accessed sequentially with one pass through the triangular part +C of A. +C + IF ((UPLO.EQ.'U' .OR. UPLO.EQ.'u')) THEN +C +C Form A when A is stored in upper triangle. +C + IF (INCX.EQ.1) THEN + DO 40 J = 1, N + IF (X(J).NE.ZERO) THEN + TEMP = ALPHA*X(J) + DO 20 I = 1, J + A(I,J) = A(I,J) + X(I)*TEMP + 20 CONTINUE + END IF + 40 CONTINUE + ELSE + JX = KX + DO 80 J = 1, N + IF (X(JX).NE.ZERO) THEN + TEMP = ALPHA*X(JX) + IX = KX + DO 60 I = 1, J + A(I,J) = A(I,J) + X(IX)*TEMP + IX = IX + INCX + 60 CONTINUE + END IF + JX = JX + INCX + 80 CONTINUE + END IF + ELSE +C +C Form A when A is stored in lower triangle. +C + IF (INCX.EQ.1) THEN + DO 120 J = 1, N + IF (X(J).NE.ZERO) THEN + TEMP = ALPHA*X(J) + DO 100 I = J, N + A(I,J) = A(I,J) + X(I)*TEMP + 100 CONTINUE + END IF + 120 CONTINUE + ELSE + JX = KX + DO 160 J = 1, N + IF (X(JX).NE.ZERO) THEN + TEMP = ALPHA*X(JX) + IX = JX + DO 140 I = J, N + A(I,J) = A(I,J) + X(IX)*TEMP + IX = IX + INCX + 140 CONTINUE + END IF + JX = JX + INCX + 160 CONTINUE + END IF + END IF +C + RETURN +C +C End of F07NRV (ZSYR) +C + END +C + SUBROUTINE F07NRW(N,CX,INCX,CY,INCY,C,S) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C ENTRY ZLACRT(N,CX,INCX,CY,INCY,C,S) +C +C Purpose +C ======= +C +C ZLACRT applies a plane rotation, where the cos and sin (C and S) are +C complex and the vectors CX and CY are complex. +C +C Arguments +C ========= +C +C N (input) INTEGER +C The number of elements in the vectors CX and CY. +C +C CX (input/output) COMPLEX array, dimension (N) +C On input, the vector X. +C On output, CX is overwritten with C*X + S*Y. +C +C INCX (input) INTEGER +C The increment between successive values of CY. INCX <> 0. +C +C CY (input/output) COMPLEX array, dimension (N) +C On input, the vector Y. +C On output, CY is overwritten with -S*X + C*Y. +C +C INCY (input) INTEGER +C The increment between successive values of CY. INCX <> 0. +C +C C (input) COMPLEX +C S (input) COMPLEX +C C and S define a complex rotation +C [ C S ] +C [ -S C ] +C where C*C + S*S = 1.0. +C +C +C -- LAPACK auxiliary routine (adapted for NAG Library) +C Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., +C Courant Institute, Argonne National Lab, and Rice University +C +C .. Scalar Arguments .. + COMPLEX*16 C, S + INTEGER INCX, INCY, N +C .. Array Arguments .. + COMPLEX*16 CX(*), CY(*) +C .. Local Scalars .. + COMPLEX*16 CTEMP + INTEGER I, IX, IY +C .. Executable Statements .. +C + IF (N.LE.0) RETURN + IF (INCX.EQ.1 .AND. INCY.EQ.1) GO TO 40 +C +C Code for unequal increments or equal increments not equal to 1 +C + IX = 1 + IY = 1 + IF (INCX.LT.0) IX = (-N+1)*INCX + 1 + IF (INCY.LT.0) IY = (-N+1)*INCY + 1 + DO 20 I = 1, N + CTEMP = C*CX(IX) + S*CY(IY) + CY(IY) = C*CY(IY) - S*CX(IX) + CX(IX) = CTEMP + IX = IX + INCX + IY = IY + INCY + 20 CONTINUE + RETURN +C +C Code for both increments equal to 1 +C + 40 CONTINUE + DO 60 I = 1, N + CTEMP = C*CX(I) + S*CY(I) + CY(I) = C*CY(I) - S*CX(I) + CX(I) = CTEMP + 60 CONTINUE + RETURN + END +C + SUBROUTINE F07NRX(A,B,C,RT1,RT2,EVSCAL,CS1,SN1) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C ENTRY ZLAESY(A,B,C,RT1,RT2,EVSCAL,CS1,SN1) +C +C Purpose +C ======= +C +C ZLAESY computes the eigendecomposition of a 2x2 symmetric matrix +C ( ( A, B );( B, C ) ) +C provided the norm of the matrix of eigenvectors is larger than +C some threshold value. +C +C RT1 is the eigenvalue of larger absolute value, and RT2 of +C smaller absolute value. If the eigenvectors are computed, then +C on return ( CS1, SN1 ) is the unit eigenvector for RT1, hence +C +C [ CS1 SN1 ] . [ A B ] . [ CS1 -SN1 ] = [ RT1 0 ] +C [ -SN1 CS1 ] [ B C ] [ SN1 CS1 ] [ 0 RT2 ] +C +C Arguments +C ========= +C +C A (input) COMPLEX +C The ( 1, 1 ) entry of input matrix. +C +C B (input) COMPLEX +C The ( 1, 2 ) entry of input matrix. The ( 2, 1 ) entry is +C also given by B, since the 2 x 2 matrix is symmetric. +C +C C (input) COMPLEX +C The ( 2, 2 ) entry of input matrix. +C +C RT1 (output) COMPLEX +C The eigenvalue of larger modulus. +C +C RT2 (output) COMPLEX +C The eigenvalue of smaller modulus. +C +C EVSCAL (output) COMPLEX +C The complex value by which the eigenvector matrix was scaled +C to make it orthonormal. If EVSCAL is zero, the eigenvectors +C were not computed. This means one of two things: the 2 x 2 +C matrix could not be diagonalized, or the norm of the matrix +C of eigenvectors before scaling was larger than the threshold +C value THRESH (set below). +C +C CS1 (output) COMPLEX +C SN1 (output) COMPLEX +C If EVSCAL .NE. 0, ( CS1, SN1 ) is the unit right eigenvector +C for RT1. +C +C +C -- LAPACK auxiliary routine (adapted for NAG Library) +C Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., +C Courant Institute, Argonne National Lab, and Rice University +C +C .. Parameters .. + DOUBLE PRECISION ZERO + PARAMETER (ZERO=0.0D0) + DOUBLE PRECISION ONE + PARAMETER (ONE=1.0D0) + COMPLEX*16 CONE + PARAMETER (CONE=(1.0D0,0.0D0)) + DOUBLE PRECISION HALF + PARAMETER (HALF=0.5D0) + DOUBLE PRECISION THRESH + PARAMETER (THRESH=0.1D0) +C .. Scalar Arguments .. + COMPLEX*16 A, B, C, CS1, EVSCAL, RT1, RT2, SN1 +C .. Local Scalars .. + COMPLEX*16 S, T, TMP + DOUBLE PRECISION BABS, EVNORM, TABS, Z +C .. Intrinsic Functions .. + INTRINSIC ABS, MAX, MIN, SQRT +C .. Executable Statements .. +C +C +C Special case: The matrix is actually diagonal. +C To avoid divide by zero later, we treat this case separately. +C + IF (ABS(B).EQ.ZERO) THEN + RT1 = A + RT2 = C + IF (ABS(RT1).LT.ABS(RT2)) THEN + TMP = RT1 + RT1 = RT2 + RT2 = TMP + CS1 = ZERO + SN1 = ONE + ELSE + CS1 = ONE + SN1 = ZERO + END IF + ELSE +C +C Compute the eigenvalues and eigenvectors. +C The characteristic equation is +C lamba **2 - (A+C) lamba + (A*C - B*B) +C and we solve it using the quadratic formula. +C + S = (A+C)*HALF + T = (A-C)*HALF +C +C Take the square root carefully to avoid over/under flow. +C + BABS = ABS(B) + TABS = ABS(T) + Z = MAX(BABS,TABS) + IF (MIN(BABS,TABS).GT.ZERO) T = Z*SQRT((T/Z)**2+(B/Z)**2) +C +C Compute the two eigenvalues. RT1 and RT2 are exchanged +C if necessary so that RT1 will have the greater magnitude. +C + RT1 = S + T + RT2 = S - T + IF (ABS(RT1).LT.ABS(RT2)) THEN + TMP = RT1 + RT1 = RT2 + RT2 = TMP + END IF +C +C Choose CS1 = 1 and SN1 to satisfy the first equation, then +C scale the components of this eigenvector so that the matrix +C of eigenvectors X satisfies X * X' = I . (No scaling is +C done if the norm of the eigenvalue matrix is less than THRESH.) +C + SN1 = (RT1-A)/B + TABS = ABS(SN1) + IF (TABS.GT.ONE) THEN + T = TABS*SQRT((ONE/TABS)**2+(SN1/TABS)**2) + ELSE + T = SQRT(CONE+SN1*SN1) + END IF + EVNORM = ABS(T) + IF (EVNORM.GE.THRESH) THEN + EVSCAL = CONE/T + CS1 = EVSCAL + SN1 = SN1*EVSCAL + ELSE + EVSCAL = ZERO + END IF + END IF + RETURN +C +C End of F07NRX (ZLAESY) +C + END +C + SUBROUTINE F07NRY(UPLO,N,NB,KB,A,LDA,IPIV,W,LDW,INFO) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C ENTRY ZLASYF(UPLO,N,NB,KB,A,LDA,IPIV,W,LDW,INFO) +C +C Purpose +C ======= +C +C ZLASYF computes a partial factorization of a complex symmetric matrix +C A using the Bunch-Kaufman diagonal pivoting method. The partial +C factorization has the form: +C +C A = ( I U12 ) ( A11 0 ) ( I 0 ) if UPLO = 'U', or: +C ( 0 U22 ) ( 0 D ) ( U12' U22' ) +C +C A = ( L11 0 ) ( D 0 ) ( L11' L21' ) if UPLO = 'L' +C ( L21 I ) ( 0 A22 ) ( 0 I ) +C +C where the order of D is at most NB. The actual order is returned in +C the argument KB, and is either NB or NB-1, or N if N <= NB. +C Note that U' denotes the transpose of U. +C +C ZLASYF is an auxiliary routine called by F07NRF. It uses blocked code +C (calling Level 3 BLAS) to update the submatrix A11 (if UPLO = 'U') or +C A22 (if UPLO = 'L'). +C +C Arguments +C ========= +C +C UPLO (input) CHARACTER*1 +C Specifies whether the upper or lower triangular part of the +C symmetric matrix A is stored: +C = 'U': Upper triangular +C = 'L': Lower triangular +C +C N (input) INTEGER +C The order of the matrix A. N >= 0. +C +C NB (input) INTEGER +C The maximum number of columns of the matrix A that should be +C factored. NB should be at least 2 to allow for 2-by-2 pivot +C blocks. +C +C KB (output) INTEGER +C The number of columns of A that were actually factored. +C KB is either NB-1 or NB, or N if N <= NB. +C +C A (input/output) COMPLEX array, dimension (LDA,N) +C On entry, the symmetric matrix A. If UPLO = 'U', the leading +C n-by-n upper triangular part of A contains the upper +C triangular part of the matrix A, and the strictly lower +C triangular part of A is not referenced. If UPLO = 'L', the +C leading n-by-n lower triangular part of A contains the lower +C triangular part of the matrix A, and the strictly upper +C triangular part of A is not referenced. +C On exit, A contains details of the partial factorization. +C +C LDA (input) INTEGER +C The leading dimension of the array A. LDA >= max(1,N). +C +C IPIV (output) INTEGER array, dimension (N) +C Details of the interchanges and the block structure of D. +C If UPLO = 'U', only the last KB elements of IPIV are set; +C if UPLO = 'L', only the first KB elements are set. +C +C If IPIV(k) > 0, then rows and columns k and IPIV(k) were +C interchanged and D(k,k) is a 1-by-1 diagonal block. +C If UPLO = 'U' and IPIV(k) = IPIV(k-1) < 0, then rows and +C columns k-1 and -IPIV(k) were interchanged and D(k-1:k,k-1:k) +C is a 2-by-2 diagonal block. If UPLO = 'L' and IPIV(k) = +C IPIV(k+1) < 0, then rows and columns k+1 and -IPIV(k) were +C interchanged and D(k:k+1,k:k+1) is a 2-by-2 diagonal block. +C +C W (workspace) COMPLEX array, dimension (LDW,NB) +C +C LDW (input) INTEGER +C The leading dimension of the array W. LDW >= max(1,N). +C +C INFO (output) INTEGER +C = 0: successful exit +C > 0: if INFO = k, D(k,k) is exactly zero. The factorization +C has been completed, but the block diagonal matrix D is +C exactly singular. +C +C -- LAPACK routine (adapted for NAG Library) +C Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., +C Courant Institute, Argonne National Lab, and Rice University +C +C ===================================================================== +C +C .. Parameters .. + DOUBLE PRECISION ZERO, ONE + PARAMETER (ZERO=0.0D+0,ONE=1.0D+0) + DOUBLE PRECISION EIGHT, SEVTEN + PARAMETER (EIGHT=8.0D+0,SEVTEN=17.0D+0) + COMPLEX*16 CONE + PARAMETER (CONE=(1.0D+0,0.0D+0)) +C .. Scalar Arguments .. + INTEGER INFO, KB, LDA, LDW, N, NB + CHARACTER UPLO +C .. Array Arguments .. + COMPLEX*16 A(LDA,*), W(LDW,*) + INTEGER IPIV(*) +C .. Local Scalars .. + COMPLEX*16 D11, D21, D22, R1, T, Z + DOUBLE PRECISION ABSAKK, ALPHA, COLMAX, ROWMAX + INTEGER IMAX, J, JB, JJ, JMAX, JP, K, KK, KKW, KP, + * KSTEP, KW +C .. External Functions .. + INTEGER IZAMAX + EXTERNAL IZAMAX +C .. External Subroutines .. + EXTERNAL ZCOPY, ZGEMM, ZGEMV, ZSCAL, ZSWAP +C .. Intrinsic Functions .. + INTRINSIC ABS, DIMAG, MAX, MIN, DBLE, SQRT +C .. Statement Functions .. + DOUBLE PRECISION CABS1 +C .. Statement Function definitions .. + CABS1(Z) = ABS(DBLE(Z)) + ABS(DIMAG(Z)) +C .. Executable Statements .. +C + INFO = 0 +C +C Initialize ALPHA for use in choosing pivot block size. +C + ALPHA = (ONE+SQRT(SEVTEN))/EIGHT +C + IF ((UPLO.EQ.'U' .OR. UPLO.EQ.'u')) THEN +C +C Factorize the trailing columns of A using the upper triangle +C of A and working backwards, and compute the matrix W = U12*D +C for use in updating A11 +C +C K is the main loop index, decreasing from N in steps of 1 or 2 +C +C KW is the column of W which corresponds to column K of A +C + K = N + 20 CONTINUE + KW = NB + K - N +C +C Exit from loop +C + IF ((K.LE.N-NB+1 .AND. NB.LT.N) .OR. K.LT.1) GO TO 60 +C +C Copy column K of A to column KW of W and update it +C + CALL ZCOPY(K,A(1,K),1,W(1,KW),1) + IF (K.LT.N) CALL ZGEMV('No transpose',K,N-K,-CONE,A(1,K+1),LDA, + * W(K,KW+1),LDW,CONE,W(1,KW),1) +C + KSTEP = 1 +C +C Determine rows and columns to be interchanged and whether +C a 1-by-1 or 2-by-2 pivot block will be used +C + ABSAKK = CABS1(W(K,KW)) +C +C IMAX is the row-index of the largest off-diagonal element in +C column K, and COLMAX is its absolute value +C + IF (K.GT.1) THEN + IMAX = IZAMAX(K-1,W(1,KW),1) + COLMAX = CABS1(W(IMAX,KW)) + ELSE + COLMAX = ZERO + END IF +C + IF (MAX(ABSAKK,COLMAX).EQ.ZERO) THEN +C +C Column K is zero: set INFO and continue +C + IF (INFO.EQ.0) INFO = K + KP = K + ELSE + IF (ABSAKK.GE.ALPHA*COLMAX) THEN +C +C no interchange, use 1-by-1 pivot block +C + KP = K + ELSE +C +C Copy column IMAX to column KW-1 of W and update it +C + CALL ZCOPY(IMAX,A(1,IMAX),1,W(1,KW-1),1) + CALL ZCOPY(K-IMAX,A(IMAX,IMAX+1),LDA,W(IMAX+1,KW-1),1) + IF (K.LT.N) CALL ZGEMV('No transpose',K,N-K,-CONE, + * A(1,K+1),LDA,W(IMAX,KW+1),LDW, + * CONE,W(1,KW-1),1) +C +C JMAX is the column-index of the largest off-diagonal +C element in row IMAX, and ROWMAX is its absolute value +C + JMAX = IMAX + IZAMAX(K-IMAX,W(IMAX+1,KW-1),1) + ROWMAX = CABS1(W(JMAX,KW-1)) + IF (IMAX.GT.1) THEN + JMAX = IZAMAX(IMAX-1,W(1,KW-1),1) + ROWMAX = MAX(ROWMAX,CABS1(W(JMAX,KW-1))) + END IF +C + IF (ABSAKK.GE.ALPHA*COLMAX*(COLMAX/ROWMAX)) THEN +C +C no interchange, use 1-by-1 pivot block +C + KP = K + ELSE IF (CABS1(W(IMAX,KW-1)).GE.ALPHA*ROWMAX) THEN +C +C interchange rows and columns K and IMAX, use 1-by-1 +C pivot block +C + KP = IMAX +C +C copy column KW-1 of W to column KW +C + CALL ZCOPY(K,W(1,KW-1),1,W(1,KW),1) + ELSE +C +C interchange rows and columns K-1 and IMAX, use 2-by-2 +C pivot block +C + KP = IMAX + KSTEP = 2 + END IF + END IF +C + KK = K - KSTEP + 1 + KKW = NB + KK - N +C +C Updated column KP is already stored in column KKW of W +C + IF (KP.NE.KK) THEN +C +C Copy non-updated column KK to column KP +C + A(KP,K) = A(KK,K) + CALL ZCOPY(K-1-KP,A(KP+1,KK),1,A(KP,KP+1),LDA) + CALL ZCOPY(KP,A(1,KK),1,A(1,KP),1) +C +C Interchange rows KK and KP in last KK columns of A and W +C + CALL ZSWAP(N-KK+1,A(KK,KK),LDA,A(KP,KK),LDA) + CALL ZSWAP(N-KK+1,W(KK,KKW),LDW,W(KP,KKW),LDW) + END IF +C + IF (KSTEP.EQ.1) THEN +C +C 1-by-1 pivot block D(k): column KW of W now holds +C +C W(k) = U(k)*D(k) +C +C where U(k) is the k-th column of U +C +C Store U(k) in column k of A +C + CALL ZCOPY(K,W(1,KW),1,A(1,K),1) + R1 = CONE/A(K,K) + CALL ZSCAL(K-1,R1,A(1,K),1) + ELSE +C +C 2-by-2 pivot block D(k): columns KW and KW-1 of W now +C hold +C +C ( W(k-1) W(k) ) = ( U(k-1) U(k) )*D(k) +C +C where U(k) and U(k-1) are the k-th and (k-1)-th columns +C of U +C + IF (K.GT.2) THEN +C +C Store U(k) and U(k-1) in columns k and k-1 of A +C + D21 = W(K-1,KW) + D11 = W(K,KW)/D21 + D22 = W(K-1,KW-1)/D21 + T = CONE/(D11*D22-CONE) + D21 = T/D21 + DO 40 J = 1, K - 2 + A(J,K-1) = D21*(D11*W(J,KW-1)-W(J,KW)) + A(J,K) = D21*(D22*W(J,KW)-W(J,KW-1)) + 40 CONTINUE + END IF +C +C Copy D(k) to A +C + A(K-1,K-1) = W(K-1,KW-1) + A(K-1,K) = W(K-1,KW) + A(K,K) = W(K,KW) + END IF + END IF +C +C Store details of the interchanges in IPIV +C + IF (KSTEP.EQ.1) THEN + IPIV(K) = KP + ELSE + IPIV(K) = -KP + IPIV(K-1) = -KP + END IF +C +C Decrease K and return to the start of the main loop +C + K = K - KSTEP + GO TO 20 +C + 60 CONTINUE +C +C Update the upper triangle of A11 (= A(1:k,1:k)) as +C +C A11 := A11 - U12*D*U12' = A11 - U12*W' +C +C computing blocks of NB columns at a time +C + DO 100 J = ((K-1)/NB)*NB + 1, 1, -NB + JB = MIN(NB,K-J+1) +C +C Update the upper triangle of the diagonal block +C + DO 80 JJ = J, J + JB - 1 + CALL ZGEMV('No transpose',JJ-J+1,N-K,-CONE,A(J,K+1),LDA, + * W(JJ,KW+1),LDW,CONE,A(J,JJ),1) + 80 CONTINUE +C +C Update the rectangular superdiagonal block +C + CALL ZGEMM('No transpose','Transpose',J-1,JB,N-K,-CONE, + * A(1,K+1),LDA,W(J,KW+1),LDW,CONE,A(1,J),LDA) + 100 CONTINUE +C +C Put U12 in standard form by partially undoing the interchanges +C in columns k+1:n +C + J = K + 1 + 120 CONTINUE + JJ = J + JP = IPIV(J) + IF (JP.LT.0) THEN + JP = -JP + J = J + 1 + END IF + J = J + 1 + IF (JP.NE.JJ .AND. J.LE.N) CALL ZSWAP(N-J+1,A(JP,J),LDA, + * A(JJ,J),LDA) + IF (J.LE.N) GO TO 120 +C +C Set KB to the number of columns factorized +C + KB = N - K +C + ELSE +C +C Factorize the leading columns of A using the lower triangle +C of A and working forwards, and compute the matrix W = L21*D +C for use in updating A22 +C +C K is the main loop index, increasing from 1 in steps of 1 or 2 +C + K = 1 + 140 CONTINUE +C +C Exit from loop +C + IF ((K.GE.NB .AND. NB.LT.N) .OR. K.GT.N) GO TO 180 +C +C Copy column K of A to column K of W and update it +C + CALL ZCOPY(N-K+1,A(K,K),1,W(K,K),1) + CALL ZGEMV('No transpose',N-K+1,K-1,-CONE,A(K,1),LDA,W(K,1), + * LDW,CONE,W(K,K),1) +C + KSTEP = 1 +C +C Determine rows and columns to be interchanged and whether +C a 1-by-1 or 2-by-2 pivot block will be used +C + ABSAKK = CABS1(W(K,K)) +C +C IMAX is the row-index of the largest off-diagonal element in +C column K, and COLMAX is its absolute value +C + IF (K.LT.N) THEN + IMAX = K + IZAMAX(N-K,W(K+1,K),1) + COLMAX = CABS1(W(IMAX,K)) + ELSE + COLMAX = ZERO + END IF +C + IF (MAX(ABSAKK,COLMAX).EQ.ZERO) THEN +C +C Column K is zero: set INFO and continue +C + IF (INFO.EQ.0) INFO = K + KP = K + ELSE + IF (ABSAKK.GE.ALPHA*COLMAX) THEN +C +C no interchange, use 1-by-1 pivot block +C + KP = K + ELSE +C +C Copy column IMAX to column K+1 of W and update it +C + CALL ZCOPY(IMAX-K,A(IMAX,K),LDA,W(K,K+1),1) + CALL ZCOPY(N-IMAX+1,A(IMAX,IMAX),1,W(IMAX,K+1),1) + CALL ZGEMV('No transpose',N-K+1,K-1,-CONE,A(K,1),LDA, + * W(IMAX,1),LDW,CONE,W(K,K+1),1) +C +C JMAX is the column-index of the largest off-diagonal +C element in row IMAX, and ROWMAX is its absolute value +C + JMAX = K - 1 + IZAMAX(IMAX-K,W(K,K+1),1) + ROWMAX = CABS1(W(JMAX,K+1)) + IF (IMAX.LT.N) THEN + JMAX = IMAX + IZAMAX(N-IMAX,W(IMAX+1,K+1),1) + ROWMAX = MAX(ROWMAX,CABS1(W(JMAX,K+1))) + END IF +C + IF (ABSAKK.GE.ALPHA*COLMAX*(COLMAX/ROWMAX)) THEN +C +C no interchange, use 1-by-1 pivot block +C + KP = K + ELSE IF (CABS1(W(IMAX,K+1)).GE.ALPHA*ROWMAX) THEN +C +C interchange rows and columns K and IMAX, use 1-by-1 +C pivot block +C + KP = IMAX +C +C copy column K+1 of W to column K +C + CALL ZCOPY(N-K+1,W(K,K+1),1,W(K,K),1) + ELSE +C +C interchange rows and columns K+1 and IMAX, use 2-by-2 +C pivot block +C + KP = IMAX + KSTEP = 2 + END IF + END IF +C + KK = K + KSTEP - 1 +C +C Updated column KP is already stored in column KK of W +C + IF (KP.NE.KK) THEN +C +C Copy non-updated column KK to column KP +C + A(KP,K) = A(KK,K) + CALL ZCOPY(KP-K-1,A(K+1,KK),1,A(KP,K+1),LDA) + CALL ZCOPY(N-KP+1,A(KP,KK),1,A(KP,KP),1) +C +C Interchange rows KK and KP in first KK columns of A and W +C + CALL ZSWAP(KK,A(KK,1),LDA,A(KP,1),LDA) + CALL ZSWAP(KK,W(KK,1),LDW,W(KP,1),LDW) + END IF +C + IF (KSTEP.EQ.1) THEN +C +C 1-by-1 pivot block D(k): column k of W now holds +C +C W(k) = L(k)*D(k) +C +C where L(k) is the k-th column of L +C +C Store L(k) in column k of A +C + CALL ZCOPY(N-K+1,W(K,K),1,A(K,K),1) + IF (K.LT.N) THEN + R1 = CONE/A(K,K) + CALL ZSCAL(N-K,R1,A(K+1,K),1) + END IF + ELSE +C +C 2-by-2 pivot block D(k): columns k and k+1 of W now hold +C +C ( W(k) W(k+1) ) = ( L(k) L(k+1) )*D(k) +C +C where L(k) and L(k+1) are the k-th and (k+1)-th columns +C of L +C + IF (K.LT.N-1) THEN +C +C Store L(k) and L(k+1) in columns k and k+1 of A +C + D21 = W(K+1,K) + D11 = W(K+1,K+1)/D21 + D22 = W(K,K)/D21 + T = CONE/(D11*D22-CONE) + D21 = T/D21 + DO 160 J = K + 2, N + A(J,K) = D21*(D11*W(J,K)-W(J,K+1)) + A(J,K+1) = D21*(D22*W(J,K+1)-W(J,K)) + 160 CONTINUE + END IF +C +C Copy D(k) to A +C + A(K,K) = W(K,K) + A(K+1,K) = W(K+1,K) + A(K+1,K+1) = W(K+1,K+1) + END IF + END IF +C +C Store details of the interchanges in IPIV +C + IF (KSTEP.EQ.1) THEN + IPIV(K) = KP + ELSE + IPIV(K) = -KP + IPIV(K+1) = -KP + END IF +C +C Increase K and return to the start of the main loop +C + K = K + KSTEP + GO TO 140 +C + 180 CONTINUE +C +C Update the lower triangle of A22 (= A(k:n,k:n)) as +C +C A22 := A22 - L21*D*L21' = A22 - L21*W' +C +C computing blocks of NB columns at a time +C + DO 220 J = K, N, NB + JB = MIN(NB,N-J+1) +C +C Update the lower triangle of the diagonal block +C + DO 200 JJ = J, J + JB - 1 + CALL ZGEMV('No transpose',J+JB-JJ,K-1,-CONE,A(JJ,1),LDA, + * W(JJ,1),LDW,CONE,A(JJ,JJ),1) + 200 CONTINUE +C +C Update the rectangular subdiagonal block +C + IF (J+JB.LE.N) CALL ZGEMM('No transpose','Transpose', + * N-J-JB+1,JB,K-1,-CONE,A(J+JB,1), + * LDA,W(J,1),LDW,CONE,A(J+JB,J),LDA) + 220 CONTINUE +C +C Put L21 in standard form by partially undoing the interchanges +C in columns 1:k-1 +C + J = K - 1 + 240 CONTINUE + JJ = J + JP = IPIV(J) + IF (JP.LT.0) THEN + JP = -JP + J = J - 1 + END IF + J = J - 1 + IF (JP.NE.JJ) CALL ZSWAP(J,A(JP,1),LDA,A(JJ,1),LDA) + IF (J.GE.1) GO TO 240 +C +C Set KB to the number of columns factorized +C + KB = K - 1 +C + END IF + RETURN +C +C End of F07NRY (ZLASYF) +C + END +C + SUBROUTINE F07NRZ(UPLO,N,A,LDA,IPIV,INFO) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C ENTRY ZSYTF2(UPLO,N,A,LDA,IPIV,INFO) +C +C Purpose +C ======= +C +C ZSYTF2 computes the factorization of a complex symmetric matrix A +C using the Bunch-Kaufman diagonal pivoting method: +C +C A = U*D*U' or A = L*D*L' +C +C where U (or L) is a product of permutation and unit upper (lower) +C triangular matrices, U' is the transpose of U, and D is symmetric and +C block diagonal with 1-by-1 and 2-by-2 diagonal blocks. +C +C This is the unblocked version of the algorithm, calling Level 2 BLAS. +C +C Arguments +C ========= +C +C UPLO (input) CHARACTER*1 +C Specifies whether the upper or lower triangular part of the +C symmetric matrix A is stored: +C = 'U': Upper triangular +C = 'L': Lower triangular +C +C N (input) INTEGER +C The order of the matrix A. N >= 0. +C +C A (input/output) COMPLEX array, dimension (LDA,N) +C On entry, the symmetric matrix A. If UPLO = 'U', the leading +C n-by-n upper triangular part of A contains the upper +C triangular part of the matrix A, and the strictly lower +C triangular part of A is not referenced. If UPLO = 'L', the +C leading n-by-n lower triangular part of A contains the lower +C triangular part of the matrix A, and the strictly upper +C triangular part of A is not referenced. +C +C On exit, the block diagonal matrix D and the multipliers used +C to obtain the factor U or L (see below for further details). +C +C LDA (input) INTEGER +C The leading dimension of the array A. LDA >= max(1,N). +C +C IPIV (output) INTEGER array, dimension (N) +C Details of the interchanges and the block structure of D. +C If IPIV(k) > 0, then rows and columns k and IPIV(k) were +C interchanged and D(k,k) is a 1-by-1 diagonal block. +C If UPLO = 'U' and IPIV(k) = IPIV(k-1) < 0, then rows and +C columns k-1 and -IPIV(k) were interchanged and D(k-1:k,k-1:k) +C is a 2-by-2 diagonal block. If UPLO = 'L' and IPIV(k) = +C IPIV(k+1) < 0, then rows and columns k+1 and -IPIV(k) were +C interchanged and D(k:k+1,k:k+1) is a 2-by-2 diagonal block. +C +C INFO (output) INTEGER +C = 0: successful exit +C < 0: if INFO = -k, the k-th argument had an illegal value +C > 0: if INFO = k, D(k,k) is exactly zero. The factorization +C has been completed, but the block diagonal matrix D is +C exactly singular, and division by zero will occur if it +C is used to solve a system of equations. +C +C Further Details +C =============== +C +C If UPLO = 'U', then A = U*D*U', where +C U = P(n)*U(n)* ... *P(k)U(k)* ..., +C i.e., U is a product of terms P(k)*U(k), where k decreases from n to +C 1 in steps of 1 or 2, and D is a block diagonal matrix with 1-by-1 +C and 2-by-2 diagonal blocks D(k). P(k) is a permutation matrix as +C defined by IPIV(k), and U(k) is a unit upper triangular matrix, such +C that if the diagonal block D(k) is of order s (s = 1 or 2), then +C +C ( I v 0 ) k-s +C U(k) = ( 0 I 0 ) s +C ( 0 0 I ) n-k +C k-s s n-k +C +C If s = 1, D(k) overwrites A(k,k), and v overwrites A(1:k-1,k). +C If s = 2, the upper triangle of D(k) overwrites A(k-1,k-1), A(k-1,k), +C and A(k,k), and v overwrites A(1:k-2,k-1:k). +C +C If UPLO = 'L', then A = L*D*L', where +C L = P(1)*L(1)* ... *P(k)*L(k)* ..., +C i.e., L is a product of terms P(k)*L(k), where k increases from 1 to +C n in steps of 1 or 2, and D is a block diagonal matrix with 1-by-1 +C and 2-by-2 diagonal blocks D(k). P(k) is a permutation matrix as +C defined by IPIV(k), and L(k) is a unit lower triangular matrix, such +C that if the diagonal block D(k) is of order s (s = 1 or 2), then +C +C ( I 0 0 ) k-1 +C L(k) = ( 0 I 0 ) s +C ( 0 v I ) n-k-s+1 +C k-1 s n-k-s+1 +C +C If s = 1, D(k) overwrites A(k,k), and v overwrites A(k+1:n,k). +C If s = 2, the lower triangle of D(k) overwrites A(k,k), A(k+1,k), +C and A(k+1,k+1), and v overwrites A(k+2:n,k:k+1). +C +C -- LAPACK routine (adapted for NAG Library) +C Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., +C Courant Institute, Argonne National Lab, and Rice University +C +C ===================================================================== +C +C .. Parameters .. + DOUBLE PRECISION ZERO, ONE + PARAMETER (ZERO=0.0D+0,ONE=1.0D+0) + DOUBLE PRECISION EIGHT, SEVTEN + PARAMETER (EIGHT=8.0D+0,SEVTEN=17.0D+0) + COMPLEX*16 CONE + PARAMETER (CONE=(1.0D+0,0.0D+0)) +C .. Scalar Arguments .. + INTEGER INFO, LDA, N + CHARACTER UPLO +C .. Array Arguments .. + COMPLEX*16 A(LDA,*) + INTEGER IPIV(*) +C .. Local Scalars .. + COMPLEX*16 C, D11, D12, D21, D22, R1, R2, S, T, T1, T2, Z + DOUBLE PRECISION ABSAKK, ALPHA, COLMAX, ROWMAX + INTEGER IMAX, J, JMAX, K, KK, KP, KSTEP + LOGICAL UPPER +C .. External Functions .. + INTEGER IZAMAX + EXTERNAL IZAMAX +C .. External Subroutines .. + EXTERNAL ZAXPY, ZSCAL, ZSWAP, F06AAZ, F07NRV, F07NRW, + * F07NRX +C .. Intrinsic Functions .. + INTRINSIC ABS, DIMAG, MAX, DBLE, SQRT +C .. Statement Functions .. + DOUBLE PRECISION CABS1 +C .. Statement Function definitions .. + CABS1(Z) = ABS(DBLE(Z)) + ABS(DIMAG(Z)) +C .. Executable Statements .. +C +C Test the input parameters. +C + INFO = 0 + UPPER = (UPLO.EQ.'U' .OR. UPLO.EQ.'u') + IF ( .NOT. UPPER .AND. .NOT. (UPLO.EQ.'L' .OR. UPLO.EQ.'l')) THEN + INFO = -1 + ELSE IF (N.LT.0) THEN + INFO = -2 + ELSE IF (LDA.LT.MAX(1,N)) THEN + INFO = -4 + END IF + IF (INFO.NE.0) THEN + CALL F06AAZ('F07NRZ/ZSYTF2',-INFO) + RETURN + END IF +C +C Initialize ALPHA for use in choosing pivot block size. +C + ALPHA = (ONE+SQRT(SEVTEN))/EIGHT +C + IF (UPPER) THEN +C +C Factorize A as U*D*U' using the upper triangle of A +C +C K is the main loop index, decreasing from N to 1 in steps of +C 1 or 2 +C + K = N + 20 CONTINUE +C +C If K < 1, exit from loop +C + IF (K.LT.1) GO TO 140 + KSTEP = 1 +C +C Determine rows and columns to be interchanged and whether +C a 1-by-1 or 2-by-2 pivot block will be used +C + ABSAKK = CABS1(A(K,K)) +C +C IMAX is the row-index of the largest off-diagonal element in +C column K, and COLMAX is its absolute value +C + IF (K.GT.1) THEN + IMAX = IZAMAX(K-1,A(1,K),1) + COLMAX = CABS1(A(IMAX,K)) + ELSE + COLMAX = ZERO + END IF +C + IF (MAX(ABSAKK,COLMAX).EQ.ZERO) THEN +C +C Column K is zero: set INFO and continue +C + IF (INFO.EQ.0) INFO = K + KP = K + ELSE + IF (ABSAKK.GE.ALPHA*COLMAX) THEN +C +C no interchange, use 1-by-1 pivot block +C + KP = K + ELSE +C +C JMAX is the column-index of the largest off-diagonal +C element in row IMAX, and ROWMAX is its absolute value +C + JMAX = IMAX + IZAMAX(K-IMAX,A(IMAX,IMAX+1),LDA) + ROWMAX = CABS1(A(IMAX,JMAX)) + IF (IMAX.GT.1) THEN + JMAX = IZAMAX(IMAX-1,A(1,IMAX),1) + ROWMAX = MAX(ROWMAX,CABS1(A(JMAX,IMAX))) + END IF +C + IF (ABSAKK.GE.ALPHA*COLMAX*(COLMAX/ROWMAX)) THEN +C +C no interchange, use 1-by-1 pivot block +C + KP = K + ELSE IF (CABS1(A(IMAX,IMAX)).GE.ALPHA*ROWMAX) THEN +C +C interchange rows and columns K and IMAX, use 1-by-1 +C pivot block +C + KP = IMAX + ELSE +C +C interchange rows and columns K-1 and IMAX, use 2-by-2 +C pivot block +C + KP = IMAX + KSTEP = 2 + END IF + END IF +C + KK = K - KSTEP + 1 + IF (KP.NE.KK) THEN +C +C Interchange rows and columns KK and KP in the leading +C submatrix A(1:k,1:k) +C + CALL ZSWAP(KP,A(1,KK),1,A(1,KP),1) + DO 40 J = KK, KP, -1 + T = A(J,KK) + A(J,KK) = A(KP,J) + A(KP,J) = T + 40 CONTINUE + IF (KSTEP.EQ.2) THEN + T = A(K-1,K) + A(K-1,K) = A(KP,K) + A(KP,K) = T + END IF + END IF +C +C Update the leading submatrix +C + IF (KSTEP.EQ.1) THEN +C +C 1-by-1 pivot block D(k): column k now holds +C +C W(k) = U(k)*D(k) +C +C where U(k) is the k-th column of U +C +C Perform a rank-1 update of A(1:k-1,1:k-1) as +C +C A := A - U(k)*D(k)*U(k)' = A - W(k)*1/D(k)*W(k)' +C + R1 = CONE/A(K,K) + CALL F07NRV(UPLO,K-1,-R1,A(1,K),1,A,LDA) +C +C Store U(k) in column k +C + CALL ZSCAL(K-1,R1,A(1,K),1) + ELSE +C +C 2-by-2 pivot block D(k): columns k and k-1 now hold +C +C ( W(k-1) W(k) ) = ( U(k-1) U(k) )*D(k) +C +C where U(k) and U(k-1) are the k-th and (k-1)-th columns +C of U +C +C Perform a rank-2 update of A(1:k-2,1:k-2) as +C +C A := A - ( U(k-1) U(k) )*D(k)*( U(k-1) U(k) )' +C = A - ( W(k-1) W(k) )*inv(D(k))*( W(k-1) W(k) )' +C +C Convert this to two rank-1 updates by using the eigen- +C decomposition of D(k) +C + CALL F07NRX(A(K-1,K-1),A(K-1,K),A(K,K),R1,R2,Z,C,S) +C + IF (CABS1(Z).NE.ZERO) THEN +C +C Apply two rank-1 updates to A(1:k-2,1:k-2) using the +C eigendecomposition of D(k). +C + R1 = CONE/R1 + R2 = CONE/R2 + CALL F07NRW(K-2,A(1,K-1),1,A(1,K),1,C,S) + CALL F07NRV(UPLO,K-2,-R1,A(1,K-1),1,A,LDA) + CALL F07NRV(UPLO,K-2,-R2,A(1,K),1,A,LDA) +C +C Store U(k) and U(k-1) in columns k and k-1 +C + CALL ZSCAL(K-2,R1,A(1,K-1),1) + CALL ZSCAL(K-2,R2,A(1,K),1) + CALL F07NRW(K-2,A(1,K-1),1,A(1,K),1,C,-S) + ELSE +C +C Apply a rank-2 update to A(1:k-2,1:k-2) using the +C explicit inverse of D(K) = [a b; b c], computed as +C (1/b) ( c/b -1 ) +C inv(D(k)) = -------------- ( ) +C 1 - (a/b)(c/b) ( -1 a/b ) +C + D12 = CONE/A(K-1,K) + D11 = A(K,K)*D12 + D22 = A(K-1,K-1)*D12 + Z = -D12/(CONE-D11*D22) + DO 60 J = K - 2, 1, -1 +C +C Compute inv(D(k)) * A(j,k-1:k)' +C + T1 = Z*(D11*A(J,K-1)-A(J,K)) + T2 = Z*(D22*A(J,K)-A(J,K-1)) +C +C Update column j of A +C + CALL ZAXPY(J,-T1,A(1,K-1),1,A(1,J),1) + CALL ZAXPY(J,-T2,A(1,K),1,A(1,J),1) +C +C Store the multipliers in columns k-1 and k +C + A(J,K-1) = T1 + A(J,K) = T2 + 60 CONTINUE + END IF + END IF + END IF +C +C Store details of the interchanges in IPIV +C + IF (KSTEP.EQ.1) THEN + IPIV(K) = KP + ELSE + IPIV(K) = -KP + IPIV(K-1) = -KP + END IF +C +C Decrease K and return to the start of the main loop +C + K = K - KSTEP + GO TO 20 +C + ELSE +C +C Factorize A as L*D*L' using the lower triangle of A +C +C K is the main loop index, increasing from 1 to N in steps of +C 1 or 2 +C + K = 1 + 80 CONTINUE +C +C If K > N, exit from loop +C + IF (K.GT.N) GO TO 140 + KSTEP = 1 +C +C Determine rows and columns to be interchanged and whether +C a 1-by-1 or 2-by-2 pivot block will be used +C + ABSAKK = CABS1(A(K,K)) +C +C IMAX is the row-index of the largest off-diagonal element in +C column K, and COLMAX is its absolute value +C + IF (K.LT.N) THEN + IMAX = K + IZAMAX(N-K,A(K+1,K),1) + COLMAX = CABS1(A(IMAX,K)) + ELSE + COLMAX = ZERO + END IF +C + IF (MAX(ABSAKK,COLMAX).EQ.ZERO) THEN +C +C Column K is zero: set INFO and continue +C + IF (INFO.EQ.0) INFO = K + KP = K + ELSE + IF (ABSAKK.GE.ALPHA*COLMAX) THEN +C +C no interchange, use 1-by-1 pivot block +C + KP = K + ELSE +C +C JMAX is the column-index of the largest off-diagonal +C element in row IMAX, and ROWMAX is its absolute value +C + JMAX = K - 1 + IZAMAX(IMAX-K,A(IMAX,K),LDA) + ROWMAX = CABS1(A(IMAX,JMAX)) + IF (IMAX.LT.N) THEN + JMAX = IMAX + IZAMAX(N-IMAX,A(IMAX+1,IMAX),1) + ROWMAX = MAX(ROWMAX,CABS1(A(JMAX,IMAX))) + END IF +C + IF (ABSAKK.GE.ALPHA*COLMAX*(COLMAX/ROWMAX)) THEN +C +C no interchange, use 1-by-1 pivot block +C + KP = K + ELSE IF (CABS1(A(IMAX,IMAX)).GE.ALPHA*ROWMAX) THEN +C +C interchange rows and columns K and IMAX, use 1-by-1 +C pivot block +C + KP = IMAX + ELSE +C +C interchange rows and columns K+1 and IMAX, use 2-by-2 +C pivot block +C + KP = IMAX + KSTEP = 2 + END IF + END IF +C + KK = K + KSTEP - 1 + IF (KP.NE.KK) THEN +C +C Interchange rows and columns KK and KP in the trailing +C submatrix A(k:n,k:n) +C + CALL ZSWAP(N-KP+1,A(KP,KK),1,A(KP,KP),1) + DO 100 J = KK, KP + T = A(J,KK) + A(J,KK) = A(KP,J) + A(KP,J) = T + 100 CONTINUE + IF (KSTEP.EQ.2) THEN + T = A(K+1,K) + A(K+1,K) = A(KP,K) + A(KP,K) = T + END IF + END IF +C +C Update the trailing submatrix +C + IF (KSTEP.EQ.1) THEN +C +C 1-by-1 pivot block D(k): column k now holds +C +C W(k) = L(k)*D(k) +C +C where L(k) is the k-th column of L +C + IF (K.LT.N) THEN +C +C Perform a rank-1 update of A(k+1:n,k+1:n) as +C +C A := A - L(k)*D(k)*L(k)' = A - W(k)*(1/D(k))*W(k)' +C + R1 = CONE/A(K,K) + CALL F07NRV(UPLO,N-K,-R1,A(K+1,K),1,A(K+1,K+1),LDA) +C +C Store L(k) in column K +C + CALL ZSCAL(N-K,R1,A(K+1,K),1) + END IF + ELSE +C +C 2-by-2 pivot block D(k): columns K and K+1 now hold +C +C ( W(k) W(k+1) ) = ( L(k) L(k+1) )*D(k) +C +C where L(k) and L(k+1) are the k-th and (k+1)-th columns +C of L +C + IF (K.LT.N-1) THEN +C +C Perform a rank-2 update of A(k+2:n,k+2:n) as +C +C A := A - ( L(k) L(k+1) )*D(k)*( L(k) L(k+1) )' +C = A - ( W(k) W(k+1) )*inv(D(k))*( W(k) W(k+1) )' +C +C Convert this to two rank-1 updates by using the eigen- +C decomposition of D(k) +C + CALL F07NRX(A(K,K),A(K+1,K),A(K+1,K+1),R1,R2,Z,C,S) +C + IF (CABS1(Z).NE.ZERO) THEN +C +C Apply two rank-1 updates to A(k+2:n,k+2:n) using +C the eigendecomposition of D(k) +C + R1 = CONE/R1 + R2 = CONE/R2 + CALL F07NRW(N-K-1,A(K+2,K),1,A(K+2,K+1),1,C,S) + CALL F07NRV(UPLO,N-K-1,-R1,A(K+2,K),1,A(K+2,K+2), + * LDA) + CALL F07NRV(UPLO,N-K-1,-R2,A(K+2,K+1),1,A(K+2,K+2), + * LDA) +C +C Store L(k) and L(k+1) in columns k and k+1 +C + CALL ZSCAL(N-K-1,R1,A(K+2,K),1) + CALL ZSCAL(N-K-1,R2,A(K+2,K+1),1) + CALL F07NRW(N-K-1,A(K+2,K),1,A(K+2,K+1),1,C,-S) + ELSE +C +C Apply a rank-2 update to A(k+2:n,k+2:n) using the +C explicit inverse of D(K) = [a b; b c], computed as +C (1/b) ( c/b -1 ) +C inv(D(k)) = -------------- ( ) +C 1 - (a/b)(c/b) ( -1 a/b ) +C + D21 = CONE/A(K+1,K) + D11 = A(K+1,K+1)*D21 + D22 = A(K,K)*D21 + Z = -D21/(CONE-D11*D22) + DO 120 J = K + 2, N +C +C Compute inv(D(k)) * A(j,k:k+1)' +C + T1 = Z*(D11*A(J,K)-A(J,K+1)) + T2 = Z*(D22*A(J,K+1)-A(J,K)) +C +C Update column j of A +C + CALL ZAXPY(N-J+1,-T1,A(J,K),1,A(J,J),1) + CALL ZAXPY(N-J+1,-T2,A(J,K+1),1,A(J,J),1) +C +C Store the multipliers in columns k and k+1 +C + A(J,K) = T1 + A(J,K+1) = T2 + 120 CONTINUE + END IF + END IF + END IF + END IF +C +C Store details of the interchanges in IPIV +C + IF (KSTEP.EQ.1) THEN + IPIV(K) = KP + ELSE + IPIV(K) = -KP + IPIV(K+1) = -KP + END IF +C +C Increase K and return to the start of the main loop +C + K = K + KSTEP + GO TO 80 +C + END IF +C + 140 CONTINUE + RETURN +C +C End of F07NRZ (ZSYTF2) +C + END +C + INTEGER FUNCTION F07ZAY(NAME) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C +C F07ZAY returns a unique positive integer code +C corresponding to a six-letter NAG routine name +C given in NAME. If NAME is not recognised, 0 +C is returned. +C +C .. Scalar Arguments .. + CHARACTER*6 NAME +C .. Local Scalars .. + INTEGER J, K + CHARACTER NAME4, NAME5 +C .. Executable Statements .. +C + IF (NAME(3:3).EQ.'7') THEN + NAME4 = NAME(4:4) + NAME5 = NAME(5:5) + ELSE + NAME4 = NAME(1:1) + NAME5 = NAME(2:2) + END IF +C + IF (NAME4.EQ.'A') THEN + J = 0 + ELSE IF (NAME4.EQ.'B') THEN + J = 1 + ELSE IF (NAME4.EQ.'F') THEN + J = 2 + ELSE IF (NAME4.EQ.'H') THEN + J = 3 + ELSE IF (NAME4.EQ.'M') THEN + J = 4 + ELSE IF (NAME4.EQ.'N') THEN + J = 5 + ELSE IF (NAME4.EQ.'T') THEN + J = 6 + ELSE + J = -1 + END IF +C + IF (NAME5.EQ.'D') THEN + K = 0 + ELSE IF (NAME5.EQ.'J') THEN + K = 1 + ELSE IF (NAME5.EQ.'R') THEN + K = 2 + ELSE IF (NAME5.EQ.'W') THEN + K = 3 + ELSE + K = -1 + END IF +C + IF (J.LT.0 .OR. K.LT.0) THEN + F07ZAY = 0 + ELSE +C F07ZAY is in the range 1-28. + F07ZAY = 1 + 4*J + K + END IF +C + RETURN +C + END +C + SUBROUTINE F07ZAZ(ISPEC,NAME,IVAL,RWFLAG) +* +* Mark 15 Release. NAG Copyright 1991 +* -- NAG version of LAPACK auxiliary routine ILAENV +* This version generated by program GENZAZ +* +* Purpose +* ======= +* +* F07ZAZ sets or returns problem-dependent +* parameters for the local environment. See +* ISPEC for a description of the parameters. +* +* The problem-dependent parameters are contained +* in the integer array IPARMS, and the value with +* index ISPEC is set or copied to IVAL. +* +* Arguments +* ========= +* +* ISPEC (input) INTEGER +* Specifies the parameter to be set or +* returned by F07ZAZ. +* = 1: the optimal blocksize; if this value +* is 1, an unblocked algorithm will give +* the best performance. +* = 2: the minimum block size for which the +* block routine should be used; if the +* usable block size is less than this +* value, an unblocked routine should be +* used. +* = 3: the crossover point (for N less than +* this value, an unblocked routine should +* be used) +* +* NAME (input) CHARACTER*(*) +* The name of the calling subroutine. +* +* IVAL (input/output) INTEGER +* the value of the parameter set or returned. +* +* FLAG (input) INTEGER +* = 0: F07ZAZ returns in IVAL the value of +* the parameter specified by ISPEC. +* = 1: F07ZAZ sets the parameter specified +* by ISPEC to the value in IVAL. +* +* ============================================== +* +* .. Parameters .. + INTEGER NSPECS, NCODES, MAXIC + PARAMETER (NSPECS=3,NCODES= 17,MAXIC= 28) +* .. Scalar Arguments .. + INTEGER ISPEC, IVAL, RWFLAG + CHARACTER*(*) NAME +* .. Local Scalars .. + INTEGER ICODE +* .. Local Arrays .. + INTEGER IPARMS(NSPECS,NCODES), + + POINT(MAXIC) +* .. External Functions .. + INTEGER F07ZAY + EXTERNAL F07ZAY +* .. Save statement .. + SAVE IPARMS, POINT +* .. Data statements .. + DATA IPARMS(1, 1), IPARMS(2, 1), IPARMS(3, 1) + + / 16, 0, 0 / + DATA IPARMS(1, 2), IPARMS(2, 2), IPARMS(3, 2) + + / 16, 5, 0 / + DATA IPARMS(1, 3), IPARMS(2, 3), IPARMS(3, 3) + + / 48, 0, 0 / + DATA IPARMS(1, 4), IPARMS(2, 4), IPARMS(3, 4) + + / 1, 1, 0 / + DATA IPARMS(1, 5), IPARMS(2, 5), IPARMS(3, 5) + + / 16, 0, 0 / + DATA IPARMS(1, 6), IPARMS(2, 6), IPARMS(3, 6) + + / 1, 0, 0 / + DATA IPARMS(1, 7), IPARMS(2, 7), IPARMS(3, 7) + + / 16, 0, 0 / + DATA IPARMS(1, 8), IPARMS(2, 8), IPARMS(3, 8) + + / 16, 0, 0 / + DATA IPARMS(1, 9), IPARMS(2, 9), IPARMS(3, 9) + + / 1, 0, 0 / + DATA IPARMS(1, 10), IPARMS(2, 10), IPARMS(3, 10) + + / 16, 0, 0 / + DATA IPARMS(1, 11), IPARMS(2, 11), IPARMS(3, 11) + + / 1, 0, 0 / + DATA IPARMS(1, 12), IPARMS(2, 12), IPARMS(3, 12) + + / 1, 0, 0 / + DATA IPARMS(1, 13), IPARMS(2, 13), IPARMS(3, 13) + + / 24, 7, 0 / + DATA IPARMS(1, 14), IPARMS(2, 14), IPARMS(3, 14) + + / 1, 1, 0 / + DATA IPARMS(1, 15), IPARMS(2, 15), IPARMS(3, 15) + + / 1, 1, 0 / + DATA IPARMS(1, 16), IPARMS(2, 16), IPARMS(3, 16) + + / 1, 0, 0 / + DATA IPARMS(1, 17), IPARMS(2, 17), IPARMS(3, 17) + + / 16, 0, 0 / + DATA POINT / + + 1, 2, 3, 4, 5, 0, 6, 0, + + 7, 8, 9, 10, 11, 0, 12, 0, + + 13, 0, 14, 0, 0, 0, 15, 0, + + 0, 16, 0, 17 + + / +* .. Executable Statements .. +* +* Convert the NAG name to an integer code. + ICODE = F07ZAY(NAME) +* + IF (ISPEC.LT.1 .OR. ISPEC.GT.NSPECS) THEN +* Invalid value for ISPEC + IVAL = -1 + ELSE IF (ICODE.EQ.0) THEN +* Invalid value for NAME + IVAL = -2 + ELSE IF (POINT(ICODE).EQ.0) THEN +* Invalid value for NAME + IVAL = -2 + ELSE IF (RWFLAG.EQ.0) THEN +* Read the value of a parameter + IVAL = IPARMS(ISPEC,POINT(ICODE)) + ELSE +* Set the value of a parameter + IPARMS(ISPEC,POINT(ICODE)) = IVAL + END IF +* + RETURN +* +* End of F07ZAZ +* + END +C + SUBROUTINE P01ABZ +C MARK 11.5(F77) RELEASE. NAG COPYRIGHT 1986. +C +C Terminates execution when a hard failure occurs. +C +C ******************** IMPLEMENTATION NOTE ******************** +C The following STOP statement may be replaced by a call to an +C implementation-dependent routine to display a message and/or +C to abort the program. +C ************************************************************* +C .. Executable Statements .. + STOP + END +C + INTEGER FUNCTION P01ACF(IFAIL,IERROR,SRNAME,VARBNM,NREC,REC) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C +C P01ACF is the error-handling routine for the F06 AND F07 +C Chapters of the NAG Fortran Library. It is a slightly modified +C version of P01ABF. +C +C P01ACF either returns the value of IERROR through the routine +C name (soft failure), or terminates execution of the program +C (hard failure). Diagnostic messages may be output. +C +C If IERROR = 0 (successful exit from the calling routine), +C the value 0 is returned through the routine name, and no +C message is output +C +C If IERROR is non-zero (abnormal exit from the calling routine), +C the action taken depends on the value of IFAIL. +C +C IFAIL = 1: soft failure, silent exit (i.e. no messages are +C output) +C IFAIL = -1: soft failure, noisy exit (i.e. messages are output) +C IFAIL =-13: soft failure, noisy exit but standard messages from +C P01ACF are suppressed +C IFAIL = 0: hard failure, noisy exit +C +C For compatibility with certain routines included before Mark 12 +C P01ACF also allows an alternative specification of IFAIL in which +C it is regarded as a decimal integer with least significant digits +C cba. Then +C +C a = 0: hard failure a = 1: soft failure +C b = 0: silent exit b = 1: noisy exit +C +C except that hard failure now always implies a noisy exit. +C +C S.Hammarling, M.P.Hooper and J.J.du Croz, NAG Central Office. +C +C .. Scalar Arguments .. + INTEGER IERROR, IFAIL, NREC + CHARACTER*(*) SRNAME, VARBNM +C .. Array Arguments .. + CHARACTER*(*) REC(*) +C .. Local Scalars .. + INTEGER I, NERR, VARLEN + CHARACTER*72 MESS +C .. External Subroutines .. + EXTERNAL P01ABZ, X04AAF, X04BAF +C .. Intrinsic Functions .. + INTRINSIC ABS, LEN, MOD +C .. Executable Statements .. + IF (IERROR.NE.0) THEN + VARLEN = 0 + DO 20 I = LEN(VARBNM), 1, -1 + IF (VARBNM(I:I).NE.' ') THEN + VARLEN = I + GO TO 40 + END IF + 20 CONTINUE + 40 CONTINUE +C Abnormal exit from calling routine + IF (IFAIL.EQ.-1 .OR. IFAIL.EQ.0 .OR. IFAIL.EQ.-13 .OR. + * (IFAIL.GT.0 .AND. MOD(IFAIL/10,10).NE.0)) THEN +C Noisy exit + CALL X04AAF(0,NERR) + DO 60 I = 1, NREC + CALL X04BAF(NERR,REC(I)) + 60 CONTINUE + IF (IFAIL.NE.-13) THEN + IF (VARLEN.NE.0) THEN + WRITE (MESS,FMT=99999) SRNAME, VARBNM(1:VARLEN), + * IERROR + ELSE + WRITE (MESS,FMT=99998) SRNAME + END IF + CALL X04BAF(NERR,MESS) + IF (ABS(MOD(IFAIL,10)).NE.1) THEN +C Hard failure + CALL X04BAF(NERR, + * ' ** NAG hard failure - execution terminated' + * ) + CALL P01ABZ + ELSE +C Soft failure + CALL X04BAF(NERR, + * ' ** NAG soft failure - control returned') + END IF + END IF + END IF + END IF + P01ACF = IERROR + RETURN +C +99999 FORMAT (' ** ABNORMAL EXIT from NAG Library routine ',A,': ',A, + * ' =',I6) +99998 FORMAT (' ** ABNORMAL EXIT from NAG Library routine ',A) + END +C + SUBROUTINE X04AAF(I,NERR) +C MARK 7 RELEASE. NAG COPYRIGHT 1978 +C MARK 7C REVISED IER-190 (MAY 1979) +C MARK 11.5(F77) REVISED. (SEPT 1985.) +C MARK 14 REVISED. IER-829 (DEC 1989). +C IF I = 0, SETS NERR TO CURRENT ERROR MESSAGE UNIT NUMBER +C (STORED IN NERR1). +C IF I = 1, CHANGES CURRENT ERROR MESSAGE UNIT NUMBER TO +C VALUE SPECIFIED BY NERR. +C +C .. Scalar Arguments .. + INTEGER I, NERR +C .. Local Scalars .. + INTEGER NERR1 +C .. Save statement .. + SAVE NERR1 +C .. Data statements .. + DATA NERR1/6/ +C .. Executable Statements .. + IF (I.EQ.0) NERR = NERR1 + IF (I.EQ.1) NERR1 = NERR + RETURN + END + SUBROUTINE X04BAF(NOUT,REC) +C MARK 11.5(F77) RELEASE. NAG COPYRIGHT 1986. +C +C X04BAF writes the contents of REC to the unit defined by NOUT. +C +C Trailing blanks are not output, except that if REC is entirely +C blank, a single blank character is output. +C If NOUT.lt.0, i.e. if NOUT is not a valid Fortran unit identifier, +C then no output occurs. +C +C .. Scalar Arguments .. + INTEGER NOUT + CHARACTER*(*) REC +C .. Local Scalars .. + INTEGER I +C .. Intrinsic Functions .. + INTRINSIC LEN +C .. Executable Statements .. + IF (NOUT.GE.0) THEN +C Remove trailing blanks + DO 20 I = LEN(REC), 2, -1 + IF (REC(I:I).NE.' ') GO TO 40 + 20 CONTINUE +C Write record to external file + 40 WRITE (NOUT,FMT=99999) REC(1:I) + END IF + RETURN +C +99999 FORMAT (A) + END +C + SUBROUTINE ZAXPY( N, ALPHA, X, INCX, Y, INCY ) +C MARK 12 RELEASE. NAG COPYRIGHT 1986. +C .. Entry Points .. +C ENTRY ZAXPY ( N, ALPHA, X, INCX, Y, INCY ) +C .. Scalar Arguments .. + COMPLEX*16 ALPHA + INTEGER INCX, INCY, N +C .. Array Arguments .. + COMPLEX*16 X( * ), Y( * ) +C .. +C +C F06GCF performs the operation +C +C y := alpha*x + y +C +C +C Nag Fortran 77 version of the Blas routine ZAXPY. +C Nag Fortran 77 O( n ) basic linear algebra routine. +C +C -- written on 28-April-1983. +C Sven Hammarling, Nag Central Office. +C +C +C .. Parameters .. + COMPLEX*16 ZERO + PARAMETER ( ZERO = ( 0.0D+0, 0.0D+0 ) ) +C .. Local Scalars .. + INTEGER I, IX, IY +C .. Executable Statements .. + IF( N.GT.0 )THEN + IF( ALPHA.NE.ZERO )THEN + IF( ( INCX.EQ.INCY ).AND.( INCX.GT.0 ) )THEN + DO 10, IX = 1, 1 + ( N - 1 )*INCX, INCX + Y( IX ) = ALPHA*X( IX ) + Y( IX ) + 10 CONTINUE + ELSE + IF( INCY.GE.0 )THEN + IY = 1 + ELSE + IY = 1 - ( N - 1 )*INCY + END IF + IF( INCX.GT.0 )THEN + DO 20, IX = 1, 1 + ( N - 1 )*INCX, INCX + Y( IY ) = ALPHA*X( IX ) + Y( IY ) + IY = IY + INCY + 20 CONTINUE + ELSE + IX = 1 - ( N - 1 )*INCX + DO 30, I = 1, N + Y( IY ) = ALPHA*X( IX ) + Y( IY ) + IX = IX + INCX + IY = IY + INCY + 30 CONTINUE + END IF + END IF + END IF + END IF +C + RETURN +C +C End of F06GCF. ( ZAXPY ) +C + END +C + SUBROUTINE ZCOPY( N, X, INCX, Y, INCY ) +C MARK 12 RELEASE. NAG COPYRIGHT 1986. +C .. Entry Points .. +C ENTRY ZCOPY ( N, X, INCX, Y, INCY ) +C .. Scalar Arguments .. + INTEGER INCX, INCY, N +C .. Array Arguments .. + COMPLEX*16 X( * ), Y( * ) +C .. +C +C F06GFF performs the operation +C +C y := x +C +C +C Nag Fortran 77 version of the Blas routine ZCOPY. +C Nag Fortran 77 O( n ) basic linear algebra routine. +C +C -- Written on 26-November-1982. +C Sven Hammarling, Nag Central Office. +C +C +C .. Local Scalars .. + INTEGER I, IX, IY +C .. +C .. Executable Statements .. + IF( N.GT.0 )THEN + IF( ( INCX.EQ.INCY ).AND.( INCY.GT.0 ) )THEN + DO 10, IY = 1, 1 + ( N - 1 )*INCY, INCY + Y( IY ) = X( IY ) + 10 CONTINUE + ELSE + IF( INCX.GE.0 )THEN + IX = 1 + ELSE + IX = 1 - ( N - 1 )*INCX + END IF + IF( INCY.GT.0 )THEN + DO 20, IY = 1, 1 + ( N - 1 )*INCY, INCY + Y( IY ) = X( IX ) + IX = IX + INCX + 20 CONTINUE + ELSE + IY = 1 - ( N - 1 )*INCY + DO 30, I = 1, N + Y( IY ) = X( IX ) + IY = IY + INCY + IX = IX + INCX + 30 CONTINUE + END IF + END IF + END IF +C + RETURN +C +C End of F06GFF. ( ZCOPY ) +C + END +C + SUBROUTINE ZGEMM(TRANSA,TRANSB,M,N,K,ALPHA,A,LDA,B,LDB,BETA,C, + * LDC) +C MARK 14 RELEASE. NAG COPYRIGHT 1989. +C +C Purpose +C ======= +C +C ZGEMM performs one of the matrix-matrix operations +C +C C := alpha*op( A )*op( B ) + beta*C, +C +C where op( X ) is one of +C +C op( X ) = X or op( X ) = X' or op( X ) = conjg( X' ), +C +C alpha and beta are scalars, and A, B and C are matrices, with op( A ) +C an m by k matrix, op( B ) a k by n matrix and C an m by n matrix. +C +C Parameters +C ========== +C +C TRANSA - CHARACTER*1. +C On entry, TRANSA specifies the form of op( A ) to be used in +C the matrix multiplication as follows: +C +C TRANSA = 'N' or 'n', op( A ) = A. +C +C TRANSA = 'T' or 't', op( A ) = A'. +C +C TRANSA = 'C' or 'c', op( A ) = conjg( A' ). +C +C Unchanged on exit. +C +C TRANSB - CHARACTER*1. +C On entry, TRANSB specifies the form of op( B ) to be used in +C the matrix multiplication as follows: +C +C TRANSB = 'N' or 'n', op( B ) = B. +C +C TRANSB = 'T' or 't', op( B ) = B'. +C +C TRANSB = 'C' or 'c', op( B ) = conjg( B' ). +C +C Unchanged on exit. +C +C M - INTEGER. +C On entry, M specifies the number of rows of the matrix +C op( A ) and of the matrix C. M must be at least zero. +C Unchanged on exit. +C +C N - INTEGER. +C On entry, N specifies the number of columns of the matrix +C op( B ) and the number of columns of the matrix C. N must be +C at least zero. +C Unchanged on exit. +C +C K - INTEGER. +C On entry, K specifies the number of columns of the matrix +C op( A ) and the number of rows of the matrix op( B ). K must +C be at least zero. +C Unchanged on exit. +C +C ALPHA - COMPLEX . +C On entry, ALPHA specifies the scalar alpha. +C Unchanged on exit. +C +C A - COMPLEX array of DIMENSION ( LDA, ka ), where ka is +C k when TRANSA = 'N' or 'n', and is m otherwise. +C Before entry with TRANSA = 'N' or 'n', the leading m by k +C part of the array A must contain the matrix A, otherwise +C the leading k by m part of the array A must contain the +C matrix A. +C Unchanged on exit. +C +C LDA - INTEGER. +C On entry, LDA specifies the first dimension of A as declared +C in the calling (sub) program. When TRANSA = 'N' or 'n' then +C LDA must be at least max( 1, m ), otherwise LDA must be at +C least max( 1, k ). +C Unchanged on exit. +C +C B - COMPLEX array of DIMENSION ( LDB, kb ), where kb is +C n when TRANSB = 'N' or 'n', and is k otherwise. +C Before entry with TRANSB = 'N' or 'n', the leading k by n +C part of the array B must contain the matrix B, otherwise +C the leading n by k part of the array B must contain the +C matrix B. +C Unchanged on exit. +C +C LDB - INTEGER. +C On entry, LDB specifies the first dimension of B as declared +C in the calling (sub) program. When TRANSB = 'N' or 'n' then +C LDB must be at least max( 1, k ), otherwise LDB must be at +C least max( 1, n ). +C Unchanged on exit. +C +C BETA - COMPLEX . +C On entry, BETA specifies the scalar beta. When BETA is +C supplied as zero then C need not be set on input. +C Unchanged on exit. +C +C C - COMPLEX array of DIMENSION ( LDC, n ). +C Before entry, the leading m by n part of the array C must +C contain the matrix C, except when beta is zero, in which +C case C need not be set on entry. +C On exit, the array C is overwritten by the m by n matrix +C ( alpha*op( A )*op( B ) + beta*C ). +C +C LDC - INTEGER. +C On entry, LDC specifies the first dimension of C as declared +C in the calling (sub) program. LDC must be at least +C max( 1, m ). +C Unchanged on exit. +C +C +C Level 3 Blas routine. +C +C -- Written on 8-February-1989. +C Jack Dongarra, Argonne National Laboratory. +C Iain Duff, AERE Harwell. +C Jeremy Du Croz, Numerical Algorithms Group Ltd. +C Sven Hammarling, Numerical Algorithms Group Ltd. +C +C +C .. Entry Points .. +C ENTRY ZGEMM(TRANSA,TRANSB,M,N,K,ALPHA,A,LDA,B,LDB, +C * BETA,C,LDC) +C .. Parameters .. + COMPLEX*16 ONE + PARAMETER (ONE=(1.0D+0,0.0D+0)) + COMPLEX*16 ZERO + PARAMETER (ZERO=(0.0D+0,0.0D+0)) +C .. Scalar Arguments .. + COMPLEX*16 ALPHA, BETA + INTEGER K, LDA, LDB, LDC, M, N + CHARACTER*1 TRANSA, TRANSB +C .. Array Arguments .. + COMPLEX*16 A(LDA,*), B(LDB,*), C(LDC,*) +C .. Local Scalars .. + COMPLEX*16 TEMP + INTEGER I, INFO, J, L, NCOLA, NROWA, NROWB + LOGICAL CONJA, CONJB, NOTA, NOTB +C .. External Subroutines .. + EXTERNAL F06AAZ +C .. Intrinsic Functions .. + INTRINSIC DCONJG, MAX +C .. Executable Statements .. +C +C Set NOTA and NOTB as true if A and B respectively are not +C conjugated or transposed, set CONJA and CONJB as true if A and +C B respectively are to be transposed but not conjugated and set +C NROWA, NCOLA and NROWB as the number of rows and columns of A +C and the number of rows of B respectively. +C + NOTA = (TRANSA.EQ.'N' .OR. TRANSA.EQ.'n') + NOTB = (TRANSB.EQ.'N' .OR. TRANSB.EQ.'n') + CONJA = (TRANSA.EQ.'C' .OR. TRANSA.EQ.'c') + CONJB = (TRANSB.EQ.'C' .OR. TRANSB.EQ.'c') + IF (NOTA) THEN + NROWA = M + NCOLA = K + ELSE + NROWA = K + NCOLA = M + END IF + IF (NOTB) THEN + NROWB = K + ELSE + NROWB = N + END IF +C +C Test the input parameters. +C + INFO = 0 + IF (( .NOT. NOTA) .AND. ( .NOT. CONJA) + * .AND. ( .NOT. (TRANSA.EQ.'T' .OR. TRANSA.EQ.'t'))) THEN + INFO = 1 + ELSE IF (( .NOT. NOTB) .AND. ( .NOT. CONJB) + * .AND. ( .NOT. (TRANSB.EQ.'T' .OR. TRANSB.EQ.'t'))) THEN + INFO = 2 + ELSE IF (M.LT.0) THEN + INFO = 3 + ELSE IF (N.LT.0) THEN + INFO = 4 + ELSE IF (K.LT.0) THEN + INFO = 5 + ELSE IF (LDA.LT.MAX(1,NROWA)) THEN + INFO = 8 + ELSE IF (LDB.LT.MAX(1,NROWB)) THEN + INFO = 10 + ELSE IF (LDC.LT.MAX(1,M)) THEN + INFO = 13 + END IF + IF (INFO.NE.0) THEN + CALL F06AAZ('F06ZAF/ZGEMM ',INFO) + RETURN + END IF +C +C Quick return if possible. +C + IF ((M.EQ.0) .OR. (N.EQ.0) .OR. (((ALPHA.EQ.ZERO) .OR. (K.EQ.0)) + * .AND. (BETA.EQ.ONE))) RETURN +C +C And when alpha.eq.zero. +C + IF (ALPHA.EQ.ZERO) THEN + IF (BETA.EQ.ZERO) THEN + DO 40 J = 1, N + DO 20 I = 1, M + C(I,J) = ZERO + 20 CONTINUE + 40 CONTINUE + ELSE + DO 80 J = 1, N + DO 60 I = 1, M + C(I,J) = BETA*C(I,J) + 60 CONTINUE + 80 CONTINUE + END IF + RETURN + END IF +C +C Start the operations. +C + IF (NOTB) THEN + IF (NOTA) THEN +C +C Form C := alpha*A*B + beta*C. +C + DO 180 J = 1, N + IF (BETA.EQ.ZERO) THEN + DO 100 I = 1, M + C(I,J) = ZERO + 100 CONTINUE + ELSE IF (BETA.NE.ONE) THEN + DO 120 I = 1, M + C(I,J) = BETA*C(I,J) + 120 CONTINUE + END IF + DO 160 L = 1, K + IF (B(L,J).NE.ZERO) THEN + TEMP = ALPHA*B(L,J) + DO 140 I = 1, M + C(I,J) = C(I,J) + TEMP*A(I,L) + 140 CONTINUE + END IF + 160 CONTINUE + 180 CONTINUE + ELSE IF (CONJA) THEN +C +C Form C := alpha*conjg( A' )*B + beta*C. +C + DO 240 J = 1, N + DO 220 I = 1, M + TEMP = ZERO + DO 200 L = 1, K + TEMP = TEMP + DCONJG(A(L,I))*B(L,J) + 200 CONTINUE + IF (BETA.EQ.ZERO) THEN + C(I,J) = ALPHA*TEMP + ELSE + C(I,J) = ALPHA*TEMP + BETA*C(I,J) + END IF + 220 CONTINUE + 240 CONTINUE + ELSE +C +C Form C := alpha*A'*B + beta*C +C + DO 300 J = 1, N + DO 280 I = 1, M + TEMP = ZERO + DO 260 L = 1, K + TEMP = TEMP + A(L,I)*B(L,J) + 260 CONTINUE + IF (BETA.EQ.ZERO) THEN + C(I,J) = ALPHA*TEMP + ELSE + C(I,J) = ALPHA*TEMP + BETA*C(I,J) + END IF + 280 CONTINUE + 300 CONTINUE + END IF + ELSE IF (NOTA) THEN + IF (CONJB) THEN +C +C Form C := alpha*A*conjg( B' ) + beta*C. +C + DO 400 J = 1, N + IF (BETA.EQ.ZERO) THEN + DO 320 I = 1, M + C(I,J) = ZERO + 320 CONTINUE + ELSE IF (BETA.NE.ONE) THEN + DO 340 I = 1, M + C(I,J) = BETA*C(I,J) + 340 CONTINUE + END IF + DO 380 L = 1, K + IF (B(J,L).NE.ZERO) THEN + TEMP = ALPHA*DCONJG(B(J,L)) + DO 360 I = 1, M + C(I,J) = C(I,J) + TEMP*A(I,L) + 360 CONTINUE + END IF + 380 CONTINUE + 400 CONTINUE + ELSE +C +C Form C := alpha*A*B' + beta*C +C + DO 500 J = 1, N + IF (BETA.EQ.ZERO) THEN + DO 420 I = 1, M + C(I,J) = ZERO + 420 CONTINUE + ELSE IF (BETA.NE.ONE) THEN + DO 440 I = 1, M + C(I,J) = BETA*C(I,J) + 440 CONTINUE + END IF + DO 480 L = 1, K + IF (B(J,L).NE.ZERO) THEN + TEMP = ALPHA*B(J,L) + DO 460 I = 1, M + C(I,J) = C(I,J) + TEMP*A(I,L) + 460 CONTINUE + END IF + 480 CONTINUE + 500 CONTINUE + END IF + ELSE IF (CONJA) THEN + IF (CONJB) THEN +C +C Form C := alpha*conjg( A' )*conjg( B' ) + beta*C. +C + DO 560 J = 1, N + DO 540 I = 1, M + TEMP = ZERO + DO 520 L = 1, K + TEMP = TEMP + DCONJG(A(L,I))*DCONJG(B(J,L)) + 520 CONTINUE + IF (BETA.EQ.ZERO) THEN + C(I,J) = ALPHA*TEMP + ELSE + C(I,J) = ALPHA*TEMP + BETA*C(I,J) + END IF + 540 CONTINUE + 560 CONTINUE + ELSE +C +C Form C := alpha*conjg( A' )*B' + beta*C +C + DO 620 J = 1, N + DO 600 I = 1, M + TEMP = ZERO + DO 580 L = 1, K + TEMP = TEMP + DCONJG(A(L,I))*B(J,L) + 580 CONTINUE + IF (BETA.EQ.ZERO) THEN + C(I,J) = ALPHA*TEMP + ELSE + C(I,J) = ALPHA*TEMP + BETA*C(I,J) + END IF + 600 CONTINUE + 620 CONTINUE + END IF + ELSE + IF (CONJB) THEN +C +C Form C := alpha*A'*conjg( B' ) + beta*C +C + DO 680 J = 1, N + DO 660 I = 1, M + TEMP = ZERO + DO 640 L = 1, K + TEMP = TEMP + A(L,I)*DCONJG(B(J,L)) + 640 CONTINUE + IF (BETA.EQ.ZERO) THEN + C(I,J) = ALPHA*TEMP + ELSE + C(I,J) = ALPHA*TEMP + BETA*C(I,J) + END IF + 660 CONTINUE + 680 CONTINUE + ELSE +C +C Form C := alpha*A'*B' + beta*C +C + DO 740 J = 1, N + DO 720 I = 1, M + TEMP = ZERO + DO 700 L = 1, K + TEMP = TEMP + A(L,I)*B(J,L) + 700 CONTINUE + IF (BETA.EQ.ZERO) THEN + C(I,J) = ALPHA*TEMP + ELSE + C(I,J) = ALPHA*TEMP + BETA*C(I,J) + END IF + 720 CONTINUE + 740 CONTINUE + END IF + END IF +C + RETURN +C +C End of F06ZAF (ZGEMM ). +C + END +C + SUBROUTINE ZGEMV( TRANS, M, N, ALPHA, A, LDA, X, INCX, + $ BETA, Y, INCY ) +C MARK 13 RE-ISSUE. NAG COPYRIGHT 1988. +C .. Entry Points .. +C ENTRY ZGEMV ( TRANS, M, N, ALPHA, A, LDA, X, INCX, +C $ BETA, Y, INCY ) +C .. Scalar Arguments .. + COMPLEX*16 ALPHA, BETA + INTEGER INCX, INCY, LDA, M, N + CHARACTER*1 TRANS +C .. Array Arguments .. + COMPLEX*16 A( LDA, * ), X( * ), Y( * ) +C .. +C +C Purpose +C ======= +C +C ZGEMV performs one of the matrix-vector operations +C +C y := alpha*A*x + beta*y, or y := alpha*A'*x + beta*y, or +C +C y := alpha*conjg( A' )*x + beta*y, +C +C where alpha and beta are scalars, x and y are vectors and A is an +C m by n matrix. +C +C Parameters +C ========== +C +C TRANS - CHARACTER*1. +C On entry, TRANS specifies the operation to be performed as +C follows: +C +C TRANS = 'N' or 'n' y := alpha*A*x + beta*y. +C +C TRANS = 'T' or 't' y := alpha*A'*x + beta*y. +C +C TRANS = 'C' or 'c' y := alpha*conjg( A' )*x + beta*y. +C +C Unchanged on exit. +C +C M - INTEGER. +C On entry, M specifies the number of rows of the matrix A. +C M must be at least zero. +C Unchanged on exit. +C +C N - INTEGER. +C On entry, N specifies the number of columns of the matrix A. +C N must be at least zero. +C Unchanged on exit. +C +C ALPHA - COMPLEX*16 . +C On entry, ALPHA specifies the scalar alpha. +C Unchanged on exit. +C +C A - COMPLEX*16 array of DIMENSION ( LDA, n ). +C Before entry, the leading m by n part of the array A must +C contain the matrix of coefficients. +C Unchanged on exit. +C +C LDA - INTEGER. +C On entry, LDA specifies the first dimension of A as declared +C in the calling (sub) program. LDA must be at least +C max( 1, m ). +C Unchanged on exit. +C +C X - COMPLEX*16 array of DIMENSION at least +C ( 1 + ( n - 1 )*abs( INCX ) ) when TRANS = 'N' or 'n' +C and at least +C ( 1 + ( m - 1 )*abs( INCX ) ) otherwise. +C Before entry, the incremented array X must contain the +C vector x. +C Unchanged on exit. +C +C INCX - INTEGER. +C On entry, INCX specifies the increment for the elements of +C X. INCX must not be zero. +C Unchanged on exit. +C +C BETA - COMPLEX*16 . +C On entry, BETA specifies the scalar beta. When BETA is +C supplied as zero then Y need not be set on input. +C Unchanged on exit. +C +C Y - COMPLEX*16 array of DIMENSION at least +C ( 1 + ( m - 1 )*abs( INCY ) ) when TRANS = 'N' or 'n' +C and at least +C ( 1 + ( n - 1 )*abs( INCY ) ) otherwise. +C Before entry with BETA non-zero, the incremented array Y +C must contain the vector y. On exit, Y is overwritten by the +C updated vector y. +C +C INCY - INTEGER. +C On entry, INCY specifies the increment for the elements of +C Y. INCY must not be zero. +C Unchanged on exit. +C +C +C Level 2 Blas routine. +C +C -- Written on 22-October-1986. +C Jack Dongarra, Argonne National Lab. +C Jeremy Du Croz, Nag Central Office. +C Sven Hammarling, Nag Central Office. +C Richard Hanson, Sandia National Labs. +C +C +C .. Parameters .. + COMPLEX*16 ONE + PARAMETER ( ONE = ( 1.0D+0, 0.0D+0 ) ) + COMPLEX*16 ZERO + PARAMETER ( ZERO = ( 0.0D+0, 0.0D+0 ) ) +C .. Local Scalars .. + COMPLEX*16 TEMP + INTEGER I, INFO, IX, IY, J, JX, JY, KX, KY, LENX, LENY + LOGICAL NOCONJ +C .. External Subroutines .. + EXTERNAL F06AAZ +C .. Intrinsic Functions .. + INTRINSIC DCONJG, MAX +C .. +C .. Executable Statements .. +C +C Test the input parameters. +C + INFO = 0 + IF ( .NOT.(TRANS.EQ.'N' .OR. TRANS.EQ.'n').AND. + $ .NOT.(TRANS.EQ.'T' .OR. TRANS.EQ.'t').AND. + $ .NOT.(TRANS.EQ.'C' .OR. TRANS.EQ.'c') )THEN + INFO = 1 + ELSE IF( M.LT.0 )THEN + INFO = 2 + ELSE IF( N.LT.0 )THEN + INFO = 3 + ELSE IF( LDA.LT.MAX( 1, M ) )THEN + INFO = 6 + ELSE IF( INCX.EQ.0 )THEN + INFO = 8 + ELSE IF( INCY.EQ.0 )THEN + INFO = 11 + END IF + IF( INFO.NE.0 )THEN + CALL F06AAZ( 'F06SAF/ZGEMV ', INFO ) + RETURN + END IF +C +C Quick return if possible. +C + IF( ( M.EQ.0 ).OR.( N.EQ.0 ).OR. + $ ( ( ALPHA.EQ.ZERO ).AND.( BETA.EQ.ONE ) ) ) + $ RETURN +C + NOCONJ = (TRANS.EQ.'T' .OR. TRANS.EQ.'t') +C +C Set LENX and LENY, the lengths of the vectors x and y, and set +C up the start points in X and Y. +C + IF( (TRANS.EQ.'N' .OR. TRANS.EQ.'n') )THEN + LENX = N + LENY = M + ELSE + LENX = M + LENY = N + END IF + IF( INCX.GT.0 )THEN + KX = 1 + ELSE + KX = 1 - ( LENX - 1 )*INCX + END IF + IF( INCY.GT.0 )THEN + KY = 1 + ELSE + KY = 1 - ( LENY - 1 )*INCY + END IF +C +C Start the operations. In this version the elements of A are +C accessed sequentially with one pass through A. +C +C First form y := beta*y. +C + IF( BETA.NE.ONE )THEN + IF( INCY.EQ.1 )THEN + IF( BETA.EQ.ZERO )THEN + DO 10, I = 1, LENY + Y( I ) = ZERO + 10 CONTINUE + ELSE + DO 20, I = 1, LENY + Y( I ) = BETA*Y( I ) + 20 CONTINUE + END IF + ELSE + IY = KY + IF( BETA.EQ.ZERO )THEN + DO 30, I = 1, LENY + Y( IY ) = ZERO + IY = IY + INCY + 30 CONTINUE + ELSE + DO 40, I = 1, LENY + Y( IY ) = BETA*Y( IY ) + IY = IY + INCY + 40 CONTINUE + END IF + END IF + END IF + IF( ALPHA.EQ.ZERO ) + $ RETURN + IF( (TRANS.EQ.'N' .OR. TRANS.EQ.'n') )THEN +C +C Form y := alpha*A*x + y. +C + JX = KX + IF( INCY.EQ.1 )THEN + DO 60, J = 1, N + IF( X( JX ).NE.ZERO )THEN + TEMP = ALPHA*X( JX ) + DO 50, I = 1, M + Y( I ) = Y( I ) + TEMP*A( I, J ) + 50 CONTINUE + END IF + JX = JX + INCX + 60 CONTINUE + ELSE + DO 80, J = 1, N + IF( X( JX ).NE.ZERO )THEN + TEMP = ALPHA*X( JX ) + IY = KY + DO 70, I = 1, M + Y( IY ) = Y( IY ) + TEMP*A( I, J ) + IY = IY + INCY + 70 CONTINUE + END IF + JX = JX + INCX + 80 CONTINUE + END IF + ELSE +C +C Form y := alpha*A'*x + y or y := alpha*conjg( A' )*x + y. +C + JY = KY + IF( INCX.EQ.1 )THEN + DO 110, J = 1, N + TEMP = ZERO + IF( NOCONJ )THEN + DO 90, I = 1, M + TEMP = TEMP + A( I, J )*X( I ) + 90 CONTINUE + ELSE + DO 100, I = 1, M + TEMP = TEMP + DCONJG( A( I, J ) )*X( I ) + 100 CONTINUE + END IF + Y( JY ) = Y( JY ) + ALPHA*TEMP + JY = JY + INCY + 110 CONTINUE + ELSE + DO 140, J = 1, N + TEMP = ZERO + IX = KX + IF( NOCONJ )THEN + DO 120, I = 1, M + TEMP = TEMP + A( I, J )*X( IX ) + IX = IX + INCX + 120 CONTINUE + ELSE + DO 130, I = 1, M + TEMP = TEMP + DCONJG( A( I, J ) )*X( IX ) + IX = IX + INCX + 130 CONTINUE + END IF + Y( JY ) = Y( JY ) + ALPHA*TEMP + JY = JY + INCY + 140 CONTINUE + END IF + END IF +C + RETURN +C +C End of F06SAF (ZGEMV ). +C + END +C + SUBROUTINE ZSCAL( N, ALPHA, X, INCX ) +C MARK 12 RELEASE. NAG COPYRIGHT 1986. +C .. Entry Points .. +C ENTRY ZSCAL ( N, ALPHA, X, INCX ) +C .. Scalar Arguments .. + COMPLEX*16 ALPHA + INTEGER INCX, N +C .. Array Arguments .. + COMPLEX*16 X( * ) +C .. +C +C F06GDF performs the operation +C +C x := alpha*x +C +C +C Nag Fortran 77 version of the Blas routine ZSCAL. +C Nag Fortran 77 O( n ) basic linear algebra routine. +C +C -- Written on 26-November-1982. +C Sven Hammarling, Nag Central Office. +C +C +C .. Parameters .. + COMPLEX*16 CZERO + PARAMETER ( CZERO = ( 0.0D+0, 0.0D+0 ) ) +C .. Local Scalars .. + INTEGER IX +C .. +C .. Executable Statements .. + IF( N.GT.0 )THEN + IF( ALPHA.EQ.CZERO )THEN + DO 10, IX = 1, 1 + ( N - 1 )*INCX, INCX + X( IX ) = CZERO + 10 CONTINUE + ELSE + DO 20, IX = 1, 1 + ( N - 1 )*INCX, INCX + X( IX ) = ALPHA*X( IX ) + 20 CONTINUE + END IF + END IF +C + RETURN +C +C End of F06GDF. ( ZSCAL ) +C + END +C + SUBROUTINE ZSWAP( N, X, INCX, Y, INCY ) +C MARK 12 RELEASE. NAG COPYRIGHT 1986. +C .. Entry Points .. +C ENTRY ZSWAP ( N, X, INCX, Y, INCY ) +C .. Scalar Arguments .. + INTEGER INCX, INCY, N +C .. Array Arguments .. + COMPLEX*16 X( * ), Y( * ) +C .. +C +C F06GGF performs the operations +C +C temp := x, x := y, y := temp. +C +C +C Nag Fortran 77 version of the Blas routine ZSWAP. +C Nag Fortran 77 O( n ) basic linear algebra routine. +C +C -- Written on 26-November-1982. +C Sven Hammarling, Nag Central Office. +C +C +C .. Local Scalars .. + COMPLEX*16 TEMP + INTEGER I, IX, IY +C .. +C .. Executable Statements .. + IF( N.GT.0 )THEN + IF( ( INCX.EQ.INCY ).AND.( INCY.GT.0 ) )THEN + DO 10, IY = 1, 1 + ( N - 1 )*INCY, INCY + TEMP = X( IY ) + X( IY ) = Y( IY ) + Y( IY ) = TEMP + 10 CONTINUE + ELSE + IF( INCX.GE.0 )THEN + IX = 1 + ELSE + IX = 1 - ( N - 1 )*INCX + END IF + IF( INCY.GT.0 )THEN + DO 20, IY = 1, 1 + ( N - 1 )*INCY, INCY + TEMP = X( IX ) + X( IX ) = Y( IY ) + Y( IY ) = TEMP + IX = IX + INCX + 20 CONTINUE + ELSE + IY = 1 - ( N - 1 )*INCY + DO 30, I = 1, N + TEMP = X( IX ) + X( IX ) = Y( IY ) + Y( IY ) = TEMP + IY = IY + INCY + IX = IX + INCX + 30 CONTINUE + END IF + END IF + END IF +C + RETURN +C +C End of F06GGF. ( ZSWAP ) +C + END +C + INTEGER FUNCTION IZAMAX( N, X, INCX ) +C MARK 12 RELEASE. NAG COPYRIGHT 1986. +C .. Entry Points .. +C INTEGER IZAMAX +C ENTRY IZAMAX( N, X, INCX ) +C .. Scalar Arguments .. + INTEGER INCX, N +C .. Array Arguments .. + COMPLEX*16 X( * ) +C .. +C +C F06JMF returns the smallest value of i such that +C +C alpha( i ) = max( abs( real( x( j ) ) ) + abs( imag( x( j ) ) ) ) +C j +C +C Nag Fortran 77 version of the Blas routine IZAMAX. +C Nag Fortran 77 O( n ) basic linear algebra routine. +C +C -- Written on 31-May-1983. +C Sven Hammarling, Nag Central Office. +C +C +C .. Local Scalars .. + DOUBLE PRECISION TEMP, XMAX + INTEGER I, IMAX, IX +C .. Intrinsic Functions .. + INTRINSIC ABS, DIMAG, DBLE +C .. +C .. Executable Statements .. + IF( N.GT.0 )THEN + IMAX = 1 + IF( N.GT.1 )THEN + XMAX = ABS( DBLE( X( 1 ) ) ) + ABS( DIMAG( X( 1 ) ) ) + IX = 1 + DO 10, I = 2, N + IX = IX + INCX + TEMP = ABS( DBLE( X( IX ) ) ) + ABS( DIMAG( X( IX ) ) ) + IF( XMAX.LT.TEMP )THEN + XMAX = TEMP + IMAX = I + END IF + 10 CONTINUE + END IF + ELSE + IMAX = 0 + END IF +C + IZAMAX = IMAX + RETURN +C +C End of F06JMF. ( IZAMAX ) +C + END +C + SUBROUTINE ZGERU( M, N, ALPHA, X, INCX, Y, INCY, A, LDA ) +C MARK 13 RE-ISSUE. NAG COPYRIGHT 1988. +C .. Entry Points .. +C ENTRY ZGERU ( M, N, ALPHA, X, INCX, Y, INCY, A, LDA ) +C .. Scalar Arguments .. + COMPLEX*16 ALPHA + INTEGER INCX, INCY, LDA, M, N +C .. Array Arguments .. + COMPLEX*16 A( LDA, * ), X( * ), Y( * ) +C .. +C +C Purpose +C ======= +C +C ZGERU performs the rank 1 operation +C +C A := alpha*x*y' + A, +C +C where alpha is a scalar, x is an m element vector, y is an n element +C vector and A is an m by n matrix. +C +C Parameters +C ========== +C +C M - INTEGER. +C On entry, M specifies the number of rows of the matrix A. +C M must be at least zero. +C Unchanged on exit. +C +C N - INTEGER. +C On entry, N specifies the number of columns of the matrix A. +C N must be at least zero. +C Unchanged on exit. +C +C ALPHA - COMPLEX*16 . +C On entry, ALPHA specifies the scalar alpha. +C Unchanged on exit. +C +C X - COMPLEX*16 array of dimension at least +C ( 1 + ( m - 1 )*abs( INCX ) ). +C Before entry, the incremented array X must contain the m +C element vector x. +C Unchanged on exit. +C +C INCX - INTEGER. +C On entry, INCX specifies the increment for the elements of +C X. INCX must not be zero. +C Unchanged on exit. +C +C Y - COMPLEX*16 array of dimension at least +C ( 1 + ( n - 1 )*abs( INCY ) ). +C Before entry, the incremented array Y must contain the n +C element vector y. +C Unchanged on exit. +C +C INCY - INTEGER. +C On entry, INCY specifies the increment for the elements of +C Y. INCY must not be zero. +C Unchanged on exit. +C +C A - COMPLEX*16 array of DIMENSION ( LDA, n ). +C Before entry, the leading m by n part of the array A must +C contain the matrix of coefficients. On exit, A is +C overwritten by the updated matrix. +C +C LDA - INTEGER. +C On entry, LDA specifies the first dimension of A as declared +C in the calling (sub) program. LDA must be at least +C max( 1, m ). +C Unchanged on exit. +C +C +C Level 2 Blas routine. +C +C -- Written on 22-October-1986. +C Jack Dongarra, Argonne National Lab. +C Jeremy Du Croz, Nag Central Office. +C Sven Hammarling, Nag Central Office. +C Richard Hanson, Sandia National Labs. +C +C +C .. Parameters .. + COMPLEX*16 ZERO + PARAMETER ( ZERO = ( 0.0D+0, 0.0D+0 ) ) +C .. Local Scalars .. + COMPLEX*16 TEMP + INTEGER I, INFO, IX, J, JY, KX +C .. External Subroutines .. + EXTERNAL F06AAZ +C .. Intrinsic Functions .. + INTRINSIC MAX +C .. +C .. Executable Statements .. +C +C Test the input parameters. +C + INFO = 0 + IF ( M.LT.0 )THEN + INFO = 1 + ELSE IF( N.LT.0 )THEN + INFO = 2 + ELSE IF( INCX.EQ.0 )THEN + INFO = 5 + ELSE IF( INCY.EQ.0 )THEN + INFO = 7 + ELSE IF( LDA.LT.MAX( 1, M ) )THEN + INFO = 9 + END IF + IF( INFO.NE.0 )THEN + CALL F06AAZ( 'F06SMF/ZGERU ', INFO ) + RETURN + END IF +C +C Quick return if possible. +C + IF( ( M.EQ.0 ).OR.( N.EQ.0 ).OR.( ALPHA.EQ.ZERO ) ) + $ RETURN +C +C Start the operations. In this version the elements of A are +C accessed sequentially with one pass through A. +C + IF( INCY.GT.0 )THEN + JY = 1 + ELSE + JY = 1 - ( N - 1 )*INCY + END IF + IF( INCX.EQ.1 )THEN + DO 20, J = 1, N + IF( Y( JY ).NE.ZERO )THEN + TEMP = ALPHA*Y( JY ) + DO 10, I = 1, M + A( I, J ) = A( I, J ) + X( I )*TEMP + 10 CONTINUE + END IF + JY = JY + INCY + 20 CONTINUE + ELSE + IF( INCX.GT.0 )THEN + KX = 1 + ELSE + KX = 1 - ( M - 1 )*INCX + END IF + DO 40, J = 1, N + IF( Y( JY ).NE.ZERO )THEN + TEMP = ALPHA*Y( JY ) + IX = KX + DO 30, I = 1, M + A( I, J ) = A( I, J ) + X( IX )*TEMP + IX = IX + INCX + 30 CONTINUE + END IF + JY = JY + INCY + 40 CONTINUE + END IF +C + RETURN +C +C End of F06SMF (ZGERU ). +C + END +C + SUBROUTINE F07NSF(UPLO,N,NRHS,A,LDA,IPIV,B,LDB,INFO) +C MARK 15 RELEASE. NAG COPYRIGHT 1991. +C .. Entry Points .. + ENTRY ZSYTRS(UPLO,N,NRHS,A,LDA,IPIV,B,LDB,INFO) +C +C Purpose +C ======= +C +C ZSYTRS solves a system of linear equations A*X = B with a complex +C symmetric matrix A using the factorization A = U*D*U' or A = L*D*L' +C computed by F07NRF. +C +C Arguments +C ========= +C +C UPLO (input) CHARACTER*1 +C Specifies whether the details of the factorization are stored +C as an upper or lower triangular matrix. +C = 'U': Upper triangular (form is A = U*D*U') +C = 'L': Lower triangular (form is A = L*D*L') +C +C N (input) INTEGER +C The order of the matrix A. N >= 0. +C +C NRHS (input) INTEGER +C The number of right hand sides, i.e., the number of columns +C of the matrix B. NRHS >= 0. +C +C A (input) COMPLEX array, dimension (LDA,N) +C The block diagonal matrix D and the multipliers used to +C obtain the factor U or L as computed by F07NRF. +C +C LDA (input) INTEGER +C The leading dimension of the array A. LDA >= max(1,N). +C +C IPIV (input) INTEGER array, dimension (N) +C Details of the interchanges and the block structure of D +C as determined by F07NRF. +C +C B (input/output) COMPLEX array, dimension (LDB,NRHS) +C On entry, the right hand side vectors B for the system of +C linear equations. +C On exit, the solution vectors, X. +C +C LDB (input) INTEGER +C The leading dimension of the array B. LDB >= max(1,N). +C +C INFO (output) INTEGER +C = 0: successful exit +C < 0: if INFO = -k, the k-th argument had an illegal value +C +C -- LAPACK routine (adapted for NAG Library) +C Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., +C Courant Institute, Argonne National Lab, and Rice University +C +C ===================================================================== +C +C .. Parameters .. + COMPLEX*16 ONE + PARAMETER (ONE=1.0D+0) +C .. Scalar Arguments .. + INTEGER INFO, LDA, LDB, N, NRHS + CHARACTER UPLO +C .. Array Arguments .. + COMPLEX*16 A(LDA,*), B(LDB,*) + INTEGER IPIV(*) +C .. Local Scalars .. + COMPLEX*16 AK, AKM1, AKM1K, BK, BKM1, DENOM + INTEGER J, K, KP + LOGICAL UPPER +C .. External Subroutines .. + EXTERNAL ZGEMV, ZGERU, ZSCAL, ZSWAP, F06AAZ +C .. Intrinsic Functions .. + INTRINSIC MAX +C .. Executable Statements .. +C + INFO = 0 + UPPER = (UPLO.EQ.'U' .OR. UPLO.EQ.'u') + IF ( .NOT. UPPER .AND. .NOT. (UPLO.EQ.'L' .OR. UPLO.EQ.'l')) THEN + INFO = -1 + ELSE IF (N.LT.0) THEN + INFO = -2 + ELSE IF (NRHS.LT.0) THEN + INFO = -3 + ELSE IF (LDA.LT.MAX(1,N)) THEN + INFO = -5 + ELSE IF (LDB.LT.MAX(1,N)) THEN + INFO = -8 + END IF + IF (INFO.NE.0) THEN + CALL F06AAZ('F07NSF/ZSYTRS',-INFO) + RETURN + END IF +C +C Quick return if possible +C + IF (N.EQ.0 .OR. NRHS.EQ.0) RETURN +C + IF (UPPER) THEN +C +C Solve A*X = B, where A = U*D*U'. +C +C First solve U*D*X = B, overwriting B with X. +C +C K is the main loop index, decreasing from N to 1 in steps of +C 1 or 2, depending on the size of the diagonal blocks. +C + K = N + 20 CONTINUE +C +C If K < 1, exit from loop. +C + IF (K.LT.1) GO TO 60 +C + IF (IPIV(K).GT.0) THEN +C +C 1 x 1 diagonal block +C +C Interchange rows K and IPIV(K). +C + KP = IPIV(K) + IF (KP.NE.K) CALL ZSWAP(NRHS,B(K,1),LDB,B(KP,1),LDB) +C +C Multiply by inv(U(K)), where U(K) is the transformation +C stored in column K of A. +C + CALL ZGERU(K-1,NRHS,-ONE,A(1,K),1,B(K,1),LDB,B(1,1),LDB) +C +C Multiply by the inverse of the diagonal block. +C + CALL ZSCAL(NRHS,ONE/A(K,K),B(K,1),LDB) + K = K - 1 + ELSE +C +C 2 x 2 diagonal block +C +C Interchange rows K-1 and -IPIV(K). +C + KP = -IPIV(K) + IF (KP.NE.K-1) CALL ZSWAP(NRHS,B(K-1,1),LDB,B(KP,1),LDB) +C +C Multiply by inv(U(K)), where U(K) is the transformation +C stored in columns K-1 and K of A. +C + CALL ZGERU(K-2,NRHS,-ONE,A(1,K),1,B(K,1),LDB,B(1,1),LDB) + CALL ZGERU(K-2,NRHS,-ONE,A(1,K-1),1,B(K-1,1),LDB,B(1,1),LDB) +C +C Multiply by the inverse of the diagonal block. +C + AKM1K = A(K-1,K) + AKM1 = A(K-1,K-1)/AKM1K + AK = A(K,K)/AKM1K + DENOM = AKM1*AK - ONE + DO 40 J = 1, NRHS + BKM1 = B(K-1,J)/AKM1K + BK = B(K,J)/AKM1K + B(K-1,J) = (AK*BKM1-BK)/DENOM + B(K,J) = (AKM1*BK-BKM1)/DENOM + 40 CONTINUE + K = K - 2 + END IF +C + GO TO 20 + 60 CONTINUE +C +C Next solve U'*X = B, overwriting B with X. +C +C K is the main loop index, increasing from 1 to N in steps of +C 1 or 2, depending on the size of the diagonal blocks. +C + K = 1 + 80 CONTINUE +C +C If K > N, exit from loop. +C + IF (K.GT.N) GO TO 100 +C + IF (IPIV(K).GT.0) THEN +C +C 1 x 1 diagonal block +C +C Multiply by inv(U'(K)), where U(K) is the transformation +C stored in column K of A. +C + CALL ZGEMV('Transpose',K-1,NRHS,-ONE,B,LDB,A(1,K),1,ONE, + * B(K,1),LDB) +C +C Interchange rows K and IPIV(K). +C + KP = IPIV(K) + IF (KP.NE.K) CALL ZSWAP(NRHS,B(K,1),LDB,B(KP,1),LDB) + K = K + 1 + ELSE +C +C 2 x 2 diagonal block +C +C Multiply by inv(U'(K+1)), where U(K+1) is the transformation +C stored in columns K and K+1 of A. +C + CALL ZGEMV('Transpose',K-1,NRHS,-ONE,B,LDB,A(1,K),1,ONE, + * B(K,1),LDB) + CALL ZGEMV('Transpose',K-1,NRHS,-ONE,B,LDB,A(1,K+1),1,ONE, + * B(K+1,1),LDB) +C +C Interchange rows K and -IPIV(K). +C + KP = -IPIV(K) + IF (KP.NE.K) CALL ZSWAP(NRHS,B(K,1),LDB,B(KP,1),LDB) + K = K + 2 + END IF +C + GO TO 80 + 100 CONTINUE +C + ELSE +C +C Solve A*X = B, where A = L*D*L'. +C +C First solve L*D*X = B, overwriting B with X. +C +C K is the main loop index, increasing from 1 to N in steps of +C 1 or 2, depending on the size of the diagonal blocks. +C + K = 1 + 120 CONTINUE +C +C If K > N, exit from loop. +C + IF (K.GT.N) GO TO 160 +C + IF (IPIV(K).GT.0) THEN +C +C 1 x 1 diagonal block +C +C Interchange rows K and IPIV(K). +C + KP = IPIV(K) + IF (KP.NE.K) CALL ZSWAP(NRHS,B(K,1),LDB,B(KP,1),LDB) +C +C Multiply by inv(L(K)), where L(K) is the transformation +C stored in column K of A. +C + IF (K.LT.N) CALL ZGERU(N-K,NRHS,-ONE,A(K+1,K),1,B(K,1),LDB, + * B(K+1,1),LDB) +C +C Multiply by the inverse of the diagonal block. +C + CALL ZSCAL(NRHS,ONE/A(K,K),B(K,1),LDB) + K = K + 1 + ELSE +C +C 2 x 2 diagonal block +C +C Interchange rows K+1 and -IPIV(K). +C + KP = -IPIV(K) + IF (KP.NE.K+1) CALL ZSWAP(NRHS,B(K+1,1),LDB,B(KP,1),LDB) +C +C Multiply by inv(L(K)), where L(K) is the transformation +C stored in columns K and K+1 of A. +C + IF (K.LT.N-1) THEN + CALL ZGERU(N-K-1,NRHS,-ONE,A(K+2,K),1,B(K,1),LDB,B(K+2,1) + * ,LDB) + CALL ZGERU(N-K-1,NRHS,-ONE,A(K+2,K+1),1,B(K+1,1),LDB, + * B(K+2,1),LDB) + END IF +C +C Multiply by the inverse of the diagonal block. +C + AKM1K = A(K+1,K) + AKM1 = A(K,K)/AKM1K + AK = A(K+1,K+1)/AKM1K + DENOM = AKM1*AK - ONE + DO 140 J = 1, NRHS + BKM1 = B(K,J)/AKM1K + BK = B(K+1,J)/AKM1K + B(K,J) = (AK*BKM1-BK)/DENOM + B(K+1,J) = (AKM1*BK-BKM1)/DENOM + 140 CONTINUE + K = K + 2 + END IF +C + GO TO 120 + 160 CONTINUE +C +C Next solve L'*X = B, overwriting B with X. +C +C K is the main loop index, decreasing from N to 1 in steps of +C 1 or 2, depending on the size of the diagonal blocks. +C + K = N + 180 CONTINUE +C +C If K < 1, exit from loop. +C + IF (K.LT.1) GO TO 200 +C + IF (IPIV(K).GT.0) THEN +C +C 1 x 1 diagonal block +C +C Multiply by inv(L'(K)), where L(K) is the transformation +C stored in column K of A. +C + IF (K.LT.N) CALL ZGEMV('Transpose',N-K,NRHS,-ONE,B(K+1,1), + * LDB,A(K+1,K),1,ONE,B(K,1),LDB) +C +C Interchange rows K and IPIV(K). +C + KP = IPIV(K) + IF (KP.NE.K) CALL ZSWAP(NRHS,B(K,1),LDB,B(KP,1),LDB) + K = K - 1 + ELSE +C +C 2 x 2 diagonal block +C +C Multiply by inv(L'(K-1)), where L(K-1) is the transformation +C stored in columns K-1 and K of A. +C + IF (K.LT.N) THEN + CALL ZGEMV('Transpose',N-K,NRHS,-ONE,B(K+1,1),LDB, + * A(K+1,K),1,ONE,B(K,1),LDB) + CALL ZGEMV('Transpose',N-K,NRHS,-ONE,B(K+1,1),LDB, + * A(K+1,K-1),1,ONE,B(K-1,1),LDB) + END IF +C +C Interchange rows K and -IPIV(K). +C + KP = -IPIV(K) + IF (KP.NE.K) CALL ZSWAP(NRHS,B(K,1),LDB,B(KP,1),LDB) + K = K - 2 + END IF +C + GO TO 180 + 200 CONTINUE + END IF +C + RETURN +C +C End of F07NSF (ZSYTRS) +C + END +c +c--------------------------------- +c + diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 1233160..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'muntwiler_m' diff --git a/tests/calculators/__init__.py b/tests/calculators/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/calculators/phagen/__init__.py b/tests/calculators/phagen/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/database/test_common.py b/tests/database/test_common.py new file mode 100644 index 0000000..449332b --- /dev/null +++ b/tests/database/test_common.py @@ -0,0 +1,232 @@ +""" +@package tests.database.test_common +unit tests for pmsco.database.common + +the purpose of these tests is to help debugging the code. + +to run the tests, change to the directory which contains the tests directory, and execute =nosetests=. + +@pre nose must be installed (python-nose package on Debian). + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import unittest +import datetime +import sqlalchemy.exc +import pmsco.database.access as db +import pmsco.database.common as db_common +import pmsco.database.orm as orm +import pmsco.dispatch as dispatch + + +def setup_sample_database(session): + p1 = orm.Project(name="oldproject", code="oldcode") + p2 = orm.Project(name="unittest", code="testcode") + j1 = orm.Job(project=p1, name="oldjob", mode="oldmode", machine="oldhost", datetime=datetime.datetime.now()) + j2 = orm.Job(project=p2, name="testjob", mode="testmode", machine="testhost", datetime=datetime.datetime.now()) + pk1 = orm.Param(key='parA') + pk2 = orm.Param(key='parB') + pk3 = orm.Param(key='parC') + m1 = orm.Model(job=j1, model=91) + m2 = orm.Model(job=j2, model=92) + r1 = orm.Result(calc_id=dispatch.CalcID(91, -1, -1, -1, -1), rfac=0.534, secs=37.9) + r1.model = m1 + pv1 = orm.ParamValue(model=m1, param=pk1, value=1.234, delta=0.1234) + pv2 = orm.ParamValue(model=m1, param=pk2, value=5.678, delta=-0.5678) + pv3 = orm.ParamValue(model=m2, param=pk3, value=6.785, delta=0.6785) + objects = {'p1': p1, 'p2': p2, 'j1': j1, 'j2': j2, 'm1': m1, 'm2': m2, 'r1': r1, + 'pv1': pv1, 'pv2': pv2, 'pv3': pv3, 'pk1': pk1, 'pk2': pk2, 'pk3': pk3} + session.add_all(objects.values()) + session.commit() + return objects + + +class TestDatabaseCommon(unittest.TestCase): + def setUp(self): + self.db = db.DatabaseAccess() + self.db.connect(":memory:") + + def tearDown(self): + pass + + @classmethod + def setup_class(cls): + # before any methods in this class + pass + + @classmethod + def teardown_class(cls): + # teardown_class() after any methods in this class + pass + + def test_setup_database(self): + with self.db.session() as session: + setup_sample_database(session) + self.assertEqual(session.query(orm.Project).count(), 2) + self.assertEqual(session.query(orm.Job).count(), 2) + self.assertEqual(session.query(orm.Param).count(), 3) + self.assertEqual(session.query(orm.Model).count(), 2) + self.assertEqual(session.query(orm.Result).count(), 1) + self.assertEqual(session.query(orm.ParamValue).count(), 3) + + def test_get_project(self): + with self.db.session() as session: + p1 = orm.Project(name="p1") + p2 = orm.Project(name="p2") + p3 = orm.Project(name="p3") + p4 = orm.Project(name="p4") + session.add_all([p1, p2, p3]) + session.commit() + q1 = db_common.get_project(session, p1) + q2 = db_common.get_project(session, p2.id) + q3 = db_common.get_project(session, p3.name) + q4 = db_common.get_project(session, p4) + self.assertIs(q1, p1, "by object") + self.assertIs(q2, p2, "by id") + self.assertIs(q3, p3, "by name") + self.assertIs(q4, p4, "detached object by object") + with self.assertRaises(sqlalchemy.exc.InvalidRequestError, msg="detached object by name"): + db_common.get_project(session, p4.name) + + def test_get_job(self): + with self.db.session() as session: + p1 = orm.Project(name="p1") + p2 = orm.Project(name="p2") + p3 = orm.Project(name="p3") + p4 = orm.Project(name="p4") + j1 = orm.Job(name="j1") + j1.project = p1 + j2 = orm.Job(name="j2") + j2.project = p2 + j3 = orm.Job(name="j1") + j3.project = p3 + j4 = orm.Job(name="j4") + j4.project = p4 + session.add_all([p1, p2, p3, j1, j2, j3]) + session.commit() + + self.assertIsNot(j3, j1, "jobs with same name") + q1 = db_common.get_job(session, p1, j1) + q2 = db_common.get_job(session, p2, j2.id) + q3 = db_common.get_job(session, p3, j3.name) + q4 = db_common.get_job(session, p4, j4) + self.assertIs(q1, j1, "by object") + self.assertIs(q2, j2, "by id") + self.assertIs(q3, j3, "by name") + self.assertIs(q4, j4, "detached object by object") + with self.assertRaises(sqlalchemy.exc.InvalidRequestError, msg="detached object by name"): + db_common.get_job(session, p4, j4.name) + q5 = db_common.get_job(session, p1, j4) + self.assertIs(q5, j4) + + def test_register_project(self): + with self.db.session() as session: + id1 = db_common.register_project(session, "unittest1", "Atest", allow_existing=True) + self.assertIsInstance(id1, orm.Project) + id2 = db_common.register_project(session, "unittest2", "Btest", allow_existing=True) + self.assertIsInstance(id2, orm.Project) + id3 = db_common.register_project(session, "unittest1", "Ctest", allow_existing=True) + self.assertIsInstance(id3, orm.Project) + self.assertNotEqual(id1, id2) + self.assertEqual(id1, id3) + session.commit() + + c = session.execute("select count(*) from Projects") + row = c.fetchone() + self.assertEqual(row[0], 2) + c = session.execute("select name, code from Projects where id=:id", {'id': id1.id}) + row = c.fetchone() + self.assertIsNotNone(row) + self.assertEqual(len(row), 2) + self.assertEqual(row[0], "unittest1") + self.assertEqual(row[1], "Atest") + self.assertEqual(row['name'], "unittest1") + self.assertEqual(row['code'], "Atest") + + with self.assertRaises(ValueError): + db_common.register_project(session, "unittest1", "Ctest") + + def test_register_job(self): + with self.db.session() as session: + pid1 = db_common.register_project(session, "unittest1", "Acode") + pid2 = db_common.register_project(session, "unittest2", "Bcode") + dt1 = datetime.datetime.now() + + # insert new job + id1 = db_common.register_job(session, pid1, "Ajob", mode="Amode", machine="local", git_hash="Ahash", + datetime=dt1, description="Adesc") + self.assertIsInstance(id1, orm.Job) + # insert another job + id2 = db_common.register_job(session, pid1.id, "Bjob", mode="Bmode", machine="local", git_hash="Ahash", + datetime=dt1, description="Adesc") + self.assertIsInstance(id2, orm.Job) + # update first job + id3 = db_common.register_job(session, "unittest1", "Ajob", mode="Cmode", machine="local", git_hash="Chash", + datetime=dt1, description="Cdesc", + allow_existing=True) + self.assertIsInstance(id3, orm.Job) + # insert another job with same name but in other project + id4 = db_common.register_job(session, pid2, "Ajob", mode="Dmode", machine="local", git_hash="Dhash", + datetime=dt1, description="Ddesc") + self.assertIsInstance(id4, orm.Job) + # existing job + with self.assertRaises(ValueError): + db_common.register_job(session, pid1, "Ajob", mode="Emode", machine="local", git_hash="Dhash", + datetime=dt1, description="Ddesc") + + self.assertIsNot(id1, id2) + self.assertIs(id1, id3) + self.assertIsNot(id1, id4) + + c = session.execute("select count(*) from Jobs") + row = c.fetchone() + self.assertEqual(row[0], 3) + c = session.execute("select name, mode, machine, git_hash, datetime, description from Jobs where id=:id", + {'id': id1.id}) + row = c.fetchone() + self.assertIsNotNone(row) + self.assertEqual(len(row), 6) + self.assertEqual(row[0], "Ajob") + self.assertEqual(row[1], "Amode") + self.assertEqual(row['machine'], "local") + self.assertEqual(str(row['datetime']), str(dt1)) + self.assertEqual(row['git_hash'], "Ahash") + self.assertEqual(row['description'], "Adesc") + + def test_register_params(self): + with self.db.session() as session: + setup_sample_database(session) + model5 = {'parA': 2.341, 'parC': 6.785, '_model': 92, '_rfac': 0.453} + db_common.register_params(session, model5) + expected = ['parA', 'parB', 'parC'] + session.commit() + + c = session.execute("select * from Params order by key") + results = c.fetchall() + self.assertEqual(len(results), 3) + result_params = [row['key'] for row in results] + self.assertEqual(result_params, expected) + + def test_query_params(self): + with self.db.session() as session: + objs = setup_sample_database(session) + results = db_common.query_params(session, project=objs['p1'].id) + expected = ['parA', 'parB'] + self.assertEqual(expected, sorted(list(results.keys()))) + self.assertIsInstance(results['parA'], orm.Param) + self.assertIsInstance(results['parB'], orm.Param) + results = db_common.query_params(session, project=objs['p2'].name) + expected = ['parC'] + self.assertEqual(expected, sorted(list(results.keys()))) + self.assertIsInstance(results['parC'], orm.Param) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/database/test_ingest.py b/tests/database/test_ingest.py new file mode 100644 index 0000000..0ad63b8 --- /dev/null +++ b/tests/database/test_ingest.py @@ -0,0 +1,211 @@ +""" +@package tests.database.test_ingest +unit tests for pmsco.database + +the purpose of these tests is to help debugging the code. + +to run the tests, change to the directory which contains the tests directory, and execute =nosetests=. + +@pre nose must be installed (python-nose package on Debian). + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import unittest +import pmsco.database.access as db +import pmsco.database.ingest as db_ingest +import pmsco.database.orm as orm +import pmsco.dispatch as dispatch +from tests.database.test_common import setup_sample_database + + +class TestDatabase(unittest.TestCase): + def setUp(self): + self.db = db.DatabaseAccess() + self.db.connect(":memory:") + + def tearDown(self): + pass + + @classmethod + def setup_class(cls): + # before any methods in this class + pass + + @classmethod + def teardown_class(cls): + # teardown_class() after any methods in this class + pass + + def test_insert_result(self): + with self.db.session() as session: + objs = setup_sample_database(session) + index = dispatch.CalcID(15, 16, 17, 18, -1) + result_data = {'parA': 4.123, 'parB': 8.567, '_rfac': 0.654, '_gen': 3, '_particle': 21, '_secs': 27.8} + result_delta = {'parA': 0.4123, 'parB': 0.8567} + model_obj, result_obj = db_ingest.insert_result(session, objs['j1'], index, result_data, result_delta) + session.commit() + + # model + q = session.query(orm.Model) + q = q.filter(orm.Model.job_id == objs['j1'].id) + q = q.filter(orm.Model.model == index.model) + m = q.one() + self.assertIsNot(model_obj, objs['m1']) + self.assertIs(m, model_obj) + self.assertEqual(m.id, model_obj.id) + self.assertEqual(m.job_id, objs['j1'].id) + self.assertEqual(m.model, index.model) + self.assertEqual(m.gen, result_data['_gen']) + self.assertEqual(m.particle, result_data['_particle']) + + # result + q = session.query(orm.Result) + q = q.filter(orm.Result.model_id == model_obj.id) + r = q.one() + self.assertIsNot(r, objs['r1']) + self.assertIs(r, result_obj) + self.assertEqual(r.id, result_obj.id) + self.assertIs(r.model, model_obj) + self.assertEqual(r.scan, index.scan) + self.assertEqual(r.domain, index.domain) + self.assertEqual(r.emit, index.emit) + self.assertEqual(r.region, index.region) + self.assertEqual(r.rfac, result_data['_rfac']) + self.assertEqual(r.secs, result_data['_secs']) + + # param values + q = session.query(orm.ParamValue) + q = q.filter(orm.ParamValue.model_id == model_obj.id) + pvs = q.all() + values = {pv.param_key: pv.value for pv in pvs} + deltas = {pv.param_key: pv.delta for pv in pvs} + for k in result_data: + if k[0] != '_': + self.assertAlmostEqual(values[k], result_data[k]) + self.assertAlmostEqual(deltas[k], result_delta[k]) + self.assertAlmostEqual(m.values[k], result_data[k]) + self.assertAlmostEqual(m.deltas[k], result_delta[k]) + + def test_update_result(self): + """ + test update an existing model and result + + update parameters parA and parB and rfac of result (91, -1, -1, -1, -1) + + @return: None + """ + with self.db.session() as session: + objs = setup_sample_database(session) + index = dispatch.CalcID(91, -1, -1, -1, -1) + result_data = {'parA': 4.123, 'parB': 8.567, '_rfac': 0.654, '_gen': 3, '_particle': 21, '_secs': 27.8} + result_delta = {'parA': 0.4123, 'parB': 0.8567} + model_obj, result_obj = db_ingest.insert_result(session, objs['j1'], index, result_data, result_delta) + session.commit() + + # model + q = session.query(orm.Model) + q = q.filter(orm.Model.job_id == objs['j1'].id) + q = q.filter(orm.Model.model == index.model) + m = q.one() + self.assertIs(model_obj, objs['m1']) + self.assertIs(m, objs['m1']) + self.assertEqual(m.id, model_obj.id) + self.assertEqual(m.job_id, objs['j1'].id) + self.assertEqual(m.model, index.model) + self.assertEqual(m.gen, result_data['_gen']) + self.assertEqual(m.particle, result_data['_particle']) + + # result + q = session.query(orm.Result) + q = q.filter(orm.Result.model_id == model_obj.id) + r = q.one() + self.assertIs(result_obj, objs['r1']) + self.assertIs(r, objs['r1']) + self.assertEqual(r.id, result_obj.id) + self.assertIs(r.model, model_obj) + self.assertEqual(r.scan, index.scan) + self.assertEqual(r.domain, index.domain) + self.assertEqual(r.emit, index.emit) + self.assertEqual(r.region, index.region) + self.assertEqual(r.rfac, result_data['_rfac']) + self.assertEqual(r.secs, result_data['_secs']) + + # param values + q = session.query(orm.ParamValue) + q = q.filter(orm.ParamValue.model_id == model_obj.id) + pvs = q.all() + values = {pv.param_key: pv.value for pv in pvs} + deltas = {pv.param_key: pv.delta for pv in pvs} + for k in result_data: + if k[0] != '_': + self.assertAlmostEqual(values[k], result_data[k]) + self.assertAlmostEqual(deltas[k], result_delta[k]) + self.assertAlmostEqual(m.values[k], result_data[k]) + self.assertAlmostEqual(m.deltas[k], result_delta[k]) + + def test_update_result_dict(self): + """ + test update an existing model and result with dictionary arguments + + update parameters parA and parB and rfac of result (91, -1, -1, -1, -1) + + @return: None + """ + with self.db.session() as session: + objs = setup_sample_database(session) + result_data = {'_model': 91, '_scan': -1, '_domain': -1, '_emit': -1, '_region': -1, + 'parA': 4.123, 'parB': 8.567, '_rfac': 0.654, '_gen': 3, '_particle': 21, '_secs': 27.8} + result_delta = {'parA': 0.4123, 'parB': 0.8567} + model_obj, result_obj = db_ingest.insert_result(session, objs['j1'], result_data, result_data, result_delta) + session.commit() + + # model + q = session.query(orm.Model) + q = q.filter(orm.Model.job_id == objs['j1'].id) + q = q.filter(orm.Model.model == result_data['_model']) + m = q.one() + self.assertIs(model_obj, objs['m1']) + self.assertIs(m, objs['m1']) + self.assertEqual(m.id, model_obj.id) + self.assertEqual(m.job_id, objs['j1'].id) + self.assertEqual(m.model, result_data['_model']) + self.assertEqual(m.gen, result_data['_gen']) + self.assertEqual(m.particle, result_data['_particle']) + + # result + q = session.query(orm.Result) + q = q.filter(orm.Result.model_id == model_obj.id) + r = q.one() + self.assertIs(result_obj, objs['r1']) + self.assertIs(r, objs['r1']) + self.assertEqual(r.id, result_obj.id) + self.assertIs(r.model, model_obj) + self.assertEqual(r.scan, result_data['_scan']) + self.assertEqual(r.domain, result_data['_domain']) + self.assertEqual(r.emit, result_data['_emit']) + self.assertEqual(r.region, result_data['_region']) + self.assertEqual(r.rfac, result_data['_rfac']) + + # param values + q = session.query(orm.ParamValue) + q = q.filter(orm.ParamValue.model_id == model_obj.id) + pvs = q.all() + values = {pv.param_key: pv.value for pv in pvs} + deltas = {pv.param_key: pv.delta for pv in pvs} + for k in result_data: + if k[0] != '_': + self.assertAlmostEqual(values[k], result_data[k]) + self.assertAlmostEqual(deltas[k], result_delta[k]) + self.assertAlmostEqual(m.values[k], result_data[k]) + self.assertAlmostEqual(m.deltas[k], result_delta[k]) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/database/test_orm.py b/tests/database/test_orm.py new file mode 100644 index 0000000..ce2b082 --- /dev/null +++ b/tests/database/test_orm.py @@ -0,0 +1,279 @@ +""" +@package tests.database.test_orm +unit tests for pmsco.database.orm + +the purpose of these tests is to help debugging the code. + +to run the tests, change to the directory which contains the tests directory, and execute =nosetests=. + +@pre nose must be installed (python-nose package on Debian). + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2021 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import unittest +import pmsco.database.access as db +import pmsco.database.orm as orm +import pmsco.database.util as util +import pmsco.dispatch as dispatch + + +class TestDatabase(unittest.TestCase): + def setUp(self): + self.db = db.DatabaseAccess() + self.db.connect(":memory:") + + def tearDown(self): + pass + + @classmethod + def setup_class(cls): + # before any methods in this class + pass + + @classmethod + def teardown_class(cls): + # teardown_class() after any methods in this class + pass + + def test_orm_1(self): + with self.db.session() as session: + prj = orm.Project(name="test1", code=__file__) + session.add(prj) + job = orm.Job(name="test_database") + job.project = prj + session.add(job) + tag1 = orm.Tag(key="phase") + tag2 = orm.Tag(key="scatter") + session.add_all([tag1, tag2]) + jt1 = orm.JobTag() + jt1.tag = tag1 + jt1.job = job + jt1.value = 'phagen' + jt2 = orm.JobTag() + jt2.tag = tag2 + jt2.job = job + jt2.value = 'edac' + session.commit() + + qprj = session.query(orm.Project).filter_by(id=1).one() + self.assertEqual(prj.name, qprj.name) + qjob = session.query(orm.Job).filter_by(id=1).one() + self.assertEqual(job.name, qjob.name) + self.assertEqual(job.project.name, prj.name) + self.assertEqual(len(qprj.jobs), 1) + self.assertEqual(len(qjob.job_tags), 2) + self.assertEqual(qjob.tags['phase'], 'phagen') + self.assertEqual(qjob.tags['scatter'], 'edac') + + def test_orm_2(self): + with self.db.session() as session: + prj = orm.Project(name="project 1", code=__file__) + session.add(prj) + + job = orm.Job(name="job 1") + job.project = prj + session.add(job) + + jt1 = orm.JobTag('phase', 'phagen') + session.add(jt1) + job.job_tags[jt1.tag_key] = jt1 + job.tags['scatter'] = 'edac' + + mod = orm.Model(model=1111, gen=111, particle=11) + session.add(mod) + + pv1 = orm.ParamValue(key='dAB', value=123.456, delta=7.543) + session.add(pv1) + mod.param_values[pv1.param_key] = pv1 + mod.values['dBC'] = 234.567 + + cid = dispatch.CalcID(1111, 2, 3, 4, 5) + res = orm.Result(calc_id=cid, rfac=0.123) + res.model = mod + session.add(res) + + session.commit() + + qprj = session.query(orm.Project).filter_by(id=1).one() + self.assertEqual(qprj.name, prj.name) + self.assertEqual(len(qprj.jobs), 1) + job_names = [k for k in qprj.jobs.keys()] + self.assertEqual(job_names[0], job.name) + self.assertEqual(qprj.jobs[job.name], job) + + qjob = session.query(orm.Job).filter_by(id=1).one() + self.assertEqual(qjob.name, job.name) + self.assertEqual(qjob.project.name, prj.name) + self.assertEqual(len(qjob.job_tags), 2) + self.assertEqual(qjob.job_tags['phase'].value, 'phagen') + self.assertEqual(qjob.job_tags['scatter'].value, 'edac') + self.assertEqual(len(qjob.tags), 2) + self.assertEqual(qjob.tags['phase'], 'phagen') + self.assertEqual(qjob.tags['scatter'], 'edac') + + qmod = session.query(orm.Model).filter_by(id=1).one() + self.assertEqual(qmod.model, mod.model) + self.assertEqual(len(qmod.param_values), 2) + self.assertEqual(qmod.values['dAB'], 123.456) + self.assertEqual(qmod.deltas['dAB'], 7.543) + self.assertEqual(qmod.values['dBC'], 234.567) + + self.assertEqual(len(qmod.results), 1) + self.assertEqual(qmod.results[0].rfac, 0.123) + + def test_job_tags(self): + with self.db.session() as session: + prj = orm.Project(name="project 1", code=__file__) + session.add(prj) + + job1 = orm.Job(name="job 1") + job1.project = prj + session.add(job1) + job2 = orm.Job(name="job 2") + job2.project = prj + session.add(job2) + + job1.tags['color'] = 'blue' + job1.tags['shape'] = 'round' + session.flush() + job2.tags['color'] = 'red' + job1.tags['color'] = 'green' + + session.commit() + + qjob1 = session.query(orm.Job).filter_by(name='job 1').one() + self.assertEqual(qjob1.tags['color'], 'green') + qjob2 = session.query(orm.Job).filter_by(name='job 2').one() + self.assertEqual(qjob2.tags['color'], 'red') + + def test_job_jobtags(self): + with self.db.session() as session: + prj = orm.Project(name="project 1", code=__file__) + session.add(prj) + + job1 = orm.Job(name="job 1") + job1.project = prj + session.add(job1) + job2 = orm.Job(name="job 2") + job2.project = prj + session.add(job2) + + jt1 = orm.JobTag('color', 'blue') + job1.job_tags[jt1.tag_key] = jt1 + session.flush() + jt2 = orm.JobTag('color', 'red') + job2.job_tags[jt2.tag_key] = jt2 + + session.commit() + + qjob1 = session.query(orm.Job).filter_by(name='job 1').one() + self.assertIsInstance(qjob1.job_tags['color'], orm.JobTag) + self.assertEqual(qjob1.job_tags['color'].value, 'blue') + qjob2 = session.query(orm.Job).filter_by(name='job 2').one() + self.assertIsInstance(qjob2.job_tags['color'], orm.JobTag) + self.assertEqual(qjob2.job_tags['color'].value, 'red') + + def test_param_values(self): + with self.db.session() as session: + prj = orm.Project(name="project 1", code=__file__) + session.add(prj) + job = orm.Job(name="job 1") + job.project = prj + session.add(job) + + mod1 = orm.Model(model=1, gen=11, particle=111) + session.add(mod1) + mod2 = orm.Model(model=2, gen=22, particle=222) + session.add(mod2) + + mod1.values['dBC'] = 234.567 + # note: this flush is necessary before accessing the same param in another model + session.flush() + mod2.values['dBC'] = 345.678 + + session.commit() + + qmod1 = session.query(orm.Model).filter_by(model=1).one() + self.assertEqual(qmod1.values['dBC'], 234.567) + qmod2 = session.query(orm.Model).filter_by(model=2).one() + self.assertEqual(qmod2.values['dBC'], 345.678) + + def test_filter_job(self): + """ + test sqlalchemy filter syntax + + @return: None + """ + with self.db.session() as session: + p1 = orm.Project(name="p1") + p2 = orm.Project(name="p2") + j11 = orm.Job(name="j1") + j11.project = p1 + j12 = orm.Job(name="j2") + j12.project = p1 + j21 = orm.Job(name="j1") + j21.project = p2 + j22 = orm.Job(name="j2") + j22.project = p2 + session.add_all([p1, p2, j11, j12, j21, j22]) + session.commit() + + q1 = session.query(orm.Job).join(orm.Project) + q1 = q1.filter(orm.Project.name == 'p1') + q1 = q1.filter(orm.Job.name == 'j1') + jobs1 = q1.all() + + sql = """ + select Projects.name project_name, Jobs.name job_name + from Projects join Jobs on Projects.id = Jobs.project_id + where Jobs.name = 'j1' and Projects.name = 'p1' + """ + jobs2 = session.execute(sql) + + n = 0 + for j in jobs2: + self.assertEqual(j.project_name, 'p1') + self.assertEqual(j.job_name, 'j1') + n += 1 + self.assertEqual(n, 1) + + for j in jobs1: + self.assertEqual(j.project.name, 'p1') + self.assertEqual(j.name, 'j1') + self.assertEqual(len(jobs1), 1) + + def test_filter_in(self): + """ + test sqlalchemy filter syntax: in_ operator + + @return: None + """ + with self.db.session() as session: + p1 = orm.Project(name="p1") + p2 = orm.Project(name="p2") + j11 = orm.Job(name="j1") + j11.project = p1 + j12 = orm.Job(name="j2") + j12.project = p1 + j21 = orm.Job(name="j1") + j21.project = p2 + j22 = orm.Job(name="j2") + j22.project = p2 + session.add_all([p1, p2, j11, j12, j21, j22]) + session.commit() + + q1 = session.query(orm.Job) + q1 = q1.filter(orm.Job.id.in_([2, 3, 7])) + jobs1 = q1.all() + self.assertEqual(len(jobs1), 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/database/test_query.py b/tests/database/test_query.py new file mode 100644 index 0000000..f636a58 --- /dev/null +++ b/tests/database/test_query.py @@ -0,0 +1,311 @@ +""" +@package tests.database.test_query +unit tests for pmsco.database + +the purpose of these tests is to help debugging the code. + +to run the tests, change to the directory which contains the tests directory, and execute =nosetests=. + +@pre nose must be installed (python-nose package on Debian). + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import unittest +import numpy as np +import pmsco.database.access as db +import pmsco.database.common as db_common +import pmsco.database.ingest as db_ingest +import pmsco.database.orm as db_orm +import pmsco.database.query as db_query +import pmsco.database.util as db_util +from tests.database.test_common import setup_sample_database + + +def pop_query_hook(query, gen): + return query.filter(db_orm.Model.gen == gen) + + +class TestDatabase(unittest.TestCase): + def setUp(self): + self.db = db.DatabaseAccess() + self.db.connect(":memory:") + + def tearDown(self): + pass + + @classmethod + def setup_class(cls): + # before any methods in this class + pass + + @classmethod + def teardown_class(cls): + # teardown_class() after any methods in this class + pass + + def test_query_model_results_array(self): + with self.db.session() as session: + objs = setup_sample_database(session) + job = objs['j1'] + + index = {'_scan': -1, '_domain': -1, '_emit': -1, '_region': -1} + model2 = {'parA': 4.123, 'parB': 8.567, '_model': 92, '_rfac': 0.654, '_gen': 1, '_particle': 1, '_secs': 0.1} + model3 = {'parA': 3.412, 'parB': 7.856, '_model': 93, '_rfac': 0.345, '_gen': 2, '_particle': 2, '_secs': 0.2} + model4 = {'parA': 4.123, 'parB': 8.567, '_model': 94, '_rfac': 0.354, '_gen': 2, '_particle': 3, '_secs': 0.3} + model5 = {'parA': 2.341, 'parC': 6.785, '_model': 95, '_rfac': 0.453} + model6 = {'parA': 4.123, 'parB': 8.567, '_model': 96, '_rfac': 0.354, '_gen': 3, '_particle': 5, '_secs': 0.5} + model2.update(index) + model3.update(index) + model4.update(index) + model5.update(index) + model6.update(index) + m2, r2 = db_ingest.insert_result(session, job, model2, model2, model2) + m3, r3 = db_ingest.insert_result(session, job, model3, model3, model3) + m4, r4 = db_ingest.insert_result(session, job, model4, model4, model4) + m5, r5 = db_ingest.insert_result(session, job, model5, model5, model5) + m6, r6 = db_ingest.insert_result(session, job, model6, model6, model6) + session.commit() + + models = [m3, m4, m5] + result_values, result_deltas = db_query.query_model_results_array(session, models=models, include_params=True) + + template = ['parA', 'parB', 'parC', '_model', '_rfac', '_gen', '_particle', '_secs'] + dt = [(field, db_util.field_to_numpy_type(field)) for field in template] + expected = np.zeros((len(models),), dtype=dt) + expected['parA'] = np.array([3.412, 4.123, 2.341]) + expected['parB'] = np.array([7.856, 8.567, None]) + expected['parC'] = np.array([None, None, 6.785]) + expected['_model'] = np.array([93, 94, 95]) + expected['_rfac'] = np.array([0.345, 0.354, 0.453]) + expected['_gen'] = np.array([2, 2, 0]) + expected['_particle'] = np.array([2, 3, 0]) + expected['_secs'] = np.array([0.2, 0.3, None]) + + self.assertEqual(result_values.shape, expected.shape) + np.testing.assert_array_almost_equal(result_values['parA'], expected['parA']) + np.testing.assert_array_almost_equal(result_values['parB'], expected['parB']) + np.testing.assert_array_almost_equal(result_values['parC'], expected['parC']) + np.testing.assert_array_almost_equal(result_values['_model'], expected['_model']) + np.testing.assert_array_almost_equal(result_values['_gen'], expected['_gen']) + np.testing.assert_array_almost_equal(result_values['_particle'], expected['_particle']) + np.testing.assert_array_almost_equal(result_values['_rfac'], expected['_rfac']) + np.testing.assert_array_almost_equal(result_values['_secs'], expected['_secs']) + + self.assertEqual(result_deltas.shape, expected.shape) + np.testing.assert_array_almost_equal(result_deltas['parA'], expected['parA']) + np.testing.assert_array_almost_equal(result_deltas['parB'], expected['parB']) + np.testing.assert_array_almost_equal(result_deltas['parC'], expected['parC']) + np.testing.assert_array_almost_equal(result_deltas['_model'], expected['_model']) + np.testing.assert_array_almost_equal(result_deltas['_gen'], expected['_gen']) + np.testing.assert_array_almost_equal(result_deltas['_particle'], expected['_particle']) + + def test_query_model_results_array_index(self): + with self.db.session() as session: + objs = setup_sample_database(session) + job = objs['j1'] + + model = {'parA': 4.123, 'parB': 8.567, 'parC': 6.785} + + index1 = {'_model': 99, '_scan': -1, '_domain': -1, '_emit': -1, '_region': -1} + index2 = {'_model': 99, '_scan': 1, '_domain': -1, '_emit': -1, '_region': -1} + index3 = {'_model': 99, '_scan': 1, '_domain': 1, '_emit': -1, '_region': -1} + index4 = {'_model': 99, '_scan': 1, '_domain': 1, '_emit': 1, '_region': -1} + index5 = {'_model': 99, '_scan': 1, '_domain': 1, '_emit': 1, '_region': 1} + + result1 = {'_rfac': 0.154, '_gen': 1, '_particle': 1} + result1.update(model) + result2 = {'_rfac': 0.254, '_gen': 1, '_particle': 1} + result2.update(model) + result3 = {'_rfac': 0.354, '_gen': 1, '_particle': 1} + result3.update(model) + result4 = {'_rfac': 0.454, '_gen': 1, '_particle': 1} + result4.update(model) + result5 = {'_rfac': 0.554, '_gen': 1, '_particle': 1} + result5.update(model) + + m1, r1 = db_ingest.insert_result(session, job, index1, result1, result1) + m2, r2 = db_ingest.insert_result(session, job, index2, result2, result2) + m3, r3 = db_ingest.insert_result(session, job, index3, result3, result3) + m4, r4 = db_ingest.insert_result(session, job, index4, result4, result4) + m5, r5 = db_ingest.insert_result(session, job, index5, result5, result5) + session.commit() + + self.assertEqual(m1.id, m2.id) + self.assertEqual(m1.id, m3.id) + self.assertEqual(m1.id, m4.id) + self.assertEqual(m1.id, m5.id) + + result_values, result_deltas = db_query.query_model_results_array(session, + model=99, domain=1, include_params=True) + + pars = ['parA', 'parB', 'parC'] + dt = [(k, 'f8') for k in pars] + controls = ['_model', '_scan', '_domain', '_emit', '_region', '_rfac'] + dt.extend(((k, db_util.field_to_numpy_type(k)) for k in controls)) + expected = np.zeros((3,), dtype=dt) + expected['parA'] = np.array([4.123, 4.123, 4.123]) + expected['parB'] = np.array([8.567, 8.567, 8.567]) + expected['parC'] = np.array([6.785, 6.785, 6.785]) + expected['_model'] = np.array([99, 99, 99]) + expected['_scan'] = np.array([1, 1, 1]) + expected['_domain'] = np.array([1, 1, 1]) + expected['_emit'] = np.array([-1, 1, 1]) + expected['_region'] = np.array([-1, -1, 1]) + expected['_rfac'] = np.array([0.354, 0.454, 0.554]) + + self.assertEqual(result_values.shape, expected.shape) + np.testing.assert_array_almost_equal(result_values['parA'], expected['parA']) + np.testing.assert_array_almost_equal(result_values['parB'], expected['parB']) + np.testing.assert_array_almost_equal(result_values['parC'], expected['parC']) + np.testing.assert_array_almost_equal(result_values['_model'], expected['_model']) + np.testing.assert_array_almost_equal(result_values['_scan'], expected['_scan']) + np.testing.assert_array_almost_equal(result_values['_domain'], expected['_domain']) + np.testing.assert_array_almost_equal(result_values['_emit'], expected['_emit']) + np.testing.assert_array_almost_equal(result_values['_region'], expected['_region']) + np.testing.assert_array_almost_equal(result_values['_rfac'], expected['_rfac']) + + def test_query_model_results_hook(self): + with self.db.session() as session: + objs = setup_sample_database(session) + job = objs['j1'] + + index = {'_scan': -1, '_domain': -1, '_emit': -1, '_region': -1} + model2 = {'parA': 4.123, 'parB': 8.567, '_model': 92, '_rfac': 0.654, '_gen': 1, '_particle': 1} + model3 = {'parA': 3.412, 'parB': 7.856, '_model': 93, '_rfac': 0.345, '_gen': 2, '_particle': 2} + model4 = {'parA': 4.123, 'parB': 8.567, '_model': 94, '_rfac': 0.354, '_gen': 2, '_particle': 3} + model5 = {'parA': 2.341, 'parC': 6.785, '_model': 95, '_rfac': 0.453} + model6 = {'parA': 4.123, 'parB': 8.567, '_model': 96, '_rfac': 0.354, '_gen': 3, '_particle': 5} + model2.update(index) + model3.update(index) + model4.update(index) + model5.update(index) + model6.update(index) + m2, r2 = db_ingest.insert_result(session, job, model2, model2, model2) + m3, r3 = db_ingest.insert_result(session, job, model3, model3, model3) + m4, r4 = db_ingest.insert_result(session, job, model4, model4, model4) + m5, r5 = db_ingest.insert_result(session, job, model5, model5, model5) + m6, r6 = db_ingest.insert_result(session, job, model6, model6, model6) + session.commit() + + models = [m3, m4] + hd = {'gen': 2} + result_values, result_deltas = db_query.query_model_results_array(session, include_params=True, + query_hook=pop_query_hook, hook_data=hd) + + template = ['parA', 'parB', 'parC', '_model', '_rfac', '_gen', '_particle'] + dt = [(field, db_util.field_to_numpy_type(field)) for field in template] + + expected = np.zeros((len(models),), dtype=dt) + expected['parA'] = np.array([3.412, 4.123]) + expected['parB'] = np.array([7.856, 8.567]) + expected['_model'] = np.array([93, 94]) + expected['_rfac'] = np.array([0.345, 0.354]) + expected['_gen'] = np.array([2, 2]) + expected['_particle'] = np.array([2, 3]) + + self.assertEqual(result_values.shape, expected.shape) + self.assertNotIn('parC', result_values.dtype.names) + np.testing.assert_array_almost_equal(result_values['parA'], expected['parA']) + np.testing.assert_array_almost_equal(result_values['parB'], expected['parB']) + np.testing.assert_array_almost_equal(result_values['_model'], expected['_model']) + np.testing.assert_array_almost_equal(result_values['_gen'], expected['_gen']) + np.testing.assert_array_almost_equal(result_values['_particle'], expected['_particle']) + + def test_query_best_task_models(self): + with self.db.session() as session: + objs = setup_sample_database(session) + job = objs['j1'] + + model0xxx = {'_model': 0, '_scan': -1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, + '_rfac': 0.01} + model00xx = {'_model': 1, '_scan': 0, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, + '_rfac': 0.02} + model000x = {'_model': 2, '_scan': 0, '_domain': 0, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, + '_rfac': 0.03} + model01xx = {'_model': 3, '_scan': 1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, + '_rfac': 0.04} + model010x = {'_model': 4, '_scan': 1, '_domain': 0, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, + '_rfac': 0.05} + + model1xxx = {'_model': 5, '_scan': -1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4.123, + 'parB': 8.567, '_rfac': 0.09} + model10xx = {'_model': 6, '_scan': 0, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4.123, + 'parB': 8.567, '_rfac': 0.08} + model100x = {'_model': 7, '_scan': 0, '_domain': 0, '_emit': -1, '_region': -1, 'parA': 4.123, + 'parB': 8.567, '_rfac': 0.07} + model11xx = {'_model': 8, '_scan': 1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4.123, + 'parB': 8.567, '_rfac': 0.06} + model110x = {'_model': 9, '_scan': 1, '_domain': 0, '_emit': -1, '_region': -1, 'parA': 4.123, + 'parB': 8.567, '_rfac': 0.05} + + model2xxx = {'_model': 10, '_scan': -1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4.123, + 'parB': 8.567, '_rfac': 0.01} + + db_ingest.insert_result(session, job, model0xxx, model0xxx) + db_ingest.insert_result(session, job, model00xx, model00xx) + db_ingest.insert_result(session, job, model000x, model000x) + db_ingest.insert_result(session, job, model01xx, model01xx) + db_ingest.insert_result(session, job, model010x, model010x) + + db_ingest.insert_result(session, job, model1xxx, model1xxx) + db_ingest.insert_result(session, job, model10xx, model10xx) + db_ingest.insert_result(session, job, model100x, model100x) + db_ingest.insert_result(session, job, model11xx, model11xx) + db_ingest.insert_result(session, job, model110x, model110x) + + db_ingest.insert_result(session, job, model2xxx, model2xxx) + + result = db_query.query_best_task_models(session, job.id, level=1, count=2) + + expected = {0, 1, 3, 6, 8, 10} + self.assertEqual(result, expected) + + def test_query_best_models_per_job(self): + with self.db.session() as session: + objs = setup_sample_database(session) + job = objs['j2'] + + model2 = {'parA': 4.123, 'parB': 8.567, '_model': 92, '_rfac': 0.654, '_gen': 1, '_particle': 2} + model3 = {'parA': 3.412, 'parB': 7.856, '_model': 93, '_rfac': 0.345, '_gen': 1, '_particle': 3} + model4 = {'parA': 4.123, 'parB': 8.567, '_model': 94, '_rfac': 0.354, '_gen': 1, '_particle': 4} + model5 = {'parA': 2.341, 'parC': 6.785, '_model': 95, '_rfac': 0.453, '_gen': 1, '_particle': 5} + model6 = {'parA': 4.123, 'parB': 8.567, '_model': 96, '_rfac': 0.354, '_gen': 1, '_particle': 6} + model7 = {'parA': 5.123, 'parB': 6.567, '_model': 97, '_rfac': 0.154, '_gen': 1, '_particle': 7} + + model2.update({'_scan': -1, '_domain': -1, '_emit': -1, '_region': -1}) + model3.update({'_scan': 1, '_domain': -1, '_emit': -1, '_region': -1}) + model4.update({'_scan': 2, '_domain': 11, '_emit': 23, '_region': 33}) + model5.update({'_scan': 3, '_domain': 11, '_emit': -1, '_region': -1}) + model6.update({'_scan': 4, '_domain': 11, '_emit': 25, '_region': -1}) + model7.update({'_scan': 5, '_domain': -1, '_emit': -1, '_region': -1}) + m2, r2 = db_ingest.insert_result(session, job, model2, model2) + m3, r3 = db_ingest.insert_result(session, job, model3, model3) + m4, r4 = db_ingest.insert_result(session, job, model4, model4) + m5, r5 = db_ingest.insert_result(session, job, model5, model5) + m6, r6 = db_ingest.insert_result(session, job, model6, model6) + m7, r7 = db_ingest.insert_result(session, job, model7, model7) + + lim = 3 + query = db_query.query_best_models_per_job(session, task_level='domain', limit=lim) + expected_models = [91, 97] + self.assertEqual(len(query), len(expected_models)) + for model, result in query: + self.assertIn(model.model, expected_models) + + lim = 3 + query = db_query.query_best_models_per_job(session, jobs=[job], task_level='domain', limit=lim) + expected_models = [97] + self.assertEqual(len(query), len(expected_models)) + for model, result in query: + self.assertIn(model.model, expected_models) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/database/test_util.py b/tests/database/test_util.py new file mode 100644 index 0000000..a5481d2 --- /dev/null +++ b/tests/database/test_util.py @@ -0,0 +1,83 @@ +""" +@package tests.test_database +unit tests for pmsco.database + +the purpose of these tests is to help debugging the code. + +to run the tests, change to the directory which contains the tests directory, and execute =nosetests=. + +@pre nose must be installed (python-nose package on Debian). + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2016 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import numpy as np +import unittest +import pmsco.database.util as util +import pmsco.dispatch as dispatch + + +class TestDatabase(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + @classmethod + def setup_class(cls): + # before any methods in this class + pass + + @classmethod + def teardown_class(cls): + # teardown_class() after any methods in this class + pass + + def test_regular_params(self): + d1 = {'parA': 1.234, 'par_B': 5.678, '_model': 91, '_rfac': 0.534} + d2 = util.regular_params(d1) + d3 = {'parA': d1['parA'], 'par_B': d1['par_B']} + self.assertEqual(d2, d3) + self.assertIsNot(d2, d1) + + def test_special_params(self): + d1 = {'parA': 1.234, 'par_B': 5.678, '_model': 91, '_rfac': 0.534, '_db_model_id': 99} + d2 = util.special_params(d1) + d3 = {'model': d1['_model'], 'rfac': d1['_rfac']} + self.assertEqual(d2, d3) + self.assertIsNot(d2, d1) + + dt = [('parA', 'f4'), ('par_B', 'f4'), ('_model', 'i4'), ('_rfac', 'f4'), ('_db_model_id', 'f4')] + arr = np.zeros(1, dtype=dt) + for k, v in d1.items(): + arr[0][k] = v + d4 = util.special_params(arr[0]) + self.assertEqual(d4.keys(), d3.keys()) + for k in d4: + self.assertAlmostEqual(d4[k], d3[k]) + + cid1 = dispatch.CalcID(1, 2, 3, 4, -1) + cid2 = util.special_params(cid1) + cid3 = {'model': 1, 'scan': 2, 'domain': 3, 'emit': 4, 'region': -1} + self.assertEqual(cid2, cid3) + + l1 = d1.keys() + l2 = util.special_params(l1) + l3 = d3.keys() + self.assertEqual(list(l2), list(l3)) + + t1 = tuple(l1) + t2 = util.special_params(t1) + t3 = tuple(l3) + self.assertEqual(t2, t3) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/reports/test_results.py b/tests/reports/test_results.py new file mode 100644 index 0000000..c709efa --- /dev/null +++ b/tests/reports/test_results.py @@ -0,0 +1,71 @@ +import datetime +import numpy as np +from pathlib import Path +import unittest +import pmsco.database.access as db_access +import pmsco.database.orm as db_orm +import pmsco.reports.results as rp_results +import pmsco.dispatch as dispatch + + +def setup_sample_database(session): + p1 = db_orm.Project(name="oldproject", code="oldcode") + p2 = db_orm.Project(name="unittest", code="testcode") + j1 = db_orm.Job(project=p1, name="oldjob", mode="oldmode", machine="oldhost", datetime=datetime.datetime.now()) + j2 = db_orm.Job(project=p2, name="testjob", mode="testmode", machine="testhost", datetime=datetime.datetime.now()) + pk1 = db_orm.Param(key='parA') + pk2 = db_orm.Param(key='parB') + pk3 = db_orm.Param(key='parC') + m1 = db_orm.Model(job=j1, model=91) + m2 = db_orm.Model(job=j2, model=92) + r1 = db_orm.Result(calc_id=dispatch.CalcID(91, -1, -1, -1, -1), rfac=0.534, secs=37.9) + r1.model = m1 + pv1 = db_orm.ParamValue(model=m1, param=pk1, value=1.234, delta=0.1234) + pv2 = db_orm.ParamValue(model=m1, param=pk2, value=5.678, delta=-0.5678) + pv3 = db_orm.ParamValue(model=m2, param=pk3, value=6.785, delta=0.6785) + objects = {'p1': p1, 'p2': p2, 'j1': j1, 'j2': j2, 'm1': m1, 'm2': m2, 'r1': r1, + 'pv1': pv1, 'pv2': pv2, 'pv3': pv3, 'pk1': pk1, 'pk2': pk2, 'pk3': pk3} + session.add_all(objects.values()) + session.commit() + return objects + + +class TestResultsMethods(unittest.TestCase): + def test_array_range(self): + dtype = [('A', 'f8'), ('B', 'f8'), ('C', 'f8')] + data = np.array([(1.5, 3.5, 3.5), + (1.6, 2.6, 2.6), + (1.7, 2.7, 3.7), + (1.8, 2.8, 3.8)], dtype=dtype) + exp_rmin = {'A': 1.5, 'B': 2.6, 'C': 2.6} + exp_rmax = {'A': 1.8, 'B': 3.5, 'C': 3.8} + rmin, rmax = rp_results.array_range(data) + self.assertEqual(exp_rmin, rmin) + self.assertEqual(exp_rmax, rmax) + + +class TestResultData(unittest.TestCase): + def setUp(self): + self.db = db_access.DatabaseAccess() + self.db.connect(":memory:") + + def test_update_collections(self): + data_dir = Path(__file__).parent.parent + data_file = data_dir / "test_swarm.setup_with_results.1.dat" + raw_values = np.atleast_1d(np.genfromtxt(data_file, names=True)) + + rd = rp_results.ResultData() + rd.values = raw_values + rd.update_collections() + np.testing.assert_array_equal(rd.generations, np.array((1, 2, 3, 4))) + + def test_load_from_db(self): + with self.db.session() as session: + setup_sample_database(session) + rd = rp_results.ResultData() + rd.levels = {'scan': -1} + rd.load_from_db(session) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/reports/test_rfactor.py b/tests/reports/test_rfactor.py new file mode 100644 index 0000000..a4e4319 --- /dev/null +++ b/tests/reports/test_rfactor.py @@ -0,0 +1,69 @@ +import numpy as np +import unittest +import pmsco.reports.rfactor as rp_rfactor + + +class TestGridMethods(unittest.TestCase): + def test_triplet_to_grid__basic(self): + x = np.array([-1, 0, 1, 1, 0, -1]) + y = np.array([2, 2, 2, 3, 3, 3]) + z = np.array([0.1, 0.2, 0.3, 0.6, 0.5, 0.4]) + gx, gy, gz = rp_rfactor.triplet_to_grid(x, y, z) + expected_gx = np.array([[-1, 0, 1], [-1, 0, 1]]).T + expected_gy = np.array([[2, 2, 2], [3, 3, 3]]).T + expected_gz = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]).T + np.testing.assert_array_almost_equal(gx, expected_gx, 1, "grid_x") + np.testing.assert_array_almost_equal(gy, expected_gy, 1, "grid_y") + np.testing.assert_array_almost_equal(gz, expected_gz, 2, "grid_z") + + def test_triplet_to_grid__imprecise(self): + x = np.array([-0.99, 0, 1, 1.001, 0, -1]) + y = np.array([1.999, 2.00001, 2, 3.01, 2.98, 3]) + z = np.array([0.1, 0.2, 0.3, 0.6, 0.5, 0.4]) + gx, gy, gz = rp_rfactor.triplet_to_grid(x, y, z) + expected_gx = np.array([[-1, 0, 1], [-1, 0, 1]]).T + expected_gy = np.array([[2, 2, 2], [3, 3, 3]]).T + expected_gz = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]).T + np.testing.assert_array_almost_equal(gx, expected_gx, 1, "grid_x") + np.testing.assert_array_almost_equal(gy, expected_gy, 1, "grid_y") + np.testing.assert_array_almost_equal(gz, expected_gz, 2, "grid_z") + + def test_triplet_to_grid__missing(self): + x = np.array([-1, 0, 1, 0, -1]) + y = np.array([2, 2, 3, 3, 3]) + z = np.array([0.1, 0.2, 0.6, 0.5, 0.4]) + gx, gy, gz = rp_rfactor.triplet_to_grid(x, y, z) + expected_gx = np.array([[-1, 0, 1], [-1, 0, 1]]).T + expected_gy = np.array([[2, 2, 2], [3, 3, 3]]).T + expected_gz = np.array([[0.1, 0.2, 0.2], [0.4, 0.5, 0.6]]).T + np.testing.assert_array_almost_equal(gx, expected_gx, 1, "grid_x") + np.testing.assert_array_almost_equal(gy, expected_gy, 1, "grid_y") + np.testing.assert_array_almost_equal(gz, expected_gz, 2, "grid_z") + + def test_triplet_to_grid__extra(self): + x = np.array([-1, 0, 1, 1, 1, 0, -1]) + y = np.array([2, 2, 2, 2.01, 3, 3, 3]) + z = np.array([0.1, 0.2, 0.3, 0.35, 0.6, 0.5, 0.4]) + gx, gy, gz = rp_rfactor.triplet_to_grid(x, y, z) + expected_gx = np.array([[-1, 0, 1], [-1, 0, 1]]).T + expected_gy = np.array([[2, 2, 2], [3, 3, 3]]).T + expected_gz = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]).T + np.testing.assert_array_almost_equal(gx, expected_gx, 1, "grid_x") + np.testing.assert_array_almost_equal(gy, expected_gy, 1, "grid_y") + np.testing.assert_array_almost_equal(gz, expected_gz, 2, "grid_z") + + def test_triplet_to_grid__split_column(self): + x = np.array([-1, 0, 0.5, 1, 1, 0.5, 0, -1]) + y = np.array([2, 2, 2, 2, 3, 3, 3, 3]) + z = np.array([0.1, 0.2, 0.24, 0.3, 0.6, 0.45, 0.5, 0.4]) + gx, gy, gz = rp_rfactor.triplet_to_grid(x, y, z) + expected_gx = np.array([[-1, -0.5, 0, 0.5, 1], [-1, -0.5, 0, 0.5, 1]]).T + expected_gy = np.array([[2, 2, 2, 2, 2], [3, 3, 3, 3, 3]]).T + expected_gz = np.array([[0.1, 0.1, 0.2, 0.24, 0.3], [0.4, 0.5, 0.5, 0.45, 0.6]]).T + np.testing.assert_array_almost_equal(gx, expected_gx, 1, "grid_x") + np.testing.assert_array_almost_equal(gy, expected_gy, 1, "grid_y") + np.testing.assert_array_almost_equal(gz, expected_gz, 2, "grid_z") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cluster.py b/tests/test_cluster.py index 9dc61f0..b5305fc 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -459,9 +459,9 @@ class TestClusterFunctions(unittest.TestCase): line = f.readline() self.assertEqual(line, b"# index element symbol class x y z emitter charge\n", b"line 1: " + line) line = f.readline() - self.assertRegexpMatches(line, b"[0-9]+ +1 +H +[0-9]+ +[0.]+ +[0.]+ +[0.]+ +1 +[0.]", b"line 3: " + line) + self.assertRegex(line, b"[0-9]+ +1 +H +[0-9]+ +[0.]+ +[0.]+ +[0.]+ +1 +[0.]", b"line 3: " + line) line = f.readline() - self.assertRegexpMatches(line, b"[0-9]+ +14 +Si +[0-9]+ +[01.-]+ +[01.-]+ +[0.]+ +1 +[0.]", b"line 4: " + line) + self.assertRegex(line, b"[0-9]+ +14 +Si +[0-9]+ +[01.-]+ +[01.-]+ +[0.]+ +1 +[0.]", b"line 4: " + line) line = f.readline() self.assertEqual(b"", line, b"end of file") @@ -473,9 +473,9 @@ class TestClusterFunctions(unittest.TestCase): line = f.readline() self.assertEqual(b"qwerty\n", line, b"line 2: " + line) line = f.readline() - self.assertRegexpMatches(line, b"H +[0.]+ +[0.]+ +[0.]+", b"line 3: " + line) + self.assertRegex(line, b"H +[0.]+ +[0.]+ +[0.]+", b"line 3: " + line) line = f.readline() - self.assertRegexpMatches(line, b"Si +[01.-]+ +[01.-]+ +[0.]+", b"line 4: " + line) + self.assertRegex(line, b"Si +[01.-]+ +[01.-]+ +[0.]+", b"line 4: " + line) line = f.readline() self.assertEqual(b"", line, b"end of file") diff --git a/tests/test_data.py b/tests/test_data.py index 7413495..c382d6b 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -20,12 +20,11 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import unittest import math import numpy as np +from pathlib import Path +import unittest + import pmsco.data as md @@ -140,6 +139,50 @@ class TestDataFunctions(unittest.TestCase): for dim in expected_positions: np.testing.assert_almost_equal(scan_positions[dim], expected_positions[dim], decimal=3) + def test_holo_array(self): + args = { + "theta_start": 90, + "theta_step": 1, + "theta_range": 90, + "phi_start": 0, + "phi_range": 360, + "phi_refinement": 1 + } + + result = md.holo_array(generator=md.holo_grid, generator_args=args, datatype="ETPI") + result['e'] = 250. + md.sort_data(result) + + ref_path = Path(__file__).parent.parent / "pmsco" / "projects" / "twoatom" / "twoatom_hemi_250e.etpi" + ref_array = md.load_data(ref_path) + np.testing.assert_array_almost_equal(result['t'], ref_array['t'], decimal=1) + np.testing.assert_array_almost_equal(result['p'], ref_array['p'], decimal=1) + + def test_analyse_holoscan_steps(self): + args = { + "theta_start": 90.0, + "theta_step": 2.0, + "theta_range": 90.0, + "phi_start": 0.0, + "phi_range": 120.0, + "phi_refinement": 1.0 + } + + holo = md.holo_array(generator=md.holo_grid, generator_args=args, datatype="TPI") + theta, dtheta, dphi = md.analyse_holoscan_steps(holo) + + expected_theta = np.arange(args["theta_start"] - args["theta_range"], + args["theta_start"] + args["theta_step"], + args["theta_step"]) + expected_dtheta = np.ones_like(expected_theta) * args["theta_step"] + + np.testing.assert_almost_equal(theta, expected_theta) + np.testing.assert_almost_equal(dtheta, expected_dtheta) + self.assertEqual(expected_theta.shape, dphi.shape) + self.assertEqual(args["phi_range"], dphi[0]) + self.assertEqual(args["theta_step"], dphi[-1]) + np.testing.assert_array_less(np.ones_like(expected_theta) * args["theta_step"] * 0.999, dphi) + def test_calc_modfunc_mean_1d(self): modf = md.calc_modfunc_mean(self.e_scan) @@ -176,6 +219,11 @@ class TestDataFunctions(unittest.TestCase): """ check that the result of msc_data.calc_modfunc_loess() is between -1 and 1. """ + + # loess package not available + if md.loess is None: + return + modf = md.calc_modfunc_loess(self.e_scan) self.assertEqual(self.e_scan.shape, modf.shape) exp_modf = self.e_scan.copy() @@ -190,6 +238,11 @@ class TestDataFunctions(unittest.TestCase): """ check that data.calc_modfunc_loess() ignores NaNs gracefully. """ + + # loess package not available + if md.loess is None: + return + modified_index = 2 self.e_scan['i'][modified_index] = np.nan modf = md.calc_modfunc_loess(self.e_scan) @@ -207,6 +260,11 @@ class TestDataFunctions(unittest.TestCase): """ check that the msc_data.calc_modfunc_loess() function does approximately what we want for a two-dimensional dataset. """ + + # loess package not available + if md.loess is None: + return + n_e = 10 n_a = 15 shape = (n_e * n_a, ) @@ -236,8 +294,7 @@ class TestDataFunctions(unittest.TestCase): # this is rough estimate of the result, manually optimized by trial and error in Igor. # the R factor should be sensitive enough to detect mixed-up axes. exp_modf['i'] = 0.03 * np.sin((scan['e'] - 150) / 50 * math.pi) - rf = md.rfactor(modf, exp_modf) - print(rf) + rf = md.square_diff_rfactor(modf, exp_modf) self.assertLessEqual(rf, 0.50) def test_alpha_mirror_average(self): @@ -377,11 +434,11 @@ class TestDataFunctions(unittest.TestCase): weights = np.ones_like(exp_modf['i']) - r = md.scaled_rfactor(1.4, exp_modf['i'], weights, calc_modf['i']) + r = md.scaled_rfactor_func(1.4, exp_modf['i'], weights, calc_modf['i']) self.assertGreater(r, 0.0) self.assertLess(r, 0.05) - def test_rfactor(self): + def test_square_diff_rfactor(self): n = 20 calc_modf = md.create_data((n,), dtype=md.DTYPE_ETPI) calc_modf['e'] = 0.0 @@ -393,18 +450,18 @@ class TestDataFunctions(unittest.TestCase): exp_modf['i'] = 0.6 * np.sin(exp_modf['p'] + np.pi / 100.0) exp_modf['s'] = np.sqrt(np.abs(exp_modf['i'])) - r1 = md.rfactor(exp_modf, calc_modf) + r1 = md.square_diff_rfactor(exp_modf, calc_modf) self.assertAlmostEqual(r1, 0.95, delta=0.02) # one nan should not make a big difference calc_modf['i'][3] = np.nan - r2 = md.rfactor(exp_modf, calc_modf) + r2 = md.square_diff_rfactor(exp_modf, calc_modf) self.assertAlmostEqual(r1, r2, delta=0.02) # all values nan should raise an exception with self.assertRaises(ValueError): calc_modf['i'] = np.nan - md.rfactor(exp_modf, calc_modf) + md.square_diff_rfactor(exp_modf, calc_modf) def test_optimize_rfactor(self): n = 20 diff --git a/tests/test_database.py b/tests/test_database.py deleted file mode 100644 index 71eaa38..0000000 --- a/tests/test_database.py +++ /dev/null @@ -1,570 +0,0 @@ -""" -@package tests.test_database -unit tests for pmsco.database - -the purpose of these tests is to help debugging the code. - -to run the tests, change to the directory which contains the tests directory, and execute =nosetests=. - -@pre nose must be installed (python-nose package on Debian). - -@author Matthias Muntwiler, matthias.muntwiler@psi.ch - -@copyright (c) 2016 by Paul Scherrer Institut @n -Licensed under the Apache License, Version 2.0 (the "License"); @n - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import unittest -import datetime -import os.path -import tempfile -import shutil -import numpy as np -import pmsco.database as db -import pmsco.dispatch as dispatch -import pmsco.optimizers.population as population - - -class TestDatabase(unittest.TestCase): - def setUp(self): - self.test_dir = tempfile.mkdtemp() - self.lock_filename = os.path.join(self.test_dir, "test_database.lock") - self.db = db.ResultsDatabase() - self.db.connect(":memory:", lock_filename=self.lock_filename) - - def tearDown(self): - self.db.disconnect() - shutil.rmtree(self.test_dir) - - @classmethod - def setup_class(cls): - # before any methods in this class - pass - - @classmethod - def teardown_class(cls): - # teardown_class() after any methods in this class - pass - - def test_regular_params(self): - d1 = {'parA': 1.234, 'par_B': 5.678, '_model': 91, '_rfac': 0.534} - d2 = db.regular_params(d1) - d3 = {'parA': d1['parA'], 'par_B': d1['par_B']} - self.assertEqual(d2, d3) - self.assertIsNot(d2, d1) - - def test_special_params(self): - d1 = {'parA': 1.234, 'par_B': 5.678, '_model': 91, '_rfac': 0.534, '_db_model': 99} - d2 = db.special_params(d1) - d3 = {'model': d1['_model'], 'rfac': d1['_rfac']} - self.assertEqual(d2, d3) - self.assertIsNot(d2, d1) - - dt = [('parA', 'f4'), ('par_B', 'f4'), ('_model', 'i4'), ('_rfac', 'f4'), ('_db_model', 'f4')] - arr = np.zeros(1, dtype=dt) - for k, v in d1.items(): - arr[0][k] = v - d4 = db.special_params(arr[0]) - self.assertEqual(d4.keys(), d3.keys()) - for k in d4: - self.assertAlmostEqual(d4[k], d3[k]) - - cid1 = dispatch.CalcID(1, 2, 3, 4, -1) - cid2 = db.special_params(cid1) - cid3 = {'model': 1, 'scan': 2, 'domain': 3, 'emit': 4, 'region': -1} - self.assertEqual(cid2, cid3) - - l1 = d1.keys() - l2 = db.special_params(l1) - l3 = d3.keys() - self.assertEqual(list(l2), list(l3)) - - t1 = tuple(l1) - t2 = db.special_params(t1) - t3 = tuple(l3) - self.assertEqual(t2, t3) - - def setup_sample_database(self): - self.db.register_project("oldproject", "oldcode") - self.db.register_project("unittest", "testcode") - self.db.register_job(self.db.project_id, "testjob", "testmode", "testhost", None, datetime.datetime.now()) - self.ex_model = {'parA': 1.234, 'parB': 5.678, '_model': 91, '_rfac': 0.534} - self.db.register_params(self.ex_model) - self.db.insert_model(self.ex_model) - self.db.create_models_view() - - def test_register_project(self): - id1 = self.db.register_project("unittest1", "Atest") - self.assertIsInstance(id1, int) - self.assertEqual(id1, self.db.project_id) - id2 = self.db.register_project("unittest2", "Btest") - self.assertIsInstance(id2, int) - self.assertEqual(id2, self.db.project_id) - id3 = self.db.register_project("unittest1", "Ctest") - self.assertIsInstance(id3, int) - self.assertEqual(id3, self.db.project_id) - self.assertNotEqual(id1, id2) - self.assertEqual(id1, id3) - c = self.db._conn.cursor() - c.execute("select count(*) from Projects") - count = c.fetchone() - self.assertEqual(count[0], 2) - c.execute("select name, code from Projects where id=:id", {'id': id1}) - row = c.fetchone() - self.assertIsNotNone(row) - self.assertEqual(len(row), 2) - self.assertEqual(row[0], "unittest1") - self.assertEqual(row[1], "Atest") - self.assertEqual(row['name'], "unittest1") - self.assertEqual(row['code'], "Atest") - - def test_register_job(self): - pid1 = self.db.register_project("unittest1", "Acode") - pid2 = self.db.register_project("unittest2", "Bcode") - dt1 = datetime.datetime.now() - - # insert new job - id1 = self.db.register_job(pid1, "Ajob", "Amode", "local", "Ahash", dt1, "Adesc") - self.assertIsInstance(id1, int) - self.assertEqual(id1, self.db.job_id) - # insert another job - id2 = self.db.register_job(pid1, "Bjob", "Amode", "local", "Ahash", dt1, "Adesc") - self.assertIsInstance(id2, int) - self.assertEqual(id2, self.db.job_id) - # update first job - id3 = self.db.register_job(pid1, "Ajob", "Cmode", "local", "Chash", dt1, "Cdesc") - self.assertIsInstance(id3, int) - self.assertEqual(id3, self.db.job_id) - # insert another job with same name but in other project - id4 = self.db.register_job(pid2, "Ajob", "Dmode", "local", "Dhash", dt1, "Ddesc") - self.assertIsInstance(id4, int) - self.assertEqual(id4, self.db.job_id) - - self.assertNotEqual(id1, id2) - self.assertEqual(id1, id3) - self.assertNotEqual(id1, id4) - - c = self.db._conn.cursor() - c.execute("select count(*) from Jobs") - count = c.fetchone() - self.assertEqual(count[0], 3) - c.execute("select name, mode, machine, git_hash, datetime, description from Jobs where id=:id", {'id': id1}) - row = c.fetchone() - self.assertIsNotNone(row) - self.assertEqual(len(row), 6) - self.assertEqual(row[0], "Ajob") - self.assertEqual(row[1], "Amode") - self.assertEqual(row['machine'], "local") - self.assertEqual(str(row['datetime']), str(dt1)) - self.assertEqual(row['git_hash'], "Ahash") - self.assertEqual(row['description'], "Adesc") - - def test_register_params(self): - self.setup_sample_database() - model5 = {'parA': 2.341, 'parC': 6.785, '_model': 92, '_rfac': 0.453} - self.db.register_params(model5) - expected = ['parA', 'parB', 'parC'] - - c = self.db._conn.cursor() - c.execute("select * from Params order by key") - results = c.fetchall() - self.assertEqual(len(results), 3) - result_params = [row['key'] for row in results] - self.assertEqual(result_params, expected) - - def test_query_project_params(self): - self.setup_sample_database() - project1 = self.db.project_id - self.db.register_project("unittest2", "testcode2") - self.db.register_job(self.db.project_id, "testjob2", "test", "localhost", None, datetime.datetime.now()) - model5 = {'parA': 2.341, 'parC': 6.785, '_model': 92, '_rfac': 0.453} - self.db.register_params(model5) - self.db.insert_model(model5) - results = self.db.query_project_params(project_id=project1) - expected = ['parA', 'parB'] - self.assertEqual(expected, sorted(list(results.keys()))) - - def test_insert_model(self): - self.setup_sample_database() - c = self.db._conn.cursor() - c.execute("select count(*) from Models") - count = c.fetchone() - self.assertEqual(count[0], 1) - c.execute("select * from Models") - row = c.fetchone() - model_id = row['id'] - self.assertIsInstance(model_id, int) - self.assertEqual(row['job_id'], self.db.job_id) - self.assertEqual(row['model'], self.ex_model['_model']) - self.assertIsNone(row['gen']) - self.assertIsNone(row['particle']) - sql = "select key, value from ParamValues " + \ - "join Params on ParamValues.param_id = Params.id " + \ - "where model_id = :model_id" - c.execute(sql, {'model_id': model_id}) - result = c.fetchall() # list of Row objects - self.assertEqual(len(result), 2) - for row in result: - self.assertAlmostEqual(row['value'], self.ex_model[row['key']]) - - def test_query_model(self): - self.setup_sample_database() - c = self.db._conn.cursor() - c.execute("select * from Models") - row = c.fetchone() - model_id = row['id'] - model = self.db.query_model(model_id) - del self.ex_model['_model'] - del self.ex_model['_rfac'] - self.assertEqual(model, self.ex_model) - - def test_query_model_array(self): - self.setup_sample_database() - index = {'_scan': -1, '_domain': -1, '_emit': -1, '_region': -1} - model2 = {'parA': 4.123, 'parB': 8.567, '_model': 92, '_rfac': 0.654} - model3 = {'parA': 3.412, 'parB': 7.856, '_model': 93, '_rfac': 0.345} - model4 = {'parA': 4.123, 'parB': 8.567, '_model': 94, '_rfac': 0.354} - model5 = {'parA': 2.341, 'parC': 6.785, '_model': 95, '_rfac': 0.453} - model6 = {'parA': 4.123, 'parB': 8.567, '_model': 96, '_rfac': 0.354} - self.db.register_params(model5) - self.db.create_models_view() - model2.update(index) - model3.update(index) - model4.update(index) - model5.update(index) - model6.update(index) - self.db.insert_result(model2, model2) - self.db.insert_result(model3, model3) - self.db.insert_result(model4, model4) - self.db.insert_result(model5, model5) - self.db.insert_result(model6, model6) - - # only model3, model4 and model5 fulfill all conditions and limits - fil = ['mode = "testmode"', 'rfac <= 0.6'] - lim = 3 - result = self.db.query_model_array(filter=fil, limit=lim) - - template = ['parA', 'parB', 'parC', '_model', '_rfac', '_gen', '_particle'] - dt = population.Population.get_pop_dtype(template) - expected = np.zeros((lim,), dtype=dt) - expected['parA'] = np.array([3.412, 4.123, 2.341]) - expected['parB'] = np.array([7.856, 8.567, None]) - expected['parC'] = np.array([None, None, 6.785]) - expected['_model'] = np.array([93, 94, 95]) - expected['_rfac'] = np.array([0.345, 0.354, 0.453]) - expected['_gen'] = np.array([0, 0, 0]) - expected['_particle'] = np.array([0, 0, 0]) - - self.assertEqual(result.shape, expected.shape) - np.testing.assert_array_almost_equal(result['parA'], expected['parA']) - np.testing.assert_array_almost_equal(result['parB'], expected['parB']) - np.testing.assert_array_almost_equal(result['parC'], expected['parC']) - np.testing.assert_array_almost_equal(result['_model'], expected['_model']) - np.testing.assert_array_almost_equal(result['_gen'], expected['_gen']) - np.testing.assert_array_almost_equal(result['_particle'], expected['_particle']) - - def test_query_best_results(self): - self.setup_sample_database() - model2 = {'parA': 4.123, 'parB': 8.567, '_model': 92, '_rfac': 0.654, '_gen': 1, '_particle': 2} - model3 = {'parA': 3.412, 'parB': 7.856, '_model': 93, '_rfac': 0.345, '_gen': 1, '_particle': 3} - model4 = {'parA': 4.123, 'parB': 8.567, '_model': 94, '_rfac': 0.354, '_gen': 1, '_particle': 4} - model5 = {'parA': 2.341, 'parC': 6.785, '_model': 95, '_rfac': 0.453, '_gen': 1, '_particle': 5} - model6 = {'parA': 4.123, 'parB': 8.567, '_model': 96, '_rfac': 0.354, '_gen': 1, '_particle': 6} - model7 = {'parA': 5.123, 'parB': 6.567, '_model': 97, '_rfac': 0.154, '_gen': 1, '_particle': 7} - self.db.register_params(model5) - self.db.create_models_view() - model2.update({'_scan': -1, '_domain': 11, '_emit': 21, '_region': 31}) - model3.update({'_scan': 1, '_domain': 12, '_emit': 22, '_region': 32}) - model4.update({'_scan': 2, '_domain': 11, '_emit': 23, '_region': 33}) - model5.update({'_scan': 3, '_domain': 11, '_emit': 24, '_region': 34}) - model6.update({'_scan': 4, '_domain': 11, '_emit': 25, '_region': 35}) - model7.update({'_scan': 5, '_domain': -1, '_emit': -1, '_region': -1}) - self.db.insert_result(model2, model2) - self.db.insert_result(model3, model3) - self.db.insert_result(model4, model4) - self.db.insert_result(model5, model5) - self.db.insert_result(model6, model6) - self.db.insert_result(model7, model7) - - # only model3, model4 and model5 fulfill all conditions and limits - fil = ['mode = "testmode"', 'domain = 11'] - lim = 3 - result = self.db.query_best_results(filter=fil, limit=lim) - - ifields = ['_db_job', '_db_model', '_db_result', - '_model', '_scan', '_domain', '_emit', '_region', - '_gen', '_particle'] - ffields = ['_rfac'] - dt = [(f, 'i8') for f in ifields] - dt.extend([(f, 'f8') for f in ffields]) - expected = np.zeros((lim,), dtype=dt) - expected['_rfac'] = np.array([0.354, 0.354, 0.453]) - expected['_model'] = np.array([94, 96, 95]) - expected['_scan'] = np.array([2, 4, 3]) - expected['_domain'] = np.array([11, 11, 11]) - expected['_emit'] = np.array([23, 25, 24]) - expected['_region'] = np.array([33, 35, 34]) - expected['_gen'] = np.array([1, 1, 1]) - expected['_particle'] = np.array([4, 6, 5]) - - self.assertEqual(result.shape, expected.shape) - np.testing.assert_array_almost_equal(result['_rfac'], expected['_rfac']) - np.testing.assert_array_equal(result['_model'], expected['_model']) - np.testing.assert_array_equal(result['_scan'], expected['_scan']) - np.testing.assert_array_equal(result['_domain'], expected['_domain']) - np.testing.assert_array_equal(result['_emit'], expected['_emit']) - np.testing.assert_array_equal(result['_region'], expected['_region']) - np.testing.assert_array_equal(result['_gen'], expected['_gen']) - np.testing.assert_array_equal(result['_particle'], expected['_particle']) - - def test_insert_result(self): - self.setup_sample_database() - index = dispatch.CalcID(15, 16, 17, 18, -1) - result = {'parA': 4.123, 'parB': 8.567, '_rfac': 0.654, '_particle': 21} - result_id = self.db.insert_result(index, result) - - c = self.db._conn.cursor() - c.execute("select count(*) from Results") - count = c.fetchone() - self.assertEqual(count[0], 1) - - c.execute("select * from Results") - row = c.fetchone() - self.assertIsInstance(row['id'], int) - self.assertEqual(row['id'], result_id) - model_id = row['model_id'] - self.assertIsInstance(model_id, int) - self.assertEqual(row['scan'], index.scan) - self.assertEqual(row['domain'], index.domain) - self.assertEqual(row['emit'], index.emit) - self.assertEqual(row['region'], index.region) - self.assertEqual(row['rfac'], result['_rfac']) - - c.execute("select * from Models where id = :model_id", {'model_id': model_id}) - row = c.fetchone() - model_id = row['id'] - self.assertIsInstance(model_id, int) - self.assertEqual(row['job_id'], self.db.job_id) - self.assertEqual(row['model'], index.model) - self.assertIsNone(row['gen']) - self.assertEqual(row['particle'], result['_particle']) - - sql = "select key, value from ParamValues " + \ - "join Params on ParamValues.param_id = Params.id " + \ - "where model_id = :model_id" - c.execute(sql, {'model_id': model_id}) - rows = c.fetchall() # list of Row objects - self.assertEqual(len(rows), 2) - for row in rows: - self.assertAlmostEqual(row['value'], result[row['key']]) - - def test_update_result(self): - self.setup_sample_database() - index = dispatch.CalcID(15, 16, 17, 18, -1) - result1 = {'parA': 4.123, 'parB': 8.567, '_rfac': 0.654, '_particle': 21} - result_id1 = self.db.insert_result(index, result1) - result2 = {'parA': 5.456, '_rfac': 0.254, '_particle': 11} - result_id2 = self.db.insert_result(index, result2) - result3 = result1.copy() - result3.update(result2) - self.assertEqual(result_id1, result_id2) - - c = self.db._conn.cursor() - c.execute("select count(*) from Results") - count = c.fetchone() - self.assertEqual(count[0], 1) - - c.execute("select * from Results") - row = c.fetchone() - self.assertIsInstance(row['id'], int) - self.assertEqual(row['id'], result_id1) - model_id = row['model_id'] - self.assertIsInstance(model_id, int) - self.assertEqual(row['scan'], index.scan) - self.assertEqual(row['domain'], index.domain) - self.assertEqual(row['emit'], index.emit) - self.assertEqual(row['region'], index.region) - self.assertEqual(row['rfac'], result2['_rfac']) - - c.execute("select * from Models where id = :model_id", {'model_id': model_id}) - row = c.fetchone() - model_id = row['id'] - self.assertIsInstance(model_id, int) - self.assertEqual(row['job_id'], self.db.job_id) - self.assertEqual(row['model'], index.model) - self.assertIsNone(row['gen']) - self.assertEqual(row['particle'], result2['_particle']) - - sql = "select key, value from ParamValues " + \ - "join Params on ParamValues.param_id = Params.id " + \ - "where model_id = :model_id" - c.execute(sql, {'model_id': model_id}) - rows = c.fetchall() # list of Row objects - self.assertEqual(len(rows), 2) - for row in rows: - self.assertAlmostEqual(row['value'], result3[row['key']]) - - def test_update_result_dict(self): - """ - test update result with index as dictionary - - @return: - """ - self.setup_sample_database() - index = {'_model': 15, '_scan': 16, '_domain': 17, '_emit': 18, '_region': -1} - result1 = {'parA': 4.123, 'parB': 8.567, '_rfac': 0.654, '_particle': 21} - result_id1 = self.db.insert_result(index, result1) - result2 = {'parA': 5.456, '_rfac': 0.254, '_particle': 11} - result_id2 = self.db.insert_result(index, result2) - result3 = result1.copy() - result3.update(result2) - self.assertEqual(result_id1, result_id2) - - c = self.db._conn.cursor() - c.execute("select count(*) from Results") - count = c.fetchone() - self.assertEqual(count[0], 1) - - c.execute("select * from Results") - row = c.fetchone() - self.assertIsInstance(row['id'], int) - self.assertEqual(row['id'], result_id1) - model_id = row['model_id'] - self.assertIsInstance(model_id, int) - self.assertEqual(row['scan'], index['_scan']) - self.assertEqual(row['domain'], index['_domain']) - self.assertEqual(row['emit'], index['_emit']) - self.assertEqual(row['region'], index['_region']) - self.assertEqual(row['rfac'], result2['_rfac']) - - c.execute("select * from Models where id = :model_id", {'model_id': model_id}) - row = c.fetchone() - model_id = row['id'] - self.assertIsInstance(model_id, int) - self.assertEqual(row['job_id'], self.db.job_id) - self.assertEqual(row['model'], index['_model']) - self.assertIsNone(row['gen']) - self.assertEqual(row['particle'], result2['_particle']) - - sql = "select key, value from ParamValues " + \ - "join Params on ParamValues.param_id = Params.id " + \ - "where model_id = :model_id" - c.execute(sql, {'model_id': model_id}) - rows = c.fetchall() # list of Row objects - self.assertEqual(len(rows), 2) - for row in rows: - self.assertAlmostEqual(row['value'], result3[row['key']]) - - def test_query_best_task_models(self): - self.setup_sample_database() - model0xxx = {'_model': 0, '_scan': -1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, '_rfac': 0.01} - model00xx = {'_model': 1, '_scan': 0, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, '_rfac': 0.02} - model000x = {'_model': 2, '_scan': 0, '_domain': 0, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, '_rfac': 0.03} - model01xx = {'_model': 3, '_scan': 1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, '_rfac': 0.04} - model010x = {'_model': 4, '_scan': 1, '_domain': 0, '_emit': -1, '_region': -1, 'parA': 4., 'parB': 8.567, '_rfac': 0.05} - - model1xxx = {'_model': 5, '_scan': -1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4.123, 'parB': 8.567, '_rfac': 0.09} - model10xx = {'_model': 6, '_scan': 0, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4.123, 'parB': 8.567, '_rfac': 0.08} - model100x = {'_model': 7, '_scan': 0, '_domain': 0, '_emit': -1, '_region': -1, 'parA': 4.123, 'parB': 8.567, '_rfac': 0.07} - model11xx = {'_model': 8, '_scan': 1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4.123, 'parB': 8.567, '_rfac': 0.06} - model110x = {'_model': 9, '_scan': 1, '_domain': 0, '_emit': -1, '_region': -1, 'parA': 4.123, 'parB': 8.567, '_rfac': 0.05} - - model2xxx = {'_model': 10, '_scan': -1, '_domain': -1, '_emit': -1, '_region': -1, 'parA': 4.123, 'parB': 8.567, '_rfac': 0.01} - - self.db.insert_result(model0xxx, model0xxx) - self.db.insert_result(model00xx, model00xx) - self.db.insert_result(model000x, model000x) - self.db.insert_result(model01xx, model01xx) - self.db.insert_result(model010x, model010x) - - self.db.insert_result(model1xxx, model1xxx) - self.db.insert_result(model10xx, model10xx) - self.db.insert_result(model100x, model100x) - self.db.insert_result(model11xx, model11xx) - self.db.insert_result(model110x, model110x) - - self.db.insert_result(model2xxx, model2xxx) - - result = self.db.query_best_task_models(level=1, count=2) - - expected = {0, 1, 3, 6, 8, 10} - self.assertEqual(result, expected) - - def test_sample_project(self): - """ - test ingestion of two results - - this test uses the same call sequence as the actual pmsco code. - it has been used to debug a problem in the main code - where prevous results were overwritten. - """ - db_filename = os.path.join(self.test_dir, "sample_database.db") - lock_filename = os.path.join(self.test_dir, "sample_database.lock") - - # project - project_name = self.__class__.__name__ - project_module = self.__class__.__module__ - - # job 1 - job_name1 = "job1" - result1 = {'parA': 1.234, 'parB': 5.678, '_model': 91, '_rfac': 0.534} - task1 = dispatch.CalcID(91, -1, -1, -1, -1) - - # ingest job 1 - _db = db.ResultsDatabase() - _db.connect(db_filename, lock_filename=lock_filename) - project_id1 = _db.register_project(project_name, project_module) - job_id1 = _db.register_job(project_id1, job_name1, "test", "localhost", "", datetime.datetime.now(), "") - # _db.insert_jobtags(job_id, self.job_tags) - _db.register_params(result1.keys()) - _db.create_models_view() - result_id1 = _db.insert_result(task1, result1) - _db.disconnect() - - # job 2 - job_name2 = "job2" - result2 = {'parA': 1.345, 'parB': 5.789, '_model': 91, '_rfac': 0.654} - task2 = dispatch.CalcID(91, -1, -1, -1, -1) - - # ingest job 2 - _db = db.ResultsDatabase() - _db.connect(db_filename, lock_filename=lock_filename) - project_id2 = _db.register_project(project_name, project_module) - job_id2 = _db.register_job(project_id2, job_name2, "test", "localhost", "", datetime.datetime.now(), "") - # _db.insert_jobtags(job_id, self.job_tags) - _db.register_params(result2.keys()) - _db.create_models_view() - result_id2 = _db.insert_result(task2, result2) - _db.disconnect() - - # check jobs - _db = db.ResultsDatabase() - _db.connect(db_filename, lock_filename=lock_filename) - sql = "select * from Jobs " - c = _db._conn.execute(sql) - rows = c.fetchall() - self.assertEqual(len(rows), 2) - - # check models - sql = "select * from Models " - c = _db._conn.execute(sql) - rows = c.fetchall() - self.assertEqual(len(rows), 2) - - # check results - sql = "select * from Results " - c = _db._conn.execute(sql) - rows = c.fetchall() - self.assertEqual(len(rows), 2) - - _db.disconnect() - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_population.py b/tests/test_population.py index a4fd1e0..461ef60 100644 --- a/tests/test_population.py +++ b/tests/test_population.py @@ -17,11 +17,6 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n http://www.apache.org/licenses/LICENSE-2.0 """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import six - import numpy as np import os import os.path diff --git a/tests/test_project.py b/tests/test_project.py index 7e22f91..caa3a61 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -10,7 +10,7 @@ to run the tests, change to the directory which contains the tests directory, an @author Matthias Muntwiler, matthias.muntwiler@psi.ch -@copyright (c) 2015-21 by Paul Scherrer Institut @n +@copyright (c) 2015-25 by Paul Scherrer Institut @n Licensed under the Apache License, Version 2.0 (the "License"); @n you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -19,13 +19,13 @@ Licensed under the Apache License, Version 2.0 (the "License"); @n import mock import numpy as np -import os from pathlib import Path import unittest import pmsco.data as data import pmsco.dispatch as dispatch import pmsco.project as project +from pmsco.scan import Scan class TestModelSpace(unittest.TestCase): @@ -37,6 +37,20 @@ class TestModelSpace(unittest.TestCase): "C": {"start": 22.0, "min": 15.0, "max": 25.0, "step": 1.0}, "D": {"start": 1.5, "min": 0.5, "max": 2.0, "step": 0.25}} + def test_eval_param_value(self): + dummy_value = 15.3 + ms = project.ModelSpace() + ms.project_symbols = {'numpy': np} + self.assertAlmostEqual(ms._eval_param_value(0.01), 0.01) + self.assertAlmostEqual(ms._eval_param_value('0.01'), 0.01) + self.assertAlmostEqual(ms._eval_param_value('numpy.sin(0.1)'), np.sin(0.1)) + self.assertAlmostEqual(ms._eval_param_value('abs(-0.1)'), 0.1) + self.assertTrue(np.isnan(ms._eval_param_value(None))) + self.assertTrue(np.isnan(ms._eval_param_value(np.nan))) + self.assertRaises(ValueError, ms._eval_param_value, '') + # should not have access to local symbols + self.assertRaises(NameError, ms._eval_param_value, 'dummy_value') + def test_add_param(self): ms = project.ModelSpace() ms.start['A'] = 2.1 @@ -80,105 +94,6 @@ class TestModelSpace(unittest.TestCase): self.assertDictEqual(ms.step, d_step) -class TestScanCreator(unittest.TestCase): - """ - test case for @ref pmsco.project.ScanCreator class - - """ - def test_load_1(self): - """ - test the load method, case 1 - - test for: - - correct array expansion of an ['e', 'a'] scan. - - correct file name expansion with place holders and pathlib.Path objects. - """ - sc = project.ScanCreator() - sc.filename = Path("{test_p}", "twoatom_energy_alpha.etpai") - sc.positions = { - "e": "np.arange(10, 400, 5)", - "t": "0", - "p": "0", - "a": "np.linspace(-30, 30, 31)" - } - sc.emitter = "Cu" - sc.initial_state = "2p3/2" - - p = Path(__file__).parent / ".." / "projects" / "twoatom" - dirs = {"test_p": p, - "test_s": str(p)} - - result = sc.load(dirs=dirs) - - self.assertEqual(result.mode, ['e', 'a']) - self.assertEqual(result.emitter, sc.emitter) - self.assertEqual(result.initial_state, sc.initial_state) - - e = np.arange(10, 400, 5) - a = np.linspace(-30, 30, 31) - t = p = np.asarray([0]) - np.testing.assert_array_equal(result.energies, e) - np.testing.assert_array_equal(result.thetas, t) - np.testing.assert_array_equal(result.phis, p) - np.testing.assert_array_equal(result.alphas, a) - - self.assertTrue(Path(result.filename).is_file(), msg=f"file {result.filename} not found") - - -class TestScan(unittest.TestCase): - """ - test case for @ref pmsco.project.Scan class - - """ - def test_import_scan_file(self): - base_dir = os.path.dirname(os.path.abspath(__file__)) - test_file = os.path.join(base_dir, "..", "projects", "twoatom", "twoatom_energy_alpha.etpai") - - scan = project.Scan() - scan.import_scan_file(test_file, "C", "1s") - - mode = ['e', 'a'] - self.assertEqual(scan.mode, mode) - - ae = np.arange(10, 1005, 5) - at = np.asarray([0]) - ap = np.asarray([0]) - aa = np.arange(-90, 91, 1) - - np.testing.assert_array_almost_equal(scan.energies, ae) - np.testing.assert_array_almost_equal(scan.thetas, at) - np.testing.assert_array_almost_equal(scan.phis, ap) - np.testing.assert_array_almost_equal(scan.alphas, aa) - - def test_define_scan(self): - scan = project.Scan() - p0 = np.asarray([20]) - p1 = np.linspace(1, 4, 4) - p2 = np.linspace(11, 13, 3) - d = {'t': p1, 'e': p0, 'p': p2} - scan.define_scan(d, "C", "1s") - - ae = np.asarray([20]) - at = np.asarray([1, 2, 3, 4]) - ap = np.asarray([11, 12, 13]) - aa = np.asarray([0]) - - np.testing.assert_array_almost_equal(scan.energies, ae) - np.testing.assert_array_almost_equal(scan.thetas, at) - np.testing.assert_array_almost_equal(scan.phis, ap) - np.testing.assert_array_almost_equal(scan.alphas, aa) - - re = np.ones(12) * 20 - rt = np.asarray([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]) - rp = np.asarray([11, 12, 13, 11, 12, 13, 11, 12, 13, 11, 12, 13]) - ra = np.ones(12) * 0 - - np.testing.assert_array_almost_equal(scan.raw_data['e'], re) - np.testing.assert_array_almost_equal(scan.raw_data['t'], rt) - np.testing.assert_array_almost_equal(scan.raw_data['p'], rp) - np.testing.assert_array_almost_equal(scan.raw_data['a'], ra) - - class TestProject(unittest.TestCase): def setUp(self): # before each test method @@ -198,10 +113,41 @@ class TestProject(unittest.TestCase): # teardown_class() after any methods in this class pass + def test_resolve_directories(self): + self.project.job_name = "jn1" + self.project.job_tags['jt1'] = 'tag1' + self.project.directories['report'] = Path("${output}/reports") + self.project.directories['output'] = Path("${home}/test/output") + self.project.directories.resolve_directories(check=True) + + expected = Path(Path.home(), "test", "output") + self.assertEqual(expected, self.project.directories['output']) + expected = Path(Path.home(), "test", "output", "reports") + self.assertEqual(expected, self.project.directories['report']) + + def test_resolve_path(self): + self.project.job_name = "jn1" + self.project.job_tags['jt1'] = 'tag1' + self.project.directories['output'] = Path.home() / "test" / "output" + self.project.directories['report'] = self.project.directories['output'] / "reports" + extra = {'param_name': 'A', 'value': 25.6} + template = "${report}/${job_name}-${jt1}-${param_name}" + rps = self.project.directories.resolve_path(template, extra) + rpp = self.project.directories.resolve_path(Path(template), extra) + expected = Path(Path.home(), "test", "output", "reports", "jn1-tag1-A") + self.assertEqual(str(expected), rps) + self.assertEqual(expected, rpp) + + fdict = {'base': rpp.stem, 'gen': 14, 'param0': 'A', 'param1': 'B'} + template = "my_calc gen ${gen}" + title = self.project.directories.resolve_path(template, fdict) + expected = "my_calc gen 14" + self.assertEqual(expected, title) + @mock.patch('pmsco.data.load_data') @mock.patch('pmsco.data.save_data') def test_combine_domains(self, save_data_mock, load_data_mock): - self.project.scans.append(project.Scan()) + self.project.scans.append(Scan()) parent_task = dispatch.CalculationTask() parent_task.change_id(model=0, scan=0) diff --git a/tests/test_scan.py b/tests/test_scan.py new file mode 100644 index 0000000..a428a42 --- /dev/null +++ b/tests/test_scan.py @@ -0,0 +1,141 @@ +""" +@package tests.test_scan +unit tests for pmsco.scan. + +the purpose of these tests is to help debugging the code. + +to run the tests, change to the directory which contains the tests directory, and execute =nosetests=. + +@pre nose and mock must be installed. + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2015-21 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import numpy as np +import os +from pathlib import Path +import unittest + +from pmsco.data import holo_grid +from pmsco.scan import Scan, ScanKey, ScanLoader, ScanCreator + + +class TestScanCreator(unittest.TestCase): + """ + test case for @ref pmsco.project.ScanCreator class + + """ + def test_load_1(self): + """ + test the load method, case 1 + + test for: + - correct array expansion of an ['e', 'a'] scan. + - correct file name expansion with place holders and pathlib.Path objects. + """ + sc = ScanCreator() + sc.filename = Path("${test_p}", "twoatom_energy_alpha.etpai") + sc.positions = { + "e": "np.arange(10, 400, 5)", + "t": "0", + "p": "0", + "a": "np.linspace(-30, 30, 31)" + } + sc.emitter = "Cu" + sc.initial_state = "2p3/2" + + p = Path(__file__).parent.parent / "pmsco" / "projects" / "twoatom" + dirs = {"test_p": p, + "test_s": str(p)} + + result = sc.load(dirs=dirs) + + self.assertEqual(result.mode, ['e', 'a']) + self.assertEqual(result.emitter, sc.emitter) + self.assertEqual(result.initial_state, sc.initial_state) + + e = np.arange(10, 400, 5) + a = np.linspace(-30, 30, 31) + t = p = np.asarray([0]) + np.testing.assert_array_equal(result.energies, e) + np.testing.assert_array_equal(result.thetas, t) + np.testing.assert_array_equal(result.phis, p) + np.testing.assert_array_equal(result.alphas, a) + + self.assertTrue(Path(result.filename).is_file(), msg=f"file {result.filename} not found") + + +class TestScan(unittest.TestCase): + """ + test case for @ref pmsco.project.Scan class + + """ + def test_import_scan_file(self): + base_dir = os.path.dirname(os.path.abspath(__file__)) + test_file = os.path.join(base_dir, "..", "pmsco", "projects", "twoatom", "twoatom_energy_alpha.etpai") + + scan = Scan() + scan.import_scan_file(test_file, "C", "1s") + + mode = ['e', 'a'] + self.assertEqual(scan.mode, mode) + + ae = np.arange(10, 1005, 5) + at = np.asarray([0]) + ap = np.asarray([0]) + aa = np.arange(-90, 91, 1) + + np.testing.assert_array_almost_equal(scan.energies, ae) + np.testing.assert_array_almost_equal(scan.thetas, at) + np.testing.assert_array_almost_equal(scan.phis, ap) + np.testing.assert_array_almost_equal(scan.alphas, aa) + + def test_define_scan(self): + scan = Scan() + p0 = np.asarray([20]) + p1 = np.linspace(1, 4, 4) + p2 = np.linspace(11, 13, 3) + d = {'t': p1, 'e': p0, 'p': p2} + scan.define_scan(d, "C", "1s") + + ae = np.asarray([20]) + at = np.asarray([1, 2, 3, 4]) + ap = np.asarray([11, 12, 13]) + aa = np.asarray([0]) + + np.testing.assert_array_almost_equal(scan.energies, ae) + np.testing.assert_array_almost_equal(scan.thetas, at) + np.testing.assert_array_almost_equal(scan.phis, ap) + np.testing.assert_array_almost_equal(scan.alphas, aa) + + re = np.ones(12) * 20 + rt = np.asarray([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]) + rp = np.asarray([11, 12, 13, 11, 12, 13, 11, 12, 13, 11, 12, 13]) + ra = np.ones(12) * 0 + + np.testing.assert_array_almost_equal(scan.raw_data['e'], re) + np.testing.assert_array_almost_equal(scan.raw_data['t'], rt) + np.testing.assert_array_almost_equal(scan.raw_data['p'], rp) + np.testing.assert_array_almost_equal(scan.raw_data['a'], ra) + + def test_generate_holo_scan(self): + scan = Scan() + scan.generate_holo_scan(generator=holo_grid, + generator_args={}, + other_positions={"e": 250, "a": 5}, + emitter="C", initial_state="1s") + + self.assertEqual(scan.thetas.shape, (16376,)) + self.assertEqual(scan.phis.shape, (16376,)) + np.testing.assert_array_almost_equal(scan.alphas, np.asarray((5,))) + np.testing.assert_array_almost_equal(scan.energies, np.asarray((250,))) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_schedule.py b/tests/test_schedule.py new file mode 100644 index 0000000..fa3c9d0 --- /dev/null +++ b/tests/test_schedule.py @@ -0,0 +1,76 @@ +import datetime +from pathlib import Path +import unittest + +from pmsco.schedule import JobSchedule, SlurmSchedule, PsiRaSchedule + + +class TestSlurmSchedule(unittest.TestCase): + def test_parse_timedelta(self): + """ + @param td: + str: [days-]hours[:minutes[:seconds]] + dict: days, hours, minutes, seconds - at least one needs to be defined. values must be numeric. + datetime.timedelta - native type + @return: datetime.timedelta + + """ + + input = "1-15:20:23" + expected = datetime.timedelta(days=1, hours=15, minutes=20, seconds=23) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + input = {"days": 1, "hours": 15, "minutes": 20, "seconds": 23} + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + input = {"days": "1", "hours": "15", "minutes": "20", "seconds": "23"} + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + input = "15" + expected = datetime.timedelta(hours=15) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + input = {"hours": 15} + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + input = "12:00" + expected = datetime.timedelta(hours=12, minutes=0) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + input = "15:20:23" + expected = datetime.timedelta(hours=15, minutes=20, seconds=23) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + input = "1-15" + expected = datetime.timedelta(days=1, hours=15) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + input = "1-15:20" + expected = datetime.timedelta(days=1, hours=15, minutes=20) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + input = {"days": 1} + expected = datetime.timedelta(days=1) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + input = "24:00" + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + expected = datetime.timedelta(days=2) + input = "48:00" + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + input = {"minutes": 20} + expected = datetime.timedelta(minutes=20) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + input = {"seconds": 23} + expected = datetime.timedelta(seconds=23) + self.assertEqual(SlurmSchedule.parse_timedelta(input), expected) + + def test_detect_env(self): + result = SlurmSchedule.detect_env() + self.assertTrue(result, "undetectable environment") + for key, value in result.items(): + self.assertTrue(key in {"conda", "venv", "system"}, "unknown environment type") + self.assertTrue(Path(value).is_dir()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_swarm.py b/tests/test_swarm.py index c80966d..36f5bf3 100644 --- a/tests/test_swarm.py +++ b/tests/test_swarm.py @@ -96,7 +96,7 @@ class TestSwarmPopulation(unittest.TestCase): pos1 = self.pop.pos['A'][0] self.assertNotAlmostEqual(pos0, pos1, delta=0.001) - for key in ['A','B','C']: + for key in ['A', 'B', 'C']: for pos in self.pop.pos[key]: self.assertGreaterEqual(pos, self.model_space.min[key]) self.assertLessEqual(pos, self.model_space.max[key]) diff --git a/tests/transforms/test_multipoles.py b/tests/transforms/test_multipoles.py new file mode 100644 index 0000000..dfdda78 --- /dev/null +++ b/tests/transforms/test_multipoles.py @@ -0,0 +1,75 @@ +""" +@package tests.test_data +unit tests for pmsco.data + +the purpose of these tests is to mainly to check the syntax, and correct data types, +i.e. anything that could cause a run-time error. +calculation results are sometimes checked for plausibility but not exact values, +depending on the level of debugging required for a specific part of the code. + +to run the tests, change to the directory which contains the tests directory, and execute =nosetests=. + +@pre nose must be installed (python-nose package on Debian). + +@author Matthias Muntwiler, matthias.muntwiler@psi.ch + +@copyright (c) 2015-24 by Paul Scherrer Institut @n +Licensed under the Apache License, Version 2.0 (the "License"); @n + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +""" + +import numpy as np +import unittest + +import pmsco.data as md +from pmsco.transforms.multipoles import MultipoleExpansion + + +class TestMultipoleExpansion(unittest.TestCase): + def setUp(self): + # before each test method + pass + + def tearDown(self): + # after each test method + pass + + @classmethod + def setup_class(cls): + # before any methods in this class + pass + + @classmethod + def teardown_class(cls): + # teardown_class() after any methods in this class + pass + + def test_generate_expand(self): + orig_data = md.holo_array(md.holo_grid, {"theta_step": 2.}, datatype="ITP") + orig_data["i"] = np.cos(np.deg2rad(orig_data["t"])) * np.sin(np.deg2rad(orig_data["p"]) * 3) ** 2 + + lmax = 12 + me = MultipoleExpansion() + me.holoscan = orig_data + me.lmax = lmax + alm = me.generate() + + self.assertEqual((lmax / 2 + 1, lmax * 2 + 1), alm.shape) + self.assertTrue(np.any(np.real(alm) > 0.), "real part non-zero?") + self.assertTrue(np.any(np.imag(alm) > 0.), "imaginary part non-zero?") + + me.expand() + expanded_data = me.expansion + + self.assertEqual(expanded_data.shape, orig_data.shape) + self.assertTrue(np.any(np.abs(expanded_data['i']) > 0.), "output array non-zero?") + + rf = md.square_diff_rfactor(expanded_data, orig_data) + print(rf) + self.assertLess(rf, 0.25) + + +if __name__ == '__main__': + unittest.main() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..55e0492 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1840 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <3.14" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[manifest] +members = [ + "edac", + "loess", + "phagen", + "pmsco", +] + +[[package]] +name = "aalto-boss" +version = "1.14.1" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "gpy" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://pypi.psi.ch/packages/68/ba/501fa273dc0751dc90fe23deb577883a52d282e660f98c88948043afa07f/aalto_boss-1.14.1.tar.gz", hash = "sha256:3ada46b52e3fe27afd0570560962cb513b290248e2bae96c86ab2606110a16d6", size = 117800, upload-time = "2025-11-28T14:10:45.685Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/34/96/a6ac5e661fe8ba289de8100f7d2fae8a633d86620599b76aec51b9da6684/aalto_boss-1.14.1-py3-none-any.whl", hash = "sha256:85af1fe0abedcaa113e0dc77d91195d565f9bdfd98668f61329dc1f44ff20b51", size = 147486, upload-time = "2025-11-28T14:10:44.558Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "ase" +version = "3.26.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://pypi.psi.ch/packages/30/33/2ffa44950267450f6a5cdb711c68e12d0d72d626e246e5897e3bada7bac6/ase-3.26.0.tar.gz", hash = "sha256:a071a355775b0a8062d23e9266e9d811b19d9f6d9ec5215e8032f7d93dc65075", size = 2405567, upload-time = "2025-08-12T13:06:55.589Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/dd/f0/6e52d797bee63530f4b778cb4cbb3a01970f104197c364a8ff51bc9f5a21/ase-3.26.0-py3-none-any.whl", hash = "sha256:77fd0e609bd3868006d4bb3bb95cdc4081d9e292ac84f6c9fb564b5751d2689e", size = 2946787, upload-time = "2025-08-12T13:06:53.316Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://pypi.psi.ch/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://pypi.psi.ch/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://pypi.psi.ch/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://pypi.psi.ch/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://pypi.psi.ch/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://pypi.psi.ch/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://pypi.psi.ch/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://pypi.psi.ch/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://pypi.psi.ch/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://pypi.psi.ch/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://pypi.psi.ch/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://pypi.psi.ch/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://pypi.psi.ch/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://pypi.psi.ch/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://pypi.psi.ch/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://pypi.psi.ch/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://pypi.psi.ch/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://pypi.psi.ch/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://pypi.psi.ch/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://pypi.psi.ch/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://pypi.psi.ch/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://pypi.psi.ch/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://pypi.psi.ch/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://pypi.psi.ch/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://pypi.psi.ch/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://pypi.psi.ch/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://pypi.psi.ch/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://pypi.psi.ch/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://pypi.psi.ch/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://pypi.psi.ch/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://pypi.psi.ch/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://pypi.psi.ch/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://pypi.psi.ch/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://pypi.psi.ch/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://pypi.psi.ch/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://pypi.psi.ch/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://pypi.psi.ch/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://pypi.psi.ch/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://pypi.psi.ch/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://pypi.psi.ch/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://pypi.psi.ch/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://pypi.psi.ch/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://pypi.psi.ch/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://pypi.psi.ch/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://pypi.psi.ch/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://pypi.psi.ch/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://pypi.psi.ch/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://pypi.psi.ch/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.psi.ch/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://pypi.psi.ch/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://pypi.psi.ch/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://pypi.psi.ch/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://pypi.psi.ch/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://pypi.psi.ch/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://pypi.psi.ch/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://pypi.psi.ch/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://pypi.psi.ch/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://pypi.psi.ch/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://pypi.psi.ch/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://pypi.psi.ch/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://pypi.psi.ch/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://pypi.psi.ch/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://pypi.psi.ch/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://pypi.psi.ch/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://pypi.psi.ch/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://pypi.psi.ch/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://pypi.psi.ch/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://pypi.psi.ch/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://pypi.psi.ch/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://pypi.psi.ch/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://pypi.psi.ch/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://pypi.psi.ch/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://pypi.psi.ch/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://pypi.psi.ch/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://pypi.psi.ch/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://pypi.psi.ch/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://pypi.psi.ch/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://pypi.psi.ch/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://pypi.psi.ch/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://pypi.psi.ch/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://pypi.psi.ch/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://pypi.psi.ch/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://pypi.psi.ch/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://pypi.psi.ch/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://pypi.psi.ch/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://pypi.psi.ch/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://pypi.psi.ch/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://pypi.psi.ch/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://pypi.psi.ch/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://pypi.psi.ch/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://pypi.psi.ch/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://pypi.psi.ch/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://pypi.psi.ch/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://pypi.psi.ch/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://pypi.psi.ch/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://pypi.psi.ch/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://pypi.psi.ch/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://pypi.psi.ch/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://pypi.psi.ch/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://pypi.psi.ch/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://pypi.psi.ch/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://pypi.psi.ch/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://pypi.psi.ch/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://pypi.psi.ch/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.psi.ch/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://pypi.psi.ch/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://pypi.psi.ch/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://pypi.psi.ch/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://pypi.psi.ch/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://pypi.psi.ch/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://pypi.psi.ch/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://pypi.psi.ch/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://pypi.psi.ch/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://pypi.psi.ch/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://pypi.psi.ch/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://pypi.psi.ch/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://pypi.psi.ch/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://pypi.psi.ch/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://pypi.psi.ch/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://pypi.psi.ch/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://pypi.psi.ch/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://pypi.psi.ch/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://pypi.psi.ch/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://pypi.psi.ch/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://pypi.psi.ch/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://pypi.psi.ch/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://pypi.psi.ch/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://pypi.psi.ch/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://pypi.psi.ch/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://pypi.psi.ch/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://pypi.psi.ch/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://pypi.psi.ch/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://pypi.psi.ch/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://pypi.psi.ch/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://pypi.psi.ch/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://pypi.psi.ch/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://pypi.psi.ch/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://pypi.psi.ch/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://pypi.psi.ch/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://pypi.psi.ch/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://pypi.psi.ch/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://pypi.psi.ch/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://pypi.psi.ch/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://pypi.psi.ch/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://pypi.psi.ch/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://pypi.psi.ch/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://pypi.psi.ch/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://pypi.psi.ch/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://pypi.psi.ch/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://pypi.psi.ch/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://pypi.psi.ch/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://pypi.psi.ch/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://pypi.psi.ch/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "cython" +version = "3.2.3" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/39/e1/c0d92b1258722e1bc62a12e630c33f1f842fdab53fd8cd5de2f75c6449a9/cython-3.2.3.tar.gz", hash = "sha256:f13832412d633376ffc08d751cc18ed0d7d00a398a4065e2871db505258748a6", size = 3276650, upload-time = "2025-12-14T07:50:34.691Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/11/77/71c2aef97648548116ca22197c191f8293178f9d4e939e2cb4cbe912619e/cython-3.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55c0157a5940fbf0b054508207fe0fc5cc796d0532af492c0fa35b5b41a883f7", size = 2959265, upload-time = "2025-12-14T07:50:46.035Z" }, + { url = "https://pypi.psi.ch/packages/76/b8/bc06c6427dfe46164d36c0b35e45028d0427faac28d218e065da05edcce5/cython-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51fd1a56d0fc682c05ecc44f11927dbe28dd2867c30148557b62d7d1017a13d8", size = 3368365, upload-time = "2025-12-14T07:50:48.111Z" }, + { url = "https://pypi.psi.ch/packages/c7/3e/7550e90ccd6493842dede63ac484181d4a254ed7332eaad01253ab789d36/cython-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1309bdce06f767e8514377f44b3a5b9e5b91e58af1348010cca10b572e1852ad", size = 3536996, upload-time = "2025-12-14T07:50:50.175Z" }, + { url = "https://pypi.psi.ch/packages/33/94/df8d414d8fb3afd5a0350245ebc589e5bc25b655342ad7341e5cfc869cf5/cython-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:6b6dd6b7aca8447b2a6779b314cc402f1e4990754507a88477e535b3c8b41ad1", size = 2765625, upload-time = "2025-12-14T07:50:51.962Z" }, + { url = "https://pypi.psi.ch/packages/c3/85/77315c92d29d782bee1b36e30b8d76ad1e731cb7ea0af17e285885f3bb68/cython-3.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c041f7e338cca2422e0924716b04fabeda57636214324fc1941396acce99e7c7", size = 2951618, upload-time = "2025-12-14T07:50:53.883Z" }, + { url = "https://pypi.psi.ch/packages/cb/dd/a8209e0d424a0207ddb4a3097a97b667027af3cfada762d85f3bed08ccf8/cython-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:283262b8f902323ceb6ed3b643f275a2a963e7ab059f0714a467933383cbc56d", size = 3243636, upload-time = "2025-12-14T07:50:56.346Z" }, + { url = "https://pypi.psi.ch/packages/1f/2d/bc1927fd7174f7928b86cc9b83589d39592b9273c8b1d2295ca0c0071984/cython-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22a624290c2883387b2c2cfb5224c15bff21432c6a2cf0c23ac8df3dcbd45e96", size = 3378528, upload-time = "2025-12-14T07:50:57.988Z" }, + { url = "https://pypi.psi.ch/packages/ad/10/5add6a6e1721f9c36b5d5b4f3b75fa7af43196e4f2a474921a7277e31b7a/cython-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:26404441f733fd1cfb0dd9c45477f501437e7d51fad05bb402bd2feb4e127aa3", size = 2769341, upload-time = "2025-12-14T07:50:59.581Z" }, + { url = "https://pypi.psi.ch/packages/b4/14/d16282d17c9eb2f78ca9ccd5801fed22f6c3360f5a55dbcce3c93cc70352/cython-3.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf210228c15b5c625824d8e31d43b6fea25f9e13c81dac632f2f7d838e0229a5", size = 2968471, upload-time = "2025-12-14T07:51:01.207Z" }, + { url = "https://pypi.psi.ch/packages/d0/3c/46304a942dac5a636701c55f5b05ec00ad151e6722cd068fe3d0993349bb/cython-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5bf0cebeb4147e172a114437d3fce5a507595d8fdd821be792b1bb25c691514", size = 3223581, upload-time = "2025-12-14T07:51:04.336Z" }, + { url = "https://pypi.psi.ch/packages/29/ad/15da606d71f40bcf2c405f84ca3d4195cb252f4eaa2f551fe6b2e630ee7c/cython-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1f8700ba89c977438744f083890d87187f15709507a5489e0f6d682053b7fa0", size = 3391391, upload-time = "2025-12-14T07:51:05.998Z" }, + { url = "https://pypi.psi.ch/packages/51/9e/045b35eb678682edc3e2d57112cf5ac3581a9ef274eb220b638279195678/cython-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:25732f3981a93407826297f4423206e5e22c3cfccfc74e37bf444453bbdc076f", size = 2756814, upload-time = "2025-12-14T07:51:07.759Z" }, + { url = "https://pypi.psi.ch/packages/d5/c2/35cedff7fcbc844e4e872c6719df5ece26551e14f37d76eb41c412d778c6/cython-3.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1d097ad4686b58b8c03d760d08eca28f79878d404ef7452c49636170571654e0", size = 2959019, upload-time = "2025-12-14T07:51:09.429Z" }, + { url = "https://pypi.psi.ch/packages/44/1b/05787f71b4834a28b19a0a3edee44537c239924f9a7d96ea38ebba365e5c/cython-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a18f2e3bcd018416157d0a83446e29b4a31437ab79061fe5504c077e70389d0", size = 3212912, upload-time = "2025-12-14T07:51:11.512Z" }, + { url = "https://pypi.psi.ch/packages/48/fe/f5d560e3a2eb1891d55f465d17437179d9f5fbd4f46aebf2c00d01fa5e80/cython-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73afc824896ffaf22bf8122d0a7107f0120e3188a353bdcfa92317fc0d9a87ce", size = 3375222, upload-time = "2025-12-14T07:51:13.762Z" }, + { url = "https://pypi.psi.ch/packages/3d/b9/dcf5a68ac2ef89424657b03f751ca799861db097fa83bd52068bed198120/cython-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:9aa1a8abf3d8bb53cc19cfaa21c004afad8d4ccb17513f8aa11a788d1f525abd", size = 2754908, upload-time = "2025-12-14T07:51:15.575Z" }, + { url = "https://pypi.psi.ch/packages/43/49/afe1e3df87a770861cf17ba39f4a91f6d22a2571010fc1890b3708360630/cython-3.2.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:74f482da8b605c61b4df6ff716d013f20131949cb2fa59b03e63abd36ef5bac0", size = 2874467, upload-time = "2025-12-14T07:51:31.568Z" }, + { url = "https://pypi.psi.ch/packages/c7/da/044f725a083e28fb4de5bd33d13ec13f0753734b6ae52d4bc07434610cc8/cython-3.2.3-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a75a04688875b275a6c875565e672325bae04327dd6ec2fc25aeb5c6cf82fce", size = 3211272, upload-time = "2025-12-14T07:51:33.673Z" }, + { url = "https://pypi.psi.ch/packages/95/14/af02ba6e2e03279f2ca2956e3024a44faed4c8496bda8170b663dc3ba6e8/cython-3.2.3-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b01b36c9eb1b68c25bddbeef7379f7bfc37f7c9afc044e71840ffab761a2dd0", size = 2856058, upload-time = "2025-12-14T07:51:36.015Z" }, + { url = "https://pypi.psi.ch/packages/69/16/d254359396c2f099ab154f89b2b35f5b8b0dd21a8102c2c96a7e00291434/cython-3.2.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3829f99d611412288f44ff543e9d2b5c0c83274998b2a6680bbe5cca3539c1fd", size = 2993276, upload-time = "2025-12-14T07:51:37.863Z" }, + { url = "https://pypi.psi.ch/packages/51/0e/1a071381923e896f751f8fbff2a01c5dc8860a8b9a90066f6ec8df561dc4/cython-3.2.3-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c2365a0c79ab9c0fa86d30a4a6ba7e37fc1be9537c48b79b9d63ee7e08bf2fef", size = 2890843, upload-time = "2025-12-14T07:51:40.409Z" }, + { url = "https://pypi.psi.ch/packages/f4/46/1e93e10766db988e6bb8e5c6f7e2e90b9e62f1ac8dee4c1a6cf1fc170773/cython-3.2.3-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3141734fb15f8b5e9402b9240f8da8336edecae91742b41c85678c31ab68f66d", size = 3225339, upload-time = "2025-12-14T07:51:42.09Z" }, + { url = "https://pypi.psi.ch/packages/d4/ae/c284b06ae6a9c95d5883bf8744d10466cf0df64cef041a4c80ccf9fd07bd/cython-3.2.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9a24cc653fad3adbd9cbaa638d80df3aa08a1fe27f62eb35850971c70be680df", size = 3114751, upload-time = "2025-12-14T07:51:44.088Z" }, + { url = "https://pypi.psi.ch/packages/c6/d6/7795a4775c70256217134195f06b07233cf17b00f8905d5b3d782208af64/cython-3.2.3-cp39-abi3-win32.whl", hash = "sha256:b39dff92db70cbd95528f3b81d70e06bd6d3fc9c1dd91321e4d3b999ece3bceb", size = 2435616, upload-time = "2025-12-14T07:51:46.063Z" }, + { url = "https://pypi.psi.ch/packages/18/9e/2a3edcb858ad74e6274448dccf32150c532bc6e423f112a71f65ff3b5680/cython-3.2.3-cp39-abi3-win_arm64.whl", hash = "sha256:18edc858e6a52de47fe03ffa97ea14dadf450e20069de0a8aef531006c4bbd93", size = 2440952, upload-time = "2025-12-14T07:51:47.943Z" }, + { url = "https://pypi.psi.ch/packages/e5/41/54fd429ff8147475fc24ca43246f85d78fb4e747c27f227e68f1594648f1/cython-3.2.3-py3-none-any.whl", hash = "sha256:06a1317097f540d3bb6c7b81ed58a0d8b9dbfa97abf39dfd4c22ee87a6c7241e", size = 1255561, upload-time = "2025-12-14T07:50:31.217Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.19" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/73/75/9e12d4d42349b817cd545b89247696c67917aab907012ae5b64bbfea3199/debugpy-1.8.19.tar.gz", hash = "sha256:eea7e5987445ab0b5ed258093722d5ecb8bb72217c5c9b1e21f64efe23ddebdb", size = 1644590, upload-time = "2025-12-15T21:53:28.044Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/bf/98/d57054371887f37d3c959a7a8dc3c76b763acb65f5e78d849d7db7cadc5b/debugpy-1.8.19-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:fce6da15d73be5935b4438435c53adb512326a3e11e4f90793ea87cd9f018254", size = 2098493, upload-time = "2025-12-15T21:53:30.149Z" }, + { url = "https://pypi.psi.ch/packages/ee/dd/c517b9aa3500157a30e4f4c4f5149f880026bd039d2b940acd2383a85d8e/debugpy-1.8.19-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:e24b1652a1df1ab04d81e7ead446a91c226de704ff5dde6bd0a0dbaab07aa3f2", size = 3087875, upload-time = "2025-12-15T21:53:31.511Z" }, + { url = "https://pypi.psi.ch/packages/d8/57/3d5a5b0da9b63445253107ead151eff29190c6ad7440c68d1a59d56613aa/debugpy-1.8.19-cp310-cp310-win32.whl", hash = "sha256:327cb28c3ad9e17bc925efc7f7018195fd4787c2fe4b7af1eec11f1d19bdec62", size = 5239378, upload-time = "2025-12-15T21:53:32.979Z" }, + { url = "https://pypi.psi.ch/packages/a6/36/7f9053c4c549160c87ae7e43800138f2695578c8b65947114c97250983b6/debugpy-1.8.19-cp310-cp310-win_amd64.whl", hash = "sha256:b7dd275cf2c99e53adb9654f5ae015f70415bbe2bacbe24cfee30d54b6aa03c5", size = 5271129, upload-time = "2025-12-15T21:53:35.085Z" }, + { url = "https://pypi.psi.ch/packages/80/e2/48531a609b5a2aa94c6b6853afdfec8da05630ab9aaa96f1349e772119e9/debugpy-1.8.19-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:c5dcfa21de1f735a4f7ced4556339a109aa0f618d366ede9da0a3600f2516d8b", size = 2207620, upload-time = "2025-12-15T21:53:37.1Z" }, + { url = "https://pypi.psi.ch/packages/1b/d4/97775c01d56071969f57d93928899e5616a4cfbbf4c8cc75390d3a51c4a4/debugpy-1.8.19-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:806d6800246244004625d5222d7765874ab2d22f3ba5f615416cf1342d61c488", size = 3170796, upload-time = "2025-12-15T21:53:38.513Z" }, + { url = "https://pypi.psi.ch/packages/8d/7e/8c7681bdb05be9ec972bbb1245eb7c4c7b0679bb6a9e6408d808bc876d3d/debugpy-1.8.19-cp311-cp311-win32.whl", hash = "sha256:783a519e6dfb1f3cd773a9bda592f4887a65040cb0c7bd38dde410f4e53c40d4", size = 5164287, upload-time = "2025-12-15T21:53:40.857Z" }, + { url = "https://pypi.psi.ch/packages/f2/a8/aaac7ff12ddf5d68a39e13a423a8490426f5f661384f5ad8d9062761bd8e/debugpy-1.8.19-cp311-cp311-win_amd64.whl", hash = "sha256:14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa", size = 5188269, upload-time = "2025-12-15T21:53:42.359Z" }, + { url = "https://pypi.psi.ch/packages/4a/15/d762e5263d9e25b763b78be72dc084c7a32113a0bac119e2f7acae7700ed/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e", size = 2549995, upload-time = "2025-12-15T21:53:43.773Z" }, + { url = "https://pypi.psi.ch/packages/a7/88/f7d25c68b18873b7c53d7c156ca7a7ffd8e77073aa0eac170a9b679cf786/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de", size = 4309891, upload-time = "2025-12-15T21:53:45.26Z" }, + { url = "https://pypi.psi.ch/packages/c5/4f/a65e973aba3865794da65f71971dca01ae66666132c7b2647182d5be0c5f/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee", size = 5286355, upload-time = "2025-12-15T21:53:46.763Z" }, + { url = "https://pypi.psi.ch/packages/d8/3a/d3d8b48fec96e3d824e404bf428276fb8419dfa766f78f10b08da1cb2986/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d", size = 5328239, upload-time = "2025-12-15T21:53:48.868Z" }, + { url = "https://pypi.psi.ch/packages/71/3d/388035a31a59c26f1ecc8d86af607d0c42e20ef80074147cd07b180c4349/debugpy-1.8.19-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:91e35db2672a0abaf325f4868fcac9c1674a0d9ad9bb8a8c849c03a5ebba3e6d", size = 2538859, upload-time = "2025-12-15T21:53:50.478Z" }, + { url = "https://pypi.psi.ch/packages/4a/19/c93a0772d0962294f083dbdb113af1a7427bb632d36e5314297068f55db7/debugpy-1.8.19-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:85016a73ab84dea1c1f1dcd88ec692993bcbe4532d1b49ecb5f3c688ae50c606", size = 4292575, upload-time = "2025-12-15T21:53:51.821Z" }, + { url = "https://pypi.psi.ch/packages/5c/56/09e48ab796b0a77e3d7dc250f95251832b8bf6838c9632f6100c98bdf426/debugpy-1.8.19-cp313-cp313-win32.whl", hash = "sha256:b605f17e89ba0ecee994391194285fada89cee111cfcd29d6f2ee11cbdc40976", size = 5286209, upload-time = "2025-12-15T21:53:53.602Z" }, + { url = "https://pypi.psi.ch/packages/fb/4e/931480b9552c7d0feebe40c73725dd7703dcc578ba9efc14fe0e6d31cfd1/debugpy-1.8.19-cp313-cp313-win_amd64.whl", hash = "sha256:c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73", size = 5328206, upload-time = "2025-12-15T21:53:55.433Z" }, + { url = "https://pypi.psi.ch/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl", hash = "sha256:360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38", size = 5292321, upload-time = "2025-12-15T21:54:16.024Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "edac" +version = "0.1.0" +source = { directory = "subprojects/edac" } + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fasteners" +version = "0.20" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/2d/18/7881a99ba5244bfc82f06017316ffe93217dbbbcfa52b887caa1d4f2a6d3/fasteners-0.20.tar.gz", hash = "sha256:55dce8792a41b56f727ba6e123fcaee77fd87e638a6863cec00007bfea84c8d8", size = 25087, upload-time = "2025-08-11T10:19:37.785Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, +] + +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799, upload-time = "2025-12-12T17:29:27.5Z" }, + { url = "https://pypi.psi.ch/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032, upload-time = "2025-12-12T17:29:30.115Z" }, + { url = "https://pypi.psi.ch/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" }, + { url = "https://pypi.psi.ch/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" }, + { url = "https://pypi.psi.ch/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" }, + { url = "https://pypi.psi.ch/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" }, + { url = "https://pypi.psi.ch/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" }, + { url = "https://pypi.psi.ch/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" }, + { url = "https://pypi.psi.ch/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://pypi.psi.ch/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://pypi.psi.ch/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://pypi.psi.ch/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://pypi.psi.ch/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://pypi.psi.ch/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://pypi.psi.ch/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://pypi.psi.ch/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://pypi.psi.ch/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://pypi.psi.ch/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://pypi.psi.ch/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://pypi.psi.ch/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://pypi.psi.ch/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://pypi.psi.ch/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://pypi.psi.ch/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://pypi.psi.ch/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://pypi.psi.ch/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://pypi.psi.ch/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://pypi.psi.ch/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://pypi.psi.ch/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://pypi.psi.ch/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://pypi.psi.ch/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://pypi.psi.ch/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://pypi.psi.ch/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://pypi.psi.ch/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + +[[package]] +name = "future" +version = "1.0.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://pypi.psi.ch/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://pypi.psi.ch/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + +[[package]] +name = "gpy" +version = "1.13.2" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "cython" }, + { name = "numpy" }, + { name = "paramz" }, + { name = "scipy" }, + { name = "six" }, +] +sdist = { url = "https://pypi.psi.ch/packages/dc/30/9aac3d92db5ea57dfbc15a7ef5d8b0d8db5b32cfca5e9952a284a0ea8b2c/GPy-1.13.2.tar.gz", hash = "sha256:a38256b4dda536a5b5e6134a3924b42d454e987ee801fb6fc8b55a922da27920", size = 1138525, upload-time = "2024-07-23T06:30:27.107Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/02/3c/293597b7c466ca2f2ef44e739a9fde4210259cc9926ad1d97701edb455d4/GPy-1.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b56c71cd272c12845abb205617eb3d227ba6b369066011bffd5d6daf76292be1", size = 1917096, upload-time = "2024-07-23T06:30:03.248Z" }, + { url = "https://pypi.psi.ch/packages/bc/f6/bf80c45bbd4d7c91183ba7ec5da33200452fe639f23ff78872ec096cb997/GPy-1.13.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e73d9188f529c4b9837b526afd786c242ba6bca598fd0f475eb03c0c3622dad", size = 3600188, upload-time = "2024-07-23T06:30:05.913Z" }, + { url = "https://pypi.psi.ch/packages/ea/fe/a0622fbcb41a9f05457903e3d1078bfa3d04085bfa245025a5660cbffe21/GPy-1.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:f5a497ca01648e61044e9c1d0e8cdeb3f136a9878e9bc797698afeb96a84c24b", size = 1641683, upload-time = "2024-07-23T06:30:07.903Z" }, + { url = "https://pypi.psi.ch/packages/61/d7/5f2fbdbdfb4ebfa04205f47fb09742dec0a4dca441e294ed852884b7313d/GPy-1.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:385bf6c1d5872d6519d50e2bed1f08bc679d4a351ad0741ae580233934223e3e", size = 1918661, upload-time = "2024-07-23T06:30:10.217Z" }, + { url = "https://pypi.psi.ch/packages/73/ad/7cb1221f78e059fb1c5cf8115409bbb5de2a9b1ad3f85d4a04ddebc0053b/GPy-1.13.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7f3a5d9f4562ec72936db01877406fd42a5020bd59f567ed6649c89769b782b", size = 3813537, upload-time = "2024-07-23T06:30:12.375Z" }, + { url = "https://pypi.psi.ch/packages/d1/f8/b4c1085db806109aebbe2c4226ffccbb285d71d982f5bde9a68c681ae41a/GPy-1.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:28195c9d5a9d8c7cc9809b916edb81dfcc48fd9a1d3bee22cb2ba90b1e88c876", size = 1643091, upload-time = "2024-07-23T06:30:14.671Z" }, + { url = "https://pypi.psi.ch/packages/98/74/0f20626f4f3081d841f93625c8f7ba5af12dd09249705b4d2a2c77b596f6/GPy-1.13.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d3514cfcc1b46d2503aa75a6e12e57ca81d29d96e72bfc01f1b9702f4c298b80", size = 1919145, upload-time = "2024-07-23T06:30:16.64Z" }, + { url = "https://pypi.psi.ch/packages/47/05/4ee17fd89661d92ed621a95d252ec5800c37cdfe5728d2f49be380f819bf/GPy-1.13.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39819890941a60685a1ba4b7ae834dfb6ac3c1d1a9aa31b8bd25ad573483a8e8", size = 3752464, upload-time = "2024-07-23T06:30:18.421Z" }, + { url = "https://pypi.psi.ch/packages/fe/ea/b53ea5728eae7baeaab78f627dcd629ce469ff5591387a04c78a138101e4/GPy-1.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:1e783eb33d2b7d23d005c09b0450a468ff20c50a1312ba998b4643677c05781a", size = 1639326, upload-time = "2024-07-23T06:30:20.596Z" }, +] + +[[package]] +name = "greenlet" +version = "3.3.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/32/6a/33d1702184d94106d3cdd7bfb788e19723206fce152e303473ca3b946c7b/greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d", size = 273658, upload-time = "2025-12-04T14:23:37.494Z" }, + { url = "https://pypi.psi.ch/packages/d6/b7/2b5805bbf1907c26e434f4e448cd8b696a0b71725204fa21a211ff0c04a7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb", size = 574810, upload-time = "2025-12-04T14:50:04.154Z" }, + { url = "https://pypi.psi.ch/packages/94/38/343242ec12eddf3d8458c73f555c084359883d4ddc674240d9e61ec51fd6/greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd", size = 586248, upload-time = "2025-12-04T14:57:39.35Z" }, + { url = "https://pypi.psi.ch/packages/f0/d0/0ae86792fb212e4384041e0ef8e7bc66f59a54912ce407d26a966ed2914d/greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b", size = 597403, upload-time = "2025-12-04T15:07:10.831Z" }, + { url = "https://pypi.psi.ch/packages/b6/a8/15d0aa26c0036a15d2659175af00954aaaa5d0d66ba538345bd88013b4d7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5", size = 586910, upload-time = "2025-12-04T14:25:59.705Z" }, + { url = "https://pypi.psi.ch/packages/e1/9b/68d5e3b7ccaba3907e5532cf8b9bf16f9ef5056a008f195a367db0ff32db/greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9", size = 1547206, upload-time = "2025-12-04T15:04:21.027Z" }, + { url = "https://pypi.psi.ch/packages/66/bd/e3086ccedc61e49f91e2cfb5ffad9d8d62e5dc85e512a6200f096875b60c/greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d", size = 1613359, upload-time = "2025-12-04T14:27:26.548Z" }, + { url = "https://pypi.psi.ch/packages/f4/6b/d4e73f5dfa888364bbf02efa85616c6714ae7c631c201349782e5b428925/greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082", size = 300740, upload-time = "2025-12-04T14:47:52.773Z" }, + { url = "https://pypi.psi.ch/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" }, + { url = "https://pypi.psi.ch/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" }, + { url = "https://pypi.psi.ch/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" }, + { url = "https://pypi.psi.ch/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" }, + { url = "https://pypi.psi.ch/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" }, + { url = "https://pypi.psi.ch/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" }, + { url = "https://pypi.psi.ch/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" }, + { url = "https://pypi.psi.ch/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" }, + { url = "https://pypi.psi.ch/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, + { url = "https://pypi.psi.ch/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, + { url = "https://pypi.psi.ch/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, + { url = "https://pypi.psi.ch/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, + { url = "https://pypi.psi.ch/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, + { url = "https://pypi.psi.ch/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, + { url = "https://pypi.psi.ch/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, + { url = "https://pypi.psi.ch/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, + { url = "https://pypi.psi.ch/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, + { url = "https://pypi.psi.ch/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, + { url = "https://pypi.psi.ch/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, + { url = "https://pypi.psi.ch/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, + { url = "https://pypi.psi.ch/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, + { url = "https://pypi.psi.ch/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, + { url = "https://pypi.psi.ch/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, + { url = "https://pypi.psi.ch/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" }, +] + +[[package]] +name = "h5py" +version = "3.15.1" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.psi.ch/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/86/30/8fa61698b438dd751fa46a359792e801191dadab560d0a5f1c709443ef8e/h5py-3.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67e59f6c2f19a32973a40f43d9a088ae324fe228c8366e25ebc57ceebf093a6b", size = 3414477, upload-time = "2025-10-16T10:33:24.201Z" }, + { url = "https://pypi.psi.ch/packages/16/16/db2f63302937337c4e9e51d97a5984b769bdb7488e3d37632a6ac297f8ef/h5py-3.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e2f471688402c3404fa4e13466e373e622fd4b74b47b56cfdff7cc688209422", size = 2850298, upload-time = "2025-10-16T10:33:27.747Z" }, + { url = "https://pypi.psi.ch/packages/fc/2e/f1bb7de9b05112bfd14d5206090f0f92f1e75bbb412fbec5d4653c3d44dd/h5py-3.15.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c45802bcb711e128a6839cb6c01e9ac648dc55df045c9542a675c771f15c8d5", size = 4523605, upload-time = "2025-10-16T10:33:31.168Z" }, + { url = "https://pypi.psi.ch/packages/05/8a/63f4b08f3628171ce8da1a04681a65ee7ac338fde3cb3e9e3c9f7818e4da/h5py-3.15.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64ce3f6470adb87c06e3a8dd1b90e973699f1759ad79bfa70c230939bff356c9", size = 4735346, upload-time = "2025-10-16T10:33:34.759Z" }, + { url = "https://pypi.psi.ch/packages/74/48/f16d12d9de22277605bcc11c0dcab5e35f06a54be4798faa2636b5d44b3c/h5py-3.15.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4411c1867b9899a25e983fff56d820a66f52ac326bbe10c7cdf7d832c9dcd883", size = 4175305, upload-time = "2025-10-16T10:33:38.83Z" }, + { url = "https://pypi.psi.ch/packages/d6/2f/47cdbff65b2ce53c27458c6df63a232d7bb1644b97df37b2342442342c84/h5py-3.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2cbc4104d3d4aca9d6db8c0c694555e255805bfeacf9eb1349bda871e26cacbe", size = 4653602, upload-time = "2025-10-16T10:33:42.188Z" }, + { url = "https://pypi.psi.ch/packages/c3/28/dc08de359c2f43a67baa529cb70d7f9599848750031975eed92d6ae78e1d/h5py-3.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:01f55111ca516f5568ae7a7fc8247dfce607de331b4467ee8a9a6ed14e5422c7", size = 2873601, upload-time = "2025-10-16T10:33:45.323Z" }, + { url = "https://pypi.psi.ch/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135, upload-time = "2025-10-16T10:33:47.954Z" }, + { url = "https://pypi.psi.ch/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958, upload-time = "2025-10-16T10:33:50.907Z" }, + { url = "https://pypi.psi.ch/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770, upload-time = "2025-10-16T10:33:54.357Z" }, + { url = "https://pypi.psi.ch/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329, upload-time = "2025-10-16T10:33:57.616Z" }, + { url = "https://pypi.psi.ch/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456, upload-time = "2025-10-16T10:34:00.843Z" }, + { url = "https://pypi.psi.ch/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295, upload-time = "2025-10-16T10:34:04.154Z" }, + { url = "https://pypi.psi.ch/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129, upload-time = "2025-10-16T10:34:06.886Z" }, + { url = "https://pypi.psi.ch/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121, upload-time = "2025-10-16T10:34:09.579Z" }, + { url = "https://pypi.psi.ch/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, + { url = "https://pypi.psi.ch/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, + { url = "https://pypi.psi.ch/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, + { url = "https://pypi.psi.ch/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, + { url = "https://pypi.psi.ch/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, + { url = "https://pypi.psi.ch/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, + { url = "https://pypi.psi.ch/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, + { url = "https://pypi.psi.ch/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, + { url = "https://pypi.psi.ch/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, + { url = "https://pypi.psi.ch/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, + { url = "https://pypi.psi.ch/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, + { url = "https://pypi.psi.ch/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, + { url = "https://pypi.psi.ch/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, + { url = "https://pypi.psi.ch/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, + { url = "https://pypi.psi.ch/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, + { url = "https://pypi.psi.ch/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.1.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.8.0", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://pypi.psi.ch/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, +] + +[[package]] +name = "ipython" +version = "8.37.0" +source = { registry = "https://pypi.psi.ch/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" }, +] + +[[package]] +name = "ipython" +version = "9.8.0" +source = { registry = "https://pypi.psi.ch/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/12/51/a703c030f4928646d390b4971af4938a1b10c9dfce694f0d99a0bb073cb2/ipython-9.8.0.tar.gz", hash = "sha256:8e4ce129a627eb9dd221c41b1d2cdaed4ef7c9da8c17c63f6f578fe231141f83", size = 4424940, upload-time = "2025-12-03T10:18:24.353Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/f1/df/8ee1c5dd1e3308b5d5b2f2dfea323bb2f3827da8d654abb6642051199049/ipython-9.8.0-py3-none-any.whl", hash = "sha256:ebe6d1d58d7d988fbf23ff8ff6d8e1622cfdb194daf4b7b73b792c4ec3b85385", size = 621374, upload-time = "2025-12-03T10:18:22.335Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://pypi.psi.ch/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://pypi.psi.ch/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://pypi.psi.ch/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.7.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://pypi.psi.ch/packages/a6/27/d10de45e8ad4ce872372c4a3a37b7b35b6b064f6f023a5c14ffcced4d59d/jupyter_client-8.7.0.tar.gz", hash = "sha256:3357212d9cbe01209e59190f67a3a7e1f387a4f4e88d1e0433ad84d7b262531d", size = 344691, upload-time = "2025-12-09T18:37:01.953Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl", hash = "sha256:3671a94fd25e62f5f2f554f5e95389c2294d89822378a5f2dd24353e1494a9e0", size = 106215, upload-time = "2025-12-09T18:37:00.024Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://pypi.psi.ch/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" }, + { url = "https://pypi.psi.ch/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" }, + { url = "https://pypi.psi.ch/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" }, + { url = "https://pypi.psi.ch/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, + { url = "https://pypi.psi.ch/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, + { url = "https://pypi.psi.ch/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, + { url = "https://pypi.psi.ch/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, + { url = "https://pypi.psi.ch/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, + { url = "https://pypi.psi.ch/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, + { url = "https://pypi.psi.ch/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, + { url = "https://pypi.psi.ch/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, + { url = "https://pypi.psi.ch/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, + { url = "https://pypi.psi.ch/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, + { url = "https://pypi.psi.ch/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://pypi.psi.ch/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://pypi.psi.ch/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://pypi.psi.ch/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://pypi.psi.ch/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://pypi.psi.ch/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://pypi.psi.ch/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://pypi.psi.ch/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://pypi.psi.ch/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://pypi.psi.ch/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://pypi.psi.ch/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://pypi.psi.ch/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://pypi.psi.ch/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://pypi.psi.ch/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://pypi.psi.ch/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://pypi.psi.ch/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://pypi.psi.ch/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://pypi.psi.ch/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://pypi.psi.ch/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://pypi.psi.ch/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://pypi.psi.ch/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://pypi.psi.ch/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://pypi.psi.ch/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://pypi.psi.ch/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://pypi.psi.ch/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://pypi.psi.ch/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://pypi.psi.ch/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://pypi.psi.ch/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://pypi.psi.ch/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://pypi.psi.ch/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://pypi.psi.ch/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://pypi.psi.ch/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://pypi.psi.ch/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://pypi.psi.ch/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://pypi.psi.ch/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://pypi.psi.ch/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://pypi.psi.ch/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://pypi.psi.ch/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://pypi.psi.ch/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://pypi.psi.ch/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://pypi.psi.ch/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://pypi.psi.ch/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://pypi.psi.ch/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://pypi.psi.ch/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://pypi.psi.ch/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://pypi.psi.ch/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://pypi.psi.ch/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://pypi.psi.ch/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://pypi.psi.ch/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://pypi.psi.ch/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://pypi.psi.ch/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://pypi.psi.ch/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" }, + { url = "https://pypi.psi.ch/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" }, + { url = "https://pypi.psi.ch/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, + { url = "https://pypi.psi.ch/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, + { url = "https://pypi.psi.ch/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, + { url = "https://pypi.psi.ch/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://pypi.psi.ch/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://pypi.psi.ch/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://pypi.psi.ch/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://pypi.psi.ch/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[package]] +name = "loess" +version = "0.1.0" +source = { directory = "subprojects/loess" } +dependencies = [ + { name = "numpy" }, +] + +[package.metadata] +requires-dist = [{ name = "numpy", specifier = ">=1.22,<2" }] + +[[package]] +name = "matplotlib" +version = "3.8.4" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version < '3.11'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://pypi.psi.ch/packages/38/4f/8487737a74d8be4ab5fbe6019b0fae305c1604cf7209500969b879b5f462/matplotlib-3.8.4.tar.gz", hash = "sha256:8aac397d5e9ec158960e31c381c5ffc52ddd52bd9a47717e2a694038167dffea", size = 35934425, upload-time = "2024-04-04T01:47:18.594Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/67/c0/1f88491656d21a2fecd90fbfae999b2f87bc44d439ef301ec8e0e4a937a0/matplotlib-3.8.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:abc9d838f93583650c35eca41cfcec65b2e7cb50fd486da6f0c49b5e1ed23014", size = 7603557, upload-time = "2024-04-04T01:47:46.363Z" }, + { url = "https://pypi.psi.ch/packages/86/9c/aa059a4fb8154d5875a5ddd33f8d0a42d77c0225fe4325e9b9358f39b0bf/matplotlib-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f65c9f002d281a6e904976007b2d46a1ee2bcea3a68a8c12dda24709ddc9106", size = 7497421, upload-time = "2024-04-04T01:47:54.074Z" }, + { url = "https://pypi.psi.ch/packages/0b/67/ded5217d42de1532193cd87db925c67997d23c68b20c3eaa9e4c6a0adb67/matplotlib-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce1edd9f5383b504dbc26eeea404ed0a00656c526638129028b758fd43fc5f10", size = 11377985, upload-time = "2024-04-04T01:48:04.955Z" }, + { url = "https://pypi.psi.ch/packages/d6/07/061f97211f942101070a46fecd813a6b1bd83590ed7b07c473cabd707fe7/matplotlib-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd79298550cba13a43c340581a3ec9c707bd895a6a061a78fa2524660482fc0", size = 11608003, upload-time = "2024-04-04T01:48:16.25Z" }, + { url = "https://pypi.psi.ch/packages/9a/d3/5d0bb1d905e219543fdfd7ab04e9d641a766367c83a5ffbcea60d2b2cf2d/matplotlib-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:90df07db7b599fe7035d2f74ab7e438b656528c68ba6bb59b7dc46af39ee48ef", size = 9535368, upload-time = "2024-04-04T01:48:26.265Z" }, + { url = "https://pypi.psi.ch/packages/62/5a/a5108ae3db37f35f8a2be8a57d62da327af239214c9661464ce09ee32d7d/matplotlib-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:ac24233e8f2939ac4fd2919eed1e9c0871eac8057666070e94cbf0b33dd9c338", size = 7656037, upload-time = "2024-04-04T01:48:34.761Z" }, + { url = "https://pypi.psi.ch/packages/36/11/62250ea25780d4b59c2c6044ec161235c47cc05a18d0ec0a05657de75b7d/matplotlib-3.8.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:72f9322712e4562e792b2961971891b9fbbb0e525011e09ea0d1f416c4645661", size = 7606117, upload-time = "2024-04-04T01:48:42.545Z" }, + { url = "https://pypi.psi.ch/packages/14/60/12d4f27b859a74359306662da69c2d08826a2b05cfe7f96e66b490f41573/matplotlib-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:232ce322bfd020a434caaffbd9a95333f7c2491e59cfc014041d95e38ab90d1c", size = 7500108, upload-time = "2024-04-04T01:48:50.21Z" }, + { url = "https://pypi.psi.ch/packages/4e/ba/9e4f7f34dccf2d2768504410410db8d551c940457a2bec658dc4fa3b5aa2/matplotlib-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6addbd5b488aedb7f9bc19f91cd87ea476206f45d7116fcfe3d31416702a82fa", size = 11382998, upload-time = "2024-04-04T01:49:01.346Z" }, + { url = "https://pypi.psi.ch/packages/80/3b/e363612ac1a514abfb5505aa209dd5b724b3232a6de98710d7759559706a/matplotlib-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc4ccdc64e3039fc303defd119658148f2349239871db72cd74e2eeaa9b80b71", size = 11613309, upload-time = "2024-04-04T01:49:13.428Z" }, + { url = "https://pypi.psi.ch/packages/32/4c/63164901acadb3ada55c5e0fd6b7f29c9033d7e131302884cd735611b77a/matplotlib-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b7a2a253d3b36d90c8993b4620183b55665a429da8357a4f621e78cd48b2b30b", size = 9546019, upload-time = "2024-04-04T01:49:23.752Z" }, + { url = "https://pypi.psi.ch/packages/2d/d5/6227732ecab9165586966ccb54301e3164f61b470c954c4cf6940654fbe1/matplotlib-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:8080d5081a86e690d7688ffa542532e87f224c38a6ed71f8fbed34dd1d9fedae", size = 7658174, upload-time = "2024-04-04T01:49:32.066Z" }, + { url = "https://pypi.psi.ch/packages/91/eb/65f3bd78ce757dadd455c220273349428384b162485cd8aa380b61a867ed/matplotlib-3.8.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6485ac1f2e84676cff22e693eaa4fbed50ef5dc37173ce1f023daef4687df616", size = 7604083, upload-time = "2024-04-04T01:49:40.442Z" }, + { url = "https://pypi.psi.ch/packages/da/2b/2bb6073ca8d336da07ace7d98bf7bb9da8233f55876bb3db6a5ee924f3e9/matplotlib-3.8.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c89ee9314ef48c72fe92ce55c4e95f2f39d70208f9f1d9db4e64079420d8d732", size = 7496013, upload-time = "2024-04-04T01:49:48.174Z" }, + { url = "https://pypi.psi.ch/packages/61/cd/976d3a9c10328da1d2fe183f7c92c45f1e125536226a6eb3a820c4753cd1/matplotlib-3.8.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50bac6e4d77e4262c4340d7a985c30912054745ec99756ce213bfbc3cb3808eb", size = 11376749, upload-time = "2024-04-04T01:49:58.572Z" }, + { url = "https://pypi.psi.ch/packages/cd/ba/412149958e951876096198609b958b90a8a2c9bc07a96eeeaa9e2c480f30/matplotlib-3.8.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f51c4c869d4b60d769f7b4406eec39596648d9d70246428745a681c327a8ad30", size = 11600837, upload-time = "2024-04-04T01:50:09.279Z" }, + { url = "https://pypi.psi.ch/packages/dc/4f/e5b56ca109d8ab6bae37f519f15b891fc18809ddb8bc1aa26e0bfca83e25/matplotlib-3.8.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b12ba985837e4899b762b81f5b2845bd1a28f4fdd1a126d9ace64e9c4eb2fb25", size = 9538883, upload-time = "2024-04-04T01:50:19.268Z" }, + { url = "https://pypi.psi.ch/packages/7d/ca/e7bd1876a341ed8c456095962a582696cac1691cb6e55bd5ead15a755c5d/matplotlib-3.8.4-cp312-cp312-win_amd64.whl", hash = "sha256:7a6769f58ce51791b4cb8b4d7642489df347697cd3e23d88266aaaee93b41d9a", size = 7659712, upload-time = "2024-04-04T01:50:26.938Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://pypi.psi.ch/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mock" +version = "5.2.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/07/8c/14c2ae915e5f9dca5a22edd68b35be94400719ccfa068a03e0fb63d0f6f6/mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0", size = 92796, upload-time = "2025-03-03T12:31:42.911Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/bd/d9/617e6af809bf3a1d468e0d58c3997b1dc219a9a9202e650d30c2fc85d481/mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f", size = 31617, upload-time = "2025-03-03T12:31:41.518Z" }, +] + +[[package]] +name = "mpi4py" +version = "4.1.1" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/62/74/28ea85b0b949cad827ea50720e00e814e88c8fd536c27c3c491e4f025724/mpi4py-4.1.1.tar.gz", hash = "sha256:eb2c8489bdbc47fdc6b26ca7576e927a11b070b6de196a443132766b3d0a2a22", size = 500518, upload-time = "2025-10-10T13:55:20.402Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/36/b3/2e7df40608f2188dca16e38f8030add1071f06b1cd94dd8a4e16b9acbd84/mpi4py-4.1.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1586f5d1557abed9cba7e984d18f32e787b353be0986e599974db177ae36329a", size = 1422849, upload-time = "2025-10-10T13:53:40.082Z" }, + { url = "https://pypi.psi.ch/packages/6d/ed/970bd3edc0e614eccc726fa406255b88f728a8bc059e81f96f28d6ede0af/mpi4py-4.1.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ba85e4778d63c750226de95115c92b709f38d7e661be660a275da4f0992ee197", size = 1326982, upload-time = "2025-10-10T13:53:42.32Z" }, + { url = "https://pypi.psi.ch/packages/5d/c3/f9a5d1f9ba52ac6386bf3d3550027f42a6b102b0432113cc43294420feb2/mpi4py-4.1.1-cp310-abi3-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0a8332884626994d9ef48da233dc7a0355f4868dd7ff59f078d5813a2935b930", size = 1373127, upload-time = "2025-10-10T13:53:43.957Z" }, + { url = "https://pypi.psi.ch/packages/84/d1/1fe75025df801d817ed49371c719559f742f3f263323442d34dbe3366af3/mpi4py-4.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e0352860f0b3e18bc0dcb47e42e583ccb9472f89752d711a6fca46a38670554", size = 1225134, upload-time = "2025-10-10T13:53:45.583Z" }, + { url = "https://pypi.psi.ch/packages/40/44/d653fec0e4ca8181645da4bfb2763017625e5b3f151b208fadd932cb1766/mpi4py-4.1.1-cp310-abi3-win_amd64.whl", hash = "sha256:0f46dfe666a599e4bd2641116b2b4852a3ed9d37915edf98fae471d666663128", size = 1478863, upload-time = "2025-10-10T13:53:47.178Z" }, + { url = "https://pypi.psi.ch/packages/58/f7/793c9a532e5367cffb2b97ca6a879285ca73a14f79e6ff208bb390651a43/mpi4py-4.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9082e04c8afcffa7d650a262d800af1a617c555d610810deeab265a4a5f7d42e", size = 1585904, upload-time = "2025-10-10T13:53:49.129Z" }, + { url = "https://pypi.psi.ch/packages/b7/fe/cdead6721426b25d817a1bf45d5adc6dc90fd8bb0831f5ca06a4edd2015c/mpi4py-4.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d618e6a5a8f6f86c33a954356d8ed398bec31f34b63321570661ac157063bb6", size = 1438343, upload-time = "2025-10-10T13:53:51.098Z" }, + { url = "https://pypi.psi.ch/packages/c0/c4/4a73c80cf483df603770278f0fdc57da5394edee376790c62f1eba04bb3b/mpi4py-4.1.1-cp310-cp310-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d4c460609bd6decc22ad89cbfe48e4c5a2461ff52ada9345a4c19edee39f93da", size = 1432321, upload-time = "2025-10-10T13:53:53.235Z" }, + { url = "https://pypi.psi.ch/packages/49/56/7b32631f3cc5cf741610a108a7f40a3714c9862c1f637b5ded525af32be9/mpi4py-4.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c04a388c7a945e751c82742c6bb277434d26a67768a01952f7494d1c25dff94b", size = 1299883, upload-time = "2025-10-10T13:53:55.22Z" }, + { url = "https://pypi.psi.ch/packages/14/76/53caf807ec74c042fbecf76162e071c09c53fb0ed66b1edf31dabd64c588/mpi4py-4.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ad4b225a5a1a02a2b89979ed8f328c6a2bc3bd6ad4a57e453727f90373fa5f8", size = 1622884, upload-time = "2025-10-10T13:53:56.882Z" }, + { url = "https://pypi.psi.ch/packages/20/8f/5d28174048ef02fb91dd0759a32c07b272c9f1df265e19145712aa7bd712/mpi4py-4.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a428ba96b992a8911cf932fa71dd8c0260d47ab7e5dee2b09239ad91fc540b79", size = 1596913, upload-time = "2025-10-10T13:53:58.466Z" }, + { url = "https://pypi.psi.ch/packages/ab/81/dce928b11816fac9713e93e609476ddac520fc50368aa7591728c329ff19/mpi4py-4.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fc0cf81445fac2ae2e5716c365fd72e1bb545df065f5a3f6731f64b3beed886e", size = 1433274, upload-time = "2025-10-10T13:54:00.508Z" }, + { url = "https://pypi.psi.ch/packages/5d/15/1a869a35d3e3438866dc8d8c9cb04dc6aa484171343627a8baf82c3c1ca9/mpi4py-4.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a753d5d61b46f90260247f344a6c57c527a6a4e7bea126830120ab41c3d057e5", size = 1423333, upload-time = "2025-10-10T13:54:03.679Z" }, + { url = "https://pypi.psi.ch/packages/25/33/072781fb85f5bc50b93ee7e8d3b3afb849d50570431b6cb2aa957db79b59/mpi4py-4.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a36ef9d7b2b6b62026dbf9b59b44efb5430f7b9ca5fb855bfbf8d403218e37c", size = 1299183, upload-time = "2025-10-10T13:54:05.3Z" }, + { url = "https://pypi.psi.ch/packages/f9/a7/152af3c6412702a4e0fcfd0fe572307ed52821de13db9c96535f31a39aa7/mpi4py-4.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20bf4c0c65fd67287664f8b1b6dc7c7b341838f10bba34a2e452d47530ce8a5f", size = 1632284, upload-time = "2025-10-10T13:54:06.786Z" }, + { url = "https://pypi.psi.ch/packages/ff/2c/e201cd4828555f10306a5439875cbd0ecfba766ace01ff5c6df43f795650/mpi4py-4.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4403a7cec985be9963efc626193e6df3f63f5ada0c26373c28e640e623e56c3", size = 1669517, upload-time = "2025-10-10T13:54:08.404Z" }, + { url = "https://pypi.psi.ch/packages/7b/53/18d978c3a19deecf38217ce54319e6c9162fec3569c4256c039b66eac2f4/mpi4py-4.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a2ffccc9f3a8c7c957403faad594d650c60234ac08cbedf45beaa96602debe9", size = 1454721, upload-time = "2025-10-10T13:54:09.977Z" }, + { url = "https://pypi.psi.ch/packages/ee/15/b908d1d23a4bd2bd7b2e98de5df23b26e43145119fe294728bf89211b935/mpi4py-4.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3d9b619bf197a290f7fd67eb61b1c2a5c204afd9621651a50dc0b1c1280d45", size = 1448977, upload-time = "2025-10-10T13:54:11.65Z" }, + { url = "https://pypi.psi.ch/packages/5d/19/088a2d37e80e0feb7851853b2a71cbe6f9b18bdf0eab680977864ea83aab/mpi4py-4.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0699c194db5d95fc2085711e4e0013083bd7ae9a88438e1fd64ddb67e9b0cf9e", size = 1318737, upload-time = "2025-10-10T13:54:13.075Z" }, + { url = "https://pypi.psi.ch/packages/97/3a/526261f39bf096e5ff396d18b76740a58d872425612ff84113dd85c2c08e/mpi4py-4.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:0abf5490c3d49c30542b461bfc5ad88dd7d147a4bdb456b7163640577fdfef88", size = 1725676, upload-time = "2025-10-10T13:54:14.681Z" }, + { url = "https://pypi.psi.ch/packages/30/75/2ffccd69360680a0216e71f90fd50dc8ff49711be54502d522a068196c68/mpi4py-4.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3dd973c509f2dbb6904c035a4a071509cde98decf0528fa21e2e7d5db5cc988", size = 1710002, upload-time = "2025-10-10T13:54:17.042Z" }, + { url = "https://pypi.psi.ch/packages/3c/13/22fa9dcbc5e4ae6fd10cba6d49b7c879c30c5bea88f450f79b373d200f40/mpi4py-4.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c8c83a359e62dd7fdd030360f430e0e8986df029c0953ab216ff97a110038dc4", size = 1484623, upload-time = "2025-10-10T13:54:19.097Z" }, + { url = "https://pypi.psi.ch/packages/47/01/476f0f9dc96261d02214009f42e10338fc56f260f1f10b23ee89c515c8b7/mpi4py-4.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:323ba354ba951c7736c033c5f2ad07bb1276f9696f0312ea6ff0a28cd0ab3e3d", size = 1448403, upload-time = "2025-10-10T13:54:21.211Z" }, + { url = "https://pypi.psi.ch/packages/a2/20/dc990edb7b075ecdba4e02bcd03d1583faeb84f664d1585c4c00a0f9851a/mpi4py-4.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c4ef9fe5fb211b1c5b6afe521397e3feb01e104024d6bc37aa4289c370605e2", size = 1318018, upload-time = "2025-10-10T13:54:23.23Z" }, + { url = "https://pypi.psi.ch/packages/4e/bf/b0ab43a99ac2a1d6d5765cb7d2a4f093656090ce07528043057ecc3e87cb/mpi4py-4.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:e13a1ba26604514a12c95b7d76058ce800d5740d5f5f3b50c4b782cfa0dfaa1f", size = 1722939, upload-time = "2025-10-10T13:54:24.862Z" }, + { url = "https://pypi.psi.ch/packages/84/26/3e00dc536311e758096414b4f33beb4c7f04dff875e87a6e88fbbe4fc2d8/mpi4py-4.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:28ce1f7412f5e99a6b9fe2547203633431d0ee45670413a475a07e6c785e63b1", size = 1798116, upload-time = "2025-10-10T13:54:26.378Z" }, + { url = "https://pypi.psi.ch/packages/15/51/d06d2b126be5660aca8c00fe0d940a8658085038f61a9cfc834d3d5ffa80/mpi4py-4.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd1e49b84a0651018517e87daf68085719eca25e5c9a7cd05d98a73418c88836", size = 1586285, upload-time = "2025-10-10T13:54:27.838Z" }, + { url = "https://pypi.psi.ch/packages/51/63/eeb936e0e8cfd8160b6b297645c730b22d242595861cf6a2fa627a358175/mpi4py-4.1.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dd869ea7758b591ffbb1483588a6fbf84952a5090e80a45ea89674d55cf25f3b", size = 1514102, upload-time = "2025-10-10T13:54:29.297Z" }, + { url = "https://pypi.psi.ch/packages/1a/c1/06967d4c107ea7169d2120c4fb86c404707e6de82e277dc9f0fa5a9c1bf1/mpi4py-4.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:475da0797442cba723c0ad37da6a1c51d9624e697dd8bf89f23d0fad81e73eda", size = 1395247, upload-time = "2025-10-10T13:54:30.881Z" }, + { url = "https://pypi.psi.ch/packages/9e/7c/5f0f32b39185f0a7074c165dc37cdd235bfd737928a2fe223e41b308fb4c/mpi4py-4.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8d3bfa074776d9507ee957f5230d11ecd03da23f601a85349a1a333eaf55e5fa", size = 1771515, upload-time = "2025-10-10T13:54:32.395Z" }, + { url = "https://pypi.psi.ch/packages/e9/63/b6a2863fb7dd5a9eccfdb055bf1124b999ff755d0187223b307161479b76/mpi4py-4.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:95bb98d946eb88c9ae4dc6c42d11b3af8ce6b91e644c288cc3f85ec7596ffcd3", size = 1480110, upload-time = "2025-10-10T13:55:11.381Z" }, + { url = "https://pypi.psi.ch/packages/de/18/358f0eb58fb3b79f65861ed682af9e735d86669663dfbce396e8673ed518/mpi4py-4.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84e9eb2e609b0b94cd0e9a3e3b57d897f748fb0207c4f72e81e5a95aba033767", size = 1340704, upload-time = "2025-10-10T13:55:12.973Z" }, + { url = "https://pypi.psi.ch/packages/b9/66/b342e330ac543d0147ebfab754f69854c4777ac9785cb5b7610e3cd0c29a/mpi4py-4.1.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:027b1a1ff9d57afed10af6b79041b95f85fd11b2af74e4c34ef4866ce81ecc24", size = 1380452, upload-time = "2025-10-10T13:55:14.582Z" }, + { url = "https://pypi.psi.ch/packages/dd/61/bbf87de6f3a8a9c54e7a4b72878c9069646ca9cafac8217fa5493a54b068/mpi4py-4.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c1191856906967a48fdcc484b326c179747e68c186261d76480a75156bcc73bf", size = 1255980, upload-time = "2025-10-10T13:55:17.075Z" }, + { url = "https://pypi.psi.ch/packages/8d/4b/227091dec11518e5545bd1ec91f52e06f64bdae697adc5fb33f9f20c04dc/mpi4py-4.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:189d49b0ae963f8f6f5dd8ed0f5f37923285c97bc725476990ec0556972bb4b2", size = 1452641, upload-time = "2025-10-10T13:55:18.562Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.psi.ch/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://pypi.psi.ch/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.psi.ch/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://pypi.psi.ch/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" }, + { url = "https://pypi.psi.ch/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411, upload-time = "2024-02-05T23:48:29.038Z" }, + { url = "https://pypi.psi.ch/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016, upload-time = "2024-02-05T23:48:54.098Z" }, + { url = "https://pypi.psi.ch/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889, upload-time = "2024-02-05T23:49:25.361Z" }, + { url = "https://pypi.psi.ch/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746, upload-time = "2024-02-05T23:49:51.983Z" }, + { url = "https://pypi.psi.ch/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620, upload-time = "2024-02-05T23:50:22.515Z" }, + { url = "https://pypi.psi.ch/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659, upload-time = "2024-02-05T23:50:35.834Z" }, + { url = "https://pypi.psi.ch/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905, upload-time = "2024-02-05T23:51:03.701Z" }, + { url = "https://pypi.psi.ch/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, + { url = "https://pypi.psi.ch/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, + { url = "https://pypi.psi.ch/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, + { url = "https://pypi.psi.ch/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, + { url = "https://pypi.psi.ch/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, + { url = "https://pypi.psi.ch/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, + { url = "https://pypi.psi.ch/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, + { url = "https://pypi.psi.ch/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, + { url = "https://pypi.psi.ch/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://pypi.psi.ch/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://pypi.psi.ch/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://pypi.psi.ch/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://pypi.psi.ch/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://pypi.psi.ch/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://pypi.psi.ch/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://pypi.psi.ch/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://pypi.psi.ch/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://pypi.psi.ch/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://pypi.psi.ch/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://pypi.psi.ch/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://pypi.psi.ch/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://pypi.psi.ch/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://pypi.psi.ch/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://pypi.psi.ch/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://pypi.psi.ch/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://pypi.psi.ch/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://pypi.psi.ch/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://pypi.psi.ch/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://pypi.psi.ch/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://pypi.psi.ch/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://pypi.psi.ch/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://pypi.psi.ch/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://pypi.psi.ch/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://pypi.psi.ch/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://pypi.psi.ch/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://pypi.psi.ch/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://pypi.psi.ch/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://pypi.psi.ch/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://pypi.psi.ch/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://pypi.psi.ch/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://pypi.psi.ch/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://pypi.psi.ch/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://pypi.psi.ch/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://pypi.psi.ch/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://pypi.psi.ch/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://pypi.psi.ch/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://pypi.psi.ch/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://pypi.psi.ch/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://pypi.psi.ch/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://pypi.psi.ch/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, +] + +[[package]] +name = "paramz" +version = "0.9.6" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "decorator" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "six" }, +] +wheels = [ + { url = "https://pypi.psi.ch/packages/67/5b/f9b09d1e5b67ee147492f7117ee5519cc70bbcd595e313b2d6b31dba33ef/paramz-0.9.6-py3-none-any.whl", hash = "sha256:4916be6f77f457316bcac8460be9c226026aed81fe7be302b32c0ba74e2ac6dd", size = 103203, upload-time = "2024-01-12T16:56:47.386Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "patsy" +version = "1.0.2" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.psi.ch/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301, upload-time = "2025-10-20T16:17:36.563Z" }, +] + +[[package]] +name = "periodictable" +version = "2.0.2" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pyparsing" }, +] +wheels = [ + { url = "https://pypi.psi.ch/packages/82/e4/f2a338abfb5a6b9e2a48dc4acaa9df0bd94126df336ab403178f6a7a901a/periodictable-2.0.2-py3-none-any.whl", hash = "sha256:7d0680baf758715dd66bdac733de82dcb976aa03981fbd6d75a84213fb3b5f86", size = 811077, upload-time = "2024-12-20T02:26:26.165Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://pypi.psi.ch/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "phagen" +version = "0.1.0" +source = { directory = "subprojects/phagen" } + +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/5d/08/26e68b6b5da219c2a2cb7b563af008b53bb8e6b6fcb3fa40715fcdb2523a/pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b", size = 5289809, upload-time = "2025-10-15T18:21:27.791Z" }, + { url = "https://pypi.psi.ch/packages/cb/e9/4e58fb097fb74c7b4758a680aacd558810a417d1edaa7000142976ef9d2f/pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1", size = 4650606, upload-time = "2025-10-15T18:21:29.823Z" }, + { url = "https://pypi.psi.ch/packages/4b/e0/1fa492aa9f77b3bc6d471c468e62bfea1823056bf7e5e4f1914d7ab2565e/pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363", size = 6221023, upload-time = "2025-10-15T18:21:31.415Z" }, + { url = "https://pypi.psi.ch/packages/c1/09/4de7cd03e33734ccd0c876f0251401f1314e819cbfd89a0fcb6e77927cc6/pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca", size = 8024937, upload-time = "2025-10-15T18:21:33.453Z" }, + { url = "https://pypi.psi.ch/packages/2e/69/0688e7c1390666592876d9d474f5e135abb4acb39dcb583c4dc5490f1aff/pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e", size = 6334139, upload-time = "2025-10-15T18:21:35.395Z" }, + { url = "https://pypi.psi.ch/packages/ed/1c/880921e98f525b9b44ce747ad1ea8f73fd7e992bafe3ca5e5644bf433dea/pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782", size = 7026074, upload-time = "2025-10-15T18:21:37.219Z" }, + { url = "https://pypi.psi.ch/packages/28/03/96f718331b19b355610ef4ebdbbde3557c726513030665071fd025745671/pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10", size = 6448852, upload-time = "2025-10-15T18:21:39.168Z" }, + { url = "https://pypi.psi.ch/packages/3a/a0/6a193b3f0cc9437b122978d2c5cbce59510ccf9a5b48825096ed7472da2f/pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa", size = 7117058, upload-time = "2025-10-15T18:21:40.997Z" }, + { url = "https://pypi.psi.ch/packages/a7/c4/043192375eaa4463254e8e61f0e2ec9a846b983929a8d0a7122e0a6d6fff/pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275", size = 6295431, upload-time = "2025-10-15T18:21:42.518Z" }, + { url = "https://pypi.psi.ch/packages/92/c6/c2f2fc7e56301c21827e689bb8b0b465f1b52878b57471a070678c0c33cd/pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d", size = 7000412, upload-time = "2025-10-15T18:21:44.404Z" }, + { url = "https://pypi.psi.ch/packages/b2/d2/5f675067ba82da7a1c238a73b32e3fd78d67f9d9f80fbadd33a40b9c0481/pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7", size = 2435903, upload-time = "2025-10-15T18:21:46.29Z" }, + { url = "https://pypi.psi.ch/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, + { url = "https://pypi.psi.ch/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, + { url = "https://pypi.psi.ch/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, + { url = "https://pypi.psi.ch/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, + { url = "https://pypi.psi.ch/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, + { url = "https://pypi.psi.ch/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, + { url = "https://pypi.psi.ch/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, + { url = "https://pypi.psi.ch/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, + { url = "https://pypi.psi.ch/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, + { url = "https://pypi.psi.ch/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, + { url = "https://pypi.psi.ch/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, + { url = "https://pypi.psi.ch/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, + { url = "https://pypi.psi.ch/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, + { url = "https://pypi.psi.ch/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, + { url = "https://pypi.psi.ch/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, + { url = "https://pypi.psi.ch/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, + { url = "https://pypi.psi.ch/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, + { url = "https://pypi.psi.ch/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, + { url = "https://pypi.psi.ch/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, + { url = "https://pypi.psi.ch/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, + { url = "https://pypi.psi.ch/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, + { url = "https://pypi.psi.ch/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, + { url = "https://pypi.psi.ch/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" }, + { url = "https://pypi.psi.ch/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" }, + { url = "https://pypi.psi.ch/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" }, + { url = "https://pypi.psi.ch/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" }, + { url = "https://pypi.psi.ch/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" }, + { url = "https://pypi.psi.ch/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" }, + { url = "https://pypi.psi.ch/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" }, + { url = "https://pypi.psi.ch/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" }, + { url = "https://pypi.psi.ch/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" }, + { url = "https://pypi.psi.ch/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" }, + { url = "https://pypi.psi.ch/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" }, + { url = "https://pypi.psi.ch/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" }, + { url = "https://pypi.psi.ch/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" }, + { url = "https://pypi.psi.ch/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" }, + { url = "https://pypi.psi.ch/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" }, + { url = "https://pypi.psi.ch/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" }, + { url = "https://pypi.psi.ch/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" }, + { url = "https://pypi.psi.ch/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" }, + { url = "https://pypi.psi.ch/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" }, + { url = "https://pypi.psi.ch/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" }, + { url = "https://pypi.psi.ch/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" }, + { url = "https://pypi.psi.ch/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" }, + { url = "https://pypi.psi.ch/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" }, + { url = "https://pypi.psi.ch/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" }, + { url = "https://pypi.psi.ch/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" }, + { url = "https://pypi.psi.ch/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, + { url = "https://pypi.psi.ch/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, + { url = "https://pypi.psi.ch/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, + { url = "https://pypi.psi.ch/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, + { url = "https://pypi.psi.ch/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, + { url = "https://pypi.psi.ch/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, + { url = "https://pypi.psi.ch/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pmsco" +version = "4.2.0" +source = { editable = "." } +dependencies = [ + { name = "ase" }, + { name = "edac" }, + { name = "fasteners" }, + { name = "future" }, + { name = "h5py" }, + { name = "ipykernel" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.8.0", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jsonschema" }, + { name = "loess" }, + { name = "matplotlib" }, + { name = "mpi4py" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, + { name = "periodictable" }, + { name = "phagen" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.psi.ch/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy" }, + { name = "sqlalchemy" }, + { name = "statsmodels" }, +] + +[package.optional-dependencies] +boss = [ + { name = "aalto-boss" }, +] + +[package.dev-dependencies] +dev = [ + { name = "gitpython" }, + { name = "mock" }, + { name = "pynose" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "aalto-boss", marker = "extra == 'boss'" }, + { name = "ase" }, + { name = "edac", directory = "subprojects/edac" }, + { name = "fasteners" }, + { name = "future" }, + { name = "h5py" }, + { name = "ipykernel" }, + { name = "ipython" }, + { name = "jsonschema" }, + { name = "loess", directory = "subprojects/loess" }, + { name = "matplotlib" }, + { name = "mpi4py" }, + { name = "networkx" }, + { name = "numpy", specifier = ">=1.22,<2" }, + { name = "periodictable" }, + { name = "phagen", directory = "subprojects/phagen" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "sqlalchemy", specifier = "==1.4" }, + { name = "statsmodels" }, +] +provides-extras = ["boss"] + +[package.metadata.requires-dev] +dev = [ + { name = "gitpython" }, + { name = "mock" }, + { name = "pynose" }, + { name = "ruff", specifier = ">=0.14.10" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://pypi.psi.ch/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psutil" +version = "7.1.3" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://pypi.psi.ch/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://pypi.psi.ch/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://pypi.psi.ch/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://pypi.psi.ch/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://pypi.psi.ch/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://pypi.psi.ch/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://pypi.psi.ch/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://pypi.psi.ch/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://pypi.psi.ch/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://pypi.psi.ch/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://pypi.psi.ch/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pynose" +version = "1.5.5" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/15/ad/1b5d064d53a3cc33999649c5f3b0e7cee739e7404a30bb7efd1443604a7c/pynose-1.5.5.tar.gz", hash = "sha256:81da4e26473f98dd37497248eef4352d3221d1d56edf874a00c6bdda6daf7f49", size = 123419, upload-time = "2025-09-08T20:45:15.595Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/29/02/70dd00c947bcb4a1e9f883761aa4632a9d61b5bf5f5ad4fae5e8cb06200c/pynose-1.5.5-py3-none-any.whl", hash = "sha256:673751d53fcfc79b1e48c14f36c7a24779ad43676eeb85736934de6a2b3d8ec8", size = 130706, upload-time = "2025-09-08T20:45:13.697Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://pypi.psi.ch/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://pypi.psi.ch/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://pypi.psi.ch/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://pypi.psi.ch/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://pypi.psi.ch/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://pypi.psi.ch/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://pypi.psi.ch/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://pypi.psi.ch/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://pypi.psi.ch/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://pypi.psi.ch/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { url = "https://pypi.psi.ch/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://pypi.psi.ch/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://pypi.psi.ch/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://pypi.psi.ch/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://pypi.psi.ch/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://pypi.psi.ch/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://pypi.psi.ch/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://pypi.psi.ch/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://pypi.psi.ch/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://pypi.psi.ch/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://pypi.psi.ch/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://pypi.psi.ch/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://pypi.psi.ch/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://pypi.psi.ch/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://pypi.psi.ch/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://pypi.psi.ch/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://pypi.psi.ch/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://pypi.psi.ch/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://pypi.psi.ch/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://pypi.psi.ch/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://pypi.psi.ch/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://pypi.psi.ch/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://pypi.psi.ch/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://pypi.psi.ch/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://pypi.psi.ch/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://pypi.psi.ch/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://pypi.psi.ch/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://pypi.psi.ch/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://pypi.psi.ch/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://pypi.psi.ch/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://pypi.psi.ch/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://pypi.psi.ch/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://pypi.psi.ch/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://pypi.psi.ch/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://pypi.psi.ch/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://pypi.psi.ch/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://pypi.psi.ch/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { url = "https://pypi.psi.ch/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://pypi.psi.ch/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://pypi.psi.ch/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://pypi.psi.ch/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://pypi.psi.ch/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://pypi.psi.ch/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://pypi.psi.ch/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://pypi.psi.ch/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://pypi.psi.ch/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://pypi.psi.ch/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://pypi.psi.ch/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://pypi.psi.ch/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://pypi.psi.ch/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://pypi.psi.ch/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://pypi.psi.ch/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://pypi.psi.ch/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://pypi.psi.ch/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://pypi.psi.ch/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://pypi.psi.ch/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://pypi.psi.ch/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://pypi.psi.ch/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://pypi.psi.ch/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://pypi.psi.ch/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://pypi.psi.ch/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://pypi.psi.ch/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://pypi.psi.ch/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://pypi.psi.ch/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://pypi.psi.ch/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://pypi.psi.ch/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://pypi.psi.ch/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://pypi.psi.ch/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://pypi.psi.ch/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://pypi.psi.ch/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://pypi.psi.ch/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://pypi.psi.ch/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://pypi.psi.ch/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://pypi.psi.ch/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://pypi.psi.ch/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://pypi.psi.ch/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://pypi.psi.ch/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://pypi.psi.ch/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://pypi.psi.ch/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://pypi.psi.ch/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://pypi.psi.ch/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://pypi.psi.ch/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://pypi.psi.ch/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://pypi.psi.ch/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://pypi.psi.ch/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://pypi.psi.ch/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://pypi.psi.ch/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://pypi.psi.ch/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://pypi.psi.ch/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://pypi.psi.ch/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://pypi.psi.ch/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://pypi.psi.ch/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://pypi.psi.ch/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://pypi.psi.ch/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://pypi.psi.ch/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://pypi.psi.ch/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://pypi.psi.ch/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://pypi.psi.ch/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://pypi.psi.ch/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://pypi.psi.ch/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://pypi.psi.ch/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://pypi.psi.ch/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://pypi.psi.ch/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://pypi.psi.ch/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://pypi.psi.ch/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://pypi.psi.ch/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://pypi.psi.ch/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://pypi.psi.ch/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://pypi.psi.ch/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://pypi.psi.ch/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://pypi.psi.ch/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://pypi.psi.ch/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://pypi.psi.ch/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://pypi.psi.ch/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://pypi.psi.ch/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://pypi.psi.ch/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://pypi.psi.ch/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://pypi.psi.ch/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://pypi.psi.ch/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://pypi.psi.ch/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://pypi.psi.ch/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://pypi.psi.ch/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://pypi.psi.ch/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://pypi.psi.ch/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://pypi.psi.ch/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://pypi.psi.ch/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.10" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, + { url = "https://pypi.psi.ch/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, + { url = "https://pypi.psi.ch/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, + { url = "https://pypi.psi.ch/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, + { url = "https://pypi.psi.ch/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, + { url = "https://pypi.psi.ch/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, + { url = "https://pypi.psi.ch/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, + { url = "https://pypi.psi.ch/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, + { url = "https://pypi.psi.ch/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, + { url = "https://pypi.psi.ch/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, + { url = "https://pypi.psi.ch/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, + { url = "https://pypi.psi.ch/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, + { url = "https://pypi.psi.ch/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, + { url = "https://pypi.psi.ch/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, + { url = "https://pypi.psi.ch/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, + { url = "https://pypi.psi.ch/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, + { url = "https://pypi.psi.ch/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, + { url = "https://pypi.psi.ch/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.psi.ch/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "joblib", marker = "python_full_version < '3.11'" }, + { name = "numpy", marker = "python_full_version < '3.11'" }, + { name = "scipy", marker = "python_full_version < '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f", size = 9336221, upload-time = "2025-09-09T08:20:19.328Z" }, + { url = "https://pypi.psi.ch/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c", size = 8653834, upload-time = "2025-09-09T08:20:22.073Z" }, + { url = "https://pypi.psi.ch/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8", size = 9660938, upload-time = "2025-09-09T08:20:24.327Z" }, + { url = "https://pypi.psi.ch/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18", size = 9477818, upload-time = "2025-09-09T08:20:26.845Z" }, + { url = "https://pypi.psi.ch/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5", size = 8886969, upload-time = "2025-09-09T08:20:29.329Z" }, + { url = "https://pypi.psi.ch/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://pypi.psi.ch/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://pypi.psi.ch/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://pypi.psi.ch/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://pypi.psi.ch/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, + { url = "https://pypi.psi.ch/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://pypi.psi.ch/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://pypi.psi.ch/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://pypi.psi.ch/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://pypi.psi.ch/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, + { url = "https://pypi.psi.ch/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://pypi.psi.ch/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://pypi.psi.ch/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://pypi.psi.ch/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://pypi.psi.ch/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://pypi.psi.ch/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://pypi.psi.ch/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://pypi.psi.ch/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://pypi.psi.ch/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://pypi.psi.ch/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.8.0" +source = { registry = "https://pypi.psi.ch/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "joblib", marker = "python_full_version >= '3.11'" }, + { name = "numpy", marker = "python_full_version >= '3.11'" }, + { name = "scipy", marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://pypi.psi.ch/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/c9/92/53ea2181da8ac6bf27170191028aee7251f8f841f8d3edbfdcaf2008fde9/scikit_learn-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:146b4d36f800c013d267b29168813f7a03a43ecd2895d04861f1240b564421da", size = 8595835, upload-time = "2025-12-10T07:07:39.385Z" }, + { url = "https://pypi.psi.ch/packages/01/18/d154dc1638803adf987910cdd07097d9c526663a55666a97c124d09fb96a/scikit_learn-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f984ca4b14914e6b4094c5d52a32ea16b49832c03bd17a110f004db3c223e8e1", size = 8080381, upload-time = "2025-12-10T07:07:41.93Z" }, + { url = "https://pypi.psi.ch/packages/8a/44/226142fcb7b7101e64fdee5f49dbe6288d4c7af8abf593237b70fca080a4/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e30adb87f0cc81c7690a84f7932dd66be5bac57cfe16b91cb9151683a4a2d3b", size = 8799632, upload-time = "2025-12-10T07:07:43.899Z" }, + { url = "https://pypi.psi.ch/packages/36/4d/4a67f30778a45d542bbea5db2dbfa1e9e100bf9ba64aefe34215ba9f11f6/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ada8121bcb4dac28d930febc791a69f7cb1673c8495e5eee274190b73a4559c1", size = 9103788, upload-time = "2025-12-10T07:07:45.982Z" }, + { url = "https://pypi.psi.ch/packages/89/3c/45c352094cfa60050bcbb967b1faf246b22e93cb459f2f907b600f2ceda5/scikit_learn-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:c57b1b610bd1f40ba43970e11ce62821c2e6569e4d74023db19c6b26f246cb3b", size = 8081706, upload-time = "2025-12-10T07:07:48.111Z" }, + { url = "https://pypi.psi.ch/packages/3d/46/5416595bb395757f754feb20c3d776553a386b661658fb21b7c814e89efe/scikit_learn-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:2838551e011a64e3053ad7618dda9310175f7515f1742fa2d756f7c874c05961", size = 7688451, upload-time = "2025-12-10T07:07:49.873Z" }, + { url = "https://pypi.psi.ch/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, + { url = "https://pypi.psi.ch/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, + { url = "https://pypi.psi.ch/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, + { url = "https://pypi.psi.ch/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, + { url = "https://pypi.psi.ch/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, + { url = "https://pypi.psi.ch/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, + { url = "https://pypi.psi.ch/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, + { url = "https://pypi.psi.ch/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, + { url = "https://pypi.psi.ch/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, + { url = "https://pypi.psi.ch/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, + { url = "https://pypi.psi.ch/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, + { url = "https://pypi.psi.ch/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, + { url = "https://pypi.psi.ch/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, + { url = "https://pypi.psi.ch/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, + { url = "https://pypi.psi.ch/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, + { url = "https://pypi.psi.ch/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, + { url = "https://pypi.psi.ch/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, + { url = "https://pypi.psi.ch/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, +] + +[[package]] +name = "scipy" +version = "1.12.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.psi.ch/packages/30/85/cdbf2c3c460fe5aae812917866392068a88d02f07de0fe31ce738734c477/scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3", size = 56811768, upload-time = "2024-01-20T21:13:43.442Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/c7/d9/214971dae573bd7e9303b56d2612dae439decbfc0dae0f539a591c0562ce/scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b", size = 38900384, upload-time = "2024-01-20T21:10:31.498Z" }, + { url = "https://pypi.psi.ch/packages/dd/14/549fd7066a112c4bdf1cc11228d11284bc784ea09124fc4d663f28815564/scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1", size = 31357553, upload-time = "2024-01-20T21:10:38.509Z" }, + { url = "https://pypi.psi.ch/packages/69/1d/0582401b6d77865e080c90f39e52f65ca2bdc94e668e0bfbed8977dae3f4/scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563", size = 34789974, upload-time = "2024-01-20T21:10:45.054Z" }, + { url = "https://pypi.psi.ch/packages/f5/aa/8e6071a5e4dca4ec68b5b22e4991ee74c59c5d372112b9c236ec1faff57d/scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c", size = 38441046, upload-time = "2024-01-20T21:10:51.285Z" }, + { url = "https://pypi.psi.ch/packages/65/9e/43b86ec57ecdc9931b43aaf727f9d71743bfd06bdddfd441165bd3d8c6be/scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd", size = 38630107, upload-time = "2024-01-20T21:10:58.406Z" }, + { url = "https://pypi.psi.ch/packages/fd/a7/5f829b100d208c85163aecba93faf01d088d944fc91585338751d812f1e4/scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2", size = 46191228, upload-time = "2024-01-20T21:11:05.92Z" }, + { url = "https://pypi.psi.ch/packages/c3/32/7915195ca4643508fe9730691eaed57b879646279572b10b02bdadf165c5/scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08", size = 38908720, upload-time = "2024-01-20T21:11:13.467Z" }, + { url = "https://pypi.psi.ch/packages/21/d4/e6c57acc61e59cd46acca27af1f400094d5dee218e372cc604b8162b97cb/scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c", size = 31392892, upload-time = "2024-01-20T21:11:18.947Z" }, + { url = "https://pypi.psi.ch/packages/e3/c5/d40abc1a857c1c6519e1a4e096d6aee86861eddac019fb736b6af8a58d25/scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467", size = 34733860, upload-time = "2024-01-20T21:11:26.666Z" }, + { url = "https://pypi.psi.ch/packages/d4/b8/7169935f9a2ea9e274ad8c21d6133d492079e6ebc3fc69a915c2375616b0/scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a", size = 38418720, upload-time = "2024-01-20T21:11:33.479Z" }, + { url = "https://pypi.psi.ch/packages/64/e7/4dbb779d09d1cb757ddbe42cae7c4fe8270497566bb902138d637b04d88c/scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba", size = 38652247, upload-time = "2024-01-20T21:11:40.229Z" }, + { url = "https://pypi.psi.ch/packages/9a/25/5b30cb3efc9566f0ebeaeca1976150316353c17031ad7868ef46de5ab8dc/scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70", size = 46162940, upload-time = "2024-01-20T21:11:47.726Z" }, + { url = "https://pypi.psi.ch/packages/0d/4a/b2b2cae0c5dfd46361245a67102886ed7188805bdf7044e36fe838bbcf26/scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372", size = 38911995, upload-time = "2024-01-20T21:11:54.759Z" }, + { url = "https://pypi.psi.ch/packages/71/ba/744bbdd65eb3fce1412dd4633fc425ad39e6b4068b5b158aee1cd3afeb54/scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3", size = 31433326, upload-time = "2024-01-20T21:12:00.295Z" }, + { url = "https://pypi.psi.ch/packages/db/fd/81feac476e1ae495b51b8c3636aee1f50a1c5ca2a3557f5b0043d4e2fb02/scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc", size = 34165749, upload-time = "2024-01-20T21:12:06.38Z" }, + { url = "https://pypi.psi.ch/packages/11/7d/850bfe9462fff393130519eb54f97d43ad9c280ec4297b4cb98b7c2e96cd/scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c", size = 37790844, upload-time = "2024-01-20T21:12:12.826Z" }, + { url = "https://pypi.psi.ch/packages/7e/7f/504b7b3834d8c9229831c6c58a44943e29a34004eeb34c7ff150add4e001/scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338", size = 38026369, upload-time = "2024-01-20T21:12:19.69Z" }, + { url = "https://pypi.psi.ch/packages/f3/31/91a2a3c5eb85d2bfa86d7c98f2df5d77dcdefb3d80ca9f9037ad04393acf/scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c", size = 45816713, upload-time = "2024-01-20T21:12:26.619Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "1.4.0" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "greenlet" }, +] +sdist = { url = "https://pypi.psi.ch/packages/d4/73/d59247fa14556efa5981879252cef3d8e1bd1db9bb4895e34957b9f7d8e8/SQLAlchemy-1.4.0.tar.gz", hash = "sha256:9cfef2ad30c5ee1d494d98f3c55a9ac29ec6d294b70849c541d139e4fe1a74e6", size = 7440276, upload-time = "2021-03-15T16:59:17.56Z" } + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://pypi.psi.ch/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "statsmodels" +version = "0.14.6" +source = { registry = "https://pypi.psi.ch/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "patsy" }, + { name = "scipy" }, +] +sdist = { url = "https://pypi.psi.ch/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/b5/6d/9ec309a175956f88eb8420ac564297f37cf9b1f73f89db74da861052dc29/statsmodels-0.14.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4ff0649a2df674c7ffb6fa1a06bffdb82a6adf09a48e90e000a15a6aaa734b0", size = 10142419, upload-time = "2025-12-05T19:27:35.625Z" }, + { url = "https://pypi.psi.ch/packages/86/8f/338c5568315ec5bf3ac7cd4b71e34b98cb3b0f834919c0c04a0762f878a1/statsmodels-0.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:109012088b3e370080846ab053c76d125268631410142daad2f8c10770e8e8d9", size = 10022819, upload-time = "2025-12-05T19:27:49.385Z" }, + { url = "https://pypi.psi.ch/packages/b0/77/5fc4cbc2d608f9b483b0675f82704a8bcd672962c379fe4d82100d388dbf/statsmodels-0.14.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e93bd5d220f3cb6fc5fc1bffd5b094966cab8ee99f6c57c02e95710513d6ac3f", size = 10118927, upload-time = "2025-12-05T23:07:51.256Z" }, + { url = "https://pypi.psi.ch/packages/94/55/b86c861c32186403fe121d9ab27bc16d05839b170d92a978beb33abb995e/statsmodels-0.14.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06eec42d682fdb09fe5d70a05930857efb141754ec5a5056a03304c1b5e32fd9", size = 10413015, upload-time = "2025-12-05T23:08:53.95Z" }, + { url = "https://pypi.psi.ch/packages/f9/be/daf0dba729ccdc4176605f4a0fd5cfe71cdda671749dca10e74a732b8b1c/statsmodels-0.14.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0444e88557df735eda7db330806fe09d51c9f888bb1f5906cb3a61fb1a3ed4a8", size = 10441248, upload-time = "2025-12-05T23:09:09.353Z" }, + { url = "https://pypi.psi.ch/packages/9a/1c/2e10b7c7cc44fa418272996bf0427b8016718fd62f995d9c1f7ab37adf35/statsmodels-0.14.6-cp310-cp310-win_amd64.whl", hash = "sha256:e83a9abe653835da3b37fb6ae04b45480c1de11b3134bd40b09717192a1456ea", size = 9583410, upload-time = "2025-12-05T19:28:02.086Z" }, + { url = "https://pypi.psi.ch/packages/a9/4d/df4dd089b406accfc3bb5ee53ba29bb3bdf5ae61643f86f8f604baa57656/statsmodels-0.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ad5c2810fc6c684254a7792bf1cbaf1606cdee2a253f8bd259c43135d87cfb4", size = 10121514, upload-time = "2025-12-05T19:28:16.521Z" }, + { url = "https://pypi.psi.ch/packages/82/af/ec48daa7f861f993b91a0dcc791d66e1cf56510a235c5cbd2ab991a31d5c/statsmodels-0.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:341fa68a7403e10a95c7b6e41134b0da3a7b835ecff1eb266294408535a06eb6", size = 10003346, upload-time = "2025-12-05T19:28:29.568Z" }, + { url = "https://pypi.psi.ch/packages/a9/2c/c8f7aa24cd729970728f3f98822fb45149adc216f445a9301e441f7ac760/statsmodels-0.14.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf1dfe2a3ca56f5529118baf33a13efed2783c528f4a36409b46bbd2d9d48eb", size = 10129872, upload-time = "2025-12-05T23:09:25.724Z" }, + { url = "https://pypi.psi.ch/packages/40/c6/9ae8e9b0721e9b6eb5f340c3a0ce8cd7cce4f66e03dd81f80d60f111987f/statsmodels-0.14.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3764ba8195c9baf0925a96da0743ff218067a269f01d155ca3558deed2658ca", size = 10381964, upload-time = "2025-12-05T23:09:41.326Z" }, + { url = "https://pypi.psi.ch/packages/28/8c/cf3d30c8c2da78e2ad1f50ade8b7fabec3ff4cdfc56fbc02e097c4577f90/statsmodels-0.14.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e8d2e519852adb1b420e018f5ac6e6684b2b877478adf7fda2cfdb58f5acb5d", size = 10409611, upload-time = "2025-12-05T23:09:57.131Z" }, + { url = "https://pypi.psi.ch/packages/bf/cc/018f14ecb58c6cb89de9d52695740b7d1f5a982aa9ea312483ea3c3d5f77/statsmodels-0.14.6-cp311-cp311-win_amd64.whl", hash = "sha256:2738a00fca51196f5a7d44b06970ace6b8b30289839e4808d656f8a98e35faa7", size = 9580385, upload-time = "2025-12-05T19:28:42.778Z" }, + { url = "https://pypi.psi.ch/packages/25/ce/308e5e5da57515dd7cab3ec37ea2d5b8ff50bef1fcc8e6d31456f9fae08e/statsmodels-0.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe76140ae7adc5ff0e60a3f0d56f4fffef484efa803c3efebf2fcd734d72ecb5", size = 10091932, upload-time = "2025-12-05T19:28:55.446Z" }, + { url = "https://pypi.psi.ch/packages/05/30/affbabf3c27fb501ec7b5808230c619d4d1a4525c07301074eb4bda92fa9/statsmodels-0.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26d4f0ed3b31f3c86f83a92f5c1f5cbe63fc992cd8915daf28ca49be14463a1c", size = 9997345, upload-time = "2025-12-05T19:29:10.278Z" }, + { url = "https://pypi.psi.ch/packages/48/f5/3a73b51e6450c31652c53a8e12e24eac64e3824be816c0c2316e7dbdcb7d/statsmodels-0.14.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c00a42863e4f4733ac9d078bbfad816249c01451740e6f5053ecc7db6d6368", size = 10058649, upload-time = "2025-12-05T23:10:12.775Z" }, + { url = "https://pypi.psi.ch/packages/81/68/dddd76117df2ef14c943c6bbb6618be5c9401280046f4ddfc9fb4596a1b8/statsmodels-0.14.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b58cf7474aa9e7e3b0771a66537148b2df9b5884fbf156096c0e6c1ff0469d", size = 10339446, upload-time = "2025-12-05T23:10:28.503Z" }, + { url = "https://pypi.psi.ch/packages/56/4a/dce451c74c4050535fac1ec0c14b80706d8fc134c9da22db3c8a0ec62c33/statsmodels-0.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e7dcc5e9587f2567e52deaff5220b175bf2f648951549eae5fc9383b62bc37", size = 10368705, upload-time = "2025-12-05T23:10:44.339Z" }, + { url = "https://pypi.psi.ch/packages/60/15/3daba2df40be8b8a9a027d7f54c8dedf24f0d81b96e54b52293f5f7e3418/statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f", size = 9543991, upload-time = "2025-12-05T23:10:58.536Z" }, + { url = "https://pypi.psi.ch/packages/81/59/a5aad5b0cc266f5be013db8cde563ac5d2a025e7efc0c328d83b50c72992/statsmodels-0.14.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47ee7af083623d2091954fa71c7549b8443168f41b7c5dce66510274c50fd73e", size = 10072009, upload-time = "2025-12-05T23:11:14.021Z" }, + { url = "https://pypi.psi.ch/packages/53/dd/d8cfa7922fc6dc3c56fa6c59b348ea7de829a94cd73208c6f8202dd33f17/statsmodels-0.14.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa60d82e29fcd0a736e86feb63a11d2380322d77a9369a54be8b0965a3985f71", size = 9980018, upload-time = "2025-12-05T23:11:30.907Z" }, + { url = "https://pypi.psi.ch/packages/ee/77/0ec96803eba444efd75dba32f2ef88765ae3e8f567d276805391ec2c98c6/statsmodels-0.14.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89ee7d595f5939cc20bf946faedcb5137d975f03ae080f300ebb4398f16a5bd4", size = 10060269, upload-time = "2025-12-05T23:11:46.338Z" }, + { url = "https://pypi.psi.ch/packages/10/b9/fd41f1f6af13a1a1212a06bb377b17762feaa6d656947bf666f76300fc05/statsmodels-0.14.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:730f3297b26749b216a06e4327fe0be59b8d05f7d594fb6caff4287b69654589", size = 10324155, upload-time = "2025-12-05T23:12:01.805Z" }, + { url = "https://pypi.psi.ch/packages/ee/0f/a6900e220abd2c69cd0a07e3ad26c71984be6061415a60e0f17b152ecf08/statsmodels-0.14.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f1c08befa85e93acc992b72a390ddb7bd876190f1360e61d10cf43833463bc9c", size = 10349765, upload-time = "2025-12-05T23:12:18.018Z" }, + { url = "https://pypi.psi.ch/packages/98/08/b79f0c614f38e566eebbdcff90c0bcacf3c6ba7a5bbb12183c09c29ca400/statsmodels-0.14.6-cp313-cp313-win_amd64.whl", hash = "sha256:8021271a79f35b842c02a1794465a651a9d06ec2080f76ebc3b7adce77d08233", size = 9540043, upload-time = "2025-12-05T23:12:33.887Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://pypi.psi.ch/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://pypi.psi.ch/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://pypi.psi.ch/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://pypi.psi.ch/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://pypi.psi.ch/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://pypi.psi.ch/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://pypi.psi.ch/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://pypi.psi.ch/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://pypi.psi.ch/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://pypi.psi.ch/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.psi.ch/simple" } +sdist = { url = "https://pypi.psi.ch/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://pypi.psi.ch/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +]