From f4ac2e9684a584827b524a947bf91e07c69138bf Mon Sep 17 00:00:00 2001 From: Kazuyoshi Yoshimi Date: Thu, 29 Jan 2026 20:14:51 +0900 Subject: [PATCH 01/16] Add lattice plugin architecture, stdface entry point, and documentation - Add LatticePlugin ABC and registry in lattice/__init__.py - Wrap all 10 lattice modules in plugin classes with auto-registration - Replace LATTICE_DISPATCH/BOOST_DISPATCH dicts with registry-based proxies - Update HPhi plugin to use lattice registry for boost dispatch - Add pyproject.toml with dev dependencies and entry points - Add stdface shell script entry point at project root - Add plugin tutorial (docs/tutorial_plugin.md) with solver and lattice examples - Update README.md and python/README.md with architecture, install, and usage docs - Rewrite test_lattice_dispatch.py to test plugin registry Co-Authored-By: Claude Opus 4.5 --- README.md | 94 +- docs/tutorial_plugin.md | 499 +++++++ python/README.md | 162 ++- python/history/refactoring_log.md | 80 ++ python/pyproject.toml | 39 + python/stdface/core/stdface_main.py | 732 ++++++++++ python/stdface/lattice/__init__.py | 158 ++ python/stdface/lattice/chain_lattice.py | 313 ++++ python/stdface/lattice/fc_ortho.py | 220 +++ python/stdface/lattice/honeycomb_lattice.py | 304 ++++ python/stdface/lattice/kagome.py | 318 +++++ python/stdface/lattice/ladder.py | 319 +++++ python/stdface/lattice/orthorhombic.py | 215 +++ python/stdface/lattice/pyrochlore.py | 230 +++ python/stdface/lattice/square_lattice.py | 201 +++ python/stdface/lattice/triangular_lattice.py | 222 +++ python/stdface/lattice/wannier90.py | 1350 ++++++++++++++++++ python/stdface/solvers/hphi/_plugin.py | 143 ++ stdface | 8 + test/unit/test_lattice_dispatch.py | 201 +-- 20 files changed, 5623 insertions(+), 185 deletions(-) create mode 100644 docs/tutorial_plugin.md create mode 100644 python/pyproject.toml create mode 100644 python/stdface/core/stdface_main.py create mode 100644 python/stdface/lattice/__init__.py create mode 100644 python/stdface/lattice/chain_lattice.py create mode 100644 python/stdface/lattice/fc_ortho.py create mode 100644 python/stdface/lattice/honeycomb_lattice.py create mode 100644 python/stdface/lattice/kagome.py create mode 100644 python/stdface/lattice/ladder.py create mode 100644 python/stdface/lattice/orthorhombic.py create mode 100644 python/stdface/lattice/pyrochlore.py create mode 100644 python/stdface/lattice/square_lattice.py create mode 100644 python/stdface/lattice/triangular_lattice.py create mode 100644 python/stdface/lattice/wannier90.py create mode 100644 python/stdface/solvers/hphi/_plugin.py create mode 100755 stdface diff --git a/README.md b/README.md index 8c1fa7e..282ea17 100644 --- a/README.md +++ b/README.md @@ -88,14 +88,30 @@ cmake --build build ### Python Implementation -The Python implementation requires no installation. Simply ensure Python 3.10+ and NumPy are available: +Requires Python 3.10+ and NumPy. + +**Install with pip (recommended):** ```bash -# Check Python version -python3 --version # Should be 3.10 or later +cd python +pip install -e . # runtime dependencies (numpy) are installed automatically +pip install -e ".[dev]" # also install development dependencies (pytest, pytest-cov) +``` + +After installation, the `stdface` command becomes available: + +```bash +stdface stan.in +stdface stan.in --solver mVMC +stdface -v +``` + +**Run without installing:** -# Install NumPy if needed -pip install numpy +Use the wrapper script at the project root: + +```bash +./stdface stan.in ``` ## Quick Start @@ -120,16 +136,21 @@ nelec = 4 ./hphi_dry.out stan.in ``` -**Python Implementation:** +**Python Implementation (after pip install):** ```bash -PYTHONPATH=python python3 python/__main__.py stan.in +stdface stan.in ``` -To select a solver (Python): +**Python Implementation (without installation):** ```bash -PYTHONPATH=python python3 python/__main__.py stan.in --solver mVMC -PYTHONPATH=python python3 python/__main__.py stan.in --solver UHF -PYTHONPATH=python python3 python/__main__.py stan.in --solver HWAVE +./stdface stan.in +``` + +To select a solver: +```bash +stdface stan.in --solver mVMC +stdface stan.in --solver UHF +stdface stan.in --solver HWAVE ``` 3. Input files for the target solver are generated in the current directory. @@ -140,9 +161,10 @@ Both implementations produce identical output files. The `python/` directory contains a fully-featured Python port of StdFace that produces byte-identical output to the C implementation. The Python codebase has been refactored into idiomatic Python with: -- **Modular architecture**: Organized into `lattice/` and `writer/` subpackages -- **Comprehensive testing**: 1,252 unit tests and 83 integration tests -- **Python idioms**: Enums, dict dispatch, context managers, and helper functions +- **Plugin architecture**: Solvers and lattices are self-registering plugins -- new ones can be added without modifying dispatch logic +- **Modular architecture**: Organized into `lattice/`, `solvers/`, and `writer/` subpackages +- **Comprehensive testing**: 1,268 unit tests and 83 integration tests +- **Python idioms**: Enums, ABC, context managers, type hints, and helper functions - **Full feature parity**: Supports all lattices, models, and solvers ### Python Project Structure @@ -150,25 +172,35 @@ The `python/` directory contains a fully-featured Python port of StdFace that pr ``` python/ __main__.py # CLI entry point - stdface_main.py # Main logic - stdface_vals.py # Data structures - stdface_model_util.py # Shared utilities - keyword_parser.py # Keyword parsing - param_check.py # Parameter validation - lattice/ # Lattice implementations - chain_lattice.py - square_lattice.py - honeycomb_lattice.py - kagome.py - wannier90.py - ... - writer/ # Solver-specific writers - common_writer.py - hphi_writer.py - mvmc_writer.py - ... + stdface/ + plugin.py # SolverPlugin ABC + registry + core/ + stdface_main.py # Main logic + stdface_vals.py # Data structures + keyword_parser.py # Keyword parsing + param_check.py # Parameter validation + lattice/ # Lattice plugins + __init__.py # LatticePlugin ABC + registry + chain_lattice.py # ChainPlugin + square_lattice.py # SquarePlugin + kagome.py # KagomePlugin + wannier90.py # Wannier90Plugin + ... + solvers/ # Solver plugins + hphi/_plugin.py # HPhiPlugin + mvmc/_plugin.py # MVMCPlugin + uhf/_plugin.py # UHFPlugin + hwave/_plugin.py # HWavePlugin + writer/ # Shared output writers + common_writer.py + interaction_writer.py + ... ``` +### Adding New Solvers or Lattices + +See [docs/tutorial_plugin.md](docs/tutorial_plugin.md) for a step-by-step guide with examples. + ### Running Python Tests **Unit Tests:** diff --git a/docs/tutorial_plugin.md b/docs/tutorial_plugin.md new file mode 100644 index 0000000..ae41440 --- /dev/null +++ b/docs/tutorial_plugin.md @@ -0,0 +1,499 @@ +# Plugin Tutorial: Adding New Solvers and Lattices + +StdFace uses a plugin architecture where solvers and lattices are self-registering modules. This tutorial explains how to add a new solver or lattice without modifying any existing code. + +## Table of Contents + +- [Overview](#overview) +- [Part 1: Adding a New Lattice](#part-1-adding-a-new-lattice) +- [Part 2: Adding a New Solver](#part-2-adding-a-new-solver) +- [Appendix: Existing Plugins Reference](#appendix-existing-plugins-reference) + +--- + +## Overview + +### How the Plugin System Works + +``` +User input (stan.in) + | + v +stdface_main() ---> get_lattice("chain") ---> ChainPlugin.setup(StdI) + | | + | v + | (builds geometry & interactions) + | + +---> get_plugin("HPhi") ---> HPhiPlugin.write(StdI) + | + v + (writes .def files) +``` + +1. The input file specifies `lattice = ...` and `model = ...`. +2. `stdface_main` looks up the lattice plugin by name and calls `setup(StdI)`. +3. After lattice construction, the solver plugin's `write(StdI)` generates output files. + +Both registries use **auto-registration**: when a module is imported, it creates a plugin instance and registers it. No central dispatch table needs to be edited. + +--- + +## Part 1: Adding a New Lattice + +### Example: Adding a "dice" (T3) lattice + +The dice lattice is a 2D lattice with 3 sites per unit cell. We will create a plugin for it. + +### Step 1: Create the lattice module + +Create `python/stdface/lattice/dice.py`: + +```python +""" +Standard mode for the dice (T3) lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo +(GPL v3) +""" +from __future__ import annotations + +import math +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + print_val_d, print_val_i, + not_used_j, not_used_d, not_used_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + compute_max_interactions, malloc_interactions, + add_neighbor_interaction, add_local_terms, +) +from .site_util import init_site, set_label, set_local_spin_flags, lattice_gp + + +def dice(StdI: StdIntList) -> None: + """Set up the Hamiltonian for the dice (T3) lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters (modified in-place). + """ + with lattice_gp(StdI) as fp: + + # --- (1) Geometry --- + StdI.NsiteUC = 3 # 3 sites per unit cell + + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], + StdI.length[1] * 0.5) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], + StdI.length[1] * math.sqrt(3.0) / 2.0) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + + init_site(StdI, fp, 2) + + # tau positions for 3 sites in the unit cell + StdI.tau[0, :] = [0.0, 0.0, 0.0] + StdI.tau[1, :] = [1.0 / 3.0, 1.0 / 3.0, 0.0] + StdI.tau[2, :] = [2.0 / 3.0, 2.0 / 3.0, 0.0] + + # --- (2) Hamiltonian parameters --- + print("\n @ Hamiltonian \n") + + # ... (validate and set parameters, same pattern as kagome.py) + # ... (omitted for brevity) + + # --- (3) Local spin flags --- + print("\n @ Numerical conditions\n") + set_local_spin_flags(StdI, StdI.W * StdI.L) + + # --- (4) Allocate interactions --- + ntransMax, nintrMax = compute_max_interactions(StdI, nbond=6) + malloc_interactions(StdI, ntransMax, nintrMax) + + # --- (5) Build interactions --- + for iW in range(StdI.W): + for iL in range(StdI.L): + isite = iW + iL * StdI.W + add_local_terms(StdI, isite, iL) + + # Define bonds here... + # add_neighbor_interaction(StdI, fp, iW, iL, ...) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + + +class DicePlugin(LatticePlugin): + """Plugin for the 2D dice (T3) lattice.""" + + @property + def name(self) -> str: + return "dice" + + @property + def aliases(self) -> list[str]: + return ["dice", "t3", "dicelattice"] + + @property + def ndim(self) -> int: + return 2 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the dice() function.""" + dice(StdI) + + # No boost() override needed -- default no-op is fine. + + +register_lattice(DicePlugin()) +``` + +**Key points:** +- The `LatticePlugin` subclass defines `name`, `aliases`, `ndim`, and `setup()`. +- `register_lattice()` is called at module level -- the plugin registers itself when the module is imported. +- If the lattice supports HPhi Boost mode, override `boost(self, StdI)`. + +### Step 2: Register the module for auto-discovery + +Edit `python/stdface/lattice/__init__.py` and add your module to `_discover_lattices()`: + +```python +def _discover_lattices() -> None: + """Import all lattice modules to trigger auto-registration.""" + from . import chain_lattice, square_lattice, ladder, triangular_lattice + from . import honeycomb_lattice, kagome, orthorhombic, fc_ortho, pyrochlore + from . import wannier90 + from . import dice # <-- Add this line +``` + +That's it. Now `lattice = dice` (or `lattice = t3`) will work in input files. + +### Step 3: Add tests + +Create `test/unit/test_dice.py`: + +```python +"""Unit tests for the dice lattice plugin.""" +from __future__ import annotations + +import pytest +from stdface.lattice import get_lattice, LatticePlugin + + +class TestDicePlugin: + """Tests for the dice lattice plugin.""" + + def test_registered(self): + plugin = get_lattice("dice") + assert isinstance(plugin, LatticePlugin) + + def test_name(self): + assert get_lattice("dice").name == "dice" + + def test_ndim(self): + assert get_lattice("dice").ndim == 2 + + def test_aliases(self): + p = get_lattice("dice") + assert get_lattice("t3") is p + assert get_lattice("dicelattice") is p + + def test_setup_callable(self): + assert callable(get_lattice("dice").setup) +``` + +### Step 4: Verify + +```bash +# Unit tests +python3 -m pytest test/unit/ -q + +# Integration test (if you have a reference stan.in for the dice lattice) +/path/to/StdFace/stdface stan.in +``` + +--- + +## Part 2: Adding a New Solver + +### Example: Adding a "MySolver" solver + +Suppose you want to add a new solver that writes its own output format. + +### Step 1: Create the solver package + +``` +python/stdface/solvers/mysolver/ + __init__.py + _plugin.py + writer.py # (optional) solver-specific output functions +``` + +### Step 2: Implement the plugin + +Create `python/stdface/solvers/mysolver/_plugin.py`: + +```python +"""MySolver plugin. + +Encapsulates MySolver-specific keyword parsing, field reset tables, +and Expert-mode file writing. +""" +from __future__ import annotations + +from ...plugin import SolverPlugin, register +from ...core.stdface_vals import StdIntList, NaN_i, NaN_d +from ...core.keyword_parser import ( + store_with_check_dup_i, store_with_check_dup_d, + store_with_check_dup_s, +) + + +class MySolverPlugin(SolverPlugin): + """Plugin for the MySolver backend.""" + + # --- Required abstract properties --- + + @property + def name(self) -> str: + """Canonical solver name (used in --solver CLI argument).""" + return "MySolver" + + @property + def keyword_table(self) -> dict[str, tuple]: + """Solver-specific keywords recognized in stan.in. + + Each entry maps a lowercased keyword to a tuple: + (store_function, field_name, [extra_args...]) + + For example, if MySolver accepts 'maxiter = 1000' in stan.in: + "maxiter": (store_with_check_dup_i, "MaxIter") + """ + return { + "maxiter": (store_with_check_dup_i, "MaxIter"), + "threshold": (store_with_check_dup_d, "Threshold"), + "outputfile": (store_with_check_dup_s, "OutputFile"), + } + + @property + def reset_scalars(self) -> list[tuple[str, object]]: + """Fields to reset to sentinel values at initialization. + + These are set via setattr(StdI, field_name, value) during + _reset_vals(). Use NaN_i for integers, NaN_d for floats. + """ + return [ + ("MaxIter", NaN_i), + ("Threshold", NaN_d), + ] + + @property + def reset_arrays(self) -> list[tuple[str, object]]: + """Array fields to reset (filled via arr[...] = value). + + Return an empty list if there are no array fields. + """ + return [] + + # --- Template method steps (override as needed) --- + + def write_solver_specific(self, StdI: StdIntList) -> None: + """Write MySolver-specific output files. + + This is called as part of the write() template method, + after locspn, trans, interactions, and modpara. + """ + _write_mysolver_config(StdI) + + def write_green(self, StdI: StdIntList) -> None: + """Override if MySolver needs different Green's function output. + + For example, if it only needs greenone.def: + """ + from ...writer.common_writer import print_1_green + print_1_green(StdI) + + # --- Optional lifecycle hooks --- + + def post_lattice(self, StdI: StdIntList) -> None: + """Called after lattice construction, before file writing. + + Use this for solver-specific validation or derived quantities. + """ + # Example: set default MaxIter if not specified + if StdI.MaxIter == NaN_i: + StdI.MaxIter = 500 + + +def _write_mysolver_config(StdI: StdIntList) -> None: + """Write mysolver_config.def.""" + with open("mysolver_config.def", "w") as fp: + fp.write(f"MaxIter = {StdI.MaxIter}\n") + fp.write(f"Threshold = {StdI.Threshold}\n") + if hasattr(StdI, 'OutputFile'): + fp.write(f"OutputFile = {StdI.OutputFile}\n") + + +# Auto-register on import +register(MySolverPlugin()) +``` + +### Step 3: Make it discoverable + +Create `python/stdface/solvers/mysolver/__init__.py`: + +```python +"""MySolver plugin package.""" +from . import _plugin # noqa: F401 — triggers auto-registration +``` + +Edit `python/stdface/solvers/__init__.py` to import the new package: + +```python +"""Built-in solver plugins for StdFace.""" +from __future__ import annotations + +from . import hphi, mvmc, uhf, hwave # noqa: F401 +from . import mysolver # noqa: F401 <-- Add this line +``` + +### Step 4: Add tests + +Create `test/unit/test_mysolver_plugin.py`: + +```python +"""Unit tests for the MySolver plugin.""" +from __future__ import annotations + +import pytest +from stdface.plugin import get_plugin, SolverPlugin + + +class TestMySolverPlugin: + """Tests for the MySolver plugin.""" + + def test_registered(self): + plugin = get_plugin("MySolver") + assert isinstance(plugin, SolverPlugin) + + def test_name(self): + assert get_plugin("MySolver").name == "MySolver" + + def test_keyword_table(self): + plugin = get_plugin("MySolver") + assert "maxiter" in plugin.keyword_table + assert "threshold" in plugin.keyword_table + + def test_reset_scalars(self): + plugin = get_plugin("MySolver") + names = [name for name, _ in plugin.reset_scalars] + assert "MaxIter" in names + assert "Threshold" in names +``` + +### Step 5: Use it + +```bash +./stdface stan.in --solver MySolver +``` + +--- + +## Plugin API Reference + +### LatticePlugin (lattice/__init__.py) + +| Member | Type | Required | Description | +|--------|------|----------|-------------| +| `name` | `str` (property) | Yes | Canonical name (e.g. `"chain"`) | +| `aliases` | `list[str]` (property) | Yes | All recognized aliases | +| `ndim` | `int` (property) | Yes | Spatial dimensions (1, 2, or 3) | +| `setup(StdI)` | method | Yes | Build lattice geometry and interactions | +| `boost(StdI)` | method | No | HPhi Boost mode (default: no-op) | + +Registration: call `register_lattice(MyPlugin())` at module level. + +### SolverPlugin (plugin.py) + +| Member | Type | Required | Description | +|--------|------|----------|-------------| +| `name` | `str` (property) | Yes | Canonical name (e.g. `"HPhi"`) | +| `keyword_table` | `dict` (property) | Yes | Input keyword dispatch table | +| `reset_scalars` | `list[tuple]` (property) | Yes | Scalar field reset table | +| `reset_arrays` | `list[tuple]` (property) | Yes | Array field reset table | +| `write(StdI)` | method | No | Template method (calls steps below) | +| `write_locspn(StdI)` | method | No | Write locspn.def | +| `write_trans(StdI)` | method | No | Write trans.def | +| `write_interactions(StdI)` | method | No | Write interaction files | +| `check_and_write_modpara(StdI)` | method | No | Write modpara.def | +| `write_solver_specific(StdI)` | method | No | Solver-specific files | +| `write_green(StdI)` | method | No | Write Green's function files | +| `write_namelist(StdI)` | method | No | Write namelist.def | +| `post_lattice(StdI)` | method | No | Hook after lattice construction | + +Registration: call `register(MyPlugin())` at module level. + +### Template Method Flow (SolverPlugin.write) + +``` +write() + |-- write_locspn() # locspn.def + |-- write_trans() # trans.def + |-- write_interactions() # coulombinter.def, etc. + |-- check_and_write_modpara() # modpara.def + |-- write_solver_specific() # solver-specific files + |-- check_output_mode() # validate output settings + |-- write_green() # greenone.def, greentwo.def + |-- write_namelist() # namelist.def +``` + +Override individual steps to customize output. Override `write()` entirely for a completely different output sequence. + +--- + +## Appendix: Existing Plugins Reference + +### Lattice Plugins + +| Plugin Class | Module | name | aliases | ndim | +|---|---|---|---|---| +| `ChainPlugin` | `chain_lattice.py` | `chain` | chain, chainlattice | 1 | +| `LadderPlugin` | `ladder.py` | `ladder` | ladder, ladderlattice | 1 | +| `SquarePlugin` | `square_lattice.py` | `tetragonal` | tetragonal, tetragonallattice, square, squarelattice | 2 | +| `TriangularPlugin` | `triangular_lattice.py` | `triangular` | triangular, triangularlattice | 2 | +| `HoneycombPlugin` | `honeycomb_lattice.py` | `honeycomb` | honeycomb, honeycomblattice | 2 | +| `KagomePlugin` | `kagome.py` | `kagome` | kagome, kagomelattice | 2 | +| `OrthorhombicPlugin` | `orthorhombic.py` | `orthorhombic` | orthorhombic, simpleorthorhombic, cubic, simplecubic | 3 | +| `FCOrthoPlugin` | `fc_ortho.py` | `fco` | face-centeredorthorhombic, fcorthorhombic, fco, face-centeredcubic, fccubic, fcc | 3 | +| `PyrochlorePlugin` | `pyrochlore.py` | `pyrochlore` | pyrochlore | 3 | +| `Wannier90Plugin` | `wannier90.py` | `wannier90` | wannier90 | 3 | + +Lattices with Boost support: chain, honeycomb, kagome, ladder. + +### Solver Plugins + +| Plugin Class | Module | name | +|---|---|---| +| `HPhiPlugin` | `solvers/hphi/_plugin.py` | `HPhi` | +| `MVMCPlugin` | `solvers/mvmc/_plugin.py` | `mVMC` | +| `UHFPlugin` | `solvers/uhf/_plugin.py` | `UHF` | +| `HWavePlugin` | `solvers/hwave/_plugin.py` | `HWAVE` | diff --git a/python/README.md b/python/README.md index 1b53cf1..a54a05f 100644 --- a/python/README.md +++ b/python/README.md @@ -9,45 +9,83 @@ for HPhi, mVMC, UHF, and H-wave. - Python 3.10 or later - NumPy +## Installation + +```bash +cd python +pip install -e . # install with runtime dependencies (numpy) +pip install -e ".[dev]" # also install dev dependencies (pytest, pytest-cov) +``` + +After installation, the `stdface` command becomes available: + +```bash +stdface stan.in +stdface stan.in --solver mVMC +stdface -v +``` + +The editable install (`-e`) is recommended for development. Source changes take effect immediately without reinstalling. + +## Architecture + +StdFace uses a **plugin architecture** for both solvers and lattices. +New solvers and lattices can be added without modifying any existing dispatch logic. + +### Plugin System Overview + +``` +stdface/ + plugin.py # SolverPlugin ABC + solver registry + lattice/__init__.py # LatticePlugin ABC + lattice registry +``` + +- **SolverPlugin** (`plugin.py`): Defines how a solver parses keywords, resets fields, and writes output files. Each solver (HPhi, mVMC, UHF, H-wave) is a plugin registered at import time. +- **LatticePlugin** (`lattice/__init__.py`): Defines lattice geometry, aliases, and the setup/boost methods. Each lattice (chain, square, kagome, etc.) is a plugin registered at import time. + +See [docs/tutorial_plugin.md](../docs/tutorial_plugin.md) for a step-by-step guide on adding new solvers and lattices. + ## Directory Structure ``` python/ __main__.py # CLI entry point (port of dry.c) - stdface_main.py # Main logic (port of StdFace_main.c) - stdface_vals.py # StdIntList dataclass (port of StdFace_vals.h) - stdface_model_util.py # Shared utilities (port of StdFace_ModelUtil.c) - version.py # Version information - keyword_parser.py # Keyword parsing subsystem - param_check.py # Parameter validation utilities + stdface/ + plugin.py # SolverPlugin ABC + solver registry + core/ + stdface_main.py # Main logic (port of StdFace_main.c) + stdface_vals.py # StdIntList dataclass (port of StdFace_vals.h) + keyword_parser.py # Keyword parsing subsystem + param_check.py # Parameter validation utilities + lattice/ # Lattice plugins + __init__.py # LatticePlugin ABC + lattice registry + chain_lattice.py # 1D chain (ChainPlugin) + square_lattice.py # 2D square (SquarePlugin) + ladder.py # 2-leg ladder (LadderPlugin) + triangular_lattice.py # 2D triangular (TriangularPlugin) + honeycomb_lattice.py # 2D honeycomb (HoneycombPlugin) + kagome.py # 2D kagome (KagomePlugin) + orthorhombic.py # 3D orthorhombic (OrthorhombicPlugin) + fc_ortho.py # 3D face-centered orthorhombic (FCOrthoPlugin) + pyrochlore.py # 3D pyrochlore (PyrochlorePlugin) + wannier90.py # Wannier90 interface (Wannier90Plugin) + boost_output.py # Boost output utilities + geometry_output.py # Geometry output functions + input_params.py # Input parameter resolution + interaction_builder.py # Interaction building utilities + site_util.py # Site utility functions + solvers/ # Solver plugins + __init__.py # Auto-imports all solver plugins + hphi/_plugin.py # HPhi plugin (HPhiPlugin) + mvmc/_plugin.py # mVMC plugin (MVMCPlugin) + uhf/_plugin.py # UHF plugin (UHFPlugin) + hwave/_plugin.py # H-wave plugin (HWavePlugin) + writer/ # Output writers (shared) + common_writer.py # Common output functions + interaction_writer.py # Interaction file writer + export_wannier90.py # Wannier90 format export history/ refactoring_log.md # Refactoring change log - lattice/ # Lattice implementations - __init__.py - chain_lattice.py # 1D chain lattice - square_lattice.py # 2D square lattice - ladder.py # 2-leg ladder lattice - triangular_lattice.py # 2D triangular lattice - honeycomb_lattice.py # 2D honeycomb lattice - kagome.py # 2D kagome lattice - orthorhombic.py # 3D orthorhombic lattice - fc_ortho.py # Face-centered orthorhombic lattice - pyrochlore.py # Pyrochlore lattice - wannier90.py # Wannier90 input reader - boost_output.py # Boost output utilities - geometry_output.py # Geometry output functions - input_params.py # Input parameter resolution - interaction_builder.py # Interaction building utilities - site_util.py # Site utility functions - writer/ # Solver-specific writers - __init__.py - common_writer.py # Common output functions - hphi_writer.py # HPhi-specific writer - mvmc_writer.py # mVMC-specific writer - mvmc_variational.py # mVMC variational functions - interaction_writer.py # Interaction file writer - solver_writer.py # Solver writer base classes - export_wannier90.py # Wannier90 format export ``` ## Usage @@ -57,15 +95,15 @@ python/ From the project root: ```bash -PYTHONPATH=python python3 python/__main__.py stan.in +./stdface stan.in ``` To select a solver: ```bash -PYTHONPATH=python python3 python/__main__.py stan.in --solver mVMC -PYTHONPATH=python python3 python/__main__.py stan.in --solver UHF -PYTHONPATH=python python3 python/__main__.py stan.in --solver HWAVE +./stdface stan.in --solver mVMC +./stdface stan.in --solver UHF +./stdface stan.in --solver HWAVE ``` The default solver is HPhi. @@ -73,7 +111,7 @@ The default solver is HPhi. ### Print Version ```bash -PYTHONPATH=python python3 python/__main__.py -v +./stdface -v ``` ### Calling from Python @@ -82,7 +120,7 @@ PYTHONPATH=python python3 python/__main__.py -v import sys sys.path.insert(0, "python") -from stdface_main import stdface_main +from stdface.core.stdface_main import stdface_main stdface_main("stan.in", solver="HPhi") ``` @@ -142,8 +180,8 @@ python3 -m pytest test/unit/ --cov=python --cov-report=html ``` **Test Coverage:** -- 1,252 unit tests covering all modules -- Tests for lattice implementations, writers, parsers, and utilities +- 1,268 unit tests covering all modules +- Tests for lattice implementations, writers, parsers, plugin registries, and utilities - All tests maintain byte-identical output with C version ### Integration Tests @@ -158,8 +196,7 @@ bash test/run_all_integration.sh # Copy test input to a working directory and run cp test/hphi/lanczos_hubbard_square/stan.in /tmp/work/ cd /tmp/work -PYTHONPATH=/path/to/StdFace/python python3 -c \ - "from stdface_main import stdface_main; stdface_main('stan.in', solver='HPhi')" +/path/to/StdFace/stdface stan.in # Compare outputs against reference diff /path/to/StdFace/test/hphi/lanczos_hubbard_square/ref/modpara.def modpara.def @@ -174,34 +211,35 @@ diff /path/to/StdFace/test/hphi/lanczos_hubbard_square/ref/modpara.def modpara.d The Python implementation has been refactored from a direct C translation into idiomatic Python: -- **Modular design**: Code organized into logical subpackages (`lattice/`, `writer/`) -- **Python idioms**: Enums, dict dispatch, context managers, type hints +- **Plugin architecture**: Solvers and lattices are self-registering plugins +- **Modular design**: Code organized into logical subpackages (`lattice/`, `solvers/`, `writer/`) +- **Python idioms**: Enums, ABC, context managers, type hints - **Reduced duplication**: Helper functions extracted to eliminate code duplication -- **Comprehensive testing**: 1,252 unit tests with full coverage +- **Comprehensive testing**: 1,268 unit tests with full coverage - **Documentation**: NumPy-style docstrings throughout -See `history/refactoring_log.md` for detailed refactoring history (Steps 1-77). +See `history/refactoring_log.md` for detailed refactoring history. ## Mapping to C Sources -| C Source File | Python File | +| C Source File | Python Module | |---|---| | `dry.c` | `__main__.py` | -| `StdFace_main.c` | `stdface_main.py` (refactored into multiple modules) | -| `StdFace_vals.h` | `stdface_vals.py` | -| `StdFace_ModelUtil.c/h` | `stdface_model_util.py` | -| `version.h` | `version.py` | -| `ChainLattice.c` | `lattice/chain_lattice.py` | -| `SquareLattice.c` | `lattice/square_lattice.py` | -| `Ladder.c` | `lattice/ladder.py` | -| `TriangularLattice.c` | `lattice/triangular_lattice.py` | -| `HoneycombLattice.c` | `lattice/honeycomb_lattice.py` | -| `Kagome.c` | `lattice/kagome.py` | -| `Orthorhombic.c` | `lattice/orthorhombic.py` | -| `FCOrtho.c` | `lattice/fc_ortho.py` | -| `Pyrochlore.c` | `lattice/pyrochlore.py` | -| `Wannier90.c` | `lattice/wannier90.py` | -| `export_wannier90.c` | `writer/export_wannier90.py` | +| `StdFace_main.c` | `stdface/core/stdface_main.py` | +| `StdFace_vals.h` | `stdface/core/stdface_vals.py` | +| `StdFace_ModelUtil.c/h` | `stdface/core/stdface_model_util.py` | +| `version.h` | `stdface/version.py` | +| `ChainLattice.c` | `stdface/lattice/chain_lattice.py` | +| `SquareLattice.c` | `stdface/lattice/square_lattice.py` | +| `Ladder.c` | `stdface/lattice/ladder.py` | +| `TriangularLattice.c` | `stdface/lattice/triangular_lattice.py` | +| `HoneycombLattice.c` | `stdface/lattice/honeycomb_lattice.py` | +| `Kagome.c` | `stdface/lattice/kagome.py` | +| `Orthorhombic.c` | `stdface/lattice/orthorhombic.py` | +| `FCOrtho.c` | `stdface/lattice/fc_ortho.py` | +| `Pyrochlore.c` | `stdface/lattice/pyrochlore.py` | +| `Wannier90.c` | `stdface/lattice/wannier90.py` | +| `export_wannier90.c` | `stdface/writer/export_wannier90.py` | Note: The Python implementation has been significantly refactored beyond the original C structure for better maintainability and Pythonic code style. diff --git a/python/history/refactoring_log.md b/python/history/refactoring_log.md index d61a827..1913e19 100644 --- a/python/history/refactoring_log.md +++ b/python/history/refactoring_log.md @@ -8113,3 +8113,83 @@ Each function body is now 2-3 lines shorter. **Tests**: - Unit: 1260 passed - Integration: 83/83 passed + +## Step 189 — 2026-01-29 + +**Files**: Multiple — major restructuring +**Change**: Implemented Solver Plugin Architecture: +1. Restructured `python/` into `python/stdface/` package with `core/`, `lattice/`, `writer/`, `solvers/` subpackages +2. Created `pyproject.toml` for `pip install -e .` support +3. Defined `SolverPlugin` ABC and plugin registry in `stdface/plugin.py` +4. Extracted 4 solver plugins: `solvers/hphi.py`, `solvers/mvmc.py`, `solvers/uhf.py`, `solvers/hwave.py` +5. Each plugin encapsulates solver-specific: keyword table, reset tables, write method, post_lattice hook +6. Refactored `stdface_main.py` to delegate to plugins for field resets, keyword parsing, post-lattice, and writing +7. Backward-compatible shim modules at old `python/` locations for existing imports +8. Added entry_points support in pyproject.toml for future external plugins +**Phase**: 4 — Architecture (plugin system) + +**Tests**: +- Unit: 1260 passed +- Integration: verified HPhi solver produces correct output + +## Step 190 — 2026-01-29 + +**Files**: `stdface/writer/` → `stdface/solvers/` +**Change**: Moved solver-specific writer modules into solver plugin directory: +- `writer/hphi_writer.py` → `solvers/hphi_writer.py` +- `writer/mvmc_writer.py` → `solvers/mvmc_writer.py` +- `writer/mvmc_variational.py` → `solvers/mvmc_variational.py` +- `writer/export_wannier90.py` → `solvers/export_wannier90.py` +- `writer/solver_writer.py` replaced with legacy wrapper delegating to plugins +- `writer/` retains only shared code: `common_writer.py`, `interaction_writer.py` +- Plugin files now import writer helpers from sibling modules within `solvers/` +- Backward-compatible shims use `sys.modules` aliasing for monkeypatch support +**Phase**: 4 — Architecture (plugin system) + +**Tests**: +- Unit: 1260 passed +- Integration: 83/83 passed + +## Step 191 — 2026-01-29 + +**Files**: `python/`, `test/unit/` +**Change**: Removed all backward-compatibility shim modules: +- Deleted top-level shims: `stdface_vals.py`, `param_check.py`, `keyword_parser.py`, `stdface_main.py`, `stdface_model_util.py`, `version.py` +- Deleted old `lattice/` and `writer/` directories (contained only shims) +- Deleted `writer/solver_writer.py`, `writer/hphi_writer.py`, etc. shims from `stdface/writer/` +- Updated all test imports to use new `stdface.*` paths directly +- Retained only `python/__main__.py` as entry point for integration tests +- Rewrote `test_solver_writer.py` to test plugin system instead of legacy Writer classes +**Phase**: 4 — Architecture (cleanup) + +**Tests**: +- Unit: 1260 passed +- Integration: 83/83 passed + +--- + +### Step 189: Lattice plugin architecture (2026-01-29) + +**Files changed**: +- `python/stdface/lattice/__init__.py` — added `LatticePlugin` ABC, registry (`register_lattice`, `get_lattice`, `get_all_lattices`) +- `python/stdface/lattice/chain_lattice.py` — added `ChainPlugin` (auto-registered) +- `python/stdface/lattice/square_lattice.py` — added `SquarePlugin` +- `python/stdface/lattice/triangular_lattice.py` — added `TriangularPlugin` +- `python/stdface/lattice/honeycomb_lattice.py` — added `HoneycombPlugin` (with boost) +- `python/stdface/lattice/kagome.py` — added `KagomePlugin` (with boost) +- `python/stdface/lattice/ladder.py` — added `LadderPlugin` (with boost) +- `python/stdface/lattice/orthorhombic.py` — added `OrthorhombicPlugin` +- `python/stdface/lattice/fc_ortho.py` — added `FCOrthoPlugin` +- `python/stdface/lattice/pyrochlore.py` — added `PyrochlorePlugin` +- `python/stdface/lattice/wannier90.py` — added `Wannier90Plugin` +- `python/stdface/core/stdface_main.py` — replaced `LATTICE_DISPATCH`/`BOOST_DISPATCH` dicts with proxy objects over the plugin registry; `_build_lattice_and_boost` now uses `get_lattice()` directly +- `python/stdface/solvers/hphi/_plugin.py` — `post_lattice` uses `get_lattice().boost()` instead of `BOOST_DISPATCH` +- `test/unit/test_lattice_dispatch.py` — rewritten to test plugin registry + proxies + +**Why**: Apply the same plugin/template pattern used for solvers to lattices, enabling new lattices to be added without modifying dispatch tables in `stdface_main.py`. + +**Phase**: 4 — Architecture (lattice plugins) + +**Tests**: +- Unit: 1268 passed +- Integration: pending diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..db50111 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] +requires = ["setuptools>=64"] +build-backend = "setuptools.build_meta" + +[project] +name = "stdface" +version = "0.5.0" +description = "Standard-mode input generator for HPhi / mVMC / UHF / H-wave" +readme = "README.md" +license = "GPL-3.0-or-later" +requires-python = ">=3.10" +dependencies = ["numpy"] + +[project.optional-dependencies] +dev = ["pytest", "pytest-cov"] + +[project.scripts] +stdface = "stdface.__main__:main" + +[project.entry-points."stdface.solvers"] +hphi = "stdface.solvers.hphi:HPhiPlugin" +mvmc = "stdface.solvers.mvmc:MVMCPlugin" +uhf = "stdface.solvers.uhf:UHFPlugin" +hwave = "stdface.solvers.hwave:HWavePlugin" + +[project.entry-points."stdface.lattices"] +chain = "stdface.lattice.chain_lattice:ChainPlugin" +square = "stdface.lattice.square_lattice:SquarePlugin" +triangular = "stdface.lattice.triangular_lattice:TriangularPlugin" +honeycomb = "stdface.lattice.honeycomb_lattice:HoneycombPlugin" +kagome = "stdface.lattice.kagome:KagomePlugin" +ladder = "stdface.lattice.ladder:LadderPlugin" +orthorhombic = "stdface.lattice.orthorhombic:OrthorhombicPlugin" +fc_ortho = "stdface.lattice.fc_ortho:FCOrthoPlugin" +pyrochlore = "stdface.lattice.pyrochlore:PyrochlorePlugin" +wannier90 = "stdface.lattice.wannier90:Wannier90Plugin" + +[tool.setuptools.packages.find] +include = ["stdface*"] diff --git a/python/stdface/core/stdface_main.py b/python/stdface/core/stdface_main.py new file mode 100644 index 0000000..6d43fee --- /dev/null +++ b/python/stdface/core/stdface_main.py @@ -0,0 +1,732 @@ +""" +Read input file and write files for Expert mode; initialize variables; check parameters. + +This module is the Python translation of ``StdFace_main.c``. It provides the +top-level entry point for the Standard-mode input generator used by HPhi, mVMC, +UHF, and H-wave. + +The following lattices are supported: + +- 1D Chain +- 1D Ladder +- 2D Tetragonal +- 2D Triangular +- 2D Honeycomb +- 2D Kagome +- 3D Simple Orthorhombic +- 3D Face Centered Orthorhombic +- 3D Pyrochlore + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +from collections.abc import Callable +from typing import NamedTuple + +from .stdface_vals import ( + StdIntList, ModelType, SolverType, MethodType, + NaN_i, NaN_d, NaN_c, UNSET_STRING, +) +from .param_check import exit_program +from ..solvers.hphi.writer import ( + vector_potential as _vector_potential, +) +from ..writer.common_writer import ( + unsupported_system as _unsupported_system, +) +from .keyword_parser import ( + trim_space_quote as _trim_space_quote, + parse_common_keyword as _parse_common_keyword, + parse_solver_keyword as _parse_solver_keyword, + _apply_keyword_table, +) +from ..lattice import get_lattice as _get_lattice + +# --------------------------------------------------------------------------- +# Lattice dispatch (via plugin registry) +# --------------------------------------------------------------------------- +# Backward-compatible dict-like objects that delegate to the lattice plugin +# registry. New code should use ``get_lattice(name).setup(StdI)`` and +# ``get_lattice(name).boost(StdI)`` directly. + + +class _LatticeDispatchProxy: + """Dict-like proxy over the lattice plugin registry (setup methods).""" + + def __getitem__(self, key: str) -> Callable[[StdIntList], None]: + return _get_lattice(key).setup + + def get(self, key: str, default=None): + try: + return self[key] + except KeyError: + return default + + def __contains__(self, key: str) -> bool: + try: + _get_lattice(key) + return True + except KeyError: + return False + + def items(self): + from ..lattice import get_all_lattices + result = [] + for plugin in get_all_lattices(): + for alias in plugin.aliases: + result.append((alias, plugin.setup)) + return result + + +class _BoostDispatchProxy: + """Dict-like proxy over the lattice plugin registry (boost methods).""" + + # Only lattices whose boost() is overridden (not the ABC default) + _BOOST_LATTICES = {"chain", "chainlattice", "honeycomb", "honeycomblattice", + "kagome", "kagomelattice", "ladder", "ladderlattice"} + + def __getitem__(self, key: str) -> Callable[[StdIntList], None]: + if key not in self._BOOST_LATTICES: + raise KeyError(key) + return _get_lattice(key).boost + + def get(self, key: str, default=None): + try: + return self[key] + except KeyError: + return default + + def __contains__(self, key: str) -> bool: + return key in self._BOOST_LATTICES + + def items(self): + result = [] + seen = set() + for alias in self._BOOST_LATTICES: + try: + plugin = _get_lattice(alias) + if id(plugin) not in seen: + seen.add(id(plugin)) + for a in plugin.aliases: + if a in self._BOOST_LATTICES: + result.append((a, plugin.boost)) + except KeyError: + pass + return result + + +LATTICE_DISPATCH = _LatticeDispatchProxy() +"""Dict-like proxy that maps lattice aliases to setup functions via the plugin registry.""" + +BOOST_DISPATCH = _BoostDispatchProxy() +"""Dict-like proxy that maps lattice aliases to boost functions via the plugin registry.""" + +# --------------------------------------------------------------------------- +# Model name normalisation table +# --------------------------------------------------------------------------- + + +class _ModelConfig(NamedTuple): + """Configuration resolved from a user-facing model name alias. + + Attributes + ---------- + model : ModelType + Canonical model type (HUBBARD, SPIN, or KONDO). + lGC : int + Grand-canonical flag (0 = canonical, 1 = grand-canonical). + lBoost : int + Boost extension flag (0 = off, 1 = on, HPhi only). + """ + + model: ModelType + lGC: int + lBoost: int + + +# Maps user-facing model name aliases to a ``_ModelConfig``. +# Entries that require ``solver == SolverType.HPhi`` are in a separate dict. +MODEL_ALIASES: dict[str, _ModelConfig] = { + "fermionhubbard": _ModelConfig(ModelType.HUBBARD, 0, 0), + "hubbard": _ModelConfig(ModelType.HUBBARD, 0, 0), + "fermionhubbardgc": _ModelConfig(ModelType.HUBBARD, 1, 0), + "hubbardgc": _ModelConfig(ModelType.HUBBARD, 1, 0), + "spin": _ModelConfig(ModelType.SPIN, 0, 0), + "spingc": _ModelConfig(ModelType.SPIN, 1, 0), + "kondolattice": _ModelConfig(ModelType.KONDO, 0, 0), + "kondo": _ModelConfig(ModelType.KONDO, 0, 0), + "kondolatticegc": _ModelConfig(ModelType.KONDO, 1, 0), + "kondogc": _ModelConfig(ModelType.KONDO, 1, 0), +} +"""Maps model name aliases to a :class:`_ModelConfig`.""" + +MODEL_ALIASES_HPHI_BOOST: dict[str, _ModelConfig] = { + "spingcboost": _ModelConfig(ModelType.SPIN, 1, 1), + "spingccma": _ModelConfig(ModelType.SPIN, 1, 1), +} +"""HPhi-only model aliases that enable the Boost extension.""" + +# --------------------------------------------------------------------------- +# Method name normalisation table (HPhi only) +# --------------------------------------------------------------------------- +METHOD_ALIASES: dict[str, MethodType] = { + "direct": MethodType.FULLDIAG, + "alldiag": MethodType.FULLDIAG, + "te": MethodType.TIME_EVOLUTION, + "time-evolution": MethodType.TIME_EVOLUTION, +} +"""Maps HPhi method name aliases to their canonical forms.""" + +# Sentinel constants imported from stdface_vals: +# NaN_i, NaN_d, NaN_c, UNSET_STRING + + +# =================================================================== +# Solver-specific field reset tables +# =================================================================== + +# Each solver maps to a list of (field_name, value) pairs for scalar +# assignments, plus a list of (field_name, value) pairs for array-fill +# assignments (``getattr(StdI, name)[:] = value`` or ``[:, :] = value``). +# +# UHF and HWAVE share a common base; HWAVE adds two extra fields. + +# =================================================================== +# Common (solver-independent) field reset tables +# =================================================================== +# +# _COMMON_RESET_SCALARS lists (field_name, sentinel_value) pairs for +# scalar fields that are reset to NaN_d, NaN_i, or NaN_c by every +# call to _reset_vals(). These replace ~80 individual ``StdI.x = val`` +# assignments. + +_COMMON_RESET_SCALARS: list[tuple[str, object]] = [ + # Lattice scalars + ("a", NaN_d), + # Magnetic field + ("Gamma", NaN_d), + ("Gamma_y", NaN_d), + ("h", NaN_d), + # Lattice dimensions + ("Height", NaN_i), + ("L", NaN_i), + ("W", NaN_i), + # Isotropic scalar spin couplings + ("JAll", NaN_d), + ("JpAll", NaN_d), + ("JppAll", NaN_d), + ("J0All", NaN_d), + ("J0pAll", NaN_d), + ("J0ppAll", NaN_d), + ("J1All", NaN_d), + ("J1pAll", NaN_d), + ("J1ppAll", NaN_d), + ("J2All", NaN_d), + ("J2pAll", NaN_d), + ("J2ppAll", NaN_d), + # Anisotropy / single-ion + ("K", NaN_d), + # Chemical potential / spin + ("mu", NaN_d), + ("S2", NaN_i), + # Hopping parameters (complex) + ("t", NaN_c), + ("tp", NaN_c), + ("tpp", NaN_c), + ("t0", NaN_c), + ("t0p", NaN_c), + ("t0pp", NaN_c), + ("t1", NaN_c), + ("t1p", NaN_c), + ("t1pp", NaN_c), + ("t2", NaN_c), + ("t2p", NaN_c), + ("t2pp", NaN_c), + # Coulomb parameters + ("U", NaN_d), + ("V", NaN_d), + ("Vp", NaN_d), + ("Vpp", NaN_d), + ("V0", NaN_d), + ("V0p", NaN_d), + ("V0pp", NaN_d), + ("V1", NaN_d), + ("V1p", NaN_d), + ("V1pp", NaN_d), + ("V2", NaN_d), + ("V2p", NaN_d), + ("V2pp", NaN_d), + # Calculation conditions + ("ncond", NaN_i), + ("Sz2", NaN_i), + # Wannier90 cutoffs + ("cutoff_t", NaN_d), + ("cutoff_u", NaN_d), + ("cutoff_j", NaN_d), + ("cutoff_length_t", NaN_d), + ("cutoff_length_U", NaN_d), + ("cutoff_length_J", NaN_d), + ("lambda_", NaN_d), + ("lambda_U", NaN_d), + ("lambda_J", NaN_d), + ("alpha", NaN_d), +] +"""Common scalar field resets — ``setattr(StdI, name, value)``.""" + + +_COMMON_RESET_ARRAYS: list[tuple[str, object]] = [ + # Lattice vectors + ("length", NaN_d), + ("box", NaN_i), + ("direct", NaN_d), + # 3x3 spin coupling matrices + ("J", NaN_d), + ("Jp", NaN_d), + ("Jpp", NaN_d), + ("J0", NaN_d), + ("J0p", NaN_d), + ("J0pp", NaN_d), + ("J1", NaN_d), + ("J1p", NaN_d), + ("J1pp", NaN_d), + ("J2", NaN_d), + ("J2p", NaN_d), + ("J2pp", NaN_d), + # Phase / boundary + ("phase", NaN_d), + # Wannier90 cutoff vectors + ("cutoff_tR", NaN_i), + ("cutoff_UR", NaN_i), + ("cutoff_JR", NaN_i), + ("cutoff_tVec", NaN_d), + ("cutoff_UVec", NaN_d), + ("cutoff_JVec", NaN_d), +] +"""Common array-fill field resets — ``getattr(StdI, name)[...] = value``.""" + + +_UHF_BASE_SCALARS: list[tuple[str, object]] = [ + ("NMPTrans", NaN_i), + ("RndSeed", NaN_i), + ("mix", NaN_d), + ("eps", NaN_i), + ("eps_slater", NaN_i), + ("Iteration_max", NaN_i), + ("Hsub", NaN_i), + ("Lsub", NaN_i), + ("Wsub", NaN_i), +] + +_UHF_BASE_ARRAYS: list[tuple[str, object]] = [ + ("boxsub", NaN_i), +] + +_SOLVER_RESET_SCALARS: dict[SolverType, list[tuple[str, object]]] = { + SolverType.HPhi: [ + ("LargeValue", NaN_d), + ("OmegaMax", NaN_d), + ("OmegaMin", NaN_d), + ("OmegaOrg", NaN_d), + ("OmegaIm", NaN_d), + ("Nomega", NaN_i), + ("FlgTemp", 1), + ("Lanczos_max", NaN_i), + ("initial_iv", NaN_i), + ("nvec", NaN_i), + ("exct", NaN_i), + ("LanczosEps", NaN_i), + ("LanczosTarget", NaN_i), + ("NumAve", NaN_i), + ("ExpecInterval", NaN_i), + ("dt", NaN_d), + ("tdump", NaN_d), + ("tshift", NaN_d), + ("freq", NaN_d), + ("Uquench", NaN_d), + ("ExpandCoef", NaN_i), + ("NGPU", NaN_i), + ("Scalapack", NaN_i), + ], + SolverType.mVMC: [ + ("NVMCCalMode", NaN_i), + ("NLanczosMode", NaN_i), + ("NDataIdxStart", NaN_i), + ("NDataQtySmp", NaN_i), + ("NSPGaussLeg", NaN_i), + ("NSPStot", NaN_i), + ("NMPTrans", NaN_i), + ("NSROptItrStep", NaN_i), + ("NSROptItrSmp", NaN_i), + ("DSROptRedCut", NaN_d), + ("DSROptStaDel", NaN_d), + ("DSROptStepDt", NaN_d), + ("NVMCWarmUp", NaN_i), + ("NVMCInterval", NaN_i), + ("NVMCSample", NaN_i), + ("NExUpdatePath", NaN_i), + ("RndSeed", NaN_i), + ("NSplitSize", NaN_i), + ("NStore", NaN_i), + ("NSRCG", NaN_i), + ("ComplexType", NaN_i), + ("Hsub", NaN_i), + ("Lsub", NaN_i), + ("Wsub", NaN_i), + ], + SolverType.UHF: _UHF_BASE_SCALARS, + SolverType.HWAVE: _UHF_BASE_SCALARS + [ + ("export_all", NaN_i), + ("lattice_gp", NaN_i), + ], +} +"""Scalar field resets for each solver — ``setattr(StdI, name, value)``.""" + +_SOLVER_RESET_ARRAYS: dict[SolverType, list[tuple[str, object]]] = { + SolverType.HPhi: [ + ("SpectrumQ", NaN_d), + ("VecPot", NaN_d), + ], + SolverType.mVMC: [ + ("boxsub", NaN_i), + ], + SolverType.UHF: _UHF_BASE_ARRAYS, + SolverType.HWAVE: _UHF_BASE_ARRAYS, +} +"""Array-fill field resets for each solver — ``getattr(StdI, name)[...] = value``.""" + + +def _apply_field_resets(StdI: StdIntList, solver: SolverType) -> None: + """Apply solver-specific field resets from the plugin registry. + + Parameters + ---------- + StdI : StdIntList + The parameter structure to reset (modified in place). + solver : SolverType + The solver whose field-reset tables to apply. + """ + from ..plugin import get_plugin + try: + plugin = get_plugin(solver) + except KeyError: + return + for name, value in plugin.reset_scalars: + setattr(StdI, name, value) + for name, value in plugin.reset_arrays: + arr = getattr(StdI, name) + arr[...] = value + + +# =================================================================== +# _reset_vals +# =================================================================== + + +def _reset_vals(StdI: StdIntList) -> None: + """Clear / initialize every field in *StdI* to its sentinel value. + + This is the Python translation of the C function + ``StdFace_ResetVals()``. Fields that have not been specified by + the user are filled with NaN sentinels so that duplicate-input + detection and default-value assignment work correctly later. + + The common (solver-independent) fields are driven by the + ``_COMMON_RESET_SCALARS`` and ``_COMMON_RESET_ARRAYS`` tables. + Solver-specific fields are handled by ``_apply_field_resets()``. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure whose fields are reset **in + place**. + """ + # --- Common scalar fields (table-driven) -------------------------------- + for name, value in _COMMON_RESET_SCALARS: + setattr(StdI, name, value) + + # --- Common array fields (table-driven) --------------------------------- + for name, value in _COMMON_RESET_ARRAYS: + getattr(StdI, name)[...] = value + + # --- D matrix: zero everywhere except D[2][2] = NaN_d ------------------- + StdI.D[:, :] = 0.0 + StdI.D[2, 2] = NaN_d + + # --- Solver-specific fields (table-driven) ------------------------------ + _apply_field_resets(StdI, StdI.solver) + + # --- Boost (always zero, not NaN) --------------------------------------- + StdI.lBoost = 0 + + +# Keyword parsing helpers (_trim_space_quote, +# _store_with_check_dup_s/sl/i/d/c) and keyword parsers +# (_parse_common_keyword, _parse_solver_keyword) +# have been moved to keyword_parser.py + + +# =================================================================== +# Model / method resolution +# =================================================================== + + +def _resolve_model_and_method(StdI: StdIntList, solver: str) -> None: + """Normalise the model name and HPhi method, updating *StdI* in place. + + Looks up ``StdI.model`` in :data:`MODEL_ALIASES` (and, for HPhi, + :data:`MODEL_ALIASES_HPHI_BOOST`) to resolve the canonical + :class:`ModelType`, the grand-canonical flag ``lGC``, and the Boost + flag ``lBoost``. + + For HPhi, also normalises ``StdI.method`` via :data:`METHOD_ALIASES` + and, if the method is time-evolution, computes the vector potential. + + Parameters + ---------- + StdI : StdIntList + Parameter structure (modified in place). + solver : str + Solver name. + + Raises + ------ + SystemExit + If the model name is not recognised. + """ + StdI.lGC = 0 + StdI.lBoost = 0 + + model_info = MODEL_ALIASES.get(StdI.model) + if model_info is None and solver == SolverType.HPhi: + model_info = MODEL_ALIASES_HPHI_BOOST.get(StdI.model) + if model_info is not None: + StdI.model, StdI.lGC, StdI.lBoost = model_info + else: + _unsupported_system(StdI.model, StdI.lattice) + + if solver == SolverType.HPhi: + StdI.method = METHOD_ALIASES.get(StdI.method, StdI.method) + + if StdI.method == MethodType.TIME_EVOLUTION: + _vector_potential(StdI) + + +# =================================================================== +# Lattice construction and Boost +# =================================================================== + + +def _build_lattice_and_boost(StdI: StdIntList, solver: str) -> None: + """Dispatch to the lattice builder and, for HPhi, apply LargeValue and Boost. + + Looks up ``StdI.lattice`` in :data:`LATTICE_DISPATCH` to generate + the Hamiltonian definition files. For the HPhi solver, also + computes the large value and optionally runs the Boost builder. + + Parameters + ---------- + StdI : StdIntList + Parameter structure (modified in place). + solver : str + Solver name. + + Raises + ------ + SystemExit + If the lattice is not recognised. + """ + lattice = StdI.lattice + try: + lattice_plugin = _get_lattice(lattice) + except KeyError: + _unsupported_system(StdI.model, StdI.lattice) + else: + lattice_plugin.setup(StdI) + + from ..plugin import get_plugin + try: + plugin = get_plugin(solver) + plugin.post_lattice(StdI) + except KeyError: + pass + + +# =================================================================== +# Input file parsing +# =================================================================== + + +def _parse_solver_keyword_via_plugin( + keyword: str, value: str, StdI: StdIntList, solver: str +) -> bool: + """Parse a solver-specific keyword using the plugin registry. + + Falls back to the legacy ``parse_solver_keyword`` if no plugin is found. + + Parameters + ---------- + keyword : str + The lowered keyword string. + value : str + The raw value string from the input file. + StdI : StdIntList + The global parameter structure, modified in place. + solver : str + The solver type. + + Returns + ------- + bool + True if the keyword was recognised, False otherwise. + """ + from ..plugin import get_plugin + try: + plugin = get_plugin(solver) + return _apply_keyword_table(plugin.keyword_table, keyword, value, StdI) + except KeyError: + return _parse_solver_keyword(keyword, value, StdI, solver) + + +def _parse_input_file(fname: str, StdI: StdIntList, solver: str) -> None: + """Open and parse a Standard-mode input file into *StdI*. + + Each non-blank, non-comment line must contain ``keyword = value``. + Common keywords are tried first, then solver-specific keywords. + The program exits on duplicate keywords, missing ``=``, or + unrecognised keywords. + + Parameters + ---------- + fname : str + Path to the Standard-mode input file. + StdI : StdIntList + Parameter structure to populate (modified in place). + solver : str + Solver name (``"HPhi"``, ``"mVMC"``, ``"UHF"``, or ``"HWAVE"``). + + Raises + ------ + SystemExit + If the file cannot be opened, a line lacks ``=``, or a keyword + is unrecognised. + """ + try: + fp_in = open(fname, "r") + except OSError: + print(f"\n ERROR ! Cannot open input file {fname} !\n") + exit_program(-1) + + print(f"\n Open Standard-Mode Inputfile {fname} \n") + + with fp_in: + for raw_line in fp_in: + line = _trim_space_quote(raw_line) + + if line.startswith("//") or line == "": + print(" Skipping a line.") + continue + + parts = line.split("=", 1) + if len(parts) < 2: + print('\n ERROR ! "=" is NOT found !\n') + exit_program(-1) + + keyword = parts[0].lower() + value = parts[1] + print(f" KEYWORD : {keyword:<20s} | VALUE : {value} ") + + if not _parse_common_keyword(keyword, value, StdI): + if not _parse_solver_keyword_via_plugin(keyword, value, StdI, solver): + print("ERROR ! Unsupported Keyword in Standard mode!") + exit_program(-1) + + +# =================================================================== +# stdface_main -- top-level entry point +# =================================================================== + + +def stdface_main(fname: str, solver: str = "HPhi") -> None: + """Read a Standard-mode input file and generate Expert-mode definition files. + + This is the Python translation of the C function ``StdFace_main()`` + (``StdFace_main.c``, lines 2455--3067). It performs the following + sequence of operations: + + 1. Create and initialise a :class:`StdIntList` parameter structure. + 2. Parse the input file, mapping keywords to structure fields. + 3. Validate and normalise the model name, lattice name and method. + 4. Dispatch to the appropriate lattice-generation function. + 5. Write all Expert-mode definition files required by the chosen + solver. + + Parameters + ---------- + fname : str + Path to the Standard-mode input file. + solver : str, optional + Name of the solver that will consume the generated files. + Must be one of ``"HPhi"``, ``"mVMC"``, ``"UHF"`` or ``"HWAVE"``. + Defaults to ``"HPhi"``. This replaces the compile-time + ``#ifdef`` mechanism of the original C code. + + Raises + ------ + SystemExit + If the input file cannot be opened, a keyword is duplicated or + unrecognised, or a required parameter is missing. + """ + # ------------------------------------------------------------------ + # Initialise + # ------------------------------------------------------------------ + StdI = StdIntList() + StdI.solver = solver + + print("\n###### Input Parameter of Standard Intarface ######") + + _reset_vals(StdI) + _parse_input_file(fname, StdI, solver) + + # ------------------------------------------------------------------ + # Construct Model + # ------------------------------------------------------------------ + print("") + print("####### Construct Model #######") + print("") + + # CDataFileHead default + if StdI.CDataFileHead == UNSET_STRING: + StdI.CDataFileHead = "zvo" + print(f" CDataFileHead = {'zvo':<12s}###### DEFAULT VALUE IS USED ######") + else: + print(f" CDataFileHead = {StdI.CDataFileHead}") + + _resolve_model_and_method(StdI, solver) + + _build_lattice_and_boost(StdI, solver) + + # ------------------------------------------------------------------ + # Print Expert input files + # ------------------------------------------------------------------ + print("") + print("###### Print Expert input files ######") + print("") + + from ..plugin import get_plugin + plugin = get_plugin(solver) + plugin.write(StdI) + + # ------------------------------------------------------------------ + # Finalise + # ------------------------------------------------------------------ + print("\n###### Input files are generated. ######\n") diff --git a/python/stdface/lattice/__init__.py b/python/stdface/lattice/__init__.py new file mode 100644 index 0000000..c4d9d9f --- /dev/null +++ b/python/stdface/lattice/__init__.py @@ -0,0 +1,158 @@ +"""Lattice construction subpackage. + +This package contains modules for building lattice geometries and their +associated interaction terms. Each lattice type is implemented in its own +module; shared utilities (site initialisation, geometry output, interaction +building, input parameter helpers) are also included. + +The :class:`LatticePlugin` ABC and plugin registry provide a uniform +interface for lattice discovery and dispatch. +""" +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ..core.stdface_vals import StdIntList + + +class LatticePlugin(ABC): + """Abstract base class for lattice plugins. + + Each lattice plugin declares its geometry metadata and provides + a :meth:`setup` method that builds the lattice Hamiltonian terms. + Lattices that support HPhi Boost mode override :meth:`boost`. + + Attributes + ---------- + name : str + Canonical lattice name (e.g. ``"chain"``). + aliases : list[str] + All recognised name aliases (including the canonical name). + ndim : int + Number of spatial dimensions (1, 2, or 3). + """ + + @property + @abstractmethod + def name(self) -> str: + """Canonical lattice name.""" + + @property + @abstractmethod + def aliases(self) -> list[str]: + """All recognised name aliases for this lattice.""" + + @property + @abstractmethod + def ndim(self) -> int: + """Number of spatial dimensions (1, 2, or 3).""" + + @abstractmethod + def setup(self, StdI: StdIntList) -> None: + """Build the lattice geometry and Hamiltonian terms. + + Parameters + ---------- + StdI : StdIntList + The parameter structure (modified in place). + """ + + def boost(self, StdI: StdIntList) -> None: + """Build HPhi Boost-mode data for this lattice. + + The default implementation does nothing. Override in lattices + that support Boost (chain, honeycomb, kagome, ladder). + + Parameters + ---------- + StdI : StdIntList + The parameter structure (modified in place). + """ + + +# --------------------------------------------------------------------------- +# Lattice plugin registry +# --------------------------------------------------------------------------- + +_lattice_plugins: dict[str, LatticePlugin] = {} +"""Maps every alias to its :class:`LatticePlugin` instance.""" + + +def register_lattice(plugin: LatticePlugin) -> None: + """Register a lattice plugin under all its aliases. + + Parameters + ---------- + plugin : LatticePlugin + The plugin instance to register. + + Raises + ------ + ValueError + If any alias is already registered to a *different* plugin. + """ + for alias in plugin.aliases: + existing = _lattice_plugins.get(alias) + if existing is not None and existing is not plugin: + raise ValueError( + f"Lattice alias {alias!r} is already registered to " + f"{existing.name!r}, cannot register {plugin.name!r}" + ) + _lattice_plugins[alias] = plugin + + +def get_lattice(name: str) -> LatticePlugin: + """Retrieve a registered lattice plugin by alias. + + Parameters + ---------- + name : str + A lattice name alias (e.g. ``"chain"``, ``"squarelattice"``). + + Returns + ------- + LatticePlugin + The registered plugin instance. + + Raises + ------ + KeyError + If no plugin is registered under *name*. + """ + if name not in _lattice_plugins: + _discover_lattices() + if name not in _lattice_plugins: + raise KeyError( + f"No lattice plugin registered for {name!r}. " + f"Available: {', '.join(sorted(set(p.name for p in _lattice_plugins.values()))) or '(none)'}" + ) + return _lattice_plugins[name] + + +def get_all_lattices() -> list[LatticePlugin]: + """Return all unique registered lattice plugins. + + Returns + ------- + list[LatticePlugin] + Unique plugin instances (deduplicated by identity). + """ + if not _lattice_plugins: + _discover_lattices() + seen: set[int] = set() + result: list[LatticePlugin] = [] + for plugin in _lattice_plugins.values(): + pid = id(plugin) + if pid not in seen: + seen.add(pid) + result.append(plugin) + return result + + +def _discover_lattices() -> None: + """Import all lattice modules to trigger auto-registration.""" + from . import chain_lattice, square_lattice, ladder, triangular_lattice # noqa: F401 + from . import honeycomb_lattice, kagome, orthorhombic, fc_ortho, pyrochlore # noqa: F401 + from . import wannier90 # noqa: F401 diff --git a/python/stdface/lattice/chain_lattice.py b/python/stdface/lattice/chain_lattice.py new file mode 100644 index 0000000..94d54b2 --- /dev/null +++ b/python/stdface/lattice/chain_lattice.py @@ -0,0 +1,313 @@ +""" +Standard mode for the chain lattice. + +This module sets up the Hamiltonian for a 1D chain lattice model, +supporting spin, Hubbard, and Kondo models. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType, SolverType +from ..core.param_check import ( + exit_program, print_val_d, print_val_i, + not_used_d, not_used_i, not_used_j, required_val_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + malloc_interactions, + add_neighbor_interaction, add_local_terms, +) +from .site_util import ( + init_site, set_label, set_local_spin_flags, + lattice_gp, +) +from .boost_output import ( + write_boost_mag_field, write_boost_j_full, + write_boost_6spin_star, write_boost_6spin_pair, +) + + +def chain(StdI: StdIntList) -> None: + """Set up a Hamiltonian for the Hubbard model on a chain lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + + Notes + ----- + This function handles three different model types: + + - Spin model + - Hubbard model + - Kondo model + + The function performs the following steps: + + 1. Computes super-cell shape and sites + 2. Validates and stores Hamiltonian parameters + 3. Sets local spin flags and number of sites + 4. Allocates memory for interactions + 5. Sets up transfers and interactions between sites + + The lattice geometry is written to ``lattice.gp`` for visualization. + For the Kondo model, the number of sites is doubled to account for + localized spins. + """ + # (1) Compute the shape of the super-cell and sites in the super-cell + with lattice_gp(StdI) as fp: + + StdI.NsiteUC = 1 + + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.0) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1]) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + not_used_d("phase1", StdI.phase[1]) + StdI.phase[1] = StdI.phase[0] + StdI.phase[0] = 0.0 + + required_val_i("L", StdI.L) + not_used_i("W", StdI.W) + StdI.W = 1 + init_site(StdI, fp, 2) + StdI.tau[0, 0] = 0.0 + StdI.tau[0, 1] = 0.0 + StdI.tau[0, 2] = 0.0 + + # (2) Check & store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J1'", StdI.J1pAll, StdI.J1p) + not_used_j("J2'", StdI.J2pAll, StdI.J2p) + not_used_d("t1", StdI.t1) + not_used_d("t2", StdI.t2) + not_used_d("t1'", StdI.t1p) + not_used_d("t2'", StdI.t2p) + not_used_d("V1", StdI.V1) + not_used_d("V2", StdI.V2) + not_used_d("V1'", StdI.V1p) + not_used_d("V2'", StdI.V2p) + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t", StdI.t) + not_used_d("t0", StdI.t0) + not_used_d("t'", StdI.tp) + not_used_d("V", StdI.V) + not_used_d("V0", StdI.V0) + not_used_d("V'", StdI.Vp) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") + StdI.t0pp = input_hopp(StdI.tpp, StdI.t0pp, "t0''") + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") + StdI.V0pp = input_coulomb_v(StdI.Vpp, StdI.V0pp, "V0''") + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J0'", StdI.J0pAll, StdI.J0p) + not_used_j("J0''", StdI.J0ppAll, StdI.J0pp) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + elif StdI.model == ModelType.KONDO: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and the number of sites + set_local_spin_flags(StdI, StdI.L) + + # (4) Compute upper limit of Transfer & Interaction and allocate them + if StdI.model == ModelType.SPIN: + ntransMax = StdI.L * (StdI.S2 + 1 + 2 * StdI.S2) + nintrMax = (StdI.L * (StdI.NsiteUC + 1 + 1 + 1) + * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1)) + else: + ntransMax = StdI.L * 2 * (2 * StdI.NsiteUC + 2 + 2 + 2) + nintrMax = StdI.L * (StdI.NsiteUC + 4 * (1 + 1 + 1)) + + if StdI.model == ModelType.KONDO: + ntransMax += StdI.L * (StdI.S2 + 1 + 2 * StdI.S2) + nintrMax += StdI.nsite // 2 * (3 * 1 + 1) * (3 * StdI.S2 + 1) + + malloc_interactions(StdI, ntransMax, nintrMax) + + # (5) Set Transfer & Interaction + for iL in range(StdI.L): + + isite = iL + if StdI.model == ModelType.KONDO: + isite += StdI.L + + # Local term + add_local_terms(StdI, isite, iL) + + # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) + _BONDS = ( + (0, 1, 0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # nn + (0, 2, 0, 0, 2, StdI.J0p, StdI.t0p, StdI.V0p), # nnn + (0, 3, 0, 0, 3, StdI.J0pp, StdI.t0pp, StdI.V0pp), # nnnn + ) + for dW, dL, si, sj, nn, J, t, V in _BONDS: + add_neighbor_interaction( + StdI, fp, 0, iL, dW, dL, si, sj, nn, J, t, V) + + +def chain_boost(StdI: StdIntList) -> None: + """Set up a Hamiltonian for the generalized Heisenberg model on a chain + lattice using HPhi boost mode. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + + Notes + ----- + This function handles: + + - Magnetic field terms + - Nearest and next-nearest neighbor interactions + - Specialized 6-spin interactions + + The function performs: + + 1. Sets up unit cell and lattice parameters + 2. Writes magnetic field configuration to ``boost.def`` + 3. Writes interaction parameters + 4. Sets up topology and pivot sites + 5. Configures 6-spin interaction lists + + Only available when ``StdI.solver == "HPhi"``. + + Warnings + -------- + - ``S2`` must be 1 in Boost mode. + - ``L`` must be divisible by 8. + """ + if StdI.solver != SolverType.HPhi: + return + + StdI.NsiteUC = 1 + + # Magnetic field + with open("boost.def", "w") as fp: + write_boost_mag_field(fp, StdI) + + # Interaction + fp.write(f"{2} # Number of type of J\n") + fp.write("# J 1\n") + write_boost_j_full(fp, StdI.J0) + fp.write("# J 2\n") + write_boost_j_full(fp, StdI.Jp) + + # Topology + if StdI.S2 != 1: + print("\n ERROR! S2 must be 1 in Boost. \n") + exit_program(-1) + StdI.ishift_nspin = 4 + if StdI.L % 8 != 0: + print("\n ERROR! L % 8 != 0 \n") + exit_program(-1) + StdI.W = StdI.L // 2 + StdI.L = 2 + StdI.num_pivot = StdI.W // 4 + + fp.write("# W0 R0 StdI->num_pivot StdI->ishift_nspin\n") + fp.write(f"{StdI.W} {StdI.L} {StdI.num_pivot} {StdI.ishift_nspin}\n") + + # list_6spin_star: shape (num_pivot, 7) + StdI.list_6spin_star = np.zeros((StdI.num_pivot, 7), dtype=int) + for ipivot in range(StdI.num_pivot): + StdI.list_6spin_star[ipivot, :] = [8, 1, 1, 1, 1, 1, 1] + + write_boost_6spin_star(fp, StdI) + + # list_6spin_pair: shape (num_pivot, 7, 8) + StdI.list_6spin_pair = np.zeros((StdI.num_pivot, 7, 8), dtype=int) + for ipivot in range(StdI.num_pivot): + StdI.list_6spin_pair[ipivot, :, 0] = [0, 1, 2, 3, 4, 5, 1] + StdI.list_6spin_pair[ipivot, :, 1] = [1, 2, 0, 3, 4, 5, 1] + StdI.list_6spin_pair[ipivot, :, 2] = [2, 3, 0, 1, 4, 5, 1] + StdI.list_6spin_pair[ipivot, :, 3] = [3, 4, 0, 1, 2, 5, 1] + StdI.list_6spin_pair[ipivot, :, 4] = [0, 2, 1, 3, 4, 5, 2] + StdI.list_6spin_pair[ipivot, :, 5] = [1, 3, 0, 2, 4, 5, 2] + StdI.list_6spin_pair[ipivot, :, 6] = [2, 4, 0, 1, 3, 5, 2] + StdI.list_6spin_pair[ipivot, :, 7] = [3, 5, 0, 1, 2, 4, 2] + + write_boost_6spin_pair(fp, StdI) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + + +class ChainPlugin(LatticePlugin): + """Plugin for the 1D chain lattice.""" + + @property + def name(self) -> str: + return "chain" + + @property + def aliases(self) -> list[str]: + return ["chain", "chainlattice"] + + @property + def ndim(self) -> int: + return 1 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the chain() function.""" + chain(StdI) + + def boost(self, StdI: StdIntList) -> None: + """Delegate to the chain_boost() function.""" + chain_boost(StdI) + + +register_lattice(ChainPlugin()) diff --git a/python/stdface/lattice/fc_ortho.py b/python/stdface/lattice/fc_ortho.py new file mode 100644 index 0000000..01b34ff --- /dev/null +++ b/python/stdface/lattice/fc_ortho.py @@ -0,0 +1,220 @@ +""" +Standard mode for the face-centered orthorhombic lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + print_val_d, print_val_i, + not_used_j, not_used_d, not_used_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + compute_max_interactions, malloc_interactions, + mag_field, general_j, hubbard_local, + add_neighbor_interaction_3d, +) +from .site_util import init_site, set_local_spin_flags, close_lattice_xsf + + +def fc_ortho(StdI: StdIntList) -> None: + """Setup a Hamiltonian for the face-centered orthorhombic lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + # (1) Compute the shape of the super-cell and sites in the super-cell + StdI.NsiteUC = 1 + + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.length[2] = print_val_d("Hlength", StdI.length[2], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], 0.0) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.5 * StdI.length[1]) + StdI.direct[0, 2] = print_val_d("Wz", StdI.direct[0, 2], 0.5 * StdI.length[2]) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.5 * StdI.length[0]) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], 0.0) + StdI.direct[1, 2] = print_val_d("Lz", StdI.direct[1, 2], 0.5 * StdI.length[2]) + StdI.direct[2, 0] = print_val_d("Hx", StdI.direct[2, 0], 0.5 * StdI.length[0]) + StdI.direct[2, 1] = print_val_d("Hy", StdI.direct[2, 1], 0.5 * StdI.length[1]) + StdI.direct[2, 2] = print_val_d("Hz", StdI.direct[2, 2], 0.0) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + StdI.phase[2] = print_val_d("phase2", StdI.phase[2], 0.0) + + init_site(StdI, None, 3) + StdI.tau[0, 0] = 0.0 + StdI.tau[0, 1] = 0.0 + StdI.tau[0, 2] = 0.0 + + # (2) check & store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") + input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") + input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J1pp, StdI.J1ppAll, "J1''") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J2pp, StdI.J2ppAll, "J2''") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t", StdI.t) + not_used_d("t0", StdI.t0) + not_used_d("t1", StdI.t1) + not_used_d("t2", StdI.t2) + not_used_d("t'", StdI.tp) + not_used_d("t0'", StdI.t0p) + not_used_d("t1'", StdI.t1p) + not_used_d("t2'", StdI.t2p) + not_used_d("t''", StdI.tpp) + not_used_d("V", StdI.V) + not_used_d("V0", StdI.V0) + not_used_d("V1", StdI.V1) + not_used_d("V'", StdI.Vp) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") + StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") + StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") + StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") + StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") + StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") + StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") + StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") + StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J0'", StdI.J0pAll, StdI.J0p) + not_used_j("J1'", StdI.J1pAll, StdI.J1p) + not_used_j("J2'", StdI.J2pAll, StdI.J2p) + not_used_j("J''", StdI.JppAll, StdI.Jpp) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + else: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and number of sites + set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) + + # (4) Compute upper limit of Transfer & Interaction + # nn=6, nnn=3 → n_bonds=9 + ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=6 + 3) + malloc_interactions(StdI, ntransMax, nintrMax) + + # (5) Set Transfer & Interaction + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + iH = StdI.Cell[kCell, 2] + + # Local term + isite = kCell + if StdI.model == ModelType.KONDO: + isite += StdI.NCell + + if StdI.model == ModelType.SPIN: + mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, isite) + general_j(StdI, StdI.D, StdI.S2, StdI.S2, isite, isite) + else: + hubbard_local(StdI, StdI.mu, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, StdI.U, isite) + if StdI.model == ModelType.KONDO: + jsite = kCell + general_j(StdI, StdI.J, 1, StdI.S2, isite, jsite) + + # Neighbor bonds: (dW, dL, dH, site_i, site_j, J, t, V) + _BONDS = ( + # Nearest neighbor (6 equivalent pairs) + (1, 0, 0, 0, 0, StdI.J0, StdI.t0, StdI.V0), # along W + (0, 1, -1, 0, 0, StdI.J0, StdI.t0, StdI.V0), # along W (equiv) + (0, 1, 0, 0, 0, StdI.J1, StdI.t1, StdI.V1), # along L + (-1, 0, 1, 0, 0, StdI.J1, StdI.t1, StdI.V1), # along L (equiv) + (0, 0, 1, 0, 0, StdI.J2, StdI.t2, StdI.V2), # along H + (1, -1, 0, 0, 0, StdI.J2, StdI.t2, StdI.V2), # along H (equiv) + # Second nearest neighbor + (-1, 1, 1, 0, 0, StdI.J0p, StdI.t0p, StdI.V0p), # -W+L+H + (1, -1, 1, 0, 0, StdI.J1p, StdI.t1p, StdI.V1p), # -L+H+W + (1, 1, -1, 0, 0, StdI.J2p, StdI.t2p, StdI.V2p), # -H+W+L + ) + for dW, dL, dH, si, sj, J, t, V in _BONDS: + add_neighbor_interaction_3d( + StdI, iW, iL, iH, dW, dL, dH, si, sj, J, t, V) + + close_lattice_xsf(StdI) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + +_fc_ortho_setup = fc_ortho + + +class FCOrthoPlugin(LatticePlugin): + """Plugin for the 3D face-centered orthorhombic lattice.""" + + @property + def name(self) -> str: + return "fco" + + @property + def aliases(self) -> list[str]: + return [ + "face-centeredorthorhombic", "fcorthorhombic", "fco", + "face-centeredcubic", "fccubic", "fcc", + ] + + @property + def ndim(self) -> int: + return 3 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the fc_ortho() function.""" + _fc_ortho_setup(StdI) + + +register_lattice(FCOrthoPlugin()) diff --git a/python/stdface/lattice/honeycomb_lattice.py b/python/stdface/lattice/honeycomb_lattice.py new file mode 100644 index 0000000..4596cb5 --- /dev/null +++ b/python/stdface/lattice/honeycomb_lattice.py @@ -0,0 +1,304 @@ +""" +Standard mode for the honeycomb lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import math + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + exit_program, print_val_d, print_val_i, + not_used_j, not_used_d, not_used_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + compute_max_interactions, malloc_interactions, + add_neighbor_interaction, add_local_terms, +) +from .site_util import ( + init_site, set_label, set_local_spin_flags, + lattice_gp, +) +from .boost_output import ( + write_boost_mag_field, write_boost_j_symmetric, + write_boost_6spin_star, write_boost_6spin_pair, +) + + +def honeycomb(StdI: StdIntList) -> None: + """Setup a Hamiltonian for the honeycomb lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + # (1) Compute the shape of the super-cell and sites in the super-cell + with lattice_gp(StdI) as fp: + + StdI.NsiteUC = 2 + + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], StdI.length[1] * 0.5) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1] * 0.5 * math.sqrt(3.0)) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + + init_site(StdI, fp, 2) + StdI.tau[0, 0] = 0.0 + StdI.tau[0, 1] = 0.0 + StdI.tau[0, 2] = 0.0 + StdI.tau[1, 0] = 1.0 / 3.0 + StdI.tau[1, 1] = 1.0 / 3.0 + StdI.tau[1, 2] = 0.0 + + # (2) check & store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") + input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") + input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J1pp, StdI.J1ppAll, "J1''") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J2pp, StdI.J2ppAll, "J2''") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t", StdI.t) + not_used_d("t0", StdI.t0) + not_used_d("t1", StdI.t1) + not_used_d("t2", StdI.t2) + not_used_d("t'", StdI.tp) + not_used_d("t0'", StdI.t0p) + not_used_d("t1'", StdI.t1p) + not_used_d("t2'", StdI.t2p) + not_used_d("V", StdI.V) + not_used_d("V0", StdI.V0) + not_used_d("V1", StdI.V1) + not_used_d("V2", StdI.V2) + not_used_d("V'", StdI.Vp) + not_used_d("V0'", StdI.V0p) + not_used_d("V1'", StdI.V1p) + not_used_d("V2'", StdI.V2p) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") + StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") + StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") + StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") + StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") + StdI.t0pp = input_hopp(StdI.tpp, StdI.t0pp, "t0''") + StdI.t1pp = input_hopp(StdI.tpp, StdI.t1pp, "t1''") + StdI.t2pp = input_hopp(StdI.tpp, StdI.t2pp, "t2''") + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") + StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") + StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") + StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") + StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") + StdI.V0pp = input_coulomb_v(StdI.Vpp, StdI.V0pp, "V0''") + StdI.V1pp = input_coulomb_v(StdI.Vpp, StdI.V1pp, "V1''") + StdI.V2pp = input_coulomb_v(StdI.Vpp, StdI.V2pp, "V2''") + StdI.Vp = print_val_d("V'", StdI.Vp, 0.0) + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J'", StdI.JpAll, StdI.Jp) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + else: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and number of sites + set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) + + # (4) Compute upper limit of Transfer & Interaction + # nn=3, nnn=6, nnnn=3 → n_bonds=12 + ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=3 + 6 + 3) + malloc_interactions(StdI, ntransMax, nintrMax) + + # (5) Set Transfer & Interaction + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + + # Local term + isite = StdI.NsiteUC * kCell + if StdI.model == ModelType.KONDO: + isite += StdI.NsiteUC * StdI.NCell + jsite_base = StdI.NsiteUC * kCell + for isiteUC in range(StdI.NsiteUC): + add_local_terms(StdI, isite + isiteUC, jsite_base + isiteUC) + + # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) + _BONDS = ( + # Nearest neighbor (nn=1) + (0, 0, 0, 1, 1, StdI.J0, StdI.t0, StdI.V0), # intra cell + (1, 0, 1, 0, 1, StdI.J1, StdI.t1, StdI.V1), # along W + (0, 1, 1, 0, 1, StdI.J2, StdI.t2, StdI.V2), # along L + # Second nearest neighbor (nn=2) + (1, 0, 0, 0, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along W, 0->0 + (1, 0, 1, 1, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along W, 1->1 + (0, 1, 0, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # along L, 0->0 + (0, 1, 1, 1, 2, StdI.J1p, StdI.t1p, StdI.V1p), # along L, 1->1 + (1, -1, 0, 0, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along W-L, 0->0 + (1, -1, 1, 1, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along W-L, 1->1 + # Third nearest neighbor (nn=3) + (1, -1, 0, 1, 3, StdI.J1pp, StdI.t1pp, StdI.V1pp), # along W-L + (-1, -1, 0, 1, 3, StdI.J0pp, StdI.t0pp, StdI.V0pp), # along -W-L + (-1, 1, 0, 1, 3, StdI.J2pp, StdI.t2pp, StdI.V2pp), # along -W+L + ) + for dW, dL, si, sj, nn, J, t, V in _BONDS: + add_neighbor_interaction( + StdI, fp, iW, iL, dW, dL, si, sj, nn, J, t, V) + + +def honeycomb_boost(StdI: StdIntList) -> None: + """Setup a boosted Hamiltonian for the honeycomb lattice (HPhi only). + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + if StdI.box[0, 1] != 0 or StdI.box[1, 0] != 0: + print("\nERROR ! (a0W, a0L, a1W, a1L) can not be used with SpinGCBoost.\n") + exit_program(-1) + + if np.any(np.abs(StdI.Jp) > 1.0e-8): + print("\nERROR ! J' can not be used with SpinGCBoost.\n") + exit_program(-1) + + # Magnetic field + with open("boost.def", "w") as fp: + write_boost_mag_field(fp, StdI) + + # Interaction + fp.write(f"{3} # Number of type of J\n") + fp.write("# J 0\n") + write_boost_j_symmetric(fp, StdI.J0) + fp.write("# J 1\n") + write_boost_j_symmetric(fp, StdI.J1) + fp.write("# J 2\n") + write_boost_j_symmetric(fp, StdI.J2) + + # Topology + if StdI.S2 != 1: + print("\n ERROR! S2 must be 1 in Boost. \n") + exit_program(-1) + StdI.ishift_nspin = 3 + if StdI.L < 2: + print("\n ERROR! L < 2 \n") + exit_program(-1) + if StdI.W % StdI.ishift_nspin != 0: + print(f"\n ERROR! W %% {StdI.ishift_nspin} != 0 \n") + exit_program(-1) + StdI.num_pivot = 2 + if StdI.W != 3: + print("DEBUG: W != 3") + exit_program(-1) + StdI.W = 6 + fp.write("# W0 R0 StdI->num_pivot StdI->ishift_nspin\n") + fp.write(f"{StdI.W} {StdI.L} {StdI.num_pivot} {StdI.ishift_nspin}\n") + + # 6-spin star list + StdI.list_6spin_star = np.zeros((StdI.num_pivot, 7), dtype=int) + + StdI.list_6spin_star[0, :] = [5, 1, 1, 1, 2, 1, 1] + StdI.list_6spin_star[1, :] = [4, 1, 1, 1, 2, 2, 1] + + write_boost_6spin_star(fp, StdI) + + # 6-spin pair list + max_kintr = max(StdI.list_6spin_star[ip, 0] for ip in range(StdI.num_pivot)) + StdI.list_6spin_pair = np.zeros((StdI.num_pivot, 7, max_kintr), dtype=int) + + # pivot 0 + StdI.list_6spin_pair[0, :, 0] = [0, 1, 2, 3, 4, 5, 1] + StdI.list_6spin_pair[0, :, 1] = [1, 2, 0, 3, 4, 5, 2] + StdI.list_6spin_pair[0, :, 2] = [2, 3, 0, 1, 4, 5, 1] + StdI.list_6spin_pair[0, :, 3] = [0, 4, 1, 2, 3, 5, 2] + StdI.list_6spin_pair[0, :, 4] = [1, 5, 0, 2, 3, 4, 3] + + # pivot 1 + StdI.list_6spin_pair[1, :, 0] = [0, 1, 2, 3, 4, 5, 2] + StdI.list_6spin_pair[1, :, 1] = [1, 2, 0, 3, 4, 5, 1] + StdI.list_6spin_pair[1, :, 2] = [0, 4, 1, 2, 3, 5, 3] + StdI.list_6spin_pair[1, :, 3] = [2, 5, 0, 1, 3, 4, 3] + + write_boost_6spin_pair(fp, StdI) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + + +class HoneycombPlugin(LatticePlugin): + """Plugin for the 2D honeycomb lattice.""" + + @property + def name(self) -> str: + return "honeycomb" + + @property + def aliases(self) -> list[str]: + return ["honeycomb", "honeycomblattice"] + + @property + def ndim(self) -> int: + return 2 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the honeycomb() function.""" + honeycomb(StdI) + + def boost(self, StdI: StdIntList) -> None: + """Delegate to the honeycomb_boost() function.""" + honeycomb_boost(StdI) + + +register_lattice(HoneycombPlugin()) diff --git a/python/stdface/lattice/kagome.py b/python/stdface/lattice/kagome.py new file mode 100644 index 0000000..ecfc6a4 --- /dev/null +++ b/python/stdface/lattice/kagome.py @@ -0,0 +1,318 @@ +""" +Standard mode for the kagome lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import math + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + exit_program, print_val_d, print_val_i, + not_used_j, not_used_d, not_used_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + compute_max_interactions, malloc_interactions, + add_neighbor_interaction, add_local_terms, +) +from .site_util import ( + init_site, set_local_spin_flags, + lattice_gp, +) +from .boost_output import ( + write_boost_mag_field, write_boost_j_symmetric, + write_boost_6spin_star, write_boost_6spin_pair, +) + + +def kagome(StdI: StdIntList) -> None: + """Setup a Hamiltonian for the kagome lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + # (1) Compute the shape of the super-cell and sites in the super-cell + with lattice_gp(StdI) as fp: + + StdI.NsiteUC = 3 + + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], StdI.length[1] * 0.5) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1] * 0.5 * math.sqrt(3.0)) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + + init_site(StdI, fp, 2) + StdI.tau[0, 0] = 0.0 + StdI.tau[0, 1] = 0.0 + StdI.tau[0, 2] = 0.0 + StdI.tau[1, 0] = 0.5 + StdI.tau[1, 1] = 0.0 + StdI.tau[1, 2] = 0.0 + StdI.tau[2, 0] = 0.0 + StdI.tau[2, 1] = 0.5 + StdI.tau[2, 2] = 0.0 + + # (2) check & store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") + input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") + input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t", StdI.t) + not_used_d("t0", StdI.t) + not_used_d("t0", StdI.t0) + not_used_d("t1", StdI.t1) + not_used_d("t2", StdI.t2) + not_used_d("t'", StdI.tp) + not_used_d("t0'", StdI.t0p) + not_used_d("t1'", StdI.t1p) + not_used_d("t2'", StdI.t2p) + not_used_d("V", StdI.V) + not_used_d("V0", StdI.V0) + not_used_d("V1", StdI.V1) + not_used_d("V2", StdI.V2) + not_used_d("V'", StdI.Vp) + not_used_d("V0'", StdI.V0p) + not_used_d("V1'", StdI.V1p) + not_used_d("V2'", StdI.V2p) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") + StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") + StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") + StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") + StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") + StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") + StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") + StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") + StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J'", StdI.JpAll, StdI.Jp) + not_used_j("J0'", StdI.J0pAll, StdI.J0p) + not_used_j("J1'", StdI.J1pAll, StdI.J1p) + not_used_j("J2'", StdI.J2pAll, StdI.J2p) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + else: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and number of sites + set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) + + # (4) Compute upper limit of Transfer & Interaction + # nn=6, nnn=6 → n_bonds=12 + ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=6 + 6) + malloc_interactions(StdI, ntransMax, nintrMax) + + # (5) Set Transfer & Interaction + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + + # Local term + isite = StdI.NsiteUC * kCell + if StdI.model == ModelType.KONDO: + isite += StdI.nsite // 2 + jsite_base = StdI.NsiteUC * kCell + for isiteUC in range(StdI.NsiteUC): + add_local_terms(StdI, isite + isiteUC, jsite_base + isiteUC) + + # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) + _BONDS = ( + # Nearest neighbor (nn=1) + (0, 0, 0, 1, 1, StdI.J2, StdI.t2, StdI.V2), # intra 0->1 + (0, 0, 0, 2, 1, StdI.J1, StdI.t1, StdI.V1), # intra 0->2 + (0, 0, 1, 2, 1, StdI.J0, StdI.t0, StdI.V0), # intra 1->2 + (1, 0, 1, 0, 1, StdI.J2, StdI.t2, StdI.V2), # along W + (0, 1, 2, 0, 1, StdI.J1, StdI.t1, StdI.V1), # along L + (1, -1, 1, 2, 1, StdI.J0, StdI.t0, StdI.V0), # along W-L + # Second nearest neighbor (nn=2) + (1, 0, 2, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # along W, 2->0 + (1, 0, 1, 2, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along W, 1->2 + (0, 1, 1, 0, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along L, 1->0 + (0, 1, 2, 1, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along L, 2->1 + (1, -1, 0, 2, 2, StdI.J1p, StdI.t1p, StdI.V1p), # along W-L, 0->2 + (-1, 1, 0, 1, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along L-W, 0->1 + ) + for dW, dL, si, sj, nn, J, t, V in _BONDS: + add_neighbor_interaction( + StdI, fp, iW, iL, dW, dL, si, sj, nn, J, t, V) + + +def kagome_boost(StdI: StdIntList) -> None: + """Setup a boosted Hamiltonian for the kagome lattice (HPhi only). + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + if StdI.box[0, 1] != 0 or StdI.box[1, 0] != 0: + print("\nERROR ! (a0W, a0L, a1W, a1L) can not be used with SpinGCBoost.\n") + exit_program(-1) + + if np.any(np.abs(StdI.Jp) > 1.0e-8): + print("\nERROR ! J' can not be used with SpinGCBoost.\n") + exit_program(-1) + + # Magnetic field + with open("boost.def", "w") as fp: + write_boost_mag_field(fp, StdI) + + # Interaction + fp.write(f"{3} # Number of type of J\n") + fp.write("# J 0\n") + write_boost_j_symmetric(fp, StdI.J0) + fp.write("# J 1\n") + write_boost_j_symmetric(fp, StdI.J1) + fp.write("# J 2\n") + write_boost_j_symmetric(fp, StdI.J2) + + # Topology + if StdI.S2 != 1: + print("\n ERROR! S2 must be 1 in Boost. \n") + exit_program(-1) + StdI.ishift_nspin = 3 + if StdI.L < 2: + print("\n ERROR! L < 2 \n") + exit_program(-1) + if StdI.W % StdI.ishift_nspin != 0: + print(f"\n ERROR! W %% {StdI.ishift_nspin} != 0 \n") + exit_program(-1) + StdI.num_pivot = 4 + if StdI.W != 3: + print("DEBUG: W != 3") + exit_program(-1) + StdI.W = 9 + fp.write("# W0 R0 StdI->num_pivot StdI->ishift_nspin\n") + fp.write(f"{StdI.W} {StdI.L} {StdI.num_pivot} {StdI.ishift_nspin}\n") + + # 6-spin star list + StdI.list_6spin_star = np.zeros((StdI.num_pivot, 7), dtype=int) + + StdI.list_6spin_star[0, :] = [1, 1, 1, 1, 4, 2, -1] + StdI.list_6spin_star[1, :] = [6, 1, 1, 1, 6, 7, 1] + StdI.list_6spin_star[2, :] = [6, 1, 1, 1, 4, 2, 1] + StdI.list_6spin_star[3, :] = [5, 1, 1, 1, 4, 2, 1] + + write_boost_6spin_star(fp, StdI) + + # 6-spin pair list + max_kintr = max(StdI.list_6spin_star[ip, 0] for ip in range(StdI.num_pivot)) + StdI.list_6spin_pair = np.zeros((StdI.num_pivot, 7, max_kintr), dtype=int) + + # pivot 0 + StdI.list_6spin_pair[0, :, 0] = [0, 4, 1, 2, 3, 5, 3] + + # pivot 1 + StdI.list_6spin_pair[1, :, 0] = [0, 1, 2, 3, 4, 5, 3] + StdI.list_6spin_pair[1, :, 1] = [1, 2, 0, 3, 4, 5, 1] + StdI.list_6spin_pair[1, :, 2] = [0, 2, 1, 3, 4, 5, 2] + StdI.list_6spin_pair[1, :, 3] = [1, 3, 0, 2, 4, 5, 3] + StdI.list_6spin_pair[1, :, 4] = [2, 4, 0, 1, 3, 5, 2] + StdI.list_6spin_pair[1, :, 5] = [2, 5, 0, 1, 3, 4, 1] + + # pivot 2 + StdI.list_6spin_pair[2, :, 0] = [0, 1, 2, 3, 4, 5, 3] + StdI.list_6spin_pair[2, :, 1] = [1, 2, 0, 3, 4, 5, 1] + StdI.list_6spin_pair[2, :, 2] = [0, 2, 1, 3, 4, 5, 2] + StdI.list_6spin_pair[2, :, 3] = [1, 3, 0, 2, 4, 5, 3] + StdI.list_6spin_pair[2, :, 4] = [2, 5, 0, 1, 3, 4, 2] + StdI.list_6spin_pair[2, :, 5] = [2, 4, 0, 1, 3, 5, 1] + + # pivot 3 + StdI.list_6spin_pair[3, :, 0] = [0, 1, 2, 3, 4, 5, 3] + StdI.list_6spin_pair[3, :, 1] = [1, 2, 0, 3, 4, 5, 1] + StdI.list_6spin_pair[3, :, 2] = [0, 2, 1, 3, 4, 5, 2] + StdI.list_6spin_pair[3, :, 3] = [2, 5, 0, 1, 3, 4, 2] + StdI.list_6spin_pair[3, :, 4] = [2, 4, 0, 1, 3, 5, 1] + + write_boost_6spin_pair(fp, StdI) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + +_kagome_setup = kagome +_kagome_boost = kagome_boost + + +class KagomePlugin(LatticePlugin): + """Plugin for the 2D kagome lattice.""" + + @property + def name(self) -> str: + return "kagome" + + @property + def aliases(self) -> list[str]: + return ["kagome", "kagomelattice"] + + @property + def ndim(self) -> int: + return 2 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the kagome() function.""" + _kagome_setup(StdI) + + def boost(self, StdI: StdIntList) -> None: + """Delegate to the kagome_boost() function.""" + _kagome_boost(StdI) + + +register_lattice(KagomePlugin()) diff --git a/python/stdface/lattice/ladder.py b/python/stdface/lattice/ladder.py new file mode 100644 index 0000000..618761a --- /dev/null +++ b/python/stdface/lattice/ladder.py @@ -0,0 +1,319 @@ +""" +Standard mode for the ladder lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + exit_program, print_val_d, print_val_i, + not_used_j, not_used_d, not_used_i, required_val_i, +) +from .input_params import input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + malloc_interactions, + add_neighbor_interaction, add_local_terms, +) +from .site_util import ( + init_site, set_label, set_local_spin_flags, + lattice_gp, +) +from .boost_output import ( + write_boost_mag_field, write_boost_j_symmetric, + write_boost_6spin_star, write_boost_6spin_pair, +) + + +def ladder(StdI: StdIntList) -> None: + """Setup a Hamiltonian for the ladder lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + with lattice_gp(StdI) as fp: + + # 1. Set lattice size and shape parameters + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.0) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1]) + + required_val_i("L", StdI.L) + required_val_i("W", StdI.W) + + not_used_i("a0W", StdI.box[0, 0]) + not_used_i("a0L", StdI.box[0, 1]) + not_used_i("a1W", StdI.box[1, 0]) + not_used_i("a1L", StdI.box[1, 1]) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + not_used_d("phase1", StdI.phase[1]) + StdI.phase[1] = StdI.phase[0] + StdI.phase[0] = 0.0 + + StdI.NsiteUC = StdI.W + StdI.W = 1 + StdI.direct[0, 0] = float(StdI.NsiteUC) + init_site(StdI, fp, 2) + + for isite in range(StdI.NsiteUC): + StdI.tau[isite, 0] = float(isite) / float(StdI.NsiteUC) + StdI.tau[isite, 1] = 0.0 + StdI.tau[isite, 2] = 0.0 + + # 2. Set Hamiltonian parameters + print("\n @ Hamiltonian \n") + + not_used_j("J", StdI.JAll, StdI.J) + not_used_j("J'", StdI.JpAll, StdI.Jp) + not_used_d("t", StdI.t) + not_used_d("t'", StdI.tp) + not_used_d("V", StdI.V) + not_used_d("V'", StdI.Vp) + not_used_d("K", StdI.K) + + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin(StdI.J0, StdI.J0All, "J0") + input_spin(StdI.J1, StdI.J1All, "J1") + input_spin(StdI.J2, StdI.J2All, "J2") + input_spin(StdI.J1p, StdI.J1pAll, "J1'") + input_spin(StdI.J2p, StdI.J2pAll, "J2'") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t0", StdI.t0) + not_used_d("t1", StdI.t1) + not_used_d("t2", StdI.t2) + not_used_d("t1'", StdI.t1p) + not_used_d("t2'", StdI.t2p) + not_used_d("V0", StdI.V0) + not_used_d("V1", StdI.V1) + not_used_d("V2", StdI.V2) + not_used_d("V1'", StdI.V1p) + not_used_d("V2'", StdI.V2p) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") + StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") + StdI.t1p = input_hopp(StdI.t, StdI.t1p, "t1'") + StdI.t2p = input_hopp(StdI.t, StdI.t2p, "t2'") + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") + StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") + StdI.V1p = input_coulomb_v(StdI.V, StdI.V1p, "V1'") + StdI.V2p = input_coulomb_v(StdI.V, StdI.V2p, "V2'") + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J1p", StdI.J1pAll, StdI.J1p) + not_used_j("J2p", StdI.J2pAll, StdI.J2p) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + else: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # 3. Set local spin flags and number of sites + set_local_spin_flags(StdI, StdI.L * StdI.NsiteUC) + + # 4. Calculate maximum number of interactions and allocate arrays + if StdI.model == ModelType.SPIN: + ntransMax = StdI.L * StdI.NsiteUC * (StdI.S2 + 1 + 2 * StdI.S2) + nintrMax = (StdI.L * StdI.NsiteUC * (1 + 1 + 1) + * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1) + + StdI.L * (StdI.NsiteUC - 1) * (1 + 1 + 1) + * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1)) + else: + ntransMax = (StdI.L * StdI.NsiteUC * 2 * (2 + 2 + 2) + + StdI.L * (StdI.NsiteUC - 1) * 2 * (2 + 2 + 2)) + nintrMax = (StdI.L * StdI.NsiteUC * 1 + + StdI.L * StdI.NsiteUC * 4 * (1 + 1) + + StdI.L * (StdI.NsiteUC - 1) * 4 * (1 + 1 + 1)) + if StdI.model == ModelType.KONDO: + ntransMax += StdI.L * StdI.NsiteUC * (StdI.S2 + 1 + 2 * StdI.S2) + nintrMax += StdI.nsite // 2 * (3 * 1 + 1) * (3 * StdI.S2 + 1) + + malloc_interactions(StdI, ntransMax, nintrMax) + + # 5. Set all interactions + for iL in range(StdI.L): + for isiteUC in range(StdI.NsiteUC): + isite = isiteUC + iL * StdI.NsiteUC + if StdI.model == ModelType.KONDO: + isite += StdI.L * StdI.NsiteUC + + # Local terms + add_local_terms(StdI, isite, isiteUC + iL * StdI.NsiteUC) + + # Leg bonds: (dW, dL, sj_offset, nn, J, t, V) + _LEG_BONDS = ( + (0, 1, 0, 1, StdI.J1, StdI.t1, StdI.V1), # nn along ladder + (0, 2, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # nnn along ladder + ) + for dW, dL, sj_off, nn, J, t, V in _LEG_BONDS: + add_neighbor_interaction( + StdI, fp, 0, iL, dW, dL, isiteUC, isiteUC + sj_off, nn, J, t, V) + + # Rung/diagonal bonds (only between adjacent legs) + if isiteUC < StdI.NsiteUC - 1: + _RUNG_BONDS = ( + (0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # vertical + (0, 1, 1, StdI.J2, StdI.t2, StdI.V2), # diagonal 1 + (0, -1, 1, StdI.J2p, StdI.t2p, StdI.V2p), # diagonal 2 + ) + for dW, dL, nn, J, t, V in _RUNG_BONDS: + add_neighbor_interaction( + StdI, fp, 0, iL, dW, dL, isiteUC, isiteUC + 1, nn, J, t, V) + + +def ladder_boost(StdI: StdIntList) -> None: + """Setup a boosted Hamiltonian for the ladder lattice (HPhi only). + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + StdI.W = StdI.NsiteUC + StdI.NsiteUC = 1 + + with open("boost.def", "w") as fp: + # Magnetic field + write_boost_mag_field(fp, StdI) + + # Interaction parameters + fp.write(f"{5} # Number of type of J\n") + + # J1 - Vertical interactions + fp.write("# J 1 (inter chain, vertical)\n") + write_boost_j_symmetric(fp, StdI.J0) + + # J2 - Nearest neighbor along chain + fp.write("# J 2 (Nearest neighbor, along chain)\n") + write_boost_j_symmetric(fp, StdI.J1) + + # J3 - Second nearest neighbor along chain + fp.write("# J 3 (Second nearest neighbor, along chain)\n") + write_boost_j_symmetric(fp, StdI.J1p) + + # J4 - Diagonal 1 + fp.write("# J 4 (inter chain, diagonal1)\n") + write_boost_j_symmetric(fp, StdI.J2) + + # J5 - Diagonal 2 + fp.write("# J 5 (inter chain, diagonal2)\n") + write_boost_j_symmetric(fp, StdI.J2p) + + # Validate parameters + if StdI.S2 != 1: + print("\n ERROR! S2 must be 1 in Boost. \n") + exit_program(-1) + StdI.ishift_nspin = 2 + if StdI.W != 2: + print("\n ERROR! W != 2 \n") + exit_program(-1) + if StdI.L % 2 != 0: + print("\n ERROR! L %% 2 != 0 \n") + exit_program(-1) + if StdI.L < 4: + print("\n ERROR! L < 4 \n") + exit_program(-1) + + StdI.W = StdI.L + StdI.L = 2 + StdI.num_pivot = StdI.W // 2 + + fp.write("# W0 R0 StdI->num_pivot StdI->ishift_nspin\n") + fp.write(f"{StdI.W} {StdI.L} {StdI.num_pivot} {StdI.ishift_nspin}\n") + + # 6-spin star list + StdI.list_6spin_star = np.zeros((StdI.num_pivot, 7), dtype=int) + for ipivot in range(StdI.num_pivot): + StdI.list_6spin_star[ipivot, :] = [7, 1, 1, 1, 1, 1, 1] + + write_boost_6spin_star(fp, StdI) + + # 6-spin pair list + StdI.list_6spin_pair = np.zeros((StdI.num_pivot, 7, 7), dtype=int) + for ipivot in range(StdI.num_pivot): + StdI.list_6spin_pair[ipivot, :, 0] = [0, 1, 2, 3, 4, 5, 1] + StdI.list_6spin_pair[ipivot, :, 1] = [0, 2, 1, 3, 4, 5, 2] + StdI.list_6spin_pair[ipivot, :, 2] = [1, 3, 0, 2, 4, 5, 2] + StdI.list_6spin_pair[ipivot, :, 3] = [0, 4, 1, 2, 3, 5, 3] + StdI.list_6spin_pair[ipivot, :, 4] = [1, 5, 0, 2, 3, 4, 3] + StdI.list_6spin_pair[ipivot, :, 5] = [0, 3, 1, 2, 4, 5, 4] + StdI.list_6spin_pair[ipivot, :, 6] = [1, 2, 0, 3, 4, 5, 5] + + write_boost_6spin_pair(fp, StdI) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + +_ladder_setup = ladder +_ladder_boost = ladder_boost + + +class LadderPlugin(LatticePlugin): + """Plugin for the 1D ladder lattice.""" + + @property + def name(self) -> str: + return "ladder" + + @property + def aliases(self) -> list[str]: + return ["ladder", "ladderlattice"] + + @property + def ndim(self) -> int: + return 1 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the ladder() function.""" + _ladder_setup(StdI) + + def boost(self, StdI: StdIntList) -> None: + """Delegate to the ladder_boost() function.""" + _ladder_boost(StdI) + + +register_lattice(LadderPlugin()) diff --git a/python/stdface/lattice/orthorhombic.py b/python/stdface/lattice/orthorhombic.py new file mode 100644 index 0000000..490f032 --- /dev/null +++ b/python/stdface/lattice/orthorhombic.py @@ -0,0 +1,215 @@ +""" +Standard mode for the orthorhombic lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + print_val_d, print_val_c, print_val_i, + not_used_j, not_used_d, not_used_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + compute_max_interactions, malloc_interactions, + add_neighbor_interaction_3d, add_local_terms, +) +from .site_util import init_site, set_local_spin_flags, close_lattice_xsf + + +def orthorhombic(StdI: StdIntList) -> None: + """Setup a Hamiltonian for the simple orthorhombic lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + # (1) Compute the shape of the super-cell and sites in the super-cell + StdI.NsiteUC = 1 + + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.length[2] = print_val_d("Hlength", StdI.length[2], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) + StdI.direct[0, 2] = print_val_d("Wz", StdI.direct[0, 2], 0.0) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.0) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1]) + StdI.direct[1, 2] = print_val_d("Lz", StdI.direct[1, 2], 0.0) + StdI.direct[2, 0] = print_val_d("Hx", StdI.direct[2, 0], 0.0) + StdI.direct[2, 1] = print_val_d("Hy", StdI.direct[2, 1], 0.0) + StdI.direct[2, 2] = print_val_d("Hz", StdI.direct[2, 2], StdI.length[2]) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + StdI.phase[2] = print_val_d("phase2", StdI.phase[2], 0.0) + + init_site(StdI, None, 3) + StdI.tau[0, 0] = 0.0 + StdI.tau[0, 1] = 0.0 + StdI.tau[0, 2] = 0.0 + + # (2) check & store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") + input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") + input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") + input_spin(StdI.Jpp, StdI.JppAll, "J''") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t", StdI.t) + not_used_d("t0", StdI.t0) + not_used_d("t1", StdI.t1) + not_used_d("t2", StdI.t2) + not_used_d("t'", StdI.tp) + not_used_d("t0'", StdI.t0p) + not_used_d("t1'", StdI.t1p) + not_used_d("t2'", StdI.t2p) + not_used_d("t''", StdI.tpp) + not_used_d("V", StdI.V) + not_used_d("V0", StdI.V0) + not_used_d("V1", StdI.V1) + not_used_d("V'", StdI.Vp) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") + StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") + StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") + StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") + StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") + StdI.tpp = print_val_c("tpp", StdI.tpp, 0.0) + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") + StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") + StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") + StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") + StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") + StdI.Vpp = print_val_d("Vpp", StdI.Vpp, 0.0) + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J0'", StdI.J0pAll, StdI.J0p) + not_used_j("J1'", StdI.J1pAll, StdI.J1p) + not_used_j("J2'", StdI.J2pAll, StdI.J2p) + not_used_j("J0''", StdI.J0ppAll, StdI.J0pp) + not_used_j("J1''", StdI.J1ppAll, StdI.J1pp) + not_used_j("J2''", StdI.J2ppAll, StdI.J2pp) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + else: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and number of sites + set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) + + # (4) Compute upper limit of Transfer & Interaction + # nn=3, nnn=6, nnnn=4 → n_bonds=13 + ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=3 + 6 + 4) + malloc_interactions(StdI, ntransMax, nintrMax) + + # (5) Set Transfer & Interaction + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + iH = StdI.Cell[kCell, 2] + + # Local term + isite = kCell + if StdI.model == ModelType.KONDO: + isite += StdI.NCell + add_local_terms(StdI, isite, kCell) + + # Neighbor bonds: (dW, dL, dH, site_i, site_j, J, t, V) + _BONDS = ( + # Nearest neighbor + (1, 0, 0, 0, 0, StdI.J0, StdI.t0, StdI.V0), # along W + (0, 1, 0, 0, 0, StdI.J1, StdI.t1, StdI.V1), # along L + (0, 0, 1, 0, 0, StdI.J2, StdI.t2, StdI.V2), # along H + # Second nearest neighbor + (0, 1, 1, 0, 0, StdI.J0p, StdI.t0p, StdI.V0p), # +L+H + (0, 1, -1, 0, 0, StdI.J0p, StdI.t0p, StdI.V0p), # +L-H + (1, 0, 1, 0, 0, StdI.J1p, StdI.t1p, StdI.V1p), # +H+W + (-1, 0, 1, 0, 0, StdI.J1p, StdI.t1p, StdI.V1p), # +H-W + (1, 1, 0, 0, 0, StdI.J2p, StdI.t2p, StdI.V2p), # +W+L + (1, -1, 0, 0, 0, StdI.J2p, StdI.t2p, StdI.V2p), # +W-L + # Third nearest neighbor + (1, 1, 1, 0, 0, StdI.Jpp, StdI.tpp, StdI.Vpp), # +W+L+H + (-1, 1, 1, 0, 0, StdI.Jpp, StdI.tpp, StdI.Vpp), # -W+L+H + (1, -1, 1, 0, 0, StdI.Jpp, StdI.tpp, StdI.Vpp), # +W-L+H + (1, 1, -1, 0, 0, StdI.Jpp, StdI.tpp, StdI.Vpp), # +W+L-H + ) + for dW, dL, dH, si, sj, J, t, V in _BONDS: + add_neighbor_interaction_3d( + StdI, iW, iL, iH, dW, dL, dH, si, sj, J, t, V) + + close_lattice_xsf(StdI) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + +_orthorhombic_setup = orthorhombic + + +class OrthorhombicPlugin(LatticePlugin): + """Plugin for the 3D simple orthorhombic lattice.""" + + @property + def name(self) -> str: + return "orthorhombic" + + @property + def aliases(self) -> list[str]: + return ["orthorhombic", "simpleorthorhombic", "cubic", "simplecubic"] + + @property + def ndim(self) -> int: + return 3 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the orthorhombic() function.""" + _orthorhombic_setup(StdI) + + +register_lattice(OrthorhombicPlugin()) diff --git a/python/stdface/lattice/pyrochlore.py b/python/stdface/lattice/pyrochlore.py new file mode 100644 index 0000000..bb70809 --- /dev/null +++ b/python/stdface/lattice/pyrochlore.py @@ -0,0 +1,230 @@ +""" +Standard mode for the pyrochlore lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + print_val_d, print_val_i, + not_used_j, not_used_d, not_used_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + compute_max_interactions, malloc_interactions, + mag_field, general_j, hubbard_local, + add_neighbor_interaction_3d, +) +from .site_util import init_site, set_local_spin_flags, close_lattice_xsf + + +def pyrochlore(StdI: StdIntList) -> None: + """Setup a Hamiltonian for the pyrochlore lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + # (1) Compute the shape of the super-cell and sites in the super-cell + StdI.NsiteUC = 4 + + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.length[2] = print_val_d("Hlength", StdI.length[2], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], 0.0) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.5 * StdI.length[1]) + StdI.direct[0, 2] = print_val_d("Wz", StdI.direct[0, 2], 0.5 * StdI.length[2]) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.5 * StdI.length[0]) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], 0.0) + StdI.direct[1, 2] = print_val_d("Lz", StdI.direct[1, 2], 0.5 * StdI.length[2]) + StdI.direct[2, 0] = print_val_d("Hx", StdI.direct[2, 0], 0.5 * StdI.length[0]) + StdI.direct[2, 1] = print_val_d("Hy", StdI.direct[2, 1], 0.5 * StdI.length[1]) + StdI.direct[2, 2] = print_val_d("Hz", StdI.direct[2, 2], 0.0) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + StdI.phase[2] = print_val_d("phase2", StdI.phase[2], 0.0) + + init_site(StdI, None, 3) + StdI.tau[0, 0] = 0.0 + StdI.tau[0, 1] = 0.0 + StdI.tau[0, 2] = 0.0 + StdI.tau[1, 0] = 0.5 + StdI.tau[1, 1] = 0.0 + StdI.tau[1, 2] = 0.0 + StdI.tau[2, 0] = 0.0 + StdI.tau[2, 1] = 0.5 + StdI.tau[2, 2] = 0.0 + StdI.tau[3, 0] = 0.0 + StdI.tau[3, 1] = 0.0 + StdI.tau[3, 2] = 0.5 + + # (2) check & store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") + input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") + input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") + input_spin_nn(StdI.J, StdI.JAll, StdI.J0p, StdI.J0pAll, "J0'") + input_spin_nn(StdI.J, StdI.JAll, StdI.J1p, StdI.J1pAll, "J1'") + input_spin_nn(StdI.J, StdI.JAll, StdI.J2p, StdI.J2pAll, "J2'") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t", StdI.t) + not_used_d("t0", StdI.t0) + not_used_d("t1", StdI.t1) + not_used_d("t2", StdI.t2) + not_used_d("t'", StdI.tp) + not_used_d("t0'", StdI.t0p) + not_used_d("t1'", StdI.t1p) + not_used_d("t2'", StdI.t2p) + not_used_d("t''", StdI.tpp) + not_used_d("V", StdI.V) + not_used_d("V0", StdI.V0) + not_used_d("V1", StdI.V1) + not_used_d("V'", StdI.Vp) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") + StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") + StdI.t0p = input_hopp(StdI.t, StdI.t0p, "t0'") + StdI.t1p = input_hopp(StdI.t, StdI.t1p, "t1'") + StdI.t2p = input_hopp(StdI.t, StdI.t2p, "t2'") + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") + StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") + StdI.V0p = input_coulomb_v(StdI.V, StdI.V0p, "V0'") + StdI.V1p = input_coulomb_v(StdI.V, StdI.V1p, "V1'") + StdI.V2p = input_coulomb_v(StdI.V, StdI.V2p, "V2'") + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J0'", StdI.J0pAll, StdI.J0p) + not_used_j("J1'", StdI.J1pAll, StdI.J1p) + not_used_j("J2'", StdI.J2pAll, StdI.J2p) + not_used_j("J''", StdI.JppAll, StdI.Jpp) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + else: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and number of sites + set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) + + # (4) Compute upper limit of Transfer & Interaction + # nn=12 → n_bonds=12 + ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=12) + malloc_interactions(StdI, ntransMax, nintrMax) + + # (5) Set Transfer & Interaction + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + iH = StdI.Cell[kCell, 2] + + # Local term + isite = StdI.NsiteUC * kCell + if StdI.model == ModelType.KONDO: + isite += StdI.nsite // 2 + + if StdI.model == ModelType.SPIN: + for isiteUC in range(StdI.NsiteUC): + mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, isite + isiteUC) + general_j(StdI, StdI.D, StdI.S2, StdI.S2, isite + isiteUC, isite + isiteUC) + else: + for isiteUC in range(StdI.NsiteUC): + hubbard_local(StdI, StdI.mu, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, StdI.U, isite + isiteUC) + if StdI.model == ModelType.KONDO: + jsite = StdI.NsiteUC * kCell + for isiteUC in range(StdI.NsiteUC): + general_j(StdI, StdI.J, 1, StdI.S2, isite + 3, jsite + isiteUC) + mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, jsite + isiteUC) + + # Neighbor bonds: (dW, dL, dH, site_i, site_j, J, t, V) + _BONDS = ( + # Intra-cell + (0, 0, 0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # along W + (0, 0, 0, 0, 2, StdI.J1, StdI.t1, StdI.V1), # along L + (0, 0, 0, 0, 3, StdI.J2, StdI.t2, StdI.V2), # along H + (0, 0, 0, 2, 3, StdI.J0p, StdI.t0p, StdI.V0p), # along L-H + (0, 0, 0, 3, 1, StdI.J1p, StdI.t1p, StdI.V1p), # along H-W + (0, 0, 0, 1, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along W-L + # Inter-cell + (1, 0, 0, 1, 0, StdI.J0, StdI.t0, StdI.V0), # along W + (0, 1, 0, 2, 0, StdI.J1, StdI.t1, StdI.V1), # along L + (0, 0, 1, 3, 0, StdI.J2, StdI.t2, StdI.V2), # along H + (0, -1, 1, 3, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along L-H + (1, 0, -1, 1, 3, StdI.J1p, StdI.t1p, StdI.V1p), # along H-W + (-1, 1, 0, 2, 1, StdI.J2p, StdI.t2p, StdI.V2p), # along W-L + ) + for dW, dL, dH, si, sj, J, t, V in _BONDS: + add_neighbor_interaction_3d( + StdI, iW, iL, iH, dW, dL, dH, si, sj, J, t, V) + + close_lattice_xsf(StdI) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + +_pyrochlore_setup = pyrochlore + + +class PyrochlorePlugin(LatticePlugin): + """Plugin for the 3D pyrochlore lattice.""" + + @property + def name(self) -> str: + return "pyrochlore" + + @property + def aliases(self) -> list[str]: + return ["pyrochlore"] + + @property + def ndim(self) -> int: + return 3 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the pyrochlore() function.""" + _pyrochlore_setup(StdI) + + +register_lattice(PyrochlorePlugin()) diff --git a/python/stdface/lattice/square_lattice.py b/python/stdface/lattice/square_lattice.py new file mode 100644 index 0000000..7180f7c --- /dev/null +++ b/python/stdface/lattice/square_lattice.py @@ -0,0 +1,201 @@ +""" +Standard mode for the tetragonal (square) lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + print_val_d, print_val_i, + not_used_j, not_used_d, not_used_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + compute_max_interactions, malloc_interactions, + add_neighbor_interaction, add_local_terms, +) +from .site_util import ( + init_site, set_label, set_local_spin_flags, + lattice_gp, +) + + +def tetragonal(StdI: StdIntList) -> None: + """Setup a Hamiltonian for the square (tetragonal) lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + # (1) Compute the shape of the super-cell and sites in the super-cell + with lattice_gp(StdI) as fp: + + StdI.NsiteUC = 1 + + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.0) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1]) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + + init_site(StdI, fp, 2) + StdI.tau[0, 0] = 0.0 + StdI.tau[0, 1] = 0.0 + StdI.tau[0, 2] = 0.0 + + # (2) check & store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J2'", StdI.J2pAll, StdI.J2p) + not_used_d("t2", StdI.t2) + not_used_d("t2'", StdI.t2p) + not_used_d("V2", StdI.V2) + not_used_d("V2'", StdI.V2p) + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") + input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J1pp, StdI.J1ppAll, "J1''") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t", StdI.t) + not_used_d("t0", StdI.t0) + not_used_d("t1", StdI.t1) + not_used_d("t'", StdI.tp) + not_used_d("t0'", StdI.t0p) + not_used_d("t1'", StdI.t1p) + not_used_d("t''", StdI.tpp) + not_used_d("t0''", StdI.t0pp) + not_used_d("t1''", StdI.t1pp) + not_used_d("V", StdI.V) + not_used_d("V0", StdI.V0) + not_used_d("V1", StdI.V1) + not_used_d("V'", StdI.Vp) + not_used_d("V0'", StdI.V0p) + not_used_d("V1'", StdI.V1p) + not_used_d("V''", StdI.Vpp) + not_used_d("V0''", StdI.V0pp) + not_used_d("V1''", StdI.V1pp) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") + StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") + StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") + StdI.t0pp = input_hopp(StdI.tpp, StdI.t0pp, "t0''") + StdI.t1pp = input_hopp(StdI.tpp, StdI.t1pp, "t1''") + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") + StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") + StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") + StdI.V0pp = input_coulomb_v(StdI.Vpp, StdI.V0pp, "V0''") + StdI.V1pp = input_coulomb_v(StdI.Vpp, StdI.V1pp, "V1''") + StdI.Vp = print_val_d("V'", StdI.Vp, 0.0) + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J'", StdI.JpAll, StdI.Jp) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + else: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and number of sites + set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) + + # (4) Compute upper limit of Transfer & Interaction and malloc them + # nn=2, nnn=2, nnnn=2 → n_bonds=6 + ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=2 + 2 + 2) + malloc_interactions(StdI, ntransMax, nintrMax) + + # (5) Set Transfer & Interaction + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + + # Local term + isite = kCell + if StdI.model == ModelType.KONDO: + isite += StdI.NCell + add_local_terms(StdI, isite, kCell) + + # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) + _BONDS = ( + (1, 0, 0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # nn along W + (0, 1, 0, 0, 1, StdI.J1, StdI.t1, StdI.V1), # nn along L + (1, 1, 0, 0, 2, StdI.J0p, StdI.t0p, StdI.V0p), # nnn W+L + (1, -1, 0, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # nnn W-L + (2, 0, 0, 0, 3, StdI.J0pp, StdI.t0pp, StdI.V0pp), # nnnn 2W + (0, 2, 0, 0, 3, StdI.J1pp, StdI.t1pp, StdI.V1pp), # nnnn 2L + ) + for dW, dL, si, sj, nn, J, t, V in _BONDS: + add_neighbor_interaction( + StdI, fp, iW, iL, dW, dL, si, sj, nn, J, t, V) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + + +class SquarePlugin(LatticePlugin): + """Plugin for the 2D tetragonal (square) lattice.""" + + @property + def name(self) -> str: + return "tetragonal" + + @property + def aliases(self) -> list[str]: + return ["tetragonal", "tetragonallattice", "square", "squarelattice"] + + @property + def ndim(self) -> int: + return 2 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the tetragonal() function.""" + tetragonal(StdI) + + +register_lattice(SquarePlugin()) diff --git a/python/stdface/lattice/triangular_lattice.py b/python/stdface/lattice/triangular_lattice.py new file mode 100644 index 0000000..cfce8c6 --- /dev/null +++ b/python/stdface/lattice/triangular_lattice.py @@ -0,0 +1,222 @@ +""" +Standard mode for the triangular lattice. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import math + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType +from ..core.param_check import ( + print_val_d, print_val_i, + not_used_j, not_used_d, not_used_i, +) +from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v +from .interaction_builder import ( + compute_max_interactions, malloc_interactions, + add_neighbor_interaction, add_local_terms, +) +from .site_util import ( + init_site, set_label, set_local_spin_flags, + lattice_gp, +) + + +def triangular(StdI: StdIntList) -> None: + """Setup a Hamiltonian for the triangular lattice. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + """ + # (1) Compute the shape of the super-cell and sites in the super-cell + with lattice_gp(StdI) as fp: + + StdI.NsiteUC = 1 + print(" @ Lattice Size & Shape\n") + + StdI.a = print_val_d("a", StdI.a, 1.0) + StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) + StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) + StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) + StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) + StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], StdI.length[1] * 0.5) + StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1] * 0.5 * math.sqrt(3.0)) + + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + + init_site(StdI, fp, 2) + StdI.tau[0, 0] = 0.0 + StdI.tau[0, 1] = 0.0 + StdI.tau[0, 2] = 0.0 + + # (2) check & store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) + input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") + input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") + input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") + input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J1pp, StdI.J1ppAll, "J1''") + input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J2pp, StdI.J2ppAll, "J2''") + + not_used_d("mu", StdI.mu) + not_used_d("U", StdI.U) + not_used_d("t", StdI.t) + not_used_d("t0", StdI.t0) + not_used_d("t1", StdI.t1) + not_used_d("t2", StdI.t2) + not_used_d("t'", StdI.tp) + not_used_d("t0'", StdI.t0p) + not_used_d("t1'", StdI.t1p) + not_used_d("t2'", StdI.t2p) + not_used_d("t''", StdI.tp) + not_used_d("t0''", StdI.t0pp) + not_used_d("t1''", StdI.t1pp) + not_used_d("t2''", StdI.t2pp) + not_used_d("V", StdI.V) + not_used_d("V0", StdI.V0) + not_used_d("V1", StdI.V1) + not_used_d("V2", StdI.V2) + not_used_d("V'", StdI.Vp) + not_used_d("V0'", StdI.V0p) + not_used_d("V1'", StdI.V1p) + not_used_d("V2'", StdI.V2p) + not_used_d("V''", StdI.Vpp) + not_used_d("V0''", StdI.V0pp) + not_used_d("V1''", StdI.V1pp) + not_used_d("V2''", StdI.V2pp) + else: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + StdI.U = print_val_d("U", StdI.U, 0.0) + StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") + StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") + StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") + StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") + StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") + StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") + StdI.t0pp = input_hopp(StdI.tpp, StdI.t0pp, "t0''") + StdI.t1pp = input_hopp(StdI.tpp, StdI.t1pp, "t1''") + StdI.t2pp = input_hopp(StdI.tpp, StdI.t2pp, "t2''") + StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") + StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") + StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") + StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") + StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") + StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") + StdI.V0pp = input_coulomb_v(StdI.Vpp, StdI.V0pp, "V0''") + StdI.V1pp = input_coulomb_v(StdI.Vpp, StdI.V1pp, "V1''") + StdI.V2pp = input_coulomb_v(StdI.Vpp, StdI.V2pp, "V2''") + + not_used_j("J0", StdI.J0All, StdI.J0) + not_used_j("J1", StdI.J1All, StdI.J1) + not_used_j("J2", StdI.J2All, StdI.J2) + not_used_j("J0'", StdI.J0pAll, StdI.J0p) + not_used_j("J1'", StdI.J1pAll, StdI.J1p) + not_used_j("J2'", StdI.J2pAll, StdI.J2p) + not_used_j("J0''", StdI.J0ppAll, StdI.J0pp) + not_used_j("J1''", StdI.J1ppAll, StdI.J1pp) + not_used_j("J2''", StdI.J2ppAll, StdI.J2pp) + not_used_d("D", StdI.D[2, 2]) + + if StdI.model == ModelType.HUBBARD: + not_used_i("2S", StdI.S2) + not_used_j("J", StdI.JAll, StdI.J) + else: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + input_spin(StdI.J, StdI.JAll, "J") + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and number of sites + set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) + + # (4) Compute upper limit of Transfer & Interaction + # nn=3, nnn=3, nnnn=3 → n_bonds=9 + ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=3 + 3 + 3) + malloc_interactions(StdI, ntransMax, nintrMax) + + # (5) Set Transfer & Interaction + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + + # Local term + isite = kCell + if StdI.model == ModelType.KONDO: + isite += StdI.NCell + add_local_terms(StdI, isite, kCell) + + # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) + _BONDS = ( + # Nearest neighbor (nn=1) + (1, 0, 0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # along W + (0, 1, 0, 0, 1, StdI.J1, StdI.t1, StdI.V1), # along L + (1, -1, 0, 0, 1, StdI.J2, StdI.t2, StdI.V2), # along W-L + # Second nearest neighbor (nn=2) + (2, -1, 0, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # 2W-L + (1, 1, 0, 0, 2, StdI.J2p, StdI.t2p, StdI.V2p), # W+L + (-1, 2, 0, 0, 2, StdI.J0p, StdI.t0p, StdI.V0p), # -W+2L + # Third nearest neighbor (nn=3) + (2, 0, 0, 0, 3, StdI.J0pp, StdI.t0pp, StdI.V0pp), # 2W + (0, 2, 0, 0, 3, StdI.J1pp, StdI.t1pp, StdI.V1pp), # 2L + (2, -2, 0, 0, 3, StdI.J2pp, StdI.t2pp, StdI.V2pp),# 2W-2L + ) + for dW, dL, si, sj, nn, J, t, V in _BONDS: + add_neighbor_interaction( + StdI, fp, iW, iL, dW, dL, si, sj, nn, J, t, V) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + + +class TriangularPlugin(LatticePlugin): + """Plugin for the 2D triangular lattice.""" + + @property + def name(self) -> str: + return "triangular" + + @property + def aliases(self) -> list[str]: + return ["triangular", "triangularlattice"] + + @property + def ndim(self) -> int: + return 2 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the triangular() function.""" + triangular(StdI) + + +register_lattice(TriangularPlugin()) diff --git a/python/stdface/lattice/wannier90.py b/python/stdface/lattice/wannier90.py new file mode 100644 index 0000000..b756760 --- /dev/null +++ b/python/stdface/lattice/wannier90.py @@ -0,0 +1,1350 @@ +""" +Standard mode for the Wannier90 interface. + +This module sets up the Hamiltonian for the Wannier90 ``*_hr.dat`` format, +supporting hopping, Coulomb, and Hund coupling terms read from +Wannier90/RESPACK output files. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import itertools +import math +import sys +from enum import IntEnum +from typing import NamedTuple, TextIO + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType, SolverType, NaN_i, UNSET_STRING, AMPLITUDE_EPS +from ..core.param_check import exit_program, print_val_d, print_val_i, not_used_d +from .geometry_output import print_geometry, print_xsf +from .interaction_builder import ( + malloc_interactions, mag_field, general_j, hubbard_local, hopping, coulomb, +) +from .site_util import init_site, find_site, set_local_spin_flags + + +# --------------------------------------------------------------------------- +# Internal helpers +# --------------------------------------------------------------------------- + + +def _check_in_box(rvec: np.ndarray, inverse_matrix: np.ndarray) -> bool: + """Check if a lattice vector is inside the unit cell box. + + Parameters + ---------- + rvec : numpy.ndarray + Integer lattice vector to check (shape ``(3,)``). + inverse_matrix : numpy.ndarray + Inverse of the cutoff lattice vectors (shape ``(3, 3)``). + + Returns + ------- + bool + True if inside the box, False otherwise. + """ + judge_vec = rvec @ inverse_matrix + return bool(np.all(np.abs(judge_vec) <= 1)) + + +def _skip_degeneracy_weights(fp: TextIO, n_wigner_seitz: int) -> None: + """Skip the degeneracy-weight lines in a Wannier90 ``*_hr.dat`` file. + + The weights are written as whitespace-separated integers, potentially + spanning multiple lines. This helper reads and discards exactly + *n_wigner_seitz* values. + + Parameters + ---------- + fp : TextIO + Open file positioned just after the ``nWSC`` header line. + n_wigner_seitz : int + Total number of Wigner-Seitz cells (degeneracy entries to skip). + """ + count = 0 + while count < n_wigner_seitz: + count += len(fp.readline().split()) + + +def _geometry_w90(StdI: StdIntList) -> None: + """Read Wannier90 geometry file. + + Reads lattice vectors and Wannier center positions from the geometry + file ``_geom.dat``. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. Modified in-place: + ``StdI.direct`` (lattice vectors) and ``StdI.tau`` (Wannier centres) + are populated. + """ + filename = f"{StdI.CDataFileHead}_geom.dat" + print(f" Wannier90 Geometry file = {filename}") + + try: + fp_geom = open(filename, "r") + except FileNotFoundError: + + print(f"\n Error: Fail to open the file {filename}. \n", file=sys.stderr) + exit_program(-1) + + with fp_geom: + # Read direct lattice vectors + for ii in range(3): + StdI.direct[ii, :] = [float(x) for x in fp_geom.readline().split()[:3]] + + # Read number of correlated sites + StdI.NsiteUC = int(fp_geom.readline().split()[0]) + print(f" Number of Correlated Sites = {StdI.NsiteUC}") + + # Allocate and read Wannier centre positions + StdI.tau = np.zeros((StdI.NsiteUC, 3)) + for isite in range(StdI.NsiteUC): + StdI.tau[isite, :] = [float(x) for x in fp_geom.readline().split()[:3]] + + print(" Direct lattice vectors:") + for row in StdI.direct: + print(f" {row[0]:10.5f} {row[1]:10.5f} {row[2]:10.5f}") + print(" Wannier centres:") + for tau_row in StdI.tau[:StdI.NsiteUC]: + print(f" {tau_row[0]:10.5f} {tau_row[1]:10.5f} {tau_row[2]:10.5f}") + + +def _apply_boundary_weights( + indx_tot: np.ndarray, + Weight_tot: np.ndarray, + nWSC: int, + StdI: StdIntList, +) -> np.ndarray: + """Apply boundary-halving weights at model lattice boundaries. + + For periodic models, matrix elements at the boundary of the model + super-cell are halved to avoid double-counting. + + Parameters + ---------- + indx_tot : numpy.ndarray + R-vector indices for each Wigner-Seitz cell, shape ``(nWSC, 3)``. + Weight_tot : numpy.ndarray + Weight array for each WSC, shape ``(nWSC,)``. Modified in-place. + nWSC : int + Number of Wigner-Seitz cells. + StdI : StdIntList + Structure containing model parameters (``W``, ``L``, ``Height``). + + Returns + ------- + numpy.ndarray + Band lattice extent for each dimension, shape ``(3,)``, dtype int. + """ + # Compute max absolute index per dimension + Band_lattice = np.max(np.abs(indx_tot[:nWSC]), axis=0).astype(int) + + if StdI.W != NaN_i and StdI.L != NaN_i and StdI.Height != NaN_i: + dims = np.array([StdI.W, StdI.L, StdI.Height], dtype=int) + # Model_lattice[i] = dims[i] // 2 if dims[i] is even, else 0 + Model_lattice = np.where(dims % 2 == 0, dims // 2, 0) + for ii in range(3): + if Model_lattice[ii] < Band_lattice[ii] and Model_lattice[ii] != 0: + # Apply 0.5 weight at boundary + mask = np.abs(indx_tot[:nWSC, ii]) == Model_lattice[ii] + Weight_tot[:nWSC][mask] *= 0.5 + + return Band_lattice + + +def _count_and_store_terms( + Mat_tot: np.ndarray, + indx_tot: np.ndarray, + Weight_tot: np.ndarray, + nWSC: int, + NsiteUC: int, + cutoff: float, + itUJ: int, + NtUJ: list[int], + tUJindx: list, + tUJ: list, +) -> None: + """Apply weights, count effective terms, and store surviving terms. + + Multiplies each matrix element by its weight, counts terms above the + cutoff threshold, prints them, and packs the surviving terms into + the output arrays ``tUJ`` and ``tUJindx``. + + Parameters + ---------- + Mat_tot : numpy.ndarray + Matrix elements, shape ``(nWSC, nWan, nWan)``, dtype complex. + indx_tot : numpy.ndarray + R-vector indices, shape ``(nWSC, 3)``, dtype int. + Weight_tot : numpy.ndarray + Per-WSC weights, shape ``(nWSC,)``. + nWSC : int + Number of Wigner-Seitz cells. + NsiteUC : int + Number of correlated sites in the unit cell. + cutoff : float + Threshold for matrix elements. + itUJ : int + Interaction type index (0=t, 1=U, 2=J). + NtUJ : list of int + Counts per interaction type. Modified in-place. + tUJindx : list + Index arrays. Modified in-place. + tUJ : list + Coefficient arrays. Modified in-place. + """ + # Apply weights: broadcast Weight_tot over Wannier indices + Mat_tot[:nWSC, :, :] *= Weight_tot[:nWSC, np.newaxis, np.newaxis] + + # Print and count effective terms + print("\n EFFECTIVE terms:") + print(" R0 R1 R2 band_i band_f Hamiltonian") + NtUJ[itUJ] = 0 + for iWSC in range(nWSC): + for iWan in range(NsiteUC): + for jWan in range(NsiteUC): + if cutoff < abs(Mat_tot[iWSC, iWan, jWan]): + print( + f" {indx_tot[iWSC, 0]:5d}{indx_tot[iWSC, 1]:5d}" + f"{indx_tot[iWSC, 2]:5d}{iWan:5d}{jWan:5d}" + f"{Mat_tot[iWSC, iWan, jWan].real:12.6f}" + f"{Mat_tot[iWSC, iWan, jWan].imag:12.6f}" + ) + NtUJ[itUJ] += 1 + print(f" Total number of EFFECTIVE term = {NtUJ[itUJ]}") + + # Extract surviving terms using numpy masking + abs_mat = np.abs(Mat_tot[:nWSC, :NsiteUC, :NsiteUC]) + mask = abs_mat > cutoff + wsc_idx, iwan_idx, jwan_idx = np.nonzero(mask) + + tUJ_arr = Mat_tot[wsc_idx, iwan_idx, jwan_idx].copy() + tUJindx_arr = np.column_stack([ + indx_tot[wsc_idx, :], + iwan_idx, + jwan_idx, + ]) + + # Extend the lists to hold the results + while len(tUJ) <= itUJ: + tUJ.append(None) + while len(tUJindx) <= itUJ: + tUJindx.append(None) + tUJ[itUJ] = tUJ_arr + tUJindx[itUJ] = tUJindx_arr + + +def _read_w90( + StdI: StdIntList, + filename: str, + cutoff: float, + cutoff_R: np.ndarray, + cutoff_Rvec: np.ndarray, + cutoff_length: float, + itUJ: int, + NtUJ: list[int], + tUJindx: list, + lam: float, + tUJ: list, +) -> None: + """Read Wannier90 hopping/interaction file. + + Reads hopping or interaction matrix elements from Wannier90 files, + applies cutoffs and stores non-zero terms. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. + filename : str + Input filename (e.g. ``*_hr.dat``, ``*_ur.dat``, ``*_jr.dat``). + cutoff : float + Threshold for matrix elements. + cutoff_R : numpy.ndarray + Cutoff for R vectors (shape ``(3,)``, int). + cutoff_Rvec : numpy.ndarray + Cutoff vectors for unit cell (shape ``(3, 3)``). + cutoff_length : float + Real-space cutoff length. + itUJ : int + Type of interaction (0: hopping t, 1: Coulomb U, 2: Hund J). + NtUJ : list of int + Number of terms for each interaction type. Modified in-place. + tUJindx : list + Indices for terms. Modified in-place. + lam : float + Scaling factor (lambda). + tUJ : list + Matrix elements. Modified in-place. + """ + flg_vec = int(cutoff_Rvec[0, 0] != NaN_i) + + # Try to open the file + try: + fp_hr = open(filename, "r") + except FileNotFoundError: + print(f"\n Skip to read the file {filename}. \n") + return + + with fp_hr: + # Header part + _header_line = fp_hr.readline() # comment line + nWan = int(fp_hr.readline().split()[0]) + nWSC = int(fp_hr.readline().split()[0]) + + # Skip degeneracy weights + _skip_degeneracy_weights(fp_hr, nWSC) + + # Allocate arrays + Weight_tot = np.ones(nWSC) + Mat_tot = np.zeros((nWSC, nWan, nWan), dtype=complex) + indx_tot = np.zeros((nWSC, 3), dtype=int) + + if flg_vec: + inverse_rvec = np.linalg.inv(cutoff_Rvec.astype(float)) + + # Read body + for iWSC in range(nWSC): + for iWan in range(nWan): + for jWan in range(nWan): + vals = fp_hr.readline().split() + indx_tot[iWSC, :] = [int(vals[0]), int(vals[1]), int(vals[2])] + iWan0 = int(vals[3]) + jWan0 = int(vals[4]) + dtmp_re = float(vals[5]) + dtmp_im = float(vals[6]) + # Compute Euclidean length + tau_diff = StdI.tau[jWan, :] - StdI.tau[iWan, :] + indx_tot[iWSC, :] + dR = StdI.direct.T @ tau_diff + length = np.linalg.norm(dR) + if length > cutoff_length > 0.0: + dtmp_re = 0.0 + dtmp_im = 0.0 + + if flg_vec: + if not _check_in_box(indx_tot[iWSC], inverse_rvec): + dtmp_re = 0.0 + dtmp_im = 0.0 + else: + if np.any(np.abs(indx_tot[iWSC]) > cutoff_R): + dtmp_re = 0.0 + dtmp_im = 0.0 + + if iWan0 <= StdI.NsiteUC and jWan0 <= StdI.NsiteUC: + Mat_tot[iWSC, iWan0 - 1, jWan0 - 1] = lam * (dtmp_re + 1j * dtmp_im) + + # Apply inversion symmetry and delete duplication + for jWSC in range(iWSC): + if np.all(indx_tot[iWSC] == -indx_tot[jWSC]): + Mat_tot[iWSC, :, :] = 0.0 + + if np.all(indx_tot[iWSC] == 0): + for iWan in range(StdI.NsiteUC): + Mat_tot[iWSC, iWan, :iWan] = 0.0 + + # Apply boundary-halving weights + _apply_boundary_weights(indx_tot, Weight_tot, nWSC, StdI) + + # Count effective terms, print summary, and store + _count_and_store_terms( + Mat_tot, indx_tot, Weight_tot, nWSC, + StdI.NsiteUC, cutoff, itUJ, NtUJ, tUJindx, tUJ, + ) + + +def _read_density_matrix( + StdI: StdIntList, + filename: str, +) -> dict[tuple[int, int, int], np.ndarray]: + """Read RESPACK density matrix file. + + Reads density matrix elements from a RESPACK output file. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. + filename : str + Input filename (e.g. ``*_dr.dat``). + + Returns + ------- + dict of tuple to numpy.ndarray + Dictionary mapping ``(R0, R1, R2)`` lattice vector tuples to + 2D numpy arrays of shape ``(NsiteUC, NsiteUC)`` containing the + density matrix elements. + """ + + + try: + fp_dr = open(filename, "r") + except FileNotFoundError: + print(f"\n Error: Fail to open the file {filename}. \n", file=sys.stderr) + exit_program(-1) + + with fp_dr: + # Header + _header_line = fp_dr.readline() + nWan = int(fp_dr.readline().split()[0]) + nWSC = int(fp_dr.readline().split()[0]) + + _skip_degeneracy_weights(fp_dr, nWSC) + + # Allocate + Mat_tot = np.zeros((nWSC, nWan, nWan), dtype=complex) + indx_tot = np.zeros((nWSC, 3), dtype=int) + + Rmin = np.zeros(3, dtype=int) + Rmax = np.zeros(3, dtype=int) + + # Read body + for iWSC in range(nWSC): + for iWan in range(nWan): + for jWan in range(nWan): + vals = fp_dr.readline().split() + indx_tot[iWSC, :] = [int(vals[0]), int(vals[1]), int(vals[2])] + iWan0 = int(vals[3]) + jWan0 = int(vals[4]) + dtmp_re = float(vals[5]) + dtmp_im = float(vals[6]) + + if iWan0 <= StdI.NsiteUC and jWan0 <= StdI.NsiteUC: + Mat_tot[iWSC, iWan0 - 1, jWan0 - 1] = dtmp_re + 1j * dtmp_im + Rmin = np.minimum(Rmin, indx_tot[iWSC]) + Rmax = np.maximum(Rmax, indx_tot[iWSC]) + + NR = Rmax - Rmin + 1 + print(f" Minimum R : {Rmin[0]} {Rmin[1]} {Rmin[2]}") + print(f" Maximum R : {Rmax[0]} {Rmax[1]} {Rmax[2]}") + print(f" Numver of R : {NR[0]} {NR[1]} {NR[2]}") + + # Build dictionary: (R0, R1, R2) -> 2D array + DenMat: dict[tuple[int, int, int], np.ndarray] = {} + for i0, i1, i2 in itertools.product( + range(Rmin[0], Rmax[0] + 1), + range(Rmin[1], Rmax[1] + 1), + range(Rmin[2], Rmax[2] + 1), + ): + DenMat[(i0, i1, i2)] = np.zeros( + (StdI.NsiteUC, StdI.NsiteUC), dtype=complex + ) + + for iWSC in range(nWSC): + key = tuple(indx_tot[iWSC].astype(int)) + DenMat[key][:, :] = Mat_tot[iWSC, :nWan, :nWan] + + return DenMat + + +def _print_uhf_initial( + StdI: StdIntList, + NtUJ: list[int], + tUJ: list[np.ndarray], + DenMat: dict[tuple[int, int, int], np.ndarray], + tUJindx: list[np.ndarray], +) -> None: + """Print initial UHF guess to ``initial.def``. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. + NtUJ : list of int + Number of terms for each interaction type. + tUJ : list of numpy.ndarray + Matrix elements. + DenMat : dict + Density matrix elements keyed by R-vector tuples. + tUJindx : list of numpy.ndarray + Indices for interaction terms. + """ + IniGuess = np.zeros((StdI.nsite, StdI.nsite), dtype=complex) + + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + iH = StdI.Cell[kCell, 2] + + # Diagonal term + for isite_uc in range(StdI.NsiteUC): + jsite = isite_uc + StdI.NsiteUC * kCell + IniGuess[jsite, jsite] = DenMat[(0, 0, 0)][isite_uc, isite_uc] + + # Coulomb integral (U) and Exchange integral (J) + for idx in (1, 2): + for it in range(NtUJ[idx]): + row = tUJindx[idx][it] + isite, jsite, Cphase, dR = find_site( + StdI, iW, iL, iH, + int(row[0]), int(row[1]), int(row[2]), + int(row[3]), int(row[4]), + ) + key = (int(row[0]), int(row[1]), int(row[2])) + dm_val = DenMat[key][int(row[3]), int(row[4])] + IniGuess[isite, jsite] = dm_val + IniGuess[jsite, isite] = np.conj(dm_val) + + mask = np.abs(IniGuess) > AMPLITUDE_EPS + NIniGuess = int(np.count_nonzero(mask)) + + with open("initial.def", "w") as fp: + fp.write("======================== \n") + fp.write(f"NInitialGuess {NIniGuess * 2:7d} \n") + fp.write("======================== \n") + fp.write("========i_j_s_tijs====== \n") + fp.write("======================== \n") + + rows, cols = np.nonzero(mask) + for isite, jsite in zip(rows, cols): + val = 0.5 * IniGuess[isite, jsite] + for ispin in range(2): + fp.write( + f"{jsite:5d} {ispin:5d} {isite:5d} {ispin:5d} " + f"{val.real:25.15f} " + f"{val.imag:25.15f}\n" + ) + + print(" initial.def is written.") + + +# --------------------------------------------------------------------------- +# Double-counting mode enum +# --------------------------------------------------------------------------- + + +class _DCMode(IntEnum): + """Double-counting correction mode.""" + NOTCORRECT = 0 + HARTREE = 1 + HARTREE_U = 2 + FULL = 3 + + +_DC_MODE_MAP: dict[str, _DCMode] = { + "none": _DCMode.NOTCORRECT, + UNSET_STRING: _DCMode.NOTCORRECT, + "hartree": _DCMode.HARTREE, + "hartree_u": _DCMode.HARTREE_U, + "full": _DCMode.FULL, +} +"""Maps double-counting mode strings to :class:`_DCMode` enum members. + +The sentinel :data:`UNSET_STRING` (``"****"``) is treated the same as +``"none"`` (no correction). +""" + + +def _parse_double_counting_mode(mode_str: str) -> _DCMode: + """Convert a double-counting mode string to the corresponding enum. + + Uses the :data:`_DC_MODE_MAP` dispatch table for lookup. + + Parameters + ---------- + mode_str : str + Mode specification from the input file. Recognised values (case + sensitive) are ``"none"``, ``"hartree"``, ``"hartree_u"`` and + ``"full"``. The sentinel :data:`UNSET_STRING` is treated the same + as ``"none"``. + + Returns + ------- + _DCMode + The matching enum member. + + Raises + ------ + SystemExit + If *mode_str* is not one of the recognised values. + """ + result = _DC_MODE_MAP.get(mode_str) + if result is not None: + return result + + + print( + "\n Error: the word of doublecounting is not correct " + "(select from none, hartree, hartree_u, full). \n", + file=sys.stderr, + ) + exit_program(-1) + + +# --------------------------------------------------------------------------- +# Cutoff-parameter setup + file read helper +# --------------------------------------------------------------------------- + + +def _read_w90_with_cutoff( + StdI: StdIntList, + label: str, + label_suffix: str, + file_suffix: str, + cutoff_val: float, + cutoff_length: float, + cutoff_R: np.ndarray, + cutoff_Vec: np.ndarray, + cutoff_length_default: float, + cutoff_R_defaults: tuple[int | None, int | None, int | None], + itUJ: int, + NtUJ: list[int], + tUJindx: list, + lam: float, + tUJ: list, +) -> tuple[float, float]: + """Set cutoff parameters and read a Wannier90 interaction file. + + Prints parameter values, sets cutoff thresholds for R-vectors and + real-space length, then calls :func:`_read_w90` to read the file. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. + label : str + Lowercase label for the cutoff threshold name + (e.g. ``"t"``, ``"u"``, ``"j"``). + label_suffix : str + Suffix for length/R/Vec parameter names (e.g. ``"t"``, ``"U"``, + ``"J"``). May differ in case from *label*. + file_suffix : str + File suffix including underscore (e.g. ``"_hr.dat"``). + cutoff_val : float + Current cutoff threshold value (may be NaN if unset). + cutoff_length : float + Current cutoff length value (may be NaN if unset). + cutoff_R : numpy.ndarray + Cutoff R-vector array (shape ``(3,)``, int). Modified in-place. + cutoff_Vec : numpy.ndarray + Cutoff vector matrix (shape ``(3, 3)``). Modified in-place. + cutoff_length_default : float + Default value for cutoff length if unset. + cutoff_R_defaults : tuple of (int or None) + Default values for cutoff_R[0], cutoff_R[1], cutoff_R[2]. + Use ``None`` to skip a dimension (leave unchanged). + itUJ : int + Interaction type index (0=hopping, 1=Coulomb, 2=Hund). + NtUJ : list of int + Number of terms per interaction type. Modified in-place. + tUJindx : list + Indices per interaction type. Modified in-place. + lam : float + Scaling factor (lambda). + tUJ : list + Matrix elements per interaction type. Modified in-place. + + Returns + ------- + tuple of (float, float) + Updated ``(cutoff_val, cutoff_length)`` after applying defaults. + """ + cutoff_name = f"cutoff_{label}" + cutoff_length_name = f"cutoff_length_{label_suffix}" + cutoff_R_name = f"cutoff_{label_suffix}R" + cutoff_Vec_name = f"cutoff_{label_suffix}Vec" + + cutoff_val = print_val_d(cutoff_name, cutoff_val, 1.0e-8) + cutoff_length = print_val_d(cutoff_length_name, cutoff_length, cutoff_length_default) + + for dim in range(3): + if cutoff_R_defaults[dim] is not None: + cutoff_R[dim] = print_val_i( + f"{cutoff_R_name}[{dim}]", int(cutoff_R[dim]), cutoff_R_defaults[dim] + ) + + for i in range(3): + for j in range(3): + if StdI.box[i, j] != NaN_i: + param_name = f"{cutoff_Vec_name}[{i}][{j}]" + cutoff_Vec[i, j] = print_val_d( + param_name, cutoff_Vec[i, j], float(StdI.box[i, j]) * 0.5 + ) + + filename = f"{StdI.CDataFileHead}{file_suffix}" + _read_w90( + StdI, filename, + cutoff_val, cutoff_R, cutoff_Vec, cutoff_length, + itUJ, NtUJ, tUJindx, lam, tUJ, + ) + + return cutoff_val, cutoff_length + + +# --------------------------------------------------------------------------- +# Per-cell interaction helpers +# --------------------------------------------------------------------------- + + +def _apply_hopping_terms( + StdI: StdIntList, + kCell: int, + iW: int, + iL: int, + iH: int, + NtUJ: list[int], + tUJ: list, + tUJindx: list, + Uspin: np.ndarray | None, +) -> None: + """Apply hopping transfer terms for one unit cell. + + Processes all hopping (t) terms for the unit cell at position + ``(iW, iL, iH)``. Local terms contribute on-site energies (Hubbard) + and non-local terms contribute either super-exchange (spin) or + hopping integrals (Hubbard). + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. Modified in-place. + kCell : int + Linear index of the current unit cell. + iW, iL, iH : int + Unit-cell coordinates. + NtUJ : list of int + Number of terms per interaction type. + tUJ : list + Matrix elements per interaction type. + tUJindx : list + Indices per interaction type. + Uspin : numpy.ndarray or None + On-site Coulomb values per orbital (spin model only). + """ + if tUJindx[0] is None: + return + for it in range(NtUJ[0]): + # Local term + if (tUJindx[0][it, 0] == 0 and tUJindx[0][it, 1] == 0 + and tUJindx[0][it, 2] == 0 + and tUJindx[0][it, 3] == tUJindx[0][it, 4]): + if StdI.model == ModelType.HUBBARD: + isite = StdI.NsiteUC * kCell + int(tUJindx[0][it, 3]) + for ispin in range(2): + StdI.trans[StdI.ntrans] = -tUJ[0][it] + StdI.transindx[StdI.ntrans, 0] = isite + StdI.transindx[StdI.ntrans, 1] = ispin + StdI.transindx[StdI.ntrans, 2] = isite + StdI.transindx[StdI.ntrans, 3] = ispin + StdI.ntrans += 1 + else: + # Non-local term + isite, jsite, Cphase, dR = find_site( + StdI, iW, iL, iH, + int(tUJindx[0][it, 0]), int(tUJindx[0][it, 1]), + int(tUJindx[0][it, 2]), + int(tUJindx[0][it, 3]), int(tUJindx[0][it, 4]), + ) + if StdI.model == ModelType.SPIN: + diag_val = ( + 2.0 * tUJ[0][it] * np.conj(tUJ[0][it]) + * (1.0 / Uspin[int(tUJindx[0][it, 3])] + + 1.0 / Uspin[int(tUJindx[0][it, 4])]) + ).real + Jtmp = np.diag([diag_val, diag_val, diag_val]) + general_j(StdI, Jtmp, StdI.S2, StdI.S2, isite, jsite) + else: + hopping(StdI, -Cphase * tUJ[0][it], jsite, isite, dR) + + +def _apply_coulomb_terms( + StdI: StdIntList, + kCell: int, + iW: int, + iL: int, + iH: int, + NtUJ: list[int], + tUJ: list, + tUJindx: list, + idcmode: _DCMode, + DenMat: dict[tuple[int, int, int], np.ndarray] | None, +) -> None: + """Apply Coulomb (U) interaction terms for one unit cell. + + Processes all Coulomb terms for the unit cell at position + ``(iW, iL, iH)``, including local intra-site Coulomb, non-local + inter-site Coulomb, and double-counting corrections when enabled. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. Modified in-place. + kCell : int + Linear index of the current unit cell. + iW, iL, iH : int + Unit-cell coordinates. + NtUJ : list of int + Number of terms per interaction type. + tUJ : list + Matrix elements per interaction type. + tUJindx : list + Indices per interaction type. + idcmode : _DCMode + Double-counting correction mode. + DenMat : dict or None + Density matrix elements keyed by R-vector tuples. + """ + if tUJindx[1] is None: + return + for it in range(NtUJ[1]): + # Local term + if (tUJindx[1][it, 0] == 0 and tUJindx[1][it, 1] == 0 + and tUJindx[1][it, 2] == 0 + and tUJindx[1][it, 3] == tUJindx[1][it, 4]): + StdI.Cintra[StdI.NCintra] = tUJ[1][it].real + StdI.CintraIndx[StdI.NCintra, 0] = ( + StdI.NsiteUC * kCell + int(tUJindx[1][it, 3]) + ) + StdI.NCintra += 1 + + # Double-counting correction + if idcmode != _DCMode.NOTCORRECT: + isite = StdI.NsiteUC * kCell + int(tUJindx[1][it, 3]) + for ispin in range(2): + DenMat0 = DenMat[(0, 0, 0)][ + int(tUJindx[1][it, 3]), int(tUJindx[1][it, 3]) + ] + StdI.trans[StdI.ntrans] = ( + StdI.alpha * tUJ[1][it].real * DenMat0 + ) + StdI.transindx[StdI.ntrans, 0] = isite + StdI.transindx[StdI.ntrans, 1] = ispin + StdI.transindx[StdI.ntrans, 2] = isite + StdI.transindx[StdI.ntrans, 3] = ispin + StdI.ntrans += 1 + else: + # Non-local term + isite, jsite, Cphase, dR = find_site( + StdI, iW, iL, iH, + int(tUJindx[1][it, 0]), int(tUJindx[1][it, 1]), + int(tUJindx[1][it, 2]), + int(tUJindx[1][it, 3]), int(tUJindx[1][it, 4]), + ) + coulomb(StdI, tUJ[1][it].real, isite, jsite) + + # Double-counting correction + if idcmode != _DCMode.NOTCORRECT: + for ispin in range(2): + # U_{Rij} D_{0jj} (Local) + DenMat0 = DenMat[(0, 0, 0)][ + int(tUJindx[1][it, 4]), int(tUJindx[1][it, 4]) + ] + StdI.trans[StdI.ntrans] = tUJ[1][it].real * DenMat0 + StdI.transindx[StdI.ntrans, 0] = isite + StdI.transindx[StdI.ntrans, 1] = ispin + StdI.transindx[StdI.ntrans, 2] = isite + StdI.transindx[StdI.ntrans, 3] = ispin + StdI.ntrans += 1 + + # U_{Rij} D_{0ii} (Local) + DenMat0 = DenMat[(0, 0, 0)][ + int(tUJindx[1][it, 3]), int(tUJindx[1][it, 3]) + ] + StdI.trans[StdI.ntrans] = tUJ[1][it].real * DenMat0 + StdI.transindx[StdI.ntrans, 0] = jsite + StdI.transindx[StdI.ntrans, 1] = ispin + StdI.transindx[StdI.ntrans, 2] = jsite + StdI.transindx[StdI.ntrans, 3] = ispin + StdI.ntrans += 1 + + # Hartree-Fock correction + if idcmode == _DCMode.FULL: + key = ( + int(tUJindx[1][it, 0]), + int(tUJindx[1][it, 1]), + int(tUJindx[1][it, 2]), + ) + DenMat0 = DenMat[key][ + int(tUJindx[1][it, 3]), int(tUJindx[1][it, 4]) + ] + hopping( + StdI, + -0.5 * Cphase * tUJ[1][it].real * DenMat0, + jsite, isite, dR, + ) + + +def _apply_hund_terms( + StdI: StdIntList, + kCell: int, + iW: int, + iL: int, + iH: int, + NtUJ: list[int], + tUJ: list, + tUJindx: list, + idcmode: _DCMode, + DenMat: dict[tuple[int, int, int], np.ndarray] | None, +) -> None: + """Apply Hund (J) coupling terms for one unit cell. + + Processes all Hund coupling terms for the unit cell at position + ``(iW, iL, iH)``, including exchange, pair-hopping (Hubbard), and + double-counting corrections when enabled. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. Modified in-place. + kCell : int + Linear index of the current unit cell. + iW, iL, iH : int + Unit-cell coordinates. + NtUJ : list of int + Number of terms per interaction type. + tUJ : list + Matrix elements per interaction type. + tUJindx : list + Indices per interaction type. + idcmode : _DCMode + Double-counting correction mode. + DenMat : dict or None + Density matrix elements keyed by R-vector tuples. + """ + if tUJindx[2] is None: + return + for it in range(NtUJ[2]): + # Local term should not be computed + if (tUJindx[2][it, 0] != 0 or tUJindx[2][it, 1] != 0 + or tUJindx[2][it, 2] != 0 + or tUJindx[2][it, 3] != tUJindx[2][it, 4]): + isite, jsite, Cphase, dR = find_site( + StdI, iW, iL, iH, + int(tUJindx[2][it, 0]), int(tUJindx[2][it, 1]), + int(tUJindx[2][it, 2]), + int(tUJindx[2][it, 3]), int(tUJindx[2][it, 4]), + ) + + StdI.Hund[StdI.NHund] = tUJ[2][it].real + StdI.HundIndx[StdI.NHund, 0] = isite + StdI.HundIndx[StdI.NHund, 1] = jsite + StdI.NHund += 1 + + if StdI.model == ModelType.HUBBARD: + StdI.Ex[StdI.NEx] = tUJ[2][it].real + StdI.ExIndx[StdI.NEx, 0] = isite + StdI.ExIndx[StdI.NEx, 1] = jsite + StdI.NEx += 1 + + StdI.PairHopp[StdI.NPairHopp] = tUJ[2][it].real + StdI.PHIndx[StdI.NPairHopp, 0] = isite + StdI.PHIndx[StdI.NPairHopp, 1] = jsite + StdI.NPairHopp += 1 + + # Double-counting correction + if idcmode != _DCMode.NOTCORRECT and idcmode != _DCMode.HARTREE_U: + for ispin in range(2): + # -0.5 J_{Rij} D_{0jj} + DenMat0 = DenMat[(0, 0, 0)][ + int(tUJindx[2][it, 4]), int(tUJindx[2][it, 4]) + ] + StdI.trans[StdI.ntrans] = ( + -(1.0 - StdI.alpha) * tUJ[2][it].real * DenMat0 + ) + StdI.transindx[StdI.ntrans, 0] = isite + StdI.transindx[StdI.ntrans, 1] = ispin + StdI.transindx[StdI.ntrans, 2] = isite + StdI.transindx[StdI.ntrans, 3] = ispin + StdI.ntrans += 1 + + # -0.5 J_{Rij} D_{0ii} + DenMat0 = DenMat[(0, 0, 0)][ + int(tUJindx[2][it, 3]), int(tUJindx[2][it, 3]) + ] + StdI.trans[StdI.ntrans] = ( + -(1.0 - StdI.alpha) * tUJ[2][it].real * DenMat0 + ) + StdI.transindx[StdI.ntrans, 0] = jsite + StdI.transindx[StdI.ntrans, 1] = ispin + StdI.transindx[StdI.ntrans, 2] = jsite + StdI.transindx[StdI.ntrans, 3] = ispin + StdI.ntrans += 1 + + # Hartree-Fock correction + if idcmode == _DCMode.FULL: + key = ( + int(tUJindx[2][it, 0]), + int(tUJindx[2][it, 1]), + int(tUJindx[2][it, 2]), + ) + DenMat0 = DenMat[key][ + int(tUJindx[2][it, 3]), int(tUJindx[2][it, 4]) + ] + hopping( + StdI, + 0.5 * Cphase * tUJ[2][it].real + * (DenMat0 + 2.0 * DenMat0.real), + jsite, isite, dR, + ) + else: + # spin model + if StdI.solver == SolverType.mVMC: + StdI.Ex[StdI.NEx] = tUJ[2][it].real + else: + StdI.Ex[StdI.NEx] = -tUJ[2][it].real + StdI.ExIndx[StdI.NEx, 0] = isite + StdI.ExIndx[StdI.NEx, 1] = jsite + StdI.NEx += 1 + + +# --------------------------------------------------------------------------- +# High-level helpers (called by wannier90) +# --------------------------------------------------------------------------- + + +def _validate_wannier_params(StdI: StdIntList) -> None: + """Check and store Hamiltonian parameters for the Wannier90 lattice. + + Validates model-specific parameters (``S2`` for spin, ``mu`` for Hubbard) + and reports unused parameters. Exits on unsupported Kondo model. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters. Modified in-place. + """ + not_used_d("K", StdI.K) + StdI.h = print_val_d("h", StdI.h, 0.0) + StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) + StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) + not_used_d("U", StdI.U) + + if StdI.model == ModelType.SPIN: + StdI.S2 = print_val_i("2S", StdI.S2, 1) + elif StdI.model == ModelType.HUBBARD: + StdI.mu = print_val_d("mu", StdI.mu, 0.0) + else: + print("wannier + Kondo is not available !") + exit_program(-1) + + +def _build_wannier_interactions( + StdI: StdIntList, + NtUJ: list[int], + tUJ: list, + tUJindx: list, + idcmode: _DCMode, + DenMat: dict | None, +) -> None: + """Allocate interaction arrays and populate transfer / interaction terms. + + Computes upper bounds for transfer and interaction arrays, allocates + memory via :func:`malloc_interactions`, then loops over super-cells to + apply hopping, Coulomb, and Hund terms. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice. Modified in-place. + NtUJ : list of int + Number of hopping / Coulomb / Hund terms (length 3). + tUJ : list + Complex coefficient arrays for hopping / Coulomb / Hund (length 3). + tUJindx : list + Index arrays for hopping / Coulomb / Hund (length 3). + idcmode : _DCMode + Double-counting mode enum. + DenMat : dict or None + Density matrix (keyed by ``(R0, R1, R2)``), or ``None``. + """ + # Compute upper limits for Transfer & Interaction arrays + if StdI.model == ModelType.SPIN: + ntransMax = StdI.nsite * (StdI.S2 + 1 + 2 * StdI.S2) + nintrMax = StdI.NCell * ( + StdI.NsiteUC + NtUJ[0] + NtUJ[1] + NtUJ[2] + ) * (3 * StdI.S2 + 1) * (3 * StdI.S2 + StdI.NsiteUC) + elif StdI.model == ModelType.HUBBARD: + ntransMax = StdI.NCell * 2 * ( + 2 * StdI.NsiteUC + NtUJ[0] * 2 + + NtUJ[1] * 2 * 3 + NtUJ[2] * 2 * 2 + ) + nintrMax = StdI.NCell * (NtUJ[1] + NtUJ[2] + StdI.NsiteUC) + + malloc_interactions(StdI, ntransMax, nintrMax) + + # For spin systems, compute super-exchange interaction on-site U + Uspin = None + if StdI.model == ModelType.SPIN: + Uspin = np.zeros(StdI.NsiteUC) + if tUJindx[1] is not None: + for it in range(NtUJ[1]): + if (tUJindx[1][it, 0] == 0 and tUJindx[1][it, 1] == 0 + and tUJindx[1][it, 2] == 0 + and tUJindx[1][it, 3] == tUJindx[1][it, 4]): + Uspin[int(tUJindx[1][it, 3])] = tUJ[1][it].real + + # Main cell loop — apply all interaction terms + for kCell in range(StdI.NCell): + iW = StdI.Cell[kCell, 0] + iL = StdI.Cell[kCell, 1] + iH = StdI.Cell[kCell, 2] + + # Local term + if StdI.model == ModelType.SPIN: + for isite in range(StdI.NsiteUC * kCell, StdI.NsiteUC * (kCell + 1)): + mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, isite) + else: + for isite in range(StdI.NsiteUC * kCell, StdI.NsiteUC * (kCell + 1)): + hubbard_local(StdI, StdI.mu, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, 0.0, isite) + + # Hopping + _apply_hopping_terms(StdI, kCell, iW, iL, iH, NtUJ, tUJ, tUJindx, Uspin) + + # Coulomb integral (U) + _apply_coulomb_terms(StdI, kCell, iW, iL, iH, NtUJ, tUJ, tUJindx, idcmode, DenMat) + + # Hund coupling (J) + _apply_hund_terms(StdI, kCell, iW, iL, iH, NtUJ, tUJ, tUJindx, idcmode, DenMat) + + +def _write_wan2site(StdI: StdIntList) -> None: + """Write ``wan2site.dat`` mapping Wannier orbitals to super-cell sites. + + Parameters + ---------- + StdI : StdIntList + Structure containing lattice and cell information. + """ + with open("wan2site.dat", "w") as fp: + fp.write("======================== \n") + fp.write(f"Total site number {StdI.NCell * StdI.NsiteUC:7d} \n") + fp.write("======================== \n") + fp.write("========site nx ny nz norb====== \n") + fp.write("======================== \n") + + for kCell in range(StdI.NCell): + nx = StdI.Cell[kCell, 0] + ny = StdI.Cell[kCell, 1] + nz = StdI.Cell[kCell, 2] + for it in range(StdI.NsiteUC): + isite = StdI.NsiteUC * kCell + it + fp.write(f"{isite:5d}{nx:5d}{ny:5d}{nz:5d}{it:5d}\n") + + +def _validate_interaction_params(StdI: StdIntList) -> None: + """Validate and default lambda/alpha interaction-strength parameters. + + Sets ``lambda_U``, ``lambda_J``, and ``alpha`` on *StdI*, using + ``lambda_`` as a shared default when it is not NaN. Exits if + any value is out of range. + + Parameters + ---------- + StdI : StdIntList + Parameter structure (modified in place). + + Raises + ------ + SystemExit + If ``lambda_U`` or ``lambda_J`` is negative, or ``alpha`` is + outside [0, 1]. + """ + if math.isnan(StdI.lambda_): + StdI.lambda_U = print_val_d("lambda_U", StdI.lambda_U, 1.0) + StdI.lambda_J = print_val_d("lambda_J", StdI.lambda_J, 1.0) + else: + StdI.lambda_U = print_val_d("lambda_U", StdI.lambda_U, StdI.lambda_) + StdI.lambda_J = print_val_d("lambda_J", StdI.lambda_J, StdI.lambda_) + + if StdI.lambda_U < 0.0 or StdI.lambda_J < 0.0: + print( + "\n Error: the value of lambda_U / lambda_J must be " + "greater than or equal to 0. \n", + file=sys.stderr, + ) + exit_program(-1) + + StdI.alpha = print_val_d("alpha", StdI.alpha, 0.5) + if StdI.alpha > 1.0 or StdI.alpha < 0.0: + print( + "\n Error: the value of alpha must be in the range 0<= alpha <= 1. \n", + file=sys.stderr, + ) + exit_program(-1) + + +# --------------------------------------------------------------------------- +# Main entry point +# --------------------------------------------------------------------------- + + +class _W90Channel(NamedTuple): + """Configuration for one Wannier90 interaction channel (hopping/Coulomb/Hund).""" + + label: str + key_lower: str + key_upper: str + file_suffix: str + cutoff_length_default: float + cutoff_R_defaults: tuple[int | None, ...] + itUJ: int + lam: float + + +# Mapping from channel key_lower to (cutoff_attr, cutoff_length_attr, cutoffR_attr, cutoffVec_attr) +_W90_FIELD_MAP: dict[str, tuple[str, str, str, str]] = { + "t": ("cutoff_t", "cutoff_length_t", "cutoff_tR", "cutoff_tVec"), + "u": ("cutoff_u", "cutoff_length_U", "cutoff_UR", "cutoff_UVec"), + "j": ("cutoff_j", "cutoff_length_J", "cutoff_JR", "cutoff_JVec"), +} + + +def _read_w90_channels( + StdI: StdIntList, + channels: tuple[_W90Channel, ...], + NtUJ: int, + tUJindx: np.ndarray, + tUJ: np.ndarray, +) -> None: + """Read Wannier90 interaction files for all channels. + + Parameters + ---------- + StdI : StdIntList + Standard interface data; cutoff fields are updated in place. + channels : tuple of _W90Channel + Channel configurations (hopping, Coulomb, Hund). + NtUJ : int + Number of interaction entries. + tUJindx : numpy.ndarray + Interaction index array. + tUJ : numpy.ndarray + Interaction value array. + """ + for ch in channels: + print(f"\n @ Wannier90 {ch.label} \n") + co_attr, cl_attr, cr_attr, cv_attr = _W90_FIELD_MAP[ch.key_lower] + cutoff, cutoff_length = _read_w90_with_cutoff( + StdI, ch.key_lower, ch.key_upper, ch.file_suffix, + getattr(StdI, co_attr), getattr(StdI, cl_attr), + getattr(StdI, cr_attr), getattr(StdI, cv_attr), + cutoff_length_default=ch.cutoff_length_default, + cutoff_R_defaults=ch.cutoff_R_defaults, + itUJ=ch.itUJ, NtUJ=NtUJ, tUJindx=tUJindx, lam=ch.lam, tUJ=tUJ, + ) + setattr(StdI, co_attr, cutoff) + setattr(StdI, cl_attr, cutoff_length) + + +def wannier90(StdI: StdIntList) -> None: + """Set up a Hamiltonian for the Wannier90 ``*_hr.dat``. + + Parameters + ---------- + StdI : StdIntList + Structure containing model parameters and lattice information. + Modified in-place. + + Notes + ----- + This function performs the following steps: + + 1. Compute the shape of the super-cell and sites in the super-cell. + 2. Read Wannier90 geometry, hopping, Coulomb and Hund files. + 3. Validate and store Hamiltonian parameters. + 4. Set local spin flags and number of sites. + 5. Allocate memory for interactions. + 6. Set up transfers and interactions between sites. + 7. Write ``lattice.xsf``, ``geometry.dat`` and ``wan2site.dat``. + """ + NtUJ = [0, 0, 0] + tUJ: list = [None, None, None] + tUJindx: list = [None, None, None] + + # (1) Compute the shape of the super-cell and sites in the super-cell + with open("lattice.xsf", "w") as fp_xsf: + StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) + StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) + StdI.phase[2] = print_val_d("phase2", StdI.phase[2], 0.0) + StdI.NsiteUC = 1 + init_site(StdI, fp_xsf, 3) + print("\n @ Wannier90 Geometry \n") + _geometry_w90(StdI) + + _validate_interaction_params(StdI) + idcmode = _parse_double_counting_mode(StdI.double_counting_mode) + + # Read hopping, Coulomb, and Hund interaction files + hopping_R_defaults = ( + (StdI.W - 1) // 2 if StdI.W != NaN_i else None, + (StdI.L - 1) // 2 if StdI.L != NaN_i else None, + (StdI.Height - 1) // 2 if StdI.Height != NaN_i else None, + ) + _W90_CHANNELS = ( + _W90Channel("hopping", "t", "t", "_hr.dat", -1.0, hopping_R_defaults, 0, 1.0), + _W90Channel("Coulomb", "u", "U", "_ur.dat", 0.3, (0, 0, 0), 1, StdI.lambda_U), + _W90Channel("Hund", "j", "J", "_jr.dat", 0.3, (0, 0, 0), 2, StdI.lambda_J), + ) + _read_w90_channels(StdI, _W90_CHANNELS, NtUJ, tUJindx, tUJ) + + # Read Density matrix + DenMat = None + if idcmode != _DCMode.NOTCORRECT: + print("\n @ Wannier90 Density-matrix \n") + filename = f"{StdI.CDataFileHead}_dr.dat" + DenMat = _read_density_matrix(StdI, filename) + + # (2) Check and store parameters of Hamiltonian + print("\n @ Hamiltonian \n") + _validate_wannier_params(StdI) + + print("\n @ Numerical conditions\n") + + # (3) Set local spin flag and number of sites + set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) + + # (4)-(5) Allocate arrays and populate transfer / interaction terms + _build_wannier_interactions(StdI, NtUJ, tUJ, tUJindx, idcmode, DenMat) + + if idcmode != _DCMode.NOTCORRECT: + _print_uhf_initial(StdI, NtUJ, tUJ, DenMat, tUJindx) + print_xsf(StdI) + print_geometry(StdI) + + # Write wan2site.dat + _write_wan2site(StdI) + + +# --------------------------------------------------------------------------- +# Lattice plugin registration +# --------------------------------------------------------------------------- + +from . import LatticePlugin, register_lattice + +_wannier90_setup = wannier90 + + +class Wannier90Plugin(LatticePlugin): + """Plugin for the Wannier90 interface.""" + + @property + def name(self) -> str: + return "wannier90" + + @property + def aliases(self) -> list[str]: + return ["wannier90"] + + @property + def ndim(self) -> int: + return 3 + + def setup(self, StdI: StdIntList) -> None: + """Delegate to the wannier90() function.""" + _wannier90_setup(StdI) + + +register_lattice(Wannier90Plugin()) diff --git a/python/stdface/solvers/hphi/_plugin.py b/python/stdface/solvers/hphi/_plugin.py new file mode 100644 index 0000000..dd878e6 --- /dev/null +++ b/python/stdface/solvers/hphi/_plugin.py @@ -0,0 +1,143 @@ +"""HPhi solver plugin. + +Encapsulates all HPhi-specific keyword parsing, field reset tables, +post-lattice hooks (LargeValue, Boost), and Expert-mode file writing. +""" +from __future__ import annotations + +from ...plugin import SolverPlugin, register +from ...core.stdface_vals import StdIntList, SolverType, MethodType, NaN_i, NaN_d +from ...core.keyword_parser import ( + store_with_check_dup_s, store_with_check_dup_sl, + store_with_check_dup_i, store_with_check_dup_d, +) +from .writer import large_value, print_calc_mod, print_excitation, print_pump + + +class HPhiPlugin(SolverPlugin): + """Plugin for the HPhi exact-diagonalisation / Lanczos solver.""" + + @property + def name(self) -> str: + return SolverType.HPhi + + @property + def keyword_table(self) -> dict[str, tuple]: + return _HPHI_KEYWORDS + + @property + def reset_scalars(self) -> list[tuple[str, object]]: + return _RESET_SCALARS + + @property + def reset_arrays(self) -> list[tuple[str, object]]: + return _RESET_ARRAYS + + def post_lattice(self, StdI: StdIntList) -> None: + """Compute LargeValue and optionally run Boost builder.""" + from ...lattice import get_lattice + from ...writer.common_writer import unsupported_system + + large_value(StdI) + + if StdI.lBoost == 1: + try: + lattice_plugin = get_lattice(StdI.lattice) + except KeyError: + unsupported_system(StdI.model, StdI.lattice) + else: + lattice_plugin.boost(StdI) + + def write_solver_specific(self, StdI: StdIntList) -> None: + """Write HPhi-specific files (excitation, pump, calcmod).""" + print_excitation(StdI) + if StdI.method == MethodType.TIME_EVOLUTION: + print_pump(StdI) + print_calc_mod(StdI) + + +# ----------------------------------------------------------------------- +# Keyword table +# ----------------------------------------------------------------------- + +_HPHI_KEYWORDS: dict[str, tuple] = { + "calcspec": (store_with_check_dup_sl, "CalcSpec"), + "exct": (store_with_check_dup_i, "exct"), + "eigenvecio": (store_with_check_dup_sl, "EigenVecIO"), + "expandcoef": (store_with_check_dup_i, "ExpandCoef"), + "expecinterval": (store_with_check_dup_i, "ExpecInterval"), + "cdatafilehead": (store_with_check_dup_s, "CDataFileHead"), + "dt": (store_with_check_dup_d, "dt"), + "flgtemp": (store_with_check_dup_i, "FlgTemp"), + "freq": (store_with_check_dup_d, "freq"), + "hamio": (store_with_check_dup_sl, "HamIO"), + "initialvectype": (store_with_check_dup_sl, "InitialVecType"), + "initial_iv": (store_with_check_dup_i, "initial_iv"), + "lanczoseps": (store_with_check_dup_i, "LanczosEps"), + "lanczostarget": (store_with_check_dup_i, "LanczosTarget"), + "lanczos_max": (store_with_check_dup_i, "Lanczos_max"), + "largevalue": (store_with_check_dup_d, "LargeValue"), + "method": (store_with_check_dup_sl, "method"), + "nomega": (store_with_check_dup_i, "Nomega"), + "numave": (store_with_check_dup_i, "NumAve"), + "nvec": (store_with_check_dup_i, "nvec"), + "omegamax": (store_with_check_dup_d, "OmegaMax"), + "omegamin": (store_with_check_dup_d, "OmegaMin"), + "omegaorg": (store_with_check_dup_d, "OmegaOrg"), + "omegaim": (store_with_check_dup_d, "OmegaIm"), + "outputexcitedvec": (store_with_check_dup_sl, "OutputExVec"), + "pumptype": (store_with_check_dup_sl, "PumpType"), + "restart": (store_with_check_dup_sl, "Restart"), + "spectrumqh": (store_with_check_dup_d, "SpectrumQ", 2, float), + "spectrumql": (store_with_check_dup_d, "SpectrumQ", 1, float), + "spectrumqw": (store_with_check_dup_d, "SpectrumQ", 0, float), + "spectrumtype": (store_with_check_dup_sl, "SpectrumType"), + "tdump": (store_with_check_dup_d, "tdump"), + "tshift": (store_with_check_dup_d, "tshift"), + "uquench": (store_with_check_dup_d, "Uquench"), + "vecpoth": (store_with_check_dup_d, "VecPot", 2, float), + "vecpotl": (store_with_check_dup_d, "VecPot", 1, float), + "vecpotw": (store_with_check_dup_d, "VecPot", 0, float), + "2s": (store_with_check_dup_i, "S2"), + "ngpu": (store_with_check_dup_i, "NGPU"), + "scalapack": (store_with_check_dup_i, "Scalapack"), +} + +# ----------------------------------------------------------------------- +# Reset tables +# ----------------------------------------------------------------------- + +_RESET_SCALARS: list[tuple[str, object]] = [ + ("LargeValue", NaN_d), + ("OmegaMax", NaN_d), + ("OmegaMin", NaN_d), + ("OmegaOrg", NaN_d), + ("OmegaIm", NaN_d), + ("Nomega", NaN_i), + ("FlgTemp", 1), + ("Lanczos_max", NaN_i), + ("initial_iv", NaN_i), + ("nvec", NaN_i), + ("exct", NaN_i), + ("LanczosEps", NaN_i), + ("LanczosTarget", NaN_i), + ("NumAve", NaN_i), + ("ExpecInterval", NaN_i), + ("dt", NaN_d), + ("tdump", NaN_d), + ("tshift", NaN_d), + ("freq", NaN_d), + ("Uquench", NaN_d), + ("ExpandCoef", NaN_i), + ("NGPU", NaN_i), + ("Scalapack", NaN_i), +] + +_RESET_ARRAYS: list[tuple[str, object]] = [ + ("SpectrumQ", NaN_d), + ("VecPot", NaN_d), +] + + +# Auto-register on import +register(HPhiPlugin()) diff --git a/stdface b/stdface new file mode 100755 index 0000000..cdb7f85 --- /dev/null +++ b/stdface @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# StdFace entry point +# Usage: +# ./stdface stan.in +# ./stdface stan.in --solver mVMC +# ./stdface -v +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PYTHONPATH="${SCRIPT_DIR}/python" exec python3 -m stdface "$@" diff --git a/test/unit/test_lattice_dispatch.py b/test/unit/test_lattice_dispatch.py index 56d94b9..88079c5 100644 --- a/test/unit/test_lattice_dispatch.py +++ b/test/unit/test_lattice_dispatch.py @@ -1,126 +1,143 @@ -"""Unit tests for lattice dispatch tables. +"""Unit tests for lattice dispatch tables and plugin registry. -Tests for the ``LATTICE_DISPATCH`` and ``BOOST_DISPATCH`` dict tables -in ``stdface_main``. +Tests for the lattice plugin registry and backward-compatible +``LATTICE_DISPATCH`` and ``BOOST_DISPATCH`` proxies in ``stdface_main``. """ from __future__ import annotations import pytest -from stdface_main import LATTICE_DISPATCH, BOOST_DISPATCH +from stdface.core.stdface_main import LATTICE_DISPATCH, BOOST_DISPATCH +from stdface.lattice import get_lattice, get_all_lattices, LatticePlugin -from lattice import chain_lattice -from lattice import square_lattice -from lattice import ladder -from lattice import triangular_lattice -from lattice import honeycomb_lattice -from lattice import kagome -from lattice import orthorhombic -from lattice import fc_ortho -from lattice import pyrochlore -from lattice import wannier90 as wannier90_mod +class TestLatticeRegistry: + """Tests for the lattice plugin registry.""" -class TestLatticeDispatch: - """Tests for the LATTICE_DISPATCH table.""" + def test_get_chain(self): + plugin = get_lattice("chain") + assert plugin.name == "chain" + assert plugin.ndim == 1 - def test_chain_aliases(self): - """Test that chain aliases all resolve to chain_lattice.chain.""" - assert LATTICE_DISPATCH["chain"] is chain_lattice.chain - assert LATTICE_DISPATCH["chainlattice"] is chain_lattice.chain + def test_get_chain_alias(self): + assert get_lattice("chain") is get_lattice("chainlattice") + + def test_get_square(self): + plugin = get_lattice("tetragonal") + assert plugin.name == "tetragonal" + assert plugin.ndim == 2 def test_square_aliases(self): - """Test that square/tetragonal aliases resolve correctly.""" - assert LATTICE_DISPATCH["tetragonal"] is square_lattice.tetragonal - assert LATTICE_DISPATCH["tetragonallattice"] is square_lattice.tetragonal - assert LATTICE_DISPATCH["square"] is square_lattice.tetragonal - assert LATTICE_DISPATCH["squarelattice"] is square_lattice.tetragonal - - def test_ladder_aliases(self): - """Test that ladder aliases resolve correctly.""" - assert LATTICE_DISPATCH["ladder"] is ladder.ladder - assert LATTICE_DISPATCH["ladderlattice"] is ladder.ladder - - def test_triangular_aliases(self): - """Test that triangular aliases resolve correctly.""" - assert LATTICE_DISPATCH["triangular"] is triangular_lattice.triangular - assert LATTICE_DISPATCH["triangularlattice"] is triangular_lattice.triangular - - def test_honeycomb_aliases(self): - """Test that honeycomb aliases resolve correctly.""" - assert LATTICE_DISPATCH["honeycomb"] is honeycomb_lattice.honeycomb - assert LATTICE_DISPATCH["honeycomblattice"] is honeycomb_lattice.honeycomb - - def test_kagome_aliases(self): - """Test that kagome aliases resolve correctly.""" - assert LATTICE_DISPATCH["kagome"] is kagome.kagome - assert LATTICE_DISPATCH["kagomelattice"] is kagome.kagome + p = get_lattice("tetragonal") + assert get_lattice("tetragonallattice") is p + assert get_lattice("square") is p + assert get_lattice("squarelattice") is p + + def test_get_triangular(self): + plugin = get_lattice("triangular") + assert plugin.name == "triangular" + assert plugin.ndim == 2 + + def test_get_honeycomb(self): + plugin = get_lattice("honeycomb") + assert plugin.name == "honeycomb" + assert plugin.ndim == 2 + + def test_get_kagome(self): + plugin = get_lattice("kagome") + assert plugin.name == "kagome" + assert plugin.ndim == 2 + + def test_get_ladder(self): + plugin = get_lattice("ladder") + assert plugin.name == "ladder" + assert plugin.ndim == 1 + + def test_get_orthorhombic(self): + plugin = get_lattice("orthorhombic") + assert plugin.name == "orthorhombic" + assert plugin.ndim == 3 def test_orthorhombic_aliases(self): - """Test that orthorhombic/cubic aliases resolve correctly.""" - assert LATTICE_DISPATCH["orthorhombic"] is orthorhombic.orthorhombic - assert LATTICE_DISPATCH["simpleorthorhombic"] is orthorhombic.orthorhombic - assert LATTICE_DISPATCH["cubic"] is orthorhombic.orthorhombic - assert LATTICE_DISPATCH["simplecubic"] is orthorhombic.orthorhombic + p = get_lattice("orthorhombic") + assert get_lattice("simpleorthorhombic") is p + assert get_lattice("cubic") is p + assert get_lattice("simplecubic") is p + + def test_get_fco(self): + plugin = get_lattice("fco") + assert plugin.name == "fco" + assert plugin.ndim == 3 def test_fco_aliases(self): - """Test that face-centered orthorhombic aliases resolve correctly.""" - assert LATTICE_DISPATCH["face-centeredorthorhombic"] is fc_ortho.fc_ortho - assert LATTICE_DISPATCH["fcorthorhombic"] is fc_ortho.fc_ortho - assert LATTICE_DISPATCH["fco"] is fc_ortho.fc_ortho - assert LATTICE_DISPATCH["face-centeredcubic"] is fc_ortho.fc_ortho - assert LATTICE_DISPATCH["fccubic"] is fc_ortho.fc_ortho - assert LATTICE_DISPATCH["fcc"] is fc_ortho.fc_ortho - - def test_pyrochlore(self): - """Test that pyrochlore resolves correctly.""" - assert LATTICE_DISPATCH["pyrochlore"] is pyrochlore.pyrochlore - - def test_wannier90(self): - """Test that wannier90 resolves correctly.""" - assert LATTICE_DISPATCH["wannier90"] is wannier90_mod.wannier90 - - def test_unknown_returns_none(self): - """Test that unknown lattice returns None via .get().""" + p = get_lattice("fco") + assert get_lattice("face-centeredorthorhombic") is p + assert get_lattice("fcorthorhombic") is p + assert get_lattice("face-centeredcubic") is p + assert get_lattice("fccubic") is p + assert get_lattice("fcc") is p + + def test_get_pyrochlore(self): + plugin = get_lattice("pyrochlore") + assert plugin.name == "pyrochlore" + assert plugin.ndim == 3 + + def test_get_wannier90(self): + plugin = get_lattice("wannier90") + assert plugin.name == "wannier90" + assert plugin.ndim == 3 + + def test_unknown_raises(self): + with pytest.raises(KeyError): + get_lattice("nosuchlattice") + + def test_all_plugins_are_lattice_plugins(self): + for plugin in get_all_lattices(): + assert isinstance(plugin, LatticePlugin) + + def test_all_have_setup(self): + for plugin in get_all_lattices(): + assert callable(plugin.setup) + + def test_all_have_boost(self): + for plugin in get_all_lattices(): + assert callable(plugin.boost) + + def test_get_all_lattices_count(self): + """There should be 10 unique lattice plugins.""" + assert len(get_all_lattices()) == 10 + + +class TestLatticeDispatchProxy: + """Tests for backward-compatible LATTICE_DISPATCH proxy.""" + + def test_chain_callable(self): + assert callable(LATTICE_DISPATCH["chain"]) + + def test_contains(self): + assert "chain" in LATTICE_DISPATCH + assert "nosuchlattice" not in LATTICE_DISPATCH + + def test_get_returns_none_for_unknown(self): assert LATTICE_DISPATCH.get("nosuchlattice") is None def test_all_entries_callable(self): - """Test that every entry in the dispatch table is callable.""" for name, func in LATTICE_DISPATCH.items(): assert callable(func), f"LATTICE_DISPATCH[{name!r}] is not callable" -class TestBoostDispatch: - """Tests for the BOOST_DISPATCH table.""" - - def test_chain_boost(self): - """Test chain boost alias.""" - assert BOOST_DISPATCH["chain"] is chain_lattice.chain_boost - assert BOOST_DISPATCH["chainlattice"] is chain_lattice.chain_boost - - def test_honeycomb_boost(self): - """Test honeycomb boost alias.""" - assert BOOST_DISPATCH["honeycomb"] is honeycomb_lattice.honeycomb_boost - assert BOOST_DISPATCH["honeycomblattice"] is honeycomb_lattice.honeycomb_boost - - def test_kagome_boost(self): - """Test kagome boost alias.""" - assert BOOST_DISPATCH["kagome"] is kagome.kagome_boost - assert BOOST_DISPATCH["kagomelattice"] is kagome.kagome_boost +class TestBoostDispatchProxy: + """Tests for backward-compatible BOOST_DISPATCH proxy.""" - def test_ladder_boost(self): - """Test ladder boost alias.""" - assert BOOST_DISPATCH["ladder"] is ladder.ladder_boost - assert BOOST_DISPATCH["ladderlattice"] is ladder.ladder_boost + def test_chain_boost_callable(self): + assert callable(BOOST_DISPATCH["chain"]) def test_unsupported_lattice_not_in_boost(self): - """Test that lattices without boost are not in BOOST_DISPATCH.""" assert "square" not in BOOST_DISPATCH assert "triangular" not in BOOST_DISPATCH assert "pyrochlore" not in BOOST_DISPATCH assert "wannier90" not in BOOST_DISPATCH def test_all_entries_callable(self): - """Test that every entry in the boost table is callable.""" for name, func in BOOST_DISPATCH.items(): assert callable(func), f"BOOST_DISPATCH[{name!r}] is not callable" From 456c56145d658af106381789e122e7a393c68082 Mon Sep 17 00:00:00 2001 From: Kazuyoshi Yoshimi Date: Thu, 29 Jan 2026 20:17:42 +0900 Subject: [PATCH 02/16] Add missing stdface package files to repository The previous commit included only the files modified for the lattice plugin architecture but omitted the rest of the stdface package (core modules, solver plugins, writers) that had been created in earlier refactoring steps but never committed. This caused ModuleNotFoundError on CI. Co-Authored-By: Claude Opus 4.5 --- python/stdface/__init__.py | 6 + python/stdface/__main__.py | 80 ++ python/stdface/core/__init__.py | 2 + python/stdface/core/keyword_parser.py | 646 ++++++++++ python/stdface/core/param_check.py | 274 ++++ python/stdface/core/stdface_model_util.py | 75 ++ python/stdface/core/stdface_vals.py | 877 +++++++++++++ python/stdface/core/version.py | 45 + python/stdface/lattice/boost_output.py | 147 +++ python/stdface/lattice/geometry_output.py | 145 +++ python/stdface/lattice/input_params.py | 301 +++++ python/stdface/lattice/interaction_builder.py | 719 +++++++++++ python/stdface/lattice/site_util.py | 659 ++++++++++ python/stdface/plugin.py | 265 ++++ python/stdface/solvers/__init__.py | 7 + python/stdface/solvers/hphi/__init__.py | 9 + python/stdface/solvers/hphi/writer.py | 1100 ++++++++++++++++ python/stdface/solvers/hwave/__init__.py | 9 + python/stdface/solvers/hwave/_plugin.py | 114 ++ .../stdface/solvers/hwave/export_wannier90.py | 899 +++++++++++++ python/stdface/solvers/mvmc/__init__.py | 9 + python/stdface/solvers/mvmc/_plugin.py | 127 ++ python/stdface/solvers/mvmc/variational.py | 485 +++++++ python/stdface/solvers/mvmc/writer.py | 466 +++++++ python/stdface/solvers/uhf/__init__.py | 9 + python/stdface/solvers/uhf/_plugin.py | 84 ++ python/stdface/writer/__init__.py | 8 + python/stdface/writer/common_writer.py | 1136 +++++++++++++++++ python/stdface/writer/interaction_writer.py | 534 ++++++++ 29 files changed, 9237 insertions(+) create mode 100644 python/stdface/__init__.py create mode 100644 python/stdface/__main__.py create mode 100644 python/stdface/core/__init__.py create mode 100644 python/stdface/core/keyword_parser.py create mode 100644 python/stdface/core/param_check.py create mode 100644 python/stdface/core/stdface_model_util.py create mode 100644 python/stdface/core/stdface_vals.py create mode 100644 python/stdface/core/version.py create mode 100644 python/stdface/lattice/boost_output.py create mode 100644 python/stdface/lattice/geometry_output.py create mode 100644 python/stdface/lattice/input_params.py create mode 100644 python/stdface/lattice/interaction_builder.py create mode 100644 python/stdface/lattice/site_util.py create mode 100644 python/stdface/plugin.py create mode 100644 python/stdface/solvers/__init__.py create mode 100644 python/stdface/solvers/hphi/__init__.py create mode 100644 python/stdface/solvers/hphi/writer.py create mode 100644 python/stdface/solvers/hwave/__init__.py create mode 100644 python/stdface/solvers/hwave/_plugin.py create mode 100644 python/stdface/solvers/hwave/export_wannier90.py create mode 100644 python/stdface/solvers/mvmc/__init__.py create mode 100644 python/stdface/solvers/mvmc/_plugin.py create mode 100644 python/stdface/solvers/mvmc/variational.py create mode 100644 python/stdface/solvers/mvmc/writer.py create mode 100644 python/stdface/solvers/uhf/__init__.py create mode 100644 python/stdface/solvers/uhf/_plugin.py create mode 100644 python/stdface/writer/__init__.py create mode 100644 python/stdface/writer/common_writer.py create mode 100644 python/stdface/writer/interaction_writer.py diff --git a/python/stdface/__init__.py b/python/stdface/__init__.py new file mode 100644 index 0000000..69c7afd --- /dev/null +++ b/python/stdface/__init__.py @@ -0,0 +1,6 @@ +"""StdFace: Standard-mode input generator for HPhi / mVMC / UHF / H-wave. + +This package provides tools for generating Expert-mode definition files +from a simplified Standard-mode input file. +""" +from __future__ import annotations diff --git a/python/stdface/__main__.py b/python/stdface/__main__.py new file mode 100644 index 0000000..dc174a6 --- /dev/null +++ b/python/stdface/__main__.py @@ -0,0 +1,80 @@ +"""Command-line entry point for the StdFace standard-mode input generator. + +This module is the Python translation of ``dry.c``. It can be invoked as:: + + python -m stdface stan.in # default solver: HPhi + python -m stdface stan.in --solver mVMC + +or, equivalently, via the installed console script (if packaged). + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" +from __future__ import annotations + +import argparse +import sys + +from .core.version import print_version +from .core.stdface_main import stdface_main + + +def main(argv: list[str] | None = None) -> int: + """Parse command-line arguments and run the standard-mode generator. + + Parameters + ---------- + argv : list of str or None + Command-line arguments. ``None`` means ``sys.argv[1:]``. + + Returns + ------- + int + Exit status (0 = success, 1 = usage error). + """ + parser = argparse.ArgumentParser( + prog="stdface", + description="StdFace: standard-mode input generator for HPhi / mVMC / UHF / H-wave", + ) + parser.add_argument( + "-v", "--version", + action="store_true", + help="print version and exit", + ) + parser.add_argument( + "input_file", + nargs="?", + default=None, + help="standard-mode input file (e.g. stan.in)", + ) + parser.add_argument( + "--solver", + choices=["HPhi", "mVMC", "UHF", "HWAVE"], + default="HPhi", + help="target solver (default: HPhi)", + ) + + args = parser.parse_args(argv) + + if args.version: + print_version() + return 0 + + if args.input_file is None: + print_version() + parser.print_usage() + return 1 + + stdface_main(args.input_file, solver=args.solver) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/python/stdface/core/__init__.py b/python/stdface/core/__init__.py new file mode 100644 index 0000000..985c691 --- /dev/null +++ b/python/stdface/core/__init__.py @@ -0,0 +1,2 @@ +"""Core modules for the StdFace standard-mode input generator.""" +from __future__ import annotations diff --git a/python/stdface/core/keyword_parser.py b/python/stdface/core/keyword_parser.py new file mode 100644 index 0000000..920cceb --- /dev/null +++ b/python/stdface/core/keyword_parser.py @@ -0,0 +1,646 @@ +"""Keyword parsing helpers and solver-specific parsers. + +This module contains functions for parsing keyword-value pairs from +StdFace input files, including duplicate-checking storage helpers and +solver-specific keyword dispatchers for HPhi, mVMC, UHF, and H-wave. +""" +from __future__ import annotations + +import cmath +import math + +from .stdface_vals import StdIntList, SolverType, NaN_i, UNSET_STRING +from .param_check import exit_program + + +_TRIM_TABLE = str.maketrans("", "", " :;\"\\\b\v\n\0") +"""Translation table for :func:`trim_space_quote`. + +Removes: space, colon, semicolon, double-quote, backslash, +backspace (``\\b``), vertical-tab (``\\v``), newline, and null. +""" + + +def trim_space_quote(text: str) -> str: + """Remove whitespace, colons, semicolons, quotes and backslashes from *text*. + + Uses a precomputed :func:`str.maketrans` table for efficient + character deletion. + + Parameters + ---------- + text : str + Raw keyword or value string from an input file. + + Returns + ------- + str + The cleaned string with the above characters removed. + """ + return text.translate(_TRIM_TABLE) + + +def _fail_duplicate(keyword: str) -> None: + """Print a duplicate-keyword error and terminate. + + Parameters + ---------- + keyword : str + The duplicated keyword name. + """ + print(f"ERROR ! Keyword {keyword} is duplicated ! ") + exit_program(-1) + + +def store_with_check_dup_s(keyword: str, value: str, current: str) -> str: + """Store a string value after checking for duplicate assignment. + + If *current* has already been assigned (i.e. it is not the sentinel + ``"****"``), the program prints an error and exits. + + Parameters + ---------- + keyword : str + The keyword name (used only for the error message). + value : str + The new value read from the input file. + current : str + The current stored value (``"****"`` means unset). + + Returns + ------- + str + The accepted value (equal to *value*). + + Raises + ------ + SystemExit + If *current* is not the sentinel, indicating a duplicate keyword. + """ + if current != UNSET_STRING: + _fail_duplicate(keyword) + return value + + +def store_with_check_dup_sl( + keyword: str, value: str, current: str, maxlen: int = 256 +) -> str: + """Store a string value (forced lower-case) after checking for duplicates. + + Behaves like :func:`store_with_check_dup_s` but additionally + converts *value* to lower case and truncates it to *maxlen* + characters. + + Parameters + ---------- + keyword : str + The keyword name (used only for the error message). + value : str + The new value read from the input file. + current : str + The current stored value (``"****"`` means unset). + maxlen : int, optional + Maximum number of characters to keep (default 256). + + Returns + ------- + str + The accepted value, lower-cased and truncated. + + Raises + ------ + SystemExit + If *current* is not the sentinel, indicating a duplicate keyword. + """ + if current != UNSET_STRING: + _fail_duplicate(keyword) + return value[:maxlen].lower() + + +def store_with_check_dup_i(keyword: str, value: str, current: int) -> int: + """Store an integer value after checking for duplicate assignment. + + If *current* differs from the integer sentinel (``2147483647``), + the program prints an error and exits. + + Parameters + ---------- + keyword : str + The keyword name (used only for the error message). + value : str + The new value read from the input file (will be converted to int). + current : int + The current stored value (``NaN_i`` means unset). + + Returns + ------- + int + The parsed integer value. + + Raises + ------ + SystemExit + If *current* is not the sentinel, indicating a duplicate keyword. + """ + if current != NaN_i: + _fail_duplicate(keyword) + # C sscanf("%d") truncates floats like "2.0" -> 2 + return int(float(value)) + + +def store_with_check_dup_d(keyword: str, value: str, current: float) -> float: + """Store a float value after checking for duplicate assignment. + + If *current* is **not** NaN the program prints an error and exits. + + Parameters + ---------- + keyword : str + The keyword name (used only for the error message). + value : str + The new value read from the input file (will be converted to float). + current : float + The current stored value (``NaN`` means unset). + + Returns + ------- + float + The parsed float value. + + Raises + ------ + SystemExit + If *current* is not NaN, indicating a duplicate keyword. + """ + if not math.isnan(current): + _fail_duplicate(keyword) + return float(value) + + +def _safe_float(s: str) -> float: + """Parse a string to float, returning 0.0 on empty or invalid input. + + Parameters + ---------- + s : str + String to parse. + + Returns + ------- + float + Parsed value, or 0.0 if *s* is empty or not a valid number. + """ + s = s.strip() + if not s: + return 0.0 + try: + return float(s) + except ValueError: + return 0.0 + + +def store_with_check_dup_c(keyword: str, value: str, current: complex) -> complex: + """Store a complex value after checking for duplicate assignment. + + The input string *value* may be in one of the following forms: + + * ``"real,imag"`` -- both parts specified + * ``"real"`` -- imaginary part defaults to 0 + * ``",imag"`` -- real part defaults to 0 + + If the real part of *current* is **not** NaN the program prints an + error and exits. + + Parameters + ---------- + keyword : str + The keyword name (used only for the error message). + value : str + The new value read from the input file. + current : complex + The current stored value (real-part ``NaN`` means unset). + + Returns + ------- + complex + The parsed complex value. + + Raises + ------ + SystemExit + If *current* is already set, indicating a duplicate keyword. + """ + if not cmath.isnan(current): + _fail_duplicate(keyword) + + # Split on comma, mirroring the C strtok(",") logic + parts = value.split(",", 1) if "," in value else [value] + real_part = _safe_float(parts[0]) + imag_part = _safe_float(parts[1]) if len(parts) > 1 else 0.0 + + return complex(real_part, imag_part) + + +# ===================================================================== +# Common keyword dispatch table +# ===================================================================== + + +def _j_matrix_keywords(prefix: str, scalar_field: str, + matrix_field: str) -> dict[str, tuple]: + """Generate keyword entries for a 3x3 spin-coupling matrix and its isotropic scalar. + + Each J-family (J, J0, J0', J0'', J1, ..., J2'', J', J'') has an + isotropic scalar keyword (e.g. ``"j0"``) and 9 anisotropic component + keywords (e.g. ``"j0x"``, ``"j0xy"``, ``"j0xz"``, ...). + + Parameters + ---------- + prefix : str + Lowered keyword prefix (e.g. ``"j0"``, ``"j0'"``, ``"j2''"``, ``"j"``). + scalar_field : str + StdIntList attribute for the isotropic scalar (e.g. ``"J0All"``). + matrix_field : str + StdIntList attribute for the 3x3 matrix (e.g. ``"J0"``). + + Returns + ------- + dict[str, tuple] + 10 keyword entries (1 scalar + 9 array-element). + """ + # Component suffixes → (row, col) in the 3×3 matrix + _COMPONENTS = [ + ("x", (0, 0)), ("xy", (0, 1)), ("xz", (0, 2)), + ("y", (1, 1)), ("yx", (1, 0)), ("yz", (1, 2)), + ("z", (2, 2)), ("zx", (2, 0)), ("zy", (2, 1)), + ] + d: dict[str, tuple] = { + prefix: (store_with_check_dup_d, scalar_field), + } + for suffix, idx in _COMPONENTS: + d[prefix + suffix] = (store_with_check_dup_d, matrix_field, idx, float) + return d + + +def _grid3x3_keywords( + fmt: str, field: str, store_func: object, cast: type, +) -> dict[str, tuple]: + """Generate 9 keyword entries for a 3x3 array indexed by (a0/a1/a2) × (w/l/h). + + Parameters + ---------- + fmt : str + Format string with ``{a}`` and ``{c}`` placeholders for the row name + and column name, e.g. ``"{a}{c}"`` → ``"a0w"``, ``"a1l"``, etc. + or ``"cutoff_j_{a}{c}"`` → ``"cutoff_j_a0w"``, etc. + field : str + StdIntList attribute name for the 3x3 array (e.g. ``"box"``). + store_func : callable + Store-with-duplicate-check function (e.g. ``store_with_check_dup_i``). + cast : type + Type cast for the stored value (``int`` or ``float``). + + Returns + ------- + dict[str, tuple] + 9 keyword entries mapping ``fmt.format(a=..., c=...)`` to + ``(store_func, field, (row, col), cast)``. + """ + d: dict[str, tuple] = {} + for row, aname in enumerate(("a0", "a1", "a2")): + for col, comp in enumerate(("w", "l", "h")): + d[fmt.format(a=aname, c=comp)] = ( + store_func, field, (row, col), cast + ) + return d + + +_COMMON_KEYWORDS: dict[str, tuple] = { + # --- scalar a -------------------------------------------------------- + "a": (store_with_check_dup_d, "a"), + # --- box (supercell) ------------------------------------------------- + **_grid3x3_keywords("{a}{c}", "box", store_with_check_dup_i, int), + # --- cutoff J -------------------------------------------------------- + "cutoff_j": (store_with_check_dup_d, "cutoff_j"), + "cutoff_jw": (store_with_check_dup_i, "cutoff_JR", 0, int), + "cutoff_jl": (store_with_check_dup_i, "cutoff_JR", 1, int), + "cutoff_jh": (store_with_check_dup_i, "cutoff_JR", 2, int), + **_grid3x3_keywords("cutoff_j_{a}{c}", "cutoff_JVec", store_with_check_dup_d, float), + "cutoff_length_j": (store_with_check_dup_d, "cutoff_length_J"), + "cutoff_length_u": (store_with_check_dup_d, "cutoff_length_U"), + "cutoff_length_t": (store_with_check_dup_d, "cutoff_length_t"), + # --- cutoff t -------------------------------------------------------- + "cutoff_t": (store_with_check_dup_d, "cutoff_t"), + "cutoff_tw": (store_with_check_dup_i, "cutoff_tR", 0, int), + "cutoff_tl": (store_with_check_dup_i, "cutoff_tR", 1, int), + "cutoff_th": (store_with_check_dup_i, "cutoff_tR", 2, int), + **_grid3x3_keywords("cutoff_t_{a}{c}", "cutoff_tVec", store_with_check_dup_d, float), + # --- cutoff U -------------------------------------------------------- + "cutoff_u": (store_with_check_dup_d, "cutoff_u"), + "cutoff_uw": (store_with_check_dup_i, "cutoff_UR", 0, int), + "cutoff_ul": (store_with_check_dup_i, "cutoff_UR", 1, int), + "cutoff_uh": (store_with_check_dup_i, "cutoff_UR", 2, int), + **_grid3x3_keywords("cutoff_u_{a}{c}", "cutoff_UVec", store_with_check_dup_d, float), + # --- lambda, alpha, D ------------------------------------------------ + "lambda": (store_with_check_dup_d, "lambda_"), + "lambda_u": (store_with_check_dup_d, "lambda_U"), + "lambda_j": (store_with_check_dup_d, "lambda_J"), + "alpha": (store_with_check_dup_d, "alpha"), + "d": (store_with_check_dup_d, "D", (2, 2), float), + "doublecounting": (store_with_check_dup_sl, "double_counting_mode"), + # --- magnetic field and related -------------------------------------- + "gamma": (store_with_check_dup_d, "Gamma"), + "h": (store_with_check_dup_d, "h"), + "gamma_y": (store_with_check_dup_d, "Gamma_y"), + "height": (store_with_check_dup_i, "Height"), + "hlength": (store_with_check_dup_d, "length", 2, float), + "hx": (store_with_check_dup_d, "direct", (2, 0), float), + "hy": (store_with_check_dup_d, "direct", (2, 1), float), + "hz": (store_with_check_dup_d, "direct", (2, 2), float), + # --- J exchange couplings -------------------------------------------- + **_j_matrix_keywords("j", "JAll", "J"), + **_j_matrix_keywords("j0", "J0All", "J0"), + **_j_matrix_keywords("j0'", "J0pAll", "J0p"), + **_j_matrix_keywords("j0''", "J0ppAll", "J0pp"), + **_j_matrix_keywords("j1", "J1All", "J1"), + **_j_matrix_keywords("j1'", "J1pAll", "J1p"), + **_j_matrix_keywords("j1''", "J1ppAll", "J1pp"), + **_j_matrix_keywords("j2", "J2All", "J2"), + **_j_matrix_keywords("j2'", "J2pAll", "J2p"), + **_j_matrix_keywords("j2''", "J2ppAll", "J2pp"), + **_j_matrix_keywords("j'", "JpAll", "Jp"), + **_j_matrix_keywords("j''", "JppAll", "Jpp"), + # --- K, L, lattice, length ------------------------------------------- + "k": (store_with_check_dup_d, "K"), + "l": (store_with_check_dup_i, "L"), + "lattice": (store_with_check_dup_sl, "lattice"), + "llength": (store_with_check_dup_d, "length", 1, float), + "lx": (store_with_check_dup_d, "direct", (1, 0), float), + "ly": (store_with_check_dup_d, "direct", (1, 1), float), + "lz": (store_with_check_dup_d, "direct", (1, 2), float), + # --- model, mu, ncond, outputmode ------------------------------------ + "model": (store_with_check_dup_sl, "model"), + "mu": (store_with_check_dup_d, "mu"), + "ncond": (store_with_check_dup_i, "ncond"), + "nelec": (store_with_check_dup_i, "ncond"), # alias + "outputmode": (store_with_check_dup_sl, "outputmode"), + # --- phase ----------------------------------------------------------- + "phase0": (store_with_check_dup_d, "phase", 0, float), + "phase1": (store_with_check_dup_d, "phase", 1, float), + "phase2": (store_with_check_dup_d, "phase", 2, float), + # --- hopping t ------------------------------------------------------- + "t": (store_with_check_dup_c, "t"), + "t0": (store_with_check_dup_c, "t0"), + "t0'": (store_with_check_dup_c, "t0p"), + "t0''": (store_with_check_dup_c, "t0pp"), + "t1": (store_with_check_dup_c, "t1"), + "t1'": (store_with_check_dup_c, "t1p"), + "t1''": (store_with_check_dup_c, "t1pp"), + "t2": (store_with_check_dup_c, "t2"), + "t2'": (store_with_check_dup_c, "t2p"), + "t2''": (store_with_check_dup_c, "t2pp"), + "t'": (store_with_check_dup_c, "tp"), + "t''": (store_with_check_dup_c, "tpp"), + # --- U, V ------------------------------------------------------------ + "u": (store_with_check_dup_d, "U"), + "v": (store_with_check_dup_d, "V"), + "v0": (store_with_check_dup_d, "V0"), + "v0'": (store_with_check_dup_d, "V0p"), + "v0''": (store_with_check_dup_d, "V0pp"), + "v1": (store_with_check_dup_d, "V1"), + "v1'": (store_with_check_dup_d, "V1p"), + "v1''": (store_with_check_dup_d, "V1pp"), + "v2": (store_with_check_dup_d, "V2"), + "v2'": (store_with_check_dup_d, "V2p"), + "v2''": (store_with_check_dup_d, "V2pp"), + "v'": (store_with_check_dup_d, "Vp"), + "v''": (store_with_check_dup_d, "Vpp"), + # --- W, wlength, wx/wy/wz ------------------------------------------- + "w": (store_with_check_dup_i, "W"), + "wlength": (store_with_check_dup_d, "length", 0, float), + "wx": (store_with_check_dup_d, "direct", (0, 0), float), + "wy": (store_with_check_dup_d, "direct", (0, 1), float), + "wz": (store_with_check_dup_d, "direct", (0, 2), float), + # --- 2Sz ------------------------------------------------------------- + "2sz": (store_with_check_dup_i, "Sz2"), +} +"""Common keyword dispatch table shared by all solvers.""" + + +def parse_common_keyword(keyword: str, value: str, StdI: StdIntList) -> bool: + """Parse a keyword common to all solvers. + + Looks up *keyword* in :data:`_COMMON_KEYWORDS` and applies the + corresponding store operation to *StdI*. + + Parameters + ---------- + keyword : str + The lowered keyword string. + value : str + The raw value string from the input file. + StdI : StdIntList + The global parameter structure, modified in place. + + Returns + ------- + bool + True if the keyword was recognised, False otherwise. + """ + return _apply_keyword_table(_COMMON_KEYWORDS, keyword, value, StdI) + + +# ===================================================================== +# Solver-specific keyword dispatch tables +# ===================================================================== +# +# Each entry maps a lowered keyword string to a descriptor: +# - Scalar field: (store_func, "field_name") +# - Array element: (store_func, "array_name", index, cast) +# +# ``_apply_keyword_table`` applies the matching entry generically. + +# Shared boxsub keywords (mVMC, UHF, HWAVE all accept these). +_BOXSUB_KEYWORDS: dict[str, tuple] = _grid3x3_keywords( + "{a}{c}sub", "boxsub", store_with_check_dup_i, int +) + +# Shared UHF base keywords (UHF and HWAVE both accept these). +_UHF_BASE_KEYWORDS: dict[str, tuple] = { + "iteration_max": (store_with_check_dup_i, "Iteration_max"), + "rndseed": (store_with_check_dup_i, "RndSeed"), + "nmptrans": (store_with_check_dup_i, "NMPTrans"), + **_BOXSUB_KEYWORDS, + "hsub": (store_with_check_dup_i, "Hsub"), + "lsub": (store_with_check_dup_i, "Lsub"), + "wsub": (store_with_check_dup_i, "Wsub"), + "eps": (store_with_check_dup_i, "eps"), + "epsslater": (store_with_check_dup_i, "eps_slater"), + "mix": (store_with_check_dup_d, "mix"), +} + +_HPHI_KEYWORDS: dict[str, tuple] = { + "calcspec": (store_with_check_dup_sl, "CalcSpec"), + "exct": (store_with_check_dup_i, "exct"), + "eigenvecio": (store_with_check_dup_sl, "EigenVecIO"), + "expandcoef": (store_with_check_dup_i, "ExpandCoef"), + "expecinterval": (store_with_check_dup_i, "ExpecInterval"), + "cdatafilehead": (store_with_check_dup_s, "CDataFileHead"), + "dt": (store_with_check_dup_d, "dt"), + "flgtemp": (store_with_check_dup_i, "FlgTemp"), + "freq": (store_with_check_dup_d, "freq"), + "hamio": (store_with_check_dup_sl, "HamIO"), + "initialvectype": (store_with_check_dup_sl, "InitialVecType"), + "initial_iv": (store_with_check_dup_i, "initial_iv"), + "lanczoseps": (store_with_check_dup_i, "LanczosEps"), + "lanczostarget": (store_with_check_dup_i, "LanczosTarget"), + "lanczos_max": (store_with_check_dup_i, "Lanczos_max"), + "largevalue": (store_with_check_dup_d, "LargeValue"), + "method": (store_with_check_dup_sl, "method"), + "nomega": (store_with_check_dup_i, "Nomega"), + "numave": (store_with_check_dup_i, "NumAve"), + "nvec": (store_with_check_dup_i, "nvec"), + "omegamax": (store_with_check_dup_d, "OmegaMax"), + "omegamin": (store_with_check_dup_d, "OmegaMin"), + "omegaorg": (store_with_check_dup_d, "OmegaOrg"), + "omegaim": (store_with_check_dup_d, "OmegaIm"), + "outputexcitedvec": (store_with_check_dup_sl, "OutputExVec"), + "pumptype": (store_with_check_dup_sl, "PumpType"), + "restart": (store_with_check_dup_sl, "Restart"), + "spectrumqh": (store_with_check_dup_d, "SpectrumQ", 2, float), + "spectrumql": (store_with_check_dup_d, "SpectrumQ", 1, float), + "spectrumqw": (store_with_check_dup_d, "SpectrumQ", 0, float), + "spectrumtype": (store_with_check_dup_sl, "SpectrumType"), + "tdump": (store_with_check_dup_d, "tdump"), + "tshift": (store_with_check_dup_d, "tshift"), + "uquench": (store_with_check_dup_d, "Uquench"), + "vecpoth": (store_with_check_dup_d, "VecPot", 2, float), + "vecpotl": (store_with_check_dup_d, "VecPot", 1, float), + "vecpotw": (store_with_check_dup_d, "VecPot", 0, float), + "2s": (store_with_check_dup_i, "S2"), + "ngpu": (store_with_check_dup_i, "NGPU"), + "scalapack": (store_with_check_dup_i, "Scalapack"), +} +"""HPhi-specific keyword → (store_func, field_name[, index, cast]) map.""" + +_MVMC_KEYWORDS: dict[str, tuple] = { + **_BOXSUB_KEYWORDS, + "complextype": (store_with_check_dup_i, "ComplexType"), + "cparafilehead": (store_with_check_dup_s, "CParaFileHead"), + "dsroptredcut": (store_with_check_dup_d, "DSROptRedCut"), + "dsroptstadel": (store_with_check_dup_d, "DSROptStaDel"), + "dsroptstepdt": (store_with_check_dup_d, "DSROptStepDt"), + "hsub": (store_with_check_dup_i, "Hsub"), + "lsub": (store_with_check_dup_i, "Lsub"), + "nvmccalmode": (store_with_check_dup_i, "NVMCCalMode"), + "ndataidxstart": (store_with_check_dup_i, "NDataIdxStart"), + "ndataqtysmp": (store_with_check_dup_i, "NDataQtySmp"), + "nlanczosmode": (store_with_check_dup_i, "NLanczosMode"), + "nmptrans": (store_with_check_dup_i, "NMPTrans"), + "nspgaussleg": (store_with_check_dup_i, "NSPGaussLeg"), + "nsplitsize": (store_with_check_dup_i, "NSplitSize"), + "nspstot": (store_with_check_dup_i, "NSPStot"), + "nsroptitrsmp": (store_with_check_dup_i, "NSROptItrSmp"), + "nsroptitrstep": (store_with_check_dup_i, "NSROptItrStep"), + "nstore": (store_with_check_dup_i, "NStore"), + "nsrcg": (store_with_check_dup_i, "NSRCG"), + "nvmcinterval": (store_with_check_dup_i, "NVMCInterval"), + "nvmcsample": (store_with_check_dup_i, "NVMCSample"), + "nvmcwarmup": (store_with_check_dup_i, "NVMCWarmUp"), + "rndseed": (store_with_check_dup_i, "RndSeed"), + "wsub": (store_with_check_dup_i, "Wsub"), +} +"""mVMC-specific keyword → (store_func, field_name) map.""" + +_UHF_KEYWORDS: dict[str, tuple] = _UHF_BASE_KEYWORDS +"""UHF-specific keyword map (identical to the shared base).""" + +_HWAVE_KEYWORDS: dict[str, tuple] = { + **_UHF_BASE_KEYWORDS, + "calcmode": (store_with_check_dup_sl, "calcmode"), + "fileprefix": (store_with_check_dup_sl, "fileprefix"), + "exportall": (store_with_check_dup_i, "export_all"), + "lattice_gp": (store_with_check_dup_i, "lattice_gp"), +} +"""HWAVE-specific keyword map (extends UHF base with 4 extra keywords).""" + +_SOLVER_KEYWORD_TABLES: dict[str, dict[str, tuple]] = { + SolverType.HPhi: _HPHI_KEYWORDS, + SolverType.mVMC: _MVMC_KEYWORDS, + SolverType.UHF: _UHF_KEYWORDS, + SolverType.HWAVE: _HWAVE_KEYWORDS, +} +"""Maps solver type to its keyword dispatch table.""" + + +def _apply_keyword_table( + table: dict[str, tuple], + keyword: str, + value: str, + StdI: StdIntList, +) -> bool: + """Look up *keyword* in *table* and apply the corresponding store operation. + + Parameters + ---------- + table : dict[str, tuple] + Keyword dispatch table. Each entry is either: + - ``(store_func, field_name)`` for scalar fields, or + - ``(store_func, array_name, index, cast)`` for array elements. + keyword : str + The lowered keyword string. + value : str + The raw value string from the input file. + StdI : StdIntList + The global parameter structure, modified in place. + + Returns + ------- + bool + True if *keyword* was found in *table*, False otherwise. + """ + entry = table.get(keyword) + if entry is None: + return False + if len(entry) == 2: + # Scalar field: (store_func, field_name) + store_func, field_name = entry + setattr(StdI, field_name, + store_func(keyword, value, getattr(StdI, field_name))) + else: + # Array element: (store_func, array_name, index, cast) + store_func, array_name, index, cast = entry + arr = getattr(StdI, array_name) + arr[index] = store_func(keyword, value, cast(arr[index])) + return True + + +def parse_solver_keyword(keyword: str, value: str, StdI: StdIntList, + solver: str) -> bool: + """Parse a solver-specific keyword. + + Looks up the keyword in the dispatch table for *solver* and applies + the corresponding store operation to *StdI*. + + Parameters + ---------- + keyword : str + The lowered keyword string. + value : str + The raw value string from the input file. + StdI : StdIntList + The global parameter structure, modified in place. + solver : str + The solver type (one of ``SolverType`` values). + + Returns + ------- + bool + True if the keyword was recognised, False otherwise. + """ + table = _SOLVER_KEYWORD_TABLES.get(solver) + if table is None: + return False + return _apply_keyword_table(table, keyword, value, StdI) + + diff --git a/python/stdface/core/param_check.py b/python/stdface/core/param_check.py new file mode 100644 index 0000000..833cdf1 --- /dev/null +++ b/python/stdface/core/param_check.py @@ -0,0 +1,274 @@ +"""Parameter validation, printing, and program-exit utilities. + +This module provides helper functions for printing parameter values with +default-value handling, aborting on unused or missing parameters, and +terminating the program. These utilities are shared across all lattice +implementations, solver writers, and the main entry point. + +Functions +--------- +exit_program + Terminate the program with a given error code. +print_val_d + Print / default a real-valued parameter. +print_val_dd + Print / default a real-valued parameter with two fallback defaults. +print_val_c + Print / default a complex-valued parameter. +print_val_i + Print / default an integer parameter. +not_used_d + Abort if a real or complex parameter is specified but unused. +not_used_j + Abort if any component of a J-type interaction is specified but unused. +not_used_i + Abort if an integer parameter is specified but unused. +required_val_i + Abort if a required integer parameter is missing. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import itertools +import math +import sys + +import numpy as np + +from .stdface_vals import NaN_i + + +# --------------------------------------------------------------------------- +# Exit wrapper +# --------------------------------------------------------------------------- + + +def exit_program(errorcode: int) -> None: + """Terminate the program with the given error code. + + Parameters + ---------- + errorcode : int + Exit code passed to ``sys.exit``. + + Notes + ----- + In the original C code this was ``StdFace_exit`` which wrapped + ``MPI_Abort`` / ``MPI_Finalize``. The Python version simply calls + ``sys.exit``. + """ + sys.stdout.flush() + sys.stderr.flush() + sys.exit(errorcode) + + +# --------------------------------------------------------------------------- +# PrintVal / NotUsed / Required helpers +# --------------------------------------------------------------------------- + + +def print_val_d(valname: str, val: float, val0: float) -> float: + """Print and optionally set a real-valued parameter. + + If *val* is NaN, set it to the default *val0* and print with a + ``DEFAULT VALUE`` tag. + + Parameters + ---------- + valname : str + Name of the variable (for display). + val : float + Current value (may be NaN if not specified). + val0 : float + Default value to use when *val* is NaN. + + Returns + ------- + float + The (possibly updated) value. + """ + if math.isnan(val): + val = val0 + print(f" {valname:>15s} = {val:<10.5f} ###### DEFAULT VALUE IS USED ######") + else: + print(f" {valname:>15s} = {val:<10.5f}") + return val + + +def print_val_dd(valname: str, val: float, val0: float, val1: float) -> float: + """Print and optionally set a real-valued parameter with two defaults. + + If *val* is NaN, use the primary default *val0* if specified, + otherwise fall back to the secondary default *val1*. Delegates to + :func:`print_val_d` after resolving the effective default. + + Parameters + ---------- + valname : str + Name of the variable (for display). + val : float + Current value (may be NaN). + val0 : float + Primary default (may itself be NaN). + val1 : float + Secondary default. + + Returns + ------- + float + The (possibly updated) value. + """ + default = val1 if math.isnan(val0) else val0 + return print_val_d(valname, val, default) + + +def print_val_c(valname: str, val: complex, val0: complex) -> complex: + """Print and optionally set a complex-valued parameter. + + Parameters + ---------- + valname : str + Name of the variable (for display). + val : complex + Current value (real part NaN if not specified). + val0 : complex + Default value. + + Returns + ------- + complex + The (possibly updated) value. + """ + if math.isnan(val.real): + val = val0 + print(f" {valname:>15s} = {val.real:<10.5f} {val.imag:<10.5f}" + f" ###### DEFAULT VALUE IS USED ######") + else: + print(f" {valname:>15s} = {val.real:<10.5f} {val.imag:<10.5f}") + return val + + +def print_val_i(valname: str, val: int, val0: int) -> int: + """Print and optionally set an integer parameter. + + If *val* equals the sentinel ``2147483647`` (NaN_i), set it to *val0*. + + Parameters + ---------- + valname : str + Name of the variable (for display). + val : int + Current value (sentinel if not specified). + val0 : int + Default value. + + Returns + ------- + int + The (possibly updated) value. + """ + if val == NaN_i: + val = val0 + print(f" {valname:>15s} = {val:<10d} ###### DEFAULT VALUE IS USED ######") + else: + print(f" {valname:>15s} = {val:<10d}") + return val + + +def _fail_not_used(valname: str) -> None: + """Print "specified but not used" error and terminate. + + Parameters + ---------- + valname : str + Name of the unused parameter. + """ + print(f"\n Check ! {valname} is SPECIFIED but will NOT be USED. ") + print(" Please COMMENT-OUT this line ") + print(" or check this input is REALLY APPROPRIATE for your purpose !\n") + exit_program(-1) + + +def not_used_d(valname: str, val: float | complex) -> None: + """Abort if a real parameter is specified but will not be used. + + Parameters + ---------- + valname : str + Name of the variable. + val : float or complex + Value to check (abort if not NaN). If complex, the real part + is tested (matching the C behaviour of implicitly casting + ``double complex`` to ``double``). + """ + check = val.real if isinstance(val, complex) else val + if not math.isnan(check): + _fail_not_used(valname) + + +# Spin-interaction suffix matrix for 3x3 J-coupling tensors. +# Shared by not_used_j and input_params module. +SPIN_SUFFIXES: list[list[str]] = [ + ["x", "xy", "xz"], + ["yx", "y", "yz"], + ["zx", "zy", "z"], +] +"""3x3 suffix matrix for spin-interaction tensor components (Jx, Jxy, etc.).""" + + +def not_used_j(valname: str, JAll: float, J: np.ndarray) -> None: + """Abort if any component of a J-type interaction is specified but unused. + + Parameters + ---------- + valname : str + Base name of the variable (e.g. ``"J0"``). + JAll : float + Scalar (isotropic) part. + J : numpy.ndarray + 3x3 matrix of anisotropic components. + """ + not_used_d(valname, JAll) + for i1, i2 in itertools.product(range(3), repeat=2): + not_used_d(f"{valname}{SPIN_SUFFIXES[i1][i2]}", J[i1, i2]) + + +def not_used_i(valname: str, val: int) -> None: + """Abort if an integer parameter is specified but will not be used. + + Parameters + ---------- + valname : str + Name of the variable. + val : int + Value to check (abort if not the sentinel 2147483647). + """ + if val != NaN_i: + _fail_not_used(valname) + + +def required_val_i(valname: str, val: int) -> None: + """Abort if a required integer parameter is missing. + + Parameters + ---------- + valname : str + Name of the variable. + val : int + Value to check (abort if equals the sentinel 2147483647). + """ + if val == NaN_i: + print(f"ERROR ! {valname} is NOT specified !") + exit_program(-1) + else: + print(f" {valname:>15s} = {val:<3d}") diff --git a/python/stdface/core/stdface_model_util.py b/python/stdface/core/stdface_model_util.py new file mode 100644 index 0000000..9555434 --- /dev/null +++ b/python/stdface/core/stdface_model_util.py @@ -0,0 +1,75 @@ +""" +Utility functions for constructing lattice models in Standard mode. + +This module was formerly a monolithic utility library (~1,700 lines). +It has been decomposed into focused modules during Phase 2 refactoring. +All public symbols are re-exported here for backward compatibility so +that existing lattice modules can continue to use:: + + from stdface_model_util import init_site, find_site, ... + +without modification. + +Re-exported modules +------------------- +param_check + Parameter validation and printing utilities. +input_params + Input parameter resolution helpers. +geometry_output + Geometry and structure output functions. +interaction_builder + Hamiltonian term builder functions and array allocation. +site_util + Super-cell initialisation, site folding, finding, and labelling. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +from .param_check import ( # noqa: F401 – re-exported for backward compatibility + exit_program, + print_val_d, + print_val_dd, + print_val_c, + print_val_i, + not_used_d, + not_used_j, + not_used_i, + required_val_i, +) +from ..lattice.input_params import ( # noqa: F401 – re-exported for backward compatibility + input_spin_nn, + input_spin, + input_coulomb_v, + input_hopp, +) +from ..lattice.geometry_output import ( # noqa: F401 – re-exported for backward compatibility + print_xsf, + print_geometry, +) +from ..lattice.interaction_builder import ( # noqa: F401 – re-exported for backward compatibility + trans, + hopping, + hubbard_local, + mag_field, + intr, + general_j, + coulomb, + malloc_interactions, +) +from ..lattice.site_util import ( # noqa: F401 – re-exported for backward compatibility + _fold_site, + init_site, + find_site, + set_label, +) diff --git a/python/stdface/core/stdface_vals.py b/python/stdface/core/stdface_vals.py new file mode 100644 index 0000000..05ede65 --- /dev/null +++ b/python/stdface/core/stdface_vals.py @@ -0,0 +1,877 @@ +""" +Variables used in the Standard mode. + +This module defines the StdIntList dataclass which contains all the variables +and parameters used in the Standard mode of HPhi-mVMC-StdFace. In the original +C code these variables are passed as a pointer to the StdIntList structure. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum + +import numpy as np + + +class ModelType(str, Enum): + """Canonical model type identifiers. + + Inherits from ``str`` so that ``ModelType.SPIN == "spin"`` is ``True``, + preserving full backward compatibility with existing string comparisons. + + Attributes + ---------- + SPIN : str + Pure spin model (S=1/2 or general S). + HUBBARD : str + Fermion Hubbard model. + KONDO : str + Kondo lattice model (itinerant + localised spins). + """ + + SPIN = "spin" + HUBBARD = "hubbard" + KONDO = "kondo" + + +class SolverType(str, Enum): + """Canonical solver type identifiers. + + Inherits from ``str`` so that ``SolverType.HPhi == "HPhi"`` is ``True``, + preserving full backward compatibility with existing string comparisons. + + Attributes + ---------- + HPhi : str + Exact-diagonalisation / Lanczos solver. + mVMC : str + Many-variable Variational Monte Carlo solver. + UHF : str + Unrestricted Hartree-Fock solver. + HWAVE : str + H-wave solver (Hartree-Fock / RPA / Wannier90 export). + """ + + HPhi = "HPhi" + mVMC = "mVMC" + UHF = "UHF" + HWAVE = "HWAVE" + + +class MethodType(str, Enum): + """Canonical HPhi calculation method identifiers. + + Inherits from ``str`` so that ``MethodType.LANCZOS == "lanczos"`` is + ``True``, preserving full backward compatibility with existing string + comparisons. + + Attributes + ---------- + LANCZOS : str + Lanczos diagonalisation. + LANCZOS_ENERGY : str + Lanczos (energy-only, skip eigenvector). + TPQ : str + Thermal Pure Quantum state method. + FULLDIAG : str + Full (direct) diagonalisation. + CG : str + Conjugate Gradient method. + TIME_EVOLUTION : str + Real-time evolution (pump / quench). + CTPQ : str + Canonical Thermal Pure Quantum state method. + """ + + LANCZOS = "lanczos" + LANCZOS_ENERGY = "lanczosenergy" + TPQ = "tpq" + FULLDIAG = "fulldiag" + CG = "cg" + TIME_EVOLUTION = "timeevolution" + CTPQ = "ctpq" + + +# --------------------------------------------------------------------------- +# Sentinel constants (matching the C code) +# --------------------------------------------------------------------------- + +NaN_i: int = 2147483647 +"""Sentinel for an unset integer parameter (same as ``INT_MAX`` in C).""" + +NaN_d: float = float("nan") +"""Sentinel for an unset float parameter (IEEE NaN).""" + +NaN_c: complex = complex(float("nan"), 0.0) +"""Sentinel for an unset complex parameter (real part is NaN).""" + +UNSET_STRING: str = "****" +"""Sentinel for an unset string parameter (C convention ``"****"``).""" + + +# --------------------------------------------------------------------------- +# Numerical tolerances +# --------------------------------------------------------------------------- + +AMPLITUDE_EPS: float = 1e-6 +"""Threshold for treating an amplitude as non-zero in output files. + +Terms with ``abs(coeff) <= AMPLITUDE_EPS`` are suppressed when writing +interaction, transfer, and pump definition files. +""" + +ZERO_BODY_EPS: float = 1e-12 +"""Threshold for skipping zero-amplitude terms when accumulating +one-body (transfer) and two-body (InterAll) Hamiltonian entries. +""" + + +@dataclass +class StdIntList: + """Main structure containing all parameters and variables for Standard mode. + + Attributes + ---------- + lattice : str + Name of lattice. Input parameter. + a : float + The lattice constant. Input parameter. + length : np.ndarray + Anisotropic lattice constant (shape ``(3,)``), input parameter + ``wlength``, ``llength``, ``hlength``. + W : int + Number of sites along the 1st axis, input parameter. + L : int + Number of sites along the 2nd axis, input parameter. + Height : int + Number of sites along the 3rd axis, input parameter. + direct : np.ndarray + The unit direct lattice vector (shape ``(3, 3)``). + Set in ``StdFace_InitSite()``. + box : np.ndarray + The shape of the super-cell (shape ``(3, 3)``, int). + Input parameter ``a0W``, ``a0L``, ``a0H``, etc. or defined from + ``W``, etc. in ``StdFace_InitSite()``. + rbox : np.ndarray + The inversion of ``box`` (shape ``(3, 3)``, int). + Set in ``StdFace_InitSite()``. + NCell : int + The number of the unit cell in the super-cell (determinant of ``box``). + Set in ``StdFace_InitSite()``. + Cell : np.ndarray or None + ``[NCell][3]`` The cell position in the fractional coordinate. + Malloc and set in ``StdFace_InitSite()``. + NsiteUC : int + Number of sites in the unit cell. Defined in the beginning of each + lattice function. + tau : np.ndarray or None + Cell-internal site position in the fractional coordinate. + Defined in the beginning of each lattice function. + model : str + Name of model, input parameter. + mu : float + Chemical potential, input parameter. + t : complex + Nearest-neighbor hopping, input parameter. + tp : complex + 2nd-nearest hopping, input parameter. + t0 : complex + Anisotropic hopping (1st), input parameter. + t0p : complex + Anisotropic hopping (2nd), input parameter. + t0pp : complex + Anisotropic hopping (3rd), input parameter. + t1 : complex + Anisotropic hopping (1st), input parameter. + t1p : complex + Anisotropic hopping (2nd), input parameter. + t1pp : complex + Anisotropic hopping (3rd), input parameter. + t2 : complex + Anisotropic hopping (1st), input parameter. + t2p : complex + Anisotropic hopping (2nd), input parameter. + t2pp : complex + Anisotropic hopping (3rd), input parameter. + tpp : complex + 3rd-nearest hopping, input parameter. + U : float + On-site Coulomb potential, input parameter. + V : float + Off-site Coulomb potential (1st), input parameter. + Vp : float + Off-site Coulomb potential (2nd), input parameter. + V0 : float + Anisotropic Coulomb potential (1st), input parameter. + V0p : float + Anisotropic Coulomb potential (2nd), input parameter. + V0pp : float + Anisotropic Coulomb potential (3rd), input parameter. + V1 : float + Anisotropic Coulomb potential (1st), input parameter. + V1p : float + Anisotropic Coulomb potential (2nd), input parameter. + V1pp : float + Anisotropic Coulomb potential (3rd), input parameter. + V2 : float + Anisotropic Coulomb potential (1st), input parameter. + V2p : float + Anisotropic Coulomb potential (2nd), input parameter. + V2pp : float + Anisotropic Coulomb potential (3rd), input parameter. + Vpp : float + Off-site Coulomb potential (3rd), input parameter. + JAll : float + Isotropic, diagonal spin coupling (1st Near.), input parameter ``J``. + JpAll : float + Isotropic, diagonal spin coupling (2nd Near), input parameter ``Jp``. + J0All : float + Anisotropic, diagonal spin coupling (1st Near), input parameter ``J0``. + J0pAll : float + Anisotropic, diagonal spin coupling (2nd Near), input parameter ``J0'``. + J0ppAll : float + Anisotropic, diagonal spin coupling (3rd Near), input parameter ``J0''``. + J1All : float + Anisotropic, diagonal spin coupling (1st Near), input parameter ``J1``. + J1pAll : float + Anisotropic, diagonal spin coupling (2nd Near), input parameter ``J1'``. + J1ppAll : float + Anisotropic, diagonal spin coupling (3rd Near), input parameter ``J1''``. + J2All : float + Anisotropic, diagonal spin coupling (1st Near), input parameter ``J2``. + J2pAll : float + Anisotropic, diagonal spin coupling (2nd Near), input parameter ``J2'``. + J2ppAll : float + Anisotropic, diagonal spin coupling (3rd Near), input parameter ``J2''``. + JppAll : float + Isotropic, diagonal spin coupling (3rd Near), input parameter ``J''``. + J : np.ndarray + Isotropic, diagonal/off-diagonal spin coupling (1st Near.) + (shape ``(3, 3)``). + Jp : np.ndarray + Isotropic, diagonal/off-diagonal spin coupling (2nd Near.) + (shape ``(3, 3)``). + J0 : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (1st Near.) + (shape ``(3, 3)``). + J0p : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (2nd Near.) + (shape ``(3, 3)``). + J0pp : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (3rd Near.) + (shape ``(3, 3)``). + J1 : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (1st Near.) + (shape ``(3, 3)``). + J1p : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (2nd Near.) + (shape ``(3, 3)``). + J1pp : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (3rd Near.) + (shape ``(3, 3)``). + J2 : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (1st Near.) + (shape ``(3, 3)``). + J2p : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (2nd Near.) + (shape ``(3, 3)``). + J2pp : np.ndarray + Anisotropic, diagonal/off-diagonal spin coupling (3rd Near.) + (shape ``(3, 3)``). + Jpp : np.ndarray + Isotropic, diagonal/off-diagonal spin coupling (3rd Near.) + (shape ``(3, 3)``). + D : np.ndarray + Coefficient for S_iz S_iz (shape ``(3, 3)``). + Only ``D[2][2]`` is used. + h : float + Longitudinal magnetic field, input parameter. + Gamma : float + Transverse magnetic field, input parameter. + Gamma_y : float + Transverse magnetic field (y), input parameter. + K : float + 4-spin term. Not used. + phase : np.ndarray + Boundary phase (shape ``(3,)``), input parameter ``phase0``, etc. + ExpPhase : np.ndarray + exp(i * pi * phase / 180) (shape ``(3,)``, complex). + AntiPeriod : np.ndarray + If corresponding ``phase`` == 180, it becomes 1 (shape ``(3,)``, int). + nsite : int + Number of sites, set in each lattice file. + locspinflag : np.ndarray or None + ``[nsite]`` LocSpin in Expert mode. + ntrans : int + Number of transfer, counted in each lattice file. + transindx : np.ndarray or None + ``[ntrans][4]`` Site/spin indices of one-body term. + trans : np.ndarray or None + ``[ntrans]`` Coefficient of one-body term (complex). + nintr : int + Number of InterAll, counted in each lattice file. + Lintr : int + Print ``interall.def`` or not. + intrindx : np.ndarray or None + ``[nintr][8]`` Site/spin indices of two-body term. + intr : np.ndarray or None + ``[nintr]`` Coefficient of general two-body term (complex). + NCintra : int + Number of intra-site Coulomb interaction. + LCintra : int + Print ``coulombintra.def`` or not. + CintraIndx : np.ndarray or None + ``[NCintra][1]`` Site indices of intra-site Coulomb term. + Cintra : np.ndarray or None + ``[NCintra]`` Coefficient of intra-site Coulomb term. + NCinter : int + Number of inter-site Coulomb interaction. + LCinter : int + Print ``coulombinter.def`` or not. + CinterIndx : np.ndarray or None + ``[NCinter][2]`` Site indices of inter-site Coulomb term. + Cinter : np.ndarray or None + ``[NCinter]`` Coefficient of inter-site Coulomb term. + NHund : int + Number of Hund term. + LHund : int + Print ``hund.def`` or not. + HundIndx : np.ndarray or None + ``[NHund][2]`` Site indices of Hund term. + Hund : np.ndarray or None + ``[NHund]`` Coefficient of Hund term. + NEx : int + Number of exchange term. + LEx : int + Print ``exchange.def`` or not. + ExIndx : np.ndarray or None + ``[NEx][2]`` Site indices of exchange term. + Ex : np.ndarray or None + ``[NEx]`` Coefficient of exchange term. + NPairLift : int + Number of pair-lift term. + LPairLift : int + Print ``pairlift.def`` or not. + PLIndx : np.ndarray or None + ``[NPairLift][2]`` Site indices of pair-lift term. + PairLift : np.ndarray or None + ``[NPairLift]`` Coefficient of pair-lift term. + NPairHopp : int + Number of pair-hopping term. + LPairHopp : int + Print ``pairhopp.def`` or not. + PHIndx : np.ndarray or None + ``[NPairHopp][2]`` Site indices of pair-hopping term. + PairHopp : np.ndarray or None + ``[NPairHopp]`` Coefficient of pair-hopping term. + lBoost : int + Boost flag. + ncond : int + Number of electrons, input from file. + lGC : int + Switch for computing grand-canonical ensemble (== 1). + S2 : int + Total spin |S| of a local spin, input from file. + outputmode : str + Select amount of correlation function, input from file. + CDataFileHead : str + Header of the output files, input from file. + Sz2 : int + Total Sz, input from file. + ioutputmode : int + Switch associated to ``outputmode``. + cutoff_t : float + Cutoff for the hopping in wannier90, input from file. + cutoff_u : float + Cutoff for the Coulomb in wannier90, input from file. + cutoff_j : float + Cutoff for the Hund in wannier90, input from file. + cutoff_length_t : float + Cutoff for R in wannier90, input from file. + cutoff_length_U : float + Cutoff for R in wannier90, input from file. + cutoff_length_J : float + Cutoff for R in wannier90, input from file. + cutoff_tR : np.ndarray + Cutoff R-vector for hopping (shape ``(3,)``, int). + cutoff_UR : np.ndarray + Cutoff R-vector for Coulomb (shape ``(3,)``, int). + cutoff_JR : np.ndarray + Cutoff R-vector for Hund (shape ``(3,)``, int). + cutoff_tVec : np.ndarray + Cutoff vector for hopping (shape ``(3, 3)``). + cutoff_UVec : np.ndarray + Cutoff vector for Coulomb (shape ``(3, 3)``). + cutoff_JVec : np.ndarray + Cutoff vector for Hund (shape ``(3, 3)``). + lambda_ : float + Tuning parameter of U and J in wannier90, input from file. + Named ``lambda_`` because ``lambda`` is a Python keyword. + lambda_U : float + Tuning parameter of U in wannier90, input from file. + lambda_J : float + Tuning parameter of J in wannier90, input from file. + double_counting_mode : str + Select mode of double counting, input from file. + alpha : float + Tuning parameter of chemical potential correction in wannier90, + input from file. + solver : str + Which solver is active: ``"HPhi"``, ``"mVMC"``, ``"UHF"``, + or ``"HWAVE"``. + method : str + (HPhi) The name of method, input from file. + Restart : str + (HPhi) The name of restart mode, input from file. + InitialVecType : str + (HPhi) The name of initial-guess type, input from file. + EigenVecIO : str + (HPhi) The name of I/O mode for eigenvector, input from file. + HamIO : str + (HPhi) The name of I/O mode for Hamiltonian, input from file. + FlgTemp : int + (HPhi) Temperature flag. + Lanczos_max : int + (HPhi) The maximum number of iterations, input from file. + initial_iv : int + (HPhi) The number for generating random number, input from file. + nvec : int + (HPhi) Number of vectors. + exct : int + (HPhi) The number of eigenvectors to be computed, input from file. + LanczosEps : int + (HPhi) Convergence threshold for the Lanczos method. + LanczosTarget : int + (HPhi) Which eigenvector is used for the convergence check. + NumAve : int + (HPhi) Number of trials for TPQ calculation. + ExpecInterval : int + (HPhi) Interval for the iteration when the expectation value is + computed. + LargeValue : float + (HPhi) The shift parameter for the TPQ calculation. + NGPU : int + (HPhi) Number of GPU for FullDiag. + Scalapack : int + (HPhi) Flag for FullDiag w/ Scalapack. + list_6spin_pair : None + (HPhi) Boost 6-spin pair list (dynamic 3D int array). + list_6spin_star : None + (HPhi) Boost 6-spin star list (dynamic 2D int array). + num_pivot : int + (HPhi) Boost pivot number. + ishift_nspin : int + (HPhi) Boost spin shift. + CalcSpec : str + (HPhi) The name of mode for spectrum, input from file. + SpectrumType : str + (HPhi) The type of mode for spectrum, input from file. + Nomega : int + (HPhi) Number of frequencies, input from file. + OmegaMax : float + (HPhi) Maximum of frequency for spectrum, input from file. + OmegaMin : float + (HPhi) Minimum of frequency for spectrum, input from file. + OmegaOrg : float + (HPhi) Origin of frequency for spectrum, input from file. + OmegaIm : float + (HPhi) Imaginary part of frequency. + SpectrumQ : np.ndarray + (HPhi) Wavenumber (q-vector) in fractional coordinate + (shape ``(3,)``). + SpectrumBody : int + (HPhi) One- or two-body excitation, defined from ``SpectrumType``. + OutputExVec : str + (HPhi) The name of output mode for the excited vector, input from file. + dt : float + (HPhi) Time step. + tshift : float + (HPhi) Shift of time-step of laser. + tdump : float + (HPhi) Time scale of dumping. + freq : float + (HPhi) Frequency of laser. + Uquench : float + (HPhi) Quenched on-site potential. + VecPot : np.ndarray + (HPhi) Vector potential (shape ``(3,)``). + PumpType : str + (HPhi) The type of pump. + PumpBody : int + (HPhi) One- or two-body pumping, defined from ``PumpType``. + npump : None + (HPhi) Number of pump transfers (dynamic 1D int array). + pumpindx : None + (HPhi) Site/spin indices for pump (dynamic 3D int array). + pump : None + (HPhi) Coefficient for pump (dynamic 2D complex array). + At : None + (HPhi) Vector potential time series (dynamic 2D float array). + ExpandCoef : int + (HPhi) The number of Hamiltonian-vector operations for time evolution. + CParaFileHead : str + (mVMC) Header of the optimized wavefunction, input from file. + NVMCCalMode : int + (mVMC) Optimization (=0) or compute correlation function (=1), + input from file. + NLanczosMode : int + (mVMC) Power Lanczos (=1), input from file. + NDataIdxStart : int + (mVMC) Start index of trials, input from file. + NDataQtySmp : int + (mVMC) Number of trials, input from file. + NSPGaussLeg : int + (mVMC) Number of Gauss-Legendre points for spin projection, + input from file. + NMPTrans : int + (mVMC/UHF/HWAVE) Number of translation symmetry. + NSROptItrStep : int + (mVMC) Number of iterations for stochastic reconfiguration. + NSROptItrSmp : int + (mVMC) Number of steps for sampling. + NSROptFixSmp : int + (mVMC) Stochastic reconfiguration parameter. + DSROptRedCut : float + (mVMC) Stochastic reconfiguration parameter, input from file. + DSROptStaDel : float + (mVMC) Stochastic reconfiguration parameter, input from file. + DSROptStepDt : float + (mVMC) Stochastic reconfiguration parameter, input from file. + NVMCWarmUp : int + (mVMC) VMC warm-up steps. + NVMCInterval : int + (mVMC) VMC interval. + NVMCSample : int + (mVMC) VMC sample count. + NExUpdatePath : int + (mVMC) Exchange update path. + RndSeed : int + (mVMC/UHF/HWAVE) Random seed. + NSplitSize : int + (mVMC) Split size. + NSPStot : int + (mVMC) Total spin S. + NStore : int + (mVMC) Store flag. + NSRCG : int + (mVMC) SR-CG flag. + ComplexType : int + (mVMC) Complex type flag. + Lsub : int + (mVMC/UHF/HWAVE) Sublattice L. + Wsub : int + (mVMC/UHF/HWAVE) Sublattice W. + Hsub : int + (mVMC/UHF/HWAVE) Sublattice H. + NCellsub : int + (mVMC/UHF/HWAVE) Number of cells in a sublattice. + boxsub : np.ndarray + (mVMC/UHF/HWAVE) Sublattice box (shape ``(3, 3)``, int). + rboxsub : np.ndarray + (mVMC/UHF/HWAVE) Sublattice inverse box (shape ``(3, 3)``, int). + Orb : np.ndarray or None + (mVMC) ``[nsite][nsite]`` Orbital index (dynamic 2D int array). + AntiOrb : np.ndarray or None + (mVMC) ``[nsite][nsite]`` Anti-periodic switch (dynamic 2D int array). + NOrb : int + (mVMC) Number of independent orbital index. + NSym : int + (mVMC) Number of translation symmetries. + mix : float + (UHF/HWAVE) Linear mixing ratio for update. + eps : int + (UHF/HWAVE) Convergence threshold for Green's functions. + eps_slater : int + (UHF/HWAVE) Convergence threshold for Slater's functions. + Iteration_max : int + (UHF/HWAVE) Max number for iterations. + calcmode : str + (HWAVE) Calculation mode: UHF, UHFk. + fileprefix : str + (HWAVE) Prefix of output filenames. + export_all : int + (HWAVE) Output zero elements in UHFk mode. + lattice_gp : int + (HWAVE) Create ``lattice.gp``. + """ + + # ------------------------------------------------------------------ + # Parameters for LATTICE + # ------------------------------------------------------------------ + lattice: str = UNSET_STRING + a: float = 0.0 + length: np.ndarray = field(default_factory=lambda: np.zeros(3)) + W: int = 0 + L: int = 0 + Height: int = 0 + direct: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + box: np.ndarray = field(default_factory=lambda: np.zeros((3, 3), dtype=int)) + rbox: np.ndarray = field(default_factory=lambda: np.zeros((3, 3), dtype=int)) + NCell: int = 0 + Cell: None = None + NsiteUC: int = 0 + tau: None = None + + # ------------------------------------------------------------------ + # Parameters for MODEL + # ------------------------------------------------------------------ + model: str = UNSET_STRING + mu: float = 0.0 + + # Hopping parameters (complex) + t: complex = 0 + 0j + tp: complex = 0 + 0j + t0: complex = 0 + 0j + t0p: complex = 0 + 0j + t0pp: complex = 0 + 0j + t1: complex = 0 + 0j + t1p: complex = 0 + 0j + t1pp: complex = 0 + 0j + t2: complex = 0 + 0j + t2p: complex = 0 + 0j + t2pp: complex = 0 + 0j + tpp: complex = 0 + 0j + + # Coulomb parameters (float) + U: float = 0.0 + V: float = 0.0 + Vp: float = 0.0 + V0: float = 0.0 + V0p: float = 0.0 + V0pp: float = 0.0 + V1: float = 0.0 + V1p: float = 0.0 + V1pp: float = 0.0 + V2: float = 0.0 + V2p: float = 0.0 + V2pp: float = 0.0 + Vpp: float = 0.0 + + # Isotropic/anisotropic diagonal spin couplings (float) + JAll: float = 0.0 + JpAll: float = 0.0 + J0All: float = 0.0 + J0pAll: float = 0.0 + J0ppAll: float = 0.0 + J1All: float = 0.0 + J1pAll: float = 0.0 + J1ppAll: float = 0.0 + J2All: float = 0.0 + J2pAll: float = 0.0 + J2ppAll: float = 0.0 + JppAll: float = 0.0 + + # Spin coupling matrices (3x3 float arrays) + J: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + Jp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J0: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J0p: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J0pp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J1: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J1p: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J1pp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J2: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J2p: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + J2pp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + Jpp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + D: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + + # Magnetic field parameters + h: float = 0.0 + Gamma: float = 0.0 + Gamma_y: float = 0.0 + K: float = 0.0 + + # ------------------------------------------------------------------ + # Phase for the boundary + # ------------------------------------------------------------------ + phase: np.ndarray = field(default_factory=lambda: np.zeros(3)) + ExpPhase: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=complex)) + AntiPeriod: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=int)) + + # ------------------------------------------------------------------ + # Transfer, Interaction, Locspin + # ------------------------------------------------------------------ + nsite: int = 0 + locspinflag: None = None + ntrans: int = 0 + transindx: None = None + trans: None = None + nintr: int = 0 + Lintr: int = 0 + intrindx: None = None + intr: None = None + NCintra: int = 0 + LCintra: int = 0 + CintraIndx: None = None + Cintra: None = None + NCinter: int = 0 + LCinter: int = 0 + CinterIndx: None = None + Cinter: None = None + NHund: int = 0 + LHund: int = 0 + HundIndx: None = None + Hund: None = None + NEx: int = 0 + LEx: int = 0 + ExIndx: None = None + Ex: None = None + NPairLift: int = 0 + LPairLift: int = 0 + PLIndx: None = None + PairLift: None = None + NPairHopp: int = 0 + LPairHopp: int = 0 + PHIndx: None = None + PairHopp: None = None + lBoost: int = 0 + + # ------------------------------------------------------------------ + # Calculation conditions + # ------------------------------------------------------------------ + ncond: int = 0 + lGC: int = 0 + S2: int = 0 + outputmode: str = UNSET_STRING + CDataFileHead: str = UNSET_STRING + Sz2: int = 0 + ioutputmode: int = 0 + + # ------------------------------------------------------------------ + # Wannier90 mode + # ------------------------------------------------------------------ + cutoff_t: float = 0.0 + cutoff_u: float = 0.0 + cutoff_j: float = 0.0 + cutoff_length_t: float = 0.0 + cutoff_length_U: float = 0.0 + cutoff_length_J: float = 0.0 + cutoff_tR: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=int)) + cutoff_UR: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=int)) + cutoff_JR: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=int)) + cutoff_tVec: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + cutoff_UVec: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + cutoff_JVec: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) + lambda_: float = 0.0 + lambda_U: float = 0.0 + lambda_J: float = 0.0 + double_counting_mode: str = UNSET_STRING + alpha: float = 0.0 + + # ------------------------------------------------------------------ + # Solver selector + # ------------------------------------------------------------------ + solver: str = "" + + # ------------------------------------------------------------------ + # HPhi fields + # ------------------------------------------------------------------ + method: str = UNSET_STRING + Restart: str = UNSET_STRING + InitialVecType: str = UNSET_STRING + EigenVecIO: str = UNSET_STRING + HamIO: str = UNSET_STRING + FlgTemp: int = 0 + Lanczos_max: int = 0 + initial_iv: int = 0 + nvec: int = 0 + exct: int = 0 + LanczosEps: int = 0 + LanczosTarget: int = 0 + NumAve: int = 0 + ExpecInterval: int = 0 + LargeValue: float = 0.0 + NGPU: int = 0 + Scalapack: int = 0 + list_6spin_pair: None = None + list_6spin_star: None = None + num_pivot: int = 0 + ishift_nspin: int = 0 + CalcSpec: str = UNSET_STRING + SpectrumType: str = UNSET_STRING + Nomega: int = 0 + OmegaMax: float = 0.0 + OmegaMin: float = 0.0 + OmegaOrg: float = 0.0 + OmegaIm: float = 0.0 + SpectrumQ: np.ndarray = field(default_factory=lambda: np.zeros(3)) + SpectrumBody: int = 0 + OutputExVec: str = UNSET_STRING + dt: float = 0.0 + tshift: float = 0.0 + tdump: float = 0.0 + freq: float = 0.0 + Uquench: float = 0.0 + VecPot: np.ndarray = field(default_factory=lambda: np.zeros(3)) + PumpType: str = UNSET_STRING + PumpBody: int = 0 + npump: None = None + pumpindx: None = None + pump: None = None + At: None = None + ExpandCoef: int = 0 + + # ------------------------------------------------------------------ + # mVMC fields + # ------------------------------------------------------------------ + CParaFileHead: str = UNSET_STRING + NVMCCalMode: int = 0 + NLanczosMode: int = 0 + NDataIdxStart: int = 0 + NDataQtySmp: int = 0 + NSPGaussLeg: int = 0 + NMPTrans: int = 0 + NSROptItrStep: int = 0 + NSROptItrSmp: int = 0 + NSROptFixSmp: int = 0 + DSROptRedCut: float = 0.0 + DSROptStaDel: float = 0.0 + DSROptStepDt: float = 0.0 + NVMCWarmUp: int = 0 + NVMCInterval: int = 0 + NVMCSample: int = 0 + NExUpdatePath: int = 0 + RndSeed: int = 0 + NSplitSize: int = 0 + NSPStot: int = 0 + NStore: int = 0 + NSRCG: int = 0 + ComplexType: int = 0 + Lsub: int = 0 + Wsub: int = 0 + Hsub: int = 0 + NCellsub: int = 0 + boxsub: np.ndarray = field(default_factory=lambda: np.zeros((3, 3), dtype=int)) + rboxsub: np.ndarray = field(default_factory=lambda: np.zeros((3, 3), dtype=int)) + Orb: None = None + AntiOrb: None = None + NOrb: int = 0 + NSym: int = 0 + + # ------------------------------------------------------------------ + # UHF / HWAVE fields + # ------------------------------------------------------------------ + mix: float = 0.0 + eps: int = 0 + eps_slater: int = 0 + Iteration_max: int = 0 + + # ------------------------------------------------------------------ + # HWAVE-only fields + # ------------------------------------------------------------------ + calcmode: str = UNSET_STRING + fileprefix: str = UNSET_STRING + export_all: int = 0 + lattice_gp: int = 0 diff --git a/python/stdface/core/version.py b/python/stdface/core/version.py new file mode 100644 index 0000000..1a812fe --- /dev/null +++ b/python/stdface/core/version.py @@ -0,0 +1,45 @@ +""" +Version information for StdFace. + +mVMC - A numerical solver package for a wide range of quantum lattice models +based on many-variable Variational Monte Carlo method Copyright (C) 2016 The +University of Tokyo, All rights reserved. + +This program is developed based on the mVMC-mini program +(https://github.com/fiber-miniapp/mVMC-mini) +which follows "The BSD 3-Clause License". + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/. +""" + +from __future__ import annotations + +# Semantic Versioning http://semver.org +# ..- +VERSION_MAJOR = 0 +VERSION_MINOR = 5 +VERSION_PATCH = 0 +VERSION_PRERELEASE = "" # "alpha", "beta.1", etc. + + +def print_version() -> None: + """Print the StdFace version string to standard output. + + Prints a version string in the format ``major.minor.patch``. + If ``VERSION_PRERELEASE`` is non-empty, ``-`` is appended. + """ + version_str = f"StdFace version {VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}" + if VERSION_PRERELEASE: + version_str += f"-{VERSION_PRERELEASE}" + print(version_str) diff --git a/python/stdface/lattice/boost_output.py b/python/stdface/lattice/boost_output.py new file mode 100644 index 0000000..65f08c5 --- /dev/null +++ b/python/stdface/lattice/boost_output.py @@ -0,0 +1,147 @@ +"""Boost-output helpers for writing ``boost.def`` sections. + +This module provides functions used by lattice builders that support HPhi's +Boost method (chain, honeycomb, kagome, ladder). Each function writes a +specific section of the ``boost.def`` file. + +Functions +--------- +write_boost_mag_field + Write the magnetic-field line to ``boost.def``. +write_boost_j_full + Write a full 3×3 J-coupling matrix to ``boost.def``. +write_boost_j_symmetric + Write an upper-triangle symmetric 3×3 J-coupling matrix to ``boost.def``. +write_boost_6spin_star + Write the ``list_6spin_star`` array to ``boost.def``. +write_boost_6spin_pair + Write the ``list_6spin_pair`` array to ``boost.def``. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +from typing import TextIO + +import numpy as np + +from ..core.stdface_vals import StdIntList + + +def write_boost_mag_field(fp: TextIO, StdI: StdIntList) -> None: + """Write the magnetic-field header line to ``boost.def``. + + Outputs ``# Magnetic field`` followed by a line containing the three + scaled field components ``-0.5 * Gamma``, ``-0.5 * Gamma_y``, and + ``-0.5 * h``. + + Parameters + ---------- + fp : TextIO + Open file handle for ``boost.def``. + StdI : StdIntList + Model parameter structure (read-only access to ``Gamma``, + ``Gamma_y``, ``h``). + """ + fp.write("# Magnetic field\n") + fp.write( + f"{-0.5 * StdI.Gamma:25.15e} " + f"{-0.5 * StdI.Gamma_y:25.15e} " + f"{-0.5 * StdI.h:25.15e}\n" + ) + + +def write_boost_j_full(fp: TextIO, J: np.ndarray, scale: float = 0.25) -> None: + """Write a full 3×3 J-coupling matrix to ``boost.def``. + + Outputs three rows of three values each, where each element is + ``scale * J[i, j]``. Used by the chain lattice. + + Parameters + ---------- + fp : TextIO + Open file handle for ``boost.def``. + J : numpy.ndarray + 3×3 coupling matrix. + scale : float, optional + Multiplicative scaling factor. Default is ``0.25``. + """ + for row in J: + scaled = scale * row + fp.write(f"{scaled[0]:25.15e} {scaled[1]:25.15e} {scaled[2]:25.15e}\n") + + +def write_boost_j_symmetric(fp: TextIO, J: np.ndarray, scale: float = 0.25) -> None: + """Write an upper-triangle symmetric 3×3 J-coupling matrix to ``boost.def``. + + Outputs three rows using only upper-triangle elements:: + + J[0,0] J[0,1] J[0,2] + J[0,1] J[1,1] J[1,2] + J[0,2] J[1,2] J[2,2] + + Each element is multiplied by *scale*. Used by honeycomb, ladder, + and kagome lattices. + + Parameters + ---------- + fp : TextIO + Open file handle for ``boost.def``. + J : numpy.ndarray + 3×3 coupling matrix (only upper-triangle elements are read). + scale : float, optional + Multiplicative scaling factor. Default is ``0.25``. + """ + Jsym = np.triu(J) + np.triu(J, 1).T + write_boost_j_full(fp, Jsym, scale) + + +def write_boost_6spin_star(fp: TextIO, StdI: StdIntList) -> None: + """Write the ``list_6spin_star`` array to ``boost.def``. + + Outputs each pivot's 7-element row from ``StdI.list_6spin_star``. + + Parameters + ---------- + fp : TextIO + Open file handle for ``boost.def``. + StdI : StdIntList + Structure containing ``num_pivot`` and ``list_6spin_star``. + """ + fp.write("# StdI->list_6spin_star\n") + for ipivot in range(StdI.num_pivot): + fp.write(f"# pivot {ipivot}\n") + row = StdI.list_6spin_star[ipivot, :7] + fp.write(" ".join(str(x) for x in row) + " \n") + + +def write_boost_6spin_pair(fp: TextIO, StdI: StdIntList) -> None: + """Write the ``list_6spin_pair`` array to ``boost.def``. + + Outputs each pivot's interaction rows from ``StdI.list_6spin_pair``, + with the number of interactions per pivot given by + ``StdI.list_6spin_star[ipivot, 0]``. + + Parameters + ---------- + fp : TextIO + Open file handle for ``boost.def``. + StdI : StdIntList + Structure containing ``num_pivot``, ``list_6spin_star``, + and ``list_6spin_pair``. + """ + fp.write("# StdI->list_6spin_pair\n") + for ipivot in range(StdI.num_pivot): + fp.write(f"# pivot {ipivot}\n") + for kintr in range(StdI.list_6spin_star[ipivot, 0]): + row = StdI.list_6spin_pair[ipivot, :7, kintr] + fp.write(" ".join(str(x) for x in row) + " \n") diff --git a/python/stdface/lattice/geometry_output.py b/python/stdface/lattice/geometry_output.py new file mode 100644 index 0000000..53caec3 --- /dev/null +++ b/python/stdface/lattice/geometry_output.py @@ -0,0 +1,145 @@ +"""Geometry and structure output functions. + +This module provides functions for writing lattice geometry files used by +post-processing tools and visualisation programs. + +Functions +--------- +print_xsf + Write ``lattice.xsf`` in XCrysDen format. +print_geometry + Write ``geometry.dat`` for correlation-function post-processing. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType, SolverType + + +def _cell_diff(Cell, iCell: int, jCell: int) -> list[int]: + """Compute difference between two cell coordinate rows as integers. + + Parameters + ---------- + Cell : np.ndarray + ``(NCell, 3)`` array of fractional cell coordinates. + iCell : int + Row index of the first (minuend) cell. + jCell : int + Row index of the second (subtrahend) cell. + + Returns + ------- + list of int + ``[int(Cell[iCell, k] - Cell[jCell, k]) for k in range(3)]``. + """ + return (Cell[iCell] - Cell[jCell]).astype(int).tolist() + + +def print_xsf(StdI: StdIntList) -> None: + """Print lattice.xsf file (XCrysDen format). + + Writes a ``lattice.xsf`` file containing primitive vectors, + optional conventional vectors (for orthorhombic and face-centred + lattices), and atomic coordinates for visualisation. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. The following fields are read: + + - ``lattice`` : str -- lattice type name. + - ``box`` : ndarray (3x3) -- supercell box matrix. + - ``direct`` : ndarray (3x3) -- direct lattice vectors. + - ``length`` : ndarray (3,) -- lattice constant lengths. + - ``NCell`` : int -- number of unit cells. + - ``NsiteUC`` : int -- sites per unit cell. + - ``Cell`` : ndarray (NCell, 3) -- cell fractional coordinates. + - ``tau`` : ndarray (NsiteUC, 3) -- basis positions. + """ + do_convvec = StdI.lattice in ( + "orthorhombic", "face-centeredorthorhombic", + "fcorthorhombic", "fco", "pyrochlore") + + with open("lattice.xsf", "w") as fp: + fp.write("CRYSTAL\n") + fp.write("PRIMVEC\n") + # PRIMVEC = box @ direct (each row is a primitive vector) + primvec = StdI.box @ StdI.direct + for vec in primvec: + fp.write(f"{vec[0]:15.5f} {vec[1]:15.5f} {vec[2]:15.5f}\n") + + if do_convvec: + fp.write("CONVVEC\n") + for ii, length_val in enumerate(StdI.length): + row = [0.0, 0.0, 0.0] + row[ii] = length_val + fp.write(f"{row[0]:15.5f} {row[1]:15.5f} {row[2]:15.5f}\n") + + fp.write("PRIMCOORD\n") + fp.write(f"{StdI.NCell * StdI.NsiteUC} 1\n") + for iCell in range(StdI.NCell): + for isite in range(StdI.NsiteUC): + # vec = (Cell[iCell] + tau[isite]) @ direct + frac_coord = StdI.Cell[iCell, :] + StdI.tau[isite, :] + vec = frac_coord @ StdI.direct + fp.write(f"H {vec[0]:15.5f} {vec[1]:15.5f} {vec[2]:15.5f}\n") + + +def print_geometry(StdI: StdIntList) -> None: + """Print geometry.dat for post-processing of correlation functions. + + Writes a ``geometry.dat`` file containing direct lattice vectors, + boundary phases, the supercell box matrix, and site coordinates + relative to the first cell. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. The following fields are read: + + - ``solver`` : str -- solver name. + - ``calcmode`` : str -- calculation mode (HWAVE only). + - ``direct`` : ndarray (3x3) -- direct lattice vectors. + - ``phase`` : ndarray (3,) -- boundary phase angles. + - ``box`` : ndarray (3x3) -- supercell box matrix. + - ``NCell`` : int -- number of unit cells. + - ``NsiteUC`` : int -- sites per unit cell. + - ``Cell`` : ndarray (NCell, 3) -- cell fractional coordinates. + - ``model`` : str -- model name (``"kondo"`` doubles sites). + """ + if (StdI.solver == SolverType.HWAVE + and StdI.calcmode in ("uhfk", "rpa")): + return + + with open("geometry.dat", "w") as fp: + for row in StdI.direct: + fp.write(f"{row[0]:25.15e} {row[1]:25.15e} {row[2]:25.15e}\n") + fp.write(f"{StdI.phase[0]:25.15e} " + f"{StdI.phase[1]:25.15e} " + f"{StdI.phase[2]:25.15e}\n") + for row in StdI.box: + fp.write(f"{int(row[0])} {int(row[1])} {int(row[2])}\n") + + for iCell in range(StdI.NCell): + diff = _cell_diff(StdI.Cell, iCell, 0) + for isite in range(StdI.NsiteUC): + fp.write(f"{diff[0]} {diff[1]} {diff[2]} {isite}\n") + if StdI.model == ModelType.KONDO: + for iCell in range(StdI.NCell): + diff = _cell_diff(StdI.Cell, iCell, 0) + for isite in range(StdI.NsiteUC): + fp.write(f"{diff[0]} {diff[1]} {diff[2]} " + f"{isite + StdI.NsiteUC}\n") diff --git a/python/stdface/lattice/input_params.py b/python/stdface/lattice/input_params.py new file mode 100644 index 0000000..2777d20 --- /dev/null +++ b/python/stdface/lattice/input_params.py @@ -0,0 +1,301 @@ +"""Input parameter resolution helpers for spin, Coulomb, and hopping. + +This module provides functions that resolve user-specified input parameters +into concrete values, handling conflicts between isotropic and anisotropic +specifications. These are used by all lattice modules when setting up +Hamiltonian parameters. + +Functions +--------- +input_spin_nn + Resolve nearest-neighbour spin-spin interaction (handles J/J0 conflicts). +input_spin + Resolve spin-spin interaction for beyond-nearest-neighbour bonds. +input_coulomb_v + Resolve off-site Coulomb interaction. +input_hopp + Resolve hopping integral. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import itertools +import math + +import numpy as np + +from ..core.param_check import exit_program, SPIN_SUFFIXES + + + +# --------------------------------------------------------------------------- +# Vectorised conflict-detection helpers +# --------------------------------------------------------------------------- + + +def _has_set_elements(mat: np.ndarray) -> bool: + """Return True if *mat* contains any non-NaN elements. + + Parameters + ---------- + mat : numpy.ndarray + A 3×3 float array. + + Returns + ------- + bool + True if at least one element is finite (not NaN). + """ + return bool(np.any(~np.isnan(mat))) + + +def _first_set_index(mat: np.ndarray) -> tuple[int, int] | None: + """Return the ``(row, col)`` of the first non-NaN element, or None. + + Parameters + ---------- + mat : numpy.ndarray + A 3×3 float array. + + Returns + ------- + tuple[int, int] or None + Index of the first set element, or None if all NaN. + """ + mask = ~np.isnan(mat) + if not np.any(mask): + return None + idx = np.argmax(mask) # index of first True in flattened array + return int(idx // 3), int(idx % 3) + + +def _check_scalar_vs_matrix(scalar: float, mat: np.ndarray, + scalar_name: str, mat_name: str) -> None: + """Abort if both a scalar and any matrix element are set. + + Parameters + ---------- + scalar : float + Isotropic scalar value (NaN means unset). + mat : numpy.ndarray + 3×3 anisotropic matrix (NaN elements are unset). + scalar_name : str + Display name for the scalar in error messages. + mat_name : str + Base name for the matrix in error messages. + """ + if math.isnan(scalar): + return + idx = _first_set_index(mat) + if idx is not None: + i1, i2 = idx + print(f"\n ERROR! {scalar_name} and {mat_name}{SPIN_SUFFIXES[i1][i2]} conflict !\n") + exit_program(-1) + + +def _check_matrix_vs_matrix(mat_a: np.ndarray, mat_b: np.ndarray, + name_a: str, name_b: str) -> None: + """Abort if any element in *mat_a* and any element in *mat_b* are both set. + + Parameters + ---------- + mat_a : numpy.ndarray + First 3×3 matrix (NaN elements are unset). + mat_b : numpy.ndarray + Second 3×3 matrix (NaN elements are unset). + name_a : str + Base name for *mat_a* in error messages. + name_b : str + Base name for *mat_b* in error messages. + """ + idx_a = _first_set_index(mat_a) + idx_b = _first_set_index(mat_b) + if idx_a is not None and idx_b is not None: + i1, i2 = idx_a + i3, i4 = idx_b + print(f"\n ERROR! {name_a}{SPIN_SUFFIXES[i1][i2]} " + f"and {name_b}{SPIN_SUFFIXES[i3][i4]} conflict !\n") + exit_program(-1) + + +def _resolve_spin_matrix( + J0: np.ndarray, + J0name: str, + *, + J: np.ndarray | None = None, + J0All: float = float("nan"), + JAll: float = float("nan"), +) -> None: + """Fill a 3×3 spin interaction matrix in-place using the resolution cascade. + + Priority order for each element ``(i, j)``: + 1. Explicit ``J0[i, j]`` (already set) + 2. Fallback ``J[i, j]`` (if provided and set) + 3. Diagonal ``J0All`` (if ``i == j``) + 4. Diagonal ``JAll`` (if ``i == j``) + 5. Default ``0.0`` + + Parameters + ---------- + J0 : numpy.ndarray + Output 3×3 matrix (modified in-place). + J0name : str + Display name for the interaction. + J : numpy.ndarray or None, optional + Fallback 3×3 matrix. Default is None (no fallback). + J0All : float, optional + Isotropic value for this bond. Default is NaN (unset). + JAll : float, optional + Global isotropic value. Default is NaN (unset). + """ + for i1, i2 in itertools.product(range(3), repeat=2): + resolved = False + if not math.isnan(J0[i1, i2]): + resolved = True + elif J is not None and not math.isnan(J[i1, i2]): + J0[i1, i2] = J[i1, i2] + resolved = True + elif i1 == i2 and not math.isnan(J0All): + J0[i1, i2] = J0All + resolved = True + elif i1 == i2 and not math.isnan(JAll): + J0[i1, i2] = JAll + resolved = True + else: + J0[i1, i2] = 0.0 + + if resolved: + label = J0name + SPIN_SUFFIXES[i1][i2] + print(f" {label:>14s} = {J0[i1, i2]:<10.5f}") + + +def input_spin_nn( + J: np.ndarray, + JAll: float, + J0: np.ndarray, + J0All: float, + J0name: str, +) -> None: + """Input nearest-neighbour spin-spin interaction. + + Resolves conflicts between isotropic and anisotropic specifications + and fills in the output matrix *J0* in-place. + + Parameters + ---------- + J : numpy.ndarray + Anisotropic spin interaction (3x3). + JAll : float + Isotropic interaction. + J0 : numpy.ndarray + Output anisotropic spin interaction (3x3, modified in-place). + J0All : float + Isotropic interaction for this bond. + J0name : str + Name of this spin interaction (e.g. ``"J1"``). + """ + # Scalar-scalar conflict: JAll vs J0All + if not math.isnan(JAll) and not math.isnan(J0All): + print(f"\n ERROR! {J0name} conflict !\n") + exit_program(-1) + + # Scalar-matrix conflicts + _check_scalar_vs_matrix(JAll, J, "J", "J") + _check_scalar_vs_matrix(J0All, J, J0name, "J") + _check_scalar_vs_matrix(J0All, J0, J0name, J0name) + _check_scalar_vs_matrix(JAll, J0, J0name, J0name) + + # Cross-matrix conflict: any J0 element vs any J element + _check_matrix_vs_matrix(J0, J, J0name, "J") + + # Resolve values using the cascade + _resolve_spin_matrix(J0, J0name, J=J, J0All=J0All, JAll=JAll) + + +def input_spin(Jp: np.ndarray, JpAll: float, Jpname: str) -> None: + """Input spin-spin interaction other than nearest-neighbour. + + Parameters + ---------- + Jp : numpy.ndarray + Fully anisotropic spin interaction (3x3, modified in-place). + JpAll : float + Isotropic interaction value. + Jpname : str + Name of this spin interaction (e.g. ``"J'"``). + """ + # Scalar-matrix conflict + _check_scalar_vs_matrix(JpAll, Jp, Jpname, Jpname) + + # Resolve values + _resolve_spin_matrix(Jp, Jpname, J0All=JpAll) + + +def input_coulomb_v(V: float, V0: float, V0name: str) -> float: + """Input off-site Coulomb interaction from the input file. + + Parameters + ---------- + V : float + Isotropic Coulomb interaction (may be NaN). + V0 : float + Specific Coulomb parameter (may be NaN). + V0name : str + Name of the parameter (e.g. ``"V1"``). + + Returns + ------- + float + The resolved value of *V0*. + """ + if not math.isnan(V) and not math.isnan(V0): + print(f"\n ERROR! {V0name} conflicts !\n") + exit_program(-1) + elif not math.isnan(V0): + print(f" {V0name:>15s} = {V0:<10.5f}") + elif not math.isnan(V): + V0 = V + print(f" {V0name:>15s} = {V0:<10.5f}") + else: + V0 = 0.0 + return V0 + + +def input_hopp(t: complex, t0: complex, t0name: str) -> complex: + """Input hopping integral from the input file. + + Parameters + ---------- + t : complex + Isotropic hopping (may have NaN real part). + t0 : complex + Specific hopping parameter (may have NaN real part). + t0name : str + Name of the parameter (e.g. ``"t1"``). + + Returns + ------- + complex + The resolved value of *t0*. + """ + if not math.isnan(t.real) and not math.isnan(t0.real): + print(f"\n ERROR! {t0name} conflicts !\n") + exit_program(-1) + elif not math.isnan(t0.real): + print(f" {t0name:>15s} = {t0.real:<10.5f} {t0.imag:<10.5f}") + elif not math.isnan(t.real): + t0 = t + print(f" {t0name:>15s} = {t0.real:<10.5f} {t0.imag:<10.5f}") + else: + t0 = 0.0 + 0j + return t0 diff --git a/python/stdface/lattice/interaction_builder.py b/python/stdface/lattice/interaction_builder.py new file mode 100644 index 0000000..603ce31 --- /dev/null +++ b/python/stdface/lattice/interaction_builder.py @@ -0,0 +1,719 @@ +"""Interaction term builder functions. + +This module provides functions for constructing one-body (transfer) and +two-body (interaction) Hamiltonian terms, as well as the memory allocation +helper that prepares the arrays these builders populate. + +Functions +--------- +trans + Add a single transfer (one-body) term. +hopping + Add hopping for both spin channels. +hubbard_local + Add intra-Coulomb, magnetic field, and chemical potential. +mag_field + Add longitudinal and transverse magnetic field terms. +intr + Add a general two-body (InterAll) interaction term. +general_j + Add a general 3x3 spin-spin interaction. +coulomb + Add an off-site Coulomb interaction term. +compute_max_interactions + Compute upper limits for transfer and interaction arrays. +malloc_interactions + Allocate arrays for transfer and interaction terms. +add_neighbor_interaction + Label a neighbor bond and add spin or electron interaction terms (2D). +add_neighbor_interaction_3d + Find a neighbor site and add spin or electron interaction terms (3D). +add_local_terms + Add on-site (magnetic field, anisotropy, Hubbard U, Kondo) terms for one site. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import math + +import numpy as np + +from typing import TextIO + +from ..core.stdface_vals import StdIntList, ModelType, SolverType, MethodType, ZERO_BODY_EPS, AMPLITUDE_EPS + + +def trans( + StdI: StdIntList, + trans0: complex, + isite: int, + ispin: int, + jsite: int, + jspin: int, +) -> None: + """Add a transfer (one-body) term to the list. + + Appends to ``StdI.trans`` and ``StdI.transindx`` and increments + ``StdI.ntrans``. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + trans0 : complex + Hopping integral t, mu, etc. + isite : int + Site index i for c†_{i σ}. + ispin : int + Spin index σ for c†_{i σ}. + jsite : int + Site index j for c_{j σ'}. + jspin : int + Spin index σ' for c_{j σ'}. + """ + if abs(trans0) < ZERO_BODY_EPS: + return + n = StdI.ntrans + StdI.trans[n] = trans0 + StdI.transindx[n] = [isite, ispin, jsite, jspin] + StdI.ntrans = n + 1 + + +def hopping( + StdI: StdIntList, + trans0: complex, + isite: int, + jsite: int, + dR: np.ndarray, +) -> None: + """Add hopping for both spin channels. + + Both c†_{i σ} c_{j σ} and c†_{j σ} c_{i σ} for every spin channel + (σ) are added. For HPhi time-evolution pump mode, the pump arrays + are populated instead. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + trans0 : complex + Hopping integral t. + isite : int + Site index i. + jsite : int + Site index j. + dR : numpy.ndarray + Distance vector R_i - R_j (shape ``(3,)``). + """ + if (StdI.solver == SolverType.HPhi + and StdI.method == MethodType.TIME_EVOLUTION + and StdI.PumpBody == 1): + for it in range(StdI.Lanczos_max): + Cphase = np.dot(StdI.At[it], dR) + coef = np.exp(-1j * Cphase) + for ispin in range(2): + n = StdI.npump[it] + StdI.pump[it][n] = coef * trans0 + StdI.pumpindx[it][n] = [isite, ispin, jsite, ispin] + n += 1 + + StdI.pump[it][n] = np.conj(coef * trans0) + StdI.pumpindx[it][n] = [jsite, ispin, isite, ispin] + StdI.npump[it] = n + 1 + else: + for ispin in range(2): + trans(StdI, trans0, jsite, ispin, isite, ispin) + trans(StdI, np.conj(trans0), isite, ispin, jsite, ispin) + + +def hubbard_local( + StdI: StdIntList, + mu0: float, + h0: float, + Gamma0: float, + Gamma0_y: float, + U0: float, + isite: int, +) -> None: + """Add intra-Coulomb, magnetic field and chemical potential for itinerant electrons. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + mu0 : float + Chemical potential. + h0 : float + Longitudinal magnetic field. + Gamma0 : float + Transverse magnetic field (x). + Gamma0_y : float + Transverse magnetic field (y). + U0 : float + Intra-site Coulomb potential. + isite : int + Site index. + """ + trans(StdI, mu0 - 0.5 * h0, isite, 0, isite, 0) + trans(StdI, mu0 + 0.5 * h0, isite, 1, isite, 1) + trans(StdI, -0.5 * Gamma0, isite, 1, isite, 0) + trans(StdI, -0.5 * Gamma0, isite, 0, isite, 1) + trans(StdI, -0.5 * 1j * Gamma0_y, isite, 1, isite, 0) + trans(StdI, 0.5 * 1j * Gamma0_y, isite, 0, isite, 1) + + StdI.Cintra[StdI.NCintra] = U0 + StdI.CintraIndx[StdI.NCintra][0] = isite + StdI.NCintra += 1 + + +def mag_field( + StdI: StdIntList, + S2: int, + h: float, + Gamma: float, + Gamma_y: float, + isite: int, +) -> None: + """Add longitudinal and transverse magnetic field terms. + + Uses the Bogoliubov representation for arbitrary spin S. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + S2 : int + Twice the spin moment (2S) at site *isite*. + h : float + Longitudinal magnetic field. + Gamma : float + Transverse magnetic field (x). + Gamma_y : float + Transverse magnetic field (y). + isite : int + Site index. + """ + S = S2 * 0.5 + for ispin in range(S2 + 1): + Sz = S - float(ispin) + # Longitudinal part: -h σ c†_{i σ} c_{i σ} + trans(StdI, -h * Sz, isite, ispin, isite, ispin) + # Transverse part + if ispin > 0: + factor = math.sqrt(S * (S + 1.0) - Sz * (Sz + 1.0)) + trans(StdI, -0.5 * Gamma * factor - 0.5 * 1j * Gamma_y * factor, + isite, ispin, isite, ispin - 1) + trans(StdI, -0.5 * Gamma * factor + 0.5 * 1j * Gamma_y * factor, + isite, ispin - 1, isite, ispin) + + +def intr( + StdI: StdIntList, + intr0: complex, + site1: int, spin1: int, + site2: int, spin2: int, + site3: int, spin3: int, + site4: int, spin4: int, +) -> None: + """Add a general two-body (InterAll) interaction term to the list. + + Appends to ``StdI.intr`` and ``StdI.intrindx`` and increments + ``StdI.nintr``. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + intr0 : complex + Interaction coefficient U, V, J, etc. + site1, spin1 : int + Indices for c†_{i₁ σ₁}. + site2, spin2 : int + Indices for c_{i₂ σ₂}. + site3, spin3 : int + Indices for c†_{i₃ σ₃}. + site4, spin4 : int + Indices for c_{i₄ σ₄}. + """ + if abs(intr0) < ZERO_BODY_EPS: + return + n = StdI.nintr + StdI.intr[n] = intr0 + StdI.intrindx[n] = [site1, spin1, site2, spin2, site3, spin3, site4, spin4] + StdI.nintr = n + 1 + + +def _spin_ladder_factor(S: float, Sz: float) -> float: + """Compute the spin-ladder matrix element sqrt(S(S+1) - Sz(Sz+1)). + + This is the factor ```` appearing in the + spin-raising and lowering operator matrix elements. + + Parameters + ---------- + S : float + Total spin quantum number. + Sz : float + z-component of spin. + + Returns + ------- + float + ``sqrt(S(S+1) - Sz(Sz+1))`` + """ + return math.sqrt(S * (S + 1.0) - Sz * (Sz + 1.0)) + + +def _add_spin_half_terms( + StdI: StdIntList, + J: np.ndarray, + isite: int, + jsite: int, +) -> tuple[bool, bool]: + """Add shortcut Hund/Cinter/Ex/PairLift terms for spin-1/2 sites. + + When at least one site is spin-1/2, the Ising (Jzz) part is handled + via dedicated Hund and Cinter arrays, and the transverse part may be + handled via Ex and PairLift arrays if the off-diagonal J components + are negligible. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + J : numpy.ndarray + 3x3 spin interaction matrix. + isite : int + First site index. + jsite : int + Second site index. + + Returns + ------- + tuple of (bool, bool) + ``(use_z_general, use_ex_general)`` — flags indicating whether the + general intr() path should still handle the Jzz and exchange terms, + respectively. + """ + # Hund: -0.5 * Jzz + n = StdI.NHund + StdI.Hund[n] = -0.5 * J[2, 2] + StdI.HundIndx[n] = [isite, jsite] + StdI.NHund = n + 1 + + # Cinter: -0.25 * Jzz + n = StdI.NCinter + StdI.Cinter[n] = -0.25 * J[2, 2] + StdI.CinterIndx[n] = [isite, jsite] + StdI.NCinter = n + 1 + + # Check whether off-diagonal J elements allow Ex/PairLift shortcut + cond_offdiag = (abs(J[0, 1]) < AMPLITUDE_EPS and abs(J[1, 0]) < AMPLITUDE_EPS) + if StdI.solver == SolverType.mVMC: + cond_offdiag = cond_offdiag and (abs(J[0, 0] - J[1, 1]) < AMPLITUDE_EPS) + + if not cond_offdiag: + return False, True # z handled by shortcut, ex goes through general + + # Exchange + if StdI.solver == SolverType.mVMC or StdI.model == ModelType.KONDO: + StdI.Ex[StdI.NEx] = -0.25 * (J[0, 0] + J[1, 1]) + else: + StdI.Ex[StdI.NEx] = 0.25 * (J[0, 0] + J[1, 1]) + StdI.ExIndx[StdI.NEx] = [isite, jsite] + StdI.NEx += 1 + + # Pair lift + n = StdI.NPairLift + StdI.PairLift[n] = 0.25 * (J[0, 0] - J[1, 1]) + StdI.PLIndx[n] = [isite, jsite] + StdI.NPairLift = n + 1 + + return False, False # both handled by shortcut + + +def general_j( + StdI: StdIntList, + J: np.ndarray, + Si2: int, + Sj2: int, + isite: int, + jsite: int, +) -> None: + """Treat J as a 3x3 matrix for general spin interactions. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + J : numpy.ndarray + 3x3 spin interaction matrix. + Si2 : int + Twice the spin moment (2S) at site *isite*. + Sj2 : int + Twice the spin moment (2S) at site *jsite*. + isite : int + First site index. + jsite : int + Second site index. + """ + if Si2 == 1 or Sj2 == 1: + use_z, use_ex = _add_spin_half_terms(StdI, J, isite, jsite) + else: + use_z, use_ex = True, True + + Si = 0.5 * Si2 + Sj = 0.5 * Sj2 + + for ispin in range(Si2 + 1): + Siz = Si - float(ispin) + for jspin in range(Sj2 + 1): + Sjz = Sj - float(jspin) + + # (1) J_z S_{iz} S_{jz} + if use_z: + intr0 = J[2, 2] * Siz * Sjz + intr(StdI, intr0, + isite, ispin, isite, ispin, + jsite, jspin, jsite, jspin) + + if ispin > 0 and jspin > 0 and use_ex: + fi = _spin_ladder_factor(Si, Siz) + fj = _spin_ladder_factor(Sj, Sjz) + + # (2) S_i^+ S_j^- + h.c. + intr0 = 0.25 * (J[0, 0] + J[1, 1] + 1j * (J[0, 1] - J[1, 0])) * fi * fj + intr(StdI, intr0, + isite, ispin - 1, isite, ispin, + jsite, jspin, jsite, jspin - 1) + intr(StdI, np.conj(intr0), + isite, ispin, isite, ispin - 1, + jsite, jspin - 1, jsite, jspin) + + # (3) S_i^+ S_j^+ + h.c. + intr0 = 0.25 * (J[0, 0] - J[1, 1] - 1j * (J[0, 1] + J[1, 0])) * fi * fj + intr(StdI, intr0, + isite, ispin - 1, isite, ispin, + jsite, jspin - 1, jsite, jspin) + intr(StdI, np.conj(intr0), + isite, ispin, isite, ispin - 1, + jsite, jspin, jsite, jspin - 1) + + # (4) S_i^+ S_{jz} + h.c. + if ispin > 0: + fi = _spin_ladder_factor(Si, Siz) + intr0 = 0.5 * (J[0, 2] - 1j * J[1, 2]) * fi * Sjz + intr(StdI, intr0, + isite, ispin - 1, isite, ispin, + jsite, jspin, jsite, jspin) + intr(StdI, np.conj(intr0), + jsite, jspin, jsite, jspin, + isite, ispin, isite, ispin - 1) + + # (5) S_{iz} S_j^+ + h.c. + if jspin > 0: + fj = _spin_ladder_factor(Sj, Sjz) + intr0 = 0.5 * (J[2, 0] - 1j * J[2, 1]) * Siz * fj + intr(StdI, intr0, + isite, ispin, isite, ispin, + jsite, jspin - 1, jsite, jspin) + intr(StdI, np.conj(intr0), + jsite, jspin, jsite, jspin - 1, + isite, ispin, isite, ispin) + + +def coulomb(StdI: StdIntList, V: float, isite: int, jsite: int) -> None: + """Add an onsite/offsite Coulomb interaction term. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + V : float + Coulomb integral U, V, etc. + isite : int + First site index. + jsite : int + Second site index. + """ + n = StdI.NCinter + StdI.Cinter[n] = V + StdI.CinterIndx[n] = [isite, jsite] + StdI.NCinter = n + 1 + + +def compute_max_interactions( + StdI: StdIntList, + n_bonds: int, +) -> tuple[int, int]: + """Compute upper limits for the transfer and interaction arrays. + + Uses the standard formula shared by most lattice builders. The + lattice-specific information is captured by *n_bonds*, the total + number of distinct neighbor bond types per unit cell (sum of + nearest, next-nearest, and third-nearest neighbor bonds). + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (read-only; uses ``model``, ``nsite``, + ``NCell``, ``NsiteUC``, and ``S2``). + n_bonds : int + Total number of neighbor bond types per unit cell + (e.g., ``3 + 6 + 4 = 13`` for orthorhombic). + + Returns + ------- + tuple of (int, int) + ``(ntransMax, nintrMax)`` — upper limits for allocation. + """ + if StdI.model == ModelType.SPIN: + ntransMax = StdI.nsite * (StdI.S2 + 1 + 2 * StdI.S2) + nintrMax = (StdI.NCell * (StdI.NsiteUC + n_bonds) + * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1)) + else: + ntransMax = StdI.NCell * 2 * (2 * StdI.NsiteUC + 2 * n_bonds) + nintrMax = StdI.NCell * (StdI.NsiteUC + 4 * n_bonds) + if StdI.model == ModelType.KONDO: + ntransMax += StdI.nsite // 2 * (StdI.S2 + 1 + 2 * StdI.S2) + nintrMax += (StdI.nsite // 2 + * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1)) + return ntransMax, nintrMax + + +def malloc_interactions(StdI: StdIntList, ntransMax: int, nintrMax: int) -> None: + """Allocate arrays for interactions. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + ntransMax : int + Upper limit of the number of transfer terms. + nintrMax : int + Upper limit of the number of interaction terms. + """ + # (1) Transfer + StdI.transindx = np.zeros((ntransMax, 4), dtype=int) + StdI.trans = np.zeros(ntransMax, dtype=complex) + StdI.ntrans = 0 + + # HPhi pump arrays + if (StdI.solver == SolverType.HPhi + and StdI.method == MethodType.TIME_EVOLUTION + and StdI.PumpBody == 1): + StdI.npump = np.zeros(StdI.Lanczos_max, dtype=int) + StdI.pumpindx = np.zeros((StdI.Lanczos_max, ntransMax, 4), dtype=int) + StdI.pump = np.zeros((StdI.Lanczos_max, ntransMax), dtype=complex) + + # (2) InterAll + StdI.intrindx = np.zeros((nintrMax, 8), dtype=int) + StdI.intr = np.zeros(nintrMax, dtype=complex) + StdI.nintr = 0 + + # (3)-(8) Two-body shortcut arrays: (indx_attr, val_attr, count_attr, ncols) + _SHORTCUT_ARRAYS = ( + ("CintraIndx", "Cintra", "NCintra", 1), + ("CinterIndx", "Cinter", "NCinter", 2), + ("HundIndx", "Hund", "NHund", 2), + ("ExIndx", "Ex", "NEx", 2), + ("PLIndx", "PairLift", "NPairLift", 2), + ("PHIndx", "PairHopp", "NPairHopp", 2), + ) + for indx_attr, val_attr, count_attr, ncols in _SHORTCUT_ARRAYS: + setattr(StdI, indx_attr, np.zeros((nintrMax, ncols), dtype=int)) + setattr(StdI, val_attr, np.zeros(nintrMax)) + setattr(StdI, count_attr, 0) + + +def _dispatch_bond_interaction( + StdI: StdIntList, + isite: int, jsite: int, + Cphase: complex, dR: np.ndarray, + J: np.ndarray, t: complex, V: float, +) -> None: + """Apply the model-dependent interaction for a single bond. + + For spin models calls :func:`general_j`; for electron models calls + :func:`hopping` and :func:`coulomb`. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + isite, jsite : int + Global site indices of the two bond endpoints. + Cphase : complex + Boundary phase factor. + dR : numpy.ndarray + Distance vector R_i − R_j (shape ``(3,)``). + J : numpy.ndarray + 3×3 spin-coupling matrix (used when model is SPIN). + t : complex + Hopping amplitude *before* phase multiplication (used otherwise). + V : float + Coulomb repulsion (used otherwise). + """ + if StdI.model == ModelType.SPIN: + general_j(StdI, J, StdI.S2, StdI.S2, isite, jsite) + else: + hopping(StdI, Cphase * t, isite, jsite, dR) + coulomb(StdI, V, isite, jsite) + + +def add_neighbor_interaction( + StdI: StdIntList, + fp: TextIO | None, + iW: int, iL: int, + diW: int, diL: int, + isiteUC: int, jsiteUC: int, + connect: int, + J: np.ndarray, + t: complex, + V: float, +) -> tuple[int, int, complex, np.ndarray]: + """Label a neighbor bond and add the appropriate model interaction. + + Combines ``set_label`` with the model-dependent dispatch that appears + in every lattice builder: for spin models calls ``general_j``; for + electron models calls ``hopping`` + ``coulomb``. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + fp : TextIO or None + Gnuplot file handle (may be ``None``). + iW, iL : int + Cell position of the initial site. + diW, diL : int + Translation to the neighbor. + isiteUC, jsiteUC : int + Unit-cell site indices for initial and final sites. + connect : int + Connection type for gnuplot (1 = nearest, 2 = 2nd, 3+ = no arrow). + J : numpy.ndarray + 3×3 spin-coupling matrix (used when model is SPIN). + t : complex + Hopping amplitude *before* phase multiplication (used otherwise). + V : float + Coulomb repulsion (used otherwise). + + Returns + ------- + isite : int + Global index of the initial site. + jsite : int + Global index of the final site. + Cphase : complex + Boundary phase factor. + dR : numpy.ndarray + Distance vector R_i − R_j. + """ + from .site_util import set_label # local import to avoid circular dependency + + isite, jsite, Cphase, dR = set_label( + StdI, fp, iW, iL, diW, diL, isiteUC, jsiteUC, connect) + _dispatch_bond_interaction(StdI, isite, jsite, Cphase, dR, J, t, V) + return isite, jsite, Cphase, dR + + +def add_neighbor_interaction_3d( + StdI: StdIntList, + iW: int, iL: int, iH: int, + diW: int, diL: int, diH: int, + isiteUC: int, jsiteUC: int, + J: np.ndarray, + t: complex, + V: float, +) -> tuple[int, int, complex, np.ndarray]: + """Find a 3D neighbor site and add the appropriate model interaction. + + Combines ``find_site`` with the model-dependent dispatch: for spin + models calls ``general_j``; for electron models calls ``hopping`` + + ``coulomb``. This is the 3D counterpart of + :func:`add_neighbor_interaction`. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + iW, iL, iH : int + Cell position of the initial site. + diW, diL, diH : int + Translation to the neighbor. + isiteUC, jsiteUC : int + Unit-cell site indices for initial and final sites. + J : numpy.ndarray + 3×3 spin-coupling matrix (used when model is SPIN). + t : complex + Hopping amplitude *before* phase multiplication (used otherwise). + V : float + Coulomb repulsion (used otherwise). + + Returns + ------- + isite : int + Global index of the initial site. + jsite : int + Global index of the final site. + Cphase : complex + Boundary phase factor. + dR : numpy.ndarray + Distance vector R_i − R_j. + """ + from .site_util import find_site # local import to avoid circular dependency + + isite, jsite, Cphase, dR = find_site( + StdI, iW, iL, iH, diW, diL, diH, isiteUC, jsiteUC) + _dispatch_bond_interaction(StdI, isite, jsite, Cphase, dR, J, t, V) + return isite, jsite, Cphase, dR + + +def add_local_terms( + StdI: StdIntList, + isite: int, + jsite_kondo: int, +) -> None: + """Add on-site interaction terms for a single site. + + Handles the model-dependent local terms that every lattice builder + applies to each site: + + * **SPIN**: magnetic field + single-ion anisotropy ``D``. + * **HUBBARD**: Hubbard ``U``, chemical potential, and magnetic field. + * **KONDO**: same as HUBBARD for the itinerant site, plus a Kondo + coupling ``J`` and magnetic field on the localized spin site + *jsite_kondo*. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + isite : int + Global site index. For KONDO models this is the itinerant + (electron) site; for SPIN this is the spin site. + jsite_kondo : int + Global site index of the localized spin in the Kondo model. + Ignored when the model is not KONDO. + """ + if StdI.model == ModelType.SPIN: + mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, isite) + general_j(StdI, StdI.D, StdI.S2, StdI.S2, isite, isite) + else: + hubbard_local(StdI, StdI.mu, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, + StdI.U, isite) + if StdI.model == ModelType.KONDO: + general_j(StdI, StdI.J, 1, StdI.S2, isite, jsite_kondo) + mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, + jsite_kondo) diff --git a/python/stdface/lattice/site_util.py b/python/stdface/lattice/site_util.py new file mode 100644 index 0000000..891459c --- /dev/null +++ b/python/stdface/lattice/site_util.py @@ -0,0 +1,659 @@ +"""Super-cell site initialisation, folding, and labelling utilities. + +This module provides functions for setting up the simulation super-cell, +folding site coordinates into the cell, finding site indices, and writing +gnuplot labels for 2-D lattice visualisation. + +Functions +--------- +init_site + Initialise the super-cell (box, reciprocal box, cells, gnuplot header). +find_site + Find global site indices and boundary phase for a pair of sites. +set_label + Write gnuplot labels and return site indices (2-D lattices). +lattice_gp + Context manager for ``lattice.gp`` gnuplot output (2-D lattices). +close_lattice_xsf + Write ``lattice.xsf``, ``geometry.dat``, and finalise 3-D lattice output. + +The private helper ``_fold_site`` is also available for use by other +modules that need to fold a coordinate into the original cell. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import itertools +from contextlib import contextmanager +from collections.abc import Iterator +from typing import TextIO + +import numpy as np + +from ..core.stdface_vals import StdIntList, ModelType, SolverType, NaN_i, AMPLITUDE_EPS +from ..core.param_check import exit_program, print_val_i +from .geometry_output import print_geometry, print_xsf + + +def _cell_vector(Cell: np.ndarray, idx: int) -> list[int]: + """Extract a cell row as a list of three ints. + + ``StdI.Cell`` is stored as a ``float`` array, so individual elements + must be cast to ``int`` whenever they are used in integer arithmetic + or coordinate comparison. This helper centralises that conversion. + + Parameters + ---------- + Cell : np.ndarray + ``(NCell, 3)`` array of fractional cell coordinates. + idx : int + Row index of the cell to extract. + + Returns + ------- + list of int + ``[int(Cell[idx, 0]), int(Cell[idx, 1]), int(Cell[idx, 2])]``. + """ + return [int(Cell[idx, 0]), int(Cell[idx, 1]), int(Cell[idx, 2])] + + +def _find_cell_index(StdI: StdIntList, cellV: list[int]) -> int: + """Find the cell index whose coordinates match *cellV*. + + Performs a linear search over ``StdI.Cell`` to find the row whose + three coordinates match *cellV*. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure containing the ``Cell`` array and + ``NCell`` count. + cellV : list of int + Length-3 fractional coordinate vector to search for. + + Returns + ------- + int + Cell index ``k`` such that ``StdI.Cell[k]`` equals *cellV*. + Returns 0 if no match is found (matching the original C behavior). + """ + for k in range(StdI.NCell): + if _cell_vector(StdI.Cell, k) == cellV: + return k + return 0 + + +def _fold_to_cell( + rbox: np.ndarray, + ncell: int, + box: np.ndarray, + iCellV: list[int], +) -> tuple[list[int], list[int]]: + """Fold a site coordinate into a cell defined by *box*. + + This is the core algorithm shared by :func:`_fold_site` (main + super-cell) and the sub-lattice folding in ``mvmc_variational``. + + Parameters + ---------- + rbox : np.ndarray + 3×3 reciprocal (cofactor) matrix of *box*. + ncell : int + Determinant of *box* (number of cells). + box : np.ndarray + 3×3 lattice-vector matrix defining the cell. + iCellV : list of int + Fractional coordinate of a site (length 3). + + Returns + ------- + nBox : list of int + Index of the periodic image that originally contained this site. + iCellV_fold : list of int + Fractional coordinate folded into the cell. + """ + # (1) Transform to fractional coordinate (times ncell) + iCellV_arr = np.asarray(iCellV) + iCellV_frac = rbox @ iCellV_arr + + # (2) Search which periodic image contains this cell + nBox = (iCellV_frac + ncell * 1000) // ncell - 1000 + + # (3) Fractional coordinate in the original cell + iCellV_frac = iCellV_frac - ncell * nBox + + # (4) Transform back to lattice coordinates and fold + iCellV_fold = (box.T @ iCellV_frac + ncell * 1000) // ncell - 1000 + + return nBox.astype(int).tolist(), iCellV_fold.astype(int).tolist() + + +def _fold_site( + StdI: StdIntList, + iCellV: list[int], +) -> tuple[list[int], list[int]]: + """Move a site into the original super-cell if it is outside. + + Delegates to :func:`_fold_to_cell` with the main super-cell + parameters (``rbox``, ``NCell``, ``box``). + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. + iCellV : list of int + Fractional coordinate of a site (length 3). + + Returns + ------- + nBox : list of int + Index of the super-cell that originally contained this site. + iCellV_fold : list of int + Fractional coordinate folded into the original cell. + """ + return _fold_to_cell(StdI.rbox, StdI.NCell, StdI.box, iCellV) + + +def _write_gnuplot_header(fp: TextIO, StdI: StdIntList) -> None: + """Write the gnuplot header for ``lattice.gp`` (2D lattices only). + + Computes the four corner positions of the super-cell from the + ``direct`` and ``box`` matrices, sets up axis ranges, styles, and + draws the boundary as four arrows. + + Parameters + ---------- + fp : file object + Open file to write gnuplot commands to. + StdI : StdIntList + Model parameter structure. Reads ``direct`` and ``box``. + """ + pos = np.zeros((4, 2)) + # pos[1:3] = supercell corner positions: box[:2,:2] @ direct[:2,:2] + pos[1:3, :] = StdI.box[:2, :2] @ StdI.direct[:2, :2] + pos[3, :] = pos[1, :] + pos[2, :] + + xmin = min(pos[:, 0].min(), pos[:, 1].min()) - 2.0 + xmax = max(pos[:, 0].max(), pos[:, 1].max()) + 2.0 + + fp.write("#set terminal pdf color enhanced \\\n") + fp.write("#dashed dl 1.0 size 20.0cm, 20.0cm \n") + fp.write('#set output "lattice.pdf"\n') + fp.write(f"set xrange [{xmin:f}: {xmax:f}]\n") + fp.write(f"set yrange [{xmin:f}: {xmax:f}]\n") + fp.write("set size square\n") + fp.write("unset key\n") + fp.write("unset tics\n") + fp.write("unset border\n") + fp.write("set style line 1 lc 1 lt 1\n") + fp.write("set style line 2 lc 5 lt 1\n") + fp.write("set style line 3 lc 0 lt 1\n") + + # Draw boundary arrows: 0→1→3→2→0 + corners = [(0, 1), (1, 3), (3, 2), (2, 0)] + for src, dst in corners: + fp.write(f"set arrow from {pos[src][0]:f}, {pos[src][1]:f} " + f"to {pos[dst][0]:f}, {pos[dst][1]:f} nohead front ls 3\n") + + +_LATTICE_GP_FOOTER = "plot '-' w d lc 7\n0.0 0.0\nend\npause -1\n" + + +@contextmanager +def lattice_gp(StdI: StdIntList) -> Iterator[TextIO | None]: + """Context manager for ``lattice.gp`` gnuplot output. + + Opens ``lattice.gp`` for writing when the solver is not H-wave, or + when ``StdI.lattice_gp`` is explicitly set to 1. Otherwise the + yielded handle is ``None``. On exit the gnuplot footer is written, + the file is closed, and :func:`print_geometry` is called. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. + + Yields + ------ + fp : TextIO or None + Open file handle, or ``None`` when output is suppressed. + """ + fp: TextIO | None = None + if StdI.solver != SolverType.HWAVE or StdI.lattice_gp == 1: + fp = open("lattice.gp", "w") + try: + yield fp + finally: + if fp is not None: + fp.write(_LATTICE_GP_FOOTER) + fp.close() + print_geometry(StdI) + + +def close_lattice_xsf(StdI: StdIntList) -> None: + """Write ``lattice.xsf``, ``geometry.dat``, and print geometry. + + This is the 3-D counterpart of :func:`lattice_gp`. It writes + the XCrySDen structure file via :func:`print_xsf` and the + ``geometry.dat`` file via :func:`print_geometry`. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. + """ + print_xsf(StdI) + print_geometry(StdI) + + +def _validate_box_params( + L: int, W: int, Height: int, + box: np.ndarray, + suffix: str = "", + defaults: np.ndarray | None = None, +) -> tuple[int, int, int]: + """Validate and normalise L/W/Height vs box specification. + + Exactly one of {L, W, Height} or {box entries} may be specified + (i.e. differ from ``NaN_i``). If both are given an error is raised. + When L/W/Height are specified, the box is filled as a diagonal matrix + ``diag(W, L, Height)``. When the box entries are specified (or + neither is), each entry is validated with ``print_val_i`` using + *defaults* as the fallback values. + + Parameters + ---------- + L, W, Height : int + Scalar lattice dimensions (may be ``NaN_i`` for unset). + box : np.ndarray + 3x3 box matrix (modified **in-place**). + suffix : str, optional + Label suffix appended to parameter names (e.g. ``""`` for main + lattice, ``"sub"`` for the sub-lattice). Default ``""``. + defaults : np.ndarray or None, optional + 3x3 array of default values for the box-entry branch. If + ``None``, the identity matrix ``diag(1, 1, 1)`` is used. + + Returns + ------- + L, W, Height : int + Validated (and possibly defaulted) values. + + Raises + ------ + SystemExit + If both L/W/Height and box entries are specified simultaneously. + """ + sfx = suffix + lwh_specified = (L != NaN_i or W != NaN_i or Height != NaN_i) + box_specified = any(box[i, j] != NaN_i for i in range(3) for j in range(3)) + + if lwh_specified and box_specified: + lwh_names = f"(L{sfx}, W{sfx}, H{sfx + 'eight' if not sfx else sfx})" + box_names = f"(a0W{sfx}, ..., a2H{sfx})" + print(f"\nERROR ! {lwh_names} and {box_names} conflict !\n") + exit_program(-1) + elif lwh_specified: + L = print_val_i(f"L{sfx}", L, 1) + W = print_val_i(f"W{sfx}", W, 1) + h_label = f"H{sfx + 'eight' if not sfx else sfx}" + Height = print_val_i(h_label, Height, 1) + box[:, :] = 0 + box[0, 0] = W + box[1, 1] = L + box[2, 2] = Height + else: + if defaults is None: + defaults = np.eye(3, dtype=int) + _labels = [ + [f"a0W{sfx}", f"a0L{sfx}", f"a0H{sfx}"], + [f"a1W{sfx}", f"a1L{sfx}", f"a1H{sfx}"], + [f"a2W{sfx}", f"a2L{sfx}", f"a2H{sfx}"], + ] + for i in range(3): + for j in range(3): + box[i, j] = print_val_i( + _labels[i][j], int(box[i, j]), int(defaults[i, j])) + + return L, W, Height + + +def _det_and_cofactor(box: np.ndarray) -> tuple[int, np.ndarray]: + """Compute the determinant and cofactor matrix of a 3x3 integer matrix. + + Uses the Sarrus rule for the determinant and the standard cofactor + formula for the adjugate (transposed cofactor matrix). If the + determinant is negative, both the determinant and cofactor matrix + are sign-flipped so that the returned determinant is non-negative. + + Parameters + ---------- + box : np.ndarray + 3x3 integer matrix. + + Returns + ------- + det : int + Absolute value of the determinant (always >= 0). + cofactor : np.ndarray + 3x3 cofactor matrix, sign-adjusted so that ``det >= 0``. + """ + # Compute determinant using Sarrus rule (equivalent to np.linalg.det for 3x3) + det = int(round(np.linalg.det(box.astype(float)))) + + # Compute cofactor matrix using vectorized indexing + # cofactor[i,j] = box[(i+1)%3, (j+1)%3] * box[(i+2)%3, (j+2)%3] + # - box[(i+1)%3, (j+2)%3] * box[(i+2)%3, (j+1)%3] + idx = np.array([1, 2, 0]) # (i+1) % 3 for i=0,1,2 + idx2 = np.array([2, 0, 1]) # (i+2) % 3 for i=0,1,2 + cofactor = (box[idx][:, idx] * box[idx2][:, idx2] + - box[idx][:, idx2] * box[idx2][:, idx]) + + if det < 0: + cofactor = -cofactor + det = -det + + return det, cofactor + + +def _compute_reciprocal_box(StdI: StdIntList) -> None: + """Compute the number of cells and the reciprocal-box matrix. + + Sets ``StdI.NCell`` to the determinant of ``StdI.box`` and + ``StdI.rbox`` to the cofactor matrix (adjugate transpose) of + ``StdI.box``. If the determinant is negative the sign of both + ``NCell`` and ``rbox`` is flipped so that ``NCell > 0``. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + Reads ``box``; sets ``NCell`` and ``rbox``. + + Raises + ------ + SystemExit + If the determinant is zero (degenerate super-cell). + """ + StdI.NCell, StdI.rbox = _det_and_cofactor(StdI.box) + print(f" Number of Cell = {abs(StdI.NCell)}") + if StdI.NCell == 0: + exit_program(-1) + + +def _enumerate_cells(StdI: StdIntList) -> None: + """Enumerate all unit cells inside the super-cell. + + Allocates ``StdI.Cell`` of shape ``(NCell, 3)`` and fills it with the + integer coordinates of each unit cell that lies inside the super-cell. + The super-cell is defined by the ``box`` matrix; a coordinate belongs + to the cell if folding it via ``_fold_site`` produces ``nBox == [0,0,0]``. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + Reads ``NCell`` and ``box``; sets ``Cell``. + """ + # Find bounding box by checking all 8 cube corners + box_int = StdI.box.astype(int) + # All 8 corners of the unit cube: shape (8, 3) + corners = np.array(list(itertools.product(range(2), repeat=3))) + # edges[corner, dim] = corner @ box_int[:, dim] => corners @ box_int + edges = corners @ box_int + # bound[dim] = [min_edge, max_edge] + bound = list(zip(edges.min(axis=0).tolist(), edges.max(axis=0).tolist())) + + # Enumerate cells within the bounding box + # Note: iteration order must be ic2 outermost, ic0 innermost (matching C code) + StdI.Cell = np.zeros((StdI.NCell, 3), dtype=int) + jj_idx = 0 + for ic2, ic1, ic0 in itertools.product( + range(bound[2][0], bound[2][1] + 1), + range(bound[1][0], bound[1][1] + 1), + range(bound[0][0], bound[0][1] + 1), + ): + iCellV = [ic0, ic1, ic2] + nBox, iCellV_fold = _fold_site(StdI, iCellV) + if nBox == [0, 0, 0]: + StdI.Cell[jj_idx, :] = iCellV + jj_idx += 1 + + +def init_site(StdI: StdIntList, fp: TextIO | None, dim: int) -> None: + """Initialize the super-cell where simulation is performed. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + fp : file object or None + File pointer to ``lattice.gp`` (may be ``None``). + dim : int + Dimension of the system. If 2, the gnuplot header for + ``lattice.gp`` is written. + """ + print("\n @ Super-Lattice setting\n") + + # (1) Check input parameters about the shape of super-cell + StdI.L, StdI.W, StdI.Height = _validate_box_params( + StdI.L, StdI.W, StdI.Height, StdI.box) + + if dim == 2: + StdI.direct[:2, 2] = 0.0 # zero z-component of first two vectors + StdI.direct[2, :] = [0.0, 0.0, 1.0] # third vector = unit z + + # (2) Define the phase factor at each boundary + if dim == 2: + StdI.phase[2] = 0.0 + StdI.ExpPhase = np.exp(1j * (np.pi / 180.0) * StdI.phase) + StdI.AntiPeriod = np.where(np.abs(StdI.ExpPhase + 1.0) < AMPLITUDE_EPS, 1, 0) + + # (3) Allocate tau (intrinsic structure of unit-cell) + StdI.tau = np.zeros((StdI.NsiteUC, 3)) + + # (4) Calculate reciprocal lattice vectors and NCell + _compute_reciprocal_box(StdI) + + # (5) Find cells in the super-cell + _enumerate_cells(StdI) + + # (6) For 2D, print lattice.gp header + if dim == 2 and fp is not None: + _write_gnuplot_header(fp, StdI) + + +def find_site( + StdI: StdIntList, + iW: int, iL: int, iH: int, + diW: int, diL: int, diH: int, + isiteUC: int, jsiteUC: int, +) -> tuple[int, int, complex, np.ndarray]: + """Find the site indices and boundary phase for a pair of sites. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. + iW, iL, iH : int + Position of the initial site. + diW, diL, diH : int + Translation from the initial site. + isiteUC : int + Intrinsic site index of the initial site in the unit cell. + jsiteUC : int + Intrinsic site index of the final site in the unit cell. + + Returns + ------- + isite : int + Global index of the initial site. + jsite : int + Global index of the final site. + Cphase : complex + Boundary phase factor. + dR : numpy.ndarray + Distance vector R_i - R_j in fractional coordinates (shape ``(3,)``). + """ + di = np.array([diW, diL, diH], dtype=float) + dR = -di + StdI.tau[isiteUC, :] - StdI.tau[jsiteUC, :] + + jCellV = [iW + diW, iL + diL, iH + diH] + nBox, jCellV = _fold_site(StdI, jCellV) + Cphase = np.prod(StdI.ExpPhase ** np.array(nBox)) + + jCell = _find_cell_index(StdI, jCellV) + iCell = _find_cell_index(StdI, [iW, iL, iH]) + + isite = iCell * StdI.NsiteUC + isiteUC + jsite = jCell * StdI.NsiteUC + jsiteUC + if StdI.model == ModelType.KONDO: + isite += StdI.NCell * StdI.NsiteUC + jsite += StdI.NCell * StdI.NsiteUC + + return isite, jsite, Cphase, dR + + +def _write_gnuplot_bond( + fp: TextIO, + isite: int, jsite: int, + xi: float, yi: float, + xj: float, yj: float, + connect: int, +) -> None: + """Write gnuplot label and arrow commands for a single bond. + + Writes two ``set label`` commands (one per site endpoint) and, + when *connect* < 3, a ``set arrow`` command connecting them. + + Parameters + ---------- + fp : TextIO + Open gnuplot file handle. + isite, jsite : int + Global site indices (used as label text). + xi, yi : float + 2-D position of site *isite*. + xj, yj : float + 2-D position of site *jsite*. + connect : int + Line-style selector. The arrow is only drawn when ``connect < 3``. + """ + for site, x, y in ((isite, xi, yi), (jsite, xj, yj)): + w = 1 if site < 10 else 2 + fp.write(f'set label "{site:{w}d}" at {x:f}, {y:f} center front\n') + if connect < 3: + fp.write(f"set arrow from {xi:f}, {yi:f} to {xj:f}, {yj:f} " + f"nohead ls {connect:d}\n") + + +def set_label( + StdI: StdIntList, + fp: TextIO | None, + iW: int, iL: int, + diW: int, diL: int, + isiteUC: int, jsiteUC: int, + connect: int, +) -> tuple[int, int, complex, np.ndarray]: + """Set label in the gnuplot display (2D systems only). + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. + fp : file object or None + File pointer to ``lattice.gp``. + iW, iL : int + Position of the initial site. + diW, diL : int + Translation from the initial site. + isiteUC : int + Intrinsic site index of the initial site. + jsiteUC : int + Intrinsic site index of the final site. + connect : int + Connection type (1 for nearest, 2 for 2nd nearest). + + Returns + ------- + isite : int + Global index of the initial site. + jsite : int + Global index of the final site. + Cphase : complex + Boundary phase factor. + dR : numpy.ndarray + Distance vector R_i - R_j. + """ + # First print the reversed one + isite, jsite, Cphase, dR = find_site( + StdI, iW, iL, 0, -diW, -diL, 0, jsiteUC, isiteUC) + + # Compute 2D positions via direct[:2,:2].T @ fractional_coords + D = StdI.direct[:2, :2] + frac_i = np.array([iW + StdI.tau[jsiteUC, 0], iL + StdI.tau[jsiteUC, 1]]) + frac_j = np.array([iW - diW + StdI.tau[isiteUC, 0], iL - diL + StdI.tau[isiteUC, 1]]) + xi, yi = frac_i @ D + xj, yj = frac_j @ D + + if fp is not None: + _write_gnuplot_bond(fp, isite, jsite, xi, yi, xj, yj, connect) + + # Then print the normal one + isite, jsite, Cphase, dR = find_site( + StdI, iW, iL, 0, diW, diL, 0, isiteUC, jsiteUC) + + frac_i = np.array([iW + StdI.tau[isiteUC, 0], iL + StdI.tau[isiteUC, 1]]) + frac_j = np.array([iW + diW + StdI.tau[jsiteUC, 0], iL + diL + StdI.tau[jsiteUC, 1]]) + xi, yi = frac_i @ D + xj, yj = frac_j @ D + + if fp is not None: + _write_gnuplot_bond(fp, isite, jsite, xi, yi, xj, yj, connect) + + return isite, jsite, Cphase, dR + + +def set_local_spin_flags(StdI: StdIntList, nsite_base: int) -> None: + """Set local spin flags and total site count based on the model type. + + This function is shared across all lattice modules. It sets + ``StdI.nsite`` (doubling for Kondo), allocates ``StdI.locspinflag``, + and fills the array according to the model type: + + * **SPIN**: all sites get ``S2`` + * **HUBBARD**: all sites get ``0`` + * **KONDO**: first half ``S2``, second half ``0`` + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + nsite_base : int + Number of sites before Kondo doubling (e.g. + ``NsiteUC * NCell``, ``L``, or ``L * NsiteUC``). + """ + StdI.nsite = nsite_base + if StdI.model == ModelType.KONDO: + StdI.nsite *= 2 + StdI.locspinflag = np.zeros(StdI.nsite, dtype=int) + + if StdI.model == ModelType.SPIN: + StdI.locspinflag[:] = StdI.S2 + elif StdI.model == ModelType.HUBBARD: + StdI.locspinflag[:] = 0 + elif StdI.model == ModelType.KONDO: + half = StdI.nsite // 2 + StdI.locspinflag[:half] = StdI.S2 + StdI.locspinflag[half:] = 0 + diff --git a/python/stdface/plugin.py b/python/stdface/plugin.py new file mode 100644 index 0000000..6800117 --- /dev/null +++ b/python/stdface/plugin.py @@ -0,0 +1,265 @@ +"""Solver plugin interface and registry. + +This module defines the :class:`SolverPlugin` abstract base class that every +solver plugin must implement, and a plugin registry for discovering and +retrieving plugins by name. + +New solver plugins should subclass :class:`SolverPlugin` and implement the +required abstract properties and methods. The :meth:`write` method provides +a template for the common output sequence (locspn → trans → interactions → +modpara → solver-specific → green → namelist); solvers with a different +sequence can override :meth:`write` entirely. +""" +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .core.stdface_vals import StdIntList + + +class SolverPlugin(ABC): + """Abstract base class for solver plugins. + + Each solver plugin provides: + + - A keyword table for parsing solver-specific input keywords. + - Field reset tables for initialising solver-specific fields on + :class:`StdIntList`. + - A :meth:`write` method to generate Expert-mode definition files. + - Optional hooks for post-lattice processing and field initialisation. + + Template Method + --------------- + The default :meth:`write` implementation calls a sequence of steps + that is common to most solvers:: + + write_locspn → write_trans → write_interactions → + check_and_write_modpara → write_solver_specific → + write_green → write_namelist + + Subclasses should override :meth:`write_solver_specific` for + solver-specific output (e.g. excitation files, variational parameters). + Solvers that need a completely different sequence (e.g. H-wave in + Wannier90 export mode) can override :meth:`write` directly. + + Attributes + ---------- + name : str + Canonical solver name (e.g. ``"HPhi"``). + keyword_table : dict[str, tuple] + Solver-specific keyword dispatch table. + reset_scalars : list[tuple[str, object]] + ``(field_name, sentinel_value)`` pairs for scalar field resets. + reset_arrays : list[tuple[str, object]] + ``(field_name, sentinel_value)`` pairs for array-fill field resets. + """ + + # ------------------------------------------------------------------ + # Abstract properties — must be implemented by every plugin + # ------------------------------------------------------------------ + + @property + @abstractmethod + def name(self) -> str: + """Canonical solver name.""" + + @property + @abstractmethod + def keyword_table(self) -> dict[str, tuple]: + """Solver-specific keyword dispatch table.""" + + @property + @abstractmethod + def reset_scalars(self) -> list[tuple[str, object]]: + """Scalar field reset table.""" + + @property + @abstractmethod + def reset_arrays(self) -> list[tuple[str, object]]: + """Array-fill field reset table.""" + + # ------------------------------------------------------------------ + # Template method for writing output files + # ------------------------------------------------------------------ + + def write(self, StdI: StdIntList) -> None: + """Write all Expert-mode definition files for this solver. + + This is a template method that calls the common output steps in + order. Override individual steps or this entire method as needed. + + Parameters + ---------- + StdI : StdIntList + The fully-populated parameter structure. + """ + self.write_locspn(StdI) + self.write_trans(StdI) + self.write_interactions(StdI) + self.check_and_write_modpara(StdI) + self.write_solver_specific(StdI) + self.check_output_mode(StdI) + self.write_green(StdI) + self.write_namelist(StdI) + + # ------------------------------------------------------------------ + # Default step implementations — override as needed + # ------------------------------------------------------------------ + + def write_locspn(self, StdI: StdIntList) -> None: + """Write locspn.def.""" + from .writer.common_writer import print_loc_spin + print_loc_spin(StdI) + + def write_trans(self, StdI: StdIntList) -> None: + """Write trans.def.""" + from .writer.common_writer import print_trans + print_trans(StdI) + + def write_interactions(self, StdI: StdIntList) -> None: + """Write interaction definition files.""" + from .writer.interaction_writer import print_interactions + print_interactions(StdI) + + def check_and_write_modpara(self, StdI: StdIntList) -> None: + """Check and write modpara.def.""" + from .writer.common_writer import check_mod_para, print_mod_para + check_mod_para(StdI) + print_mod_para(StdI) + + def write_solver_specific(self, StdI: StdIntList) -> None: + """Write solver-specific output files. + + Override this method to add solver-specific output (e.g. excitation + files for HPhi, variational parameter files for mVMC). + The default implementation does nothing. + + Parameters + ---------- + StdI : StdIntList + The fully-populated parameter structure. + """ + + def check_output_mode(self, StdI: StdIntList) -> None: + """Check and validate the output mode setting.""" + from .writer.common_writer import check_output_mode + check_output_mode(StdI) + + def write_green(self, StdI: StdIntList) -> None: + """Write Green's function definition files. + + Default writes both greenone.def and greentwo.def. + Override to write only a subset. + + Parameters + ---------- + StdI : StdIntList + The fully-populated parameter structure. + """ + from .writer.common_writer import print_1_green, print_2_green + print_1_green(StdI) + print_2_green(StdI) + + def write_namelist(self, StdI: StdIntList) -> None: + """Write namelist.def.""" + from .writer.common_writer import print_namelist + print_namelist(StdI) + + # ------------------------------------------------------------------ + # Optional lifecycle hooks + # ------------------------------------------------------------------ + + def post_lattice(self, StdI: StdIntList) -> None: + """Optional hook called after lattice construction. + + Override this to perform solver-specific post-lattice processing + (e.g. computing LargeValue for HPhi, running Boost). + The default implementation does nothing. + + Parameters + ---------- + StdI : StdIntList + The parameter structure (modified in place). + """ + + def init_fields(self, StdI: StdIntList) -> None: + """Optional hook to initialise solver-specific attributes on StdI. + + Override this to set up dynamic attributes that don't exist on the + base :class:`StdIntList` dataclass. The default implementation + does nothing. + + Parameters + ---------- + StdI : StdIntList + The parameter structure (modified in place). + """ + + +# --------------------------------------------------------------------------- +# Plugin registry +# --------------------------------------------------------------------------- + +_plugins: dict[str, SolverPlugin] = {} + + +def register(plugin: SolverPlugin) -> None: + """Register a solver plugin. + + Parameters + ---------- + plugin : SolverPlugin + The plugin instance to register. + + Raises + ------ + ValueError + If a plugin with the same name is already registered. + """ + if plugin.name in _plugins: + raise ValueError( + f"Solver plugin {plugin.name!r} is already registered" + ) + _plugins[plugin.name] = plugin + + +def get_plugin(name: str) -> SolverPlugin: + """Retrieve a registered solver plugin by name. + + Parameters + ---------- + name : str + The solver name (e.g. ``"HPhi"``). + + Returns + ------- + SolverPlugin + The registered plugin instance. + + Raises + ------ + KeyError + If no plugin with that name is registered. + """ + if name not in _plugins: + _discover_plugins() + if name not in _plugins: + raise KeyError( + f"No solver plugin registered for {name!r}. " + f"Available: {', '.join(_plugins) or '(none)'}" + ) + return _plugins[name] + + +def _discover_plugins() -> None: + """Load built-in plugins by importing the solvers package. + + This function is called lazily on first access to ensure plugins + are registered even if the solvers package hasn't been imported yet. + """ + try: + import stdface.solvers # noqa: F401 — triggers auto-registration + except ImportError: + pass diff --git a/python/stdface/solvers/__init__.py b/python/stdface/solvers/__init__.py new file mode 100644 index 0000000..6e943ac --- /dev/null +++ b/python/stdface/solvers/__init__.py @@ -0,0 +1,7 @@ +"""Built-in solver plugins for StdFace. + +Importing this package auto-registers all built-in solver plugins. +""" +from __future__ import annotations + +from . import hphi, mvmc, uhf, hwave # noqa: F401 — auto-register plugins diff --git a/python/stdface/solvers/hphi/__init__.py b/python/stdface/solvers/hphi/__init__.py new file mode 100644 index 0000000..646ae6f --- /dev/null +++ b/python/stdface/solvers/hphi/__init__.py @@ -0,0 +1,9 @@ +"""HPhi solver plugin package. + +Importing this package auto-registers the HPhi plugin. +""" +from __future__ import annotations + +from ._plugin import HPhiPlugin # noqa: F401 — public API + +__all__ = ["HPhiPlugin"] diff --git a/python/stdface/solvers/hphi/writer.py b/python/stdface/solvers/hphi/writer.py new file mode 100644 index 0000000..a4abdd2 --- /dev/null +++ b/python/stdface/solvers/hphi/writer.py @@ -0,0 +1,1100 @@ +"""HPhi solver-specific output functions. + +This module contains functions that generate definition files specific to the +HPhi (Exact Diagonalization) solver. These were extracted from +``stdface_main.py`` during refactoring. + +Functions +--------- +large_value + Compute the ``LargeValue`` parameter for TPQ calculations. +print_calc_mod + Write ``calcmod.def`` with calculation-mode integers. +print_excitation + Write ``single.def`` or ``pair.def`` for spectrum calculations. +vector_potential + Compute vector potential A(t) and electric field E(t) for time evolution. +print_pump + Write ``teone.def`` or ``tetwo.def`` for time-evolution pump terms. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" +from __future__ import annotations + +import math +from collections.abc import Callable +from typing import NamedTuple + +import numpy as np + +from ...core.stdface_vals import StdIntList, ModelType, MethodType, NaN_i, UNSET_STRING, AMPLITUDE_EPS +from ...core.param_check import exit_program, print_val_d, print_val_i +from ...writer.common_writer import _merge_duplicate_terms + +# --------------------------------------------------------------------------- +# String → integer dispatch tables for calcmod.def +# --------------------------------------------------------------------------- + +METHOD_TO_CALC_TYPE: dict[MethodType, int] = { + MethodType.LANCZOS: 0, + MethodType.LANCZOS_ENERGY: 0, + MethodType.TPQ: 1, + MethodType.FULLDIAG: 2, + MethodType.CG: 3, + MethodType.TIME_EVOLUTION: 4, + MethodType.CTPQ: 5, +} +"""Maps each HPhi calculation method to its ``CalcType`` integer code.""" + +RESTART_TO_INT: dict[str, int] = { + "none": 0, + "restart_out": 1, + "save": 1, + "restartsave": 2, + "restart": 2, + "restart_in": 3, +} +"""Maps each HPhi ``Restart`` string to its integer code.""" + +CALC_SPEC_TO_INT: dict[str, int] = { + "none": 0, + "normal": 1, + "noiteration": 2, + "restart_out": 3, + "restart_in": 4, + "restartsave": 5, + "restart": 5, +} +"""Maps each HPhi ``CalcSpec`` string to its integer code.""" + +MODEL_GC_TO_CALC_MODEL: dict[tuple, int] = { + (ModelType.HUBBARD, 0): 0, + (ModelType.HUBBARD, 1): 3, + (ModelType.SPIN, 0): 1, + (ModelType.SPIN, 1): 4, + (ModelType.KONDO, 0): 2, + (ModelType.KONDO, 1): 5, +} +"""Maps ``(ModelType, lGC)`` to the ``CalcModel`` integer code.""" + +INITIAL_VEC_TYPE_TO_INT: dict[str, int] = { + "c": 0, + "r": 1, +} +"""Maps the ``InitialVecType`` string to its integer code.""" + +class _IOFlags(NamedTuple): + """Pair of input/output flag integers for I/O dispatch tables. + + Attributes + ---------- + flag0 : int + First flag (InputEigenVec for EigenVecIO, iOutputHam for HamIO). + flag1 : int + Second flag (OutputEigenVec for EigenVecIO, iInputHam for HamIO). + """ + + flag0: int + flag1: int + + +EIGENVEC_IO_TO_FLAGS: dict[str, _IOFlags] = { + "none": _IOFlags(0, 0), + "in": _IOFlags(1, 0), + "out": _IOFlags(0, 1), + "inout": _IOFlags(1, 1), +} +"""Maps ``EigenVecIO`` string to ``(InputEigenVec, OutputEigenVec)`` flags.""" + +HAM_IO_TO_FLAGS: dict[str, _IOFlags] = { + "none": _IOFlags(0, 0), + "out": _IOFlags(1, 0), + "in": _IOFlags(0, 1), +} +"""Maps ``HamIO`` string to ``(iOutputHam, iInputHam)`` flags.""" + +OUTPUT_EX_VEC_TO_INT: dict[str, int] = { + "none": 0, + "out": 1, +} +"""Maps ``OutputExVec`` string to its integer code.""" + + +def _resolve_string_param( + StdI: StdIntList, + field: str, + label: str, + default: str, + default_value: int | tuple[int, int], + dispatch: dict, +) -> int | tuple[int, int]: + """Resolve a string-valued parameter to its integer code(s). + + If the field on *StdI* is ``UNSET_STRING``, it is set to *default* + and *default_value* is returned. Otherwise the field value is looked + up in *dispatch*; a missing key causes ``exit_program(-1)``. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure (field is read/written in-place). + field : str + Attribute name on *StdI* (e.g. ``"Restart"``). + label : str + Display label for log messages (e.g. ``"Restart"``). + default : str + Default string value when unset (e.g. ``"none"``). + default_value : int or tuple of int + Value(s) to return when the field is unset. + dispatch : dict + Mapping from string values to integer code(s). + + Returns + ------- + int or tuple of int + The resolved integer code(s). + """ + field_val = getattr(StdI, field) + if field_val == UNSET_STRING: + setattr(StdI, field, default) + print(f" {label:>20s} = {default:<12s}###### DEFAULT VALUE IS USED ######") + return default_value + print(f" {label:>20s} = {field_val}") + result = dispatch.get(field_val) + if result is None: + print(f"\n ERROR ! {label} : {field_val}") + exit_program(-1) + return result + + +def large_value(StdI: StdIntList) -> None: + """Compute and set the ``LargeValue`` parameter for TPQ calculations. + + ``LargeValue`` is the sum of the absolute values of all one-body + (transfer) and two-body (interaction / exchange / Hund / pair-lift / + Coulomb-intra / Coulomb-inter) terms, divided by the number of + sites. The result is stored in ``StdI.LargeValue`` via + :func:`print_val_d`. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. The following fields are read: + + - ``ntrans``, ``trans`` -- one-body transfer terms. + - ``nintr``, ``intr`` -- general two-body interaction terms. + - ``NCintra``, ``Cintra`` -- intra-site Coulomb terms. + - ``NCinter``, ``Cinter`` -- inter-site Coulomb terms. + - ``NEx``, ``Ex`` -- exchange terms. + - ``NPairLift``, ``PairLift`` -- pair-lift terms. + - ``NHund``, ``Hund`` -- Hund coupling terms. + - ``nsite`` -- total number of sites. + - ``LargeValue`` -- set **in place** to the computed value + (unless the user already specified it). + """ + large_value0 = ( + np.sum(np.abs(StdI.trans[:StdI.ntrans])) + + np.sum(np.abs(StdI.intr[:StdI.nintr])) + + np.sum(np.abs(StdI.Cintra[:StdI.NCintra])) + + np.sum(np.abs(StdI.Cinter[:StdI.NCinter])) + + 2.0 * np.sum(np.abs(StdI.Ex[:StdI.NEx])) + + 2.0 * np.sum(np.abs(StdI.PairLift[:StdI.NPairLift])) + + 2.0 * np.sum(np.abs(StdI.Hund[:StdI.NHund])) + ) + + large_value0 /= float(StdI.nsite) + + StdI.LargeValue = print_val_d("LargeValue", StdI.LargeValue, large_value0) + + +def _validate_ngpu_scalapack(StdI: StdIntList) -> None: + """Validate ``NGPU`` and ``Scalapack`` parameters if set. + + Prints the parameter values and calls :func:`exit_program` if + they are out of range. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. Reads ``NGPU`` and ``Scalapack``. + """ + if StdI.NGPU != NaN_i: + print(f" NGPU = {StdI.NGPU}") + if StdI.NGPU < 1: + print(f"\n ERROR ! NGPU : {StdI.NGPU}") + print(" NGPU should be a positive integer.") + exit_program(-1) + + if StdI.Scalapack != NaN_i: + print(f" Scalapack = {StdI.Scalapack}") + if StdI.Scalapack < 0 or StdI.Scalapack > 1: + print(f"\n ERROR ! Scalapack : {StdI.Scalapack}") + print(" Scalapack should be 0 or 1.") + exit_program(-1) + + +class _CalcModParams(NamedTuple): + """Resolved integer parameters for ``calcmod.def``. + + Attributes + ---------- + iCalcType : int + Calculation type code (0–5). + iCalcModel : int + Calculation model code (0–5). + iCalcEigenvec : int + Whether to calculate eigenvectors (0 or 1). + iRestart : int + Restart flag (0–3). + iCalcSpec : int + Spectrum calculation flag (0–5). + iInitialVecType : int + Initial vector type code. + InputEigenVec : int + Whether to read eigenvectors from file. + OutputEigenVec : int + Whether to write eigenvectors to file. + iInputHam : int + Whether to read Hamiltonian from file. + iOutputHam : int + Whether to write Hamiltonian to file. + iOutputExVec : int + Whether to output excited-state vectors. + """ + + iCalcType: int + iCalcModel: int + iCalcEigenvec: int + iRestart: int + iCalcSpec: int + iInitialVecType: int + InputEigenVec: int + OutputEigenVec: int + iInputHam: int + iOutputHam: int + iOutputExVec: int + + +def _write_calcmod_file(StdI: StdIntList, params: _CalcModParams) -> None: + """Write ``calcmod.def`` with the resolved integer parameters. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. Reads ``NGPU`` and ``Scalapack`` + for conditional output lines. + params : _CalcModParams + Resolved calculation-mode parameters. + """ + with open("calcmod.def", "w") as fp: + fp.write("#CalcType = 0:Lanczos, 1:TPQCalc, 2:FullDiag, 3:CG, 4:Time-evolution 5:cTPQ\n") + fp.write("#CalcModel = 0:Hubbard, 1:Spin, 2:Kondo, 3:HubbardGC, 4:SpinGC, 5:KondoGC\n") + fp.write("#Restart = 0:None, 1:Save, 2:Restart&Save, 3:Restart\n") + fp.write("#CalcSpec = 0:None, 1:Normal, 2:No H*Phi, 3:Save, 4:Restart, 5:Restart&Save\n") + if StdI.NGPU != NaN_i: + fp.write("#NGPU (for FullDiag): The number of GPU\n") + if StdI.Scalapack != NaN_i: + fp.write("#Scalapack (for FullDiag) = 0:w/o ScaLAPACK, 1:w/ ScaLAPACK\n") + fp.write(f"CalcType {params.iCalcType:3d}\n") + fp.write(f"CalcModel {params.iCalcModel:3d}\n") + fp.write(f"ReStart {params.iRestart:3d}\n") + fp.write(f"CalcSpec {params.iCalcSpec:3d}\n") + fp.write(f"CalcEigenVec {params.iCalcEigenvec:3d}\n") + fp.write(f"InitialVecType {params.iInitialVecType:3d}\n") + fp.write(f"InputEigenVec {params.InputEigenVec:3d}\n") + fp.write(f"OutputEigenVec {params.OutputEigenVec:3d}\n") + fp.write(f"InputHam {params.iInputHam:3d}\n") + fp.write(f"OutputHam {params.iOutputHam:3d}\n") + fp.write(f"OutputExVec {params.iOutputExVec:3d}\n") + if StdI.NGPU != NaN_i: + fp.write(f"NGPU {StdI.NGPU:3d}\n") + if StdI.Scalapack != NaN_i: + fp.write(f"Scalapack {StdI.Scalapack:3d}\n") + + +def print_calc_mod(StdI: StdIntList) -> None: + """Write ``calcmod.def`` containing calculation-mode integers for HPhi. + + Maps the string-valued parameters ``method``, ``model``, ``Restart``, + ``InitialVecType``, ``EigenVecIO``, ``HamIO``, ``CalcSpec``, and + ``OutputExVec`` to the integer codes that HPhi expects. Validation + is delegated to :func:`_validate_ngpu_scalapack` and file writing to + :func:`_write_calcmod_file`. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. String fields such as + ``method``, ``model``, ``Restart``, etc. are read (and may be + overwritten with default values). ``NGPU`` and ``Scalapack`` + are validated if set. + """ + print("\n @ CalcMod\n") + + # ------------------------------------------------------------------ + # Method → CalcType integer + # ------------------------------------------------------------------ + iCalcEigenvec = 0 + + if StdI.method == UNSET_STRING: + print("ERROR ! Method is NOT specified !") + exit_program(-1) + + iCalcType = METHOD_TO_CALC_TYPE.get(StdI.method) + if iCalcType is None: + print(f"\n ERROR ! Unsupported Solver : {StdI.method}") + exit_program(-1) + + if StdI.method == MethodType.LANCZOS_ENERGY: + iCalcEigenvec = 1 + + if iCalcType != 4: + StdI.PumpBody = 0 + + # ------------------------------------------------------------------ + # Model + # ------------------------------------------------------------------ + iCalcModel = MODEL_GC_TO_CALC_MODEL.get((StdI.model, StdI.lGC)) + if iCalcModel is None: + print(f"\n ERROR ! Unsupported Model / GC combination : " + f"{StdI.model}, lGC={StdI.lGC}") + exit_program(-1) + + # ------------------------------------------------------------------ + # Resolve string parameters to integer codes + # ------------------------------------------------------------------ + iRestart = _resolve_string_param( + StdI, "Restart", "Restart", "none", 0, RESTART_TO_INT) + + # InitialVecType: default depends on method (TPQ/CTPQ → -1, else → 0) + ivt_default = -1 if StdI.method in (MethodType.TPQ, MethodType.CTPQ) else 0 + iInitialVecType = _resolve_string_param( + StdI, "InitialVecType", "InitialVecType", "c", + ivt_default, INITIAL_VEC_TYPE_TO_INT) + + InputEigenVec, OutputEigenVec = _resolve_string_param( + StdI, "EigenVecIO", "EigenVecIO", "none", (0, 0), EIGENVEC_IO_TO_FLAGS) + if StdI.method == MethodType.TIME_EVOLUTION: + InputEigenVec = 1 + + iOutputHam, iInputHam = _resolve_string_param( + StdI, "HamIO", "HamIO", "none", (0, 0), HAM_IO_TO_FLAGS) + + iCalcSpec = _resolve_string_param( + StdI, "CalcSpec", "CalcSpec", "none", 0, CALC_SPEC_TO_INT) + + iOutputExVec = _resolve_string_param( + StdI, "OutputExVec", "OutputExcitedVec", "none", + 0, OUTPUT_EX_VEC_TO_INT) + + # ------------------------------------------------------------------ + # Validate and write + # ------------------------------------------------------------------ + _validate_ngpu_scalapack(StdI) + _write_calcmod_file( + StdI, + _CalcModParams( + iCalcType, iCalcModel, iCalcEigenvec, + iRestart, iCalcSpec, iInitialVecType, + InputEigenVec, OutputEigenVec, iInputHam, iOutputHam, iOutputExVec, + ), + ) + + print(" calcmod.def is written.\n") + + +# --------------------------------------------------------------------------- +# Spectrum-type operator configuration +# --------------------------------------------------------------------------- + +def _spectrum_szsz( + model: ModelType, S2: int, + coef: list[float], spin: list[list[int]], +) -> tuple[int, int]: + """Configure SzSz spectrum operators. + + For SPIN models, produces ``S2 + 1`` operators with coefficients equal + to :math:`S_z` values. For Hubbard/Kondo models, produces two + operators with coefficients :math:`\\pm 0.5`. + + Parameters + ---------- + model : ModelType + The model type. + S2 : int + Twice the spin quantum number. + coef : list of float + Coefficient array (modified in place). + spin : list of list of int + Spin-index array (modified in place). + + Returns + ------- + tuple of (int, int) + ``(NumOp, SpectrumBody)`` — always ``SpectrumBody = 2``. + """ + if model == ModelType.SPIN: + NumOp = S2 + 1 + for ispin in range(S2 + 1): + Sz = float(ispin) - float(S2) * 0.5 + coef[ispin] = Sz + spin[ispin][0] = ispin + spin[ispin][1] = ispin + else: + NumOp = 2 + coef[0] = 0.5 + coef[1] = -0.5 + spin[0][0] = 0 + spin[0][1] = 0 + spin[1][0] = 1 + spin[1][1] = 1 + return NumOp, 2 + + +def _spectrum_spsm( + model: ModelType, S2: int, + coef: list[float], spin: list[list[int]], +) -> tuple[int, int]: + """Configure S+S- spectrum operators. + + For high-spin SPIN models (``S2 > 1``), produces ``S2`` operators with + ladder coefficients :math:`\\sqrt{S(S+1) - S_z(S_z+1)}`. Otherwise + produces a single operator with coefficient 1. + + Parameters + ---------- + model : ModelType + The model type. + S2 : int + Twice the spin quantum number. + coef : list of float + Coefficient array (modified in place). + spin : list of list of int + Spin-index array (modified in place). + + Returns + ------- + tuple of (int, int) + ``(NumOp, SpectrumBody)`` — always ``SpectrumBody = 2``. + """ + if model == ModelType.SPIN and S2 > 1: + NumOp = S2 + S = float(S2) * 0.5 + for ispin in range(1, S2 + 1): + Sz = float(S2) * 0.5 - float(ispin) + coef[ispin - 1] = math.sqrt(S * (S + 1.0) - Sz * (Sz + 1.0)) + spin[ispin - 1][0] = ispin + spin[ispin - 1][1] = ispin - 1 + else: + NumOp = 1 + coef[0] = 1.0 + spin[0][0] = 1 + spin[0][1] = 0 + return NumOp, 2 + + +def _spectrum_density( + model: ModelType, S2: int, + coef: list[float], spin: list[list[int]], +) -> tuple[int, int]: + """Configure density spectrum operators. + + Always produces two operators (up and down) with coefficient 1. + + Parameters + ---------- + model : ModelType + The model type (unused, included for dispatch signature). + S2 : int + Twice the spin quantum number (unused). + coef : list of float + Coefficient array (modified in place). + spin : list of list of int + Spin-index array (modified in place). + + Returns + ------- + tuple of (int, int) + ``(2, 2)`` — two operators, pair body. + """ + coef[0] = 1.0 + coef[1] = 1.0 + spin[0][0] = 0 + spin[0][1] = 0 + spin[1][0] = 1 + spin[1][1] = 1 + return 2, 2 + + +def _spectrum_up( + model: ModelType, S2: int, + coef: list[float], spin: list[list[int]], +) -> tuple[int, int]: + """Configure spin-up single-particle spectrum operator. + + Parameters + ---------- + model : ModelType + The model type (unused, included for dispatch signature). + S2 : int + Twice the spin quantum number (unused). + coef : list of float + Coefficient array (modified in place). + spin : list of list of int + Spin-index array (modified in place). + + Returns + ------- + tuple of (int, int) + ``(1, 1)`` — one operator, single body. + """ + coef[0] = 1.0 + spin[0][0] = 0 + return 1, 1 + + +def _spectrum_down( + model: ModelType, S2: int, + coef: list[float], spin: list[list[int]], +) -> tuple[int, int]: + """Configure spin-down single-particle spectrum operator. + + Parameters + ---------- + model : ModelType + The model type (unused, included for dispatch signature). + S2 : int + Twice the spin quantum number (unused). + coef : list of float + Coefficient array (modified in place). + spin : list of list of int + Spin-index array (modified in place). + + Returns + ------- + tuple of (int, int) + ``(1, 1)`` — one operator, single body. + """ + coef[0] = 1.0 + spin[0][0] = 1 + return 1, 1 + + +_SPECTRUM_HANDLERS: dict[str, Callable] = { + "szsz": _spectrum_szsz, + "s+s-": _spectrum_spsm, + "density": _spectrum_density, + "up": _spectrum_up, + "down": _spectrum_down, +} +"""Dispatch table mapping spectrum-type strings to handler functions.""" + + +def _configure_spectrum_ops( + spectrum_type: str, + model: ModelType, + S2: int, + coef: list[float], + spin: list[list[int]], +) -> tuple[int, int]: + """Configure operator coefficients and spins for a spectrum type. + + Dispatches to a per-type handler function via ``_SPECTRUM_HANDLERS``. + Populates *coef* and *spin* in place and returns ``(NumOp, SpectrumBody)``. + + Parameters + ---------- + spectrum_type : str + One of ``"szsz"``, ``"s+s-"``, ``"density"``, ``"up"``, ``"down"``. + model : ModelType + The model type (SPIN, HUBBARD, or KONDO). + S2 : int + Twice the spin quantum number. + coef : list of float + Coefficient array to populate (modified in place). + spin : list of list of int + Spin-index array to populate (modified in place, each element is + ``[spin_out, spin_in]``). + + Returns + ------- + tuple of (int, int) + ``(NumOp, SpectrumBody)`` where *NumOp* is the number of operators + and *SpectrumBody* is 1 (single) or 2 (pair). + + Raises + ------ + SystemExit + If *spectrum_type* is not recognized. + """ + handler = _SPECTRUM_HANDLERS.get(spectrum_type) + if handler is not None: + return handler(model, S2, coef, spin) + + print(f"\n ERROR ! SpectrumType : {spectrum_type}") + exit_program(-1) + + +def _compute_fourier_coefficients(StdI: StdIntList) -> tuple[list[float], list[float]]: + """Compute site Fourier coefficients for spectrum excitation. + + For each site in the super-cell, compute the real and imaginary parts + of :math:`\\exp(2\\pi i \\mathbf{q} \\cdot \\mathbf{r})`, where + :math:`\\mathbf{q}` is ``SpectrumQ`` and :math:`\\mathbf{r}` is + the cell + basis position. For the Kondo model, the itinerant-site + coefficients are duplicated to the local-spin sites. + + Parameters + ---------- + StdI : StdIntList + Global parameter structure. + + Returns + ------- + fourier_r : list of float + Real parts, length ``nsite``. + fourier_i : list of float + Imaginary parts, length ``nsite``. + """ + fourier_r = np.zeros(StdI.nsite) + fourier_i = np.zeros(StdI.nsite) + n_computed = StdI.NCell * StdI.NsiteUC + + if n_computed > 0: + # Compute position vectors: Cell[icell] + tau[itau] for all (icell, itau) pairs + # Shape: (NCell, 1, 3) + (1, NsiteUC, 3) -> (NCell, NsiteUC, 3) + positions = StdI.Cell[:StdI.NCell, :].astype(float)[:, np.newaxis, :] + \ + StdI.tau[:StdI.NsiteUC, :][np.newaxis, :, :] + # Dot with SpectrumQ: shape (NCell, NsiteUC), then flatten to site order + Cphase_flat = (2.0 * math.pi * (positions @ StdI.SpectrumQ)).ravel() + fourier_r[:n_computed] = np.cos(Cphase_flat) + fourier_i[:n_computed] = np.sin(Cphase_flat) + + if StdI.model == ModelType.KONDO: + half = StdI.nsite // 2 + fourier_r[half:] = fourier_r[:half] + fourier_i[half:] = fourier_i[:half] + + return fourier_r, fourier_i + + +def _write_excitation_file( + StdI: StdIntList, + NumOp: int, + coef: list[float], + spin: list[list[int]], + fourier_r: list[float], + fourier_i: list[float], +) -> None: + """Write ``single.def`` or ``pair.def`` excitation file. + + Depending on ``StdI.SpectrumBody``, writes either a single-body + excitation file (``single.def``) or a pair-body file (``pair.def``). + + Parameters + ---------- + StdI : StdIntList + Global parameter structure. + NumOp : int + Number of operator components per site. + coef : list of float + Coefficients for each operator component. + spin : list of list of int + Spin indices ``[s1, s2]`` for each operator component. + fourier_r : list of float + Real parts of Fourier coefficients, length ``nsite``. + fourier_i : list of float + Imaginary parts of Fourier coefficients, length ``nsite``. + """ + if StdI.SpectrumBody == 1: + with open("single.def", "w") as fp: + fp.write("=============================================\n") + if StdI.model == ModelType.KONDO: + fp.write(f"NSingle {StdI.nsite // 2 * NumOp}\n") + else: + fp.write(f"NSingle {StdI.nsite * NumOp}\n") + fp.write("=============================================\n") + fp.write("============== Single Excitation ============\n") + fp.write("=============================================\n") + if StdI.model == ModelType.KONDO: + for isite in range(StdI.nsite // 2, StdI.nsite): + fp.write(f"{isite} {spin[0][0]} 0 " + f"{fourier_r[isite] * coef[0]:25.15f} " + f"{fourier_i[isite] * coef[0]:25.15f}\n") + else: + for isite in range(StdI.nsite): + fp.write(f"{isite} {spin[0][0]} 0 " + f"{fourier_r[isite] * coef[0]:25.15f} " + f"{fourier_i[isite] * coef[0]:25.15f}\n") + print(" single.def is written.\n") + else: + with open("pair.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"NPair {StdI.nsite * NumOp}\n") + fp.write("=============================================\n") + fp.write("=============== Pair Excitation =============\n") + fp.write("=============================================\n") + for isite in range(StdI.nsite): + for ispin in range(NumOp): + fp.write(f"{isite} {spin[ispin][0]} {isite} {spin[ispin][1]} 1 " + f"{fourier_r[isite] * coef[ispin]:25.15f} " + f"{fourier_i[isite] * coef[ispin]:25.15f}\n") + print(" pair.def is written.\n") + + +def print_excitation(StdI: StdIntList) -> None: + """Write ``single.def`` or ``pair.def`` for spectrum calculations. + + Depending on ``SpectrumType``, this function generates excitation + operators with the appropriate Fourier coefficients and spin + structure: + + - ``"szsz"`` (default) or ``"****"``: pair excitation with Sz + diagonal coefficients. + - ``"s+s-"``: pair excitation with S+S- ladder coefficients. + - ``"density"``: pair excitation with unit coefficients on both + spins. + - ``"up"``: single excitation with spin-up. + - ``"down"``: single excitation with spin-down. + + For the Kondo model, the Fourier coefficients for the itinerant + sites are duplicated to the local-spin sites. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. Key fields read: + + - ``model``, ``S2`` -- model type and twice the spin quantum + number. + - ``SpectrumType`` -- string selecting the excitation channel. + - ``SpectrumQ`` -- wavevector components (W, L, H). + - ``NCell``, ``NsiteUC``, ``Cell``, ``tau`` -- lattice cell and + basis-site data. + - ``nsite`` -- total number of sites. + - ``pi`` -- the mathematical constant pi. + + Modified in place: + + - ``SpectrumBody`` -- set to 1 (single) or 2 (pair). + - ``SpectrumQ`` -- defaults filled via :func:`print_val_d`. + """ + # --- Allocate coefficient and spin-index arrays --- + if StdI.model == ModelType.SPIN and StdI.S2 > 1: + n_alloc = StdI.S2 + 1 + else: + n_alloc = 2 + + coef = [0.0] * n_alloc + spin = [[0, 0] for _ in range(n_alloc)] + + print("\n @ Spectrum\n") + + StdI.SpectrumQ[0] = print_val_d("SpectrumQW", StdI.SpectrumQ[0], 0.0) + StdI.SpectrumQ[1] = print_val_d("SpectrumQL", StdI.SpectrumQ[1], 0.0) + StdI.SpectrumQ[2] = print_val_d("SpectrumQH", StdI.SpectrumQ[2], 0.0) + + # ------------------------------------------------------------------ + # Resolve SpectrumType default and determine NumOp, coef, spin + # ------------------------------------------------------------------ + if StdI.SpectrumType == UNSET_STRING: + StdI.SpectrumType = "szsz" + print(" SpectrumType = szsz ###### DEFAULT VALUE IS USED ######") + else: + print(f" SpectrumType = {StdI.SpectrumType}") + + NumOp, StdI.SpectrumBody = _configure_spectrum_ops( + StdI.SpectrumType, StdI.model, StdI.S2, coef, spin) + + # Compute Fourier coefficients + fourier_r, fourier_i = _compute_fourier_coefficients(StdI) + + # Write single.def or pair.def + _write_excitation_file(StdI, NumOp, coef, spin, fourier_r, fourier_i) + + +# --------------------------------------------------------------------------- +# Pump-type A(t) / E(t) computation functions +# --------------------------------------------------------------------------- + +def _pulselaser_At_Et( + time: float, V: float, freq: float, tshift: float, tdump: float, +) -> tuple[float, float]: + """Compute A(t) and E(t) for Gaussian-enveloped cosine pulse laser. + + Parameters + ---------- + time : float + Current time. + V : float + Vector potential amplitude for one component. + freq : float + Laser frequency. + tshift : float + Time shift for the pulse center. + tdump : float + Gaussian pulse width parameter. + + Returns + ------- + tuple of (float, float) + ``(A_component, E_component)`` for this time and component. + """ + dt_shift = time - tshift + gauss = math.exp(-0.5 * dt_shift ** 2 / (tdump * tdump)) + cos_val = math.cos(freq * dt_shift) + sin_val = math.sin(freq * dt_shift) + At = V * cos_val * gauss + Et = -V * ((-dt_shift) / (tdump * tdump) * cos_val - freq * sin_val) * gauss + return At, Et + + +def _aclaser_At_Et( + time: float, V: float, freq: float, tshift: float, tdump: float, +) -> tuple[float, float]: + """Compute A(t) and E(t) for sinusoidal AC laser. + + Parameters + ---------- + time : float + Current time. + V : float + Vector potential amplitude for one component. + freq : float + Laser frequency. + tshift : float + Time shift. + tdump : float + Unused (kept for uniform signature). + + Returns + ------- + tuple of (float, float) + ``(A_component, E_component)`` for this time and component. + """ + dt_shift = time - tshift + At = V * math.sin(freq * dt_shift) + Et = V * math.cos(freq * dt_shift) * freq + return At, Et + + +def _dclaser_At_Et( + time: float, V: float, freq: float, tshift: float, tdump: float, +) -> tuple[float, float]: + """Compute A(t) and E(t) for linearly ramped DC laser. + + Parameters + ---------- + time : float + Current time. + V : float + Vector potential amplitude for one component. + freq : float + Unused (kept for uniform signature). + tshift : float + Unused (kept for uniform signature). + tdump : float + Unused (kept for uniform signature). + + Returns + ------- + tuple of (float, float) + ``(A_component, E_component)`` for this time and component. + """ + return V * time, -V + + +class _PumpTypeConfig(NamedTuple): + """Configuration for a pump-type entry in the dispatch table. + + Attributes + ---------- + pump_body : int + Pump body type (1 = one-body laser, 2 = two-body quench). + handler_fn : Callable or None + Field computation function with signature + ``(time, V, freq, tshift, tdump) -> (At, Et)``, or ``None`` + for quench (no field computation). + """ + + pump_body: int + handler_fn: Callable | None + + +_PUMP_TYPE_HANDLERS: dict[str, _PumpTypeConfig] = { + "quench": _PumpTypeConfig(2, None), + "pulselaser": _PumpTypeConfig(1, _pulselaser_At_Et), + "aclaser": _PumpTypeConfig(1, _aclaser_At_Et), + "dclaser": _PumpTypeConfig(1, _dclaser_At_Et), +} +"""Maps each PumpType to a :class:`_PumpTypeConfig`. + +For ``PumpBody == 2`` (quench), *handler_fn* is ``None`` — no field is +computed. For ``PumpBody == 1``, *handler_fn* is called once per +(timestep, component) pair with signature +``(time, V, freq, tshift, tdump) -> (At, Et)``. +""" + + +def vector_potential(StdI: StdIntList) -> None: + """Compute vector potential A(t) and electric field E(t) for time evolution. + + Depending on ``PumpType``, the time-dependent vector potential and + electric field are computed as follows: + + - ``"quench"`` (default): no laser field; uses two-body pump + (``PumpBody = 2``). + - ``"pulselaser"``: Gaussian-enveloped cosine pulse + (``PumpBody = 1``). + - ``"aclaser"``: sinusoidal AC laser (``PumpBody = 1``). + - ``"dclaser"``: linearly ramped DC laser (``PumpBody = 1``). + + For ``PumpBody == 1``, the file ``potential.dat`` is written with + columns ``time, A_W, A_L, A_H, E_W, E_L, E_H``. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. Key fields read / set: + + - ``VecPot`` -- initial vector potential components (W, L, H). + - ``Lanczos_max`` -- number of time steps. + - ``dt`` -- time step width. + - ``freq`` -- laser frequency. + - ``tshift`` -- time shift for the pulse center. + - ``tdump`` -- Gaussian pulse width. + - ``Uquench`` -- quench interaction strength. + - ``ExpandCoef`` -- expansion coefficient order. + - ``PumpType`` -- string selecting the pump type. + - ``PumpBody`` -- set to 1 (one-body) or 2 (two-body). + - ``At`` -- allocated as ``Lanczos_max x 3`` list of floats. + """ + print("\n @ Time-evolution\n") + + StdI.VecPot[0] = print_val_d("VecPotW", StdI.VecPot[0], 0.0) + StdI.VecPot[1] = print_val_d("VecPotL", StdI.VecPot[1], 0.0) + StdI.VecPot[2] = print_val_d("VecPotH", StdI.VecPot[2], 0.0) + StdI.Lanczos_max = print_val_i("Lanczos_max", StdI.Lanczos_max, 1000) + StdI.dt = print_val_d("dt", StdI.dt, 0.01) + StdI.freq = print_val_d("freq", StdI.freq, 0.1) + StdI.tshift = print_val_d("tshift", StdI.tshift, 0.0) + StdI.tdump = print_val_d("tdump", StdI.tdump, 0.1) + StdI.Uquench = print_val_d("Uquench", StdI.Uquench, 0.0) + StdI.ExpandCoef = print_val_i("ExpandCoef", StdI.ExpandCoef, 10) + + # Allocate A(t) and E(t) arrays: Lanczos_max x 3 + StdI.At = np.zeros((StdI.Lanczos_max, 3)) + Et = np.zeros((StdI.Lanczos_max, 3)) + + # Resolve PumpType default + if StdI.PumpType == UNSET_STRING: + StdI.PumpType = "quench" + print(" PumpType = quench ###### DEFAULT VALUE IS USED ######") + else: + print(f" PumpType = {StdI.PumpType}") + + # Dispatch on PumpType + handler_entry = _PUMP_TYPE_HANDLERS.get(StdI.PumpType) + if handler_entry is None: + print(f"\n ERROR ! PumpType : {StdI.PumpType}") + exit_program(-1) + + StdI.PumpBody, handler_fn = handler_entry + + # Compute A(t) and E(t) for one-body pump types + if handler_fn is not None: + for it in range(StdI.Lanczos_max): + time = StdI.dt * float(it) + results = [handler_fn(time, StdI.VecPot[ii], StdI.freq, + StdI.tshift, StdI.tdump) for ii in range(3)] + StdI.At[it, :] = [r[0] for r in results] + Et[it, :] = [r[1] for r in results] + + # ------------------------------------------------------------------ + # Write potential.dat for one-body pump + # ------------------------------------------------------------------ + if StdI.PumpBody == 1: + with open("potential.dat", "w") as fp: + fp.write("# Time A_W A_L A_H E_W E_L E_H\n") + for it in range(StdI.Lanczos_max): + time = StdI.dt * float(it) + fp.write(f"{time:f} " + f"{StdI.At[it][0]:f} {StdI.At[it][1]:f} {StdI.At[it][2]:f} " + f"{Et[it][0]:f} {Et[it][1]:f} {Et[it][2]:f}\n") + + +def print_pump(StdI: StdIntList) -> None: + """Write ``teone.def`` or ``tetwo.def`` for time-evolution pump terms. + + - ``PumpBody == 1``: writes ``teone.def`` -- one-body pump terms. + Equivalent pump terms (same index quadruples) are merged and + entries below ``AMPLITUDE_EPS`` in magnitude are suppressed. + - ``PumpBody == 2``: writes ``tetwo.def`` -- two-body pump terms + using ``Uquench`` for on-site Hubbard quench interaction. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. Key fields read: + + - ``PumpBody`` -- 1 for one-body, 2 for two-body. + - ``Lanczos_max`` -- number of time steps. + - ``dt`` -- time step width. + - ``npump`` -- list of int, per-timestep pump term counts. + - ``pumpindx`` -- 3-D list of int, shape + ``(Lanczos_max, npump[it], 4)`` -- site/spin indices. + - ``pump`` -- 2-D list of complex, shape + ``(Lanczos_max, npump[it])`` -- pump amplitudes. + - ``nsite`` -- total number of sites. + - ``Uquench`` -- quench interaction strength. + """ + if StdI.PumpBody == 1: + with open("teone.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"AllTimeStep {StdI.Lanczos_max}\n") + fp.write("=============================================\n") + fp.write("========= OneBody Time Evolution ==========\n") + fp.write("=============================================\n") + + for it in range(StdI.Lanczos_max): + npump0 = _merge_duplicate_terms( + StdI.pumpindx[it], StdI.pump[it], StdI.npump[it]) + + fp.write(f"{StdI.dt * float(it):f} {npump0}\n") + + for ipump in range(StdI.npump[it]): + val = StdI.pump[it][ipump] + if abs(val) <= AMPLITUDE_EPS: + continue + i0, s0, i1, s1 = StdI.pumpindx[it][ipump] + fp.write( + f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " + f"{val.real:25.15f} {val.imag:25.15f}\n" + ) + + print(" teone.def is written.\n") + + else: + with open("tetwo.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"AllTimeStep {StdI.Lanczos_max}\n") + fp.write("=============================================\n") + fp.write("========== TwoBody Time Evolution ===========\n") + fp.write("=============================================\n") + + for it in range(StdI.Lanczos_max): + fp.write(f"{StdI.dt * float(it):f} {StdI.nsite}\n") + for isite in range(StdI.nsite): + fp.write(f"{isite:5d} {0:5d} {isite:5d} {0:5d} " + f"{isite:5d} {1:5d} {isite:5d} {1:5d} " + f"{StdI.Uquench:25.15f} {0.0:25.15f}\n") + + print(" tetwo.def is written.\n") diff --git a/python/stdface/solvers/hwave/__init__.py b/python/stdface/solvers/hwave/__init__.py new file mode 100644 index 0000000..964177a --- /dev/null +++ b/python/stdface/solvers/hwave/__init__.py @@ -0,0 +1,9 @@ +"""H-wave solver plugin package. + +Importing this package auto-registers the H-wave plugin. +""" +from __future__ import annotations + +from ._plugin import HWavePlugin # noqa: F401 — public API + +__all__ = ["HWavePlugin"] diff --git a/python/stdface/solvers/hwave/_plugin.py b/python/stdface/solvers/hwave/_plugin.py new file mode 100644 index 0000000..0a2754a --- /dev/null +++ b/python/stdface/solvers/hwave/_plugin.py @@ -0,0 +1,114 @@ +"""H-wave solver plugin. + +Encapsulates all H-wave-specific keyword parsing, field reset tables, +and Expert-mode file writing. +""" +from __future__ import annotations + +from ...plugin import SolverPlugin, register +from ...core.stdface_vals import StdIntList, SolverType, NaN_i, NaN_d +from ...core.keyword_parser import ( + store_with_check_dup_i, store_with_check_dup_d, store_with_check_dup_sl, + _grid3x3_keywords, +) +from .export_wannier90 import export_geometry, export_interaction + + +class HWavePlugin(SolverPlugin): + """Plugin for the H-wave solver.""" + + @property + def name(self) -> str: + return SolverType.HWAVE + + @property + def keyword_table(self) -> dict[str, tuple]: + return _HWAVE_KEYWORDS + + @property + def reset_scalars(self) -> list[tuple[str, object]]: + return _RESET_SCALARS + + @property + def reset_arrays(self) -> list[tuple[str, object]]: + return _RESET_ARRAYS + + def write(self, StdI: StdIntList) -> None: + """Write H-wave output files. + + Overrides the template method entirely because H-wave has two + completely different output modes (uhfr vs wannier90 export). + """ + from ...writer.common_writer import ( + print_trans, print_1_green, check_output_mode, check_mod_para, + ) + from ...writer.interaction_writer import print_interactions + + if StdI.calcmode == "uhfr": + print_trans(StdI) + print_interactions(StdI) + check_mod_para(StdI) + check_output_mode(StdI) + print_1_green(StdI) + else: + export_geometry(StdI) + export_interaction(StdI) + + +# ----------------------------------------------------------------------- +# Keyword table +# ----------------------------------------------------------------------- + +_BOXSUB_KEYWORDS: dict[str, tuple] = _grid3x3_keywords( + "{a}{c}sub", "boxsub", store_with_check_dup_i, int +) + +_UHF_BASE_KEYWORDS: dict[str, tuple] = { + "iteration_max": (store_with_check_dup_i, "Iteration_max"), + "rndseed": (store_with_check_dup_i, "RndSeed"), + "nmptrans": (store_with_check_dup_i, "NMPTrans"), + **_BOXSUB_KEYWORDS, + "hsub": (store_with_check_dup_i, "Hsub"), + "lsub": (store_with_check_dup_i, "Lsub"), + "wsub": (store_with_check_dup_i, "Wsub"), + "eps": (store_with_check_dup_i, "eps"), + "epsslater": (store_with_check_dup_i, "eps_slater"), + "mix": (store_with_check_dup_d, "mix"), +} + +_HWAVE_KEYWORDS: dict[str, tuple] = { + **_UHF_BASE_KEYWORDS, + "calcmode": (store_with_check_dup_sl, "calcmode"), + "fileprefix": (store_with_check_dup_sl, "fileprefix"), + "exportall": (store_with_check_dup_i, "export_all"), + "lattice_gp": (store_with_check_dup_i, "lattice_gp"), +} + +# ----------------------------------------------------------------------- +# Reset tables +# ----------------------------------------------------------------------- + +_UHF_BASE_SCALARS: list[tuple[str, object]] = [ + ("NMPTrans", NaN_i), + ("RndSeed", NaN_i), + ("mix", NaN_d), + ("eps", NaN_i), + ("eps_slater", NaN_i), + ("Iteration_max", NaN_i), + ("Hsub", NaN_i), + ("Lsub", NaN_i), + ("Wsub", NaN_i), +] + +_RESET_SCALARS: list[tuple[str, object]] = _UHF_BASE_SCALARS + [ + ("export_all", NaN_i), + ("lattice_gp", NaN_i), +] + +_RESET_ARRAYS: list[tuple[str, object]] = [ + ("boxsub", NaN_i), +] + + +# Auto-register on import +register(HWavePlugin()) diff --git a/python/stdface/solvers/hwave/export_wannier90.py b/python/stdface/solvers/hwave/export_wannier90.py new file mode 100644 index 0000000..2e06c61 --- /dev/null +++ b/python/stdface/solvers/hwave/export_wannier90.py @@ -0,0 +1,899 @@ +""" +Export functions for the Wannier90/HWAVE format. + +This module contains functions to export lattice geometry and interaction +parameters in a format compatible with Wannier90. The main public functions +are: + +- ``export_geometry()`` -- Exports lattice vectors and orbital positions +- ``export_interaction()`` -- Exports hopping and interaction parameters + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import itertools +import sys +from dataclasses import dataclass, field + +import numpy as np + +from ...core.stdface_vals import StdIntList, NaN_i, UNSET_STRING +from ...core.param_check import exit_program +from ...lattice.site_util import _cell_vector + +# ----------------------------------------------------------------------- +# Module-level constants +# ----------------------------------------------------------------------- + +_EPS = 1.0e-8 +"""Small number for floating-point comparisons.""" + + +# ----------------------------------------------------------------------- +# Control flags +# ----------------------------------------------------------------------- + +_is_export_all = 1 +"""Default for the exportall parameter (1 = export zero elements too).""" + + +# ----------------------------------------------------------------------- +# Internal data structures +# ----------------------------------------------------------------------- + +@dataclass +class _IntrItem: + """Store an interaction parameter between orbitals. + + Attributes + ---------- + r : list of int + Relative coordinate between orbitals (length 3). + a : int + First orbital index. + b : int + Second orbital index. + s : int + First spin index. + t : int + Second spin index. + v : complex + Interaction strength. + """ + r: list[int] = field(default_factory=lambda: [0, 0, 0]) + a: int = 0 + b: int = 0 + s: int = 0 + t: int = 0 + v: complex = 0.0 + 0.0j + + +# ----------------------------------------------------------------------- +# Helper: fatal error +# ----------------------------------------------------------------------- + +def _fatal(msg: str) -> None: + """Print an error message and exit. + + Parameters + ---------- + msg : str + Error description. + """ + print(f"ERROR: {msg}", file=sys.stderr) + exit_program(-1) + + +# ----------------------------------------------------------------------- +# Write geometry file +# ----------------------------------------------------------------------- + +def _write_geometry(StdI: StdIntList, fname: str) -> None: + """Write geometry data to file in Wannier90 format. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters containing geometry info. + fname : str + Output filename. + + Notes + ----- + Writes: + - Primitive lattice vectors + - Number of orbitals per unit cell + - Orbital positions in fractional coordinates + """ + with open(fname, "w") as fp_out: + # Print primitive vectors + for row in StdI.direct: + fp_out.write(f"{row[0]:16.12f} {row[1]:16.12f} {row[2]:16.12f}\n") + + # Print number of orbits + fp_out.write(f"{StdI.NsiteUC}\n") + + # Print centre of orbits + for tau_row in StdI.tau[:StdI.NsiteUC]: + fp_out.write(f"{tau_row[0]:25.15e} " + f"{tau_row[1]:25.15e} " + f"{tau_row[2]:25.15e}\n") + print(f"{fname:>24s} is written.") + + +# ----------------------------------------------------------------------- +# Index macro (replaces C's _index) +# ----------------------------------------------------------------------- + +def _compute_index(rx: int, ry: int, rz: int, + a: int, b: int, s: int, t: int, + rr: list[int], nsiteuc: int, nspin: int) -> int: + """Compute flat index into the interaction matrix. + + Parameters + ---------- + rx, ry, rz : int + Relative coordinates. + a, b : int + Orbital indices. + s, t : int + Spin indices. + rr : list of int + Half-ranges for each direction. + nsiteuc : int + Number of sites per unit cell. + nspin : int + Number of spin states. + + Returns + ------- + int + Flat index into the matrix array. + """ + return (t + nspin * ( + s + nspin * ( + b + nsiteuc * ( + a + nsiteuc * ( + (rz + rr[2]) + (rr[2] * 2 + 1) * ( + (ry + rr[1]) + (rr[1] * 2 + 1) * ( + rx + rr[0] + ))))))) + + +# ----------------------------------------------------------------------- +# Write Wannier90-format interaction file +# ----------------------------------------------------------------------- + +def _build_wannier_matrix( + intr_table: list[_IntrItem], + nsiteuc: int, + nspin: int, +) -> tuple[list[int], int, np.ndarray]: + """Build the flat interaction matrix from a list of interaction items. + + Scans the items to determine the coordinate half-ranges, allocates + a flat complex matrix, and populates it — including Hermitian-conjugate + entries for any reverse-direction slot that is still empty. + + Parameters + ---------- + intr_table : list of _IntrItem + Array of interaction parameters. + nsiteuc : int + Number of sites per unit cell. + nspin : int + Number of spin states. + + Returns + ------- + rr : list of int + Half-ranges ``[rr0, rr1, rr2]`` for each lattice direction. + nvol : int + Total number of real-space unit cells in the range. + matrix : numpy.ndarray + Flat complex array of length ``nvol * nsiteuc**2 * nspin**2``. + """ + all_r = np.array([entry.r for entry in intr_table]) + rr = list(np.max(np.abs(all_r), axis=0)) + + dims = [rr[i] * 2 + 1 for i in range(3)] + nvol = dims[0] * dims[1] * dims[2] + matrix_size = nvol * nsiteuc * nsiteuc * nspin * nspin + matrix = np.zeros(matrix_size, dtype=complex) + + for entry in intr_table: + idx = _compute_index( + entry.r[0], entry.r[1], entry.r[2], + entry.a, entry.b, entry.s, entry.t, + rr, nsiteuc, nspin) + matrix[idx] = entry.v + + ridx = _compute_index( + -entry.r[0], -entry.r[1], -entry.r[2], + entry.b, entry.a, entry.t, entry.s, + rr, nsiteuc, nspin) + + if abs(matrix[ridx]) < _EPS: + matrix[ridx] = np.conj(entry.v) + + return rr, nvol, matrix + + +def _write_wannier_body( + fp: 'TextIO', + rr: list[int], + nvol: int, + nsiteuc: int, + nspin: int, + matrix: np.ndarray, +) -> None: + """Write the matrix body of a Wannier90-format interaction file. + + Iterates over all real-space cells and orbital/spin pairs, writing + one line per entry. When ``nspin > 1`` the extended format with + explicit spin columns ``s`` and ``t`` is used; otherwise the compact + format without spin columns is written. + + Parameters + ---------- + fp : file object + Open file to write matrix entries to. + rr : list of int + Half-ranges for each lattice direction. + nvol : int + Total number of real-space unit cells. + nsiteuc : int + Number of sites per unit cell. + nspin : int + Number of spin states. + matrix : numpy.ndarray + Flat complex interaction matrix. + """ + spin_pairs = list(itertools.product(range(nspin), repeat=2)) + dims = [rr[i] * 2 + 1 for i in range(3)] + for r in range(nvol): + rz = r % dims[2] - rr[2] + ry = (r // dims[2]) % dims[1] - rr[1] + rx = (r // (dims[2] * dims[1])) % dims[0] - rr[0] + + for a in range(nsiteuc): + for b in range(nsiteuc): + for s, t in spin_pairs: + idx = _compute_index(rx, ry, rz, a, b, s, t, + rr, nsiteuc, nspin) + if _is_export_all or abs(matrix[idx]) > _EPS: + spin_part = f"{s:4d} {t:4d} " if nspin > 1 else "" + fp.write( + f"{rx:4d} {ry:4d} {rz:4d} " + f"{a + 1:4d} {b + 1:4d} " + f"{spin_part}" + f"{matrix[idx].real:16.12f} " + f"{matrix[idx].imag:16.12f}\n") + + +def _write_wannier90(intr_table: list[_IntrItem], + nsiteuc: int, nspin: int, + fname: str, tagname: str) -> None: + """Write interaction parameters to file in Wannier90 format. + + Builds the interaction matrix from the item list, then writes the + Wannier90-format file with header and body. + + Parameters + ---------- + intr_table : list of _IntrItem + Array of interaction parameters. + nsiteuc : int + Number of sites per unit cell. + nspin : int + Number of spin states. + fname : str + Output filename. + tagname : str + Tag identifying interaction type. + """ + rr, nvol, matrix = _build_wannier_matrix( + intr_table, nsiteuc, nspin) + + with open(fname, "w") as fp_out: + # Write header + fp_out.write(f"{tagname} in wannier90-like format for uhfk\n") + fp_out.write(f"{nsiteuc}\n{nvol}\n") + for i in range(nvol): + if i > 0 and i % 15 == 0: + fp_out.write("\n") + fp_out.write(f" {1}") + fp_out.write("\n") + + _write_wannier_body(fp_out, rr, nvol, nsiteuc, nspin, matrix) + print(f"{fname:>24s} is written.") + + +# ----------------------------------------------------------------------- +# Unfold site coordinates +# ----------------------------------------------------------------------- + +def _unfold_site(StdI: StdIntList, v_in: list[int]) -> list[int]: + """Convert site coordinates from [0, N] to [-N/2, N/2] range. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters. + v_in : list of int + Input coordinates (length 3). + + Returns + ------- + list of int + Output coordinates in the unfolded range (length 3). + """ + # v = rbox @ v_in (matrix-vector product) + v = StdI.rbox.astype(int) @ np.array(v_in) + + # Fold to [-N/2, N/2] range using vectorized operations + vv = v / StdI.NCell + v = np.where(vv > 0.5, v - StdI.NCell, v) + v = np.where(vv <= -0.5, v + StdI.NCell, v) + + # w = (v @ box) // NCell + w = (v @ StdI.box.astype(int)) // StdI.NCell + + return w.tolist() + + +# ----------------------------------------------------------------------- +# Key generation / comparison helpers +# ----------------------------------------------------------------------- + +def _generate_key(keylen: int, index: list[int], ordered: int) -> tuple[int, ...]: + """Generate hashable key for interaction table entry. + + Parameters + ---------- + keylen : int + Length of key (1, 2, or 4). + index : list of int + Input indices. + ordered : int + Whether to order indices (1 = yes). + + Returns + ------- + tuple of int + Generated key (hashable, suitable as dict key). + """ + if keylen == 1: + return (index[0],) + elif keylen == 2: + i, j = index[:2] + return (j, i) if ordered == 1 and i > j else (i, j) + elif keylen == 4: + i, s, j, t = index[:4] + return (j, t, i, s) if ordered == 1 and i > j else (i, s, j, t) + else: + _fatal(f"unsupported keylen: {keylen}") + return () # unreachable + + +# ----------------------------------------------------------------------- +# Accumulate entries +# ----------------------------------------------------------------------- + +def _accumulate_list(keylen: int, + ntbl: int, + tbl_index: np.ndarray, + tbl_value: np.ndarray, + ordered: int + ) -> tuple: + """Accumulate interaction table entries with same key. + + Parameters + ---------- + keylen : int + Length of key (1, 2, or 4). + ntbl : int + Number of input entries. + tbl_index : np.ndarray + Input indices, shape ``(ntbl, keylen)``. + tbl_value : np.ndarray + Input values, shape ``(ntbl,)``. + ordered : int + Whether to order indices (1 = yes). + + Returns + ------- + nintr : int + Number of output entries. + intr_index : list of list of int + Output indices (each entry is a list of length *keylen*). + intr_value : list of complex + Output values. + """ + # Use a dict keyed by tuple for O(1) lookup instead of O(n) linear scan + accum: dict[tuple[int, ...], complex] = {} + key_order: list[tuple[int, ...]] = [] + + for k in range(ntbl): + idx = _generate_key(keylen, list(tbl_index[k, :keylen]), ordered) + val = complex(tbl_value[k]) + + if idx in accum: + accum[idx] += val + else: + accum[idx] = val + key_order.append(idx) + + # Filter out near-zero entries + intr_index: list[list[int]] = [] + intr_value: list[complex] = [] + for key in key_order: + if abs(accum[key]) >= _EPS: + intr_index.append(list(key)) + intr_value.append(accum[key]) + + return len(intr_index), intr_index, intr_value + + +# ----------------------------------------------------------------------- +# Export: inter-site interaction (complex) +# ----------------------------------------------------------------------- + +def _build_inter_table( + StdI: StdIntList, + nintr: int, + intr_index: list[list[int]], + intr_value: np.ndarray, +) -> list[_IntrItem]: + """Build a deduplicated interaction table in relative coordinates. + + Converts accumulated inter-site interaction entries from absolute + site indices to relative-coordinate ``_IntrItem`` entries, + deduplicating by the key ``(rr, isite, jsite)``. Spin indices + are fixed to ``(0, 0)``. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters (reads ``NsiteUC``, ``Cell``). + nintr : int + Number of accumulated entries. + intr_index : list of list of int + Accumulated indices, each row is ``[idx_i, idx_j]``. + intr_value : np.ndarray + Accumulated complex values, shape ``(nintr,)``. + + Returns + ------- + list of _IntrItem + Deduplicated interaction entries in relative coordinates. + """ + intr_table: list[_IntrItem] = [] + seen: dict[tuple, int] = {} # key -> index in intr_table + + for k in range(nintr): + idx_i, idx_j = intr_index[k][:2] + + icell, isite = divmod(idx_i, StdI.NsiteUC) + jcell, jsite = divmod(idx_j, StdI.NsiteUC) + + jCV = _cell_vector(StdI.Cell, jcell) + iCV = _cell_vector(StdI.Cell, icell) + rr = _unfold_site(StdI, [j - i for j, i in zip(jCV, iCV)]) + + lookup_key = (tuple(rr), isite, jsite) + if lookup_key in seen: + existing = intr_table[seen[lookup_key]] + if abs(existing.v - intr_value[k]) > _EPS: + print(f"WARNING: not uniform. " + f"expected=({existing.v.real},{existing.v.imag}), " + f"found=({intr_value[k].real},{intr_value[k].imag}) " + f"for index {idx_i},{idx_j}") + else: + seen[lookup_key] = len(intr_table) + intr_table.append(_IntrItem( + r=list(rr), a=isite, b=jsite, + s=0, t=0, v=intr_value[k])) + + return intr_table + + +def _export_inter(StdI: StdIntList, + ntbl: int, + tbl_index: np.ndarray, + tbl_value: np.ndarray, + fname: str, tagname: str) -> None: + """Export interaction coefficients from complex array. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters. + ntbl : int + Number of interaction terms. + tbl_index : np.ndarray + Interaction indices, shape ``(ntbl, 2)``. + tbl_value : np.ndarray + Interaction values, shape ``(ntbl,)`` (complex). + fname : str + Output filename. + tagname : str + Tag identifying interaction type. + """ + if ntbl == 0: + print(f"{fname:>24s} is skipped.") + return + + nintr, intr_index, intr_value = _accumulate_list( + 2, ntbl, tbl_index, tbl_value, 1) + + intr_table = (_build_inter_table(StdI, nintr, intr_index, intr_value) + if nintr > 0 else []) + + if intr_table: + _write_wannier90(intr_table, StdI.NsiteUC, 1, fname, tagname) + else: + print(f"{fname:>24s} is skipped.") + + +# ----------------------------------------------------------------------- +# Export: inter-site interaction (real -> complex wrapper) +# ----------------------------------------------------------------------- + +def _export_inter_real(StdI: StdIntList, + ntbl: int, + tbl_index: np.ndarray, + tbl_value: np.ndarray, + fname: str, tagname: str) -> None: + """Export interaction coefficients from real array. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters. + ntbl : int + Number of interaction terms. + tbl_index : np.ndarray + Interaction indices, shape ``(ntbl, 2)``. + tbl_value : np.ndarray + Interaction values, shape ``(ntbl,)`` (real). + fname : str + Output filename. + tagname : str + Tag identifying interaction type. + + Notes + ----- + Converts real array to complex and calls ``_export_inter()``. + """ + if tbl_value is not None: + buf = tbl_value.astype(complex) + else: + buf = np.array([], dtype=complex) + _export_inter(StdI, ntbl, tbl_index, buf, fname, tagname) + + +# ----------------------------------------------------------------------- +# Export: transfer (hopping) coefficients +# ----------------------------------------------------------------------- + +def _build_transfer_table( + StdI: StdIntList, + nintr: int, + intr_index: list[list[int]], + intr_value: np.ndarray, + spin_dep: int, +) -> list[_IntrItem]: + """Build a deduplicated transfer table in relative coordinates. + + Converts accumulated transfer entries from absolute site indices + to relative-coordinate ``_IntrItem`` entries, deduplicating by + the key ``(rr, isite, jsite, ispin, jspin)``. Values are + sign-flipped (multiplied by −1) per the Wannier90 convention. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters (reads ``NsiteUC``, ``Cell``). + nintr : int + Number of accumulated entries. + intr_index : list of list of int + Accumulated indices, each row is ``[idx_i, ispin, idx_j, jspin]``. + intr_value : np.ndarray + Accumulated complex values, shape ``(nintr,)``. + **Modified in-place** (sign-flipped). + spin_dep : int + Whether transfer is spin-dependent (1 = yes, 0 = no). + When 0, entries where ``(ispin, jspin) != (0, 0)`` are skipped. + + Returns + ------- + list of _IntrItem + Deduplicated transfer entries in relative coordinates. + """ + intr_table: list[_IntrItem] = [] + seen: dict[tuple, int] = {} # key -> index in intr_table + + for k in range(nintr): + idx_i, ispin, idx_j, jspin = intr_index[k][:4] + + icell, isite = divmod(idx_i, StdI.NsiteUC) + jcell, jsite = divmod(idx_j, StdI.NsiteUC) + + jCV = _cell_vector(StdI.Cell, jcell) + iCV = _cell_vector(StdI.Cell, icell) + rr = _unfold_site(StdI, [j - i for j, i in zip(jCV, iCV)]) + + intr_value[k] *= -1 # by convention + + lookup_key = (tuple(rr), isite, jsite, ispin, jspin) + if lookup_key in seen: + existing = intr_table[seen[lookup_key]] + if abs(existing.v - intr_value[k]) > _EPS: + print(f"WARNING: not uniform. " + f"expected=({existing.v.real},{existing.v.imag}), " + f"found=({intr_value[k].real},{intr_value[k].imag}) " + f"for index {idx_i},{idx_j}") + else: + if spin_dep == 0 and not (ispin == 0 and jspin == 0): + continue # skip + + seen[lookup_key] = len(intr_table) + intr_table.append(_IntrItem( + r=list(rr), a=isite, b=jsite, + s=ispin, t=jspin, v=intr_value[k])) + + return intr_table + + +def _export_transfer(StdI: StdIntList, + ntbl: int, + tbl_index: np.ndarray, + tbl_value: np.ndarray, + fname: str, tagname: str, + spin_dep: int) -> None: + """Export transfer (hopping) coefficients. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters. + ntbl : int + Number of transfer terms. + tbl_index : np.ndarray + Transfer indices, shape ``(ntbl, 4)``. + tbl_value : np.ndarray + Transfer values, shape ``(ntbl,)`` (complex). + fname : str + Output filename. + tagname : str + Tag identifying transfer type. + spin_dep : int + Whether transfer is spin-dependent (1 = yes, 0 = no). + """ + if ntbl == 0: + print(f"{fname:>24s} is skipped.") + return + + # Accumulate entries of the same index pair + nintr, intr_index, intr_value = _accumulate_list( + 4, ntbl, tbl_index, tbl_value, 0) + + intr_table = (_build_transfer_table( + StdI, nintr, intr_index, intr_value, spin_dep) + if nintr > 0 else []) + + if intr_table: + _write_wannier90(intr_table, StdI.NsiteUC, + 2 if spin_dep == 1 else 1, fname, tagname) + else: + print(f"{fname:>24s} is skipped.") + + +# ----------------------------------------------------------------------- +# Export: on-site Coulomb +# ----------------------------------------------------------------------- + +def _build_coulomb_intra_table( + StdI: StdIntList, + nintr: int, + intr_index: list[list[int]], + intr_value: np.ndarray, +) -> list[_IntrItem]: + """Build a deduplicated on-site Coulomb table. + + Converts accumulated on-site Coulomb entries from absolute site + indices to ``_IntrItem`` entries, deduplicating by the unit-cell + site index ``isite``. Each entry has ``rr = [0,0,0]`` and + ``a == b == isite``. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters (reads ``NsiteUC``). + nintr : int + Number of accumulated entries. + intr_index : list of list of int + Accumulated indices, each row is ``[idx_i]``. + intr_value : np.ndarray + Accumulated complex values, shape ``(nintr,)``. + + Returns + ------- + list of _IntrItem + Deduplicated on-site Coulomb entries. + """ + intr_table: list[_IntrItem] = [] + seen: dict[int, int] = {} # isite -> index in intr_table + + for k in range(nintr): + idx_i = intr_index[k][0] + isite = idx_i % StdI.NsiteUC + + if isite in seen: + existing = intr_table[seen[isite]] + if abs(existing.v - intr_value[k]) > _EPS: + print(f"WARNING: not uniform. " + f"expected=({existing.v.real},{existing.v.imag}), " + f"found=({intr_value[k].real},{intr_value[k].imag}) " + f"for index {idx_i}") + else: + seen[isite] = len(intr_table) + intr_table.append(_IntrItem( + r=[0, 0, 0], a=isite, b=isite, + s=0, t=0, v=intr_value[k])) + + return intr_table + + +def _export_coulomb_intra(StdI: StdIntList, + ntbl: int, + tbl_index: np.ndarray, + tbl_value: np.ndarray, + fname: str, tagname: str) -> None: + """Export on-site Coulomb term coefficients. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters. + ntbl : int + Number of Coulomb terms. + tbl_index : np.ndarray + Site indices, shape ``(ntbl, 1)``. + tbl_value : np.ndarray + Coulomb coefficients, shape ``(ntbl,)`` (real). + fname : str + Output filename. + tagname : str + Tag identifying the term type. + """ + if ntbl == 0: + print(f"{fname:>24s} is skipped.") + return + + tbl_value_c = tbl_value.astype(complex) + + nintr, intr_index, intr_value = _accumulate_list( + 1, ntbl, tbl_index, tbl_value_c, 1) + + intr_table = (_build_coulomb_intra_table( + StdI, nintr, intr_index, intr_value) + if nintr > 0 else []) + + if intr_table: + _write_wannier90(intr_table, StdI.NsiteUC, 1, fname, tagname) + else: + print(f"{fname:>24s} is skipped.") + + +# ----------------------------------------------------------------------- +# Filename prefix helper +# ----------------------------------------------------------------------- + +def _prefix(StdI: StdIntList, fname: str) -> str: + """Add prefix to filename if ``StdI.fileprefix`` is set. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters. + fname : str + Base filename. + + Returns + ------- + str + Filename with optional prefix prepended. + + Notes + ----- + In the C code, the sentinel for "no prefix" is ``"****"``. + In Python, the sentinel is the empty string ``""``. + """ + if StdI.fileprefix == "" or StdI.fileprefix == UNSET_STRING: + return fname + else: + return f"{StdI.fileprefix}_{fname}" + + +# ======================================================================= +# Public API +# ======================================================================= + +def export_geometry(StdI: StdIntList) -> None: + """Export geometry information to file. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters containing lattice geometry. + + Notes + ----- + This function is only meaningful when ``StdI.solver == "HWAVE"``. + It writes the file ``geom.dat`` (optionally with prefix). + """ + _write_geometry(StdI, _prefix(StdI, "geom.dat")) + + +def export_interaction(StdI: StdIntList) -> None: + """Export interaction term coefficients to files. + + Parameters + ---------- + StdI : StdIntList + Standard input parameters containing interaction terms. + + Notes + ----- + This function is only meaningful when ``StdI.solver == "HWAVE"``. + It writes the following files (optionally with prefix): + + - ``transfer.dat`` + - ``coulombintra.dat`` + - ``coulombinter.dat`` + - ``hund.dat`` + - ``exchange.dat`` + - ``pairlift.dat`` + - ``pairhopp.dat`` + """ + global _is_export_all + + if StdI.export_all != NaN_i: + _is_export_all = StdI.export_all + + _export_transfer( + StdI, + StdI.ntrans, StdI.transindx, StdI.trans, + _prefix(StdI, "transfer.dat"), "Transfer", + 0) + + _export_coulomb_intra( + StdI, + StdI.NCintra, StdI.CintraIndx, StdI.Cintra, + _prefix(StdI, "coulombintra.dat"), "CoulombIntra") + + # Two-body shortcut interactions: (count_attr, indx_attr, val_attr, filename, tag) + _INTER_REAL_EXPORTS = ( + ("NCinter", "CinterIndx", "Cinter", "coulombinter.dat", "CoulombInter"), + ("NHund", "HundIndx", "Hund", "hund.dat", "Hund"), + ("NEx", "ExIndx", "Ex", "exchange.dat", "Exchange"), + ("NPairLift", "PLIndx", "PairLift", "pairlift.dat", "PairLift"), + ("NPairHopp", "PHIndx", "PairHopp", "pairhopp.dat", "PairHopp"), + ) + for cnt_attr, idx_attr, val_attr, fname_suffix, tag in _INTER_REAL_EXPORTS: + _export_inter_real( + StdI, + getattr(StdI, cnt_attr), getattr(StdI, idx_attr), getattr(StdI, val_attr), + _prefix(StdI, fname_suffix), tag) diff --git a/python/stdface/solvers/mvmc/__init__.py b/python/stdface/solvers/mvmc/__init__.py new file mode 100644 index 0000000..39ffee6 --- /dev/null +++ b/python/stdface/solvers/mvmc/__init__.py @@ -0,0 +1,9 @@ +"""mVMC solver plugin package. + +Importing this package auto-registers the mVMC plugin. +""" +from __future__ import annotations + +from ._plugin import MVMCPlugin # noqa: F401 — public API + +__all__ = ["MVMCPlugin"] diff --git a/python/stdface/solvers/mvmc/_plugin.py b/python/stdface/solvers/mvmc/_plugin.py new file mode 100644 index 0000000..9fa7916 --- /dev/null +++ b/python/stdface/solvers/mvmc/_plugin.py @@ -0,0 +1,127 @@ +"""mVMC solver plugin. + +Encapsulates all mVMC-specific keyword parsing, field reset tables, +and Expert-mode file writing. +""" +from __future__ import annotations + +from ...plugin import SolverPlugin, register +from ...core.stdface_vals import StdIntList, SolverType, NaN_i, NaN_d +from ...core.keyword_parser import ( + store_with_check_dup_s, store_with_check_dup_i, store_with_check_dup_d, + _grid3x3_keywords, +) +from ...core.param_check import print_val_i +from .variational import generate_orb, proj, print_jastrow +from .writer import print_orb, print_orb_para, print_gutzwiller + + +class MVMCPlugin(SolverPlugin): + """Plugin for the mVMC variational Monte Carlo solver.""" + + @property + def name(self) -> str: + return SolverType.mVMC + + @property + def keyword_table(self) -> dict[str, tuple]: + return _MVMC_KEYWORDS + + @property + def reset_scalars(self) -> list[tuple[str, object]]: + return _RESET_SCALARS + + @property + def reset_arrays(self) -> list[tuple[str, object]]: + return _RESET_ARRAYS + + def write_solver_specific(self, StdI: StdIntList) -> None: + """Write mVMC-specific variational parameter files.""" + if StdI.lGC == 0 and (StdI.Sz2 == 0 or StdI.Sz2 == NaN_i): + StdI.ComplexType = print_val_i("ComplexType", StdI.ComplexType, 0) + else: + StdI.ComplexType = print_val_i("ComplexType", StdI.ComplexType, 1) + + generate_orb(StdI) + proj(StdI) + print_jastrow(StdI) + if StdI.lGC == 1 or (StdI.Sz2 != 0 and StdI.Sz2 != NaN_i): + print_orb_para(StdI) + print_gutzwiller(StdI) + print_orb(StdI) + + +# ----------------------------------------------------------------------- +# Keyword table +# ----------------------------------------------------------------------- + +_BOXSUB_KEYWORDS: dict[str, tuple] = _grid3x3_keywords( + "{a}{c}sub", "boxsub", store_with_check_dup_i, int +) + +_MVMC_KEYWORDS: dict[str, tuple] = { + **_BOXSUB_KEYWORDS, + "complextype": (store_with_check_dup_i, "ComplexType"), + "cparafilehead": (store_with_check_dup_s, "CParaFileHead"), + "dsroptredcut": (store_with_check_dup_d, "DSROptRedCut"), + "dsroptstadel": (store_with_check_dup_d, "DSROptStaDel"), + "dsroptstepdt": (store_with_check_dup_d, "DSROptStepDt"), + "hsub": (store_with_check_dup_i, "Hsub"), + "lsub": (store_with_check_dup_i, "Lsub"), + "nvmccalmode": (store_with_check_dup_i, "NVMCCalMode"), + "ndataidxstart": (store_with_check_dup_i, "NDataIdxStart"), + "ndataqtysmp": (store_with_check_dup_i, "NDataQtySmp"), + "nlanczosmode": (store_with_check_dup_i, "NLanczosMode"), + "nmptrans": (store_with_check_dup_i, "NMPTrans"), + "nspgaussleg": (store_with_check_dup_i, "NSPGaussLeg"), + "nsplitsize": (store_with_check_dup_i, "NSplitSize"), + "nspstot": (store_with_check_dup_i, "NSPStot"), + "nsroptitrsmp": (store_with_check_dup_i, "NSROptItrSmp"), + "nsroptitrstep": (store_with_check_dup_i, "NSROptItrStep"), + "nstore": (store_with_check_dup_i, "NStore"), + "nsrcg": (store_with_check_dup_i, "NSRCG"), + "nvmcinterval": (store_with_check_dup_i, "NVMCInterval"), + "nvmcsample": (store_with_check_dup_i, "NVMCSample"), + "nvmcwarmup": (store_with_check_dup_i, "NVMCWarmUp"), + "rndseed": (store_with_check_dup_i, "RndSeed"), + "wsub": (store_with_check_dup_i, "Wsub"), +} + +# ----------------------------------------------------------------------- +# Reset tables +# ----------------------------------------------------------------------- + +_RESET_SCALARS: list[tuple[str, object]] = [ + ("NVMCCalMode", NaN_i), + ("NLanczosMode", NaN_i), + ("NDataIdxStart", NaN_i), + ("NDataQtySmp", NaN_i), + ("NSPGaussLeg", NaN_i), + ("NSPStot", NaN_i), + ("NMPTrans", NaN_i), + ("NSROptItrStep", NaN_i), + ("NSROptItrSmp", NaN_i), + ("DSROptRedCut", NaN_d), + ("DSROptStaDel", NaN_d), + ("DSROptStepDt", NaN_d), + ("NVMCWarmUp", NaN_i), + ("NVMCInterval", NaN_i), + ("NVMCSample", NaN_i), + ("NExUpdatePath", NaN_i), + ("RndSeed", NaN_i), + ("NSplitSize", NaN_i), + ("NStore", NaN_i), + ("NSRCG", NaN_i), + ("ComplexType", NaN_i), + ("Hsub", NaN_i), + ("Lsub", NaN_i), + ("Wsub", NaN_i), +] + +_RESET_ARRAYS: list[tuple[str, object]] = [ + ("boxsub", NaN_i), +] + + +# Auto-register on import +register(MVMCPlugin()) diff --git a/python/stdface/solvers/mvmc/variational.py b/python/stdface/solvers/mvmc/variational.py new file mode 100644 index 0000000..3bcf6a7 --- /dev/null +++ b/python/stdface/solvers/mvmc/variational.py @@ -0,0 +1,485 @@ +"""mVMC variational parameter generation functions. + +This module contains functions for generating variational wave-function +parameter files used by the mVMC solver: + +- Quantum number projection (``qptransidx.def``) +- Orbital indices (``Orb`` / ``AntiOrb`` arrays) +- Jastrow factor indices (``jastrowidx.def``) + +These functions were extracted from ``stdface_model_util.py`` to improve +module cohesion. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +import numpy as np + +from ...core.stdface_vals import StdIntList, ModelType, NaN_i +from ...core.param_check import exit_program +from ...lattice.site_util import ( + _cell_vector, _fold_to_cell, _fold_site, _find_cell_index, + _validate_box_params, _det_and_cofactor, find_site, +) + + +def _anti_period_dot(AntiPeriod: np.ndarray, nBox: list[int]) -> int: + """Compute the dot product of anti-period flags and box indices. + + Parameters + ---------- + AntiPeriod : np.ndarray + Length-3 array of anti-periodic boundary flags (0 or 1). + nBox : list of int + Length-3 super-cell image index. + + Returns + ------- + int + Sum ``AntiPeriod[0]*nBox[0] + AntiPeriod[1]*nBox[1] + + AntiPeriod[2]*nBox[2]``. + """ + return (int(AntiPeriod[0]) * nBox[0] + + int(AntiPeriod[1]) * nBox[1] + + int(AntiPeriod[2]) * nBox[2]) + + +def _parity_sign(value: int) -> int: + """Convert an integer to +1 (even) or -1 (odd). + + Used to translate an anti-periodic boundary count into a phase sign. + + Parameters + ---------- + value : int + Integer whose parity determines the sign. + + Returns + ------- + int + ``+1`` if *value* is even, ``-1`` if odd. + """ + return 1 if value % 2 == 0 else -1 + + +def _check_commensurate(rbox_sub: np.ndarray, box: np.ndarray, ncell_sub: int) -> bool: + """Check whether a sublattice is commensurate with the main lattice. + + The sublattice is commensurate if for all i, j in {0,1,2}: + ``(rbox_sub[i, :] · box[j, :]) % ncell_sub == 0`` + + Parameters + ---------- + rbox_sub : np.ndarray + Reciprocal box matrix of the sublattice (3x3). + box : np.ndarray + Box matrix of the main lattice (3x3). + ncell_sub : int + Determinant of the sublattice box (number of cells). + + Returns + ------- + bool + ``True`` if the sublattice is commensurate, ``False`` otherwise. + """ + # Compute all dot products: prod[i,j] = rbox_sub[i,:] · box[j,:] + prod = rbox_sub.astype(int) @ box.astype(int).T + return bool(np.all(prod % ncell_sub == 0)) + + +def _fold_site_sub( + StdI: StdIntList, + iCellV: list[int], +) -> tuple[list[int], list[int]]: + """Fold site into the sub-lattice cell (mVMC only). + + Delegates to :func:`~lattice.site_util._fold_to_cell` with the + sub-lattice parameters (``rboxsub``, ``NCellsub``, ``boxsub``). + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. + iCellV : list of int + Fractional coordinate of a site (length 3). + + Returns + ------- + nBox : list of int + Super-cell index. + iCellV_fold : list of int + Folded fractional coordinate. + """ + return _fold_to_cell(StdI.rboxsub, StdI.NCellsub, StdI.boxsub, iCellV) + + +def proj(StdI: StdIntList) -> None: + """Print quantum number projection file ``qptransidx.def`` (mVMC only). + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + """ + Sym = np.zeros((StdI.nsite, StdI.nsite), dtype=int) + Anti = np.zeros((StdI.nsite, StdI.nsite), dtype=int) + + StdI.NSym = 0 + for iCell in range(StdI.NCell): + iCV = _cell_vector(StdI.Cell, iCell) + nBox, iCellV = _fold_site_sub(StdI, iCV) + nBox, iCellV = _fold_site(StdI, iCellV) + + if iCellV == iCV: + for jCell in range(StdI.NCell): + jCellV = [c + v for c, v in zip( + _cell_vector(StdI.Cell, jCell), iCellV)] + nBox, jCellV = _fold_site(StdI, jCellV) + + kCell = _find_cell_index(StdI, jCellV) + ap_dot = _anti_period_dot(StdI.AntiPeriod, nBox) + for jsite in range(StdI.NsiteUC): + Sym[StdI.NSym][jCell * StdI.NsiteUC + jsite] = ( + kCell * StdI.NsiteUC + jsite) + Anti[StdI.NSym][jCell * StdI.NsiteUC + jsite] = ap_dot + + if StdI.model == ModelType.KONDO: + half = StdI.nsite // 2 + Sym[StdI.NSym][half + jCell * StdI.NsiteUC + jsite] = ( + half + kCell * StdI.NsiteUC + jsite) + Anti[StdI.NSym][half + jCell * StdI.NsiteUC + jsite] = ap_dot + StdI.NSym += 1 + + with open("qptransidx.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"NQPTrans {StdI.NSym:10d}\n") + fp.write("=============================================\n") + fp.write("======== TrIdx_TrWeight_and_TrIdx_i_xi ======\n") + fp.write("=============================================\n") + for iSym in range(StdI.NSym): + fp.write(f"{iSym} {1.0:10.5f}\n") + for iSym in range(StdI.NSym): + for jsite in range(StdI.nsite): + a = _parity_sign(Anti[iSym][jsite]) + fp.write(f"{iSym:5d} {jsite:5d} {Sym[iSym][jsite]:5d} {a:5d}\n") + print(" qptransidx.def is written.") + + +def _init_site_sub(StdI: StdIntList) -> None: + """Initialize sub-cell for mVMC/UHF/HWAVE. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + """ + StdI.Lsub, StdI.Wsub, StdI.Hsub = _validate_box_params( + StdI.Lsub, StdI.Wsub, StdI.Hsub, StdI.boxsub, + suffix="sub", defaults=StdI.box) + + # Calculate reciprocal lattice vectors + StdI.NCellsub, StdI.rboxsub = _det_and_cofactor(StdI.boxsub) + print(f" Number of Cell in the sublattice: {abs(StdI.NCellsub)}") + if StdI.NCellsub == 0: + exit_program(-1) + + # Check commensurate + if not _check_commensurate(StdI.rboxsub, StdI.box, StdI.NCellsub): + print("\n ERROR ! Sublattice is INCOMMENSURATE !\n") + exit_program(-1) + + +def _assign_orb_sector( + StdI: StdIntList, + iOrb: int, + anti_val: int, + iCell: int, jCell: int, + iCell2: int, jCell2: int, + isite: int, jsite: int, + i_off: int, j_off: int, + is_new: bool, +) -> int: + """Assign orbital and anti-orbital indices for one sector of a cell pair. + + For each ``(i_off, j_off)`` sector (itinerant/local-spin combination + in the Kondo model, or ``(0, 0)`` for the base sector), this function: + + 1. If *is_new*, assigns a fresh ``iOrb`` to the reference cell pair + ``(iCell2, jCell2)`` and records the anti-periodic sign. + 2. Always copies the reference orbital index to the current cell pair + ``(iCell, jCell)`` and records the anti-periodic sign. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + iOrb : int + Next available orbital index. + anti_val : int + Anti-periodic sign (+1 or -1). + iCell, jCell : int + Current cell indices. + iCell2, jCell2 : int + Reference (reduced) cell indices. + isite, jsite : int + Intra-unit-cell site indices. + i_off, j_off : int + Site-index offsets for this sector (0 for itinerant sites, + ``nsite // 2`` for local-spin sites in the Kondo model). + is_new : bool + Whether this ``(iCell2, jCell2)`` pair is being visited for the + first time. + + Returns + ------- + int + Updated ``iOrb`` (incremented by 1 if *is_new*). + """ + nu = StdI.NsiteUC + row2 = i_off + iCell2 * nu + isite + col2 = j_off + jCell2 * nu + jsite + + if is_new: + StdI.Orb[row2, col2] = iOrb + StdI.AntiOrb[row2, col2] = anti_val + iOrb += 1 + + row = i_off + iCell * nu + isite + col = j_off + jCell * nu + jsite + StdI.Orb[row, col] = StdI.Orb[row2, col2] + StdI.AntiOrb[row, col] = anti_val + + return iOrb + + +def generate_orb(StdI: StdIntList) -> None: + """Generate orbital index for mVMC. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (modified in-place). + """ + _init_site_sub(StdI) + + StdI.Orb = np.zeros((StdI.nsite, StdI.nsite), dtype=int) + StdI.AntiOrb = np.zeros((StdI.nsite, StdI.nsite), dtype=int) + CellDone = np.zeros((StdI.NCell, StdI.NCell), dtype=int) + + iOrb = 0 + for iCell in range(StdI.NCell): + iCV = _cell_vector(StdI.Cell, iCell) + nBox, iCellV = _fold_site_sub(StdI, iCV) + nBox, iCellV = _fold_site(StdI, iCellV) + + iCell2 = _find_cell_index(StdI, iCellV) + + for jCell in range(StdI.NCell): + jCV = _cell_vector(StdI.Cell, jCell) + jCellV = [jc + v - ic for jc, v, ic in zip(jCV, iCellV, iCV)] + nBox, jCellV = _fold_site(StdI, jCellV) + + jCell2 = _find_cell_index(StdI, jCellV) + + # AntiPeriodic factor + dCellV = [jc - ic for jc, ic in zip(jCV, iCV)] + nBox_d, _ = _fold_site(StdI, dCellV) + anti_val = _parity_sign( + _anti_period_dot(StdI.AntiPeriod, nBox_d)) + + # Build list of (i_offset, j_offset) sector pairs + sectors = [(0, 0)] + if StdI.model == ModelType.KONDO: + half = StdI.nsite // 2 + sectors += [(half, 0), (0, half), (half, half)] + + for isite in range(StdI.NsiteUC): + for jsite in range(StdI.NsiteUC): + for i_off, j_off in sectors: + iOrb = _assign_orb_sector( + StdI, iOrb, anti_val, + iCell, jCell, iCell2, jCell2, + isite, jsite, i_off, j_off, + is_new=(CellDone[iCell2, jCell2] == 0)) + + CellDone[iCell2, jCell2] = 1 + + StdI.NOrb = iOrb + + +def _jastrow_momentum_projected( + StdI: StdIntList, + Jastrow: np.ndarray, +) -> tuple[int, np.ndarray]: + """Compute Jastrow indices using momentum-projected (symmetrised) orbital. + + Steps: + + 1. Copy the orbital index matrix into *Jastrow*. + 2. Symmetrise: for each orbital index, set ``J[j,i] = J[i,j]``. + 3. Exclude local-spin sites (set their rows/columns to -1). + 4. Renumber: walk the strict lower triangle, assign negative + temporaries, then invert so indices become non-negative. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (read-only). + Jastrow : numpy.ndarray + ``(nsite, nsite)`` integer array, modified **in place** during + steps 1-3. Replaced by ``-1 - Jastrow`` in step 4 (the + returned array may be a new object). + + Returns + ------- + NJastrow : int + Number of unique Jastrow indices. + Jastrow : numpy.ndarray + Final renumbered Jastrow index matrix. + """ + # (1) Copy Orbital index + for isite in range(StdI.nsite): + for jsite in range(StdI.nsite): + Jastrow[isite, jsite] = StdI.Orb[isite, jsite] + + # (2) Symmetrize + for iorb in range(StdI.NOrb): + for isite in range(StdI.nsite): + for jsite in range(StdI.nsite): + if Jastrow[isite, jsite] == iorb: + Jastrow[jsite, isite] = Jastrow[isite, jsite] + + # (3) Exclude local-spin sites and renumber + NJastrow = 0 if StdI.model == ModelType.HUBBARD else -1 + for isite in range(StdI.nsite): + if StdI.locspinflag[isite] != 0: + Jastrow[isite, :] = -1 + Jastrow[:, isite] = -1 + continue + + for jsite in range(isite): + if Jastrow[isite, jsite] >= 0: + iJastrow = Jastrow[isite, jsite] + NJastrow -= 1 + mask = (Jastrow == iJastrow) + Jastrow[mask] = NJastrow + + NJastrow = -NJastrow + Jastrow = -1 - Jastrow + return NJastrow, Jastrow + + +def _jastrow_global_optimization( + StdI: StdIntList, + Jastrow: np.ndarray, +) -> int: + """Compute Jastrow indices using global (cell-based) optimisation. + + For the Spin model, all pairs share a single Jastrow index. + For Hubbard/Kondo, unique indices are assigned per cell-displacement + pair, respecting reversal symmetry and excluding the on-site term. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure (read-only except for lattice lookups). + Jastrow : numpy.ndarray + ``(nsite, nsite)`` integer array, modified **in place**. + + Returns + ------- + int + Number of unique Jastrow indices (``NJastrow``). + """ + if StdI.model == ModelType.SPIN: + Jastrow[:, :] = 0 + return 1 + + NJastrow = 0 + if StdI.model == ModelType.KONDO: + half = StdI.nsite // 2 + Jastrow[:, :half] = 0 + Jastrow[:half, :] = 0 + NJastrow += 1 + + for dCell in range(StdI.NCell): + dCV = _cell_vector(StdI.Cell, dCell) + isite, jsite, Cphase, dR_arr = find_site( + StdI, 0, 0, 0, -dCV[0], -dCV[1], -dCV[2], 0, 0) + if StdI.model == ModelType.KONDO: + jsite -= StdI.NCell * StdI.NsiteUC + iCell_j = jsite // StdI.NsiteUC + if iCell_j < dCell: + continue + reversal = 1 if iCell_j == dCell else 0 + + for isiteUC in range(StdI.NsiteUC): + for jsiteUC in range(StdI.NsiteUC): + if reversal == 1 and jsiteUC > isiteUC: + continue + if isiteUC == jsiteUC and dCV == [0, 0, 0]: + continue + + for iCell_idx in range(StdI.NCell): + iCV = _cell_vector(StdI.Cell, iCell_idx) + i_s, j_s, _, _ = find_site( + StdI, + iCV[0], iCV[1], iCV[2], + dCV[0], dCV[1], dCV[2], + isiteUC, jsiteUC) + Jastrow[i_s, j_s] = NJastrow + Jastrow[j_s, i_s] = NJastrow + + NJastrow += 1 + + return NJastrow + + +def print_jastrow(StdI: StdIntList) -> None: + """Output Jastrow factor index file ``jastrowidx.def`` (mVMC only). + + Delegates computation to :func:`_jastrow_momentum_projected` or + :func:`_jastrow_global_optimization` based on ``NMPTrans``, then + writes the results to ``jastrowidx.def``. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. + """ + Jastrow = np.zeros((StdI.nsite, StdI.nsite), dtype=int) + + if abs(StdI.NMPTrans) == 1 or StdI.NMPTrans == NaN_i: + NJastrow, Jastrow = _jastrow_momentum_projected(StdI, Jastrow) + else: + NJastrow = _jastrow_global_optimization(StdI, Jastrow) + + with open("jastrowidx.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"NJastrowIdx {NJastrow:10d}\n") + fp.write(f"ComplexType {0:10d}\n") + fp.write("=============================================\n") + fp.write("=============================================\n") + + for isite in range(StdI.nsite): + for jsite in range(StdI.nsite): + if isite == jsite: + continue + fp.write(f"{isite:5d} {jsite:5d} {Jastrow[isite, jsite]:5d}\n") + + for iJastrow in range(NJastrow): + if StdI.model == ModelType.HUBBARD or iJastrow > 0: + fp.write(f"{iJastrow:5d} {1:5d}\n") + else: + fp.write(f"{iJastrow:5d} {0:5d}\n") + print(" jastrowidx.def is written.") diff --git a/python/stdface/solvers/mvmc/writer.py b/python/stdface/solvers/mvmc/writer.py new file mode 100644 index 0000000..b587627 --- /dev/null +++ b/python/stdface/solvers/mvmc/writer.py @@ -0,0 +1,466 @@ +"""mVMC solver-specific output functions. + +This module contains the mVMC (many-variable Variational Monte Carlo) +solver-specific output functions extracted from ``stdface_main.py``. +These functions write the orbital and Gutzwiller variational-parameter +definition files required by mVMC. + +Functions +--------- +print_orb + Write the anti-parallel orbital index file ``orbitalidx.def``. +print_orb_para + Write parallel orbital index files ``orbitalidxpara.def`` and + ``orbitalidxgen.def``. +print_gutzwiller + Write the Gutzwiller variational-parameter file ``gutzwilleridx.def``. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +from ...core.stdface_vals import StdIntList, ModelType, NaN_i + + +def _has_anti_period(StdI: StdIntList) -> bool: + """Return whether any lattice direction has anti-periodic boundary. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure. + + Returns + ------- + bool + ``True`` if any of the three ``AntiPeriod`` flags equals 1. + """ + return any(ap == 1 for ap in StdI.AntiPeriod) + + +def print_orb(StdI: StdIntList) -> None: + """Write the anti-parallel orbital index file ``orbitalidx.def``. + + The file records the orbital pairing indices used by mVMC for the + anti-parallel-spin part of the variational wave function. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. The following fields are read: + + - ``nsite`` -- total number of sites + - ``NOrb`` -- number of orbital indices + - ``ComplexType`` -- 0 for real, 1 for complex + - ``Orb`` -- ``nsite x nsite`` orbital index matrix + - ``AntiOrb`` -- ``nsite x nsite`` anti-periodic sign matrix + - ``AntiPeriod`` -- length-3 array of anti-periodic boundary flags + + Notes + ----- + Translated from the C function ``PrintOrb()`` in ``StdFace_main.c``. + """ + with open("orbitalidx.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"NOrbitalIdx {StdI.NOrb:10d}\n") + fp.write(f"ComplexType {StdI.ComplexType:10d}\n") + fp.write("=============================================\n") + fp.write("=============================================\n") + + has_anti = _has_anti_period(StdI) + + for isite in range(StdI.nsite): + for jsite in range(StdI.nsite): + if has_anti: + fp.write(f"{isite:5d} {jsite:5d} " + f"{StdI.Orb[isite][jsite]:5d} " + f"{StdI.AntiOrb[isite][jsite]:5d}\n") + else: + fp.write(f"{isite:5d} {jsite:5d} " + f"{StdI.Orb[isite][jsite]:5d}\n") + + for iOrb in range(StdI.NOrb): + fp.write(f"{iOrb:5d} {1:5d}\n") + + print(" orbitalidx.def is written.") + + +def _compute_parallel_orbitals( + nsite: int, + NOrb: int, + Orb, + AntiOrb, +) -> tuple[list[list[int]], list[list[int]], int]: + """Compute parallel orbital indices from anti-parallel orbital data. + + Performs three steps: + + 1. **Copy**: copy the anti-parallel orbital matrix (``Orb``) into a + local ``OrbGC`` matrix and the sign matrix (``AntiOrb``) into + ``reverse``. + 2. **Symmetrise**: for each known orbital index, set + ``OrbGC[j][i] = OrbGC[i][j]`` and ``reverse[j][i] = -reverse[i][j]``. + 3. **Renumber**: walk the strict lower triangle (``isite > jsite``), + assign negative temporaries to each newly seen orbital, then invert + all indices so they become non-negative. + + Parameters + ---------- + nsite : int + Total number of sites. + NOrb : int + Number of anti-parallel orbital indices. + Orb : array-like + ``(nsite, nsite)`` orbital index matrix (read-only). + AntiOrb : array-like + ``(nsite, nsite)`` anti-periodic sign matrix (read-only). + + Returns + ------- + OrbGC : list of list of int + ``(nsite, nsite)`` renumbered parallel orbital indices. + reverse : list of list of int + ``(nsite, nsite)`` sign-reversal matrix. + NOrbGC : int + Number of unique parallel orbital indices. + """ + # (1) Copy + OrbGC = [[0] * nsite for _ in range(nsite)] + reverse = [[0] * nsite for _ in range(nsite)] + for isite in range(nsite): + for jsite in range(nsite): + OrbGC[isite][jsite] = int(Orb[isite][jsite]) + reverse[isite][jsite] = int(AntiOrb[isite][jsite]) + + # (2) Symmetrise + for iorb in range(NOrb): + for isite in range(nsite): + for jsite in range(nsite): + if OrbGC[isite][jsite] == iorb: + OrbGC[jsite][isite] = OrbGC[isite][jsite] + reverse[jsite][isite] = -reverse[isite][jsite] + + # (3) Renumber -- lower triangle (isite > jsite) + NOrbGC = 0 + for isite in range(nsite): + for jsite in range(isite): + if OrbGC[isite][jsite] >= 0: + iOrbGC = OrbGC[isite][jsite] + NOrbGC -= 1 + for isite1 in range(nsite): + for jsite1 in range(nsite): + if OrbGC[isite1][jsite1] == iOrbGC: + OrbGC[isite1][jsite1] = NOrbGC + + NOrbGC = -NOrbGC + for isite in range(nsite): + for jsite in range(nsite): + OrbGC[isite][jsite] = -1 - OrbGC[isite][jsite] + + return OrbGC, reverse, NOrbGC + + +def _write_orbitalidxpara( + nsite: int, + ComplexType: int, + OrbGC: list, + reverse: list, + NOrbGC: int, +) -> None: + """Write ``orbitalidxpara.def`` from pre-computed parallel orbital data. + + Parameters + ---------- + nsite : int + Total number of sites. + ComplexType : int + 0 for real, 1 for complex variational parameters. + OrbGC : array-like + ``nsite x nsite`` parallel orbital index matrix. + reverse : array-like + ``nsite x nsite`` sign-reversal matrix. + NOrbGC : int + Number of parallel orbital indices. + """ + with open("orbitalidxpara.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"NOrbitalIdx {NOrbGC:10d}\n") + fp.write(f"ComplexType {ComplexType:10d}\n") + fp.write("=============================================\n") + fp.write("=============================================\n") + + for isite in range(nsite): + for jsite in range(nsite): + if isite >= jsite: + continue + fp.write(f"{isite:5d} {jsite:5d} " + f"{OrbGC[isite][jsite]:5d} " + f"{reverse[isite][jsite]:5d}\n") + + for iOrbGC in range(NOrbGC): + fp.write(f"{iOrbGC:5d} {1:5d}\n") + + +def _write_orbitalidxgen( + StdI: StdIntList, + OrbGC: list, + reverse: list, + NOrbGC: int, +) -> None: + """Write ``orbitalidxgen.def`` combining anti-parallel and parallel orbitals. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. Reads ``nsite``, ``NOrb``, + ``ComplexType``, ``Orb``, ``AntiOrb``, and ``AntiPeriod``. + OrbGC : array-like + ``nsite x nsite`` parallel orbital index matrix. + reverse : array-like + ``nsite x nsite`` sign-reversal matrix. + NOrbGC : int + Number of parallel orbital indices. + """ + nsite = StdI.nsite + has_anti = _has_anti_period(StdI) + + with open("orbitalidxgen.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"NOrbitalIdx {StdI.NOrb + 2 * NOrbGC:10d}\n") + fp.write(f"ComplexType {StdI.ComplexType:10d}\n") + fp.write("=============================================\n") + fp.write("=============================================\n") + + # -- anti-parallel section -- + for isite in range(nsite): + for jsite in range(nsite): + if has_anti: + fp.write(f"{isite:5d} 0 {jsite:5d} 1 " + f"{StdI.Orb[isite][jsite]:5d} " + f"{StdI.AntiOrb[isite][jsite]:5d}\n") + else: + fp.write(f"{isite:5d} 0 {jsite:5d} 1 " + f"{StdI.Orb[isite][jsite]:5d} {1:5d}\n") + + # -- parallel section (upper triangle) -- + for isite in range(nsite): + for jsite in range(nsite): + if isite >= jsite: + continue + fp.write(f"{isite:5d} 0 {jsite:5d} 0 " + f"{OrbGC[isite][jsite] + StdI.NOrb:5d} " + f"{reverse[isite][jsite]:5d}\n") + fp.write(f"{isite:5d} 1 {jsite:5d} 1 " + f"{OrbGC[isite][jsite] + StdI.NOrb + NOrbGC:5d} " + f"{reverse[isite][jsite]:5d}\n") + + for iOrbGC in range(StdI.NOrb): + fp.write(f"{iOrbGC:5d} {1:5d}\n") + + for iOrbGC in range(NOrbGC * 2): + fp.write(f"{iOrbGC + StdI.NOrb:5d} {1:5d}\n") + + +def print_orb_para(StdI: StdIntList) -> None: + """Write parallel orbital index files. + + Writes ``orbitalidxpara.def`` via :func:`_write_orbitalidxpara` and + ``orbitalidxgen.def`` via :func:`_write_orbitalidxgen`. Computation + of parallel orbital indices is delegated to + :func:`_compute_parallel_orbitals`. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. The following fields are read: + + - ``nsite`` -- total number of sites + - ``NOrb`` -- number of anti-parallel orbital indices + - ``ComplexType`` -- 0 for real, 1 for complex + - ``Orb`` -- ``nsite x nsite`` orbital index matrix + - ``AntiOrb`` -- ``nsite x nsite`` anti-periodic sign matrix + - ``AntiPeriod`` -- length-3 array of anti-periodic boundary flags + + Notes + ----- + Translated from the C function ``PrintOrbPara()`` in ``StdFace_main.c``. + """ + OrbGC, reverse, NOrbGC = _compute_parallel_orbitals( + StdI.nsite, StdI.NOrb, StdI.Orb, StdI.AntiOrb) + + _write_orbitalidxpara( + StdI.nsite, StdI.ComplexType, OrbGC, reverse, NOrbGC) + print(" orbitalidxpara.def is written.") + + _write_orbitalidxgen(StdI, OrbGC, reverse, NOrbGC) + print(" orbitalidxgen.def is written.") + + +def _gutzwiller_momentum_projected( + StdI: StdIntList, + Gutz: list[int], +) -> int: + """Compute Gutzwiller indices in momentum-projected mode. + + For the Hubbard model, ``NGutzwiller`` starts at 0; for other models + it starts at -1. Diagonal orbital indices (``Orb[i][i]``) are used; + local-spin sites are excluded (set to -1). Unique Gutzwiller indices + are renumbered with negative temporaries and then inverted. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. + Gutz : list of int + Mutable per-site Gutzwiller index array (modified in place). + + Returns + ------- + int + Number of unique Gutzwiller parameters (``NGutzwiller``). + """ + nsite = StdI.nsite + + if StdI.model == ModelType.HUBBARD: + NGutzwiller = 0 + else: + NGutzwiller = -1 + + for isite in range(nsite): + Gutz[isite] = int(StdI.Orb[isite][isite]) + + for isite in range(nsite): + if StdI.locspinflag[isite] != 0: + Gutz[isite] = -1 + continue + if Gutz[isite] >= 0: + iGutz = Gutz[isite] + NGutzwiller -= 1 + for jsite in range(nsite): + if Gutz[jsite] == iGutz: + Gutz[jsite] = NGutzwiller + + NGutzwiller = -NGutzwiller + for isite in range(nsite): + Gutz[isite] = -1 - Gutz[isite] + + return NGutzwiller + + +def _gutzwiller_global_optimization( + StdI: StdIntList, + Gutz: list[int], +) -> int: + """Compute Gutzwiller indices in global-optimisation mode. + + - Hubbard: ``NGutzwiller = NsiteUC``, site index modulo ``NsiteUC``. + - Spin: ``NGutzwiller = 1``, all sites map to index 0. + - Kondo: ``NGutzwiller = NsiteUC + 1``, conduction sites map to 0, + localised sites map to ``isite + 1``. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. + Gutz : list of int + Mutable per-site Gutzwiller index array (modified in place). + + Returns + ------- + int + Number of unique Gutzwiller parameters (``NGutzwiller``). + """ + if StdI.model == ModelType.HUBBARD: + NGutzwiller = StdI.NsiteUC + elif StdI.model == ModelType.SPIN: + NGutzwiller = 1 + else: + NGutzwiller = StdI.NsiteUC + 1 + + for iCell in range(StdI.NCell): + for isite in range(StdI.NsiteUC): + if StdI.model == ModelType.HUBBARD: + Gutz[isite + StdI.NsiteUC * iCell] = isite + elif StdI.model == ModelType.SPIN: + Gutz[isite + StdI.NsiteUC * iCell] = 0 + else: + Gutz[isite + StdI.NsiteUC * iCell] = 0 + Gutz[isite + StdI.NsiteUC * (iCell + StdI.NCell)] = isite + 1 + + return NGutzwiller + + +def _write_gutzwiller_file( + StdI: StdIntList, + NGutzwiller: int, + Gutz: list[int], +) -> None: + """Write ``gutzwilleridx.def`` from pre-computed Gutzwiller indices. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. Reads ``nsite`` and ``model``. + NGutzwiller : int + Number of unique Gutzwiller parameters. + Gutz : list of int + Per-site Gutzwiller index array. + """ + with open("gutzwilleridx.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"NGutzwillerIdx {NGutzwiller:10d}\n") + fp.write(f"ComplexType {0:10d}\n") + fp.write("=============================================\n") + fp.write("=============================================\n") + + for isite in range(StdI.nsite): + fp.write(f"{isite:5d} {Gutz[isite]:5d}\n") + + for iGutz in range(NGutzwiller): + flag = int(StdI.model == ModelType.HUBBARD or iGutz > 0) + fp.write(f"{iGutz:5d} {flag:5d}\n") + + +def print_gutzwiller(StdI: StdIntList) -> None: + """Write the Gutzwiller variational-parameter file ``gutzwilleridx.def``. + + Delegates computation to :func:`_gutzwiller_momentum_projected` or + :func:`_gutzwiller_global_optimization` based on ``NMPTrans``, then + writes the results via :func:`_write_gutzwiller_file`. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. The following fields are read: + + - ``nsite`` -- total number of sites + - ``NMPTrans`` -- momentum-projection control + - ``model`` -- model type + - ``Orb`` -- ``nsite x nsite`` orbital index matrix + - ``locspinflag`` -- per-site local-spin flag array + - ``NsiteUC`` -- number of sites per unit cell + - ``NCell`` -- number of unit cells + + Notes + ----- + Translated from the C function ``PrintGutzwiller()`` in + ``StdFace_main.c``. + """ + Gutz = [0] * StdI.nsite + + if abs(StdI.NMPTrans) == 1 or StdI.NMPTrans == NaN_i: + NGutzwiller = _gutzwiller_momentum_projected(StdI, Gutz) + else: + NGutzwiller = _gutzwiller_global_optimization(StdI, Gutz) + + _write_gutzwiller_file(StdI, NGutzwiller, Gutz) + print(" gutzwilleridx.def is written.") diff --git a/python/stdface/solvers/uhf/__init__.py b/python/stdface/solvers/uhf/__init__.py new file mode 100644 index 0000000..f325ed4 --- /dev/null +++ b/python/stdface/solvers/uhf/__init__.py @@ -0,0 +1,9 @@ +"""UHF solver plugin package. + +Importing this package auto-registers the UHF plugin. +""" +from __future__ import annotations + +from ._plugin import UHFPlugin # noqa: F401 — public API + +__all__ = ["UHFPlugin"] diff --git a/python/stdface/solvers/uhf/_plugin.py b/python/stdface/solvers/uhf/_plugin.py new file mode 100644 index 0000000..e72a360 --- /dev/null +++ b/python/stdface/solvers/uhf/_plugin.py @@ -0,0 +1,84 @@ +"""UHF solver plugin. + +Encapsulates all UHF-specific keyword parsing, field reset tables, +and Expert-mode file writing. +""" +from __future__ import annotations + +from ...plugin import SolverPlugin, register +from ...core.stdface_vals import StdIntList, SolverType, NaN_i, NaN_d +from ...core.keyword_parser import ( + store_with_check_dup_i, store_with_check_dup_d, + _grid3x3_keywords, +) + + +class UHFPlugin(SolverPlugin): + """Plugin for the UHF (unrestricted Hartree-Fock) solver.""" + + @property + def name(self) -> str: + return SolverType.UHF + + @property + def keyword_table(self) -> dict[str, tuple]: + return _UHF_KEYWORDS + + @property + def reset_scalars(self) -> list[tuple[str, object]]: + return _RESET_SCALARS + + @property + def reset_arrays(self) -> list[tuple[str, object]]: + return _RESET_ARRAYS + + def write_green(self, StdI: StdIntList) -> None: + """Write only greenone.def (UHF does not use greentwo).""" + from ...writer.common_writer import print_1_green + print_1_green(StdI) + + +# ----------------------------------------------------------------------- +# Keyword table +# ----------------------------------------------------------------------- + +_BOXSUB_KEYWORDS: dict[str, tuple] = _grid3x3_keywords( + "{a}{c}sub", "boxsub", store_with_check_dup_i, int +) + +_UHF_KEYWORDS: dict[str, tuple] = { + "iteration_max": (store_with_check_dup_i, "Iteration_max"), + "rndseed": (store_with_check_dup_i, "RndSeed"), + "nmptrans": (store_with_check_dup_i, "NMPTrans"), + **_BOXSUB_KEYWORDS, + "hsub": (store_with_check_dup_i, "Hsub"), + "lsub": (store_with_check_dup_i, "Lsub"), + "wsub": (store_with_check_dup_i, "Wsub"), + "eps": (store_with_check_dup_i, "eps"), + "epsslater": (store_with_check_dup_i, "eps_slater"), + "mix": (store_with_check_dup_d, "mix"), +} + +# ----------------------------------------------------------------------- +# Reset tables +# ----------------------------------------------------------------------- + +_RESET_SCALARS: list[tuple[str, object]] = [ + ("NMPTrans", NaN_i), + ("RndSeed", NaN_i), + ("mix", NaN_d), + ("eps", NaN_i), + ("eps_slater", NaN_i), + ("Iteration_max", NaN_i), + ("Hsub", NaN_i), + ("Lsub", NaN_i), + ("Wsub", NaN_i), +] + +_RESET_ARRAYS: list[tuple[str, object]] = [ + ("boxsub", NaN_i), +] + + +# Auto-register on import +register(UHFPlugin()) diff --git a/python/stdface/writer/__init__.py b/python/stdface/writer/__init__.py new file mode 100644 index 0000000..fc257ba --- /dev/null +++ b/python/stdface/writer/__init__.py @@ -0,0 +1,8 @@ +"""Output file generation subpackage. + +This package contains shared modules for writing Expert-mode definition files. +Solver-specific writers have moved to ``stdface.solvers``. +""" +from __future__ import annotations + +from .interaction_writer import print_interactions # noqa: F401 diff --git a/python/stdface/writer/common_writer.py b/python/stdface/writer/common_writer.py new file mode 100644 index 0000000..a3d974c --- /dev/null +++ b/python/stdface/writer/common_writer.py @@ -0,0 +1,1136 @@ +"""Common output writer functions shared across all solvers. + +This module contains functions that generate definition files common to all +supported solvers (HPhi, mVMC, UHF, H-wave). These were extracted from +``stdface_main.py`` during refactoring. + +Classes +------- +GreenFunctionIndices + Generator for one- and two-body Green-function index lists. + +Functions +--------- +print_loc_spin + Write ``locspn.def`` listing local-spin flags for every site. +print_trans + Write ``trans.def`` listing one-body transfer integrals. +print_namelist + Write ``namelist.def`` that lists all definition files for the solver. +print_mod_para + Write ``modpara.def`` containing model / calculation parameters. +print_1_green + Write ``greenone.def`` listing one-body Green-function indices. +print_2_green + Write ``greentwo.def`` listing two-body Green-function indices. +unsupported_system + Print an error message and abort for an unsupported model/lattice pair. +check_output_mode + Verify and set the integer output-mode flag from the string keyword. +check_mod_para + Validate and set default values for solver-specific model parameters. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +from collections.abc import Callable +from typing import NamedTuple + +import numpy as np + +from ..core.stdface_vals import ( + StdIntList, ModelType, SolverType, MethodType, NaN_i, UNSET_STRING, + AMPLITUDE_EPS, +) +from ..core.param_check import exit_program, print_val_i, print_val_d, required_val_i, not_used_i + + +# --------------------------------------------------------------------------- +# String → integer dispatch tables +# --------------------------------------------------------------------------- + +OUTPUT_MODE_TO_INT: dict[str, int] = { + "non": 0, + "none": 0, + "off": 0, + "cor": 1, + "corr": 1, + "correlation": 1, + "raw": 2, + "all": 2, + "full": 2, +} +"""Maps ``outputmode`` keyword strings to the ``ioutputmode`` integer code. + +- 0 = no correlation output +- 1 = correlation functions only +- 2 = raw (all) output +""" + +MODEL_GC_TO_EX_UPDATE_PATH: dict[tuple, int] = { + (ModelType.HUBBARD, 0): 0, + (ModelType.HUBBARD, 1): 0, + (ModelType.SPIN, 0): 2, + (ModelType.SPIN, 1): 2, + (ModelType.KONDO, 0): 1, + (ModelType.KONDO, 1): 3, +} +"""Maps ``(ModelType, lGC)`` to the ``NExUpdatePath`` integer for mVMC. + +Hubbard always gets 0, Spin always gets 2, and Kondo depends on whether +the grand-canonical flag (``lGC``) is set (1 → 3, 0 → 1). +""" + + +def _merge_duplicate_terms(indx, vals, n: int) -> int: + """Merge duplicate index quadruples and count non-negligible entries. + + For each pair of terms with identical 4-element index quadruples, + the first term's amplitude absorbs the second's, and the second is + zeroed. After merging, the number of entries whose absolute value + exceeds ``AMPLITUDE_EPS`` is returned. + + Parameters + ---------- + indx : array-like + 2-D index array, shape ``(n, 4)``. Each row is a 4-element + index quadruple ``(i, s_i, j, s_j)``. + vals : array-like + 1-D amplitude array, length ``n``. Modified **in place** during + merging (duplicates are zeroed). + n : int + Number of entries to process. + + Returns + ------- + int + Count of entries with ``abs(val) > AMPLITUDE_EPS`` after merging. + """ + # Merge duplicates: first occurrence absorbs all later ones with same key + seen: dict[tuple, int] = {} + for k in range(n): + key = tuple(indx[k]) + if key in seen: + vals[seen[key]] += vals[k] + vals[k] = 0.0 + else: + seen[key] = k + + return sum(1 for k in range(n) if abs(vals[k]) > AMPLITUDE_EPS) + + +def print_loc_spin(StdI: StdIntList) -> None: + """Write ``locspn.def`` listing local-spin flags for every site. + + This is the Python translation of the C function ``PrintLocSpin()``. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. The following fields are read: + + - ``nsite`` : int -- total number of sites. + - ``locspinflag`` : list of int -- per-site flag (0 = itinerant + electron, nonzero = local spin with :math:`S` given by the value). + """ + nlocspin = int(np.count_nonzero(StdI.locspinflag[:StdI.nsite])) + + with open("locspn.def", "w") as fp: + fp.write("================================ \n") + fp.write(f"NlocalSpin {nlocspin:5d} \n") + fp.write("================================ \n") + fp.write("========i_1LocSpn_0IteElc ====== \n") + fp.write("================================ \n") + for isite in range(StdI.nsite): + fp.write(f"{isite:5d} {StdI.locspinflag[isite]:5d}\n") + + print(" locspn.def is written.") + + +def print_trans(StdI: StdIntList) -> None: + """Write ``trans.def`` listing one-body transfer integrals. + + This is the Python translation of the C function ``PrintTrans()``. + Duplicate index quadruples are merged (their amplitudes summed) and + entries whose absolute value is below ``AMPLITUDE_EPS`` are suppressed. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. The following fields are read + and (for merging) modified **in place**: + + - ``ntrans`` : int -- number of registered transfer terms. + - ``transindx`` : 2-D array of int, shape ``(ntrans, 4)`` -- + site/spin indices ``(i, s_i, j, s_j)`` for each term. + - ``trans`` : 1-D array of complex -- transfer amplitudes. + """ + ntrans0 = _merge_duplicate_terms(StdI.transindx, StdI.trans, StdI.ntrans) + + # --- write file --- + with open("trans.def", "w") as fp: + fp.write("======================== \n") + fp.write(f"NTransfer {ntrans0:7d} \n") + fp.write("======================== \n") + fp.write("========i_j_s_tijs====== \n") + fp.write("======================== \n") + for ktrans in range(StdI.ntrans): + val = StdI.trans[ktrans] + if abs(val) > AMPLITUDE_EPS: + i0, s0, i1, s1 = StdI.transindx[ktrans] + fp.write( + f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " + f"{val.real:25.15f} {val.imag:25.15f}\n" + ) + + print(" trans.def is written.") + + +def _write_namelist_hphi(fp, StdI: StdIntList) -> None: + """Write HPhi-specific entries in ``namelist.def``. + + Parameters + ---------- + fp : file object + Open file handle for ``namelist.def``. + StdI : StdIntList + The global parameter structure. + """ + fp.write(" CalcMod calcmod.def\n") + if StdI.SpectrumBody == 1: + fp.write("SingleExcitation single.def\n") + else: + fp.write(" PairExcitation pair.def\n") + if StdI.method == MethodType.TIME_EVOLUTION: + if StdI.PumpBody == 1: + fp.write(" TEOneBody teone.def\n") + elif StdI.PumpBody == 2: + fp.write(" TETwoBody tetwo.def\n") + fp.write(f" SpectrumVec {StdI.CDataFileHead}_eigenvec_0\n") + if StdI.lBoost == 1: + fp.write(" Boost boost.def\n") + + +def _write_namelist_mvmc(fp, StdI: StdIntList) -> None: + """Write mVMC-specific entries in ``namelist.def``. + + Parameters + ---------- + fp : file object + Open file handle for ``namelist.def``. + StdI : StdIntList + The global parameter structure. + """ + fp.write(" Gutzwiller gutzwilleridx.def\n") + fp.write(" Jastrow jastrowidx.def\n") + fp.write(" Orbital orbitalidx.def\n") + if StdI.lGC == 1 or (StdI.Sz2 != 0 and StdI.Sz2 != NaN_i): + fp.write(" OrbitalParallel orbitalidxpara.def\n") + fp.write("# OrbitalGeneral orbitalidxgen.def\n") + fp.write(" TransSym qptransidx.def\n") + + +_NAMELIST_BODY_DISPATCH: dict[SolverType, Callable] = { + SolverType.HPhi: _write_namelist_hphi, + SolverType.mVMC: _write_namelist_mvmc, +} +"""Maps solver type to the function that writes solver-specific namelist entries. + +Solvers not in this dict (UHF, HWAVE) have no solver-specific entries. +""" + +# Interaction file flags and their namelist lines +_INTERACTION_FLAGS: list[tuple[str, str]] = [ + ("LCintra", " CoulombIntra coulombintra.def\n"), + ("LCinter", " CoulombInter coulombinter.def\n"), + ("LHund", " Hund hund.def\n"), + ("LEx", " Exchange exchange.def\n"), + ("LPairLift", " PairLift pairlift.def\n"), + ("LPairHopp", " PairHop pairhopp.def\n"), + ("Lintr", " InterAll interall.def\n"), +] +"""Maps ``StdIntList`` flag attribute names to their namelist entry lines. + +Each flag, when equal to 1, causes the corresponding definition file to be +listed in ``namelist.def``. +""" + + +def print_namelist(StdI: StdIntList) -> None: + """Write ``namelist.def`` that lists all definition files for the solver. + + This is the Python translation of the C function ``PrintNamelist()``. + The content depends on which solver is active (``StdI.solver``). + + The common prefix (ModPara, LocSpin, Trans, conditional interaction + files, Green-function files) is written for all solvers. Solver-specific + entries are delegated to body-writer functions via + ``_NAMELIST_BODY_DISPATCH``. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. + """ + with open("namelist.def", "w") as fp: + fp.write(" ModPara modpara.def\n") + fp.write(" LocSpin locspn.def\n") + fp.write(" Trans trans.def\n") + + for flag_attr, line in _INTERACTION_FLAGS: + if getattr(StdI, flag_attr) == 1: + fp.write(line) + + if StdI.ioutputmode != 0: + fp.write(" OneBodyG greenone.def\n") + if StdI.solver in (SolverType.HPhi, SolverType.mVMC): + fp.write(" TwoBodyG greentwo.def\n") + + body_writer = _NAMELIST_BODY_DISPATCH.get(StdI.solver) + if body_writer is not None: + body_writer(fp, StdI) + + print(" namelist.def is written.") + + +def _write_modpara_hphi(fp, StdI: StdIntList) -> None: + """Write the HPhi-specific body of ``modpara.def``. + + Parameters + ---------- + fp : file object + Open file handle for ``modpara.def``. + StdI : StdIntList + The global parameter structure. + """ + fp.write("HPhi_Cal_Parameters\n") + fp.write("--------------------\n") + fp.write(f"CDataFileHead {StdI.CDataFileHead}\n") + fp.write("CParaFileHead zqp\n") + fp.write("--------------------\n") + fp.write(f"Nsite {StdI.nsite:<5d}\n") + if StdI.Sz2 != NaN_i: + fp.write(f"2Sz {StdI.Sz2:<5d}\n") + if StdI.ncond != NaN_i: + fp.write(f"Ncond {StdI.ncond:<5d}\n") + fp.write(f"Lanczos_max {StdI.Lanczos_max:<5d}\n") + fp.write(f"initial_iv {StdI.initial_iv:<5d}\n") + if StdI.nvec != NaN_i: + fp.write(f"nvec {StdI.nvec:<5d}\n") + fp.write(f"exct {StdI.exct:<5d}\n") + fp.write(f"LanczosEps {StdI.LanczosEps:<5d}\n") + fp.write(f"LanczosTarget {StdI.LanczosTarget:<5d}\n") + fp.write(f"LargeValue {StdI.LargeValue:<25.15e}\n") + fp.write(f"NumAve {StdI.NumAve:<5d}\n") + fp.write(f"ExpecInterval {StdI.ExpecInterval:<5d}\n") + fp.write(f"NOmega {StdI.Nomega:<5d}\n") + fp.write( + f"OmegaMax {StdI.OmegaMax:<25.15e} {StdI.OmegaIm:<25.15e}\n" + ) + fp.write( + f"OmegaMin {StdI.OmegaMin:<25.15e} {StdI.OmegaIm:<25.15e}\n" + ) + fp.write( + f"OmegaOrg {StdI.OmegaOrg:<25.15e} {0.0:<25.15e}\n" + ) + fp.write(f"PreCG {0:<5d}\n") + if StdI.method == MethodType.TIME_EVOLUTION: + fp.write(f"ExpandCoef {StdI.ExpandCoef:<5d}\n") + + +def _write_modpara_mvmc(fp, StdI: StdIntList) -> None: + """Write the mVMC-specific body of ``modpara.def``. + + Parameters + ---------- + fp : file object + Open file handle for ``modpara.def``. + StdI : StdIntList + The global parameter structure. + """ + fp.write("VMC_Cal_Parameters\n") + fp.write("--------------------\n") + fp.write(f"CDataFileHead {StdI.CDataFileHead}\n") + fp.write(f"CParaFileHead {StdI.CParaFileHead}\n") + fp.write("--------------------\n") + fp.write(f"NVMCCalMode {StdI.NVMCCalMode}\n") + fp.write(f"NLanczosMode {StdI.NLanczosMode}\n") + fp.write("--------------------\n") + fp.write(f"NDataIdxStart {StdI.NDataIdxStart}\n") + fp.write(f"NDataQtySmp {StdI.NDataQtySmp}\n") + fp.write("--------------------\n") + fp.write(f"Nsite {StdI.nsite}\n") + fp.write(f"Ncond {StdI.ncond:<5d}\n") + if StdI.Sz2 != NaN_i: + fp.write(f"2Sz {StdI.Sz2}\n") + if StdI.NSPGaussLeg != NaN_i: + fp.write(f"NSPGaussLeg {StdI.NSPGaussLeg}\n") + if StdI.NSPStot != NaN_i: + fp.write(f"NSPStot {StdI.NSPStot}\n") + fp.write(f"NMPTrans {StdI.NMPTrans}\n") + fp.write(f"NSROptItrStep {StdI.NSROptItrStep}\n") + fp.write(f"NSROptItrSmp {StdI.NSROptItrSmp}\n") + fp.write(f"DSROptRedCut {StdI.DSROptRedCut:.10f}\n") + fp.write(f"DSROptStaDel {StdI.DSROptStaDel:.10f}\n") + fp.write(f"DSROptStepDt {StdI.DSROptStepDt:.10f}\n") + fp.write(f"NVMCWarmUp {StdI.NVMCWarmUp}\n") + fp.write(f"NVMCInterval {StdI.NVMCInterval}\n") + fp.write(f"NVMCSample {StdI.NVMCSample}\n") + fp.write(f"NExUpdatePath {StdI.NExUpdatePath}\n") + fp.write(f"RndSeed {StdI.RndSeed}\n") + fp.write(f"NSplitSize {StdI.NSplitSize}\n") + fp.write(f"NStore {StdI.NStore}\n") + fp.write(f"NSRCG {StdI.NSRCG}\n") + + +def _write_modpara_uhf_hwave(fp, StdI: StdIntList) -> None: + """Write the UHF / H-wave body of ``modpara.def``. + + UHF and H-wave share identical file content except for the header + banner line. The correct banner is selected from + :data:`_MODPARA_BANNER`. + + Parameters + ---------- + fp : file object + Open file handle for ``modpara.def``. + StdI : StdIntList + The global parameter structure. + """ + banner = _MODPARA_BANNER[StdI.solver] + fp.write(f"{banner}\n") + fp.write("--------------------\n") + fp.write(f"CDataFileHead {StdI.CDataFileHead}\n") + fp.write("CParaFileHead zqp\n") + fp.write("--------------------\n") + fp.write(f"Nsite {StdI.nsite}\n") + if StdI.Sz2 != NaN_i: + fp.write(f"2Sz {StdI.Sz2:<5d}\n") + fp.write(f"Ncond {StdI.ncond:<5d}\n") + fp.write(f"IterationMax {StdI.Iteration_max}\n") + fp.write(f"EPS {StdI.eps}\n") + fp.write(f"Mix {StdI.mix:.10f}\n") + fp.write(f"RndSeed {StdI.RndSeed}\n") + fp.write(f"EpsSlater {StdI.eps_slater}\n") + fp.write(f"NMPTrans {StdI.NMPTrans}\n") + + +_MODPARA_BANNER: dict[str, str] = { + SolverType.UHF: "UHF_Cal_Parameters", + SolverType.HWAVE: "HWAVE_Cal_Parameters", +} +"""Maps UHF/HWAVE solver type to the ``modpara.def`` banner line.""" + + +_MODPARA_BODY_DISPATCH: dict[str, Callable] = { + SolverType.HPhi: _write_modpara_hphi, + SolverType.mVMC: _write_modpara_mvmc, + SolverType.UHF: _write_modpara_uhf_hwave, + SolverType.HWAVE: _write_modpara_uhf_hwave, +} +"""Maps ``SolverType`` to the function that writes the solver-specific body +of ``modpara.def``. UHF and H-wave share the same writer +(:func:`_write_modpara_uhf_hwave`). +""" + + +def print_mod_para(StdI: StdIntList) -> None: + """Write ``modpara.def`` containing model / calculation parameters. + + This is the Python translation of the C function ``PrintModPara()``. + The file layout depends on the active solver (``StdI.solver``). + Solver-specific content is dispatched via :data:`_MODPARA_BODY_DISPATCH`. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. A large number of solver-specific + fields are read; see the individual body-writer functions for details. + """ + with open("modpara.def", "w") as fp: + fp.write("--------------------\n") + fp.write("Model_Parameters 0\n") + fp.write("--------------------\n") + + writer = _MODPARA_BODY_DISPATCH.get(StdI.solver) + if writer is not None: + writer(fp, StdI) + + print(" modpara.def is written.") + + +class GreenFunctionIndices: + """Generator for one- and two-body Green-function index lists. + + Encapsulates the site/spin parameters and helper logic needed to + produce the index tuples written to ``greenone.def`` and + ``greentwo.def``. + + Parameters + ---------- + nsite : int + Total number of sites in the system. + NsiteUC : int + Number of sites in the unit cell. + locspinflag : list of int + Per-site local-spin flag array (length ``nsite``). + is_kondo : bool + Whether the model is Kondo (doubles the UC range). + is_mvmc : bool + Whether the solver is mVMC (uses alternative index order for + two-body off-diagonal spin entries). + + Attributes + ---------- + nsite : int + NsiteUC : int + locspinflag : list of int + is_kondo : bool + is_mvmc : bool + """ + + def __init__( + self, + nsite: int, + NsiteUC: int, + locspinflag: list[int], + is_kondo: bool, + is_mvmc: bool = False, + ) -> None: + self.nsite = nsite + self.NsiteUC = NsiteUC + self.locspinflag = locspinflag + self.is_kondo = is_kondo + self.is_mvmc = is_mvmc + + # ------------------------------------------------------------------ + # Low-level helpers + # ------------------------------------------------------------------ + + def spin_max(self, site: int) -> int: + """Return the maximum spin index for *site*. + + For itinerant sites (``locspinflag == 0``), returns 1. + For local-spin sites, returns the local-spin value. + + Parameters + ---------- + site : int + Site index. + + Returns + ------- + int + Maximum spin index (inclusive upper bound). + """ + flag = self.locspinflag[site] + return 1 if flag == 0 else flag + + def skip_local_spin_pair(self, site_a: int, site_b: int) -> bool: + """Return True if a pair of distinct local-spin sites should be skipped. + + Parameters + ---------- + site_a : int + First site index. + site_b : int + Second site index. + + Returns + ------- + bool + ``True`` if both sites are local-spin and distinct. + """ + return (site_a != site_b + and self.locspinflag[site_a] != 0 + and self.locspinflag[site_b] != 0) + + def kondo_site(self, isite: int) -> int: + """Map a Kondo unit-cell index to the physical site index. + + Parameters + ---------- + isite : int + Unit-cell site index (``0 .. 2*NsiteUC - 1``). + + Returns + ------- + int + Physical site index. + """ + if isite >= self.NsiteUC: + return isite - self.NsiteUC + self.nsite // 2 + return isite + + # ------------------------------------------------------------------ + # One-body index generators + # ------------------------------------------------------------------ + + def green1_corr(self) -> list[tuple[int, int, int, int]]: + """Generate one-body Green-function indices for correlation mode. + + Only same-spin pairs (``ispin == jspin``) are kept. For Kondo + models the unit-cell range is doubled to include localized sites. + + Returns + ------- + list of tuple[int, int, int, int] + Index tuples ``(isite, ispin, jsite, jspin)``. + """ + indices: list[tuple[int, int, int, int]] = [] + xkondo = 2 if self.is_kondo else 1 + + for isite in range(self.NsiteUC * xkondo): + isite2 = self.kondo_site(isite) + + for ispin in range(self.spin_max(isite2) + 1): + for jsite in range(self.nsite): + for jspin in range(self.spin_max(jsite) + 1): + if self.skip_local_spin_pair(isite2, jsite): + continue + if ispin == jspin: + indices.append((isite2, ispin, jsite, jspin)) + + return indices + + def green1_raw(self) -> list[tuple[int, int, int, int]]: + """Generate one-body Green-function indices for raw (full) mode. + + All site-spin combinations are emitted, subject to the constraint + that pairs of distinct local-spin sites are skipped. + + Returns + ------- + list of tuple[int, int, int, int] + Index tuples ``(isite, ispin, jsite, jspin)``. + """ + indices: list[tuple[int, int, int, int]] = [] + + for isite in range(self.nsite): + for ispin in range(self.spin_max(isite) + 1): + for jsite in range(self.nsite): + for jspin in range(self.spin_max(jsite) + 1): + if self.skip_local_spin_pair(isite, jsite): + continue + indices.append((isite, ispin, jsite, jspin)) + + return indices + + # ------------------------------------------------------------------ + # Two-body index generators + # ------------------------------------------------------------------ + + def green2_corr(self) -> list[tuple[int, int, int, int, int, int, int, int]]: + """Generate two-body Green-function indices for correlation mode. + + For each pair ``(site1, site3)`` only spin combinations satisfying + ``spin1 - spin2 + spin3 - spin4 == 0`` are kept. For mVMC, when + ``spin1 != spin2`` or ``spin3 != spin4`` a special index order is + used; otherwise the standard HPhi order is used. + + Returns + ------- + list of tuple[int, int, int, int, int, int, int, int] + Index tuples of 8 elements each. + """ + indices: list[tuple[int, int, int, int, int, int, int, int]] = [] + xkondo = 2 if self.is_kondo else 1 + + for site1 in range(self.NsiteUC * xkondo): + site1k = self.kondo_site(site1) + S1Max = self.spin_max(site1k) + + for spin1 in range(S1Max + 1): + for spin2 in range(S1Max + 1): + for site3 in range(self.nsite): + S3Max = self.spin_max(site3) + + for spin3 in range(S3Max + 1): + for spin4 in range(S3Max + 1): + if spin1 - spin2 + spin3 - spin4 == 0: + if self.is_mvmc and ( + spin1 != spin2 or spin3 != spin4 + ): + indices.append(( + site1k, spin1, + site3, spin4, + site3, spin3, + site1k, spin2, + )) + else: + indices.append(( + site1k, spin1, + site1k, spin2, + site3, spin3, + site3, spin4, + )) + + return indices + + def green2_raw(self) -> list[tuple[int, int, int, int, int, int, int, int]]: + """Generate two-body Green-function indices for raw (full) mode. + + All four-site combinations are emitted with local-spin pair + filtering on ``(site1, site2)`` and ``(site3, site4)``. + + Returns + ------- + list of tuple[int, int, int, int, int, int, int, int] + Index tuples of 8 elements each. + """ + indices: list[tuple[int, int, int, int, int, int, int, int]] = [] + + for site1 in range(self.nsite): + for spin1 in range(self.spin_max(site1) + 1): + for site2 in range(self.nsite): + if self.skip_local_spin_pair(site1, site2): + continue + + for spin2 in range(self.spin_max(site2) + 1): + for site3 in range(self.nsite): + for spin3 in range(self.spin_max(site3) + 1): + for site4 in range(self.nsite): + if self.skip_local_spin_pair(site3, site4): + continue + + for spin4 in range(self.spin_max(site4) + 1): + indices.append(( + site1, spin1, + site2, spin2, + site3, spin3, + site4, spin4, + )) + + return indices + + +def print_1_green(StdI: StdIntList) -> None: + """Write ``greenone.def`` listing one-body Green-function indices. + + This is the Python translation of the C function ``Print1Green()``. + Index generation is delegated to :class:`GreenFunctionIndices`. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. The following fields are read: + + - ``ioutputmode`` : int -- 0 (none), 1 (correlation), or 2 (raw). + - ``model`` : ModelType -- model type (Kondo triggers doubled UC). + - ``NsiteUC`` : int -- number of sites in the unit cell. + - ``nsite`` : int -- total number of sites. + - ``locspinflag`` : list of int -- per-site local-spin flag. + """ + if StdI.ioutputmode == 0: + return + + gf = GreenFunctionIndices( + StdI.nsite, StdI.NsiteUC, StdI.locspinflag, + is_kondo=(StdI.model == ModelType.KONDO), + ) + + if StdI.ioutputmode == 1: + greenindx = gf.green1_corr() + else: + greenindx = gf.green1_raw() + + ngreen = len(greenindx) + + with open("greenone.def", "w") as fp: + fp.write("===============================\n") + fp.write(f"NCisAjs {ngreen:10d}\n") + fp.write("===============================\n") + fp.write("======== Green functions ======\n") + fp.write("===============================\n") + for row in greenindx: + i0, s0, i1, s1 = row + fp.write(f"{i0:5d} {s0:5d} {i1:5d} {s1:5d}\n") + + print(" greenone.def is written.") + + +def print_2_green(StdI: StdIntList) -> None: + """Write ``greentwo.def`` listing two-body Green-function indices. + + This is the Python translation of the C function ``Print2Green()``. + Index generation is delegated to :class:`GreenFunctionIndices`. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. The following fields are read: + + - ``ioutputmode`` : int + - ``model`` : ModelType + - ``solver`` : SolverType + - ``NsiteUC`` : int + - ``nsite`` : int + - ``locspinflag`` : list of int + """ + if StdI.ioutputmode not in (1, 2): + return + + gf = GreenFunctionIndices( + StdI.nsite, StdI.NsiteUC, StdI.locspinflag, + is_kondo=(StdI.model == ModelType.KONDO), + is_mvmc=(StdI.solver == SolverType.mVMC), + ) + + if StdI.ioutputmode == 1: + greenindx = gf.green2_corr() + else: + greenindx = gf.green2_raw() + + ngreen = len(greenindx) + with open("greentwo.def", "w") as fp: + fp.write("=============================================\n") + fp.write(f"NCisAjsCktAltDC {ngreen:10d}\n") + fp.write("=============================================\n") + fp.write("======== Green functions for Sq AND Nq ======\n") + fp.write("=============================================\n") + for row in greenindx: + i0, s0, i1, s1, i2, s2, i3, s3 = row + fp.write( + f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " + f"{i2:5d} {s2:5d} {i3:5d} {s3:5d}\n" + ) + + print(" greentwo.def is written.") + + +def unsupported_system(model: str, lattice: str) -> None: + """Print an error message and abort for an unsupported model/lattice pair. + + This is the Python translation of the C function + ``UnsupportedSystem()``. + + Parameters + ---------- + model : str + The model name specified by the user. + lattice : str + The lattice name specified by the user. + + Raises + ------ + SystemExit + Always raised after printing the error message. + """ + print("\nSorry, specified combination, ") + print(f" MODEL : {model} ") + print(f" LATTICE : {lattice}, ") + print("is unsupported in the STANDARD MODE...") + print("Please use the EXPART MODE, or write a NEW FUNCTION and post us.") + exit_program(-1) + + +def check_output_mode(StdI: StdIntList) -> None: + """Verify and set the integer output-mode flag from the string keyword. + + This is the Python translation of the C function + ``CheckOutputMode()``. The mapping is: + + - ``"non"`` / ``"none"`` / ``"off"`` -> ``ioutputmode = 0`` + - ``"cor"`` / ``"corr"`` / ``"correlation"`` -> ``ioutputmode = 1`` + - ``"****"`` (default sentinel) -> ``ioutputmode = 1`` + - ``"raw"`` / ``"all"`` / ``"full"`` -> ``ioutputmode = 2`` + - anything else -> error and exit + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. ``StdI.outputmode`` is read and + ``StdI.ioutputmode`` is set **in place**. + + Raises + ------ + SystemExit + If ``StdI.outputmode`` does not match any recognised keyword. + """ + if StdI.outputmode == UNSET_STRING: + StdI.ioutputmode = 1 + print( + f" ioutputmode = {StdI.ioutputmode:<10d}" + " ###### DEFAULT VALUE IS USED ######" + ) + else: + mode = OUTPUT_MODE_TO_INT.get(StdI.outputmode) + if mode is None: + print(f"\n ERROR ! Unsupported OutPutMode : {StdI.outputmode}") + exit_program(-1) + StdI.ioutputmode = mode + print(f" ioutputmode = {StdI.ioutputmode:<10d}") + + +def _check_mod_para_hphi(StdI: StdIntList) -> None: + """Set HPhi-specific default model parameters. + + Handles Lanczos parameters, spectrum frequency grid, and large-value + cutoff. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure, modified in place. + """ + StdI.Lanczos_max = print_val_i("Lanczos_max", StdI.Lanczos_max, 2000) + StdI.initial_iv = print_val_i("initial_iv", StdI.initial_iv, -1) + # nvec is not given a default here (commented out in C) + StdI.exct = print_val_i("exct", StdI.exct, 1) + StdI.LanczosEps = print_val_i("LanczosEps", StdI.LanczosEps, 14) + StdI.LanczosTarget = print_val_i("LanczosTarget", StdI.LanczosTarget, 2) + if StdI.LanczosTarget < StdI.exct: + StdI.LanczosTarget = StdI.exct + StdI.NumAve = print_val_i("NumAve", StdI.NumAve, 5) + StdI.ExpecInterval = print_val_i("ExpecInterval", StdI.ExpecInterval, 20) + StdI.Nomega = print_val_i("NOmega", StdI.Nomega, 200) + StdI.OmegaMax = print_val_d( + "OmegaMax", StdI.OmegaMax, StdI.LargeValue * StdI.nsite + ) + StdI.OmegaMin = print_val_d( + "OmegaMin", StdI.OmegaMin, -StdI.LargeValue * StdI.nsite + ) + StdI.OmegaOrg = print_val_d("OmegaOrg", StdI.OmegaOrg, 0.0) + StdI.OmegaIm = print_val_d( + "OmegaIm", StdI.OmegaIm, 0.01 * int(StdI.LargeValue) + ) + + +def _check_mod_para_mvmc(StdI: StdIntList) -> None: + """Set mVMC-specific default model parameters. + + Handles VMC sampling parameters, exchange-update path count, + and SR-optimisation settings. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure, modified in place. + """ + if StdI.CParaFileHead == UNSET_STRING: + StdI.CParaFileHead = "zqp" + print( + f" CParaFileHead = {StdI.CParaFileHead:<12s}" + "###### DEFAULT VALUE IS USED ######" + ) + else: + print(f" CParaFileHead = {StdI.CParaFileHead}") + + StdI.NVMCCalMode = print_val_i("NVMCCalMode", StdI.NVMCCalMode, 0) + StdI.NLanczosMode = print_val_i("NLanczosMode", StdI.NLanczosMode, 0) + StdI.NDataIdxStart = print_val_i("NDataIdxStart", StdI.NDataIdxStart, 1) + + if StdI.NVMCCalMode == 0: + not_used_i("NDataQtySmp", StdI.NDataQtySmp) + StdI.NDataQtySmp = print_val_i("NDataQtySmp", StdI.NDataQtySmp, 1) + + if StdI.lGC == 0 and (StdI.Sz2 == 0 or StdI.Sz2 == NaN_i): + StdI.NSPGaussLeg = print_val_i("NSPGaussLeg", StdI.NSPGaussLeg, 8) + StdI.NSPStot = print_val_i("NSPStot", StdI.NSPStot, 0) + else: + not_used_i("NSPGaussLeg", StdI.NSPGaussLeg) + not_used_i("NSPStot", StdI.NSPStot) + + StdI.NMPTrans = print_val_i("NMPTrans", StdI.NMPTrans, -1) + + StdI.NSROptItrStep = print_val_i("NSROptItrStep", StdI.NSROptItrStep, 1000) + + if StdI.NVMCCalMode == 1: + not_used_i("NSROptItrSmp", StdI.NSROptItrSmp) + StdI.NSROptItrSmp = print_val_i( + "NSROptItrSmp", StdI.NSROptItrSmp, StdI.NSROptItrStep // 10 + ) + + StdI.NVMCWarmUp = print_val_i("NVMCWarmUp", StdI.NVMCWarmUp, 10) + StdI.NVMCInterval = print_val_i("NVMCInterval", StdI.NVMCInterval, 1) + StdI.NVMCSample = print_val_i("NVMCSample", StdI.NVMCSample, 1000) + + key = (StdI.model, StdI.lGC) + ex_path = MODEL_GC_TO_EX_UPDATE_PATH.get(key) + if ex_path is not None: + StdI.NExUpdatePath = ex_path + print(f" {'NExUpdatePath':>15s} = {StdI.NExUpdatePath:<10d}") + + StdI.RndSeed = print_val_i("RndSeed", StdI.RndSeed, 123456789) + StdI.NSplitSize = print_val_i("NSplitSize", StdI.NSplitSize, 1) + StdI.NStore = print_val_i("NStore", StdI.NStore, 1) + StdI.NSRCG = print_val_i("NSRCG", StdI.NSRCG, 0) + + StdI.DSROptRedCut = print_val_d("DSROptRedCut", StdI.DSROptRedCut, 0.001) + StdI.DSROptStaDel = print_val_d("DSROptStaDel", StdI.DSROptStaDel, 0.02) + StdI.DSROptStepDt = print_val_d("DSROptStepDt", StdI.DSROptStepDt, 0.02) + + +def _check_mod_para_uhf(StdI: StdIntList) -> None: + """Set UHF/H-wave-specific default model parameters. + + Handles random seed, iteration limit, mixing, convergence, and + symmetry-projection parameters. UHF and H-wave share identical + defaults. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure, modified in place. + """ + StdI.RndSeed = print_val_i("RndSeed", StdI.RndSeed, 123456789) + StdI.Iteration_max = print_val_i("Iteration_max", StdI.Iteration_max, 1000) + StdI.mix = print_val_d("Mix", StdI.mix, 0.5) + StdI.eps = print_val_i("eps", StdI.eps, 8) + StdI.eps_slater = print_val_i("EpsSlater", StdI.eps_slater, 6) + StdI.NMPTrans = print_val_i("NMPTrans", StdI.NMPTrans, 0) + + +_SOLVER_DEFAULTS_DISPATCH: dict[str, Callable] = { + SolverType.HPhi: _check_mod_para_hphi, + SolverType.mVMC: _check_mod_para_mvmc, + SolverType.UHF: _check_mod_para_uhf, + SolverType.HWAVE: _check_mod_para_uhf, +} +"""Maps ``SolverType`` to the corresponding solver-specific defaults function. + +UHF and H-wave share the same defaults handler +(:func:`_check_mod_para_uhf`). +""" + + +# ------------------------------------------------------------------- +# Conserved-quantity validation rules +# ------------------------------------------------------------------- +# +class _ConservedQtyRule(NamedTuple): + """Validation rule for conserved quantities (ncond and 2Sz). + + Attributes + ---------- + ncond_label : str + Label used for the ncond check (``"nelec"`` or ``"ncond"``). + ncond_action : str or None + Action for ncond: ``"required"``, ``"not_used"``, or ``None``. + sz2_action : str or None + Action for 2Sz: ``"required"``, ``"not_used"``, ``"default_0"``, + or ``None``. + """ + + ncond_label: str + ncond_action: str | None + sz2_action: str | None + + +# The key is ``(ModelType, is_hphi: bool, lGC: int)``. +# For Spin model, is_hphi is ignored (keyed as True and False with same rule). + +_CONSERVED_QTY_RULES: dict[tuple, _ConservedQtyRule] = { + # Hubbard + (ModelType.HUBBARD, True, 0): _ConservedQtyRule("nelec", "required", None), + (ModelType.HUBBARD, True, 1): _ConservedQtyRule("nelec", "not_used", "not_used"), + (ModelType.HUBBARD, False, 0): _ConservedQtyRule("ncond", "required", "default_0"), + (ModelType.HUBBARD, False, 1): _ConservedQtyRule("ncond", "required", "not_used"), + # Spin (is_hphi dimension doesn't matter — same rules) + (ModelType.SPIN, True, 0): _ConservedQtyRule("ncond", "not_used", "required"), + (ModelType.SPIN, True, 1): _ConservedQtyRule("ncond", "not_used", "not_used"), + (ModelType.SPIN, False, 0): _ConservedQtyRule("ncond", "not_used", "required"), + (ModelType.SPIN, False, 1): _ConservedQtyRule("ncond", "not_used", "not_used"), + # Kondo + (ModelType.KONDO, True, 0): _ConservedQtyRule("ncond", "required", None), + (ModelType.KONDO, True, 1): _ConservedQtyRule("nelec", "not_used", "not_used"), + (ModelType.KONDO, False, 0): _ConservedQtyRule("ncond", "required", "default_0"), + (ModelType.KONDO, False, 1): _ConservedQtyRule("ncond", "required", "not_used"), +} +"""Rules for validating ``ncond`` and ``2Sz`` by (model, is_hphi, lGC).""" + +# Dispatch tables for ncond and Sz2 validation actions +_NCOND_ACTION_DISPATCH: dict[str, Callable] = { + "required": required_val_i, + "not_used": not_used_i, +} +"""Maps ncond action strings to validation functions (label, value) -> None.""" + +_SZ2_ACTION_DISPATCH: dict[str, Callable] = { + "required": lambda StdI: required_val_i("2Sz", StdI.Sz2), + "not_used": lambda StdI: not_used_i("2Sz", StdI.Sz2), + "default_0": lambda StdI: setattr(StdI, 'Sz2', print_val_i("2Sz", StdI.Sz2, 0)), +} +"""Maps Sz2 action strings to validation functions (StdI) -> None.""" + + +def _check_conserved_quantities(StdI: StdIntList) -> None: + """Validate conserved quantities (``ncond`` and ``2Sz``) for the current model. + + The checks depend on ``StdI.model``, ``StdI.solver``, and ``StdI.lGC`` + (grand-canonical flag): + + - **Hubbard** (HPhi, canonical): ``nelec`` required. + - **Hubbard** (HPhi, GC): ``nelec`` and ``2Sz`` unused. + - **Hubbard** (other solvers): ``ncond`` required; ``2Sz`` defaults to 0 + in canonical, unused in GC. + - **Spin**: ``ncond`` unused (set to 0 for mVMC); ``2Sz`` required in + canonical, unused in GC. + - **Kondo** (HPhi, canonical): ``ncond`` required. + - **Kondo** (HPhi, GC): ``nelec`` and ``2Sz`` unused. + - **Kondo** (other solvers): ``ncond`` required; ``2Sz`` defaults to 0 + in canonical, unused in GC. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure, modified in place. + """ + is_hphi = (StdI.solver == SolverType.HPhi) + key = (StdI.model, is_hphi, StdI.lGC) + rule = _CONSERVED_QTY_RULES.get(key) + if rule is None: + return + ncond_label, ncond_action, sz2_action = rule + + # Apply ncond validation via dispatch + if ncond_action is not None: + _NCOND_ACTION_DISPATCH[ncond_action](ncond_label, StdI.ncond) + + # Spin + mVMC: set ncond = 0 (after not_used check, matching C order) + if StdI.model == ModelType.SPIN and StdI.solver == SolverType.mVMC: + StdI.ncond = 0 + + # Apply Sz2 validation via dispatch + if sz2_action is not None: + _SZ2_ACTION_DISPATCH[sz2_action](StdI) + + +def check_mod_para(StdI: StdIntList) -> None: + """Validate and set default values for solver-specific model parameters. + + This is the Python translation of the C function ``CheckModPara()``. + Depending on ``StdI.solver``, different parameter groups are + validated via :data:`_SOLVER_DEFAULTS_DISPATCH`. Then the conserved + quantities (``ncond``, ``2Sz``) are checked via + :func:`_check_conserved_quantities`. + + Parameters + ---------- + StdI : StdIntList + The global parameter structure. Many fields are read and + modified **in place** via the helper functions + :func:`print_val_i`, :func:`print_val_d`, :func:`not_used_i`, + and :func:`required_val_i`. + """ + # ------------------------------------------------------------------ + # Solver-specific defaults (dispatched) + # ------------------------------------------------------------------ + handler = _SOLVER_DEFAULTS_DISPATCH.get(StdI.solver) + if handler is not None: + handler(StdI) + + # ------------------------------------------------------------------ + # Conserved quantities: ncond and 2Sz + # ------------------------------------------------------------------ + _check_conserved_quantities(StdI) diff --git a/python/stdface/writer/interaction_writer.py b/python/stdface/writer/interaction_writer.py new file mode 100644 index 0000000..39d9812 --- /dev/null +++ b/python/stdface/writer/interaction_writer.py @@ -0,0 +1,534 @@ +"""Interaction-term merge and file-output functions. + +This module contains the :func:`print_interactions` function that was +extracted from ``common_writer.py``. It processes all interaction types +(CoulombIntra, CoulombInter, Hund, Exchange, PairLift, PairHopp, InterAll), +merging duplicate terms, counting non-zero terms, and writing the +corresponding ``.def`` files. + +Functions +--------- +print_interactions + Merge duplicate interaction terms and write ``.def`` files. + +License +------- +HPhi-mVMC-StdFace - Common input generator +Copyright (C) 2015 The University of Tokyo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +""" + +from __future__ import annotations + +from typing import NamedTuple + +from ..core.stdface_vals import StdIntList, AMPLITUDE_EPS + + +# --------------------------------------------------------------------------- +# Helpers for the repeated merge → count → write pattern +# --------------------------------------------------------------------------- + +def _merge_1idx(nterms: int, indx: list, coeff: list) -> None: + """Merge duplicate terms for a 1-index interaction (CoulombIntra). + + Two terms are duplicates if they share the same single site index. + The coefficient of the first occurrence is updated and the duplicate + is zeroed. Uses dict-based O(n) lookup instead of O(n²) pairwise scan. + + Parameters + ---------- + nterms : int + Number of interaction terms. + indx : list + Index array; each element is a list whose first element is the + site index. + coeff : list + Coefficient array, modified in place. + """ + seen: dict[int, int] = {} # site index -> first occurrence position + for k in range(nterms): + key = indx[k][0] + if key in seen: + coeff[seen[key]] += coeff[k] + coeff[k] = 0.0 + else: + seen[key] = k + + +def _merge_2idx(nterms: int, indx: list, coeff: list) -> None: + """Merge duplicate terms for a 2-index interaction. + + Two terms are duplicates if they share the same pair of site indices + (in either order, i.e. symmetric). Uses dict-based O(n) lookup + instead of O(n²) pairwise scan. + + Parameters + ---------- + nterms : int + Number of interaction terms. + indx : list + Index array; each element is a list of two site indices. + coeff : list + Coefficient array, modified in place. + """ + seen: dict[tuple[int, int], int] = {} # canonical pair -> first occurrence + for k in range(nterms): + i0, i1 = indx[k][0], indx[k][1] + key = (min(i0, i1), max(i0, i1)) + if key in seen: + coeff[seen[key]] += coeff[k] + coeff[k] = 0.0 + else: + seen[key] = k + + +def _count_nonzero(nterms: int, coeff: list) -> int: + """Count the number of terms with ``|coeff| > AMPLITUDE_EPS``. + + Parameters + ---------- + nterms : int + Total number of terms. + coeff : list + Coefficient array. + + Returns + ------- + int + Number of non-zero terms. + """ + return sum(1 for k in range(nterms) if abs(coeff[k]) > AMPLITUDE_EPS) + + +def _write_interaction_file( + filename: str, + count_label: str, + banner: str, + nterms: int, + indx: list, + coeff: list, + n_indices: int, +) -> None: + """Write an interaction ``.def`` file with 1 or 2 site indices per term. + + Parameters + ---------- + filename : str + Output file name (e.g. ``"coulombintra.def"``). + count_label : str + Label for the count header line (e.g. ``"NCoulombIntra"``). + banner : str + Description banner line. + nterms : int + Total number of terms. + indx : list + Index array (``n_indices`` site indices per term). + coeff : list + Coefficient array. + n_indices : int + Number of site indices per term (1 or 2). + """ + nintr0 = _count_nonzero(nterms, coeff) + with open(filename, "w") as fp: + fp.write("=============================================\n") + fp.write(f"{count_label} {nintr0:10d}\n") + fp.write("=============================================\n") + fp.write(f"{banner}\n") + fp.write("=============================================\n") + for k in range(nterms): + if abs(coeff[k]) > AMPLITUDE_EPS: + idx_str = " ".join(f"{indx[k][i]:5d}" for i in range(n_indices)) + fp.write(f"{idx_str} {coeff[k]:25.15f}\n") + print(f" {filename} is written.") + + +def _process_interaction( + StdI: StdIntList, + nterms_attr: str, + indx_attr: str, + coeff_attr: str, + flag_attr: str, + filename: str, + count_label: str, + banner: str, + n_indices: int, +) -> None: + """Merge, count, and write one interaction type. + + This is the generic driver that replaces the 6 repetitive blocks in + ``print_interactions()``. + + Parameters + ---------- + StdI : StdIntList + The central data structure. + nterms_attr : str + Name of the attribute holding the term count (e.g. ``"NCintra"``). + indx_attr : str + Name of the index-array attribute (e.g. ``"CintraIndx"``). + coeff_attr : str + Name of the coefficient-array attribute (e.g. ``"Cintra"``). + flag_attr : str + Name of the output-flag attribute (e.g. ``"LCintra"``). + filename : str + Output ``.def`` file name. + count_label : str + Label for the count line in the header. + banner : str + Description banner in the header. + n_indices : int + Number of site indices per term (1 or 2). + """ + nterms = getattr(StdI, nterms_attr) + indx = getattr(StdI, indx_attr) + coeff = getattr(StdI, coeff_attr) + + # Merge duplicates + if n_indices == 1: + _merge_1idx(nterms, indx, coeff) + else: + _merge_2idx(nterms, indx, coeff) + + # Count non-zero terms + nintr0 = _count_nonzero(nterms, coeff) + + # Set output flag + if nintr0 == 0 or StdI.lBoost == 1: + setattr(StdI, flag_attr, 0) + else: + setattr(StdI, flag_attr, 1) + + # Write file if needed + if getattr(StdI, flag_attr) == 1: + _write_interaction_file( + filename, count_label, banner, nterms, indx, coeff, n_indices) + + +# --------------------------------------------------------------------------- +# Interaction-type metadata table +# --------------------------------------------------------------------------- + + +class _InteractionMeta(NamedTuple): + """Metadata for one interaction type (attribute names, file info). + + Attributes + ---------- + nterms_attr : str + Name of the StdIntList attribute holding the term count. + indx_attr : str + Name of the index-array attribute. + coeff_attr : str + Name of the coefficient-array attribute. + flag_attr : str + Name of the output-flag attribute. + filename : str + Output ``.def`` file name. + count_label : str + Label for the count header line. + banner : str + Description banner in the header. + n_indices : int + Number of site indices per term (1 or 2). + """ + + nterms_attr: str + indx_attr: str + coeff_attr: str + flag_attr: str + filename: str + count_label: str + banner: str + n_indices: int + + +_INTERACTION_TYPES: list[_InteractionMeta] = [ + _InteractionMeta("NCintra", "CintraIndx", "Cintra", "LCintra", + "coulombintra.def", "NCoulombIntra", + "================== CoulombIntra ================", 1), + _InteractionMeta("NCinter", "CinterIndx", "Cinter", "LCinter", + "coulombinter.def", "NCoulombInter", + "================== CoulombInter ================", 2), + _InteractionMeta("NHund", "HundIndx", "Hund", "LHund", + "hund.def", "NHund", + "=============== Hund coupling ===============", 2), + _InteractionMeta("NEx", "ExIndx", "Ex", "LEx", + "exchange.def", "NExchange", + "====== ExchangeCoupling coupling ============", 2), + _InteractionMeta("NPairLift", "PLIndx", "PairLift", "LPairLift", + "pairlift.def", "NPairLift", + "====== Pair-Lift term ============", 2), + _InteractionMeta("NPairHopp", "PHIndx", "PairHopp", "LPairHopp", + "pairhopp.def", "NPairHopp", + "====== Pair-Hopping term ============", 2), +] +"""Metadata for the 6 standard interaction types processed by +:func:`print_interactions`. Each entry maps attribute names, file +names, and header strings so that :func:`_process_interaction` can +handle all types generically. +""" + + +# --------------------------------------------------------------------------- +# InterAll helpers +# --------------------------------------------------------------------------- + +def _merge_interall_equivalent( + nintr: int, intrindx: list, intr: list +) -> None: + """Merge equivalent InterAll terms (Pass 1). + + For each pair of terms, check three patterns: + + - **Case A**: All 8 indices match exactly → **add** coefficients. + - **Case B**: Hermitian conjugate match (indices 0–3 ↔ 4–7) with + two exclusion conditions → **add** coefficients. + - **Case C**: Partial swap patterns → **subtract** coefficients. + + In all cases the duplicate term is zeroed. + + Parameters + ---------- + nintr : int + Number of InterAll terms. + intrindx : list + Index array; each element is a list of 8 integers. + intr : list + Coefficient array (complex), modified in place. + """ + for jintr in range(nintr): + j = intrindx[jintr] + for kintr in range(jintr + 1, nintr): + k = intrindx[kintr] + + # Case A: all 8 indices match exactly + # Case B: Hermitian conjugate match (indices 0-3 <-> 4-7) + if ( + (j[0] == k[0] and j[1] == k[1] + and j[2] == k[2] and j[3] == k[3] + and j[4] == k[4] and j[5] == k[5] + and j[6] == k[6] and j[7] == k[7]) + or + (j[0] == k[4] and j[1] == k[5] + and j[2] == k[6] and j[3] == k[7] + and j[4] == k[0] and j[5] == k[1] + and j[6] == k[2] and j[7] == k[3] + and not (j[0] == j[6] and j[1] == j[7]) + and not (j[2] == j[4] and j[3] == j[5])) + ): + intr[jintr] = intr[jintr] + intr[kintr] + intr[kintr] = 0.0 + + # Case C: Partial swap patterns -> SUBTRACT + elif ( + (j[0] == k[4] and j[1] == k[5] + and j[2] == k[2] and j[3] == k[3] + and j[4] == k[0] and j[5] == k[1] + and j[6] == k[6] and j[7] == k[7] + and not (j[2] == j[0] and j[3] == j[1]) + and not (j[2] == j[4] and j[3] == j[5])) + or + (j[0] == k[0] and j[1] == k[1] + and j[2] == k[6] and j[3] == k[7] + and j[4] == k[4] and j[5] == k[5] + and j[6] == k[2] and j[7] == k[3] + and not (j[4] == j[2] and j[5] == j[3]) + and not (j[4] == j[6] and j[5] == j[7])) + ): + intr[jintr] = intr[jintr] - intr[kintr] + intr[kintr] = 0.0 + + +def _reorder_interall_hermitian( + nintr: int, intrindx: list, intr: list +) -> None: + """Force Hermitian ordering on InterAll terms (Pass 2). + + For the two-body operator ``(c1† c2 c3† c4)† = c4† c3 c2† c1``, + certain index-pair patterns require reordering the 8 indices of + *kintr* to match the Hermitian ordering of *jintr*. + + Two reorder patterns are detected: + + - **Pattern 1**: Direct Hermitian conjugate → reorder indices. + - **Pattern 2**: Two sub-cases with sign flip → reorder indices + and negate coefficient. + + Parameters + ---------- + nintr : int + Number of InterAll terms. + intrindx : list + Index array (8 integers per term), modified in place. + intr : list + Coefficient array (complex), modified in place. + """ + for jintr in range(nintr): + j = intrindx[jintr] + for kintr in range(jintr + 1, nintr): + k = intrindx[kintr] + + # Pattern 1: direct Hermitian conjugate match + if (j[6] == k[4] and j[7] == k[5] + and j[4] == k[6] and j[5] == k[7] + and j[2] == k[0] and j[3] == k[1] + and j[0] == k[2] and j[1] == k[3] + and not (k[0] == k[6] and k[1] == k[7]) + and not (k[2] == k[4] and k[3] == k[5])): + intrindx[kintr][0] = j[6] + intrindx[kintr][1] = j[7] + intrindx[kintr][2] = j[4] + intrindx[kintr][3] = j[5] + intrindx[kintr][4] = j[2] + intrindx[kintr][5] = j[3] + intrindx[kintr][6] = j[0] + intrindx[kintr][7] = j[1] + + # Pattern 2: two sub-cases, with sign flip + elif ( + (j[6] == k[4] and j[7] == k[5] + and j[4] == k[2] and j[5] == k[3] + and j[2] == k[0] and j[3] == k[1] + and j[0] == k[6] and j[1] == k[7] + and not (k[2] == k[0] and k[3] == k[1]) + and not (k[2] == k[4] and k[3] == k[5])) + or + (j[6] == k[0] and j[7] == k[1] + and j[4] == k[6] and j[5] == k[7] + and j[2] == k[4] and j[3] == k[5] + and j[0] == k[2] and j[1] == k[3] + and not (k[4] == k[2] and k[5] == k[3]) + and not (k[4] == k[6] and k[5] == k[7])) + ): + intrindx[kintr][0] = j[6] + intrindx[kintr][1] = j[7] + intrindx[kintr][2] = j[4] + intrindx[kintr][3] = j[5] + intrindx[kintr][4] = j[2] + intrindx[kintr][5] = j[3] + intrindx[kintr][6] = j[0] + intrindx[kintr][7] = j[1] + + intr[kintr] = -intr[kintr] + + +def _remove_interall_diagonal( + nintr: int, intrindx: list, intr: list +) -> None: + """Remove spurious diagonal InterAll terms (Pass 3). + + A term is zeroed if it has a diagonal pair — i.e. + ``(site0, spin0) == (site4, spin4)`` or + ``(site2, spin2) == (site6, spin6)`` — **unless** any of the four + possible diagonal-matching conditions holds. + + Parameters + ---------- + nintr : int + Number of InterAll terms. + intrindx : list + Index array (8 integers per term). + intr : list + Coefficient array, modified in place. + """ + for jintr in range(nintr): + idx = intrindx[jintr] + + has_diagonal_pair = ( + (idx[0] == idx[4] and idx[1] == idx[5]) + or (idx[2] == idx[6] and idx[3] == idx[7]) + ) + + if has_diagonal_pair: + any_matching_diagonal = ( + (idx[0] == idx[2] and idx[1] == idx[3]) + or (idx[0] == idx[6] and idx[1] == idx[7]) + or (idx[4] == idx[2] and idx[5] == idx[3]) + or (idx[4] == idx[6] and idx[5] == idx[7]) + ) + if not any_matching_diagonal: + intr[jintr] = 0.0 + + +def _write_interall(StdI: StdIntList) -> None: + """Count non-zero InterAll terms, set the flag, and write the file. + + Parameters + ---------- + StdI : StdIntList + The central data structure. ``nintr``, ``intrindx``, ``intr``, + ``lBoost``, and ``Lintr`` are accessed / modified. + """ + nintr0 = _count_nonzero(StdI.nintr, StdI.intr) + + if nintr0 == 0 or StdI.lBoost == 1: + StdI.Lintr = 0 + else: + StdI.Lintr = 1 + + if StdI.Lintr == 1: + with open("interall.def", "w") as fp: + fp.write("====================== \n") + fp.write(f"NInterAll {nintr0:7d} \n") + fp.write("====================== \n") + fp.write("========zInterAll===== \n") + fp.write("====================== \n") + + if StdI.lBoost == 0: + for kintr in range(StdI.nintr): + val = StdI.intr[kintr] + if abs(val) > AMPLITUDE_EPS: + i0, s0, i1, s1, i2, s2, i3, s3 = StdI.intrindx[kintr] + fp.write( + f"{i0:5d} {s0:5d} " + f"{i1:5d} {s1:5d} " + f"{i2:5d} {s2:5d} " + f"{i3:5d} {s3:5d} " + f"{val.real:25.15f} {val.imag:25.15f}\n" + ) + + print(" interall.def is written.") + + +def print_interactions(StdI: StdIntList) -> None: + """Process and write definition files for all interaction types. + + For each interaction type (CoulombIntra, CoulombInter, Hund, Exchange, + PairLift, PairHopp, InterAll), this function: + + 1. Merges duplicate terms by summing their coefficients and zeroing + out the duplicate entry. + 2. Counts the number of non-zero terms. + 3. Sets a flag (e.g. ``LCintra``, ``LCinter``, ...) based on the count + and whether boost mode is active. + 4. If the flag is set, writes the corresponding ``.def`` file. + + The first six interaction types are handled generically via + :data:`_INTERACTION_TYPES` and :func:`_process_interaction`. + The InterAll section is more complex, involving three merge/reorder + passes before file output, and is handled inline. + + Parameters + ---------- + StdI : StdIntList + The central data structure holding all interaction arrays, index + arrays, counts, and flags. Modified in place. + """ + # ================================================================= + # Standard interaction types (data-driven) + # ================================================================= + for spec in _INTERACTION_TYPES: + _process_interaction(StdI, **spec._asdict()) + + # ================================================================= + # InterAll + # ================================================================= + _merge_interall_equivalent(StdI.nintr, StdI.intrindx, StdI.intr) + _reorder_interall_hermitian(StdI.nintr, StdI.intrindx, StdI.intr) + _remove_interall_diagonal(StdI.nintr, StdI.intrindx, StdI.intr) + _write_interall(StdI) From 2d1676f69e5e5ea0e96f47f9051b9414d1f572e2 Mon Sep 17 00:00:00 2001 From: Kazuyoshi Yoshimi Date: Thu, 29 Jan 2026 20:34:42 +0900 Subject: [PATCH 03/16] Optimize performance hotspots without changing output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - site_util: hash-based cell index lookup (O(NCell) -> O(1) per call) - interaction_builder: precompute spin ladder factors in general_j() - common_writer: buffer greenone/greentwo file I/O with join() - mvmc/writer: numpy-ize _compute_parallel_orbitals (O(N⁴) -> O(N²)) - mvmc/variational: vectorize Jastrow symmetrise with np.where Co-Authored-By: Claude Opus 4.5 --- python/stdface/lattice/interaction_builder.py | 18 ++++-- python/stdface/lattice/site_util.py | 39 +++++++++++-- python/stdface/solvers/mvmc/variational.py | 19 +++---- python/stdface/solvers/mvmc/writer.py | 55 ++++++++++--------- python/stdface/writer/common_writer.py | 36 ++++++------ 5 files changed, 104 insertions(+), 63 deletions(-) diff --git a/python/stdface/lattice/interaction_builder.py b/python/stdface/lattice/interaction_builder.py index 603ce31..ce28be6 100644 --- a/python/stdface/lattice/interaction_builder.py +++ b/python/stdface/lattice/interaction_builder.py @@ -375,6 +375,16 @@ def general_j( Si = 0.5 * Si2 Sj = 0.5 * Sj2 + # Precompute spin ladder factors for all needed (S, Sz) pairs + ladder_i: dict[int, float] = {} + for ispin in range(1, Si2 + 1): + Siz = Si - float(ispin) + ladder_i[ispin] = _spin_ladder_factor(Si, Siz) + ladder_j: dict[int, float] = {} + for jspin in range(1, Sj2 + 1): + Sjz = Sj - float(jspin) + ladder_j[jspin] = _spin_ladder_factor(Sj, Sjz) + for ispin in range(Si2 + 1): Siz = Si - float(ispin) for jspin in range(Sj2 + 1): @@ -388,8 +398,8 @@ def general_j( jsite, jspin, jsite, jspin) if ispin > 0 and jspin > 0 and use_ex: - fi = _spin_ladder_factor(Si, Siz) - fj = _spin_ladder_factor(Sj, Sjz) + fi = ladder_i[ispin] + fj = ladder_j[jspin] # (2) S_i^+ S_j^- + h.c. intr0 = 0.25 * (J[0, 0] + J[1, 1] + 1j * (J[0, 1] - J[1, 0])) * fi * fj @@ -411,7 +421,7 @@ def general_j( # (4) S_i^+ S_{jz} + h.c. if ispin > 0: - fi = _spin_ladder_factor(Si, Siz) + fi = ladder_i[ispin] intr0 = 0.5 * (J[0, 2] - 1j * J[1, 2]) * fi * Sjz intr(StdI, intr0, isite, ispin - 1, isite, ispin, @@ -422,7 +432,7 @@ def general_j( # (5) S_{iz} S_j^+ + h.c. if jspin > 0: - fj = _spin_ladder_factor(Sj, Sjz) + fj = ladder_j[jspin] intr0 = 0.5 * (J[2, 0] - 1j * J[2, 1]) * Siz * fj intr(StdI, intr0, isite, ispin, isite, ispin, diff --git a/python/stdface/lattice/site_util.py b/python/stdface/lattice/site_util.py index 891459c..73a0890 100644 --- a/python/stdface/lattice/site_util.py +++ b/python/stdface/lattice/site_util.py @@ -67,11 +67,39 @@ def _cell_vector(Cell: np.ndarray, idx: int) -> list[int]: return [int(Cell[idx, 0]), int(Cell[idx, 1]), int(Cell[idx, 2])] +def _build_cell_map(StdI: StdIntList) -> dict[tuple[int, int, int], int]: + """Build or return a cached mapping from cell coordinates to index. + + The map is stored on ``StdI._cell_map`` and reused on subsequent + calls. It is invalidated whenever ``_enumerate_cells`` is called. + + Parameters + ---------- + StdI : StdIntList + Model parameter structure containing the ``Cell`` array and + ``NCell`` count. + + Returns + ------- + dict[tuple[int, int, int], int] + Mapping from ``(Cell[k,0], Cell[k,1], Cell[k,2])`` to ``k``. + """ + cell_map = getattr(StdI, '_cell_map', None) + if cell_map is not None: + return cell_map + cell_map = {} + for k in range(StdI.NCell): + key = (int(StdI.Cell[k, 0]), int(StdI.Cell[k, 1]), int(StdI.Cell[k, 2])) + if key not in cell_map: + cell_map[key] = k + StdI._cell_map = cell_map + return cell_map + + def _find_cell_index(StdI: StdIntList, cellV: list[int]) -> int: """Find the cell index whose coordinates match *cellV*. - Performs a linear search over ``StdI.Cell`` to find the row whose - three coordinates match *cellV*. + Uses a hash map for O(1) lookup instead of linear scan. Parameters ---------- @@ -87,10 +115,8 @@ def _find_cell_index(StdI: StdIntList, cellV: list[int]) -> int: Cell index ``k`` such that ``StdI.Cell[k]`` equals *cellV*. Returns 0 if no match is found (matching the original C behavior). """ - for k in range(StdI.NCell): - if _cell_vector(StdI.Cell, k) == cellV: - return k - return 0 + cell_map = _build_cell_map(StdI) + return cell_map.get((cellV[0], cellV[1], cellV[2]), 0) def _fold_to_cell( @@ -417,6 +443,7 @@ def _enumerate_cells(StdI: StdIntList) -> None: # Enumerate cells within the bounding box # Note: iteration order must be ic2 outermost, ic0 innermost (matching C code) StdI.Cell = np.zeros((StdI.NCell, 3), dtype=int) + StdI._cell_map = None # invalidate cell map cache jj_idx = 0 for ic2, ic1, ic0 in itertools.product( range(bound[2][0], bound[2][1] + 1), diff --git a/python/stdface/solvers/mvmc/variational.py b/python/stdface/solvers/mvmc/variational.py index 3bcf6a7..db49d7f 100644 --- a/python/stdface/solvers/mvmc/variational.py +++ b/python/stdface/solvers/mvmc/variational.py @@ -347,17 +347,15 @@ def _jastrow_momentum_projected( Jastrow : numpy.ndarray Final renumbered Jastrow index matrix. """ - # (1) Copy Orbital index - for isite in range(StdI.nsite): - for jsite in range(StdI.nsite): - Jastrow[isite, jsite] = StdI.Orb[isite, jsite] + # (1) Copy Orbital index (vectorised) + Jastrow[:, :] = StdI.Orb[:StdI.nsite, :StdI.nsite] - # (2) Symmetrize + # (2) Symmetrize — for each orbital value in ascending order, + # mirror (i,j) -> (j,i). Sequential to preserve order semantics. for iorb in range(StdI.NOrb): - for isite in range(StdI.nsite): - for jsite in range(StdI.nsite): - if Jastrow[isite, jsite] == iorb: - Jastrow[jsite, isite] = Jastrow[isite, jsite] + rows, cols = np.where(Jastrow == iorb) + for r, c in zip(rows, cols): + Jastrow[c, r] = iorb # (3) Exclude local-spin sites and renumber NJastrow = 0 if StdI.model == ModelType.HUBBARD else -1 @@ -371,8 +369,7 @@ def _jastrow_momentum_projected( if Jastrow[isite, jsite] >= 0: iJastrow = Jastrow[isite, jsite] NJastrow -= 1 - mask = (Jastrow == iJastrow) - Jastrow[mask] = NJastrow + Jastrow[Jastrow == iJastrow] = NJastrow NJastrow = -NJastrow Jastrow = -1 - Jastrow diff --git a/python/stdface/solvers/mvmc/writer.py b/python/stdface/solvers/mvmc/writer.py index b587627..389dafa 100644 --- a/python/stdface/solvers/mvmc/writer.py +++ b/python/stdface/solvers/mvmc/writer.py @@ -133,40 +133,43 @@ def _compute_parallel_orbitals( NOrbGC : int Number of unique parallel orbital indices. """ - # (1) Copy - OrbGC = [[0] * nsite for _ in range(nsite)] - reverse = [[0] * nsite for _ in range(nsite)] - for isite in range(nsite): - for jsite in range(nsite): - OrbGC[isite][jsite] = int(Orb[isite][jsite]) - reverse[isite][jsite] = int(AntiOrb[isite][jsite]) - - # (2) Symmetrise + import numpy as np + + # (1) Copy into numpy arrays + OrbGC = np.asarray(Orb, dtype=int).copy() + reverse = np.asarray(AntiOrb, dtype=int).copy() + + # (2) Symmetrise — process each orbital in ascending order. + # For each iorb, find all (i,j) with OrbGC[i,j]==iorb and set + # OrbGC[j,i]=iorb, reverse[j,i]=-reverse[i,j]. + # Order matters: later iorb can overwrite earlier iorb's writes. + # Process sequentially to preserve the original semantics where + # each (i,j) match writes to (j,i) immediately. for iorb in range(NOrb): - for isite in range(nsite): - for jsite in range(nsite): - if OrbGC[isite][jsite] == iorb: - OrbGC[jsite][isite] = OrbGC[isite][jsite] - reverse[jsite][isite] = -reverse[isite][jsite] - - # (3) Renumber -- lower triangle (isite > jsite) + rows, cols = np.where(OrbGC == iorb) + for r, c in zip(rows, cols): + OrbGC[c, r] = iorb + reverse[c, r] = -reverse[r, c] + + # (3) Renumber — lower triangle (isite > jsite). + # Replace each newly-seen positive orbital value with a negative + # temporary across the entire matrix (vectorised). NOrbGC = 0 for isite in range(nsite): for jsite in range(isite): - if OrbGC[isite][jsite] >= 0: - iOrbGC = OrbGC[isite][jsite] + if OrbGC[isite, jsite] >= 0: + iOrbGC = OrbGC[isite, jsite] NOrbGC -= 1 - for isite1 in range(nsite): - for jsite1 in range(nsite): - if OrbGC[isite1][jsite1] == iOrbGC: - OrbGC[isite1][jsite1] = NOrbGC + OrbGC[OrbGC == iOrbGC] = NOrbGC NOrbGC = -NOrbGC - for isite in range(nsite): - for jsite in range(nsite): - OrbGC[isite][jsite] = -1 - OrbGC[isite][jsite] + OrbGC = -1 - OrbGC + + # Convert back to list-of-lists for downstream compatibility + OrbGC_list = OrbGC.tolist() + reverse_list = reverse.tolist() - return OrbGC, reverse, NOrbGC + return OrbGC_list, reverse_list, NOrbGC def _write_orbitalidxpara( diff --git a/python/stdface/writer/common_writer.py b/python/stdface/writer/common_writer.py index a3d974c..ff9bcb3 100644 --- a/python/stdface/writer/common_writer.py +++ b/python/stdface/writer/common_writer.py @@ -747,14 +747,16 @@ def print_1_green(StdI: StdIntList) -> None: ngreen = len(greenindx) with open("greenone.def", "w") as fp: - fp.write("===============================\n") - fp.write(f"NCisAjs {ngreen:10d}\n") - fp.write("===============================\n") - fp.write("======== Green functions ======\n") - fp.write("===============================\n") - for row in greenindx: - i0, s0, i1, s1 = row - fp.write(f"{i0:5d} {s0:5d} {i1:5d} {s1:5d}\n") + lines = [ + "===============================\n", + f"NCisAjs {ngreen:10d}\n", + "===============================\n", + "======== Green functions ======\n", + "===============================\n", + ] + for i0, s0, i1, s1 in greenindx: + lines.append(f"{i0:5d} {s0:5d} {i1:5d} {s1:5d}\n") + fp.write("".join(lines)) print(" greenone.def is written.") @@ -793,17 +795,19 @@ def print_2_green(StdI: StdIntList) -> None: ngreen = len(greenindx) with open("greentwo.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NCisAjsCktAltDC {ngreen:10d}\n") - fp.write("=============================================\n") - fp.write("======== Green functions for Sq AND Nq ======\n") - fp.write("=============================================\n") - for row in greenindx: - i0, s0, i1, s1, i2, s2, i3, s3 = row - fp.write( + lines = [ + "=============================================\n", + f"NCisAjsCktAltDC {ngreen:10d}\n", + "=============================================\n", + "======== Green functions for Sq AND Nq ======\n", + "=============================================\n", + ] + for i0, s0, i1, s1, i2, s2, i3, s3 in greenindx: + lines.append( f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " f"{i2:5d} {s2:5d} {i3:5d} {s3:5d}\n" ) + fp.write("".join(lines)) print(" greentwo.def is written.") From 4f3d4c041857b26f4feca0f3f5069ae0224d0eff Mon Sep 17 00:00:00 2001 From: Kazuyoshi Yoshimi Date: Thu, 29 Jan 2026 20:36:09 +0900 Subject: [PATCH 04/16] Remove old package files superseded by python/stdface/ These modules were moved into the python/stdface/ package hierarchy during the plugin architecture refactoring and are no longer needed at their original locations. Co-Authored-By: Claude Opus 4.5 --- python/keyword_parser.py | 646 ------------ python/lattice/__init__.py | 12 - python/lattice/boost_output.py | 147 --- python/lattice/chain_lattice.py | 279 ------ python/lattice/fc_ortho.py | 185 ---- python/lattice/geometry_output.py | 145 --- python/lattice/honeycomb_lattice.py | 270 ----- python/lattice/input_params.py | 301 ------ python/lattice/interaction_builder.py | 719 -------------- python/lattice/kagome.py | 281 ------ python/lattice/ladder.py | 282 ------ python/lattice/orthorhombic.py | 183 ---- python/lattice/pyrochlore.py | 198 ---- python/lattice/site_util.py | 659 ------------- python/lattice/square_lattice.py | 171 ---- python/lattice/triangular_lattice.py | 192 ---- python/lattice/wannier90.py | 1318 ------------------------- python/param_check.py | 274 ----- python/stdface_main.py | 677 ------------- python/stdface_model_util.py | 75 -- python/stdface_vals.py | 877 ---------------- python/version.py | 45 - python/writer/__init__.py | 9 - python/writer/common_writer.py | 1136 --------------------- python/writer/export_wannier90.py | 899 ----------------- python/writer/hphi_writer.py | 1100 --------------------- python/writer/interaction_writer.py | 534 ---------- python/writer/mvmc_variational.py | 485 --------- python/writer/mvmc_writer.py | 466 --------- python/writer/solver_writer.py | 252 ----- 30 files changed, 12817 deletions(-) delete mode 100644 python/keyword_parser.py delete mode 100644 python/lattice/__init__.py delete mode 100644 python/lattice/boost_output.py delete mode 100644 python/lattice/chain_lattice.py delete mode 100644 python/lattice/fc_ortho.py delete mode 100644 python/lattice/geometry_output.py delete mode 100644 python/lattice/honeycomb_lattice.py delete mode 100644 python/lattice/input_params.py delete mode 100644 python/lattice/interaction_builder.py delete mode 100644 python/lattice/kagome.py delete mode 100644 python/lattice/ladder.py delete mode 100644 python/lattice/orthorhombic.py delete mode 100644 python/lattice/pyrochlore.py delete mode 100644 python/lattice/site_util.py delete mode 100644 python/lattice/square_lattice.py delete mode 100644 python/lattice/triangular_lattice.py delete mode 100644 python/lattice/wannier90.py delete mode 100644 python/param_check.py delete mode 100644 python/stdface_main.py delete mode 100644 python/stdface_model_util.py delete mode 100644 python/stdface_vals.py delete mode 100644 python/version.py delete mode 100644 python/writer/__init__.py delete mode 100644 python/writer/common_writer.py delete mode 100644 python/writer/export_wannier90.py delete mode 100644 python/writer/hphi_writer.py delete mode 100644 python/writer/interaction_writer.py delete mode 100644 python/writer/mvmc_variational.py delete mode 100644 python/writer/mvmc_writer.py delete mode 100644 python/writer/solver_writer.py diff --git a/python/keyword_parser.py b/python/keyword_parser.py deleted file mode 100644 index e9947c3..0000000 --- a/python/keyword_parser.py +++ /dev/null @@ -1,646 +0,0 @@ -"""Keyword parsing helpers and solver-specific parsers. - -This module contains functions for parsing keyword-value pairs from -StdFace input files, including duplicate-checking storage helpers and -solver-specific keyword dispatchers for HPhi, mVMC, UHF, and H-wave. -""" -from __future__ import annotations - -import cmath -import math - -from stdface_vals import StdIntList, SolverType, NaN_i, UNSET_STRING -from param_check import exit_program - - -_TRIM_TABLE = str.maketrans("", "", " :;\"\\\b\v\n\0") -"""Translation table for :func:`trim_space_quote`. - -Removes: space, colon, semicolon, double-quote, backslash, -backspace (``\\b``), vertical-tab (``\\v``), newline, and null. -""" - - -def trim_space_quote(text: str) -> str: - """Remove whitespace, colons, semicolons, quotes and backslashes from *text*. - - Uses a precomputed :func:`str.maketrans` table for efficient - character deletion. - - Parameters - ---------- - text : str - Raw keyword or value string from an input file. - - Returns - ------- - str - The cleaned string with the above characters removed. - """ - return text.translate(_TRIM_TABLE) - - -def _fail_duplicate(keyword: str) -> None: - """Print a duplicate-keyword error and terminate. - - Parameters - ---------- - keyword : str - The duplicated keyword name. - """ - print(f"ERROR ! Keyword {keyword} is duplicated ! ") - exit_program(-1) - - -def store_with_check_dup_s(keyword: str, value: str, current: str) -> str: - """Store a string value after checking for duplicate assignment. - - If *current* has already been assigned (i.e. it is not the sentinel - ``"****"``), the program prints an error and exits. - - Parameters - ---------- - keyword : str - The keyword name (used only for the error message). - value : str - The new value read from the input file. - current : str - The current stored value (``"****"`` means unset). - - Returns - ------- - str - The accepted value (equal to *value*). - - Raises - ------ - SystemExit - If *current* is not the sentinel, indicating a duplicate keyword. - """ - if current != UNSET_STRING: - _fail_duplicate(keyword) - return value - - -def store_with_check_dup_sl( - keyword: str, value: str, current: str, maxlen: int = 256 -) -> str: - """Store a string value (forced lower-case) after checking for duplicates. - - Behaves like :func:`store_with_check_dup_s` but additionally - converts *value* to lower case and truncates it to *maxlen* - characters. - - Parameters - ---------- - keyword : str - The keyword name (used only for the error message). - value : str - The new value read from the input file. - current : str - The current stored value (``"****"`` means unset). - maxlen : int, optional - Maximum number of characters to keep (default 256). - - Returns - ------- - str - The accepted value, lower-cased and truncated. - - Raises - ------ - SystemExit - If *current* is not the sentinel, indicating a duplicate keyword. - """ - if current != UNSET_STRING: - _fail_duplicate(keyword) - return value[:maxlen].lower() - - -def store_with_check_dup_i(keyword: str, value: str, current: int) -> int: - """Store an integer value after checking for duplicate assignment. - - If *current* differs from the integer sentinel (``2147483647``), - the program prints an error and exits. - - Parameters - ---------- - keyword : str - The keyword name (used only for the error message). - value : str - The new value read from the input file (will be converted to int). - current : int - The current stored value (``NaN_i`` means unset). - - Returns - ------- - int - The parsed integer value. - - Raises - ------ - SystemExit - If *current* is not the sentinel, indicating a duplicate keyword. - """ - if current != NaN_i: - _fail_duplicate(keyword) - # C sscanf("%d") truncates floats like "2.0" -> 2 - return int(float(value)) - - -def store_with_check_dup_d(keyword: str, value: str, current: float) -> float: - """Store a float value after checking for duplicate assignment. - - If *current* is **not** NaN the program prints an error and exits. - - Parameters - ---------- - keyword : str - The keyword name (used only for the error message). - value : str - The new value read from the input file (will be converted to float). - current : float - The current stored value (``NaN`` means unset). - - Returns - ------- - float - The parsed float value. - - Raises - ------ - SystemExit - If *current* is not NaN, indicating a duplicate keyword. - """ - if not math.isnan(current): - _fail_duplicate(keyword) - return float(value) - - -def _safe_float(s: str) -> float: - """Parse a string to float, returning 0.0 on empty or invalid input. - - Parameters - ---------- - s : str - String to parse. - - Returns - ------- - float - Parsed value, or 0.0 if *s* is empty or not a valid number. - """ - s = s.strip() - if not s: - return 0.0 - try: - return float(s) - except ValueError: - return 0.0 - - -def store_with_check_dup_c(keyword: str, value: str, current: complex) -> complex: - """Store a complex value after checking for duplicate assignment. - - The input string *value* may be in one of the following forms: - - * ``"real,imag"`` -- both parts specified - * ``"real"`` -- imaginary part defaults to 0 - * ``",imag"`` -- real part defaults to 0 - - If the real part of *current* is **not** NaN the program prints an - error and exits. - - Parameters - ---------- - keyword : str - The keyword name (used only for the error message). - value : str - The new value read from the input file. - current : complex - The current stored value (real-part ``NaN`` means unset). - - Returns - ------- - complex - The parsed complex value. - - Raises - ------ - SystemExit - If *current* is already set, indicating a duplicate keyword. - """ - if not cmath.isnan(current): - _fail_duplicate(keyword) - - # Split on comma, mirroring the C strtok(",") logic - parts = value.split(",", 1) if "," in value else [value] - real_part = _safe_float(parts[0]) - imag_part = _safe_float(parts[1]) if len(parts) > 1 else 0.0 - - return complex(real_part, imag_part) - - -# ===================================================================== -# Common keyword dispatch table -# ===================================================================== - - -def _j_matrix_keywords(prefix: str, scalar_field: str, - matrix_field: str) -> dict[str, tuple]: - """Generate keyword entries for a 3x3 spin-coupling matrix and its isotropic scalar. - - Each J-family (J, J0, J0', J0'', J1, ..., J2'', J', J'') has an - isotropic scalar keyword (e.g. ``"j0"``) and 9 anisotropic component - keywords (e.g. ``"j0x"``, ``"j0xy"``, ``"j0xz"``, ...). - - Parameters - ---------- - prefix : str - Lowered keyword prefix (e.g. ``"j0"``, ``"j0'"``, ``"j2''"``, ``"j"``). - scalar_field : str - StdIntList attribute for the isotropic scalar (e.g. ``"J0All"``). - matrix_field : str - StdIntList attribute for the 3x3 matrix (e.g. ``"J0"``). - - Returns - ------- - dict[str, tuple] - 10 keyword entries (1 scalar + 9 array-element). - """ - # Component suffixes → (row, col) in the 3×3 matrix - _COMPONENTS = [ - ("x", (0, 0)), ("xy", (0, 1)), ("xz", (0, 2)), - ("y", (1, 1)), ("yx", (1, 0)), ("yz", (1, 2)), - ("z", (2, 2)), ("zx", (2, 0)), ("zy", (2, 1)), - ] - d: dict[str, tuple] = { - prefix: (store_with_check_dup_d, scalar_field), - } - for suffix, idx in _COMPONENTS: - d[prefix + suffix] = (store_with_check_dup_d, matrix_field, idx, float) - return d - - -def _grid3x3_keywords( - fmt: str, field: str, store_func: object, cast: type, -) -> dict[str, tuple]: - """Generate 9 keyword entries for a 3x3 array indexed by (a0/a1/a2) × (w/l/h). - - Parameters - ---------- - fmt : str - Format string with ``{a}`` and ``{c}`` placeholders for the row name - and column name, e.g. ``"{a}{c}"`` → ``"a0w"``, ``"a1l"``, etc. - or ``"cutoff_j_{a}{c}"`` → ``"cutoff_j_a0w"``, etc. - field : str - StdIntList attribute name for the 3x3 array (e.g. ``"box"``). - store_func : callable - Store-with-duplicate-check function (e.g. ``store_with_check_dup_i``). - cast : type - Type cast for the stored value (``int`` or ``float``). - - Returns - ------- - dict[str, tuple] - 9 keyword entries mapping ``fmt.format(a=..., c=...)`` to - ``(store_func, field, (row, col), cast)``. - """ - d: dict[str, tuple] = {} - for row, aname in enumerate(("a0", "a1", "a2")): - for col, comp in enumerate(("w", "l", "h")): - d[fmt.format(a=aname, c=comp)] = ( - store_func, field, (row, col), cast - ) - return d - - -_COMMON_KEYWORDS: dict[str, tuple] = { - # --- scalar a -------------------------------------------------------- - "a": (store_with_check_dup_d, "a"), - # --- box (supercell) ------------------------------------------------- - **_grid3x3_keywords("{a}{c}", "box", store_with_check_dup_i, int), - # --- cutoff J -------------------------------------------------------- - "cutoff_j": (store_with_check_dup_d, "cutoff_j"), - "cutoff_jw": (store_with_check_dup_i, "cutoff_JR", 0, int), - "cutoff_jl": (store_with_check_dup_i, "cutoff_JR", 1, int), - "cutoff_jh": (store_with_check_dup_i, "cutoff_JR", 2, int), - **_grid3x3_keywords("cutoff_j_{a}{c}", "cutoff_JVec", store_with_check_dup_d, float), - "cutoff_length_j": (store_with_check_dup_d, "cutoff_length_J"), - "cutoff_length_u": (store_with_check_dup_d, "cutoff_length_U"), - "cutoff_length_t": (store_with_check_dup_d, "cutoff_length_t"), - # --- cutoff t -------------------------------------------------------- - "cutoff_t": (store_with_check_dup_d, "cutoff_t"), - "cutoff_tw": (store_with_check_dup_i, "cutoff_tR", 0, int), - "cutoff_tl": (store_with_check_dup_i, "cutoff_tR", 1, int), - "cutoff_th": (store_with_check_dup_i, "cutoff_tR", 2, int), - **_grid3x3_keywords("cutoff_t_{a}{c}", "cutoff_tVec", store_with_check_dup_d, float), - # --- cutoff U -------------------------------------------------------- - "cutoff_u": (store_with_check_dup_d, "cutoff_u"), - "cutoff_uw": (store_with_check_dup_i, "cutoff_UR", 0, int), - "cutoff_ul": (store_with_check_dup_i, "cutoff_UR", 1, int), - "cutoff_uh": (store_with_check_dup_i, "cutoff_UR", 2, int), - **_grid3x3_keywords("cutoff_u_{a}{c}", "cutoff_UVec", store_with_check_dup_d, float), - # --- lambda, alpha, D ------------------------------------------------ - "lambda": (store_with_check_dup_d, "lambda_"), - "lambda_u": (store_with_check_dup_d, "lambda_U"), - "lambda_j": (store_with_check_dup_d, "lambda_J"), - "alpha": (store_with_check_dup_d, "alpha"), - "d": (store_with_check_dup_d, "D", (2, 2), float), - "doublecounting": (store_with_check_dup_sl, "double_counting_mode"), - # --- magnetic field and related -------------------------------------- - "gamma": (store_with_check_dup_d, "Gamma"), - "h": (store_with_check_dup_d, "h"), - "gamma_y": (store_with_check_dup_d, "Gamma_y"), - "height": (store_with_check_dup_i, "Height"), - "hlength": (store_with_check_dup_d, "length", 2, float), - "hx": (store_with_check_dup_d, "direct", (2, 0), float), - "hy": (store_with_check_dup_d, "direct", (2, 1), float), - "hz": (store_with_check_dup_d, "direct", (2, 2), float), - # --- J exchange couplings -------------------------------------------- - **_j_matrix_keywords("j", "JAll", "J"), - **_j_matrix_keywords("j0", "J0All", "J0"), - **_j_matrix_keywords("j0'", "J0pAll", "J0p"), - **_j_matrix_keywords("j0''", "J0ppAll", "J0pp"), - **_j_matrix_keywords("j1", "J1All", "J1"), - **_j_matrix_keywords("j1'", "J1pAll", "J1p"), - **_j_matrix_keywords("j1''", "J1ppAll", "J1pp"), - **_j_matrix_keywords("j2", "J2All", "J2"), - **_j_matrix_keywords("j2'", "J2pAll", "J2p"), - **_j_matrix_keywords("j2''", "J2ppAll", "J2pp"), - **_j_matrix_keywords("j'", "JpAll", "Jp"), - **_j_matrix_keywords("j''", "JppAll", "Jpp"), - # --- K, L, lattice, length ------------------------------------------- - "k": (store_with_check_dup_d, "K"), - "l": (store_with_check_dup_i, "L"), - "lattice": (store_with_check_dup_sl, "lattice"), - "llength": (store_with_check_dup_d, "length", 1, float), - "lx": (store_with_check_dup_d, "direct", (1, 0), float), - "ly": (store_with_check_dup_d, "direct", (1, 1), float), - "lz": (store_with_check_dup_d, "direct", (1, 2), float), - # --- model, mu, ncond, outputmode ------------------------------------ - "model": (store_with_check_dup_sl, "model"), - "mu": (store_with_check_dup_d, "mu"), - "ncond": (store_with_check_dup_i, "ncond"), - "nelec": (store_with_check_dup_i, "ncond"), # alias - "outputmode": (store_with_check_dup_sl, "outputmode"), - # --- phase ----------------------------------------------------------- - "phase0": (store_with_check_dup_d, "phase", 0, float), - "phase1": (store_with_check_dup_d, "phase", 1, float), - "phase2": (store_with_check_dup_d, "phase", 2, float), - # --- hopping t ------------------------------------------------------- - "t": (store_with_check_dup_c, "t"), - "t0": (store_with_check_dup_c, "t0"), - "t0'": (store_with_check_dup_c, "t0p"), - "t0''": (store_with_check_dup_c, "t0pp"), - "t1": (store_with_check_dup_c, "t1"), - "t1'": (store_with_check_dup_c, "t1p"), - "t1''": (store_with_check_dup_c, "t1pp"), - "t2": (store_with_check_dup_c, "t2"), - "t2'": (store_with_check_dup_c, "t2p"), - "t2''": (store_with_check_dup_c, "t2pp"), - "t'": (store_with_check_dup_c, "tp"), - "t''": (store_with_check_dup_c, "tpp"), - # --- U, V ------------------------------------------------------------ - "u": (store_with_check_dup_d, "U"), - "v": (store_with_check_dup_d, "V"), - "v0": (store_with_check_dup_d, "V0"), - "v0'": (store_with_check_dup_d, "V0p"), - "v0''": (store_with_check_dup_d, "V0pp"), - "v1": (store_with_check_dup_d, "V1"), - "v1'": (store_with_check_dup_d, "V1p"), - "v1''": (store_with_check_dup_d, "V1pp"), - "v2": (store_with_check_dup_d, "V2"), - "v2'": (store_with_check_dup_d, "V2p"), - "v2''": (store_with_check_dup_d, "V2pp"), - "v'": (store_with_check_dup_d, "Vp"), - "v''": (store_with_check_dup_d, "Vpp"), - # --- W, wlength, wx/wy/wz ------------------------------------------- - "w": (store_with_check_dup_i, "W"), - "wlength": (store_with_check_dup_d, "length", 0, float), - "wx": (store_with_check_dup_d, "direct", (0, 0), float), - "wy": (store_with_check_dup_d, "direct", (0, 1), float), - "wz": (store_with_check_dup_d, "direct", (0, 2), float), - # --- 2Sz ------------------------------------------------------------- - "2sz": (store_with_check_dup_i, "Sz2"), -} -"""Common keyword dispatch table shared by all solvers.""" - - -def parse_common_keyword(keyword: str, value: str, StdI: StdIntList) -> bool: - """Parse a keyword common to all solvers. - - Looks up *keyword* in :data:`_COMMON_KEYWORDS` and applies the - corresponding store operation to *StdI*. - - Parameters - ---------- - keyword : str - The lowered keyword string. - value : str - The raw value string from the input file. - StdI : StdIntList - The global parameter structure, modified in place. - - Returns - ------- - bool - True if the keyword was recognised, False otherwise. - """ - return _apply_keyword_table(_COMMON_KEYWORDS, keyword, value, StdI) - - -# ===================================================================== -# Solver-specific keyword dispatch tables -# ===================================================================== -# -# Each entry maps a lowered keyword string to a descriptor: -# - Scalar field: (store_func, "field_name") -# - Array element: (store_func, "array_name", index, cast) -# -# ``_apply_keyword_table`` applies the matching entry generically. - -# Shared boxsub keywords (mVMC, UHF, HWAVE all accept these). -_BOXSUB_KEYWORDS: dict[str, tuple] = _grid3x3_keywords( - "{a}{c}sub", "boxsub", store_with_check_dup_i, int -) - -# Shared UHF base keywords (UHF and HWAVE both accept these). -_UHF_BASE_KEYWORDS: dict[str, tuple] = { - "iteration_max": (store_with_check_dup_i, "Iteration_max"), - "rndseed": (store_with_check_dup_i, "RndSeed"), - "nmptrans": (store_with_check_dup_i, "NMPTrans"), - **_BOXSUB_KEYWORDS, - "hsub": (store_with_check_dup_i, "Hsub"), - "lsub": (store_with_check_dup_i, "Lsub"), - "wsub": (store_with_check_dup_i, "Wsub"), - "eps": (store_with_check_dup_i, "eps"), - "epsslater": (store_with_check_dup_i, "eps_slater"), - "mix": (store_with_check_dup_d, "mix"), -} - -_HPHI_KEYWORDS: dict[str, tuple] = { - "calcspec": (store_with_check_dup_sl, "CalcSpec"), - "exct": (store_with_check_dup_i, "exct"), - "eigenvecio": (store_with_check_dup_sl, "EigenVecIO"), - "expandcoef": (store_with_check_dup_i, "ExpandCoef"), - "expecinterval": (store_with_check_dup_i, "ExpecInterval"), - "cdatafilehead": (store_with_check_dup_s, "CDataFileHead"), - "dt": (store_with_check_dup_d, "dt"), - "flgtemp": (store_with_check_dup_i, "FlgTemp"), - "freq": (store_with_check_dup_d, "freq"), - "hamio": (store_with_check_dup_sl, "HamIO"), - "initialvectype": (store_with_check_dup_sl, "InitialVecType"), - "initial_iv": (store_with_check_dup_i, "initial_iv"), - "lanczoseps": (store_with_check_dup_i, "LanczosEps"), - "lanczostarget": (store_with_check_dup_i, "LanczosTarget"), - "lanczos_max": (store_with_check_dup_i, "Lanczos_max"), - "largevalue": (store_with_check_dup_d, "LargeValue"), - "method": (store_with_check_dup_sl, "method"), - "nomega": (store_with_check_dup_i, "Nomega"), - "numave": (store_with_check_dup_i, "NumAve"), - "nvec": (store_with_check_dup_i, "nvec"), - "omegamax": (store_with_check_dup_d, "OmegaMax"), - "omegamin": (store_with_check_dup_d, "OmegaMin"), - "omegaorg": (store_with_check_dup_d, "OmegaOrg"), - "omegaim": (store_with_check_dup_d, "OmegaIm"), - "outputexcitedvec": (store_with_check_dup_sl, "OutputExVec"), - "pumptype": (store_with_check_dup_sl, "PumpType"), - "restart": (store_with_check_dup_sl, "Restart"), - "spectrumqh": (store_with_check_dup_d, "SpectrumQ", 2, float), - "spectrumql": (store_with_check_dup_d, "SpectrumQ", 1, float), - "spectrumqw": (store_with_check_dup_d, "SpectrumQ", 0, float), - "spectrumtype": (store_with_check_dup_sl, "SpectrumType"), - "tdump": (store_with_check_dup_d, "tdump"), - "tshift": (store_with_check_dup_d, "tshift"), - "uquench": (store_with_check_dup_d, "Uquench"), - "vecpoth": (store_with_check_dup_d, "VecPot", 2, float), - "vecpotl": (store_with_check_dup_d, "VecPot", 1, float), - "vecpotw": (store_with_check_dup_d, "VecPot", 0, float), - "2s": (store_with_check_dup_i, "S2"), - "ngpu": (store_with_check_dup_i, "NGPU"), - "scalapack": (store_with_check_dup_i, "Scalapack"), -} -"""HPhi-specific keyword → (store_func, field_name[, index, cast]) map.""" - -_MVMC_KEYWORDS: dict[str, tuple] = { - **_BOXSUB_KEYWORDS, - "complextype": (store_with_check_dup_i, "ComplexType"), - "cparafilehead": (store_with_check_dup_s, "CParaFileHead"), - "dsroptredcut": (store_with_check_dup_d, "DSROptRedCut"), - "dsroptstadel": (store_with_check_dup_d, "DSROptStaDel"), - "dsroptstepdt": (store_with_check_dup_d, "DSROptStepDt"), - "hsub": (store_with_check_dup_i, "Hsub"), - "lsub": (store_with_check_dup_i, "Lsub"), - "nvmccalmode": (store_with_check_dup_i, "NVMCCalMode"), - "ndataidxstart": (store_with_check_dup_i, "NDataIdxStart"), - "ndataqtysmp": (store_with_check_dup_i, "NDataQtySmp"), - "nlanczosmode": (store_with_check_dup_i, "NLanczosMode"), - "nmptrans": (store_with_check_dup_i, "NMPTrans"), - "nspgaussleg": (store_with_check_dup_i, "NSPGaussLeg"), - "nsplitsize": (store_with_check_dup_i, "NSplitSize"), - "nspstot": (store_with_check_dup_i, "NSPStot"), - "nsroptitrsmp": (store_with_check_dup_i, "NSROptItrSmp"), - "nsroptitrstep": (store_with_check_dup_i, "NSROptItrStep"), - "nstore": (store_with_check_dup_i, "NStore"), - "nsrcg": (store_with_check_dup_i, "NSRCG"), - "nvmcinterval": (store_with_check_dup_i, "NVMCInterval"), - "nvmcsample": (store_with_check_dup_i, "NVMCSample"), - "nvmcwarmup": (store_with_check_dup_i, "NVMCWarmUp"), - "rndseed": (store_with_check_dup_i, "RndSeed"), - "wsub": (store_with_check_dup_i, "Wsub"), -} -"""mVMC-specific keyword → (store_func, field_name) map.""" - -_UHF_KEYWORDS: dict[str, tuple] = _UHF_BASE_KEYWORDS -"""UHF-specific keyword map (identical to the shared base).""" - -_HWAVE_KEYWORDS: dict[str, tuple] = { - **_UHF_BASE_KEYWORDS, - "calcmode": (store_with_check_dup_sl, "calcmode"), - "fileprefix": (store_with_check_dup_sl, "fileprefix"), - "exportall": (store_with_check_dup_i, "export_all"), - "lattice_gp": (store_with_check_dup_i, "lattice_gp"), -} -"""HWAVE-specific keyword map (extends UHF base with 4 extra keywords).""" - -_SOLVER_KEYWORD_TABLES: dict[str, dict[str, tuple]] = { - SolverType.HPhi: _HPHI_KEYWORDS, - SolverType.mVMC: _MVMC_KEYWORDS, - SolverType.UHF: _UHF_KEYWORDS, - SolverType.HWAVE: _HWAVE_KEYWORDS, -} -"""Maps solver type to its keyword dispatch table.""" - - -def _apply_keyword_table( - table: dict[str, tuple], - keyword: str, - value: str, - StdI: StdIntList, -) -> bool: - """Look up *keyword* in *table* and apply the corresponding store operation. - - Parameters - ---------- - table : dict[str, tuple] - Keyword dispatch table. Each entry is either: - - ``(store_func, field_name)`` for scalar fields, or - - ``(store_func, array_name, index, cast)`` for array elements. - keyword : str - The lowered keyword string. - value : str - The raw value string from the input file. - StdI : StdIntList - The global parameter structure, modified in place. - - Returns - ------- - bool - True if *keyword* was found in *table*, False otherwise. - """ - entry = table.get(keyword) - if entry is None: - return False - if len(entry) == 2: - # Scalar field: (store_func, field_name) - store_func, field_name = entry - setattr(StdI, field_name, - store_func(keyword, value, getattr(StdI, field_name))) - else: - # Array element: (store_func, array_name, index, cast) - store_func, array_name, index, cast = entry - arr = getattr(StdI, array_name) - arr[index] = store_func(keyword, value, cast(arr[index])) - return True - - -def parse_solver_keyword(keyword: str, value: str, StdI: StdIntList, - solver: str) -> bool: - """Parse a solver-specific keyword. - - Looks up the keyword in the dispatch table for *solver* and applies - the corresponding store operation to *StdI*. - - Parameters - ---------- - keyword : str - The lowered keyword string. - value : str - The raw value string from the input file. - StdI : StdIntList - The global parameter structure, modified in place. - solver : str - The solver type (one of ``SolverType`` values). - - Returns - ------- - bool - True if the keyword was recognised, False otherwise. - """ - table = _SOLVER_KEYWORD_TABLES.get(solver) - if table is None: - return False - return _apply_keyword_table(table, keyword, value, StdI) - - diff --git a/python/lattice/__init__.py b/python/lattice/__init__.py deleted file mode 100644 index 0985bcc..0000000 --- a/python/lattice/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Lattice construction subpackage. - -This package contains modules for building lattice geometries and their -associated interaction terms. Each lattice type is implemented in its own -module; shared utilities (site initialisation, geometry output, interaction -building, input parameter helpers) are also included. -""" -from __future__ import annotations - -from . import chain_lattice, square_lattice, ladder, triangular_lattice -from . import honeycomb_lattice, kagome, orthorhombic, fc_ortho, pyrochlore -from . import wannier90 diff --git a/python/lattice/boost_output.py b/python/lattice/boost_output.py deleted file mode 100644 index ba5a043..0000000 --- a/python/lattice/boost_output.py +++ /dev/null @@ -1,147 +0,0 @@ -"""Boost-output helpers for writing ``boost.def`` sections. - -This module provides functions used by lattice builders that support HPhi's -Boost method (chain, honeycomb, kagome, ladder). Each function writes a -specific section of the ``boost.def`` file. - -Functions ---------- -write_boost_mag_field - Write the magnetic-field line to ``boost.def``. -write_boost_j_full - Write a full 3×3 J-coupling matrix to ``boost.def``. -write_boost_j_symmetric - Write an upper-triangle symmetric 3×3 J-coupling matrix to ``boost.def``. -write_boost_6spin_star - Write the ``list_6spin_star`` array to ``boost.def``. -write_boost_6spin_pair - Write the ``list_6spin_pair`` array to ``boost.def``. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -from typing import TextIO - -import numpy as np - -from stdface_vals import StdIntList - - -def write_boost_mag_field(fp: TextIO, StdI: StdIntList) -> None: - """Write the magnetic-field header line to ``boost.def``. - - Outputs ``# Magnetic field`` followed by a line containing the three - scaled field components ``-0.5 * Gamma``, ``-0.5 * Gamma_y``, and - ``-0.5 * h``. - - Parameters - ---------- - fp : TextIO - Open file handle for ``boost.def``. - StdI : StdIntList - Model parameter structure (read-only access to ``Gamma``, - ``Gamma_y``, ``h``). - """ - fp.write("# Magnetic field\n") - fp.write( - f"{-0.5 * StdI.Gamma:25.15e} " - f"{-0.5 * StdI.Gamma_y:25.15e} " - f"{-0.5 * StdI.h:25.15e}\n" - ) - - -def write_boost_j_full(fp: TextIO, J: np.ndarray, scale: float = 0.25) -> None: - """Write a full 3×3 J-coupling matrix to ``boost.def``. - - Outputs three rows of three values each, where each element is - ``scale * J[i, j]``. Used by the chain lattice. - - Parameters - ---------- - fp : TextIO - Open file handle for ``boost.def``. - J : numpy.ndarray - 3×3 coupling matrix. - scale : float, optional - Multiplicative scaling factor. Default is ``0.25``. - """ - for row in J: - scaled = scale * row - fp.write(f"{scaled[0]:25.15e} {scaled[1]:25.15e} {scaled[2]:25.15e}\n") - - -def write_boost_j_symmetric(fp: TextIO, J: np.ndarray, scale: float = 0.25) -> None: - """Write an upper-triangle symmetric 3×3 J-coupling matrix to ``boost.def``. - - Outputs three rows using only upper-triangle elements:: - - J[0,0] J[0,1] J[0,2] - J[0,1] J[1,1] J[1,2] - J[0,2] J[1,2] J[2,2] - - Each element is multiplied by *scale*. Used by honeycomb, ladder, - and kagome lattices. - - Parameters - ---------- - fp : TextIO - Open file handle for ``boost.def``. - J : numpy.ndarray - 3×3 coupling matrix (only upper-triangle elements are read). - scale : float, optional - Multiplicative scaling factor. Default is ``0.25``. - """ - Jsym = np.triu(J) + np.triu(J, 1).T - write_boost_j_full(fp, Jsym, scale) - - -def write_boost_6spin_star(fp: TextIO, StdI: StdIntList) -> None: - """Write the ``list_6spin_star`` array to ``boost.def``. - - Outputs each pivot's 7-element row from ``StdI.list_6spin_star``. - - Parameters - ---------- - fp : TextIO - Open file handle for ``boost.def``. - StdI : StdIntList - Structure containing ``num_pivot`` and ``list_6spin_star``. - """ - fp.write("# StdI->list_6spin_star\n") - for ipivot in range(StdI.num_pivot): - fp.write(f"# pivot {ipivot}\n") - row = StdI.list_6spin_star[ipivot, :7] - fp.write(" ".join(str(x) for x in row) + " \n") - - -def write_boost_6spin_pair(fp: TextIO, StdI: StdIntList) -> None: - """Write the ``list_6spin_pair`` array to ``boost.def``. - - Outputs each pivot's interaction rows from ``StdI.list_6spin_pair``, - with the number of interactions per pivot given by - ``StdI.list_6spin_star[ipivot, 0]``. - - Parameters - ---------- - fp : TextIO - Open file handle for ``boost.def``. - StdI : StdIntList - Structure containing ``num_pivot``, ``list_6spin_star``, - and ``list_6spin_pair``. - """ - fp.write("# StdI->list_6spin_pair\n") - for ipivot in range(StdI.num_pivot): - fp.write(f"# pivot {ipivot}\n") - for kintr in range(StdI.list_6spin_star[ipivot, 0]): - row = StdI.list_6spin_pair[ipivot, :7, kintr] - fp.write(" ".join(str(x) for x in row) + " \n") diff --git a/python/lattice/chain_lattice.py b/python/lattice/chain_lattice.py deleted file mode 100644 index f144394..0000000 --- a/python/lattice/chain_lattice.py +++ /dev/null @@ -1,279 +0,0 @@ -""" -Standard mode for the chain lattice. - -This module sets up the Hamiltonian for a 1D chain lattice model, -supporting spin, Hubbard, and Kondo models. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import numpy as np - -from stdface_vals import StdIntList, ModelType, SolverType -from param_check import ( - exit_program, print_val_d, print_val_i, - not_used_d, not_used_i, not_used_j, required_val_i, -) -from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - malloc_interactions, - add_neighbor_interaction, add_local_terms, -) -from .site_util import ( - init_site, set_label, set_local_spin_flags, - lattice_gp, -) -from .boost_output import ( - write_boost_mag_field, write_boost_j_full, - write_boost_6spin_star, write_boost_6spin_pair, -) - - -def chain(StdI: StdIntList) -> None: - """Set up a Hamiltonian for the Hubbard model on a chain lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - - Notes - ----- - This function handles three different model types: - - - Spin model - - Hubbard model - - Kondo model - - The function performs the following steps: - - 1. Computes super-cell shape and sites - 2. Validates and stores Hamiltonian parameters - 3. Sets local spin flags and number of sites - 4. Allocates memory for interactions - 5. Sets up transfers and interactions between sites - - The lattice geometry is written to ``lattice.gp`` for visualization. - For the Kondo model, the number of sites is doubled to account for - localized spins. - """ - # (1) Compute the shape of the super-cell and sites in the super-cell - with lattice_gp(StdI) as fp: - - StdI.NsiteUC = 1 - - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.0) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1]) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - not_used_d("phase1", StdI.phase[1]) - StdI.phase[1] = StdI.phase[0] - StdI.phase[0] = 0.0 - - required_val_i("L", StdI.L) - not_used_i("W", StdI.W) - StdI.W = 1 - init_site(StdI, fp, 2) - StdI.tau[0, 0] = 0.0 - StdI.tau[0, 1] = 0.0 - StdI.tau[0, 2] = 0.0 - - # (2) Check & store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J1'", StdI.J1pAll, StdI.J1p) - not_used_j("J2'", StdI.J2pAll, StdI.J2p) - not_used_d("t1", StdI.t1) - not_used_d("t2", StdI.t2) - not_used_d("t1'", StdI.t1p) - not_used_d("t2'", StdI.t2p) - not_used_d("V1", StdI.V1) - not_used_d("V2", StdI.V2) - not_used_d("V1'", StdI.V1p) - not_used_d("V2'", StdI.V2p) - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t", StdI.t) - not_used_d("t0", StdI.t0) - not_used_d("t'", StdI.tp) - not_used_d("V", StdI.V) - not_used_d("V0", StdI.V0) - not_used_d("V'", StdI.Vp) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") - StdI.t0pp = input_hopp(StdI.tpp, StdI.t0pp, "t0''") - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") - StdI.V0pp = input_coulomb_v(StdI.Vpp, StdI.V0pp, "V0''") - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J0'", StdI.J0pAll, StdI.J0p) - not_used_j("J0''", StdI.J0ppAll, StdI.J0pp) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - elif StdI.model == ModelType.KONDO: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and the number of sites - set_local_spin_flags(StdI, StdI.L) - - # (4) Compute upper limit of Transfer & Interaction and allocate them - if StdI.model == ModelType.SPIN: - ntransMax = StdI.L * (StdI.S2 + 1 + 2 * StdI.S2) - nintrMax = (StdI.L * (StdI.NsiteUC + 1 + 1 + 1) - * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1)) - else: - ntransMax = StdI.L * 2 * (2 * StdI.NsiteUC + 2 + 2 + 2) - nintrMax = StdI.L * (StdI.NsiteUC + 4 * (1 + 1 + 1)) - - if StdI.model == ModelType.KONDO: - ntransMax += StdI.L * (StdI.S2 + 1 + 2 * StdI.S2) - nintrMax += StdI.nsite // 2 * (3 * 1 + 1) * (3 * StdI.S2 + 1) - - malloc_interactions(StdI, ntransMax, nintrMax) - - # (5) Set Transfer & Interaction - for iL in range(StdI.L): - - isite = iL - if StdI.model == ModelType.KONDO: - isite += StdI.L - - # Local term - add_local_terms(StdI, isite, iL) - - # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) - _BONDS = ( - (0, 1, 0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # nn - (0, 2, 0, 0, 2, StdI.J0p, StdI.t0p, StdI.V0p), # nnn - (0, 3, 0, 0, 3, StdI.J0pp, StdI.t0pp, StdI.V0pp), # nnnn - ) - for dW, dL, si, sj, nn, J, t, V in _BONDS: - add_neighbor_interaction( - StdI, fp, 0, iL, dW, dL, si, sj, nn, J, t, V) - - -def chain_boost(StdI: StdIntList) -> None: - """Set up a Hamiltonian for the generalized Heisenberg model on a chain - lattice using HPhi boost mode. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - - Notes - ----- - This function handles: - - - Magnetic field terms - - Nearest and next-nearest neighbor interactions - - Specialized 6-spin interactions - - The function performs: - - 1. Sets up unit cell and lattice parameters - 2. Writes magnetic field configuration to ``boost.def`` - 3. Writes interaction parameters - 4. Sets up topology and pivot sites - 5. Configures 6-spin interaction lists - - Only available when ``StdI.solver == "HPhi"``. - - Warnings - -------- - - ``S2`` must be 1 in Boost mode. - - ``L`` must be divisible by 8. - """ - if StdI.solver != SolverType.HPhi: - return - - StdI.NsiteUC = 1 - - # Magnetic field - with open("boost.def", "w") as fp: - write_boost_mag_field(fp, StdI) - - # Interaction - fp.write(f"{2} # Number of type of J\n") - fp.write("# J 1\n") - write_boost_j_full(fp, StdI.J0) - fp.write("# J 2\n") - write_boost_j_full(fp, StdI.Jp) - - # Topology - if StdI.S2 != 1: - print("\n ERROR! S2 must be 1 in Boost. \n") - exit_program(-1) - StdI.ishift_nspin = 4 - if StdI.L % 8 != 0: - print("\n ERROR! L % 8 != 0 \n") - exit_program(-1) - StdI.W = StdI.L // 2 - StdI.L = 2 - StdI.num_pivot = StdI.W // 4 - - fp.write("# W0 R0 StdI->num_pivot StdI->ishift_nspin\n") - fp.write(f"{StdI.W} {StdI.L} {StdI.num_pivot} {StdI.ishift_nspin}\n") - - # list_6spin_star: shape (num_pivot, 7) - StdI.list_6spin_star = np.zeros((StdI.num_pivot, 7), dtype=int) - for ipivot in range(StdI.num_pivot): - StdI.list_6spin_star[ipivot, :] = [8, 1, 1, 1, 1, 1, 1] - - write_boost_6spin_star(fp, StdI) - - # list_6spin_pair: shape (num_pivot, 7, 8) - StdI.list_6spin_pair = np.zeros((StdI.num_pivot, 7, 8), dtype=int) - for ipivot in range(StdI.num_pivot): - StdI.list_6spin_pair[ipivot, :, 0] = [0, 1, 2, 3, 4, 5, 1] - StdI.list_6spin_pair[ipivot, :, 1] = [1, 2, 0, 3, 4, 5, 1] - StdI.list_6spin_pair[ipivot, :, 2] = [2, 3, 0, 1, 4, 5, 1] - StdI.list_6spin_pair[ipivot, :, 3] = [3, 4, 0, 1, 2, 5, 1] - StdI.list_6spin_pair[ipivot, :, 4] = [0, 2, 1, 3, 4, 5, 2] - StdI.list_6spin_pair[ipivot, :, 5] = [1, 3, 0, 2, 4, 5, 2] - StdI.list_6spin_pair[ipivot, :, 6] = [2, 4, 0, 1, 3, 5, 2] - StdI.list_6spin_pair[ipivot, :, 7] = [3, 5, 0, 1, 2, 4, 2] - - write_boost_6spin_pair(fp, StdI) diff --git a/python/lattice/fc_ortho.py b/python/lattice/fc_ortho.py deleted file mode 100644 index baedd99..0000000 --- a/python/lattice/fc_ortho.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -Standard mode for the face-centered orthorhombic lattice. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import numpy as np - -from stdface_vals import StdIntList, ModelType -from param_check import ( - print_val_d, print_val_i, - not_used_j, not_used_d, not_used_i, -) -from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - compute_max_interactions, malloc_interactions, - mag_field, general_j, hubbard_local, - add_neighbor_interaction_3d, -) -from .site_util import init_site, set_local_spin_flags, close_lattice_xsf - - -def fc_ortho(StdI: StdIntList) -> None: - """Setup a Hamiltonian for the face-centered orthorhombic lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - # (1) Compute the shape of the super-cell and sites in the super-cell - StdI.NsiteUC = 1 - - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.length[2] = print_val_d("Hlength", StdI.length[2], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], 0.0) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.5 * StdI.length[1]) - StdI.direct[0, 2] = print_val_d("Wz", StdI.direct[0, 2], 0.5 * StdI.length[2]) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.5 * StdI.length[0]) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], 0.0) - StdI.direct[1, 2] = print_val_d("Lz", StdI.direct[1, 2], 0.5 * StdI.length[2]) - StdI.direct[2, 0] = print_val_d("Hx", StdI.direct[2, 0], 0.5 * StdI.length[0]) - StdI.direct[2, 1] = print_val_d("Hy", StdI.direct[2, 1], 0.5 * StdI.length[1]) - StdI.direct[2, 2] = print_val_d("Hz", StdI.direct[2, 2], 0.0) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) - StdI.phase[2] = print_val_d("phase2", StdI.phase[2], 0.0) - - init_site(StdI, None, 3) - StdI.tau[0, 0] = 0.0 - StdI.tau[0, 1] = 0.0 - StdI.tau[0, 2] = 0.0 - - # (2) check & store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") - input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") - input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J1pp, StdI.J1ppAll, "J1''") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J2pp, StdI.J2ppAll, "J2''") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t", StdI.t) - not_used_d("t0", StdI.t0) - not_used_d("t1", StdI.t1) - not_used_d("t2", StdI.t2) - not_used_d("t'", StdI.tp) - not_used_d("t0'", StdI.t0p) - not_used_d("t1'", StdI.t1p) - not_used_d("t2'", StdI.t2p) - not_used_d("t''", StdI.tpp) - not_used_d("V", StdI.V) - not_used_d("V0", StdI.V0) - not_used_d("V1", StdI.V1) - not_used_d("V'", StdI.Vp) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") - StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") - StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") - StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") - StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") - StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") - StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") - StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") - StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J0'", StdI.J0pAll, StdI.J0p) - not_used_j("J1'", StdI.J1pAll, StdI.J1p) - not_used_j("J2'", StdI.J2pAll, StdI.J2p) - not_used_j("J''", StdI.JppAll, StdI.Jpp) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - else: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and number of sites - set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) - - # (4) Compute upper limit of Transfer & Interaction - # nn=6, nnn=3 → n_bonds=9 - ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=6 + 3) - malloc_interactions(StdI, ntransMax, nintrMax) - - # (5) Set Transfer & Interaction - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - iH = StdI.Cell[kCell, 2] - - # Local term - isite = kCell - if StdI.model == ModelType.KONDO: - isite += StdI.NCell - - if StdI.model == ModelType.SPIN: - mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, isite) - general_j(StdI, StdI.D, StdI.S2, StdI.S2, isite, isite) - else: - hubbard_local(StdI, StdI.mu, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, StdI.U, isite) - if StdI.model == ModelType.KONDO: - jsite = kCell - general_j(StdI, StdI.J, 1, StdI.S2, isite, jsite) - - # Neighbor bonds: (dW, dL, dH, site_i, site_j, J, t, V) - _BONDS = ( - # Nearest neighbor (6 equivalent pairs) - (1, 0, 0, 0, 0, StdI.J0, StdI.t0, StdI.V0), # along W - (0, 1, -1, 0, 0, StdI.J0, StdI.t0, StdI.V0), # along W (equiv) - (0, 1, 0, 0, 0, StdI.J1, StdI.t1, StdI.V1), # along L - (-1, 0, 1, 0, 0, StdI.J1, StdI.t1, StdI.V1), # along L (equiv) - (0, 0, 1, 0, 0, StdI.J2, StdI.t2, StdI.V2), # along H - (1, -1, 0, 0, 0, StdI.J2, StdI.t2, StdI.V2), # along H (equiv) - # Second nearest neighbor - (-1, 1, 1, 0, 0, StdI.J0p, StdI.t0p, StdI.V0p), # -W+L+H - (1, -1, 1, 0, 0, StdI.J1p, StdI.t1p, StdI.V1p), # -L+H+W - (1, 1, -1, 0, 0, StdI.J2p, StdI.t2p, StdI.V2p), # -H+W+L - ) - for dW, dL, dH, si, sj, J, t, V in _BONDS: - add_neighbor_interaction_3d( - StdI, iW, iL, iH, dW, dL, dH, si, sj, J, t, V) - - close_lattice_xsf(StdI) diff --git a/python/lattice/geometry_output.py b/python/lattice/geometry_output.py deleted file mode 100644 index 568a795..0000000 --- a/python/lattice/geometry_output.py +++ /dev/null @@ -1,145 +0,0 @@ -"""Geometry and structure output functions. - -This module provides functions for writing lattice geometry files used by -post-processing tools and visualisation programs. - -Functions ---------- -print_xsf - Write ``lattice.xsf`` in XCrysDen format. -print_geometry - Write ``geometry.dat`` for correlation-function post-processing. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import numpy as np - -from stdface_vals import StdIntList, ModelType, SolverType - - -def _cell_diff(Cell, iCell: int, jCell: int) -> list[int]: - """Compute difference between two cell coordinate rows as integers. - - Parameters - ---------- - Cell : np.ndarray - ``(NCell, 3)`` array of fractional cell coordinates. - iCell : int - Row index of the first (minuend) cell. - jCell : int - Row index of the second (subtrahend) cell. - - Returns - ------- - list of int - ``[int(Cell[iCell, k] - Cell[jCell, k]) for k in range(3)]``. - """ - return (Cell[iCell] - Cell[jCell]).astype(int).tolist() - - -def print_xsf(StdI: StdIntList) -> None: - """Print lattice.xsf file (XCrysDen format). - - Writes a ``lattice.xsf`` file containing primitive vectors, - optional conventional vectors (for orthorhombic and face-centred - lattices), and atomic coordinates for visualisation. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. The following fields are read: - - - ``lattice`` : str -- lattice type name. - - ``box`` : ndarray (3x3) -- supercell box matrix. - - ``direct`` : ndarray (3x3) -- direct lattice vectors. - - ``length`` : ndarray (3,) -- lattice constant lengths. - - ``NCell`` : int -- number of unit cells. - - ``NsiteUC`` : int -- sites per unit cell. - - ``Cell`` : ndarray (NCell, 3) -- cell fractional coordinates. - - ``tau`` : ndarray (NsiteUC, 3) -- basis positions. - """ - do_convvec = StdI.lattice in ( - "orthorhombic", "face-centeredorthorhombic", - "fcorthorhombic", "fco", "pyrochlore") - - with open("lattice.xsf", "w") as fp: - fp.write("CRYSTAL\n") - fp.write("PRIMVEC\n") - # PRIMVEC = box @ direct (each row is a primitive vector) - primvec = StdI.box @ StdI.direct - for vec in primvec: - fp.write(f"{vec[0]:15.5f} {vec[1]:15.5f} {vec[2]:15.5f}\n") - - if do_convvec: - fp.write("CONVVEC\n") - for ii, length_val in enumerate(StdI.length): - row = [0.0, 0.0, 0.0] - row[ii] = length_val - fp.write(f"{row[0]:15.5f} {row[1]:15.5f} {row[2]:15.5f}\n") - - fp.write("PRIMCOORD\n") - fp.write(f"{StdI.NCell * StdI.NsiteUC} 1\n") - for iCell in range(StdI.NCell): - for isite in range(StdI.NsiteUC): - # vec = (Cell[iCell] + tau[isite]) @ direct - frac_coord = StdI.Cell[iCell, :] + StdI.tau[isite, :] - vec = frac_coord @ StdI.direct - fp.write(f"H {vec[0]:15.5f} {vec[1]:15.5f} {vec[2]:15.5f}\n") - - -def print_geometry(StdI: StdIntList) -> None: - """Print geometry.dat for post-processing of correlation functions. - - Writes a ``geometry.dat`` file containing direct lattice vectors, - boundary phases, the supercell box matrix, and site coordinates - relative to the first cell. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. The following fields are read: - - - ``solver`` : str -- solver name. - - ``calcmode`` : str -- calculation mode (HWAVE only). - - ``direct`` : ndarray (3x3) -- direct lattice vectors. - - ``phase`` : ndarray (3,) -- boundary phase angles. - - ``box`` : ndarray (3x3) -- supercell box matrix. - - ``NCell`` : int -- number of unit cells. - - ``NsiteUC`` : int -- sites per unit cell. - - ``Cell`` : ndarray (NCell, 3) -- cell fractional coordinates. - - ``model`` : str -- model name (``"kondo"`` doubles sites). - """ - if (StdI.solver == SolverType.HWAVE - and StdI.calcmode in ("uhfk", "rpa")): - return - - with open("geometry.dat", "w") as fp: - for row in StdI.direct: - fp.write(f"{row[0]:25.15e} {row[1]:25.15e} {row[2]:25.15e}\n") - fp.write(f"{StdI.phase[0]:25.15e} " - f"{StdI.phase[1]:25.15e} " - f"{StdI.phase[2]:25.15e}\n") - for row in StdI.box: - fp.write(f"{int(row[0])} {int(row[1])} {int(row[2])}\n") - - for iCell in range(StdI.NCell): - diff = _cell_diff(StdI.Cell, iCell, 0) - for isite in range(StdI.NsiteUC): - fp.write(f"{diff[0]} {diff[1]} {diff[2]} {isite}\n") - if StdI.model == ModelType.KONDO: - for iCell in range(StdI.NCell): - diff = _cell_diff(StdI.Cell, iCell, 0) - for isite in range(StdI.NsiteUC): - fp.write(f"{diff[0]} {diff[1]} {diff[2]} " - f"{isite + StdI.NsiteUC}\n") diff --git a/python/lattice/honeycomb_lattice.py b/python/lattice/honeycomb_lattice.py deleted file mode 100644 index 826fb49..0000000 --- a/python/lattice/honeycomb_lattice.py +++ /dev/null @@ -1,270 +0,0 @@ -""" -Standard mode for the honeycomb lattice. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import math - -import numpy as np - -from stdface_vals import StdIntList, ModelType -from param_check import ( - exit_program, print_val_d, print_val_i, - not_used_j, not_used_d, not_used_i, -) -from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - compute_max_interactions, malloc_interactions, - add_neighbor_interaction, add_local_terms, -) -from .site_util import ( - init_site, set_label, set_local_spin_flags, - lattice_gp, -) -from .boost_output import ( - write_boost_mag_field, write_boost_j_symmetric, - write_boost_6spin_star, write_boost_6spin_pair, -) - - -def honeycomb(StdI: StdIntList) -> None: - """Setup a Hamiltonian for the honeycomb lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - # (1) Compute the shape of the super-cell and sites in the super-cell - with lattice_gp(StdI) as fp: - - StdI.NsiteUC = 2 - - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], StdI.length[1] * 0.5) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1] * 0.5 * math.sqrt(3.0)) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) - - init_site(StdI, fp, 2) - StdI.tau[0, 0] = 0.0 - StdI.tau[0, 1] = 0.0 - StdI.tau[0, 2] = 0.0 - StdI.tau[1, 0] = 1.0 / 3.0 - StdI.tau[1, 1] = 1.0 / 3.0 - StdI.tau[1, 2] = 0.0 - - # (2) check & store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") - input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") - input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J1pp, StdI.J1ppAll, "J1''") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J2pp, StdI.J2ppAll, "J2''") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t", StdI.t) - not_used_d("t0", StdI.t0) - not_used_d("t1", StdI.t1) - not_used_d("t2", StdI.t2) - not_used_d("t'", StdI.tp) - not_used_d("t0'", StdI.t0p) - not_used_d("t1'", StdI.t1p) - not_used_d("t2'", StdI.t2p) - not_used_d("V", StdI.V) - not_used_d("V0", StdI.V0) - not_used_d("V1", StdI.V1) - not_used_d("V2", StdI.V2) - not_used_d("V'", StdI.Vp) - not_used_d("V0'", StdI.V0p) - not_used_d("V1'", StdI.V1p) - not_used_d("V2'", StdI.V2p) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") - StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") - StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") - StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") - StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") - StdI.t0pp = input_hopp(StdI.tpp, StdI.t0pp, "t0''") - StdI.t1pp = input_hopp(StdI.tpp, StdI.t1pp, "t1''") - StdI.t2pp = input_hopp(StdI.tpp, StdI.t2pp, "t2''") - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") - StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") - StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") - StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") - StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") - StdI.V0pp = input_coulomb_v(StdI.Vpp, StdI.V0pp, "V0''") - StdI.V1pp = input_coulomb_v(StdI.Vpp, StdI.V1pp, "V1''") - StdI.V2pp = input_coulomb_v(StdI.Vpp, StdI.V2pp, "V2''") - StdI.Vp = print_val_d("V'", StdI.Vp, 0.0) - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J'", StdI.JpAll, StdI.Jp) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - else: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and number of sites - set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) - - # (4) Compute upper limit of Transfer & Interaction - # nn=3, nnn=6, nnnn=3 → n_bonds=12 - ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=3 + 6 + 3) - malloc_interactions(StdI, ntransMax, nintrMax) - - # (5) Set Transfer & Interaction - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - - # Local term - isite = StdI.NsiteUC * kCell - if StdI.model == ModelType.KONDO: - isite += StdI.NsiteUC * StdI.NCell - jsite_base = StdI.NsiteUC * kCell - for isiteUC in range(StdI.NsiteUC): - add_local_terms(StdI, isite + isiteUC, jsite_base + isiteUC) - - # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) - _BONDS = ( - # Nearest neighbor (nn=1) - (0, 0, 0, 1, 1, StdI.J0, StdI.t0, StdI.V0), # intra cell - (1, 0, 1, 0, 1, StdI.J1, StdI.t1, StdI.V1), # along W - (0, 1, 1, 0, 1, StdI.J2, StdI.t2, StdI.V2), # along L - # Second nearest neighbor (nn=2) - (1, 0, 0, 0, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along W, 0->0 - (1, 0, 1, 1, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along W, 1->1 - (0, 1, 0, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # along L, 0->0 - (0, 1, 1, 1, 2, StdI.J1p, StdI.t1p, StdI.V1p), # along L, 1->1 - (1, -1, 0, 0, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along W-L, 0->0 - (1, -1, 1, 1, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along W-L, 1->1 - # Third nearest neighbor (nn=3) - (1, -1, 0, 1, 3, StdI.J1pp, StdI.t1pp, StdI.V1pp), # along W-L - (-1, -1, 0, 1, 3, StdI.J0pp, StdI.t0pp, StdI.V0pp), # along -W-L - (-1, 1, 0, 1, 3, StdI.J2pp, StdI.t2pp, StdI.V2pp), # along -W+L - ) - for dW, dL, si, sj, nn, J, t, V in _BONDS: - add_neighbor_interaction( - StdI, fp, iW, iL, dW, dL, si, sj, nn, J, t, V) - - -def honeycomb_boost(StdI: StdIntList) -> None: - """Setup a boosted Hamiltonian for the honeycomb lattice (HPhi only). - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - if StdI.box[0, 1] != 0 or StdI.box[1, 0] != 0: - print("\nERROR ! (a0W, a0L, a1W, a1L) can not be used with SpinGCBoost.\n") - exit_program(-1) - - if np.any(np.abs(StdI.Jp) > 1.0e-8): - print("\nERROR ! J' can not be used with SpinGCBoost.\n") - exit_program(-1) - - # Magnetic field - with open("boost.def", "w") as fp: - write_boost_mag_field(fp, StdI) - - # Interaction - fp.write(f"{3} # Number of type of J\n") - fp.write("# J 0\n") - write_boost_j_symmetric(fp, StdI.J0) - fp.write("# J 1\n") - write_boost_j_symmetric(fp, StdI.J1) - fp.write("# J 2\n") - write_boost_j_symmetric(fp, StdI.J2) - - # Topology - if StdI.S2 != 1: - print("\n ERROR! S2 must be 1 in Boost. \n") - exit_program(-1) - StdI.ishift_nspin = 3 - if StdI.L < 2: - print("\n ERROR! L < 2 \n") - exit_program(-1) - if StdI.W % StdI.ishift_nspin != 0: - print(f"\n ERROR! W %% {StdI.ishift_nspin} != 0 \n") - exit_program(-1) - StdI.num_pivot = 2 - if StdI.W != 3: - print("DEBUG: W != 3") - exit_program(-1) - StdI.W = 6 - fp.write("# W0 R0 StdI->num_pivot StdI->ishift_nspin\n") - fp.write(f"{StdI.W} {StdI.L} {StdI.num_pivot} {StdI.ishift_nspin}\n") - - # 6-spin star list - StdI.list_6spin_star = np.zeros((StdI.num_pivot, 7), dtype=int) - - StdI.list_6spin_star[0, :] = [5, 1, 1, 1, 2, 1, 1] - StdI.list_6spin_star[1, :] = [4, 1, 1, 1, 2, 2, 1] - - write_boost_6spin_star(fp, StdI) - - # 6-spin pair list - max_kintr = max(StdI.list_6spin_star[ip, 0] for ip in range(StdI.num_pivot)) - StdI.list_6spin_pair = np.zeros((StdI.num_pivot, 7, max_kintr), dtype=int) - - # pivot 0 - StdI.list_6spin_pair[0, :, 0] = [0, 1, 2, 3, 4, 5, 1] - StdI.list_6spin_pair[0, :, 1] = [1, 2, 0, 3, 4, 5, 2] - StdI.list_6spin_pair[0, :, 2] = [2, 3, 0, 1, 4, 5, 1] - StdI.list_6spin_pair[0, :, 3] = [0, 4, 1, 2, 3, 5, 2] - StdI.list_6spin_pair[0, :, 4] = [1, 5, 0, 2, 3, 4, 3] - - # pivot 1 - StdI.list_6spin_pair[1, :, 0] = [0, 1, 2, 3, 4, 5, 2] - StdI.list_6spin_pair[1, :, 1] = [1, 2, 0, 3, 4, 5, 1] - StdI.list_6spin_pair[1, :, 2] = [0, 4, 1, 2, 3, 5, 3] - StdI.list_6spin_pair[1, :, 3] = [2, 5, 0, 1, 3, 4, 3] - - write_boost_6spin_pair(fp, StdI) diff --git a/python/lattice/input_params.py b/python/lattice/input_params.py deleted file mode 100644 index 655660d..0000000 --- a/python/lattice/input_params.py +++ /dev/null @@ -1,301 +0,0 @@ -"""Input parameter resolution helpers for spin, Coulomb, and hopping. - -This module provides functions that resolve user-specified input parameters -into concrete values, handling conflicts between isotropic and anisotropic -specifications. These are used by all lattice modules when setting up -Hamiltonian parameters. - -Functions ---------- -input_spin_nn - Resolve nearest-neighbour spin-spin interaction (handles J/J0 conflicts). -input_spin - Resolve spin-spin interaction for beyond-nearest-neighbour bonds. -input_coulomb_v - Resolve off-site Coulomb interaction. -input_hopp - Resolve hopping integral. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import itertools -import math - -import numpy as np - -from param_check import exit_program, SPIN_SUFFIXES - - - -# --------------------------------------------------------------------------- -# Vectorised conflict-detection helpers -# --------------------------------------------------------------------------- - - -def _has_set_elements(mat: np.ndarray) -> bool: - """Return True if *mat* contains any non-NaN elements. - - Parameters - ---------- - mat : numpy.ndarray - A 3×3 float array. - - Returns - ------- - bool - True if at least one element is finite (not NaN). - """ - return bool(np.any(~np.isnan(mat))) - - -def _first_set_index(mat: np.ndarray) -> tuple[int, int] | None: - """Return the ``(row, col)`` of the first non-NaN element, or None. - - Parameters - ---------- - mat : numpy.ndarray - A 3×3 float array. - - Returns - ------- - tuple[int, int] or None - Index of the first set element, or None if all NaN. - """ - mask = ~np.isnan(mat) - if not np.any(mask): - return None - idx = np.argmax(mask) # index of first True in flattened array - return int(idx // 3), int(idx % 3) - - -def _check_scalar_vs_matrix(scalar: float, mat: np.ndarray, - scalar_name: str, mat_name: str) -> None: - """Abort if both a scalar and any matrix element are set. - - Parameters - ---------- - scalar : float - Isotropic scalar value (NaN means unset). - mat : numpy.ndarray - 3×3 anisotropic matrix (NaN elements are unset). - scalar_name : str - Display name for the scalar in error messages. - mat_name : str - Base name for the matrix in error messages. - """ - if math.isnan(scalar): - return - idx = _first_set_index(mat) - if idx is not None: - i1, i2 = idx - print(f"\n ERROR! {scalar_name} and {mat_name}{SPIN_SUFFIXES[i1][i2]} conflict !\n") - exit_program(-1) - - -def _check_matrix_vs_matrix(mat_a: np.ndarray, mat_b: np.ndarray, - name_a: str, name_b: str) -> None: - """Abort if any element in *mat_a* and any element in *mat_b* are both set. - - Parameters - ---------- - mat_a : numpy.ndarray - First 3×3 matrix (NaN elements are unset). - mat_b : numpy.ndarray - Second 3×3 matrix (NaN elements are unset). - name_a : str - Base name for *mat_a* in error messages. - name_b : str - Base name for *mat_b* in error messages. - """ - idx_a = _first_set_index(mat_a) - idx_b = _first_set_index(mat_b) - if idx_a is not None and idx_b is not None: - i1, i2 = idx_a - i3, i4 = idx_b - print(f"\n ERROR! {name_a}{SPIN_SUFFIXES[i1][i2]} " - f"and {name_b}{SPIN_SUFFIXES[i3][i4]} conflict !\n") - exit_program(-1) - - -def _resolve_spin_matrix( - J0: np.ndarray, - J0name: str, - *, - J: np.ndarray | None = None, - J0All: float = float("nan"), - JAll: float = float("nan"), -) -> None: - """Fill a 3×3 spin interaction matrix in-place using the resolution cascade. - - Priority order for each element ``(i, j)``: - 1. Explicit ``J0[i, j]`` (already set) - 2. Fallback ``J[i, j]`` (if provided and set) - 3. Diagonal ``J0All`` (if ``i == j``) - 4. Diagonal ``JAll`` (if ``i == j``) - 5. Default ``0.0`` - - Parameters - ---------- - J0 : numpy.ndarray - Output 3×3 matrix (modified in-place). - J0name : str - Display name for the interaction. - J : numpy.ndarray or None, optional - Fallback 3×3 matrix. Default is None (no fallback). - J0All : float, optional - Isotropic value for this bond. Default is NaN (unset). - JAll : float, optional - Global isotropic value. Default is NaN (unset). - """ - for i1, i2 in itertools.product(range(3), repeat=2): - resolved = False - if not math.isnan(J0[i1, i2]): - resolved = True - elif J is not None and not math.isnan(J[i1, i2]): - J0[i1, i2] = J[i1, i2] - resolved = True - elif i1 == i2 and not math.isnan(J0All): - J0[i1, i2] = J0All - resolved = True - elif i1 == i2 and not math.isnan(JAll): - J0[i1, i2] = JAll - resolved = True - else: - J0[i1, i2] = 0.0 - - if resolved: - label = J0name + SPIN_SUFFIXES[i1][i2] - print(f" {label:>14s} = {J0[i1, i2]:<10.5f}") - - -def input_spin_nn( - J: np.ndarray, - JAll: float, - J0: np.ndarray, - J0All: float, - J0name: str, -) -> None: - """Input nearest-neighbour spin-spin interaction. - - Resolves conflicts between isotropic and anisotropic specifications - and fills in the output matrix *J0* in-place. - - Parameters - ---------- - J : numpy.ndarray - Anisotropic spin interaction (3x3). - JAll : float - Isotropic interaction. - J0 : numpy.ndarray - Output anisotropic spin interaction (3x3, modified in-place). - J0All : float - Isotropic interaction for this bond. - J0name : str - Name of this spin interaction (e.g. ``"J1"``). - """ - # Scalar-scalar conflict: JAll vs J0All - if not math.isnan(JAll) and not math.isnan(J0All): - print(f"\n ERROR! {J0name} conflict !\n") - exit_program(-1) - - # Scalar-matrix conflicts - _check_scalar_vs_matrix(JAll, J, "J", "J") - _check_scalar_vs_matrix(J0All, J, J0name, "J") - _check_scalar_vs_matrix(J0All, J0, J0name, J0name) - _check_scalar_vs_matrix(JAll, J0, J0name, J0name) - - # Cross-matrix conflict: any J0 element vs any J element - _check_matrix_vs_matrix(J0, J, J0name, "J") - - # Resolve values using the cascade - _resolve_spin_matrix(J0, J0name, J=J, J0All=J0All, JAll=JAll) - - -def input_spin(Jp: np.ndarray, JpAll: float, Jpname: str) -> None: - """Input spin-spin interaction other than nearest-neighbour. - - Parameters - ---------- - Jp : numpy.ndarray - Fully anisotropic spin interaction (3x3, modified in-place). - JpAll : float - Isotropic interaction value. - Jpname : str - Name of this spin interaction (e.g. ``"J'"``). - """ - # Scalar-matrix conflict - _check_scalar_vs_matrix(JpAll, Jp, Jpname, Jpname) - - # Resolve values - _resolve_spin_matrix(Jp, Jpname, J0All=JpAll) - - -def input_coulomb_v(V: float, V0: float, V0name: str) -> float: - """Input off-site Coulomb interaction from the input file. - - Parameters - ---------- - V : float - Isotropic Coulomb interaction (may be NaN). - V0 : float - Specific Coulomb parameter (may be NaN). - V0name : str - Name of the parameter (e.g. ``"V1"``). - - Returns - ------- - float - The resolved value of *V0*. - """ - if not math.isnan(V) and not math.isnan(V0): - print(f"\n ERROR! {V0name} conflicts !\n") - exit_program(-1) - elif not math.isnan(V0): - print(f" {V0name:>15s} = {V0:<10.5f}") - elif not math.isnan(V): - V0 = V - print(f" {V0name:>15s} = {V0:<10.5f}") - else: - V0 = 0.0 - return V0 - - -def input_hopp(t: complex, t0: complex, t0name: str) -> complex: - """Input hopping integral from the input file. - - Parameters - ---------- - t : complex - Isotropic hopping (may have NaN real part). - t0 : complex - Specific hopping parameter (may have NaN real part). - t0name : str - Name of the parameter (e.g. ``"t1"``). - - Returns - ------- - complex - The resolved value of *t0*. - """ - if not math.isnan(t.real) and not math.isnan(t0.real): - print(f"\n ERROR! {t0name} conflicts !\n") - exit_program(-1) - elif not math.isnan(t0.real): - print(f" {t0name:>15s} = {t0.real:<10.5f} {t0.imag:<10.5f}") - elif not math.isnan(t.real): - t0 = t - print(f" {t0name:>15s} = {t0.real:<10.5f} {t0.imag:<10.5f}") - else: - t0 = 0.0 + 0j - return t0 diff --git a/python/lattice/interaction_builder.py b/python/lattice/interaction_builder.py deleted file mode 100644 index 2f67efe..0000000 --- a/python/lattice/interaction_builder.py +++ /dev/null @@ -1,719 +0,0 @@ -"""Interaction term builder functions. - -This module provides functions for constructing one-body (transfer) and -two-body (interaction) Hamiltonian terms, as well as the memory allocation -helper that prepares the arrays these builders populate. - -Functions ---------- -trans - Add a single transfer (one-body) term. -hopping - Add hopping for both spin channels. -hubbard_local - Add intra-Coulomb, magnetic field, and chemical potential. -mag_field - Add longitudinal and transverse magnetic field terms. -intr - Add a general two-body (InterAll) interaction term. -general_j - Add a general 3x3 spin-spin interaction. -coulomb - Add an off-site Coulomb interaction term. -compute_max_interactions - Compute upper limits for transfer and interaction arrays. -malloc_interactions - Allocate arrays for transfer and interaction terms. -add_neighbor_interaction - Label a neighbor bond and add spin or electron interaction terms (2D). -add_neighbor_interaction_3d - Find a neighbor site and add spin or electron interaction terms (3D). -add_local_terms - Add on-site (magnetic field, anisotropy, Hubbard U, Kondo) terms for one site. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import math - -import numpy as np - -from typing import TextIO - -from stdface_vals import StdIntList, ModelType, SolverType, MethodType, ZERO_BODY_EPS, AMPLITUDE_EPS - - -def trans( - StdI: StdIntList, - trans0: complex, - isite: int, - ispin: int, - jsite: int, - jspin: int, -) -> None: - """Add a transfer (one-body) term to the list. - - Appends to ``StdI.trans`` and ``StdI.transindx`` and increments - ``StdI.ntrans``. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - trans0 : complex - Hopping integral t, mu, etc. - isite : int - Site index i for c†_{i σ}. - ispin : int - Spin index σ for c†_{i σ}. - jsite : int - Site index j for c_{j σ'}. - jspin : int - Spin index σ' for c_{j σ'}. - """ - if abs(trans0) < ZERO_BODY_EPS: - return - n = StdI.ntrans - StdI.trans[n] = trans0 - StdI.transindx[n] = [isite, ispin, jsite, jspin] - StdI.ntrans = n + 1 - - -def hopping( - StdI: StdIntList, - trans0: complex, - isite: int, - jsite: int, - dR: np.ndarray, -) -> None: - """Add hopping for both spin channels. - - Both c†_{i σ} c_{j σ} and c†_{j σ} c_{i σ} for every spin channel - (σ) are added. For HPhi time-evolution pump mode, the pump arrays - are populated instead. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - trans0 : complex - Hopping integral t. - isite : int - Site index i. - jsite : int - Site index j. - dR : numpy.ndarray - Distance vector R_i - R_j (shape ``(3,)``). - """ - if (StdI.solver == SolverType.HPhi - and StdI.method == MethodType.TIME_EVOLUTION - and StdI.PumpBody == 1): - for it in range(StdI.Lanczos_max): - Cphase = np.dot(StdI.At[it], dR) - coef = np.exp(-1j * Cphase) - for ispin in range(2): - n = StdI.npump[it] - StdI.pump[it][n] = coef * trans0 - StdI.pumpindx[it][n] = [isite, ispin, jsite, ispin] - n += 1 - - StdI.pump[it][n] = np.conj(coef * trans0) - StdI.pumpindx[it][n] = [jsite, ispin, isite, ispin] - StdI.npump[it] = n + 1 - else: - for ispin in range(2): - trans(StdI, trans0, jsite, ispin, isite, ispin) - trans(StdI, np.conj(trans0), isite, ispin, jsite, ispin) - - -def hubbard_local( - StdI: StdIntList, - mu0: float, - h0: float, - Gamma0: float, - Gamma0_y: float, - U0: float, - isite: int, -) -> None: - """Add intra-Coulomb, magnetic field and chemical potential for itinerant electrons. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - mu0 : float - Chemical potential. - h0 : float - Longitudinal magnetic field. - Gamma0 : float - Transverse magnetic field (x). - Gamma0_y : float - Transverse magnetic field (y). - U0 : float - Intra-site Coulomb potential. - isite : int - Site index. - """ - trans(StdI, mu0 - 0.5 * h0, isite, 0, isite, 0) - trans(StdI, mu0 + 0.5 * h0, isite, 1, isite, 1) - trans(StdI, -0.5 * Gamma0, isite, 1, isite, 0) - trans(StdI, -0.5 * Gamma0, isite, 0, isite, 1) - trans(StdI, -0.5 * 1j * Gamma0_y, isite, 1, isite, 0) - trans(StdI, 0.5 * 1j * Gamma0_y, isite, 0, isite, 1) - - StdI.Cintra[StdI.NCintra] = U0 - StdI.CintraIndx[StdI.NCintra][0] = isite - StdI.NCintra += 1 - - -def mag_field( - StdI: StdIntList, - S2: int, - h: float, - Gamma: float, - Gamma_y: float, - isite: int, -) -> None: - """Add longitudinal and transverse magnetic field terms. - - Uses the Bogoliubov representation for arbitrary spin S. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - S2 : int - Twice the spin moment (2S) at site *isite*. - h : float - Longitudinal magnetic field. - Gamma : float - Transverse magnetic field (x). - Gamma_y : float - Transverse magnetic field (y). - isite : int - Site index. - """ - S = S2 * 0.5 - for ispin in range(S2 + 1): - Sz = S - float(ispin) - # Longitudinal part: -h σ c†_{i σ} c_{i σ} - trans(StdI, -h * Sz, isite, ispin, isite, ispin) - # Transverse part - if ispin > 0: - factor = math.sqrt(S * (S + 1.0) - Sz * (Sz + 1.0)) - trans(StdI, -0.5 * Gamma * factor - 0.5 * 1j * Gamma_y * factor, - isite, ispin, isite, ispin - 1) - trans(StdI, -0.5 * Gamma * factor + 0.5 * 1j * Gamma_y * factor, - isite, ispin - 1, isite, ispin) - - -def intr( - StdI: StdIntList, - intr0: complex, - site1: int, spin1: int, - site2: int, spin2: int, - site3: int, spin3: int, - site4: int, spin4: int, -) -> None: - """Add a general two-body (InterAll) interaction term to the list. - - Appends to ``StdI.intr`` and ``StdI.intrindx`` and increments - ``StdI.nintr``. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - intr0 : complex - Interaction coefficient U, V, J, etc. - site1, spin1 : int - Indices for c†_{i₁ σ₁}. - site2, spin2 : int - Indices for c_{i₂ σ₂}. - site3, spin3 : int - Indices for c†_{i₃ σ₃}. - site4, spin4 : int - Indices for c_{i₄ σ₄}. - """ - if abs(intr0) < ZERO_BODY_EPS: - return - n = StdI.nintr - StdI.intr[n] = intr0 - StdI.intrindx[n] = [site1, spin1, site2, spin2, site3, spin3, site4, spin4] - StdI.nintr = n + 1 - - -def _spin_ladder_factor(S: float, Sz: float) -> float: - """Compute the spin-ladder matrix element sqrt(S(S+1) - Sz(Sz+1)). - - This is the factor ```` appearing in the - spin-raising and lowering operator matrix elements. - - Parameters - ---------- - S : float - Total spin quantum number. - Sz : float - z-component of spin. - - Returns - ------- - float - ``sqrt(S(S+1) - Sz(Sz+1))`` - """ - return math.sqrt(S * (S + 1.0) - Sz * (Sz + 1.0)) - - -def _add_spin_half_terms( - StdI: StdIntList, - J: np.ndarray, - isite: int, - jsite: int, -) -> tuple[bool, bool]: - """Add shortcut Hund/Cinter/Ex/PairLift terms for spin-1/2 sites. - - When at least one site is spin-1/2, the Ising (Jzz) part is handled - via dedicated Hund and Cinter arrays, and the transverse part may be - handled via Ex and PairLift arrays if the off-diagonal J components - are negligible. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - J : numpy.ndarray - 3x3 spin interaction matrix. - isite : int - First site index. - jsite : int - Second site index. - - Returns - ------- - tuple of (bool, bool) - ``(use_z_general, use_ex_general)`` — flags indicating whether the - general intr() path should still handle the Jzz and exchange terms, - respectively. - """ - # Hund: -0.5 * Jzz - n = StdI.NHund - StdI.Hund[n] = -0.5 * J[2, 2] - StdI.HundIndx[n] = [isite, jsite] - StdI.NHund = n + 1 - - # Cinter: -0.25 * Jzz - n = StdI.NCinter - StdI.Cinter[n] = -0.25 * J[2, 2] - StdI.CinterIndx[n] = [isite, jsite] - StdI.NCinter = n + 1 - - # Check whether off-diagonal J elements allow Ex/PairLift shortcut - cond_offdiag = (abs(J[0, 1]) < AMPLITUDE_EPS and abs(J[1, 0]) < AMPLITUDE_EPS) - if StdI.solver == SolverType.mVMC: - cond_offdiag = cond_offdiag and (abs(J[0, 0] - J[1, 1]) < AMPLITUDE_EPS) - - if not cond_offdiag: - return False, True # z handled by shortcut, ex goes through general - - # Exchange - if StdI.solver == SolverType.mVMC or StdI.model == ModelType.KONDO: - StdI.Ex[StdI.NEx] = -0.25 * (J[0, 0] + J[1, 1]) - else: - StdI.Ex[StdI.NEx] = 0.25 * (J[0, 0] + J[1, 1]) - StdI.ExIndx[StdI.NEx] = [isite, jsite] - StdI.NEx += 1 - - # Pair lift - n = StdI.NPairLift - StdI.PairLift[n] = 0.25 * (J[0, 0] - J[1, 1]) - StdI.PLIndx[n] = [isite, jsite] - StdI.NPairLift = n + 1 - - return False, False # both handled by shortcut - - -def general_j( - StdI: StdIntList, - J: np.ndarray, - Si2: int, - Sj2: int, - isite: int, - jsite: int, -) -> None: - """Treat J as a 3x3 matrix for general spin interactions. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - J : numpy.ndarray - 3x3 spin interaction matrix. - Si2 : int - Twice the spin moment (2S) at site *isite*. - Sj2 : int - Twice the spin moment (2S) at site *jsite*. - isite : int - First site index. - jsite : int - Second site index. - """ - if Si2 == 1 or Sj2 == 1: - use_z, use_ex = _add_spin_half_terms(StdI, J, isite, jsite) - else: - use_z, use_ex = True, True - - Si = 0.5 * Si2 - Sj = 0.5 * Sj2 - - for ispin in range(Si2 + 1): - Siz = Si - float(ispin) - for jspin in range(Sj2 + 1): - Sjz = Sj - float(jspin) - - # (1) J_z S_{iz} S_{jz} - if use_z: - intr0 = J[2, 2] * Siz * Sjz - intr(StdI, intr0, - isite, ispin, isite, ispin, - jsite, jspin, jsite, jspin) - - if ispin > 0 and jspin > 0 and use_ex: - fi = _spin_ladder_factor(Si, Siz) - fj = _spin_ladder_factor(Sj, Sjz) - - # (2) S_i^+ S_j^- + h.c. - intr0 = 0.25 * (J[0, 0] + J[1, 1] + 1j * (J[0, 1] - J[1, 0])) * fi * fj - intr(StdI, intr0, - isite, ispin - 1, isite, ispin, - jsite, jspin, jsite, jspin - 1) - intr(StdI, np.conj(intr0), - isite, ispin, isite, ispin - 1, - jsite, jspin - 1, jsite, jspin) - - # (3) S_i^+ S_j^+ + h.c. - intr0 = 0.25 * (J[0, 0] - J[1, 1] - 1j * (J[0, 1] + J[1, 0])) * fi * fj - intr(StdI, intr0, - isite, ispin - 1, isite, ispin, - jsite, jspin - 1, jsite, jspin) - intr(StdI, np.conj(intr0), - isite, ispin, isite, ispin - 1, - jsite, jspin, jsite, jspin - 1) - - # (4) S_i^+ S_{jz} + h.c. - if ispin > 0: - fi = _spin_ladder_factor(Si, Siz) - intr0 = 0.5 * (J[0, 2] - 1j * J[1, 2]) * fi * Sjz - intr(StdI, intr0, - isite, ispin - 1, isite, ispin, - jsite, jspin, jsite, jspin) - intr(StdI, np.conj(intr0), - jsite, jspin, jsite, jspin, - isite, ispin, isite, ispin - 1) - - # (5) S_{iz} S_j^+ + h.c. - if jspin > 0: - fj = _spin_ladder_factor(Sj, Sjz) - intr0 = 0.5 * (J[2, 0] - 1j * J[2, 1]) * Siz * fj - intr(StdI, intr0, - isite, ispin, isite, ispin, - jsite, jspin - 1, jsite, jspin) - intr(StdI, np.conj(intr0), - jsite, jspin, jsite, jspin - 1, - isite, ispin, isite, ispin) - - -def coulomb(StdI: StdIntList, V: float, isite: int, jsite: int) -> None: - """Add an onsite/offsite Coulomb interaction term. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - V : float - Coulomb integral U, V, etc. - isite : int - First site index. - jsite : int - Second site index. - """ - n = StdI.NCinter - StdI.Cinter[n] = V - StdI.CinterIndx[n] = [isite, jsite] - StdI.NCinter = n + 1 - - -def compute_max_interactions( - StdI: StdIntList, - n_bonds: int, -) -> tuple[int, int]: - """Compute upper limits for the transfer and interaction arrays. - - Uses the standard formula shared by most lattice builders. The - lattice-specific information is captured by *n_bonds*, the total - number of distinct neighbor bond types per unit cell (sum of - nearest, next-nearest, and third-nearest neighbor bonds). - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (read-only; uses ``model``, ``nsite``, - ``NCell``, ``NsiteUC``, and ``S2``). - n_bonds : int - Total number of neighbor bond types per unit cell - (e.g., ``3 + 6 + 4 = 13`` for orthorhombic). - - Returns - ------- - tuple of (int, int) - ``(ntransMax, nintrMax)`` — upper limits for allocation. - """ - if StdI.model == ModelType.SPIN: - ntransMax = StdI.nsite * (StdI.S2 + 1 + 2 * StdI.S2) - nintrMax = (StdI.NCell * (StdI.NsiteUC + n_bonds) - * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1)) - else: - ntransMax = StdI.NCell * 2 * (2 * StdI.NsiteUC + 2 * n_bonds) - nintrMax = StdI.NCell * (StdI.NsiteUC + 4 * n_bonds) - if StdI.model == ModelType.KONDO: - ntransMax += StdI.nsite // 2 * (StdI.S2 + 1 + 2 * StdI.S2) - nintrMax += (StdI.nsite // 2 - * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1)) - return ntransMax, nintrMax - - -def malloc_interactions(StdI: StdIntList, ntransMax: int, nintrMax: int) -> None: - """Allocate arrays for interactions. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - ntransMax : int - Upper limit of the number of transfer terms. - nintrMax : int - Upper limit of the number of interaction terms. - """ - # (1) Transfer - StdI.transindx = np.zeros((ntransMax, 4), dtype=int) - StdI.trans = np.zeros(ntransMax, dtype=complex) - StdI.ntrans = 0 - - # HPhi pump arrays - if (StdI.solver == SolverType.HPhi - and StdI.method == MethodType.TIME_EVOLUTION - and StdI.PumpBody == 1): - StdI.npump = np.zeros(StdI.Lanczos_max, dtype=int) - StdI.pumpindx = np.zeros((StdI.Lanczos_max, ntransMax, 4), dtype=int) - StdI.pump = np.zeros((StdI.Lanczos_max, ntransMax), dtype=complex) - - # (2) InterAll - StdI.intrindx = np.zeros((nintrMax, 8), dtype=int) - StdI.intr = np.zeros(nintrMax, dtype=complex) - StdI.nintr = 0 - - # (3)-(8) Two-body shortcut arrays: (indx_attr, val_attr, count_attr, ncols) - _SHORTCUT_ARRAYS = ( - ("CintraIndx", "Cintra", "NCintra", 1), - ("CinterIndx", "Cinter", "NCinter", 2), - ("HundIndx", "Hund", "NHund", 2), - ("ExIndx", "Ex", "NEx", 2), - ("PLIndx", "PairLift", "NPairLift", 2), - ("PHIndx", "PairHopp", "NPairHopp", 2), - ) - for indx_attr, val_attr, count_attr, ncols in _SHORTCUT_ARRAYS: - setattr(StdI, indx_attr, np.zeros((nintrMax, ncols), dtype=int)) - setattr(StdI, val_attr, np.zeros(nintrMax)) - setattr(StdI, count_attr, 0) - - -def _dispatch_bond_interaction( - StdI: StdIntList, - isite: int, jsite: int, - Cphase: complex, dR: np.ndarray, - J: np.ndarray, t: complex, V: float, -) -> None: - """Apply the model-dependent interaction for a single bond. - - For spin models calls :func:`general_j`; for electron models calls - :func:`hopping` and :func:`coulomb`. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - isite, jsite : int - Global site indices of the two bond endpoints. - Cphase : complex - Boundary phase factor. - dR : numpy.ndarray - Distance vector R_i − R_j (shape ``(3,)``). - J : numpy.ndarray - 3×3 spin-coupling matrix (used when model is SPIN). - t : complex - Hopping amplitude *before* phase multiplication (used otherwise). - V : float - Coulomb repulsion (used otherwise). - """ - if StdI.model == ModelType.SPIN: - general_j(StdI, J, StdI.S2, StdI.S2, isite, jsite) - else: - hopping(StdI, Cphase * t, isite, jsite, dR) - coulomb(StdI, V, isite, jsite) - - -def add_neighbor_interaction( - StdI: StdIntList, - fp: TextIO | None, - iW: int, iL: int, - diW: int, diL: int, - isiteUC: int, jsiteUC: int, - connect: int, - J: np.ndarray, - t: complex, - V: float, -) -> tuple[int, int, complex, np.ndarray]: - """Label a neighbor bond and add the appropriate model interaction. - - Combines ``set_label`` with the model-dependent dispatch that appears - in every lattice builder: for spin models calls ``general_j``; for - electron models calls ``hopping`` + ``coulomb``. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - fp : TextIO or None - Gnuplot file handle (may be ``None``). - iW, iL : int - Cell position of the initial site. - diW, diL : int - Translation to the neighbor. - isiteUC, jsiteUC : int - Unit-cell site indices for initial and final sites. - connect : int - Connection type for gnuplot (1 = nearest, 2 = 2nd, 3+ = no arrow). - J : numpy.ndarray - 3×3 spin-coupling matrix (used when model is SPIN). - t : complex - Hopping amplitude *before* phase multiplication (used otherwise). - V : float - Coulomb repulsion (used otherwise). - - Returns - ------- - isite : int - Global index of the initial site. - jsite : int - Global index of the final site. - Cphase : complex - Boundary phase factor. - dR : numpy.ndarray - Distance vector R_i − R_j. - """ - from .site_util import set_label # local import to avoid circular dependency - - isite, jsite, Cphase, dR = set_label( - StdI, fp, iW, iL, diW, diL, isiteUC, jsiteUC, connect) - _dispatch_bond_interaction(StdI, isite, jsite, Cphase, dR, J, t, V) - return isite, jsite, Cphase, dR - - -def add_neighbor_interaction_3d( - StdI: StdIntList, - iW: int, iL: int, iH: int, - diW: int, diL: int, diH: int, - isiteUC: int, jsiteUC: int, - J: np.ndarray, - t: complex, - V: float, -) -> tuple[int, int, complex, np.ndarray]: - """Find a 3D neighbor site and add the appropriate model interaction. - - Combines ``find_site`` with the model-dependent dispatch: for spin - models calls ``general_j``; for electron models calls ``hopping`` + - ``coulomb``. This is the 3D counterpart of - :func:`add_neighbor_interaction`. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - iW, iL, iH : int - Cell position of the initial site. - diW, diL, diH : int - Translation to the neighbor. - isiteUC, jsiteUC : int - Unit-cell site indices for initial and final sites. - J : numpy.ndarray - 3×3 spin-coupling matrix (used when model is SPIN). - t : complex - Hopping amplitude *before* phase multiplication (used otherwise). - V : float - Coulomb repulsion (used otherwise). - - Returns - ------- - isite : int - Global index of the initial site. - jsite : int - Global index of the final site. - Cphase : complex - Boundary phase factor. - dR : numpy.ndarray - Distance vector R_i − R_j. - """ - from .site_util import find_site # local import to avoid circular dependency - - isite, jsite, Cphase, dR = find_site( - StdI, iW, iL, iH, diW, diL, diH, isiteUC, jsiteUC) - _dispatch_bond_interaction(StdI, isite, jsite, Cphase, dR, J, t, V) - return isite, jsite, Cphase, dR - - -def add_local_terms( - StdI: StdIntList, - isite: int, - jsite_kondo: int, -) -> None: - """Add on-site interaction terms for a single site. - - Handles the model-dependent local terms that every lattice builder - applies to each site: - - * **SPIN**: magnetic field + single-ion anisotropy ``D``. - * **HUBBARD**: Hubbard ``U``, chemical potential, and magnetic field. - * **KONDO**: same as HUBBARD for the itinerant site, plus a Kondo - coupling ``J`` and magnetic field on the localized spin site - *jsite_kondo*. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - isite : int - Global site index. For KONDO models this is the itinerant - (electron) site; for SPIN this is the spin site. - jsite_kondo : int - Global site index of the localized spin in the Kondo model. - Ignored when the model is not KONDO. - """ - if StdI.model == ModelType.SPIN: - mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, isite) - general_j(StdI, StdI.D, StdI.S2, StdI.S2, isite, isite) - else: - hubbard_local(StdI, StdI.mu, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, - StdI.U, isite) - if StdI.model == ModelType.KONDO: - general_j(StdI, StdI.J, 1, StdI.S2, isite, jsite_kondo) - mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, - jsite_kondo) diff --git a/python/lattice/kagome.py b/python/lattice/kagome.py deleted file mode 100644 index 148dde6..0000000 --- a/python/lattice/kagome.py +++ /dev/null @@ -1,281 +0,0 @@ -""" -Standard mode for the kagome lattice. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import math - -import numpy as np - -from stdface_vals import StdIntList, ModelType -from param_check import ( - exit_program, print_val_d, print_val_i, - not_used_j, not_used_d, not_used_i, -) -from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - compute_max_interactions, malloc_interactions, - add_neighbor_interaction, add_local_terms, -) -from .site_util import ( - init_site, set_local_spin_flags, - lattice_gp, -) -from .boost_output import ( - write_boost_mag_field, write_boost_j_symmetric, - write_boost_6spin_star, write_boost_6spin_pair, -) - - -def kagome(StdI: StdIntList) -> None: - """Setup a Hamiltonian for the kagome lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - # (1) Compute the shape of the super-cell and sites in the super-cell - with lattice_gp(StdI) as fp: - - StdI.NsiteUC = 3 - - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], StdI.length[1] * 0.5) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1] * 0.5 * math.sqrt(3.0)) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) - - init_site(StdI, fp, 2) - StdI.tau[0, 0] = 0.0 - StdI.tau[0, 1] = 0.0 - StdI.tau[0, 2] = 0.0 - StdI.tau[1, 0] = 0.5 - StdI.tau[1, 1] = 0.0 - StdI.tau[1, 2] = 0.0 - StdI.tau[2, 0] = 0.0 - StdI.tau[2, 1] = 0.5 - StdI.tau[2, 2] = 0.0 - - # (2) check & store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") - input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") - input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t", StdI.t) - not_used_d("t0", StdI.t) - not_used_d("t0", StdI.t0) - not_used_d("t1", StdI.t1) - not_used_d("t2", StdI.t2) - not_used_d("t'", StdI.tp) - not_used_d("t0'", StdI.t0p) - not_used_d("t1'", StdI.t1p) - not_used_d("t2'", StdI.t2p) - not_used_d("V", StdI.V) - not_used_d("V0", StdI.V0) - not_used_d("V1", StdI.V1) - not_used_d("V2", StdI.V2) - not_used_d("V'", StdI.Vp) - not_used_d("V0'", StdI.V0p) - not_used_d("V1'", StdI.V1p) - not_used_d("V2'", StdI.V2p) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") - StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") - StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") - StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") - StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") - StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") - StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") - StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") - StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J'", StdI.JpAll, StdI.Jp) - not_used_j("J0'", StdI.J0pAll, StdI.J0p) - not_used_j("J1'", StdI.J1pAll, StdI.J1p) - not_used_j("J2'", StdI.J2pAll, StdI.J2p) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - else: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and number of sites - set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) - - # (4) Compute upper limit of Transfer & Interaction - # nn=6, nnn=6 → n_bonds=12 - ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=6 + 6) - malloc_interactions(StdI, ntransMax, nintrMax) - - # (5) Set Transfer & Interaction - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - - # Local term - isite = StdI.NsiteUC * kCell - if StdI.model == ModelType.KONDO: - isite += StdI.nsite // 2 - jsite_base = StdI.NsiteUC * kCell - for isiteUC in range(StdI.NsiteUC): - add_local_terms(StdI, isite + isiteUC, jsite_base + isiteUC) - - # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) - _BONDS = ( - # Nearest neighbor (nn=1) - (0, 0, 0, 1, 1, StdI.J2, StdI.t2, StdI.V2), # intra 0->1 - (0, 0, 0, 2, 1, StdI.J1, StdI.t1, StdI.V1), # intra 0->2 - (0, 0, 1, 2, 1, StdI.J0, StdI.t0, StdI.V0), # intra 1->2 - (1, 0, 1, 0, 1, StdI.J2, StdI.t2, StdI.V2), # along W - (0, 1, 2, 0, 1, StdI.J1, StdI.t1, StdI.V1), # along L - (1, -1, 1, 2, 1, StdI.J0, StdI.t0, StdI.V0), # along W-L - # Second nearest neighbor (nn=2) - (1, 0, 2, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # along W, 2->0 - (1, 0, 1, 2, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along W, 1->2 - (0, 1, 1, 0, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along L, 1->0 - (0, 1, 2, 1, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along L, 2->1 - (1, -1, 0, 2, 2, StdI.J1p, StdI.t1p, StdI.V1p), # along W-L, 0->2 - (-1, 1, 0, 1, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along L-W, 0->1 - ) - for dW, dL, si, sj, nn, J, t, V in _BONDS: - add_neighbor_interaction( - StdI, fp, iW, iL, dW, dL, si, sj, nn, J, t, V) - - -def kagome_boost(StdI: StdIntList) -> None: - """Setup a boosted Hamiltonian for the kagome lattice (HPhi only). - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - if StdI.box[0, 1] != 0 or StdI.box[1, 0] != 0: - print("\nERROR ! (a0W, a0L, a1W, a1L) can not be used with SpinGCBoost.\n") - exit_program(-1) - - if np.any(np.abs(StdI.Jp) > 1.0e-8): - print("\nERROR ! J' can not be used with SpinGCBoost.\n") - exit_program(-1) - - # Magnetic field - with open("boost.def", "w") as fp: - write_boost_mag_field(fp, StdI) - - # Interaction - fp.write(f"{3} # Number of type of J\n") - fp.write("# J 0\n") - write_boost_j_symmetric(fp, StdI.J0) - fp.write("# J 1\n") - write_boost_j_symmetric(fp, StdI.J1) - fp.write("# J 2\n") - write_boost_j_symmetric(fp, StdI.J2) - - # Topology - if StdI.S2 != 1: - print("\n ERROR! S2 must be 1 in Boost. \n") - exit_program(-1) - StdI.ishift_nspin = 3 - if StdI.L < 2: - print("\n ERROR! L < 2 \n") - exit_program(-1) - if StdI.W % StdI.ishift_nspin != 0: - print(f"\n ERROR! W %% {StdI.ishift_nspin} != 0 \n") - exit_program(-1) - StdI.num_pivot = 4 - if StdI.W != 3: - print("DEBUG: W != 3") - exit_program(-1) - StdI.W = 9 - fp.write("# W0 R0 StdI->num_pivot StdI->ishift_nspin\n") - fp.write(f"{StdI.W} {StdI.L} {StdI.num_pivot} {StdI.ishift_nspin}\n") - - # 6-spin star list - StdI.list_6spin_star = np.zeros((StdI.num_pivot, 7), dtype=int) - - StdI.list_6spin_star[0, :] = [1, 1, 1, 1, 4, 2, -1] - StdI.list_6spin_star[1, :] = [6, 1, 1, 1, 6, 7, 1] - StdI.list_6spin_star[2, :] = [6, 1, 1, 1, 4, 2, 1] - StdI.list_6spin_star[3, :] = [5, 1, 1, 1, 4, 2, 1] - - write_boost_6spin_star(fp, StdI) - - # 6-spin pair list - max_kintr = max(StdI.list_6spin_star[ip, 0] for ip in range(StdI.num_pivot)) - StdI.list_6spin_pair = np.zeros((StdI.num_pivot, 7, max_kintr), dtype=int) - - # pivot 0 - StdI.list_6spin_pair[0, :, 0] = [0, 4, 1, 2, 3, 5, 3] - - # pivot 1 - StdI.list_6spin_pair[1, :, 0] = [0, 1, 2, 3, 4, 5, 3] - StdI.list_6spin_pair[1, :, 1] = [1, 2, 0, 3, 4, 5, 1] - StdI.list_6spin_pair[1, :, 2] = [0, 2, 1, 3, 4, 5, 2] - StdI.list_6spin_pair[1, :, 3] = [1, 3, 0, 2, 4, 5, 3] - StdI.list_6spin_pair[1, :, 4] = [2, 4, 0, 1, 3, 5, 2] - StdI.list_6spin_pair[1, :, 5] = [2, 5, 0, 1, 3, 4, 1] - - # pivot 2 - StdI.list_6spin_pair[2, :, 0] = [0, 1, 2, 3, 4, 5, 3] - StdI.list_6spin_pair[2, :, 1] = [1, 2, 0, 3, 4, 5, 1] - StdI.list_6spin_pair[2, :, 2] = [0, 2, 1, 3, 4, 5, 2] - StdI.list_6spin_pair[2, :, 3] = [1, 3, 0, 2, 4, 5, 3] - StdI.list_6spin_pair[2, :, 4] = [2, 5, 0, 1, 3, 4, 2] - StdI.list_6spin_pair[2, :, 5] = [2, 4, 0, 1, 3, 5, 1] - - # pivot 3 - StdI.list_6spin_pair[3, :, 0] = [0, 1, 2, 3, 4, 5, 3] - StdI.list_6spin_pair[3, :, 1] = [1, 2, 0, 3, 4, 5, 1] - StdI.list_6spin_pair[3, :, 2] = [0, 2, 1, 3, 4, 5, 2] - StdI.list_6spin_pair[3, :, 3] = [2, 5, 0, 1, 3, 4, 2] - StdI.list_6spin_pair[3, :, 4] = [2, 4, 0, 1, 3, 5, 1] - - write_boost_6spin_pair(fp, StdI) diff --git a/python/lattice/ladder.py b/python/lattice/ladder.py deleted file mode 100644 index abcc4e2..0000000 --- a/python/lattice/ladder.py +++ /dev/null @@ -1,282 +0,0 @@ -""" -Standard mode for the ladder lattice. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import numpy as np - -from stdface_vals import StdIntList, ModelType -from param_check import ( - exit_program, print_val_d, print_val_i, - not_used_j, not_used_d, not_used_i, required_val_i, -) -from .input_params import input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - malloc_interactions, - add_neighbor_interaction, add_local_terms, -) -from .site_util import ( - init_site, set_label, set_local_spin_flags, - lattice_gp, -) -from .boost_output import ( - write_boost_mag_field, write_boost_j_symmetric, - write_boost_6spin_star, write_boost_6spin_pair, -) - - -def ladder(StdI: StdIntList) -> None: - """Setup a Hamiltonian for the ladder lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - with lattice_gp(StdI) as fp: - - # 1. Set lattice size and shape parameters - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.0) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1]) - - required_val_i("L", StdI.L) - required_val_i("W", StdI.W) - - not_used_i("a0W", StdI.box[0, 0]) - not_used_i("a0L", StdI.box[0, 1]) - not_used_i("a1W", StdI.box[1, 0]) - not_used_i("a1L", StdI.box[1, 1]) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - not_used_d("phase1", StdI.phase[1]) - StdI.phase[1] = StdI.phase[0] - StdI.phase[0] = 0.0 - - StdI.NsiteUC = StdI.W - StdI.W = 1 - StdI.direct[0, 0] = float(StdI.NsiteUC) - init_site(StdI, fp, 2) - - for isite in range(StdI.NsiteUC): - StdI.tau[isite, 0] = float(isite) / float(StdI.NsiteUC) - StdI.tau[isite, 1] = 0.0 - StdI.tau[isite, 2] = 0.0 - - # 2. Set Hamiltonian parameters - print("\n @ Hamiltonian \n") - - not_used_j("J", StdI.JAll, StdI.J) - not_used_j("J'", StdI.JpAll, StdI.Jp) - not_used_d("t", StdI.t) - not_used_d("t'", StdI.tp) - not_used_d("V", StdI.V) - not_used_d("V'", StdI.Vp) - not_used_d("K", StdI.K) - - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin(StdI.J0, StdI.J0All, "J0") - input_spin(StdI.J1, StdI.J1All, "J1") - input_spin(StdI.J2, StdI.J2All, "J2") - input_spin(StdI.J1p, StdI.J1pAll, "J1'") - input_spin(StdI.J2p, StdI.J2pAll, "J2'") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t0", StdI.t0) - not_used_d("t1", StdI.t1) - not_used_d("t2", StdI.t2) - not_used_d("t1'", StdI.t1p) - not_used_d("t2'", StdI.t2p) - not_used_d("V0", StdI.V0) - not_used_d("V1", StdI.V1) - not_used_d("V2", StdI.V2) - not_used_d("V1'", StdI.V1p) - not_used_d("V2'", StdI.V2p) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") - StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") - StdI.t1p = input_hopp(StdI.t, StdI.t1p, "t1'") - StdI.t2p = input_hopp(StdI.t, StdI.t2p, "t2'") - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") - StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") - StdI.V1p = input_coulomb_v(StdI.V, StdI.V1p, "V1'") - StdI.V2p = input_coulomb_v(StdI.V, StdI.V2p, "V2'") - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J1p", StdI.J1pAll, StdI.J1p) - not_used_j("J2p", StdI.J2pAll, StdI.J2p) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - else: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # 3. Set local spin flags and number of sites - set_local_spin_flags(StdI, StdI.L * StdI.NsiteUC) - - # 4. Calculate maximum number of interactions and allocate arrays - if StdI.model == ModelType.SPIN: - ntransMax = StdI.L * StdI.NsiteUC * (StdI.S2 + 1 + 2 * StdI.S2) - nintrMax = (StdI.L * StdI.NsiteUC * (1 + 1 + 1) - * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1) - + StdI.L * (StdI.NsiteUC - 1) * (1 + 1 + 1) - * (3 * StdI.S2 + 1) * (3 * StdI.S2 + 1)) - else: - ntransMax = (StdI.L * StdI.NsiteUC * 2 * (2 + 2 + 2) - + StdI.L * (StdI.NsiteUC - 1) * 2 * (2 + 2 + 2)) - nintrMax = (StdI.L * StdI.NsiteUC * 1 - + StdI.L * StdI.NsiteUC * 4 * (1 + 1) - + StdI.L * (StdI.NsiteUC - 1) * 4 * (1 + 1 + 1)) - if StdI.model == ModelType.KONDO: - ntransMax += StdI.L * StdI.NsiteUC * (StdI.S2 + 1 + 2 * StdI.S2) - nintrMax += StdI.nsite // 2 * (3 * 1 + 1) * (3 * StdI.S2 + 1) - - malloc_interactions(StdI, ntransMax, nintrMax) - - # 5. Set all interactions - for iL in range(StdI.L): - for isiteUC in range(StdI.NsiteUC): - isite = isiteUC + iL * StdI.NsiteUC - if StdI.model == ModelType.KONDO: - isite += StdI.L * StdI.NsiteUC - - # Local terms - add_local_terms(StdI, isite, isiteUC + iL * StdI.NsiteUC) - - # Leg bonds: (dW, dL, sj_offset, nn, J, t, V) - _LEG_BONDS = ( - (0, 1, 0, 1, StdI.J1, StdI.t1, StdI.V1), # nn along ladder - (0, 2, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # nnn along ladder - ) - for dW, dL, sj_off, nn, J, t, V in _LEG_BONDS: - add_neighbor_interaction( - StdI, fp, 0, iL, dW, dL, isiteUC, isiteUC + sj_off, nn, J, t, V) - - # Rung/diagonal bonds (only between adjacent legs) - if isiteUC < StdI.NsiteUC - 1: - _RUNG_BONDS = ( - (0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # vertical - (0, 1, 1, StdI.J2, StdI.t2, StdI.V2), # diagonal 1 - (0, -1, 1, StdI.J2p, StdI.t2p, StdI.V2p), # diagonal 2 - ) - for dW, dL, nn, J, t, V in _RUNG_BONDS: - add_neighbor_interaction( - StdI, fp, 0, iL, dW, dL, isiteUC, isiteUC + 1, nn, J, t, V) - - -def ladder_boost(StdI: StdIntList) -> None: - """Setup a boosted Hamiltonian for the ladder lattice (HPhi only). - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - StdI.W = StdI.NsiteUC - StdI.NsiteUC = 1 - - with open("boost.def", "w") as fp: - # Magnetic field - write_boost_mag_field(fp, StdI) - - # Interaction parameters - fp.write(f"{5} # Number of type of J\n") - - # J1 - Vertical interactions - fp.write("# J 1 (inter chain, vertical)\n") - write_boost_j_symmetric(fp, StdI.J0) - - # J2 - Nearest neighbor along chain - fp.write("# J 2 (Nearest neighbor, along chain)\n") - write_boost_j_symmetric(fp, StdI.J1) - - # J3 - Second nearest neighbor along chain - fp.write("# J 3 (Second nearest neighbor, along chain)\n") - write_boost_j_symmetric(fp, StdI.J1p) - - # J4 - Diagonal 1 - fp.write("# J 4 (inter chain, diagonal1)\n") - write_boost_j_symmetric(fp, StdI.J2) - - # J5 - Diagonal 2 - fp.write("# J 5 (inter chain, diagonal2)\n") - write_boost_j_symmetric(fp, StdI.J2p) - - # Validate parameters - if StdI.S2 != 1: - print("\n ERROR! S2 must be 1 in Boost. \n") - exit_program(-1) - StdI.ishift_nspin = 2 - if StdI.W != 2: - print("\n ERROR! W != 2 \n") - exit_program(-1) - if StdI.L % 2 != 0: - print("\n ERROR! L %% 2 != 0 \n") - exit_program(-1) - if StdI.L < 4: - print("\n ERROR! L < 4 \n") - exit_program(-1) - - StdI.W = StdI.L - StdI.L = 2 - StdI.num_pivot = StdI.W // 2 - - fp.write("# W0 R0 StdI->num_pivot StdI->ishift_nspin\n") - fp.write(f"{StdI.W} {StdI.L} {StdI.num_pivot} {StdI.ishift_nspin}\n") - - # 6-spin star list - StdI.list_6spin_star = np.zeros((StdI.num_pivot, 7), dtype=int) - for ipivot in range(StdI.num_pivot): - StdI.list_6spin_star[ipivot, :] = [7, 1, 1, 1, 1, 1, 1] - - write_boost_6spin_star(fp, StdI) - - # 6-spin pair list - StdI.list_6spin_pair = np.zeros((StdI.num_pivot, 7, 7), dtype=int) - for ipivot in range(StdI.num_pivot): - StdI.list_6spin_pair[ipivot, :, 0] = [0, 1, 2, 3, 4, 5, 1] - StdI.list_6spin_pair[ipivot, :, 1] = [0, 2, 1, 3, 4, 5, 2] - StdI.list_6spin_pair[ipivot, :, 2] = [1, 3, 0, 2, 4, 5, 2] - StdI.list_6spin_pair[ipivot, :, 3] = [0, 4, 1, 2, 3, 5, 3] - StdI.list_6spin_pair[ipivot, :, 4] = [1, 5, 0, 2, 3, 4, 3] - StdI.list_6spin_pair[ipivot, :, 5] = [0, 3, 1, 2, 4, 5, 4] - StdI.list_6spin_pair[ipivot, :, 6] = [1, 2, 0, 3, 4, 5, 5] - - write_boost_6spin_pair(fp, StdI) diff --git a/python/lattice/orthorhombic.py b/python/lattice/orthorhombic.py deleted file mode 100644 index 7f94020..0000000 --- a/python/lattice/orthorhombic.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Standard mode for the orthorhombic lattice. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import numpy as np - -from stdface_vals import StdIntList, ModelType -from param_check import ( - print_val_d, print_val_c, print_val_i, - not_used_j, not_used_d, not_used_i, -) -from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - compute_max_interactions, malloc_interactions, - add_neighbor_interaction_3d, add_local_terms, -) -from .site_util import init_site, set_local_spin_flags, close_lattice_xsf - - -def orthorhombic(StdI: StdIntList) -> None: - """Setup a Hamiltonian for the simple orthorhombic lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - # (1) Compute the shape of the super-cell and sites in the super-cell - StdI.NsiteUC = 1 - - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.length[2] = print_val_d("Hlength", StdI.length[2], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) - StdI.direct[0, 2] = print_val_d("Wz", StdI.direct[0, 2], 0.0) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.0) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1]) - StdI.direct[1, 2] = print_val_d("Lz", StdI.direct[1, 2], 0.0) - StdI.direct[2, 0] = print_val_d("Hx", StdI.direct[2, 0], 0.0) - StdI.direct[2, 1] = print_val_d("Hy", StdI.direct[2, 1], 0.0) - StdI.direct[2, 2] = print_val_d("Hz", StdI.direct[2, 2], StdI.length[2]) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) - StdI.phase[2] = print_val_d("phase2", StdI.phase[2], 0.0) - - init_site(StdI, None, 3) - StdI.tau[0, 0] = 0.0 - StdI.tau[0, 1] = 0.0 - StdI.tau[0, 2] = 0.0 - - # (2) check & store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") - input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") - input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") - input_spin(StdI.Jpp, StdI.JppAll, "J''") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t", StdI.t) - not_used_d("t0", StdI.t0) - not_used_d("t1", StdI.t1) - not_used_d("t2", StdI.t2) - not_used_d("t'", StdI.tp) - not_used_d("t0'", StdI.t0p) - not_used_d("t1'", StdI.t1p) - not_used_d("t2'", StdI.t2p) - not_used_d("t''", StdI.tpp) - not_used_d("V", StdI.V) - not_used_d("V0", StdI.V0) - not_used_d("V1", StdI.V1) - not_used_d("V'", StdI.Vp) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") - StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") - StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") - StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") - StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") - StdI.tpp = print_val_c("tpp", StdI.tpp, 0.0) - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") - StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") - StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") - StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") - StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") - StdI.Vpp = print_val_d("Vpp", StdI.Vpp, 0.0) - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J0'", StdI.J0pAll, StdI.J0p) - not_used_j("J1'", StdI.J1pAll, StdI.J1p) - not_used_j("J2'", StdI.J2pAll, StdI.J2p) - not_used_j("J0''", StdI.J0ppAll, StdI.J0pp) - not_used_j("J1''", StdI.J1ppAll, StdI.J1pp) - not_used_j("J2''", StdI.J2ppAll, StdI.J2pp) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - else: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and number of sites - set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) - - # (4) Compute upper limit of Transfer & Interaction - # nn=3, nnn=6, nnnn=4 → n_bonds=13 - ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=3 + 6 + 4) - malloc_interactions(StdI, ntransMax, nintrMax) - - # (5) Set Transfer & Interaction - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - iH = StdI.Cell[kCell, 2] - - # Local term - isite = kCell - if StdI.model == ModelType.KONDO: - isite += StdI.NCell - add_local_terms(StdI, isite, kCell) - - # Neighbor bonds: (dW, dL, dH, site_i, site_j, J, t, V) - _BONDS = ( - # Nearest neighbor - (1, 0, 0, 0, 0, StdI.J0, StdI.t0, StdI.V0), # along W - (0, 1, 0, 0, 0, StdI.J1, StdI.t1, StdI.V1), # along L - (0, 0, 1, 0, 0, StdI.J2, StdI.t2, StdI.V2), # along H - # Second nearest neighbor - (0, 1, 1, 0, 0, StdI.J0p, StdI.t0p, StdI.V0p), # +L+H - (0, 1, -1, 0, 0, StdI.J0p, StdI.t0p, StdI.V0p), # +L-H - (1, 0, 1, 0, 0, StdI.J1p, StdI.t1p, StdI.V1p), # +H+W - (-1, 0, 1, 0, 0, StdI.J1p, StdI.t1p, StdI.V1p), # +H-W - (1, 1, 0, 0, 0, StdI.J2p, StdI.t2p, StdI.V2p), # +W+L - (1, -1, 0, 0, 0, StdI.J2p, StdI.t2p, StdI.V2p), # +W-L - # Third nearest neighbor - (1, 1, 1, 0, 0, StdI.Jpp, StdI.tpp, StdI.Vpp), # +W+L+H - (-1, 1, 1, 0, 0, StdI.Jpp, StdI.tpp, StdI.Vpp), # -W+L+H - (1, -1, 1, 0, 0, StdI.Jpp, StdI.tpp, StdI.Vpp), # +W-L+H - (1, 1, -1, 0, 0, StdI.Jpp, StdI.tpp, StdI.Vpp), # +W+L-H - ) - for dW, dL, dH, si, sj, J, t, V in _BONDS: - add_neighbor_interaction_3d( - StdI, iW, iL, iH, dW, dL, dH, si, sj, J, t, V) - - close_lattice_xsf(StdI) diff --git a/python/lattice/pyrochlore.py b/python/lattice/pyrochlore.py deleted file mode 100644 index d70367f..0000000 --- a/python/lattice/pyrochlore.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -Standard mode for the pyrochlore lattice. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import numpy as np - -from stdface_vals import StdIntList, ModelType -from param_check import ( - print_val_d, print_val_i, - not_used_j, not_used_d, not_used_i, -) -from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - compute_max_interactions, malloc_interactions, - mag_field, general_j, hubbard_local, - add_neighbor_interaction_3d, -) -from .site_util import init_site, set_local_spin_flags, close_lattice_xsf - - -def pyrochlore(StdI: StdIntList) -> None: - """Setup a Hamiltonian for the pyrochlore lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - # (1) Compute the shape of the super-cell and sites in the super-cell - StdI.NsiteUC = 4 - - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.length[2] = print_val_d("Hlength", StdI.length[2], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], 0.0) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.5 * StdI.length[1]) - StdI.direct[0, 2] = print_val_d("Wz", StdI.direct[0, 2], 0.5 * StdI.length[2]) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.5 * StdI.length[0]) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], 0.0) - StdI.direct[1, 2] = print_val_d("Lz", StdI.direct[1, 2], 0.5 * StdI.length[2]) - StdI.direct[2, 0] = print_val_d("Hx", StdI.direct[2, 0], 0.5 * StdI.length[0]) - StdI.direct[2, 1] = print_val_d("Hy", StdI.direct[2, 1], 0.5 * StdI.length[1]) - StdI.direct[2, 2] = print_val_d("Hz", StdI.direct[2, 2], 0.0) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) - StdI.phase[2] = print_val_d("phase2", StdI.phase[2], 0.0) - - init_site(StdI, None, 3) - StdI.tau[0, 0] = 0.0 - StdI.tau[0, 1] = 0.0 - StdI.tau[0, 2] = 0.0 - StdI.tau[1, 0] = 0.5 - StdI.tau[1, 1] = 0.0 - StdI.tau[1, 2] = 0.0 - StdI.tau[2, 0] = 0.0 - StdI.tau[2, 1] = 0.5 - StdI.tau[2, 2] = 0.0 - StdI.tau[3, 0] = 0.0 - StdI.tau[3, 1] = 0.0 - StdI.tau[3, 2] = 0.5 - - # (2) check & store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") - input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") - input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") - input_spin_nn(StdI.J, StdI.JAll, StdI.J0p, StdI.J0pAll, "J0'") - input_spin_nn(StdI.J, StdI.JAll, StdI.J1p, StdI.J1pAll, "J1'") - input_spin_nn(StdI.J, StdI.JAll, StdI.J2p, StdI.J2pAll, "J2'") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t", StdI.t) - not_used_d("t0", StdI.t0) - not_used_d("t1", StdI.t1) - not_used_d("t2", StdI.t2) - not_used_d("t'", StdI.tp) - not_used_d("t0'", StdI.t0p) - not_used_d("t1'", StdI.t1p) - not_used_d("t2'", StdI.t2p) - not_used_d("t''", StdI.tpp) - not_used_d("V", StdI.V) - not_used_d("V0", StdI.V0) - not_used_d("V1", StdI.V1) - not_used_d("V'", StdI.Vp) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") - StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") - StdI.t0p = input_hopp(StdI.t, StdI.t0p, "t0'") - StdI.t1p = input_hopp(StdI.t, StdI.t1p, "t1'") - StdI.t2p = input_hopp(StdI.t, StdI.t2p, "t2'") - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") - StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") - StdI.V0p = input_coulomb_v(StdI.V, StdI.V0p, "V0'") - StdI.V1p = input_coulomb_v(StdI.V, StdI.V1p, "V1'") - StdI.V2p = input_coulomb_v(StdI.V, StdI.V2p, "V2'") - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J0'", StdI.J0pAll, StdI.J0p) - not_used_j("J1'", StdI.J1pAll, StdI.J1p) - not_used_j("J2'", StdI.J2pAll, StdI.J2p) - not_used_j("J''", StdI.JppAll, StdI.Jpp) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - else: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and number of sites - set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) - - # (4) Compute upper limit of Transfer & Interaction - # nn=12 → n_bonds=12 - ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=12) - malloc_interactions(StdI, ntransMax, nintrMax) - - # (5) Set Transfer & Interaction - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - iH = StdI.Cell[kCell, 2] - - # Local term - isite = StdI.NsiteUC * kCell - if StdI.model == ModelType.KONDO: - isite += StdI.nsite // 2 - - if StdI.model == ModelType.SPIN: - for isiteUC in range(StdI.NsiteUC): - mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, isite + isiteUC) - general_j(StdI, StdI.D, StdI.S2, StdI.S2, isite + isiteUC, isite + isiteUC) - else: - for isiteUC in range(StdI.NsiteUC): - hubbard_local(StdI, StdI.mu, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, StdI.U, isite + isiteUC) - if StdI.model == ModelType.KONDO: - jsite = StdI.NsiteUC * kCell - for isiteUC in range(StdI.NsiteUC): - general_j(StdI, StdI.J, 1, StdI.S2, isite + 3, jsite + isiteUC) - mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, jsite + isiteUC) - - # Neighbor bonds: (dW, dL, dH, site_i, site_j, J, t, V) - _BONDS = ( - # Intra-cell - (0, 0, 0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # along W - (0, 0, 0, 0, 2, StdI.J1, StdI.t1, StdI.V1), # along L - (0, 0, 0, 0, 3, StdI.J2, StdI.t2, StdI.V2), # along H - (0, 0, 0, 2, 3, StdI.J0p, StdI.t0p, StdI.V0p), # along L-H - (0, 0, 0, 3, 1, StdI.J1p, StdI.t1p, StdI.V1p), # along H-W - (0, 0, 0, 1, 2, StdI.J2p, StdI.t2p, StdI.V2p), # along W-L - # Inter-cell - (1, 0, 0, 1, 0, StdI.J0, StdI.t0, StdI.V0), # along W - (0, 1, 0, 2, 0, StdI.J1, StdI.t1, StdI.V1), # along L - (0, 0, 1, 3, 0, StdI.J2, StdI.t2, StdI.V2), # along H - (0, -1, 1, 3, 2, StdI.J0p, StdI.t0p, StdI.V0p), # along L-H - (1, 0, -1, 1, 3, StdI.J1p, StdI.t1p, StdI.V1p), # along H-W - (-1, 1, 0, 2, 1, StdI.J2p, StdI.t2p, StdI.V2p), # along W-L - ) - for dW, dL, dH, si, sj, J, t, V in _BONDS: - add_neighbor_interaction_3d( - StdI, iW, iL, iH, dW, dL, dH, si, sj, J, t, V) - - close_lattice_xsf(StdI) diff --git a/python/lattice/site_util.py b/python/lattice/site_util.py deleted file mode 100644 index 77ff83c..0000000 --- a/python/lattice/site_util.py +++ /dev/null @@ -1,659 +0,0 @@ -"""Super-cell site initialisation, folding, and labelling utilities. - -This module provides functions for setting up the simulation super-cell, -folding site coordinates into the cell, finding site indices, and writing -gnuplot labels for 2-D lattice visualisation. - -Functions ---------- -init_site - Initialise the super-cell (box, reciprocal box, cells, gnuplot header). -find_site - Find global site indices and boundary phase for a pair of sites. -set_label - Write gnuplot labels and return site indices (2-D lattices). -lattice_gp - Context manager for ``lattice.gp`` gnuplot output (2-D lattices). -close_lattice_xsf - Write ``lattice.xsf``, ``geometry.dat``, and finalise 3-D lattice output. - -The private helper ``_fold_site`` is also available for use by other -modules that need to fold a coordinate into the original cell. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import itertools -from contextlib import contextmanager -from collections.abc import Iterator -from typing import TextIO - -import numpy as np - -from stdface_vals import StdIntList, ModelType, SolverType, NaN_i, AMPLITUDE_EPS -from param_check import exit_program, print_val_i -from .geometry_output import print_geometry, print_xsf - - -def _cell_vector(Cell: np.ndarray, idx: int) -> list[int]: - """Extract a cell row as a list of three ints. - - ``StdI.Cell`` is stored as a ``float`` array, so individual elements - must be cast to ``int`` whenever they are used in integer arithmetic - or coordinate comparison. This helper centralises that conversion. - - Parameters - ---------- - Cell : np.ndarray - ``(NCell, 3)`` array of fractional cell coordinates. - idx : int - Row index of the cell to extract. - - Returns - ------- - list of int - ``[int(Cell[idx, 0]), int(Cell[idx, 1]), int(Cell[idx, 2])]``. - """ - return [int(Cell[idx, 0]), int(Cell[idx, 1]), int(Cell[idx, 2])] - - -def _find_cell_index(StdI: StdIntList, cellV: list[int]) -> int: - """Find the cell index whose coordinates match *cellV*. - - Performs a linear search over ``StdI.Cell`` to find the row whose - three coordinates match *cellV*. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure containing the ``Cell`` array and - ``NCell`` count. - cellV : list of int - Length-3 fractional coordinate vector to search for. - - Returns - ------- - int - Cell index ``k`` such that ``StdI.Cell[k]`` equals *cellV*. - Returns 0 if no match is found (matching the original C behavior). - """ - for k in range(StdI.NCell): - if _cell_vector(StdI.Cell, k) == cellV: - return k - return 0 - - -def _fold_to_cell( - rbox: np.ndarray, - ncell: int, - box: np.ndarray, - iCellV: list[int], -) -> tuple[list[int], list[int]]: - """Fold a site coordinate into a cell defined by *box*. - - This is the core algorithm shared by :func:`_fold_site` (main - super-cell) and the sub-lattice folding in ``mvmc_variational``. - - Parameters - ---------- - rbox : np.ndarray - 3×3 reciprocal (cofactor) matrix of *box*. - ncell : int - Determinant of *box* (number of cells). - box : np.ndarray - 3×3 lattice-vector matrix defining the cell. - iCellV : list of int - Fractional coordinate of a site (length 3). - - Returns - ------- - nBox : list of int - Index of the periodic image that originally contained this site. - iCellV_fold : list of int - Fractional coordinate folded into the cell. - """ - # (1) Transform to fractional coordinate (times ncell) - iCellV_arr = np.asarray(iCellV) - iCellV_frac = rbox @ iCellV_arr - - # (2) Search which periodic image contains this cell - nBox = (iCellV_frac + ncell * 1000) // ncell - 1000 - - # (3) Fractional coordinate in the original cell - iCellV_frac = iCellV_frac - ncell * nBox - - # (4) Transform back to lattice coordinates and fold - iCellV_fold = (box.T @ iCellV_frac + ncell * 1000) // ncell - 1000 - - return nBox.astype(int).tolist(), iCellV_fold.astype(int).tolist() - - -def _fold_site( - StdI: StdIntList, - iCellV: list[int], -) -> tuple[list[int], list[int]]: - """Move a site into the original super-cell if it is outside. - - Delegates to :func:`_fold_to_cell` with the main super-cell - parameters (``rbox``, ``NCell``, ``box``). - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. - iCellV : list of int - Fractional coordinate of a site (length 3). - - Returns - ------- - nBox : list of int - Index of the super-cell that originally contained this site. - iCellV_fold : list of int - Fractional coordinate folded into the original cell. - """ - return _fold_to_cell(StdI.rbox, StdI.NCell, StdI.box, iCellV) - - -def _write_gnuplot_header(fp: TextIO, StdI: StdIntList) -> None: - """Write the gnuplot header for ``lattice.gp`` (2D lattices only). - - Computes the four corner positions of the super-cell from the - ``direct`` and ``box`` matrices, sets up axis ranges, styles, and - draws the boundary as four arrows. - - Parameters - ---------- - fp : file object - Open file to write gnuplot commands to. - StdI : StdIntList - Model parameter structure. Reads ``direct`` and ``box``. - """ - pos = np.zeros((4, 2)) - # pos[1:3] = supercell corner positions: box[:2,:2] @ direct[:2,:2] - pos[1:3, :] = StdI.box[:2, :2] @ StdI.direct[:2, :2] - pos[3, :] = pos[1, :] + pos[2, :] - - xmin = min(pos[:, 0].min(), pos[:, 1].min()) - 2.0 - xmax = max(pos[:, 0].max(), pos[:, 1].max()) + 2.0 - - fp.write("#set terminal pdf color enhanced \\\n") - fp.write("#dashed dl 1.0 size 20.0cm, 20.0cm \n") - fp.write('#set output "lattice.pdf"\n') - fp.write(f"set xrange [{xmin:f}: {xmax:f}]\n") - fp.write(f"set yrange [{xmin:f}: {xmax:f}]\n") - fp.write("set size square\n") - fp.write("unset key\n") - fp.write("unset tics\n") - fp.write("unset border\n") - fp.write("set style line 1 lc 1 lt 1\n") - fp.write("set style line 2 lc 5 lt 1\n") - fp.write("set style line 3 lc 0 lt 1\n") - - # Draw boundary arrows: 0→1→3→2→0 - corners = [(0, 1), (1, 3), (3, 2), (2, 0)] - for src, dst in corners: - fp.write(f"set arrow from {pos[src][0]:f}, {pos[src][1]:f} " - f"to {pos[dst][0]:f}, {pos[dst][1]:f} nohead front ls 3\n") - - -_LATTICE_GP_FOOTER = "plot '-' w d lc 7\n0.0 0.0\nend\npause -1\n" - - -@contextmanager -def lattice_gp(StdI: StdIntList) -> Iterator[TextIO | None]: - """Context manager for ``lattice.gp`` gnuplot output. - - Opens ``lattice.gp`` for writing when the solver is not H-wave, or - when ``StdI.lattice_gp`` is explicitly set to 1. Otherwise the - yielded handle is ``None``. On exit the gnuplot footer is written, - the file is closed, and :func:`print_geometry` is called. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. - - Yields - ------ - fp : TextIO or None - Open file handle, or ``None`` when output is suppressed. - """ - fp: TextIO | None = None - if StdI.solver != SolverType.HWAVE or StdI.lattice_gp == 1: - fp = open("lattice.gp", "w") - try: - yield fp - finally: - if fp is not None: - fp.write(_LATTICE_GP_FOOTER) - fp.close() - print_geometry(StdI) - - -def close_lattice_xsf(StdI: StdIntList) -> None: - """Write ``lattice.xsf``, ``geometry.dat``, and print geometry. - - This is the 3-D counterpart of :func:`lattice_gp`. It writes - the XCrySDen structure file via :func:`print_xsf` and the - ``geometry.dat`` file via :func:`print_geometry`. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. - """ - print_xsf(StdI) - print_geometry(StdI) - - -def _validate_box_params( - L: int, W: int, Height: int, - box: np.ndarray, - suffix: str = "", - defaults: np.ndarray | None = None, -) -> tuple[int, int, int]: - """Validate and normalise L/W/Height vs box specification. - - Exactly one of {L, W, Height} or {box entries} may be specified - (i.e. differ from ``NaN_i``). If both are given an error is raised. - When L/W/Height are specified, the box is filled as a diagonal matrix - ``diag(W, L, Height)``. When the box entries are specified (or - neither is), each entry is validated with ``print_val_i`` using - *defaults* as the fallback values. - - Parameters - ---------- - L, W, Height : int - Scalar lattice dimensions (may be ``NaN_i`` for unset). - box : np.ndarray - 3x3 box matrix (modified **in-place**). - suffix : str, optional - Label suffix appended to parameter names (e.g. ``""`` for main - lattice, ``"sub"`` for the sub-lattice). Default ``""``. - defaults : np.ndarray or None, optional - 3x3 array of default values for the box-entry branch. If - ``None``, the identity matrix ``diag(1, 1, 1)`` is used. - - Returns - ------- - L, W, Height : int - Validated (and possibly defaulted) values. - - Raises - ------ - SystemExit - If both L/W/Height and box entries are specified simultaneously. - """ - sfx = suffix - lwh_specified = (L != NaN_i or W != NaN_i or Height != NaN_i) - box_specified = any(box[i, j] != NaN_i for i in range(3) for j in range(3)) - - if lwh_specified and box_specified: - lwh_names = f"(L{sfx}, W{sfx}, H{sfx + 'eight' if not sfx else sfx})" - box_names = f"(a0W{sfx}, ..., a2H{sfx})" - print(f"\nERROR ! {lwh_names} and {box_names} conflict !\n") - exit_program(-1) - elif lwh_specified: - L = print_val_i(f"L{sfx}", L, 1) - W = print_val_i(f"W{sfx}", W, 1) - h_label = f"H{sfx + 'eight' if not sfx else sfx}" - Height = print_val_i(h_label, Height, 1) - box[:, :] = 0 - box[0, 0] = W - box[1, 1] = L - box[2, 2] = Height - else: - if defaults is None: - defaults = np.eye(3, dtype=int) - _labels = [ - [f"a0W{sfx}", f"a0L{sfx}", f"a0H{sfx}"], - [f"a1W{sfx}", f"a1L{sfx}", f"a1H{sfx}"], - [f"a2W{sfx}", f"a2L{sfx}", f"a2H{sfx}"], - ] - for i in range(3): - for j in range(3): - box[i, j] = print_val_i( - _labels[i][j], int(box[i, j]), int(defaults[i, j])) - - return L, W, Height - - -def _det_and_cofactor(box: np.ndarray) -> tuple[int, np.ndarray]: - """Compute the determinant and cofactor matrix of a 3x3 integer matrix. - - Uses the Sarrus rule for the determinant and the standard cofactor - formula for the adjugate (transposed cofactor matrix). If the - determinant is negative, both the determinant and cofactor matrix - are sign-flipped so that the returned determinant is non-negative. - - Parameters - ---------- - box : np.ndarray - 3x3 integer matrix. - - Returns - ------- - det : int - Absolute value of the determinant (always >= 0). - cofactor : np.ndarray - 3x3 cofactor matrix, sign-adjusted so that ``det >= 0``. - """ - # Compute determinant using Sarrus rule (equivalent to np.linalg.det for 3x3) - det = int(round(np.linalg.det(box.astype(float)))) - - # Compute cofactor matrix using vectorized indexing - # cofactor[i,j] = box[(i+1)%3, (j+1)%3] * box[(i+2)%3, (j+2)%3] - # - box[(i+1)%3, (j+2)%3] * box[(i+2)%3, (j+1)%3] - idx = np.array([1, 2, 0]) # (i+1) % 3 for i=0,1,2 - idx2 = np.array([2, 0, 1]) # (i+2) % 3 for i=0,1,2 - cofactor = (box[idx][:, idx] * box[idx2][:, idx2] - - box[idx][:, idx2] * box[idx2][:, idx]) - - if det < 0: - cofactor = -cofactor - det = -det - - return det, cofactor - - -def _compute_reciprocal_box(StdI: StdIntList) -> None: - """Compute the number of cells and the reciprocal-box matrix. - - Sets ``StdI.NCell`` to the determinant of ``StdI.box`` and - ``StdI.rbox`` to the cofactor matrix (adjugate transpose) of - ``StdI.box``. If the determinant is negative the sign of both - ``NCell`` and ``rbox`` is flipped so that ``NCell > 0``. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - Reads ``box``; sets ``NCell`` and ``rbox``. - - Raises - ------ - SystemExit - If the determinant is zero (degenerate super-cell). - """ - StdI.NCell, StdI.rbox = _det_and_cofactor(StdI.box) - print(f" Number of Cell = {abs(StdI.NCell)}") - if StdI.NCell == 0: - exit_program(-1) - - -def _enumerate_cells(StdI: StdIntList) -> None: - """Enumerate all unit cells inside the super-cell. - - Allocates ``StdI.Cell`` of shape ``(NCell, 3)`` and fills it with the - integer coordinates of each unit cell that lies inside the super-cell. - The super-cell is defined by the ``box`` matrix; a coordinate belongs - to the cell if folding it via ``_fold_site`` produces ``nBox == [0,0,0]``. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - Reads ``NCell`` and ``box``; sets ``Cell``. - """ - # Find bounding box by checking all 8 cube corners - box_int = StdI.box.astype(int) - # All 8 corners of the unit cube: shape (8, 3) - corners = np.array(list(itertools.product(range(2), repeat=3))) - # edges[corner, dim] = corner @ box_int[:, dim] => corners @ box_int - edges = corners @ box_int - # bound[dim] = [min_edge, max_edge] - bound = list(zip(edges.min(axis=0).tolist(), edges.max(axis=0).tolist())) - - # Enumerate cells within the bounding box - # Note: iteration order must be ic2 outermost, ic0 innermost (matching C code) - StdI.Cell = np.zeros((StdI.NCell, 3), dtype=int) - jj_idx = 0 - for ic2, ic1, ic0 in itertools.product( - range(bound[2][0], bound[2][1] + 1), - range(bound[1][0], bound[1][1] + 1), - range(bound[0][0], bound[0][1] + 1), - ): - iCellV = [ic0, ic1, ic2] - nBox, iCellV_fold = _fold_site(StdI, iCellV) - if nBox == [0, 0, 0]: - StdI.Cell[jj_idx, :] = iCellV - jj_idx += 1 - - -def init_site(StdI: StdIntList, fp: TextIO | None, dim: int) -> None: - """Initialize the super-cell where simulation is performed. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - fp : file object or None - File pointer to ``lattice.gp`` (may be ``None``). - dim : int - Dimension of the system. If 2, the gnuplot header for - ``lattice.gp`` is written. - """ - print("\n @ Super-Lattice setting\n") - - # (1) Check input parameters about the shape of super-cell - StdI.L, StdI.W, StdI.Height = _validate_box_params( - StdI.L, StdI.W, StdI.Height, StdI.box) - - if dim == 2: - StdI.direct[:2, 2] = 0.0 # zero z-component of first two vectors - StdI.direct[2, :] = [0.0, 0.0, 1.0] # third vector = unit z - - # (2) Define the phase factor at each boundary - if dim == 2: - StdI.phase[2] = 0.0 - StdI.ExpPhase = np.exp(1j * (np.pi / 180.0) * StdI.phase) - StdI.AntiPeriod = np.where(np.abs(StdI.ExpPhase + 1.0) < AMPLITUDE_EPS, 1, 0) - - # (3) Allocate tau (intrinsic structure of unit-cell) - StdI.tau = np.zeros((StdI.NsiteUC, 3)) - - # (4) Calculate reciprocal lattice vectors and NCell - _compute_reciprocal_box(StdI) - - # (5) Find cells in the super-cell - _enumerate_cells(StdI) - - # (6) For 2D, print lattice.gp header - if dim == 2 and fp is not None: - _write_gnuplot_header(fp, StdI) - - -def find_site( - StdI: StdIntList, - iW: int, iL: int, iH: int, - diW: int, diL: int, diH: int, - isiteUC: int, jsiteUC: int, -) -> tuple[int, int, complex, np.ndarray]: - """Find the site indices and boundary phase for a pair of sites. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. - iW, iL, iH : int - Position of the initial site. - diW, diL, diH : int - Translation from the initial site. - isiteUC : int - Intrinsic site index of the initial site in the unit cell. - jsiteUC : int - Intrinsic site index of the final site in the unit cell. - - Returns - ------- - isite : int - Global index of the initial site. - jsite : int - Global index of the final site. - Cphase : complex - Boundary phase factor. - dR : numpy.ndarray - Distance vector R_i - R_j in fractional coordinates (shape ``(3,)``). - """ - di = np.array([diW, diL, diH], dtype=float) - dR = -di + StdI.tau[isiteUC, :] - StdI.tau[jsiteUC, :] - - jCellV = [iW + diW, iL + diL, iH + diH] - nBox, jCellV = _fold_site(StdI, jCellV) - Cphase = np.prod(StdI.ExpPhase ** np.array(nBox)) - - jCell = _find_cell_index(StdI, jCellV) - iCell = _find_cell_index(StdI, [iW, iL, iH]) - - isite = iCell * StdI.NsiteUC + isiteUC - jsite = jCell * StdI.NsiteUC + jsiteUC - if StdI.model == ModelType.KONDO: - isite += StdI.NCell * StdI.NsiteUC - jsite += StdI.NCell * StdI.NsiteUC - - return isite, jsite, Cphase, dR - - -def _write_gnuplot_bond( - fp: TextIO, - isite: int, jsite: int, - xi: float, yi: float, - xj: float, yj: float, - connect: int, -) -> None: - """Write gnuplot label and arrow commands for a single bond. - - Writes two ``set label`` commands (one per site endpoint) and, - when *connect* < 3, a ``set arrow`` command connecting them. - - Parameters - ---------- - fp : TextIO - Open gnuplot file handle. - isite, jsite : int - Global site indices (used as label text). - xi, yi : float - 2-D position of site *isite*. - xj, yj : float - 2-D position of site *jsite*. - connect : int - Line-style selector. The arrow is only drawn when ``connect < 3``. - """ - for site, x, y in ((isite, xi, yi), (jsite, xj, yj)): - w = 1 if site < 10 else 2 - fp.write(f'set label "{site:{w}d}" at {x:f}, {y:f} center front\n') - if connect < 3: - fp.write(f"set arrow from {xi:f}, {yi:f} to {xj:f}, {yj:f} " - f"nohead ls {connect:d}\n") - - -def set_label( - StdI: StdIntList, - fp: TextIO | None, - iW: int, iL: int, - diW: int, diL: int, - isiteUC: int, jsiteUC: int, - connect: int, -) -> tuple[int, int, complex, np.ndarray]: - """Set label in the gnuplot display (2D systems only). - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. - fp : file object or None - File pointer to ``lattice.gp``. - iW, iL : int - Position of the initial site. - diW, diL : int - Translation from the initial site. - isiteUC : int - Intrinsic site index of the initial site. - jsiteUC : int - Intrinsic site index of the final site. - connect : int - Connection type (1 for nearest, 2 for 2nd nearest). - - Returns - ------- - isite : int - Global index of the initial site. - jsite : int - Global index of the final site. - Cphase : complex - Boundary phase factor. - dR : numpy.ndarray - Distance vector R_i - R_j. - """ - # First print the reversed one - isite, jsite, Cphase, dR = find_site( - StdI, iW, iL, 0, -diW, -diL, 0, jsiteUC, isiteUC) - - # Compute 2D positions via direct[:2,:2].T @ fractional_coords - D = StdI.direct[:2, :2] - frac_i = np.array([iW + StdI.tau[jsiteUC, 0], iL + StdI.tau[jsiteUC, 1]]) - frac_j = np.array([iW - diW + StdI.tau[isiteUC, 0], iL - diL + StdI.tau[isiteUC, 1]]) - xi, yi = frac_i @ D - xj, yj = frac_j @ D - - if fp is not None: - _write_gnuplot_bond(fp, isite, jsite, xi, yi, xj, yj, connect) - - # Then print the normal one - isite, jsite, Cphase, dR = find_site( - StdI, iW, iL, 0, diW, diL, 0, isiteUC, jsiteUC) - - frac_i = np.array([iW + StdI.tau[isiteUC, 0], iL + StdI.tau[isiteUC, 1]]) - frac_j = np.array([iW + diW + StdI.tau[jsiteUC, 0], iL + diL + StdI.tau[jsiteUC, 1]]) - xi, yi = frac_i @ D - xj, yj = frac_j @ D - - if fp is not None: - _write_gnuplot_bond(fp, isite, jsite, xi, yi, xj, yj, connect) - - return isite, jsite, Cphase, dR - - -def set_local_spin_flags(StdI: StdIntList, nsite_base: int) -> None: - """Set local spin flags and total site count based on the model type. - - This function is shared across all lattice modules. It sets - ``StdI.nsite`` (doubling for Kondo), allocates ``StdI.locspinflag``, - and fills the array according to the model type: - - * **SPIN**: all sites get ``S2`` - * **HUBBARD**: all sites get ``0`` - * **KONDO**: first half ``S2``, second half ``0`` - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - nsite_base : int - Number of sites before Kondo doubling (e.g. - ``NsiteUC * NCell``, ``L``, or ``L * NsiteUC``). - """ - StdI.nsite = nsite_base - if StdI.model == ModelType.KONDO: - StdI.nsite *= 2 - StdI.locspinflag = np.zeros(StdI.nsite, dtype=int) - - if StdI.model == ModelType.SPIN: - StdI.locspinflag[:] = StdI.S2 - elif StdI.model == ModelType.HUBBARD: - StdI.locspinflag[:] = 0 - elif StdI.model == ModelType.KONDO: - half = StdI.nsite // 2 - StdI.locspinflag[:half] = StdI.S2 - StdI.locspinflag[half:] = 0 - diff --git a/python/lattice/square_lattice.py b/python/lattice/square_lattice.py deleted file mode 100644 index 8cb9709..0000000 --- a/python/lattice/square_lattice.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Standard mode for the tetragonal (square) lattice. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import numpy as np - -from stdface_vals import StdIntList, ModelType -from param_check import ( - print_val_d, print_val_i, - not_used_j, not_used_d, not_used_i, -) -from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - compute_max_interactions, malloc_interactions, - add_neighbor_interaction, add_local_terms, -) -from .site_util import ( - init_site, set_label, set_local_spin_flags, - lattice_gp, -) - - -def tetragonal(StdI: StdIntList) -> None: - """Setup a Hamiltonian for the square (tetragonal) lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - # (1) Compute the shape of the super-cell and sites in the super-cell - with lattice_gp(StdI) as fp: - - StdI.NsiteUC = 1 - - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], 0.0) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1]) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) - - init_site(StdI, fp, 2) - StdI.tau[0, 0] = 0.0 - StdI.tau[0, 1] = 0.0 - StdI.tau[0, 2] = 0.0 - - # (2) check & store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J2'", StdI.J2pAll, StdI.J2p) - not_used_d("t2", StdI.t2) - not_used_d("t2'", StdI.t2p) - not_used_d("V2", StdI.V2) - not_used_d("V2'", StdI.V2p) - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") - input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J1pp, StdI.J1ppAll, "J1''") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t", StdI.t) - not_used_d("t0", StdI.t0) - not_used_d("t1", StdI.t1) - not_used_d("t'", StdI.tp) - not_used_d("t0'", StdI.t0p) - not_used_d("t1'", StdI.t1p) - not_used_d("t''", StdI.tpp) - not_used_d("t0''", StdI.t0pp) - not_used_d("t1''", StdI.t1pp) - not_used_d("V", StdI.V) - not_used_d("V0", StdI.V0) - not_used_d("V1", StdI.V1) - not_used_d("V'", StdI.Vp) - not_used_d("V0'", StdI.V0p) - not_used_d("V1'", StdI.V1p) - not_used_d("V''", StdI.Vpp) - not_used_d("V0''", StdI.V0pp) - not_used_d("V1''", StdI.V1pp) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") - StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") - StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") - StdI.t0pp = input_hopp(StdI.tpp, StdI.t0pp, "t0''") - StdI.t1pp = input_hopp(StdI.tpp, StdI.t1pp, "t1''") - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") - StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") - StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") - StdI.V0pp = input_coulomb_v(StdI.Vpp, StdI.V0pp, "V0''") - StdI.V1pp = input_coulomb_v(StdI.Vpp, StdI.V1pp, "V1''") - StdI.Vp = print_val_d("V'", StdI.Vp, 0.0) - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J'", StdI.JpAll, StdI.Jp) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - else: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and number of sites - set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) - - # (4) Compute upper limit of Transfer & Interaction and malloc them - # nn=2, nnn=2, nnnn=2 → n_bonds=6 - ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=2 + 2 + 2) - malloc_interactions(StdI, ntransMax, nintrMax) - - # (5) Set Transfer & Interaction - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - - # Local term - isite = kCell - if StdI.model == ModelType.KONDO: - isite += StdI.NCell - add_local_terms(StdI, isite, kCell) - - # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) - _BONDS = ( - (1, 0, 0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # nn along W - (0, 1, 0, 0, 1, StdI.J1, StdI.t1, StdI.V1), # nn along L - (1, 1, 0, 0, 2, StdI.J0p, StdI.t0p, StdI.V0p), # nnn W+L - (1, -1, 0, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # nnn W-L - (2, 0, 0, 0, 3, StdI.J0pp, StdI.t0pp, StdI.V0pp), # nnnn 2W - (0, 2, 0, 0, 3, StdI.J1pp, StdI.t1pp, StdI.V1pp), # nnnn 2L - ) - for dW, dL, si, sj, nn, J, t, V in _BONDS: - add_neighbor_interaction( - StdI, fp, iW, iL, dW, dL, si, sj, nn, J, t, V) diff --git a/python/lattice/triangular_lattice.py b/python/lattice/triangular_lattice.py deleted file mode 100644 index 8cf086c..0000000 --- a/python/lattice/triangular_lattice.py +++ /dev/null @@ -1,192 +0,0 @@ -""" -Standard mode for the triangular lattice. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import math - -import numpy as np - -from stdface_vals import StdIntList, ModelType -from param_check import ( - print_val_d, print_val_i, - not_used_j, not_used_d, not_used_i, -) -from .input_params import input_spin_nn, input_spin, input_hopp, input_coulomb_v -from .interaction_builder import ( - compute_max_interactions, malloc_interactions, - add_neighbor_interaction, add_local_terms, -) -from .site_util import ( - init_site, set_label, set_local_spin_flags, - lattice_gp, -) - - -def triangular(StdI: StdIntList) -> None: - """Setup a Hamiltonian for the triangular lattice. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - """ - # (1) Compute the shape of the super-cell and sites in the super-cell - with lattice_gp(StdI) as fp: - - StdI.NsiteUC = 1 - print(" @ Lattice Size & Shape\n") - - StdI.a = print_val_d("a", StdI.a, 1.0) - StdI.length[0] = print_val_d("Wlength", StdI.length[0], StdI.a) - StdI.length[1] = print_val_d("Llength", StdI.length[1], StdI.a) - StdI.direct[0, 0] = print_val_d("Wx", StdI.direct[0, 0], StdI.length[0]) - StdI.direct[0, 1] = print_val_d("Wy", StdI.direct[0, 1], 0.0) - StdI.direct[1, 0] = print_val_d("Lx", StdI.direct[1, 0], StdI.length[1] * 0.5) - StdI.direct[1, 1] = print_val_d("Ly", StdI.direct[1, 1], StdI.length[1] * 0.5 * math.sqrt(3.0)) - - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) - - init_site(StdI, fp, 2) - StdI.tau[0, 0] = 0.0 - StdI.tau[0, 1] = 0.0 - StdI.tau[0, 2] = 0.0 - - # (2) check & store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - StdI.D[2, 2] = print_val_d("D", StdI.D[2, 2], 0.0) - input_spin_nn(StdI.J, StdI.JAll, StdI.J0, StdI.J0All, "J0") - input_spin_nn(StdI.J, StdI.JAll, StdI.J1, StdI.J1All, "J1") - input_spin_nn(StdI.J, StdI.JAll, StdI.J2, StdI.J2All, "J2") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J0p, StdI.J0pAll, "J0'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J1p, StdI.J1pAll, "J1'") - input_spin_nn(StdI.Jp, StdI.JpAll, StdI.J2p, StdI.J2pAll, "J2'") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J0pp, StdI.J0ppAll, "J0''") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J1pp, StdI.J1ppAll, "J1''") - input_spin_nn(StdI.Jpp, StdI.JppAll, StdI.J2pp, StdI.J2ppAll, "J2''") - - not_used_d("mu", StdI.mu) - not_used_d("U", StdI.U) - not_used_d("t", StdI.t) - not_used_d("t0", StdI.t0) - not_used_d("t1", StdI.t1) - not_used_d("t2", StdI.t2) - not_used_d("t'", StdI.tp) - not_used_d("t0'", StdI.t0p) - not_used_d("t1'", StdI.t1p) - not_used_d("t2'", StdI.t2p) - not_used_d("t''", StdI.tp) - not_used_d("t0''", StdI.t0pp) - not_used_d("t1''", StdI.t1pp) - not_used_d("t2''", StdI.t2pp) - not_used_d("V", StdI.V) - not_used_d("V0", StdI.V0) - not_used_d("V1", StdI.V1) - not_used_d("V2", StdI.V2) - not_used_d("V'", StdI.Vp) - not_used_d("V0'", StdI.V0p) - not_used_d("V1'", StdI.V1p) - not_used_d("V2'", StdI.V2p) - not_used_d("V''", StdI.Vpp) - not_used_d("V0''", StdI.V0pp) - not_used_d("V1''", StdI.V1pp) - not_used_d("V2''", StdI.V2pp) - else: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - StdI.U = print_val_d("U", StdI.U, 0.0) - StdI.t0 = input_hopp(StdI.t, StdI.t0, "t0") - StdI.t1 = input_hopp(StdI.t, StdI.t1, "t1") - StdI.t2 = input_hopp(StdI.t, StdI.t2, "t2") - StdI.t0p = input_hopp(StdI.tp, StdI.t0p, "t0'") - StdI.t1p = input_hopp(StdI.tp, StdI.t1p, "t1'") - StdI.t2p = input_hopp(StdI.tp, StdI.t2p, "t2'") - StdI.t0pp = input_hopp(StdI.tpp, StdI.t0pp, "t0''") - StdI.t1pp = input_hopp(StdI.tpp, StdI.t1pp, "t1''") - StdI.t2pp = input_hopp(StdI.tpp, StdI.t2pp, "t2''") - StdI.V0 = input_coulomb_v(StdI.V, StdI.V0, "V0") - StdI.V1 = input_coulomb_v(StdI.V, StdI.V1, "V1") - StdI.V2 = input_coulomb_v(StdI.V, StdI.V2, "V2") - StdI.V0p = input_coulomb_v(StdI.Vp, StdI.V0p, "V0'") - StdI.V1p = input_coulomb_v(StdI.Vp, StdI.V1p, "V1'") - StdI.V2p = input_coulomb_v(StdI.Vp, StdI.V2p, "V2'") - StdI.V0pp = input_coulomb_v(StdI.Vpp, StdI.V0pp, "V0''") - StdI.V1pp = input_coulomb_v(StdI.Vpp, StdI.V1pp, "V1''") - StdI.V2pp = input_coulomb_v(StdI.Vpp, StdI.V2pp, "V2''") - - not_used_j("J0", StdI.J0All, StdI.J0) - not_used_j("J1", StdI.J1All, StdI.J1) - not_used_j("J2", StdI.J2All, StdI.J2) - not_used_j("J0'", StdI.J0pAll, StdI.J0p) - not_used_j("J1'", StdI.J1pAll, StdI.J1p) - not_used_j("J2'", StdI.J2pAll, StdI.J2p) - not_used_j("J0''", StdI.J0ppAll, StdI.J0pp) - not_used_j("J1''", StdI.J1ppAll, StdI.J1pp) - not_used_j("J2''", StdI.J2ppAll, StdI.J2pp) - not_used_d("D", StdI.D[2, 2]) - - if StdI.model == ModelType.HUBBARD: - not_used_i("2S", StdI.S2) - not_used_j("J", StdI.JAll, StdI.J) - else: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - input_spin(StdI.J, StdI.JAll, "J") - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and number of sites - set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) - - # (4) Compute upper limit of Transfer & Interaction - # nn=3, nnn=3, nnnn=3 → n_bonds=9 - ntransMax, nintrMax = compute_max_interactions(StdI, n_bonds=3 + 3 + 3) - malloc_interactions(StdI, ntransMax, nintrMax) - - # (5) Set Transfer & Interaction - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - - # Local term - isite = kCell - if StdI.model == ModelType.KONDO: - isite += StdI.NCell - add_local_terms(StdI, isite, kCell) - - # Neighbor bonds: (dW, dL, site_i, site_j, nn_level, J, t, V) - _BONDS = ( - # Nearest neighbor (nn=1) - (1, 0, 0, 0, 1, StdI.J0, StdI.t0, StdI.V0), # along W - (0, 1, 0, 0, 1, StdI.J1, StdI.t1, StdI.V1), # along L - (1, -1, 0, 0, 1, StdI.J2, StdI.t2, StdI.V2), # along W-L - # Second nearest neighbor (nn=2) - (2, -1, 0, 0, 2, StdI.J1p, StdI.t1p, StdI.V1p), # 2W-L - (1, 1, 0, 0, 2, StdI.J2p, StdI.t2p, StdI.V2p), # W+L - (-1, 2, 0, 0, 2, StdI.J0p, StdI.t0p, StdI.V0p), # -W+2L - # Third nearest neighbor (nn=3) - (2, 0, 0, 0, 3, StdI.J0pp, StdI.t0pp, StdI.V0pp), # 2W - (0, 2, 0, 0, 3, StdI.J1pp, StdI.t1pp, StdI.V1pp), # 2L - (2, -2, 0, 0, 3, StdI.J2pp, StdI.t2pp, StdI.V2pp),# 2W-2L - ) - for dW, dL, si, sj, nn, J, t, V in _BONDS: - add_neighbor_interaction( - StdI, fp, iW, iL, dW, dL, si, sj, nn, J, t, V) diff --git a/python/lattice/wannier90.py b/python/lattice/wannier90.py deleted file mode 100644 index ad0c5b0..0000000 --- a/python/lattice/wannier90.py +++ /dev/null @@ -1,1318 +0,0 @@ -""" -Standard mode for the Wannier90 interface. - -This module sets up the Hamiltonian for the Wannier90 ``*_hr.dat`` format, -supporting hopping, Coulomb, and Hund coupling terms read from -Wannier90/RESPACK output files. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import itertools -import math -import sys -from enum import IntEnum -from typing import NamedTuple, TextIO - -import numpy as np - -from stdface_vals import StdIntList, ModelType, SolverType, NaN_i, UNSET_STRING, AMPLITUDE_EPS -from param_check import exit_program, print_val_d, print_val_i, not_used_d -from .geometry_output import print_geometry, print_xsf -from .interaction_builder import ( - malloc_interactions, mag_field, general_j, hubbard_local, hopping, coulomb, -) -from .site_util import init_site, find_site, set_local_spin_flags - - -# --------------------------------------------------------------------------- -# Internal helpers -# --------------------------------------------------------------------------- - - -def _check_in_box(rvec: np.ndarray, inverse_matrix: np.ndarray) -> bool: - """Check if a lattice vector is inside the unit cell box. - - Parameters - ---------- - rvec : numpy.ndarray - Integer lattice vector to check (shape ``(3,)``). - inverse_matrix : numpy.ndarray - Inverse of the cutoff lattice vectors (shape ``(3, 3)``). - - Returns - ------- - bool - True if inside the box, False otherwise. - """ - judge_vec = rvec @ inverse_matrix - return bool(np.all(np.abs(judge_vec) <= 1)) - - -def _skip_degeneracy_weights(fp: TextIO, n_wigner_seitz: int) -> None: - """Skip the degeneracy-weight lines in a Wannier90 ``*_hr.dat`` file. - - The weights are written as whitespace-separated integers, potentially - spanning multiple lines. This helper reads and discards exactly - *n_wigner_seitz* values. - - Parameters - ---------- - fp : TextIO - Open file positioned just after the ``nWSC`` header line. - n_wigner_seitz : int - Total number of Wigner-Seitz cells (degeneracy entries to skip). - """ - count = 0 - while count < n_wigner_seitz: - count += len(fp.readline().split()) - - -def _geometry_w90(StdI: StdIntList) -> None: - """Read Wannier90 geometry file. - - Reads lattice vectors and Wannier center positions from the geometry - file ``_geom.dat``. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. Modified in-place: - ``StdI.direct`` (lattice vectors) and ``StdI.tau`` (Wannier centres) - are populated. - """ - filename = f"{StdI.CDataFileHead}_geom.dat" - print(f" Wannier90 Geometry file = {filename}") - - try: - fp_geom = open(filename, "r") - except FileNotFoundError: - - print(f"\n Error: Fail to open the file {filename}. \n", file=sys.stderr) - exit_program(-1) - - with fp_geom: - # Read direct lattice vectors - for ii in range(3): - StdI.direct[ii, :] = [float(x) for x in fp_geom.readline().split()[:3]] - - # Read number of correlated sites - StdI.NsiteUC = int(fp_geom.readline().split()[0]) - print(f" Number of Correlated Sites = {StdI.NsiteUC}") - - # Allocate and read Wannier centre positions - StdI.tau = np.zeros((StdI.NsiteUC, 3)) - for isite in range(StdI.NsiteUC): - StdI.tau[isite, :] = [float(x) for x in fp_geom.readline().split()[:3]] - - print(" Direct lattice vectors:") - for row in StdI.direct: - print(f" {row[0]:10.5f} {row[1]:10.5f} {row[2]:10.5f}") - print(" Wannier centres:") - for tau_row in StdI.tau[:StdI.NsiteUC]: - print(f" {tau_row[0]:10.5f} {tau_row[1]:10.5f} {tau_row[2]:10.5f}") - - -def _apply_boundary_weights( - indx_tot: np.ndarray, - Weight_tot: np.ndarray, - nWSC: int, - StdI: StdIntList, -) -> np.ndarray: - """Apply boundary-halving weights at model lattice boundaries. - - For periodic models, matrix elements at the boundary of the model - super-cell are halved to avoid double-counting. - - Parameters - ---------- - indx_tot : numpy.ndarray - R-vector indices for each Wigner-Seitz cell, shape ``(nWSC, 3)``. - Weight_tot : numpy.ndarray - Weight array for each WSC, shape ``(nWSC,)``. Modified in-place. - nWSC : int - Number of Wigner-Seitz cells. - StdI : StdIntList - Structure containing model parameters (``W``, ``L``, ``Height``). - - Returns - ------- - numpy.ndarray - Band lattice extent for each dimension, shape ``(3,)``, dtype int. - """ - # Compute max absolute index per dimension - Band_lattice = np.max(np.abs(indx_tot[:nWSC]), axis=0).astype(int) - - if StdI.W != NaN_i and StdI.L != NaN_i and StdI.Height != NaN_i: - dims = np.array([StdI.W, StdI.L, StdI.Height], dtype=int) - # Model_lattice[i] = dims[i] // 2 if dims[i] is even, else 0 - Model_lattice = np.where(dims % 2 == 0, dims // 2, 0) - for ii in range(3): - if Model_lattice[ii] < Band_lattice[ii] and Model_lattice[ii] != 0: - # Apply 0.5 weight at boundary - mask = np.abs(indx_tot[:nWSC, ii]) == Model_lattice[ii] - Weight_tot[:nWSC][mask] *= 0.5 - - return Band_lattice - - -def _count_and_store_terms( - Mat_tot: np.ndarray, - indx_tot: np.ndarray, - Weight_tot: np.ndarray, - nWSC: int, - NsiteUC: int, - cutoff: float, - itUJ: int, - NtUJ: list[int], - tUJindx: list, - tUJ: list, -) -> None: - """Apply weights, count effective terms, and store surviving terms. - - Multiplies each matrix element by its weight, counts terms above the - cutoff threshold, prints them, and packs the surviving terms into - the output arrays ``tUJ`` and ``tUJindx``. - - Parameters - ---------- - Mat_tot : numpy.ndarray - Matrix elements, shape ``(nWSC, nWan, nWan)``, dtype complex. - indx_tot : numpy.ndarray - R-vector indices, shape ``(nWSC, 3)``, dtype int. - Weight_tot : numpy.ndarray - Per-WSC weights, shape ``(nWSC,)``. - nWSC : int - Number of Wigner-Seitz cells. - NsiteUC : int - Number of correlated sites in the unit cell. - cutoff : float - Threshold for matrix elements. - itUJ : int - Interaction type index (0=t, 1=U, 2=J). - NtUJ : list of int - Counts per interaction type. Modified in-place. - tUJindx : list - Index arrays. Modified in-place. - tUJ : list - Coefficient arrays. Modified in-place. - """ - # Apply weights: broadcast Weight_tot over Wannier indices - Mat_tot[:nWSC, :, :] *= Weight_tot[:nWSC, np.newaxis, np.newaxis] - - # Print and count effective terms - print("\n EFFECTIVE terms:") - print(" R0 R1 R2 band_i band_f Hamiltonian") - NtUJ[itUJ] = 0 - for iWSC in range(nWSC): - for iWan in range(NsiteUC): - for jWan in range(NsiteUC): - if cutoff < abs(Mat_tot[iWSC, iWan, jWan]): - print( - f" {indx_tot[iWSC, 0]:5d}{indx_tot[iWSC, 1]:5d}" - f"{indx_tot[iWSC, 2]:5d}{iWan:5d}{jWan:5d}" - f"{Mat_tot[iWSC, iWan, jWan].real:12.6f}" - f"{Mat_tot[iWSC, iWan, jWan].imag:12.6f}" - ) - NtUJ[itUJ] += 1 - print(f" Total number of EFFECTIVE term = {NtUJ[itUJ]}") - - # Extract surviving terms using numpy masking - abs_mat = np.abs(Mat_tot[:nWSC, :NsiteUC, :NsiteUC]) - mask = abs_mat > cutoff - wsc_idx, iwan_idx, jwan_idx = np.nonzero(mask) - - tUJ_arr = Mat_tot[wsc_idx, iwan_idx, jwan_idx].copy() - tUJindx_arr = np.column_stack([ - indx_tot[wsc_idx, :], - iwan_idx, - jwan_idx, - ]) - - # Extend the lists to hold the results - while len(tUJ) <= itUJ: - tUJ.append(None) - while len(tUJindx) <= itUJ: - tUJindx.append(None) - tUJ[itUJ] = tUJ_arr - tUJindx[itUJ] = tUJindx_arr - - -def _read_w90( - StdI: StdIntList, - filename: str, - cutoff: float, - cutoff_R: np.ndarray, - cutoff_Rvec: np.ndarray, - cutoff_length: float, - itUJ: int, - NtUJ: list[int], - tUJindx: list, - lam: float, - tUJ: list, -) -> None: - """Read Wannier90 hopping/interaction file. - - Reads hopping or interaction matrix elements from Wannier90 files, - applies cutoffs and stores non-zero terms. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. - filename : str - Input filename (e.g. ``*_hr.dat``, ``*_ur.dat``, ``*_jr.dat``). - cutoff : float - Threshold for matrix elements. - cutoff_R : numpy.ndarray - Cutoff for R vectors (shape ``(3,)``, int). - cutoff_Rvec : numpy.ndarray - Cutoff vectors for unit cell (shape ``(3, 3)``). - cutoff_length : float - Real-space cutoff length. - itUJ : int - Type of interaction (0: hopping t, 1: Coulomb U, 2: Hund J). - NtUJ : list of int - Number of terms for each interaction type. Modified in-place. - tUJindx : list - Indices for terms. Modified in-place. - lam : float - Scaling factor (lambda). - tUJ : list - Matrix elements. Modified in-place. - """ - flg_vec = int(cutoff_Rvec[0, 0] != NaN_i) - - # Try to open the file - try: - fp_hr = open(filename, "r") - except FileNotFoundError: - print(f"\n Skip to read the file {filename}. \n") - return - - with fp_hr: - # Header part - _header_line = fp_hr.readline() # comment line - nWan = int(fp_hr.readline().split()[0]) - nWSC = int(fp_hr.readline().split()[0]) - - # Skip degeneracy weights - _skip_degeneracy_weights(fp_hr, nWSC) - - # Allocate arrays - Weight_tot = np.ones(nWSC) - Mat_tot = np.zeros((nWSC, nWan, nWan), dtype=complex) - indx_tot = np.zeros((nWSC, 3), dtype=int) - - if flg_vec: - inverse_rvec = np.linalg.inv(cutoff_Rvec.astype(float)) - - # Read body - for iWSC in range(nWSC): - for iWan in range(nWan): - for jWan in range(nWan): - vals = fp_hr.readline().split() - indx_tot[iWSC, :] = [int(vals[0]), int(vals[1]), int(vals[2])] - iWan0 = int(vals[3]) - jWan0 = int(vals[4]) - dtmp_re = float(vals[5]) - dtmp_im = float(vals[6]) - # Compute Euclidean length - tau_diff = StdI.tau[jWan, :] - StdI.tau[iWan, :] + indx_tot[iWSC, :] - dR = StdI.direct.T @ tau_diff - length = np.linalg.norm(dR) - if length > cutoff_length > 0.0: - dtmp_re = 0.0 - dtmp_im = 0.0 - - if flg_vec: - if not _check_in_box(indx_tot[iWSC], inverse_rvec): - dtmp_re = 0.0 - dtmp_im = 0.0 - else: - if np.any(np.abs(indx_tot[iWSC]) > cutoff_R): - dtmp_re = 0.0 - dtmp_im = 0.0 - - if iWan0 <= StdI.NsiteUC and jWan0 <= StdI.NsiteUC: - Mat_tot[iWSC, iWan0 - 1, jWan0 - 1] = lam * (dtmp_re + 1j * dtmp_im) - - # Apply inversion symmetry and delete duplication - for jWSC in range(iWSC): - if np.all(indx_tot[iWSC] == -indx_tot[jWSC]): - Mat_tot[iWSC, :, :] = 0.0 - - if np.all(indx_tot[iWSC] == 0): - for iWan in range(StdI.NsiteUC): - Mat_tot[iWSC, iWan, :iWan] = 0.0 - - # Apply boundary-halving weights - _apply_boundary_weights(indx_tot, Weight_tot, nWSC, StdI) - - # Count effective terms, print summary, and store - _count_and_store_terms( - Mat_tot, indx_tot, Weight_tot, nWSC, - StdI.NsiteUC, cutoff, itUJ, NtUJ, tUJindx, tUJ, - ) - - -def _read_density_matrix( - StdI: StdIntList, - filename: str, -) -> dict[tuple[int, int, int], np.ndarray]: - """Read RESPACK density matrix file. - - Reads density matrix elements from a RESPACK output file. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. - filename : str - Input filename (e.g. ``*_dr.dat``). - - Returns - ------- - dict of tuple to numpy.ndarray - Dictionary mapping ``(R0, R1, R2)`` lattice vector tuples to - 2D numpy arrays of shape ``(NsiteUC, NsiteUC)`` containing the - density matrix elements. - """ - - - try: - fp_dr = open(filename, "r") - except FileNotFoundError: - print(f"\n Error: Fail to open the file {filename}. \n", file=sys.stderr) - exit_program(-1) - - with fp_dr: - # Header - _header_line = fp_dr.readline() - nWan = int(fp_dr.readline().split()[0]) - nWSC = int(fp_dr.readline().split()[0]) - - _skip_degeneracy_weights(fp_dr, nWSC) - - # Allocate - Mat_tot = np.zeros((nWSC, nWan, nWan), dtype=complex) - indx_tot = np.zeros((nWSC, 3), dtype=int) - - Rmin = np.zeros(3, dtype=int) - Rmax = np.zeros(3, dtype=int) - - # Read body - for iWSC in range(nWSC): - for iWan in range(nWan): - for jWan in range(nWan): - vals = fp_dr.readline().split() - indx_tot[iWSC, :] = [int(vals[0]), int(vals[1]), int(vals[2])] - iWan0 = int(vals[3]) - jWan0 = int(vals[4]) - dtmp_re = float(vals[5]) - dtmp_im = float(vals[6]) - - if iWan0 <= StdI.NsiteUC and jWan0 <= StdI.NsiteUC: - Mat_tot[iWSC, iWan0 - 1, jWan0 - 1] = dtmp_re + 1j * dtmp_im - Rmin = np.minimum(Rmin, indx_tot[iWSC]) - Rmax = np.maximum(Rmax, indx_tot[iWSC]) - - NR = Rmax - Rmin + 1 - print(f" Minimum R : {Rmin[0]} {Rmin[1]} {Rmin[2]}") - print(f" Maximum R : {Rmax[0]} {Rmax[1]} {Rmax[2]}") - print(f" Numver of R : {NR[0]} {NR[1]} {NR[2]}") - - # Build dictionary: (R0, R1, R2) -> 2D array - DenMat: dict[tuple[int, int, int], np.ndarray] = {} - for i0, i1, i2 in itertools.product( - range(Rmin[0], Rmax[0] + 1), - range(Rmin[1], Rmax[1] + 1), - range(Rmin[2], Rmax[2] + 1), - ): - DenMat[(i0, i1, i2)] = np.zeros( - (StdI.NsiteUC, StdI.NsiteUC), dtype=complex - ) - - for iWSC in range(nWSC): - key = tuple(indx_tot[iWSC].astype(int)) - DenMat[key][:, :] = Mat_tot[iWSC, :nWan, :nWan] - - return DenMat - - -def _print_uhf_initial( - StdI: StdIntList, - NtUJ: list[int], - tUJ: list[np.ndarray], - DenMat: dict[tuple[int, int, int], np.ndarray], - tUJindx: list[np.ndarray], -) -> None: - """Print initial UHF guess to ``initial.def``. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. - NtUJ : list of int - Number of terms for each interaction type. - tUJ : list of numpy.ndarray - Matrix elements. - DenMat : dict - Density matrix elements keyed by R-vector tuples. - tUJindx : list of numpy.ndarray - Indices for interaction terms. - """ - IniGuess = np.zeros((StdI.nsite, StdI.nsite), dtype=complex) - - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - iH = StdI.Cell[kCell, 2] - - # Diagonal term - for isite_uc in range(StdI.NsiteUC): - jsite = isite_uc + StdI.NsiteUC * kCell - IniGuess[jsite, jsite] = DenMat[(0, 0, 0)][isite_uc, isite_uc] - - # Coulomb integral (U) and Exchange integral (J) - for idx in (1, 2): - for it in range(NtUJ[idx]): - row = tUJindx[idx][it] - isite, jsite, Cphase, dR = find_site( - StdI, iW, iL, iH, - int(row[0]), int(row[1]), int(row[2]), - int(row[3]), int(row[4]), - ) - key = (int(row[0]), int(row[1]), int(row[2])) - dm_val = DenMat[key][int(row[3]), int(row[4])] - IniGuess[isite, jsite] = dm_val - IniGuess[jsite, isite] = np.conj(dm_val) - - mask = np.abs(IniGuess) > AMPLITUDE_EPS - NIniGuess = int(np.count_nonzero(mask)) - - with open("initial.def", "w") as fp: - fp.write("======================== \n") - fp.write(f"NInitialGuess {NIniGuess * 2:7d} \n") - fp.write("======================== \n") - fp.write("========i_j_s_tijs====== \n") - fp.write("======================== \n") - - rows, cols = np.nonzero(mask) - for isite, jsite in zip(rows, cols): - val = 0.5 * IniGuess[isite, jsite] - for ispin in range(2): - fp.write( - f"{jsite:5d} {ispin:5d} {isite:5d} {ispin:5d} " - f"{val.real:25.15f} " - f"{val.imag:25.15f}\n" - ) - - print(" initial.def is written.") - - -# --------------------------------------------------------------------------- -# Double-counting mode enum -# --------------------------------------------------------------------------- - - -class _DCMode(IntEnum): - """Double-counting correction mode.""" - NOTCORRECT = 0 - HARTREE = 1 - HARTREE_U = 2 - FULL = 3 - - -_DC_MODE_MAP: dict[str, _DCMode] = { - "none": _DCMode.NOTCORRECT, - UNSET_STRING: _DCMode.NOTCORRECT, - "hartree": _DCMode.HARTREE, - "hartree_u": _DCMode.HARTREE_U, - "full": _DCMode.FULL, -} -"""Maps double-counting mode strings to :class:`_DCMode` enum members. - -The sentinel :data:`UNSET_STRING` (``"****"``) is treated the same as -``"none"`` (no correction). -""" - - -def _parse_double_counting_mode(mode_str: str) -> _DCMode: - """Convert a double-counting mode string to the corresponding enum. - - Uses the :data:`_DC_MODE_MAP` dispatch table for lookup. - - Parameters - ---------- - mode_str : str - Mode specification from the input file. Recognised values (case - sensitive) are ``"none"``, ``"hartree"``, ``"hartree_u"`` and - ``"full"``. The sentinel :data:`UNSET_STRING` is treated the same - as ``"none"``. - - Returns - ------- - _DCMode - The matching enum member. - - Raises - ------ - SystemExit - If *mode_str* is not one of the recognised values. - """ - result = _DC_MODE_MAP.get(mode_str) - if result is not None: - return result - - - print( - "\n Error: the word of doublecounting is not correct " - "(select from none, hartree, hartree_u, full). \n", - file=sys.stderr, - ) - exit_program(-1) - - -# --------------------------------------------------------------------------- -# Cutoff-parameter setup + file read helper -# --------------------------------------------------------------------------- - - -def _read_w90_with_cutoff( - StdI: StdIntList, - label: str, - label_suffix: str, - file_suffix: str, - cutoff_val: float, - cutoff_length: float, - cutoff_R: np.ndarray, - cutoff_Vec: np.ndarray, - cutoff_length_default: float, - cutoff_R_defaults: tuple[int | None, int | None, int | None], - itUJ: int, - NtUJ: list[int], - tUJindx: list, - lam: float, - tUJ: list, -) -> tuple[float, float]: - """Set cutoff parameters and read a Wannier90 interaction file. - - Prints parameter values, sets cutoff thresholds for R-vectors and - real-space length, then calls :func:`_read_w90` to read the file. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. - label : str - Lowercase label for the cutoff threshold name - (e.g. ``"t"``, ``"u"``, ``"j"``). - label_suffix : str - Suffix for length/R/Vec parameter names (e.g. ``"t"``, ``"U"``, - ``"J"``). May differ in case from *label*. - file_suffix : str - File suffix including underscore (e.g. ``"_hr.dat"``). - cutoff_val : float - Current cutoff threshold value (may be NaN if unset). - cutoff_length : float - Current cutoff length value (may be NaN if unset). - cutoff_R : numpy.ndarray - Cutoff R-vector array (shape ``(3,)``, int). Modified in-place. - cutoff_Vec : numpy.ndarray - Cutoff vector matrix (shape ``(3, 3)``). Modified in-place. - cutoff_length_default : float - Default value for cutoff length if unset. - cutoff_R_defaults : tuple of (int or None) - Default values for cutoff_R[0], cutoff_R[1], cutoff_R[2]. - Use ``None`` to skip a dimension (leave unchanged). - itUJ : int - Interaction type index (0=hopping, 1=Coulomb, 2=Hund). - NtUJ : list of int - Number of terms per interaction type. Modified in-place. - tUJindx : list - Indices per interaction type. Modified in-place. - lam : float - Scaling factor (lambda). - tUJ : list - Matrix elements per interaction type. Modified in-place. - - Returns - ------- - tuple of (float, float) - Updated ``(cutoff_val, cutoff_length)`` after applying defaults. - """ - cutoff_name = f"cutoff_{label}" - cutoff_length_name = f"cutoff_length_{label_suffix}" - cutoff_R_name = f"cutoff_{label_suffix}R" - cutoff_Vec_name = f"cutoff_{label_suffix}Vec" - - cutoff_val = print_val_d(cutoff_name, cutoff_val, 1.0e-8) - cutoff_length = print_val_d(cutoff_length_name, cutoff_length, cutoff_length_default) - - for dim in range(3): - if cutoff_R_defaults[dim] is not None: - cutoff_R[dim] = print_val_i( - f"{cutoff_R_name}[{dim}]", int(cutoff_R[dim]), cutoff_R_defaults[dim] - ) - - for i in range(3): - for j in range(3): - if StdI.box[i, j] != NaN_i: - param_name = f"{cutoff_Vec_name}[{i}][{j}]" - cutoff_Vec[i, j] = print_val_d( - param_name, cutoff_Vec[i, j], float(StdI.box[i, j]) * 0.5 - ) - - filename = f"{StdI.CDataFileHead}{file_suffix}" - _read_w90( - StdI, filename, - cutoff_val, cutoff_R, cutoff_Vec, cutoff_length, - itUJ, NtUJ, tUJindx, lam, tUJ, - ) - - return cutoff_val, cutoff_length - - -# --------------------------------------------------------------------------- -# Per-cell interaction helpers -# --------------------------------------------------------------------------- - - -def _apply_hopping_terms( - StdI: StdIntList, - kCell: int, - iW: int, - iL: int, - iH: int, - NtUJ: list[int], - tUJ: list, - tUJindx: list, - Uspin: np.ndarray | None, -) -> None: - """Apply hopping transfer terms for one unit cell. - - Processes all hopping (t) terms for the unit cell at position - ``(iW, iL, iH)``. Local terms contribute on-site energies (Hubbard) - and non-local terms contribute either super-exchange (spin) or - hopping integrals (Hubbard). - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. Modified in-place. - kCell : int - Linear index of the current unit cell. - iW, iL, iH : int - Unit-cell coordinates. - NtUJ : list of int - Number of terms per interaction type. - tUJ : list - Matrix elements per interaction type. - tUJindx : list - Indices per interaction type. - Uspin : numpy.ndarray or None - On-site Coulomb values per orbital (spin model only). - """ - if tUJindx[0] is None: - return - for it in range(NtUJ[0]): - # Local term - if (tUJindx[0][it, 0] == 0 and tUJindx[0][it, 1] == 0 - and tUJindx[0][it, 2] == 0 - and tUJindx[0][it, 3] == tUJindx[0][it, 4]): - if StdI.model == ModelType.HUBBARD: - isite = StdI.NsiteUC * kCell + int(tUJindx[0][it, 3]) - for ispin in range(2): - StdI.trans[StdI.ntrans] = -tUJ[0][it] - StdI.transindx[StdI.ntrans, 0] = isite - StdI.transindx[StdI.ntrans, 1] = ispin - StdI.transindx[StdI.ntrans, 2] = isite - StdI.transindx[StdI.ntrans, 3] = ispin - StdI.ntrans += 1 - else: - # Non-local term - isite, jsite, Cphase, dR = find_site( - StdI, iW, iL, iH, - int(tUJindx[0][it, 0]), int(tUJindx[0][it, 1]), - int(tUJindx[0][it, 2]), - int(tUJindx[0][it, 3]), int(tUJindx[0][it, 4]), - ) - if StdI.model == ModelType.SPIN: - diag_val = ( - 2.0 * tUJ[0][it] * np.conj(tUJ[0][it]) - * (1.0 / Uspin[int(tUJindx[0][it, 3])] - + 1.0 / Uspin[int(tUJindx[0][it, 4])]) - ).real - Jtmp = np.diag([diag_val, diag_val, diag_val]) - general_j(StdI, Jtmp, StdI.S2, StdI.S2, isite, jsite) - else: - hopping(StdI, -Cphase * tUJ[0][it], jsite, isite, dR) - - -def _apply_coulomb_terms( - StdI: StdIntList, - kCell: int, - iW: int, - iL: int, - iH: int, - NtUJ: list[int], - tUJ: list, - tUJindx: list, - idcmode: _DCMode, - DenMat: dict[tuple[int, int, int], np.ndarray] | None, -) -> None: - """Apply Coulomb (U) interaction terms for one unit cell. - - Processes all Coulomb terms for the unit cell at position - ``(iW, iL, iH)``, including local intra-site Coulomb, non-local - inter-site Coulomb, and double-counting corrections when enabled. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. Modified in-place. - kCell : int - Linear index of the current unit cell. - iW, iL, iH : int - Unit-cell coordinates. - NtUJ : list of int - Number of terms per interaction type. - tUJ : list - Matrix elements per interaction type. - tUJindx : list - Indices per interaction type. - idcmode : _DCMode - Double-counting correction mode. - DenMat : dict or None - Density matrix elements keyed by R-vector tuples. - """ - if tUJindx[1] is None: - return - for it in range(NtUJ[1]): - # Local term - if (tUJindx[1][it, 0] == 0 and tUJindx[1][it, 1] == 0 - and tUJindx[1][it, 2] == 0 - and tUJindx[1][it, 3] == tUJindx[1][it, 4]): - StdI.Cintra[StdI.NCintra] = tUJ[1][it].real - StdI.CintraIndx[StdI.NCintra, 0] = ( - StdI.NsiteUC * kCell + int(tUJindx[1][it, 3]) - ) - StdI.NCintra += 1 - - # Double-counting correction - if idcmode != _DCMode.NOTCORRECT: - isite = StdI.NsiteUC * kCell + int(tUJindx[1][it, 3]) - for ispin in range(2): - DenMat0 = DenMat[(0, 0, 0)][ - int(tUJindx[1][it, 3]), int(tUJindx[1][it, 3]) - ] - StdI.trans[StdI.ntrans] = ( - StdI.alpha * tUJ[1][it].real * DenMat0 - ) - StdI.transindx[StdI.ntrans, 0] = isite - StdI.transindx[StdI.ntrans, 1] = ispin - StdI.transindx[StdI.ntrans, 2] = isite - StdI.transindx[StdI.ntrans, 3] = ispin - StdI.ntrans += 1 - else: - # Non-local term - isite, jsite, Cphase, dR = find_site( - StdI, iW, iL, iH, - int(tUJindx[1][it, 0]), int(tUJindx[1][it, 1]), - int(tUJindx[1][it, 2]), - int(tUJindx[1][it, 3]), int(tUJindx[1][it, 4]), - ) - coulomb(StdI, tUJ[1][it].real, isite, jsite) - - # Double-counting correction - if idcmode != _DCMode.NOTCORRECT: - for ispin in range(2): - # U_{Rij} D_{0jj} (Local) - DenMat0 = DenMat[(0, 0, 0)][ - int(tUJindx[1][it, 4]), int(tUJindx[1][it, 4]) - ] - StdI.trans[StdI.ntrans] = tUJ[1][it].real * DenMat0 - StdI.transindx[StdI.ntrans, 0] = isite - StdI.transindx[StdI.ntrans, 1] = ispin - StdI.transindx[StdI.ntrans, 2] = isite - StdI.transindx[StdI.ntrans, 3] = ispin - StdI.ntrans += 1 - - # U_{Rij} D_{0ii} (Local) - DenMat0 = DenMat[(0, 0, 0)][ - int(tUJindx[1][it, 3]), int(tUJindx[1][it, 3]) - ] - StdI.trans[StdI.ntrans] = tUJ[1][it].real * DenMat0 - StdI.transindx[StdI.ntrans, 0] = jsite - StdI.transindx[StdI.ntrans, 1] = ispin - StdI.transindx[StdI.ntrans, 2] = jsite - StdI.transindx[StdI.ntrans, 3] = ispin - StdI.ntrans += 1 - - # Hartree-Fock correction - if idcmode == _DCMode.FULL: - key = ( - int(tUJindx[1][it, 0]), - int(tUJindx[1][it, 1]), - int(tUJindx[1][it, 2]), - ) - DenMat0 = DenMat[key][ - int(tUJindx[1][it, 3]), int(tUJindx[1][it, 4]) - ] - hopping( - StdI, - -0.5 * Cphase * tUJ[1][it].real * DenMat0, - jsite, isite, dR, - ) - - -def _apply_hund_terms( - StdI: StdIntList, - kCell: int, - iW: int, - iL: int, - iH: int, - NtUJ: list[int], - tUJ: list, - tUJindx: list, - idcmode: _DCMode, - DenMat: dict[tuple[int, int, int], np.ndarray] | None, -) -> None: - """Apply Hund (J) coupling terms for one unit cell. - - Processes all Hund coupling terms for the unit cell at position - ``(iW, iL, iH)``, including exchange, pair-hopping (Hubbard), and - double-counting corrections when enabled. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. Modified in-place. - kCell : int - Linear index of the current unit cell. - iW, iL, iH : int - Unit-cell coordinates. - NtUJ : list of int - Number of terms per interaction type. - tUJ : list - Matrix elements per interaction type. - tUJindx : list - Indices per interaction type. - idcmode : _DCMode - Double-counting correction mode. - DenMat : dict or None - Density matrix elements keyed by R-vector tuples. - """ - if tUJindx[2] is None: - return - for it in range(NtUJ[2]): - # Local term should not be computed - if (tUJindx[2][it, 0] != 0 or tUJindx[2][it, 1] != 0 - or tUJindx[2][it, 2] != 0 - or tUJindx[2][it, 3] != tUJindx[2][it, 4]): - isite, jsite, Cphase, dR = find_site( - StdI, iW, iL, iH, - int(tUJindx[2][it, 0]), int(tUJindx[2][it, 1]), - int(tUJindx[2][it, 2]), - int(tUJindx[2][it, 3]), int(tUJindx[2][it, 4]), - ) - - StdI.Hund[StdI.NHund] = tUJ[2][it].real - StdI.HundIndx[StdI.NHund, 0] = isite - StdI.HundIndx[StdI.NHund, 1] = jsite - StdI.NHund += 1 - - if StdI.model == ModelType.HUBBARD: - StdI.Ex[StdI.NEx] = tUJ[2][it].real - StdI.ExIndx[StdI.NEx, 0] = isite - StdI.ExIndx[StdI.NEx, 1] = jsite - StdI.NEx += 1 - - StdI.PairHopp[StdI.NPairHopp] = tUJ[2][it].real - StdI.PHIndx[StdI.NPairHopp, 0] = isite - StdI.PHIndx[StdI.NPairHopp, 1] = jsite - StdI.NPairHopp += 1 - - # Double-counting correction - if idcmode != _DCMode.NOTCORRECT and idcmode != _DCMode.HARTREE_U: - for ispin in range(2): - # -0.5 J_{Rij} D_{0jj} - DenMat0 = DenMat[(0, 0, 0)][ - int(tUJindx[2][it, 4]), int(tUJindx[2][it, 4]) - ] - StdI.trans[StdI.ntrans] = ( - -(1.0 - StdI.alpha) * tUJ[2][it].real * DenMat0 - ) - StdI.transindx[StdI.ntrans, 0] = isite - StdI.transindx[StdI.ntrans, 1] = ispin - StdI.transindx[StdI.ntrans, 2] = isite - StdI.transindx[StdI.ntrans, 3] = ispin - StdI.ntrans += 1 - - # -0.5 J_{Rij} D_{0ii} - DenMat0 = DenMat[(0, 0, 0)][ - int(tUJindx[2][it, 3]), int(tUJindx[2][it, 3]) - ] - StdI.trans[StdI.ntrans] = ( - -(1.0 - StdI.alpha) * tUJ[2][it].real * DenMat0 - ) - StdI.transindx[StdI.ntrans, 0] = jsite - StdI.transindx[StdI.ntrans, 1] = ispin - StdI.transindx[StdI.ntrans, 2] = jsite - StdI.transindx[StdI.ntrans, 3] = ispin - StdI.ntrans += 1 - - # Hartree-Fock correction - if idcmode == _DCMode.FULL: - key = ( - int(tUJindx[2][it, 0]), - int(tUJindx[2][it, 1]), - int(tUJindx[2][it, 2]), - ) - DenMat0 = DenMat[key][ - int(tUJindx[2][it, 3]), int(tUJindx[2][it, 4]) - ] - hopping( - StdI, - 0.5 * Cphase * tUJ[2][it].real - * (DenMat0 + 2.0 * DenMat0.real), - jsite, isite, dR, - ) - else: - # spin model - if StdI.solver == SolverType.mVMC: - StdI.Ex[StdI.NEx] = tUJ[2][it].real - else: - StdI.Ex[StdI.NEx] = -tUJ[2][it].real - StdI.ExIndx[StdI.NEx, 0] = isite - StdI.ExIndx[StdI.NEx, 1] = jsite - StdI.NEx += 1 - - -# --------------------------------------------------------------------------- -# High-level helpers (called by wannier90) -# --------------------------------------------------------------------------- - - -def _validate_wannier_params(StdI: StdIntList) -> None: - """Check and store Hamiltonian parameters for the Wannier90 lattice. - - Validates model-specific parameters (``S2`` for spin, ``mu`` for Hubbard) - and reports unused parameters. Exits on unsupported Kondo model. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters. Modified in-place. - """ - not_used_d("K", StdI.K) - StdI.h = print_val_d("h", StdI.h, 0.0) - StdI.Gamma = print_val_d("Gamma", StdI.Gamma, 0.0) - StdI.Gamma_y = print_val_d("Gamma_y", StdI.Gamma_y, 0.0) - not_used_d("U", StdI.U) - - if StdI.model == ModelType.SPIN: - StdI.S2 = print_val_i("2S", StdI.S2, 1) - elif StdI.model == ModelType.HUBBARD: - StdI.mu = print_val_d("mu", StdI.mu, 0.0) - else: - print("wannier + Kondo is not available !") - exit_program(-1) - - -def _build_wannier_interactions( - StdI: StdIntList, - NtUJ: list[int], - tUJ: list, - tUJindx: list, - idcmode: _DCMode, - DenMat: dict | None, -) -> None: - """Allocate interaction arrays and populate transfer / interaction terms. - - Computes upper bounds for transfer and interaction arrays, allocates - memory via :func:`malloc_interactions`, then loops over super-cells to - apply hopping, Coulomb, and Hund terms. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice. Modified in-place. - NtUJ : list of int - Number of hopping / Coulomb / Hund terms (length 3). - tUJ : list - Complex coefficient arrays for hopping / Coulomb / Hund (length 3). - tUJindx : list - Index arrays for hopping / Coulomb / Hund (length 3). - idcmode : _DCMode - Double-counting mode enum. - DenMat : dict or None - Density matrix (keyed by ``(R0, R1, R2)``), or ``None``. - """ - # Compute upper limits for Transfer & Interaction arrays - if StdI.model == ModelType.SPIN: - ntransMax = StdI.nsite * (StdI.S2 + 1 + 2 * StdI.S2) - nintrMax = StdI.NCell * ( - StdI.NsiteUC + NtUJ[0] + NtUJ[1] + NtUJ[2] - ) * (3 * StdI.S2 + 1) * (3 * StdI.S2 + StdI.NsiteUC) - elif StdI.model == ModelType.HUBBARD: - ntransMax = StdI.NCell * 2 * ( - 2 * StdI.NsiteUC + NtUJ[0] * 2 - + NtUJ[1] * 2 * 3 + NtUJ[2] * 2 * 2 - ) - nintrMax = StdI.NCell * (NtUJ[1] + NtUJ[2] + StdI.NsiteUC) - - malloc_interactions(StdI, ntransMax, nintrMax) - - # For spin systems, compute super-exchange interaction on-site U - Uspin = None - if StdI.model == ModelType.SPIN: - Uspin = np.zeros(StdI.NsiteUC) - if tUJindx[1] is not None: - for it in range(NtUJ[1]): - if (tUJindx[1][it, 0] == 0 and tUJindx[1][it, 1] == 0 - and tUJindx[1][it, 2] == 0 - and tUJindx[1][it, 3] == tUJindx[1][it, 4]): - Uspin[int(tUJindx[1][it, 3])] = tUJ[1][it].real - - # Main cell loop — apply all interaction terms - for kCell in range(StdI.NCell): - iW = StdI.Cell[kCell, 0] - iL = StdI.Cell[kCell, 1] - iH = StdI.Cell[kCell, 2] - - # Local term - if StdI.model == ModelType.SPIN: - for isite in range(StdI.NsiteUC * kCell, StdI.NsiteUC * (kCell + 1)): - mag_field(StdI, StdI.S2, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, isite) - else: - for isite in range(StdI.NsiteUC * kCell, StdI.NsiteUC * (kCell + 1)): - hubbard_local(StdI, StdI.mu, -StdI.h, -StdI.Gamma, -StdI.Gamma_y, 0.0, isite) - - # Hopping - _apply_hopping_terms(StdI, kCell, iW, iL, iH, NtUJ, tUJ, tUJindx, Uspin) - - # Coulomb integral (U) - _apply_coulomb_terms(StdI, kCell, iW, iL, iH, NtUJ, tUJ, tUJindx, idcmode, DenMat) - - # Hund coupling (J) - _apply_hund_terms(StdI, kCell, iW, iL, iH, NtUJ, tUJ, tUJindx, idcmode, DenMat) - - -def _write_wan2site(StdI: StdIntList) -> None: - """Write ``wan2site.dat`` mapping Wannier orbitals to super-cell sites. - - Parameters - ---------- - StdI : StdIntList - Structure containing lattice and cell information. - """ - with open("wan2site.dat", "w") as fp: - fp.write("======================== \n") - fp.write(f"Total site number {StdI.NCell * StdI.NsiteUC:7d} \n") - fp.write("======================== \n") - fp.write("========site nx ny nz norb====== \n") - fp.write("======================== \n") - - for kCell in range(StdI.NCell): - nx = StdI.Cell[kCell, 0] - ny = StdI.Cell[kCell, 1] - nz = StdI.Cell[kCell, 2] - for it in range(StdI.NsiteUC): - isite = StdI.NsiteUC * kCell + it - fp.write(f"{isite:5d}{nx:5d}{ny:5d}{nz:5d}{it:5d}\n") - - -def _validate_interaction_params(StdI: StdIntList) -> None: - """Validate and default lambda/alpha interaction-strength parameters. - - Sets ``lambda_U``, ``lambda_J``, and ``alpha`` on *StdI*, using - ``lambda_`` as a shared default when it is not NaN. Exits if - any value is out of range. - - Parameters - ---------- - StdI : StdIntList - Parameter structure (modified in place). - - Raises - ------ - SystemExit - If ``lambda_U`` or ``lambda_J`` is negative, or ``alpha`` is - outside [0, 1]. - """ - if math.isnan(StdI.lambda_): - StdI.lambda_U = print_val_d("lambda_U", StdI.lambda_U, 1.0) - StdI.lambda_J = print_val_d("lambda_J", StdI.lambda_J, 1.0) - else: - StdI.lambda_U = print_val_d("lambda_U", StdI.lambda_U, StdI.lambda_) - StdI.lambda_J = print_val_d("lambda_J", StdI.lambda_J, StdI.lambda_) - - if StdI.lambda_U < 0.0 or StdI.lambda_J < 0.0: - print( - "\n Error: the value of lambda_U / lambda_J must be " - "greater than or equal to 0. \n", - file=sys.stderr, - ) - exit_program(-1) - - StdI.alpha = print_val_d("alpha", StdI.alpha, 0.5) - if StdI.alpha > 1.0 or StdI.alpha < 0.0: - print( - "\n Error: the value of alpha must be in the range 0<= alpha <= 1. \n", - file=sys.stderr, - ) - exit_program(-1) - - -# --------------------------------------------------------------------------- -# Main entry point -# --------------------------------------------------------------------------- - - -class _W90Channel(NamedTuple): - """Configuration for one Wannier90 interaction channel (hopping/Coulomb/Hund).""" - - label: str - key_lower: str - key_upper: str - file_suffix: str - cutoff_length_default: float - cutoff_R_defaults: tuple[int | None, ...] - itUJ: int - lam: float - - -# Mapping from channel key_lower to (cutoff_attr, cutoff_length_attr, cutoffR_attr, cutoffVec_attr) -_W90_FIELD_MAP: dict[str, tuple[str, str, str, str]] = { - "t": ("cutoff_t", "cutoff_length_t", "cutoff_tR", "cutoff_tVec"), - "u": ("cutoff_u", "cutoff_length_U", "cutoff_UR", "cutoff_UVec"), - "j": ("cutoff_j", "cutoff_length_J", "cutoff_JR", "cutoff_JVec"), -} - - -def _read_w90_channels( - StdI: StdIntList, - channels: tuple[_W90Channel, ...], - NtUJ: int, - tUJindx: np.ndarray, - tUJ: np.ndarray, -) -> None: - """Read Wannier90 interaction files for all channels. - - Parameters - ---------- - StdI : StdIntList - Standard interface data; cutoff fields are updated in place. - channels : tuple of _W90Channel - Channel configurations (hopping, Coulomb, Hund). - NtUJ : int - Number of interaction entries. - tUJindx : numpy.ndarray - Interaction index array. - tUJ : numpy.ndarray - Interaction value array. - """ - for ch in channels: - print(f"\n @ Wannier90 {ch.label} \n") - co_attr, cl_attr, cr_attr, cv_attr = _W90_FIELD_MAP[ch.key_lower] - cutoff, cutoff_length = _read_w90_with_cutoff( - StdI, ch.key_lower, ch.key_upper, ch.file_suffix, - getattr(StdI, co_attr), getattr(StdI, cl_attr), - getattr(StdI, cr_attr), getattr(StdI, cv_attr), - cutoff_length_default=ch.cutoff_length_default, - cutoff_R_defaults=ch.cutoff_R_defaults, - itUJ=ch.itUJ, NtUJ=NtUJ, tUJindx=tUJindx, lam=ch.lam, tUJ=tUJ, - ) - setattr(StdI, co_attr, cutoff) - setattr(StdI, cl_attr, cutoff_length) - - -def wannier90(StdI: StdIntList) -> None: - """Set up a Hamiltonian for the Wannier90 ``*_hr.dat``. - - Parameters - ---------- - StdI : StdIntList - Structure containing model parameters and lattice information. - Modified in-place. - - Notes - ----- - This function performs the following steps: - - 1. Compute the shape of the super-cell and sites in the super-cell. - 2. Read Wannier90 geometry, hopping, Coulomb and Hund files. - 3. Validate and store Hamiltonian parameters. - 4. Set local spin flags and number of sites. - 5. Allocate memory for interactions. - 6. Set up transfers and interactions between sites. - 7. Write ``lattice.xsf``, ``geometry.dat`` and ``wan2site.dat``. - """ - NtUJ = [0, 0, 0] - tUJ: list = [None, None, None] - tUJindx: list = [None, None, None] - - # (1) Compute the shape of the super-cell and sites in the super-cell - with open("lattice.xsf", "w") as fp_xsf: - StdI.phase[0] = print_val_d("phase0", StdI.phase[0], 0.0) - StdI.phase[1] = print_val_d("phase1", StdI.phase[1], 0.0) - StdI.phase[2] = print_val_d("phase2", StdI.phase[2], 0.0) - StdI.NsiteUC = 1 - init_site(StdI, fp_xsf, 3) - print("\n @ Wannier90 Geometry \n") - _geometry_w90(StdI) - - _validate_interaction_params(StdI) - idcmode = _parse_double_counting_mode(StdI.double_counting_mode) - - # Read hopping, Coulomb, and Hund interaction files - hopping_R_defaults = ( - (StdI.W - 1) // 2 if StdI.W != NaN_i else None, - (StdI.L - 1) // 2 if StdI.L != NaN_i else None, - (StdI.Height - 1) // 2 if StdI.Height != NaN_i else None, - ) - _W90_CHANNELS = ( - _W90Channel("hopping", "t", "t", "_hr.dat", -1.0, hopping_R_defaults, 0, 1.0), - _W90Channel("Coulomb", "u", "U", "_ur.dat", 0.3, (0, 0, 0), 1, StdI.lambda_U), - _W90Channel("Hund", "j", "J", "_jr.dat", 0.3, (0, 0, 0), 2, StdI.lambda_J), - ) - _read_w90_channels(StdI, _W90_CHANNELS, NtUJ, tUJindx, tUJ) - - # Read Density matrix - DenMat = None - if idcmode != _DCMode.NOTCORRECT: - print("\n @ Wannier90 Density-matrix \n") - filename = f"{StdI.CDataFileHead}_dr.dat" - DenMat = _read_density_matrix(StdI, filename) - - # (2) Check and store parameters of Hamiltonian - print("\n @ Hamiltonian \n") - _validate_wannier_params(StdI) - - print("\n @ Numerical conditions\n") - - # (3) Set local spin flag and number of sites - set_local_spin_flags(StdI, StdI.NsiteUC * StdI.NCell) - - # (4)-(5) Allocate arrays and populate transfer / interaction terms - _build_wannier_interactions(StdI, NtUJ, tUJ, tUJindx, idcmode, DenMat) - - if idcmode != _DCMode.NOTCORRECT: - _print_uhf_initial(StdI, NtUJ, tUJ, DenMat, tUJindx) - print_xsf(StdI) - print_geometry(StdI) - - # Write wan2site.dat - _write_wan2site(StdI) diff --git a/python/param_check.py b/python/param_check.py deleted file mode 100644 index 2e2486d..0000000 --- a/python/param_check.py +++ /dev/null @@ -1,274 +0,0 @@ -"""Parameter validation, printing, and program-exit utilities. - -This module provides helper functions for printing parameter values with -default-value handling, aborting on unused or missing parameters, and -terminating the program. These utilities are shared across all lattice -implementations, solver writers, and the main entry point. - -Functions ---------- -exit_program - Terminate the program with a given error code. -print_val_d - Print / default a real-valued parameter. -print_val_dd - Print / default a real-valued parameter with two fallback defaults. -print_val_c - Print / default a complex-valued parameter. -print_val_i - Print / default an integer parameter. -not_used_d - Abort if a real or complex parameter is specified but unused. -not_used_j - Abort if any component of a J-type interaction is specified but unused. -not_used_i - Abort if an integer parameter is specified but unused. -required_val_i - Abort if a required integer parameter is missing. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import itertools -import math -import sys - -import numpy as np - -from stdface_vals import NaN_i - - -# --------------------------------------------------------------------------- -# Exit wrapper -# --------------------------------------------------------------------------- - - -def exit_program(errorcode: int) -> None: - """Terminate the program with the given error code. - - Parameters - ---------- - errorcode : int - Exit code passed to ``sys.exit``. - - Notes - ----- - In the original C code this was ``StdFace_exit`` which wrapped - ``MPI_Abort`` / ``MPI_Finalize``. The Python version simply calls - ``sys.exit``. - """ - sys.stdout.flush() - sys.stderr.flush() - sys.exit(errorcode) - - -# --------------------------------------------------------------------------- -# PrintVal / NotUsed / Required helpers -# --------------------------------------------------------------------------- - - -def print_val_d(valname: str, val: float, val0: float) -> float: - """Print and optionally set a real-valued parameter. - - If *val* is NaN, set it to the default *val0* and print with a - ``DEFAULT VALUE`` tag. - - Parameters - ---------- - valname : str - Name of the variable (for display). - val : float - Current value (may be NaN if not specified). - val0 : float - Default value to use when *val* is NaN. - - Returns - ------- - float - The (possibly updated) value. - """ - if math.isnan(val): - val = val0 - print(f" {valname:>15s} = {val:<10.5f} ###### DEFAULT VALUE IS USED ######") - else: - print(f" {valname:>15s} = {val:<10.5f}") - return val - - -def print_val_dd(valname: str, val: float, val0: float, val1: float) -> float: - """Print and optionally set a real-valued parameter with two defaults. - - If *val* is NaN, use the primary default *val0* if specified, - otherwise fall back to the secondary default *val1*. Delegates to - :func:`print_val_d` after resolving the effective default. - - Parameters - ---------- - valname : str - Name of the variable (for display). - val : float - Current value (may be NaN). - val0 : float - Primary default (may itself be NaN). - val1 : float - Secondary default. - - Returns - ------- - float - The (possibly updated) value. - """ - default = val1 if math.isnan(val0) else val0 - return print_val_d(valname, val, default) - - -def print_val_c(valname: str, val: complex, val0: complex) -> complex: - """Print and optionally set a complex-valued parameter. - - Parameters - ---------- - valname : str - Name of the variable (for display). - val : complex - Current value (real part NaN if not specified). - val0 : complex - Default value. - - Returns - ------- - complex - The (possibly updated) value. - """ - if math.isnan(val.real): - val = val0 - print(f" {valname:>15s} = {val.real:<10.5f} {val.imag:<10.5f}" - f" ###### DEFAULT VALUE IS USED ######") - else: - print(f" {valname:>15s} = {val.real:<10.5f} {val.imag:<10.5f}") - return val - - -def print_val_i(valname: str, val: int, val0: int) -> int: - """Print and optionally set an integer parameter. - - If *val* equals the sentinel ``2147483647`` (NaN_i), set it to *val0*. - - Parameters - ---------- - valname : str - Name of the variable (for display). - val : int - Current value (sentinel if not specified). - val0 : int - Default value. - - Returns - ------- - int - The (possibly updated) value. - """ - if val == NaN_i: - val = val0 - print(f" {valname:>15s} = {val:<10d} ###### DEFAULT VALUE IS USED ######") - else: - print(f" {valname:>15s} = {val:<10d}") - return val - - -def _fail_not_used(valname: str) -> None: - """Print "specified but not used" error and terminate. - - Parameters - ---------- - valname : str - Name of the unused parameter. - """ - print(f"\n Check ! {valname} is SPECIFIED but will NOT be USED. ") - print(" Please COMMENT-OUT this line ") - print(" or check this input is REALLY APPROPRIATE for your purpose !\n") - exit_program(-1) - - -def not_used_d(valname: str, val: float | complex) -> None: - """Abort if a real parameter is specified but will not be used. - - Parameters - ---------- - valname : str - Name of the variable. - val : float or complex - Value to check (abort if not NaN). If complex, the real part - is tested (matching the C behaviour of implicitly casting - ``double complex`` to ``double``). - """ - check = val.real if isinstance(val, complex) else val - if not math.isnan(check): - _fail_not_used(valname) - - -# Spin-interaction suffix matrix for 3x3 J-coupling tensors. -# Shared by not_used_j and input_params module. -SPIN_SUFFIXES: list[list[str]] = [ - ["x", "xy", "xz"], - ["yx", "y", "yz"], - ["zx", "zy", "z"], -] -"""3x3 suffix matrix for spin-interaction tensor components (Jx, Jxy, etc.).""" - - -def not_used_j(valname: str, JAll: float, J: np.ndarray) -> None: - """Abort if any component of a J-type interaction is specified but unused. - - Parameters - ---------- - valname : str - Base name of the variable (e.g. ``"J0"``). - JAll : float - Scalar (isotropic) part. - J : numpy.ndarray - 3x3 matrix of anisotropic components. - """ - not_used_d(valname, JAll) - for i1, i2 in itertools.product(range(3), repeat=2): - not_used_d(f"{valname}{SPIN_SUFFIXES[i1][i2]}", J[i1, i2]) - - -def not_used_i(valname: str, val: int) -> None: - """Abort if an integer parameter is specified but will not be used. - - Parameters - ---------- - valname : str - Name of the variable. - val : int - Value to check (abort if not the sentinel 2147483647). - """ - if val != NaN_i: - _fail_not_used(valname) - - -def required_val_i(valname: str, val: int) -> None: - """Abort if a required integer parameter is missing. - - Parameters - ---------- - valname : str - Name of the variable. - val : int - Value to check (abort if equals the sentinel 2147483647). - """ - if val == NaN_i: - print(f"ERROR ! {valname} is NOT specified !") - exit_program(-1) - else: - print(f" {valname:>15s} = {val:<3d}") diff --git a/python/stdface_main.py b/python/stdface_main.py deleted file mode 100644 index 13b7a28..0000000 --- a/python/stdface_main.py +++ /dev/null @@ -1,677 +0,0 @@ -""" -Read input file and write files for Expert mode; initialize variables; check parameters. - -This module is the Python translation of ``StdFace_main.c``. It provides the -top-level entry point for the Standard-mode input generator used by HPhi, mVMC, -UHF, and H-wave. - -The following lattices are supported: - -- 1D Chain -- 1D Ladder -- 2D Tetragonal -- 2D Triangular -- 2D Honeycomb -- 2D Kagome -- 3D Simple Orthorhombic -- 3D Face Centered Orthorhombic -- 3D Pyrochlore - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -from collections.abc import Callable -from typing import NamedTuple - -from stdface_vals import ( - StdIntList, ModelType, SolverType, MethodType, - NaN_i, NaN_d, NaN_c, UNSET_STRING, -) -from param_check import exit_program -from writer.hphi_writer import ( - large_value as _large_value, - vector_potential as _vector_potential, -) -from writer.common_writer import ( - unsupported_system as _unsupported_system, -) -from writer.solver_writer import get_solver_writer -from keyword_parser import ( - trim_space_quote as _trim_space_quote, - parse_common_keyword as _parse_common_keyword, - parse_solver_keyword as _parse_solver_keyword, -) -from lattice import ( - chain_lattice, - square_lattice, - ladder, - triangular_lattice, - honeycomb_lattice, - kagome, - orthorhombic, - fc_ortho, - pyrochlore, - wannier90 as wannier90_mod, -) - -# --------------------------------------------------------------------------- -# Lattice dispatch tables -# --------------------------------------------------------------------------- -# Maps every recognised lattice alias to its builder function. -# Each function has the signature ``(StdI: StdIntList) -> None``. -LATTICE_DISPATCH: dict[str, Callable[[StdIntList], None]] = { - "chain": chain_lattice.chain, - "chainlattice": chain_lattice.chain, - "face-centeredorthorhombic": fc_ortho.fc_ortho, - "fcorthorhombic": fc_ortho.fc_ortho, - "fco": fc_ortho.fc_ortho, - "face-centeredcubic": fc_ortho.fc_ortho, - "fccubic": fc_ortho.fc_ortho, - "fcc": fc_ortho.fc_ortho, - "honeycomb": honeycomb_lattice.honeycomb, - "honeycomblattice": honeycomb_lattice.honeycomb, - "kagome": kagome.kagome, - "kagomelattice": kagome.kagome, - "ladder": ladder.ladder, - "ladderlattice": ladder.ladder, - "orthorhombic": orthorhombic.orthorhombic, - "simpleorthorhombic": orthorhombic.orthorhombic, - "cubic": orthorhombic.orthorhombic, - "simplecubic": orthorhombic.orthorhombic, - "pyrochlore": pyrochlore.pyrochlore, - "tetragonal": square_lattice.tetragonal, - "tetragonallattice": square_lattice.tetragonal, - "square": square_lattice.tetragonal, - "squarelattice": square_lattice.tetragonal, - "triangular": triangular_lattice.triangular, - "triangularlattice": triangular_lattice.triangular, - "wannier90": wannier90_mod.wannier90, -} -"""Maps lattice name aliases to their builder functions.""" - -BOOST_DISPATCH: dict[str, Callable[[StdIntList], None]] = { - "chain": chain_lattice.chain_boost, - "chainlattice": chain_lattice.chain_boost, - "honeycomb": honeycomb_lattice.honeycomb_boost, - "honeycomblattice": honeycomb_lattice.honeycomb_boost, - "kagome": kagome.kagome_boost, - "kagomelattice": kagome.kagome_boost, - "ladder": ladder.ladder_boost, - "ladderlattice": ladder.ladder_boost, -} -"""Maps lattice name aliases to their boost builder functions (HPhi only).""" - -# --------------------------------------------------------------------------- -# Model name normalisation table -# --------------------------------------------------------------------------- - - -class _ModelConfig(NamedTuple): - """Configuration resolved from a user-facing model name alias. - - Attributes - ---------- - model : ModelType - Canonical model type (HUBBARD, SPIN, or KONDO). - lGC : int - Grand-canonical flag (0 = canonical, 1 = grand-canonical). - lBoost : int - Boost extension flag (0 = off, 1 = on, HPhi only). - """ - - model: ModelType - lGC: int - lBoost: int - - -# Maps user-facing model name aliases to a ``_ModelConfig``. -# Entries that require ``solver == SolverType.HPhi`` are in a separate dict. -MODEL_ALIASES: dict[str, _ModelConfig] = { - "fermionhubbard": _ModelConfig(ModelType.HUBBARD, 0, 0), - "hubbard": _ModelConfig(ModelType.HUBBARD, 0, 0), - "fermionhubbardgc": _ModelConfig(ModelType.HUBBARD, 1, 0), - "hubbardgc": _ModelConfig(ModelType.HUBBARD, 1, 0), - "spin": _ModelConfig(ModelType.SPIN, 0, 0), - "spingc": _ModelConfig(ModelType.SPIN, 1, 0), - "kondolattice": _ModelConfig(ModelType.KONDO, 0, 0), - "kondo": _ModelConfig(ModelType.KONDO, 0, 0), - "kondolatticegc": _ModelConfig(ModelType.KONDO, 1, 0), - "kondogc": _ModelConfig(ModelType.KONDO, 1, 0), -} -"""Maps model name aliases to a :class:`_ModelConfig`.""" - -MODEL_ALIASES_HPHI_BOOST: dict[str, _ModelConfig] = { - "spingcboost": _ModelConfig(ModelType.SPIN, 1, 1), - "spingccma": _ModelConfig(ModelType.SPIN, 1, 1), -} -"""HPhi-only model aliases that enable the Boost extension.""" - -# --------------------------------------------------------------------------- -# Method name normalisation table (HPhi only) -# --------------------------------------------------------------------------- -METHOD_ALIASES: dict[str, MethodType] = { - "direct": MethodType.FULLDIAG, - "alldiag": MethodType.FULLDIAG, - "te": MethodType.TIME_EVOLUTION, - "time-evolution": MethodType.TIME_EVOLUTION, -} -"""Maps HPhi method name aliases to their canonical forms.""" - -# Sentinel constants imported from stdface_vals: -# NaN_i, NaN_d, NaN_c, UNSET_STRING - - -# =================================================================== -# Solver-specific field reset tables -# =================================================================== - -# Each solver maps to a list of (field_name, value) pairs for scalar -# assignments, plus a list of (field_name, value) pairs for array-fill -# assignments (``getattr(StdI, name)[:] = value`` or ``[:, :] = value``). -# -# UHF and HWAVE share a common base; HWAVE adds two extra fields. - -# =================================================================== -# Common (solver-independent) field reset tables -# =================================================================== -# -# _COMMON_RESET_SCALARS lists (field_name, sentinel_value) pairs for -# scalar fields that are reset to NaN_d, NaN_i, or NaN_c by every -# call to _reset_vals(). These replace ~80 individual ``StdI.x = val`` -# assignments. - -_COMMON_RESET_SCALARS: list[tuple[str, object]] = [ - # Lattice scalars - ("a", NaN_d), - # Magnetic field - ("Gamma", NaN_d), - ("Gamma_y", NaN_d), - ("h", NaN_d), - # Lattice dimensions - ("Height", NaN_i), - ("L", NaN_i), - ("W", NaN_i), - # Isotropic scalar spin couplings - ("JAll", NaN_d), - ("JpAll", NaN_d), - ("JppAll", NaN_d), - ("J0All", NaN_d), - ("J0pAll", NaN_d), - ("J0ppAll", NaN_d), - ("J1All", NaN_d), - ("J1pAll", NaN_d), - ("J1ppAll", NaN_d), - ("J2All", NaN_d), - ("J2pAll", NaN_d), - ("J2ppAll", NaN_d), - # Anisotropy / single-ion - ("K", NaN_d), - # Chemical potential / spin - ("mu", NaN_d), - ("S2", NaN_i), - # Hopping parameters (complex) - ("t", NaN_c), - ("tp", NaN_c), - ("tpp", NaN_c), - ("t0", NaN_c), - ("t0p", NaN_c), - ("t0pp", NaN_c), - ("t1", NaN_c), - ("t1p", NaN_c), - ("t1pp", NaN_c), - ("t2", NaN_c), - ("t2p", NaN_c), - ("t2pp", NaN_c), - # Coulomb parameters - ("U", NaN_d), - ("V", NaN_d), - ("Vp", NaN_d), - ("Vpp", NaN_d), - ("V0", NaN_d), - ("V0p", NaN_d), - ("V0pp", NaN_d), - ("V1", NaN_d), - ("V1p", NaN_d), - ("V1pp", NaN_d), - ("V2", NaN_d), - ("V2p", NaN_d), - ("V2pp", NaN_d), - # Calculation conditions - ("ncond", NaN_i), - ("Sz2", NaN_i), - # Wannier90 cutoffs - ("cutoff_t", NaN_d), - ("cutoff_u", NaN_d), - ("cutoff_j", NaN_d), - ("cutoff_length_t", NaN_d), - ("cutoff_length_U", NaN_d), - ("cutoff_length_J", NaN_d), - ("lambda_", NaN_d), - ("lambda_U", NaN_d), - ("lambda_J", NaN_d), - ("alpha", NaN_d), -] -"""Common scalar field resets — ``setattr(StdI, name, value)``.""" - - -_COMMON_RESET_ARRAYS: list[tuple[str, object]] = [ - # Lattice vectors - ("length", NaN_d), - ("box", NaN_i), - ("direct", NaN_d), - # 3x3 spin coupling matrices - ("J", NaN_d), - ("Jp", NaN_d), - ("Jpp", NaN_d), - ("J0", NaN_d), - ("J0p", NaN_d), - ("J0pp", NaN_d), - ("J1", NaN_d), - ("J1p", NaN_d), - ("J1pp", NaN_d), - ("J2", NaN_d), - ("J2p", NaN_d), - ("J2pp", NaN_d), - # Phase / boundary - ("phase", NaN_d), - # Wannier90 cutoff vectors - ("cutoff_tR", NaN_i), - ("cutoff_UR", NaN_i), - ("cutoff_JR", NaN_i), - ("cutoff_tVec", NaN_d), - ("cutoff_UVec", NaN_d), - ("cutoff_JVec", NaN_d), -] -"""Common array-fill field resets — ``getattr(StdI, name)[...] = value``.""" - - -_UHF_BASE_SCALARS: list[tuple[str, object]] = [ - ("NMPTrans", NaN_i), - ("RndSeed", NaN_i), - ("mix", NaN_d), - ("eps", NaN_i), - ("eps_slater", NaN_i), - ("Iteration_max", NaN_i), - ("Hsub", NaN_i), - ("Lsub", NaN_i), - ("Wsub", NaN_i), -] - -_UHF_BASE_ARRAYS: list[tuple[str, object]] = [ - ("boxsub", NaN_i), -] - -_SOLVER_RESET_SCALARS: dict[SolverType, list[tuple[str, object]]] = { - SolverType.HPhi: [ - ("LargeValue", NaN_d), - ("OmegaMax", NaN_d), - ("OmegaMin", NaN_d), - ("OmegaOrg", NaN_d), - ("OmegaIm", NaN_d), - ("Nomega", NaN_i), - ("FlgTemp", 1), - ("Lanczos_max", NaN_i), - ("initial_iv", NaN_i), - ("nvec", NaN_i), - ("exct", NaN_i), - ("LanczosEps", NaN_i), - ("LanczosTarget", NaN_i), - ("NumAve", NaN_i), - ("ExpecInterval", NaN_i), - ("dt", NaN_d), - ("tdump", NaN_d), - ("tshift", NaN_d), - ("freq", NaN_d), - ("Uquench", NaN_d), - ("ExpandCoef", NaN_i), - ("NGPU", NaN_i), - ("Scalapack", NaN_i), - ], - SolverType.mVMC: [ - ("NVMCCalMode", NaN_i), - ("NLanczosMode", NaN_i), - ("NDataIdxStart", NaN_i), - ("NDataQtySmp", NaN_i), - ("NSPGaussLeg", NaN_i), - ("NSPStot", NaN_i), - ("NMPTrans", NaN_i), - ("NSROptItrStep", NaN_i), - ("NSROptItrSmp", NaN_i), - ("DSROptRedCut", NaN_d), - ("DSROptStaDel", NaN_d), - ("DSROptStepDt", NaN_d), - ("NVMCWarmUp", NaN_i), - ("NVMCInterval", NaN_i), - ("NVMCSample", NaN_i), - ("NExUpdatePath", NaN_i), - ("RndSeed", NaN_i), - ("NSplitSize", NaN_i), - ("NStore", NaN_i), - ("NSRCG", NaN_i), - ("ComplexType", NaN_i), - ("Hsub", NaN_i), - ("Lsub", NaN_i), - ("Wsub", NaN_i), - ], - SolverType.UHF: _UHF_BASE_SCALARS, - SolverType.HWAVE: _UHF_BASE_SCALARS + [ - ("export_all", NaN_i), - ("lattice_gp", NaN_i), - ], -} -"""Scalar field resets for each solver — ``setattr(StdI, name, value)``.""" - -_SOLVER_RESET_ARRAYS: dict[SolverType, list[tuple[str, object]]] = { - SolverType.HPhi: [ - ("SpectrumQ", NaN_d), - ("VecPot", NaN_d), - ], - SolverType.mVMC: [ - ("boxsub", NaN_i), - ], - SolverType.UHF: _UHF_BASE_ARRAYS, - SolverType.HWAVE: _UHF_BASE_ARRAYS, -} -"""Array-fill field resets for each solver — ``getattr(StdI, name)[...] = value``.""" - - -def _apply_field_resets(StdI: StdIntList, solver: SolverType) -> None: - """Apply solver-specific field resets from the data tables. - - Parameters - ---------- - StdI : StdIntList - The parameter structure to reset (modified in place). - solver : SolverType - The solver whose field-reset tables to apply. - """ - for name, value in _SOLVER_RESET_SCALARS.get(solver, ()): - setattr(StdI, name, value) - for name, value in _SOLVER_RESET_ARRAYS.get(solver, ()): - arr = getattr(StdI, name) - arr[...] = value - - -# =================================================================== -# _reset_vals -# =================================================================== - - -def _reset_vals(StdI: StdIntList) -> None: - """Clear / initialize every field in *StdI* to its sentinel value. - - This is the Python translation of the C function - ``StdFace_ResetVals()``. Fields that have not been specified by - the user are filled with NaN sentinels so that duplicate-input - detection and default-value assignment work correctly later. - - The common (solver-independent) fields are driven by the - ``_COMMON_RESET_SCALARS`` and ``_COMMON_RESET_ARRAYS`` tables. - Solver-specific fields are handled by ``_apply_field_resets()``. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure whose fields are reset **in - place**. - """ - # --- Common scalar fields (table-driven) -------------------------------- - for name, value in _COMMON_RESET_SCALARS: - setattr(StdI, name, value) - - # --- Common array fields (table-driven) --------------------------------- - for name, value in _COMMON_RESET_ARRAYS: - getattr(StdI, name)[...] = value - - # --- D matrix: zero everywhere except D[2][2] = NaN_d ------------------- - StdI.D[:, :] = 0.0 - StdI.D[2, 2] = NaN_d - - # --- Solver-specific fields (table-driven) ------------------------------ - _apply_field_resets(StdI, StdI.solver) - - # --- Boost (always zero, not NaN) --------------------------------------- - StdI.lBoost = 0 - - -# Keyword parsing helpers (_trim_space_quote, -# _store_with_check_dup_s/sl/i/d/c) and keyword parsers -# (_parse_common_keyword, _parse_solver_keyword) -# have been moved to keyword_parser.py - - -# =================================================================== -# Model / method resolution -# =================================================================== - - -def _resolve_model_and_method(StdI: StdIntList, solver: str) -> None: - """Normalise the model name and HPhi method, updating *StdI* in place. - - Looks up ``StdI.model`` in :data:`MODEL_ALIASES` (and, for HPhi, - :data:`MODEL_ALIASES_HPHI_BOOST`) to resolve the canonical - :class:`ModelType`, the grand-canonical flag ``lGC``, and the Boost - flag ``lBoost``. - - For HPhi, also normalises ``StdI.method`` via :data:`METHOD_ALIASES` - and, if the method is time-evolution, computes the vector potential. - - Parameters - ---------- - StdI : StdIntList - Parameter structure (modified in place). - solver : str - Solver name. - - Raises - ------ - SystemExit - If the model name is not recognised. - """ - StdI.lGC = 0 - StdI.lBoost = 0 - - model_info = MODEL_ALIASES.get(StdI.model) - if model_info is None and solver == SolverType.HPhi: - model_info = MODEL_ALIASES_HPHI_BOOST.get(StdI.model) - if model_info is not None: - StdI.model, StdI.lGC, StdI.lBoost = model_info - else: - _unsupported_system(StdI.model, StdI.lattice) - - if solver == SolverType.HPhi: - StdI.method = METHOD_ALIASES.get(StdI.method, StdI.method) - - if StdI.method == MethodType.TIME_EVOLUTION: - _vector_potential(StdI) - - -# =================================================================== -# Lattice construction and Boost -# =================================================================== - - -def _build_lattice_and_boost(StdI: StdIntList, solver: str) -> None: - """Dispatch to the lattice builder and, for HPhi, apply LargeValue and Boost. - - Looks up ``StdI.lattice`` in :data:`LATTICE_DISPATCH` to generate - the Hamiltonian definition files. For the HPhi solver, also - computes the large value and optionally runs the Boost builder. - - Parameters - ---------- - StdI : StdIntList - Parameter structure (modified in place). - solver : str - Solver name. - - Raises - ------ - SystemExit - If the lattice is not recognised. - """ - lattice = StdI.lattice - lattice_builder = LATTICE_DISPATCH.get(lattice) - if lattice_builder is not None: - lattice_builder(StdI) - else: - _unsupported_system(StdI.model, StdI.lattice) - - if solver == SolverType.HPhi: - _large_value(StdI) - - if StdI.lBoost == 1: - boost_builder = BOOST_DISPATCH.get(lattice) - if boost_builder is not None: - boost_builder(StdI) - else: - _unsupported_system(StdI.model, StdI.lattice) - - -# =================================================================== -# Input file parsing -# =================================================================== - - -def _parse_input_file(fname: str, StdI: StdIntList, solver: str) -> None: - """Open and parse a Standard-mode input file into *StdI*. - - Each non-blank, non-comment line must contain ``keyword = value``. - Common keywords are tried first, then solver-specific keywords. - The program exits on duplicate keywords, missing ``=``, or - unrecognised keywords. - - Parameters - ---------- - fname : str - Path to the Standard-mode input file. - StdI : StdIntList - Parameter structure to populate (modified in place). - solver : str - Solver name (``"HPhi"``, ``"mVMC"``, ``"UHF"``, or ``"HWAVE"``). - - Raises - ------ - SystemExit - If the file cannot be opened, a line lacks ``=``, or a keyword - is unrecognised. - """ - try: - fp_in = open(fname, "r") - except OSError: - print(f"\n ERROR ! Cannot open input file {fname} !\n") - exit_program(-1) - - print(f"\n Open Standard-Mode Inputfile {fname} \n") - - with fp_in: - for raw_line in fp_in: - line = _trim_space_quote(raw_line) - - if line.startswith("//") or line == "": - print(" Skipping a line.") - continue - - parts = line.split("=", 1) - if len(parts) < 2: - print('\n ERROR ! "=" is NOT found !\n') - exit_program(-1) - - keyword = parts[0].lower() - value = parts[1] - print(f" KEYWORD : {keyword:<20s} | VALUE : {value} ") - - if not _parse_common_keyword(keyword, value, StdI): - if not _parse_solver_keyword(keyword, value, StdI, solver): - print("ERROR ! Unsupported Keyword in Standard mode!") - exit_program(-1) - - -# =================================================================== -# stdface_main -- top-level entry point -# =================================================================== - - -def stdface_main(fname: str, solver: str = "HPhi") -> None: - """Read a Standard-mode input file and generate Expert-mode definition files. - - This is the Python translation of the C function ``StdFace_main()`` - (``StdFace_main.c``, lines 2455--3067). It performs the following - sequence of operations: - - 1. Create and initialise a :class:`StdIntList` parameter structure. - 2. Parse the input file, mapping keywords to structure fields. - 3. Validate and normalise the model name, lattice name and method. - 4. Dispatch to the appropriate lattice-generation function. - 5. Write all Expert-mode definition files required by the chosen - solver. - - Parameters - ---------- - fname : str - Path to the Standard-mode input file. - solver : str, optional - Name of the solver that will consume the generated files. - Must be one of ``"HPhi"``, ``"mVMC"``, ``"UHF"`` or ``"HWAVE"``. - Defaults to ``"HPhi"``. This replaces the compile-time - ``#ifdef`` mechanism of the original C code. - - Raises - ------ - SystemExit - If the input file cannot be opened, a keyword is duplicated or - unrecognised, or a required parameter is missing. - """ - # ------------------------------------------------------------------ - # Initialise - # ------------------------------------------------------------------ - StdI = StdIntList() - StdI.solver = solver - - print("\n###### Input Parameter of Standard Intarface ######") - - _reset_vals(StdI) - _parse_input_file(fname, StdI, solver) - - # ------------------------------------------------------------------ - # Construct Model - # ------------------------------------------------------------------ - print("") - print("####### Construct Model #######") - print("") - - # CDataFileHead default - if StdI.CDataFileHead == UNSET_STRING: - StdI.CDataFileHead = "zvo" - print(f" CDataFileHead = {'zvo':<12s}###### DEFAULT VALUE IS USED ######") - else: - print(f" CDataFileHead = {StdI.CDataFileHead}") - - _resolve_model_and_method(StdI, solver) - - _build_lattice_and_boost(StdI, solver) - - # ------------------------------------------------------------------ - # Print Expert input files - # ------------------------------------------------------------------ - print("") - print("###### Print Expert input files ######") - print("") - - writer = get_solver_writer(solver) - writer.write(StdI) - - # ------------------------------------------------------------------ - # Finalise - # ------------------------------------------------------------------ - print("\n###### Input files are generated. ######\n") diff --git a/python/stdface_model_util.py b/python/stdface_model_util.py deleted file mode 100644 index e773c80..0000000 --- a/python/stdface_model_util.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Utility functions for constructing lattice models in Standard mode. - -This module was formerly a monolithic utility library (~1,700 lines). -It has been decomposed into focused modules during Phase 2 refactoring. -All public symbols are re-exported here for backward compatibility so -that existing lattice modules can continue to use:: - - from stdface_model_util import init_site, find_site, ... - -without modification. - -Re-exported modules -------------------- -param_check - Parameter validation and printing utilities. -input_params - Input parameter resolution helpers. -geometry_output - Geometry and structure output functions. -interaction_builder - Hamiltonian term builder functions and array allocation. -site_util - Super-cell initialisation, site folding, finding, and labelling. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -from param_check import ( # noqa: F401 – re-exported for backward compatibility - exit_program, - print_val_d, - print_val_dd, - print_val_c, - print_val_i, - not_used_d, - not_used_j, - not_used_i, - required_val_i, -) -from lattice.input_params import ( # noqa: F401 – re-exported for backward compatibility - input_spin_nn, - input_spin, - input_coulomb_v, - input_hopp, -) -from lattice.geometry_output import ( # noqa: F401 – re-exported for backward compatibility - print_xsf, - print_geometry, -) -from lattice.interaction_builder import ( # noqa: F401 – re-exported for backward compatibility - trans, - hopping, - hubbard_local, - mag_field, - intr, - general_j, - coulomb, - malloc_interactions, -) -from lattice.site_util import ( # noqa: F401 – re-exported for backward compatibility - _fold_site, - init_site, - find_site, - set_label, -) diff --git a/python/stdface_vals.py b/python/stdface_vals.py deleted file mode 100644 index 05ede65..0000000 --- a/python/stdface_vals.py +++ /dev/null @@ -1,877 +0,0 @@ -""" -Variables used in the Standard mode. - -This module defines the StdIntList dataclass which contains all the variables -and parameters used in the Standard mode of HPhi-mVMC-StdFace. In the original -C code these variables are passed as a pointer to the StdIntList structure. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from enum import Enum - -import numpy as np - - -class ModelType(str, Enum): - """Canonical model type identifiers. - - Inherits from ``str`` so that ``ModelType.SPIN == "spin"`` is ``True``, - preserving full backward compatibility with existing string comparisons. - - Attributes - ---------- - SPIN : str - Pure spin model (S=1/2 or general S). - HUBBARD : str - Fermion Hubbard model. - KONDO : str - Kondo lattice model (itinerant + localised spins). - """ - - SPIN = "spin" - HUBBARD = "hubbard" - KONDO = "kondo" - - -class SolverType(str, Enum): - """Canonical solver type identifiers. - - Inherits from ``str`` so that ``SolverType.HPhi == "HPhi"`` is ``True``, - preserving full backward compatibility with existing string comparisons. - - Attributes - ---------- - HPhi : str - Exact-diagonalisation / Lanczos solver. - mVMC : str - Many-variable Variational Monte Carlo solver. - UHF : str - Unrestricted Hartree-Fock solver. - HWAVE : str - H-wave solver (Hartree-Fock / RPA / Wannier90 export). - """ - - HPhi = "HPhi" - mVMC = "mVMC" - UHF = "UHF" - HWAVE = "HWAVE" - - -class MethodType(str, Enum): - """Canonical HPhi calculation method identifiers. - - Inherits from ``str`` so that ``MethodType.LANCZOS == "lanczos"`` is - ``True``, preserving full backward compatibility with existing string - comparisons. - - Attributes - ---------- - LANCZOS : str - Lanczos diagonalisation. - LANCZOS_ENERGY : str - Lanczos (energy-only, skip eigenvector). - TPQ : str - Thermal Pure Quantum state method. - FULLDIAG : str - Full (direct) diagonalisation. - CG : str - Conjugate Gradient method. - TIME_EVOLUTION : str - Real-time evolution (pump / quench). - CTPQ : str - Canonical Thermal Pure Quantum state method. - """ - - LANCZOS = "lanczos" - LANCZOS_ENERGY = "lanczosenergy" - TPQ = "tpq" - FULLDIAG = "fulldiag" - CG = "cg" - TIME_EVOLUTION = "timeevolution" - CTPQ = "ctpq" - - -# --------------------------------------------------------------------------- -# Sentinel constants (matching the C code) -# --------------------------------------------------------------------------- - -NaN_i: int = 2147483647 -"""Sentinel for an unset integer parameter (same as ``INT_MAX`` in C).""" - -NaN_d: float = float("nan") -"""Sentinel for an unset float parameter (IEEE NaN).""" - -NaN_c: complex = complex(float("nan"), 0.0) -"""Sentinel for an unset complex parameter (real part is NaN).""" - -UNSET_STRING: str = "****" -"""Sentinel for an unset string parameter (C convention ``"****"``).""" - - -# --------------------------------------------------------------------------- -# Numerical tolerances -# --------------------------------------------------------------------------- - -AMPLITUDE_EPS: float = 1e-6 -"""Threshold for treating an amplitude as non-zero in output files. - -Terms with ``abs(coeff) <= AMPLITUDE_EPS`` are suppressed when writing -interaction, transfer, and pump definition files. -""" - -ZERO_BODY_EPS: float = 1e-12 -"""Threshold for skipping zero-amplitude terms when accumulating -one-body (transfer) and two-body (InterAll) Hamiltonian entries. -""" - - -@dataclass -class StdIntList: - """Main structure containing all parameters and variables for Standard mode. - - Attributes - ---------- - lattice : str - Name of lattice. Input parameter. - a : float - The lattice constant. Input parameter. - length : np.ndarray - Anisotropic lattice constant (shape ``(3,)``), input parameter - ``wlength``, ``llength``, ``hlength``. - W : int - Number of sites along the 1st axis, input parameter. - L : int - Number of sites along the 2nd axis, input parameter. - Height : int - Number of sites along the 3rd axis, input parameter. - direct : np.ndarray - The unit direct lattice vector (shape ``(3, 3)``). - Set in ``StdFace_InitSite()``. - box : np.ndarray - The shape of the super-cell (shape ``(3, 3)``, int). - Input parameter ``a0W``, ``a0L``, ``a0H``, etc. or defined from - ``W``, etc. in ``StdFace_InitSite()``. - rbox : np.ndarray - The inversion of ``box`` (shape ``(3, 3)``, int). - Set in ``StdFace_InitSite()``. - NCell : int - The number of the unit cell in the super-cell (determinant of ``box``). - Set in ``StdFace_InitSite()``. - Cell : np.ndarray or None - ``[NCell][3]`` The cell position in the fractional coordinate. - Malloc and set in ``StdFace_InitSite()``. - NsiteUC : int - Number of sites in the unit cell. Defined in the beginning of each - lattice function. - tau : np.ndarray or None - Cell-internal site position in the fractional coordinate. - Defined in the beginning of each lattice function. - model : str - Name of model, input parameter. - mu : float - Chemical potential, input parameter. - t : complex - Nearest-neighbor hopping, input parameter. - tp : complex - 2nd-nearest hopping, input parameter. - t0 : complex - Anisotropic hopping (1st), input parameter. - t0p : complex - Anisotropic hopping (2nd), input parameter. - t0pp : complex - Anisotropic hopping (3rd), input parameter. - t1 : complex - Anisotropic hopping (1st), input parameter. - t1p : complex - Anisotropic hopping (2nd), input parameter. - t1pp : complex - Anisotropic hopping (3rd), input parameter. - t2 : complex - Anisotropic hopping (1st), input parameter. - t2p : complex - Anisotropic hopping (2nd), input parameter. - t2pp : complex - Anisotropic hopping (3rd), input parameter. - tpp : complex - 3rd-nearest hopping, input parameter. - U : float - On-site Coulomb potential, input parameter. - V : float - Off-site Coulomb potential (1st), input parameter. - Vp : float - Off-site Coulomb potential (2nd), input parameter. - V0 : float - Anisotropic Coulomb potential (1st), input parameter. - V0p : float - Anisotropic Coulomb potential (2nd), input parameter. - V0pp : float - Anisotropic Coulomb potential (3rd), input parameter. - V1 : float - Anisotropic Coulomb potential (1st), input parameter. - V1p : float - Anisotropic Coulomb potential (2nd), input parameter. - V1pp : float - Anisotropic Coulomb potential (3rd), input parameter. - V2 : float - Anisotropic Coulomb potential (1st), input parameter. - V2p : float - Anisotropic Coulomb potential (2nd), input parameter. - V2pp : float - Anisotropic Coulomb potential (3rd), input parameter. - Vpp : float - Off-site Coulomb potential (3rd), input parameter. - JAll : float - Isotropic, diagonal spin coupling (1st Near.), input parameter ``J``. - JpAll : float - Isotropic, diagonal spin coupling (2nd Near), input parameter ``Jp``. - J0All : float - Anisotropic, diagonal spin coupling (1st Near), input parameter ``J0``. - J0pAll : float - Anisotropic, diagonal spin coupling (2nd Near), input parameter ``J0'``. - J0ppAll : float - Anisotropic, diagonal spin coupling (3rd Near), input parameter ``J0''``. - J1All : float - Anisotropic, diagonal spin coupling (1st Near), input parameter ``J1``. - J1pAll : float - Anisotropic, diagonal spin coupling (2nd Near), input parameter ``J1'``. - J1ppAll : float - Anisotropic, diagonal spin coupling (3rd Near), input parameter ``J1''``. - J2All : float - Anisotropic, diagonal spin coupling (1st Near), input parameter ``J2``. - J2pAll : float - Anisotropic, diagonal spin coupling (2nd Near), input parameter ``J2'``. - J2ppAll : float - Anisotropic, diagonal spin coupling (3rd Near), input parameter ``J2''``. - JppAll : float - Isotropic, diagonal spin coupling (3rd Near), input parameter ``J''``. - J : np.ndarray - Isotropic, diagonal/off-diagonal spin coupling (1st Near.) - (shape ``(3, 3)``). - Jp : np.ndarray - Isotropic, diagonal/off-diagonal spin coupling (2nd Near.) - (shape ``(3, 3)``). - J0 : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (1st Near.) - (shape ``(3, 3)``). - J0p : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (2nd Near.) - (shape ``(3, 3)``). - J0pp : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (3rd Near.) - (shape ``(3, 3)``). - J1 : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (1st Near.) - (shape ``(3, 3)``). - J1p : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (2nd Near.) - (shape ``(3, 3)``). - J1pp : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (3rd Near.) - (shape ``(3, 3)``). - J2 : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (1st Near.) - (shape ``(3, 3)``). - J2p : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (2nd Near.) - (shape ``(3, 3)``). - J2pp : np.ndarray - Anisotropic, diagonal/off-diagonal spin coupling (3rd Near.) - (shape ``(3, 3)``). - Jpp : np.ndarray - Isotropic, diagonal/off-diagonal spin coupling (3rd Near.) - (shape ``(3, 3)``). - D : np.ndarray - Coefficient for S_iz S_iz (shape ``(3, 3)``). - Only ``D[2][2]`` is used. - h : float - Longitudinal magnetic field, input parameter. - Gamma : float - Transverse magnetic field, input parameter. - Gamma_y : float - Transverse magnetic field (y), input parameter. - K : float - 4-spin term. Not used. - phase : np.ndarray - Boundary phase (shape ``(3,)``), input parameter ``phase0``, etc. - ExpPhase : np.ndarray - exp(i * pi * phase / 180) (shape ``(3,)``, complex). - AntiPeriod : np.ndarray - If corresponding ``phase`` == 180, it becomes 1 (shape ``(3,)``, int). - nsite : int - Number of sites, set in each lattice file. - locspinflag : np.ndarray or None - ``[nsite]`` LocSpin in Expert mode. - ntrans : int - Number of transfer, counted in each lattice file. - transindx : np.ndarray or None - ``[ntrans][4]`` Site/spin indices of one-body term. - trans : np.ndarray or None - ``[ntrans]`` Coefficient of one-body term (complex). - nintr : int - Number of InterAll, counted in each lattice file. - Lintr : int - Print ``interall.def`` or not. - intrindx : np.ndarray or None - ``[nintr][8]`` Site/spin indices of two-body term. - intr : np.ndarray or None - ``[nintr]`` Coefficient of general two-body term (complex). - NCintra : int - Number of intra-site Coulomb interaction. - LCintra : int - Print ``coulombintra.def`` or not. - CintraIndx : np.ndarray or None - ``[NCintra][1]`` Site indices of intra-site Coulomb term. - Cintra : np.ndarray or None - ``[NCintra]`` Coefficient of intra-site Coulomb term. - NCinter : int - Number of inter-site Coulomb interaction. - LCinter : int - Print ``coulombinter.def`` or not. - CinterIndx : np.ndarray or None - ``[NCinter][2]`` Site indices of inter-site Coulomb term. - Cinter : np.ndarray or None - ``[NCinter]`` Coefficient of inter-site Coulomb term. - NHund : int - Number of Hund term. - LHund : int - Print ``hund.def`` or not. - HundIndx : np.ndarray or None - ``[NHund][2]`` Site indices of Hund term. - Hund : np.ndarray or None - ``[NHund]`` Coefficient of Hund term. - NEx : int - Number of exchange term. - LEx : int - Print ``exchange.def`` or not. - ExIndx : np.ndarray or None - ``[NEx][2]`` Site indices of exchange term. - Ex : np.ndarray or None - ``[NEx]`` Coefficient of exchange term. - NPairLift : int - Number of pair-lift term. - LPairLift : int - Print ``pairlift.def`` or not. - PLIndx : np.ndarray or None - ``[NPairLift][2]`` Site indices of pair-lift term. - PairLift : np.ndarray or None - ``[NPairLift]`` Coefficient of pair-lift term. - NPairHopp : int - Number of pair-hopping term. - LPairHopp : int - Print ``pairhopp.def`` or not. - PHIndx : np.ndarray or None - ``[NPairHopp][2]`` Site indices of pair-hopping term. - PairHopp : np.ndarray or None - ``[NPairHopp]`` Coefficient of pair-hopping term. - lBoost : int - Boost flag. - ncond : int - Number of electrons, input from file. - lGC : int - Switch for computing grand-canonical ensemble (== 1). - S2 : int - Total spin |S| of a local spin, input from file. - outputmode : str - Select amount of correlation function, input from file. - CDataFileHead : str - Header of the output files, input from file. - Sz2 : int - Total Sz, input from file. - ioutputmode : int - Switch associated to ``outputmode``. - cutoff_t : float - Cutoff for the hopping in wannier90, input from file. - cutoff_u : float - Cutoff for the Coulomb in wannier90, input from file. - cutoff_j : float - Cutoff for the Hund in wannier90, input from file. - cutoff_length_t : float - Cutoff for R in wannier90, input from file. - cutoff_length_U : float - Cutoff for R in wannier90, input from file. - cutoff_length_J : float - Cutoff for R in wannier90, input from file. - cutoff_tR : np.ndarray - Cutoff R-vector for hopping (shape ``(3,)``, int). - cutoff_UR : np.ndarray - Cutoff R-vector for Coulomb (shape ``(3,)``, int). - cutoff_JR : np.ndarray - Cutoff R-vector for Hund (shape ``(3,)``, int). - cutoff_tVec : np.ndarray - Cutoff vector for hopping (shape ``(3, 3)``). - cutoff_UVec : np.ndarray - Cutoff vector for Coulomb (shape ``(3, 3)``). - cutoff_JVec : np.ndarray - Cutoff vector for Hund (shape ``(3, 3)``). - lambda_ : float - Tuning parameter of U and J in wannier90, input from file. - Named ``lambda_`` because ``lambda`` is a Python keyword. - lambda_U : float - Tuning parameter of U in wannier90, input from file. - lambda_J : float - Tuning parameter of J in wannier90, input from file. - double_counting_mode : str - Select mode of double counting, input from file. - alpha : float - Tuning parameter of chemical potential correction in wannier90, - input from file. - solver : str - Which solver is active: ``"HPhi"``, ``"mVMC"``, ``"UHF"``, - or ``"HWAVE"``. - method : str - (HPhi) The name of method, input from file. - Restart : str - (HPhi) The name of restart mode, input from file. - InitialVecType : str - (HPhi) The name of initial-guess type, input from file. - EigenVecIO : str - (HPhi) The name of I/O mode for eigenvector, input from file. - HamIO : str - (HPhi) The name of I/O mode for Hamiltonian, input from file. - FlgTemp : int - (HPhi) Temperature flag. - Lanczos_max : int - (HPhi) The maximum number of iterations, input from file. - initial_iv : int - (HPhi) The number for generating random number, input from file. - nvec : int - (HPhi) Number of vectors. - exct : int - (HPhi) The number of eigenvectors to be computed, input from file. - LanczosEps : int - (HPhi) Convergence threshold for the Lanczos method. - LanczosTarget : int - (HPhi) Which eigenvector is used for the convergence check. - NumAve : int - (HPhi) Number of trials for TPQ calculation. - ExpecInterval : int - (HPhi) Interval for the iteration when the expectation value is - computed. - LargeValue : float - (HPhi) The shift parameter for the TPQ calculation. - NGPU : int - (HPhi) Number of GPU for FullDiag. - Scalapack : int - (HPhi) Flag for FullDiag w/ Scalapack. - list_6spin_pair : None - (HPhi) Boost 6-spin pair list (dynamic 3D int array). - list_6spin_star : None - (HPhi) Boost 6-spin star list (dynamic 2D int array). - num_pivot : int - (HPhi) Boost pivot number. - ishift_nspin : int - (HPhi) Boost spin shift. - CalcSpec : str - (HPhi) The name of mode for spectrum, input from file. - SpectrumType : str - (HPhi) The type of mode for spectrum, input from file. - Nomega : int - (HPhi) Number of frequencies, input from file. - OmegaMax : float - (HPhi) Maximum of frequency for spectrum, input from file. - OmegaMin : float - (HPhi) Minimum of frequency for spectrum, input from file. - OmegaOrg : float - (HPhi) Origin of frequency for spectrum, input from file. - OmegaIm : float - (HPhi) Imaginary part of frequency. - SpectrumQ : np.ndarray - (HPhi) Wavenumber (q-vector) in fractional coordinate - (shape ``(3,)``). - SpectrumBody : int - (HPhi) One- or two-body excitation, defined from ``SpectrumType``. - OutputExVec : str - (HPhi) The name of output mode for the excited vector, input from file. - dt : float - (HPhi) Time step. - tshift : float - (HPhi) Shift of time-step of laser. - tdump : float - (HPhi) Time scale of dumping. - freq : float - (HPhi) Frequency of laser. - Uquench : float - (HPhi) Quenched on-site potential. - VecPot : np.ndarray - (HPhi) Vector potential (shape ``(3,)``). - PumpType : str - (HPhi) The type of pump. - PumpBody : int - (HPhi) One- or two-body pumping, defined from ``PumpType``. - npump : None - (HPhi) Number of pump transfers (dynamic 1D int array). - pumpindx : None - (HPhi) Site/spin indices for pump (dynamic 3D int array). - pump : None - (HPhi) Coefficient for pump (dynamic 2D complex array). - At : None - (HPhi) Vector potential time series (dynamic 2D float array). - ExpandCoef : int - (HPhi) The number of Hamiltonian-vector operations for time evolution. - CParaFileHead : str - (mVMC) Header of the optimized wavefunction, input from file. - NVMCCalMode : int - (mVMC) Optimization (=0) or compute correlation function (=1), - input from file. - NLanczosMode : int - (mVMC) Power Lanczos (=1), input from file. - NDataIdxStart : int - (mVMC) Start index of trials, input from file. - NDataQtySmp : int - (mVMC) Number of trials, input from file. - NSPGaussLeg : int - (mVMC) Number of Gauss-Legendre points for spin projection, - input from file. - NMPTrans : int - (mVMC/UHF/HWAVE) Number of translation symmetry. - NSROptItrStep : int - (mVMC) Number of iterations for stochastic reconfiguration. - NSROptItrSmp : int - (mVMC) Number of steps for sampling. - NSROptFixSmp : int - (mVMC) Stochastic reconfiguration parameter. - DSROptRedCut : float - (mVMC) Stochastic reconfiguration parameter, input from file. - DSROptStaDel : float - (mVMC) Stochastic reconfiguration parameter, input from file. - DSROptStepDt : float - (mVMC) Stochastic reconfiguration parameter, input from file. - NVMCWarmUp : int - (mVMC) VMC warm-up steps. - NVMCInterval : int - (mVMC) VMC interval. - NVMCSample : int - (mVMC) VMC sample count. - NExUpdatePath : int - (mVMC) Exchange update path. - RndSeed : int - (mVMC/UHF/HWAVE) Random seed. - NSplitSize : int - (mVMC) Split size. - NSPStot : int - (mVMC) Total spin S. - NStore : int - (mVMC) Store flag. - NSRCG : int - (mVMC) SR-CG flag. - ComplexType : int - (mVMC) Complex type flag. - Lsub : int - (mVMC/UHF/HWAVE) Sublattice L. - Wsub : int - (mVMC/UHF/HWAVE) Sublattice W. - Hsub : int - (mVMC/UHF/HWAVE) Sublattice H. - NCellsub : int - (mVMC/UHF/HWAVE) Number of cells in a sublattice. - boxsub : np.ndarray - (mVMC/UHF/HWAVE) Sublattice box (shape ``(3, 3)``, int). - rboxsub : np.ndarray - (mVMC/UHF/HWAVE) Sublattice inverse box (shape ``(3, 3)``, int). - Orb : np.ndarray or None - (mVMC) ``[nsite][nsite]`` Orbital index (dynamic 2D int array). - AntiOrb : np.ndarray or None - (mVMC) ``[nsite][nsite]`` Anti-periodic switch (dynamic 2D int array). - NOrb : int - (mVMC) Number of independent orbital index. - NSym : int - (mVMC) Number of translation symmetries. - mix : float - (UHF/HWAVE) Linear mixing ratio for update. - eps : int - (UHF/HWAVE) Convergence threshold for Green's functions. - eps_slater : int - (UHF/HWAVE) Convergence threshold for Slater's functions. - Iteration_max : int - (UHF/HWAVE) Max number for iterations. - calcmode : str - (HWAVE) Calculation mode: UHF, UHFk. - fileprefix : str - (HWAVE) Prefix of output filenames. - export_all : int - (HWAVE) Output zero elements in UHFk mode. - lattice_gp : int - (HWAVE) Create ``lattice.gp``. - """ - - # ------------------------------------------------------------------ - # Parameters for LATTICE - # ------------------------------------------------------------------ - lattice: str = UNSET_STRING - a: float = 0.0 - length: np.ndarray = field(default_factory=lambda: np.zeros(3)) - W: int = 0 - L: int = 0 - Height: int = 0 - direct: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - box: np.ndarray = field(default_factory=lambda: np.zeros((3, 3), dtype=int)) - rbox: np.ndarray = field(default_factory=lambda: np.zeros((3, 3), dtype=int)) - NCell: int = 0 - Cell: None = None - NsiteUC: int = 0 - tau: None = None - - # ------------------------------------------------------------------ - # Parameters for MODEL - # ------------------------------------------------------------------ - model: str = UNSET_STRING - mu: float = 0.0 - - # Hopping parameters (complex) - t: complex = 0 + 0j - tp: complex = 0 + 0j - t0: complex = 0 + 0j - t0p: complex = 0 + 0j - t0pp: complex = 0 + 0j - t1: complex = 0 + 0j - t1p: complex = 0 + 0j - t1pp: complex = 0 + 0j - t2: complex = 0 + 0j - t2p: complex = 0 + 0j - t2pp: complex = 0 + 0j - tpp: complex = 0 + 0j - - # Coulomb parameters (float) - U: float = 0.0 - V: float = 0.0 - Vp: float = 0.0 - V0: float = 0.0 - V0p: float = 0.0 - V0pp: float = 0.0 - V1: float = 0.0 - V1p: float = 0.0 - V1pp: float = 0.0 - V2: float = 0.0 - V2p: float = 0.0 - V2pp: float = 0.0 - Vpp: float = 0.0 - - # Isotropic/anisotropic diagonal spin couplings (float) - JAll: float = 0.0 - JpAll: float = 0.0 - J0All: float = 0.0 - J0pAll: float = 0.0 - J0ppAll: float = 0.0 - J1All: float = 0.0 - J1pAll: float = 0.0 - J1ppAll: float = 0.0 - J2All: float = 0.0 - J2pAll: float = 0.0 - J2ppAll: float = 0.0 - JppAll: float = 0.0 - - # Spin coupling matrices (3x3 float arrays) - J: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - Jp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J0: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J0p: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J0pp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J1: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J1p: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J1pp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J2: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J2p: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - J2pp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - Jpp: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - D: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - - # Magnetic field parameters - h: float = 0.0 - Gamma: float = 0.0 - Gamma_y: float = 0.0 - K: float = 0.0 - - # ------------------------------------------------------------------ - # Phase for the boundary - # ------------------------------------------------------------------ - phase: np.ndarray = field(default_factory=lambda: np.zeros(3)) - ExpPhase: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=complex)) - AntiPeriod: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=int)) - - # ------------------------------------------------------------------ - # Transfer, Interaction, Locspin - # ------------------------------------------------------------------ - nsite: int = 0 - locspinflag: None = None - ntrans: int = 0 - transindx: None = None - trans: None = None - nintr: int = 0 - Lintr: int = 0 - intrindx: None = None - intr: None = None - NCintra: int = 0 - LCintra: int = 0 - CintraIndx: None = None - Cintra: None = None - NCinter: int = 0 - LCinter: int = 0 - CinterIndx: None = None - Cinter: None = None - NHund: int = 0 - LHund: int = 0 - HundIndx: None = None - Hund: None = None - NEx: int = 0 - LEx: int = 0 - ExIndx: None = None - Ex: None = None - NPairLift: int = 0 - LPairLift: int = 0 - PLIndx: None = None - PairLift: None = None - NPairHopp: int = 0 - LPairHopp: int = 0 - PHIndx: None = None - PairHopp: None = None - lBoost: int = 0 - - # ------------------------------------------------------------------ - # Calculation conditions - # ------------------------------------------------------------------ - ncond: int = 0 - lGC: int = 0 - S2: int = 0 - outputmode: str = UNSET_STRING - CDataFileHead: str = UNSET_STRING - Sz2: int = 0 - ioutputmode: int = 0 - - # ------------------------------------------------------------------ - # Wannier90 mode - # ------------------------------------------------------------------ - cutoff_t: float = 0.0 - cutoff_u: float = 0.0 - cutoff_j: float = 0.0 - cutoff_length_t: float = 0.0 - cutoff_length_U: float = 0.0 - cutoff_length_J: float = 0.0 - cutoff_tR: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=int)) - cutoff_UR: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=int)) - cutoff_JR: np.ndarray = field(default_factory=lambda: np.zeros(3, dtype=int)) - cutoff_tVec: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - cutoff_UVec: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - cutoff_JVec: np.ndarray = field(default_factory=lambda: np.zeros((3, 3))) - lambda_: float = 0.0 - lambda_U: float = 0.0 - lambda_J: float = 0.0 - double_counting_mode: str = UNSET_STRING - alpha: float = 0.0 - - # ------------------------------------------------------------------ - # Solver selector - # ------------------------------------------------------------------ - solver: str = "" - - # ------------------------------------------------------------------ - # HPhi fields - # ------------------------------------------------------------------ - method: str = UNSET_STRING - Restart: str = UNSET_STRING - InitialVecType: str = UNSET_STRING - EigenVecIO: str = UNSET_STRING - HamIO: str = UNSET_STRING - FlgTemp: int = 0 - Lanczos_max: int = 0 - initial_iv: int = 0 - nvec: int = 0 - exct: int = 0 - LanczosEps: int = 0 - LanczosTarget: int = 0 - NumAve: int = 0 - ExpecInterval: int = 0 - LargeValue: float = 0.0 - NGPU: int = 0 - Scalapack: int = 0 - list_6spin_pair: None = None - list_6spin_star: None = None - num_pivot: int = 0 - ishift_nspin: int = 0 - CalcSpec: str = UNSET_STRING - SpectrumType: str = UNSET_STRING - Nomega: int = 0 - OmegaMax: float = 0.0 - OmegaMin: float = 0.0 - OmegaOrg: float = 0.0 - OmegaIm: float = 0.0 - SpectrumQ: np.ndarray = field(default_factory=lambda: np.zeros(3)) - SpectrumBody: int = 0 - OutputExVec: str = UNSET_STRING - dt: float = 0.0 - tshift: float = 0.0 - tdump: float = 0.0 - freq: float = 0.0 - Uquench: float = 0.0 - VecPot: np.ndarray = field(default_factory=lambda: np.zeros(3)) - PumpType: str = UNSET_STRING - PumpBody: int = 0 - npump: None = None - pumpindx: None = None - pump: None = None - At: None = None - ExpandCoef: int = 0 - - # ------------------------------------------------------------------ - # mVMC fields - # ------------------------------------------------------------------ - CParaFileHead: str = UNSET_STRING - NVMCCalMode: int = 0 - NLanczosMode: int = 0 - NDataIdxStart: int = 0 - NDataQtySmp: int = 0 - NSPGaussLeg: int = 0 - NMPTrans: int = 0 - NSROptItrStep: int = 0 - NSROptItrSmp: int = 0 - NSROptFixSmp: int = 0 - DSROptRedCut: float = 0.0 - DSROptStaDel: float = 0.0 - DSROptStepDt: float = 0.0 - NVMCWarmUp: int = 0 - NVMCInterval: int = 0 - NVMCSample: int = 0 - NExUpdatePath: int = 0 - RndSeed: int = 0 - NSplitSize: int = 0 - NSPStot: int = 0 - NStore: int = 0 - NSRCG: int = 0 - ComplexType: int = 0 - Lsub: int = 0 - Wsub: int = 0 - Hsub: int = 0 - NCellsub: int = 0 - boxsub: np.ndarray = field(default_factory=lambda: np.zeros((3, 3), dtype=int)) - rboxsub: np.ndarray = field(default_factory=lambda: np.zeros((3, 3), dtype=int)) - Orb: None = None - AntiOrb: None = None - NOrb: int = 0 - NSym: int = 0 - - # ------------------------------------------------------------------ - # UHF / HWAVE fields - # ------------------------------------------------------------------ - mix: float = 0.0 - eps: int = 0 - eps_slater: int = 0 - Iteration_max: int = 0 - - # ------------------------------------------------------------------ - # HWAVE-only fields - # ------------------------------------------------------------------ - calcmode: str = UNSET_STRING - fileprefix: str = UNSET_STRING - export_all: int = 0 - lattice_gp: int = 0 diff --git a/python/version.py b/python/version.py deleted file mode 100644 index 1a812fe..0000000 --- a/python/version.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Version information for StdFace. - -mVMC - A numerical solver package for a wide range of quantum lattice models -based on many-variable Variational Monte Carlo method Copyright (C) 2016 The -University of Tokyo, All rights reserved. - -This program is developed based on the mVMC-mini program -(https://github.com/fiber-miniapp/mVMC-mini) -which follows "The BSD 3-Clause License". - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see http://www.gnu.org/licenses/. -""" - -from __future__ import annotations - -# Semantic Versioning http://semver.org -# ..- -VERSION_MAJOR = 0 -VERSION_MINOR = 5 -VERSION_PATCH = 0 -VERSION_PRERELEASE = "" # "alpha", "beta.1", etc. - - -def print_version() -> None: - """Print the StdFace version string to standard output. - - Prints a version string in the format ``major.minor.patch``. - If ``VERSION_PRERELEASE`` is non-empty, ``-`` is appended. - """ - version_str = f"StdFace version {VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}" - if VERSION_PRERELEASE: - version_str += f"-{VERSION_PRERELEASE}" - print(version_str) diff --git a/python/writer/__init__.py b/python/writer/__init__.py deleted file mode 100644 index c84891e..0000000 --- a/python/writer/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Output file generation subpackage. - -This package contains modules for writing Expert-mode definition files -(``.def``) for all supported solvers (HPhi, mVMC, UHF, H-wave). -""" -from __future__ import annotations - -from .solver_writer import get_solver_writer -from .interaction_writer import print_interactions diff --git a/python/writer/common_writer.py b/python/writer/common_writer.py deleted file mode 100644 index 2fd8e2f..0000000 --- a/python/writer/common_writer.py +++ /dev/null @@ -1,1136 +0,0 @@ -"""Common output writer functions shared across all solvers. - -This module contains functions that generate definition files common to all -supported solvers (HPhi, mVMC, UHF, H-wave). These were extracted from -``stdface_main.py`` during refactoring. - -Classes -------- -GreenFunctionIndices - Generator for one- and two-body Green-function index lists. - -Functions ---------- -print_loc_spin - Write ``locspn.def`` listing local-spin flags for every site. -print_trans - Write ``trans.def`` listing one-body transfer integrals. -print_namelist - Write ``namelist.def`` that lists all definition files for the solver. -print_mod_para - Write ``modpara.def`` containing model / calculation parameters. -print_1_green - Write ``greenone.def`` listing one-body Green-function indices. -print_2_green - Write ``greentwo.def`` listing two-body Green-function indices. -unsupported_system - Print an error message and abort for an unsupported model/lattice pair. -check_output_mode - Verify and set the integer output-mode flag from the string keyword. -check_mod_para - Validate and set default values for solver-specific model parameters. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -from collections.abc import Callable -from typing import NamedTuple - -import numpy as np - -from stdface_vals import ( - StdIntList, ModelType, SolverType, MethodType, NaN_i, UNSET_STRING, - AMPLITUDE_EPS, -) -from param_check import exit_program, print_val_i, print_val_d, required_val_i, not_used_i - - -# --------------------------------------------------------------------------- -# String → integer dispatch tables -# --------------------------------------------------------------------------- - -OUTPUT_MODE_TO_INT: dict[str, int] = { - "non": 0, - "none": 0, - "off": 0, - "cor": 1, - "corr": 1, - "correlation": 1, - "raw": 2, - "all": 2, - "full": 2, -} -"""Maps ``outputmode`` keyword strings to the ``ioutputmode`` integer code. - -- 0 = no correlation output -- 1 = correlation functions only -- 2 = raw (all) output -""" - -MODEL_GC_TO_EX_UPDATE_PATH: dict[tuple, int] = { - (ModelType.HUBBARD, 0): 0, - (ModelType.HUBBARD, 1): 0, - (ModelType.SPIN, 0): 2, - (ModelType.SPIN, 1): 2, - (ModelType.KONDO, 0): 1, - (ModelType.KONDO, 1): 3, -} -"""Maps ``(ModelType, lGC)`` to the ``NExUpdatePath`` integer for mVMC. - -Hubbard always gets 0, Spin always gets 2, and Kondo depends on whether -the grand-canonical flag (``lGC``) is set (1 → 3, 0 → 1). -""" - - -def _merge_duplicate_terms(indx, vals, n: int) -> int: - """Merge duplicate index quadruples and count non-negligible entries. - - For each pair of terms with identical 4-element index quadruples, - the first term's amplitude absorbs the second's, and the second is - zeroed. After merging, the number of entries whose absolute value - exceeds ``AMPLITUDE_EPS`` is returned. - - Parameters - ---------- - indx : array-like - 2-D index array, shape ``(n, 4)``. Each row is a 4-element - index quadruple ``(i, s_i, j, s_j)``. - vals : array-like - 1-D amplitude array, length ``n``. Modified **in place** during - merging (duplicates are zeroed). - n : int - Number of entries to process. - - Returns - ------- - int - Count of entries with ``abs(val) > AMPLITUDE_EPS`` after merging. - """ - # Merge duplicates: first occurrence absorbs all later ones with same key - seen: dict[tuple, int] = {} - for k in range(n): - key = tuple(indx[k]) - if key in seen: - vals[seen[key]] += vals[k] - vals[k] = 0.0 - else: - seen[key] = k - - return sum(1 for k in range(n) if abs(vals[k]) > AMPLITUDE_EPS) - - -def print_loc_spin(StdI: StdIntList) -> None: - """Write ``locspn.def`` listing local-spin flags for every site. - - This is the Python translation of the C function ``PrintLocSpin()``. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. The following fields are read: - - - ``nsite`` : int -- total number of sites. - - ``locspinflag`` : list of int -- per-site flag (0 = itinerant - electron, nonzero = local spin with :math:`S` given by the value). - """ - nlocspin = int(np.count_nonzero(StdI.locspinflag[:StdI.nsite])) - - with open("locspn.def", "w") as fp: - fp.write("================================ \n") - fp.write(f"NlocalSpin {nlocspin:5d} \n") - fp.write("================================ \n") - fp.write("========i_1LocSpn_0IteElc ====== \n") - fp.write("================================ \n") - for isite in range(StdI.nsite): - fp.write(f"{isite:5d} {StdI.locspinflag[isite]:5d}\n") - - print(" locspn.def is written.") - - -def print_trans(StdI: StdIntList) -> None: - """Write ``trans.def`` listing one-body transfer integrals. - - This is the Python translation of the C function ``PrintTrans()``. - Duplicate index quadruples are merged (their amplitudes summed) and - entries whose absolute value is below ``AMPLITUDE_EPS`` are suppressed. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. The following fields are read - and (for merging) modified **in place**: - - - ``ntrans`` : int -- number of registered transfer terms. - - ``transindx`` : 2-D array of int, shape ``(ntrans, 4)`` -- - site/spin indices ``(i, s_i, j, s_j)`` for each term. - - ``trans`` : 1-D array of complex -- transfer amplitudes. - """ - ntrans0 = _merge_duplicate_terms(StdI.transindx, StdI.trans, StdI.ntrans) - - # --- write file --- - with open("trans.def", "w") as fp: - fp.write("======================== \n") - fp.write(f"NTransfer {ntrans0:7d} \n") - fp.write("======================== \n") - fp.write("========i_j_s_tijs====== \n") - fp.write("======================== \n") - for ktrans in range(StdI.ntrans): - val = StdI.trans[ktrans] - if abs(val) > AMPLITUDE_EPS: - i0, s0, i1, s1 = StdI.transindx[ktrans] - fp.write( - f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " - f"{val.real:25.15f} {val.imag:25.15f}\n" - ) - - print(" trans.def is written.") - - -def _write_namelist_hphi(fp, StdI: StdIntList) -> None: - """Write HPhi-specific entries in ``namelist.def``. - - Parameters - ---------- - fp : file object - Open file handle for ``namelist.def``. - StdI : StdIntList - The global parameter structure. - """ - fp.write(" CalcMod calcmod.def\n") - if StdI.SpectrumBody == 1: - fp.write("SingleExcitation single.def\n") - else: - fp.write(" PairExcitation pair.def\n") - if StdI.method == MethodType.TIME_EVOLUTION: - if StdI.PumpBody == 1: - fp.write(" TEOneBody teone.def\n") - elif StdI.PumpBody == 2: - fp.write(" TETwoBody tetwo.def\n") - fp.write(f" SpectrumVec {StdI.CDataFileHead}_eigenvec_0\n") - if StdI.lBoost == 1: - fp.write(" Boost boost.def\n") - - -def _write_namelist_mvmc(fp, StdI: StdIntList) -> None: - """Write mVMC-specific entries in ``namelist.def``. - - Parameters - ---------- - fp : file object - Open file handle for ``namelist.def``. - StdI : StdIntList - The global parameter structure. - """ - fp.write(" Gutzwiller gutzwilleridx.def\n") - fp.write(" Jastrow jastrowidx.def\n") - fp.write(" Orbital orbitalidx.def\n") - if StdI.lGC == 1 or (StdI.Sz2 != 0 and StdI.Sz2 != NaN_i): - fp.write(" OrbitalParallel orbitalidxpara.def\n") - fp.write("# OrbitalGeneral orbitalidxgen.def\n") - fp.write(" TransSym qptransidx.def\n") - - -_NAMELIST_BODY_DISPATCH: dict[SolverType, Callable] = { - SolverType.HPhi: _write_namelist_hphi, - SolverType.mVMC: _write_namelist_mvmc, -} -"""Maps solver type to the function that writes solver-specific namelist entries. - -Solvers not in this dict (UHF, HWAVE) have no solver-specific entries. -""" - -# Interaction file flags and their namelist lines -_INTERACTION_FLAGS: list[tuple[str, str]] = [ - ("LCintra", " CoulombIntra coulombintra.def\n"), - ("LCinter", " CoulombInter coulombinter.def\n"), - ("LHund", " Hund hund.def\n"), - ("LEx", " Exchange exchange.def\n"), - ("LPairLift", " PairLift pairlift.def\n"), - ("LPairHopp", " PairHop pairhopp.def\n"), - ("Lintr", " InterAll interall.def\n"), -] -"""Maps ``StdIntList`` flag attribute names to their namelist entry lines. - -Each flag, when equal to 1, causes the corresponding definition file to be -listed in ``namelist.def``. -""" - - -def print_namelist(StdI: StdIntList) -> None: - """Write ``namelist.def`` that lists all definition files for the solver. - - This is the Python translation of the C function ``PrintNamelist()``. - The content depends on which solver is active (``StdI.solver``). - - The common prefix (ModPara, LocSpin, Trans, conditional interaction - files, Green-function files) is written for all solvers. Solver-specific - entries are delegated to body-writer functions via - ``_NAMELIST_BODY_DISPATCH``. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. - """ - with open("namelist.def", "w") as fp: - fp.write(" ModPara modpara.def\n") - fp.write(" LocSpin locspn.def\n") - fp.write(" Trans trans.def\n") - - for flag_attr, line in _INTERACTION_FLAGS: - if getattr(StdI, flag_attr) == 1: - fp.write(line) - - if StdI.ioutputmode != 0: - fp.write(" OneBodyG greenone.def\n") - if StdI.solver in (SolverType.HPhi, SolverType.mVMC): - fp.write(" TwoBodyG greentwo.def\n") - - body_writer = _NAMELIST_BODY_DISPATCH.get(StdI.solver) - if body_writer is not None: - body_writer(fp, StdI) - - print(" namelist.def is written.") - - -def _write_modpara_hphi(fp, StdI: StdIntList) -> None: - """Write the HPhi-specific body of ``modpara.def``. - - Parameters - ---------- - fp : file object - Open file handle for ``modpara.def``. - StdI : StdIntList - The global parameter structure. - """ - fp.write("HPhi_Cal_Parameters\n") - fp.write("--------------------\n") - fp.write(f"CDataFileHead {StdI.CDataFileHead}\n") - fp.write("CParaFileHead zqp\n") - fp.write("--------------------\n") - fp.write(f"Nsite {StdI.nsite:<5d}\n") - if StdI.Sz2 != NaN_i: - fp.write(f"2Sz {StdI.Sz2:<5d}\n") - if StdI.ncond != NaN_i: - fp.write(f"Ncond {StdI.ncond:<5d}\n") - fp.write(f"Lanczos_max {StdI.Lanczos_max:<5d}\n") - fp.write(f"initial_iv {StdI.initial_iv:<5d}\n") - if StdI.nvec != NaN_i: - fp.write(f"nvec {StdI.nvec:<5d}\n") - fp.write(f"exct {StdI.exct:<5d}\n") - fp.write(f"LanczosEps {StdI.LanczosEps:<5d}\n") - fp.write(f"LanczosTarget {StdI.LanczosTarget:<5d}\n") - fp.write(f"LargeValue {StdI.LargeValue:<25.15e}\n") - fp.write(f"NumAve {StdI.NumAve:<5d}\n") - fp.write(f"ExpecInterval {StdI.ExpecInterval:<5d}\n") - fp.write(f"NOmega {StdI.Nomega:<5d}\n") - fp.write( - f"OmegaMax {StdI.OmegaMax:<25.15e} {StdI.OmegaIm:<25.15e}\n" - ) - fp.write( - f"OmegaMin {StdI.OmegaMin:<25.15e} {StdI.OmegaIm:<25.15e}\n" - ) - fp.write( - f"OmegaOrg {StdI.OmegaOrg:<25.15e} {0.0:<25.15e}\n" - ) - fp.write(f"PreCG {0:<5d}\n") - if StdI.method == MethodType.TIME_EVOLUTION: - fp.write(f"ExpandCoef {StdI.ExpandCoef:<5d}\n") - - -def _write_modpara_mvmc(fp, StdI: StdIntList) -> None: - """Write the mVMC-specific body of ``modpara.def``. - - Parameters - ---------- - fp : file object - Open file handle for ``modpara.def``. - StdI : StdIntList - The global parameter structure. - """ - fp.write("VMC_Cal_Parameters\n") - fp.write("--------------------\n") - fp.write(f"CDataFileHead {StdI.CDataFileHead}\n") - fp.write(f"CParaFileHead {StdI.CParaFileHead}\n") - fp.write("--------------------\n") - fp.write(f"NVMCCalMode {StdI.NVMCCalMode}\n") - fp.write(f"NLanczosMode {StdI.NLanczosMode}\n") - fp.write("--------------------\n") - fp.write(f"NDataIdxStart {StdI.NDataIdxStart}\n") - fp.write(f"NDataQtySmp {StdI.NDataQtySmp}\n") - fp.write("--------------------\n") - fp.write(f"Nsite {StdI.nsite}\n") - fp.write(f"Ncond {StdI.ncond:<5d}\n") - if StdI.Sz2 != NaN_i: - fp.write(f"2Sz {StdI.Sz2}\n") - if StdI.NSPGaussLeg != NaN_i: - fp.write(f"NSPGaussLeg {StdI.NSPGaussLeg}\n") - if StdI.NSPStot != NaN_i: - fp.write(f"NSPStot {StdI.NSPStot}\n") - fp.write(f"NMPTrans {StdI.NMPTrans}\n") - fp.write(f"NSROptItrStep {StdI.NSROptItrStep}\n") - fp.write(f"NSROptItrSmp {StdI.NSROptItrSmp}\n") - fp.write(f"DSROptRedCut {StdI.DSROptRedCut:.10f}\n") - fp.write(f"DSROptStaDel {StdI.DSROptStaDel:.10f}\n") - fp.write(f"DSROptStepDt {StdI.DSROptStepDt:.10f}\n") - fp.write(f"NVMCWarmUp {StdI.NVMCWarmUp}\n") - fp.write(f"NVMCInterval {StdI.NVMCInterval}\n") - fp.write(f"NVMCSample {StdI.NVMCSample}\n") - fp.write(f"NExUpdatePath {StdI.NExUpdatePath}\n") - fp.write(f"RndSeed {StdI.RndSeed}\n") - fp.write(f"NSplitSize {StdI.NSplitSize}\n") - fp.write(f"NStore {StdI.NStore}\n") - fp.write(f"NSRCG {StdI.NSRCG}\n") - - -def _write_modpara_uhf_hwave(fp, StdI: StdIntList) -> None: - """Write the UHF / H-wave body of ``modpara.def``. - - UHF and H-wave share identical file content except for the header - banner line. The correct banner is selected from - :data:`_MODPARA_BANNER`. - - Parameters - ---------- - fp : file object - Open file handle for ``modpara.def``. - StdI : StdIntList - The global parameter structure. - """ - banner = _MODPARA_BANNER[StdI.solver] - fp.write(f"{banner}\n") - fp.write("--------------------\n") - fp.write(f"CDataFileHead {StdI.CDataFileHead}\n") - fp.write("CParaFileHead zqp\n") - fp.write("--------------------\n") - fp.write(f"Nsite {StdI.nsite}\n") - if StdI.Sz2 != NaN_i: - fp.write(f"2Sz {StdI.Sz2:<5d}\n") - fp.write(f"Ncond {StdI.ncond:<5d}\n") - fp.write(f"IterationMax {StdI.Iteration_max}\n") - fp.write(f"EPS {StdI.eps}\n") - fp.write(f"Mix {StdI.mix:.10f}\n") - fp.write(f"RndSeed {StdI.RndSeed}\n") - fp.write(f"EpsSlater {StdI.eps_slater}\n") - fp.write(f"NMPTrans {StdI.NMPTrans}\n") - - -_MODPARA_BANNER: dict[str, str] = { - SolverType.UHF: "UHF_Cal_Parameters", - SolverType.HWAVE: "HWAVE_Cal_Parameters", -} -"""Maps UHF/HWAVE solver type to the ``modpara.def`` banner line.""" - - -_MODPARA_BODY_DISPATCH: dict[str, Callable] = { - SolverType.HPhi: _write_modpara_hphi, - SolverType.mVMC: _write_modpara_mvmc, - SolverType.UHF: _write_modpara_uhf_hwave, - SolverType.HWAVE: _write_modpara_uhf_hwave, -} -"""Maps ``SolverType`` to the function that writes the solver-specific body -of ``modpara.def``. UHF and H-wave share the same writer -(:func:`_write_modpara_uhf_hwave`). -""" - - -def print_mod_para(StdI: StdIntList) -> None: - """Write ``modpara.def`` containing model / calculation parameters. - - This is the Python translation of the C function ``PrintModPara()``. - The file layout depends on the active solver (``StdI.solver``). - Solver-specific content is dispatched via :data:`_MODPARA_BODY_DISPATCH`. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. A large number of solver-specific - fields are read; see the individual body-writer functions for details. - """ - with open("modpara.def", "w") as fp: - fp.write("--------------------\n") - fp.write("Model_Parameters 0\n") - fp.write("--------------------\n") - - writer = _MODPARA_BODY_DISPATCH.get(StdI.solver) - if writer is not None: - writer(fp, StdI) - - print(" modpara.def is written.") - - -class GreenFunctionIndices: - """Generator for one- and two-body Green-function index lists. - - Encapsulates the site/spin parameters and helper logic needed to - produce the index tuples written to ``greenone.def`` and - ``greentwo.def``. - - Parameters - ---------- - nsite : int - Total number of sites in the system. - NsiteUC : int - Number of sites in the unit cell. - locspinflag : list of int - Per-site local-spin flag array (length ``nsite``). - is_kondo : bool - Whether the model is Kondo (doubles the UC range). - is_mvmc : bool - Whether the solver is mVMC (uses alternative index order for - two-body off-diagonal spin entries). - - Attributes - ---------- - nsite : int - NsiteUC : int - locspinflag : list of int - is_kondo : bool - is_mvmc : bool - """ - - def __init__( - self, - nsite: int, - NsiteUC: int, - locspinflag: list[int], - is_kondo: bool, - is_mvmc: bool = False, - ) -> None: - self.nsite = nsite - self.NsiteUC = NsiteUC - self.locspinflag = locspinflag - self.is_kondo = is_kondo - self.is_mvmc = is_mvmc - - # ------------------------------------------------------------------ - # Low-level helpers - # ------------------------------------------------------------------ - - def spin_max(self, site: int) -> int: - """Return the maximum spin index for *site*. - - For itinerant sites (``locspinflag == 0``), returns 1. - For local-spin sites, returns the local-spin value. - - Parameters - ---------- - site : int - Site index. - - Returns - ------- - int - Maximum spin index (inclusive upper bound). - """ - flag = self.locspinflag[site] - return 1 if flag == 0 else flag - - def skip_local_spin_pair(self, site_a: int, site_b: int) -> bool: - """Return True if a pair of distinct local-spin sites should be skipped. - - Parameters - ---------- - site_a : int - First site index. - site_b : int - Second site index. - - Returns - ------- - bool - ``True`` if both sites are local-spin and distinct. - """ - return (site_a != site_b - and self.locspinflag[site_a] != 0 - and self.locspinflag[site_b] != 0) - - def kondo_site(self, isite: int) -> int: - """Map a Kondo unit-cell index to the physical site index. - - Parameters - ---------- - isite : int - Unit-cell site index (``0 .. 2*NsiteUC - 1``). - - Returns - ------- - int - Physical site index. - """ - if isite >= self.NsiteUC: - return isite - self.NsiteUC + self.nsite // 2 - return isite - - # ------------------------------------------------------------------ - # One-body index generators - # ------------------------------------------------------------------ - - def green1_corr(self) -> list[tuple[int, int, int, int]]: - """Generate one-body Green-function indices for correlation mode. - - Only same-spin pairs (``ispin == jspin``) are kept. For Kondo - models the unit-cell range is doubled to include localized sites. - - Returns - ------- - list of tuple[int, int, int, int] - Index tuples ``(isite, ispin, jsite, jspin)``. - """ - indices: list[tuple[int, int, int, int]] = [] - xkondo = 2 if self.is_kondo else 1 - - for isite in range(self.NsiteUC * xkondo): - isite2 = self.kondo_site(isite) - - for ispin in range(self.spin_max(isite2) + 1): - for jsite in range(self.nsite): - for jspin in range(self.spin_max(jsite) + 1): - if self.skip_local_spin_pair(isite2, jsite): - continue - if ispin == jspin: - indices.append((isite2, ispin, jsite, jspin)) - - return indices - - def green1_raw(self) -> list[tuple[int, int, int, int]]: - """Generate one-body Green-function indices for raw (full) mode. - - All site-spin combinations are emitted, subject to the constraint - that pairs of distinct local-spin sites are skipped. - - Returns - ------- - list of tuple[int, int, int, int] - Index tuples ``(isite, ispin, jsite, jspin)``. - """ - indices: list[tuple[int, int, int, int]] = [] - - for isite in range(self.nsite): - for ispin in range(self.spin_max(isite) + 1): - for jsite in range(self.nsite): - for jspin in range(self.spin_max(jsite) + 1): - if self.skip_local_spin_pair(isite, jsite): - continue - indices.append((isite, ispin, jsite, jspin)) - - return indices - - # ------------------------------------------------------------------ - # Two-body index generators - # ------------------------------------------------------------------ - - def green2_corr(self) -> list[tuple[int, int, int, int, int, int, int, int]]: - """Generate two-body Green-function indices for correlation mode. - - For each pair ``(site1, site3)`` only spin combinations satisfying - ``spin1 - spin2 + spin3 - spin4 == 0`` are kept. For mVMC, when - ``spin1 != spin2`` or ``spin3 != spin4`` a special index order is - used; otherwise the standard HPhi order is used. - - Returns - ------- - list of tuple[int, int, int, int, int, int, int, int] - Index tuples of 8 elements each. - """ - indices: list[tuple[int, int, int, int, int, int, int, int]] = [] - xkondo = 2 if self.is_kondo else 1 - - for site1 in range(self.NsiteUC * xkondo): - site1k = self.kondo_site(site1) - S1Max = self.spin_max(site1k) - - for spin1 in range(S1Max + 1): - for spin2 in range(S1Max + 1): - for site3 in range(self.nsite): - S3Max = self.spin_max(site3) - - for spin3 in range(S3Max + 1): - for spin4 in range(S3Max + 1): - if spin1 - spin2 + spin3 - spin4 == 0: - if self.is_mvmc and ( - spin1 != spin2 or spin3 != spin4 - ): - indices.append(( - site1k, spin1, - site3, spin4, - site3, spin3, - site1k, spin2, - )) - else: - indices.append(( - site1k, spin1, - site1k, spin2, - site3, spin3, - site3, spin4, - )) - - return indices - - def green2_raw(self) -> list[tuple[int, int, int, int, int, int, int, int]]: - """Generate two-body Green-function indices for raw (full) mode. - - All four-site combinations are emitted with local-spin pair - filtering on ``(site1, site2)`` and ``(site3, site4)``. - - Returns - ------- - list of tuple[int, int, int, int, int, int, int, int] - Index tuples of 8 elements each. - """ - indices: list[tuple[int, int, int, int, int, int, int, int]] = [] - - for site1 in range(self.nsite): - for spin1 in range(self.spin_max(site1) + 1): - for site2 in range(self.nsite): - if self.skip_local_spin_pair(site1, site2): - continue - - for spin2 in range(self.spin_max(site2) + 1): - for site3 in range(self.nsite): - for spin3 in range(self.spin_max(site3) + 1): - for site4 in range(self.nsite): - if self.skip_local_spin_pair(site3, site4): - continue - - for spin4 in range(self.spin_max(site4) + 1): - indices.append(( - site1, spin1, - site2, spin2, - site3, spin3, - site4, spin4, - )) - - return indices - - -def print_1_green(StdI: StdIntList) -> None: - """Write ``greenone.def`` listing one-body Green-function indices. - - This is the Python translation of the C function ``Print1Green()``. - Index generation is delegated to :class:`GreenFunctionIndices`. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. The following fields are read: - - - ``ioutputmode`` : int -- 0 (none), 1 (correlation), or 2 (raw). - - ``model`` : ModelType -- model type (Kondo triggers doubled UC). - - ``NsiteUC`` : int -- number of sites in the unit cell. - - ``nsite`` : int -- total number of sites. - - ``locspinflag`` : list of int -- per-site local-spin flag. - """ - if StdI.ioutputmode == 0: - return - - gf = GreenFunctionIndices( - StdI.nsite, StdI.NsiteUC, StdI.locspinflag, - is_kondo=(StdI.model == ModelType.KONDO), - ) - - if StdI.ioutputmode == 1: - greenindx = gf.green1_corr() - else: - greenindx = gf.green1_raw() - - ngreen = len(greenindx) - - with open("greenone.def", "w") as fp: - fp.write("===============================\n") - fp.write(f"NCisAjs {ngreen:10d}\n") - fp.write("===============================\n") - fp.write("======== Green functions ======\n") - fp.write("===============================\n") - for row in greenindx: - i0, s0, i1, s1 = row - fp.write(f"{i0:5d} {s0:5d} {i1:5d} {s1:5d}\n") - - print(" greenone.def is written.") - - -def print_2_green(StdI: StdIntList) -> None: - """Write ``greentwo.def`` listing two-body Green-function indices. - - This is the Python translation of the C function ``Print2Green()``. - Index generation is delegated to :class:`GreenFunctionIndices`. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. The following fields are read: - - - ``ioutputmode`` : int - - ``model`` : ModelType - - ``solver`` : SolverType - - ``NsiteUC`` : int - - ``nsite`` : int - - ``locspinflag`` : list of int - """ - if StdI.ioutputmode not in (1, 2): - return - - gf = GreenFunctionIndices( - StdI.nsite, StdI.NsiteUC, StdI.locspinflag, - is_kondo=(StdI.model == ModelType.KONDO), - is_mvmc=(StdI.solver == SolverType.mVMC), - ) - - if StdI.ioutputmode == 1: - greenindx = gf.green2_corr() - else: - greenindx = gf.green2_raw() - - ngreen = len(greenindx) - with open("greentwo.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NCisAjsCktAltDC {ngreen:10d}\n") - fp.write("=============================================\n") - fp.write("======== Green functions for Sq AND Nq ======\n") - fp.write("=============================================\n") - for row in greenindx: - i0, s0, i1, s1, i2, s2, i3, s3 = row - fp.write( - f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " - f"{i2:5d} {s2:5d} {i3:5d} {s3:5d}\n" - ) - - print(" greentwo.def is written.") - - -def unsupported_system(model: str, lattice: str) -> None: - """Print an error message and abort for an unsupported model/lattice pair. - - This is the Python translation of the C function - ``UnsupportedSystem()``. - - Parameters - ---------- - model : str - The model name specified by the user. - lattice : str - The lattice name specified by the user. - - Raises - ------ - SystemExit - Always raised after printing the error message. - """ - print("\nSorry, specified combination, ") - print(f" MODEL : {model} ") - print(f" LATTICE : {lattice}, ") - print("is unsupported in the STANDARD MODE...") - print("Please use the EXPART MODE, or write a NEW FUNCTION and post us.") - exit_program(-1) - - -def check_output_mode(StdI: StdIntList) -> None: - """Verify and set the integer output-mode flag from the string keyword. - - This is the Python translation of the C function - ``CheckOutputMode()``. The mapping is: - - - ``"non"`` / ``"none"`` / ``"off"`` -> ``ioutputmode = 0`` - - ``"cor"`` / ``"corr"`` / ``"correlation"`` -> ``ioutputmode = 1`` - - ``"****"`` (default sentinel) -> ``ioutputmode = 1`` - - ``"raw"`` / ``"all"`` / ``"full"`` -> ``ioutputmode = 2`` - - anything else -> error and exit - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. ``StdI.outputmode`` is read and - ``StdI.ioutputmode`` is set **in place**. - - Raises - ------ - SystemExit - If ``StdI.outputmode`` does not match any recognised keyword. - """ - if StdI.outputmode == UNSET_STRING: - StdI.ioutputmode = 1 - print( - f" ioutputmode = {StdI.ioutputmode:<10d}" - " ###### DEFAULT VALUE IS USED ######" - ) - else: - mode = OUTPUT_MODE_TO_INT.get(StdI.outputmode) - if mode is None: - print(f"\n ERROR ! Unsupported OutPutMode : {StdI.outputmode}") - exit_program(-1) - StdI.ioutputmode = mode - print(f" ioutputmode = {StdI.ioutputmode:<10d}") - - -def _check_mod_para_hphi(StdI: StdIntList) -> None: - """Set HPhi-specific default model parameters. - - Handles Lanczos parameters, spectrum frequency grid, and large-value - cutoff. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure, modified in place. - """ - StdI.Lanczos_max = print_val_i("Lanczos_max", StdI.Lanczos_max, 2000) - StdI.initial_iv = print_val_i("initial_iv", StdI.initial_iv, -1) - # nvec is not given a default here (commented out in C) - StdI.exct = print_val_i("exct", StdI.exct, 1) - StdI.LanczosEps = print_val_i("LanczosEps", StdI.LanczosEps, 14) - StdI.LanczosTarget = print_val_i("LanczosTarget", StdI.LanczosTarget, 2) - if StdI.LanczosTarget < StdI.exct: - StdI.LanczosTarget = StdI.exct - StdI.NumAve = print_val_i("NumAve", StdI.NumAve, 5) - StdI.ExpecInterval = print_val_i("ExpecInterval", StdI.ExpecInterval, 20) - StdI.Nomega = print_val_i("NOmega", StdI.Nomega, 200) - StdI.OmegaMax = print_val_d( - "OmegaMax", StdI.OmegaMax, StdI.LargeValue * StdI.nsite - ) - StdI.OmegaMin = print_val_d( - "OmegaMin", StdI.OmegaMin, -StdI.LargeValue * StdI.nsite - ) - StdI.OmegaOrg = print_val_d("OmegaOrg", StdI.OmegaOrg, 0.0) - StdI.OmegaIm = print_val_d( - "OmegaIm", StdI.OmegaIm, 0.01 * int(StdI.LargeValue) - ) - - -def _check_mod_para_mvmc(StdI: StdIntList) -> None: - """Set mVMC-specific default model parameters. - - Handles VMC sampling parameters, exchange-update path count, - and SR-optimisation settings. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure, modified in place. - """ - if StdI.CParaFileHead == UNSET_STRING: - StdI.CParaFileHead = "zqp" - print( - f" CParaFileHead = {StdI.CParaFileHead:<12s}" - "###### DEFAULT VALUE IS USED ######" - ) - else: - print(f" CParaFileHead = {StdI.CParaFileHead}") - - StdI.NVMCCalMode = print_val_i("NVMCCalMode", StdI.NVMCCalMode, 0) - StdI.NLanczosMode = print_val_i("NLanczosMode", StdI.NLanczosMode, 0) - StdI.NDataIdxStart = print_val_i("NDataIdxStart", StdI.NDataIdxStart, 1) - - if StdI.NVMCCalMode == 0: - not_used_i("NDataQtySmp", StdI.NDataQtySmp) - StdI.NDataQtySmp = print_val_i("NDataQtySmp", StdI.NDataQtySmp, 1) - - if StdI.lGC == 0 and (StdI.Sz2 == 0 or StdI.Sz2 == NaN_i): - StdI.NSPGaussLeg = print_val_i("NSPGaussLeg", StdI.NSPGaussLeg, 8) - StdI.NSPStot = print_val_i("NSPStot", StdI.NSPStot, 0) - else: - not_used_i("NSPGaussLeg", StdI.NSPGaussLeg) - not_used_i("NSPStot", StdI.NSPStot) - - StdI.NMPTrans = print_val_i("NMPTrans", StdI.NMPTrans, -1) - - StdI.NSROptItrStep = print_val_i("NSROptItrStep", StdI.NSROptItrStep, 1000) - - if StdI.NVMCCalMode == 1: - not_used_i("NSROptItrSmp", StdI.NSROptItrSmp) - StdI.NSROptItrSmp = print_val_i( - "NSROptItrSmp", StdI.NSROptItrSmp, StdI.NSROptItrStep // 10 - ) - - StdI.NVMCWarmUp = print_val_i("NVMCWarmUp", StdI.NVMCWarmUp, 10) - StdI.NVMCInterval = print_val_i("NVMCInterval", StdI.NVMCInterval, 1) - StdI.NVMCSample = print_val_i("NVMCSample", StdI.NVMCSample, 1000) - - key = (StdI.model, StdI.lGC) - ex_path = MODEL_GC_TO_EX_UPDATE_PATH.get(key) - if ex_path is not None: - StdI.NExUpdatePath = ex_path - print(f" {'NExUpdatePath':>15s} = {StdI.NExUpdatePath:<10d}") - - StdI.RndSeed = print_val_i("RndSeed", StdI.RndSeed, 123456789) - StdI.NSplitSize = print_val_i("NSplitSize", StdI.NSplitSize, 1) - StdI.NStore = print_val_i("NStore", StdI.NStore, 1) - StdI.NSRCG = print_val_i("NSRCG", StdI.NSRCG, 0) - - StdI.DSROptRedCut = print_val_d("DSROptRedCut", StdI.DSROptRedCut, 0.001) - StdI.DSROptStaDel = print_val_d("DSROptStaDel", StdI.DSROptStaDel, 0.02) - StdI.DSROptStepDt = print_val_d("DSROptStepDt", StdI.DSROptStepDt, 0.02) - - -def _check_mod_para_uhf(StdI: StdIntList) -> None: - """Set UHF/H-wave-specific default model parameters. - - Handles random seed, iteration limit, mixing, convergence, and - symmetry-projection parameters. UHF and H-wave share identical - defaults. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure, modified in place. - """ - StdI.RndSeed = print_val_i("RndSeed", StdI.RndSeed, 123456789) - StdI.Iteration_max = print_val_i("Iteration_max", StdI.Iteration_max, 1000) - StdI.mix = print_val_d("Mix", StdI.mix, 0.5) - StdI.eps = print_val_i("eps", StdI.eps, 8) - StdI.eps_slater = print_val_i("EpsSlater", StdI.eps_slater, 6) - StdI.NMPTrans = print_val_i("NMPTrans", StdI.NMPTrans, 0) - - -_SOLVER_DEFAULTS_DISPATCH: dict[str, Callable] = { - SolverType.HPhi: _check_mod_para_hphi, - SolverType.mVMC: _check_mod_para_mvmc, - SolverType.UHF: _check_mod_para_uhf, - SolverType.HWAVE: _check_mod_para_uhf, -} -"""Maps ``SolverType`` to the corresponding solver-specific defaults function. - -UHF and H-wave share the same defaults handler -(:func:`_check_mod_para_uhf`). -""" - - -# ------------------------------------------------------------------- -# Conserved-quantity validation rules -# ------------------------------------------------------------------- -# -class _ConservedQtyRule(NamedTuple): - """Validation rule for conserved quantities (ncond and 2Sz). - - Attributes - ---------- - ncond_label : str - Label used for the ncond check (``"nelec"`` or ``"ncond"``). - ncond_action : str or None - Action for ncond: ``"required"``, ``"not_used"``, or ``None``. - sz2_action : str or None - Action for 2Sz: ``"required"``, ``"not_used"``, ``"default_0"``, - or ``None``. - """ - - ncond_label: str - ncond_action: str | None - sz2_action: str | None - - -# The key is ``(ModelType, is_hphi: bool, lGC: int)``. -# For Spin model, is_hphi is ignored (keyed as True and False with same rule). - -_CONSERVED_QTY_RULES: dict[tuple, _ConservedQtyRule] = { - # Hubbard - (ModelType.HUBBARD, True, 0): _ConservedQtyRule("nelec", "required", None), - (ModelType.HUBBARD, True, 1): _ConservedQtyRule("nelec", "not_used", "not_used"), - (ModelType.HUBBARD, False, 0): _ConservedQtyRule("ncond", "required", "default_0"), - (ModelType.HUBBARD, False, 1): _ConservedQtyRule("ncond", "required", "not_used"), - # Spin (is_hphi dimension doesn't matter — same rules) - (ModelType.SPIN, True, 0): _ConservedQtyRule("ncond", "not_used", "required"), - (ModelType.SPIN, True, 1): _ConservedQtyRule("ncond", "not_used", "not_used"), - (ModelType.SPIN, False, 0): _ConservedQtyRule("ncond", "not_used", "required"), - (ModelType.SPIN, False, 1): _ConservedQtyRule("ncond", "not_used", "not_used"), - # Kondo - (ModelType.KONDO, True, 0): _ConservedQtyRule("ncond", "required", None), - (ModelType.KONDO, True, 1): _ConservedQtyRule("nelec", "not_used", "not_used"), - (ModelType.KONDO, False, 0): _ConservedQtyRule("ncond", "required", "default_0"), - (ModelType.KONDO, False, 1): _ConservedQtyRule("ncond", "required", "not_used"), -} -"""Rules for validating ``ncond`` and ``2Sz`` by (model, is_hphi, lGC).""" - -# Dispatch tables for ncond and Sz2 validation actions -_NCOND_ACTION_DISPATCH: dict[str, Callable] = { - "required": required_val_i, - "not_used": not_used_i, -} -"""Maps ncond action strings to validation functions (label, value) -> None.""" - -_SZ2_ACTION_DISPATCH: dict[str, Callable] = { - "required": lambda StdI: required_val_i("2Sz", StdI.Sz2), - "not_used": lambda StdI: not_used_i("2Sz", StdI.Sz2), - "default_0": lambda StdI: setattr(StdI, 'Sz2', print_val_i("2Sz", StdI.Sz2, 0)), -} -"""Maps Sz2 action strings to validation functions (StdI) -> None.""" - - -def _check_conserved_quantities(StdI: StdIntList) -> None: - """Validate conserved quantities (``ncond`` and ``2Sz``) for the current model. - - The checks depend on ``StdI.model``, ``StdI.solver``, and ``StdI.lGC`` - (grand-canonical flag): - - - **Hubbard** (HPhi, canonical): ``nelec`` required. - - **Hubbard** (HPhi, GC): ``nelec`` and ``2Sz`` unused. - - **Hubbard** (other solvers): ``ncond`` required; ``2Sz`` defaults to 0 - in canonical, unused in GC. - - **Spin**: ``ncond`` unused (set to 0 for mVMC); ``2Sz`` required in - canonical, unused in GC. - - **Kondo** (HPhi, canonical): ``ncond`` required. - - **Kondo** (HPhi, GC): ``nelec`` and ``2Sz`` unused. - - **Kondo** (other solvers): ``ncond`` required; ``2Sz`` defaults to 0 - in canonical, unused in GC. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure, modified in place. - """ - is_hphi = (StdI.solver == SolverType.HPhi) - key = (StdI.model, is_hphi, StdI.lGC) - rule = _CONSERVED_QTY_RULES.get(key) - if rule is None: - return - ncond_label, ncond_action, sz2_action = rule - - # Apply ncond validation via dispatch - if ncond_action is not None: - _NCOND_ACTION_DISPATCH[ncond_action](ncond_label, StdI.ncond) - - # Spin + mVMC: set ncond = 0 (after not_used check, matching C order) - if StdI.model == ModelType.SPIN and StdI.solver == SolverType.mVMC: - StdI.ncond = 0 - - # Apply Sz2 validation via dispatch - if sz2_action is not None: - _SZ2_ACTION_DISPATCH[sz2_action](StdI) - - -def check_mod_para(StdI: StdIntList) -> None: - """Validate and set default values for solver-specific model parameters. - - This is the Python translation of the C function ``CheckModPara()``. - Depending on ``StdI.solver``, different parameter groups are - validated via :data:`_SOLVER_DEFAULTS_DISPATCH`. Then the conserved - quantities (``ncond``, ``2Sz``) are checked via - :func:`_check_conserved_quantities`. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. Many fields are read and - modified **in place** via the helper functions - :func:`print_val_i`, :func:`print_val_d`, :func:`not_used_i`, - and :func:`required_val_i`. - """ - # ------------------------------------------------------------------ - # Solver-specific defaults (dispatched) - # ------------------------------------------------------------------ - handler = _SOLVER_DEFAULTS_DISPATCH.get(StdI.solver) - if handler is not None: - handler(StdI) - - # ------------------------------------------------------------------ - # Conserved quantities: ncond and 2Sz - # ------------------------------------------------------------------ - _check_conserved_quantities(StdI) diff --git a/python/writer/export_wannier90.py b/python/writer/export_wannier90.py deleted file mode 100644 index a298585..0000000 --- a/python/writer/export_wannier90.py +++ /dev/null @@ -1,899 +0,0 @@ -""" -Export functions for the Wannier90/HWAVE format. - -This module contains functions to export lattice geometry and interaction -parameters in a format compatible with Wannier90. The main public functions -are: - -- ``export_geometry()`` -- Exports lattice vectors and orbital positions -- ``export_interaction()`` -- Exports hopping and interaction parameters - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import itertools -import sys -from dataclasses import dataclass, field - -import numpy as np - -from stdface_vals import StdIntList, NaN_i, UNSET_STRING -from param_check import exit_program -from lattice.site_util import _cell_vector - -# ----------------------------------------------------------------------- -# Module-level constants -# ----------------------------------------------------------------------- - -_EPS = 1.0e-8 -"""Small number for floating-point comparisons.""" - - -# ----------------------------------------------------------------------- -# Control flags -# ----------------------------------------------------------------------- - -_is_export_all = 1 -"""Default for the exportall parameter (1 = export zero elements too).""" - - -# ----------------------------------------------------------------------- -# Internal data structures -# ----------------------------------------------------------------------- - -@dataclass -class _IntrItem: - """Store an interaction parameter between orbitals. - - Attributes - ---------- - r : list of int - Relative coordinate between orbitals (length 3). - a : int - First orbital index. - b : int - Second orbital index. - s : int - First spin index. - t : int - Second spin index. - v : complex - Interaction strength. - """ - r: list[int] = field(default_factory=lambda: [0, 0, 0]) - a: int = 0 - b: int = 0 - s: int = 0 - t: int = 0 - v: complex = 0.0 + 0.0j - - -# ----------------------------------------------------------------------- -# Helper: fatal error -# ----------------------------------------------------------------------- - -def _fatal(msg: str) -> None: - """Print an error message and exit. - - Parameters - ---------- - msg : str - Error description. - """ - print(f"ERROR: {msg}", file=sys.stderr) - exit_program(-1) - - -# ----------------------------------------------------------------------- -# Write geometry file -# ----------------------------------------------------------------------- - -def _write_geometry(StdI: StdIntList, fname: str) -> None: - """Write geometry data to file in Wannier90 format. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters containing geometry info. - fname : str - Output filename. - - Notes - ----- - Writes: - - Primitive lattice vectors - - Number of orbitals per unit cell - - Orbital positions in fractional coordinates - """ - with open(fname, "w") as fp_out: - # Print primitive vectors - for row in StdI.direct: - fp_out.write(f"{row[0]:16.12f} {row[1]:16.12f} {row[2]:16.12f}\n") - - # Print number of orbits - fp_out.write(f"{StdI.NsiteUC}\n") - - # Print centre of orbits - for tau_row in StdI.tau[:StdI.NsiteUC]: - fp_out.write(f"{tau_row[0]:25.15e} " - f"{tau_row[1]:25.15e} " - f"{tau_row[2]:25.15e}\n") - print(f"{fname:>24s} is written.") - - -# ----------------------------------------------------------------------- -# Index macro (replaces C's _index) -# ----------------------------------------------------------------------- - -def _compute_index(rx: int, ry: int, rz: int, - a: int, b: int, s: int, t: int, - rr: list[int], nsiteuc: int, nspin: int) -> int: - """Compute flat index into the interaction matrix. - - Parameters - ---------- - rx, ry, rz : int - Relative coordinates. - a, b : int - Orbital indices. - s, t : int - Spin indices. - rr : list of int - Half-ranges for each direction. - nsiteuc : int - Number of sites per unit cell. - nspin : int - Number of spin states. - - Returns - ------- - int - Flat index into the matrix array. - """ - return (t + nspin * ( - s + nspin * ( - b + nsiteuc * ( - a + nsiteuc * ( - (rz + rr[2]) + (rr[2] * 2 + 1) * ( - (ry + rr[1]) + (rr[1] * 2 + 1) * ( - rx + rr[0] - ))))))) - - -# ----------------------------------------------------------------------- -# Write Wannier90-format interaction file -# ----------------------------------------------------------------------- - -def _build_wannier_matrix( - intr_table: list[_IntrItem], - nsiteuc: int, - nspin: int, -) -> tuple[list[int], int, np.ndarray]: - """Build the flat interaction matrix from a list of interaction items. - - Scans the items to determine the coordinate half-ranges, allocates - a flat complex matrix, and populates it — including Hermitian-conjugate - entries for any reverse-direction slot that is still empty. - - Parameters - ---------- - intr_table : list of _IntrItem - Array of interaction parameters. - nsiteuc : int - Number of sites per unit cell. - nspin : int - Number of spin states. - - Returns - ------- - rr : list of int - Half-ranges ``[rr0, rr1, rr2]`` for each lattice direction. - nvol : int - Total number of real-space unit cells in the range. - matrix : numpy.ndarray - Flat complex array of length ``nvol * nsiteuc**2 * nspin**2``. - """ - all_r = np.array([entry.r for entry in intr_table]) - rr = list(np.max(np.abs(all_r), axis=0)) - - dims = [rr[i] * 2 + 1 for i in range(3)] - nvol = dims[0] * dims[1] * dims[2] - matrix_size = nvol * nsiteuc * nsiteuc * nspin * nspin - matrix = np.zeros(matrix_size, dtype=complex) - - for entry in intr_table: - idx = _compute_index( - entry.r[0], entry.r[1], entry.r[2], - entry.a, entry.b, entry.s, entry.t, - rr, nsiteuc, nspin) - matrix[idx] = entry.v - - ridx = _compute_index( - -entry.r[0], -entry.r[1], -entry.r[2], - entry.b, entry.a, entry.t, entry.s, - rr, nsiteuc, nspin) - - if abs(matrix[ridx]) < _EPS: - matrix[ridx] = np.conj(entry.v) - - return rr, nvol, matrix - - -def _write_wannier_body( - fp: 'TextIO', - rr: list[int], - nvol: int, - nsiteuc: int, - nspin: int, - matrix: np.ndarray, -) -> None: - """Write the matrix body of a Wannier90-format interaction file. - - Iterates over all real-space cells and orbital/spin pairs, writing - one line per entry. When ``nspin > 1`` the extended format with - explicit spin columns ``s`` and ``t`` is used; otherwise the compact - format without spin columns is written. - - Parameters - ---------- - fp : file object - Open file to write matrix entries to. - rr : list of int - Half-ranges for each lattice direction. - nvol : int - Total number of real-space unit cells. - nsiteuc : int - Number of sites per unit cell. - nspin : int - Number of spin states. - matrix : numpy.ndarray - Flat complex interaction matrix. - """ - spin_pairs = list(itertools.product(range(nspin), repeat=2)) - dims = [rr[i] * 2 + 1 for i in range(3)] - for r in range(nvol): - rz = r % dims[2] - rr[2] - ry = (r // dims[2]) % dims[1] - rr[1] - rx = (r // (dims[2] * dims[1])) % dims[0] - rr[0] - - for a in range(nsiteuc): - for b in range(nsiteuc): - for s, t in spin_pairs: - idx = _compute_index(rx, ry, rz, a, b, s, t, - rr, nsiteuc, nspin) - if _is_export_all or abs(matrix[idx]) > _EPS: - spin_part = f"{s:4d} {t:4d} " if nspin > 1 else "" - fp.write( - f"{rx:4d} {ry:4d} {rz:4d} " - f"{a + 1:4d} {b + 1:4d} " - f"{spin_part}" - f"{matrix[idx].real:16.12f} " - f"{matrix[idx].imag:16.12f}\n") - - -def _write_wannier90(intr_table: list[_IntrItem], - nsiteuc: int, nspin: int, - fname: str, tagname: str) -> None: - """Write interaction parameters to file in Wannier90 format. - - Builds the interaction matrix from the item list, then writes the - Wannier90-format file with header and body. - - Parameters - ---------- - intr_table : list of _IntrItem - Array of interaction parameters. - nsiteuc : int - Number of sites per unit cell. - nspin : int - Number of spin states. - fname : str - Output filename. - tagname : str - Tag identifying interaction type. - """ - rr, nvol, matrix = _build_wannier_matrix( - intr_table, nsiteuc, nspin) - - with open(fname, "w") as fp_out: - # Write header - fp_out.write(f"{tagname} in wannier90-like format for uhfk\n") - fp_out.write(f"{nsiteuc}\n{nvol}\n") - for i in range(nvol): - if i > 0 and i % 15 == 0: - fp_out.write("\n") - fp_out.write(f" {1}") - fp_out.write("\n") - - _write_wannier_body(fp_out, rr, nvol, nsiteuc, nspin, matrix) - print(f"{fname:>24s} is written.") - - -# ----------------------------------------------------------------------- -# Unfold site coordinates -# ----------------------------------------------------------------------- - -def _unfold_site(StdI: StdIntList, v_in: list[int]) -> list[int]: - """Convert site coordinates from [0, N] to [-N/2, N/2] range. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters. - v_in : list of int - Input coordinates (length 3). - - Returns - ------- - list of int - Output coordinates in the unfolded range (length 3). - """ - # v = rbox @ v_in (matrix-vector product) - v = StdI.rbox.astype(int) @ np.array(v_in) - - # Fold to [-N/2, N/2] range using vectorized operations - vv = v / StdI.NCell - v = np.where(vv > 0.5, v - StdI.NCell, v) - v = np.where(vv <= -0.5, v + StdI.NCell, v) - - # w = (v @ box) // NCell - w = (v @ StdI.box.astype(int)) // StdI.NCell - - return w.tolist() - - -# ----------------------------------------------------------------------- -# Key generation / comparison helpers -# ----------------------------------------------------------------------- - -def _generate_key(keylen: int, index: list[int], ordered: int) -> tuple[int, ...]: - """Generate hashable key for interaction table entry. - - Parameters - ---------- - keylen : int - Length of key (1, 2, or 4). - index : list of int - Input indices. - ordered : int - Whether to order indices (1 = yes). - - Returns - ------- - tuple of int - Generated key (hashable, suitable as dict key). - """ - if keylen == 1: - return (index[0],) - elif keylen == 2: - i, j = index[:2] - return (j, i) if ordered == 1 and i > j else (i, j) - elif keylen == 4: - i, s, j, t = index[:4] - return (j, t, i, s) if ordered == 1 and i > j else (i, s, j, t) - else: - _fatal(f"unsupported keylen: {keylen}") - return () # unreachable - - -# ----------------------------------------------------------------------- -# Accumulate entries -# ----------------------------------------------------------------------- - -def _accumulate_list(keylen: int, - ntbl: int, - tbl_index: np.ndarray, - tbl_value: np.ndarray, - ordered: int - ) -> tuple: - """Accumulate interaction table entries with same key. - - Parameters - ---------- - keylen : int - Length of key (1, 2, or 4). - ntbl : int - Number of input entries. - tbl_index : np.ndarray - Input indices, shape ``(ntbl, keylen)``. - tbl_value : np.ndarray - Input values, shape ``(ntbl,)``. - ordered : int - Whether to order indices (1 = yes). - - Returns - ------- - nintr : int - Number of output entries. - intr_index : list of list of int - Output indices (each entry is a list of length *keylen*). - intr_value : list of complex - Output values. - """ - # Use a dict keyed by tuple for O(1) lookup instead of O(n) linear scan - accum: dict[tuple[int, ...], complex] = {} - key_order: list[tuple[int, ...]] = [] - - for k in range(ntbl): - idx = _generate_key(keylen, list(tbl_index[k, :keylen]), ordered) - val = complex(tbl_value[k]) - - if idx in accum: - accum[idx] += val - else: - accum[idx] = val - key_order.append(idx) - - # Filter out near-zero entries - intr_index: list[list[int]] = [] - intr_value: list[complex] = [] - for key in key_order: - if abs(accum[key]) >= _EPS: - intr_index.append(list(key)) - intr_value.append(accum[key]) - - return len(intr_index), intr_index, intr_value - - -# ----------------------------------------------------------------------- -# Export: inter-site interaction (complex) -# ----------------------------------------------------------------------- - -def _build_inter_table( - StdI: StdIntList, - nintr: int, - intr_index: list[list[int]], - intr_value: np.ndarray, -) -> list[_IntrItem]: - """Build a deduplicated interaction table in relative coordinates. - - Converts accumulated inter-site interaction entries from absolute - site indices to relative-coordinate ``_IntrItem`` entries, - deduplicating by the key ``(rr, isite, jsite)``. Spin indices - are fixed to ``(0, 0)``. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters (reads ``NsiteUC``, ``Cell``). - nintr : int - Number of accumulated entries. - intr_index : list of list of int - Accumulated indices, each row is ``[idx_i, idx_j]``. - intr_value : np.ndarray - Accumulated complex values, shape ``(nintr,)``. - - Returns - ------- - list of _IntrItem - Deduplicated interaction entries in relative coordinates. - """ - intr_table: list[_IntrItem] = [] - seen: dict[tuple, int] = {} # key -> index in intr_table - - for k in range(nintr): - idx_i, idx_j = intr_index[k][:2] - - icell, isite = divmod(idx_i, StdI.NsiteUC) - jcell, jsite = divmod(idx_j, StdI.NsiteUC) - - jCV = _cell_vector(StdI.Cell, jcell) - iCV = _cell_vector(StdI.Cell, icell) - rr = _unfold_site(StdI, [j - i for j, i in zip(jCV, iCV)]) - - lookup_key = (tuple(rr), isite, jsite) - if lookup_key in seen: - existing = intr_table[seen[lookup_key]] - if abs(existing.v - intr_value[k]) > _EPS: - print(f"WARNING: not uniform. " - f"expected=({existing.v.real},{existing.v.imag}), " - f"found=({intr_value[k].real},{intr_value[k].imag}) " - f"for index {idx_i},{idx_j}") - else: - seen[lookup_key] = len(intr_table) - intr_table.append(_IntrItem( - r=list(rr), a=isite, b=jsite, - s=0, t=0, v=intr_value[k])) - - return intr_table - - -def _export_inter(StdI: StdIntList, - ntbl: int, - tbl_index: np.ndarray, - tbl_value: np.ndarray, - fname: str, tagname: str) -> None: - """Export interaction coefficients from complex array. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters. - ntbl : int - Number of interaction terms. - tbl_index : np.ndarray - Interaction indices, shape ``(ntbl, 2)``. - tbl_value : np.ndarray - Interaction values, shape ``(ntbl,)`` (complex). - fname : str - Output filename. - tagname : str - Tag identifying interaction type. - """ - if ntbl == 0: - print(f"{fname:>24s} is skipped.") - return - - nintr, intr_index, intr_value = _accumulate_list( - 2, ntbl, tbl_index, tbl_value, 1) - - intr_table = (_build_inter_table(StdI, nintr, intr_index, intr_value) - if nintr > 0 else []) - - if intr_table: - _write_wannier90(intr_table, StdI.NsiteUC, 1, fname, tagname) - else: - print(f"{fname:>24s} is skipped.") - - -# ----------------------------------------------------------------------- -# Export: inter-site interaction (real -> complex wrapper) -# ----------------------------------------------------------------------- - -def _export_inter_real(StdI: StdIntList, - ntbl: int, - tbl_index: np.ndarray, - tbl_value: np.ndarray, - fname: str, tagname: str) -> None: - """Export interaction coefficients from real array. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters. - ntbl : int - Number of interaction terms. - tbl_index : np.ndarray - Interaction indices, shape ``(ntbl, 2)``. - tbl_value : np.ndarray - Interaction values, shape ``(ntbl,)`` (real). - fname : str - Output filename. - tagname : str - Tag identifying interaction type. - - Notes - ----- - Converts real array to complex and calls ``_export_inter()``. - """ - if tbl_value is not None: - buf = tbl_value.astype(complex) - else: - buf = np.array([], dtype=complex) - _export_inter(StdI, ntbl, tbl_index, buf, fname, tagname) - - -# ----------------------------------------------------------------------- -# Export: transfer (hopping) coefficients -# ----------------------------------------------------------------------- - -def _build_transfer_table( - StdI: StdIntList, - nintr: int, - intr_index: list[list[int]], - intr_value: np.ndarray, - spin_dep: int, -) -> list[_IntrItem]: - """Build a deduplicated transfer table in relative coordinates. - - Converts accumulated transfer entries from absolute site indices - to relative-coordinate ``_IntrItem`` entries, deduplicating by - the key ``(rr, isite, jsite, ispin, jspin)``. Values are - sign-flipped (multiplied by −1) per the Wannier90 convention. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters (reads ``NsiteUC``, ``Cell``). - nintr : int - Number of accumulated entries. - intr_index : list of list of int - Accumulated indices, each row is ``[idx_i, ispin, idx_j, jspin]``. - intr_value : np.ndarray - Accumulated complex values, shape ``(nintr,)``. - **Modified in-place** (sign-flipped). - spin_dep : int - Whether transfer is spin-dependent (1 = yes, 0 = no). - When 0, entries where ``(ispin, jspin) != (0, 0)`` are skipped. - - Returns - ------- - list of _IntrItem - Deduplicated transfer entries in relative coordinates. - """ - intr_table: list[_IntrItem] = [] - seen: dict[tuple, int] = {} # key -> index in intr_table - - for k in range(nintr): - idx_i, ispin, idx_j, jspin = intr_index[k][:4] - - icell, isite = divmod(idx_i, StdI.NsiteUC) - jcell, jsite = divmod(idx_j, StdI.NsiteUC) - - jCV = _cell_vector(StdI.Cell, jcell) - iCV = _cell_vector(StdI.Cell, icell) - rr = _unfold_site(StdI, [j - i for j, i in zip(jCV, iCV)]) - - intr_value[k] *= -1 # by convention - - lookup_key = (tuple(rr), isite, jsite, ispin, jspin) - if lookup_key in seen: - existing = intr_table[seen[lookup_key]] - if abs(existing.v - intr_value[k]) > _EPS: - print(f"WARNING: not uniform. " - f"expected=({existing.v.real},{existing.v.imag}), " - f"found=({intr_value[k].real},{intr_value[k].imag}) " - f"for index {idx_i},{idx_j}") - else: - if spin_dep == 0 and not (ispin == 0 and jspin == 0): - continue # skip - - seen[lookup_key] = len(intr_table) - intr_table.append(_IntrItem( - r=list(rr), a=isite, b=jsite, - s=ispin, t=jspin, v=intr_value[k])) - - return intr_table - - -def _export_transfer(StdI: StdIntList, - ntbl: int, - tbl_index: np.ndarray, - tbl_value: np.ndarray, - fname: str, tagname: str, - spin_dep: int) -> None: - """Export transfer (hopping) coefficients. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters. - ntbl : int - Number of transfer terms. - tbl_index : np.ndarray - Transfer indices, shape ``(ntbl, 4)``. - tbl_value : np.ndarray - Transfer values, shape ``(ntbl,)`` (complex). - fname : str - Output filename. - tagname : str - Tag identifying transfer type. - spin_dep : int - Whether transfer is spin-dependent (1 = yes, 0 = no). - """ - if ntbl == 0: - print(f"{fname:>24s} is skipped.") - return - - # Accumulate entries of the same index pair - nintr, intr_index, intr_value = _accumulate_list( - 4, ntbl, tbl_index, tbl_value, 0) - - intr_table = (_build_transfer_table( - StdI, nintr, intr_index, intr_value, spin_dep) - if nintr > 0 else []) - - if intr_table: - _write_wannier90(intr_table, StdI.NsiteUC, - 2 if spin_dep == 1 else 1, fname, tagname) - else: - print(f"{fname:>24s} is skipped.") - - -# ----------------------------------------------------------------------- -# Export: on-site Coulomb -# ----------------------------------------------------------------------- - -def _build_coulomb_intra_table( - StdI: StdIntList, - nintr: int, - intr_index: list[list[int]], - intr_value: np.ndarray, -) -> list[_IntrItem]: - """Build a deduplicated on-site Coulomb table. - - Converts accumulated on-site Coulomb entries from absolute site - indices to ``_IntrItem`` entries, deduplicating by the unit-cell - site index ``isite``. Each entry has ``rr = [0,0,0]`` and - ``a == b == isite``. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters (reads ``NsiteUC``). - nintr : int - Number of accumulated entries. - intr_index : list of list of int - Accumulated indices, each row is ``[idx_i]``. - intr_value : np.ndarray - Accumulated complex values, shape ``(nintr,)``. - - Returns - ------- - list of _IntrItem - Deduplicated on-site Coulomb entries. - """ - intr_table: list[_IntrItem] = [] - seen: dict[int, int] = {} # isite -> index in intr_table - - for k in range(nintr): - idx_i = intr_index[k][0] - isite = idx_i % StdI.NsiteUC - - if isite in seen: - existing = intr_table[seen[isite]] - if abs(existing.v - intr_value[k]) > _EPS: - print(f"WARNING: not uniform. " - f"expected=({existing.v.real},{existing.v.imag}), " - f"found=({intr_value[k].real},{intr_value[k].imag}) " - f"for index {idx_i}") - else: - seen[isite] = len(intr_table) - intr_table.append(_IntrItem( - r=[0, 0, 0], a=isite, b=isite, - s=0, t=0, v=intr_value[k])) - - return intr_table - - -def _export_coulomb_intra(StdI: StdIntList, - ntbl: int, - tbl_index: np.ndarray, - tbl_value: np.ndarray, - fname: str, tagname: str) -> None: - """Export on-site Coulomb term coefficients. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters. - ntbl : int - Number of Coulomb terms. - tbl_index : np.ndarray - Site indices, shape ``(ntbl, 1)``. - tbl_value : np.ndarray - Coulomb coefficients, shape ``(ntbl,)`` (real). - fname : str - Output filename. - tagname : str - Tag identifying the term type. - """ - if ntbl == 0: - print(f"{fname:>24s} is skipped.") - return - - tbl_value_c = tbl_value.astype(complex) - - nintr, intr_index, intr_value = _accumulate_list( - 1, ntbl, tbl_index, tbl_value_c, 1) - - intr_table = (_build_coulomb_intra_table( - StdI, nintr, intr_index, intr_value) - if nintr > 0 else []) - - if intr_table: - _write_wannier90(intr_table, StdI.NsiteUC, 1, fname, tagname) - else: - print(f"{fname:>24s} is skipped.") - - -# ----------------------------------------------------------------------- -# Filename prefix helper -# ----------------------------------------------------------------------- - -def _prefix(StdI: StdIntList, fname: str) -> str: - """Add prefix to filename if ``StdI.fileprefix`` is set. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters. - fname : str - Base filename. - - Returns - ------- - str - Filename with optional prefix prepended. - - Notes - ----- - In the C code, the sentinel for "no prefix" is ``"****"``. - In Python, the sentinel is the empty string ``""``. - """ - if StdI.fileprefix == "" or StdI.fileprefix == UNSET_STRING: - return fname - else: - return f"{StdI.fileprefix}_{fname}" - - -# ======================================================================= -# Public API -# ======================================================================= - -def export_geometry(StdI: StdIntList) -> None: - """Export geometry information to file. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters containing lattice geometry. - - Notes - ----- - This function is only meaningful when ``StdI.solver == "HWAVE"``. - It writes the file ``geom.dat`` (optionally with prefix). - """ - _write_geometry(StdI, _prefix(StdI, "geom.dat")) - - -def export_interaction(StdI: StdIntList) -> None: - """Export interaction term coefficients to files. - - Parameters - ---------- - StdI : StdIntList - Standard input parameters containing interaction terms. - - Notes - ----- - This function is only meaningful when ``StdI.solver == "HWAVE"``. - It writes the following files (optionally with prefix): - - - ``transfer.dat`` - - ``coulombintra.dat`` - - ``coulombinter.dat`` - - ``hund.dat`` - - ``exchange.dat`` - - ``pairlift.dat`` - - ``pairhopp.dat`` - """ - global _is_export_all - - if StdI.export_all != NaN_i: - _is_export_all = StdI.export_all - - _export_transfer( - StdI, - StdI.ntrans, StdI.transindx, StdI.trans, - _prefix(StdI, "transfer.dat"), "Transfer", - 0) - - _export_coulomb_intra( - StdI, - StdI.NCintra, StdI.CintraIndx, StdI.Cintra, - _prefix(StdI, "coulombintra.dat"), "CoulombIntra") - - # Two-body shortcut interactions: (count_attr, indx_attr, val_attr, filename, tag) - _INTER_REAL_EXPORTS = ( - ("NCinter", "CinterIndx", "Cinter", "coulombinter.dat", "CoulombInter"), - ("NHund", "HundIndx", "Hund", "hund.dat", "Hund"), - ("NEx", "ExIndx", "Ex", "exchange.dat", "Exchange"), - ("NPairLift", "PLIndx", "PairLift", "pairlift.dat", "PairLift"), - ("NPairHopp", "PHIndx", "PairHopp", "pairhopp.dat", "PairHopp"), - ) - for cnt_attr, idx_attr, val_attr, fname_suffix, tag in _INTER_REAL_EXPORTS: - _export_inter_real( - StdI, - getattr(StdI, cnt_attr), getattr(StdI, idx_attr), getattr(StdI, val_attr), - _prefix(StdI, fname_suffix), tag) diff --git a/python/writer/hphi_writer.py b/python/writer/hphi_writer.py deleted file mode 100644 index 105d42c..0000000 --- a/python/writer/hphi_writer.py +++ /dev/null @@ -1,1100 +0,0 @@ -"""HPhi solver-specific output functions. - -This module contains functions that generate definition files specific to the -HPhi (Exact Diagonalization) solver. These were extracted from -``stdface_main.py`` during refactoring. - -Functions ---------- -large_value - Compute the ``LargeValue`` parameter for TPQ calculations. -print_calc_mod - Write ``calcmod.def`` with calculation-mode integers. -print_excitation - Write ``single.def`` or ``pair.def`` for spectrum calculations. -vector_potential - Compute vector potential A(t) and electric field E(t) for time evolution. -print_pump - Write ``teone.def`` or ``tetwo.def`` for time-evolution pump terms. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" -from __future__ import annotations - -import math -from collections.abc import Callable -from typing import NamedTuple - -import numpy as np - -from stdface_vals import StdIntList, ModelType, MethodType, NaN_i, UNSET_STRING, AMPLITUDE_EPS -from param_check import exit_program, print_val_d, print_val_i -from .common_writer import _merge_duplicate_terms - -# --------------------------------------------------------------------------- -# String → integer dispatch tables for calcmod.def -# --------------------------------------------------------------------------- - -METHOD_TO_CALC_TYPE: dict[MethodType, int] = { - MethodType.LANCZOS: 0, - MethodType.LANCZOS_ENERGY: 0, - MethodType.TPQ: 1, - MethodType.FULLDIAG: 2, - MethodType.CG: 3, - MethodType.TIME_EVOLUTION: 4, - MethodType.CTPQ: 5, -} -"""Maps each HPhi calculation method to its ``CalcType`` integer code.""" - -RESTART_TO_INT: dict[str, int] = { - "none": 0, - "restart_out": 1, - "save": 1, - "restartsave": 2, - "restart": 2, - "restart_in": 3, -} -"""Maps each HPhi ``Restart`` string to its integer code.""" - -CALC_SPEC_TO_INT: dict[str, int] = { - "none": 0, - "normal": 1, - "noiteration": 2, - "restart_out": 3, - "restart_in": 4, - "restartsave": 5, - "restart": 5, -} -"""Maps each HPhi ``CalcSpec`` string to its integer code.""" - -MODEL_GC_TO_CALC_MODEL: dict[tuple, int] = { - (ModelType.HUBBARD, 0): 0, - (ModelType.HUBBARD, 1): 3, - (ModelType.SPIN, 0): 1, - (ModelType.SPIN, 1): 4, - (ModelType.KONDO, 0): 2, - (ModelType.KONDO, 1): 5, -} -"""Maps ``(ModelType, lGC)`` to the ``CalcModel`` integer code.""" - -INITIAL_VEC_TYPE_TO_INT: dict[str, int] = { - "c": 0, - "r": 1, -} -"""Maps the ``InitialVecType`` string to its integer code.""" - -class _IOFlags(NamedTuple): - """Pair of input/output flag integers for I/O dispatch tables. - - Attributes - ---------- - flag0 : int - First flag (InputEigenVec for EigenVecIO, iOutputHam for HamIO). - flag1 : int - Second flag (OutputEigenVec for EigenVecIO, iInputHam for HamIO). - """ - - flag0: int - flag1: int - - -EIGENVEC_IO_TO_FLAGS: dict[str, _IOFlags] = { - "none": _IOFlags(0, 0), - "in": _IOFlags(1, 0), - "out": _IOFlags(0, 1), - "inout": _IOFlags(1, 1), -} -"""Maps ``EigenVecIO`` string to ``(InputEigenVec, OutputEigenVec)`` flags.""" - -HAM_IO_TO_FLAGS: dict[str, _IOFlags] = { - "none": _IOFlags(0, 0), - "out": _IOFlags(1, 0), - "in": _IOFlags(0, 1), -} -"""Maps ``HamIO`` string to ``(iOutputHam, iInputHam)`` flags.""" - -OUTPUT_EX_VEC_TO_INT: dict[str, int] = { - "none": 0, - "out": 1, -} -"""Maps ``OutputExVec`` string to its integer code.""" - - -def _resolve_string_param( - StdI: StdIntList, - field: str, - label: str, - default: str, - default_value: int | tuple[int, int], - dispatch: dict, -) -> int | tuple[int, int]: - """Resolve a string-valued parameter to its integer code(s). - - If the field on *StdI* is ``UNSET_STRING``, it is set to *default* - and *default_value* is returned. Otherwise the field value is looked - up in *dispatch*; a missing key causes ``exit_program(-1)``. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure (field is read/written in-place). - field : str - Attribute name on *StdI* (e.g. ``"Restart"``). - label : str - Display label for log messages (e.g. ``"Restart"``). - default : str - Default string value when unset (e.g. ``"none"``). - default_value : int or tuple of int - Value(s) to return when the field is unset. - dispatch : dict - Mapping from string values to integer code(s). - - Returns - ------- - int or tuple of int - The resolved integer code(s). - """ - field_val = getattr(StdI, field) - if field_val == UNSET_STRING: - setattr(StdI, field, default) - print(f" {label:>20s} = {default:<12s}###### DEFAULT VALUE IS USED ######") - return default_value - print(f" {label:>20s} = {field_val}") - result = dispatch.get(field_val) - if result is None: - print(f"\n ERROR ! {label} : {field_val}") - exit_program(-1) - return result - - -def large_value(StdI: StdIntList) -> None: - """Compute and set the ``LargeValue`` parameter for TPQ calculations. - - ``LargeValue`` is the sum of the absolute values of all one-body - (transfer) and two-body (interaction / exchange / Hund / pair-lift / - Coulomb-intra / Coulomb-inter) terms, divided by the number of - sites. The result is stored in ``StdI.LargeValue`` via - :func:`print_val_d`. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. The following fields are read: - - - ``ntrans``, ``trans`` -- one-body transfer terms. - - ``nintr``, ``intr`` -- general two-body interaction terms. - - ``NCintra``, ``Cintra`` -- intra-site Coulomb terms. - - ``NCinter``, ``Cinter`` -- inter-site Coulomb terms. - - ``NEx``, ``Ex`` -- exchange terms. - - ``NPairLift``, ``PairLift`` -- pair-lift terms. - - ``NHund``, ``Hund`` -- Hund coupling terms. - - ``nsite`` -- total number of sites. - - ``LargeValue`` -- set **in place** to the computed value - (unless the user already specified it). - """ - large_value0 = ( - np.sum(np.abs(StdI.trans[:StdI.ntrans])) - + np.sum(np.abs(StdI.intr[:StdI.nintr])) - + np.sum(np.abs(StdI.Cintra[:StdI.NCintra])) - + np.sum(np.abs(StdI.Cinter[:StdI.NCinter])) - + 2.0 * np.sum(np.abs(StdI.Ex[:StdI.NEx])) - + 2.0 * np.sum(np.abs(StdI.PairLift[:StdI.NPairLift])) - + 2.0 * np.sum(np.abs(StdI.Hund[:StdI.NHund])) - ) - - large_value0 /= float(StdI.nsite) - - StdI.LargeValue = print_val_d("LargeValue", StdI.LargeValue, large_value0) - - -def _validate_ngpu_scalapack(StdI: StdIntList) -> None: - """Validate ``NGPU`` and ``Scalapack`` parameters if set. - - Prints the parameter values and calls :func:`exit_program` if - they are out of range. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. Reads ``NGPU`` and ``Scalapack``. - """ - if StdI.NGPU != NaN_i: - print(f" NGPU = {StdI.NGPU}") - if StdI.NGPU < 1: - print(f"\n ERROR ! NGPU : {StdI.NGPU}") - print(" NGPU should be a positive integer.") - exit_program(-1) - - if StdI.Scalapack != NaN_i: - print(f" Scalapack = {StdI.Scalapack}") - if StdI.Scalapack < 0 or StdI.Scalapack > 1: - print(f"\n ERROR ! Scalapack : {StdI.Scalapack}") - print(" Scalapack should be 0 or 1.") - exit_program(-1) - - -class _CalcModParams(NamedTuple): - """Resolved integer parameters for ``calcmod.def``. - - Attributes - ---------- - iCalcType : int - Calculation type code (0–5). - iCalcModel : int - Calculation model code (0–5). - iCalcEigenvec : int - Whether to calculate eigenvectors (0 or 1). - iRestart : int - Restart flag (0–3). - iCalcSpec : int - Spectrum calculation flag (0–5). - iInitialVecType : int - Initial vector type code. - InputEigenVec : int - Whether to read eigenvectors from file. - OutputEigenVec : int - Whether to write eigenvectors to file. - iInputHam : int - Whether to read Hamiltonian from file. - iOutputHam : int - Whether to write Hamiltonian to file. - iOutputExVec : int - Whether to output excited-state vectors. - """ - - iCalcType: int - iCalcModel: int - iCalcEigenvec: int - iRestart: int - iCalcSpec: int - iInitialVecType: int - InputEigenVec: int - OutputEigenVec: int - iInputHam: int - iOutputHam: int - iOutputExVec: int - - -def _write_calcmod_file(StdI: StdIntList, params: _CalcModParams) -> None: - """Write ``calcmod.def`` with the resolved integer parameters. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. Reads ``NGPU`` and ``Scalapack`` - for conditional output lines. - params : _CalcModParams - Resolved calculation-mode parameters. - """ - with open("calcmod.def", "w") as fp: - fp.write("#CalcType = 0:Lanczos, 1:TPQCalc, 2:FullDiag, 3:CG, 4:Time-evolution 5:cTPQ\n") - fp.write("#CalcModel = 0:Hubbard, 1:Spin, 2:Kondo, 3:HubbardGC, 4:SpinGC, 5:KondoGC\n") - fp.write("#Restart = 0:None, 1:Save, 2:Restart&Save, 3:Restart\n") - fp.write("#CalcSpec = 0:None, 1:Normal, 2:No H*Phi, 3:Save, 4:Restart, 5:Restart&Save\n") - if StdI.NGPU != NaN_i: - fp.write("#NGPU (for FullDiag): The number of GPU\n") - if StdI.Scalapack != NaN_i: - fp.write("#Scalapack (for FullDiag) = 0:w/o ScaLAPACK, 1:w/ ScaLAPACK\n") - fp.write(f"CalcType {params.iCalcType:3d}\n") - fp.write(f"CalcModel {params.iCalcModel:3d}\n") - fp.write(f"ReStart {params.iRestart:3d}\n") - fp.write(f"CalcSpec {params.iCalcSpec:3d}\n") - fp.write(f"CalcEigenVec {params.iCalcEigenvec:3d}\n") - fp.write(f"InitialVecType {params.iInitialVecType:3d}\n") - fp.write(f"InputEigenVec {params.InputEigenVec:3d}\n") - fp.write(f"OutputEigenVec {params.OutputEigenVec:3d}\n") - fp.write(f"InputHam {params.iInputHam:3d}\n") - fp.write(f"OutputHam {params.iOutputHam:3d}\n") - fp.write(f"OutputExVec {params.iOutputExVec:3d}\n") - if StdI.NGPU != NaN_i: - fp.write(f"NGPU {StdI.NGPU:3d}\n") - if StdI.Scalapack != NaN_i: - fp.write(f"Scalapack {StdI.Scalapack:3d}\n") - - -def print_calc_mod(StdI: StdIntList) -> None: - """Write ``calcmod.def`` containing calculation-mode integers for HPhi. - - Maps the string-valued parameters ``method``, ``model``, ``Restart``, - ``InitialVecType``, ``EigenVecIO``, ``HamIO``, ``CalcSpec``, and - ``OutputExVec`` to the integer codes that HPhi expects. Validation - is delegated to :func:`_validate_ngpu_scalapack` and file writing to - :func:`_write_calcmod_file`. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. String fields such as - ``method``, ``model``, ``Restart``, etc. are read (and may be - overwritten with default values). ``NGPU`` and ``Scalapack`` - are validated if set. - """ - print("\n @ CalcMod\n") - - # ------------------------------------------------------------------ - # Method → CalcType integer - # ------------------------------------------------------------------ - iCalcEigenvec = 0 - - if StdI.method == UNSET_STRING: - print("ERROR ! Method is NOT specified !") - exit_program(-1) - - iCalcType = METHOD_TO_CALC_TYPE.get(StdI.method) - if iCalcType is None: - print(f"\n ERROR ! Unsupported Solver : {StdI.method}") - exit_program(-1) - - if StdI.method == MethodType.LANCZOS_ENERGY: - iCalcEigenvec = 1 - - if iCalcType != 4: - StdI.PumpBody = 0 - - # ------------------------------------------------------------------ - # Model - # ------------------------------------------------------------------ - iCalcModel = MODEL_GC_TO_CALC_MODEL.get((StdI.model, StdI.lGC)) - if iCalcModel is None: - print(f"\n ERROR ! Unsupported Model / GC combination : " - f"{StdI.model}, lGC={StdI.lGC}") - exit_program(-1) - - # ------------------------------------------------------------------ - # Resolve string parameters to integer codes - # ------------------------------------------------------------------ - iRestart = _resolve_string_param( - StdI, "Restart", "Restart", "none", 0, RESTART_TO_INT) - - # InitialVecType: default depends on method (TPQ/CTPQ → -1, else → 0) - ivt_default = -1 if StdI.method in (MethodType.TPQ, MethodType.CTPQ) else 0 - iInitialVecType = _resolve_string_param( - StdI, "InitialVecType", "InitialVecType", "c", - ivt_default, INITIAL_VEC_TYPE_TO_INT) - - InputEigenVec, OutputEigenVec = _resolve_string_param( - StdI, "EigenVecIO", "EigenVecIO", "none", (0, 0), EIGENVEC_IO_TO_FLAGS) - if StdI.method == MethodType.TIME_EVOLUTION: - InputEigenVec = 1 - - iOutputHam, iInputHam = _resolve_string_param( - StdI, "HamIO", "HamIO", "none", (0, 0), HAM_IO_TO_FLAGS) - - iCalcSpec = _resolve_string_param( - StdI, "CalcSpec", "CalcSpec", "none", 0, CALC_SPEC_TO_INT) - - iOutputExVec = _resolve_string_param( - StdI, "OutputExVec", "OutputExcitedVec", "none", - 0, OUTPUT_EX_VEC_TO_INT) - - # ------------------------------------------------------------------ - # Validate and write - # ------------------------------------------------------------------ - _validate_ngpu_scalapack(StdI) - _write_calcmod_file( - StdI, - _CalcModParams( - iCalcType, iCalcModel, iCalcEigenvec, - iRestart, iCalcSpec, iInitialVecType, - InputEigenVec, OutputEigenVec, iInputHam, iOutputHam, iOutputExVec, - ), - ) - - print(" calcmod.def is written.\n") - - -# --------------------------------------------------------------------------- -# Spectrum-type operator configuration -# --------------------------------------------------------------------------- - -def _spectrum_szsz( - model: ModelType, S2: int, - coef: list[float], spin: list[list[int]], -) -> tuple[int, int]: - """Configure SzSz spectrum operators. - - For SPIN models, produces ``S2 + 1`` operators with coefficients equal - to :math:`S_z` values. For Hubbard/Kondo models, produces two - operators with coefficients :math:`\\pm 0.5`. - - Parameters - ---------- - model : ModelType - The model type. - S2 : int - Twice the spin quantum number. - coef : list of float - Coefficient array (modified in place). - spin : list of list of int - Spin-index array (modified in place). - - Returns - ------- - tuple of (int, int) - ``(NumOp, SpectrumBody)`` — always ``SpectrumBody = 2``. - """ - if model == ModelType.SPIN: - NumOp = S2 + 1 - for ispin in range(S2 + 1): - Sz = float(ispin) - float(S2) * 0.5 - coef[ispin] = Sz - spin[ispin][0] = ispin - spin[ispin][1] = ispin - else: - NumOp = 2 - coef[0] = 0.5 - coef[1] = -0.5 - spin[0][0] = 0 - spin[0][1] = 0 - spin[1][0] = 1 - spin[1][1] = 1 - return NumOp, 2 - - -def _spectrum_spsm( - model: ModelType, S2: int, - coef: list[float], spin: list[list[int]], -) -> tuple[int, int]: - """Configure S+S- spectrum operators. - - For high-spin SPIN models (``S2 > 1``), produces ``S2`` operators with - ladder coefficients :math:`\\sqrt{S(S+1) - S_z(S_z+1)}`. Otherwise - produces a single operator with coefficient 1. - - Parameters - ---------- - model : ModelType - The model type. - S2 : int - Twice the spin quantum number. - coef : list of float - Coefficient array (modified in place). - spin : list of list of int - Spin-index array (modified in place). - - Returns - ------- - tuple of (int, int) - ``(NumOp, SpectrumBody)`` — always ``SpectrumBody = 2``. - """ - if model == ModelType.SPIN and S2 > 1: - NumOp = S2 - S = float(S2) * 0.5 - for ispin in range(1, S2 + 1): - Sz = float(S2) * 0.5 - float(ispin) - coef[ispin - 1] = math.sqrt(S * (S + 1.0) - Sz * (Sz + 1.0)) - spin[ispin - 1][0] = ispin - spin[ispin - 1][1] = ispin - 1 - else: - NumOp = 1 - coef[0] = 1.0 - spin[0][0] = 1 - spin[0][1] = 0 - return NumOp, 2 - - -def _spectrum_density( - model: ModelType, S2: int, - coef: list[float], spin: list[list[int]], -) -> tuple[int, int]: - """Configure density spectrum operators. - - Always produces two operators (up and down) with coefficient 1. - - Parameters - ---------- - model : ModelType - The model type (unused, included for dispatch signature). - S2 : int - Twice the spin quantum number (unused). - coef : list of float - Coefficient array (modified in place). - spin : list of list of int - Spin-index array (modified in place). - - Returns - ------- - tuple of (int, int) - ``(2, 2)`` — two operators, pair body. - """ - coef[0] = 1.0 - coef[1] = 1.0 - spin[0][0] = 0 - spin[0][1] = 0 - spin[1][0] = 1 - spin[1][1] = 1 - return 2, 2 - - -def _spectrum_up( - model: ModelType, S2: int, - coef: list[float], spin: list[list[int]], -) -> tuple[int, int]: - """Configure spin-up single-particle spectrum operator. - - Parameters - ---------- - model : ModelType - The model type (unused, included for dispatch signature). - S2 : int - Twice the spin quantum number (unused). - coef : list of float - Coefficient array (modified in place). - spin : list of list of int - Spin-index array (modified in place). - - Returns - ------- - tuple of (int, int) - ``(1, 1)`` — one operator, single body. - """ - coef[0] = 1.0 - spin[0][0] = 0 - return 1, 1 - - -def _spectrum_down( - model: ModelType, S2: int, - coef: list[float], spin: list[list[int]], -) -> tuple[int, int]: - """Configure spin-down single-particle spectrum operator. - - Parameters - ---------- - model : ModelType - The model type (unused, included for dispatch signature). - S2 : int - Twice the spin quantum number (unused). - coef : list of float - Coefficient array (modified in place). - spin : list of list of int - Spin-index array (modified in place). - - Returns - ------- - tuple of (int, int) - ``(1, 1)`` — one operator, single body. - """ - coef[0] = 1.0 - spin[0][0] = 1 - return 1, 1 - - -_SPECTRUM_HANDLERS: dict[str, Callable] = { - "szsz": _spectrum_szsz, - "s+s-": _spectrum_spsm, - "density": _spectrum_density, - "up": _spectrum_up, - "down": _spectrum_down, -} -"""Dispatch table mapping spectrum-type strings to handler functions.""" - - -def _configure_spectrum_ops( - spectrum_type: str, - model: ModelType, - S2: int, - coef: list[float], - spin: list[list[int]], -) -> tuple[int, int]: - """Configure operator coefficients and spins for a spectrum type. - - Dispatches to a per-type handler function via ``_SPECTRUM_HANDLERS``. - Populates *coef* and *spin* in place and returns ``(NumOp, SpectrumBody)``. - - Parameters - ---------- - spectrum_type : str - One of ``"szsz"``, ``"s+s-"``, ``"density"``, ``"up"``, ``"down"``. - model : ModelType - The model type (SPIN, HUBBARD, or KONDO). - S2 : int - Twice the spin quantum number. - coef : list of float - Coefficient array to populate (modified in place). - spin : list of list of int - Spin-index array to populate (modified in place, each element is - ``[spin_out, spin_in]``). - - Returns - ------- - tuple of (int, int) - ``(NumOp, SpectrumBody)`` where *NumOp* is the number of operators - and *SpectrumBody* is 1 (single) or 2 (pair). - - Raises - ------ - SystemExit - If *spectrum_type* is not recognized. - """ - handler = _SPECTRUM_HANDLERS.get(spectrum_type) - if handler is not None: - return handler(model, S2, coef, spin) - - print(f"\n ERROR ! SpectrumType : {spectrum_type}") - exit_program(-1) - - -def _compute_fourier_coefficients(StdI: StdIntList) -> tuple[list[float], list[float]]: - """Compute site Fourier coefficients for spectrum excitation. - - For each site in the super-cell, compute the real and imaginary parts - of :math:`\\exp(2\\pi i \\mathbf{q} \\cdot \\mathbf{r})`, where - :math:`\\mathbf{q}` is ``SpectrumQ`` and :math:`\\mathbf{r}` is - the cell + basis position. For the Kondo model, the itinerant-site - coefficients are duplicated to the local-spin sites. - - Parameters - ---------- - StdI : StdIntList - Global parameter structure. - - Returns - ------- - fourier_r : list of float - Real parts, length ``nsite``. - fourier_i : list of float - Imaginary parts, length ``nsite``. - """ - fourier_r = np.zeros(StdI.nsite) - fourier_i = np.zeros(StdI.nsite) - n_computed = StdI.NCell * StdI.NsiteUC - - if n_computed > 0: - # Compute position vectors: Cell[icell] + tau[itau] for all (icell, itau) pairs - # Shape: (NCell, 1, 3) + (1, NsiteUC, 3) -> (NCell, NsiteUC, 3) - positions = StdI.Cell[:StdI.NCell, :].astype(float)[:, np.newaxis, :] + \ - StdI.tau[:StdI.NsiteUC, :][np.newaxis, :, :] - # Dot with SpectrumQ: shape (NCell, NsiteUC), then flatten to site order - Cphase_flat = (2.0 * math.pi * (positions @ StdI.SpectrumQ)).ravel() - fourier_r[:n_computed] = np.cos(Cphase_flat) - fourier_i[:n_computed] = np.sin(Cphase_flat) - - if StdI.model == ModelType.KONDO: - half = StdI.nsite // 2 - fourier_r[half:] = fourier_r[:half] - fourier_i[half:] = fourier_i[:half] - - return fourier_r, fourier_i - - -def _write_excitation_file( - StdI: StdIntList, - NumOp: int, - coef: list[float], - spin: list[list[int]], - fourier_r: list[float], - fourier_i: list[float], -) -> None: - """Write ``single.def`` or ``pair.def`` excitation file. - - Depending on ``StdI.SpectrumBody``, writes either a single-body - excitation file (``single.def``) or a pair-body file (``pair.def``). - - Parameters - ---------- - StdI : StdIntList - Global parameter structure. - NumOp : int - Number of operator components per site. - coef : list of float - Coefficients for each operator component. - spin : list of list of int - Spin indices ``[s1, s2]`` for each operator component. - fourier_r : list of float - Real parts of Fourier coefficients, length ``nsite``. - fourier_i : list of float - Imaginary parts of Fourier coefficients, length ``nsite``. - """ - if StdI.SpectrumBody == 1: - with open("single.def", "w") as fp: - fp.write("=============================================\n") - if StdI.model == ModelType.KONDO: - fp.write(f"NSingle {StdI.nsite // 2 * NumOp}\n") - else: - fp.write(f"NSingle {StdI.nsite * NumOp}\n") - fp.write("=============================================\n") - fp.write("============== Single Excitation ============\n") - fp.write("=============================================\n") - if StdI.model == ModelType.KONDO: - for isite in range(StdI.nsite // 2, StdI.nsite): - fp.write(f"{isite} {spin[0][0]} 0 " - f"{fourier_r[isite] * coef[0]:25.15f} " - f"{fourier_i[isite] * coef[0]:25.15f}\n") - else: - for isite in range(StdI.nsite): - fp.write(f"{isite} {spin[0][0]} 0 " - f"{fourier_r[isite] * coef[0]:25.15f} " - f"{fourier_i[isite] * coef[0]:25.15f}\n") - print(" single.def is written.\n") - else: - with open("pair.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NPair {StdI.nsite * NumOp}\n") - fp.write("=============================================\n") - fp.write("=============== Pair Excitation =============\n") - fp.write("=============================================\n") - for isite in range(StdI.nsite): - for ispin in range(NumOp): - fp.write(f"{isite} {spin[ispin][0]} {isite} {spin[ispin][1]} 1 " - f"{fourier_r[isite] * coef[ispin]:25.15f} " - f"{fourier_i[isite] * coef[ispin]:25.15f}\n") - print(" pair.def is written.\n") - - -def print_excitation(StdI: StdIntList) -> None: - """Write ``single.def`` or ``pair.def`` for spectrum calculations. - - Depending on ``SpectrumType``, this function generates excitation - operators with the appropriate Fourier coefficients and spin - structure: - - - ``"szsz"`` (default) or ``"****"``: pair excitation with Sz - diagonal coefficients. - - ``"s+s-"``: pair excitation with S+S- ladder coefficients. - - ``"density"``: pair excitation with unit coefficients on both - spins. - - ``"up"``: single excitation with spin-up. - - ``"down"``: single excitation with spin-down. - - For the Kondo model, the Fourier coefficients for the itinerant - sites are duplicated to the local-spin sites. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. Key fields read: - - - ``model``, ``S2`` -- model type and twice the spin quantum - number. - - ``SpectrumType`` -- string selecting the excitation channel. - - ``SpectrumQ`` -- wavevector components (W, L, H). - - ``NCell``, ``NsiteUC``, ``Cell``, ``tau`` -- lattice cell and - basis-site data. - - ``nsite`` -- total number of sites. - - ``pi`` -- the mathematical constant pi. - - Modified in place: - - - ``SpectrumBody`` -- set to 1 (single) or 2 (pair). - - ``SpectrumQ`` -- defaults filled via :func:`print_val_d`. - """ - # --- Allocate coefficient and spin-index arrays --- - if StdI.model == ModelType.SPIN and StdI.S2 > 1: - n_alloc = StdI.S2 + 1 - else: - n_alloc = 2 - - coef = [0.0] * n_alloc - spin = [[0, 0] for _ in range(n_alloc)] - - print("\n @ Spectrum\n") - - StdI.SpectrumQ[0] = print_val_d("SpectrumQW", StdI.SpectrumQ[0], 0.0) - StdI.SpectrumQ[1] = print_val_d("SpectrumQL", StdI.SpectrumQ[1], 0.0) - StdI.SpectrumQ[2] = print_val_d("SpectrumQH", StdI.SpectrumQ[2], 0.0) - - # ------------------------------------------------------------------ - # Resolve SpectrumType default and determine NumOp, coef, spin - # ------------------------------------------------------------------ - if StdI.SpectrumType == UNSET_STRING: - StdI.SpectrumType = "szsz" - print(" SpectrumType = szsz ###### DEFAULT VALUE IS USED ######") - else: - print(f" SpectrumType = {StdI.SpectrumType}") - - NumOp, StdI.SpectrumBody = _configure_spectrum_ops( - StdI.SpectrumType, StdI.model, StdI.S2, coef, spin) - - # Compute Fourier coefficients - fourier_r, fourier_i = _compute_fourier_coefficients(StdI) - - # Write single.def or pair.def - _write_excitation_file(StdI, NumOp, coef, spin, fourier_r, fourier_i) - - -# --------------------------------------------------------------------------- -# Pump-type A(t) / E(t) computation functions -# --------------------------------------------------------------------------- - -def _pulselaser_At_Et( - time: float, V: float, freq: float, tshift: float, tdump: float, -) -> tuple[float, float]: - """Compute A(t) and E(t) for Gaussian-enveloped cosine pulse laser. - - Parameters - ---------- - time : float - Current time. - V : float - Vector potential amplitude for one component. - freq : float - Laser frequency. - tshift : float - Time shift for the pulse center. - tdump : float - Gaussian pulse width parameter. - - Returns - ------- - tuple of (float, float) - ``(A_component, E_component)`` for this time and component. - """ - dt_shift = time - tshift - gauss = math.exp(-0.5 * dt_shift ** 2 / (tdump * tdump)) - cos_val = math.cos(freq * dt_shift) - sin_val = math.sin(freq * dt_shift) - At = V * cos_val * gauss - Et = -V * ((-dt_shift) / (tdump * tdump) * cos_val - freq * sin_val) * gauss - return At, Et - - -def _aclaser_At_Et( - time: float, V: float, freq: float, tshift: float, tdump: float, -) -> tuple[float, float]: - """Compute A(t) and E(t) for sinusoidal AC laser. - - Parameters - ---------- - time : float - Current time. - V : float - Vector potential amplitude for one component. - freq : float - Laser frequency. - tshift : float - Time shift. - tdump : float - Unused (kept for uniform signature). - - Returns - ------- - tuple of (float, float) - ``(A_component, E_component)`` for this time and component. - """ - dt_shift = time - tshift - At = V * math.sin(freq * dt_shift) - Et = V * math.cos(freq * dt_shift) * freq - return At, Et - - -def _dclaser_At_Et( - time: float, V: float, freq: float, tshift: float, tdump: float, -) -> tuple[float, float]: - """Compute A(t) and E(t) for linearly ramped DC laser. - - Parameters - ---------- - time : float - Current time. - V : float - Vector potential amplitude for one component. - freq : float - Unused (kept for uniform signature). - tshift : float - Unused (kept for uniform signature). - tdump : float - Unused (kept for uniform signature). - - Returns - ------- - tuple of (float, float) - ``(A_component, E_component)`` for this time and component. - """ - return V * time, -V - - -class _PumpTypeConfig(NamedTuple): - """Configuration for a pump-type entry in the dispatch table. - - Attributes - ---------- - pump_body : int - Pump body type (1 = one-body laser, 2 = two-body quench). - handler_fn : Callable or None - Field computation function with signature - ``(time, V, freq, tshift, tdump) -> (At, Et)``, or ``None`` - for quench (no field computation). - """ - - pump_body: int - handler_fn: Callable | None - - -_PUMP_TYPE_HANDLERS: dict[str, _PumpTypeConfig] = { - "quench": _PumpTypeConfig(2, None), - "pulselaser": _PumpTypeConfig(1, _pulselaser_At_Et), - "aclaser": _PumpTypeConfig(1, _aclaser_At_Et), - "dclaser": _PumpTypeConfig(1, _dclaser_At_Et), -} -"""Maps each PumpType to a :class:`_PumpTypeConfig`. - -For ``PumpBody == 2`` (quench), *handler_fn* is ``None`` — no field is -computed. For ``PumpBody == 1``, *handler_fn* is called once per -(timestep, component) pair with signature -``(time, V, freq, tshift, tdump) -> (At, Et)``. -""" - - -def vector_potential(StdI: StdIntList) -> None: - """Compute vector potential A(t) and electric field E(t) for time evolution. - - Depending on ``PumpType``, the time-dependent vector potential and - electric field are computed as follows: - - - ``"quench"`` (default): no laser field; uses two-body pump - (``PumpBody = 2``). - - ``"pulselaser"``: Gaussian-enveloped cosine pulse - (``PumpBody = 1``). - - ``"aclaser"``: sinusoidal AC laser (``PumpBody = 1``). - - ``"dclaser"``: linearly ramped DC laser (``PumpBody = 1``). - - For ``PumpBody == 1``, the file ``potential.dat`` is written with - columns ``time, A_W, A_L, A_H, E_W, E_L, E_H``. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. Key fields read / set: - - - ``VecPot`` -- initial vector potential components (W, L, H). - - ``Lanczos_max`` -- number of time steps. - - ``dt`` -- time step width. - - ``freq`` -- laser frequency. - - ``tshift`` -- time shift for the pulse center. - - ``tdump`` -- Gaussian pulse width. - - ``Uquench`` -- quench interaction strength. - - ``ExpandCoef`` -- expansion coefficient order. - - ``PumpType`` -- string selecting the pump type. - - ``PumpBody`` -- set to 1 (one-body) or 2 (two-body). - - ``At`` -- allocated as ``Lanczos_max x 3`` list of floats. - """ - print("\n @ Time-evolution\n") - - StdI.VecPot[0] = print_val_d("VecPotW", StdI.VecPot[0], 0.0) - StdI.VecPot[1] = print_val_d("VecPotL", StdI.VecPot[1], 0.0) - StdI.VecPot[2] = print_val_d("VecPotH", StdI.VecPot[2], 0.0) - StdI.Lanczos_max = print_val_i("Lanczos_max", StdI.Lanczos_max, 1000) - StdI.dt = print_val_d("dt", StdI.dt, 0.01) - StdI.freq = print_val_d("freq", StdI.freq, 0.1) - StdI.tshift = print_val_d("tshift", StdI.tshift, 0.0) - StdI.tdump = print_val_d("tdump", StdI.tdump, 0.1) - StdI.Uquench = print_val_d("Uquench", StdI.Uquench, 0.0) - StdI.ExpandCoef = print_val_i("ExpandCoef", StdI.ExpandCoef, 10) - - # Allocate A(t) and E(t) arrays: Lanczos_max x 3 - StdI.At = np.zeros((StdI.Lanczos_max, 3)) - Et = np.zeros((StdI.Lanczos_max, 3)) - - # Resolve PumpType default - if StdI.PumpType == UNSET_STRING: - StdI.PumpType = "quench" - print(" PumpType = quench ###### DEFAULT VALUE IS USED ######") - else: - print(f" PumpType = {StdI.PumpType}") - - # Dispatch on PumpType - handler_entry = _PUMP_TYPE_HANDLERS.get(StdI.PumpType) - if handler_entry is None: - print(f"\n ERROR ! PumpType : {StdI.PumpType}") - exit_program(-1) - - StdI.PumpBody, handler_fn = handler_entry - - # Compute A(t) and E(t) for one-body pump types - if handler_fn is not None: - for it in range(StdI.Lanczos_max): - time = StdI.dt * float(it) - results = [handler_fn(time, StdI.VecPot[ii], StdI.freq, - StdI.tshift, StdI.tdump) for ii in range(3)] - StdI.At[it, :] = [r[0] for r in results] - Et[it, :] = [r[1] for r in results] - - # ------------------------------------------------------------------ - # Write potential.dat for one-body pump - # ------------------------------------------------------------------ - if StdI.PumpBody == 1: - with open("potential.dat", "w") as fp: - fp.write("# Time A_W A_L A_H E_W E_L E_H\n") - for it in range(StdI.Lanczos_max): - time = StdI.dt * float(it) - fp.write(f"{time:f} " - f"{StdI.At[it][0]:f} {StdI.At[it][1]:f} {StdI.At[it][2]:f} " - f"{Et[it][0]:f} {Et[it][1]:f} {Et[it][2]:f}\n") - - -def print_pump(StdI: StdIntList) -> None: - """Write ``teone.def`` or ``tetwo.def`` for time-evolution pump terms. - - - ``PumpBody == 1``: writes ``teone.def`` -- one-body pump terms. - Equivalent pump terms (same index quadruples) are merged and - entries below ``AMPLITUDE_EPS`` in magnitude are suppressed. - - ``PumpBody == 2``: writes ``tetwo.def`` -- two-body pump terms - using ``Uquench`` for on-site Hubbard quench interaction. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. Key fields read: - - - ``PumpBody`` -- 1 for one-body, 2 for two-body. - - ``Lanczos_max`` -- number of time steps. - - ``dt`` -- time step width. - - ``npump`` -- list of int, per-timestep pump term counts. - - ``pumpindx`` -- 3-D list of int, shape - ``(Lanczos_max, npump[it], 4)`` -- site/spin indices. - - ``pump`` -- 2-D list of complex, shape - ``(Lanczos_max, npump[it])`` -- pump amplitudes. - - ``nsite`` -- total number of sites. - - ``Uquench`` -- quench interaction strength. - """ - if StdI.PumpBody == 1: - with open("teone.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"AllTimeStep {StdI.Lanczos_max}\n") - fp.write("=============================================\n") - fp.write("========= OneBody Time Evolution ==========\n") - fp.write("=============================================\n") - - for it in range(StdI.Lanczos_max): - npump0 = _merge_duplicate_terms( - StdI.pumpindx[it], StdI.pump[it], StdI.npump[it]) - - fp.write(f"{StdI.dt * float(it):f} {npump0}\n") - - for ipump in range(StdI.npump[it]): - val = StdI.pump[it][ipump] - if abs(val) <= AMPLITUDE_EPS: - continue - i0, s0, i1, s1 = StdI.pumpindx[it][ipump] - fp.write( - f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " - f"{val.real:25.15f} {val.imag:25.15f}\n" - ) - - print(" teone.def is written.\n") - - else: - with open("tetwo.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"AllTimeStep {StdI.Lanczos_max}\n") - fp.write("=============================================\n") - fp.write("========== TwoBody Time Evolution ===========\n") - fp.write("=============================================\n") - - for it in range(StdI.Lanczos_max): - fp.write(f"{StdI.dt * float(it):f} {StdI.nsite}\n") - for isite in range(StdI.nsite): - fp.write(f"{isite:5d} {0:5d} {isite:5d} {0:5d} " - f"{isite:5d} {1:5d} {isite:5d} {1:5d} " - f"{StdI.Uquench:25.15f} {0.0:25.15f}\n") - - print(" tetwo.def is written.\n") diff --git a/python/writer/interaction_writer.py b/python/writer/interaction_writer.py deleted file mode 100644 index 79d668d..0000000 --- a/python/writer/interaction_writer.py +++ /dev/null @@ -1,534 +0,0 @@ -"""Interaction-term merge and file-output functions. - -This module contains the :func:`print_interactions` function that was -extracted from ``common_writer.py``. It processes all interaction types -(CoulombIntra, CoulombInter, Hund, Exchange, PairLift, PairHopp, InterAll), -merging duplicate terms, counting non-zero terms, and writing the -corresponding ``.def`` files. - -Functions ---------- -print_interactions - Merge duplicate interaction terms and write ``.def`` files. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -from typing import NamedTuple - -from stdface_vals import StdIntList, AMPLITUDE_EPS - - -# --------------------------------------------------------------------------- -# Helpers for the repeated merge → count → write pattern -# --------------------------------------------------------------------------- - -def _merge_1idx(nterms: int, indx: list, coeff: list) -> None: - """Merge duplicate terms for a 1-index interaction (CoulombIntra). - - Two terms are duplicates if they share the same single site index. - The coefficient of the first occurrence is updated and the duplicate - is zeroed. Uses dict-based O(n) lookup instead of O(n²) pairwise scan. - - Parameters - ---------- - nterms : int - Number of interaction terms. - indx : list - Index array; each element is a list whose first element is the - site index. - coeff : list - Coefficient array, modified in place. - """ - seen: dict[int, int] = {} # site index -> first occurrence position - for k in range(nterms): - key = indx[k][0] - if key in seen: - coeff[seen[key]] += coeff[k] - coeff[k] = 0.0 - else: - seen[key] = k - - -def _merge_2idx(nterms: int, indx: list, coeff: list) -> None: - """Merge duplicate terms for a 2-index interaction. - - Two terms are duplicates if they share the same pair of site indices - (in either order, i.e. symmetric). Uses dict-based O(n) lookup - instead of O(n²) pairwise scan. - - Parameters - ---------- - nterms : int - Number of interaction terms. - indx : list - Index array; each element is a list of two site indices. - coeff : list - Coefficient array, modified in place. - """ - seen: dict[tuple[int, int], int] = {} # canonical pair -> first occurrence - for k in range(nterms): - i0, i1 = indx[k][0], indx[k][1] - key = (min(i0, i1), max(i0, i1)) - if key in seen: - coeff[seen[key]] += coeff[k] - coeff[k] = 0.0 - else: - seen[key] = k - - -def _count_nonzero(nterms: int, coeff: list) -> int: - """Count the number of terms with ``|coeff| > AMPLITUDE_EPS``. - - Parameters - ---------- - nterms : int - Total number of terms. - coeff : list - Coefficient array. - - Returns - ------- - int - Number of non-zero terms. - """ - return sum(1 for k in range(nterms) if abs(coeff[k]) > AMPLITUDE_EPS) - - -def _write_interaction_file( - filename: str, - count_label: str, - banner: str, - nterms: int, - indx: list, - coeff: list, - n_indices: int, -) -> None: - """Write an interaction ``.def`` file with 1 or 2 site indices per term. - - Parameters - ---------- - filename : str - Output file name (e.g. ``"coulombintra.def"``). - count_label : str - Label for the count header line (e.g. ``"NCoulombIntra"``). - banner : str - Description banner line. - nterms : int - Total number of terms. - indx : list - Index array (``n_indices`` site indices per term). - coeff : list - Coefficient array. - n_indices : int - Number of site indices per term (1 or 2). - """ - nintr0 = _count_nonzero(nterms, coeff) - with open(filename, "w") as fp: - fp.write("=============================================\n") - fp.write(f"{count_label} {nintr0:10d}\n") - fp.write("=============================================\n") - fp.write(f"{banner}\n") - fp.write("=============================================\n") - for k in range(nterms): - if abs(coeff[k]) > AMPLITUDE_EPS: - idx_str = " ".join(f"{indx[k][i]:5d}" for i in range(n_indices)) - fp.write(f"{idx_str} {coeff[k]:25.15f}\n") - print(f" {filename} is written.") - - -def _process_interaction( - StdI: StdIntList, - nterms_attr: str, - indx_attr: str, - coeff_attr: str, - flag_attr: str, - filename: str, - count_label: str, - banner: str, - n_indices: int, -) -> None: - """Merge, count, and write one interaction type. - - This is the generic driver that replaces the 6 repetitive blocks in - ``print_interactions()``. - - Parameters - ---------- - StdI : StdIntList - The central data structure. - nterms_attr : str - Name of the attribute holding the term count (e.g. ``"NCintra"``). - indx_attr : str - Name of the index-array attribute (e.g. ``"CintraIndx"``). - coeff_attr : str - Name of the coefficient-array attribute (e.g. ``"Cintra"``). - flag_attr : str - Name of the output-flag attribute (e.g. ``"LCintra"``). - filename : str - Output ``.def`` file name. - count_label : str - Label for the count line in the header. - banner : str - Description banner in the header. - n_indices : int - Number of site indices per term (1 or 2). - """ - nterms = getattr(StdI, nterms_attr) - indx = getattr(StdI, indx_attr) - coeff = getattr(StdI, coeff_attr) - - # Merge duplicates - if n_indices == 1: - _merge_1idx(nterms, indx, coeff) - else: - _merge_2idx(nterms, indx, coeff) - - # Count non-zero terms - nintr0 = _count_nonzero(nterms, coeff) - - # Set output flag - if nintr0 == 0 or StdI.lBoost == 1: - setattr(StdI, flag_attr, 0) - else: - setattr(StdI, flag_attr, 1) - - # Write file if needed - if getattr(StdI, flag_attr) == 1: - _write_interaction_file( - filename, count_label, banner, nterms, indx, coeff, n_indices) - - -# --------------------------------------------------------------------------- -# Interaction-type metadata table -# --------------------------------------------------------------------------- - - -class _InteractionMeta(NamedTuple): - """Metadata for one interaction type (attribute names, file info). - - Attributes - ---------- - nterms_attr : str - Name of the StdIntList attribute holding the term count. - indx_attr : str - Name of the index-array attribute. - coeff_attr : str - Name of the coefficient-array attribute. - flag_attr : str - Name of the output-flag attribute. - filename : str - Output ``.def`` file name. - count_label : str - Label for the count header line. - banner : str - Description banner in the header. - n_indices : int - Number of site indices per term (1 or 2). - """ - - nterms_attr: str - indx_attr: str - coeff_attr: str - flag_attr: str - filename: str - count_label: str - banner: str - n_indices: int - - -_INTERACTION_TYPES: list[_InteractionMeta] = [ - _InteractionMeta("NCintra", "CintraIndx", "Cintra", "LCintra", - "coulombintra.def", "NCoulombIntra", - "================== CoulombIntra ================", 1), - _InteractionMeta("NCinter", "CinterIndx", "Cinter", "LCinter", - "coulombinter.def", "NCoulombInter", - "================== CoulombInter ================", 2), - _InteractionMeta("NHund", "HundIndx", "Hund", "LHund", - "hund.def", "NHund", - "=============== Hund coupling ===============", 2), - _InteractionMeta("NEx", "ExIndx", "Ex", "LEx", - "exchange.def", "NExchange", - "====== ExchangeCoupling coupling ============", 2), - _InteractionMeta("NPairLift", "PLIndx", "PairLift", "LPairLift", - "pairlift.def", "NPairLift", - "====== Pair-Lift term ============", 2), - _InteractionMeta("NPairHopp", "PHIndx", "PairHopp", "LPairHopp", - "pairhopp.def", "NPairHopp", - "====== Pair-Hopping term ============", 2), -] -"""Metadata for the 6 standard interaction types processed by -:func:`print_interactions`. Each entry maps attribute names, file -names, and header strings so that :func:`_process_interaction` can -handle all types generically. -""" - - -# --------------------------------------------------------------------------- -# InterAll helpers -# --------------------------------------------------------------------------- - -def _merge_interall_equivalent( - nintr: int, intrindx: list, intr: list -) -> None: - """Merge equivalent InterAll terms (Pass 1). - - For each pair of terms, check three patterns: - - - **Case A**: All 8 indices match exactly → **add** coefficients. - - **Case B**: Hermitian conjugate match (indices 0–3 ↔ 4–7) with - two exclusion conditions → **add** coefficients. - - **Case C**: Partial swap patterns → **subtract** coefficients. - - In all cases the duplicate term is zeroed. - - Parameters - ---------- - nintr : int - Number of InterAll terms. - intrindx : list - Index array; each element is a list of 8 integers. - intr : list - Coefficient array (complex), modified in place. - """ - for jintr in range(nintr): - j = intrindx[jintr] - for kintr in range(jintr + 1, nintr): - k = intrindx[kintr] - - # Case A: all 8 indices match exactly - # Case B: Hermitian conjugate match (indices 0-3 <-> 4-7) - if ( - (j[0] == k[0] and j[1] == k[1] - and j[2] == k[2] and j[3] == k[3] - and j[4] == k[4] and j[5] == k[5] - and j[6] == k[6] and j[7] == k[7]) - or - (j[0] == k[4] and j[1] == k[5] - and j[2] == k[6] and j[3] == k[7] - and j[4] == k[0] and j[5] == k[1] - and j[6] == k[2] and j[7] == k[3] - and not (j[0] == j[6] and j[1] == j[7]) - and not (j[2] == j[4] and j[3] == j[5])) - ): - intr[jintr] = intr[jintr] + intr[kintr] - intr[kintr] = 0.0 - - # Case C: Partial swap patterns -> SUBTRACT - elif ( - (j[0] == k[4] and j[1] == k[5] - and j[2] == k[2] and j[3] == k[3] - and j[4] == k[0] and j[5] == k[1] - and j[6] == k[6] and j[7] == k[7] - and not (j[2] == j[0] and j[3] == j[1]) - and not (j[2] == j[4] and j[3] == j[5])) - or - (j[0] == k[0] and j[1] == k[1] - and j[2] == k[6] and j[3] == k[7] - and j[4] == k[4] and j[5] == k[5] - and j[6] == k[2] and j[7] == k[3] - and not (j[4] == j[2] and j[5] == j[3]) - and not (j[4] == j[6] and j[5] == j[7])) - ): - intr[jintr] = intr[jintr] - intr[kintr] - intr[kintr] = 0.0 - - -def _reorder_interall_hermitian( - nintr: int, intrindx: list, intr: list -) -> None: - """Force Hermitian ordering on InterAll terms (Pass 2). - - For the two-body operator ``(c1† c2 c3† c4)† = c4† c3 c2† c1``, - certain index-pair patterns require reordering the 8 indices of - *kintr* to match the Hermitian ordering of *jintr*. - - Two reorder patterns are detected: - - - **Pattern 1**: Direct Hermitian conjugate → reorder indices. - - **Pattern 2**: Two sub-cases with sign flip → reorder indices - and negate coefficient. - - Parameters - ---------- - nintr : int - Number of InterAll terms. - intrindx : list - Index array (8 integers per term), modified in place. - intr : list - Coefficient array (complex), modified in place. - """ - for jintr in range(nintr): - j = intrindx[jintr] - for kintr in range(jintr + 1, nintr): - k = intrindx[kintr] - - # Pattern 1: direct Hermitian conjugate match - if (j[6] == k[4] and j[7] == k[5] - and j[4] == k[6] and j[5] == k[7] - and j[2] == k[0] and j[3] == k[1] - and j[0] == k[2] and j[1] == k[3] - and not (k[0] == k[6] and k[1] == k[7]) - and not (k[2] == k[4] and k[3] == k[5])): - intrindx[kintr][0] = j[6] - intrindx[kintr][1] = j[7] - intrindx[kintr][2] = j[4] - intrindx[kintr][3] = j[5] - intrindx[kintr][4] = j[2] - intrindx[kintr][5] = j[3] - intrindx[kintr][6] = j[0] - intrindx[kintr][7] = j[1] - - # Pattern 2: two sub-cases, with sign flip - elif ( - (j[6] == k[4] and j[7] == k[5] - and j[4] == k[2] and j[5] == k[3] - and j[2] == k[0] and j[3] == k[1] - and j[0] == k[6] and j[1] == k[7] - and not (k[2] == k[0] and k[3] == k[1]) - and not (k[2] == k[4] and k[3] == k[5])) - or - (j[6] == k[0] and j[7] == k[1] - and j[4] == k[6] and j[5] == k[7] - and j[2] == k[4] and j[3] == k[5] - and j[0] == k[2] and j[1] == k[3] - and not (k[4] == k[2] and k[5] == k[3]) - and not (k[4] == k[6] and k[5] == k[7])) - ): - intrindx[kintr][0] = j[6] - intrindx[kintr][1] = j[7] - intrindx[kintr][2] = j[4] - intrindx[kintr][3] = j[5] - intrindx[kintr][4] = j[2] - intrindx[kintr][5] = j[3] - intrindx[kintr][6] = j[0] - intrindx[kintr][7] = j[1] - - intr[kintr] = -intr[kintr] - - -def _remove_interall_diagonal( - nintr: int, intrindx: list, intr: list -) -> None: - """Remove spurious diagonal InterAll terms (Pass 3). - - A term is zeroed if it has a diagonal pair — i.e. - ``(site0, spin0) == (site4, spin4)`` or - ``(site2, spin2) == (site6, spin6)`` — **unless** any of the four - possible diagonal-matching conditions holds. - - Parameters - ---------- - nintr : int - Number of InterAll terms. - intrindx : list - Index array (8 integers per term). - intr : list - Coefficient array, modified in place. - """ - for jintr in range(nintr): - idx = intrindx[jintr] - - has_diagonal_pair = ( - (idx[0] == idx[4] and idx[1] == idx[5]) - or (idx[2] == idx[6] and idx[3] == idx[7]) - ) - - if has_diagonal_pair: - any_matching_diagonal = ( - (idx[0] == idx[2] and idx[1] == idx[3]) - or (idx[0] == idx[6] and idx[1] == idx[7]) - or (idx[4] == idx[2] and idx[5] == idx[3]) - or (idx[4] == idx[6] and idx[5] == idx[7]) - ) - if not any_matching_diagonal: - intr[jintr] = 0.0 - - -def _write_interall(StdI: StdIntList) -> None: - """Count non-zero InterAll terms, set the flag, and write the file. - - Parameters - ---------- - StdI : StdIntList - The central data structure. ``nintr``, ``intrindx``, ``intr``, - ``lBoost``, and ``Lintr`` are accessed / modified. - """ - nintr0 = _count_nonzero(StdI.nintr, StdI.intr) - - if nintr0 == 0 or StdI.lBoost == 1: - StdI.Lintr = 0 - else: - StdI.Lintr = 1 - - if StdI.Lintr == 1: - with open("interall.def", "w") as fp: - fp.write("====================== \n") - fp.write(f"NInterAll {nintr0:7d} \n") - fp.write("====================== \n") - fp.write("========zInterAll===== \n") - fp.write("====================== \n") - - if StdI.lBoost == 0: - for kintr in range(StdI.nintr): - val = StdI.intr[kintr] - if abs(val) > AMPLITUDE_EPS: - i0, s0, i1, s1, i2, s2, i3, s3 = StdI.intrindx[kintr] - fp.write( - f"{i0:5d} {s0:5d} " - f"{i1:5d} {s1:5d} " - f"{i2:5d} {s2:5d} " - f"{i3:5d} {s3:5d} " - f"{val.real:25.15f} {val.imag:25.15f}\n" - ) - - print(" interall.def is written.") - - -def print_interactions(StdI: StdIntList) -> None: - """Process and write definition files for all interaction types. - - For each interaction type (CoulombIntra, CoulombInter, Hund, Exchange, - PairLift, PairHopp, InterAll), this function: - - 1. Merges duplicate terms by summing their coefficients and zeroing - out the duplicate entry. - 2. Counts the number of non-zero terms. - 3. Sets a flag (e.g. ``LCintra``, ``LCinter``, ...) based on the count - and whether boost mode is active. - 4. If the flag is set, writes the corresponding ``.def`` file. - - The first six interaction types are handled generically via - :data:`_INTERACTION_TYPES` and :func:`_process_interaction`. - The InterAll section is more complex, involving three merge/reorder - passes before file output, and is handled inline. - - Parameters - ---------- - StdI : StdIntList - The central data structure holding all interaction arrays, index - arrays, counts, and flags. Modified in place. - """ - # ================================================================= - # Standard interaction types (data-driven) - # ================================================================= - for spec in _INTERACTION_TYPES: - _process_interaction(StdI, **spec._asdict()) - - # ================================================================= - # InterAll - # ================================================================= - _merge_interall_equivalent(StdI.nintr, StdI.intrindx, StdI.intr) - _reorder_interall_hermitian(StdI.nintr, StdI.intrindx, StdI.intr) - _remove_interall_diagonal(StdI.nintr, StdI.intrindx, StdI.intr) - _write_interall(StdI) diff --git a/python/writer/mvmc_variational.py b/python/writer/mvmc_variational.py deleted file mode 100644 index 1882233..0000000 --- a/python/writer/mvmc_variational.py +++ /dev/null @@ -1,485 +0,0 @@ -"""mVMC variational parameter generation functions. - -This module contains functions for generating variational wave-function -parameter files used by the mVMC solver: - -- Quantum number projection (``qptransidx.def``) -- Orbital indices (``Orb`` / ``AntiOrb`` arrays) -- Jastrow factor indices (``jastrowidx.def``) - -These functions were extracted from ``stdface_model_util.py`` to improve -module cohesion. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -import numpy as np - -from stdface_vals import StdIntList, ModelType, NaN_i -from param_check import exit_program -from lattice.site_util import ( - _cell_vector, _fold_to_cell, _fold_site, _find_cell_index, - _validate_box_params, _det_and_cofactor, find_site, -) - - -def _anti_period_dot(AntiPeriod: np.ndarray, nBox: list[int]) -> int: - """Compute the dot product of anti-period flags and box indices. - - Parameters - ---------- - AntiPeriod : np.ndarray - Length-3 array of anti-periodic boundary flags (0 or 1). - nBox : list of int - Length-3 super-cell image index. - - Returns - ------- - int - Sum ``AntiPeriod[0]*nBox[0] + AntiPeriod[1]*nBox[1] + - AntiPeriod[2]*nBox[2]``. - """ - return (int(AntiPeriod[0]) * nBox[0] - + int(AntiPeriod[1]) * nBox[1] - + int(AntiPeriod[2]) * nBox[2]) - - -def _parity_sign(value: int) -> int: - """Convert an integer to +1 (even) or -1 (odd). - - Used to translate an anti-periodic boundary count into a phase sign. - - Parameters - ---------- - value : int - Integer whose parity determines the sign. - - Returns - ------- - int - ``+1`` if *value* is even, ``-1`` if odd. - """ - return 1 if value % 2 == 0 else -1 - - -def _check_commensurate(rbox_sub: np.ndarray, box: np.ndarray, ncell_sub: int) -> bool: - """Check whether a sublattice is commensurate with the main lattice. - - The sublattice is commensurate if for all i, j in {0,1,2}: - ``(rbox_sub[i, :] · box[j, :]) % ncell_sub == 0`` - - Parameters - ---------- - rbox_sub : np.ndarray - Reciprocal box matrix of the sublattice (3x3). - box : np.ndarray - Box matrix of the main lattice (3x3). - ncell_sub : int - Determinant of the sublattice box (number of cells). - - Returns - ------- - bool - ``True`` if the sublattice is commensurate, ``False`` otherwise. - """ - # Compute all dot products: prod[i,j] = rbox_sub[i,:] · box[j,:] - prod = rbox_sub.astype(int) @ box.astype(int).T - return bool(np.all(prod % ncell_sub == 0)) - - -def _fold_site_sub( - StdI: StdIntList, - iCellV: list[int], -) -> tuple[list[int], list[int]]: - """Fold site into the sub-lattice cell (mVMC only). - - Delegates to :func:`~lattice.site_util._fold_to_cell` with the - sub-lattice parameters (``rboxsub``, ``NCellsub``, ``boxsub``). - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. - iCellV : list of int - Fractional coordinate of a site (length 3). - - Returns - ------- - nBox : list of int - Super-cell index. - iCellV_fold : list of int - Folded fractional coordinate. - """ - return _fold_to_cell(StdI.rboxsub, StdI.NCellsub, StdI.boxsub, iCellV) - - -def proj(StdI: StdIntList) -> None: - """Print quantum number projection file ``qptransidx.def`` (mVMC only). - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - """ - Sym = np.zeros((StdI.nsite, StdI.nsite), dtype=int) - Anti = np.zeros((StdI.nsite, StdI.nsite), dtype=int) - - StdI.NSym = 0 - for iCell in range(StdI.NCell): - iCV = _cell_vector(StdI.Cell, iCell) - nBox, iCellV = _fold_site_sub(StdI, iCV) - nBox, iCellV = _fold_site(StdI, iCellV) - - if iCellV == iCV: - for jCell in range(StdI.NCell): - jCellV = [c + v for c, v in zip( - _cell_vector(StdI.Cell, jCell), iCellV)] - nBox, jCellV = _fold_site(StdI, jCellV) - - kCell = _find_cell_index(StdI, jCellV) - ap_dot = _anti_period_dot(StdI.AntiPeriod, nBox) - for jsite in range(StdI.NsiteUC): - Sym[StdI.NSym][jCell * StdI.NsiteUC + jsite] = ( - kCell * StdI.NsiteUC + jsite) - Anti[StdI.NSym][jCell * StdI.NsiteUC + jsite] = ap_dot - - if StdI.model == ModelType.KONDO: - half = StdI.nsite // 2 - Sym[StdI.NSym][half + jCell * StdI.NsiteUC + jsite] = ( - half + kCell * StdI.NsiteUC + jsite) - Anti[StdI.NSym][half + jCell * StdI.NsiteUC + jsite] = ap_dot - StdI.NSym += 1 - - with open("qptransidx.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NQPTrans {StdI.NSym:10d}\n") - fp.write("=============================================\n") - fp.write("======== TrIdx_TrWeight_and_TrIdx_i_xi ======\n") - fp.write("=============================================\n") - for iSym in range(StdI.NSym): - fp.write(f"{iSym} {1.0:10.5f}\n") - for iSym in range(StdI.NSym): - for jsite in range(StdI.nsite): - a = _parity_sign(Anti[iSym][jsite]) - fp.write(f"{iSym:5d} {jsite:5d} {Sym[iSym][jsite]:5d} {a:5d}\n") - print(" qptransidx.def is written.") - - -def _init_site_sub(StdI: StdIntList) -> None: - """Initialize sub-cell for mVMC/UHF/HWAVE. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - """ - StdI.Lsub, StdI.Wsub, StdI.Hsub = _validate_box_params( - StdI.Lsub, StdI.Wsub, StdI.Hsub, StdI.boxsub, - suffix="sub", defaults=StdI.box) - - # Calculate reciprocal lattice vectors - StdI.NCellsub, StdI.rboxsub = _det_and_cofactor(StdI.boxsub) - print(f" Number of Cell in the sublattice: {abs(StdI.NCellsub)}") - if StdI.NCellsub == 0: - exit_program(-1) - - # Check commensurate - if not _check_commensurate(StdI.rboxsub, StdI.box, StdI.NCellsub): - print("\n ERROR ! Sublattice is INCOMMENSURATE !\n") - exit_program(-1) - - -def _assign_orb_sector( - StdI: StdIntList, - iOrb: int, - anti_val: int, - iCell: int, jCell: int, - iCell2: int, jCell2: int, - isite: int, jsite: int, - i_off: int, j_off: int, - is_new: bool, -) -> int: - """Assign orbital and anti-orbital indices for one sector of a cell pair. - - For each ``(i_off, j_off)`` sector (itinerant/local-spin combination - in the Kondo model, or ``(0, 0)`` for the base sector), this function: - - 1. If *is_new*, assigns a fresh ``iOrb`` to the reference cell pair - ``(iCell2, jCell2)`` and records the anti-periodic sign. - 2. Always copies the reference orbital index to the current cell pair - ``(iCell, jCell)`` and records the anti-periodic sign. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - iOrb : int - Next available orbital index. - anti_val : int - Anti-periodic sign (+1 or -1). - iCell, jCell : int - Current cell indices. - iCell2, jCell2 : int - Reference (reduced) cell indices. - isite, jsite : int - Intra-unit-cell site indices. - i_off, j_off : int - Site-index offsets for this sector (0 for itinerant sites, - ``nsite // 2`` for local-spin sites in the Kondo model). - is_new : bool - Whether this ``(iCell2, jCell2)`` pair is being visited for the - first time. - - Returns - ------- - int - Updated ``iOrb`` (incremented by 1 if *is_new*). - """ - nu = StdI.NsiteUC - row2 = i_off + iCell2 * nu + isite - col2 = j_off + jCell2 * nu + jsite - - if is_new: - StdI.Orb[row2, col2] = iOrb - StdI.AntiOrb[row2, col2] = anti_val - iOrb += 1 - - row = i_off + iCell * nu + isite - col = j_off + jCell * nu + jsite - StdI.Orb[row, col] = StdI.Orb[row2, col2] - StdI.AntiOrb[row, col] = anti_val - - return iOrb - - -def generate_orb(StdI: StdIntList) -> None: - """Generate orbital index for mVMC. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (modified in-place). - """ - _init_site_sub(StdI) - - StdI.Orb = np.zeros((StdI.nsite, StdI.nsite), dtype=int) - StdI.AntiOrb = np.zeros((StdI.nsite, StdI.nsite), dtype=int) - CellDone = np.zeros((StdI.NCell, StdI.NCell), dtype=int) - - iOrb = 0 - for iCell in range(StdI.NCell): - iCV = _cell_vector(StdI.Cell, iCell) - nBox, iCellV = _fold_site_sub(StdI, iCV) - nBox, iCellV = _fold_site(StdI, iCellV) - - iCell2 = _find_cell_index(StdI, iCellV) - - for jCell in range(StdI.NCell): - jCV = _cell_vector(StdI.Cell, jCell) - jCellV = [jc + v - ic for jc, v, ic in zip(jCV, iCellV, iCV)] - nBox, jCellV = _fold_site(StdI, jCellV) - - jCell2 = _find_cell_index(StdI, jCellV) - - # AntiPeriodic factor - dCellV = [jc - ic for jc, ic in zip(jCV, iCV)] - nBox_d, _ = _fold_site(StdI, dCellV) - anti_val = _parity_sign( - _anti_period_dot(StdI.AntiPeriod, nBox_d)) - - # Build list of (i_offset, j_offset) sector pairs - sectors = [(0, 0)] - if StdI.model == ModelType.KONDO: - half = StdI.nsite // 2 - sectors += [(half, 0), (0, half), (half, half)] - - for isite in range(StdI.NsiteUC): - for jsite in range(StdI.NsiteUC): - for i_off, j_off in sectors: - iOrb = _assign_orb_sector( - StdI, iOrb, anti_val, - iCell, jCell, iCell2, jCell2, - isite, jsite, i_off, j_off, - is_new=(CellDone[iCell2, jCell2] == 0)) - - CellDone[iCell2, jCell2] = 1 - - StdI.NOrb = iOrb - - -def _jastrow_momentum_projected( - StdI: StdIntList, - Jastrow: np.ndarray, -) -> tuple[int, np.ndarray]: - """Compute Jastrow indices using momentum-projected (symmetrised) orbital. - - Steps: - - 1. Copy the orbital index matrix into *Jastrow*. - 2. Symmetrise: for each orbital index, set ``J[j,i] = J[i,j]``. - 3. Exclude local-spin sites (set their rows/columns to -1). - 4. Renumber: walk the strict lower triangle, assign negative - temporaries, then invert so indices become non-negative. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (read-only). - Jastrow : numpy.ndarray - ``(nsite, nsite)`` integer array, modified **in place** during - steps 1-3. Replaced by ``-1 - Jastrow`` in step 4 (the - returned array may be a new object). - - Returns - ------- - NJastrow : int - Number of unique Jastrow indices. - Jastrow : numpy.ndarray - Final renumbered Jastrow index matrix. - """ - # (1) Copy Orbital index - for isite in range(StdI.nsite): - for jsite in range(StdI.nsite): - Jastrow[isite, jsite] = StdI.Orb[isite, jsite] - - # (2) Symmetrize - for iorb in range(StdI.NOrb): - for isite in range(StdI.nsite): - for jsite in range(StdI.nsite): - if Jastrow[isite, jsite] == iorb: - Jastrow[jsite, isite] = Jastrow[isite, jsite] - - # (3) Exclude local-spin sites and renumber - NJastrow = 0 if StdI.model == ModelType.HUBBARD else -1 - for isite in range(StdI.nsite): - if StdI.locspinflag[isite] != 0: - Jastrow[isite, :] = -1 - Jastrow[:, isite] = -1 - continue - - for jsite in range(isite): - if Jastrow[isite, jsite] >= 0: - iJastrow = Jastrow[isite, jsite] - NJastrow -= 1 - mask = (Jastrow == iJastrow) - Jastrow[mask] = NJastrow - - NJastrow = -NJastrow - Jastrow = -1 - Jastrow - return NJastrow, Jastrow - - -def _jastrow_global_optimization( - StdI: StdIntList, - Jastrow: np.ndarray, -) -> int: - """Compute Jastrow indices using global (cell-based) optimisation. - - For the Spin model, all pairs share a single Jastrow index. - For Hubbard/Kondo, unique indices are assigned per cell-displacement - pair, respecting reversal symmetry and excluding the on-site term. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure (read-only except for lattice lookups). - Jastrow : numpy.ndarray - ``(nsite, nsite)`` integer array, modified **in place**. - - Returns - ------- - int - Number of unique Jastrow indices (``NJastrow``). - """ - if StdI.model == ModelType.SPIN: - Jastrow[:, :] = 0 - return 1 - - NJastrow = 0 - if StdI.model == ModelType.KONDO: - half = StdI.nsite // 2 - Jastrow[:, :half] = 0 - Jastrow[:half, :] = 0 - NJastrow += 1 - - for dCell in range(StdI.NCell): - dCV = _cell_vector(StdI.Cell, dCell) - isite, jsite, Cphase, dR_arr = find_site( - StdI, 0, 0, 0, -dCV[0], -dCV[1], -dCV[2], 0, 0) - if StdI.model == ModelType.KONDO: - jsite -= StdI.NCell * StdI.NsiteUC - iCell_j = jsite // StdI.NsiteUC - if iCell_j < dCell: - continue - reversal = 1 if iCell_j == dCell else 0 - - for isiteUC in range(StdI.NsiteUC): - for jsiteUC in range(StdI.NsiteUC): - if reversal == 1 and jsiteUC > isiteUC: - continue - if isiteUC == jsiteUC and dCV == [0, 0, 0]: - continue - - for iCell_idx in range(StdI.NCell): - iCV = _cell_vector(StdI.Cell, iCell_idx) - i_s, j_s, _, _ = find_site( - StdI, - iCV[0], iCV[1], iCV[2], - dCV[0], dCV[1], dCV[2], - isiteUC, jsiteUC) - Jastrow[i_s, j_s] = NJastrow - Jastrow[j_s, i_s] = NJastrow - - NJastrow += 1 - - return NJastrow - - -def print_jastrow(StdI: StdIntList) -> None: - """Output Jastrow factor index file ``jastrowidx.def`` (mVMC only). - - Delegates computation to :func:`_jastrow_momentum_projected` or - :func:`_jastrow_global_optimization` based on ``NMPTrans``, then - writes the results to ``jastrowidx.def``. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. - """ - Jastrow = np.zeros((StdI.nsite, StdI.nsite), dtype=int) - - if abs(StdI.NMPTrans) == 1 or StdI.NMPTrans == NaN_i: - NJastrow, Jastrow = _jastrow_momentum_projected(StdI, Jastrow) - else: - NJastrow = _jastrow_global_optimization(StdI, Jastrow) - - with open("jastrowidx.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NJastrowIdx {NJastrow:10d}\n") - fp.write(f"ComplexType {0:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") - - for isite in range(StdI.nsite): - for jsite in range(StdI.nsite): - if isite == jsite: - continue - fp.write(f"{isite:5d} {jsite:5d} {Jastrow[isite, jsite]:5d}\n") - - for iJastrow in range(NJastrow): - if StdI.model == ModelType.HUBBARD or iJastrow > 0: - fp.write(f"{iJastrow:5d} {1:5d}\n") - else: - fp.write(f"{iJastrow:5d} {0:5d}\n") - print(" jastrowidx.def is written.") diff --git a/python/writer/mvmc_writer.py b/python/writer/mvmc_writer.py deleted file mode 100644 index a1f4a80..0000000 --- a/python/writer/mvmc_writer.py +++ /dev/null @@ -1,466 +0,0 @@ -"""mVMC solver-specific output functions. - -This module contains the mVMC (many-variable Variational Monte Carlo) -solver-specific output functions extracted from ``stdface_main.py``. -These functions write the orbital and Gutzwiller variational-parameter -definition files required by mVMC. - -Functions ---------- -print_orb - Write the anti-parallel orbital index file ``orbitalidx.def``. -print_orb_para - Write parallel orbital index files ``orbitalidxpara.def`` and - ``orbitalidxgen.def``. -print_gutzwiller - Write the Gutzwiller variational-parameter file ``gutzwilleridx.def``. - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - -from __future__ import annotations - -from stdface_vals import StdIntList, ModelType, NaN_i - - -def _has_anti_period(StdI: StdIntList) -> bool: - """Return whether any lattice direction has anti-periodic boundary. - - Parameters - ---------- - StdI : StdIntList - Model parameter structure. - - Returns - ------- - bool - ``True`` if any of the three ``AntiPeriod`` flags equals 1. - """ - return any(ap == 1 for ap in StdI.AntiPeriod) - - -def print_orb(StdI: StdIntList) -> None: - """Write the anti-parallel orbital index file ``orbitalidx.def``. - - The file records the orbital pairing indices used by mVMC for the - anti-parallel-spin part of the variational wave function. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. The following fields are read: - - - ``nsite`` -- total number of sites - - ``NOrb`` -- number of orbital indices - - ``ComplexType`` -- 0 for real, 1 for complex - - ``Orb`` -- ``nsite x nsite`` orbital index matrix - - ``AntiOrb`` -- ``nsite x nsite`` anti-periodic sign matrix - - ``AntiPeriod`` -- length-3 array of anti-periodic boundary flags - - Notes - ----- - Translated from the C function ``PrintOrb()`` in ``StdFace_main.c``. - """ - with open("orbitalidx.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NOrbitalIdx {StdI.NOrb:10d}\n") - fp.write(f"ComplexType {StdI.ComplexType:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") - - has_anti = _has_anti_period(StdI) - - for isite in range(StdI.nsite): - for jsite in range(StdI.nsite): - if has_anti: - fp.write(f"{isite:5d} {jsite:5d} " - f"{StdI.Orb[isite][jsite]:5d} " - f"{StdI.AntiOrb[isite][jsite]:5d}\n") - else: - fp.write(f"{isite:5d} {jsite:5d} " - f"{StdI.Orb[isite][jsite]:5d}\n") - - for iOrb in range(StdI.NOrb): - fp.write(f"{iOrb:5d} {1:5d}\n") - - print(" orbitalidx.def is written.") - - -def _compute_parallel_orbitals( - nsite: int, - NOrb: int, - Orb, - AntiOrb, -) -> tuple[list[list[int]], list[list[int]], int]: - """Compute parallel orbital indices from anti-parallel orbital data. - - Performs three steps: - - 1. **Copy**: copy the anti-parallel orbital matrix (``Orb``) into a - local ``OrbGC`` matrix and the sign matrix (``AntiOrb``) into - ``reverse``. - 2. **Symmetrise**: for each known orbital index, set - ``OrbGC[j][i] = OrbGC[i][j]`` and ``reverse[j][i] = -reverse[i][j]``. - 3. **Renumber**: walk the strict lower triangle (``isite > jsite``), - assign negative temporaries to each newly seen orbital, then invert - all indices so they become non-negative. - - Parameters - ---------- - nsite : int - Total number of sites. - NOrb : int - Number of anti-parallel orbital indices. - Orb : array-like - ``(nsite, nsite)`` orbital index matrix (read-only). - AntiOrb : array-like - ``(nsite, nsite)`` anti-periodic sign matrix (read-only). - - Returns - ------- - OrbGC : list of list of int - ``(nsite, nsite)`` renumbered parallel orbital indices. - reverse : list of list of int - ``(nsite, nsite)`` sign-reversal matrix. - NOrbGC : int - Number of unique parallel orbital indices. - """ - # (1) Copy - OrbGC = [[0] * nsite for _ in range(nsite)] - reverse = [[0] * nsite for _ in range(nsite)] - for isite in range(nsite): - for jsite in range(nsite): - OrbGC[isite][jsite] = int(Orb[isite][jsite]) - reverse[isite][jsite] = int(AntiOrb[isite][jsite]) - - # (2) Symmetrise - for iorb in range(NOrb): - for isite in range(nsite): - for jsite in range(nsite): - if OrbGC[isite][jsite] == iorb: - OrbGC[jsite][isite] = OrbGC[isite][jsite] - reverse[jsite][isite] = -reverse[isite][jsite] - - # (3) Renumber -- lower triangle (isite > jsite) - NOrbGC = 0 - for isite in range(nsite): - for jsite in range(isite): - if OrbGC[isite][jsite] >= 0: - iOrbGC = OrbGC[isite][jsite] - NOrbGC -= 1 - for isite1 in range(nsite): - for jsite1 in range(nsite): - if OrbGC[isite1][jsite1] == iOrbGC: - OrbGC[isite1][jsite1] = NOrbGC - - NOrbGC = -NOrbGC - for isite in range(nsite): - for jsite in range(nsite): - OrbGC[isite][jsite] = -1 - OrbGC[isite][jsite] - - return OrbGC, reverse, NOrbGC - - -def _write_orbitalidxpara( - nsite: int, - ComplexType: int, - OrbGC: list, - reverse: list, - NOrbGC: int, -) -> None: - """Write ``orbitalidxpara.def`` from pre-computed parallel orbital data. - - Parameters - ---------- - nsite : int - Total number of sites. - ComplexType : int - 0 for real, 1 for complex variational parameters. - OrbGC : array-like - ``nsite x nsite`` parallel orbital index matrix. - reverse : array-like - ``nsite x nsite`` sign-reversal matrix. - NOrbGC : int - Number of parallel orbital indices. - """ - with open("orbitalidxpara.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NOrbitalIdx {NOrbGC:10d}\n") - fp.write(f"ComplexType {ComplexType:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") - - for isite in range(nsite): - for jsite in range(nsite): - if isite >= jsite: - continue - fp.write(f"{isite:5d} {jsite:5d} " - f"{OrbGC[isite][jsite]:5d} " - f"{reverse[isite][jsite]:5d}\n") - - for iOrbGC in range(NOrbGC): - fp.write(f"{iOrbGC:5d} {1:5d}\n") - - -def _write_orbitalidxgen( - StdI: StdIntList, - OrbGC: list, - reverse: list, - NOrbGC: int, -) -> None: - """Write ``orbitalidxgen.def`` combining anti-parallel and parallel orbitals. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. Reads ``nsite``, ``NOrb``, - ``ComplexType``, ``Orb``, ``AntiOrb``, and ``AntiPeriod``. - OrbGC : array-like - ``nsite x nsite`` parallel orbital index matrix. - reverse : array-like - ``nsite x nsite`` sign-reversal matrix. - NOrbGC : int - Number of parallel orbital indices. - """ - nsite = StdI.nsite - has_anti = _has_anti_period(StdI) - - with open("orbitalidxgen.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NOrbitalIdx {StdI.NOrb + 2 * NOrbGC:10d}\n") - fp.write(f"ComplexType {StdI.ComplexType:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") - - # -- anti-parallel section -- - for isite in range(nsite): - for jsite in range(nsite): - if has_anti: - fp.write(f"{isite:5d} 0 {jsite:5d} 1 " - f"{StdI.Orb[isite][jsite]:5d} " - f"{StdI.AntiOrb[isite][jsite]:5d}\n") - else: - fp.write(f"{isite:5d} 0 {jsite:5d} 1 " - f"{StdI.Orb[isite][jsite]:5d} {1:5d}\n") - - # -- parallel section (upper triangle) -- - for isite in range(nsite): - for jsite in range(nsite): - if isite >= jsite: - continue - fp.write(f"{isite:5d} 0 {jsite:5d} 0 " - f"{OrbGC[isite][jsite] + StdI.NOrb:5d} " - f"{reverse[isite][jsite]:5d}\n") - fp.write(f"{isite:5d} 1 {jsite:5d} 1 " - f"{OrbGC[isite][jsite] + StdI.NOrb + NOrbGC:5d} " - f"{reverse[isite][jsite]:5d}\n") - - for iOrbGC in range(StdI.NOrb): - fp.write(f"{iOrbGC:5d} {1:5d}\n") - - for iOrbGC in range(NOrbGC * 2): - fp.write(f"{iOrbGC + StdI.NOrb:5d} {1:5d}\n") - - -def print_orb_para(StdI: StdIntList) -> None: - """Write parallel orbital index files. - - Writes ``orbitalidxpara.def`` via :func:`_write_orbitalidxpara` and - ``orbitalidxgen.def`` via :func:`_write_orbitalidxgen`. Computation - of parallel orbital indices is delegated to - :func:`_compute_parallel_orbitals`. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. The following fields are read: - - - ``nsite`` -- total number of sites - - ``NOrb`` -- number of anti-parallel orbital indices - - ``ComplexType`` -- 0 for real, 1 for complex - - ``Orb`` -- ``nsite x nsite`` orbital index matrix - - ``AntiOrb`` -- ``nsite x nsite`` anti-periodic sign matrix - - ``AntiPeriod`` -- length-3 array of anti-periodic boundary flags - - Notes - ----- - Translated from the C function ``PrintOrbPara()`` in ``StdFace_main.c``. - """ - OrbGC, reverse, NOrbGC = _compute_parallel_orbitals( - StdI.nsite, StdI.NOrb, StdI.Orb, StdI.AntiOrb) - - _write_orbitalidxpara( - StdI.nsite, StdI.ComplexType, OrbGC, reverse, NOrbGC) - print(" orbitalidxpara.def is written.") - - _write_orbitalidxgen(StdI, OrbGC, reverse, NOrbGC) - print(" orbitalidxgen.def is written.") - - -def _gutzwiller_momentum_projected( - StdI: StdIntList, - Gutz: list[int], -) -> int: - """Compute Gutzwiller indices in momentum-projected mode. - - For the Hubbard model, ``NGutzwiller`` starts at 0; for other models - it starts at -1. Diagonal orbital indices (``Orb[i][i]``) are used; - local-spin sites are excluded (set to -1). Unique Gutzwiller indices - are renumbered with negative temporaries and then inverted. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. - Gutz : list of int - Mutable per-site Gutzwiller index array (modified in place). - - Returns - ------- - int - Number of unique Gutzwiller parameters (``NGutzwiller``). - """ - nsite = StdI.nsite - - if StdI.model == ModelType.HUBBARD: - NGutzwiller = 0 - else: - NGutzwiller = -1 - - for isite in range(nsite): - Gutz[isite] = int(StdI.Orb[isite][isite]) - - for isite in range(nsite): - if StdI.locspinflag[isite] != 0: - Gutz[isite] = -1 - continue - if Gutz[isite] >= 0: - iGutz = Gutz[isite] - NGutzwiller -= 1 - for jsite in range(nsite): - if Gutz[jsite] == iGutz: - Gutz[jsite] = NGutzwiller - - NGutzwiller = -NGutzwiller - for isite in range(nsite): - Gutz[isite] = -1 - Gutz[isite] - - return NGutzwiller - - -def _gutzwiller_global_optimization( - StdI: StdIntList, - Gutz: list[int], -) -> int: - """Compute Gutzwiller indices in global-optimisation mode. - - - Hubbard: ``NGutzwiller = NsiteUC``, site index modulo ``NsiteUC``. - - Spin: ``NGutzwiller = 1``, all sites map to index 0. - - Kondo: ``NGutzwiller = NsiteUC + 1``, conduction sites map to 0, - localised sites map to ``isite + 1``. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. - Gutz : list of int - Mutable per-site Gutzwiller index array (modified in place). - - Returns - ------- - int - Number of unique Gutzwiller parameters (``NGutzwiller``). - """ - if StdI.model == ModelType.HUBBARD: - NGutzwiller = StdI.NsiteUC - elif StdI.model == ModelType.SPIN: - NGutzwiller = 1 - else: - NGutzwiller = StdI.NsiteUC + 1 - - for iCell in range(StdI.NCell): - for isite in range(StdI.NsiteUC): - if StdI.model == ModelType.HUBBARD: - Gutz[isite + StdI.NsiteUC * iCell] = isite - elif StdI.model == ModelType.SPIN: - Gutz[isite + StdI.NsiteUC * iCell] = 0 - else: - Gutz[isite + StdI.NsiteUC * iCell] = 0 - Gutz[isite + StdI.NsiteUC * (iCell + StdI.NCell)] = isite + 1 - - return NGutzwiller - - -def _write_gutzwiller_file( - StdI: StdIntList, - NGutzwiller: int, - Gutz: list[int], -) -> None: - """Write ``gutzwilleridx.def`` from pre-computed Gutzwiller indices. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. Reads ``nsite`` and ``model``. - NGutzwiller : int - Number of unique Gutzwiller parameters. - Gutz : list of int - Per-site Gutzwiller index array. - """ - with open("gutzwilleridx.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NGutzwillerIdx {NGutzwiller:10d}\n") - fp.write(f"ComplexType {0:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") - - for isite in range(StdI.nsite): - fp.write(f"{isite:5d} {Gutz[isite]:5d}\n") - - for iGutz in range(NGutzwiller): - flag = int(StdI.model == ModelType.HUBBARD or iGutz > 0) - fp.write(f"{iGutz:5d} {flag:5d}\n") - - -def print_gutzwiller(StdI: StdIntList) -> None: - """Write the Gutzwiller variational-parameter file ``gutzwilleridx.def``. - - Delegates computation to :func:`_gutzwiller_momentum_projected` or - :func:`_gutzwiller_global_optimization` based on ``NMPTrans``, then - writes the results via :func:`_write_gutzwiller_file`. - - Parameters - ---------- - StdI : StdIntList - The global parameter structure. The following fields are read: - - - ``nsite`` -- total number of sites - - ``NMPTrans`` -- momentum-projection control - - ``model`` -- model type - - ``Orb`` -- ``nsite x nsite`` orbital index matrix - - ``locspinflag`` -- per-site local-spin flag array - - ``NsiteUC`` -- number of sites per unit cell - - ``NCell`` -- number of unit cells - - Notes - ----- - Translated from the C function ``PrintGutzwiller()`` in - ``StdFace_main.c``. - """ - Gutz = [0] * StdI.nsite - - if abs(StdI.NMPTrans) == 1 or StdI.NMPTrans == NaN_i: - NGutzwiller = _gutzwiller_momentum_projected(StdI, Gutz) - else: - NGutzwiller = _gutzwiller_global_optimization(StdI, Gutz) - - _write_gutzwiller_file(StdI, NGutzwiller, Gutz) - print(" gutzwilleridx.def is written.") diff --git a/python/writer/solver_writer.py b/python/writer/solver_writer.py deleted file mode 100644 index 776969b..0000000 --- a/python/writer/solver_writer.py +++ /dev/null @@ -1,252 +0,0 @@ -"""Solver-specific output writer classes. - -This module provides a class hierarchy for writing Expert-mode definition -files for each supported solver. It replaces the ``if solver == ...`` -dispatch block that was previously in ``stdface_main.stdface_main()``. - -Classes -------- -SolverWriter - Abstract base class defining the writer interface. -HPhiWriter - Writes definition files for the HPhi solver. -MVMCWriter - Writes definition files for the mVMC solver. -UHFWriter - Writes definition files for the UHF solver. -HWaveWriter - Writes definition files for the H-wave solver. - -Functions ---------- -get_solver_writer - Factory function returning the correct writer for a solver name. -""" -from __future__ import annotations - -from abc import ABC, abstractmethod - -from stdface_vals import StdIntList, SolverType, MethodType, NaN_i -from param_check import print_val_i -from .mvmc_variational import ( - generate_orb, - proj, - print_jastrow, -) -from .hphi_writer import ( - print_calc_mod, - print_excitation, - print_pump, -) -from .mvmc_writer import ( - print_orb, - print_orb_para, - print_gutzwiller, -) -from .common_writer import ( - print_loc_spin, - print_trans, - print_namelist, - print_mod_para, - print_1_green, - print_2_green, - check_output_mode, - check_mod_para, -) -from .interaction_writer import print_interactions -from .export_wannier90 import export_geometry, export_interaction - - -class SolverWriter(ABC): - """Abstract base class for solver-specific output writers. - - Each concrete subclass implements :meth:`write` to produce the set of - Expert-mode definition files required by a particular solver. - - Parameters - ---------- - name : str - Human-readable solver name (e.g. ``"HPhi"``). - """ - - def __init__(self, name: str) -> None: - self.name = name - - @abstractmethod - def write(self, StdI: StdIntList) -> None: - """Write all Expert-mode definition files for this solver. - - Parameters - ---------- - StdI : StdIntList - The fully-populated parameter structure. - """ - - -class HPhiWriter(SolverWriter): - """Writer for the HPhi exact-diagonalisation / Lanczos solver. - - Produces: locspn, trans, interactions, modpara, single/pair (excitation), - teone/tetwo (time-evolution pump), calcmod, greenone, greentwo, namelist. - """ - - def __init__(self) -> None: - super().__init__(SolverType.HPhi) - - def write(self, StdI: StdIntList) -> None: - """Write all HPhi definition files. - - Parameters - ---------- - StdI : StdIntList - The fully-populated parameter structure. - """ - print_loc_spin(StdI) - print_trans(StdI) - print_interactions(StdI) - check_mod_para(StdI) - print_mod_para(StdI) - print_excitation(StdI) - if StdI.method == MethodType.TIME_EVOLUTION: - print_pump(StdI) - print_calc_mod(StdI) - check_output_mode(StdI) - print_1_green(StdI) - print_2_green(StdI) - print_namelist(StdI) - - -class MVMCWriter(SolverWriter): - """Writer for the mVMC variational Monte Carlo solver. - - Produces: locspn, trans, interactions, modpara, orbitalidx, - orbitalidxpara/gen, gutzwilleridx, greenone, greentwo, namelist, - plus Jastrow and projection files. - """ - - def __init__(self) -> None: - super().__init__(SolverType.mVMC) - - def write(self, StdI: StdIntList) -> None: - """Write all mVMC definition files. - - Parameters - ---------- - StdI : StdIntList - The fully-populated parameter structure. - """ - print_loc_spin(StdI) - print_trans(StdI) - print_interactions(StdI) - check_mod_para(StdI) - print_mod_para(StdI) - - if StdI.lGC == 0 and (StdI.Sz2 == 0 or StdI.Sz2 == NaN_i): - StdI.ComplexType = print_val_i("ComplexType", StdI.ComplexType, 0) - else: - StdI.ComplexType = print_val_i("ComplexType", StdI.ComplexType, 1) - - generate_orb(StdI) - proj(StdI) - print_jastrow(StdI) - if StdI.lGC == 1 or (StdI.Sz2 != 0 and StdI.Sz2 != NaN_i): - print_orb_para(StdI) - print_gutzwiller(StdI) - print_orb(StdI) - check_output_mode(StdI) - print_1_green(StdI) - print_2_green(StdI) - print_namelist(StdI) - - -class UHFWriter(SolverWriter): - """Writer for the UHF (unrestricted Hartree--Fock) solver. - - Produces: locspn, trans, interactions, modpara, greenone, namelist. - """ - - def __init__(self) -> None: - super().__init__(SolverType.UHF) - - def write(self, StdI: StdIntList) -> None: - """Write all UHF definition files. - - Parameters - ---------- - StdI : StdIntList - The fully-populated parameter structure. - """ - print_loc_spin(StdI) - print_trans(StdI) - print_interactions(StdI) - check_mod_para(StdI) - print_mod_para(StdI) - check_output_mode(StdI) - print_1_green(StdI) - print_namelist(StdI) - - -class HWaveWriter(SolverWriter): - """Writer for the H-wave solver. - - In ``uhfr`` calc-mode, writes: trans, interactions, modpara, greenone. - Otherwise, exports Wannier90 geometry and interaction files. - """ - - def __init__(self) -> None: - super().__init__(SolverType.HWAVE) - - def write(self, StdI: StdIntList) -> None: - """Write all H-wave definition files. - - Parameters - ---------- - StdI : StdIntList - The fully-populated parameter structure. - """ - if StdI.calcmode == "uhfr": - print_trans(StdI) - print_interactions(StdI) - check_mod_para(StdI) - check_output_mode(StdI) - print_1_green(StdI) - else: - export_geometry(StdI) - export_interaction(StdI) - - -# Registry of solver writers -_SOLVER_WRITERS: dict[SolverType, type[SolverWriter]] = { - SolverType.HPhi: HPhiWriter, - SolverType.mVMC: MVMCWriter, - SolverType.UHF: UHFWriter, - SolverType.HWAVE: HWaveWriter, -} - - -def get_solver_writer(solver: str) -> SolverWriter: - """Return the appropriate solver writer instance. - - Parameters - ---------- - solver : str - Solver name. Must be one of ``"HPhi"``, ``"mVMC"``, ``"UHF"``, - or ``"HWAVE"``. - - Returns - ------- - SolverWriter - An instance of the concrete writer for *solver*. - - Raises - ------ - ValueError - If *solver* is not a recognised solver name. - """ - cls = _SOLVER_WRITERS.get(solver) - if cls is None: - raise ValueError( - f"Unknown solver {solver!r}. " - f"Expected one of: {', '.join(_SOLVER_WRITERS)}" - ) - return cls() From 3d5cb5f21e910915332cc6519e3d44c95ab1f6fe Mon Sep 17 00:00:00 2001 From: Kazuyoshi Yoshimi Date: Thu, 29 Jan 2026 20:36:14 +0900 Subject: [PATCH 05/16] Update imports to use new stdface package paths Migrate __main__.py and all unit tests from old flat imports (e.g. from stdface_vals import ...) to the new package structure (e.g. from stdface.core.stdface_vals import ...). Also update test_solver_writer.py for the plugin architecture rename. Co-Authored-By: Claude Opus 4.5 --- python/__main__.py | 77 +-------------- test/unit/conftest.py | 4 +- test/unit/test_boost_output.py | 4 +- test/unit/test_chain_lattice.py | 4 +- test/unit/test_common_writer.py | 6 +- test/unit/test_export_wannier90.py | 4 +- test/unit/test_geometry_output.py | 8 +- test/unit/test_hphi_writer.py | 6 +- test/unit/test_input_params.py | 6 +- test/unit/test_interaction_builder.py | 8 +- test/unit/test_interaction_writer.py | 4 +- test/unit/test_keyword_parser.py | 4 +- test/unit/test_lattices.py | 18 ++-- test/unit/test_model_method_dispatch.py | 2 +- test/unit/test_mvmc_variational.py | 4 +- test/unit/test_mvmc_writer.py | 4 +- test/unit/test_param_check.py | 11 +-- test/unit/test_reset_vals_dispatch.py | 4 +- test/unit/test_site_util.py | 12 +-- test/unit/test_solver_writer.py | 120 ++++++++++++------------ test/unit/test_stdface_main_helpers.py | 4 +- test/unit/test_stdface_model_util.py | 4 +- test/unit/test_stdface_vals.py | 2 +- test/unit/test_version.py | 2 +- test/unit/test_wannier90.py | 10 +- 25 files changed, 128 insertions(+), 204 deletions(-) diff --git a/python/__main__.py b/python/__main__.py index d3549d0..6382f28 100644 --- a/python/__main__.py +++ b/python/__main__.py @@ -1,80 +1,9 @@ -"""Command-line entry point for the StdFace standard-mode input generator. +"""Entry point for ``python python/__main__.py``. -This module is the Python translation of ``dry.c``. It can be invoked as:: - - python -m stdface stan.in # default solver: HPhi - python -m stdface stan.in --solver mVMC - -or, equivalently, via the installed console script (if packaged). - -License -------- -HPhi-mVMC-StdFace - Common input generator -Copyright (C) 2015 The University of Tokyo - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +Delegates to ``stdface.__main__.main()``. """ -from __future__ import annotations - -import argparse +from stdface.__main__ import main import sys -from version import print_version -from stdface_main import stdface_main - - -def main(argv: list[str] | None = None) -> int: - """Parse command-line arguments and run the standard-mode generator. - - Parameters - ---------- - argv : list of str or None - Command-line arguments. ``None`` means ``sys.argv[1:]``. - - Returns - ------- - int - Exit status (0 = success, 1 = usage error). - """ - parser = argparse.ArgumentParser( - prog="stdface", - description="StdFace: standard-mode input generator for HPhi / mVMC / UHF / H-wave", - ) - parser.add_argument( - "-v", "--version", - action="store_true", - help="print version and exit", - ) - parser.add_argument( - "input_file", - nargs="?", - default=None, - help="standard-mode input file (e.g. stan.in)", - ) - parser.add_argument( - "--solver", - choices=["HPhi", "mVMC", "UHF", "HWAVE"], - default="HPhi", - help="target solver (default: HPhi)", - ) - - args = parser.parse_args(argv) - - if args.version: - print_version() - return 0 - - if args.input_file is None: - print_version() - parser.print_usage() - return 1 - - stdface_main(args.input_file, solver=args.solver) - return 0 - - if __name__ == "__main__": sys.exit(main()) diff --git a/test/unit/conftest.py b/test/unit/conftest.py index ce034cc..ab8a2ef 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -1,7 +1,7 @@ """Pytest configuration for StdFace unit tests. -Adds the python/ directory to sys.path so that translated Python modules -can be imported directly by name (e.g., ``from stdface_vals import StdIntList``). +Adds the python/ directory to sys.path so that the ``stdface`` package +can be imported (e.g., ``from stdface.core.stdface_vals import StdIntList``). """ from __future__ import annotations diff --git a/test/unit/test_boost_output.py b/test/unit/test_boost_output.py index 06c02f2..5899d4d 100644 --- a/test/unit/test_boost_output.py +++ b/test/unit/test_boost_output.py @@ -11,8 +11,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -from lattice.boost_output import ( +from stdface.core.stdface_vals import StdIntList +from stdface.lattice.boost_output import ( write_boost_mag_field, write_boost_j_full, write_boost_j_symmetric, diff --git a/test/unit/test_chain_lattice.py b/test/unit/test_chain_lattice.py index a2a8c15..7b0fc75 100644 --- a/test/unit/test_chain_lattice.py +++ b/test/unit/test_chain_lattice.py @@ -10,8 +10,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -from lattice import chain_lattice as cl +from stdface.core.stdface_vals import StdIntList +from stdface.lattice import chain_lattice as cl # --------------------------------------------------------------------------- diff --git a/test/unit/test_common_writer.py b/test/unit/test_common_writer.py index 7b2d220..0216976 100644 --- a/test/unit/test_common_writer.py +++ b/test/unit/test_common_writer.py @@ -10,8 +10,8 @@ import pytest -from stdface_vals import StdIntList, ModelType, SolverType -from writer.common_writer import ( +from stdface.core.stdface_vals import StdIntList, ModelType, SolverType +from stdface.writer.common_writer import ( print_loc_spin, print_trans, print_namelist, @@ -41,7 +41,7 @@ GreenFunctionIndices, _merge_duplicate_terms, ) -from writer.interaction_writer import print_interactions +from stdface.writer.interaction_writer import print_interactions # Sentinel values matching what _reset_vals sets at runtime NaN_i = 2147483647 diff --git a/test/unit/test_export_wannier90.py b/test/unit/test_export_wannier90.py index f47b1ea..d0593bc 100644 --- a/test/unit/test_export_wannier90.py +++ b/test/unit/test_export_wannier90.py @@ -12,8 +12,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -from writer import export_wannier90 as ew +from stdface.core.stdface_vals import StdIntList +from stdface.solvers.hwave import export_wannier90 as ew # --------------------------------------------------------------------------- diff --git a/test/unit/test_geometry_output.py b/test/unit/test_geometry_output.py index 5e8c7e6..2d2ccb2 100644 --- a/test/unit/test_geometry_output.py +++ b/test/unit/test_geometry_output.py @@ -11,8 +11,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -from lattice.geometry_output import print_xsf, print_geometry, _cell_diff +from stdface.core.stdface_vals import StdIntList +from stdface.lattice.geometry_output import print_xsf, print_geometry, _cell_diff def _make_stdi( @@ -301,10 +301,10 @@ class TestBackwardCompatibility: def test_import_from_stdface_model_util(self): """Test that both functions are re-exported.""" - from stdface_model_util import ( + from stdface.core.stdface_model_util import ( print_xsf as pxsf, print_geometry as pg, ) - from lattice.geometry_output import print_xsf, print_geometry + from stdface.lattice.geometry_output import print_xsf, print_geometry assert pxsf is print_xsf assert pg is print_geometry diff --git a/test/unit/test_hphi_writer.py b/test/unit/test_hphi_writer.py index 3eaa677..88e5f1d 100644 --- a/test/unit/test_hphi_writer.py +++ b/test/unit/test_hphi_writer.py @@ -11,9 +11,9 @@ import pytest -from stdface_vals import StdIntList, MethodType, ModelType -from stdface_vals import UNSET_STRING -from writer.hphi_writer import ( +from stdface.core.stdface_vals import StdIntList, MethodType, ModelType +from stdface.core.stdface_vals import UNSET_STRING +from stdface.solvers.hphi.writer import ( large_value, print_calc_mod, print_excitation, diff --git a/test/unit/test_input_params.py b/test/unit/test_input_params.py index 3d60286..bc13c6c 100644 --- a/test/unit/test_input_params.py +++ b/test/unit/test_input_params.py @@ -10,7 +10,7 @@ import numpy as np import pytest -from lattice.input_params import ( +from stdface.lattice.input_params import ( input_spin_nn, input_spin, input_coulomb_v, @@ -214,13 +214,13 @@ class TestBackwardCompatibility: def test_import_from_stdface_model_util(self): """Test that all 4 functions are re-exported.""" - from stdface_model_util import ( + from stdface.core.stdface_model_util import ( input_spin_nn as isn, input_spin as isp, input_coulomb_v as icv, input_hopp as ih, ) - from lattice.input_params import ( + from stdface.lattice.input_params import ( input_spin_nn, input_spin, input_coulomb_v, diff --git a/test/unit/test_interaction_builder.py b/test/unit/test_interaction_builder.py index f8330a5..94583ff 100644 --- a/test/unit/test_interaction_builder.py +++ b/test/unit/test_interaction_builder.py @@ -10,8 +10,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -from lattice.interaction_builder import ( +from stdface.core.stdface_vals import StdIntList +from stdface.lattice.interaction_builder import ( trans, hopping, hubbard_local, @@ -379,7 +379,7 @@ class TestBackwardCompatibility: def test_import_from_stdface_model_util(self): """Test that all 8 functions are re-exported.""" - from stdface_model_util import ( + from stdface.core.stdface_model_util import ( trans as t, hopping as h, hubbard_local as hl, @@ -389,7 +389,7 @@ def test_import_from_stdface_model_util(self): coulomb as c, malloc_interactions as mi, ) - from lattice.interaction_builder import ( + from stdface.lattice.interaction_builder import ( trans, hopping, hubbard_local, diff --git a/test/unit/test_interaction_writer.py b/test/unit/test_interaction_writer.py index 32b6f3b..bd7200a 100644 --- a/test/unit/test_interaction_writer.py +++ b/test/unit/test_interaction_writer.py @@ -8,8 +8,8 @@ import os import tempfile -from stdface_vals import StdIntList -from writer.interaction_writer import ( +from stdface.core.stdface_vals import StdIntList +from stdface.writer.interaction_writer import ( print_interactions, _merge_1idx, _merge_2idx, diff --git a/test/unit/test_keyword_parser.py b/test/unit/test_keyword_parser.py index ba8342f..ad74a65 100644 --- a/test/unit/test_keyword_parser.py +++ b/test/unit/test_keyword_parser.py @@ -10,7 +10,7 @@ import pytest -from keyword_parser import ( +from stdface.core.keyword_parser import ( trim_space_quote, store_with_check_dup_s, store_with_check_dup_sl, @@ -32,7 +32,7 @@ _grid3x3_keywords, NaN_i, ) -from stdface_vals import StdIntList, SolverType +from stdface.core.stdface_vals import StdIntList, SolverType import numpy as np diff --git a/test/unit/test_lattices.py b/test/unit/test_lattices.py index cde52c5..87ce269 100644 --- a/test/unit/test_lattices.py +++ b/test/unit/test_lattices.py @@ -16,15 +16,15 @@ # Each entry is (module_name, list_of_expected_functions). LATTICE_MODULES = [ - ("lattice.square_lattice", ["tetragonal"]), - ("lattice.ladder", ["ladder", "ladder_boost"]), - ("lattice.triangular_lattice", ["triangular"]), - ("lattice.honeycomb_lattice", ["honeycomb", "honeycomb_boost"]), - ("lattice.kagome", ["kagome", "kagome_boost"]), - ("lattice.orthorhombic", ["orthorhombic"]), - ("lattice.fc_ortho", ["fc_ortho"]), - ("lattice.pyrochlore", ["pyrochlore"]), - ("lattice.chain_lattice", ["chain", "chain_boost"]), + ("stdface.lattice.square_lattice", ["tetragonal"]), + ("stdface.lattice.ladder", ["ladder", "ladder_boost"]), + ("stdface.lattice.triangular_lattice", ["triangular"]), + ("stdface.lattice.honeycomb_lattice", ["honeycomb", "honeycomb_boost"]), + ("stdface.lattice.kagome", ["kagome", "kagome_boost"]), + ("stdface.lattice.orthorhombic", ["orthorhombic"]), + ("stdface.lattice.fc_ortho", ["fc_ortho"]), + ("stdface.lattice.pyrochlore", ["pyrochlore"]), + ("stdface.lattice.chain_lattice", ["chain", "chain_boost"]), ] # Flat list of (module_name, function_name) for parametrized tests. diff --git a/test/unit/test_model_method_dispatch.py b/test/unit/test_model_method_dispatch.py index d96e990..99eabb0 100644 --- a/test/unit/test_model_method_dispatch.py +++ b/test/unit/test_model_method_dispatch.py @@ -7,7 +7,7 @@ import pytest -from stdface_main import MODEL_ALIASES, MODEL_ALIASES_HPHI_BOOST, METHOD_ALIASES +from stdface.core.stdface_main import MODEL_ALIASES, MODEL_ALIASES_HPHI_BOOST, METHOD_ALIASES class TestModelAliases: diff --git a/test/unit/test_mvmc_variational.py b/test/unit/test_mvmc_variational.py index 3e18f8e..777a69e 100644 --- a/test/unit/test_mvmc_variational.py +++ b/test/unit/test_mvmc_variational.py @@ -14,8 +14,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -from writer.mvmc_variational import ( +from stdface.core.stdface_vals import StdIntList +from stdface.solvers.mvmc.variational import ( _anti_period_dot, _parity_sign, _check_commensurate, diff --git a/test/unit/test_mvmc_writer.py b/test/unit/test_mvmc_writer.py index 93216d4..67dd837 100644 --- a/test/unit/test_mvmc_writer.py +++ b/test/unit/test_mvmc_writer.py @@ -10,10 +10,10 @@ import pytest -from stdface_vals import StdIntList +from stdface.core.stdface_vals import StdIntList import numpy as np -from writer.mvmc_writer import ( +from stdface.solvers.mvmc.writer import ( print_orb, print_orb_para, print_gutzwiller, diff --git a/test/unit/test_param_check.py b/test/unit/test_param_check.py index f4ccca2..ddc413c 100644 --- a/test/unit/test_param_check.py +++ b/test/unit/test_param_check.py @@ -10,7 +10,7 @@ import numpy as np import pytest -from param_check import ( +from stdface.core.param_check import ( exit_program, print_val_d, print_val_dd, @@ -21,7 +21,7 @@ not_used_i, required_val_i, ) -from stdface_vals import NaN_i, NaN_d +from stdface.core.stdface_vals import NaN_i, NaN_d class TestExitProgram: @@ -198,11 +198,11 @@ def test_prints_value(self, capsys): class TestBackwardCompatibility: - """Test that functions are still importable from stdface_model_util.""" + """Test that functions are importable from stdface.core.stdface_model_util.""" def test_import_from_stdface_model_util(self): """Test that all extracted functions are re-exported.""" - from stdface_model_util import ( + from stdface.core.stdface_model_util import ( exit_program as ep, print_val_d as pvd, print_val_dd as pvdd, @@ -213,8 +213,7 @@ def test_import_from_stdface_model_util(self): not_used_i as nui, required_val_i as rvi, ) - # Verify they are the same objects as param_check - from param_check import ( + from stdface.core.param_check import ( exit_program, print_val_d, print_val_dd, diff --git a/test/unit/test_reset_vals_dispatch.py b/test/unit/test_reset_vals_dispatch.py index 0a4cc8a..d5fd8ea 100644 --- a/test/unit/test_reset_vals_dispatch.py +++ b/test/unit/test_reset_vals_dispatch.py @@ -13,7 +13,7 @@ import numpy as np import pytest -from stdface_main import ( +from stdface.core.stdface_main import ( _COMMON_RESET_SCALARS, _COMMON_RESET_ARRAYS, _SOLVER_RESET_SCALARS, @@ -26,7 +26,7 @@ NaN_d, NaN_c, ) -from stdface_vals import StdIntList, SolverType +from stdface.core.stdface_vals import StdIntList, SolverType def _make_stdi(solver: str) -> StdIntList: diff --git a/test/unit/test_site_util.py b/test/unit/test_site_util.py index d5a4415..294d1d4 100644 --- a/test/unit/test_site_util.py +++ b/test/unit/test_site_util.py @@ -12,8 +12,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -from lattice.site_util import ( +from stdface.core.stdface_vals import StdIntList +from stdface.lattice.site_util import ( _cell_vector, _fold_to_cell, _fold_site, _find_cell_index, _write_gnuplot_header, _write_gnuplot_bond, @@ -24,7 +24,7 @@ init_site, find_site, set_label, set_local_spin_flags, ) -from stdface_vals import ModelType, SolverType, NaN_i +from stdface.core.stdface_vals import ModelType, SolverType, NaN_i def _make_stdi_chain(L: int = 4) -> StdIntList: @@ -764,17 +764,17 @@ def test_fp_none_no_error(self): class TestBackwardCompatibility: - """Test that functions are still importable from stdface_model_util.""" + """Test that functions are importable from stdface.core.stdface_model_util.""" def test_import_from_stdface_model_util(self): """Test that all 4 functions are re-exported.""" - from stdface_model_util import ( + from stdface.core.stdface_model_util import ( _fold_site as fs, init_site as iis, find_site as fis, set_label as sl, ) - from lattice.site_util import _fold_site, init_site, find_site, set_label + from stdface.lattice.site_util import _fold_site, init_site, find_site, set_label assert fs is _fold_site assert iis is init_site assert fis is find_site diff --git a/test/unit/test_solver_writer.py b/test/unit/test_solver_writer.py index ca959a0..9dbe7c4 100644 --- a/test/unit/test_solver_writer.py +++ b/test/unit/test_solver_writer.py @@ -1,7 +1,6 @@ -"""Unit tests for solver_writer module. +"""Unit tests for solver plugin system. -Tests for the SolverWriter class hierarchy and the ``get_solver_writer`` -factory function. +Tests for the SolverPlugin classes and the plugin registry. """ from __future__ import annotations @@ -11,15 +10,12 @@ import pytest -from writer.solver_writer import ( - SolverWriter, - HPhiWriter, - MVMCWriter, - UHFWriter, - HWaveWriter, - get_solver_writer, -) -from stdface_vals import StdIntList +from stdface.plugin import SolverPlugin, get_plugin +from stdface.solvers.hphi import HPhiPlugin +from stdface.solvers.mvmc import MVMCPlugin +from stdface.solvers.uhf import UHFPlugin +from stdface.solvers.hwave import HWavePlugin +from stdface.core.stdface_vals import StdIntList # Sentinel values matching the C code NaN_i = 2147483647 @@ -103,70 +99,70 @@ def _make_stdi_for_hphi(nsite: int = 4) -> StdIntList: # ===================================================================== -# Tests for get_solver_writer factory +# Tests for get_plugin registry # ===================================================================== -class TestGetSolverWriter: - """Tests for the get_solver_writer factory function.""" +class TestGetPlugin: + """Tests for the get_plugin registry function.""" - def test_returns_hphi_writer(self): - """Test that 'HPhi' returns an HPhiWriter.""" - writer = get_solver_writer("HPhi") - assert isinstance(writer, HPhiWriter) - assert writer.name == "HPhi" + def test_returns_hphi_plugin(self): + """Test that 'HPhi' returns an HPhiPlugin.""" + plugin = get_plugin("HPhi") + assert isinstance(plugin, HPhiPlugin) + assert plugin.name == "HPhi" - def test_returns_mvmc_writer(self): - """Test that 'mVMC' returns an MVMCWriter.""" - writer = get_solver_writer("mVMC") - assert isinstance(writer, MVMCWriter) - assert writer.name == "mVMC" + def test_returns_mvmc_plugin(self): + """Test that 'mVMC' returns an MVMCPlugin.""" + plugin = get_plugin("mVMC") + assert isinstance(plugin, MVMCPlugin) + assert plugin.name == "mVMC" - def test_returns_uhf_writer(self): - """Test that 'UHF' returns a UHFWriter.""" - writer = get_solver_writer("UHF") - assert isinstance(writer, UHFWriter) - assert writer.name == "UHF" + def test_returns_uhf_plugin(self): + """Test that 'UHF' returns a UHFPlugin.""" + plugin = get_plugin("UHF") + assert isinstance(plugin, UHFPlugin) + assert plugin.name == "UHF" - def test_returns_hwave_writer(self): - """Test that 'HWAVE' returns an HWaveWriter.""" - writer = get_solver_writer("HWAVE") - assert isinstance(writer, HWaveWriter) - assert writer.name == "HWAVE" + def test_returns_hwave_plugin(self): + """Test that 'HWAVE' returns an HWavePlugin.""" + plugin = get_plugin("HWAVE") + assert isinstance(plugin, HWavePlugin) + assert plugin.name == "HWAVE" def test_raises_on_unknown_solver(self): - """Test that an unknown solver raises ValueError.""" - with pytest.raises(ValueError, match="Unknown solver"): - get_solver_writer("unknown") + """Test that an unknown solver raises KeyError.""" + with pytest.raises(KeyError, match="No solver plugin"): + get_plugin("unknown") # ===================================================================== -# Tests for SolverWriter subclasses +# Tests for SolverPlugin subclasses # ===================================================================== -class TestSolverWriterIsAbstract: +class TestSolverPluginIsAbstract: """Tests for the abstract base class.""" def test_cannot_instantiate_directly(self): - """Test that SolverWriter cannot be instantiated directly.""" + """Test that SolverPlugin cannot be instantiated directly.""" with pytest.raises(TypeError): - SolverWriter("test") + SolverPlugin() -class TestHPhiWriter: - """Tests for the HPhiWriter class.""" +class TestHPhiPlugin: + """Tests for the HPhiPlugin class.""" def test_writes_namelist_def(self): - """Test that HPhiWriter creates namelist.def.""" + """Test that HPhiPlugin creates namelist.def.""" StdI = _make_stdi_for_hphi(nsite=4) - writer = HPhiWriter() + plugin = get_plugin("HPhi") with tempfile.TemporaryDirectory() as tmpdir: orig = os.getcwd() os.chdir(tmpdir) try: - writer.write(StdI) + plugin.write(StdI) assert os.path.exists("namelist.def") assert os.path.exists("calcmod.def") assert os.path.exists("modpara.def") @@ -175,50 +171,50 @@ def test_writes_namelist_def(self): os.chdir(orig) def test_writes_trans_def(self): - """Test that HPhiWriter creates trans.def.""" + """Test that HPhiPlugin creates trans.def.""" StdI = _make_stdi_for_hphi(nsite=4) - writer = HPhiWriter() + plugin = get_plugin("HPhi") with tempfile.TemporaryDirectory() as tmpdir: orig = os.getcwd() os.chdir(tmpdir) try: - writer.write(StdI) + plugin.write(StdI) assert os.path.exists("trans.def") finally: os.chdir(orig) def test_writes_green_files(self): - """Test that HPhiWriter creates greenone.def and greentwo.def.""" + """Test that HPhiPlugin creates greenone.def and greentwo.def.""" StdI = _make_stdi_for_hphi(nsite=4) - writer = HPhiWriter() + plugin = get_plugin("HPhi") with tempfile.TemporaryDirectory() as tmpdir: orig = os.getcwd() os.chdir(tmpdir) try: - writer.write(StdI) + plugin.write(StdI) assert os.path.exists("greenone.def") assert os.path.exists("greentwo.def") finally: os.chdir(orig) -class TestUHFWriter: - """Tests for the UHFWriter class.""" +class TestUHFPlugin: + """Tests for the UHFPlugin class.""" def test_writes_expected_files(self): - """Test that UHFWriter creates the expected set of files.""" + """Test that UHFPlugin creates the expected set of files.""" StdI = _make_stdi_for_hphi(nsite=4) StdI.solver = "UHF" StdI.outputmode = "****" - writer = UHFWriter() + plugin = get_plugin("UHF") with tempfile.TemporaryDirectory() as tmpdir: orig = os.getcwd() os.chdir(tmpdir) try: - writer.write(StdI) + plugin.write(StdI) assert os.path.exists("locspn.def") assert os.path.exists("trans.def") assert os.path.exists("modpara.def") @@ -228,8 +224,8 @@ def test_writes_expected_files(self): os.chdir(orig) -class TestHWaveWriter: - """Tests for the HWaveWriter class.""" +class TestHWavePlugin: + """Tests for the HWavePlugin class.""" def test_uhfr_mode_writes_trans(self): """Test that HWAVE in uhfr mode writes trans.def.""" @@ -237,13 +233,13 @@ def test_uhfr_mode_writes_trans(self): StdI.solver = "HWAVE" StdI.calcmode = "uhfr" StdI.outputmode = "****" - writer = HWaveWriter() + plugin = get_plugin("HWAVE") with tempfile.TemporaryDirectory() as tmpdir: orig = os.getcwd() os.chdir(tmpdir) try: - writer.write(StdI) + plugin.write(StdI) assert os.path.exists("trans.def") assert os.path.exists("greenone.def") finally: diff --git a/test/unit/test_stdface_main_helpers.py b/test/unit/test_stdface_main_helpers.py index eeaecd7..55f5564 100644 --- a/test/unit/test_stdface_main_helpers.py +++ b/test/unit/test_stdface_main_helpers.py @@ -8,8 +8,8 @@ import pytest -from stdface_vals import StdIntList, ModelType, SolverType, MethodType, NaN_d -from stdface_main import _parse_input_file, _resolve_model_and_method +from stdface.core.stdface_vals import StdIntList, ModelType, SolverType, MethodType, NaN_d +from stdface.core.stdface_main import _parse_input_file, _resolve_model_and_method class TestParseInputFile: diff --git a/test/unit/test_stdface_model_util.py b/test/unit/test_stdface_model_util.py index 80c0ade..7a7daf2 100644 --- a/test/unit/test_stdface_model_util.py +++ b/test/unit/test_stdface_model_util.py @@ -10,8 +10,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -import stdface_model_util as smu +from stdface.core.stdface_vals import StdIntList +import stdface.core.stdface_model_util as smu # --------------------------------------------------------------------------- diff --git a/test/unit/test_stdface_vals.py b/test/unit/test_stdface_vals.py index 23c4b92..d7b59db 100644 --- a/test/unit/test_stdface_vals.py +++ b/test/unit/test_stdface_vals.py @@ -7,7 +7,7 @@ import numpy as np import pytest -from stdface_vals import ( +from stdface.core.stdface_vals import ( StdIntList, ModelType, SolverType, MethodType, NaN_i, NaN_d, NaN_c, UNSET_STRING, AMPLITUDE_EPS, ZERO_BODY_EPS, diff --git a/test/unit/test_version.py b/test/unit/test_version.py index 6b6ae59..b0a9f06 100644 --- a/test/unit/test_version.py +++ b/test/unit/test_version.py @@ -4,7 +4,7 @@ """ from __future__ import annotations -import version +import stdface.core.version as version class TestVersionConstants: diff --git a/test/unit/test_wannier90.py b/test/unit/test_wannier90.py index 6601d4b..2215408 100644 --- a/test/unit/test_wannier90.py +++ b/test/unit/test_wannier90.py @@ -11,8 +11,8 @@ import numpy as np import pytest -from stdface_vals import StdIntList -from lattice import wannier90 as w90 +from stdface.core.stdface_vals import StdIntList +from stdface.lattice import wannier90 as w90 # --------------------------------------------------------------------------- @@ -227,7 +227,7 @@ class TestModuleStructure: def test_import(self): """wannier90 module should import without error.""" - from lattice import wannier90 + from stdface.lattice import wannier90 assert wannier90 is not None def test_wannier90_function_exists(self): @@ -750,7 +750,7 @@ def test_none_returns_notcorrect(self): def test_unset_string_returns_notcorrect(self): """UNSET_STRING sentinel should map to NOTCORRECT.""" - from stdface_vals import UNSET_STRING + from stdface.core.stdface_vals import UNSET_STRING assert w90._parse_double_counting_mode(UNSET_STRING) == w90._DCMode.NOTCORRECT def test_hartree_returns_hartree(self): @@ -777,7 +777,7 @@ def test_return_type_is_dcmode(self): def test_dc_mode_map_keys(self): """_DC_MODE_MAP should contain exactly the expected keys.""" - from stdface_vals import UNSET_STRING + from stdface.core.stdface_vals import UNSET_STRING expected_keys = {"none", UNSET_STRING, "hartree", "hartree_u", "full"} assert set(w90._DC_MODE_MAP.keys()) == expected_keys From 7b66d80574c00b475eec401f36955eca2a00a375 Mon Sep 17 00:00:00 2001 From: Kazuyoshi Yoshimi Date: Thu, 29 Jan 2026 20:41:30 +0900 Subject: [PATCH 06/16] Optimize mVMC writers and Green function index generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - writer.py: buffer all file I/O (print_orb, orbitalidxpara, orbitalidxgen, gutzwilleridx) with join() instead of per-line write - writer.py: replace O(NOrb × N²) symmetrise loop with single-pass reverse map construction + targeted updates - variational.py: buffer jastrowidx.def and qptransidx.def I/O - variational.py: single-pass reverse map for Jastrow symmetrise - variational.py: pre-compute cell_vectors and sectors outside loops in generate_orb and _jastrow_global_optimization - common_writer.py: pre-compute spin_max and is_local_spin lookup tables in GreenFunctionIndices.__init__ to eliminate per-call overhead in deeply nested Green function index generation loops Co-Authored-By: Claude Opus 4.5 --- python/stdface/solvers/mvmc/variational.py | 82 ++++++++----- python/stdface/solvers/mvmc/writer.py | 134 ++++++++++++--------- python/stdface/writer/common_writer.py | 12 +- 3 files changed, 133 insertions(+), 95 deletions(-) diff --git a/python/stdface/solvers/mvmc/variational.py b/python/stdface/solvers/mvmc/variational.py index db49d7f..9e7b5fc 100644 --- a/python/stdface/solvers/mvmc/variational.py +++ b/python/stdface/solvers/mvmc/variational.py @@ -161,17 +161,20 @@ def proj(StdI: StdIntList) -> None: StdI.NSym += 1 with open("qptransidx.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NQPTrans {StdI.NSym:10d}\n") - fp.write("=============================================\n") - fp.write("======== TrIdx_TrWeight_and_TrIdx_i_xi ======\n") - fp.write("=============================================\n") + lines = [ + "=============================================\n", + f"NQPTrans {StdI.NSym:10d}\n", + "=============================================\n", + "======== TrIdx_TrWeight_and_TrIdx_i_xi ======\n", + "=============================================\n", + ] for iSym in range(StdI.NSym): - fp.write(f"{iSym} {1.0:10.5f}\n") + lines.append(f"{iSym} {1.0:10.5f}\n") for iSym in range(StdI.NSym): for jsite in range(StdI.nsite): a = _parity_sign(Anti[iSym][jsite]) - fp.write(f"{iSym:5d} {jsite:5d} {Sym[iSym][jsite]:5d} {a:5d}\n") + lines.append(f"{iSym:5d} {jsite:5d} {Sym[iSym][jsite]:5d} {a:5d}\n") + fp.write("".join(lines)) print(" qptransidx.def is written.") @@ -276,16 +279,23 @@ def generate_orb(StdI: StdIntList) -> None: StdI.AntiOrb = np.zeros((StdI.nsite, StdI.nsite), dtype=int) CellDone = np.zeros((StdI.NCell, StdI.NCell), dtype=int) + # Pre-compute cell vectors and sector list outside loops + cell_vectors = [_cell_vector(StdI.Cell, k) for k in range(StdI.NCell)] + sectors = [(0, 0)] + if StdI.model == ModelType.KONDO: + half = StdI.nsite // 2 + sectors += [(half, 0), (0, half), (half, half)] + iOrb = 0 for iCell in range(StdI.NCell): - iCV = _cell_vector(StdI.Cell, iCell) + iCV = cell_vectors[iCell] nBox, iCellV = _fold_site_sub(StdI, iCV) nBox, iCellV = _fold_site(StdI, iCellV) iCell2 = _find_cell_index(StdI, iCellV) for jCell in range(StdI.NCell): - jCV = _cell_vector(StdI.Cell, jCell) + jCV = cell_vectors[jCell] jCellV = [jc + v - ic for jc, v, ic in zip(jCV, iCellV, iCV)] nBox, jCellV = _fold_site(StdI, jCellV) @@ -297,12 +307,6 @@ def generate_orb(StdI: StdIntList) -> None: anti_val = _parity_sign( _anti_period_dot(StdI.AntiPeriod, nBox_d)) - # Build list of (i_offset, j_offset) sector pairs - sectors = [(0, 0)] - if StdI.model == ModelType.KONDO: - half = StdI.nsite // 2 - sectors += [(half, 0), (0, half), (half, half)] - for isite in range(StdI.NsiteUC): for jsite in range(StdI.NsiteUC): for i_off, j_off in sectors: @@ -350,12 +354,21 @@ def _jastrow_momentum_projected( # (1) Copy Orbital index (vectorised) Jastrow[:, :] = StdI.Orb[:StdI.nsite, :StdI.nsite] - # (2) Symmetrize — for each orbital value in ascending order, - # mirror (i,j) -> (j,i). Sequential to preserve order semantics. + # (2) Symmetrize — build reverse map in one pass, then process + # each orbital in ascending order to preserve overwrite semantics. + from collections import defaultdict + nsite = StdI.nsite + orb_positions: dict[int, list[tuple[int, int]]] = defaultdict(list) + for r in range(nsite): + for c in range(nsite): + v = int(Jastrow[r, c]) + if 0 <= v < StdI.NOrb: + orb_positions[v].append((r, c)) + for iorb in range(StdI.NOrb): - rows, cols = np.where(Jastrow == iorb) - for r, c in zip(rows, cols): - Jastrow[c, r] = iorb + for r, c in orb_positions.get(iorb, ()): + if Jastrow[r, c] == iorb: + Jastrow[c, r] = iorb # (3) Exclude local-spin sites and renumber NJastrow = 0 if StdI.model == ModelType.HUBBARD else -1 @@ -409,8 +422,11 @@ def _jastrow_global_optimization( Jastrow[:half, :] = 0 NJastrow += 1 + # Pre-compute cell vectors to avoid repeated _cell_vector calls + cell_vectors = [_cell_vector(StdI.Cell, k) for k in range(StdI.NCell)] + for dCell in range(StdI.NCell): - dCV = _cell_vector(StdI.Cell, dCell) + dCV = cell_vectors[dCell] isite, jsite, Cphase, dR_arr = find_site( StdI, 0, 0, 0, -dCV[0], -dCV[1], -dCV[2], 0, 0) if StdI.model == ModelType.KONDO: @@ -419,16 +435,17 @@ def _jastrow_global_optimization( if iCell_j < dCell: continue reversal = 1 if iCell_j == dCell else 0 + dCV_is_zero = (dCV == [0, 0, 0]) for isiteUC in range(StdI.NsiteUC): for jsiteUC in range(StdI.NsiteUC): if reversal == 1 and jsiteUC > isiteUC: continue - if isiteUC == jsiteUC and dCV == [0, 0, 0]: + if isiteUC == jsiteUC and dCV_is_zero: continue for iCell_idx in range(StdI.NCell): - iCV = _cell_vector(StdI.Cell, iCell_idx) + iCV = cell_vectors[iCell_idx] i_s, j_s, _, _ = find_site( StdI, iCV[0], iCV[1], iCV[2], @@ -462,21 +479,24 @@ def print_jastrow(StdI: StdIntList) -> None: NJastrow = _jastrow_global_optimization(StdI, Jastrow) with open("jastrowidx.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NJastrowIdx {NJastrow:10d}\n") - fp.write(f"ComplexType {0:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") + lines = [ + "=============================================\n", + f"NJastrowIdx {NJastrow:10d}\n", + f"ComplexType {0:10d}\n", + "=============================================\n", + "=============================================\n", + ] for isite in range(StdI.nsite): for jsite in range(StdI.nsite): if isite == jsite: continue - fp.write(f"{isite:5d} {jsite:5d} {Jastrow[isite, jsite]:5d}\n") + lines.append(f"{isite:5d} {jsite:5d} {Jastrow[isite, jsite]:5d}\n") for iJastrow in range(NJastrow): if StdI.model == ModelType.HUBBARD or iJastrow > 0: - fp.write(f"{iJastrow:5d} {1:5d}\n") + lines.append(f"{iJastrow:5d} {1:5d}\n") else: - fp.write(f"{iJastrow:5d} {0:5d}\n") + lines.append(f"{iJastrow:5d} {0:5d}\n") + fp.write("".join(lines)) print(" jastrowidx.def is written.") diff --git a/python/stdface/solvers/mvmc/writer.py b/python/stdface/solvers/mvmc/writer.py index 389dafa..fa6c84e 100644 --- a/python/stdface/solvers/mvmc/writer.py +++ b/python/stdface/solvers/mvmc/writer.py @@ -70,26 +70,29 @@ def print_orb(StdI: StdIntList) -> None: Translated from the C function ``PrintOrb()`` in ``StdFace_main.c``. """ with open("orbitalidx.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NOrbitalIdx {StdI.NOrb:10d}\n") - fp.write(f"ComplexType {StdI.ComplexType:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") + lines = [ + "=============================================\n", + f"NOrbitalIdx {StdI.NOrb:10d}\n", + f"ComplexType {StdI.ComplexType:10d}\n", + "=============================================\n", + "=============================================\n", + ] has_anti = _has_anti_period(StdI) for isite in range(StdI.nsite): for jsite in range(StdI.nsite): if has_anti: - fp.write(f"{isite:5d} {jsite:5d} " - f"{StdI.Orb[isite][jsite]:5d} " - f"{StdI.AntiOrb[isite][jsite]:5d}\n") + lines.append(f"{isite:5d} {jsite:5d} " + f"{StdI.Orb[isite][jsite]:5d} " + f"{StdI.AntiOrb[isite][jsite]:5d}\n") else: - fp.write(f"{isite:5d} {jsite:5d} " - f"{StdI.Orb[isite][jsite]:5d}\n") + lines.append(f"{isite:5d} {jsite:5d} " + f"{StdI.Orb[isite][jsite]:5d}\n") for iOrb in range(StdI.NOrb): - fp.write(f"{iOrb:5d} {1:5d}\n") + lines.append(f"{iOrb:5d} {1:5d}\n") + fp.write("".join(lines)) print(" orbitalidx.def is written.") @@ -140,16 +143,22 @@ def _compute_parallel_orbitals( reverse = np.asarray(AntiOrb, dtype=int).copy() # (2) Symmetrise — process each orbital in ascending order. - # For each iorb, find all (i,j) with OrbGC[i,j]==iorb and set - # OrbGC[j,i]=iorb, reverse[j,i]=-reverse[i,j]. - # Order matters: later iorb can overwrite earlier iorb's writes. - # Process sequentially to preserve the original semantics where - # each (i,j) match writes to (j,i) immediately. + # Build a reverse map (value -> list of positions) in a single + # pass, then iterate orbitals in order. This replaces NOrb + # full-matrix scans with one scan + targeted updates. + from collections import defaultdict + orb_positions: dict[int, list[tuple[int, int]]] = defaultdict(list) + for r in range(nsite): + for c in range(nsite): + v = int(OrbGC[r, c]) + if 0 <= v < NOrb: + orb_positions[v].append((r, c)) + for iorb in range(NOrb): - rows, cols = np.where(OrbGC == iorb) - for r, c in zip(rows, cols): - OrbGC[c, r] = iorb - reverse[c, r] = -reverse[r, c] + for r, c in orb_positions.get(iorb, ()): + if OrbGC[r, c] == iorb: + OrbGC[c, r] = iorb + reverse[c, r] = -reverse[r, c] # (3) Renumber — lower triangle (isite > jsite). # Replace each newly-seen positive orbital value with a negative @@ -195,22 +204,23 @@ def _write_orbitalidxpara( Number of parallel orbital indices. """ with open("orbitalidxpara.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NOrbitalIdx {NOrbGC:10d}\n") - fp.write(f"ComplexType {ComplexType:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") + lines = [ + "=============================================\n", + f"NOrbitalIdx {NOrbGC:10d}\n", + f"ComplexType {ComplexType:10d}\n", + "=============================================\n", + "=============================================\n", + ] for isite in range(nsite): - for jsite in range(nsite): - if isite >= jsite: - continue - fp.write(f"{isite:5d} {jsite:5d} " - f"{OrbGC[isite][jsite]:5d} " - f"{reverse[isite][jsite]:5d}\n") + for jsite in range(isite + 1, nsite): + lines.append(f"{isite:5d} {jsite:5d} " + f"{OrbGC[isite][jsite]:5d} " + f"{reverse[isite][jsite]:5d}\n") for iOrbGC in range(NOrbGC): - fp.write(f"{iOrbGC:5d} {1:5d}\n") + lines.append(f"{iOrbGC:5d} {1:5d}\n") + fp.write("".join(lines)) def _write_orbitalidxgen( @@ -237,40 +247,41 @@ def _write_orbitalidxgen( has_anti = _has_anti_period(StdI) with open("orbitalidxgen.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NOrbitalIdx {StdI.NOrb + 2 * NOrbGC:10d}\n") - fp.write(f"ComplexType {StdI.ComplexType:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") + lines = [ + "=============================================\n", + f"NOrbitalIdx {StdI.NOrb + 2 * NOrbGC:10d}\n", + f"ComplexType {StdI.ComplexType:10d}\n", + "=============================================\n", + "=============================================\n", + ] # -- anti-parallel section -- for isite in range(nsite): for jsite in range(nsite): if has_anti: - fp.write(f"{isite:5d} 0 {jsite:5d} 1 " - f"{StdI.Orb[isite][jsite]:5d} " - f"{StdI.AntiOrb[isite][jsite]:5d}\n") + lines.append(f"{isite:5d} 0 {jsite:5d} 1 " + f"{StdI.Orb[isite][jsite]:5d} " + f"{StdI.AntiOrb[isite][jsite]:5d}\n") else: - fp.write(f"{isite:5d} 0 {jsite:5d} 1 " - f"{StdI.Orb[isite][jsite]:5d} {1:5d}\n") + lines.append(f"{isite:5d} 0 {jsite:5d} 1 " + f"{StdI.Orb[isite][jsite]:5d} {1:5d}\n") # -- parallel section (upper triangle) -- for isite in range(nsite): - for jsite in range(nsite): - if isite >= jsite: - continue - fp.write(f"{isite:5d} 0 {jsite:5d} 0 " - f"{OrbGC[isite][jsite] + StdI.NOrb:5d} " - f"{reverse[isite][jsite]:5d}\n") - fp.write(f"{isite:5d} 1 {jsite:5d} 1 " - f"{OrbGC[isite][jsite] + StdI.NOrb + NOrbGC:5d} " - f"{reverse[isite][jsite]:5d}\n") + for jsite in range(isite + 1, nsite): + lines.append(f"{isite:5d} 0 {jsite:5d} 0 " + f"{OrbGC[isite][jsite] + StdI.NOrb:5d} " + f"{reverse[isite][jsite]:5d}\n") + lines.append(f"{isite:5d} 1 {jsite:5d} 1 " + f"{OrbGC[isite][jsite] + StdI.NOrb + NOrbGC:5d} " + f"{reverse[isite][jsite]:5d}\n") for iOrbGC in range(StdI.NOrb): - fp.write(f"{iOrbGC:5d} {1:5d}\n") + lines.append(f"{iOrbGC:5d} {1:5d}\n") for iOrbGC in range(NOrbGC * 2): - fp.write(f"{iOrbGC + StdI.NOrb:5d} {1:5d}\n") + lines.append(f"{iOrbGC + StdI.NOrb:5d} {1:5d}\n") + fp.write("".join(lines)) def print_orb_para(StdI: StdIntList) -> None: @@ -419,18 +430,21 @@ def _write_gutzwiller_file( Per-site Gutzwiller index array. """ with open("gutzwilleridx.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NGutzwillerIdx {NGutzwiller:10d}\n") - fp.write(f"ComplexType {0:10d}\n") - fp.write("=============================================\n") - fp.write("=============================================\n") + lines = [ + "=============================================\n", + f"NGutzwillerIdx {NGutzwiller:10d}\n", + f"ComplexType {0:10d}\n", + "=============================================\n", + "=============================================\n", + ] for isite in range(StdI.nsite): - fp.write(f"{isite:5d} {Gutz[isite]:5d}\n") + lines.append(f"{isite:5d} {Gutz[isite]:5d}\n") for iGutz in range(NGutzwiller): flag = int(StdI.model == ModelType.HUBBARD or iGutz > 0) - fp.write(f"{iGutz:5d} {flag:5d}\n") + lines.append(f"{iGutz:5d} {flag:5d}\n") + fp.write("".join(lines)) def print_gutzwiller(StdI: StdIntList) -> None: diff --git a/python/stdface/writer/common_writer.py b/python/stdface/writer/common_writer.py index ff9bcb3..16523cf 100644 --- a/python/stdface/writer/common_writer.py +++ b/python/stdface/writer/common_writer.py @@ -513,6 +513,11 @@ def __init__( self.is_kondo = is_kondo self.is_mvmc = is_mvmc + # Pre-compute lookup tables to avoid per-call overhead in tight loops + self._spin_max = [1 if locspinflag[s] == 0 else locspinflag[s] + for s in range(nsite)] + self._is_local_spin = [locspinflag[s] != 0 for s in range(nsite)] + # ------------------------------------------------------------------ # Low-level helpers # ------------------------------------------------------------------ @@ -533,8 +538,7 @@ def spin_max(self, site: int) -> int: int Maximum spin index (inclusive upper bound). """ - flag = self.locspinflag[site] - return 1 if flag == 0 else flag + return self._spin_max[site] def skip_local_spin_pair(self, site_a: int, site_b: int) -> bool: """Return True if a pair of distinct local-spin sites should be skipped. @@ -552,8 +556,8 @@ def skip_local_spin_pair(self, site_a: int, site_b: int) -> bool: ``True`` if both sites are local-spin and distinct. """ return (site_a != site_b - and self.locspinflag[site_a] != 0 - and self.locspinflag[site_b] != 0) + and self._is_local_spin[site_a] + and self._is_local_spin[site_b]) def kondo_site(self, isite: int) -> int: """Map a Kondo unit-cell index to the physical site index. From ba61dcd1f3b48fd0a83fd8ea866c2b4fecf77d0d Mon Sep 17 00:00:00 2001 From: Kazuyoshi Yoshimi Date: Thu, 29 Jan 2026 20:52:23 +0900 Subject: [PATCH 07/16] Optimize HPhi writers, Green function generators, and interaction I/O - Buffer file I/O for HPhi excitation/pump writers and all interaction writers - Use list comprehensions and itertools.product for Green function index generation - Eliminate inner loop in green2_corr by computing spin4 directly from constraint - Vectorize Wannier90 inversion symmetry check with numpy broadcasting Co-Authored-By: Claude Opus 4.5 --- python/stdface/lattice/wannier90.py | 5 +- python/stdface/solvers/hphi/writer.py | 129 ++++++++-------- python/stdface/writer/common_writer.py | 158 ++++++++++---------- python/stdface/writer/interaction_writer.py | 58 +++---- 4 files changed, 176 insertions(+), 174 deletions(-) diff --git a/python/stdface/lattice/wannier90.py b/python/stdface/lattice/wannier90.py index b756760..26bf17a 100644 --- a/python/stdface/lattice/wannier90.py +++ b/python/stdface/lattice/wannier90.py @@ -348,9 +348,8 @@ def _read_w90( Mat_tot[iWSC, iWan0 - 1, jWan0 - 1] = lam * (dtmp_re + 1j * dtmp_im) # Apply inversion symmetry and delete duplication - for jWSC in range(iWSC): - if np.all(indx_tot[iWSC] == -indx_tot[jWSC]): - Mat_tot[iWSC, :, :] = 0.0 + if iWSC > 0 and np.any(np.all(indx_tot[iWSC] == -indx_tot[:iWSC], axis=1)): + Mat_tot[iWSC, :, :] = 0.0 if np.all(indx_tot[iWSC] == 0): for iWan in range(StdI.NsiteUC): diff --git a/python/stdface/solvers/hphi/writer.py b/python/stdface/solvers/hphi/writer.py index a4abdd2..e0a97b8 100644 --- a/python/stdface/solvers/hphi/writer.py +++ b/python/stdface/solvers/hphi/writer.py @@ -715,38 +715,40 @@ def _write_excitation_file( Imaginary parts of Fourier coefficients, length ``nsite``. """ if StdI.SpectrumBody == 1: - with open("single.def", "w") as fp: - fp.write("=============================================\n") - if StdI.model == ModelType.KONDO: - fp.write(f"NSingle {StdI.nsite // 2 * NumOp}\n") - else: - fp.write(f"NSingle {StdI.nsite * NumOp}\n") - fp.write("=============================================\n") - fp.write("============== Single Excitation ============\n") - fp.write("=============================================\n") - if StdI.model == ModelType.KONDO: - for isite in range(StdI.nsite // 2, StdI.nsite): - fp.write(f"{isite} {spin[0][0]} 0 " + lines = ["=============================================\n"] + if StdI.model == ModelType.KONDO: + lines.append(f"NSingle {StdI.nsite // 2 * NumOp}\n") + else: + lines.append(f"NSingle {StdI.nsite * NumOp}\n") + lines.append("=============================================\n") + lines.append("============== Single Excitation ============\n") + lines.append("=============================================\n") + if StdI.model == ModelType.KONDO: + for isite in range(StdI.nsite // 2, StdI.nsite): + lines.append(f"{isite} {spin[0][0]} 0 " f"{fourier_r[isite] * coef[0]:25.15f} " f"{fourier_i[isite] * coef[0]:25.15f}\n") - else: - for isite in range(StdI.nsite): - fp.write(f"{isite} {spin[0][0]} 0 " + else: + for isite in range(StdI.nsite): + lines.append(f"{isite} {spin[0][0]} 0 " f"{fourier_r[isite] * coef[0]:25.15f} " f"{fourier_i[isite] * coef[0]:25.15f}\n") + with open("single.def", "w") as fp: + fp.write("".join(lines)) print(" single.def is written.\n") else: - with open("pair.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"NPair {StdI.nsite * NumOp}\n") - fp.write("=============================================\n") - fp.write("=============== Pair Excitation =============\n") - fp.write("=============================================\n") - for isite in range(StdI.nsite): - for ispin in range(NumOp): - fp.write(f"{isite} {spin[ispin][0]} {isite} {spin[ispin][1]} 1 " + lines = ["=============================================\n", + f"NPair {StdI.nsite * NumOp}\n", + "=============================================\n", + "=============== Pair Excitation =============\n", + "=============================================\n"] + for isite in range(StdI.nsite): + for ispin in range(NumOp): + lines.append(f"{isite} {spin[ispin][0]} {isite} {spin[ispin][1]} 1 " f"{fourier_r[isite] * coef[ispin]:25.15f} " f"{fourier_i[isite] * coef[ispin]:25.15f}\n") + with open("pair.def", "w") as fp: + fp.write("".join(lines)) print(" pair.def is written.\n") @@ -1022,13 +1024,14 @@ def vector_potential(StdI: StdIntList) -> None: # Write potential.dat for one-body pump # ------------------------------------------------------------------ if StdI.PumpBody == 1: - with open("potential.dat", "w") as fp: - fp.write("# Time A_W A_L A_H E_W E_L E_H\n") - for it in range(StdI.Lanczos_max): - time = StdI.dt * float(it) - fp.write(f"{time:f} " + lines = ["# Time A_W A_L A_H E_W E_L E_H\n"] + for it in range(StdI.Lanczos_max): + time = StdI.dt * float(it) + lines.append(f"{time:f} " f"{StdI.At[it][0]:f} {StdI.At[it][1]:f} {StdI.At[it][2]:f} " f"{Et[it][0]:f} {Et[it][1]:f} {Et[it][2]:f}\n") + with open("potential.dat", "w") as fp: + fp.write("".join(lines)) def print_pump(StdI: StdIntList) -> None: @@ -1057,44 +1060,46 @@ def print_pump(StdI: StdIntList) -> None: - ``Uquench`` -- quench interaction strength. """ if StdI.PumpBody == 1: - with open("teone.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"AllTimeStep {StdI.Lanczos_max}\n") - fp.write("=============================================\n") - fp.write("========= OneBody Time Evolution ==========\n") - fp.write("=============================================\n") - - for it in range(StdI.Lanczos_max): - npump0 = _merge_duplicate_terms( - StdI.pumpindx[it], StdI.pump[it], StdI.npump[it]) - - fp.write(f"{StdI.dt * float(it):f} {npump0}\n") - - for ipump in range(StdI.npump[it]): - val = StdI.pump[it][ipump] - if abs(val) <= AMPLITUDE_EPS: - continue - i0, s0, i1, s1 = StdI.pumpindx[it][ipump] - fp.write( - f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " - f"{val.real:25.15f} {val.imag:25.15f}\n" - ) + lines = ["=============================================\n", + f"AllTimeStep {StdI.Lanczos_max}\n", + "=============================================\n", + "========= OneBody Time Evolution ==========\n", + "=============================================\n"] + for it in range(StdI.Lanczos_max): + npump0 = _merge_duplicate_terms( + StdI.pumpindx[it], StdI.pump[it], StdI.npump[it]) + + lines.append(f"{StdI.dt * float(it):f} {npump0}\n") + + for ipump in range(StdI.npump[it]): + val = StdI.pump[it][ipump] + if abs(val) <= AMPLITUDE_EPS: + continue + i0, s0, i1, s1 = StdI.pumpindx[it][ipump] + lines.append( + f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " + f"{val.real:25.15f} {val.imag:25.15f}\n" + ) + + with open("teone.def", "w") as fp: + fp.write("".join(lines)) print(" teone.def is written.\n") else: - with open("tetwo.def", "w") as fp: - fp.write("=============================================\n") - fp.write(f"AllTimeStep {StdI.Lanczos_max}\n") - fp.write("=============================================\n") - fp.write("========== TwoBody Time Evolution ===========\n") - fp.write("=============================================\n") - - for it in range(StdI.Lanczos_max): - fp.write(f"{StdI.dt * float(it):f} {StdI.nsite}\n") - for isite in range(StdI.nsite): - fp.write(f"{isite:5d} {0:5d} {isite:5d} {0:5d} " + lines = ["=============================================\n", + f"AllTimeStep {StdI.Lanczos_max}\n", + "=============================================\n", + "========== TwoBody Time Evolution ===========\n", + "=============================================\n"] + + for it in range(StdI.Lanczos_max): + lines.append(f"{StdI.dt * float(it):f} {StdI.nsite}\n") + for isite in range(StdI.nsite): + lines.append(f"{isite:5d} {0:5d} {isite:5d} {0:5d} " f"{isite:5d} {1:5d} {isite:5d} {1:5d} " f"{StdI.Uquench:25.15f} {0.0:25.15f}\n") + with open("tetwo.def", "w") as fp: + fp.write("".join(lines)) print(" tetwo.def is written.\n") diff --git a/python/stdface/writer/common_writer.py b/python/stdface/writer/common_writer.py index 16523cf..ce0fec8 100644 --- a/python/stdface/writer/common_writer.py +++ b/python/stdface/writer/common_writer.py @@ -44,6 +44,7 @@ from __future__ import annotations from collections.abc import Callable +from itertools import product from typing import NamedTuple import numpy as np @@ -145,14 +146,15 @@ def print_loc_spin(StdI: StdIntList) -> None: """ nlocspin = int(np.count_nonzero(StdI.locspinflag[:StdI.nsite])) + lines = ["================================ \n", + f"NlocalSpin {nlocspin:5d} \n", + "================================ \n", + "========i_1LocSpn_0IteElc ====== \n", + "================================ \n"] + for isite in range(StdI.nsite): + lines.append(f"{isite:5d} {StdI.locspinflag[isite]:5d}\n") with open("locspn.def", "w") as fp: - fp.write("================================ \n") - fp.write(f"NlocalSpin {nlocspin:5d} \n") - fp.write("================================ \n") - fp.write("========i_1LocSpn_0IteElc ====== \n") - fp.write("================================ \n") - for isite in range(StdI.nsite): - fp.write(f"{isite:5d} {StdI.locspinflag[isite]:5d}\n") + fp.write("".join(lines)) print(" locspn.def is written.") @@ -178,20 +180,21 @@ def print_trans(StdI: StdIntList) -> None: ntrans0 = _merge_duplicate_terms(StdI.transindx, StdI.trans, StdI.ntrans) # --- write file --- + lines = ["======================== \n", + f"NTransfer {ntrans0:7d} \n", + "======================== \n", + "========i_j_s_tijs====== \n", + "======================== \n"] + for ktrans in range(StdI.ntrans): + val = StdI.trans[ktrans] + if abs(val) > AMPLITUDE_EPS: + i0, s0, i1, s1 = StdI.transindx[ktrans] + lines.append( + f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " + f"{val.real:25.15f} {val.imag:25.15f}\n" + ) with open("trans.def", "w") as fp: - fp.write("======================== \n") - fp.write(f"NTransfer {ntrans0:7d} \n") - fp.write("======================== \n") - fp.write("========i_j_s_tijs====== \n") - fp.write("======================== \n") - for ktrans in range(StdI.ntrans): - val = StdI.trans[ktrans] - if abs(val) > AMPLITUDE_EPS: - i0, s0, i1, s1 = StdI.transindx[ktrans] - fp.write( - f"{i0:5d} {s0:5d} {i1:5d} {s1:5d} " - f"{val.real:25.15f} {val.imag:25.15f}\n" - ) + fp.write("".join(lines)) print(" trans.def is written.") @@ -593,17 +596,16 @@ def green1_corr(self) -> list[tuple[int, int, int, int]]: """ indices: list[tuple[int, int, int, int]] = [] xkondo = 2 if self.is_kondo else 1 + is_local = self._is_local_spin for isite in range(self.NsiteUC * xkondo): isite2 = self.kondo_site(isite) - - for ispin in range(self.spin_max(isite2) + 1): + for ispin in range(self._spin_max[isite2] + 1): for jsite in range(self.nsite): - for jspin in range(self.spin_max(jsite) + 1): - if self.skip_local_spin_pair(isite2, jsite): - continue - if ispin == jspin: - indices.append((isite2, ispin, jsite, jspin)) + if is_local[isite2] and is_local[jsite] and isite2 != jsite: + continue + if ispin <= self._spin_max[jsite]: + indices.append((isite2, ispin, jsite, ispin)) return indices @@ -618,16 +620,15 @@ def green1_raw(self) -> list[tuple[int, int, int, int]]: list of tuple[int, int, int, int] Index tuples ``(isite, ispin, jsite, jspin)``. """ - indices: list[tuple[int, int, int, int]] = [] - - for isite in range(self.nsite): - for ispin in range(self.spin_max(isite) + 1): - for jsite in range(self.nsite): - for jspin in range(self.spin_max(jsite) + 1): - if self.skip_local_spin_pair(isite, jsite): - continue - indices.append((isite, ispin, jsite, jspin)) - + site_spins = [(s, sp) for s in range(self.nsite) + for sp in range(self._spin_max[s] + 1)] + is_local = self._is_local_spin + indices: list[tuple[int, int, int, int]] = [ + (isite, ispin, jsite, jspin) + for isite, ispin in site_spins + for jsite, jspin in site_spins + if not (is_local[isite] and is_local[jsite] and isite != jsite) + ] return indices # ------------------------------------------------------------------ @@ -649,35 +650,39 @@ def green2_corr(self) -> list[tuple[int, int, int, int, int, int, int, int]]: """ indices: list[tuple[int, int, int, int, int, int, int, int]] = [] xkondo = 2 if self.is_kondo else 1 + is_mvmc = self.is_mvmc + spin_max = self._spin_max for site1 in range(self.NsiteUC * xkondo): site1k = self.kondo_site(site1) - S1Max = self.spin_max(site1k) + S1Max = spin_max[site1k] - for spin1 in range(S1Max + 1): - for spin2 in range(S1Max + 1): - for site3 in range(self.nsite): - S3Max = self.spin_max(site3) + for site3 in range(self.nsite): + S3Max = spin_max[site3] + for spin1 in range(S1Max + 1): + for spin2 in range(S1Max + 1): + # spin4 = spin1 - spin2 + spin3 for spin3 in range(S3Max + 1): - for spin4 in range(S3Max + 1): - if spin1 - spin2 + spin3 - spin4 == 0: - if self.is_mvmc and ( - spin1 != spin2 or spin3 != spin4 - ): - indices.append(( - site1k, spin1, - site3, spin4, - site3, spin3, - site1k, spin2, - )) - else: - indices.append(( - site1k, spin1, - site1k, spin2, - site3, spin3, - site3, spin4, - )) + spin4 = spin1 - spin2 + spin3 + if spin4 < 0 or spin4 > S3Max: + continue + if is_mvmc and ( + spin1 != spin2 or spin3 != spin4 + ): + indices.append(( + site1k, spin1, + site3, spin4, + site3, spin3, + site1k, spin2, + )) + else: + indices.append(( + site1k, spin1, + site1k, spin2, + site3, spin3, + site3, spin4, + )) return indices @@ -692,28 +697,19 @@ def green2_raw(self) -> list[tuple[int, int, int, int, int, int, int, int]]: list of tuple[int, int, int, int, int, int, int, int] Index tuples of 8 elements each. """ - indices: list[tuple[int, int, int, int, int, int, int, int]] = [] - - for site1 in range(self.nsite): - for spin1 in range(self.spin_max(site1) + 1): - for site2 in range(self.nsite): - if self.skip_local_spin_pair(site1, site2): - continue + site_spins = [(s, sp) for s in range(self.nsite) + for sp in range(self._spin_max[s] + 1)] + is_local = self._is_local_spin - for spin2 in range(self.spin_max(site2) + 1): - for site3 in range(self.nsite): - for spin3 in range(self.spin_max(site3) + 1): - for site4 in range(self.nsite): - if self.skip_local_spin_pair(site3, site4): - continue - - for spin4 in range(self.spin_max(site4) + 1): - indices.append(( - site1, spin1, - site2, spin2, - site3, spin3, - site4, spin4, - )) + # Precompute valid site pairs (no local-spin pair skip) + indices: list[tuple[int, int, int, int, int, int, int, int]] = [] + for (s1, sp1), (s2, sp2) in product(site_spins, repeat=2): + if is_local[s1] and is_local[s2] and s1 != s2: + continue + for (s3, sp3), (s4, sp4) in product(site_spins, repeat=2): + if is_local[s3] and is_local[s4] and s3 != s4: + continue + indices.append((s1, sp1, s2, sp2, s3, sp3, s4, sp4)) return indices diff --git a/python/stdface/writer/interaction_writer.py b/python/stdface/writer/interaction_writer.py index 39d9812..be32f74 100644 --- a/python/stdface/writer/interaction_writer.py +++ b/python/stdface/writer/interaction_writer.py @@ -134,16 +134,17 @@ def _write_interaction_file( Number of site indices per term (1 or 2). """ nintr0 = _count_nonzero(nterms, coeff) + lines = ["=============================================\n", + f"{count_label} {nintr0:10d}\n", + "=============================================\n", + f"{banner}\n", + "=============================================\n"] + for k in range(nterms): + if abs(coeff[k]) > AMPLITUDE_EPS: + idx_str = " ".join(f"{indx[k][i]:5d}" for i in range(n_indices)) + lines.append(f"{idx_str} {coeff[k]:25.15f}\n") with open(filename, "w") as fp: - fp.write("=============================================\n") - fp.write(f"{count_label} {nintr0:10d}\n") - fp.write("=============================================\n") - fp.write(f"{banner}\n") - fp.write("=============================================\n") - for k in range(nterms): - if abs(coeff[k]) > AMPLITUDE_EPS: - idx_str = " ".join(f"{indx[k][i]:5d}" for i in range(n_indices)) - fp.write(f"{idx_str} {coeff[k]:25.15f}\n") + fp.write("".join(lines)) print(f" {filename} is written.") @@ -472,26 +473,27 @@ def _write_interall(StdI: StdIntList) -> None: StdI.Lintr = 1 if StdI.Lintr == 1: - with open("interall.def", "w") as fp: - fp.write("====================== \n") - fp.write(f"NInterAll {nintr0:7d} \n") - fp.write("====================== \n") - fp.write("========zInterAll===== \n") - fp.write("====================== \n") - - if StdI.lBoost == 0: - for kintr in range(StdI.nintr): - val = StdI.intr[kintr] - if abs(val) > AMPLITUDE_EPS: - i0, s0, i1, s1, i2, s2, i3, s3 = StdI.intrindx[kintr] - fp.write( - f"{i0:5d} {s0:5d} " - f"{i1:5d} {s1:5d} " - f"{i2:5d} {s2:5d} " - f"{i3:5d} {s3:5d} " - f"{val.real:25.15f} {val.imag:25.15f}\n" - ) + lines = ["====================== \n", + f"NInterAll {nintr0:7d} \n", + "====================== \n", + "========zInterAll===== \n", + "====================== \n"] + + if StdI.lBoost == 0: + for kintr in range(StdI.nintr): + val = StdI.intr[kintr] + if abs(val) > AMPLITUDE_EPS: + i0, s0, i1, s1, i2, s2, i3, s3 = StdI.intrindx[kintr] + lines.append( + f"{i0:5d} {s0:5d} " + f"{i1:5d} {s1:5d} " + f"{i2:5d} {s2:5d} " + f"{i3:5d} {s3:5d} " + f"{val.real:25.15f} {val.imag:25.15f}\n" + ) + with open("interall.def", "w") as fp: + fp.write("".join(lines)) print(" interall.def is written.") From 1437f12073992433b8e13b20ecd17785ec2e0b87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:08:32 +0000 Subject: [PATCH 08/16] Initial plan From 0dc2106a58a380ddae35251881456654aedab882 Mon Sep 17 00:00:00 2001 From: Yoshimi Date: Thu, 29 Jan 2026 21:08:46 +0900 Subject: [PATCH 09/16] Update docs/tutorial_plugin.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/tutorial_plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial_plugin.md b/docs/tutorial_plugin.md index ae41440..8172f4c 100644 --- a/docs/tutorial_plugin.md +++ b/docs/tutorial_plugin.md @@ -1,6 +1,6 @@ # Plugin Tutorial: Adding New Solvers and Lattices -StdFace uses a plugin architecture where solvers and lattices are self-registering modules. This tutorial explains how to add a new solver or lattice without modifying any existing code. +StdFace uses a plugin architecture where solvers and lattices are self-registering modules. This tutorial explains how to add a new solver or lattice by creating a new plugin module and wiring it into the existing discovery mechanism. ## Table of Contents From fbfc871e8dc95aa5c2eeb4d3f5f20ae4d627eec5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:09:14 +0000 Subject: [PATCH 10/16] Initial plan From a91d40620eb780702002cc765721ab636a598eff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:09:30 +0000 Subject: [PATCH 11/16] Initial plan From 650dc2c60e6cc55d5652edb5123e43d6261fedcc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:09:37 +0000 Subject: [PATCH 12/16] Initial plan From 7d2f5a52cce4976d2aade92dead3efdc275300ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:11:22 +0000 Subject: [PATCH 13/16] Remove unused NaN_d import from test_stdface_main_helpers.py Co-authored-by: k-yoshimi <4516893+k-yoshimi@users.noreply.github.com> --- python/stdface.egg-info/PKG-INFO | 260 +++++++++++++++++++ python/stdface.egg-info/SOURCES.txt | 50 ++++ python/stdface.egg-info/dependency_links.txt | 1 + python/stdface.egg-info/entry_points.txt | 20 ++ python/stdface.egg-info/requires.txt | 5 + python/stdface.egg-info/top_level.txt | 1 + test/unit/test_stdface_main_helpers.py | 2 +- 7 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 python/stdface.egg-info/PKG-INFO create mode 100644 python/stdface.egg-info/SOURCES.txt create mode 100644 python/stdface.egg-info/dependency_links.txt create mode 100644 python/stdface.egg-info/entry_points.txt create mode 100644 python/stdface.egg-info/requires.txt create mode 100644 python/stdface.egg-info/top_level.txt diff --git a/python/stdface.egg-info/PKG-INFO b/python/stdface.egg-info/PKG-INFO new file mode 100644 index 0000000..3b8684d --- /dev/null +++ b/python/stdface.egg-info/PKG-INFO @@ -0,0 +1,260 @@ +Metadata-Version: 2.4 +Name: stdface +Version: 0.5.0 +Summary: Standard-mode input generator for HPhi / mVMC / UHF / H-wave +License-Expression: GPL-3.0-or-later +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +Requires-Dist: numpy +Provides-Extra: dev +Requires-Dist: pytest; extra == "dev" +Requires-Dist: pytest-cov; extra == "dev" + +# StdFace (Python) + +The `python/` directory contains a Python port of the C-based StdFace input generator. +It reads the same input files as the C version and produces solver-specific definition files +for HPhi, mVMC, UHF, and H-wave. + +## Requirements + +- Python 3.10 or later +- NumPy + +## Installation + +```bash +cd python +pip install -e . # install with runtime dependencies (numpy) +pip install -e ".[dev]" # also install dev dependencies (pytest, pytest-cov) +``` + +After installation, the `stdface` command becomes available: + +```bash +stdface stan.in +stdface stan.in --solver mVMC +stdface -v +``` + +The editable install (`-e`) is recommended for development. Source changes take effect immediately without reinstalling. + +## Architecture + +StdFace uses a **plugin architecture** for both solvers and lattices. +New solvers and lattices can be added without modifying any existing dispatch logic. + +### Plugin System Overview + +``` +stdface/ + plugin.py # SolverPlugin ABC + solver registry + lattice/__init__.py # LatticePlugin ABC + lattice registry +``` + +- **SolverPlugin** (`plugin.py`): Defines how a solver parses keywords, resets fields, and writes output files. Each solver (HPhi, mVMC, UHF, H-wave) is a plugin registered at import time. +- **LatticePlugin** (`lattice/__init__.py`): Defines lattice geometry, aliases, and the setup/boost methods. Each lattice (chain, square, kagome, etc.) is a plugin registered at import time. + +See [docs/tutorial_plugin.md](../docs/tutorial_plugin.md) for a step-by-step guide on adding new solvers and lattices. + +## Directory Structure + +``` +python/ + __main__.py # CLI entry point (port of dry.c) + stdface/ + plugin.py # SolverPlugin ABC + solver registry + core/ + stdface_main.py # Main logic (port of StdFace_main.c) + stdface_vals.py # StdIntList dataclass (port of StdFace_vals.h) + keyword_parser.py # Keyword parsing subsystem + param_check.py # Parameter validation utilities + lattice/ # Lattice plugins + __init__.py # LatticePlugin ABC + lattice registry + chain_lattice.py # 1D chain (ChainPlugin) + square_lattice.py # 2D square (SquarePlugin) + ladder.py # 2-leg ladder (LadderPlugin) + triangular_lattice.py # 2D triangular (TriangularPlugin) + honeycomb_lattice.py # 2D honeycomb (HoneycombPlugin) + kagome.py # 2D kagome (KagomePlugin) + orthorhombic.py # 3D orthorhombic (OrthorhombicPlugin) + fc_ortho.py # 3D face-centered orthorhombic (FCOrthoPlugin) + pyrochlore.py # 3D pyrochlore (PyrochlorePlugin) + wannier90.py # Wannier90 interface (Wannier90Plugin) + boost_output.py # Boost output utilities + geometry_output.py # Geometry output functions + input_params.py # Input parameter resolution + interaction_builder.py # Interaction building utilities + site_util.py # Site utility functions + solvers/ # Solver plugins + __init__.py # Auto-imports all solver plugins + hphi/_plugin.py # HPhi plugin (HPhiPlugin) + mvmc/_plugin.py # mVMC plugin (MVMCPlugin) + uhf/_plugin.py # UHF plugin (UHFPlugin) + hwave/_plugin.py # H-wave plugin (HWavePlugin) + writer/ # Output writers (shared) + common_writer.py # Common output functions + interaction_writer.py # Interaction file writer + export_wannier90.py # Wannier90 format export + history/ + refactoring_log.md # Refactoring change log +``` + +## Usage + +### Command Line + +From the project root: + +```bash +./stdface stan.in +``` + +To select a solver: + +```bash +./stdface stan.in --solver mVMC +./stdface stan.in --solver UHF +./stdface stan.in --solver HWAVE +``` + +The default solver is HPhi. + +### Print Version + +```bash +./stdface -v +``` + +### Calling from Python + +```python +import sys +sys.path.insert(0, "python") + +from stdface.core.stdface_main import stdface_main + +stdface_main("stan.in", solver="HPhi") +``` + +## Input File Examples + +Parameters are specified in `stan.in` using the `key = value` format. + +### Hubbard Model (Square Lattice) + +```text +model = Hubbard +lattice = square +W = 2 +L = 2 +t = 1.0 +U = 4.0 +nelec = 4 +2Sz = 0 +``` + +### Spin Model (Kagome Lattice) + +```text +model = Spin +lattice = kagome +W = 2 +L = 2 +J = 1.0 +2S = 1 +2Sz = 0 +``` + +### Kondo Lattice Model (1D Chain) + +```text +model = Kondo +lattice = chain +L = 4 +t = 1.0 +U = 4.0 +J = 0.8 +nelec = 4 +2Sz = 0 +``` + +## Running Tests + +### Unit Tests + +```bash +# Run all unit tests +python3 -m pytest test/unit/ -v + +# Run with coverage +python3 -m pytest test/unit/ --cov=python --cov-report=html +``` + +**Test Coverage:** +- 1,268 unit tests covering all modules +- Tests for lattice implementations, writers, parsers, plugin registries, and utilities +- All tests maintain byte-identical output with C version + +### Integration Tests + +**Run all integration tests:** +```bash +bash test/run_all_integration.sh +``` + +**Single test case:** +```bash +# Copy test input to a working directory and run +cp test/hphi/lanczos_hubbard_square/stan.in /tmp/work/ +cd /tmp/work +/path/to/StdFace/stdface stan.in + +# Compare outputs against reference +diff /path/to/StdFace/test/hphi/lanczos_hubbard_square/ref/modpara.def modpara.def +``` + +**Integration Test Results:** +- 83/83 integration tests passing +- All solvers (HPhi, mVMC, UHF, H-wave) verified +- Byte-identical output confirmed for all test cases + +## Code Quality + +The Python implementation has been refactored from a direct C translation into idiomatic Python: + +- **Plugin architecture**: Solvers and lattices are self-registering plugins +- **Modular design**: Code organized into logical subpackages (`lattice/`, `solvers/`, `writer/`) +- **Python idioms**: Enums, ABC, context managers, type hints +- **Reduced duplication**: Helper functions extracted to eliminate code duplication +- **Comprehensive testing**: 1,268 unit tests with full coverage +- **Documentation**: NumPy-style docstrings throughout + +See `history/refactoring_log.md` for detailed refactoring history. + +## Mapping to C Sources + +| C Source File | Python Module | +|---|---| +| `dry.c` | `__main__.py` | +| `StdFace_main.c` | `stdface/core/stdface_main.py` | +| `StdFace_vals.h` | `stdface/core/stdface_vals.py` | +| `StdFace_ModelUtil.c/h` | `stdface/core/stdface_model_util.py` | +| `version.h` | `stdface/version.py` | +| `ChainLattice.c` | `stdface/lattice/chain_lattice.py` | +| `SquareLattice.c` | `stdface/lattice/square_lattice.py` | +| `Ladder.c` | `stdface/lattice/ladder.py` | +| `TriangularLattice.c` | `stdface/lattice/triangular_lattice.py` | +| `HoneycombLattice.c` | `stdface/lattice/honeycomb_lattice.py` | +| `Kagome.c` | `stdface/lattice/kagome.py` | +| `Orthorhombic.c` | `stdface/lattice/orthorhombic.py` | +| `FCOrtho.c` | `stdface/lattice/fc_ortho.py` | +| `Pyrochlore.c` | `stdface/lattice/pyrochlore.py` | +| `Wannier90.c` | `stdface/lattice/wannier90.py` | +| `export_wannier90.c` | `stdface/writer/export_wannier90.py` | + +Note: The Python implementation has been significantly refactored beyond the original C structure for better maintainability and Pythonic code style. + +## License + +GNU General Public License v3 (GPLv3) diff --git a/python/stdface.egg-info/SOURCES.txt b/python/stdface.egg-info/SOURCES.txt new file mode 100644 index 0000000..2cfcc2a --- /dev/null +++ b/python/stdface.egg-info/SOURCES.txt @@ -0,0 +1,50 @@ +README.md +pyproject.toml +stdface/__init__.py +stdface/__main__.py +stdface/plugin.py +stdface.egg-info/PKG-INFO +stdface.egg-info/SOURCES.txt +stdface.egg-info/dependency_links.txt +stdface.egg-info/entry_points.txt +stdface.egg-info/requires.txt +stdface.egg-info/top_level.txt +stdface/core/__init__.py +stdface/core/keyword_parser.py +stdface/core/param_check.py +stdface/core/stdface_main.py +stdface/core/stdface_model_util.py +stdface/core/stdface_vals.py +stdface/core/version.py +stdface/lattice/__init__.py +stdface/lattice/boost_output.py +stdface/lattice/chain_lattice.py +stdface/lattice/fc_ortho.py +stdface/lattice/geometry_output.py +stdface/lattice/honeycomb_lattice.py +stdface/lattice/input_params.py +stdface/lattice/interaction_builder.py +stdface/lattice/kagome.py +stdface/lattice/ladder.py +stdface/lattice/orthorhombic.py +stdface/lattice/pyrochlore.py +stdface/lattice/site_util.py +stdface/lattice/square_lattice.py +stdface/lattice/triangular_lattice.py +stdface/lattice/wannier90.py +stdface/solvers/__init__.py +stdface/solvers/hphi/__init__.py +stdface/solvers/hphi/_plugin.py +stdface/solvers/hphi/writer.py +stdface/solvers/hwave/__init__.py +stdface/solvers/hwave/_plugin.py +stdface/solvers/hwave/export_wannier90.py +stdface/solvers/mvmc/__init__.py +stdface/solvers/mvmc/_plugin.py +stdface/solvers/mvmc/variational.py +stdface/solvers/mvmc/writer.py +stdface/solvers/uhf/__init__.py +stdface/solvers/uhf/_plugin.py +stdface/writer/__init__.py +stdface/writer/common_writer.py +stdface/writer/interaction_writer.py \ No newline at end of file diff --git a/python/stdface.egg-info/dependency_links.txt b/python/stdface.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/python/stdface.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/python/stdface.egg-info/entry_points.txt b/python/stdface.egg-info/entry_points.txt new file mode 100644 index 0000000..a44b744 --- /dev/null +++ b/python/stdface.egg-info/entry_points.txt @@ -0,0 +1,20 @@ +[console_scripts] +stdface = stdface.__main__:main + +[stdface.lattices] +chain = stdface.lattice.chain_lattice:ChainPlugin +fc_ortho = stdface.lattice.fc_ortho:FCOrthoPlugin +honeycomb = stdface.lattice.honeycomb_lattice:HoneycombPlugin +kagome = stdface.lattice.kagome:KagomePlugin +ladder = stdface.lattice.ladder:LadderPlugin +orthorhombic = stdface.lattice.orthorhombic:OrthorhombicPlugin +pyrochlore = stdface.lattice.pyrochlore:PyrochlorePlugin +square = stdface.lattice.square_lattice:SquarePlugin +triangular = stdface.lattice.triangular_lattice:TriangularPlugin +wannier90 = stdface.lattice.wannier90:Wannier90Plugin + +[stdface.solvers] +hphi = stdface.solvers.hphi:HPhiPlugin +hwave = stdface.solvers.hwave:HWavePlugin +mvmc = stdface.solvers.mvmc:MVMCPlugin +uhf = stdface.solvers.uhf:UHFPlugin diff --git a/python/stdface.egg-info/requires.txt b/python/stdface.egg-info/requires.txt new file mode 100644 index 0000000..2f01eef --- /dev/null +++ b/python/stdface.egg-info/requires.txt @@ -0,0 +1,5 @@ +numpy + +[dev] +pytest +pytest-cov diff --git a/python/stdface.egg-info/top_level.txt b/python/stdface.egg-info/top_level.txt new file mode 100644 index 0000000..1bbec28 --- /dev/null +++ b/python/stdface.egg-info/top_level.txt @@ -0,0 +1 @@ +stdface diff --git a/test/unit/test_stdface_main_helpers.py b/test/unit/test_stdface_main_helpers.py index 55f5564..85c8b4c 100644 --- a/test/unit/test_stdface_main_helpers.py +++ b/test/unit/test_stdface_main_helpers.py @@ -8,7 +8,7 @@ import pytest -from stdface.core.stdface_vals import StdIntList, ModelType, SolverType, MethodType, NaN_d +from stdface.core.stdface_vals import StdIntList, ModelType, SolverType, MethodType from stdface.core.stdface_main import _parse_input_file, _resolve_model_and_method From 1d39de2dae3ea296914003107b909ff7a11fdc44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:11:41 +0000 Subject: [PATCH 14/16] Remove build artifacts and update .gitignore Co-authored-by: k-yoshimi <4516893+k-yoshimi@users.noreply.github.com> --- .gitignore | 5 + python/stdface.egg-info/PKG-INFO | 260 ------------------- python/stdface.egg-info/SOURCES.txt | 50 ---- python/stdface.egg-info/dependency_links.txt | 1 - python/stdface.egg-info/entry_points.txt | 20 -- python/stdface.egg-info/requires.txt | 5 - python/stdface.egg-info/top_level.txt | 1 - 7 files changed, 5 insertions(+), 337 deletions(-) delete mode 100644 python/stdface.egg-info/PKG-INFO delete mode 100644 python/stdface.egg-info/SOURCES.txt delete mode 100644 python/stdface.egg-info/dependency_links.txt delete mode 100644 python/stdface.egg-info/entry_points.txt delete mode 100644 python/stdface.egg-info/requires.txt delete mode 100644 python/stdface.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore index 525619c..bc61e16 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,11 @@ cmake-build-debug/ __pycache__/ *.pyc +# Python build artifacts +*.egg-info/ +dist/ +*.egg + # Build Output build/ diff --git a/python/stdface.egg-info/PKG-INFO b/python/stdface.egg-info/PKG-INFO deleted file mode 100644 index 3b8684d..0000000 --- a/python/stdface.egg-info/PKG-INFO +++ /dev/null @@ -1,260 +0,0 @@ -Metadata-Version: 2.4 -Name: stdface -Version: 0.5.0 -Summary: Standard-mode input generator for HPhi / mVMC / UHF / H-wave -License-Expression: GPL-3.0-or-later -Requires-Python: >=3.10 -Description-Content-Type: text/markdown -Requires-Dist: numpy -Provides-Extra: dev -Requires-Dist: pytest; extra == "dev" -Requires-Dist: pytest-cov; extra == "dev" - -# StdFace (Python) - -The `python/` directory contains a Python port of the C-based StdFace input generator. -It reads the same input files as the C version and produces solver-specific definition files -for HPhi, mVMC, UHF, and H-wave. - -## Requirements - -- Python 3.10 or later -- NumPy - -## Installation - -```bash -cd python -pip install -e . # install with runtime dependencies (numpy) -pip install -e ".[dev]" # also install dev dependencies (pytest, pytest-cov) -``` - -After installation, the `stdface` command becomes available: - -```bash -stdface stan.in -stdface stan.in --solver mVMC -stdface -v -``` - -The editable install (`-e`) is recommended for development. Source changes take effect immediately without reinstalling. - -## Architecture - -StdFace uses a **plugin architecture** for both solvers and lattices. -New solvers and lattices can be added without modifying any existing dispatch logic. - -### Plugin System Overview - -``` -stdface/ - plugin.py # SolverPlugin ABC + solver registry - lattice/__init__.py # LatticePlugin ABC + lattice registry -``` - -- **SolverPlugin** (`plugin.py`): Defines how a solver parses keywords, resets fields, and writes output files. Each solver (HPhi, mVMC, UHF, H-wave) is a plugin registered at import time. -- **LatticePlugin** (`lattice/__init__.py`): Defines lattice geometry, aliases, and the setup/boost methods. Each lattice (chain, square, kagome, etc.) is a plugin registered at import time. - -See [docs/tutorial_plugin.md](../docs/tutorial_plugin.md) for a step-by-step guide on adding new solvers and lattices. - -## Directory Structure - -``` -python/ - __main__.py # CLI entry point (port of dry.c) - stdface/ - plugin.py # SolverPlugin ABC + solver registry - core/ - stdface_main.py # Main logic (port of StdFace_main.c) - stdface_vals.py # StdIntList dataclass (port of StdFace_vals.h) - keyword_parser.py # Keyword parsing subsystem - param_check.py # Parameter validation utilities - lattice/ # Lattice plugins - __init__.py # LatticePlugin ABC + lattice registry - chain_lattice.py # 1D chain (ChainPlugin) - square_lattice.py # 2D square (SquarePlugin) - ladder.py # 2-leg ladder (LadderPlugin) - triangular_lattice.py # 2D triangular (TriangularPlugin) - honeycomb_lattice.py # 2D honeycomb (HoneycombPlugin) - kagome.py # 2D kagome (KagomePlugin) - orthorhombic.py # 3D orthorhombic (OrthorhombicPlugin) - fc_ortho.py # 3D face-centered orthorhombic (FCOrthoPlugin) - pyrochlore.py # 3D pyrochlore (PyrochlorePlugin) - wannier90.py # Wannier90 interface (Wannier90Plugin) - boost_output.py # Boost output utilities - geometry_output.py # Geometry output functions - input_params.py # Input parameter resolution - interaction_builder.py # Interaction building utilities - site_util.py # Site utility functions - solvers/ # Solver plugins - __init__.py # Auto-imports all solver plugins - hphi/_plugin.py # HPhi plugin (HPhiPlugin) - mvmc/_plugin.py # mVMC plugin (MVMCPlugin) - uhf/_plugin.py # UHF plugin (UHFPlugin) - hwave/_plugin.py # H-wave plugin (HWavePlugin) - writer/ # Output writers (shared) - common_writer.py # Common output functions - interaction_writer.py # Interaction file writer - export_wannier90.py # Wannier90 format export - history/ - refactoring_log.md # Refactoring change log -``` - -## Usage - -### Command Line - -From the project root: - -```bash -./stdface stan.in -``` - -To select a solver: - -```bash -./stdface stan.in --solver mVMC -./stdface stan.in --solver UHF -./stdface stan.in --solver HWAVE -``` - -The default solver is HPhi. - -### Print Version - -```bash -./stdface -v -``` - -### Calling from Python - -```python -import sys -sys.path.insert(0, "python") - -from stdface.core.stdface_main import stdface_main - -stdface_main("stan.in", solver="HPhi") -``` - -## Input File Examples - -Parameters are specified in `stan.in` using the `key = value` format. - -### Hubbard Model (Square Lattice) - -```text -model = Hubbard -lattice = square -W = 2 -L = 2 -t = 1.0 -U = 4.0 -nelec = 4 -2Sz = 0 -``` - -### Spin Model (Kagome Lattice) - -```text -model = Spin -lattice = kagome -W = 2 -L = 2 -J = 1.0 -2S = 1 -2Sz = 0 -``` - -### Kondo Lattice Model (1D Chain) - -```text -model = Kondo -lattice = chain -L = 4 -t = 1.0 -U = 4.0 -J = 0.8 -nelec = 4 -2Sz = 0 -``` - -## Running Tests - -### Unit Tests - -```bash -# Run all unit tests -python3 -m pytest test/unit/ -v - -# Run with coverage -python3 -m pytest test/unit/ --cov=python --cov-report=html -``` - -**Test Coverage:** -- 1,268 unit tests covering all modules -- Tests for lattice implementations, writers, parsers, plugin registries, and utilities -- All tests maintain byte-identical output with C version - -### Integration Tests - -**Run all integration tests:** -```bash -bash test/run_all_integration.sh -``` - -**Single test case:** -```bash -# Copy test input to a working directory and run -cp test/hphi/lanczos_hubbard_square/stan.in /tmp/work/ -cd /tmp/work -/path/to/StdFace/stdface stan.in - -# Compare outputs against reference -diff /path/to/StdFace/test/hphi/lanczos_hubbard_square/ref/modpara.def modpara.def -``` - -**Integration Test Results:** -- 83/83 integration tests passing -- All solvers (HPhi, mVMC, UHF, H-wave) verified -- Byte-identical output confirmed for all test cases - -## Code Quality - -The Python implementation has been refactored from a direct C translation into idiomatic Python: - -- **Plugin architecture**: Solvers and lattices are self-registering plugins -- **Modular design**: Code organized into logical subpackages (`lattice/`, `solvers/`, `writer/`) -- **Python idioms**: Enums, ABC, context managers, type hints -- **Reduced duplication**: Helper functions extracted to eliminate code duplication -- **Comprehensive testing**: 1,268 unit tests with full coverage -- **Documentation**: NumPy-style docstrings throughout - -See `history/refactoring_log.md` for detailed refactoring history. - -## Mapping to C Sources - -| C Source File | Python Module | -|---|---| -| `dry.c` | `__main__.py` | -| `StdFace_main.c` | `stdface/core/stdface_main.py` | -| `StdFace_vals.h` | `stdface/core/stdface_vals.py` | -| `StdFace_ModelUtil.c/h` | `stdface/core/stdface_model_util.py` | -| `version.h` | `stdface/version.py` | -| `ChainLattice.c` | `stdface/lattice/chain_lattice.py` | -| `SquareLattice.c` | `stdface/lattice/square_lattice.py` | -| `Ladder.c` | `stdface/lattice/ladder.py` | -| `TriangularLattice.c` | `stdface/lattice/triangular_lattice.py` | -| `HoneycombLattice.c` | `stdface/lattice/honeycomb_lattice.py` | -| `Kagome.c` | `stdface/lattice/kagome.py` | -| `Orthorhombic.c` | `stdface/lattice/orthorhombic.py` | -| `FCOrtho.c` | `stdface/lattice/fc_ortho.py` | -| `Pyrochlore.c` | `stdface/lattice/pyrochlore.py` | -| `Wannier90.c` | `stdface/lattice/wannier90.py` | -| `export_wannier90.c` | `stdface/writer/export_wannier90.py` | - -Note: The Python implementation has been significantly refactored beyond the original C structure for better maintainability and Pythonic code style. - -## License - -GNU General Public License v3 (GPLv3) diff --git a/python/stdface.egg-info/SOURCES.txt b/python/stdface.egg-info/SOURCES.txt deleted file mode 100644 index 2cfcc2a..0000000 --- a/python/stdface.egg-info/SOURCES.txt +++ /dev/null @@ -1,50 +0,0 @@ -README.md -pyproject.toml -stdface/__init__.py -stdface/__main__.py -stdface/plugin.py -stdface.egg-info/PKG-INFO -stdface.egg-info/SOURCES.txt -stdface.egg-info/dependency_links.txt -stdface.egg-info/entry_points.txt -stdface.egg-info/requires.txt -stdface.egg-info/top_level.txt -stdface/core/__init__.py -stdface/core/keyword_parser.py -stdface/core/param_check.py -stdface/core/stdface_main.py -stdface/core/stdface_model_util.py -stdface/core/stdface_vals.py -stdface/core/version.py -stdface/lattice/__init__.py -stdface/lattice/boost_output.py -stdface/lattice/chain_lattice.py -stdface/lattice/fc_ortho.py -stdface/lattice/geometry_output.py -stdface/lattice/honeycomb_lattice.py -stdface/lattice/input_params.py -stdface/lattice/interaction_builder.py -stdface/lattice/kagome.py -stdface/lattice/ladder.py -stdface/lattice/orthorhombic.py -stdface/lattice/pyrochlore.py -stdface/lattice/site_util.py -stdface/lattice/square_lattice.py -stdface/lattice/triangular_lattice.py -stdface/lattice/wannier90.py -stdface/solvers/__init__.py -stdface/solvers/hphi/__init__.py -stdface/solvers/hphi/_plugin.py -stdface/solvers/hphi/writer.py -stdface/solvers/hwave/__init__.py -stdface/solvers/hwave/_plugin.py -stdface/solvers/hwave/export_wannier90.py -stdface/solvers/mvmc/__init__.py -stdface/solvers/mvmc/_plugin.py -stdface/solvers/mvmc/variational.py -stdface/solvers/mvmc/writer.py -stdface/solvers/uhf/__init__.py -stdface/solvers/uhf/_plugin.py -stdface/writer/__init__.py -stdface/writer/common_writer.py -stdface/writer/interaction_writer.py \ No newline at end of file diff --git a/python/stdface.egg-info/dependency_links.txt b/python/stdface.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/python/stdface.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/python/stdface.egg-info/entry_points.txt b/python/stdface.egg-info/entry_points.txt deleted file mode 100644 index a44b744..0000000 --- a/python/stdface.egg-info/entry_points.txt +++ /dev/null @@ -1,20 +0,0 @@ -[console_scripts] -stdface = stdface.__main__:main - -[stdface.lattices] -chain = stdface.lattice.chain_lattice:ChainPlugin -fc_ortho = stdface.lattice.fc_ortho:FCOrthoPlugin -honeycomb = stdface.lattice.honeycomb_lattice:HoneycombPlugin -kagome = stdface.lattice.kagome:KagomePlugin -ladder = stdface.lattice.ladder:LadderPlugin -orthorhombic = stdface.lattice.orthorhombic:OrthorhombicPlugin -pyrochlore = stdface.lattice.pyrochlore:PyrochlorePlugin -square = stdface.lattice.square_lattice:SquarePlugin -triangular = stdface.lattice.triangular_lattice:TriangularPlugin -wannier90 = stdface.lattice.wannier90:Wannier90Plugin - -[stdface.solvers] -hphi = stdface.solvers.hphi:HPhiPlugin -hwave = stdface.solvers.hwave:HWavePlugin -mvmc = stdface.solvers.mvmc:MVMCPlugin -uhf = stdface.solvers.uhf:UHFPlugin diff --git a/python/stdface.egg-info/requires.txt b/python/stdface.egg-info/requires.txt deleted file mode 100644 index 2f01eef..0000000 --- a/python/stdface.egg-info/requires.txt +++ /dev/null @@ -1,5 +0,0 @@ -numpy - -[dev] -pytest -pytest-cov diff --git a/python/stdface.egg-info/top_level.txt b/python/stdface.egg-info/top_level.txt deleted file mode 100644 index 1bbec28..0000000 --- a/python/stdface.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -stdface From c275b74c70526955c4e04192e7b9ba6aab5e4890 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:12:00 +0000 Subject: [PATCH 15/16] Add explanatory comment for ImportError handling in _discover_plugins Co-authored-by: k-yoshimi <4516893+k-yoshimi@users.noreply.github.com> --- python/stdface.egg-info/PKG-INFO | 260 +++++++++++++++++++ python/stdface.egg-info/SOURCES.txt | 50 ++++ python/stdface.egg-info/dependency_links.txt | 1 + python/stdface.egg-info/entry_points.txt | 20 ++ python/stdface.egg-info/requires.txt | 5 + python/stdface.egg-info/top_level.txt | 1 + python/stdface/plugin.py | 3 + 7 files changed, 340 insertions(+) create mode 100644 python/stdface.egg-info/PKG-INFO create mode 100644 python/stdface.egg-info/SOURCES.txt create mode 100644 python/stdface.egg-info/dependency_links.txt create mode 100644 python/stdface.egg-info/entry_points.txt create mode 100644 python/stdface.egg-info/requires.txt create mode 100644 python/stdface.egg-info/top_level.txt diff --git a/python/stdface.egg-info/PKG-INFO b/python/stdface.egg-info/PKG-INFO new file mode 100644 index 0000000..3b8684d --- /dev/null +++ b/python/stdface.egg-info/PKG-INFO @@ -0,0 +1,260 @@ +Metadata-Version: 2.4 +Name: stdface +Version: 0.5.0 +Summary: Standard-mode input generator for HPhi / mVMC / UHF / H-wave +License-Expression: GPL-3.0-or-later +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +Requires-Dist: numpy +Provides-Extra: dev +Requires-Dist: pytest; extra == "dev" +Requires-Dist: pytest-cov; extra == "dev" + +# StdFace (Python) + +The `python/` directory contains a Python port of the C-based StdFace input generator. +It reads the same input files as the C version and produces solver-specific definition files +for HPhi, mVMC, UHF, and H-wave. + +## Requirements + +- Python 3.10 or later +- NumPy + +## Installation + +```bash +cd python +pip install -e . # install with runtime dependencies (numpy) +pip install -e ".[dev]" # also install dev dependencies (pytest, pytest-cov) +``` + +After installation, the `stdface` command becomes available: + +```bash +stdface stan.in +stdface stan.in --solver mVMC +stdface -v +``` + +The editable install (`-e`) is recommended for development. Source changes take effect immediately without reinstalling. + +## Architecture + +StdFace uses a **plugin architecture** for both solvers and lattices. +New solvers and lattices can be added without modifying any existing dispatch logic. + +### Plugin System Overview + +``` +stdface/ + plugin.py # SolverPlugin ABC + solver registry + lattice/__init__.py # LatticePlugin ABC + lattice registry +``` + +- **SolverPlugin** (`plugin.py`): Defines how a solver parses keywords, resets fields, and writes output files. Each solver (HPhi, mVMC, UHF, H-wave) is a plugin registered at import time. +- **LatticePlugin** (`lattice/__init__.py`): Defines lattice geometry, aliases, and the setup/boost methods. Each lattice (chain, square, kagome, etc.) is a plugin registered at import time. + +See [docs/tutorial_plugin.md](../docs/tutorial_plugin.md) for a step-by-step guide on adding new solvers and lattices. + +## Directory Structure + +``` +python/ + __main__.py # CLI entry point (port of dry.c) + stdface/ + plugin.py # SolverPlugin ABC + solver registry + core/ + stdface_main.py # Main logic (port of StdFace_main.c) + stdface_vals.py # StdIntList dataclass (port of StdFace_vals.h) + keyword_parser.py # Keyword parsing subsystem + param_check.py # Parameter validation utilities + lattice/ # Lattice plugins + __init__.py # LatticePlugin ABC + lattice registry + chain_lattice.py # 1D chain (ChainPlugin) + square_lattice.py # 2D square (SquarePlugin) + ladder.py # 2-leg ladder (LadderPlugin) + triangular_lattice.py # 2D triangular (TriangularPlugin) + honeycomb_lattice.py # 2D honeycomb (HoneycombPlugin) + kagome.py # 2D kagome (KagomePlugin) + orthorhombic.py # 3D orthorhombic (OrthorhombicPlugin) + fc_ortho.py # 3D face-centered orthorhombic (FCOrthoPlugin) + pyrochlore.py # 3D pyrochlore (PyrochlorePlugin) + wannier90.py # Wannier90 interface (Wannier90Plugin) + boost_output.py # Boost output utilities + geometry_output.py # Geometry output functions + input_params.py # Input parameter resolution + interaction_builder.py # Interaction building utilities + site_util.py # Site utility functions + solvers/ # Solver plugins + __init__.py # Auto-imports all solver plugins + hphi/_plugin.py # HPhi plugin (HPhiPlugin) + mvmc/_plugin.py # mVMC plugin (MVMCPlugin) + uhf/_plugin.py # UHF plugin (UHFPlugin) + hwave/_plugin.py # H-wave plugin (HWavePlugin) + writer/ # Output writers (shared) + common_writer.py # Common output functions + interaction_writer.py # Interaction file writer + export_wannier90.py # Wannier90 format export + history/ + refactoring_log.md # Refactoring change log +``` + +## Usage + +### Command Line + +From the project root: + +```bash +./stdface stan.in +``` + +To select a solver: + +```bash +./stdface stan.in --solver mVMC +./stdface stan.in --solver UHF +./stdface stan.in --solver HWAVE +``` + +The default solver is HPhi. + +### Print Version + +```bash +./stdface -v +``` + +### Calling from Python + +```python +import sys +sys.path.insert(0, "python") + +from stdface.core.stdface_main import stdface_main + +stdface_main("stan.in", solver="HPhi") +``` + +## Input File Examples + +Parameters are specified in `stan.in` using the `key = value` format. + +### Hubbard Model (Square Lattice) + +```text +model = Hubbard +lattice = square +W = 2 +L = 2 +t = 1.0 +U = 4.0 +nelec = 4 +2Sz = 0 +``` + +### Spin Model (Kagome Lattice) + +```text +model = Spin +lattice = kagome +W = 2 +L = 2 +J = 1.0 +2S = 1 +2Sz = 0 +``` + +### Kondo Lattice Model (1D Chain) + +```text +model = Kondo +lattice = chain +L = 4 +t = 1.0 +U = 4.0 +J = 0.8 +nelec = 4 +2Sz = 0 +``` + +## Running Tests + +### Unit Tests + +```bash +# Run all unit tests +python3 -m pytest test/unit/ -v + +# Run with coverage +python3 -m pytest test/unit/ --cov=python --cov-report=html +``` + +**Test Coverage:** +- 1,268 unit tests covering all modules +- Tests for lattice implementations, writers, parsers, plugin registries, and utilities +- All tests maintain byte-identical output with C version + +### Integration Tests + +**Run all integration tests:** +```bash +bash test/run_all_integration.sh +``` + +**Single test case:** +```bash +# Copy test input to a working directory and run +cp test/hphi/lanczos_hubbard_square/stan.in /tmp/work/ +cd /tmp/work +/path/to/StdFace/stdface stan.in + +# Compare outputs against reference +diff /path/to/StdFace/test/hphi/lanczos_hubbard_square/ref/modpara.def modpara.def +``` + +**Integration Test Results:** +- 83/83 integration tests passing +- All solvers (HPhi, mVMC, UHF, H-wave) verified +- Byte-identical output confirmed for all test cases + +## Code Quality + +The Python implementation has been refactored from a direct C translation into idiomatic Python: + +- **Plugin architecture**: Solvers and lattices are self-registering plugins +- **Modular design**: Code organized into logical subpackages (`lattice/`, `solvers/`, `writer/`) +- **Python idioms**: Enums, ABC, context managers, type hints +- **Reduced duplication**: Helper functions extracted to eliminate code duplication +- **Comprehensive testing**: 1,268 unit tests with full coverage +- **Documentation**: NumPy-style docstrings throughout + +See `history/refactoring_log.md` for detailed refactoring history. + +## Mapping to C Sources + +| C Source File | Python Module | +|---|---| +| `dry.c` | `__main__.py` | +| `StdFace_main.c` | `stdface/core/stdface_main.py` | +| `StdFace_vals.h` | `stdface/core/stdface_vals.py` | +| `StdFace_ModelUtil.c/h` | `stdface/core/stdface_model_util.py` | +| `version.h` | `stdface/version.py` | +| `ChainLattice.c` | `stdface/lattice/chain_lattice.py` | +| `SquareLattice.c` | `stdface/lattice/square_lattice.py` | +| `Ladder.c` | `stdface/lattice/ladder.py` | +| `TriangularLattice.c` | `stdface/lattice/triangular_lattice.py` | +| `HoneycombLattice.c` | `stdface/lattice/honeycomb_lattice.py` | +| `Kagome.c` | `stdface/lattice/kagome.py` | +| `Orthorhombic.c` | `stdface/lattice/orthorhombic.py` | +| `FCOrtho.c` | `stdface/lattice/fc_ortho.py` | +| `Pyrochlore.c` | `stdface/lattice/pyrochlore.py` | +| `Wannier90.c` | `stdface/lattice/wannier90.py` | +| `export_wannier90.c` | `stdface/writer/export_wannier90.py` | + +Note: The Python implementation has been significantly refactored beyond the original C structure for better maintainability and Pythonic code style. + +## License + +GNU General Public License v3 (GPLv3) diff --git a/python/stdface.egg-info/SOURCES.txt b/python/stdface.egg-info/SOURCES.txt new file mode 100644 index 0000000..2cfcc2a --- /dev/null +++ b/python/stdface.egg-info/SOURCES.txt @@ -0,0 +1,50 @@ +README.md +pyproject.toml +stdface/__init__.py +stdface/__main__.py +stdface/plugin.py +stdface.egg-info/PKG-INFO +stdface.egg-info/SOURCES.txt +stdface.egg-info/dependency_links.txt +stdface.egg-info/entry_points.txt +stdface.egg-info/requires.txt +stdface.egg-info/top_level.txt +stdface/core/__init__.py +stdface/core/keyword_parser.py +stdface/core/param_check.py +stdface/core/stdface_main.py +stdface/core/stdface_model_util.py +stdface/core/stdface_vals.py +stdface/core/version.py +stdface/lattice/__init__.py +stdface/lattice/boost_output.py +stdface/lattice/chain_lattice.py +stdface/lattice/fc_ortho.py +stdface/lattice/geometry_output.py +stdface/lattice/honeycomb_lattice.py +stdface/lattice/input_params.py +stdface/lattice/interaction_builder.py +stdface/lattice/kagome.py +stdface/lattice/ladder.py +stdface/lattice/orthorhombic.py +stdface/lattice/pyrochlore.py +stdface/lattice/site_util.py +stdface/lattice/square_lattice.py +stdface/lattice/triangular_lattice.py +stdface/lattice/wannier90.py +stdface/solvers/__init__.py +stdface/solvers/hphi/__init__.py +stdface/solvers/hphi/_plugin.py +stdface/solvers/hphi/writer.py +stdface/solvers/hwave/__init__.py +stdface/solvers/hwave/_plugin.py +stdface/solvers/hwave/export_wannier90.py +stdface/solvers/mvmc/__init__.py +stdface/solvers/mvmc/_plugin.py +stdface/solvers/mvmc/variational.py +stdface/solvers/mvmc/writer.py +stdface/solvers/uhf/__init__.py +stdface/solvers/uhf/_plugin.py +stdface/writer/__init__.py +stdface/writer/common_writer.py +stdface/writer/interaction_writer.py \ No newline at end of file diff --git a/python/stdface.egg-info/dependency_links.txt b/python/stdface.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/python/stdface.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/python/stdface.egg-info/entry_points.txt b/python/stdface.egg-info/entry_points.txt new file mode 100644 index 0000000..a44b744 --- /dev/null +++ b/python/stdface.egg-info/entry_points.txt @@ -0,0 +1,20 @@ +[console_scripts] +stdface = stdface.__main__:main + +[stdface.lattices] +chain = stdface.lattice.chain_lattice:ChainPlugin +fc_ortho = stdface.lattice.fc_ortho:FCOrthoPlugin +honeycomb = stdface.lattice.honeycomb_lattice:HoneycombPlugin +kagome = stdface.lattice.kagome:KagomePlugin +ladder = stdface.lattice.ladder:LadderPlugin +orthorhombic = stdface.lattice.orthorhombic:OrthorhombicPlugin +pyrochlore = stdface.lattice.pyrochlore:PyrochlorePlugin +square = stdface.lattice.square_lattice:SquarePlugin +triangular = stdface.lattice.triangular_lattice:TriangularPlugin +wannier90 = stdface.lattice.wannier90:Wannier90Plugin + +[stdface.solvers] +hphi = stdface.solvers.hphi:HPhiPlugin +hwave = stdface.solvers.hwave:HWavePlugin +mvmc = stdface.solvers.mvmc:MVMCPlugin +uhf = stdface.solvers.uhf:UHFPlugin diff --git a/python/stdface.egg-info/requires.txt b/python/stdface.egg-info/requires.txt new file mode 100644 index 0000000..2f01eef --- /dev/null +++ b/python/stdface.egg-info/requires.txt @@ -0,0 +1,5 @@ +numpy + +[dev] +pytest +pytest-cov diff --git a/python/stdface.egg-info/top_level.txt b/python/stdface.egg-info/top_level.txt new file mode 100644 index 0000000..1bbec28 --- /dev/null +++ b/python/stdface.egg-info/top_level.txt @@ -0,0 +1 @@ +stdface diff --git a/python/stdface/plugin.py b/python/stdface/plugin.py index 6800117..67d7551 100644 --- a/python/stdface/plugin.py +++ b/python/stdface/plugin.py @@ -262,4 +262,7 @@ def _discover_plugins() -> None: try: import stdface.solvers # noqa: F401 — triggers auto-registration except ImportError: + # Solvers package not available (e.g., minimal install, missing + # dependencies, or corrupted package). Plugin lookup will fail + # with a helpful KeyError listing available plugins. pass From f1e350e69f44e25aafbb8fa71f30aed657ace1a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:12:21 +0000 Subject: [PATCH 16/16] Remove egg-info build artifacts and update .gitignore Co-authored-by: k-yoshimi <4516893+k-yoshimi@users.noreply.github.com> --- .gitignore | 4 + python/stdface.egg-info/PKG-INFO | 260 ------------------- python/stdface.egg-info/SOURCES.txt | 50 ---- python/stdface.egg-info/dependency_links.txt | 1 - python/stdface.egg-info/entry_points.txt | 20 -- python/stdface.egg-info/requires.txt | 5 - python/stdface.egg-info/top_level.txt | 1 - 7 files changed, 4 insertions(+), 337 deletions(-) delete mode 100644 python/stdface.egg-info/PKG-INFO delete mode 100644 python/stdface.egg-info/SOURCES.txt delete mode 100644 python/stdface.egg-info/dependency_links.txt delete mode 100644 python/stdface.egg-info/entry_points.txt delete mode 100644 python/stdface.egg-info/requires.txt delete mode 100644 python/stdface.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore index 525619c..ebec809 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,10 @@ cmake-build-debug/ __pycache__/ *.pyc +# Python package metadata +*.egg-info/ +dist/ + # Build Output build/ diff --git a/python/stdface.egg-info/PKG-INFO b/python/stdface.egg-info/PKG-INFO deleted file mode 100644 index 3b8684d..0000000 --- a/python/stdface.egg-info/PKG-INFO +++ /dev/null @@ -1,260 +0,0 @@ -Metadata-Version: 2.4 -Name: stdface -Version: 0.5.0 -Summary: Standard-mode input generator for HPhi / mVMC / UHF / H-wave -License-Expression: GPL-3.0-or-later -Requires-Python: >=3.10 -Description-Content-Type: text/markdown -Requires-Dist: numpy -Provides-Extra: dev -Requires-Dist: pytest; extra == "dev" -Requires-Dist: pytest-cov; extra == "dev" - -# StdFace (Python) - -The `python/` directory contains a Python port of the C-based StdFace input generator. -It reads the same input files as the C version and produces solver-specific definition files -for HPhi, mVMC, UHF, and H-wave. - -## Requirements - -- Python 3.10 or later -- NumPy - -## Installation - -```bash -cd python -pip install -e . # install with runtime dependencies (numpy) -pip install -e ".[dev]" # also install dev dependencies (pytest, pytest-cov) -``` - -After installation, the `stdface` command becomes available: - -```bash -stdface stan.in -stdface stan.in --solver mVMC -stdface -v -``` - -The editable install (`-e`) is recommended for development. Source changes take effect immediately without reinstalling. - -## Architecture - -StdFace uses a **plugin architecture** for both solvers and lattices. -New solvers and lattices can be added without modifying any existing dispatch logic. - -### Plugin System Overview - -``` -stdface/ - plugin.py # SolverPlugin ABC + solver registry - lattice/__init__.py # LatticePlugin ABC + lattice registry -``` - -- **SolverPlugin** (`plugin.py`): Defines how a solver parses keywords, resets fields, and writes output files. Each solver (HPhi, mVMC, UHF, H-wave) is a plugin registered at import time. -- **LatticePlugin** (`lattice/__init__.py`): Defines lattice geometry, aliases, and the setup/boost methods. Each lattice (chain, square, kagome, etc.) is a plugin registered at import time. - -See [docs/tutorial_plugin.md](../docs/tutorial_plugin.md) for a step-by-step guide on adding new solvers and lattices. - -## Directory Structure - -``` -python/ - __main__.py # CLI entry point (port of dry.c) - stdface/ - plugin.py # SolverPlugin ABC + solver registry - core/ - stdface_main.py # Main logic (port of StdFace_main.c) - stdface_vals.py # StdIntList dataclass (port of StdFace_vals.h) - keyword_parser.py # Keyword parsing subsystem - param_check.py # Parameter validation utilities - lattice/ # Lattice plugins - __init__.py # LatticePlugin ABC + lattice registry - chain_lattice.py # 1D chain (ChainPlugin) - square_lattice.py # 2D square (SquarePlugin) - ladder.py # 2-leg ladder (LadderPlugin) - triangular_lattice.py # 2D triangular (TriangularPlugin) - honeycomb_lattice.py # 2D honeycomb (HoneycombPlugin) - kagome.py # 2D kagome (KagomePlugin) - orthorhombic.py # 3D orthorhombic (OrthorhombicPlugin) - fc_ortho.py # 3D face-centered orthorhombic (FCOrthoPlugin) - pyrochlore.py # 3D pyrochlore (PyrochlorePlugin) - wannier90.py # Wannier90 interface (Wannier90Plugin) - boost_output.py # Boost output utilities - geometry_output.py # Geometry output functions - input_params.py # Input parameter resolution - interaction_builder.py # Interaction building utilities - site_util.py # Site utility functions - solvers/ # Solver plugins - __init__.py # Auto-imports all solver plugins - hphi/_plugin.py # HPhi plugin (HPhiPlugin) - mvmc/_plugin.py # mVMC plugin (MVMCPlugin) - uhf/_plugin.py # UHF plugin (UHFPlugin) - hwave/_plugin.py # H-wave plugin (HWavePlugin) - writer/ # Output writers (shared) - common_writer.py # Common output functions - interaction_writer.py # Interaction file writer - export_wannier90.py # Wannier90 format export - history/ - refactoring_log.md # Refactoring change log -``` - -## Usage - -### Command Line - -From the project root: - -```bash -./stdface stan.in -``` - -To select a solver: - -```bash -./stdface stan.in --solver mVMC -./stdface stan.in --solver UHF -./stdface stan.in --solver HWAVE -``` - -The default solver is HPhi. - -### Print Version - -```bash -./stdface -v -``` - -### Calling from Python - -```python -import sys -sys.path.insert(0, "python") - -from stdface.core.stdface_main import stdface_main - -stdface_main("stan.in", solver="HPhi") -``` - -## Input File Examples - -Parameters are specified in `stan.in` using the `key = value` format. - -### Hubbard Model (Square Lattice) - -```text -model = Hubbard -lattice = square -W = 2 -L = 2 -t = 1.0 -U = 4.0 -nelec = 4 -2Sz = 0 -``` - -### Spin Model (Kagome Lattice) - -```text -model = Spin -lattice = kagome -W = 2 -L = 2 -J = 1.0 -2S = 1 -2Sz = 0 -``` - -### Kondo Lattice Model (1D Chain) - -```text -model = Kondo -lattice = chain -L = 4 -t = 1.0 -U = 4.0 -J = 0.8 -nelec = 4 -2Sz = 0 -``` - -## Running Tests - -### Unit Tests - -```bash -# Run all unit tests -python3 -m pytest test/unit/ -v - -# Run with coverage -python3 -m pytest test/unit/ --cov=python --cov-report=html -``` - -**Test Coverage:** -- 1,268 unit tests covering all modules -- Tests for lattice implementations, writers, parsers, plugin registries, and utilities -- All tests maintain byte-identical output with C version - -### Integration Tests - -**Run all integration tests:** -```bash -bash test/run_all_integration.sh -``` - -**Single test case:** -```bash -# Copy test input to a working directory and run -cp test/hphi/lanczos_hubbard_square/stan.in /tmp/work/ -cd /tmp/work -/path/to/StdFace/stdface stan.in - -# Compare outputs against reference -diff /path/to/StdFace/test/hphi/lanczos_hubbard_square/ref/modpara.def modpara.def -``` - -**Integration Test Results:** -- 83/83 integration tests passing -- All solvers (HPhi, mVMC, UHF, H-wave) verified -- Byte-identical output confirmed for all test cases - -## Code Quality - -The Python implementation has been refactored from a direct C translation into idiomatic Python: - -- **Plugin architecture**: Solvers and lattices are self-registering plugins -- **Modular design**: Code organized into logical subpackages (`lattice/`, `solvers/`, `writer/`) -- **Python idioms**: Enums, ABC, context managers, type hints -- **Reduced duplication**: Helper functions extracted to eliminate code duplication -- **Comprehensive testing**: 1,268 unit tests with full coverage -- **Documentation**: NumPy-style docstrings throughout - -See `history/refactoring_log.md` for detailed refactoring history. - -## Mapping to C Sources - -| C Source File | Python Module | -|---|---| -| `dry.c` | `__main__.py` | -| `StdFace_main.c` | `stdface/core/stdface_main.py` | -| `StdFace_vals.h` | `stdface/core/stdface_vals.py` | -| `StdFace_ModelUtil.c/h` | `stdface/core/stdface_model_util.py` | -| `version.h` | `stdface/version.py` | -| `ChainLattice.c` | `stdface/lattice/chain_lattice.py` | -| `SquareLattice.c` | `stdface/lattice/square_lattice.py` | -| `Ladder.c` | `stdface/lattice/ladder.py` | -| `TriangularLattice.c` | `stdface/lattice/triangular_lattice.py` | -| `HoneycombLattice.c` | `stdface/lattice/honeycomb_lattice.py` | -| `Kagome.c` | `stdface/lattice/kagome.py` | -| `Orthorhombic.c` | `stdface/lattice/orthorhombic.py` | -| `FCOrtho.c` | `stdface/lattice/fc_ortho.py` | -| `Pyrochlore.c` | `stdface/lattice/pyrochlore.py` | -| `Wannier90.c` | `stdface/lattice/wannier90.py` | -| `export_wannier90.c` | `stdface/writer/export_wannier90.py` | - -Note: The Python implementation has been significantly refactored beyond the original C structure for better maintainability and Pythonic code style. - -## License - -GNU General Public License v3 (GPLv3) diff --git a/python/stdface.egg-info/SOURCES.txt b/python/stdface.egg-info/SOURCES.txt deleted file mode 100644 index 2cfcc2a..0000000 --- a/python/stdface.egg-info/SOURCES.txt +++ /dev/null @@ -1,50 +0,0 @@ -README.md -pyproject.toml -stdface/__init__.py -stdface/__main__.py -stdface/plugin.py -stdface.egg-info/PKG-INFO -stdface.egg-info/SOURCES.txt -stdface.egg-info/dependency_links.txt -stdface.egg-info/entry_points.txt -stdface.egg-info/requires.txt -stdface.egg-info/top_level.txt -stdface/core/__init__.py -stdface/core/keyword_parser.py -stdface/core/param_check.py -stdface/core/stdface_main.py -stdface/core/stdface_model_util.py -stdface/core/stdface_vals.py -stdface/core/version.py -stdface/lattice/__init__.py -stdface/lattice/boost_output.py -stdface/lattice/chain_lattice.py -stdface/lattice/fc_ortho.py -stdface/lattice/geometry_output.py -stdface/lattice/honeycomb_lattice.py -stdface/lattice/input_params.py -stdface/lattice/interaction_builder.py -stdface/lattice/kagome.py -stdface/lattice/ladder.py -stdface/lattice/orthorhombic.py -stdface/lattice/pyrochlore.py -stdface/lattice/site_util.py -stdface/lattice/square_lattice.py -stdface/lattice/triangular_lattice.py -stdface/lattice/wannier90.py -stdface/solvers/__init__.py -stdface/solvers/hphi/__init__.py -stdface/solvers/hphi/_plugin.py -stdface/solvers/hphi/writer.py -stdface/solvers/hwave/__init__.py -stdface/solvers/hwave/_plugin.py -stdface/solvers/hwave/export_wannier90.py -stdface/solvers/mvmc/__init__.py -stdface/solvers/mvmc/_plugin.py -stdface/solvers/mvmc/variational.py -stdface/solvers/mvmc/writer.py -stdface/solvers/uhf/__init__.py -stdface/solvers/uhf/_plugin.py -stdface/writer/__init__.py -stdface/writer/common_writer.py -stdface/writer/interaction_writer.py \ No newline at end of file diff --git a/python/stdface.egg-info/dependency_links.txt b/python/stdface.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/python/stdface.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/python/stdface.egg-info/entry_points.txt b/python/stdface.egg-info/entry_points.txt deleted file mode 100644 index a44b744..0000000 --- a/python/stdface.egg-info/entry_points.txt +++ /dev/null @@ -1,20 +0,0 @@ -[console_scripts] -stdface = stdface.__main__:main - -[stdface.lattices] -chain = stdface.lattice.chain_lattice:ChainPlugin -fc_ortho = stdface.lattice.fc_ortho:FCOrthoPlugin -honeycomb = stdface.lattice.honeycomb_lattice:HoneycombPlugin -kagome = stdface.lattice.kagome:KagomePlugin -ladder = stdface.lattice.ladder:LadderPlugin -orthorhombic = stdface.lattice.orthorhombic:OrthorhombicPlugin -pyrochlore = stdface.lattice.pyrochlore:PyrochlorePlugin -square = stdface.lattice.square_lattice:SquarePlugin -triangular = stdface.lattice.triangular_lattice:TriangularPlugin -wannier90 = stdface.lattice.wannier90:Wannier90Plugin - -[stdface.solvers] -hphi = stdface.solvers.hphi:HPhiPlugin -hwave = stdface.solvers.hwave:HWavePlugin -mvmc = stdface.solvers.mvmc:MVMCPlugin -uhf = stdface.solvers.uhf:UHFPlugin diff --git a/python/stdface.egg-info/requires.txt b/python/stdface.egg-info/requires.txt deleted file mode 100644 index 2f01eef..0000000 --- a/python/stdface.egg-info/requires.txt +++ /dev/null @@ -1,5 +0,0 @@ -numpy - -[dev] -pytest -pytest-cov diff --git a/python/stdface.egg-info/top_level.txt b/python/stdface.egg-info/top_level.txt deleted file mode 100644 index 1bbec28..0000000 --- a/python/stdface.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -stdface