def test_optimization_pass_serialization(water, opti_input, opti_success): opti_in = OptimizationInput(initial_molecule=water, **opti_input) assert isinstance(opti_in.dict(), dict) assert isinstance(opti_in.json(), str) opti_out = Optimization(initial_molecule=water, **opti_input, **opti_success) assert isinstance(opti_out.dict(), dict) assert isinstance(opti_out.json(), str)
def _generate_optimization_input(mol, compute_spec, factory): import ast from qcelemental.models import OptimizationInput # TODO: bug report in openff where `atom_map` is a string if isinstance(mol.properties.get('atom_map'), str): mol.properties['atom_map'] = ast.literal_eval( mol.properties['atom_map']) # This block will fail for OFF Toolkit 0.8.4, but succeed for 0.8.4rc1 try: attributes = factory.create_cmiles_metadata(mol) qcmol = mol.to_qcschema(extras=attributes) # In OFFTK 0.8.4, the CMILES field is automatically populated by this method except: qcmol = mol.to_qcschema() method = compute_spec['method'] basis = compute_spec['basis'] program = compute_spec['program'] # generate optimization inputs input_data = OptimizationInput(keywords={ "coordsys": "dlc", "enforce": 0, "epsilon": 1e-05, "reset": True, "qccnv": False, "molcnv": False, "check": 0, "trust": 0.1, "tmax": 0.3, "maxiter": 300, "convergence_set": "gau", "program": program }, extras={}, protocols={}, input_specification={ "driver": "gradient", "model": { "method": method, "basis": basis, }, "keywords": { "maxiter": 200, "scf_properties": [ "dipole", "quadrupole", "wiberg_lowdin_indices", "mayer_indices" ] }, }, initial_molecule=qcmol) return input_data.dict()
def test_nwchem_restart(tmpdir): # Make the input file input_data = { "input_specification": { "model": { "method": "HF", "basis": "sto-3g" }, "keywords": { "driver__maxiter": 2, "set__driver:linopt": 0 }, "extras": { "allow_restarts": True }, }, "initial_molecule": qcng.get_molecule("hydrogen"), } input_data = OptimizationInput(**input_data) # Run an initial step, which should not converge local_opts = {"scratch_messy": True, "scratch_directory": str(tmpdir)} ret = qcng.compute_procedure(input_data, "nwchemdriver", local_options=local_opts, raise_error=False) assert not ret.success # Run it again, which should converge new_ret = qcng.compute_procedure(input_data, "nwchemdriver", local_options=local_opts, raise_error=True) assert new_ret.success
def relax_structure(smiles: str, qc_config: QCInputSpecification, compute_config: Optional[Union[TaskConfig, Dict]] = None, compute_connectivity: bool = False, code: str = _code) -> Tuple[str, float]: """Compute the atomization energy of a molecule given the SMILES string Args: smiles (str): SMILES of a molecule qc_config (dict): Quantum Chemistry configuration used for evaluating the energy compute_config (TaskConfig): Configuration for the quantum chemistry code compute_connectivity (bool): Whether we must compute connectivity before calling code code (str): Which QC code to use for the evaluation Returns: (str): Structure of the molecule (float): Electronic energy of this molecule """ # Generate 3D coordinates by minimizing MMFF forcefield xyz = generate_atomic_coordinates(smiles) mol = Molecule.from_data(xyz, dtype='xyz') # Generate connectivity, if needed if compute_connectivity: conn = guess_connectivity(mol.symbols, mol.geometry, default_connectivity=1.0) mol = Molecule.from_data({**mol.dict(), 'connectivity': conn}) # Run the relaxation opt_input = OptimizationInput(input_specification=qc_config, initial_molecule=mol, keywords={'program': code, 'convergence_set': 'GAU_VERYTIGHT'}) res: OptimizationResult = \ compute_procedure(opt_input, 'geometric', local_options=compute_config, raise_error=True) return res.final_molecule.to_string('xyz'), res.energies[-1]
def test_geometric_retries(failure_engine, input_data): failure_engine.iter_modes = ["random_error", "pass", "random_error", "random_error", "pass"] # Iter 1 # Iter 2 failure_engine.iter_modes.extend(["pass"] * 20) input_data["initial_molecule"] = { "symbols": ["He", "He"], "geometry": [0, 0, 0, 0, 0, failure_engine.start_distance], } input_data["input_specification"]["model"] = {"method": "something"} input_data["keywords"]["program"] = failure_engine.name input_data = OptimizationInput(**input_data) ret = qcng.compute_procedure(input_data, "geometric", local_options={"ncores": 13}, raise_error=True) assert ret.success is True assert ret.trajectory[0].provenance.retries == 1 assert ret.trajectory[0].provenance.ncores == 13 assert ret.trajectory[1].provenance.retries == 2 assert ret.trajectory[1].provenance.ncores == 13 assert "retries" not in ret.trajectory[2].provenance.dict() # Ensure we still fail failure_engine.iter_modes = ["random_error", "pass", "random_error", "random_error", "pass"] # Iter 1 # Iter 2 ret = qcng.compute_procedure(input_data, "geometric", local_options={"ncores": 13, "retries": 1}) assert ret.success is False assert ret.input_data["trajectory"][0]["provenance"]["retries"] == 1 assert len(ret.input_data["trajectory"]) == 2
def relax_structure(xyz: str, qc_config: QCInputSpecification, charge: int = 0, compute_config: Optional[Union[TaskConfig, Dict]] = None, code: str = _code) -> OptimizationResult: """Compute the atomization energy of a molecule given the SMILES string Args: xyz (str): Structure of a molecule in XYZ format qc_config (dict): Quantum Chemistry configuration used for evaluating the energy charge (int): Charge of the molecule compute_config (TaskConfig): Configuration for the quantum chemistry code, such as parallelization settings code (str): Which QC code to use for the evaluation Returns: (OptimizationResult): Full output from the calculation """ # Parse the molecule mol = Molecule.from_data(xyz, dtype='xyz', molecular_charge=charge) # Run the relaxation if code == "nwchem": keywords = {"driver__maxiter": 100, "set__driver:linopt": 0} relax_code = "nwchemdriver" else: keywords = {"program": code} relax_code = "geometric" opt_input = OptimizationInput(input_specification=qc_config, initial_molecule=mol, keywords=keywords) return compute_procedure(opt_input, relax_code, local_options=compute_config, raise_error=True)
def test_geometric_psi4(input_data): input_data["initial_molecule"] = qcng.get_molecule("hydrogen") input_data["input_specification"]["model"] = { "method": "HF", "basis": "sto-3g" } input_data["input_specification"]["keywords"] = { "scf_properties": ["wiberg_lowdin_indices"] } input_data["keywords"]["program"] = "psi4" input_data = OptimizationInput(**input_data) ret = qcng.compute_procedure(input_data, "geometric", raise_error=True) assert 10 > len(ret.trajectory) > 1 assert pytest.approx(ret.final_molecule.measure([0, 1]), 1.0e-4) == 1.3459150737 assert ret.provenance.creator.lower() == "geometric" assert ret.trajectory[0].provenance.creator.lower() == "psi4" # Check keywords passing for single in ret.trajectory: assert "scf_properties" in single.keywords assert "WIBERG_LOWDIN_INDICES" in single.extras["qcvars"]
def test_geometric_stdout(input_data): input_data["initial_molecule"] = qcng.get_molecule("water") input_data["input_specification"]["model"] = {"method": "UFF", "basis": ""} input_data["keywords"]["program"] = "rdkit" input_data = OptimizationInput(**input_data) ret = qcng.compute_procedure(input_data, "geometric", raise_error=True) assert ret.success is True assert "Converged!" in ret.stdout
def test_geometric_rdkit_error(input_data): input_data["initial_molecule"] = qcng.get_molecule("water").copy(exclude={"connectivity_"}) input_data["input_specification"]["model"] = {"method": "UFF", "basis": ""} input_data["keywords"]["program"] = "rdkit" input_data = OptimizationInput(**input_data) ret = qcng.compute_procedure(input_data, "geometric") assert ret.success is False assert isinstance(ret.error.error_message, str)
def _args_from_optimizationrecord(opt, client): from qcelemental.models import OptimizationInput # generate optimization inputs input_data = OptimizationInput( keywords=opt.keywords, extras={}, protocols={}, input_specification={ "driver": "gradient", "model": { "method": opt.qc_spec.method, "basis": opt.qc_spec.basis, }, "keywords": client.query_keywords(opt.qc_spec.keywords)[0].values }, initial_molecule=opt.get_initial_molecule()) return input_data.dict()
def test_run_procedure(tmp_path): """Tests qcengine run-procedure with geometric, psi4, and JSON input""" def check_result(stdout): output = json.loads(stdout) assert output["provenance"]["creator"].lower() == "geometric" assert output["success"] is True inp = {"schema_name": "qcschema_optimization_input", "schema_version": 1, "keywords": { "coordsys": "tric", "maxiter": 100, "program": "psi4" }, "input_specification": { "schema_name": "qcschema_input", "schema_version": 1, "driver": "gradient", "model": {"method": "HF", "basis": "sto-3g"}, "keywords": {}, }, "initial_molecule": get_molecule("hydrogen")} inp = OptimizationInput(**inp) args = ["run-procedure", "geometric", inp.json()] check_result(run_qcengine_cli(args)) args = ["run-procedure", "geometric", os.path.join(tmp_path, "input.json")] with util.disk_files({"input.json": inp.json()}, {}, cwd=tmp_path): check_result(run_qcengine_cli(args)) args = ["run-procedure", "geometric", inp.json()] check_result(run_qcengine_cli(args, stdin=inp.json()))
def test_berny_stdout(input_data): input_data["initial_molecule"] = qcng.get_molecule("water") input_data["input_specification"]["model"] = { "method": "HF", "basis": "sto-3g" } input_data["keywords"]["program"] = "psi4" input_data = OptimizationInput(**input_data) ret = qcng.compute_procedure(input_data, "berny", raise_error=True) assert ret.success is True assert "All criteria matched" in ret.stdout
def test_geometric_psi4(): inp = copy.deepcopy(_base_json) inp["initial_molecule"] = dc.get_molecule("hydrogen") inp["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} inp["keywords"]["program"] = "psi4" inp = OptimizationInput(**inp) ret = dc.compute_procedure(inp, "geometric", raise_error=True) assert 10 > len(ret["trajectory"]) > 1 geom = ret["final_molecule"]["geometry"] assert pytest.approx(_bond_dist(geom, 0, 1), 1.e-4) == 1.3459150737
def test_geometric_local_options(input_data): input_data["initial_molecule"] = qcng.get_molecule("hydrogen") input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} input_data["keywords"]["program"] = "psi4" input_data = OptimizationInput(**input_data) # Set some extremely large number to test ret = qcng.compute_procedure(input_data, "geometric", raise_error=True, local_options={"memory": "5000"}) assert pytest.approx(ret.trajectory[0].provenance.memory, 1) == 4900 # Make sure we cleaned up assert "_qcengine_local_config" not in ret.input_specification assert "_qcengine_local_config" not in ret.trajectory[0].extras
def test_optimization_protocols(input_data): input_data["initial_molecule"] = qcng.get_molecule("water") input_data["input_specification"]["model"] = {"method": "UFF"} input_data["keywords"]["program"] = "rdkit" input_data["protocols"] = {"trajectory": "initial_and_final"} input_data = OptimizationInput(**input_data) ret = qcng.compute_procedure(input_data, "geometric", raise_error=True) assert ret.success, ret.error.error_message assert len(ret.trajectory) == 2 assert ret.initial_molecule.get_hash() == ret.trajectory[0].molecule.get_hash() assert ret.final_molecule.get_hash() == ret.trajectory[1].molecule.get_hash()
def test_geometric_psi4(): inp = copy.deepcopy(_base_json) inp["initial_molecule"] = qcng.get_molecule("hydrogen") inp["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} inp["keywords"]["program"] = "psi4" inp = OptimizationInput(**inp) ret = qcng.compute_procedure(inp, "geometric", raise_error=True) assert 10 > len(ret.trajectory) > 1 assert pytest.approx(ret.final_molecule.measure([0, 1]), 1.e-4) == 1.3459150737 assert ret.provenance.creator.lower() == "geometric" assert ret.trajectory[0].provenance.creator.lower() == "psi4"
def test_geometric_stdout(): inp = copy.deepcopy(_base_json) inp["initial_molecule"] = dc.get_molecule("water") inp["input_specification"]["model"] = {"method": "UFF", "basis": ""} inp["keywords"]["program"] = "rdkit" inp = OptimizationInput(**inp) ret = dc.compute_procedure(inp, "geometric", raise_error=True) assert ret["success"] is True assert "Converged!" in ret["stdout"] assert ret["stderr"] == "No stderr recieved." with pytest.raises(ValueError): _ = dc.compute_procedure(inp, "rdkit", raise_error=True)
def test_geometric_rdkit_error(): inp = copy.deepcopy(_base_json) inp["initial_molecule"] = dc.get_molecule("water").copy( exclude="connectivity") inp["input_specification"]["model"] = {"method": "UFF", "basis": ""} inp["keywords"]["program"] = "rdkit" inp = OptimizationInput(**inp) ret = dc.compute_procedure(inp, "geometric") assert ret["success"] is False assert isinstance(ret["error"]["error_message"], str) with pytest.raises(ValueError): _ = dc.compute_procedure(inp, "rdkit", raise_error=True)
def test_input_through_json(inp, expected): with open(os.path.join(os.path.dirname(__file__), inp)) as input_data: input_copy = json.load(input_data) opt_schema = OptimizationInput(**input_copy) #optking.run_json_file(os.path.join(os.path.dirname(__file__), inp)) json_dict = optking.optimize_qcengine(input_copy) #For testing purposes. If this works, we have properly returned the output, and added the result #to the original file. In order to preserve the form of the test suite, we now resore the input #to its original state #with open(os.path.join(os.path.dirname(__file__), inp)) as input_data: # json_dict = json.load(input_data) assert psi4.compare_values(expected[0], json_dict['trajectory'][-1]['properties']['nuclear_repulsion_energy'], 2, "Nuclear repulsion energy") assert psi4.compare_values(expected[1], json_dict['trajectory'][-1]['properties']['return_energy'], 6, "Reference energy")
def test_repr_optimization(): opt = OptimizationInput( **{ "input_specification": { "driver": "gradient", "model": { "method": "UFF" } }, "initial_molecule": { "symbols": ["He"], "geometry": [0, 0, 0] }, }) assert "molecule_hash" in str(opt) assert "molecule_hash" in repr(opt)
def test_berny_failed_gradient_computation(input_data): input_data["initial_molecule"] = qcng.get_molecule("water") input_data["input_specification"]["model"] = { "method": "HF", "basis": "sto-3g" } input_data["input_specification"]["keywords"] = { "badpsi4key": "badpsi4value" } input_data["keywords"]["program"] = "psi4" input_data = OptimizationInput(**input_data) ret = qcng.compute_procedure(input_data, "berny", raise_error=False) assert isinstance(ret, FailedOperation) assert ret.success is False assert ret.error.error_type == qcng.exceptions.InputError.error_type
def test_geometric_local_options(): inp = copy.deepcopy(_base_json) inp["initial_molecule"] = dc.get_molecule("hydrogen") inp["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} inp["keywords"]["program"] = "psi4" inp = OptimizationInput(**inp) # Set some extremely large number to test ret = dc.compute_procedure(inp, "geometric", raise_error=True, local_options={"memory": "5000"}) assert pytest.approx(ret["trajectory"][0]["provenance"]["memory"], 1) == 4900 # Make sure we cleaned up assert "_qcengine_local_config" not in ret["input_specification"] assert "_qcengine_local_config" not in ret["trajectory"][0]
def test_nwchem_relax(linopt): # Make the input file input_data = { "input_specification": { "model": { "method": "HF", "basis": "sto-3g" }, "keywords": { "set__driver:linopt": linopt }, }, "initial_molecule": qcng.get_molecule("hydrogen"), } input_data = OptimizationInput(**input_data) # Run the relaxation ret = qcng.compute_procedure(input_data, "nwchemdriver", raise_error=True) assert 10 > len(ret.trajectory) > 1 assert pytest.approx(ret.final_molecule.measure([0, 1]), 1.0e-4) == 1.3459150737
def optimize_psi4(calc_name, program='psi4', dertype=None): """ Wrapper for optimize.optimize() Looks for an active psi4 molecule and optimizes. This is the written warning that Optking will try to use psi4 if no program is provided Parameters ---------- calc_name: str level of theory for optimization. eg MP2 program: str program used for gradients, hessians... Returns ------- opt_output: dict dictionary serialized MOLSSI OptimizationResult. see https://github.com/MolSSI/QCElemental/blob/master/qcelemental/models/procedures.py """ import psi4 logger = logging.getLogger(__name__) mol = psi4.core.get_active_molecule() oMolsys = molsys.Molsys.from_psi4_molecule(mol) # Get optking options and globals from psi4 # Look through optking module specific options first. If a global has already appeared # in optking's options, don't include as a qc package option logger.debug("Getting module and psi4 options for qcschema construction") module_options = psi4.driver.p4util.prepare_options_for_modules() all_options = psi4.core.get_global_option_list() opt_keys = {'program': program} qc_keys = {} if dertype is not None: qc_keys['dertype'] = 0 optking_options = module_options['OPTKING'] for opt, optval in optking_options.items(): if optval['has_changed']: opt_keys[opt.lower()] = optval['value'] for option in all_options: if psi4.core.has_global_option_changed(option): if option in opt_keys: pass else: qc_keys[option.lower()] = psi4.core.get_global_option(option) # Make a qcSchema OptimizationInput opt_input = { "keywords": opt_keys, "initial_molecule": oMolsys.molsys_to_qc_molecule(), "input_specification": { "model": { 'basis': qc_keys.pop('basis'), 'method': calc_name }, "driver": "gradient", "keywords": qc_keys } } logger.debug("Creating OptimizationInput") opt_input = OptimizationInput(**opt_input) # Remove numpy elements to allow at will json serialization opt_input = json.loads(json_dumps(opt_input)) opt_output = copy.deepcopy(opt_input) try: initialize_options(opt_keys) computer = make_computer(opt_input) opt_output = optimize(oMolsys, computer) except (OptError, KeyError, ValueError, AttributeError) as error: opt_output = { "success": False, "error": { "error_type": error.err_type, "error_message": error.mesg } } logger.critical(f"Error placed in qcschema: {opt_output}") except Exception as error: logger.critical( "An unknown error has occured and evaded all error checking") opt_output = { "success": False, "error": { "error_type": error, "error_message": str(error) } } logger.critical(f"Error placed in qcschema: {opt_output}") finally: opt_input.update({"provenance": optking._optking_provenance_stamp}) opt_input["provenance"]["routine"] = "optimize_psi4" opt_input.update(opt_output) return opt_input
def initialize_from_psi4(calc_name, program, computer_type, dertype=None, **xtra_opt_params): """Gathers information from an active psi4 instance. to cleanly run optking from a psithon or psi4api input file Parameters ---------- calc_name: str computer_type: str dertype: Union[int, None] program: str **xtra_opt_params extra keywords which are not recognized by psi4 Returns ------- params: op.OptParams o_molsys: molsys.Molsys computer: ComputeWrapper opt_input: qcelemental.models.OptimizationInput """ import psi4 logger = logging.getLogger(__name__) mol = psi4.core.get_active_molecule() o_molsys, qc_mol = molsys.Molsys.from_psi4(mol) # Get optking options and globals from psi4 # Look through optking module specific options first. If a global has already appeared # in optking's options, don't include as a qc package option logger.debug("Getting module and psi4 options for qcschema construction") module_options = psi4.driver.p4util.prepare_options_for_modules() all_options = psi4.core.get_global_option_list() opt_keys = {"program": program} qc_keys = {} if dertype is not None: qc_keys["dertype"] = 0 optking_options = module_options["OPTKING"] for opt, optval in optking_options.items(): if optval["has_changed"]: opt_keys[opt.lower()] = optval["value"] if xtra_opt_params: for xtra_key, xtra_value in xtra_opt_params.items(): opt_keys[xtra_key.lower()] = xtra_value for option in all_options: if psi4.core.has_global_option_changed(option): if option not in opt_keys: qc_keys[option.lower()] = psi4.core.get_global_option(option) # Make a qcSchema OptimizationInput opt_input = { "keywords": opt_keys, "initial_molecule": qc_mol, "input_specification": { "model": { "basis": qc_keys.pop("basis"), "method": calc_name }, "driver": "gradient", "keywords": qc_keys, }, } logger.debug("Creating OptimizationInput") opt_input = OptimizationInput(**opt_input) # Remove numpy elements to allow at will json serialization opt_input = json.loads(json_dumps(opt_input)) initialize_options(opt_keys) params = op.Params computer = make_computer(opt_input, computer_type) return params, o_molsys, computer, opt_input