Repository navigation
Add generalized active space self-consistent field solver - #211
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
A critical scanner reset defect and several moderate correctness, performance, validation, and concurrency issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a generalized active-space self-consistent-field solver built on GASCI and PySCF’s Newton/CIAH framework.
Changes:
- Adds GASSCF orbital optimization, state averaging, scanners, canonicalization, and density fitting.
- Adds reusable GAS contraction and spin-plan support.
- Adds tests and a restricted-GAS usage example.
File summaries
| File | Summary |
|---|---|
pyscf/mcscf/test/test_gasci.py |
Adds spin-plan lifetime and validation tests. |
pyscf/mcscf/gasscf.py |
Implements the GASSCF driver. Findings include one critical scanner/DF reset issue, five moderate issues involving ERI reuse, global stderr, input validation, RAS handling, and scanner invariants, plus one documentation nit. |
pyscf/mcscf/fci_gas.py |
Adds reusable GAS spin-contraction planning support. |
examples/mcscf/93_gasscf.py |
Demonstrates restricted-GAS orbital optimization. |
Review details
Suppressed comments (5)
pyscf/mcscf/gasscf.py:1152
- The
erisobject supplied by the native Newton driver is discarded here, and_run_fixed_orbital_gascialways recomputes the active-space two-electron integrals from the orbitals. This forces a fresh AO-to-MO transformation on every macro iteration (including DF calculations), defeating the precomputed-ERI path and adding a substantial avoidable cost.
e_tot, e_gas, ci = self._run_fixed_orbital_gasci(
mo_coeff, ci0, verbose)
pyscf/mcscf/gasscf.py:451
- This repeats the same eager
intconversion for an explicit solver, so a caller can pass a non-integral or Booleansolver.gas_orbsand have it silently changed rather than rejected according to the GASCI input contract. Preserve the original values and validate them before adapting the solver.
solver.gas_orbs = tuple(int(value) for value in solver.gas_orbs)
pyscf/mcscf/gasscf.py:453
- Explicit GASCI solvers with RAS restrictions have the same valid zero-sized RAS1/RAS3 cases, but this check rejects them before
normalize_gas_speccan remove empty spaces. This makes the explicit-solver construction path inconsistent with GASCI and with the documented RAS input format.
if any(value <= 0 for value in solver.gas_orbs):
raise ValueError("explicit GASCI solver gas_orbs entries must be positive")
pyscf/mcscf/gasscf.py:1484
- The public setter also truncates non-integral and Boolean values, so mutating
mc.gas_orbsafter construction bypasses the strict GASCI validation even if the constructor is corrected. Route this assignment through the same integer validator instead of converting withint.
self.fcisolver.gas_orbs = (
None if value is None else tuple(int(item) for item in value))
pyscf/mcscf/gasscf.py:501
extrasymdirectly changesuniq_var_indicesand therefore the orbital variables optimized by a scanner (see lines 937-940), but it is not part of this signature. If a caller changesscanner.extrasymafter creating the scanner,__call__silently accepts a different optimization problem instead of raising like it does forfrozen; include the orbital-mask constraint in the scanner invariants.
"frozen": _digest(mc.frozen),
- Files reviewed: 4/5 changed files
- Comments generated: 5
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| self.mo_coeff, copy=True) | ||
| guess_ci = _copy_ci(self.ci if ci0 is None else ci0, signature) | ||
|
|
||
| self.reset(mol) |
There was a problem hiding this comment.
_DFGASSCF inherits PySCF's _DFCASSCF / _DFCAS, and _DFCAS.reset() already calls self.with_df.reset(mol) before forwarding to super().reset(mol). Therefore the auxiliary DF object is already rebound to the new molecule when the scanner calls self.reset(mol). I left the reset implementation unchanged, but added test_df_scanner_reset_updates_with_df_molecule in 3c9a0ef to make this behavior explicit and guard it against regression.
| if gas_orbs is None: | ||
| raise ValueError( | ||
| "gas_orbs is required without an explicit GASCI solver") | ||
| gas_orbs = tuple(int(value) for value in gas_orbs) |
There was a problem hiding this comment.
Addressed in 3c9a0ef. GASSCF now uses a shared _validated_user_gas_orbs helper that delegates to the same strict integer validation used by GASCI (addons_gas._integer_vector), instead of eagerly converting entries with int. The constructor, explicit-solver adaptation path, and public gas_orbs setter now all reject floating-point and Boolean values. A regression test covers both (2.5,) and (True,).
| if any(value <= 0 for value in gas_orbs): | ||
| raise ValueError("gas_orbs entries must be positive integers") |
There was a problem hiding this comment.
Addressed in 3c9a0ef. I removed the GASSCF-specific requirement that every gas_orbs entry be strictly positive. GASSCF now preserves the user GAS definition and delegates the restriction semantics to the shared GASCI normalizer. This restores support for valid RAS specifications with empty RAS1/RAS3 spaces, such as gas_orbs=(0, 2, 0). The new regression test checks both the normal constructor and explicit-GASCI-solver construction paths and verifies that the kernel space is normalized to (2,).
| stderr = sys.stderr | ||
| restore_stderr = not isinstance(stderr, _GASSCFLogFilter) | ||
| if restore_stderr: | ||
| sys.stderr = _GASSCFLogFilter(stderr) |
There was a problem hiding this comment.
Addressed in 3c9a0ef. GASSCF no longer replaces process-global sys.stderr. The native Newton logging is now handled with a calculation-local _GASSCFLogger together with the existing stream-label filter. The logger suppresses the inherited SO-CASSCF experimental warning without mutating global process state. A regression test also checks that sys.stderr remains unchanged while the warning is filtered.
| Notes: | ||
| This first implementation stage establishes the public object and | ||
| solver ownership convention only. Later commits add the GAS orbital | ||
| rotation mask and native CIAH derivative adapter. |
There was a problem hiding this comment.
Addressed in 3c9a0ef. I updated the public GASSCF docstring and the remaining development-stage comments so they describe the current implementation: the native PySCF Newton/CIAH driver is reused, with GAS-specific CI/RDM/spin operations and the GAS orbital-rotation mask supplied by the adapter. The outdated references to later/staged implementation work have been removed.
|
Addressed the review findings in |
|
A short note on why this implementation is based on During development, we first explored a GASSCF extension of the We therefore tested several This motivated the final design used in this PR: reuse PySCF's existing The intent here is therefore not to replace |
|
I have completed a final pass over the current GASSCF implementation and the associated GASCI interfaces. The implementation now has the following structure:
The review feedback has also led to several robustness improvements. In particular, the current code has strict GAS input validation, support for valid zero-sized RAS edge spaces, calculation-local logging without global stream mutation, explicit ownership and lifetime rules for reusable GAS contraction/RDM/spin plans, scanner and DF resource handling, and GAS-preserving CI transformations for pseudo-natural orbital rotations. The regression coverage now includes the GAS-as-CAS limit, genuine restricted-GAS calculations, state averaging and zero-weight roots, DF and scanner paths, canonicalization invariance, spin-penalty physical/objective energy separation, helper-plan ownership/lifetime, GAS CI orbital transformations against full-FCI references, and finite-difference checks of the orbital gradient including inter-GAS rotations. The OpenMolcas comparison examples are kept separately, while Unsupported features are explicitly guarded rather than falling through to incompatible CASSCF implementations, including state-average-mix/state-specific excited-state wrappers, spatial symmetry, X2C, solvent models, analytic gradients/NACs, GPU conversion, and the legacy CASSCF optimization drivers. The commit |
Summary
This PR adds a generalized active space self-consistent field (GASSCF) implementation to
pyscf-forge. The implementation builds on the determinant-based GASCI solver and PySCF's existing second-order CASSCF frameworknewton_casscf. It includes:GASSCFdriver following the existing PySCF CASSCF interface.as_scanner().density_fit()andDFGASSCF.Implementation
GASSCF driver
The public interface is provided by:
and follows the existing PySCF MCSCF object style:
The GAS definition and restriction parsing are shared with
pyscf.mcscf.gasci. All GAS restriction formats accepted by GASCI are therefore supported by GASSCF, including explicit spin-supergroups, supergroups, cumulative-occupation restrictions, and RAS-style restrictions.Orbital optimization
The implementation reuses PySCF's native second-order CASSCF/Newton optimizer rather than introducing a separate orbital-optimization framework. The native augmented-Hessian machinery provides the macro/micro iteration structure, keyframes, trust-region handling, and orbital/CI Newton updates. The GASSCF layer adapts this machinery to the restricted GAS determinant space by providing:
In addition to the usual core-active, core-virtual, and active-virtual rotations, rotations between different GAS subspaces are included because they are nonredundant in GASSCF. Active-active rotations within the same GAS subspace remain redundant and are excluded. For a single GAS containing the complete active space, the implementation reduces to the ordinary CASSCF orbital-optimization problem and is tested against
newton_casscf.State averaging
Ordinary state-averaged GASSCF is supported through the existing PySCF state-average interface. Zero-weight roots are also supported. Such roots are solved and retained in the CI state space and can affect the optimization path, but do not contribute directly to the weighted state-average objective. The state-specific limit is validated using state-average weights of the form
and reproduces the corresponding state-specific stationary solution.
Canonicalization and scanners
GASSCF supports canonicalization of the core and virtual orbital spaces. Active space natural orbital rotations are not applied because rotations mixing different GAS subspaces can change the definition of a restricted GAS wave function.
Energy-only potential energy surface scans are supported through:
Density fitting
Density fitting is supported through the usual PySCF-style interface:
and through the
DFGASSCFfactory. Both construction paths are covered by the numerical regression tests.Tests
The implementation is tested at several levels.
GAS-as-CAS limit
A single GAS containing the complete active determinant space is compared with the existing PySCF
newton_casscfimplementation. This validates the GASSCF adapter and the native Newton driver in the limit where GASSCF reduces to CASSCF.Genuine restricted-GAS regression
A converged N2 genuine-GAS calculation is used as a numerical regression test:
Using converged OpenMolcas GASSCF orbitals, the fixed-orbital GASCI energy is
and the state-specific GASSCF result is
The difference is approximately
2e-11 Eh. The same regression fixture is used to validate:The zero-weight state-average result agrees with the state-specific energy to approximately
3e-11 Eh. Core/virtual canonicalization preserves the fixed-orbital GAS energy to approximately2e-12 Eh.Unit tests
The current test suites pass with:
They can be run with:
Scope
This PR is intentionally limited to GASSCF orbital optimization based on the existing determinant-based GASCI solver.
It does not add: