def _register_driver(cls): # Verify that the driver is not already registered. if cls in [driver.cls for driver in _REGISTERED_DRIVERS.values()]: raise QiskitChemistryError( 'Could not register class {} is already registered'.format(cls)) # Verify that it has a minimal valid configuration. try: driver_name = cls.CONFIGURATION['name'] except (LookupError, TypeError): raise QiskitChemistryError( 'Could not register driver: invalid configuration') # Verify that the driver is valid check_driver_valid = getattr(cls, 'check_driver_valid', None) if check_driver_valid is not None: try: check_driver_valid() except Exception as e: logger.debug(str(e)) raise QiskitChemistryError( 'Could not register class {}. Name {} is not valid'.format( cls, driver_name)) from e if driver_name in _REGISTERED_DRIVERS: raise QiskitChemistryError( 'Could not register class {}. Name {} {} is already registered'. format(cls, driver_name, _REGISTERED_DRIVERS[driver_name].cls)) # Append the driver to the `registered_classes` dict. _REGISTERED_DRIVERS[driver_name] = RegisteredDriver( driver_name, cls, copy.deepcopy(cls.CONFIGURATION)) return driver_name
def _run_g16(cfg): # Run Gaussian 16. We capture stdout and if error log the last 10 lines that # should include the error description from Gaussian process = None try: process = Popen(GAUSSIAN_16, stdin=PIPE, stdout=PIPE, universal_newlines=True) stdout, stderr = process.communicate(cfg) process.wait() except: if process is not None: process.kill() raise QiskitChemistryError('{} run has failed'.format(GAUSSIAN_16_DESC)) if process.returncode != 0: errmsg = "" if stdout is not None: lines = stdout.splitlines() start = 0 if len(lines) > 10: start = len(lines) - 10 for i in range(start, len(lines)): logger.error(lines[i]) errmsg += lines[i] + "\n" raise QiskitChemistryError('{} process return code {}\n{}'.format(GAUSSIAN_16_DESC, process.returncode, errmsg)) else: if logger.isEnabledFor(logging.DEBUG): alltext = "" if stdout is not None: lines = stdout.splitlines() for line in lines: alltext += line + "\n" logger.debug("Gaussian output:\n{}".format(alltext))
def _run_psi4(input_file, output_file): # Run psi4. process = None try: process = subprocess.Popen([PSI4, input_file, output_file], stdout=subprocess.PIPE, universal_newlines=True) stdout, stderr = process.communicate() process.wait() except: if process is not None: process.kill() raise QiskitChemistryError('{} run has failed'.format(PSI4)) if process.returncode != 0: errmsg = "" if stdout is not None: lines = stdout.splitlines() for i in range(len(lines)): logger.error(lines[i]) errmsg += lines[i] + "\n" raise QiskitChemistryError('{} process return code {}\n{}'.format( PSI4, process.returncode, errmsg))
def _check_molecule_format(val): """If it seems to be zmatrix rather than xyz format we convert before returning""" atoms = [x.strip() for x in val.split(';')] if atoms is None or len(atoms) < 1: raise QiskitChemistryError('Molecule format error: ' + val) # An xyz format has 4 parts in each atom, if not then do zmatrix convert # Allows dummy atoms, using symbol 'X' in zmatrix format for coord computation to xyz parts = [x.strip() for x in atoms[0].split(' ')] if len(parts) != 4: try: zmat = [] for atom in atoms: parts = [x.strip() for x in atom.split(' ')] z = [parts[0]] for i in range(1, len(parts), 2): z.append(int(parts[i])) z.append(float(parts[i + 1])) zmat.append(z) xyz = z2xyz(zmat) new_val = "" for i in range(len(xyz)): atm = xyz[i] if atm[0].upper() == 'X': continue if len(new_val) > 0: new_val += "; " new_val += "{} {} {} {}".format(atm[0], atm[1], atm[2], atm[3]) return new_val except Exception as exc: raise QiskitChemistryError('Failed to convert atom string: ' + val) from exc return val
def _augment_config(self, fname, cfg): cfgaug = "" with io.StringIO() as outf: with io.StringIO(cfg) as inf: # Add our Route line at the end of any existing ones line = "" added = False while not added: line = inf.readline() if not line: break if line.startswith('#'): outf.write(line) while not added: line = inf.readline() if not line: raise QiskitChemistryError('Unexpected end of Gaussian input') if len(line.strip()) == 0: outf.write('# Window=Full Int=NoRaff Symm=(NoInt,None) output=(matrix,i4labels,mo2el) tran=full\n') added = True outf.write(line) else: outf.write(line) # Now add our filename after the title and molecule but before any additional data. We located # the end of the # section by looking for a blank line after the first #. Allows comment lines # to be inter-mixed with Route lines if that's ever done. From here we need to see two sections # more, the title and molecule so we can add the filename. added = False section_count = 0 blank = True while not added: line = inf.readline() if not line: raise QiskitChemistryError('Unexpected end of Gaussian input') if len(line.strip()) == 0: blank = True if section_count == 2: break else: if blank: section_count += 1 blank = False outf.write(line) outf.write(line) outf.write(fname) outf.write('\n\n') # Whatever is left in the original config we just append without further inspection while True: line = inf.readline() if not line: break outf.write(line) cfgaug = outf.getvalue() return cfgaug
def compute_integrals(config): # Get config from input parameters # Molecule is in this format xyz as below or in Z-matrix e.g "H; O 1 1.08; H 2 1.08 1 107.5": # atoms=H .0 .0 .0; H .0 .0 0.2 # units=Angstrom # charge=0 # multiplicity=1 # where we support symbol for atom as well as number if 'atoms' not in config: raise QiskitChemistryError('Atoms is missing') val = config['atoms'] if val is None: raise QiskitChemistryError('Atoms value is missing') charge = int(config.get('charge', '0')) multiplicity = int(config.get('multiplicity', '1')) units = _check_units(config.get('units', 'Angstrom')) mol = _parse_molecule(val, units, charge, multiplicity) basis = config.get('basis', 'sto3g') calc_type = config.get('calc_type', 'rhf').lower() try: ehf, enuke, norbs, mohij, mohijkl, orbs, orbs_energy = _calculate_integrals( mol, basis, calc_type) except Exception as exc: raise QiskitChemistryError( 'Failed electronic structure computation') from exc # Create driver level molecule object and populate _q_ = QMolecule() # Energies and orbits _q_.hf_energy = ehf _q_.nuclear_repulsion_energy = enuke _q_.num_orbitals = norbs _q_.num_alpha = mol.nup() _q_.num_beta = mol.ndown() _q_.mo_coeff = orbs _q_.orbital_energies = orbs_energy # Molecule geometry _q_.molecular_charge = mol.charge _q_.multiplicity = mol.multiplicity _q_.num_atoms = len(mol) _q_.atom_symbol = [] _q_.atom_xyz = np.empty([len(mol), 3]) atoms = mol.atoms for _n in range(0, _q_.num_atoms): atuple = atoms[_n].atuple() _q_.atom_symbol.append(QMolecule.symbols[atuple[0]]) _q_.atom_xyz[_n][0] = atuple[1] _q_.atom_xyz[_n][1] = atuple[2] _q_.atom_xyz[_n][2] = atuple[3] # 1 and 2 electron integrals _q_.mo_onee_ints = mohij _q_.mo_eri_ints = mohijkl return _q_
def check_driver_valid(): err_msg = "PySCF is not installed. Use 'pip install pyscf'" try: spec = importlib.util.find_spec('pyscf') if spec is not None: return except Exception as e: logger.debug('PySCF check error {}'.format(str(e))) raise QiskitChemistryError(err_msg) from e raise QiskitChemistryError(err_msg)
def check_driver_valid(): err_msg = 'PyQuante2 is not installed. See https://github.com/rpmuller/pyquante2' try: spec = importlib.util.find_spec('pyquante2') if spec is not None: return except Exception as e: logger.debug('PyQuante2 check error {}'.format(str(e))) raise QiskitChemistryError(err_msg) from e raise QiskitChemistryError(err_msg)
def run_algorithm_from_json(self, params, output=None, backend=None): """ Runs the Aqua Chemistry experiment from json dictionary Args: params (dictionary): Input data output (filename): Output data backend (BaseBackend): backend object Returns: result dictionary """ ret = run_algorithm(params, None, True, backend) if not isinstance(ret, dict): raise QiskitChemistryError( "Algorithm run result should be a dictionary") convert_json_to_dict(ret) if logger.isEnabledFor(logging.DEBUG): logger.debug('Algorithm returned: {}'.format( pprint.pformat(ret, indent=4))) print('Output:') if isinstance(ret, dict): for k, v in ret.items(): print("'{}': {}".format(k, v)) else: print(ret) return ret
def _check_molecule_format(val): """If it seems to be zmatrix rather than xyz format we convert before returning""" atoms = [x.strip() for x in val.split(';')] if atoms is None or len(atoms) < 1: raise QiskitChemistryError('Molecule format error: ' + val) # Anx xyz format has 4 parts in each atom, if not then do zmatrix convert parts = [x.strip() for x in atoms[0].split(' ')] if len(parts) != 4: try: return gto.mole.from_zmatrix(val) except Exception as exc: raise QiskitChemistryError('Failed to convert atom string: ' + val) from exc return val
def __init__(self, atoms, units=UnitsType.ANGSTROM, charge=0, multiplicity=1, basis=BasisType.BSTO3G): """ Initializer Args: atoms (str or list): atoms list or string separated by semicolons or line breaks units (UnitsType): angstrom or bohr charge (int): charge multiplicity (int): spin multiplicity basis (BasisType): sto3g or 6-31g or 6-31g** """ if not isinstance(atoms, list) and not isinstance(atoms, str): raise QiskitChemistryError( "Invalid atom input for PYQUANTE Driver '{}'".format(atoms)) if isinstance(atoms, list): atoms = ';'.join(atoms) else: atoms = atoms.replace('\n', ';') units = units.value basis = basis.value self.validate(locals()) super().__init__() self._atoms = atoms self._units = units self._charge = charge self._multiplicity = multiplicity self._basis = basis
def init_from_input(cls, section): """ Initialize via section dictionary. Args: params (dict): section dictionary Returns: Driver: Driver object """ if 'properties' not in section or len(section['properties']) == 0: raise QiskitChemistryError('Missing or empty properties section') params = section['properties'] kwargs = {} for k, v in params.items(): if k == PyQuanteDriver.KEY_UNITS: v = UnitsType(v) elif k == PyQuanteDriver.KEY_BASIS: v = BasisType(v) kwargs[k] = v logger.debug('init_from_input: {}'.format(kwargs)) return cls(**kwargs)
def run(self, section): cfg = section['data'] if cfg is None or not isinstance(cfg, str): raise QiskitChemistryError("Gaussian user supplied configuration invalid: '{}'".format(cfg)) while not cfg.endswith('\n\n'): cfg += '\n' logger.debug("User supplied configuration raw: '{}'".format(cfg.replace('\r', '\\r').replace('\n', '\\n'))) logger.debug('User supplied configuration\n{}'.format(cfg)) # To the Gaussian section of the input file passed here as section['data'] # add line '# Symm=NoInt output=(matrix,i4labels,mo2el) tran=full' # NB: Line above needs to be added in right context, i.e after any lines # beginning with % along with any others that start with # # append at end the name of the MatrixElement file to be written fd, fname = tempfile.mkstemp(suffix='.mat') os.close(fd) cfg = self._augment_config(fname, cfg) logger.debug('Augmented control information:\n{}'.format(cfg)) GaussianDriver._run_g16(cfg) q_mol = self._parse_matrix_file(fname) try: os.remove(fname) except: logger.warning("Failed to remove MatrixElement file " + fname) return q_mol
def _check_units(units): if units.lower() in ["angstrom", "ang", "a"]: units = 'Angstrom' elif units.lower() in ["bohr", "b"]: units = 'Bohr' else: raise QiskitChemistryError('Molecule units format error: ' + units) return units
def _parse_atom(val): if val is None or len(val) < 1: raise QiskitChemistryError('Molecule atom format error: empty') parts = re.split('\s+', val) if len(parts) != 4: raise QiskitChemistryError('Molecule atom format error: ' + val) parts[0] = parts[0].lower().capitalize() if not parts[0].isdigit(): if parts[0] in QMolecule.symbols: parts[0] = QMolecule.symbols.index(parts[0]) else: raise QiskitChemistryError('Molecule atom symbol error: ' + parts[0]) return int(float(parts[0])), float(parts[1]), float(parts[2]), float( parts[3])
def _calculate_integrals(molecule, basis='sto3g', calc_type='rhf'): """Function to calculate the one and two electron terms. Perform a Hartree-Fock calculation in the given basis. Args: molecule : A pyquante2 molecular object. basis : The basis set for the electronic structure computation calc_type: rhf, uhf, rohf Returns: ehf : Hartree-Fock energy enuke: Nuclear repulsion energy norbs : Number of orbitals mohij : One electron terms of the Hamiltonian. mohijkl : Two electron terms of the Hamiltonian. orbs: Molecular orbital coefficients orbs_energy: Orbital energies """ bfs = basisset(molecule, basis) integrals = onee_integrals(bfs, molecule) hij = integrals.T + integrals.V hijkl_compressed = twoe_integrals(bfs) # convert overlap integrals to molecular basis # calculate the Hartree-Fock solution of the molecule if calc_type == 'rhf': solver = rhf(molecule, bfs) elif calc_type == 'rohf': solver = rohf(molecule, bfs) elif calc_type == 'uhf': solver = uhf(molecule, bfs) else: raise QiskitChemistryError('Invalid calc_type: {}'.format(calc_type)) logger.debug('Solver name {}'.format(solver.name)) ehf = solver.converge() if hasattr(solver, 'orbs'): orbs = solver.orbs else: orbs = solver.orbsa norbs = len(orbs) if hasattr(solver, 'orbe'): orbs_energy = solver.orbe else: orbs_energy = solver.orbea enuke = molecule.nuclear_repulsion() # Get ints in molecular orbital basis mohij = simx(hij, orbs) mohijkl_compressed = transformintegrals(hijkl_compressed, orbs) mohijkl = np.zeros((norbs, norbs, norbs, norbs)) for i in range(norbs): for j in range(norbs): for k in range(norbs): for l in range(norbs): mohijkl[i, j, k, l] = mohijkl_compressed[ijkl2intindex(i, j, k, l)] return ehf[0], enuke, norbs, mohij, mohijkl, orbs, orbs_energy
def _register_chemistry_operator(cls): # Verify that the pluggable is not already registered if cls in [input.cls for input in _REGISTERED_CHEMISTRY_OPERATORS.values()]: raise QiskitChemistryError('Could not register class {} is already registered'.format(cls)) # Verify that it has a minimal valid configuration. try: chemistry_operator_name = cls.CONFIGURATION['name'] except (LookupError, TypeError): raise QiskitChemistryError('Could not register chemistry operator: invalid configuration') if chemistry_operator_name in _REGISTERED_CHEMISTRY_OPERATORS: raise QiskitChemistryError('Could not register class {}. Name {} {} is already registered'.format(cls, chemistry_operator_name, _REGISTERED_CHEMISTRY_OPERATORS[chemistry_operator_name].cls)) # Append the pluggable to the `registered_classes` dict. _REGISTERED_CHEMISTRY_OPERATORS[chemistry_operator_name] = RegisteredChemOp( chemistry_operator_name, cls, copy.deepcopy(cls.CONFIGURATION)) return chemistry_operator_name
def run(self, input, output=None, backend=None): """ Runs the Aqua Chemistry experiment Args: input (dictionary/filename): Input data output (filename): Output data backend (BaseBackend): backend object Returns: result dictionary """ if input is None: raise QiskitChemistryError("Missing input.") self._parser = InputParser(input) self._parser.parse() driver_return = self._run_driver_from_parser(self._parser, False) if driver_return[0] == QiskitChemistry._DRIVER_RUN_TO_HDF5: logger.info('No further process.') return {'printable': [driver_return[1]]} data = run_algorithm(driver_return[1], driver_return[2], True, backend) if not isinstance(data, dict): raise QiskitChemistryError( "Algorithm run result should be a dictionary") convert_json_to_dict(data) if logger.isEnabledFor(logging.DEBUG): logger.debug('Algorithm returned: {}'.format( pprint.pformat(data, indent=4))) lines, result = self._format_result(data) logger.info('Processing complete. Final result available') result['printable'] = lines if output is not None: with open(output, 'w') as f: for line in lines: print(line, file=f) return result
def save_input(self, input_file): """ Save the input of a run to a file. Params: input_file (string): file path """ if self._parser is None: raise QiskitChemistryError("Missing input information.") self._parser.save_to_file(input_file)
def _check_molecule_format(val): """If it seems to be zmatrix rather than xyz format we convert before returning""" atoms = [x.strip() for x in val.split(';')] if atoms is None or len(atoms) < 1: raise QiskitChemistryError('Molecule format error: ' + val) # An xyz format has 4 parts in each atom, if not then do zmatrix convert # Allows dummy atoms, using symbol 'X' in zmatrix format for coord computation to xyz parts = [x.strip() for x in atoms[0].split(' ')] if len(parts) != 4: try: newval = [] for entry in gto.mole.from_zmatrix(val): if entry[0].upper() != 'X': newval.append(entry) return newval except Exception as exc: raise QiskitChemistryError('Failed to convert atom string: ' + val) from exc return val
def _run_drive(self, input, save_json_algo_file): if input is None: raise QiskitChemistryError("Missing input.") self._parser = InputParser(input) self._parser.parse() driver_return = self._run_driver_from_parser(self._parser, save_json_algo_file) driver_return[1]['input'] = driver_return[2].to_params() driver_return[1]['input']['name'] = driver_return[2].configuration[ 'name'] return driver_return[1]
def _parse_molecule(val, units, charge, multiplicity): val = _check_molecule_format(val) parts = [x.strip() for x in val.split(';')] if parts is None or len(parts) < 1: raise QiskitChemistryError('Molecule format error: ' + val) geom = [] for n in range(len(parts)): part = parts[n] geom.append(_parse_atom(part)) if len(geom) < 1: raise QiskitChemistryError('Molecule format error: ' + val) try: return molecule(geom, units=units, charge=charge, multiplicity=multiplicity) except Exception as exc: raise QiskitChemistryError('Failed to create molecule') from exc
def register_driver(cls): """ Registers a driver class Args: cls (object): Driver class. Returns: name: driver name """ _discover_on_demand() if not issubclass(cls, BaseDriver): raise QiskitChemistryError('Could not register class {} is not subclass of BaseDriver'.format(cls)) return _register_driver(cls)
def deregister_driver(driver_name): """Remove driver from list of available drivers Args: driver_name (str): name of driver to unregister Raises: QiskitChemistryError if name is not registered. """ _discover_on_demand() if driver_name not in _REGISTERED_DRIVERS: raise QiskitChemistryError('Could not deregister {} not registered'.format(driver_name)) _REGISTERED_DRIVERS.pop(driver_name)
def deregister_driver(self, driver_name): """Remove driver from list of available drivers Args: driver_name (str): name of driver to unregister Raises: QiskitChemistryError if name is not registered. """ self._discover_on_demand() if driver_name not in self._registration: raise QiskitChemistryError('Could not deregister {} not registered'.format(driver_name)) self._registration.pop(driver_name)
def run_drive_to_jsonfile(self, input, jsonfile): if jsonfile is None: raise QiskitChemistryError("Missing json file") data = self._run_drive(input, True) if data is None: logger.info('No data to save. No further process.') return with open(jsonfile, 'w') as fp: json.dump(data, fp, sort_keys=True, indent=4) print("Algorithm input file saved: '{}'".format(jsonfile))
def __init__(self, config): """ Initializer Args: config (str or list): driver configuration """ if not isinstance(config, list) and not isinstance(config, str): raise QiskitChemistryError("Invalid input for Gaussian Driver '{}'".format(config)) if isinstance(config, list): config = '\n'.join(config) super().__init__() self._config = config
def get_driver_instance(self, name): """Return an instance for the name in configuration. Args: name (str): the name Returns: Object: module instance Raises: QiskitChemistryError: if module is unavailable """ cls = self.get_driver_class(name) try: return cls() except Exception as err: raise QiskitChemistryError('{} could not be instantiated: {}'.format(cls, err))
def run(self, section): properties = section['properties'] if HDF5Driver.KEY_HDF5_INPUT not in properties: raise QiskitChemistryError('Missing hdf5 input property') hdf5_file = properties[HDF5Driver.KEY_HDF5_INPUT] if self.work_path is not None and not os.path.isabs(hdf5_file): hdf5_file = os.path.abspath(os.path.join(self.work_path, hdf5_file)) if not os.path.isfile(hdf5_file): raise LookupError('HDF5 file not found: {}'.format(hdf5_file)) molecule = QMolecule(hdf5_file) molecule.load() return molecule
def get_driver_configuration(driver_name): """Return the configuration for the named module. Args: driver_name (str): the module name Returns: dict: configuration dict Raises: QiskitChemistryError: if module is unavailable """ _discover_on_demand() if driver_name not in _REGISTERED_DRIVERS: raise QiskitChemistryError('{} not registered'.format(driver_name)) return copy.deepcopy(_REGISTERED_DRIVERS[driver_name].configuration)