def test_calc_delta_e(): r1 = reaction.Reactant(name='h', atoms=[Atom('H', 0.0, 0.0, 0.0)]) r1.energy = -0.5 r2 = reaction.Reactant(name='h', atoms=[Atom('H', 0.0, 0.0, 0.0)]) r2.energy = -0.5 tsguess = TSguess(atoms=None, reactant=ReactantComplex(r1), product=ProductComplex(r2)) tsguess.bond_rearrangement = BondRearrangement() ts = TransitionState(tsguess) ts.energy = -0.8 p = reaction.Product( name='hh', atoms=[Atom('H', 0.0, 0.0, 0.0), Atom('H', 1.0, 0.0, 0.0)]) p.energy = -1.0 reac = reaction.Reaction(r1, r2, p) reac.ts = ts assert -1E-6 < reac.calc_delta_e() < 1E-6 assert 0.2 - 1E-6 < reac.calc_delta_e_ddagger() < 0.2 + 1E-6
def test_species_class(): assert hasattr(mol, 'print_xyz_file') assert hasattr(mol, 'translate') assert hasattr(mol, 'rotate') assert hasattr(mol, 'get_coordinates') assert hasattr(mol, 'set_atoms') assert hasattr(mol, 'set_coordinates') assert mol.charge == 0 assert mol.mult == 1 assert mol.name == 'H2' assert not mol.is_explicitly_solvated() # A not very sensible water geometry! water = Species(name='H2O', charge=0, mult=1, atoms=[Atom('O'), Atom('H', z=-1), Atom('H', z=1)]) # Base class for molecules and TSs and complexes shouldn't have a # implemented conformer method – needs to do different things based on the # type of species to find conformers with pytest.raises(NotImplementedError): water.find_lowest_energy_conformer(lmethod=xtb)
def test_gradients(): h2 = Molecule(name='h2', atoms=[Atom('H'), Atom('H', x=1.0)]) calc = Calculation(name='h2_grad', molecule=h2, method=method, keywords=method.keywords.grad()) calc.run() h2.energy = calc.get_energy() delta_r = 1E-8 # Energy of a finite difference approximation h2_disp = Molecule(name='h2_disp', atoms=[Atom('H'), Atom('H', x=1.0 + delta_r)]) calc = Calculation(name='h2_disp', molecule=h2_disp, method=method, keywords=method.keywords.grad) calc.run() h2_disp.energy = calc.get_energy() delta_energy = h2_disp.energy - h2.energy # Ha grad = delta_energy / delta_r # Ha A^-1 calc = Calculation(name='h2_grad', molecule=h2, method=method, keywords=method.keywords.grad) calc.run() diff = calc.get_gradients()[1, 0] - grad # Ha A^-1 # Difference between the absolute and finite difference approximation assert np.abs(diff) < 1E-3
def test_set_lowest_energy_conformer(): from autode.mol_graphs import make_graph hb = Atom('H', z=0.7) hydrogen = Species(name='H2', atoms=[h1, hb], charge=0, mult=1) make_graph(hydrogen) hydrogen_wo_e = Species(name='H2', atoms=[h1, hb], charge=0, mult=1) hydrogen_with_e = Species(name='H2', atoms=[h1, hb], charge=0, mult=1) hydrogen_with_e.energy = -1 hydrogen.conformers = [hydrogen_wo_e, hydrogen_with_e] hydrogen._set_lowest_energy_conformer() # Conformers without energy should be skipped assert hydrogen.energy == -1 # Conformers with a different molecular graph should be skipped h_atom = Species(name='H', atoms=[Atom('H')], charge=0, mult=1) h_atom.energy = -2 hydrogen.conformers = [hydrogen_with_e, h_atom] assert hydrogen.energy == -1
def test_set_pi_bonds(): ethene = Species(name='ethene', charge=0, mult=1, atoms=[ Atom('C', -2.20421, 0.40461, 0.00000), Atom('C', -0.87115, 0.38845, 0.00000), Atom('H', -2.76098, -0.22576, 0.68554), Atom('H', -2.74554, 1.04829, -0.68554), Atom('H', -0.32982, -0.25523, 0.68554), Atom('H', -0.31437, 1.01882, -0.68554) ]) mol_graphs.make_graph(ethene) assert ethene.graph.edges[0, 1]['pi'] is True assert ethene.graph.edges[1, 0]['pi'] is True assert ethene.graph.edges[0, 2]['pi'] is False acetylene = Species(name='acetylene', charge=0, mult=1, atoms=[ Atom('C', -2.14031, 0.40384, 0.00000), Atom('C', -0.93505, 0.38923, 0.00000), Atom('H', -3.19861, 0.41666, 0.00000), Atom('H', 0.12326, 0.37640, 0.00000) ]) mol_graphs.make_graph(acetylene) assert acetylene.graph.edges[0, 1]['pi'] is True assert acetylene.graph.edges[0, 2]['pi'] is False
def test_constrained_opt(): os.chdir(os.path.join(here, 'data')) mol = Molecule(name='h3', mult=2, charge=0, atoms=[ Atom('H', 0.0, 0.0, 0.0), Atom('H', 0.7, 0.0, 0.0), Atom('H', 1.7, 0.0, 0.0) ]) Config.XTB.path = here # A path that exists ts_guess = get_ts_guess_constrained_opt( reactant=ReactantComplex(mol), distance_consts={(0, 1): 1.0}, method=orca, keywords=Config.ORCA.keywords.low_opt, name='template_ts_guess', product=ProductComplex(mol)) assert ts_guess.n_atoms == 3 os.remove('xcontrol_template_ts_guess_constrained_opt_ll_xtb') os.remove('template_ts_guess_constrained_opt_ll_xtb.xyz') os.remove('template_ts_guess_constrained_opt_orca.inp') os.chdir(here)
def test_species(): species = Species(name='species', atoms=None, charge=0, mult=1) assert species.n_atoms == 0 h2 = Species(name='H2', charge=0, mult=1, atoms=[Atom('H'), Atom('H')]) assert h2.n_atoms == 2 # Expecting both atoms to be initialised at the origin assert np.linalg.norm(h2.atoms[0].coord - h2.atoms[1].coord) < 1E-6 atom1, atom2 = h2.atoms atom1.translate(vec=np.array([1.0, 0.0, 0.0])) atom1.rotate(theta=np.pi, axis=np.array([0.0, 0.0, 1.0])) assert np.linalg.norm(atom1.coord - np.array([-1., 0., 0.])) < 1E-6 assert h2.solvent is None f = Species(name='F-', charge=-1, mult=1, atoms=[Atom('F')], solvent_name='DCM') assert f.solvent.g09 == 'Dichloromethane' assert f.solvent.xtb == 'CH2Cl2'
def test_conf_class(): h2_conf = Conformer( name='h2_conf', charge=0, mult=1, atoms=[Atom('H', 0.0, 0.0, 0.0), Atom('H', 0.0, 0.0, 0.7)]) assert hasattr(h2_conf, 'optimise') assert hasattr(h2_conf, 'dist_consts') assert h2_conf.n_atoms == 2 assert h2_conf.energy is None assert h2_conf.dist_consts is None h2_conf.optimise(method=orca) assert h2_conf.energy == -1.160780546661 assert h2_conf.atoms is not None assert h2_conf.n_atoms == 2 # Check that if the conformer calculation does not complete successfully # then don't raise an exception for a conformer h2_conf_broken = Conformer( name='h2_conf_broken', charge=0, mult=1, atoms=[Atom('H', 0.0, 0.0, 0.0), Atom('H', 0.0, 0.0, 0.7)]) h2_conf_broken.optimise(method=orca) assert h2_conf_broken.atoms is None assert h2_conf_broken.n_atoms == 0
def test_rmsd_confs(): methane1 = Conformer(name='methane1', charge=0, mult=1, atoms=[ Atom('C', -1.38718, 0.38899, 0.00000), Atom('H', -0.27778, 0.38899, -0.00000), Atom('H', -1.75698, 1.06232, 0.80041), Atom('H', -1.75698, -0.64084, 0.18291), Atom('H', -1.75698, 0.74551, -0.98332) ]) methane2 = Conformer(name='methane2', charge=0, mult=1, atoms=[ Atom('C', -1.38718, 0.38899, 0.00000), Atom('H', -0.43400, 0.50158, -0.55637), Atom('H', -2.23299, 0.69379, -0.64998), Atom('H', -1.36561, 1.03128, 0.90431), Atom('H', -1.51612, -0.67068, 0.30205) ]) # Methane but rotated should have an RMSD ~ 0 Angstroms assert not conf_is_unique_rmsd( conf=methane2, conf_list=[methane1], rmsd_tol=0.1)
def test_grad(): h2 = Molecule(name='h2', atoms=[Atom('H'), Atom('H', x=0.5)]) grad_calc = Calculation(name='h2_grad', molecule=h2, method=method, keywords=Config.MOPAC.keywords.grad) grad_calc.run() energy = grad_calc.get_energy() assert energy is not None gradients = grad_calc.get_gradients() assert gradients.shape == (2, 3) delta_r = 1E-5 h2_disp = Molecule(name='h2_disp', atoms=[Atom('H'), Atom('H', x=0.5 + delta_r)]) h2_disp.single_point(method) delta_energy = h2_disp.energy - energy # Ha] grad = delta_energy / delta_r # Ha A^-1 # Difference between the absolute and finite difference approximation assert np.abs(gradients[1, 0] - grad) < 1E-1 # Broken gradient file grad_calc.output.filename = 'h2_grad_broken.out' grad_calc.output.file_lines = open('h2_grad_broken.out', 'r').readlines() with pytest.raises(CouldNotGetProperty): _ = grad_calc.get_gradients()
def test_get_ideal_bond_length_matrix(): atoms = [Atom('H', 0.0, 0.0, 0.0), Atom('H', 1.0, 0.0, 0.0)] bond_list = [(0, 1)] matrix = bonds.get_ideal_bond_length_matrix(atoms=atoms, bonds=bond_list) assert matrix.shape == (2, 2) assert 0.5 < matrix[0, 1] < 1.0 # H-H ~ 0.7 Å
def test_graph_without_active_edges(): mol = Molecule(name='H2', atoms=[Atom('H'), Atom('H', x=0.7)]) mol.graph.edges[(0, 1)]['active'] = True graph = mol_graphs.get_graph_no_active_edges(mol.graph) # Should now have no edges if the one bond was defined as active assert graph.number_of_edges() == 0
def test_bad_balance(): hh_product = reaction.Product(name='hh', atoms=[Atom('H'), Atom('H', x=1.0)]) with pytest.raises(UnbalancedReaction): reaction.Reaction(h1, hh_product) h_minus = reaction.Reactant(name='h1_minus', atoms=[Atom('H')], charge=-1) with pytest.raises(UnbalancedReaction): reaction.Reaction(h1, h_minus, hh_product) h1_water = reaction.Reactant(name='h1', atoms=[Atom('H')], solvent_name='water') h2_water = reaction.Reactant(name='h2', atoms=[Atom('H', x=1.0)], solvent_name='water') hh_thf = reaction.Product(name='hh', atoms=[Atom('H'), Atom('H', x=1.0)], solvent_name='thf') with pytest.raises(SolventsDontMatch): reaction.Reaction(h1_water, h2_water, hh_thf) with pytest.raises(NotImplementedError): hh_triplet = reaction.Product(name='hh_trip', atoms=[Atom('H'), Atom('H', x=0.7)], mult=3) reaction.Reaction(h1, h2, hh_triplet)
def test_add_solvent_mols(): species = SolvatedMolecule(atoms=[Atom('C', 0.0, 0.0, 0.0)]) species.solvent_mol = SolvatedMolecule(atoms=[Atom('H'), Atom('O', 0.7)]) explicit_solvent.add_solvent_molecules(species, 5, 10) assert len(species.qm_solvent_atoms) == 10 assert len(species.mm_solvent_atoms) == 10 all_atoms = species.qm_solvent_atoms + species.mm_solvent_atoms assert all(0.699 < np.linalg.norm(all_atoms[i * 2].coord - all_atoms[i * 2 + 1].coord) < 0.701 for i in range(10))
def test_gradients(): os.chdir(os.path.join(here, 'data', 'xtb')) h2 = Molecule(name='h2', atoms=[Atom('H'), Atom('H', x=1.0)]) h2.single_point(method) delta_r = 1E-5 h2_disp = Molecule(name='h2_disp', atoms=[Atom('H'), Atom('H', x=1.0 + delta_r)]) h2_disp.single_point(method) delta_energy = h2_disp.energy - h2.energy # Ha grad = delta_energy / delta_r # Ha A^-1 calc = Calculation(name='h2_grad', molecule=h2, method=method, keywords=method.keywords.grad) calc.run() diff = calc.get_gradients()[1, 0] - grad # Ha A^-1 # Difference between the absolute and finite difference approximation assert np.abs(diff) < 1E-5 # Older xtb version with open('gradient', 'w') as gradient_file: print( '$gradient\n' 'cycle = 1 SCF energy = -4.17404780397 |dE/dxyz| = 0.027866\n' '3.63797523123375 -1.13138130908142 -0.00032759661848 C \n' '5.72449332438353 -1.13197561185651 0.00028950521969 H \n' ' 2.94133258016711 0.22776472016180 -1.42078243039077 H \n' ' 2.94175598539510 -0.58111835182372 1.88747566982948 H \n' '2.94180792167968 -3.04156357656436 -0.46665514803992 H \n' '-1.7221823521705E-05 7.9930724499610E-05 -1.1737079840097E-04\n' ' 1.4116296505865E-02 -4.0359524399270E-05 3.9719638516747E-05\n' '-4.7199424681741E-03 9.0086220034949E-03 -9.4114548523723E-03\n' '-4.6956970257351E-03 3.6356853660431E-03 1.2558467871909E-02\n' ' -4.6834351884340E-03 -1.2683878569638E-02 -3.0693618596526E-03\n' '$end', file=gradient_file) calc = Calculation(name='methane', molecule=Molecule(name='methane', smiles='C'), method=method, keywords=method.keywords.grad) gradients = method.get_gradients(calc) assert gradients.shape == (5, 3) assert np.abs(gradients[0, 0]) < 1E-3 os.chdir(here)
def test_unavail_properties(): ha = reaction.Reactant(name='ha', atoms=[Atom('H')]) hb = reaction.Product(name='hb', atoms=[Atom('H')]) rxn = reaction.Reaction(ha, hb) delta = reaction.calc_delta_with_cont(left=[ha], right=[hb], cont='h_cont') assert delta is None # Should not raise an exception(?) rxn.find_lowest_energy_ts_conformer() rxn.calculate_thermochemical_cont(free_energy=False, enthalpy=False)
def test_truncated_active_graph(): h_c = Atom(atomic_symbol='H', x=0.0, y=0.0, z=1.4) h_d = Atom(atomic_symbol='H', x=0.0, y=0.0, z=2.1) ts = Species(name='template', charge=0, mult=1, atoms=[h_a, h_b, h_c, h_d]) mol_graphs.make_graph(species=ts, allow_invalid_valancies=True) # H--active--H--H--H should truncate by keeping only the nearest neighbours to the first two atoms truncated_graph = mol_graphs.get_truncated_active_mol_graph(ts.graph, active_bonds=[(0, 1)]) assert truncated_graph.number_of_nodes() == 3 assert truncated_graph.number_of_edges() == 2
def test_bad_geometry(): # Calculation with the wrong spin state should fail calc = Calculation(name='h2_overlap_opt', molecule=Molecule(atoms=[Atom('H'), Atom('H')]), method=method, keywords=Config.MOPAC.keywords.opt) calc.output.filename = 'h2_overlap_opt_mopac.out' calc.output.file_lines = open(calc.output.filename, 'r').readlines() assert not calc.terminated_normally() assert calc.get_energy() is None assert not calc.optimisation_converged()
def test_shifted_atoms(): atoms = [Atom('H', 0.0, 0.0, 0.0), Atom('H', 0.0, 0.0, 2.0)] new_atoms = geom.get_atoms_linear_interp(atoms, bonds=[(0, 1)], final_distances=[1.0]) # Linear interpolation of the coordinates should move the atom either end of the bond half way assert np.linalg.norm(new_atoms[0].coord - np.array([0.0, 0.0, 0.5])) < 1E-6 assert np.linalg.norm(new_atoms[1].coord - np.array([0.0, 0.0, 1.5])) < 1E-6
def test_reaction_class(): h1 = reaction.Reactant(name='h1', atoms=[Atom('H', 0.0, 0.0, 0.0)]) hh_product = reaction.Product(name='hh', atoms=[Atom('H', 0.0, 0.0, 0.0), Atom('H', 0.7, 0.0, 0.0)]) # h + h > mol hh_reac = reaction.Reaction(h1, h2, hh_product, name='h2_assoc') h1.energy = 2 h2.energy = 3 hh_product.energy = 1 # Only swap to dissociation in invoking locate_ts() assert hh_reac.type == reaction_types.Addition assert len(hh_reac.prods) == 1 assert len(hh_reac.reacs) == 2 assert hh_reac.ts is None assert hh_reac.tss is None assert hh_reac.name == 'h2_assoc' assert hh_reac.calc_delta_e() == -4 h1 = reaction.Reactant(name='h1', atoms=[Atom('H')]) hh_reactant = reaction.Reactant(name='hh', atoms=[Atom('H'), Atom('H', x=1.0)]) hh_product = reaction.Product(name='hh', atoms=[Atom('H'), Atom('H', x=1.0)]) # h + mol > mol + h h_sub = reaction.Reaction(h1, hh_reactant, h2_product, hh_product, solvent_name='water') assert h_sub.type == reaction_types.Substitution assert h_sub.name == 'reaction' assert h_sub.solvent.name == 'water' assert h_sub.solvent.smiles == 'O'
def test_get_bond_rearrangs(): if os.path.exists('test_bond_rearrangs.txt'): os.remove('test_bond_rearrangs.txt') # ethane --> Ch3 + Ch3 reac = Molecule(smiles='CC') prod = Molecule(atoms=[Atom('C', -8.3, 1.4, 0.0), Atom('C', 12, 1.7, -0.0), Atom('H', -8.6, 0.5, -0.5), Atom('H', -8.6, 2.3, -0.4), Atom('H', -8.6, 1.3, 1), Atom('H', 12.3, 1.7, -1.0), Atom('H', 12.4, 0.8, 0.4), Atom('H', 12.3, 2.5, 0.5)]) assert br.get_bond_rearrangs(ReactantComplex(reac), ProductComplex(prod), name='test') == [br.BondRearrangement(breaking_bonds=[(0, 1)])] # Rerunning the get function should read test_bond_rearrangs.txt, so modify it, swapping 0 and 1 in the breaking # bond then reopen with open('test_bond_rearrangs.txt', 'w') as rearr_file: print('fbond\n' 'bbonds\n' '1 0\n' 'end', file=rearr_file) rearr = br.get_bond_rearrangs(ReactantComplex(reac), ProductComplex(prod), name='test')[0] assert rearr == BondRearrangement(breaking_bonds=[(1, 0)]) assert br.get_bond_rearrangs(ReactantComplex(prod), ProductComplex(reac), name='test2') is None # If reactants and products are identical then the rearrangement is undetermined assert br.get_bond_rearrangs(ReactantComplex(reac), ProductComplex(reac), name='test3') is None os.remove('test_bond_rearrangs.txt')
def test_subgraph_isomorphism(): h_c = Atom(atomic_symbol='H', x=0.0, y=0.0, z=1.4) h_d = Atom(atomic_symbol='H', x=0.0, y=0.0, z=2.1) h4 = Species(name='H4', atoms=[h_a, h_b, h_c, h_d], charge=0, mult=1) mol_graphs.make_graph(h4) assert mol_graphs.is_subgraph_isomorphic(larger_graph=h4.graph, smaller_graph=h2.graph) is True # H3 in a triangular arrangement should not be sub-graph isomorphic to linear H4 h_e = Atom(atomic_symbol='H', x=0.3, y=0.0, z=0.3) h3 = Species(name='H_H', charge=0, mult=1, atoms=[h_a, h_b, h_e]) mol_graphs.make_graph(h3, allow_invalid_valancies=True) assert mol_graphs.is_subgraph_isomorphic(larger_graph=h4.graph, smaller_graph=h3.graph) is False
def test_atom(): h = Atom(atomic_symbol='H', x=0.0, y=0.0, z=0.0) assert h.label == 'H' assert type(h.coord) == np.ndarray assert len(h.coord) == 3 assert h.coord[0] == 0 assert h.coord[1] == 0 assert h.coord[2] == 0 # Translate the H atom by 1 A in the z direction h.translate(vec=np.array([0.0, 0.0, 1.0])) assert np.linalg.norm(h.coord - np.array([0.0, 0.0, 1.0])) < 1E-6 # Rotate the atom 180° (pi radians) in the x axis h.rotate(axis=np.array([1.0, 0.0, 0.0]), theta=np.pi) assert np.linalg.norm(h.coord - np.array([0.0, 0.0, -1.0])) < 1E-6 # Perform a rotation about a different origin e.g. (1, 0, -1) h.rotate(axis=np.array([0.0, 0.0, 1.0]), theta=np.pi, origin=np.array([1.0, 0.0, -1.0])) assert np.linalg.norm(h.coord - np.array([2.0, 0.0, -1.0])) < 1E-6 # Ensure that the atoms has a string representation assert len(str(h)) > 0
def test_n_membered_rings(): h2o = Molecule(atoms=[Atom('O'), Atom('H', x=-1), Atom('H', x=1)]) bond_rearr = BondRearrangement(forming_bonds=[(1, 2)]) # Forming bond over H-H should give a single 3-membered ring assert bond_rearr.n_membered_rings(h2o) == [3] bond_rearr = BondRearrangement(breaking_bonds=[(0, 1)]) assert bond_rearr.n_membered_rings(h2o) == [] # Breaking an O-H and forming a H-H should not make any rings bond_rearr = BondRearrangement(breaking_bonds=[(0, 2)], forming_bonds=[(1, 2)]) assert bond_rearr.n_membered_rings(h2o) == [3]
def test_reaction_identical_reac_prods(): os.chdir(os.path.join(here, 'data')) hh_reactant = reaction.Reactant(name='hh', atoms=[Atom('H'), Atom('H', x=1.0)]) hh_product = reaction.Product(name='hh', atoms=[Atom('H'), Atom('H', x=1.0)]) h2_reaction = reaction.Reaction(hh_reactant, hh_product) h2_reaction.locate_transition_state() assert h2_reaction.ts is None shutil.rmtree('transition_states') os.chdir(here)
def get_final_atoms(self, calc): atoms = [] optimized = False section = False for line in calc.output.file_lines: if 'Optimization is complete!' in line: optimized = True continue if optimized and 'Cartesian geometry (in Angstrom)' in line: section = True continue if optimized and section and len(line.split()) == 4: try: atom_label, x, y, z = line.split() atoms.append(Atom(atom_label, x=x, y=y, z=z)) except ValueError: pass if optimized and 'Saving final (previous) structure.' in line: return atoms logger.info('Could not get the optimised geometry from psi4.') return AtomsNotFound
def add_atom(self, atom_string): """Given an string starting with an atom label (e.g Cl), add the atom and return the rest of the string Arguments: atom_string (str): string starting with an atom label Returns: (str): rest of the string, some details about the atom """ if self.atom_no != 0: self.bonds.append((self.prev_atom_no, self.atom_no)) if atom_string[:2] in atoms_and_electrons.keys(): label = atom_string[:2] rest_of_string = atom_string[2:] else: label = atom_string[0] rest_of_string = atom_string[1:] self.atoms.append(Atom(label, 0.0, 0.0, 0.0)) self.prev_atom_no = self.atom_no self.atom_no += 1 return rest_of_string
def atoms_from_rdkit_mol(rdkit_mol_obj, conf_id): """Generate atoms for conformers in rdkit_mol_obj Arguments: rdkit_mol_obj (rdkit.Chem.Mol): RDKit molecule conf_id (int): Conformer id to convert to atoms Returns: (list(autode.atoms.Atom)): Atoms """ mol_block_lines = Chem.MolToMolBlock(rdkit_mol_obj, confId=conf_id).split('\n') mol_file_atoms = [] # Extract atoms from the mol block for line in mol_block_lines: split_line = line.split() if len(split_line) == 16: atom_label = split_line[3] x, y, z = split_line[0], split_line[1], split_line[2] mol_file_atoms.append(Atom(atom_label, x=x, y=y, z=z)) return mol_file_atoms
def test_not_isomorphic2(): c = Atom(atomic_symbol='C', x=0.0, y=0.0, z=0.7) ch = Species(name='ch', atoms=[h_a, c], charge=0, mult=2) mol_graphs.make_graph(ch) assert mol_graphs.is_isomorphic(h2.graph, ch.graph) is False
def test_contains_peak(): species_list = [] for i in range(5): h2 = Species(name='h2', charge=0, mult=2, atoms=[Atom('H'), Atom('H', x=0)]) h2.energy = i species_list.append(h2) assert not neb.contains_peak(species_list) species_list[2].energy = 5 assert neb.contains_peak(species_list)