Modify path and add a CI/CD workflow. #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: interface | ||
| # 仅支持手动触发,不自动运行 | ||
| on: | ||
| workflow_dispatch: | ||
| jobs: | ||
| wannier-interface: | ||
| name: "wannier interface — ${{ matrix.name }}" | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - name: basic (LCAO) | ||
| script: example_basic.py | ||
| prefix: Bi2Se3_basic | ||
| basis: lcao | ||
| - name: pw (Plane Wave) | ||
| script: example_pw.py | ||
| prefix: Bi2Se3_pw | ||
| basis: pw | ||
| - name: advance (LCAO) | ||
| script: example_advance.py | ||
| prefix: Bi2Se3_advanced | ||
| basis: lcao | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| - name: Set up Python 3.10 | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.10" | ||
| - name: Install abacusw90 | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install -e . | ||
| - name: Confirm no wannier90.x in environment | ||
| run: | | ||
| if command -v wannier90.x 2>/dev/null; then | ||
| echo "::error::wannier90.x found — CI must test WITHOUT external binaries" | ||
| exit 1 | ||
| fi | ||
| echo "OK: no wannier90.x (dryrun mode)" | ||
| # ================================================================ | ||
| # Shared mock + patch (covers all three scripts) | ||
| # ================================================================ | ||
| - name: Create mock data & patch script | ||
| run: python3 << 'PYEOF' | ||
| import os, re, textwrap | ||
| script = os.environ["SCRIPT"] | ||
| prefix = os.environ["PREFIX"] | ||
| # ── 1. Patch the example script ──────────────────────────── | ||
| with open(script) as f: | ||
| src = f.read() | ||
| # Enable dry run | ||
| src = src.replace("DRY_RUN = False", "DRY_RUN = True") | ||
| # Skip step0 — requires ABACUS binary | ||
| src = re.sub( | ||
| r"\s*job\.step0_run_scf\([^)]*\)", | ||
| " # [CI:skip] step0_run_scf — no ABACUS in this environment", | ||
| src, | ||
| flags=re.DOTALL, | ||
| ) | ||
| # Skip step1 — requires wannier90.x binary | ||
| src = src.replace( | ||
| "job.step1_generate_wannier_win()", | ||
| "# [CI:skip] step1_generate_wannier_win — no wannier90.x in this environment", | ||
| ) | ||
| with open(script, "w") as f: | ||
| f.write(src) | ||
| print(f"[patch] {script}") | ||
| # ── 2. Create work directories ───────────────────────────── | ||
| for d in [f"{prefix}/scf", f"{prefix}/wannier"]: | ||
| os.makedirs(d, exist_ok=True) | ||
| # ── 3. Mock STRU (scf/ + wannier/) ──────────────────────── | ||
| stru = textwrap.dedent("""\ | ||
| ATOMIC_SPECIES | ||
| Bi 208.980 Bi.upf | ||
| Se 78.960 Se.upf | ||
| LATTICE_CONSTANT | ||
| 1.0 | ||
| LATTICE_VECTORS | ||
| -2.069 -3.583614 0.0 | ||
| 2.069 -3.583614 0.0 | ||
| 0.000 2.389075 9.546667 | ||
| ATOMIC_POSITIONS | ||
| Direct | ||
| Bi | ||
| 0.399 0.399 0.697 1 1 1 | ||
| 0.601 0.601 0.303 1 1 1 | ||
| Se | ||
| 0.000 0.000 0.500 1 1 1 | ||
| 0.206 0.206 0.118 1 1 1 | ||
| 0.794 0.794 0.882 1 1 1 | ||
| """) | ||
| for d in [f"{prefix}/scf", f"{prefix}/wannier"]: | ||
| p = os.path.join(d, "STRU") | ||
| with open(p, "w") as f: | ||
| f.write(stru) | ||
| print(f"[mock] {p}") | ||
| # ── 4. Mock wannier90.nnkp ──────────────────────────────── | ||
| nbands = 20 | ||
| nk = 64 # 4 × 4 × 4 | ||
| nntot = 14 | ||
| lines = [ | ||
| f" {nbands}", "! num_bands", | ||
| f" {nk}", "! num_kpts", | ||
| " 4 4 4", "! mp_grid", | ||
| ] | ||
| for ix in range(4): | ||
| for iy in range(4): | ||
| for iz in range(4): | ||
| lines.append(f" {ix/4:.15f} {iy/4:.15f} {iz/4:.15f}") | ||
| lines.append(f" {nntot}") | ||
| lines.append("! nntot") | ||
| for ik in range(nk): | ||
| lines.append(f" {nntot}") | ||
| for ib in range(nntot): | ||
| lines.append(f" {(ik + ib) % nk + 1}") | ||
| lines.append(f" {nntot}") | ||
| for ib in range(nntot): | ||
| lines.append(" 0 0 0") | ||
| lines += [ | ||
| f" {nbands}", "! num_exclude_bands", | ||
| " 30", "! num_wann", | ||
| " 6", "! num_proj", | ||
| ] | ||
| for proj in ["Bi : pz","Bi : px","Bi : py", | ||
| "Se : pz","Se : px","Se : py"]: | ||
| lines.append(f" {proj}") | ||
| nnkp = os.path.join(prefix, "wannier", "wannier90.nnkp") | ||
| with open(nnkp, "w") as f: | ||
| f.write("\n".join(lines) + "\n") | ||
| print(f"[mock] {nnkp} ({len(lines)} lines)") | ||
| # ── 5. Mock PP & ORB files (unified path for all 3 scripts) ── | ||
| orb_dir = "tests/PP_ORB/for_interface/Wannier90_interface" | ||
| os.makedirs(orb_dir, exist_ok=True) | ||
| for name in ["Bi.upf", "Se.upf", "Bi.orb", "Se.orb"]: | ||
| p = os.path.join(orb_dir, name) | ||
| with open(p, "w") as f: | ||
| f.write("# mock for CI dryrun\n") | ||
| print(f"[mock] {p}") | ||
| print("\n[done] all mocks ready") | ||
| PYEOF | ||
| env: | ||
| SCRIPT: ${{ matrix.script }} | ||
| PREFIX: ${{ matrix.prefix }} | ||
| # ================================================================ | ||
| # Run | ||
| # ================================================================ | ||
| - name: Run ${{ matrix.script }} — dryrun | ||
| run: python ${{ matrix.script }} 2>&1 | tee run.log | ||
| # ================================================================ | ||
| # Validate | ||
| # ================================================================ | ||
| - name: Check dryrun completion banner | ||
| run: | | ||
| if grep -q "DRY RUN COMPLETE" run.log; then | ||
| echo " [OK] dryrun banner found" | ||
| else | ||
| echo " [FAIL] 'DRY RUN COMPLETE' not found — script may have crashed" | ||
| exit 1 | ||
| fi | ||
| - name: Validate generated files (${{ matrix.name }}) | ||
| run: | | ||
| W="${{ matrix.prefix }}/wannier" | ||
| ERR=0 | ||
| check() { | ||
| if [ -f "$1" ]; then | ||
| echo " [OK] $1 ($(wc -c < "$1") bytes)" | ||
| else | ||
| echo " [FAIL] $1 (not found)" | ||
| ERR=1 | ||
| fi | ||
| } | ||
| echo "==========================================" | ||
| echo " NSCF input files (from step2)" | ||
| echo "==========================================" | ||
| check "$W/INPUT" | ||
| check "$W/KPT" | ||
| check "$W/STRU" | ||
| # ── INPUT content checks ────────────────────── | ||
| if [ -f "$W/INPUT" ]; then | ||
| echo "" | ||
| echo "--- INPUT keyword checks ---" | ||
| for key in "calculation" "towannier90" "nspin"; do | ||
| if grep -qi "$key" "$W/INPUT"; then | ||
| echo " [OK] INPUT contains '$key'" | ||
| else | ||
| echo " [FAIL] INPUT missing '$key'" | ||
| ERR=1 | ||
| fi | ||
| done | ||
| # gamma_only is FORBIDDEN | ||
| if grep -qi "gamma_only" "$W/INPUT"; then | ||
| echo " [FAIL] INPUT contains 'gamma_only' — forbidden!" | ||
| ERR=1 | ||
| else | ||
| echo " [OK] INPUT does NOT contain 'gamma_only'" | ||
| fi | ||
| fi | ||
| # ── Basis-specific checks ───────────────────── | ||
| echo "" | ||
| echo "--- Basis-specific checks (${{ matrix.basis }}) ---" | ||
| if [ "${{ matrix.basis }}" = "pw" ]; then | ||
| if grep -qi "orbital_file\|orbital_dir" "$W/INPUT"; then | ||
| echo " [FAIL] PW INPUT contains orbital_file/orbital_dir" | ||
| ERR=1 | ||
| else | ||
| echo " [OK] PW INPUT does NOT contain orbital_file/orbital_dir" | ||
| fi | ||
| else | ||
| if grep -qi "orbital_file\|orbital_dir" "$W/INPUT"; then | ||
| echo " [OK] LCAO INPUT contains orbital_file/orbital_dir" | ||
| else | ||
| echo " [WARN] LCAO INPUT missing orbital references (may be in STRU)" | ||
| fi | ||
| fi | ||
| # ── KPT sanity ──────────────────────────────── | ||
| if [ -f "$W/KPT" ]; then | ||
| echo "" | ||
| klines=$(wc -l < "$W/KPT") | ||
| if [ "$klines" -gt 5 ]; then | ||
| echo " [OK] KPT has $klines lines (reasonable)" | ||
| else | ||
| echo " [FAIL] KPT too short ($klines lines)" | ||
| ERR=1 | ||
| fi | ||
| fi | ||
| echo "" | ||
| if [ "$ERR" -ne 0 ]; then | ||
| echo "==========================================" | ||
| echo " VALIDATION FAILED" | ||
| echo "==========================================" | ||
| exit 1 | ||
| fi | ||
| echo "==========================================" | ||
| echo " ALL CHECKS PASSED (${{ matrix.name }})" | ||
| echo "==========================================" | ||
| # ================================================================ | ||
| # Debug (always runs, even on failure) | ||
| # ================================================================ | ||
| - name: Show full script output | ||
| if: always() | ||
| run: cat run.log | ||
| - name: Show generated INPUT | ||
| if: always() | ||
| run: | | ||
| f="${{ matrix.prefix }}/wannier/INPUT" | ||
| if [ -f "$f" ]; then echo "=== $f ==="; cat "$f"; else echo "$f not found"; fi | ||
| - name: Show generated KPT (first 20 lines) | ||
| if: always() | ||
| run: | | ||
| f="${{ matrix.prefix }}/wannier/KPT" | ||
| if [ -f "$f" ]; then echo "=== $f (first 20 lines) ==="; head -20 "$f"; else echo "$f not found"; fi | ||
| - name: List all generated files | ||
| if: always() | ||
| run: find ${{ matrix.prefix }} -type f | sort | ||