Initial commit
This commit is contained in:
276
verify.py
Executable file
276
verify.py
Executable file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify that the Firedrake conda environment is correctly configured.
|
||||
|
||||
Checks:
|
||||
1. Environment variables (PETSC_DIR, CC, etc.)
|
||||
2. Conda-provided libraries (PETSc, MPI, HDF5)
|
||||
3. Python imports (petsc4py, mpi4py, firedrake, icepack)
|
||||
4. JIT compilation (the most likely failure point)
|
||||
5. A minimal PDE solve
|
||||
|
||||
Run with:
|
||||
conda activate firedrake
|
||||
python verify.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import importlib
|
||||
import ctypes
|
||||
from pathlib import Path
|
||||
|
||||
PASS = "\033[92m✓\033[0m"
|
||||
FAIL = "\033[91m✗\033[0m"
|
||||
WARN = "\033[93m!\033[0m"
|
||||
passed = 0
|
||||
failed = 0
|
||||
warned = 0
|
||||
|
||||
|
||||
def check(description, condition, detail=""):
|
||||
global passed, failed
|
||||
if condition:
|
||||
print(f" {PASS} {description}")
|
||||
passed += 1
|
||||
return True
|
||||
else:
|
||||
msg = f" {FAIL} {description}"
|
||||
if detail:
|
||||
msg += f" ({detail})"
|
||||
print(msg)
|
||||
failed += 1
|
||||
return False
|
||||
|
||||
|
||||
def warn_check(description, detail=""):
|
||||
global warned
|
||||
msg = f" {WARN} {description}"
|
||||
if detail:
|
||||
msg += f" ({detail})"
|
||||
print(msg)
|
||||
warned += 1
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print("\n1. Environment variables")
|
||||
print("─" * 40)
|
||||
|
||||
conda_prefix = os.environ.get("CONDA_PREFIX", "")
|
||||
check("CONDA_PREFIX is set", bool(conda_prefix), conda_prefix or "not set")
|
||||
|
||||
petsc_dir = os.environ.get("PETSC_DIR", "")
|
||||
check(
|
||||
"PETSC_DIR points to conda prefix",
|
||||
petsc_dir == conda_prefix,
|
||||
f"PETSC_DIR={petsc_dir!r}, expected {conda_prefix!r}"
|
||||
)
|
||||
|
||||
petsc_arch = os.environ.get("PETSC_ARCH", "")
|
||||
check(
|
||||
"PETSC_ARCH is empty (prefix install)",
|
||||
petsc_arch == "",
|
||||
f"PETSC_ARCH={petsc_arch!r}"
|
||||
)
|
||||
|
||||
cc = os.environ.get("CC", "")
|
||||
check("CC is set to mpicc", cc == "mpicc", f"CC={cc!r}")
|
||||
|
||||
hdf5_mpi = os.environ.get("HDF5_MPI", "")
|
||||
check("HDF5_MPI=ON", hdf5_mpi == "ON", f"HDF5_MPI={hdf5_mpi!r}")
|
||||
|
||||
pyop2_cflags = os.environ.get("PYOP2_CFLAGS", "")
|
||||
check("PYOP2_CFLAGS has -fPIC", "-fPIC" in pyop2_cflags, f"PYOP2_CFLAGS={pyop2_cflags!r}")
|
||||
|
||||
pyop2_ldflags = os.environ.get("PYOP2_LDFLAGS", "")
|
||||
check("PYOP2_LDFLAGS has -shared", "-shared" in pyop2_ldflags, f"PYOP2_LDFLAGS={pyop2_ldflags!r}")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print("\n2. Shared libraries")
|
||||
print("─" * 40)
|
||||
|
||||
if conda_prefix:
|
||||
lib_dir = Path(conda_prefix) / "lib"
|
||||
ext = "dylib" if sys.platform == "darwin" else "so"
|
||||
|
||||
for lib_name in ["libpetsc", "libmpi", "libhdf5"]:
|
||||
lib_path = lib_dir / f"{lib_name}.{ext}"
|
||||
if not lib_path.exists():
|
||||
# Try finding any matching file
|
||||
matches = list(lib_dir.glob(f"{lib_name}*"))
|
||||
found = len(matches) > 0
|
||||
detail = str(matches[0]) if found else f"no {lib_name}* in {lib_dir}"
|
||||
else:
|
||||
found = True
|
||||
detail = str(lib_path)
|
||||
check(f"{lib_name} found", found, detail)
|
||||
else:
|
||||
warn_check("Skipping library checks (CONDA_PREFIX not set)")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print("\n3. Python imports")
|
||||
print("─" * 40)
|
||||
|
||||
modules = [
|
||||
("numpy", "numpy"),
|
||||
("mpi4py", "mpi4py"),
|
||||
("petsc4py", "petsc4py"),
|
||||
("h5py", "h5py"),
|
||||
("ufl", "ufl"),
|
||||
("FIAT", "FIAT"),
|
||||
("finat", "finat"),
|
||||
("loopy", "loopy"),
|
||||
("tsfc", "tsfc"),
|
||||
("pyop2", "pyop2"),
|
||||
("firedrake", "firedrake"),
|
||||
("icepack", "icepack"),
|
||||
]
|
||||
|
||||
imported = {}
|
||||
for display_name, module_name in modules:
|
||||
try:
|
||||
mod = importlib.import_module(module_name)
|
||||
version = getattr(mod, "__version__", "?")
|
||||
check(f"import {display_name}", True, f"v{version}")
|
||||
imported[module_name] = mod
|
||||
except Exception as e:
|
||||
check(f"import {display_name}", False, str(e))
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print("\n4. PETSc configuration")
|
||||
print("─" * 40)
|
||||
|
||||
if "petsc4py" in imported:
|
||||
from petsc4py import PETSc as petsc
|
||||
|
||||
# Check that PETSc has key external packages
|
||||
has_mumps = petsc.Sys.hasExternalPackage("mumps")
|
||||
check("PETSc has MUMPS", has_mumps)
|
||||
|
||||
has_hypre = petsc.Sys.hasExternalPackage("hypre")
|
||||
check("PETSc has hypre", has_hypre)
|
||||
|
||||
has_superlu = petsc.Sys.hasExternalPackage("superlu_dist")
|
||||
check("PETSc has SuperLU_dist", has_superlu)
|
||||
|
||||
# Scalar type
|
||||
import numpy as np
|
||||
scalar = petsc.ScalarType
|
||||
is_real = scalar in (float, np.float64, np.float32)
|
||||
check("PETSc scalar type is real", is_real, f"scalar={scalar}")
|
||||
else:
|
||||
warn_check("Skipping PETSc config checks (import failed)")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print("\n5. JIT compilation test")
|
||||
print("─" * 40)
|
||||
|
||||
if "firedrake" in imported:
|
||||
try:
|
||||
import firedrake
|
||||
|
||||
# This triggers the full JIT pipeline:
|
||||
# UFL form → TSFC → loopy → C code → compile → dlopen
|
||||
mesh = firedrake.UnitSquareMesh(4, 4)
|
||||
V = firedrake.FunctionSpace(mesh, "CG", 1)
|
||||
u = firedrake.TrialFunction(V)
|
||||
v = firedrake.TestFunction(V)
|
||||
a = firedrake.inner(firedrake.grad(u), firedrake.grad(v)) * firedrake.dx
|
||||
A = firedrake.assemble(a)
|
||||
|
||||
check("JIT compilation works", True, "assembled a Laplacian matrix")
|
||||
except Exception as e:
|
||||
check("JIT compilation works", False, str(e))
|
||||
# Try to find and display the actual compiler error
|
||||
import glob
|
||||
err_files = sorted(
|
||||
glob.glob("/tmp/pyop2-tempcache-*/**/*.err", recursive=True),
|
||||
key=lambda f: Path(f).stat().st_mtime,
|
||||
reverse=True,
|
||||
)
|
||||
log_files = sorted(
|
||||
glob.glob("/tmp/pyop2-tempcache-*/**/*.log", recursive=True),
|
||||
key=lambda f: Path(f).stat().st_mtime,
|
||||
reverse=True,
|
||||
)
|
||||
for label, files in [("COMPILE ERRORS", err_files), ("COMPILE LOG", log_files)]:
|
||||
if files:
|
||||
content = Path(files[0]).read_text().strip()
|
||||
if content:
|
||||
print(f"\n ── {label}: {files[0]} ──")
|
||||
for line in content.splitlines()[-20:]:
|
||||
print(f" {line}")
|
||||
else:
|
||||
warn_check("Skipping JIT test (firedrake import failed)")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print("\n6. Minimal PDE solve")
|
||||
print("─" * 40)
|
||||
|
||||
if "firedrake" in imported:
|
||||
try:
|
||||
import firedrake
|
||||
from firedrake import *
|
||||
|
||||
mesh = UnitSquareMesh(8, 8)
|
||||
V = FunctionSpace(mesh, "CG", 1)
|
||||
|
||||
u = TrialFunction(V)
|
||||
v = TestFunction(V)
|
||||
|
||||
x, y = SpatialCoordinate(mesh)
|
||||
f = sin(pi * x) * sin(pi * y)
|
||||
|
||||
a = inner(grad(u), grad(v)) * dx
|
||||
L = f * v * dx
|
||||
bc = DirichletBC(V, 0, "on_boundary")
|
||||
|
||||
u_sol = Function(V)
|
||||
solve(a == L, u_sol, bcs=bc)
|
||||
|
||||
# Check the solution is reasonable
|
||||
import numpy as np
|
||||
u_data = u_sol.dat.data_ro
|
||||
check(
|
||||
"Poisson solve produces valid output",
|
||||
np.all(np.isfinite(u_data)) and np.max(np.abs(u_data)) > 0,
|
||||
f"max|u| = {np.max(np.abs(u_data)):.6f}",
|
||||
)
|
||||
except Exception as e:
|
||||
check("Poisson solve", False, str(e))
|
||||
else:
|
||||
warn_check("Skipping PDE solve (firedrake import failed)")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print("\n7. icepack smoke test")
|
||||
print("─" * 40)
|
||||
|
||||
if "icepack" in imported and "firedrake" in imported:
|
||||
try:
|
||||
import icepack
|
||||
import firedrake
|
||||
|
||||
# Check that core model classes exist and instantiate
|
||||
model = icepack.models.IceShelf()
|
||||
check("icepack.models.IceShelf()", True)
|
||||
|
||||
model = icepack.models.IceStream()
|
||||
check("icepack.models.IceStream()", True)
|
||||
|
||||
# Check rate factor function
|
||||
A = icepack.rate_factor(260.0)
|
||||
check(
|
||||
"icepack.rate_factor(260 K)",
|
||||
A > 0,
|
||||
f"A = {A:.4e}",
|
||||
)
|
||||
except Exception as e:
|
||||
check("icepack smoke test", False, str(e))
|
||||
else:
|
||||
warn_check("Skipping icepack test (import failed)")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print("\n" + "═" * 50)
|
||||
print(f"Results: {PASS} {passed} passed {FAIL} {failed} failed {WARN} {warned} warnings")
|
||||
print("═" * 50)
|
||||
|
||||
sys.exit(0 if failed == 0 else 1)
|
||||
Reference in New Issue
Block a user