├── .gitattributes ├── .github └── workflows │ ├── PyQUDA-Utils.yaml │ └── PyQUDA.yaml ├── .gitignore ├── .gitmodules ├── LICENSE ├── MANIFEST.in ├── README.md ├── examples ├── 1_Quenched_HMC.ipynb ├── 1_Quenched_HMC.py ├── 2_Quark_Propagator.py ├── 3_Pion_Proton_2pt.py ├── 4_Pion_PCAC.py ├── 5_Pion_Dispersion.py ├── README.md ├── hmc.py └── plaquette.drawio.svg ├── pyproject.toml ├── pyquda_core ├── LICENSE ├── MANIFEST.in ├── README.md ├── pyproject.toml ├── pyquda │ ├── __init__.py │ ├── __main__.py │ ├── _version.py │ ├── action │ │ ├── __init__.py │ │ ├── abstract.py │ │ ├── clover_wilson.py │ │ ├── gauge.py │ │ └── hisq.py │ ├── dirac │ │ ├── __init__.py │ │ ├── abstract.py │ │ ├── clover_wilson.py │ │ ├── gauge.py │ │ ├── general.py │ │ ├── hisq.py │ │ ├── staggered.py │ │ └── wilson.py │ ├── enum_quda.in.py │ ├── field.py │ ├── hmc.py │ ├── pyquda.pyi │ └── src │ │ ├── malloc_pyquda.pyx │ │ ├── malloc_quda.pxd │ │ ├── pyquda.in.pyx │ │ └── quda.in.pxd ├── pyquda_comm │ ├── __init__.py │ ├── field.py │ ├── hdf5.py │ ├── pointer.pxd │ ├── pointer.pyi │ └── src │ │ └── pointer.pyx ├── pyquda_pyx.py ├── quda │ ├── LICENSE │ └── include │ │ ├── enum_quda.h │ │ ├── malloc_quda.h │ │ ├── quda.h │ │ └── quda_constants.h ├── requirements.txt └── setup.py ├── pyquda_io ├── __init__.py ├── _field_utils.py ├── _mpi_file.py ├── chroma.py ├── io_general.py ├── kyu.py ├── lime.py ├── milc.py ├── nersc.py ├── npy.py ├── openqcd.py └── xqcd.py ├── pyquda_plugins ├── __main__.py ├── plugin_pyx.py ├── pycontract │ └── __init__.py ├── pycparser │ ├── LICENSE │ ├── pycparser │ └── utils │ │ └── fake_libc_include └── pygwu │ └── __init__.py ├── pyquda_utils ├── __init__.py ├── alg_remez.py ├── convert.py ├── core.py ├── deprecated.py ├── eigensolve.py ├── gamma.py ├── gauge_nd_sun.py ├── geo_block_size.py ├── gpt.py ├── hmc_param.py ├── io │ └── __init__.py ├── milc_rhmc_param.py ├── phase.py ├── quasi_axial_gauge_fixing.py ├── source.py └── wilson_loop.py ├── setup.py └── tests ├── check_pyquda.py ├── chroma ├── Dockerfile ├── LICENSE ├── bin │ └── chroma └── build.sh ├── generate_resource.py ├── test_baryon.py ├── test_checksum.py ├── test_clover.ini.xml ├── test_clover.py ├── test_clover_cli.py ├── test_clover_distance.py ├── test_clover_isotropic.ini.xml ├── test_clover_isotropic.py ├── test_clover_multigrid.py ├── test_clover_numpy.py ├── test_clover_torch.py ├── test_covdev.py ├── test_dslash.py ├── test_gaussian.ini.xml ├── test_gaussian.py ├── test_gfix.ini.xml ├── test_gfix.py ├── test_grid.py ├── test_hisq.ini.xml ├── test_hisq.py ├── test_hmc_clover.py ├── test_hmc_gauge.py ├── test_hmc_hisq.py ├── test_io.py ├── test_laplace.py ├── test_loop.py ├── test_mesonspec.ini.xml ├── test_mesonspec.py ├── test_shift.py ├── test_smear.ini.xml ├── test_smear.py ├── test_sun.py ├── test_wflow.ini.xml ├── test_wflow.py ├── test_wflow.scale.py ├── test_wilson.ini.xml ├── test_wilson.py ├── weak_field.ini.xml └── weak_field.lime /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/chroma/bin/chroma filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/workflows/PyQUDA-Utils.yaml: -------------------------------------------------------------------------------- 1 | name: PyQUDA-Utils publish 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | pypi-publish: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | environment: 12 | name: pypi 13 | url: https://pypi.org/p/PyQUDA-Utils 14 | 15 | permissions: 16 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 17 | contents: write 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | submodules: 'recursive' 24 | fetch-depth: '0' 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.8' 30 | 31 | - name: Install dependencies 32 | run: python -m pip install build 33 | 34 | - name: Build sdist 35 | run: python -m build . --sdist 36 | 37 | - name: Publish sdist 38 | uses: pypa/gh-action-pypi-publish@release/v1 39 | -------------------------------------------------------------------------------- /.github/workflows/PyQUDA.yaml: -------------------------------------------------------------------------------- 1 | name: PyQUDA publish 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: Which tag to create and release? 8 | required: true 9 | default: 0.0.0 10 | 11 | jobs: 12 | pypi-publish: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: pypi 18 | url: https://pypi.org/p/PyQUDA 19 | 20 | permissions: 21 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 22 | contents: write 23 | 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | submodules: 'recursive' 29 | 30 | - name: Set up Python 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: '3.8' 34 | 35 | - name: Install dependencies 36 | run: python -m pip install build 37 | 38 | - name: Build sdist 39 | run: python -m build ./pyquda_core --sdist 40 | 41 | - name: Upload sdist 42 | if: github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' 43 | uses: softprops/action-gh-release@v1 44 | with: 45 | tag_name: v${{ github.event.inputs.tag }} 46 | files: pyquda_core/dist/pyquda-${{ github.event.inputs.tag }}.tar.gz 47 | fail_on_unmatched_files: true 48 | 49 | - name: Prepare sdist 50 | run: mv ./pyquda_core/dist ./ 51 | 52 | - name: Publish sdist 53 | uses: pypa/gh-action-pypi-publish@release/v1 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor/IDE 2 | .idea/ 3 | .vscode/ 4 | 5 | # Python 6 | __pycache__/ 7 | *.so 8 | build/ 9 | dist/ 10 | *.egg-info/ 11 | MANIFEST 12 | .env/ 13 | .venv/ 14 | env/ 15 | venv/ 16 | 17 | # Autogenerated files 18 | pyquda_core/pyquda_comm/src/* 19 | !pyquda_core/pyquda_comm/src/pointer.pyx 20 | pyquda_core/pyquda/src/* 21 | !pyquda_core/pyquda/src/malloc_pyquda.pyx 22 | !pyquda_core/pyquda/src/malloc_quda.pxd 23 | !pyquda_core/pyquda/src/pyquda.in.pyx 24 | !pyquda_core/pyquda/src/quda.in.pxd 25 | pyquda_core/pyquda/enum_quda.py 26 | pyquda_plugins/*/src/ 27 | pyquda_plugins/*/*.pyi 28 | pyquda_utils/_version.py 29 | pyquda_io/_version.py 30 | 31 | # Symbolic link 32 | /pyquda 33 | /pyquda_comm 34 | 35 | # pycparser 36 | yacctab.py 37 | lextab.py 38 | 39 | # Cache 40 | .cache/ 41 | 42 | # Scripts, logs, figs and data 43 | *.sh 44 | *.log 45 | *.svg 46 | *.png 47 | *.pdf 48 | data 49 | 50 | # Chroma files 51 | XMLLOG 52 | XMLDAT 53 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pyquda_core/pycparser"] 2 | path = pyquda_core/pycparser 3 | url = https://github.com/eliben/pycparser.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2024 PyQUDA Developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | exclude tests/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyQUDA 2 | 3 | Python wrapper for [QUDA](https://github.com/lattice/quda) written in Cython. 4 | 5 | This project aims to benefit from the optimized linear algebra library [CuPy](https://cupy.dev/) in Python based on CUDA. CuPy and QUDA will allow us to perform most lattice QCD research operations with high performance. [PyTorch](https://pytorch.org/) is an alternative option. 6 | 7 | This project is based on the latest QUDA `develop` branch. PyQUDA should be compatible with any commit of QUDA after https://github.com/lattice/quda/pull/1489, but leave some features disabled. 8 | 9 | ## Feature 10 | 11 | - Multi-GPU supported 12 | - with [MPI for Python](https://mpi4py.readthedocs.io/en/stable/) package 13 | - File I/O 14 | - Read gauge and propagator in Chroma format (with checksum) 15 | - Read gauge and propagator in MILC format (with checksum) 16 | - Read/write gauge and propagator in KYU format 17 | - Read/write gauge and propagator in XQCD format 18 | - Read/write gauge and propagator in NPY format (numpy) 19 | - Quark propagator 20 | - Isotropic/anisotropic Wilson fermion action with multigrid support 21 | - Isotropic/anisotropic Clover fermion action with multigrid support 22 | - Isotropic HISQ fermion action 23 | - HMC 24 | - Isotropic gauge action 25 | - 1-flavor isotropic clover fermion action 26 | - 2-flavor isotropic clover fermion action 27 | - N-flavor isotropic HISQ fermion action 28 | - Gauge fixing 29 | - Rotation field with over-relaxation method (waiting for QUDA merge) 30 | - Gauge smearing 31 | - 3D/4D APE smearing 32 | - 3D/4D stout smearing 33 | - 3D/4D HYP smearing 34 | - Fermion smearing 35 | - Gaussian smearing 36 | - Gradient flow 37 | - Wilson flow 38 | - Symanzik flow 39 | 40 | ## Installation 41 | 42 | ### Install from PyPI 43 | 44 | Assuming the QUDA installation path is `/path/to/quda/build/usqcd` 45 | 46 | ```bash 47 | export QUDA_PATH=/path/to/quda/build/usqcd 48 | python3 -m pip install pyquda pyquda-utils 49 | ``` 50 | 51 | ### Install from source 52 | 53 | Refer to https://github.com/CLQCD/PyQUDA/wiki/Installation for detailed instructions to install PyQUDA from the source. 54 | 55 | ## Benchmark 56 | 57 | Refer to https://github.com/CLQCD/PyQUDA/wiki/Benchmark for detailed instructions to run the PyQUDA benchmark. 58 | 59 | ## Documentation (draft) 60 | 61 | https://github.com/CLQCD/PyQUDA/wiki/Documentation 62 | 63 | ## Plugin 64 | 65 | Refer to https://github.com/CLQCD/PyQUDA/wiki/Plugin for detailed instructions to build PyQUDA plugins. 66 | 67 | Here are plugins developed by PyQUDA developers: 68 | - [pycontract](https://github.com/CLQCD/contract) 69 | 70 | ## Development 71 | 72 | We recommend building PyQUDA using in-place mode instead of installing PyQUDA for development. 73 | 74 | ```bash 75 | git clone --recursive https://github.com/CLQCD/PyQUDA.git 76 | cd PyQUDA 77 | ln -s pyquda_core/pyquda_comm ./ 78 | ln -s pyquda_core/pyquda ./ 79 | cd pyquda_core 80 | export QUDA_PATH=/path/to/quda/build/usqcd 81 | python3 setup.py build_ext --inplace 82 | cd .. 83 | ``` 84 | 85 | Now you can modify Python files in the project and immediately get the new result by running scripts. Adding the root directory to `sys.path` is needed if you are running scripts from other directories. 86 | 87 | ## Maintenance 88 | 89 | Function definitions (mainly in `quda.in.pxd` and `pyquda.in.pyx`) and most docstrings (mainly in `pyquda.pyi` and `enum_quda.in.py`) should be manually updated as they cannot be autogenerated now. This also means PyQUDA should work well with future versions if the API remains unchanged. 90 | -------------------------------------------------------------------------------- /examples/1_Quenched_HMC.py: -------------------------------------------------------------------------------- 1 | from math import exp 2 | from time import perf_counter 3 | 4 | from pyquda.action import GaugeAction, abstract 5 | from pyquda.hmc import HMC, Integrator 6 | from pyquda_utils import core, io 7 | from pyquda_utils.core import X, Y, Z, T 8 | 9 | core.init(resource_path=".cache") 10 | latt_info = core.LatticeInfo([16, 16, 16, 32]) 11 | 12 | u_0 = 0.855453 13 | beta = 6.20 14 | loop_param = abstract.LoopParam( 15 | [ 16 | [X, Y, -X, -Y], 17 | [X, Z, -X, -Z], 18 | [Y, Z, -Y, -Z], 19 | [X, T, -X, -T], 20 | [Y, T, -Y, -T], 21 | [Z, T, -Z, -T], 22 | ], 23 | [1.0, 1.0, 1.0, 1.0, 1.0, 1.0], 24 | ) 25 | start, stop, warm, save = 0, 2000, 500, 5 26 | t, n_steps = 1.0, 100 27 | 28 | 29 | class O2Nf1Ng0V(Integrator): 30 | R"""https://doi.org/10.1016/S0010-4655(02)00754-3 31 | BAB: Eq.(23), \xi=0""" 32 | 33 | def integrate(self, updateGauge, updateMom, t: float): 34 | dt = t / self.n_steps 35 | for _ in range(self.n_steps): 36 | updateMom(dt / 2) 37 | updateGauge(dt) 38 | updateMom(dt / 2) 39 | 40 | 41 | hmc = HMC(latt_info, [GaugeAction(latt_info, loop_param, beta)], O2Nf1Ng0V(n_steps)) 42 | gauge = core.LatticeGauge(latt_info) 43 | hmc.initialize(10086, gauge) 44 | 45 | plaq = hmc.plaquette() 46 | core.getLogger().info(f"Trajectory {start}:\n" f"plaquette = {plaq}\n") 47 | 48 | for i in range(start, stop): 49 | s = perf_counter() 50 | 51 | hmc.gaussMom() 52 | 53 | kinetic_old, potential_old = hmc.momAction(), hmc.gaugeAction() + hmc.fermionAction() 54 | energy_old = kinetic_old + potential_old 55 | 56 | hmc.integrate(t, 2e-14) 57 | 58 | kinetic, potential = hmc.momAction(), hmc.gaugeAction() + hmc.fermionAction() 59 | energy = kinetic + potential 60 | 61 | accept = hmc.accept(energy - energy_old) 62 | if accept or i < warm: 63 | hmc.saveGauge(gauge) 64 | else: 65 | hmc.loadGauge(gauge) 66 | 67 | plaq = hmc.plaquette() 68 | core.getLogger().info( 69 | f"Trajectory {i + 1}:\n" 70 | f"Plaquette = {plaq}\n" 71 | f"P_old = {potential_old}, K_old = {kinetic_old}\n" 72 | f"P = {potential}, K = {kinetic}\n" 73 | f"Delta_P = {potential - potential_old}, Delta_K = {kinetic - kinetic_old}\n" 74 | f"Delta_E = {energy - energy_old}\n" 75 | f"acceptance rate = {min(1, exp(energy_old - energy)) * 100:.2f}%\n" 76 | f"accept? {accept or i < warm}\n" 77 | f"HMC time = {perf_counter() - s:.3f} secs\n" 78 | ) 79 | 80 | if (i + 1) % save == 0: 81 | io.writeNPYGauge(f"./cfg/cfg_{i + 1}.npy", gauge) 82 | -------------------------------------------------------------------------------- /examples/2_Quark_Propagator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cupy as cp 3 | from opt_einsum import contract 4 | from matplotlib import pyplot as plt 5 | 6 | from pyquda_utils import core, io, gamma, source 7 | 8 | core.init([1, 1, 1, 2], resource_path=".cache") 9 | latt_info = core.LatticeInfo([24, 24, 24, 72], -1, 1.0) 10 | 11 | dirac = core.getDirac(latt_info, -0.2770, 1e-12, 1000, 1.0, 1.160920226, 1.160920226, [[6, 6, 6, 4], [4, 4, 4, 9]]) 12 | gauge = io.readChromaQIOGauge("/public/ensemble/C24P29/beta6.20_mu-0.2770_ms-0.2400_L24x72_cfg_48000.lime") 13 | gauge.stoutSmear(1, 0.125, 4) 14 | dirac.loadGauge(gauge) 15 | 16 | G5 = gamma.gamma(15) 17 | t_src_list = list(range(0, 72, 1)) 18 | pion = cp.zeros((len(t_src_list), latt_info.Lt), "t", propag.data.conj(), propag.data).real 28 | dirac.destroy() 29 | 30 | tmp = core.gatherLattice(pion.get(), [1, -1, -1, -1]) 31 | if latt_info.mpi_rank == 0: 32 | for t_idx, t_src in enumerate(t_src_list): 33 | tmp[t_idx] = np.roll(tmp[t_idx], -t_src, 0) 34 | twopt = tmp.mean(0) 35 | plt.plot(np.arange(72), twopt, ",-") 36 | plt.yscale("log") 37 | plt.savefig("pion_2pt.svg") 38 | # np.save("pion.npy", tmp) 39 | print(tmp) 40 | -------------------------------------------------------------------------------- /examples/3_Pion_Proton_2pt.py: -------------------------------------------------------------------------------- 1 | from itertools import permutations 2 | 3 | import numpy as np 4 | import cupy as cp 5 | from opt_einsum import contract 6 | from matplotlib import pyplot as plt 7 | 8 | from pyquda_utils import core, io, gamma 9 | 10 | core.init([1, 1, 1, 2], resource_path=".cache") 11 | latt_info = core.LatticeInfo([24, 24, 24, 72], -1, 1.0) 12 | 13 | dirac = core.getDirac(latt_info, -0.2770, 1e-12, 1000, 1.0, 1.160920226, 1.160920226, [[6, 6, 6, 4], [4, 4, 4, 9]]) 14 | gauge = io.readChromaQIOGauge("/public/ensemble/C24P29/beta6.20_mu-0.2770_ms-0.2400_L24x72_cfg_48000.lime") 15 | gauge.stoutSmear(1, 0.125, 4) 16 | dirac.loadGauge(gauge) 17 | 18 | C = gamma.gamma(2) @ gamma.gamma(8) 19 | G0 = gamma.gamma(0) 20 | G4 = gamma.gamma(8) 21 | G5 = gamma.gamma(15) 22 | P = cp.zeros((72, 4, 4), "t", 37 | propag.data.conj(), 38 | G5 @ G5, 39 | propag.data, 40 | G5 @ G5, 41 | ) 42 | 43 | P_ = cp.roll(P, t_src, 0)[latt_info.gt * latt_info.Lt : (latt_info.gt + 1) * latt_info.Lt] 44 | T_ = T[72 - t_src : 2 * 72 - t_src][latt_info.gt * latt_info.Lt : (latt_info.gt + 1) * latt_info.Lt] 45 | for a, b, c in permutations(tuple(range(3))): 46 | for d, e, f in permutations(tuple(range(3))): 47 | sign = 1 if b == (a + 1) % 3 else -1 48 | sign *= 1 if e == (d + 1) % 3 else -1 49 | proton[t_idx] += (sign * T_) * ( 50 | contract( 51 | "ij,kl,tmn,wtzyxik,wtzyxjl,wtzyxmn->t", 52 | C @ G5, 53 | C @ G5, 54 | P_, 55 | propag.data[:, :, :, :, :, :, :, a, d], 56 | propag.data[:, :, :, :, :, :, :, b, e], 57 | propag.data[:, :, :, :, :, :, :, c, f], 58 | ) 59 | + contract( 60 | "ij,kl,tmn,wtzyxik,wtzyxjn,wtzyxml->t", 61 | C @ G5, 62 | C @ G5, 63 | P_, 64 | propag.data[:, :, :, :, :, :, :, a, d], 65 | propag.data[:, :, :, :, :, :, :, b, e], 66 | propag.data[:, :, :, :, :, :, :, c, f], 67 | ) 68 | ) 69 | 70 | dirac.destroy() 71 | 72 | tmp = core.gatherLattice(pion.real.get(), [1, -1, -1, -1]) 73 | if latt_info.mpi_rank == 0: 74 | for t_idx, t_src in enumerate(t_src_list): 75 | tmp[t_idx] = np.roll(tmp[t_idx], -t_src, 0) 76 | twopt = tmp.mean(0) 77 | plt.plot(np.arange(72), twopt, ",-") 78 | plt.yscale("log") 79 | plt.savefig("pion_2pt.svg") 80 | plt.clf() 81 | mass = np.arccosh((twopt[2:] + twopt[:-2]) / (2 * twopt[1:-1])) 82 | plt.plot(np.arange(1, 72 - 1), mass, ",-") 83 | # plt.ylim(0, 1) 84 | plt.savefig("pion_mass.svg") 85 | plt.clf() 86 | # np.save("pion.npy", tmp) 87 | print(tmp) 88 | tmp = core.gatherLattice(proton.real.get(), [1, -1, -1, -1]) 89 | if latt_info.mpi_rank == 0: 90 | for t_idx, t_src in enumerate(t_src_list): 91 | tmp[t_idx] = np.roll(tmp[t_idx], -t_src, 0) 92 | twopt = tmp.mean(0) 93 | plt.plot(np.arange(72), twopt, ",-") 94 | plt.yscale("log") 95 | plt.savefig("proton_2pt.svg") 96 | plt.clf() 97 | mass = np.arccosh((twopt[2:] + twopt[:-2]) / (2 * twopt[1:-1])) 98 | plt.plot(np.arange(1, 72 - 1), mass, ",-") 99 | # plt.ylim(0, 1) 100 | plt.savefig("proton_mass.svg") 101 | plt.clf() 102 | # np.save("proton.npy", tmp) 103 | print(tmp) 104 | -------------------------------------------------------------------------------- /examples/4_Pion_PCAC.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cupy as cp 3 | from opt_einsum import contract 4 | from matplotlib import pyplot as plt 5 | 6 | from pyquda_utils import core, io, gamma 7 | 8 | core.init([1, 1, 1, 2], resource_path=".cache") 9 | latt_info = core.LatticeInfo([24, 24, 24, 72], -1, 1.0) 10 | 11 | dirac = core.getDirac(latt_info, -0.2770, 1e-12, 1000, 1.0, 1.160920226, 1.160920226, [[6, 6, 6, 4], [4, 4, 4, 9]]) 12 | gauge = io.readChromaQIOGauge("/public/ensemble/C24P29/beta6.20_mu-0.2770_ms-0.2400_L24x72_cfg_48000.lime") 13 | gauge.stoutSmear(1, 0.125, 4) 14 | dirac.loadGauge(gauge) 15 | 16 | G4 = gamma.gamma(8) 17 | G5 = gamma.gamma(15) 18 | t_src_list = list(range(0, 72, 72)) 19 | pion = cp.zeros((len(t_src_list), latt_info.Lt), "t", 27 | propag.data.conj(), 28 | G5 @ G5, 29 | propag.data, 30 | G5 @ G5, 31 | ) 32 | 33 | pionA4[t_idx] += contract( 34 | "wtzyxjiba,jk,wtzyxklba,li->t", 35 | propag.data.conj(), 36 | G5 @ G5 @ G4, 37 | propag.data, 38 | G5 @ G5, 39 | ) 40 | 41 | dirac.destroy() 42 | 43 | tmp = core.gatherLattice(pion.real.get(), [1, -1, -1, -1]) 44 | tmpA4 = core.gatherLattice(pionA4.real.get(), [1, -1, -1, -1]) 45 | if latt_info.mpi_rank == 0: 46 | for t_idx, t_src in enumerate(t_src_list): 47 | tmp[t_idx] = np.roll(tmp[t_idx], -t_src, 0) 48 | tmpA4[t_idx] = np.roll(tmpA4[t_idx], -t_src, 0) 49 | twopt = tmp.mean(0) 50 | twoptA4 = tmpA4.mean(0) 51 | ratio = twoptA4 / twopt 52 | plt.plot(np.arange(72), ratio, ",-") 53 | plt.savefig("pion_pcac.svg") 54 | plt.clf() 55 | -------------------------------------------------------------------------------- /examples/5_Pion_Dispersion.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cupy as cp 3 | from opt_einsum import contract 4 | from matplotlib import pyplot as plt 5 | 6 | from pyquda_utils import core, io, gamma, phase 7 | 8 | core.init([1, 1, 1, 2], resource_path=".cache") 9 | latt_info = core.LatticeInfo([24, 24, 24, 72], -1, 1.0) 10 | 11 | dirac = core.getDirac(latt_info, -0.2770, 1e-12, 1000, 1.0, 1.160920226, 1.160920226, [[6, 6, 6, 4], [4, 4, 4, 9]]) 12 | gauge = io.readChromaQIOGauge("/public/ensemble/C24P29/beta6.20_mu-0.2770_ms-0.2400_L24x72_cfg_48000.lime") 13 | gauge.stoutSmear(1, 0.125, 4) 14 | dirac.loadGauge(gauge) 15 | 16 | G5 = gamma.gamma(15) 17 | momentum_list = [[0, 0, 0], [0, 0, 1], [0, 1, 1], [1, 1, 1], [0, 0, 2], [0, 1, 2], [1, 1, 2], [0, 2, 2], [1, 2, 2]] 18 | momentum_phase = phase.MomentumPhase(latt_info).getPhases(momentum_list) 19 | t_src_list = list(range(0, 72, 72)) 20 | pion = cp.zeros((len(t_src_list), len(momentum_list), latt_info.Lt), "pt", 27 | momentum_phase, 28 | propag.data.conj(), 29 | G5 @ G5, 30 | propag.data, 31 | G5 @ G5, 32 | ) 33 | 34 | dirac.destroy() 35 | 36 | tmp = core.gatherLattice(pion.real.get(), [2, -1, -1, -1]) 37 | if latt_info.mpi_rank == 0: 38 | for t_idx, t_src in enumerate(t_src_list): 39 | tmp[t_idx] = np.roll(tmp[t_idx], -t_src, 1) 40 | twopt = tmp.mean(0) 41 | mass = np.arccosh((twopt[:, 2:] + twopt[:, :-2]) / (2 * twopt[:, 1:-1])) 42 | for p_idx, mom in enumerate(momentum_list): 43 | plt.plot(np.arange(1, 72 - 1), mass[p_idx], ",-", label=f"{mom}") 44 | plt.legend() 45 | plt.savefig("pion_dispersion.svg") 46 | plt.clf() 47 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Note for example 2 | 3 | [`opt_einsum`](https://pypi.org/project/opt-einsum/) optimizes einsum functions in NumPy, Tensorflow, Dask, and more with contraction order optimization. 4 | 5 | You need `opt_einsum` to run the examples. You can install it by 6 | ```bash 7 | python3 -m pip install opt-einsum 8 | ``` 9 | -------------------------------------------------------------------------------- /examples/hmc.py: -------------------------------------------------------------------------------- 1 | from typing import Union, List 2 | 3 | import numpy 4 | 5 | from pyquda.field import LatticeInfo, LatticeGauge, LatticeMom 6 | from pyquda.pyquda import ( 7 | gaussGaugeQuda, 8 | gaussMomQuda, 9 | loadGaugeQuda, 10 | momActionQuda, 11 | momResidentQuda, 12 | plaqQuda, 13 | saveGaugeQuda, 14 | setVerbosityQuda, 15 | updateGaugeFieldQuda, 16 | computeGaugeLoopTraceQuda, 17 | computeGaugeForceQuda, 18 | ) 19 | from pyquda.enum_quda import QudaTboundary, QudaVerbosity 20 | from pyquda.dirac import GaugeDirac 21 | 22 | nullptr = numpy.empty((0, 0), " None: 53 | assert latt_info.anisotropy == 1.0 54 | self.latt_info = latt_info 55 | self._gauge_dirac = GaugeDirac(latt_info) 56 | self.gauge_param = self._gauge_dirac.gauge_param 57 | 58 | def setVerbosity(self, verbosity: QudaVerbosity): 59 | setVerbosityQuda(verbosity, b"\0") 60 | 61 | def initialize(self, gauge: Union[LatticeGauge, int, None] = None): 62 | if isinstance(gauge, LatticeGauge): 63 | self.loadGauge(gauge) 64 | self.loadMom(gauge) 65 | else: 66 | unit = LatticeGauge(self.latt_info) 67 | self.loadGauge(unit) 68 | self.loadMom(unit) 69 | if gauge is not None: 70 | self.gaussGauge(gauge) 71 | 72 | def actionGauge(self, loops: List[List[int]], coeffs: List[float]) -> float: 73 | input_path_buf, path_length, loop_coeff, num_paths, max_length = getLoopsCoeffs(loops, coeffs) 74 | traces = numpy.zeros((num_paths), " float: 87 | return momActionQuda(nullptr, self.gauge_param) 88 | 89 | def updateGauge(self, dt: float): 90 | updateGaugeFieldQuda(nullptr, nullptr, dt, False, True, self.gauge_param) 91 | loadGaugeQuda(nullptr, self.gauge_param) 92 | 93 | def updateMom(self, loops: List[List[List[int]]], coeffs: List[float], dt: float): 94 | input_path_buf_, path_length, loop_coeff, num_paths, max_length = getLoopsCoeffs(loops[0], coeffs) 95 | input_path_buf = numpy.full((4, num_paths, max_length - 1), -1, " 0: 98 | input_path_buf_, path_length_, loop_coeff_, num_paths_, max_length_ = getLoopsCoeffs(loops[i], coeffs) 99 | assert (path_length == path_length_).all() and num_paths == num_paths_ and max_length == max_length_ 100 | assert (input_path_buf_[:, 0] == i).all() 101 | input_path_buf[i] = input_path_buf_[:, 1:] 102 | computeGaugeForceQuda( 103 | nullptr, 104 | nullptr, 105 | input_path_buf, 106 | path_length - 1, 107 | loop_coeff, 108 | num_paths, 109 | max_length - 1, 110 | dt, 111 | self.gauge_param, 112 | ) 113 | 114 | def loadGauge(self, gauge: LatticeGauge): 115 | gauge_in = gauge.copy() 116 | if self.gauge_param.t_boundary == QudaTboundary.QUDA_ANTI_PERIODIC_T: 117 | gauge_in.setAntiPeriodicT() 118 | self.gauge_param.use_resident_gauge = 0 119 | loadGaugeQuda(gauge_in.data_ptrs, self.gauge_param) 120 | self.gauge_param.use_resident_gauge = 1 121 | 122 | def saveGauge(self, gauge: LatticeGauge): 123 | saveGaugeQuda(gauge.data_ptrs, self.gauge_param) 124 | if self.gauge_param.t_boundary == QudaTboundary.QUDA_ANTI_PERIODIC_T: 125 | gauge.setAntiPeriodicT() 126 | 127 | def gaussGauge(self, seed: int): 128 | gaussGaugeQuda(seed, 1.0) 129 | 130 | def loadMom(self, mom: LatticeMom): 131 | momResidentQuda(mom.data_ptrs, self.gauge_param) 132 | 133 | def saveMom(self, mom: LatticeMom): 134 | self.gauge_param.make_resident_mom = 0 135 | self.gauge_param.return_result_mom = 1 136 | momResidentQuda(mom.data_ptrs, self.gauge_param) 137 | self.gauge_param.make_resident_mom = 1 138 | self.gauge_param.return_result_mom = 0 139 | momResidentQuda(mom.data_ptrs, self.gauge_param) # keep momResident 140 | 141 | def gaussMom(self, seed: int): 142 | gaussMomQuda(seed, 1.0) 143 | 144 | def reunitGauge(self, tol: float): 145 | gauge = LatticeGauge(self.latt_info) 146 | self.saveGauge(gauge) 147 | gauge.projectSU3(tol) 148 | self.loadGauge(gauge) 149 | 150 | def plaquette(self): 151 | return plaqQuda()[0] 152 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | 4 | [tool.setuptools.packages.find] 5 | include = ["pyquda_utils*", "pyquda_io*", "pyquda_plugins*"] 6 | 7 | [tool.setuptools.package-data] 8 | pyquda_plugins = ["pycparser/**/LICENSE", "pycparser/**/*.py", "pycparser/**/*.cfg", "pycparser/**/*.h"] 9 | 10 | [tool.setuptools.dynamic] 11 | version = { attr = "pyquda_utils._version.__version__" } 12 | 13 | [project] 14 | dynamic = ["version"] 15 | name = "PyQUDA-Utils" 16 | dependencies = ["PyQUDA"] 17 | requires-python = ">=3.8" 18 | authors = [ 19 | { name = "SaltyChiang", email = "SaltyChiang@users.noreply.github.com" }, 20 | ] 21 | maintainers = [ 22 | { name = "SaltyChiang", email = "SaltyChiang@users.noreply.github.com" }, 23 | ] 24 | description = "Utility scripts based on PyQUDA" 25 | readme = "README.md" 26 | license = { file = "LICENSE" } 27 | keywords = [ 28 | "lattice-gauge-theory", 29 | "lattice-field-theory", 30 | "lattice-qcd", 31 | "hep-lat", 32 | ] 33 | classifiers = [ 34 | "Development Status :: 2 - Pre-Alpha", 35 | "Programming Language :: Python :: 3 :: Only", 36 | "Programming Language :: Cython", 37 | "Operating System :: POSIX :: Linux", 38 | "Environment :: GPU", 39 | "Environment :: GPU :: NVIDIA CUDA", 40 | "Topic :: Scientific/Engineering :: Physics", 41 | ] 42 | 43 | [project.urls] 44 | Homepage = "https://github.com/CLQCD/PyQUDA" 45 | Documentation = "https://github.com/CLQCD/PyQUDA/wiki/Documentation" 46 | Repository = "https://github.com/CLQCD/PyQUDA.git" 47 | Issues = "https://github.com/CLQCD/PyQUDA/issues" 48 | 49 | [project.scripts] 50 | pyquda_plugins = "pyquda_plugins.__main__:main" 51 | -------------------------------------------------------------------------------- /pyquda_core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2024 PyQUDA Developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyquda_core/MANIFEST.in: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | exclude pyquda/src/quda_constants.pxd 3 | exclude pyquda/src/enum_quda.pxd 4 | exclude pyquda/src/quda.pxd 5 | exclude pyquda/src/pyquda.pyx 6 | exclude pyquda/enum_quda.py 7 | exclude pyquda/src/*.c 8 | exclude pyquda/src/*.cpp 9 | 10 | include pyquda_pyx.py 11 | include pycparser/LICENSE 12 | recursive-include pycparser/pycparser *.py *.cfg LICENSE 13 | recursive-include pycparser/utils/fake_libc_include *.h 14 | -------------------------------------------------------------------------------- /pyquda_core/README.md: -------------------------------------------------------------------------------- 1 | # PyQUDA 2 | 3 | Python wrapper for [QUDA](https://github.com/lattice/quda) written in Cython. 4 | 5 | This project aims to benefit from the optimized linear algebra library [CuPy](https://cupy.dev/) in Python based on CUDA. CuPy and QUDA will allow us to perform most lattice QCD research operations with high performance. [PyTorch](https://pytorch.org/) is an alternative option. 6 | 7 | This project is based on the latest QUDA `develop` branch. PyQUDA should be compatible with any commit of QUDA after https://github.com/lattice/quda/pull/1489, but leave some features disabled. 8 | 9 | ## Feature 10 | 11 | - Multi-GPU supported 12 | - with [MPI for Python](https://mpi4py.readthedocs.io/en/stable/) package 13 | - File I/O 14 | - Read gauge and propagator in Chroma format (with checksum) 15 | - Read gauge and propagator in MILC format (with checksum) 16 | - Read/write gauge and propagator in KYU format 17 | - Read/write gauge and propagator in XQCD format 18 | - Read/write gauge and propagator in NPY format (numpy) 19 | - Quark propagator 20 | - Isotropic/anisotropic Wilson fermion action with multigrid support 21 | - Isotropic/anisotropic Clover fermion action with multigrid support 22 | - Isotropic HISQ fermion action 23 | - HMC 24 | - Isotropic gauge action 25 | - 1-flavor isotropic clover fermion action 26 | - 2-flavor isotropic clover fermion action 27 | - N-flavor isotropic HISQ fermion action 28 | - Gauge fixing 29 | - Rotation field with over-relaxation method (waiting for QUDA merge) 30 | - Gauge smearing 31 | - 3D/4D APE smearing 32 | - 3D/4D stout smearing 33 | - 3D/4D HYP smearing 34 | - Fermion smearing 35 | - Gaussian smearing 36 | - Gradient flow 37 | - Wilson flow 38 | - Symanzik flow 39 | 40 | ## Installation 41 | 42 | ### Install from PyPI 43 | 44 | Assuming the QUDA installation path is `/path/to/quda/build/usqcd` 45 | 46 | ```bash 47 | export QUDA_PATH=/path/to/quda/build/usqcd 48 | python3 -m pip install pyquda pyquda-utils 49 | ``` 50 | 51 | ### Install from source 52 | 53 | Refer to https://github.com/CLQCD/PyQUDA/wiki/Installation for detailed instructions to install PyQUDA from the source. 54 | 55 | ## Benchmark 56 | 57 | Refer to https://github.com/CLQCD/PyQUDA/wiki/Benchmark for detailed instructions to run the PyQUDA benchmark. 58 | 59 | ## Documentation (draft) 60 | 61 | https://github.com/CLQCD/PyQUDA/wiki/Documentation 62 | 63 | ## Development 64 | 65 | We recommend building PyQUDA using in-place mode instead of installing PyQUDA for development. 66 | 67 | ```bash 68 | git clone --recursive https://github.com/CLQCD/PyQUDA.git 69 | cd PyQUDA 70 | ln -s pyquda_core/pyquda pyquda 71 | cd pyquda_core 72 | export QUDA_PATH=/path/to/quda/build/usqcd 73 | python3 setup.py build_ext --inplace 74 | ``` 75 | 76 | Now you can modify Python files in the project and immediately get the new result by running scripts. Adding the root directory to `sys.path` is needed if you are running scripts from other directories. 77 | 78 | ## Maintenance 79 | 80 | Function definitions (mainly in `quda.in.pxd` and `pyquda.in.pyx`) and most docstrings (mainly in `pyquda.pyi` and `enum_quda.in.py`) should be manually updated as they cannot be autogenerated now. This also means PyQUDA should work well with future versions if the API remains unchanged. 81 | -------------------------------------------------------------------------------- /pyquda_core/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "Cython", "numpy"] 3 | 4 | [tool.setuptools.packages.find] 5 | include = ["pyquda_comm*", "pyquda*"] 6 | 7 | [tool.setuptools.package-data] 8 | pyquda_comm = ["*.pyi", "*.pxd", "src/*.pxd", "src/*.pyx"] 9 | pyquda = ["*.pyi", "*.pxd", "src/*.pxd", "src/*.pyx"] 10 | 11 | [tool.setuptools.dynamic] 12 | version = { attr = "pyquda._version.__version__" } 13 | 14 | [project] 15 | dynamic = ["version"] 16 | name = "PyQUDA" 17 | dependencies = ["mpi4py", "numpy"] 18 | requires-python = ">=3.8" 19 | authors = [ 20 | { name = "SaltyChiang", email = "SaltyChiang@users.noreply.github.com" }, 21 | ] 22 | maintainers = [ 23 | { name = "SaltyChiang", email = "SaltyChiang@users.noreply.github.com" }, 24 | ] 25 | description = "Python wrapper for QUDA written in Cython" 26 | readme = "README.md" 27 | license = { file = "LICENSE" } 28 | keywords = [ 29 | "lattice-gauge-theory", 30 | "lattice-field-theory", 31 | "lattice-qcd", 32 | "hep-lat", 33 | ] 34 | classifiers = [ 35 | "Development Status :: 2 - Pre-Alpha", 36 | "Programming Language :: Python :: 3 :: Only", 37 | "Programming Language :: Cython", 38 | "Operating System :: POSIX :: Linux", 39 | "Environment :: GPU", 40 | "Environment :: GPU :: NVIDIA CUDA", 41 | "Topic :: Scientific/Engineering :: Physics", 42 | ] 43 | 44 | [project.urls] 45 | Homepage = "https://github.com/CLQCD/PyQUDA" 46 | Documentation = "https://github.com/CLQCD/PyQUDA/wiki/Documentation" 47 | Repository = "https://github.com/CLQCD/PyQUDA.git" 48 | Issues = "https://github.com/CLQCD/PyQUDA/issues" 49 | 50 | [project.scripts] 51 | pyquda = "pyquda.__main__:main" 52 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/__main__.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | from . import init 4 | 5 | 6 | def main(): 7 | parser = ArgumentParser(prog="pyquda", description="PyQUDA initializer", epilog="") 8 | parser.add_argument("script") 9 | parser.add_argument( 10 | "-g", 11 | "--grid", 12 | nargs=4, 13 | type=int, 14 | help="GPU grid size used to split the lattice", 15 | metavar=("Gx", "Gy", "Gz", "Gt"), 16 | ) 17 | parser.add_argument( 18 | "-l", 19 | "--latt", 20 | "--lattice", 21 | nargs=4, 22 | type=int, 23 | help="Lattice size used as the default", 24 | metavar=("Lx", "Ly", "Lz", "Lt"), 25 | ) 26 | parser.add_argument( 27 | "-t", 28 | "--t-boundary", 29 | type=int, 30 | choices=(1, -1), 31 | help="Lattice t boundary used as the default (required if the lattice size is set)", 32 | ) 33 | parser.add_argument( 34 | "-a", 35 | "--anisotropy", 36 | type=float, 37 | help="Lattice anisotropy used as the default (required if the lattice size is set)", 38 | metavar="xi", 39 | ) 40 | parser.add_argument( 41 | "-b", 42 | "--backend", 43 | default="cupy", 44 | choices=("numpy", "cupy", "torch"), 45 | help="CUDA backend used for PyQUDA (default: cupy)", 46 | ) 47 | parser.add_argument( 48 | "--no-init-quda", 49 | action="store_true", 50 | help="Don't initialize the QUDA library", 51 | ) 52 | parser.add_argument( 53 | "-p", 54 | "--resource-path", 55 | help="(default: QUDA_RESOURCE_PATH)", 56 | metavar="QUDA_RESOURCE_PATH", 57 | ) 58 | args = parser.parse_args() 59 | init( 60 | args.grid, 61 | args.latt, 62 | args.t_boundary, 63 | args.anisotropy, 64 | args.backend, 65 | not args.no_init_quda, 66 | resource_path=args.resource_path, 67 | ) 68 | exec(open(args.script).read(), globals(), globals()) 69 | 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.10.15" 2 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/action/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | # GaugeAction 4 | from .gauge import GaugeAction 5 | 6 | # FermionAction 7 | from .clover_wilson import CloverWilsonAction 8 | 9 | # StaggeredFermionAction 10 | from .hisq import HISQAction 11 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/action/clover_wilson.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | 3 | from pyquda_comm import getLogger 4 | from ..field import LatticeInfo, LatticeFermion, MultiLatticeFermion 5 | from ..pyquda import computeCloverForceQuda, loadCloverQuda, loadGaugeQuda 6 | from ..enum_quda import ( 7 | QudaDagType, 8 | QudaInverterType, 9 | QudaMassNormalization, 10 | QudaMatPCType, 11 | QudaSolutionType, 12 | QudaSolveType, 13 | QudaVerbosity, 14 | ) 15 | from ..dirac import CloverWilsonDirac 16 | 17 | nullptr = numpy.empty((0, 0), " None: 36 | if latt_info.anisotropy != 1.0: 37 | getLogger().critical("anisotropy != 1.0 not implemented", NotImplementedError) 38 | super().__init__(latt_info, CloverWilsonDirac(latt_info, mass, tol, maxiter, clover_csw, 1, None)) 39 | 40 | kappa = 1 / (2 * (mass + latt_info.Nd)) 41 | self.setForceParam(rational_param, kappa, clover_csw, n_flavor) 42 | self.quark = MultiLatticeFermion(self.latt_info, self.max_num_offset) 43 | self.phi = LatticeFermion(latt_info) 44 | self.eta = LatticeFermion(latt_info) 45 | 46 | self.invert_param.inv_type = QudaInverterType.QUDA_CG_INVERTER 47 | self.invert_param.solution_type = QudaSolutionType.QUDA_MATPCDAG_MATPC_SOLUTION 48 | self.invert_param.solve_type = QudaSolveType.QUDA_NORMOP_PC_SOLVE # This is set to compute action 49 | self.invert_param.matpc_type = QudaMatPCType.QUDA_MATPC_EVEN_EVEN_ASYMMETRIC 50 | self.invert_param.mass_normalization = QudaMassNormalization.QUDA_KAPPA_NORMALIZATION 51 | self.invert_param.verbosity = verbosity 52 | 53 | def setForceParam(self, rational_param: RationalParam, kappa: float, clover_csw: float, n_flavor: int): 54 | self.coeff = numpy.array(rational_param.residue_molecular_dynamics, " float: 77 | self.invert_param.compute_clover_trlog = 1 78 | self.updateClover(new_gauge) 79 | self.invert_param.compute_clover_trlog = 0 80 | self.invert_param.compute_action = 1 81 | self.invertMultiShift("molecular_dynamics") 82 | self.invert_param.compute_action = 0 83 | return ( 84 | self.invert_param.action[0] 85 | - self.latt_info.volume // 2 * self.latt_info.Ns * self.latt_info.Nc # volume_cb2 here 86 | - self.multiplicity * self.invert_param.trlogA[1] 87 | ) 88 | 89 | def force(self, dt, new_gauge: bool): 90 | self.updateClover(new_gauge) 91 | self.invertMultiShift("molecular_dynamics") 92 | # Some conventions force the dagger to be YES here 93 | self.invert_param.dagger = QudaDagType.QUDA_DAG_YES 94 | computeCloverForceQuda( 95 | nullptr, 96 | dt, 97 | self.quark.even_ptrs, 98 | self.coeff, 99 | self.kappa2, 100 | self.ck, 101 | self.nvector, 102 | self.multiplicity, 103 | self.gauge_param, 104 | self.invert_param, 105 | ) 106 | self.invert_param.dagger = QudaDagType.QUDA_DAG_NO 107 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/action/gauge.py: -------------------------------------------------------------------------------- 1 | from typing import List, NamedTuple 2 | 3 | import numpy 4 | from numpy.typing import NDArray 5 | 6 | from ..field import LatticeInfo 7 | from ..pyquda import computeGaugeLoopTraceQuda, computeGaugeForceQuda 8 | from ..dirac import GaugeDirac 9 | 10 | nullptr = numpy.empty((0, 0), " float: 100 | traces = numpy.zeros((self.action_path.num_paths), " None: 21 | kappa = 1 / (2 * (mass + 1 + (latt_info.Nd - 1) / latt_info.anisotropy)) 22 | super().__init__(latt_info) 23 | self.clover: LatticeClover = None 24 | self.clover_inv: LatticeClover = None 25 | self.newQudaGaugeParam() 26 | self.newQudaMultigridParam(multigrid, mass, kappa) 27 | self.newQudaInvertParam(mass, kappa, tol, maxiter, clover_csw, clover_xi) 28 | # Using half with multigrid doesn't work 29 | if multigrid is not None: 30 | self.setPrecision(sloppy=max(self.precision.sloppy, QudaPrecision.QUDA_SINGLE_PRECISION)) 31 | else: 32 | self.setPrecision() 33 | self.setReconstruct() 34 | 35 | def newQudaGaugeParam(self): 36 | gauge_param = general.newQudaGaugeParam(self.latt_info, 1.0, 0.0) 37 | self.gauge_param = gauge_param 38 | 39 | def newQudaMultigridParam(self, multigrid: Union[List[List[int]], Multigrid], mass: float, kappa: float): 40 | if isinstance(multigrid, Multigrid): 41 | self.multigrid = multigrid 42 | elif multigrid is not None: 43 | geo_block_size = multigrid 44 | mg_param, mg_inv_param = general.newQudaMultigridParam(mass, kappa, geo_block_size, False) 45 | mg_inv_param.dslash_type = QudaDslashType.QUDA_CLOVER_WILSON_DSLASH 46 | self.multigrid = Multigrid(mg_param, mg_inv_param) 47 | self.multigrid.setParam() 48 | else: 49 | self.multigrid = Multigrid(None, None) 50 | 51 | def newQudaInvertParam( 52 | self, mass: float, kappa: float, tol: float, maxiter: int, clover_csw: float, clover_xi: float 53 | ): 54 | invert_param = general.newQudaInvertParam( 55 | mass, kappa, tol, maxiter, kappa * clover_csw, clover_xi, self.multigrid.param 56 | ) 57 | invert_param.dslash_type = QudaDslashType.QUDA_CLOVER_WILSON_DSLASH 58 | self.invert_param = invert_param 59 | 60 | def saveClover(self, gauge: LatticeGauge): 61 | if self.clover is None or self.clover_inv is None: 62 | self.clover = LatticeClover(gauge.latt_info) 63 | self.clover_inv = LatticeClover(gauge.latt_info) 64 | general.saveClover(self.clover, self.clover_inv, gauge, self.gauge_param, self.invert_param) 65 | 66 | def restoreClover(self): 67 | assert self.clover is not None and self.clover_inv is not None 68 | general.loadClover(self.clover, self.clover_inv, None, self.gauge_param, self.invert_param) 69 | self.updateMultigrid(True) 70 | 71 | def loadGauge(self, gauge: LatticeGauge, thin_update_only: bool = False): 72 | general.loadClover(self.clover, self.clover_inv, gauge, self.gauge_param, self.invert_param) 73 | general.loadGauge(gauge, self.gauge_param) 74 | if self.multigrid.instance is None: 75 | self.newMultigrid() 76 | else: 77 | self.updateMultigrid(thin_update_only) 78 | 79 | def destroy(self): 80 | self.destroyMultigrid() 81 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/dirac/hisq.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from ..field import LatticeInfo, LatticeGauge 4 | from ..enum_quda import QudaDslashType, QudaInverterType, QudaReconstructType, QudaPrecision 5 | 6 | from . import general 7 | from .abstract import Multigrid, StaggeredFermionDirac 8 | 9 | 10 | class HISQDirac(StaggeredFermionDirac): 11 | def __init__( 12 | self, 13 | latt_info: LatticeInfo, 14 | mass: float, 15 | tol: float, 16 | maxiter: int, 17 | naik_epsilon: float = 0.0, 18 | multigrid: Union[List[List[int]], Multigrid] = None, 19 | ) -> None: 20 | kappa = 1 / 2 # to be compatible with mass normalization 21 | super().__init__(latt_info) 22 | self.naik_epsilon = naik_epsilon 23 | self.newPathCoeff() 24 | self.newQudaGaugeParam(naik_epsilon) 25 | self.newQudaMultigridParam(multigrid, mass, kappa) 26 | self.newQudaInvertParam(mass, kappa, tol, maxiter) 27 | # Using half with multigrid doesn't work 28 | if multigrid is not None: 29 | self.setPrecision(sloppy=max(self.precision.sloppy, QudaPrecision.QUDA_SINGLE_PRECISION)) 30 | else: 31 | self.setPrecision() 32 | self.setReconstruct( 33 | cuda=QudaReconstructType.QUDA_RECONSTRUCT_NO, 34 | sloppy=QudaReconstructType.QUDA_RECONSTRUCT_NO, 35 | precondition=QudaReconstructType.QUDA_RECONSTRUCT_NO, 36 | eigensolver=QudaReconstructType.QUDA_RECONSTRUCT_NO, 37 | ) 38 | 39 | def newPathCoeff(self): 40 | self.path_coeff_1, self.path_coeff_2, self.path_coeff_3 = general.newPathCoeff(1.0) 41 | 42 | def newQudaGaugeParam(self, naik_epsilon: float): 43 | gauge_param = general.newQudaGaugeParam(self.latt_info, 1.0, naik_epsilon) 44 | gauge_param.staggered_phase_applied = 1 45 | self.gauge_param = gauge_param 46 | 47 | def newQudaMultigridParam(self, multigrid: Union[List[List[int]], Multigrid], mass: float, kappa: float): 48 | if isinstance(multigrid, Multigrid): 49 | self.multigrid = multigrid 50 | elif multigrid is not None: 51 | geo_block_size = multigrid 52 | mg_param, mg_inv_param = general.newQudaMultigridParam(mass, kappa, geo_block_size, True) 53 | mg_inv_param.dslash_type = QudaDslashType.QUDA_ASQTAD_DSLASH 54 | self.multigrid = Multigrid(mg_param, mg_inv_param) 55 | else: 56 | self.multigrid = Multigrid(None, None) 57 | 58 | def newQudaInvertParam(self, mass: float, kappa: float, tol: float, maxiter: int): 59 | invert_param = general.newQudaInvertParam(mass, kappa, tol, maxiter, 0.0, 1.0, self.multigrid.param) 60 | invert_param.dslash_type = QudaDslashType.QUDA_ASQTAD_DSLASH 61 | if self.multigrid.param is None: 62 | invert_param.inv_type = QudaInverterType.QUDA_CG_INVERTER 63 | self.invert_param = invert_param 64 | 65 | def computeULink(self, gauge: LatticeGauge): 66 | return general.computeULink(gauge, self.gauge_param) 67 | 68 | def computeWLink(self, u_link: LatticeGauge, return_v_link: bool): 69 | return general.computeWLink(u_link, return_v_link, self.path_coeff_1, self.gauge_param) 70 | 71 | def computeXLink(self, w_link: LatticeGauge): 72 | return general.computeXLink(w_link, self.path_coeff_2, self.gauge_param) 73 | 74 | def computeXLinkEpsilon(self, fatlink: LatticeGauge, longlink: LatticeGauge, w_link: LatticeGauge): 75 | return general.computeXLinkEpsilon( 76 | fatlink, longlink, w_link, self.path_coeff_3, self.naik_epsilon, self.gauge_param 77 | ) 78 | 79 | def loadFatLongGauge(self, fatlink: LatticeGauge, longlink: LatticeGauge): 80 | general.loadFatLongGauge(fatlink, longlink, self.gauge_param) 81 | 82 | def loadGauge(self, gauge: LatticeGauge, thin_update_only: bool = False): 83 | u_link = self.computeULink(gauge) 84 | v_link, w_link = self.computeWLink(u_link, False) 85 | fatlink, longlink = self.computeXLink(w_link) 86 | fatlink, longlink = self.computeXLinkEpsilon(fatlink, longlink, w_link) 87 | self.loadFatLongGauge(fatlink, longlink) 88 | if self.multigrid.instance is None: 89 | self.newMultigrid() 90 | else: 91 | self.updateMultigrid(thin_update_only) 92 | 93 | def destroy(self): 94 | self.destroyMultigrid() 95 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/dirac/staggered.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from ..field import LatticeInfo, LatticeGauge 4 | from ..enum_quda import QudaDslashType, QudaInverterType, QudaReconstructType, QudaPrecision 5 | 6 | from . import general 7 | from .abstract import Multigrid, StaggeredFermionDirac 8 | 9 | 10 | class StaggeredDirac(StaggeredFermionDirac): 11 | def __init__( 12 | self, 13 | latt_info: LatticeInfo, 14 | mass: float, 15 | tol: float, 16 | maxiter: int, 17 | tadpole_coeff: float = 1.0, 18 | multigrid: Union[List[List[int]], Multigrid] = None, 19 | ) -> None: 20 | kappa = 1 / 2 21 | super().__init__(latt_info) 22 | self.newQudaGaugeParam(tadpole_coeff) 23 | self.newQudaMultigridParam(multigrid, mass, kappa) 24 | self.newQudaInvertParam(mass, kappa, tol, maxiter) 25 | # Using half with multigrid doesn't work 26 | if multigrid is not None: 27 | self.setPrecision(sloppy=max(self.precision.sloppy, QudaPrecision.QUDA_SINGLE_PRECISION)) 28 | else: 29 | self.setPrecision() 30 | self.setReconstruct( 31 | cuda=QudaReconstructType.QUDA_RECONSTRUCT_NO, 32 | sloppy=QudaReconstructType.QUDA_RECONSTRUCT_NO, 33 | precondition=QudaReconstructType.QUDA_RECONSTRUCT_NO, 34 | eigensolver=QudaReconstructType.QUDA_RECONSTRUCT_NO, 35 | ) 36 | 37 | def newQudaGaugeParam(self, tadpole_coeff: float): 38 | gauge_param = general.newQudaGaugeParam(self.latt_info, tadpole_coeff, 0.0) 39 | gauge_param.staggered_phase_applied = 1 40 | self.gauge_param = gauge_param 41 | 42 | def newQudaMultigridParam(self, multigrid: Union[List[List[int]], Multigrid], mass: float, kappa: float): 43 | if isinstance(multigrid, Multigrid): 44 | self.multigrid = multigrid 45 | elif multigrid is not None: 46 | geo_block_size = multigrid 47 | mg_param, mg_inv_param = general.newQudaMultigridParam(mass, kappa, geo_block_size, True) 48 | mg_inv_param.dslash_type = QudaDslashType.QUDA_ASQTAD_DSLASH 49 | self.multigrid = Multigrid(mg_param, mg_inv_param) 50 | else: 51 | self.multigrid = Multigrid(None, None) 52 | 53 | def newQudaInvertParam(self, mass: float, kappa: float, tol: float, maxiter: int): 54 | invert_param = general.newQudaInvertParam(mass, kappa, tol, maxiter, 0.0, 1.0, self.multigrid.param) 55 | invert_param.dslash_type = QudaDslashType.QUDA_STAGGERED_DSLASH 56 | if self.multigrid.param is None: 57 | invert_param.inv_type = QudaInverterType.QUDA_CG_INVERTER 58 | self.invert_param = invert_param 59 | 60 | def loadGauge(self, gauge: LatticeGauge, thin_update_only: bool = False): 61 | general.loadStaggeredGauge(gauge, self.gauge_param) 62 | if self.multigrid.instance is None: 63 | self.newMultigrid() 64 | else: 65 | self.updateMultigrid(thin_update_only) 66 | 67 | def destroy(self): 68 | self.destroyMultigrid() 69 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/dirac/wilson.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from ..field import LatticeInfo, LatticeGauge 4 | from ..enum_quda import QudaDslashType, QudaPrecision 5 | 6 | from . import general 7 | from .abstract import Multigrid, FermionDirac 8 | 9 | 10 | class WilsonDirac(FermionDirac): 11 | def __init__( 12 | self, 13 | latt_info: LatticeInfo, 14 | mass: float, 15 | tol: float, 16 | maxiter: int, 17 | multigrid: Union[List[List[int]], Multigrid] = None, 18 | ) -> None: 19 | kappa = 1 / (2 * (mass + 1 + (latt_info.Nd - 1) / latt_info.anisotropy)) 20 | super().__init__(latt_info) 21 | self.newQudaGaugeParam() 22 | self.newQudaMultigridParam(multigrid, mass, kappa) 23 | self.newQudaInvertParam(mass, kappa, tol, maxiter) 24 | # Using half with multigrid doesn't work 25 | if multigrid is not None: 26 | self.setPrecision(sloppy=max(self.precision.sloppy, QudaPrecision.QUDA_SINGLE_PRECISION)) 27 | else: 28 | self.setPrecision() 29 | self.setReconstruct() 30 | 31 | def newQudaGaugeParam(self): 32 | gauge_param = general.newQudaGaugeParam(self.latt_info, 1.0, 0.0) 33 | self.gauge_param = gauge_param 34 | 35 | def newQudaMultigridParam(self, multigrid: Union[List[List[int]], Multigrid], mass: float, kappa: float): 36 | if isinstance(multigrid, Multigrid): 37 | self.multigrid = multigrid 38 | elif multigrid is not None: 39 | geo_block_size = multigrid 40 | mg_param, mg_inv_param = general.newQudaMultigridParam(mass, kappa, geo_block_size, False) 41 | mg_inv_param.dslash_type = QudaDslashType.QUDA_WILSON_DSLASH 42 | self.multigrid = Multigrid(mg_param, mg_inv_param) 43 | else: 44 | self.multigrid = Multigrid(None, None) 45 | 46 | def newQudaInvertParam(self, mass: float, kappa: float, tol: float, maxiter: int): 47 | invert_param = general.newQudaInvertParam(mass, kappa, tol, maxiter, 0.0, 1.0, self.multigrid.param) 48 | invert_param.dslash_type = QudaDslashType.QUDA_WILSON_DSLASH 49 | self.invert_param = invert_param 50 | 51 | def loadGauge(self, gauge: LatticeGauge, thin_update_only: bool = False): 52 | general.loadGauge(gauge, self.gauge_param) 53 | if self.multigrid.instance is None: 54 | self.newMultigrid() 55 | else: 56 | self.updateMultigrid(thin_update_only) 57 | 58 | def destroy(self): 59 | self.destroyMultigrid() 60 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/enum_quda.in.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | # quda_constants.py 4 | 5 | QUDA_INVALID_ENUM = -0x7FFFFFFF - 1 6 | 7 | 8 | class qudaError_t(IntEnum): 9 | QUDA_SUCCESS = 0 10 | QUDA_ERROR = 1 11 | QUDA_ERROR_UNINITIALIZED = 2 12 | 13 | 14 | class QudaDslashType(IntEnum): 15 | """ 16 | Note: make sure QudaDslashType has corresponding entries in 17 | tests/utils/misc.cpp 18 | """ 19 | 20 | pass 21 | 22 | 23 | class QudaEigSpectrumType(IntEnum): 24 | """ 25 | S=smallest L=largest\n 26 | R=real M=modulus I=imaniary 27 | """ 28 | 29 | pass 30 | 31 | 32 | class QudaCABasis(IntEnum): 33 | """Which basis to use for CA algorithms""" 34 | 35 | pass 36 | 37 | 38 | class QudaMatPCType(IntEnum): 39 | """ 40 | Whether the preconditioned matrix is (1-k^2 Deo Doe) or (1-k^2 Doe Deo) 41 | 42 | For the clover-improved Wilson Dirac operator, QUDA_MATPC_EVEN_EVEN 43 | defaults to the "symmetric" form, (1 - k^2 A_ee^-1 D_eo A_oo^-1 D_oe) 44 | and likewise for QUDA_MATPC_ODD_ODD. 45 | 46 | For the "asymmetric" form, (A_ee - k^2 D_eo A_oo^-1 D_oe), select 47 | QUDA_MATPC_EVEN_EVEN_ASYMMETRIC. 48 | """ 49 | 50 | pass 51 | 52 | 53 | class QudaParity(IntEnum): 54 | """Type used for "parity" argument to dslashQuda()""" 55 | 56 | pass 57 | 58 | 59 | class QudaFieldLocation(IntEnum): 60 | """Where the field is stored""" 61 | 62 | pass 63 | 64 | 65 | class QudaSiteSubset(IntEnum): 66 | """Which sites are included""" 67 | 68 | pass 69 | 70 | 71 | class QudaSiteOrder(IntEnum): 72 | """Site ordering (always t-z-y-x, with rightmost varying fastest)""" 73 | 74 | pass 75 | 76 | 77 | class QudaFieldOrder(IntEnum): 78 | """Degree of freedom ordering""" 79 | 80 | pass 81 | 82 | 83 | class QudaGammaBasis(IntEnum): 84 | """ 85 | gamj=((top 2 rows)(bottom 2 rows)) s1,s2,s3 are Pauli spin matrices, 1 is 2x2 identity 86 | 87 | Dirac-Pauli -> DeGrand-Rossi T = i/sqrt(2)*((s2,-s2)(s2,s2)) field_DR = T * field_DP\n 88 | UKQCD -> DeGrand-Rossi T = i/sqrt(2)*((-s2,-s2)(-s2,s2)) field_DR = T * field_UK\n 89 | Chiral -> DeGrand-Rossi T = i*((0,-s2)(s2,0)) field_DR = T * field_chiral 90 | """ 91 | 92 | pass 93 | 94 | 95 | class QudaProjectionType(IntEnum): 96 | """used to select projection method for deflated solvers""" 97 | 98 | pass 99 | 100 | 101 | class QudaPCType(IntEnum): 102 | """used to select checkerboard preconditioning method""" 103 | 104 | pass 105 | 106 | 107 | class QudaExtLibType(IntEnum): 108 | """Allows to choose an appropriate external library""" 109 | 110 | pass 111 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/src/malloc_pyquda.pyx: -------------------------------------------------------------------------------- 1 | cimport malloc_quda 2 | 3 | def pyquda_device_malloc(size_t size, int device_id) -> int: 4 | cdef size_t ptr = malloc_quda.device_malloc_("pyquda_device_malloc", "pyquda/src/malloc_pyquda.pyx", 4, size) 5 | return ptr 6 | 7 | def pyquda_device_free(size_t ptr, int device_id) -> None: 8 | malloc_quda.device_free_("pyquda_device_free", "pyquda/src/malloc_pyquda.pyx", 8, ptr) 9 | -------------------------------------------------------------------------------- /pyquda_core/pyquda/src/malloc_quda.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "malloc_quda.h" namespace "quda::pool": 2 | void *device_malloc_(const char *func, const char *file, int line, size_t size) 3 | void device_free_(const char *func, const char *file, int line, void *ptr) 4 | -------------------------------------------------------------------------------- /pyquda_core/pyquda_comm/pointer.pxd: -------------------------------------------------------------------------------- 1 | cdef class Pointer: 2 | cdef void *ptr 3 | cdef str dtype 4 | 5 | cdef set_ptr(self, void *ptr) 6 | 7 | cdef class Pointers(Pointer): 8 | cdef unsigned int n0 9 | cdef void **ptrs 10 | 11 | cdef set_ptrs(self, void **ptrs) 12 | 13 | cdef class Pointerss(Pointer): 14 | cdef unsigned int n0, n1 15 | cdef void ***ptrss 16 | 17 | cdef set_ptrss(self, void ***ptrss) 18 | 19 | cdef class _NDArray: 20 | cdef unsigned int n0, n1 21 | cdef void *ptr 22 | cdef void **ptrs 23 | cdef void ***ptrss 24 | -------------------------------------------------------------------------------- /pyquda_core/pyquda_comm/pointer.pyi: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar 2 | 3 | _DataType = TypeVar("_DataType") 4 | 5 | class Pointer(Generic[_DataType]): 6 | def __init__(self, dtype: str): ... 7 | 8 | class Pointers(Pointer[_DataType]): 9 | def __init__(self, dtype: str, n1: int): ... 10 | 11 | class Pointerss(Pointer[_DataType]): 12 | def __init__(self, dtype: str, n1: int, n2: int): ... 13 | 14 | def ndarrayPointer(ndarray, as_void: bool = False) -> Pointer: ... 15 | -------------------------------------------------------------------------------- /pyquda_core/quda/LICENSE: -------------------------------------------------------------------------------- 1 | Files in the directory are copies from QUDA (https://github.com/lattice/quda) 2 | These files are supplied under the following license: 3 | 4 | Copyright (c) 2009-2022, QUDA Developers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pyquda_core/quda/include/quda_constants.h: -------------------------------------------------------------------------------- 1 | #define QUDA_VERSION_MAJOR 1 2 | #define QUDA_VERSION_MINOR 1 3 | #define QUDA_VERSION_SUBMINOR 0 4 | 5 | /** 6 | * @def QUDA_VERSION 7 | * @brief This macro is deprecated. Use QUDA_VERSION_MAJOR, etc., instead. 8 | */ 9 | #define QUDA_VERSION ((QUDA_VERSION_MAJOR<<16) | (QUDA_VERSION_MINOR<<8) | QUDA_VERSION_SUBMINOR) 10 | 11 | 12 | /** 13 | * @def QUDA_MAX_DIM 14 | * @brief Maximum number of dimensions supported by QUDA. In practice, no 15 | * routines make use of more than 5. 16 | */ 17 | #define QUDA_MAX_DIM 6 18 | 19 | /** 20 | * @def QUDA_MAX_GEOMETRY 21 | * @brief Maximum geometry supported by a field. This essentially is 22 | * the maximum number of dimensions supported per lattice site. 23 | */ 24 | #define QUDA_MAX_GEOMETRY 8 25 | 26 | /** 27 | * @def QUDA_MAX_MULTI_SHIFT 28 | * @brief Maximum number of shifts supported by the multi-shift solver. 29 | * This number may be changed if need be. 30 | */ 31 | #define QUDA_MAX_MULTI_SHIFT 32 32 | 33 | /** 34 | * @def QUDA_MAX_BLOCK_SRC 35 | * @brief Maximum number of sources that can be supported by the multi-src solver 36 | */ 37 | #define QUDA_MAX_MULTI_SRC 128 38 | 39 | /** 40 | * @def QUDA_MAX_DWF_LS 41 | * @brief Maximum length of the Ls dimension for domain-wall fermions 42 | */ 43 | #define QUDA_MAX_DWF_LS 32 44 | 45 | /** 46 | * @def QUDA_MAX_MG_LEVEL 47 | * @brief Maximum number of multi-grid levels. This number may be 48 | * increased if needed. 49 | */ 50 | #define QUDA_MAX_MG_LEVEL 5 51 | -------------------------------------------------------------------------------- /pyquda_core/requirements.txt: -------------------------------------------------------------------------------- 1 | mpi4py 2 | numpy 3 | -------------------------------------------------------------------------------- /pyquda_core/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from setuptools import Extension, setup 4 | from pyquda_pyx import build_pyquda_pyx 5 | 6 | if "egg_info" in sys.argv or "dist_info" in sys.argv or "sdist" in sys.argv: 7 | setup() 8 | elif "QUDA_PATH" in os.environ: 9 | quda_path = os.path.realpath(os.environ["QUDA_PATH"]) 10 | build_pyquda_pyx(os.path.dirname(__file__), quda_path) 11 | if os.path.exists(os.path.join(quda_path, "lib", "libquda.so")) or os.path.exists( 12 | os.path.join(quda_path, "lib", "quda.dll") 13 | ): 14 | _STATIC = False 15 | elif os.path.exists(os.path.join(quda_path, "lib", "libquda.a")) or os.path.exists( 16 | os.path.join(quda_path, "lib", "quda.lib") 17 | ): 18 | _STATIC = True 19 | else: 20 | raise FileNotFoundError(f"Cannot find libquda.so or libquda.a in {os.path.join(quda_path, 'lib')}") 21 | 22 | from Cython.Build import cythonize 23 | import numpy 24 | 25 | extensions = cythonize( 26 | [ 27 | Extension( 28 | name="pyquda_comm.pointer", 29 | sources=["pyquda_comm/src/pointer.pyx"], 30 | language="c", 31 | ), 32 | Extension( 33 | name="pyquda.pyquda", 34 | sources=["pyquda/src/pyquda.pyx"], 35 | include_dirs=[os.path.join(quda_path, "include"), numpy.get_include()], 36 | define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], 37 | library_dirs=[os.path.join(quda_path, "lib")], 38 | libraries=["quda"], 39 | extra_link_args=[f"-Wl,-rpath={os.path.join(quda_path, 'lib')}"] if not _STATIC else None, 40 | language="c", 41 | ), 42 | Extension( 43 | name="pyquda.malloc_pyquda", 44 | sources=["pyquda/src/malloc_pyquda.pyx"], 45 | include_dirs=[os.path.join(quda_path, "include")], 46 | library_dirs=[os.path.join(quda_path, "lib")], 47 | libraries=["quda"], 48 | extra_link_args=[f"-Wl,-rpath={os.path.join(quda_path, 'lib')}"] if not _STATIC else None, 49 | language="c++", 50 | ), 51 | ], 52 | language_level="3", 53 | ) 54 | 55 | setup(ext_modules=extensions) 56 | else: 57 | raise EnvironmentError("QUDA_PATH environment is needed to link against libquda") 58 | -------------------------------------------------------------------------------- /pyquda_io/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from .io_general import ( 4 | IOGeneral, 5 | read as readIOGeneral, 6 | write as writeIOGeneral, 7 | ) 8 | from .lime import ( 9 | Lime, 10 | ) 11 | from .npy import ( 12 | readGauge as readNPYGauge, 13 | writeGauge as writeNPYGauge, 14 | readPropagator as readNPYPropagator, 15 | writePropagator as writeNPYPropagator, 16 | ) 17 | from .chroma import ( 18 | readQIOGauge as readChromaQIOGauge, 19 | readQIOPropagator as readChromaQIOPropagator, 20 | readILDGBinGauge, 21 | ) 22 | from .milc import ( 23 | readGauge as readMILCGauge, 24 | writeGauge as writeMILCGauge, 25 | readQIOPropagator as readMILCQIOPropagator, 26 | ) 27 | from .kyu import ( 28 | readGauge as readKYUGauge, 29 | writeGauge as writeKYUGauge, 30 | readPropagator as readKYUPropagator, 31 | writePropagator as writeKYUPropagator, 32 | ) 33 | from .xqcd import ( 34 | readPropagator as readXQCDPropagator, 35 | writePropagator as writeXQCDPropagator, 36 | readPropagatorFast as readXQCDPropagatorFast, 37 | writePropagatorFast as writeXQCDPropagatorFast, 38 | ) 39 | from .nersc import ( 40 | readGauge as readNERSCGauge, 41 | writeGauge as writeNERSCGauge, 42 | ) 43 | from .openqcd import ( 44 | readGauge as readOpenQCDGauge, 45 | writeGauge as writeOpenQCDGauge, 46 | ) 47 | -------------------------------------------------------------------------------- /pyquda_io/_mpi_file.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence 2 | 3 | from pyquda_comm import ( # noqa: F401 4 | initGrid, 5 | isGridInitialized, 6 | getMPIComm, 7 | getMPISize, 8 | getMPIRank, 9 | getGridSize, 10 | getGridCoord, 11 | getCoordFromRank, 12 | getRankFromCoord, 13 | readMPIFile, 14 | writeMPIFile, 15 | ) 16 | 17 | 18 | def getSublatticeSize(latt_size: Sequence[int], evenodd: bool = True): 19 | GLx, GLy, GLz, GLt = latt_size 20 | Gx, Gy, Gz, Gt = getGridSize() 21 | if evenodd: 22 | assert GLx % (2 * Gx) == 0 and GLy % (2 * Gy) == 0 and GLz % (2 * Gz) == 0 and GLt % (2 * Gt) == 0 23 | else: 24 | assert GLx % Gx == 0 and GLy % Gy == 0 and GLz % Gz == 0 and GLt % Gt == 0 25 | return [GLx // Gx, GLy // Gy, GLz // Gz, GLt // Gt] 26 | 27 | 28 | def getNeighbourRank(): 29 | Gx, Gy, Gz, Gt = getGridSize() 30 | gx, gy, gz, gt = getCoordFromRank(getMPIRank()) 31 | return [ 32 | getRankFromCoord([(gx + 1) % Gx, gy, gz, gt]), 33 | getRankFromCoord([gx, (gy + 1) % Gy, gz, gt]), 34 | getRankFromCoord([gx, gy, (gz + 1) % Gz, gt]), 35 | getRankFromCoord([gx, gy, gz, (gt + 1) % Gt]), 36 | getRankFromCoord([(gx - 1) % Gx, gy, gz, gt]), 37 | getRankFromCoord([gx, (gy - 1) % Gy, gz, gt]), 38 | getRankFromCoord([gx, gy, (gz - 1) % Gz, gt]), 39 | getRankFromCoord([gx, gy, gz, (gt - 1) % Gt]), 40 | ] 41 | -------------------------------------------------------------------------------- /pyquda_io/io_general.py: -------------------------------------------------------------------------------- 1 | from ctypes import Union, Structure, c_char, c_int, sizeof 2 | from enum import IntEnum 3 | import io 4 | from os import path 5 | from typing import Sequence 6 | 7 | import numpy 8 | from numpy.typing import NDArray 9 | 10 | 11 | class _DimensionType(IntEnum): 12 | dim_other = 0 13 | dim_x = 1 14 | dim_y = 2 15 | dim_z = 3 16 | dim_t = 4 17 | dim_d = 5 18 | dim_c = 6 19 | dim_d2 = 7 20 | dim_c2 = 8 21 | dim_complex = 9 22 | dim_mass = 10 23 | dim_smear = 11 24 | dim_displacement = 12 25 | 26 | dim_s_01 = 13 27 | dim_s_02 = 14 28 | dim_s_03 = 15 29 | dim_s_11 = 16 30 | dim_s_12 = 17 31 | dim_s_13 = 18 32 | dim_d_01 = 19 33 | dim_d_02 = 20 34 | dim_d_03 = 21 35 | dim_d_11 = 22 36 | dim_d_12 = 23 37 | dim_d_13 = 24 38 | 39 | dim_conf = 25 40 | dim_operator = 26 41 | dim_momentum = 27 42 | dim_direction = 28 43 | dim_t2 = 29 44 | dim_mass2 = 30 45 | 46 | dim_column = 31 47 | dim_row = 32 48 | dim_temporary = 33 49 | dim_temporary2 = 34 50 | dim_temporary3 = 35 51 | dim_temporary4 = 36 52 | 53 | dim_errorbar = 37 54 | """0 means average, 1 means errorbar, ...""" 55 | 56 | dim_operator2 = 38 57 | 58 | dim_param = 39 59 | dim_fitleft = 40 60 | dim_fitright = 41 61 | 62 | dim_jackknife = 42 63 | dim_jackknife2 = 43 64 | dim_jackknife3 = 44 65 | dim_jackknife4 = 45 66 | 67 | dim_summary = 46 68 | """ 69 | 0 means average, 1 means standard deviation, 2 means minimal value, 3 means maximum value, 4 means standard error, 70 | 5 means median, ... 71 | """ 72 | 73 | dim_channel = 47 74 | dim_channel2 = 48 75 | 76 | dim_eigen = 49 77 | 78 | dim_d_row = 50 79 | """on matrix multiplication, row is contracted with the left operand, col is contracted with the right operand.""" 80 | dim_d_col = 51 81 | dim_c_row = 52 82 | dim_c_col = 53 83 | 84 | dim_parity = 54 85 | """dimension for different parities. we use 1/-1 for +/- parities for baryons.""" 86 | 87 | dim_noise = 55 88 | dim_evenodd = 56 89 | 90 | dim_disp_x = 57 91 | dim_disp_y = 58 92 | dim_disp_z = 59 93 | dim_disp_t = 60 94 | 95 | dim_t3 = 61 96 | dim_t4 = 62 97 | dim_t_source = 63 98 | dim_t_current = 64 99 | dim_t_sink = 65 100 | 101 | dim_nothing = 66 102 | """do not use this unless for unused data.""" 103 | 104 | dim_bootstrap = 67 105 | 106 | # add new dimensions here and add a string name in xqcd_type_dim_desc[] in io_general.c 107 | # ... 108 | 109 | dim_last = 68 110 | 111 | 112 | class _OneDim(Structure): 113 | # _pack_ = 1 114 | _fields_ = [("type", c_int), ("n_indices", c_int), ("indices", c_int * 1024)] 115 | 116 | type: int 117 | n_indices: int 118 | indices: Sequence[int] 119 | 120 | 121 | class _Head(Structure): 122 | # _pack_ = 1 123 | _fields_ = [("n_dimensions", c_int), ("dimensions", _OneDim * 16)] 124 | 125 | n_dimensions: int 126 | dimensions: Sequence[_OneDim] 127 | 128 | @property 129 | def dimensions_type(self): 130 | return tuple([_DimensionType(self.dimensions[i].type)._name_[4:] for i in range(self.n_dimensions)]) 131 | 132 | @property 133 | def dimensions_n_indices(self): 134 | return tuple([self.dimensions[i].n_indices for i in range(self.n_dimensions)]) 135 | 136 | 137 | class _FileType(Union): 138 | _fields_ = [("head", _Head), ("blank", c_char * 102400)] 139 | 140 | head: _Head 141 | blank: bytes 142 | 143 | @property 144 | def n_dimensions(self): 145 | return self.head.n_dimensions 146 | 147 | @n_dimensions.setter 148 | def n_dimensions(self, value): 149 | self.head.n_dimensions = value 150 | 151 | @property 152 | def dimensions(self): 153 | return self.head.dimensions 154 | 155 | @property 156 | def dimensions_type(self): 157 | return self.head.dimensions_type 158 | 159 | @property 160 | def dimensions_n_indices(self): 161 | return self.head.dimensions_n_indices 162 | 163 | def __repr__(self): 164 | retval = "" 165 | for i in range(self.n_dimensions): 166 | retval += f"{_DimensionType(self.dimensions[i].type)._name_:18s}{self.dimensions[i].n_indices:<6d}( " 167 | for j in range(self.dimensions[i].n_indices): 168 | retval += f"{self.dimensions[i].indices[j]} " 169 | retval += ")\n" 170 | return retval 171 | 172 | 173 | def read(filename: str): 174 | filename = path.expanduser(path.expandvars(filename)) 175 | with open(filename, "rb") as f: 176 | head = _FileType.from_buffer_copy(f.read(sizeof(_FileType))) 177 | data = numpy.frombuffer(f.read(), "Q", f.read(8))[0] 22 | name = f.read(128).strip(b"\x00").decode("utf-8") 23 | self._records.append(LimeRecord(name, f.tell(), length)) 24 | f.seek((length + 7) // 8 * 8, io.SEEK_CUR) 25 | buffer = f.read(8) 26 | 27 | def keys(self): 28 | return [record.name for record in self._records] 29 | 30 | def records(self, key: str): 31 | return [record for record in self._records if record.name == key] 32 | 33 | def record(self, key: str, index: int = 0): 34 | return [record for record in self._records if record.name == key][index] 35 | 36 | def read(self, key: str, index: int = 0): 37 | record = self.record(key, index) 38 | with open(self.filename, "rb") as f: 39 | f.seek(record.offset) 40 | buffer = f.read(record.length) 41 | return buffer 42 | -------------------------------------------------------------------------------- /pyquda_io/nersc.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from os import path, uname 3 | from typing import Dict, List 4 | 5 | import numpy 6 | from mpi4py import MPI 7 | 8 | from ._mpi_file import getMPIComm, getMPISize, getMPIRank, getSublatticeSize, readMPIFile, writeMPIFile 9 | from ._field_utils import gaugePlaquette, gaugeReunitarize, gaugeReunitarizeReconstruct12, gaugeReconstruct12 10 | 11 | Nd, Ns, Nc = 4, 4, 3 12 | 13 | 14 | def checksum_nersc(data: numpy.ndarray) -> int: 15 | return getMPIComm().allreduce(numpy.sum(data.view(" float: 19 | return getMPIComm().allreduce(numpy.einsum("dtzyxaa->", gauge.real) / (getMPISize() * gauge.size // Nc), MPI.SUM) 20 | 21 | 22 | def readGauge( 23 | filename: str, 24 | checksum: bool = True, 25 | plaquette: bool = True, 26 | link_trace: bool = True, 27 | reunitarize_sigma: float = 5e-7, 28 | ): 29 | filename = path.expanduser(path.expandvars(filename)) 30 | header: Dict[str, str] = {} 31 | with open(filename, "rb") as f: 32 | assert f.readline().decode() == "BEGIN_HEADER\n" 33 | buffer = f.readline().decode() 34 | while buffer != "END_HEADER\n": 35 | key, val = buffer.split("=") 36 | header[key.strip()] = val.strip() 37 | buffer = f.readline().decode() 38 | offset = f.tell() 39 | latt_size = [ 40 | int(header["DIMENSION_1"]), 41 | int(header["DIMENSION_2"]), 42 | int(header["DIMENSION_3"]), 43 | int(header["DIMENSION_4"]), 44 | ] 45 | Lx, Ly, Lz, Lt = getSublatticeSize(latt_size) 46 | assert header["FLOATING_POINT"].startswith("IEEE") 47 | if header["FLOATING_POINT"][6:] == "BIG": 48 | endian = ">" 49 | elif header["FLOATING_POINT"][6:] == "LITTLE": 50 | endian = "<" 51 | else: 52 | raise ValueError(f"Unsupported endian: {header['FLOATING_POINT'][6:]}") 53 | float_nbytes = int(header["FLOATING_POINT"][4:6]) // 8 54 | dtype = f"{endian}c{2 * float_nbytes}" 55 | 56 | if header["DATATYPE"] == "4D_SU3_GAUGE_3x3": 57 | gauge = readMPIFile(filename, dtype, offset, (Lt, Lz, Ly, Lx, Nd, Nc, Nc), (3, 2, 1, 0)) 58 | gauge = gauge.astype(f" LatticeComplex: 20 | latt_info = propag_i.latt_info 21 | if isinstance(gamma_mn, Gamma): 22 | correl = LatticeComplex(latt_info) 23 | contract.baryon_two_point( 24 | correl.data_ptr, 25 | propag_i.data_ptr, 26 | propag_j.data_ptr, 27 | propag_m.data_ptr, 28 | contract_type, 29 | latt_info.volume, 30 | gamma_ij.index, 31 | gamma_kl.index, 32 | gamma_mn.index, 33 | ) 34 | correl.data *= gamma_ij.factor * gamma_kl.factor * gamma_mn.factor 35 | return correl 36 | elif isinstance(gamma_mn, Polarize): 37 | correl_left = baryonTwoPoint(propag_i, propag_j, propag_m, contract_type, gamma_ij, gamma_kl, gamma_mn.left) 38 | correl_right = baryonTwoPoint(propag_i, propag_j, propag_m, contract_type, gamma_ij, gamma_kl, gamma_mn.right) 39 | return correl_left + correl_right 40 | else: 41 | raise getLogger().critical("gamma_mn should be Gamma or Polarize", ValueError) 42 | -------------------------------------------------------------------------------- /pyquda_plugins/pycparser/LICENSE: -------------------------------------------------------------------------------- 1 | pycparser -- A C parser in Python 2 | 3 | Copyright (c) 2008-2022, Eli Bendersky 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * Neither the name of the copyright holder nor the names of its contributors may 15 | be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 24 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 27 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pyquda_plugins/pycparser/pycparser: -------------------------------------------------------------------------------- 1 | ../../pyquda_core/pycparser/pycparser -------------------------------------------------------------------------------- /pyquda_plugins/pycparser/utils/fake_libc_include: -------------------------------------------------------------------------------- 1 | ../../../pyquda_core/pycparser/utils/fake_libc_include -------------------------------------------------------------------------------- /pyquda_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ # noqa: F401 2 | -------------------------------------------------------------------------------- /pyquda_utils/convert.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from pyquda import getLogger 4 | from pyquda.field import ( 5 | LatticeLink, 6 | LatticeFermion, 7 | LatticePropagator, 8 | LatticeStaggeredPropagator, 9 | MultiLatticeFermion, 10 | MultiLatticeStaggeredFermion, 11 | ) 12 | 13 | 14 | def linkToFermion(link: LatticeLink): 15 | fermion = LatticeFermion(link.latt_info) 16 | for color in range(link.latt_info.Nc): 17 | fermion.data[:, :, :, :, :, color, :] = link.data[:, :, :, :, :, :, color] 18 | return fermion 19 | 20 | 21 | def fermionToLink(fermion: LatticeFermion): 22 | link = LatticeLink(fermion.latt_info) 23 | for color in range(fermion.latt_info.Nc): 24 | link.data[:, :, :, :, :, :, color] = fermion.data[:, :, :, :, :, color, :] 25 | return link 26 | 27 | 28 | def multiFermionToPropagator(multi_fermion: Union[MultiLatticeFermion, MultiLatticeStaggeredFermion]): 29 | latt_info = multi_fermion.latt_info 30 | if isinstance(multi_fermion, MultiLatticeFermion): 31 | assert multi_fermion.L5 == latt_info.Ns * latt_info.Nc 32 | return LatticePropagator( 33 | latt_info, 34 | multi_fermion.data.reshape( 35 | latt_info.Ns, 36 | latt_info.Nc, 37 | *multi_fermion.lattice_shape, 38 | latt_info.Ns, 39 | latt_info.Nc, 40 | ).transpose(2, 3, 4, 5, 6, 7, 0, 8, 1), 41 | ) 42 | elif isinstance(multi_fermion, MultiLatticeStaggeredFermion): 43 | assert multi_fermion.L5 == latt_info.Nc 44 | return LatticeStaggeredPropagator( 45 | latt_info, 46 | multi_fermion.data.reshape( 47 | latt_info.Nc, 48 | *multi_fermion.lattice_shape, 49 | latt_info.Nc, 50 | ).transpose(1, 2, 3, 4, 5, 6, 0), 51 | ) 52 | else: 53 | raise getLogger().critical( 54 | f"No multiFermionToPropagator implementation for {multi_fermion.__class__.__name__}", NotImplementedError 55 | ) 56 | 57 | 58 | def propagatorToMultiFermion(propagator: Union[LatticePropagator, LatticeStaggeredPropagator]): 59 | latt_info = propagator.latt_info 60 | if isinstance(propagator, LatticePropagator): 61 | return MultiLatticeFermion( 62 | latt_info, 63 | latt_info.Ns * latt_info.Nc, 64 | propagator.data.transpose(6, 8, 0, 1, 2, 3, 4, 5, 7).reshape( 65 | latt_info.Ns * latt_info.Nc, 66 | *propagator.lattice_shape, 67 | latt_info.Ns, 68 | latt_info.Nc, 69 | ), 70 | ) 71 | elif isinstance(propagator, LatticeStaggeredPropagator): 72 | return MultiLatticeStaggeredFermion( 73 | latt_info, 74 | latt_info.Nc, 75 | propagator.data.transpose(6, 0, 1, 2, 3, 4, 5).reshape( 76 | latt_info.Nc, 77 | *propagator.lattice_shape, 78 | latt_info.Nc, 79 | ), 80 | ) 81 | else: 82 | raise getLogger().critical( 83 | f"No propagatorToMultiFermion implementation for {propagator.__class__.__name__}", NotImplementedError 84 | ) 85 | -------------------------------------------------------------------------------- /pyquda_utils/deprecated.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | 3 | import numpy 4 | 5 | from pyquda import getLogger, getGridSize, pyquda as quda, enum_quda 6 | from pyquda.field import LatticeFermion, LatticeGauge, LatticeInfo, LatticePropagator, Nc, Ns, evenodd 7 | from pyquda.dirac.abstract import FermionDirac 8 | 9 | 10 | def cb2(data: numpy.ndarray, axes: List[int], dtype=None): 11 | getLogger().warning("Use evenodd instead", DeprecationWarning) 12 | return evenodd(data, axes, dtype) 13 | 14 | 15 | def smear(latt_size: List[int], gauge: LatticeGauge, nstep: int, rho: float): 16 | getLogger().warning("Use GaugeField.stoutSmear instead", DeprecationWarning) 17 | from .core import getDslash 18 | 19 | smear_param = quda.QudaGaugeSmearParam() 20 | smear_param.n_steps = nstep 21 | smear_param.rho = rho 22 | smear_param.meas_interval = nstep + 1 23 | smear_param.smear_type = enum_quda.QudaGaugeSmearType.QUDA_GAUGE_SMEAR_STOUT 24 | obs_param = quda.QudaGaugeObservableParam() 25 | obs_param.compute_qcharge = enum_quda.QudaBoolean.QUDA_BOOLEAN_TRUE 26 | dslash = getDslash(latt_size, 0, 0, 0, anti_periodic_t=False) 27 | dslash.gauge_param.reconstruct = enum_quda.QudaReconstructType.QUDA_RECONSTRUCT_NO 28 | dslash.loadGauge(gauge) 29 | quda.performGaugeSmearQuda(smear_param, obs_param) 30 | dslash.gauge_param.type = enum_quda.QudaLinkType.QUDA_SMEARED_LINKS 31 | quda.saveGaugeQuda(gauge.data_ptrs, dslash.gauge_param) 32 | 33 | 34 | def smear4(latt_size: List[int], gauge: LatticeGauge, nstep: int, rho: float): 35 | getLogger().warning("Use GaugeField.stoutSmear instead", DeprecationWarning) 36 | from .core import getDslash 37 | 38 | smear_param = quda.QudaGaugeSmearParam() 39 | smear_param.n_steps = nstep 40 | smear_param.rho = rho 41 | smear_param.epsilon = 1.0 42 | smear_param.meas_interval = nstep + 1 43 | smear_param.smear_type = enum_quda.QudaGaugeSmearType.QUDA_GAUGE_SMEAR_OVRIMP_STOUT 44 | obs_param = quda.QudaGaugeObservableParam() 45 | obs_param.compute_qcharge = enum_quda.QudaBoolean.QUDA_BOOLEAN_TRUE 46 | dslash = getDslash(latt_size, 0, 0, 0, anti_periodic_t=False) 47 | dslash.gauge_param.reconstruct = enum_quda.QudaReconstructType.QUDA_RECONSTRUCT_NO 48 | dslash.loadGauge(gauge) 49 | quda.performGaugeSmearQuda(smear_param, obs_param) 50 | dslash.gauge_param.type = enum_quda.QudaLinkType.QUDA_SMEARED_LINKS 51 | quda.saveGaugeQuda(gauge.data_ptrs, dslash.gauge_param) 52 | 53 | 54 | def invert12(b12: LatticePropagator, dslash: FermionDirac): 55 | getLogger().warning("Use core.invert instead", DeprecationWarning) 56 | latt_info = b12.latt_info 57 | Vol = latt_info.volume 58 | 59 | x12 = LatticePropagator(latt_info) 60 | for spin in range(Ns): 61 | for color in range(Nc): 62 | b = LatticeFermion(latt_info) 63 | data = b.data.reshape(Vol, Ns, Nc) 64 | data[:] = b12.data.reshape(Vol, Ns, Ns, Nc, Nc)[:, :, spin, :, color] 65 | x = dslash.invert(b) 66 | data = x12.data.reshape(Vol, Ns, Ns, Nc, Nc) 67 | data[:, :, spin, :, color] = x.data.reshape(Vol, Ns, Nc) 68 | b = None 69 | 70 | return x12 71 | 72 | 73 | def getDslash( 74 | latt_size: List[int], 75 | mass: float, 76 | tol: float, 77 | maxiter: int, 78 | xi_0: float = 1.0, 79 | nu: float = 1.0, 80 | clover_coeff_t: float = 0.0, 81 | clover_coeff_r: float = 1.0, 82 | anti_periodic_t: bool = True, 83 | multigrid: List[List[int]] = None, 84 | ): 85 | getLogger().warning("Use getDirac instead", DeprecationWarning) 86 | Gx, Gy, Gz, Gt = getGridSize() 87 | Lx, Ly, Lz, Lt = latt_size 88 | Lx, Ly, Lz, Lt = Lx * Gx, Ly * Gy, Lz * Gz, Lt * Gt 89 | 90 | xi = xi_0 / nu 91 | if xi != 1.0: 92 | clover_csw = xi_0 * clover_coeff_t**2 / clover_coeff_r 93 | clover_xi = (xi_0 * clover_coeff_t / clover_coeff_r) ** 0.5 94 | else: 95 | clover_csw = clover_coeff_t 96 | clover_xi = 1.0 97 | if anti_periodic_t: 98 | t_boundary = -1 99 | else: 100 | t_boundary = 1 101 | if not multigrid: 102 | geo_block_size = None 103 | else: 104 | if not isinstance(multigrid, list): 105 | geo_block_size = [[2, 2, 2, 2], [4, 4, 4, 4]] 106 | else: 107 | geo_block_size = multigrid 108 | latt_info = LatticeInfo([Lx, Ly, Lz, Lt], t_boundary, xi) 109 | 110 | if clover_csw != 0.0: 111 | from pyquda.dirac.clover_wilson import CloverWilsonDirac 112 | 113 | return CloverWilsonDirac(latt_info, mass, tol, maxiter, clover_csw, clover_xi, geo_block_size) 114 | else: 115 | from pyquda.dirac.wilson import WilsonDirac 116 | 117 | return WilsonDirac(latt_info, mass, tol, maxiter, geo_block_size) 118 | 119 | 120 | def getStaggeredDslash( 121 | latt_size: List[int], 122 | mass: float, 123 | tol: float, 124 | maxiter: int, 125 | tadpole_coeff: float = 1.0, 126 | naik_epsilon: float = 0.0, 127 | anti_periodic_t: bool = True, 128 | ): 129 | getLogger().warning("Use getStaggeredDirac instead", DeprecationWarning) 130 | assert tadpole_coeff == 1.0 131 | Gx, Gy, Gz, Gt = getGridSize() 132 | Lx, Ly, Lz, Lt = latt_size 133 | Lx, Ly, Lz, Lt = Lx * Gx, Ly * Gy, Lz * Gz, Lt * Gt 134 | 135 | if anti_periodic_t: 136 | t_boundary = -1 137 | else: 138 | t_boundary = 1 139 | latt_info = LatticeInfo([Lx, Ly, Lz, Lt], t_boundary, 1.0) 140 | 141 | from pyquda.dirac.hisq import HISQDirac 142 | 143 | return HISQDirac(latt_info, mass, tol, maxiter, naik_epsilon, None) 144 | -------------------------------------------------------------------------------- /pyquda_utils/eigensolve.py: -------------------------------------------------------------------------------- 1 | from pyquda import pyquda as quda 2 | from pyquda.enum_quda import QudaDslashType, QudaBoolean, QudaEigType, QudaEigSpectrumType 3 | from pyquda.field import LatticeGauge, MultiLatticeStaggeredFermion 4 | 5 | 6 | def laplace3d( 7 | gauge: LatticeGauge, n_ev: int, n_kr: int, tol: float, max_restarts: int, poly_deg: int = 1, poly_cut: float = 0.0 8 | ): 9 | import numpy 10 | 11 | latt_info = gauge.latt_info 12 | gauge.gauge_dirac.invert_param.dslash_type = QudaDslashType.QUDA_LAPLACE_DSLASH 13 | gauge.gauge_dirac.invert_param.mass = -1 14 | gauge.gauge_dirac.invert_param.kappa = 1 / 6 15 | gauge.gauge_dirac.invert_param.laplace3D = 3 16 | gauge.gauge_dirac.loadGauge(gauge) 17 | 18 | eig_param = quda.QudaEigParam() 19 | eig_param.invert_param = gauge.gauge_dirac.invert_param 20 | eig_param.eig_type = QudaEigType.QUDA_EIG_TR_LANCZOS_3D 21 | eig_param.use_dagger = QudaBoolean.QUDA_BOOLEAN_FALSE 22 | eig_param.use_norm_op = QudaBoolean.QUDA_BOOLEAN_FALSE 23 | eig_param.use_pc = QudaBoolean.QUDA_BOOLEAN_FALSE 24 | eig_param.compute_gamma5 = QudaBoolean.QUDA_BOOLEAN_FALSE 25 | eig_param.spectrum = QudaEigSpectrumType.QUDA_SPECTRUM_SR_EIG 26 | eig_param.n_ev = n_ev 27 | eig_param.n_kr = n_kr 28 | eig_param.n_conv = n_ev 29 | eig_param.tol = tol 30 | eig_param.ortho_dim = 3 31 | eig_param.ortho_dim_size_local = latt_info.Lt 32 | eig_param.vec_infile = b"" 33 | eig_param.vec_outfile = b"" 34 | eig_param.max_restarts = max_restarts 35 | eig_param.use_poly_acc = QudaBoolean(poly_deg > 1) 36 | eig_param.poly_deg = poly_deg 37 | eig_param.a_min = poly_cut 38 | eig_param.a_max = 2 39 | 40 | evals = numpy.empty((latt_info.GLt, n_ev), " 0: 15 | if d == 0 and X[0] == geo_bs[0]: 16 | print(f"X-dimension length {X[0]} cannot block length {geo_bs[0]}") 17 | elif (X[d] // geo_bs[d] + 1) % 2 == 0: 18 | print(f"Indexing does not (yet) support odd coarse dimensions: X({d}) = {X[d] // geo_bs[d]}") 19 | elif (X[d] // geo_bs[d]) * geo_bs[d] != X[d]: 20 | print(f"cannot block dim[{d}]={X[d]} with block size = {geo_bs[d]}") 21 | else: 22 | break 23 | geo_bs[d] //= 2 24 | if geo_bs[d] == 0: 25 | raise ValueError(f"Unable to block dimension {d}") 26 | X = [x // b for x, b in zip(X, geo_bs)] 27 | geo_block_size_.append(geo_bs) 28 | return geo_block_size_ 29 | 30 | 31 | def _partition(factor: List[List[List[int]]], n_level: int, block_size: List[int] = None, idx: int = 0): 32 | if idx == 0: 33 | block_size = [1 for _ in range(n_level)] 34 | factor = _factorization(factor, n_level) 35 | if idx == len(factor): 36 | yield block_size 37 | else: 38 | for factor_size in factor[idx]: 39 | yield from _partition( 40 | factor, 41 | n_level, 42 | [G * f for G, f in zip(block_size, factor_size)], 43 | idx + 1, 44 | ) 45 | -------------------------------------------------------------------------------- /pyquda_utils/gpt.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import numpy 3 | 4 | from .core import evenodd, getGridSize, LatticeGauge, LatticeInfo, LatticePropagator 5 | 6 | import gpt as g 7 | 8 | 9 | def LatticeInfoGPT(grid: g.grid, gen_simd_width: int): 10 | assert getGridSize() == grid.mpi 11 | GLx, GLy, GLz, GLt = grid.fdimensions 12 | Gx, Gy, Gz, Gt = grid.mpi 13 | Lx, Ly, Lz, Lt = GLx // Gx, GLy // Gy, GLz // Gz, GLt // Gt 14 | sublatt_size = [Lx, Ly, Lz, Lt] 15 | Nd = len(sublatt_size) 16 | precision = grid.precision.nbytes 17 | n_simd = gen_simd_width // (2 * precision) 18 | simd = [1] * Nd 19 | i = Nd - 1 20 | while n_simd > 1: 21 | simd[i] *= 2 22 | n_simd //= 2 23 | i = i - 1 if i > 0 else Nd - 1 24 | return LatticeInfo(grid.fdimensions), [sublatt_size[i] // simd[i] for i in range(Nd)], simd, precision 25 | 26 | 27 | def LatticeGaugeGPT(lattice: List[g.lattice], gen_simd_width: int, gauge: LatticeGauge = None): 28 | latt_info, gpt_latt, gpt_simd, gpt_prec = LatticeInfoGPT(lattice[0].grid, gen_simd_width) 29 | Lx, Ly, Lz, Lt = latt_info.size 30 | Nc = latt_info.Nc 31 | assert lattice[0].describe().startswith(f"ot_matrix_su_n_fundamental_group({Nc})") 32 | assert len(lattice) == latt_info.Nd 33 | if gauge is None: 34 | value = [] 35 | for index in range(latt_info.Nd): 36 | value.append( 37 | evenodd( 38 | numpy.asarray(lattice[index].mview()[0]) 39 | .view(f"= mom2_min: 17 | mom_list.append((npx, npy, npz)) 18 | return mom_list 19 | 20 | 21 | def getMomDict(mom2_max, mom2_min=0): 22 | mom_list = getMomList(mom2_max, mom2_min) 23 | mom_dict = {key: " ".join([str(np) for np in val]) for key, val in enumerate(mom_list)} 24 | return mom_dict 25 | 26 | 27 | class MomentumPhase: 28 | def __init__(self, latt_info: LatticeInfo) -> None: 29 | self.latt_info = latt_info 30 | gx, gy, gz, gt = latt_info.grid_coord 31 | Lx, Ly, Lz, Lt = latt_info.size 32 | 33 | x = numpy.zeros((4, 2, Lt, Lz, Ly, Lx // 2), " None: 110 | self.latt_info = latt_info 111 | self.stride = stride 112 | 113 | def getPhase(self, t_srce: Sequence[int]): 114 | gx, gy, gz, gt = self.latt_info.grid_coord 115 | Lx, Ly, Lz, Lt = self.latt_info.size 116 | Sx, Sy, Sz, St = self.stride 117 | x, y, z, t = t_srce 118 | sx, sy, sz, st = (x + gx * Lx) % Sx, (y + gy * Ly) % Sy, (z + gz * Lz) % Sz, (t + gt * Lt) % St 119 | phase = numpy.zeros((Lt, Lz, Ly, Lx), "xac", gauge_prod[i - 1], gauge_prod[i]) 32 | 33 | if Gi > 1: 34 | buf_shape, buf_dtype = gauge_prod[-1].shape, gauge_prod.dtype 35 | if gi != 0: 36 | buf = np.empty(buf_shape, dtype=buf_dtype) 37 | comm.Recv(buf, get_neighbor_rank((gi - 1) % Gi)) 38 | for i in range(0, Li): 39 | gauge_prod[i] = contract("xab,xbc->xac", buf, gauge_prod[i]) 40 | if gi != Gi - 1: 41 | buf = gauge_prod[-1].get() 42 | comm.Send(buf, get_neighbor_rank((gi + 1) % Gi)) 43 | if gi == Gi - 1: 44 | buf = gauge_prod[-1].get() 45 | for i in range(0, Gi - 1): 46 | comm.Send(buf, get_neighbor_rank(i)) 47 | else: 48 | buf = np.empty(buf_shape, dtype=buf_dtype) 49 | comm.Recv(buf, get_neighbor_rank(Gi - 1)) 50 | else: 51 | buf = gauge_prod[-1].get() 52 | 53 | w, v = np.linalg.eig(buf) 54 | w, v = cupy.array(w), cupy.array(v) 55 | w = cupy.angle(w) 56 | rotate = cupy.zeros_like(gauge_prod) 57 | rotate[0] = contract("xab,xb->xab", v, cupy.exp(1j * (gi * Li) / GLi * w)) 58 | for i in range(1, Li): 59 | rotate[i] = contract("xba,xbc,xc->xac", gauge_prod[i - 1].conj(), v, cupy.exp(1j * (i + gi * Li) / GLi * w)) 60 | rotate = LatticeGauge(gauge.latt_info, 1, evenodd(rotate.reshape(*axes_shape).transpose(*axes).get(), [0, 1, 2, 3])) 61 | rotate.toDevice() 62 | rotate_ = LatticeFermion(gauge.latt_info) 63 | rotate.pack(0, rotate_) 64 | gauge.data = contract("wtzyxba,dwtzyxbc->dwtzyxac", rotate.data.conj(), gauge.data) 65 | gauge.gauge_dirac.loadGauge(gauge) 66 | gauge.unpack(X, gauge.gauge_dirac.covDev(rotate_, X)) 67 | gauge.unpack(Y, gauge.gauge_dirac.covDev(rotate_, Y)) 68 | gauge.unpack(Z, gauge.gauge_dirac.covDev(rotate_, Z)) 69 | gauge.unpack(T, gauge.gauge_dirac.covDev(rotate_, T)) 70 | return rotate 71 | -------------------------------------------------------------------------------- /pyquda_utils/wilson_loop.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from .core import LatticeGauge, LatticeFermion 4 | 5 | 6 | def wilson_loop(gauge: LatticeGauge, path: List[int]): 7 | assert gauge.latt_info.Nd == 4 8 | fake_link = LatticeFermion(gauge.latt_info) 9 | link = LatticeGauge(gauge.latt_info, 1) 10 | link.pack(0, fake_link) 11 | gauge.gauge_dirac.loadGauge(gauge) 12 | for mu in path[::-1]: 13 | assert 0 <= mu < 2 * gauge.latt_info.Nd 14 | fake_link = gauge.gauge_dirac.covDev(fake_link, mu) 15 | gauge.gauge_dirac.freeGauge() 16 | link.unpack(0, fake_link) 17 | return link[0] 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from setuptools import setup 4 | 5 | 6 | if "egg_info" in sys.argv or "dist_info" in sys.argv or "sdist" in sys.argv: 7 | describe = os.popen("git describe --tags", "r").read().strip() 8 | if describe != "": 9 | if "-" in describe: 10 | tag, post, _ = describe.split("-") 11 | else: 12 | tag, post = describe, 0 13 | with open(os.path.join(os.path.dirname(__file__), "pyquda_utils", "_version.py"), "w") as f: 14 | f.write(f'__version__ = "{tag[1:]}.post{post}"\n') 15 | 16 | setup() 17 | -------------------------------------------------------------------------------- /tests/check_pyquda.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | test_dir = os.path.dirname(os.path.abspath(__file__)) 4 | weak_field = os.path.join(test_dir, "weak_field.lime") 5 | 6 | try: 7 | import pyquda 8 | except ModuleNotFoundError: 9 | import sys 10 | 11 | pyquda_dir = os.path.abspath(os.path.join(test_dir, "..")) 12 | sys.path.insert(1, pyquda_dir) 13 | import pyquda 14 | finally: 15 | pyquda.getLogger().debug(f"Using {pyquda.__file__} as pyquda") 16 | 17 | 18 | def chroma(ini_xml: str): 19 | chroma_path = os.path.abspath(os.path.join(test_dir, "chroma", "bin", "chroma")) 20 | ini_xml_path = os.path.abspath(os.path.join(test_dir, ini_xml)) 21 | return os.system(f"{chroma_path} -i {ini_xml_path}") 22 | 23 | 24 | def hmc(ini_xml: str): 25 | hmc_path = os.path.abspath(os.path.join(test_dir, "chroma", "bin", "hmc")) 26 | ini_xml_path = os.path.abspath(os.path.join(test_dir, ini_xml)) 27 | return os.system(f"{hmc_path} -i {ini_xml_path}") 28 | 29 | 30 | def data(filename: str): 31 | return os.path.abspath(os.path.join(test_dir, "data", filename)) 32 | -------------------------------------------------------------------------------- /tests/chroma/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:12 2 | 3 | RUN apt-get update && apt-get dist-upgrade -y && \ 4 | apt-get install -y \ 5 | gnulib \ 6 | pkg-config \ 7 | git 8 | 9 | RUN curl https://gitlab.gnome.org/GNOME/libxml2/-/archive/v2.9.14/libxml2-v2.9.14.tar.gz -o libxml2-v2.9.14.tar.gz && tar -xzf libxml2-v2.9.14.tar.gz && \ 10 | git clone --recursive --branch devel --depth 1 https://github.com/usqcd-software/qdpxx.git && \ 11 | git clone --recursive --branch devel --depth 1 https://github.com/JeffersonLab/chroma.git 12 | 13 | ENV LDFLAGS="-static -static-libgcc -static-libstdc++" \ 14 | CFLAGS="-Ofast -std=c99" \ 15 | CXXFLAGS="-Ofast -std=c++11" 16 | 17 | RUN cd libxml2-v2.9.14 && \ 18 | ./autogen.sh \ 19 | --prefix=$(pwd)/install \ 20 | --disable-shared \ 21 | --with-minimum \ 22 | --with-sax1 \ 23 | --with-writer \ 24 | --with-xpath && \ 25 | make -j32 && make install -j32 26 | 27 | RUN cd qdpxx && \ 28 | ./autogen.sh && ./configure \ 29 | --prefix=$(pwd)/install \ 30 | --with-libxml2=$(pwd)/../libxml2-v2.9.14/install \ 31 | --enable-parallel-arch=scalar \ 32 | --enable-precision=double && \ 33 | make -j32 && make install -j32 34 | 35 | RUN cd chroma && \ 36 | ./autogen.sh && ./configure \ 37 | --prefix=$(pwd)/install \ 38 | --with-qdp=$(pwd)/../qdpxx/install && \ 39 | make -j32 && make install -j32 40 | 41 | RUN cd chroma/install/bin && \ 42 | ls -la chroma && strip chroma && ls -la chroma && \ 43 | ls -la hmc && strip hmc && ls -la hmc 44 | 45 | FROM scratch 46 | 47 | COPY --from=0 /chroma/install/bin/chroma /chroma/install/bin/hmc / 48 | -------------------------------------------------------------------------------- /tests/chroma/LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CLQCD/PyQUDA/3c49a81b0d79f7c4c0609b2f7bb543ff771512c8/tests/chroma/LICENSE -------------------------------------------------------------------------------- /tests/chroma/bin/chroma: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6de02c5d65e69779be3e41c7200f6be4dfd8a9ea2ed1baee5fddc4382f697556 3 | size 16725512 4 | -------------------------------------------------------------------------------- /tests/chroma/build.sh: -------------------------------------------------------------------------------- 1 | docker build --net host --output bin . -------------------------------------------------------------------------------- /tests/generate_resource.py: -------------------------------------------------------------------------------- 1 | import os 2 | from check_pyquda import chroma 3 | 4 | os.chdir(os.path.dirname(__file__)) 5 | os.makedirs("data", exist_ok=True) 6 | assert chroma("test_wilson.ini.xml") == 0 # pt_prop_0 7 | assert chroma("test_clover.ini.xml") == 0 # pt_prop_1 8 | chroma("test_hisq.ini.xml") # pt_prop_2 9 | assert chroma("test_clover_isotropic.ini.xml") == 0 # pt_prop_3 10 | assert chroma("test_gaussian.ini.xml") == 0 # pt_prop_4 11 | assert chroma("test_smear.ini.xml") == 0 # ape.lime stout.lime hyp.lime 12 | assert chroma("test_wflow.ini.xml") == 0 # wflow.lime 13 | assert chroma("test_gfix.ini.xml") == 0 # wflow.lime 14 | -------------------------------------------------------------------------------- /tests/test_baryon.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from time import perf_counter 3 | 4 | import cupy as cp 5 | from cupy.cuda.runtime import deviceSynchronize 6 | from opt_einsum import contract 7 | 8 | from check_pyquda import weak_field 9 | 10 | from pyquda_utils import core, io, gamma 11 | from pyquda_plugins import pycontract 12 | 13 | core.init([1, 1, 1, 4], [24, 24, 24, 72], -1, 1.0, resource_path=".cache") 14 | dirac = core.getDefaultDirac(-0.2400, 1e-8, 1000, 1.0, 1.160920226, 1.160920226) 15 | latt_info = core.getDefaultLattice() 16 | 17 | epsilon = cp.zeros((3, 3, 3), "wtzyxilab", gamma_1.matrix, propag_j.data, gamma_3.matrix) 87 | # ) 88 | # print((a - b).norm2() ** 0.5 / a.norm2() ** 0.5) 89 | 90 | C = gamma_2 @ gamma_4 91 | CG_A = C @ gamma_2 92 | CG_B = C @ gamma_4 93 | Pp = (gamma_0 - gamma_4) / 2 94 | 95 | for contract_type in [ 96 | pycontract.BaryonContractType.IK_JL_MN, 97 | pycontract.BaryonContractType.IK_JN_ML, 98 | pycontract.BaryonContractType.IL_JK_MN, 99 | pycontract.BaryonContractType.IL_JN_MK, 100 | pycontract.BaryonContractType.IN_JK_ML, 101 | pycontract.BaryonContractType.IN_JL_MK, 102 | ]: 103 | deviceSynchronize() 104 | s = perf_counter() 105 | twopt = baryonTwoPoint(propag_i, propag_j, propag_m, contract_type, CG_A, CG_B, Pp) 106 | deviceSynchronize() 107 | core.getLogger().info(f"Time for einsum: {perf_counter() - s:.3f} sec") 108 | 109 | deviceSynchronize() 110 | s = perf_counter() 111 | twopt_ = pycontract.baryonTwoPoint(propag_i, propag_j, propag_m, contract_type, CG_A, CG_B, Pp) 112 | deviceSynchronize() 113 | core.getLogger().info(f"Time for pycontract: {perf_counter() - s:.3f} sec") 114 | 115 | core.getLogger().info(f"Relative error: {(twopt - twopt_).norm2() ** 0.5 / twopt.norm2() ** 0.5}") 116 | -------------------------------------------------------------------------------- /tests/test_checksum.py: -------------------------------------------------------------------------------- 1 | import io 2 | from math import prod 3 | from os import path 4 | import struct 5 | from typing import Dict, Tuple 6 | import zlib 7 | 8 | import numpy as np 9 | 10 | Nd, Ns, Nc = 4, 4, 3 11 | 12 | 13 | def readQIOGauge(filename: str): 14 | filename = path.expanduser(path.expandvars(filename)) 15 | with open(filename, "rb") as f: 16 | meta: Dict[str, Tuple[int, ...]] = {} 17 | buffer = f.read(8) 18 | while buffer != b"" and buffer != b"\x0A": 19 | assert buffer.startswith(b"\x45\x67\x89\xAB\x00\x01") 20 | length = (struct.unpack(">Q", f.read(8))[0] + 7) // 8 * 8 21 | name = f.read(128).strip(b"\x00").decode("utf-8") 22 | meta[name] = (f.tell(), length) 23 | f.seek(length, io.SEEK_CUR) 24 | buffer = f.read(8) 25 | for key, value in meta.items(): 26 | print(key) 27 | f.seek(value[0]) 28 | if "binary" not in key and key not in ["scidac-file-xml", "scidac-record-xml"]: 29 | print(f.read(value[1]).strip(b"\x00").decode("utf-8")) 30 | return meta["ildg-binary-data"][0], ">c16", meta["ildg-binary-data"][1] // 16 31 | 32 | 33 | def readQIOPropagator(filename: str): 34 | filename = path.expanduser(path.expandvars(filename)) 35 | with open(filename, "rb") as f: 36 | meta: Dict[str, Tuple[int, ...]] = {} 37 | buffer = f.read(8) 38 | while buffer != b"" and buffer != b"\x0A": 39 | assert buffer.startswith(b"\x45\x67\x89\xAB\x00\x01") 40 | length = (struct.unpack(">Q", f.read(8))[0] + 7) // 8 * 8 41 | name = f.read(128).strip(b"\x00").decode("utf-8") 42 | meta[name] = (f.tell(), length) 43 | f.seek(length, io.SEEK_CUR) 44 | buffer = f.read(8) 45 | for key, value in meta.items(): 46 | print(key) 47 | f.seek(value[0]) 48 | if "binary" not in key and key not in ["scidac-file-xml", "scidac-record-xml"]: 49 | print(f.read(value[1]).strip(b"\x00").decode("utf-8")) 50 | return meta["ildg-binary-data"][0], ">c16", meta["ildg-binary-data"][1] // 16 51 | 52 | 53 | def readGauge(filename: str): 54 | filename = path.expanduser(path.expandvars(filename)) 55 | with open(filename, "rb") as f: 56 | magic = f.read(4) 57 | for endian in ["<", ">"]: 58 | if struct.unpack(f"{endian}i", magic)[0] == 20103: 59 | break 60 | else: 61 | raise ValueError(f"Broken magic {magic} in MILC gauge") 62 | latt_size = struct.unpack(f"{endian}iiii", f.read(16)) 63 | timestamp = f.read(64).decode() 64 | assert struct.unpack(f"{endian}i", f.read(4))[0] == 0 65 | sum29, sum31 = struct.unpack(f"{endian}II", f.read(8)) 66 | offset = f.tell() 67 | print(latt_size, timestamp, sum29, sum31) 68 | return offset, f"{endian}c8", prod(latt_size) * Nd * Nc * Nc 69 | 70 | 71 | offset, dtype, count = readGauge("/public/ensemble/a09m310/l3296f211b630m0074m037m440e.4728") 72 | buf = np.fromfile( 73 | "/public/ensemble/a09m310/l3296f211b630m0074m037m440e.4728", 74 | dtype=dtype, 75 | count=count, 76 | offset=offset, 77 | ) 78 | work = buf.view("> (32 - rank29))) 83 | sum31 = np.bitwise_xor.reduce(np.bitwise_or(work << rank31, work >> (32 - rank31))) 84 | print(sum29, sum31) 85 | 86 | offset, dtype, count = readQIOGauge("/public/ensemble/F32P30/beta6.41_mu-0.2295_ms-0.2050_L32x96_cfg_9000.lime") 87 | buf = np.fromfile( 88 | "/public/ensemble/F32P30/beta6.41_mu-0.2295_ms-0.2050_L32x96_cfg_9000.lime", 89 | dtype=dtype, 90 | count=count, 91 | offset=offset, 92 | ) 93 | buf = buf.reshape(96 * 32 * 32 * 32, 4 * 3 * 3) 94 | work = np.empty(96 * 32 * 32 * 32, "> (32 - rank29))) 101 | sum31 = np.bitwise_xor.reduce(np.bitwise_or(work << rank31, work >> (32 - rank31))) 102 | print(hex(sum29)[2:], hex(sum31)[2:]) 103 | -------------------------------------------------------------------------------- /tests/test_clover.ini.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ; $Id: prec_clover.ini.xml,v 3.2 2006-06-11 06:30:36 edwards Exp $ 5 | ; 6 | ; Test input file for chroma main program 7 | ; 8 | 9 | 10 | 11 | 12 | 13 | MAKE_SOURCE 14 | 1 15 | 16 | 6 17 | 18 | 2 19 | POINT_SOURCE 20 | 3 21 | 0 0 0 0 22 | 23 | 24 | 1 25 | NONE 26 | 27 | 28 | 29 | 30 | default_gauge_field 31 | pt_source_0 32 | 33 | 34 | 35 | 36 | PROPAGATOR 37 | 1 38 | 39 | 10 40 | FULL 41 | false 42 | 1 43 | 44 | CLOVER 45 | 0.115 46 | 1.17 47 | 0.91 48 | 1.07 49 | 50 | true 51 | 3 52 | 2.464 53 | 0.95 54 | 55 | 56 | SIMPLE_FERMBC 57 | 1 1 1 -1 58 | 59 | 60 | 88 | 89 | BICGSTAB_INVERTER 90 | 1.0e-12 91 | 1000 92 | 93 | 94 | 95 | default_gauge_field 96 | pt_source_0 97 | pt_prop_0 98 | 99 | 100 | 101 | 102 | QIO_WRITE_NAMED_OBJECT 103 | 1 104 | 105 | pt_prop_0 106 | LatticePropagator 107 | 108 | 109 | data/pt_prop_1 110 | SINGLEFILE 111 | 112 | 113 | 114 | 115 | 4 4 4 8 116 | 117 | 118 | 119 | 120 | 11 121 | 11 122 | 11 123 | 0 124 | 125 | 126 | 127 | 128 | SZINQIO 129 | weak_field.lime 130 | 131 | -------------------------------------------------------------------------------- /tests/test_clover.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | xi_0, nu = 2.464, 0.95 6 | kappa = 0.115 7 | mass = 1 / (2 * kappa) - 4 8 | coeff_r, coeff_t = 0.91, 1.07 9 | 10 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, resource_path=".cache") 11 | 12 | gauge = io.readQIOGauge(weak_field) 13 | 14 | dslash = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r) 15 | dslash.loadGauge(gauge) 16 | propagator = core.invert(dslash, "point", [0, 0, 0, 0]) 17 | dslash.destroy() 18 | 19 | propagator_chroma = io.readQIOPropagator(data("pt_prop_1")) 20 | propagator_chroma.toDevice() 21 | print((propagator - propagator_chroma).norm2() ** 0.5) 22 | -------------------------------------------------------------------------------- /tests/test_clover_cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S python3 -m pyquda -l 4 4 4 8 -t -1 -a 2.593684210526316 -p .cache 2 | from tests.check_pyquda import weak_field, data 3 | 4 | from pyquda_utils import core, io 5 | 6 | xi_0, nu = 2.464, 0.95 7 | kappa = 0.115 8 | mass = 1 / (2 * kappa) - 4 9 | coeff_r, coeff_t = 0.91, 1.07 10 | 11 | gauge = io.readQIOGauge(weak_field) 12 | 13 | dirac = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r) 14 | dirac.loadGauge(gauge) 15 | propagator = core.invert(dirac, "point", [0, 0, 0, 0]) 16 | dirac.destroy() 17 | 18 | propagator_chroma = io.readQIOPropagator(data("pt_prop_1")) 19 | propagator_chroma.toDevice() 20 | print((propagator - propagator_chroma).norm2() ** 0.5) 21 | -------------------------------------------------------------------------------- /tests/test_clover_distance.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | xi_0, nu = 2.464, 0.95 6 | kappa = 0.115 7 | mass = 1 / (2 * kappa) - 4 8 | coeff_r, coeff_t = 0.91, 1.07 9 | 10 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, resource_path=".cache") 11 | 12 | gauge = io.readQIOGauge(weak_field) 13 | 14 | dslash = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r) 15 | dslash.loadGauge(gauge) 16 | alpha0, t0 = 0.4, 0 17 | dslash.invert_param.distance_pc_alpha0 = alpha0 18 | dslash.invert_param.distance_pc_t0 = t0 19 | propagator = core.invert(dslash, "point", [0, 0, 0, t0]) 20 | dslash.destroy() 21 | 22 | propagator_chroma = io.readQIOPropagator(data("pt_prop_1")) 23 | propagator_chroma.toDevice() 24 | print((propagator - propagator_chroma).norm2() ** 0.5) 25 | -------------------------------------------------------------------------------- /tests/test_clover_isotropic.ini.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ; $Id: prec_clover.ini.xml,v 3.2 2006-06-11 06:30:36 edwards Exp $ 5 | ; 6 | ; Test input file for chroma main program 7 | ; 8 | 9 | 10 | 11 | 12 | 13 | MAKE_SOURCE 14 | 1 15 | 16 | 6 17 | 18 | 2 19 | POINT_SOURCE 20 | 3 21 | 0 0 0 0 22 | 23 | 24 | 1 25 | NONE 26 | 27 | 28 | 29 | 30 | default_gauge_field 31 | pt_source_0 32 | 33 | 34 | 35 | 36 | PROPAGATOR 37 | 1 38 | 39 | 10 40 | FULL 41 | false 42 | 1 43 | 44 | CLOVER 45 | 0.115 46 | 1.17 47 | 0.91 48 | 1.07 49 | 50 | false 51 | 3 52 | 1.0 53 | 1.0 54 | 55 | 56 | SIMPLE_FERMBC 57 | 1 1 1 -1 58 | 59 | 60 | 61 | BICGSTAB_INVERTER 62 | 1.0e-12 63 | 1000 64 | 65 | 66 | 67 | default_gauge_field 68 | pt_source_0 69 | pt_prop_0 70 | 71 | 72 | 73 | 74 | QIO_WRITE_NAMED_OBJECT 75 | 1 76 | 77 | pt_prop_0 78 | LatticePropagator 79 | 80 | 81 | data/pt_prop_3 82 | SINGLEFILE 83 | 84 | 85 | 86 | 87 | 4 4 4 8 88 | 89 | 90 | 91 | 92 | 11 93 | 11 94 | 11 95 | 0 96 | 97 | 98 | 99 | 100 | SZINQIO 101 | weak_field.lime 102 | 103 | -------------------------------------------------------------------------------- /tests/test_clover_isotropic.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | xi_0, nu = 1.0, 1.0 6 | kappa = 0.115 7 | mass = 1 / (2 * kappa) - 4 8 | coeff_r, coeff_t = 1.17, 1.17 9 | 10 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, resource_path=".cache") 11 | 12 | gauge = io.readQIOGauge(weak_field) 13 | 14 | dslash = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r) 15 | dslash.loadGauge(gauge) 16 | propagator = core.invert(dslash, "point", [0, 0, 0, 0]) 17 | dslash.destroy() 18 | 19 | propagator_chroma = io.readQIOPropagator(data("pt_prop_3")) 20 | propagator_chroma.toDevice() 21 | print((propagator - propagator_chroma).norm2() ** 0.5) 22 | -------------------------------------------------------------------------------- /tests/test_clover_multigrid.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | xi_0, nu = 2.464, 0.95 6 | kappa = 0.115 7 | mass = 1 / (2 * kappa) - 4 8 | coeff_r, coeff_t = 0.91, 1.07 9 | 10 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, resource_path=".cache") 11 | 12 | gauge = io.readQIOGauge(weak_field) 13 | 14 | dslash = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r, [[4, 4, 4, 4]]) 15 | dslash.loadGauge(gauge) 16 | propagator = core.invert(dslash, "point", [0, 0, 0, 0], mrhs=12) 17 | dslash.destroy() 18 | 19 | propagator_chroma = io.readQIOPropagator(data("pt_prop_1")) 20 | propagator_chroma.toDevice() 21 | print((propagator - propagator_chroma).norm2() ** 0.5) 22 | -------------------------------------------------------------------------------- /tests/test_clover_numpy.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | xi_0, nu = 2.464, 0.95 6 | kappa = 0.115 7 | mass = 1 / (2 * kappa) - 4 8 | coeff_r, coeff_t = 0.91, 1.07 9 | 10 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, backend="numpy", resource_path=".cache") 11 | 12 | gauge = io.readQIOGauge(weak_field) 13 | 14 | dslash = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r) 15 | dslash.loadGauge(gauge) 16 | propagator = core.invert(dslash, "point", [0, 0, 0, 0]) 17 | dslash.destroy() 18 | 19 | propagator_chroma = io.readQIOPropagator(data("pt_prop_1")) 20 | propagator_chroma.toDevice() 21 | print((propagator - propagator_chroma).norm2() ** 0.5) 22 | -------------------------------------------------------------------------------- /tests/test_clover_torch.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | xi_0, nu = 2.464, 0.95 6 | kappa = 0.115 7 | mass = 1 / (2 * kappa) - 4 8 | coeff_r, coeff_t = 0.91, 1.07 9 | 10 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, backend="torch", resource_path=".cache") 11 | 12 | gauge = io.readQIOGauge(weak_field) 13 | 14 | dslash = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r) 15 | dslash.loadGauge(gauge) 16 | propagator = core.invert(dslash, "point", [0, 0, 0, 0]) 17 | dslash.destroy() 18 | 19 | propagator_chroma = io.readQIOPropagator(data("pt_prop_1")) 20 | propagator_chroma.toDevice() 21 | print((propagator - propagator_chroma).norm2() ** 0.5) 22 | -------------------------------------------------------------------------------- /tests/test_covdev.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cupy as cp 3 | 4 | from check_pyquda import weak_field 5 | 6 | from pyquda.field import LatticeGauge, LatticeFermion, Nd, Nc 7 | from pyquda_utils import core, io, source 8 | 9 | core.init([1, 1, 1, 1], [4, 4, 4, 8], 1, 1.0, resource_path=".cache") 10 | latt_info = core.getDefaultLattice() 11 | 12 | 13 | def covdev(U: LatticeGauge, x: LatticeFermion, mu: int): 14 | U_ = U.lexico() 15 | x_ = x.lexico() 16 | if 0 <= mu <= 3: 17 | x_ = np.einsum("tzyxab,tzyxib->tzyxia", U_[mu], np.roll(x_, -1, 3 - mu)) 18 | elif 4 <= mu <= 7: 19 | x_ = np.roll(np.einsum("tzyxba,tzyxib->tzyxia", U_[mu - 4].conj(), x_), 1, 7 - mu) 20 | x.data = core.evenodd(x_, [0, 1, 2, 3]) 21 | x.toDevice() 22 | 23 | 24 | gauge = io.readQIOGauge(weak_field) 25 | gauge.gauge_dirac.invert_param.staggered = False 26 | 27 | x = source.wall(latt_info, 0, 0, 0) 28 | for covdev_mu in range(8): 29 | b = gauge.covDev(x, covdev_mu) 30 | covdev(gauge, x, covdev_mu) 31 | 32 | print(covdev_mu, cp.linalg.norm(x.data - b.data)) 33 | 34 | 35 | def shift(U: LatticeGauge, dim: int, mu: int): 36 | U_ = U.lexico()[dim] 37 | if 0 <= mu <= 3: 38 | U_ = np.roll(U_, -1, 3 - mu) 39 | elif 4 <= mu <= 7: 40 | U_ = np.roll(U_, 1, 7 - mu) 41 | U.data[dim] = cp.asarray(core.evenodd(U_, [0, 1, 2, 3])) 42 | 43 | 44 | unit = LatticeGauge(latt_info) 45 | unit.gauge_dirac.invert_param.covdev_shift = True 46 | unit.gauge_dirac.invert_param.staggered = False 47 | 48 | gauge.toDevice() 49 | gauge2 = gauge.copy() 50 | gauge.gauge_dirac.loadGauge(gauge) 51 | x = LatticeFermion(latt_info) 52 | for dim in range(Nd): 53 | for covdev_mu in range(8): 54 | x.data[:, :, :, :, :, :Nc, :] = gauge2.data[dim] 55 | gauge2.data[dim] = unit.gauge_dirac.covDev(x, covdev_mu).data[:, :, :, :, :, :Nc, :] 56 | gauge2.projectSU3(2e-15) 57 | shift(gauge, dim, covdev_mu) 58 | 59 | print(dim, covdev_mu, cp.linalg.norm(gauge.data[dim] - gauge2.data[dim])) 60 | -------------------------------------------------------------------------------- /tests/test_dslash.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cupy as cp 3 | 4 | from check_pyquda import weak_field 5 | 6 | from pyquda import init, pyquda as quda 7 | from pyquda.field import Ns, Nc 8 | from pyquda.enum_quda import QudaParity 9 | from pyquda_utils import core 10 | 11 | init([1, 1, 1, 1], [16, 16, 16, 32], 1, 1.0, resource_path=".cache") 12 | latt_info = core.getDefaultLattice() 13 | Lx, Ly, Lz, Lt = latt_info.size 14 | 15 | 16 | def applyDslash(Mp, p, U_seed): 17 | # Set parameters in Dslash and use m=-3.5 to make kappa=1 18 | dslash = core.getDefaultDirac(-3.5, 0, 0) 19 | 20 | # Generate gauge and then load it 21 | U = core.LatticeGauge(latt_info) 22 | U.gauss(U_seed, 1.0) 23 | dslash.loadGauge(U) 24 | 25 | # Load a from p and allocate b 26 | a = core.LatticeFermion(latt_info, cp.asarray(core.evenodd(p, [0, 1, 2, 3]))) 27 | b = core.LatticeFermion(latt_info) 28 | 29 | # Dslash a = b 30 | quda.dslashQuda(b.even_ptr, a.odd_ptr, dslash.invert_param, QudaParity.QUDA_EVEN_PARITY) 31 | quda.dslashQuda(b.odd_ptr, a.even_ptr, dslash.invert_param, QudaParity.QUDA_ODD_PARITY) 32 | 33 | # Save b to Mp 34 | Mp[:] = b.lexico() 35 | 36 | # Return gauge as a ndarray with shape (Nd, Lt, Lz, Ly, Lx, Ns, Ns) 37 | return U.lexico() 38 | 39 | 40 | p = np.zeros((Lt, Lz, Ly, Lx, Ns, Nc), " 2 | 3 | 4 | QQbar on props 5 | 6 | 7 | 8 | 9 | 10 | MAKE_SOURCE 11 | 1 12 | 13 | 6 14 | 15 | 2 16 | SHELL_SOURCE 17 | 3 18 | 0 0 0 0 19 | 20 | 21 | GAUGE_INV_GAUSSIAN 22 | 2.0 23 | 5 24 | 3 25 | 26 | 27 | 28 | 1 29 | NONE 30 | 31 | 32 | 33 | APE_SMEAR 34 | 2.5 35 | 0 36 | 3 37 | 38 | 39 | 40 | 41 | 42 | default_gauge_field 43 | sh_source_0 44 | 45 | 46 | 47 | 48 | PROPAGATOR 49 | 1 50 | 51 | 10 52 | FULL 53 | false 54 | 1 55 | 56 | CLOVER 57 | 0.115 58 | 1.17 59 | 0.91 60 | 1.07 61 | 62 | true 63 | 3 64 | 2.464 65 | 0.95 66 | 67 | 68 | SIMPLE_FERMBC 69 | 1 1 1 -1 70 | 71 | 72 | 73 | BICGSTAB_INVERTER 74 | 1.0e-12 75 | 1000 76 | 77 | 78 | 79 | default_gauge_field 80 | sh_source_0 81 | sh_prop_0 82 | 83 | 84 | 85 | 86 | QIO_WRITE_NAMED_OBJECT 87 | 1 88 | 89 | sh_prop_0 90 | LatticePropagator 91 | 92 | 93 | data/pt_prop_4 94 | SINGLEFILE 95 | 96 | 97 | 98 | 99 | 4 4 4 8 100 | 101 | 102 | SZINQIO 103 | weak_field.lime 104 | 105 | -------------------------------------------------------------------------------- /tests/test_gaussian.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io, source 4 | 5 | xi_0, nu = 2.464, 0.95 6 | kappa = 0.115 7 | mass = 1 / (2 * kappa) - 4 8 | coeff_r, coeff_t = 0.91, 1.07 9 | 10 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, resource_path=".cache") 11 | 12 | gauge = io.readQIOGauge(weak_field) 13 | 14 | rho, n_steps = 2.0, 5 15 | point_source = source.propagator(core.getDefaultLattice(), "point", [0, 0, 0, 0]) 16 | shell_source = source.gaussianSmear(point_source, gauge, rho, n_steps) 17 | 18 | dslash = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r) 19 | dslash.loadGauge(gauge) 20 | propagator = core.invertPropagator(dslash, shell_source) 21 | dslash.destroy() 22 | 23 | propagator_chroma = io.readQIOPropagator(data("pt_prop_4")) 24 | propagator_chroma.toDevice() 25 | print((propagator - propagator_chroma).norm2() ** 0.5) 26 | -------------------------------------------------------------------------------- /tests/test_gfix.ini.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ; $Id: coulgauge.ini.xml,v 3.3 2007-11-09 20:57:34 edwards Exp $ 5 | ; 6 | ; Coulomb gauge fixing 7 | ; 8 | 9 | 10 | 11 | 12 | 13 | COULOMB_GAUGEFIX 14 | 1 15 | 16 | 1 17 | 2e-15 18 | 1000 19 | false 20 | 1.0 21 | 4 22 | 23 | 24 | default_gauge_field 25 | coul_cfg 26 | gauge_rot 27 | 28 | 29 | 30 | 31 | 32 | Write the config 33 | 34 | QIO_WRITE_NAMED_OBJECT 35 | 1 36 | 37 | coul_cfg 38 | Multi1dLatticeColorMatrix 39 | 40 | 41 | data/coul_cfg.lime 42 | SINGLEFILE 43 | 44 | 45 | 46 | 47 | 4 4 4 8 48 | 49 | 50 | SZINQIO 51 | weak_field.lime 52 | 53 | -------------------------------------------------------------------------------- /tests/test_gfix.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | core.init(resource_path=".cache") 6 | 7 | gauge = io.readQIOGauge(weak_field) 8 | gauge.fixingOVR(4, 1000, 1, 1.0, 2e-15, 1, 1) 9 | 10 | land_gauge = io.readQIOGauge(data("coul_cfg.lime")) 11 | print((land_gauge - gauge).norm2() ** 0.5) 12 | -------------------------------------------------------------------------------- /tests/test_grid.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | 3 | 4 | def composition4(n): 5 | """ 6 | Writing n as the sum of 4 natural numbers 7 | """ 8 | addend: List[Tuple[int, int, int, int]] = [] 9 | for i in range(n + 1): 10 | for j in range(i + 1, n + 2): 11 | for k in range(j + 1, n + 3): 12 | x, y, z, t = i, j - i - 1, k - j - 1, n + 3 - k - 1 13 | addend.append((x, y, z, t)) 14 | return addend 15 | 16 | 17 | def composition4_v2(n): 18 | """ 19 | Writing n as the sum of 4 natural numbers 20 | """ 21 | addend: List[Tuple[int, int, int, int]] = [] 22 | for i in range(n + 1): 23 | for j in range(i, n + 1): 24 | for k in range(j, n + 1): 25 | x, y, z, t = i, j - i, k - j, n - k 26 | addend.append((x, y, z, t)) 27 | return addend 28 | 29 | 30 | def factorization4(k: int): 31 | """ 32 | Writing k as the product of 4 positive numbers 33 | """ 34 | prime_factor: List[List[Tuple[int, int, int, int]]] = [] 35 | for p in range(2, int(k**0.5) + 1): 36 | n = 0 37 | while k % p == 0: 38 | n += 1 39 | k //= p 40 | if n != 0: 41 | prime_factor.append([(p**x, p**y, p**z, p**t) for x, y, z, t in composition4(n)]) 42 | if k != 1: 43 | prime_factor.append([(k**x, k**y, k**z, k**t) for x, y, z, t in composition4(1)]) 44 | return prime_factor 45 | 46 | 47 | def partition(factor: List[List[Tuple[int, int, int, int]]], idx: int, sublatt_size: List[int], grid_size: List[int]): 48 | if idx == 0: 49 | factor = factorization4(factor) 50 | if idx == len(factor): 51 | yield grid_size 52 | else: 53 | Lx, Ly, Lz, Lt = sublatt_size 54 | Gx, Gy, Gz, Gt = grid_size 55 | for x, y, z, t in factor[idx]: 56 | if Lx % x == 0 and Ly % y == 0 and Lz % z == 0 and Lt % t == 0: 57 | yield from partition( 58 | factor, idx + 1, [Lx // x, Ly // y, Lz // z, Lt // t], [Gx * x, Gy * y, Gz * z, Gt * t] 59 | ) 60 | 61 | 62 | def getDefaultGrid(mpi_size: int, latt_size: List[int]): 63 | Lx, Ly, Lz, Lt = latt_size 64 | latt_vol = Lx * Ly * Lz * Lt 65 | latt_surf = [latt_vol // latt_size[dir] for dir in range(4)] 66 | min_comm, min_grid = latt_vol, [] 67 | assert latt_vol % mpi_size == 0 68 | for grid_size in partition(mpi_size, 0, latt_size, [1, 1, 1, 1]): 69 | comm = [latt_surf[dir] * grid_size[dir] for dir in range(4) if grid_size[dir] > 1] 70 | if sum(comm) < min_comm: 71 | min_comm, min_grid = sum(comm), [grid_size] 72 | elif sum(comm) == min_comm: 73 | min_grid.append(grid_size) 74 | return min(min_grid) 75 | 76 | 77 | print(getDefaultGrid(1, [48, 48, 48, 256])) 78 | -------------------------------------------------------------------------------- /tests/test_hisq.ini.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MAKE_SOURCE_STAG 7 | 1 8 | 9 | 6 10 | 11 | 2 12 | POINT_SOURCE 13 | 3 14 | 0 0 0 0 15 | 16 | 17 | 1 18 | NONE 19 | 20 | 21 | 22 | 23 | default_gauge_field 24 | pt_source_0 25 | 26 | 27 | 28 | 29 | PROPAGATOR_STAG 30 | 1 31 | 32 | 10 33 | FULL 34 | false 35 | 1 36 | 37 | HISQ 38 | 0.0102 39 | 1.0 40 | 41 | SIMPLE_FERM_STATE 42 | 43 | SIMPLE_FERMBC 44 | 1 1 1 1 45 | 46 | 47 | 48 | 49 | CG_INVERTER 50 | 1e-12 51 | 1000 52 | 53 | 54 | 55 | default_gauge_field 56 | pt_source_0 57 | pt_prop_0 58 | 59 | 60 | 61 | 62 | QIO_WRITE_NAMED_OBJECT 63 | 1 64 | 65 | pt_prop_0 66 | LatticeStaggeredPropagator 67 | 68 | 69 | data/pt_prop_2 70 | SINGLEFILE 71 | 72 | 73 | 74 | 4 4 4 8 75 | 76 | 77 | 78 | 79 | 11 80 | 11 81 | 11 82 | 0 83 | 84 | 85 | 86 | 87 | SZINQIO 88 | weak_field.lime 89 | 90 | 91 | -------------------------------------------------------------------------------- /tests/test_hisq.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | core.init([1, 1, 1, 1], [4, 4, 4, 8], 1, 1.0, resource_path=".cache") 6 | 7 | mass = 0.0102 8 | 9 | dslash = core.getDefaultStaggeredDirac(mass, 1e-12, 1000, 1.0, 0.0) 10 | dslash.gauge_param.staggered_phase_type = 2 # QUDA_STAGGERED_PHASE_CHROMA 11 | gauge = io.readQIOGauge(weak_field) 12 | 13 | dslash.loadGauge(gauge) 14 | 15 | # Lx, Ly, Lz, Lt = latt_info.size 16 | # Cx = np.arange(Lx).reshape(1, 1, 1, Lx).repeat(Ly, 2).repeat(Lz, 1).repeat(Lt, 0) 17 | # Cy = np.arange(Ly).reshape(1, 1, Ly, 1).repeat(Lx, 3).repeat(Lz, 1).repeat(Lt, 0) 18 | # Cz = np.arange(Lz).reshape(1, Lz, 1, 1).repeat(Lx, 3).repeat(Ly, 2).repeat(Lt, 0) 19 | # Ct = np.arange(Lt).reshape(Lt, 1, 1, 1).repeat(Lx, 3).repeat(Ly, 2).repeat(Lz, 1) 20 | # Convert from CPS(QUDA, Old) to Chroma 21 | # phase = cp.asarray(core.evenodd(np.where((Cx) % 2 == 1, -1, 1), [0, 1, 2, 3])) 22 | # Convert from MILC to Chroma 23 | # phase = cp.asarray(core.evenodd(np.where(((Cx + Cy + Cz) % 2 == 1) | (Ct % 2 == 1), -1, 1), [0, 1, 2, 3])) 24 | # # Convert from CPS(QUDA, New) to Chroma 25 | # phase = cp.asarray(core.evenodd(np.where((Cx + Cy + Cz + Ct) % 2 == 1, -1, 1), [0, 1, 2, 3])) 26 | 27 | propagator = core.invertStaggered(dslash, "point", [0, 0, 0, 0]) 28 | 29 | dslash.destroy() 30 | 31 | propagator_chroma = io.readQIOPropagator(data("pt_prop_2")) 32 | propagator_chroma.toDevice() 33 | print((propagator - propagator_chroma).norm2() ** 0.5) 34 | 35 | import cupy as cp 36 | 37 | twopt = cp.einsum("wtzyxab,wtzyxab->t", propagator.data.conj(), propagator.data) 38 | twopt_chroma = cp.einsum("wtzyxab,wtzyxab->t", propagator_chroma.data.conj(), propagator_chroma.data) 39 | print(cp.linalg.norm(twopt - twopt_chroma)) 40 | -------------------------------------------------------------------------------- /tests/test_hmc_clover.py: -------------------------------------------------------------------------------- 1 | from math import exp 2 | from time import perf_counter 3 | 4 | from check_pyquda import test_dir 5 | 6 | from pyquda.hmc import HMC, O4Nf5Ng0V 7 | from pyquda.action import GaugeAction, CloverWilsonAction 8 | from pyquda_utils import core 9 | from pyquda_utils.hmc_param import ( 10 | symanzikTreeGaugeLoopParam as loopParam, 11 | wilsonFermionRationalParam as rationalParam, 12 | ) 13 | from pyquda_utils.io import writeNPYGauge 14 | 15 | beta, u_0 = 7.4, 0.890 16 | clover_csw = 1 / u_0**3 17 | tol, maxiter = 1e-6, 1000 18 | start, stop, warm, save = 0, 2000, 500, 5 19 | t = 1.0 20 | 21 | core.init([1, 1, 1, 1], resource_path=".cache", enable_force_monitor=True) 22 | latt_info = core.LatticeInfo([4, 4, 4, 8], t_boundary=-1, anisotropy=1.0) 23 | 24 | monomials = [ 25 | GaugeAction(latt_info, loopParam(u_0), beta), 26 | CloverWilsonAction(latt_info, rationalParam(2, 12, 15, 7e-4, 32, 50), 0.3, 2, tol, maxiter, clover_csw), 27 | CloverWilsonAction(latt_info, rationalParam(1, 12, 15, 7e-4, 32, 50), 0.5, 1, tol, maxiter, clover_csw), 28 | ] 29 | 30 | # hmc_inner = HMC(latt_info, monomials[:1], O4Nf5Ng0V(4)) 31 | # hmc = HMC(latt_info, monomials[1:], O4Nf5Ng0V(3), hmc_inner) 32 | hmc = HMC(latt_info, monomials, O4Nf5Ng0V(5)) 33 | gauge = core.LatticeGauge(latt_info) 34 | hmc.initialize(10086, gauge) 35 | 36 | plaq = hmc.plaquette() 37 | core.getLogger().info(f"Trajectory {start}:\n" f"Plaquette = {plaq}\n") 38 | for i in range(start, stop): 39 | s = perf_counter() 40 | 41 | hmc.gaussMom() 42 | hmc.samplePhi() 43 | 44 | kinetic_old, potential_old = hmc.momAction(), hmc.gaugeAction() + hmc.fermionAction() 45 | energy_old = kinetic_old + potential_old 46 | 47 | hmc.integrate(t, 2e-15) 48 | 49 | kinetic, potential = hmc.momAction(), hmc.gaugeAction() + hmc.fermionAction() 50 | energy = kinetic + potential 51 | 52 | accept = hmc.accept(energy - energy_old) 53 | if accept or i < warm: 54 | hmc.saveGauge(gauge) 55 | else: 56 | hmc.loadGauge(gauge) 57 | 58 | plaq = hmc.plaquette() 59 | core.getLogger().info( 60 | f"Trajectory {i + 1}:\n" 61 | f"Plaquette = {plaq}\n" 62 | f"P_old = {potential_old}, K_old = {kinetic_old}\n" 63 | f"P = {potential}, K = {kinetic}\n" 64 | f"Delta_P = {potential - potential_old}, Delta_K = {kinetic - kinetic_old}\n" 65 | f"Delta_E = {energy - energy_old}\n" 66 | f"acceptance rate = {exp(min(energy_old - energy, 0)) * 100:.2f}%\n" 67 | f"accept? {accept}\n" 68 | f"warmup? {i < warm}\n" 69 | f"HMC time = {perf_counter() - s:.3f} secs\n" 70 | ) 71 | 72 | if (i + 1) % save == 0: 73 | writeNPYGauge(f"./DATA/cfg/cfg_{i + 1}.npy", gauge) 74 | -------------------------------------------------------------------------------- /tests/test_hmc_gauge.py: -------------------------------------------------------------------------------- 1 | from math import exp 2 | from time import perf_counter 3 | 4 | from check_pyquda import test_dir 5 | 6 | from pyquda.hmc import HMC, O4Nf5Ng0V 7 | from pyquda.action import GaugeAction 8 | from pyquda_utils import core 9 | from pyquda_utils.hmc_param import symanzikTreeGaugeLoopParam as loopParam 10 | from pyquda_utils.io import writeNPYGauge 11 | 12 | beta, u_0 = 7.4, 0.890 13 | start, stop, warm, save = 0, 2000, 500, 5 14 | t = 1.0 15 | 16 | core.init(resource_path=".cache", enable_force_monitor=True) 17 | latt_info = core.LatticeInfo([4, 4, 4, 8], t_boundary=-1, anisotropy=1.0) 18 | 19 | monomials = [GaugeAction(latt_info, loopParam(u_0), beta)] 20 | 21 | hmc = HMC(latt_info, monomials, O4Nf5Ng0V(10)) 22 | gauge = core.LatticeGauge(latt_info) 23 | hmc.initialize(10086, gauge) 24 | 25 | plaq = hmc.plaquette() 26 | core.getLogger().info(f"Trajectory {start}:\n" f"Plaquette = {plaq}\n") 27 | for i in range(start, stop): 28 | s = perf_counter() 29 | 30 | hmc.gaussMom() 31 | 32 | kinetic_old, potential_old = hmc.momAction(), hmc.gaugeAction() 33 | energy_old = kinetic_old + potential_old 34 | 35 | hmc.integrate(t, 2e-15) 36 | 37 | kinetic, potential = hmc.momAction(), hmc.gaugeAction() 38 | energy = kinetic + potential 39 | 40 | accept = hmc.accept(energy - energy_old) 41 | if accept or i < warm: 42 | hmc.saveGauge(gauge) 43 | else: 44 | hmc.loadGauge(gauge) 45 | 46 | plaq = hmc.plaquette() 47 | core.getLogger().info( 48 | f"Trajectory {i + 1}:\n" 49 | f"Plaquette = {plaq}\n" 50 | f"P_old = {potential_old}, K_old = {kinetic_old}\n" 51 | f"P = {potential}, K = {kinetic}\n" 52 | f"Delta_P = {potential - potential_old}, Delta_K = {kinetic - kinetic_old}\n" 53 | f"Delta_E = {energy - energy_old}\n" 54 | f"acceptance rate = {exp(min(energy_old - energy, 0)) * 100:.2f}%\n" 55 | f"accept? {accept}\n" 56 | f"warmup? {i < warm}\n" 57 | f"HMC time = {perf_counter() - s:.3f} secs\n" 58 | ) 59 | 60 | if (i + 1) % save == 0: 61 | writeNPYGauge(f"./DATA/cfg/cfg_{i + 1}.npy", gauge) 62 | -------------------------------------------------------------------------------- /tests/test_hmc_hisq.py: -------------------------------------------------------------------------------- 1 | from math import exp 2 | from time import perf_counter 3 | 4 | from check_pyquda import test_dir 5 | 6 | from pyquda.hmc import HMC, INT_3G1F 7 | from pyquda.action import GaugeAction, HISQAction 8 | from pyquda_utils import core 9 | from pyquda_utils.hmc_param import ( 10 | symanzikTreeGaugeLoopParam as loopParam, 11 | staggeredFermionRationalParam as rationalParam, 12 | ) 13 | from pyquda_utils.io import readMILCGauge, writeNPYGauge 14 | 15 | beta, u_0 = 7.3, 0.880 16 | tol, maxiter = 1e-6, 2500 17 | start, stop, warm, save = 0, 1, 500, 5 18 | t = 0.48 19 | 20 | core.init([1, 1, 1, 2], resource_path=".cache", enable_force_monitor=True) 21 | latt_info = core.LatticeInfo([4, 4, 4, 12], t_boundary=-1, anisotropy=1.0) 22 | 23 | monomials = [ 24 | GaugeAction(latt_info, loopParam(u_0), beta), 25 | HISQAction(latt_info, rationalParam((0.0012, 0.0323, 0.2), (2, 1, -3), 9, 11, 1e-15, 90, 100), 100 * tol, maxiter), 26 | HISQAction(latt_info, rationalParam((0.2,), (1,), 7, 9, 1e-15, 90, 75), tol, maxiter), 27 | HISQAction(latt_info, rationalParam((0.2,), (1,), 7, 9, 1e-15, 90, 75), tol, maxiter), 28 | HISQAction(latt_info, rationalParam((0.2,), (1,), 7, 9, 1e-15, 90, 75), tol, maxiter), 29 | HISQAction(latt_info, rationalParam((0.432,), (1,), 7, 9, 1e-15, 90, 75), tol, maxiter, naik_epsilon=-0.116203), 30 | ] 31 | 32 | hmc = HMC(latt_info, monomials, INT_3G1F(24)) 33 | gauge = core.LatticeGauge(latt_info) 34 | # gauge = readMILCGauge("./s16t32_beta7.3_ml0.0012ms0.0323mc0.432.600") 35 | hmc.initialize(10086, gauge) 36 | 37 | plaq = hmc.plaquette() 38 | core.getLogger().info(f"Trajectory {start}:\n" f"Plaquette = {plaq}\n") 39 | for i in range(start, stop): 40 | s = perf_counter() 41 | 42 | hmc.gaussMom() 43 | hmc.samplePhi() 44 | 45 | kinetic_old, potential_old = hmc.momAction(), hmc.gaugeAction() + hmc.fermionAction() 46 | energy_old = kinetic_old + potential_old 47 | 48 | hmc.integrate(t, 2e-15) 49 | 50 | kinetic, potential = hmc.momAction(), hmc.gaugeAction() + hmc.fermionAction() 51 | energy = kinetic + potential 52 | 53 | accept = hmc.accept(energy - energy_old) 54 | if accept or i < warm: 55 | hmc.saveGauge(gauge) 56 | else: 57 | hmc.loadGauge(gauge) 58 | 59 | plaq = hmc.plaquette() 60 | core.getLogger().info( 61 | f"Trajectory {i + 1}:\n" 62 | f"Plaquette = {plaq}\n" 63 | f"P_old = {potential_old}, K_old = {kinetic_old}\n" 64 | f"P = {potential}, K = {kinetic}\n" 65 | f"Delta_P = {potential - potential_old}, Delta_K = {kinetic - kinetic_old}\n" 66 | f"Delta_E = {energy - energy_old}\n" 67 | f"acceptance rate = {exp(min(energy_old - energy, 0)) * 100:.2f}%\n" 68 | f"accept? {accept}\n" 69 | f"warmup? {i < warm}\n" 70 | f"HMC time = {perf_counter() - s:.3f} secs\n" 71 | ) 72 | 73 | if (i + 1) % save == 0: 74 | writeNPYGauge(f"./DATA/cfg/cfg_{i + 1}.npy", gauge) 75 | -------------------------------------------------------------------------------- /tests/test_io.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field 2 | 3 | from pyquda_utils import core, io, convert 4 | 5 | xi_0, nu = 2.464, 0.95 6 | kappa = 0.115 7 | mass = 1 / (2 * kappa) - 4 8 | coeff = 1.17 9 | coeff_r, coeff_t = 0.91, 1.07 10 | 11 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, resource_path=".cache") 12 | 13 | dslash = core.getDefaultDirac(mass, 1e-12, 1000, xi_0, coeff_t, coeff_r) 14 | gauge = io.readQIOGauge(weak_field) 15 | dslash.loadGauge(gauge) 16 | propagator = core.invert(dslash, "point", [0, 0, 0, 0]) 17 | dslash.destroy() 18 | 19 | print([(f"{i:08x}", f"{j:08x}") for i, j in gauge.checksum()]) 20 | gauge.save("weak_field.h5") 21 | propagator.save("pt_prop_1.h5", 0) 22 | convert.propagatorToMultiFermion(propagator).append("pt_prop_1.h5", range(12)) 23 | gauge.saveNPY("weak_field.npy") 24 | propagator.saveNPY("pt_prop_1.npy") 25 | 26 | propagator.toHost() 27 | gauge_h5 = core.LatticeGauge.load("weak_field.h5") 28 | propagator_h5 = core.LatticePropagator.load("pt_prop_1.h5", 0) 29 | multifermion_h5 = convert.multiFermionToPropagator(core.MultiLatticeFermion.load("pt_prop_1.h5", range(12))) 30 | fermion_h5 = core.LatticeFermion.load("pt_prop_1.h5", 5) 31 | propagator_chroma = io.readQIOPropagator("./tests/data/pt_prop_1") 32 | gauge_npy = core.LatticeGauge.loadNPY("weak_field.npy") 33 | propagator_npy = core.LatticePropagator.loadNPY("pt_prop_1.npy") 34 | gauge_npy_old = io.readNPYGauge("weak_field.npy") 35 | propagator_npy_old = io.readNPYPropagator("pt_prop_1.npy") 36 | print((gauge_h5 - gauge).norm2() ** 0.5) 37 | print((propagator - propagator_chroma).norm2() ** 0.5) 38 | print((propagator_h5 - propagator_chroma).norm2() ** 0.5) 39 | print((multifermion_h5 - propagator_chroma).norm2() ** 0.5) 40 | print((fermion_h5 - propagator_chroma.getFermion(1, 2)).norm2() ** 0.5) 41 | print((gauge_npy - gauge).norm2() ** 0.5) 42 | print((propagator_npy - propagator).norm2() ** 0.5) 43 | print((gauge_npy_old - gauge).norm2() ** 0.5) 44 | print((propagator_npy_old - propagator).norm2() ** 0.5) 45 | -------------------------------------------------------------------------------- /tests/test_laplace.py: -------------------------------------------------------------------------------- 1 | from time import perf_counter 2 | 3 | import numpy as np 4 | import cupy as cp 5 | from cupyx.scipy.sparse import linalg 6 | from opt_einsum import contract 7 | 8 | from check_pyquda import weak_field 9 | 10 | from pyquda.field import LatticeGauge, LatticeInfo, LatticeStaggeredFermion, MultiLatticeStaggeredFermion, Nc 11 | from pyquda import enum_quda, pyquda as quda 12 | from pyquda_utils import core, io, eigensolve 13 | 14 | core.init(resource_path=".cache") 15 | 16 | t = 3 17 | 18 | gauge = io.readChromaQIOGauge(weak_field) 19 | gauge.smearSTOUT(10, 0.12, 3) 20 | Lx, Ly, Lz, Lt = gauge.latt_info.size 21 | latt_info = LatticeInfo([Lx, Ly, Lz, 1]) 22 | gauge_tmp_lexico = cp.array(gauge.lexico()[:, t]) 23 | gauge_tmp_lexico_dagger = gauge_tmp_lexico.transpose(0, 1, 2, 3, 5, 4).conj().copy() 24 | gauge_tmp = LatticeGauge(latt_info, core.evenodd(gauge.lexico()[:, t : t + 1], [1, 2, 3, 4])) 25 | 26 | n_ev = 20 27 | n_kr = min(max(2 * n_ev, n_ev + 32), Lz * Ly * Lx * Nc - 1) 28 | tol = 1e-9 29 | max_restarts = 10 * Lz * Ly * Lx * Nc // (n_kr - n_ev) 30 | 31 | 32 | def Laplacian(x): 33 | x = x.reshape(Lz * Ly * Lx * Nc, -1) 34 | ret = cp.zeros_like(x, "zyxac", gauge_tmp_lexico[0], cp.roll(x, -1, 2)) 55 | + contract("zyxab,zyxbc->zyxac", gauge_tmp_lexico[1], cp.roll(x, -1, 1)) 56 | + contract("zyxab,zyxbc->zyxac", gauge_tmp_lexico[2], cp.roll(x, -1, 0)) 57 | + cp.roll(contract("zyxab,zyxbc->zyxac", gauge_tmp_lexico_dagger[0], x), 1, 2) 58 | + cp.roll(contract("zyxab,zyxbc->zyxac", gauge_tmp_lexico_dagger[1], x), 1, 1) 59 | + cp.roll(contract("zyxab,zyxbc->zyxac", gauge_tmp_lexico_dagger[2], x), 1, 0) 60 | ) 61 | ).reshape(Lz * Ly * Lx * Nc, -1) 62 | 63 | 64 | A = linalg.LinearOperator((Lz * Ly * Lx * Nc, Lz * Ly * Lx * Nc), matvec=_Laplacian, matmat=_Laplacian) 65 | s = perf_counter() 66 | evals, evecs = linalg.eigsh(A, n_ev, which="SA", tol=tol) 67 | print(f"{perf_counter() - s:.3f} secs") 68 | print(evals) 69 | 70 | gauge_tmp._gauge_dirac.loadGauge(gauge_tmp) 71 | eig_param = quda.QudaEigParam() 72 | eig_param.invert_param = gauge_tmp._gauge_dirac.invert_param 73 | eig_param.eig_type = enum_quda.QudaEigType.QUDA_EIG_TR_LANCZOS 74 | eig_param.use_dagger = enum_quda.QudaBoolean.QUDA_BOOLEAN_FALSE 75 | eig_param.use_norm_op = enum_quda.QudaBoolean.QUDA_BOOLEAN_FALSE 76 | eig_param.use_pc = enum_quda.QudaBoolean.QUDA_BOOLEAN_FALSE 77 | eig_param.compute_gamma5 = enum_quda.QudaBoolean.QUDA_BOOLEAN_FALSE 78 | eig_param.spectrum = enum_quda.QudaEigSpectrumType.QUDA_SPECTRUM_SR_EIG 79 | eig_param.n_ev = n_ev 80 | eig_param.n_kr = n_kr 81 | eig_param.n_conv = n_ev 82 | eig_param.tol = tol 83 | eig_param.vec_infile = b"" 84 | eig_param.vec_outfile = b"" 85 | eig_param.max_restarts = max_restarts 86 | eig_param.use_poly_acc = enum_quda.QudaBoolean.QUDA_BOOLEAN_TRUE 87 | eig_param.poly_deg = 20 88 | eig_param.a_min = 0.4 89 | eig_param.a_max = 2.0 90 | 91 | evecs = MultiLatticeStaggeredFermion(latt_info, n_ev) 92 | evals = np.zeros((n_ev), "x", 53 | gamma_src @ gamma5, 54 | propagator.data.reshape(Vol, Ns, Ns, Nc, Nc).conj(), 55 | gamma5 @ gamma_snk, 56 | propagator.data.reshape(Vol, Ns, Ns, Nc, Nc), 57 | optimize=True, 58 | ) 59 | for p in range(mom_num): 60 | res = cp.einsum( 61 | "wtzyx,wtzyx->t", 62 | phase_list[p], 63 | tmp.reshape(2, Lt, Lz, Ly, Lx // 2), 64 | optimize=True, 65 | ) 66 | res = cp.roll(res, -t) 67 | twopt[t, :, gamma_idx, p] = res.get() 68 | gamma_idx += 1 69 | print(f"Contraction for {len(gamma_insertion)} gamma insertions: {perf_counter()-s:.2f}sec.") 70 | -------------------------------------------------------------------------------- /tests/test_shift.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field 2 | 3 | from pyquda_utils import core, io 4 | from pyquda_utils.core import X, Y, Z, T 5 | 6 | core.init([1, 1, 1, 1], [4, 4, 4, 8], 1, 1.0, resource_path=".cache") 7 | 8 | gauge = io.readQIOGauge(weak_field) 9 | gauge.toDevice() 10 | gauge_shift = gauge.shift([-X, -Y, -Z, -T]) 11 | 12 | print(gauge[X].data[0, 0, 0, 0, 0]) 13 | print(gauge_shift[X].data[1, 0, 0, 0, 0]) 14 | print(gauge[Y].data[0, 0, 0, 0, 0]) 15 | print(gauge_shift[Y].data[1, 0, 0, 1, 0]) 16 | print(gauge[Z].data[0, 0, 0, 0, 0]) 17 | print(gauge_shift[Z].data[1, 0, 1, 0, 0]) 18 | print(gauge[T].data[0, 0, 0, 0, 0]) 19 | print(gauge_shift[T].data[1, 1, 0, 0, 0]) 20 | -------------------------------------------------------------------------------- /tests/test_smear.ini.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ; $Id: fermstate.ini.xml,v 1.1 2006-09-21 20:18:15 edwards Exp $ 5 | ; 6 | ; Test input file for chroma main program 7 | ; 8 | 9 | 10 | 11 | 12 | 13 | LINK_SMEAR 14 | 1 15 | 16 | 2 17 | APE_SMEAR 18 | 2.5 19 | 1 20 | 4 21 | 100 22 | 2.0e-15 23 | 24 | 25 | default_gauge_field 26 | ape 27 | 28 | 29 | 30 | 31 | QIO_WRITE_NAMED_OBJECT 32 | 1 33 | 34 | ape 35 | Multi1dLatticeColorMatrix 36 | 37 | 38 | data/ape.lime 39 | SINGLEFILE 40 | 41 | 42 | 43 | 44 | LINK_SMEAR 45 | 1 46 | 47 | STOUT_SMEAR 48 | 0.241 49 | 1 50 | 3 51 | 52 | 53 | default_gauge_field 54 | stout 55 | 56 | 57 | 58 | 79 | 80 | 81 | QIO_WRITE_NAMED_OBJECT 82 | 1 83 | 84 | stout 85 | Multi1dLatticeColorMatrix 86 | 87 | 88 | data/stout.lime 89 | SINGLEFILE 90 | 91 | 92 | 93 | 94 | LINK_SMEAR 95 | 1 96 | 97 | 5 98 | HYP_SMEAR 99 | 0.75 100 | 0.6 101 | 0.3 102 | 1 103 | 4 104 | 100 105 | 2.0e-15 106 | 107 | 108 | default_gauge_field 109 | hyp 110 | 111 | 112 | 113 | 114 | QIO_WRITE_NAMED_OBJECT 115 | 1 116 | 117 | hyp 118 | Multi1dLatticeColorMatrix 119 | 120 | 121 | data/hyp.lime 122 | SINGLEFILE 123 | 124 | 125 | 126 | 127 | 128 | 4 4 4 8 129 | 130 | 131 | 132 | 133 | 11 134 | 11 135 | 11 136 | 0 137 | 138 | 139 | 140 | 141 | SZINQIO 142 | weak_field.lime 143 | 144 | -------------------------------------------------------------------------------- /tests/test_smear.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | core.init(None, [4, 4, 4, 8], resource_path=".cache") 6 | 7 | gauge = io.readQIOGauge(weak_field) 8 | gauge_ape = gauge.copy() 9 | gauge_ape.smearAPE(1, 2.5, 4) 10 | # gauge_ape.apeSmear(1, (4 - 1) / (4 - 1 + 2.5 / 2), 4) 11 | gauge_stout = gauge.copy() 12 | gauge_stout.stoutSmear(1, 0.241, 3) 13 | gauge_hyp = gauge.copy() 14 | gauge_hyp.hypSmear(1, 0.75, 0.6, 0.3, 4) 15 | # gauge.setAntiPeriodicT() # for fermion smearing 16 | 17 | gauge_chroma = io.readQIOGauge(data("ape.lime")) 18 | print((gauge_ape - gauge_chroma).norm2() ** 0.5) 19 | 20 | gauge_chroma = io.readQIOGauge(data("stout.lime")) 21 | print((gauge_stout - gauge_chroma).norm2() ** 0.5) 22 | 23 | gauge_chroma = io.readQIOGauge(data("hyp.lime")) 24 | print((gauge_hyp - gauge_chroma).norm2() ** 0.5) 25 | -------------------------------------------------------------------------------- /tests/test_sun.py: -------------------------------------------------------------------------------- 1 | import math 2 | from time import perf_counter 3 | from mpi4py import MPI 4 | 5 | from check_pyquda import weak_field 6 | 7 | from pyquda_io.chroma import readQIOGauge as readChromaQIOGauge 8 | from pyquda_utils.gauge_nd_sun import init, LatticeGauge, link 9 | 10 | GPU = False 11 | if GPU: 12 | import cupy as numpy 13 | else: 14 | import numpy 15 | MODE = "link_four" 16 | 17 | init([1, 2, 1, 2], [4, 4, 4, 8], "cupy" if GPU else "numpy") 18 | s = perf_counter() 19 | print([0.9948041322666998, 0.9947985783413031, 0.9948096861920964]) 20 | latt_size, gauge = readChromaQIOGauge(weak_field) 21 | print(f"Load: {perf_counter() - s:.3f} s") 22 | s = perf_counter() 23 | unit = LatticeGauge(latt_size, 3, 0) 24 | gauge = LatticeGauge(latt_size, 3, 1, gauge) 25 | if GPU: 26 | gauge.toDevice() # CUDA-aware MPI required 27 | print(f"Prepare: {perf_counter() - s:.3f} s") 28 | 29 | s = perf_counter() 30 | plaq = numpy.zeros((6)) 31 | if MODE == "gauge_extend": 32 | plaq[0] = numpy.vdot( 33 | gauge[0] @ gauge.shift([1, 0, 0, 0])[1], 34 | gauge[1] @ gauge.shift([0, 1, 0, 0])[0], 35 | ).real 36 | plaq[1] = numpy.vdot( 37 | gauge[0] @ gauge.shift([1, 0, 0, 0])[2], 38 | gauge[2] @ gauge.shift([0, 0, 1, 0])[0], 39 | ).real 40 | plaq[2] = numpy.vdot( 41 | gauge[1] @ gauge.shift([0, 1, 0, 0])[2], 42 | gauge[2] @ gauge.shift([0, 0, 1, 0])[1], 43 | ).real 44 | plaq[3] = numpy.vdot( 45 | gauge[0] @ gauge.shift([1, 0, 0, 0])[3], 46 | gauge[3] @ gauge.shift([0, 0, 0, 1])[0], 47 | ).real 48 | plaq[4] = numpy.vdot( 49 | gauge[1] @ gauge.shift([0, 1, 0, 0])[3], 50 | gauge[3] @ gauge.shift([0, 0, 0, 1])[1], 51 | ).real 52 | plaq[5] = numpy.vdot( 53 | gauge[2] @ gauge.shift([0, 0, 1, 0])[3], 54 | gauge[3] @ gauge.shift([0, 0, 0, 1])[2], 55 | ).real 56 | elif MODE == "link_shift": 57 | plaq[0] = numpy.vdot(gauge[0] @ gauge[1].shift(0), gauge[1] @ gauge[0].shift(1)).real 58 | plaq[1] = numpy.vdot(gauge[0] @ gauge[2].shift(0), gauge[2] @ gauge[0].shift(2)).real 59 | plaq[2] = numpy.vdot(gauge[1] @ gauge[2].shift(1), gauge[2] @ gauge[1].shift(2)).real 60 | plaq[3] = numpy.vdot(gauge[0] @ gauge[3].shift(0), gauge[3] @ gauge[0].shift(3)).real 61 | plaq[4] = numpy.vdot(gauge[1] @ gauge[3].shift(1), gauge[3] @ gauge[1].shift(3)).real 62 | plaq[5] = numpy.vdot(gauge[2] @ gauge[3].shift(2), gauge[3] @ gauge[2].shift(3)).real 63 | elif MODE == "link_two": 64 | plaq[0] = numpy.vdot(link(gauge[0], gauge[1]).data, link(gauge[1], gauge[0]).data).real 65 | plaq[1] = numpy.vdot(link(gauge[0], gauge[2]).data, link(gauge[2], gauge[0]).data).real 66 | plaq[2] = numpy.vdot(link(gauge[1], gauge[2]).data, link(gauge[2], gauge[1]).data).real 67 | plaq[3] = numpy.vdot(link(gauge[0], gauge[3]).data, link(gauge[3], gauge[0]).data).real 68 | plaq[4] = numpy.vdot(link(gauge[1], gauge[3]).data, link(gauge[3], gauge[1]).data).real 69 | plaq[5] = numpy.vdot(link(gauge[2], gauge[3]).data, link(gauge[3], gauge[2]).data).real 70 | elif MODE == "link_four": 71 | plaq[0] = numpy.einsum("tzyxaa->", link(gauge[0], gauge[1], gauge[4], gauge[5]).data).real 72 | plaq[1] = numpy.einsum("tzyxaa->", link(gauge[0], gauge[2], gauge[4], gauge[6]).data).real 73 | plaq[2] = numpy.einsum("tzyxaa->", link(gauge[1], gauge[2], gauge[5], gauge[6]).data).real 74 | plaq[3] = numpy.einsum("tzyxaa->", link(gauge[0], gauge[3], gauge[4], gauge[7]).data).real 75 | plaq[4] = numpy.einsum("tzyxaa->", link(gauge[1], gauge[3], gauge[5], gauge[7]).data).real 76 | plaq[5] = numpy.einsum("tzyxaa->", link(gauge[2], gauge[3], gauge[6], gauge[7]).data).real 77 | plaq = MPI.COMM_WORLD.allreduce(plaq, op=MPI.SUM) 78 | plaq /= math.prod(latt_size) * gauge.Nc 79 | print(plaq.mean(), plaq[:3].mean(), plaq[3:].mean()) 80 | print(f"Compute: {perf_counter() - s:.3f} s") 81 | -------------------------------------------------------------------------------- /tests/test_wflow.ini.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ; 5 | ; Test input file for chroma main program 6 | ; 7 | 8 | 9 | 10 | 11 | 12 | WILSON_FLOW 13 | 1 14 | 15 | 2 16 | 100 17 | 1.0 18 | 3 19 | 1 1 1 1 20 | 21 | 22 | default_gauge_field 23 | wflow 24 | 25 | 26 | 27 | 28 | QIO_WRITE_NAMED_OBJECT 29 | 1 30 | 31 | wflow 32 | Multi1dLatticeColorMatrix 33 | 34 | 35 | data/wflow.lime 36 | SINGLEFILE 37 | 38 | 39 | 40 | 41 | 42 | 4 4 4 8 43 | 44 | 45 | 46 | 47 | 11 48 | 11 49 | 11 50 | 0 51 | 52 | 53 | 54 | 55 | SZINQIO 56 | weak_field.lime 57 | 58 | -------------------------------------------------------------------------------- /tests/test_wflow.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | core.init(resource_path=".cache") 6 | 7 | gauge = io.readQIOGauge(weak_field) 8 | 9 | gauge_wflow = gauge.copy() 10 | energy = gauge_wflow.flowWilson(100, 1.0) 11 | 12 | gauge_chroma = io.readQIOGauge(data("wflow.lime")) 13 | print((gauge_wflow - gauge_chroma).norm2() ** 0.5) 14 | # print(energy) 15 | -------------------------------------------------------------------------------- /tests/test_wflow.scale.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field 2 | 3 | from pyquda_utils import core, io 4 | 5 | core.init(resource_path=".cache") 6 | 7 | gauge = io.readQIOGauge(weak_field) 8 | 9 | gauge_wflow = gauge.copy() 10 | t0, w0 = gauge_wflow.wilsonFlowScale(100, 0.01) 11 | 12 | print(t0, w0) 13 | -------------------------------------------------------------------------------- /tests/test_wilson.ini.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ; $Id: prec_wilson.ini.xml,v 3.2 2006-06-11 06:30:36 edwards Exp $ 5 | ; 6 | ; Test input file for chroma main program 7 | ; 8 | 9 | 10 | 11 | 12 | 13 | MAKE_SOURCE 14 | 1 15 | 16 | 6 17 | 18 | 2 19 | POINT_SOURCE 20 | 3 21 | 0 0 0 0 22 | 0 23 | 24 | 25 | 1 26 | NONE 27 | 28 | 29 | 30 | 31 | default_gauge_field 32 | pt_source_0 33 | 34 | 35 | 36 | 37 | PROPAGATOR 38 | 1 39 | 40 | 10 41 | FULL 42 | false 43 | 1 44 | 45 | WILSON 46 | 0.125 47 | 48 | true 49 | 3 50 | 2.464 51 | 0.95 52 | 53 | 54 | SIMPLE_FERMBC 55 | 1 1 1 -1 56 | 57 | 58 | 83 | 84 | BICGSTAB_INVERTER 85 | 1.0e-12 86 | 1000 87 | 88 | 89 | 90 | default_gauge_field 91 | pt_source_0 92 | pt_prop_0 93 | 94 | 95 | 96 | 97 | QIO_WRITE_NAMED_OBJECT 98 | 1 99 | 100 | pt_prop_0 101 | LatticePropagator 102 | 103 | 104 | data/pt_prop_0 105 | SINGLEFILE 106 | 107 | 108 | 109 | 110 | 4 4 4 8 111 | 112 | 113 | 114 | 115 | 11 116 | 11 117 | 11 118 | 0 119 | 120 | 121 | 122 | 123 | SZINQIO 124 | weak_field.lime 125 | 126 | -------------------------------------------------------------------------------- /tests/test_wilson.py: -------------------------------------------------------------------------------- 1 | from check_pyquda import weak_field, data 2 | 3 | from pyquda_utils import core, io 4 | 5 | xi_0, nu = 2.464, 0.95 6 | kappa = 0.125 7 | mass = 1 / (2 * kappa) - 4 8 | 9 | core.init(None, [4, 4, 4, 8], -1, xi_0 / nu, resource_path=".cache") 10 | 11 | gauge = io.readQIOGauge(weak_field) 12 | 13 | dslash = core.getDefaultDirac(mass, 1e-12, 1000) 14 | dslash.loadGauge(gauge) 15 | propagator = core.invert(dslash, "point", [0, 0, 0, 0]) 16 | dslash.destroy() 17 | 18 | propagator_chroma = io.readQIOPropagator(data("pt_prop_0")) 19 | propagator_chroma.toDevice() 20 | print((propagator - propagator_chroma).norm2() ** 0.5) 21 | -------------------------------------------------------------------------------- /tests/weak_field.ini.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | QIO_WRITE_NAMED_OBJECT 8 | 1 9 | 10 | default_gauge_field 11 | Multi1dLatticeColorMatrix 12 | 13 | 14 | weak_field.lime 15 | SINGLEFILE 16 | 17 | 18 | 19 | 20 | 4 4 4 8 21 | 22 | 23 | 24 | 25 | 11 26 | 11 27 | 11 28 | 0 29 | 30 | 31 | 32 | 33 | WEAK_FIELD 34 | dummy 35 | 36 | -------------------------------------------------------------------------------- /tests/weak_field.lime: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CLQCD/PyQUDA/3c49a81b0d79f7c4c0609b2f7bb543ff771512c8/tests/weak_field.lime --------------------------------------------------------------------------------