|
| 1 | +name: interface |
| 2 | + |
| 3 | +# 仅支持手动触发,不自动运行 |
| 4 | +on: |
| 5 | + workflow_dispatch: |
| 6 | + |
| 7 | +jobs: |
| 8 | + wannier-interface: |
| 9 | + name: "wannier interface — ${{ matrix.name }}" |
| 10 | + runs-on: ubuntu-latest |
| 11 | + strategy: |
| 12 | + fail-fast: false |
| 13 | + matrix: |
| 14 | + include: |
| 15 | + - name: basic (LCAO) |
| 16 | + script: example_basic.py |
| 17 | + prefix: Bi2Se3_basic |
| 18 | + basis: lcao |
| 19 | + - name: pw (Plane Wave) |
| 20 | + script: example_pw.py |
| 21 | + prefix: Bi2Se3_pw |
| 22 | + basis: pw |
| 23 | + - name: advance (LCAO) |
| 24 | + script: example_advance.py |
| 25 | + prefix: Bi2Se3_advanced |
| 26 | + basis: lcao |
| 27 | + |
| 28 | + steps: |
| 29 | + - name: Checkout repository |
| 30 | + uses: actions/checkout@v4 |
| 31 | + |
| 32 | + - name: Set up Python 3.10 |
| 33 | + uses: actions/setup-python@v5 |
| 34 | + with: |
| 35 | + python-version: "3.10" |
| 36 | + |
| 37 | + - name: Install abacusw90 |
| 38 | + run: | |
| 39 | + python -m pip install --upgrade pip |
| 40 | + pip install -e . |
| 41 | +
|
| 42 | + - name: Confirm no wannier90.x in environment |
| 43 | + run: | |
| 44 | + if command -v wannier90.x 2>/dev/null; then |
| 45 | + echo "::error::wannier90.x found — CI must test WITHOUT external binaries" |
| 46 | + exit 1 |
| 47 | + fi |
| 48 | + echo "OK: no wannier90.x (dryrun mode)" |
| 49 | +
|
| 50 | + # ================================================================ |
| 51 | + # Shared mock + patch (covers all three scripts) |
| 52 | + # ================================================================ |
| 53 | + - name: Create mock data & patch script |
| 54 | + run: python3 << 'PYEOF' |
| 55 | + import os, re, textwrap |
| 56 | + |
| 57 | + script = os.environ["SCRIPT"] |
| 58 | + prefix = os.environ["PREFIX"] |
| 59 | + |
| 60 | + # ── 1. Patch the example script ──────────────────────────── |
| 61 | + with open(script) as f: |
| 62 | + src = f.read() |
| 63 | + |
| 64 | + # Enable dry run |
| 65 | + src = src.replace("DRY_RUN = False", "DRY_RUN = True") |
| 66 | + |
| 67 | + # Skip step0 — requires ABACUS binary |
| 68 | + src = re.sub( |
| 69 | + r"\s*job\.step0_run_scf\([^)]*\)", |
| 70 | + " # [CI:skip] step0_run_scf — no ABACUS in this environment", |
| 71 | + src, |
| 72 | + flags=re.DOTALL, |
| 73 | + ) |
| 74 | + |
| 75 | + # Skip step1 — requires wannier90.x binary |
| 76 | + src = src.replace( |
| 77 | + "job.step1_generate_wannier_win()", |
| 78 | + "# [CI:skip] step1_generate_wannier_win — no wannier90.x in this environment", |
| 79 | + ) |
| 80 | + |
| 81 | + with open(script, "w") as f: |
| 82 | + f.write(src) |
| 83 | + print(f"[patch] {script}") |
| 84 | + |
| 85 | + # ── 2. Create work directories ───────────────────────────── |
| 86 | + for d in [f"{prefix}/scf", f"{prefix}/wannier"]: |
| 87 | + os.makedirs(d, exist_ok=True) |
| 88 | + |
| 89 | + # ── 3. Mock STRU (scf/ + wannier/) ──────────────────────── |
| 90 | + stru = textwrap.dedent("""\ |
| 91 | + ATOMIC_SPECIES |
| 92 | + Bi 208.980 Bi.upf |
| 93 | + Se 78.960 Se.upf |
| 94 | + |
| 95 | + LATTICE_CONSTANT |
| 96 | + 1.0 |
| 97 | + |
| 98 | + LATTICE_VECTORS |
| 99 | + -2.069 -3.583614 0.0 |
| 100 | + 2.069 -3.583614 0.0 |
| 101 | + 0.000 2.389075 9.546667 |
| 102 | + |
| 103 | + ATOMIC_POSITIONS |
| 104 | + Direct |
| 105 | + |
| 106 | + Bi |
| 107 | + 0.399 0.399 0.697 1 1 1 |
| 108 | + 0.601 0.601 0.303 1 1 1 |
| 109 | + |
| 110 | + Se |
| 111 | + 0.000 0.000 0.500 1 1 1 |
| 112 | + 0.206 0.206 0.118 1 1 1 |
| 113 | + 0.794 0.794 0.882 1 1 1 |
| 114 | + """) |
| 115 | + for d in [f"{prefix}/scf", f"{prefix}/wannier"]: |
| 116 | + p = os.path.join(d, "STRU") |
| 117 | + with open(p, "w") as f: |
| 118 | + f.write(stru) |
| 119 | + print(f"[mock] {p}") |
| 120 | + |
| 121 | + # ── 4. Mock wannier90.nnkp ──────────────────────────────── |
| 122 | + nbands = 20 |
| 123 | + nk = 64 # 4 × 4 × 4 |
| 124 | + nntot = 14 |
| 125 | + |
| 126 | + lines = [ |
| 127 | + f" {nbands}", "! num_bands", |
| 128 | + f" {nk}", "! num_kpts", |
| 129 | + " 4 4 4", "! mp_grid", |
| 130 | + ] |
| 131 | + for ix in range(4): |
| 132 | + for iy in range(4): |
| 133 | + for iz in range(4): |
| 134 | + lines.append(f" {ix/4:.15f} {iy/4:.15f} {iz/4:.15f}") |
| 135 | + |
| 136 | + lines.append(f" {nntot}") |
| 137 | + lines.append("! nntot") |
| 138 | + for ik in range(nk): |
| 139 | + lines.append(f" {nntot}") |
| 140 | + for ib in range(nntot): |
| 141 | + lines.append(f" {(ik + ib) % nk + 1}") |
| 142 | + lines.append(f" {nntot}") |
| 143 | + for ib in range(nntot): |
| 144 | + lines.append(" 0 0 0") |
| 145 | + |
| 146 | + lines += [ |
| 147 | + f" {nbands}", "! num_exclude_bands", |
| 148 | + " 30", "! num_wann", |
| 149 | + " 6", "! num_proj", |
| 150 | + ] |
| 151 | + for proj in ["Bi : pz","Bi : px","Bi : py", |
| 152 | + "Se : pz","Se : px","Se : py"]: |
| 153 | + lines.append(f" {proj}") |
| 154 | + |
| 155 | + nnkp = os.path.join(prefix, "wannier", "wannier90.nnkp") |
| 156 | + with open(nnkp, "w") as f: |
| 157 | + f.write("\n".join(lines) + "\n") |
| 158 | + print(f"[mock] {nnkp} ({len(lines)} lines)") |
| 159 | + |
| 160 | + # ── 5. Mock PP & ORB files (unified path for all 3 scripts) ── |
| 161 | + orb_dir = "tests/PP_ORB/for_interface/Wannier90_interface" |
| 162 | + os.makedirs(orb_dir, exist_ok=True) |
| 163 | + for name in ["Bi.upf", "Se.upf", "Bi.orb", "Se.orb"]: |
| 164 | + p = os.path.join(orb_dir, name) |
| 165 | + with open(p, "w") as f: |
| 166 | + f.write("# mock for CI dryrun\n") |
| 167 | + print(f"[mock] {p}") |
| 168 | + |
| 169 | + print("\n[done] all mocks ready") |
| 170 | + PYEOF |
| 171 | + env: |
| 172 | + SCRIPT: ${{ matrix.script }} |
| 173 | + PREFIX: ${{ matrix.prefix }} |
| 174 | + |
| 175 | + # ================================================================ |
| 176 | + # Run |
| 177 | + # ================================================================ |
| 178 | + - name: Run ${{ matrix.script }} — dryrun |
| 179 | + run: python ${{ matrix.script }} 2>&1 | tee run.log |
| 180 | + |
| 181 | + # ================================================================ |
| 182 | + # Validate |
| 183 | + # ================================================================ |
| 184 | + - name: Check dryrun completion banner |
| 185 | + run: | |
| 186 | + if grep -q "DRY RUN COMPLETE" run.log; then |
| 187 | + echo " [OK] dryrun banner found" |
| 188 | + else |
| 189 | + echo " [FAIL] 'DRY RUN COMPLETE' not found — script may have crashed" |
| 190 | + exit 1 |
| 191 | + fi |
| 192 | +
|
| 193 | + - name: Validate generated files (${{ matrix.name }}) |
| 194 | + run: | |
| 195 | + W="${{ matrix.prefix }}/wannier" |
| 196 | + ERR=0 |
| 197 | +
|
| 198 | + check() { |
| 199 | + if [ -f "$1" ]; then |
| 200 | + echo " [OK] $1 ($(wc -c < "$1") bytes)" |
| 201 | + else |
| 202 | + echo " [FAIL] $1 (not found)" |
| 203 | + ERR=1 |
| 204 | + fi |
| 205 | + } |
| 206 | +
|
| 207 | + echo "==========================================" |
| 208 | + echo " NSCF input files (from step2)" |
| 209 | + echo "==========================================" |
| 210 | + check "$W/INPUT" |
| 211 | + check "$W/KPT" |
| 212 | + check "$W/STRU" |
| 213 | +
|
| 214 | + # ── INPUT content checks ────────────────────── |
| 215 | + if [ -f "$W/INPUT" ]; then |
| 216 | + echo "" |
| 217 | + echo "--- INPUT keyword checks ---" |
| 218 | +
|
| 219 | + for key in "calculation" "towannier90" "nspin"; do |
| 220 | + if grep -qi "$key" "$W/INPUT"; then |
| 221 | + echo " [OK] INPUT contains '$key'" |
| 222 | + else |
| 223 | + echo " [FAIL] INPUT missing '$key'" |
| 224 | + ERR=1 |
| 225 | + fi |
| 226 | + done |
| 227 | +
|
| 228 | + # gamma_only is FORBIDDEN |
| 229 | + if grep -qi "gamma_only" "$W/INPUT"; then |
| 230 | + echo " [FAIL] INPUT contains 'gamma_only' — forbidden!" |
| 231 | + ERR=1 |
| 232 | + else |
| 233 | + echo " [OK] INPUT does NOT contain 'gamma_only'" |
| 234 | + fi |
| 235 | + fi |
| 236 | +
|
| 237 | + # ── Basis-specific checks ───────────────────── |
| 238 | + echo "" |
| 239 | + echo "--- Basis-specific checks (${{ matrix.basis }}) ---" |
| 240 | +
|
| 241 | + if [ "${{ matrix.basis }}" = "pw" ]; then |
| 242 | + if grep -qi "orbital_file\|orbital_dir" "$W/INPUT"; then |
| 243 | + echo " [FAIL] PW INPUT contains orbital_file/orbital_dir" |
| 244 | + ERR=1 |
| 245 | + else |
| 246 | + echo " [OK] PW INPUT does NOT contain orbital_file/orbital_dir" |
| 247 | + fi |
| 248 | + else |
| 249 | + if grep -qi "orbital_file\|orbital_dir" "$W/INPUT"; then |
| 250 | + echo " [OK] LCAO INPUT contains orbital_file/orbital_dir" |
| 251 | + else |
| 252 | + echo " [WARN] LCAO INPUT missing orbital references (may be in STRU)" |
| 253 | + fi |
| 254 | + fi |
| 255 | +
|
| 256 | + # ── KPT sanity ──────────────────────────────── |
| 257 | + if [ -f "$W/KPT" ]; then |
| 258 | + echo "" |
| 259 | + klines=$(wc -l < "$W/KPT") |
| 260 | + if [ "$klines" -gt 5 ]; then |
| 261 | + echo " [OK] KPT has $klines lines (reasonable)" |
| 262 | + else |
| 263 | + echo " [FAIL] KPT too short ($klines lines)" |
| 264 | + ERR=1 |
| 265 | + fi |
| 266 | + fi |
| 267 | +
|
| 268 | + echo "" |
| 269 | + if [ "$ERR" -ne 0 ]; then |
| 270 | + echo "==========================================" |
| 271 | + echo " VALIDATION FAILED" |
| 272 | + echo "==========================================" |
| 273 | + exit 1 |
| 274 | + fi |
| 275 | + echo "==========================================" |
| 276 | + echo " ALL CHECKS PASSED (${{ matrix.name }})" |
| 277 | + echo "==========================================" |
| 278 | +
|
| 279 | + # ================================================================ |
| 280 | + # Debug (always runs, even on failure) |
| 281 | + # ================================================================ |
| 282 | + - name: Show full script output |
| 283 | + if: always() |
| 284 | + run: cat run.log |
| 285 | + |
| 286 | + - name: Show generated INPUT |
| 287 | + if: always() |
| 288 | + run: | |
| 289 | + f="${{ matrix.prefix }}/wannier/INPUT" |
| 290 | + if [ -f "$f" ]; then echo "=== $f ==="; cat "$f"; else echo "$f not found"; fi |
| 291 | +
|
| 292 | + - name: Show generated KPT (first 20 lines) |
| 293 | + if: always() |
| 294 | + run: | |
| 295 | + f="${{ matrix.prefix }}/wannier/KPT" |
| 296 | + if [ -f "$f" ]; then echo "=== $f (first 20 lines) ==="; head -20 "$f"; else echo "$f not found"; fi |
| 297 | +
|
| 298 | + - name: List all generated files |
| 299 | + if: always() |
| 300 | + run: find ${{ matrix.prefix }} -type f | sort |
| 301 | + |
0 commit comments