Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
8 changes: 4 additions & 4 deletions fanpy/scripts/gaussian/run_calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ def run_calc(
one_int_file,
two_int_file,
wfn_type,
nuc_nuc=None,
nuc_nuc=0.0,
optimize_orbs=False,
pspace_exc=None,
objective=None,
solver=None,
pspace_exc=(1, 2),
objective="projected",
solver="least_squares",
solver_kwargs=None,
wfn_kwargs=None,
ham_noise=None,
Expand Down
4 changes: 4 additions & 0 deletions fanpy/scripts/pyscf/make_fanci_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,10 @@ def main(): # pragma: no cover
help="Name of the file that contains the output of the script.",
)
args = parser.parse_args()
# format pspace excitations
if args.pspace_exc is not None and type(args.pspace_exc) is str:
args.pspace_exc = args.pspace_exc.strip("[]")
args.pspace_exc = [int(x) for x in args.pspace_exc.split(",")]
make_script(
args.geom,
args.wfn_type,
Expand Down
12 changes: 7 additions & 5 deletions fanpy/scripts/pyscf/pyscf_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
default="Angstrom",
help="Units for the geometry in the Hartree-Fock calculation. Default is 'angstrom'.")

parser.add_argument("--optimize_orbs",
type=bool,
default=False,
help="Whether to optimize orbitals in the Hartree-Fock calculation.")
parser.add_argument(
"--optimize_orbs",
action="store_true",
required=False,
help="Flag for optimizing orbitals. Orbitals are not optimized by default.",
)

parser.add_argument("--pspace_exc",
type=list,
type=str,
default=(1, 2),
help="Excitations to include in the pspace. E.g. '(1, 2)'")

Expand Down
39 changes: 21 additions & 18 deletions fanpy/scripts/pyscf/run_calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ def make_wfn_dirs(pattern: str, wfn_name: str, num_runs: int, rep_dirname_prefix
except FileExistsError:
pass

def write_wfn_py(pattern: str, wfn_type: str, optimize_orbs: bool=False,
pspace_exc=None, nproj=0, objective=None, solver=None,
def write_wfn_py(pattern: str, wfn_type: str, geom: list, basis: str, optimize_orbs: bool=False,
pspace_exc=None, objective=None, solver=None,
ham_noise=None, wfn_noise=None,
solver_kwargs=None, wfn_kwargs=None,
load_orbs=None, load_ham=None, load_wfn=None, load_chk=None, load_prev=False,
memory=None, filename=None, ncores=1, exclude=None, old_fanpy=False):
memory=None, filename=None, ncores=1, exclude=None, fanpy_only=False):

"""Make a script for running calculations.

Expand All @@ -100,6 +100,10 @@ def write_wfn_py(pattern: str, wfn_type: str, optimize_orbs: bool=False,
"basecc", "standardcc", "generalizedcc", "senioritycc", "pccd", "ccsd", "ccsdt", "ccsdtq",
"ap1rogsd", "ap1rogsd_spin", "apsetgd", "apsetgsd", "apg1rod", "apg1rosd",
"ccsdsen0", "ccsdqsen0", "ccsdtqsen0", "ccsdtsen2qsen0".
geom : list
List of atomic coordinates for PySCF.
basis : str
Basis set for PySCF.
optimize_orbs : bool
If True, orbitals are optimized.
If False, orbitals are not optimized.
Expand All @@ -109,10 +113,6 @@ def write_wfn_py(pattern: str, wfn_type: str, optimize_orbs: bool=False,
Orders of excitations that will be used to build the projection space.
Default is first, second, third, and fourth order excitations of the HF ground state.
Used for slower fanpy (i.e. `old_fanpy=True`)
nproj : int
Number of projection states that will be used.
Default uses all possible projection states (i.e. Slater determinants.
Used for faster fanpy (i.e. `old_fanpy=False`)
objective : str
Form of the Schrodinger equation that will be solved.
Use `system` to solve the Schrodinger equation as a system of equations.
Expand Down Expand Up @@ -171,9 +171,9 @@ def write_wfn_py(pattern: str, wfn_type: str, optimize_orbs: bool=False,
filename : str
Filename to save the generated script file.
Default just prints it out to stdout.
old_fanpy : bool
Use old, slower (but probably more robust) fanpy.
Default uses faster fanpy.
fanpy_only : bool
Use fanpy only, this is slower (but probably more robust).
Default uses faster fanpy with the PyCI interface.
Some features are not avaialble on new fanpy.

"""
Expand All @@ -186,7 +186,6 @@ def write_wfn_py(pattern: str, wfn_type: str, optimize_orbs: bool=False,

if pspace_exc is None:
pspace_exc = [1, 2, 3, 4]
pspace_exc = [str(i) for i in pspace_exc]

if objective is None:
objective = 'variational'
Expand Down Expand Up @@ -245,18 +244,21 @@ def write_wfn_py(pattern: str, wfn_type: str, optimize_orbs: bool=False,

save_chk = 'checkpoint.npy'

if old_fanpy:
pspace = ['--pspace', *pspace_exc]
else:
pspace = ['--nproj', str(nproj)]
subprocess.run(['fanpy_make_pyscf_script' if old_fanpy else 'fanpy_make_fanci_pyscf_script',
# convert geom list into json string with no space
geom_str = str(geom).replace(' ', '')
# convert to json style for fanpy
geom_str = geom_str.replace("'", '"')

subprocess.run(['fanpy_make_pyscf_script' if fanpy_only else 'fanpy_make_fanci_pyscf_script',
*optimize_orbs, '--wfn_type', wfn_type,
'--geom', geom_str,
'--pspace_exc', str(pspace_exc),
'--basis', basis,
'--objective', objective,
'--solver', solver, *kwargs,
*load_files,
'--save_chk', save_chk,
'--filename', filename, *memory,
*pspace
])

os.chdir(cwd)
Expand Down Expand Up @@ -320,9 +322,10 @@ def run_calcs(pattern: str, time=None, memory=None, ncores=1, outfile='outfile',
f.write('cwd=$PWD\n')
if calc_range:
f.write(f'for i in {{{calc_range[0]}..{calc_range[1]}}}; do\n')
f.write(' cd _$i\n')
else:
f.write('for i in */; do\n')
f.write(' cd $i\n')
f.write(' cd $i\n')
f.write(f' python -u ../calculate.py > {results_out}\n')
f.write(' cd $cwd\n')
f.write('done\n')
Expand Down
25 changes: 25 additions & 0 deletions tests/test_scripts_gaussian_run_calc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from fanpy.scripts.gaussian.run_calc import run_calc
from utils import find_datafile
import subprocess

def test_run_calc():
oneint = find_datafile("data/data_h2_hf_sto6g_oneint.npy")
twoint = find_datafile("data/data_h2_hf_sto6g_twoint.npy")
# attempt to run simple calculation with minimal inputs
# this checks if the default parameters are working correctly
run_calc(
nelec=2,
one_int_file=oneint,
two_int_file=twoint,
wfn_type="cisd")

def test_run_calc_cli():
oneint = find_datafile("data/data_h2_hf_sto6g_oneint.npy")
twoint = find_datafile("data/data_h2_hf_sto6g_twoint.npy")
subprocess.check_output([
"fanpy_run_calc",
"--nelec", "2",
"--one_int_file", oneint,
"--two_int_file", twoint,
"--wfn_type", "cisd",
])
147 changes: 147 additions & 0 deletions tests/test_scripts_pyscf_run_calc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import os
import subprocess
from pathlib import Path

import pytest

from fanpy.scripts.pyscf import run_calc


def test_write_pyscf_py_creates_file(tmp_path, monkeypatch):
# isolate make_pyscf_input to avoid heavy dependencies
monkeypatch.setattr(run_calc, "make_pyscf_input", lambda coords, **kwargs: f"COORDS: {coords}")

monkeypatch.chdir(tmp_path)
coords = "H 0 0 0; H 0 0 0.74"
run_calc.write_pyscf_py("inpdir", coords, basis="sto-3g", memory="1GB", charge=0, spin=0, units="B")

out_file = tmp_path / "inpdir" / "calculate.py"
assert out_file.exists()
content = out_file.read_text()
assert "COORDS" in content
assert "H 0 0 0.74" in content

def test_write_pyscf_py_errors():
# if memory not in MB or GB
with pytest.raises(ValueError):
run_calc.write_pyscf_py("inpdir", "H 0 0 0; H 0 0 0.74", basis="sto-3g", memory="1MQ")

def test_make_wfn_dirs_creates_dirs(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
parent = "parent_dir"
wfn_name = "mywfn"
run_calc.make_wfn_dirs(parent, wfn_name, num_runs=3, rep_dirname_prefix="rep")

for i in range(3):
d = tmp_path / parent / wfn_name / f"rep_{i}"
assert d.is_dir()

# tests for write wfn py

def test_write_wfn_py_creates_file(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
parent = "parent_dir"
wfn_name = "cisd"
h2_geom = [['H', ['0.0000000000', '0.0000000000', '0.7500000000']],
['H', ['0.0000000000', '0.0000000000', '0']]]
basis = 'sto3g'
# create parent directory to run calculation in
parent_dir = tmp_path / parent
parent_dir.mkdir(parents=True)

run_calc.write_wfn_py(parent, wfn_name, h2_geom, basis, objective='projected', solver='least_squares')

out_file = parent_dir / "calculate.py"
assert out_file.exists()
content = out_file.read_text()
assert "CISD" in content # this should be in generated file
assert "sto3g" in content
assert "ProjectedSchrodinger"

def test_write_wfn_py_kwargs(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
parent = "parent_dir"
wfn_name = "cisd"
h2_geom = [['H', ['0.0000000000', '0.0000000000', '0.7500000000']],
['H', ['0.0000000000', '0.0000000000', '0']]]
basis = 'sto3g'
# create parent directory to run calculation in
parent_dir = tmp_path / parent
parent_dir.mkdir(parents=True)
run_calc.write_wfn_py(parent, wfn_name, h2_geom, basis, objective='projected', solver='least_squares', wfn_noise=1e-5, ham_noise=1e-6, pspace_exc=[1,2], memory='2gb')
out_file = parent_dir / "calculate.py"
assert out_file.exists()
content = out_file.read_text()
assert "wfn.params + 1e-05" in content
assert "ham.params + 1e-06" in content
subprocess.run(["python", str(out_file)], check=True)


def test_run_calcs(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)

# create database folder structure. This is necessary for run_calcs to work
db_dir = os.path.join(tmp_path, "database")
os.makedirs(db_dir, exist_ok=True)
basis = "sto-3g"
basis_dir = os.path.join(db_dir, "H2", basis)
os.makedirs(basis_dir, exist_ok=True)

# create wfn dirs
run_calc.make_wfn_dirs(basis_dir, "cisd", num_runs=3)
wfn_dir = os.path.join(basis_dir, "cisd")

# create a dummy calculate.py that just writes to output.txt
calc_file = os.path.join(wfn_dir, "calculate.py")
with open(calc_file, 'w') as f:
f.write("with open('output.txt', 'w') as f: f.write('Calculation complete')")

# check if file exists
run_calc.run_calcs(calc_file, calc_range=[0,2])
output_file = os.path.join(wfn_dir, "_0", "output.txt")
assert os.path.isfile(output_file)
# check if content is correct
with open(output_file, 'r') as f:
content = f.read()
assert content == "Calculation complete"

# run calcs without calc_range:
# create wfn dirs
run_calc.make_wfn_dirs(basis_dir, "ccsd", num_runs=1)
wfn_dir = os.path.join(basis_dir, "ccsd")
calc_file = os.path.join(wfn_dir, "calculate.py")
with open(calc_file, 'w') as f:
f.write("with open('output.txt', 'w') as f: f.write('Calculation complete')")
run_calc.run_calcs(calc_file)
output_file = os.path.join(wfn_dir, "_0", "output.txt")
assert os.path.isfile(output_file)
# check if content is correct
with open(output_file, 'r') as f:
content = f.read()
assert content == "Calculation complete"

def test_run_calcs_errors(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
# create a dummy calculate.py that just writes to output.txt
calc_file = os.path.join(tmp_path, "calculate.py")
with open(calc_file, 'w') as f:
f.write("with open('output.txt', 'w') as f: f.write('Calculation complete')")

# need to provide both time and memory
with pytest.raises(ValueError) as excinfo:
run_calc.run_calcs(calc_file, memory='2gb')
assert str(excinfo.value) == "You cannot provide only one of the time and memory."

with pytest.raises(ValueError) as excinfo:
run_calc.run_calcs(calc_file, time='1h')
assert str(excinfo.value) == "You cannot provide only one of the time and memory."

# wrong time unit
with pytest.raises(ValueError) as excinfo:
run_calc.run_calcs(calc_file, time='1p', memory='2gb')
assert str(excinfo.value) == "Time must be given in minutes, hours, or days (e.g. 1440m, 24h, 1d)."

# wrong memory unit
with pytest.raises(ValueError) as excinfo:
run_calc.run_calcs(calc_file, time='1h', memory='4KB')
assert str(excinfo.value) == "Memory must be given as a MB or GB (e.g. 1024MB, 1GB)"