Skip to content

Commit

Permalink
Added command line support for multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphaelRobidas committed Feb 15, 2022
1 parent 42ab36b commit 1f43ca3
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 46 deletions.
137 changes: 130 additions & 7 deletions ccinput/tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import shlex
import os
from unittest import TestCase

from ccinput.calculation import Calculation, Parameters
from ccinput.wrapper import gen_input, get_input_from_args, get_parser
from ccinput.tests.testing_utilities import InputTests

class ManualCliTests(TestCase):
class CliEquivalenceTests(InputTests):
def are_equivalent(self, api_args, cmd_line):
ref = gen_input(**api_args)

parser = get_parser()
args = parser.parse_args(shlex.split(cmd_line))
inp = get_input_from_args(args)
return ref == inp

def struct(self, name):
return os.path.join('/'.join(__file__.split('/')[:-1]), "structures/", name + '.xyz')
calcs, outputs = get_input_from_args(args)
inp = calcs[0].input_file
return self.is_equivalent(ref, inp)

def test_basic(self):
args = {
Expand Down Expand Up @@ -360,3 +358,128 @@ def test_d3bj_orca(self):
line = "orca opt PBE0 -bs Def2SVP --xyz 'Cl 0 0 0' -n 1 --mem 1G -c -1 --d3bj"
self.assertTrue(self.are_equivalent(args, line))

class ManualCliTests(InputTests):
def test_multiple_files_no_output(self):
cmd_line = f"orca sp HF -bs Def2SVP -f {self.struct('ethanol')} {self.struct('CH4')} -n 1 --mem 1G"

parser = get_parser()
args = parser.parse_args(shlex.split(cmd_line))

objs, outputs = get_input_from_args(args)
assert len(objs) == 2
assert len(outputs) == 0

args1 = {
'software': "ORCA",
'type': "sp",
'method': "HF",
'basis_set': "Def2SVP",
'file': self.struct("ethanol"),
'nproc': 1,
'mem': "1G",
}
args2 = {
'software': "ORCA",
'type': "sp",
'method': "HF",
'basis_set': "Def2SVP",
'file': self.struct("CH4"),
'nproc': 1,
'mem': "1G",
}

inp1 = gen_input(**args1)
inp2 = gen_input(**args2)

assert self.is_equivalent(inp1, objs[0].input_file)
assert self.is_equivalent(inp2, objs[1].input_file)

def test_multiple_files_output(self):
cmd_line = f"orca sp HF -bs Def2SVP -f {self.struct('ethanol')} {self.struct('CH4')} -o test.inp -n 1 --mem 1G"

parser = get_parser()
args = parser.parse_args(shlex.split(cmd_line))

objs, outputs = get_input_from_args(args)
assert len(objs) == 2
assert len(outputs) == 2

assert os.path.basename(outputs[0]) == "test_ethanol.inp"
assert os.path.basename(outputs[1]) == "test_CH4.inp"

args1 = {
'software': "ORCA",
'type': "sp",
'method': "HF",
'basis_set': "Def2SVP",
'file': self.struct("ethanol"),
'nproc': 1,
'mem': "1G",
}
args2 = {
'software': "ORCA",
'type': "sp",
'method': "HF",
'basis_set': "Def2SVP",
'file': self.struct("CH4"),
'nproc': 1,
'mem': "1G",
}

inp1 = gen_input(**args1)
inp2 = gen_input(**args2)

assert self.is_equivalent(inp1, objs[0].input_file)
assert self.is_equivalent(inp2, objs[1].input_file)

def test_multiple_files_output_no_name(self):
cmd_line = f"orca sp HF -bs Def2SVP -f {self.struct('ethanol')} {self.struct('CH4')} -o .inp -n 1 --mem 1G"

parser = get_parser()
args = parser.parse_args(shlex.split(cmd_line))

objs, outputs = get_input_from_args(args)
assert len(objs) == 2
assert len(outputs) == 2

assert outputs[0] == "ethanol.inp"
assert outputs[1] == "CH4.inp"

def test_multiple_files_output_name_no_override(self):
cmd_line = f"orca sp HF -bs Def2SVP -f {self.struct('ethanol')} {self.struct('CH4')} -o .inp -n 1 --mem 1G --name test"

parser = get_parser()
args = parser.parse_args(shlex.split(cmd_line))

objs, outputs = get_input_from_args(args)
assert len(objs) == 2
assert len(outputs) == 2

assert outputs[0] == "ethanol.inp"
assert outputs[1] == "CH4.inp"

def test_multiple_files_output_directory(self):
cmd_line = f"orca sp HF -bs Def2SVP -f {self.struct('ethanol')} {self.struct('CH4')} -o calc_dir/.inp -n 1 --mem 1G"

parser = get_parser()
args = parser.parse_args(shlex.split(cmd_line))

objs, outputs = get_input_from_args(args)
assert len(objs) == 2
assert len(outputs) == 2

assert outputs[0] == "calc_dir/ethanol.inp"
assert outputs[1] == "calc_dir/CH4.inp"

def test_single_file_output(self):
cmd_line = f"orca sp HF -bs Def2SVP -f {self.struct('ethanol')} -o calc_dir/ethanol.inp -n 1 --mem 1G"

parser = get_parser()
args = parser.parse_args(shlex.split(cmd_line))

objs, outputs = get_input_from_args(args)
assert len(objs) == 1
assert len(outputs) == 1

assert outputs[0] == "calc_dir/ethanol.inp"

7 changes: 5 additions & 2 deletions ccinput/tests/testing_utilities.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import os
from unittest import TestCase

from ccinput.wrapper import generate_calculation
from ccinput.wrapper import gen_obj

class InputTests(TestCase):
def generate_calculation(self, **params):
params['file'] = os.path.join('/'.join(__file__.split('/')[:-1]), "structures/", params['file'])
return generate_calculation(**params)
return gen_obj(**params)

def is_equivalent(self, ref, res):
ref_lines = [i.strip() for i in ref.strip().split('\n')]
Expand Down Expand Up @@ -58,3 +58,6 @@ def xyz_line_equivalent(self, line1, line2):
return False
return True

def struct(self, name):
return os.path.join('/'.join(__file__.split('/')[:-1]), "structures/", name + '.xyz')

124 changes: 87 additions & 37 deletions ccinput/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,21 @@ def process_calculation(calc):
def generate_calculation(software=None, type=None, method="", basis_set="",
solvent="", solvation_model="", solvation_radii="", custom_solvation_radii="",
specifications="", freeze=[], scan=[], sfrom=[], sto=[], snsteps=[], sstep=[],
density_fitting="", custom_basis_sets="", xyz="", file="",
constraints="", nproc=0, mem="", charge=0, multiplicity=1, d3=False, d3bj=False,
aux_name="calc2", name="calc", header="File created by ccinput", **kwargs):
density_fitting="", custom_basis_sets="", xyz="", constraints="", nproc=0,
mem="", charge=0, multiplicity=1, d3=False, d3bj=False, aux_name="calc2",
name="calc", header="File created by ccinput", **kwargs):

if software is None:
raise InvalidParameter("Specify a software package to use (software=...)")

if type is None:
raise InvalidParameter("Specify a calculation type (type='...')")

if xyz != "":
xyz_structure = standardize_xyz(xyz)
elif file != "":
xyz_structure = parse_xyz_from_file(file)
else:
if xyz == "":
raise InvalidParameter("No input structure")

xyz_structure = standardize_xyz(xyz)

abs_software = get_abs_software(software)

calc_type = get_abs_type(type)
Expand All @@ -58,11 +56,29 @@ def generate_calculation(software=None, type=None, method="", basis_set="",

return process_calculation(calc)

def gen_obj(**args):
if 'file' in args:
if isinstance(args['file'], list):
if len(args['file']) > 1:
print("file", args['file'])
raise UnimplementedError("No support for multiple input files at once except from the command line")
elif len(args['file']) == 0:
pass
else:
xyz = parse_xyz_from_file(args['file'][0])
args['xyz'] = xyz
else:
xyz = parse_xyz_from_file(args['file'])
args['xyz'] = xyz

del args['file']
return generate_calculation(**args)

def gen_input(**args):
return generate_calculation(**args).input_file
return gen_obj(**args).input_file

def write_input(filename, **args):
inp = generate_calculation(**args).input_file
inp = gen_input(**args)
with open(filename, 'w') as out:
out.write(inp)

Expand Down Expand Up @@ -101,11 +117,11 @@ def get_parser():
parser.add_argument('--xyz', '-x', default="",
type=str, help='XYZ structure as string')

parser.add_argument('--file', '-f', default="",
type=str, help='XYZ structure as file')
parser.add_argument('--file', '-f', default=[], nargs="+",
type=str, help='XYZ structure(s) as file(s)')

parser.add_argument('--output', '-o', default="",
type=str, help='Write the result to the specified file')
type=str, help='Write the result to the specified file (single file) or using the specified pattern (multiple files)')

parser.add_argument('--constraints', '-co', default="",
type=str, help='String specification of constraints for certain calculations')
Expand Down Expand Up @@ -162,33 +178,67 @@ def cmd():
parser = get_parser()
args = parser.parse_args()

inp = get_input_from_args(args)
calcs, outputs = get_input_from_args(args)
if args.output != "":
with open(args.output, 'w') as out:
out.write(inp)
print(f"Input file written to {args.output}")
for calc, outp in zip(calcs, outputs):
with open(outp, 'w') as out:
out.write(calc.input_file)
print(f"Input file written to {outp}")
else:
print(inp)
if len(calcs) == 1:
print(calcs[0].input_file)
else:
for calc in calcs:
n = max(int((39 - len(calc.calc.name))/2), 4)
header = "-"*n + f" {calc.calc.name} " + "-"*n
print(header)
print(calc.input_file)
print("\n\n")

def get_input_from_args(args):
if args.name == "calc" and args.file:
_name = os.path.basename(args.file).split('.')[0]
xyzs = []
names = []
outputs = []

if args.file:
xyzs = [parse_xyz_from_file(f) for f in args.file]
if len(args.file) > 1 or args.name == 'calc':
names = [os.path.basename(f).split('.')[0] for f in args.file]
else:
names = [args.name]

if args.output != "":
head, tail = os.path.split(args.output)
prefix, ext = tail.split('.')
if prefix != "":
prefix += '_'
if len(args.file) > 1:
outputs = [os.path.join(head, prefix+name+'.'+ext) for name in names]
else:
outputs = [os.path.join(head, name+'.'+ext) for name in names]
else:
_name = args.name
try:
inp = gen_input(software=args.software, type=args.type, method=args.method,
basis_set=args.basis_set, solvent=args.solvent,
solvation_model=args.solvation_model, solvation_radii=args.solvation_radii,
custom_solvation_radii=args.custom_solvation_radii,
specifications=args.specifications, freeze=args.freeze,
scan=args.scan, sfrom=args.sfrom, sto=args.sto, snsteps=args.snsteps,
sstep=args.sstep, density_fitting=args.density_fitting,
custom_basis_sets=args.custom_basis_sets, xyz=args.xyz, file=args.file,
constraints=args.constraints, nproc=args.nproc, mem=args.mem,
charge=args.charge, multiplicity=args.mult, d3=args.d3, d3bj=args.d3bj,
aux_name=args.aux_name, name=_name, header=args.header)
except CCInputException as e:
print(f"*** {str(e)} ***")
return
return inp
xyzs = [args.xyz]
names = [args.name]
outputs = [args.output]

calcs = []
for name, xyz in zip(names, xyzs):
try:
calc = gen_obj(software=args.software, type=args.type, method=args.method,
basis_set=args.basis_set, solvent=args.solvent,
solvation_model=args.solvation_model, solvation_radii=args.solvation_radii,
custom_solvation_radii=args.custom_solvation_radii,
specifications=args.specifications, freeze=args.freeze,
scan=args.scan, sfrom=args.sfrom, sto=args.sto, snsteps=args.snsteps,
sstep=args.sstep, density_fitting=args.density_fitting,
custom_basis_sets=args.custom_basis_sets, xyz=xyz,
constraints=args.constraints, nproc=args.nproc, mem=args.mem,
charge=args.charge, multiplicity=args.mult, d3=args.d3, d3bj=args.d3bj,
aux_name=args.aux_name, name=name, header=args.header)
except CCInputException as e:
print(f"*** {str(e)} ***")
exit(0)
else:
calcs.append(calc)
return calcs, outputs

27 changes: 27 additions & 0 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,33 @@ Basis set to use for density fitting.

Only available for Gaussian 16 for the moment

Structure files
^^^^^^^^^^^^^^^

Structure file(s) to use in the input. Only XYZ files are currently supported.

Multiple files can be specified at once when using from the command line:

.. code-block:: console
$ ccinput [...] -f struct1.xyz struct2.xyz
If no output pattern is specified, each input file will be printed to the console sequentially separated by a header. With an output pattern, the files will be created in the chosen directory with the given prefix and extension. When specifying only one file, the exact output path will be used.

.. code-block:: console
$ ccinput [...] -f struct1.xyz struct2.xyz -o calc_dir/sp.inp
Input file written to calc_dir/sp_struct1.inp
Input file written to calc_dir/sp_struct2.inp
$ ccinput [...] -f struct1.xyz struct2.xyz -o .com
Input file written to struct1.com
Input file written to struct2.com
$ ccinput [...] -f struct1.xyz -o my_struct.com
Input file written to my_struct.com
Solvent
^^^^^^^

Expand Down

0 comments on commit 1f43ca3

Please sign in to comment.