def muster_modelchem( method: str, derint: int, ) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into MADNESS keywords Options include energy calculation with moldft Geometry optimiazation with moldft propreties calculation with molresponse...runs moldft then molresponse Args: method (str): Name of the QC method to use derint (str): Index of the run type Returns: (str): Task command for MADNESS (dict): Any options for MADNESS """ # Standardize the method name method = method.lower() opts = {} # Map the run type to # runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] runtyp = { "energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "molresponse" }[derint] # Write out the theory directive if runtyp == "energy": if method == "optimization": opts["dft__gopt"] = True elif method.split()[0] in _xc_functionals: opts["dft__xc"] = method else: raise InputError(f"Method not recognized: {method}") mdccmd = f"" elif runtyp == "molresponse": if method.split()[0] in _xc_functionals: opts["dft__xc"] = method opts["response__xc"] = method opts["response__archive"] = "restartdata" else: raise InputError(f"Method not recognized: {method}") mdccmd = f"response" ## we will split the options with the word response later ## all we have to do is add options to the dft block in order to change the run type ## default in energy # do nothing return mdccmd, opts
def compute(self, input_data: OptimizationInput, config: TaskConfig) -> "BaseModel": nwc_harness = NWChemHarness() self.found(raise_error=True) # Unify the keywords from the OptimizationInput and QCInputSpecification # Optimization input will override, but don't tell users this as it seems unnecessary keywords = input_data.keywords.copy() keywords.update(input_data.input_specification.keywords) if keywords.get("program", "nwchem").lower() != "nwchem": raise InputError("NWChemDriver procedure only works with NWChem") # Make an atomic input atomic_input = AtomicInput( molecule=input_data.initial_molecule, driver="energy", keywords=keywords, **input_data.input_specification.dict( exclude={"driver", "keywords"}), ) # Build the inputs for the job job_inputs = nwc_harness.build_input(atomic_input, config) # Replace the last line with a "task {} optimize" input_file: str = job_inputs["infiles"]["nwchem.nw"].strip() beginning, last_line = input_file.rsplit("\n", 1) assert last_line.startswith("task") last_line = f"task {last_line.split(' ')[1]} optimize" job_inputs["infiles"]["nwchem.nw"] = f"{beginning}\n{last_line}" # Run it! success, dexe = nwc_harness.execute(job_inputs) # Check for common errors if "There is an error in the input file" in dexe["stdout"]: raise InputError(dexe["stdout"]) if "not compiled" in dexe["stdout"]: # recoverable with a different compilation with optional modules raise InputError(dexe["stdout"]) # Parse it if success: dexe["outfiles"]["stdout"] = dexe["stdout"] dexe["outfiles"]["stderr"] = dexe["stderr"] return self.parse_output(dexe["outfiles"], input_data) else: raise UnknownError(dexe["stdout"])
def execute_define(stdin: str, cwd: Optional["Path"] = None) -> str: """Call define with the input define in stdin.""" # TODO: replace this with a call to the default execute provided by QCEngine # if possible. May be difficult though, as we have to pipe in stdin and # be careful with the encoding. # We cant use univeral_newlines=True or text=True in Popen as some of the # data that define returns isn't proper UTF-8, so the decoding will crash. # We will decode it later on manually. with Popen("define", stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=cwd) as proc: try: # stdout, _ = proc.communicate(str.encode(stdin), timeout=30) stdout, _ = proc.communicate(str.encode(stdin), timeout=15) stdout = decode_define(stdout) except TimeoutExpired: raise InputError(f"define call timed out!") # TODO: How to get the stdout when define times out? Calling # communiate may also result in an indefinite hang so I disabled it # for now... # # Retrieve output of timed out define call # stdout, stderr = proc.communicate() # stdout = decode_define(stdout) # stderr = decode_define(stderr) # # Attach stdout and stderr of proc to error, so they can be # # accessed later on. # error.stdout = stdout # error.stderr = stdout # raise error proc.terminate() return stdout
def qcdb_post_parse_output(self, input_model: AtomicInput, output_model: "AtomicResult") -> "AtomicResult": dqcvars = PreservingDict(copy.deepcopy(output_model.extras["qcvars"])) # badly placed # Cfour's SCS-MP2 is non adjustible and only valid for UHF # ROMP2 doesn't print SS & OS # if "MP2 OPPOSITE-SPIN CORRELATION ENERGY" in dqcvars and "MP2 SAME-SPIN CORRELATION ENERGY" in dqcvars: # oss_opt = input_model.extras['qcdb:options'].scroll['QCDB']['MP2_OS_SCALE'] # sss_opt = input_model.extras['qcdb:options'].scroll['QCDB']['MP2_SS_SCALE'] # custom_scsmp2_corl = \ # Decimal(oss_opt.value) * dqcvars["MP2 OPPOSITE-SPIN CORRELATION ENERGY"] + \ # Decimal(sss_opt.value) * dqcvars["MP2 SAME-SPIN CORRELATION ENERGY"] # if "MP2 SINGLES ENERGY" in dqcvars: # custom_scsmp2_corl += dqcvars["MP2 SINGLES ENERGY"] # dqcvars["CUSTOM SCS-MP2 CORRELATION ENERGY"] = custom_scsmp2_corl try: qcvars.build_out(dqcvars) except ValueError: raise InputError( error_stamp(output_model.native_files["input"], output_model.stdout, output_model.stderr)) calcinfo = qcvars.certify_and_datumize( dqcvars, plump=True, nat=len(output_model.molecule.symbols)) output_model.extras["qcdb:qcvars"] = calcinfo return output_model
def muster_modelchem(method: str, derint: int) -> Dict[str, Any]: """Converts the QC method into CFOUR keywords.""" method = method.lower() opts = {} if derint == 0: if method == "cfour": pass # permit clean operation of sandwich mode else: opts["deriv_level"] = "zero" elif derint == 1: opts["deriv_level"] = "first" elif derint == 2: opts["vibration"] = "exact" if method == "cfour": pass elif method in ["scf", "hf"]: opts["calc_level"] = "scf" elif method == "mp2": opts["calc_level"] = "mp2" elif method == "mp3": opts["calc_level"] = "mp3" elif method == "mp4(sdq)": opts["calc_level"] = "sdq-mp4" elif method == "mp4": opts["calc_level"] = "mp4" elif method == "cc2": opts["calc_level"] = "cc2" elif method == "ccsd": opts["calc_level"] = "ccsd" elif method == "cc3": opts["calc_level"] = "cc3" elif method == "ccsd(t)": # Can't use (T) b/c bug in xsymcor lops it off opts["calc_level"] = "ccsd[t]" elif method == "ccsdt": opts["calc_level"] = "ccsdt" else: raise InputError(f"Method not recognized: {method}") return opts
def muster_modelchem(method: str, derint: int) -> Dict[str, Any]: """Converts the QC method into GAMESS keywords.""" method = method.lower() opts = {} runtyp = { 0: "energy", 1: "gradient", 2: "hessian", #'properties': 'prop', }[derint] opts["contrl__runtyp"] = runtyp if method == "gamess": pass elif method in ["scf", "hf"]: pass # opts['contrl__mplevl'] = 0 # opts['contrl__cityp'] = 'none' # opts['contrl__cctyp'] = 'none' elif method == "mp2": opts["contrl__mplevl"] = 2 elif method == "lccd": opts["contrl__cctyp"] = "lccd" elif method == "ccd": opts["contrl__cctyp"] = "ccd" elif method == "ccsd": opts["contrl__cctyp"] = "ccsd" elif method in ["ccsd+t(ccsd)", "ccsd(t)"]: opts["contrl__cctyp"] = "ccsd(t)" elif method == "pbe": opts["contrl__dfttyp"] = "pbe" elif method == "b3lyp": opts["contrl__dfttyp"] = "b3lypv1r" elif method == "b3lyp5": opts["contrl__dfttyp"] = "b3lyp" else: raise InputError(f"Method not recognized: {method}") return opts
def format_keyword(keyword: str, val: Any) -> Tuple[str, str]: """Reformat keyword's value from Python into CFOUR-speak. Arrays are the primary target.""" keyword = keyword.upper() # Transform booleans into integers if val is True: text = "1" elif val is False: text = "0" # Transform list from [[3, 0, 1, 1], [2, 0, 1, 0]] --> 3-0-1-1/2-0-1-0 elif isinstance(val, list): if type(val[0]).__name__ == "list": if type(val[0][0]).__name__ == "list": raise InputError( "Option has level of array nesting inconsistent with CFOUR." ) else: # option is 2D array text = "/".join("-".join(map(str, no)) for no in val) else: # option is plain 1D array if keyword in ["ESTATE_SYM", "CFOUR_ESTATE_SYM"]: # [3, 1, 0, 2] --> 3/1/0/2 text = "/".join(map(str, val)) else: # [3, 1, 0, 2] --> 3-1-0-2 text = "-".join(map(str, val)) # Transform the basis sets that *must* be lowercase elif keyword in ["CFOUR_BASIS", "BASIS"] and val.upper() in [ "SVP", "DZP", "TZP", "TZP2P", "QZ2P", "PZ3D2F", "13S9P4D3F", ]: text = str(val.lower()) # Transform the methods that *must* be mixed case elif keyword in ["CFOUR_CALC_LEVEL", "CALC_LEVEL" ] and val.upper() == "CCSDT-1B": text = "CCSDT-1b" # No Transform else: text = str(val).upper() return keyword, text
def compute(self, input_model: AtomicInput, config: "TaskConfig") -> "AtomicResult": self.found(raise_error=True) verbose = 1 print_jobrec(f"[1] {self.name} RESULTINPUT PRE-PLANT", input_model.dict(), verbose >= 3) job_inputs = self.qcdb_build_input(input_model, config) print_jobrec(f"[2] {self.name}REC PRE-ENGINE", job_inputs, verbose >= 4) success, dexe = self.execute(job_inputs) print_jobrec(f"[3] {self.name}REC POST-ENGINE", dexe, verbose >= 4) if "INPUT HAS AT LEAST ONE SPELLING OR LOGIC MISTAKE" in dexe[ "stdout"]: raise InputError( error_stamp(job_inputs["infiles"]["gamess.inp"], dexe["stdout"], dexe["stderr"])) if not success: output_model = input_model output_model["error"] = { "error_type": "execution_error", "error_message": dexe["stderr"] } dexe["outfiles"]["stdout"] = dexe["stdout"] dexe["outfiles"]["stderr"] = dexe["stderr"] dexe["outfiles"]["input"] = job_inputs["infiles"]["gamess.inp"] output_model = self.parse_output(dexe["outfiles"], input_model) print_jobrec(f"[4a] {self.name} RESULT POST-HARVEST", output_model.dict(), verbose >= 5) output_model = self.qcdb_post_parse_output(input_model, output_model) print_jobrec(f"[4] {self.name} RESULT POST-POST-HARVEST", output_model.dict(), verbose >= 2) return output_model
def set_method(method, grid): if method == "hf": method_stdin = "" elif method in METHODS["ricc2"]: # Setting geoopt in $ricc2 will make the ricc2 module to produce # a gradient. # Drop the 'ri'-prefix of the method string. geoopt_stdin = f"geoopt {method[2:]} ({geoopt})" if geoopt else "" method_stdin = f"""cc freeze * cbas * ricc2 {method} list models {geoopt_stdin} list geoopt * * """ elif method in METHODS["dft_hardcoded"]: method_stdin = f"""dft on func {method} grid {grid} """ # TODO: Handle xcfuncs that aren't defined in define, e.g. # new functionals introduced in 7.4 from libxc. ... # Maybe the best idea would be to not set the functional here # but just turn on DFT and add it to the control file later on. elif method in METHODS["dft_libxc"]: raise InputError("libxc functionals are not supported right now.") return method_stdin
def prepare_stdin(method: str, basis: str, keywords: Dict[str, Any], charge: int, mult: int, geoopt: Optional[str] = "") -> str: """Prepares a str that can be sent to define to produce the desired input for Turbomole.""" # Load data from keywords unrestricted = keywords.get("unrestricted", False) grid = keywords.get("grid", "m3") methods_flat = list(it.chain(*[m for m in METHODS.values()])) if method not in methods_flat: raise InputError(f"Method {method} not in supported methods " f"{methods_flat}!") # This variable may contain substitutions that will be made to # the control file after it was created from a define call, e.g. # setting XC functionals that aren't hardcoded in define etc. subs = None def occ_num_mo_data(charge: int, mult: int, unrestricted: Optional[bool] = False) -> str: """Handles the 'Occupation Number & Molecular Orbital' section of define. Sets appropriate charge and multiplicity in the system and decided between restricted and unrestricted calculation. RHF and UHF are supported. ROHF could be implemented later on by using the 's' command to list the available MOs and then close the appropriate number of MOs to doubly occupied MOs by 'c' by comparing the number of total MOs and the desired multiplicity.""" # Do unrestricted calculation if explicitly requested or mandatory unrestricted = unrestricted or (mult != 1) unpaired = mult - 1 charge = int(charge) occ_num_mo_data_stdin = f"""eht y {charge} y """ if unrestricted: # Somehow Turbomole/define asks us if we want to write # natural orbitals... we don't want to. occ_num_mo_data_stdin = f"""eht y {charge} n u {unpaired} * n """ return occ_num_mo_data_stdin def set_method(method, grid): if method == "hf": method_stdin = "" elif method in METHODS["ricc2"]: # Setting geoopt in $ricc2 will make the ricc2 module to produce # a gradient. # Drop the 'ri'-prefix of the method string. geoopt_stdin = f"geoopt {method[2:]} ({geoopt})" if geoopt else "" method_stdin = f"""cc freeze * cbas * ricc2 {method} list models {geoopt_stdin} list geoopt * * """ elif method in METHODS["dft_hardcoded"]: method_stdin = f"""dft on func {method} grid {grid} """ # TODO: Handle xcfuncs that aren't defined in define, e.g. # new functionals introduced in 7.4 from libxc. ... # Maybe the best idea would be to not set the functional here # but just turn on DFT and add it to the control file later on. elif method in METHODS["dft_libxc"]: raise InputError("libxc functionals are not supported right now.") return method_stdin # Resolution of identity def set_ri(keywords): # TODO: senex/RIJCOSX? ri_kws = { ri_kw: keywords.get(ri_kw, False) for ri_kw in KEYWORDS["ri"] } ri_stdins = { "rijk": "rijk\non\n\n", "ri": "ri\non\n\n", "marij": "marij\n\n" } ri_stdin = "\n".join( [ri_stdins[ri_kw] for ri_kw, use in ri_kws.items() if use]) return ri_stdin # ri_stdin = "" # # Use either RIJK or RIJ if requested. # if ri_kws["rijk"]: # ri_stdin = """rijk # on # """ # elif ri_kws["rij"]: # ri_stdin = """rij # on # """ # # MARIJ can be used additionally. # if ri_kws["marij"]: # ri_stdin += """marij # """ # return ri_stdin # Dispersion correction def set_dsp(keywords): # TODO: set_ri and set_dsp are basically the same funtion. Maybe # we could abstract this somehow? dsp_kws = { dsp_kw: keywords.get(dsp_kw, False) for dsp_kw in KEYWORDS["dsp"] } dsp_stdins = {"d3": "dsp\non\n\n", "d3bj": "dsp\nbj\n\n"} dsp_stdin = "\n".join( [dsp_stdins[dsp_kw] for dsp_kw, use in dsp_kws.items() if use]) return dsp_stdin kwargs = { "init_guess": occ_num_mo_data(charge, mult, unrestricted), "set_method": set_method(method, grid), "ri": set_ri(keywords), "dsp": set_dsp(keywords), "title": "QCEngine Turbomole", "scf_conv": 8, "scf_iters": 150, "basis": basis, } stdin = """ {title} a coord * no b all {basis} * {init_guess} {set_method} {ri} {dsp} scf conv {scf_conv} iter {scf_iters} * """.format(**kwargs) return stdin, subs
def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into NWChem keywords Args: method (str): Name of the QC method to use derint (str): Index of the run type use_tce (bool): Whether to use the Tensor Contraction Engine Returns: (str): Task command for NWChem (dict): Any options for NWChem """ # Standardize the method name method = method.lower() opts = {} # Map the run type to runtyp = { "energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property" }[derint] # Write out the theory directive if method == "nwchem": mdccmd = "" elif method in ["scf", "hf"]: mdccmd = f"task scf {runtyp}\n\n" elif method == "mp2": if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__mp2"] = True else: mdccmd = f"task mp2 {runtyp}\n\n" elif method == "mp3": if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__mp3"] = True elif method == "mp4": if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__mp4"] = True elif method == "ccd": if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__ccd"] = True elif method == "ccsd": if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__ccsd"] = True else: mdccmd = f"task ccsd {runtyp}\n\n" elif method == "ccsdt": if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__ccsdt"] = True else: mdccmd = f"task ccsdt {runtyp}\n\n" elif method == "ccsd(t)": if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__ccsd(t)"] = True else: mdccmd = f"task ccsd(t) {runtyp}\n\n" elif method == "tddft": mdccmd = f"task tddft {runtyp}\n\n" elif method in [ "sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band" ]: raise InputError(f'Method "{method}" not yet supported by QCEngine') elif method == "tce": raise InputError( f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".') elif method.split()[0] in _xc_functionals: opts["dft__xc"] = method if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__"] = "dft" else: mdccmd = f"task dft {runtyp}\n\n" elif method == "dft": if use_tce: mdccmd = f"task tce {runtyp}\n\n" opts["tce__"] = "dft" else: mdccmd = f"task dft {runtyp}\n\n" else: raise InputError(f"Method not recognized: {method}") return mdccmd, opts
def compute(self, input_model: AtomicInput, config: "TaskConfig") -> "AtomicResult": self.found(raise_error=True) verbose = 1 print_jobrec(f"[1] {self.name} RESULTINPUT PRE-PLANT", input_model.dict(), verbose >= 3) job_inputs = self.qcdb_build_input(input_model, config) print_jobrec(f"[2] {self.name}REC PRE-ENGINE", job_inputs, verbose >= 4) # 'NWCHEM_OMP_NUM_CORES': os.environ.get('NWCHEM_OMP_NUM_CORES'), success, dexe = self.execute(job_inputs) stdin = job_inputs["infiles"]["nwchem.nw"] print_jobrec(f"[3] {self.name}REC POST-ENGINE", dexe, verbose >= 4) if "There is an error in the input file" in dexe["stdout"]: raise InputError(error_stamp(stdin, dexe["stdout"], dexe["stderr"])) if "not compiled" in dexe["stdout"]: # recoverable with a different compilation with optional modules raise InputError(error_stamp(stdin, dexe["stdout"], dexe["stderr"])) if success: dexe["outfiles"]["stdout"] = dexe["stdout"] dexe["outfiles"]["stderr"] = dexe["stderr"] dexe["outfiles"]["input"] = stdin output_model = self.parse_output(dexe["outfiles"], input_model) print_jobrec(f"[4a] {self.name} RESULT POST-HARVEST", output_model.dict(), verbose >= 5) output_model = self.qcdb_post_parse_output(input_model, output_model) print_jobrec(f"[4] {self.name} RESULT POST-POST-HARVEST", output_model.dict(), verbose >= 2) else: ## Check if any of the errors are known #for error in all_errors: # error.detect_error(dexe) output_model = FailedOperation( success=False, error={ "error_type": "execution_error", "error_message": error_stamp(stdin, dexe["stdout"], dexe["stderr"]), }, input_data=input_model.dict(), ) return output_model
def qcdb_build_input(self, input_model: AtomicInput, config: "JobConfig", template: Optional[str] = None) -> Dict[str, Any]: gamessrec = { "infiles": {}, "scratch_messy": config.scratch_messy, "scratch_directory": config.scratch_directory, } kwgs = {"accession": accession_stamp(), "verbose": 1} ropts = input_model.extras["qcdb:options"] mode_config = input_model.extras["qcdb:mode_config"] mf_mol, mf_data = get_master_frame(input_model.molecule, config.scratch_directory) # c1 so _all_ atoms written to BasisSet mf_qmol_c1 = Molecule.from_schema(mf_mol.dict() | {"fix_symmetry": "c1"}) _qcdb_basis = ropts.scroll["QCDB"]["BASIS"].value # _gamess_basis = ropts.scroll['GAMESS']['BASIS'].value qbs = BasisSet.pyconstruct(mf_qmol_c1, "BASIS", _qcdb_basis) sysinfo = {} # forcing nfc above. all these need to be reocmputed together for a consistent cidet input group # this will be default FC # TODO change these values when user sets custom FC nel = mf_mol.nelectrons() nfzc = mf_qmol_c1.n_frozen_core(depth=True) nels = nel - 2 * nfzc nact = qbs.nbf() - nfzc sysinfo["fc"] = { "nel": nel, "ncore": nfzc, "nact": nact, "nels": nels, } nfzc = 0 nels = nel - 2 * nfzc nact = qbs.nbf() - nfzc sysinfo["ae"] = { "nel": nel, "ncore": nfzc, "nact": nact, "nels": nels, } # Handle qcdb keywords implying gamess keyword values muster_inherited_keywords(ropts, mode_config, sysinfo) molbascmd = muster_molecule_and_basisset(mf_qmol_c1.to_dict(), qbs, ropts, mf_data["unique"], mf_data["symmetry_card"]) # Handle calc type and quantum chemical method muster_modelchem(input_model.model.method, input_model.driver.derivative_int(), ropts, mode_config, sysinfo) ropts.require("QCDB", "MEMORY", f"{config.memory} gib", **kwgs) # Handle memory # * [GiB] --> [M QW] # * docs on mwords: "This is given in units of 1,000,000 words (as opposed to 1024*1024 words)" # * docs: "the memory required on each processor core for a run using p cores is therefore MEMDDI/p + MWORDS." # * int() rounds down mwords_total = int(config.memory * (1024**3) / 8e6) asdf = "mem trials\n" for mem_frac_replicated in (1, 0.5, 0.1, 0.75): mwords, memddi = self._partition(mwords_total, mem_frac_replicated, config.ncores) asdf += f"loop {mwords_total=} {mem_frac_replicated=} {config.ncores=} -> repl: {mwords=} dist: {memddi=} -> percore={memddi/config.ncores + mwords} tot={memddi + config.ncores * mwords}\n" trial_opts = { key: ropt.value for key, ropt in sorted(ropts.scroll["GAMESS"].items()) if ropt.disputed() } trial_opts["contrl__exetyp"] = "check" trial_opts["system__parall"] = not (config.ncores == 1) trial_opts["system__mwords"] = mwords trial_opts["system__memddi"] = memddi trial_gamessrec = { "infiles": { "trial_gamess.inp": format_keywords(trial_opts) + molbascmd }, "command": [which("rungms"), "trial_gamess", "00", str(config.ncores)], "scratch_messy": False, "scratch_directory": config.scratch_directory, } success, dexe = self.execute(trial_gamessrec) # this would be a lot cleaner if there was a unique or list of memory error strings if (("ERROR: ONLY CCTYP=CCSD OR CCTYP=CCSD(T) CAN RUN IN PARALLEL." in dexe["stdout"]) or ("ERROR: ROHF'S CCTYP MUST BE CCSD OR CR-CCL, WITH SERIAL EXECUTION" in dexe["stdout"]) or ("CI PROGRAM CITYP=FSOCI DOES NOT RUN IN PARALLEL." in dexe["stdout"])): config.ncores = 1 elif "INPUT HAS AT LEAST ONE SPELLING OR LOGIC MISTAKE" in dexe[ "stdout"]: raise InputError(dexe["stdout"]) elif "EXECUTION OF GAMESS TERMINATED -ABNORMALLY-" in dexe[ "stdout"]: pass else: ropts.require("GAMESS", "SYSTEM__MWORDS", mwords, accession="12341234", verbose=True) ropts.require("GAMESS", "SYSTEM__MEMDDI", mwords, accession="12341234", verbose=True) asdf += f"breaking {mwords=} {memddi=}\n" break ropts.print_changed(history=True) # print("Touched Keywords") # debug # print(ropts.print_changed(history=True)) # debug # Handle conversion of qcsk keyword structure into program format skma_options = { key: ropt.value for key, ropt in sorted(ropts.scroll["GAMESS"].items()) if ropt.disputed() } optcmd = format_keywords(skma_options) gamessrec["infiles"]["gamess.inp"] = optcmd + molbascmd gamessrec["command"] = [ which("rungms"), "gamess", "00", str(config.ncores), ] # rungms JOB VERNO NCPUS >& JOB.log & return gamessrec