Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
# Single line: PowerShell does not support backslash line continuation.
run: pytest -m "not network" --ignore=tests/test_session_calc.py --ignore=tests/test_optimizer.py --ignore=tests/test_preopt.py --no-cov --no-header -q

# ── Windows: real PyFock Phase-1 calculation ───────────────────────────────
# ── Windows: real PyFock calculation ───────────────────────────────────────
test-windows-pyfock:
name: PyFock integration (windows-latest, Python 3.11)
runs-on: windows-latest
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

### Added

- **PyFock geometry and analysis phase** — native-Windows PBE/def2 geometry
optimization now uses PyFock's ASE calculator with analytical density-fitted
gradients. Single-point and final optimized-geometry results retain orbital
arrays, Mulliken charges, dipoles, and engine-native cube generation.

- **PyFock Phase-1 backend (M-PYFOCK PYF.2–PYF.6)** — normalized engine
dispatch, native-Windows PBE/def2 single points for neutral closed-shell
molecules, capability-driven calculation menus, engine provenance in logs
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ Real output from QuantUI, straight from the app:
- **In-session calculations** — RHF, UHF, 9 DFT functionals, MP2, CCSD,
CCSD(T), NMR shielding, TD-DFT UV-Vis, and 1D PES scans via PySCF, running
in your Python kernel. Optional PyFock 0.1.x adds native-Windows PBE
single points for neutral, closed-shell molecules with def2-SVP/def2-TZVP;
single points and geometry optimizations for neutral, closed-shell molecules
with def2-SVP/def2-TZVP, including orbital and population analysis;
engine capabilities automatically gate the setup menus
- **Implicit solvent** — PCM solvation (Water, Ethanol, THF, DMSO,
Acetonitrile) via a single checkbox
Expand Down Expand Up @@ -127,7 +128,7 @@ Real output from QuantUI, straight from the app:
| --- | --- | --- |
| Linux / macOS | Full | PySCF installs natively |
| WSL (Windows) | Full | Use an Ubuntu WSL environment |
| Windows (native) | Partial | PyFock runs guarded PBE single points; use WSL/Apptainer for the full PySCF feature set |
| Windows (native) | Partial | PyFock runs guarded PBE single points and geometry optimizations; use WSL/Apptainer for the full PySCF feature set |

### Windows users: native PyFock or the full container

Expand All @@ -140,11 +141,12 @@ python -m pip install "quantui[pyfock,ase,app]"
```

In **System Settings → Quantum engine**, select PyFock (or leave Automatic
selected when PySCF is absent). Phase 1 intentionally supports only neutral,
closed-shell PBE single points with def2-SVP or def2-TZVP. Density fitting is
always enabled. Hybrids, charged/open-shell systems, geometry optimization,
solvent, checkpoint warm starts, GPU, Mulliken/dipole analysis, and orbital
export remain PySCF-only.
selected when PySCF is absent). The validated subset supports neutral,
closed-shell PBE single points and geometry optimizations with def2-SVP or
def2-TZVP. Density fitting is always enabled, analytical gradients drive
optimization, and orbital, Mulliken, dipole, and cube analysis are retained.
Hybrids, charged/open-shell systems, solvent, checkpoint warm starts, and GPU
remain PySCF-only.

PySCF does not install on Windows natively. For the complete feature set, the
[`apptainer/quantui.def`](https://github.com/The-Schultz-Lab/QuantUI/blob/main/apptainer/quantui.def) container bundles
Expand Down
9 changes: 5 additions & 4 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ pip install "quantui[pyscf,ase,app]"

## Windows

For native Windows and the guarded PyFock single-point subset, use Python 3.11:
For native Windows and the guarded PyFock subset, use Python 3.11:

```powershell
py -3.11 -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install "quantui[pyfock,ase,app]"
```

Automatic engine selection uses PyFock when PySCF is absent. Phase 1 supports
neutral, closed-shell PBE/def2-SVP or PBE/def2-TZVP single points. For all
Automatic engine selection uses PyFock when PySCF is absent. The validated
subset supports neutral, closed-shell PBE/def2-SVP or PBE/def2-TZVP single
points and geometry optimizations. For all
other methods and workflows, use WSL or the Apptainer container described in
[Platform Support](platforms.md).

Expand All @@ -45,7 +46,7 @@ other methods and workflows, use WSL or the Apptainer container described in
| Extra | What it adds |
| --- | --- |
| `pyscf` | Canonical PySCF backend (full calculation and analysis feature set) |
| `pyfock` | PyFock Phase-1 backend (native-Windows PBE single points) |
| `pyfock` | PyFock backend (native-Windows PBE single points and geometry optimization) |
| `ase` | ASE bridge for trajectory export and structure I/O |
| `app` | Voilà, JupyterLab, and notebook launcher dependencies |
| `xtb` | GFN-FF metal pre-optimization via xtb |
Expand Down
8 changes: 4 additions & 4 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ PyFock subset or move to WSL/Apptainer for the full feature set.
| --- | --- | --- |
| **Linux / macOS** | :material-check-circle:{ .green } Full | PySCF installs natively via conda or pip |
| **WSL (Windows)** | :material-check-circle:{ .green } Full | Ubuntu WSL environment — follows the Linux path exactly |
| **Windows native** | :material-alert-circle:{ .yellow } Partial | PyFock: neutral closed-shell PBE/def2 single points; WSL/Apptainer: full PySCF feature set |
| **Windows native** | :material-alert-circle:{ .yellow } Partial | PyFock: neutral closed-shell PBE/def2 single points and geometry optimizations; WSL/Apptainer: full PySCF feature set |

!!! info "Windows users"
Install `quantui[pyfock,ase,app]` under Python 3.11 for native PBE single
points. QuantUI's engine picker hides unsupported controls. For hybrids,
ions, radicals, optimizations, spectra, solvent, checkpointing, GPU, and
orbital analysis, use [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install)
points and geometry optimizations. QuantUI's engine picker hides unsupported
controls. For hybrids, ions, radicals, spectra, solvent, checkpointing, and
GPU, use [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install)
with Ubuntu or the
[Apptainer container](https://github.com/The-Schultz-Lab/QuantUI/blob/main/apptainer/README.md)
which bundles the complete environment in a single file.
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ pyscf = [
# extra supplies and caps that runtime dependency explicitly.
pyfock = [
"pyfock>=0.1.7,<0.2",
# PyFock's ASE calculator is the geometry-optimization path used by
# QuantUI; include ASE here so the documented backend extra is complete.
"ase>=3.22.0,<4",
"scipy>=1.10,<2",
# 0.68.0rc1 can be selected by PyFock's permissive floor and brought a
# prerelease llvmlite that crashed at import on Linux/Python 3.12.
Expand Down
101 changes: 78 additions & 23 deletions quantui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,7 @@ def __init__(self) -> None:
self._last_orb_mo_occ: Any = None
self._last_orb_mol_atom: Any = None
self._last_orb_mol_basis: Any = None
self._last_orb_engine_id: str = "pyscf"
# Mulliken Populations panel state (table + Plotly bar chart).
self._last_mulliken_symbols: Any = None
self._last_mulliken_charges: Any = None
Expand Down Expand Up @@ -4076,9 +4077,7 @@ def _apply_quantum_engine_capabilities(self) -> None:
self.execution_backend_dd.value = "local"
self.solvent_cb.value = False
self.solvent_cb.disabled = True
self.solvent_cb.description = (
"Implicit solvent — unavailable in PyFock Phase 1"
)
self.solvent_cb.description = "Implicit solvent — unavailable in PyFock"
self._freq_preopt_cb.value = False
self._freq_preopt_cb.disabled = True
self._freq_preopt_cb.layout.display = "none"
Expand Down Expand Up @@ -5791,7 +5790,7 @@ def _on_scf_converged() -> None:

def _run_required_final_single_point(target_mol, reason: str):
"""Run a required post-optimization single point on target geometry."""
from quantui import run_in_session
from quantui.engines import EnginePreference, EngineRequest, run_calc

_solvent = self.solvent_dd.value if self.solvent_cb.value else None
self.run_status.value = (
Expand All @@ -5801,13 +5800,28 @@ def _run_required_final_single_point(target_mol, reason: str):
f"\n-- Required single-point ({reason}) "
"on optimized geometry --------------------------------\n"
)
sp_result = run_in_session(
molecule=target_mol,
method=self.method_dd.value,
basis=self.basis_dd.value,
progress_stream=log, # type: ignore[arg-type]
solvent=_solvent,
sp_result = run_calc(
EngineRequest(
request_id=_uuid.uuid4().hex,
calc_type="single_point",
method=self.method_dd.value,
basis=self.basis_dd.value,
charge=int(target_mol.charge),
multiplicity=int(target_mol.multiplicity),
molecule={
"atoms": list(target_mol.atoms),
"coordinates": [list(c) for c in target_mol.coordinates],
},
options={"verbose": 4, "scf_rescue": True},
progress_stream=log, # type: ignore[arg-type]
solvent=_solvent,
),
preferred=cast(
EnginePreference,
self._user_settings.compute.quantum_engine,
),
)
sp_result = sp_result.to_session_result()
if not bool(getattr(sp_result, "converged", False)):
raise RuntimeError(
"Required post-optimization single-point did not converge."
Expand Down Expand Up @@ -5961,7 +5975,7 @@ def _run_required_final_single_point(target_mol, reason: str):
)

self.run_status.value = "Optimizing geometry..."
from quantui import optimize_geometry
from quantui.engines import EnginePreference, EngineRequest, run_calc

# History-based expected step count → "step k/~N" + a floor
# for the live progress fraction. None on cold history.
Expand All @@ -5973,19 +5987,35 @@ def _run_required_final_single_point(target_mol, reason: str):
)
log.opt_fmax_target = float(self.fmax_fi.value)
log.opt_fmax0 = None
result = optimize_geometry(
molecule=calc_mol,
method=self.method_dd.value,
basis=self.basis_dd.value,
fmax=self.fmax_fi.value,
steps=self.max_steps_si.value,
progress_stream=log, # type: ignore[arg-type]
expected_steps=(
int(round(_expected_steps)) if _expected_steps else None
result = run_calc(
EngineRequest(
request_id=_uuid.uuid4().hex,
calc_type="geometry_opt",
method=self.method_dd.value,
basis=self.basis_dd.value,
charge=int(calc_mol.charge),
multiplicity=int(calc_mol.multiplicity),
molecule={
"atoms": list(calc_mol.atoms),
"coordinates": [list(c) for c in calc_mol.coordinates],
},
options={
"fmax": self.fmax_fi.value,
"steps": self.max_steps_si.value,
"expected_steps": (
int(round(_expected_steps)) if _expected_steps else None
),
"resume": _resume,
"scf_rescue": True,
},
progress_stream=log, # type: ignore[arg-type]
checkpoint=_ckpt,
),
checkpoint=_ckpt,
resume=_resume,
)
preferred=cast(
EnginePreference,
self._user_settings.compute.quantum_engine,
),
).to_session_result()
_sp_result = _run_required_final_single_point(
result.molecule,
"after geometry optimisation",
Expand Down Expand Up @@ -6017,6 +6047,19 @@ def _run_required_final_single_point(target_mol, reason: str):
"pyscf_mol_basis",
result.pyscf_mol_basis,
)
result.atom_symbols = getattr(
_sp_result, "atom_symbols", result.atom_symbols
)
result.mulliken_charges = getattr(
_sp_result, "mulliken_charges", result.mulliken_charges
)
result.dipole_moment_debye = getattr(
_sp_result, "dipole_moment_debye", result.dipole_moment_debye
)
result.dipole_vector_debye = getattr(
_sp_result, "dipole_vector_debye", result.dipole_vector_debye
)
result.engine_id = getattr(_sp_result, "engine_id", result.engine_id)
result_html = self._format_opt_result(result)
save_spectra, save_type = {}, "geometry_opt"
elif ct == "Frequency":
Expand Down Expand Up @@ -6961,6 +7004,18 @@ def _begin_run_checkpoint(self, log_stream: Optional[Any] = None) -> Optional[An
the reason a calculation doesn't start.
"""
try:
from quantui.engines import EnginePreference, resolve_engine

caps = resolve_engine(
cast(
EnginePreference,
self._user_settings.compute.quantum_engine,
)
).capabilities()
if not caps.supports_checkpoint_warm_start:
self._checkpoint_resumable = False
return None

from quantui.app_runflow import checkpoint_identity
from quantui.checkpoint import Checkpoint

Expand Down
1 change: 1 addition & 0 deletions quantui/app_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ def apply_analysis_context(app: Any, ctx: Any) -> None:
app._last_orb_mo_coeff = None
app._last_orb_mo_occ = None
app._last_orb_method = None
app._last_orb_engine_id = "pyscf"
# Mulliken state consumed by the Populations panel — reset so a context
# without charges cannot leak the prior calc's chart into this one.
app._last_mulliken_symbols = None
Expand Down
11 changes: 10 additions & 1 deletion quantui/app_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,7 @@ def show_orbital_diagram(app: Any, result: Any) -> bool:
app._last_orb_mo_occ = mo_occ
app._last_orb_mol_atom = getattr(result, "pyscf_mol_atom", None)
app._last_orb_mol_basis = getattr(result, "pyscf_mol_basis", None)
app._last_orb_engine_id = str(getattr(result, "engine_id", "pyscf") or "pyscf")
# AUDIT additional-concerns — snapshot the method that actually
# produced this mo_coeff, from the result object itself, rather than
# reading the live Method dropdown at cube-generation time (below).
Expand Down Expand Up @@ -1580,6 +1581,7 @@ def _show_range_err() -> None:

from quantui.orbital_visualization import (
generate_cube_from_arrays,
generate_pyfock_cube_from_arrays,
infer_charge_and_spin,
plot_cube_isosurface,
render_orbital_isosurface_py3dmol,
Expand Down Expand Up @@ -1641,7 +1643,14 @@ def _show_range_err() -> None:
# the dropdown shows later — immutable result provenance instead
# of a mutable, disconnectable UI control.
_method_for_provenance = str(getattr(app, "_last_orb_method", "") or "")
generate_cube_from_arrays(
# The PySCF and PyFock generators have intentionally different
# optional keyword sets; both accept this shared call shape.
_cube_generator: Any = (
generate_pyfock_cube_from_arrays
if getattr(app, "_last_orb_engine_id", "pyscf") == "pyfock"
else generate_cube_from_arrays
)
_cube_generator(
mol_atom,
mol_basis,
mo_coeff,
Expand Down
Loading