def build_input( self, input_model: AtomicInput, config: "TaskConfig", template: Optional[str] = None, ) -> Dict[str, Any]: """ Use the template files stored in QUBEKit to build a gaussian input file for the given driver. """ import os from jinja2 import Template template_file = get_data(os.path.join("templates", "gaussian.com")) with open(template_file) as file: template = Template(file.read()) template_data = { "memory": int(config.memory), "threads": config.ncores, "driver": self.driver_conversion(driver=input_model.driver), "title": "gaussian job", } molecule = input_model.molecule spec = input_model.model theory = self.functional_converter(method=spec.method) template_data["theory"] = theory template_data["basis"] = spec.basis template_data["charge"] = int(molecule.molecular_charge) template_data["multiplicity"] = molecule.molecular_multiplicity template_data["scf_maxiter"] = input_model.extras.get("maxiter", 300) # work around for extra cmdline args template_data["cmdline_extra"] = input_model.keywords.get( "cmdline_extra", []) # work around for extra trailing input template_data["add_input"] = input_model.keywords.get("add_input", []) template_data.update(input_model.keywords) # now we need to build the coords data data = [] for i, symbol in enumerate(molecule.symbols): # we must convert the atomic input back to angstroms data.append( (symbol, molecule.geometry[i] * constants.BOHR_TO_ANGS)) template_data["data"] = data rendered_template = template.render(**template_data) return { "infiles": { "gaussian.com": rendered_template }, "scratch_directory": config.scratch_directory, "input_result": input_model.copy(deep=True), }
def atomic_input_to_job_input(atomic_input: AtomicInput) -> pb.JobInput: """Convert AtomicInput to JobInput""" # Don't mutate original atomic_input object ai_copy = atomic_input.copy(deep=True) # Create Mol instance mol_msg = pb.Mol() mol_msg.atoms.extend(ai_copy.molecule.symbols) mol_msg.xyz.extend(ai_copy.molecule.geometry.flatten()) mol_msg.units = pb.Mol.UnitType.BOHR # Molecule always in bohr mol_msg.charge = int(ai_copy.molecule.molecular_charge) mol_msg.multiplicity = ai_copy.molecule.molecular_multiplicity mol_msg.closed = ai_copy.keywords.pop("closed_shell", True) mol_msg.restricted = ai_copy.keywords.pop("restricted", True) # Drop keyword terms already applied from Molecule object ai_copy.keywords.pop("charge", None) # mol_msg.charge ai_copy.keywords.pop("spinmult", None) # mol_msg.multiplicity # Create JobInput message ji = pb.JobInput(mol=mol_msg) # Set driver driver = ai_copy.driver.upper() if driver not in SUPPORTED_DRIVERS: # Only support QCEngine supported drivers; energy, gradient, hessian, properties raise ValueError( f"Driver '{driver}' not supported, please select from {SUPPORTED_DRIVERS}" ) ji.run = pb.JobInput.RunType.Value(driver) # Set Method ji.method = pb.JobInput.MethodType.Value(ai_copy.model.method.upper()) # Set Basis ji.basis = ai_copy.model.basis # Get keywords that have specific protobuf fields ji.return_bond_order = ai_copy.keywords.pop("bond_order", False) # Request AO and MO information if ai_copy.keywords.pop("mo_output", False): ji.imd_orbital_type = pb.JobInput.ImdOrbitalType.WHOLE_C # Set all other keywords under the "user_options" catch all for key, value in ai_copy.keywords.items(): ji.user_options.extend([key, str(value)]) return ji