示例#1
0
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
示例#2
0
    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"])
示例#3
0
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
示例#4
0
    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
示例#5
0
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
示例#6
0
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
示例#7
0
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
示例#8
0
    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
示例#9
0
    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
示例#10
0
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
示例#11
0
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
示例#12
0
    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
示例#13
0
    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