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 _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 _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: # pylint: disable=len-as-condition 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 atm in xyz: if atm[0].upper() == 'X': continue if new_val: 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 __init__(self, fcidump_input: str, atoms: Optional[List[str]] = None) -> None: """ Args: fcidump_input: Path to the FCIDump file. atoms: Allows to specify the atom list of the molecule. If it is provided, the created QMolecule instance will permit frozen core Hamiltonians. This list must consist of valid atom symbols. Raises: QiskitChemistryError: If ``fcidump_input`` is not a string or if ``atoms`` is not a list of valid atomic symbols as specified in ``QMolecule``. """ super().__init__() if not isinstance(fcidump_input, str): raise QiskitChemistryError( "The fcidump_input must be str, not '{}'".format( fcidump_input)) self._fcidump_input = fcidump_input if atoms and not isinstance(atoms, list) \ and not all(sym in QMolecule.symbols for sym in atoms): raise QiskitChemistryError( "The atoms must be a list of valid atomic symbols, not '{}'". format(atoms)) self.atoms = atoms
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, _ = process.communicate() process.wait() except Exception: 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 enumerate(lines): logger.error(lines[i]) errmsg += lines[i] + "\n" raise QiskitChemistryError('{} process return code {}\n{}'.format( PSI4, process.returncode, errmsg))
def save_to_file(self, file_name): if file_name is None: raise QiskitChemistryError('Missing file path') file_name = file_name.strip() if len(file_name) == 0: raise QiskitChemistryError('Missing file path') prev_filename = self.get_filename() sections = copy.deepcopy(self.get_sections()) if prev_filename is not None: prev_dirname = os.path.dirname(os.path.realpath(prev_filename)) dirname = os.path.dirname(os.path.realpath(file_name)) if prev_dirname != dirname: InputParser._from_relative_to_abs_paths(sections, prev_filename) contents = '' lastIndex = len(sections) - 1 for i, (section_name, section) in enumerate(sections.items()): contents += '{}{}'.format(InputParser._START_SECTION, section_name) if self.section_is_text(section_name): value = section if isinstance(section, str) else json.dumps(section, sort_keys=True, indent=4) contents += '\n{}'.format(value) else: for k, v in section.items(): contents += '\n {}{}{}'.format(k, InputParser._PROPVALUE_SEPARATOR, str(v)) contents += '\n{}'.format(InputParser._END_SECTION) if i < lastIndex: contents += '\n\n' with open(file_name, 'w') as f: print(contents, file=f)
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 _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 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 check_driver_valid(): err_msg = "PySCF is not installed. See https://sunqm.github.io/pyscf/install.html" 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_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 ex: # pylint: disable=broad-except logger.debug('PyQuante2 check error %s', str(ex)) raise QiskitChemistryError(err_msg) from ex raise QiskitChemistryError(err_msg)
def _process_z2symmetry_reduction(self, qubit_op, aux_ops): z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op) if z2_symmetries.is_empty(): logger.debug('No Z2 symmetries found') z2_qubit_op = qubit_op z2_aux_ops = aux_ops z2_symmetries = Z2Symmetries([], [], [], None) else: logger.debug('%s Z2 symmetries found: %s', len(z2_symmetries.symmetries), ','.join([symm.to_label() for symm in z2_symmetries.symmetries])) # Check auxiliary operators commute with main operator's symmetry logger.debug('Checking operators commute with symmetry:') symmetry_ops = [] for symmetry in z2_symmetries.symmetries: symmetry_ops.append(WeightedPauliOperator(paulis=[[1.0, symmetry]])) commutes = Hamiltonian._check_commutes(symmetry_ops, qubit_op) if not commutes: raise QiskitChemistryError('Z2 symmetry failure main operator must commute ' 'with symmetries found from it') for i, aux_op in enumerate(aux_ops): commutes = Hamiltonian._check_commutes(symmetry_ops, aux_op) if not commutes: aux_ops[i] = None # Discard since no meaningful measurement can be done if self._z2symmetry_reduction == 'auto': hf_state = HartreeFock(num_orbitals=self._molecule_info[self.INFO_NUM_ORBITALS], qubit_mapping=self._qubit_mapping, two_qubit_reduction=self._two_qubit_reduction, num_particles=self._molecule_info[self.INFO_NUM_PARTICLES]) z2_symmetries = Hamiltonian._pick_sector(z2_symmetries, hf_state.bitstr) else: if len(self._z2symmetry_reduction) != len(z2_symmetries.symmetries): raise QiskitChemistryError('z2symmetry_reduction tapering values list has ' 'invalid length {} should be {}'. format(len(self._z2symmetry_reduction), len(z2_symmetries.symmetries))) valid = np.all(np.isin(self._z2symmetry_reduction, [-1, 1])) if not valid: raise QiskitChemistryError('z2symmetry_reduction tapering values list must ' 'contain -1\'s and/or 1\'s only was {}'. format(self._z2symmetry_reduction,)) z2_symmetries.tapering_values = self._z2symmetry_reduction logger.debug('Apply symmetry with tapering values %s', z2_symmetries.tapering_values) chop_to = 0.00000001 # Use same threshold as qubit mapping to chop tapered operator z2_qubit_op = z2_symmetries.taper(qubit_op).chop(chop_to) z2_aux_ops = [] for aux_op in aux_ops: z2_aux_ops.append(z2_symmetries.taper(aux_op).chop(chop_to) if aux_op is not None else None) return z2_qubit_op, z2_aux_ops, z2_symmetries
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 = "PySCF is not installed. See https://sunqm.github.io/pyscf/install.html" try: spec = importlib.util.find_spec('pyscf') if spec is not None: return except Exception as ex: # pylint: disable=broad-except logger.debug('PySCF check error %s', str(ex)) raise QiskitChemistryError(err_msg) from ex raise QiskitChemistryError(err_msg)
def export_dictionary(self, file_name): if file_name is None: raise QiskitChemistryError('Missing file path') file_name = file_name.strip() if len(file_name) == 0: raise QiskitChemistryError('Missing file path') value = json.loads(json.dumps(self.to_dictionary())) value = pprint.pformat(value, indent=4) with open(file_name, 'w') as f: print(value, file=f)
def run_g16(cfg: str) -> str: """ Runs Gaussian 16. We capture stdout and if error log the last 10 lines that should include the error description from Gaussian. Args: cfg: configuration Returns: Text log output Raises: QiskitChemistryError: Failed run or log not captured. """ process = None try: process = Popen(_GAUSSIAN_16, stdin=PIPE, stdout=PIPE, universal_newlines=True) stdout, _ = process.communicate(cfg) process.wait() except Exception as ex: if process is not None: process.kill() raise QiskitChemistryError( '{} run has failed'.format(_GAUSSIAN_16_DESC)) from ex 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)) all_text = "" if stdout is not None: lines = stdout.splitlines() for line in lines: all_text += line + "\n" logger.debug("Gaussian output:\n%s", all_text) return all_text
def export_dictionary(self, file_name): """ export data to python dictionary, saving to file """ if file_name is None: raise QiskitChemistryError('Missing file path') file_name = file_name.strip() if not file_name: raise QiskitChemistryError('Missing file path') value = json.loads(json.dumps(self.to_dictionary())) value = pprint.pformat(value, indent=4) with open(file_name, 'w') as file: print(value, file=file)
def _validate_operator_problem(self): operator_name = self.get_section_property(InputParser.OPERATOR, JSONSchema.NAME) if operator_name is None: return problem_name = self.get_section_property(JSONSchema.PROBLEM, JSONSchema.NAME) if problem_name is None: problem_name = self.get_property_default_value(JSONSchema.PROBLEM, JSONSchema.NAME) if problem_name is None: raise QiskitChemistryError("No algorithm 'problem' section found on input.") problems = InputParser.get_operator_problems(operator_name) if problem_name not in problems: raise QiskitChemistryError("Problem: {} not in the list of problems: {} for operator: {}.".format(problem_name, problems, operator_name))
def process_substitutions(self, substitutions=None): """ change property values by their corresponding substitutions """ if substitutions is not None and not isinstance(substitutions, dict): raise QiskitChemistryError( 'Invalid substitution parameter: {}'.format(substitutions)) if not self.is_substitution_allowed(): return {} result = {} for key, value in self._substitutions.items(): key_items = key.split('.') if len(key_items) != 3: raise QiskitChemistryError( 'Invalid substitution key: {}'.format(key)) name = self.get_property_default_value(key_items[0], JSONSchema.NAME) name = self.get_section_property(key_items[0], JSONSchema.NAME, name) if name != key_items[1]: continue value_set = False value_items = value.split('.') if len(value_items) == 3: name = self.get_section_property(value_items[0], JSONSchema.NAME) if name == value_items[1]: v = self.get_property_default_value( value_items[0], value_items[2]) v = self.get_section_property(value_items[0], value_items[2], v) if v is not None: self.set_section_property(key_items[0], key_items[2], v) result[key] = v value_set = True if value_set or substitutions is None: continue if value in substitutions: self.set_section_property(key_items[0], key_items[2], substitutions[value]) result[key] = substitutions[value] return result
def init_from_input(cls, section): """ Initialize via section dictionary. Args: section (dict): section dictionary Returns: PySCFDriver: Driver object Raises: QiskitChemistryError: Invalid or missing section """ if section is None or not isinstance(section, dict): raise QiskitChemistryError('Invalid or missing section {}'.format(section)) params = section kwargs = {} for k, v in params.items(): if k == 'unit': v = UnitsType(v) elif k == 'hf_method': v = HFMethodType(v) elif k == 'init_guess': v = InitialGuess(v) kwargs[k] = v logger.debug('init_from_input: %s', kwargs) return cls(**kwargs)
def __init__(self, atom, unit=UnitsType.ANGSTROM, charge=0, spin=0, basis='sto3g', max_memory=None): """ Initializer Args: atom (str or list): atom list or string separated by semicolons or line breaks unit (UnitsType): angstrom or bohr charge (int): charge spin (int): spin basis (str): basis set max_memory (int): maximum memory """ if not isinstance(atom, list) and not isinstance(atom, str): raise QiskitChemistryError( "Invalid atom input for PYSCF Driver '{}'".format(atom)) if isinstance(atom, list): atom = ';'.join(atom) else: atom = atom.replace('\n', ';') unit = unit.value self.validate(locals()) super().__init__() self._atom = atom self._unit = unit self._charge = charge self._spin = spin self._basis = basis self._max_memory = max_memory
def _update_operator_problem(self): problem_name = self.get_section_property(JSONSchema.PROBLEM, JSONSchema.NAME) if problem_name is None: problem_name = self.get_property_default_value( JSONSchema.PROBLEM, JSONSchema.NAME) if problem_name is None: raise QiskitChemistryError( "No algorithm 'problem' section found on input.") operator_name = self.get_section_property(InputParser.OPERATOR, JSONSchema.NAME) if operator_name is not None and problem_name in InputParser.get_operator_problems( operator_name): return for operator_name in local_chemistry_operators(): if problem_name in self.get_operator_problems(operator_name): # set to the first input to solve the problem self.set_section_property(InputParser.OPERATOR, JSONSchema.NAME, operator_name) return # no input solve this problem, remove section self.delete_section(InputParser.OPERATOR)
def _process_line(self, section, line): stripLine = line.strip() if len(stripLine) == 0: if section is not None: section['data'].append(line) return section if stripLine.lower().startswith(InputParser._END_SECTION): if section is not None: self._sections[section[ JSONSchema.NAME]] = self._process_section(section) return None if stripLine.startswith(InputParser._START_SECTION): if section is not None: raise QiskitChemistryError( 'New section "{0}" starting before the end of previuos section "{1}"' .format(line, section[JSONSchema.NAME])) return OrderedDict([(JSONSchema.NAME, stripLine[1:].lower()), ('data', [])]) if section is None: return section section['data'].append(line) return section
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 section is None or not isinstance(section, dict): raise QiskitChemistryError( 'Invalid or missing section {}'.format(section)) params = section 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 _parse_atom(val): if val is None or len(val) < 1: raise QiskitChemistryError('Molecule atom format error: empty') parts = re.split(r'\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 compute_integrals(atoms, units, charge, multiplicity, basis, hf_method='rhf', tol=1e-8, maxiters=100): """ Compute integrals """ # 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 units = _check_units(units) mol = _parse_molecule(atoms, units, charge, multiplicity) hf_method = hf_method.lower() try: q_mol = _calculate_integrals(mol, basis, hf_method, tol, maxiters) except Exception as exc: raise QiskitChemistryError( 'Failed electronic structure computation') from exc 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_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 _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