Skip to content

Commit 03212d0

Browse files
committed
Modify path and add a CI/CD workflow.
1 parent c23c9d0 commit 03212d0

4 files changed

Lines changed: 328 additions & 39 deletions

File tree

.github/workflows/interface.yml

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
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+

interfaces/Wannier90_interface/examples_python/example_advanced.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ def main():
6565
# ----------------------------------------------------------
6666
# 3. Dependency files
6767
# ----------------------------------------------------------
68-
job.pp_orbitals = {"Bi": "Bi.upf", "Se": "Se.upf"}
69-
job.orbital_files = ["Bi.orb", "Se.orb"]
68+
job.pp_orbitals = {"Bi": "../../../tests/PP_ORB/for_interface/Wannier90_interface/Bi.upf", "Se": "../../../tests/PP_ORB/for_interface/Wannier90_interface/Se.upf"}
69+
job.orbital_files = ["../../../tests/PP_ORB/for_interface/Wannier90_interface/Bi.orb", "../../../tests/PP_ORB/for_interface/Wannier90_interface/Se.orb"]
7070

7171
# ----------------------------------------------------------
7272
# 4. Advanced Parameter Configuration

interfaces/Wannier90_interface/examples_python/example_basic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ def main():
6565
# ----------------------------------------------------------
6666
# 3. Dependency files
6767
# ----------------------------------------------------------
68-
job.pp_orbitals = {"Bi": "Bi.upf", "Se": "Se.upf"}
69-
job.orbital_files = ["Bi.orb", "Se.orb"]
68+
job.pp_orbitals = {"Bi": "../../../tests/PP_ORB/for_interface/Wannier90_interface/Bi.upf", "Se": "../../../tests/PP_ORB/for_interface/Wannier90_interface/Se.upf"}
69+
job.orbital_files = ["../../../tests/PP_ORB/for_interface/Wannier90_interface/Bi.orb", "../../../tests/PP_ORB/for_interface/Wannier90_interface/Se.orb"]
7070

7171
# ----------------------------------------------------------
7272
# 4. Wannier90 Parameters
@@ -156,7 +156,7 @@ def main():
156156
print(f"\n[FILE ERROR] {e}")
157157
print(" [1] wannier90.x in PATH? → which wannier90.x")
158158
print(" [2] abacus in PATH? → which abacus")
159-
print(" [3] PP / orbital files? → ls *.upf *.orb")
159+
print(" [3] PP / orbital files? → ls ../../../tests/PP_ORB/for_interface/Wannier90_interface/*.upf ../../../tests/PP_ORB/for_interface/Wannier90_interface/*.orb")
160160
except RuntimeError as e:
161161
print(f"\n[RUNTIME ERROR] {e}")
162162
except ValueError as e:

0 commit comments

Comments
 (0)