示例#1
0
def extract_core(pdb_path, chain, pdb_list, expected_seq):

    # check whether pdb numbering contains any negative numbers
    #if not len([i for i in pdb_list if i.lstrip("-").isnumeric() and int(i)<0])==0:
    #	warnings.warn("Atomium cannot handle negative values of residue ids!\nBe sure that you know what you're doing!\nhttps://github.com/samirelanduk/atomium/issues/29")

    # extract
    sel_res = [f"{chain}.{i}" for i in pdb_list]
    s = atomium.open(pdb_path)
    res = [i for i in s.model.residues() if i.id in sel_res]
    mymodel = atomium.Model(*[atomium.Chain(*res, id=chain)])

    # save
    out_file_name = pdb_path.rstrip('.pdb') + '.core.pdb'
    mymodel.save(out_file_name)

    # check
    core_seq = "".join([mymodel.residue(i).code for i in sel_res])
    tmp = solveX(core_seq, expected_seq)
    assert tmp[
        1] == expected_seq, f'failed to save the core from {pdb_path}\n{core_seq}\n{expected_seq}'
    return out_file_name
示例#2
0
    def test_simple_model_with_atoms(self):
        # Make model
        model = atomium.Model(*self.atoms)

        # Atoms are fine
        self.assertEqual(model.atoms(), set(self.atoms))
        for atom in self.atoms:
            self.assertIn(atom, model)
            self.assertIs(atom.model, model)
        model.remove(self.atoms[0])
        self.assertIsNone(self.atoms[0].model)
        self.assertNotIn(self.atoms[0], model)
        self.assertEqual(model.atoms(), set(self.atoms[1:]))
        model.add(self.atoms[0])
        self.assertIs(self.atoms[0].model, model)
        self.assertIn(self.atoms[0], model)
        self.assertEqual(model.atoms(), set(self.atoms))
        pairs = list(model.pairwise_atoms())
        for atom1 in self.atoms:
            for atom2 in self.atoms:
                if atom1 is not atom2:
                    self.assertIn({atom1, atom2}, pairs)
        self.assertEqual(model.atoms(element="FE"),
                         {self.atoms[-3], self.atoms[-1]})
        self.assertEqual(model.atoms(name="CA"),
                         {self.atoms[1], self.atoms[10], self.atoms[19]})
        self.assertEqual(model.atoms(is_metal=True),
                         {self.atoms[-3], self.atoms[-1]})
        self.assertEqual(model.atoms(is_metal=False),
                         set(self.atoms) - {self.atoms[-3], self.atoms[-1]})
        self.assertEqual(model.atoms(element__ne="H"),
                         set(self.atoms) - {self.atoms[16], self.atoms[17]})

        # Geometry is fine
        self.assertEqual(self.atoms[0].angle(self.atoms[1], self.atoms[3]),
                         math.pi / 2)
        self.assertEqual(self.atoms[0].angle(self.atoms[1], self.atoms[2]), 0)
        self.assertAlmostEqual(self.atoms[0].angle(self.atoms[1],
                                                   self.atoms[4]),
                               math.pi / 4,
                               delta=0.00001)
        self.assertAlmostEqual(self.atoms[8].angle(self.atoms[7],
                                                   self.atoms[4]),
                               math.pi / 4,
                               delta=0.00001)

        # Transformation is fine
        model.translate(12, 13, 14)
        self.assertEqual(self.atoms[0].location, (12, 13, 14))
        self.assertEqual(self.atoms[2].location, (14, 13, 14))
        model.translate(-1, 1.5, 9)
        self.assertEqual(self.atoms[0].location, (11, 14.5, 23))
        self.assertEqual(self.atoms[2].location, (13, 14.5, 23))
        model.translate(-11, -14.5, -23)
        self.assertEqual(self.atoms[0].location, (0, 0, 0))
        self.assertEqual(self.atoms[2].location, (2, 0, 0))
        model.transform([[0.985, 0, 0.174], [0, 1, 0], [-0.174, 0, 0.985]],
                        trim=2)
        self.assertEqual(self.atoms[0].location, (0, 0, 0))
        self.assertEqual(self.atoms[2].location, (1.97, 0, -0.35))
        model.rotate(math.pi * 2, "y", trim=2)
        self.assertEqual(self.atoms[0].location, (0, 0, 0))
        self.assertEqual(self.atoms[2].location, (1.97, 0, -0.35))

        # Structure calculated properties
        self.assertAlmostEqual(model.mass, 479, delta=0.5)
        self.assertAlmostEqual(model.charge, 0.6, delta=0.5)
        self.assertEqual(model.formula, {
            "C": 11,
            "N": 3,
            "O": 6,
            "FE": 2,
            "H": 2,
            "S": 2,
            "P": 1
        })
        self.assertAlmostEqual(model.center_of_mass[0], 1.1017, delta=0.005)
        self.assertAlmostEqual(model.center_of_mass[1], 1.2479, delta=0.005)
        self.assertAlmostEqual(model.center_of_mass[2], 0.9213, delta=0.005)
        self.assertAlmostEqual(model.radius_of_gyration, 1.4413, delta=0.005)

        # Can get nearby atoms
        self.assertEqual(self.atoms[0].nearby_atoms(1),
                         {self.atoms[1], self.atoms[3], self.atoms[9]})
        self.assertEqual(self.atoms[0].nearby_atoms(1, element="C"),
                         {self.atoms[1]})

        # Model copying
        copy = model.copy()
        self.assertIsNot(model, copy)
        self.assertEqual(len(copy.atoms()), 27)
        self.assertFalse(model.atoms() & copy.atoms())
示例#3
0
    def test_complex_model_with_structures(self):
        # Make ligands
        ligand1 = atomium.Ligand(*self.atoms[-9:-6], id="A100", name="BIF")
        ligand2 = atomium.Ligand(*self.atoms[-6:-3], id="B100", name="FOZ")
        ligand3 = atomium.Ligand(*self.atoms[-3:], id="C100", name="XMP")

        # Make residues
        res1 = atomium.Residue(*self.atoms[:2], id="A1", name="MET")
        res2 = atomium.Residue(*self.atoms[2:4], id="A2", name="TYR")
        res3 = atomium.Residue(*self.atoms[4:6], id="A3", name="VAL")
        res4 = atomium.Residue(*self.atoms[6:8], id="B1", name="HIS")
        res5 = atomium.Residue(*self.atoms[8:10], id="B2", name="HIS")
        res6 = atomium.Residue(*self.atoms[10:12], id="B3", name="PRO")
        res7 = atomium.Residue(*self.atoms[12:14], id="C1", name="LEU")
        res8 = atomium.Residue(*self.atoms[14:16], id="C2", name="ILE")
        res9 = atomium.Residue(*self.atoms[16:18], id="C3", name="SER")

        # Make chains and put in model
        res1.next, res2.next = res2, res3
        res4.next, res5.next = res5, res6
        res7.next, res8.next = res8, res9
        chaina = atomium.Chain(res1, res2, res3, ligand1, id="A")
        chainb = atomium.Chain(res4, res5, res6, ligand2, id="B")
        chainc = atomium.Chain(res7, res8, res9, ligand3, id="C", rep="AALIH")
        model = atomium.Model(chaina, chainb, chainc)

        # The model is fine
        self.assertEqual(model.atoms(), set(self.atoms))
        self.assertEqual(model.atoms(name_regex="(F|P2)"),
                         {self.atoms[-3], self.atoms[-1], self.atoms[17]})
        self.assertEqual(model.ligands(), {ligand1, ligand2, ligand3})
        self.assertEqual(model.ligand(name="BIF"), ligand1)
        self.assertEqual(
            model.residues(),
            {res1, res2, res3, res4, res5, res6, res7, res8, res9})
        self.assertEqual(model.residues(name="HIS"), {res4, res5})
        self.assertEqual(model.residues(name_regex="HIS|MET"),
                         {res1, res4, res5})
        self.assertEqual(model.residue(id="C2"), res8)
        self.assertEqual(model.chains(), {chaina, chainb, chainc})
        self.assertEqual(model.chain("A"), chaina)
        self.assertEqual(model.chain("B"), chainb)
        self.assertEqual(model.chains(id_regex="A|B"), {chaina, chainb})
        for atom in self.atoms:
            self.assertIs(atom.model, model)
        self.assertIs(ligand1.model, model)
        self.assertIs(res6.model, model)
        self.assertIs(chainc.model, model)

        # The chains are fine
        self.assertEqual(chaina.atoms(),
                         set(self.atoms[:6] + self.atoms[-9:-6]))
        self.assertEqual(chainb.atoms(),
                         set(self.atoms[6:12] + self.atoms[-6:-3]))
        self.assertEqual(chainc.atoms(),
                         set(self.atoms[12:18] + self.atoms[-3:]))
        self.assertEqual(chaina.residues(), (res1, res2, res3))
        self.assertEqual(chainb.ligands(), {ligand2})
        for atom in self.atoms[:6] + self.atoms[-9:-6]:
            self.assertIs(atom.chain, chaina)
        for atom in self.atoms[12:18] + self.atoms[-3:]:
            self.assertIs(atom.chain, chainc)
        self.assertIs(res2.chain, chaina)
        self.assertIs(ligand1.chain, chaina)
        self.assertEqual(chaina.formula, {"C": 5, "O": 2, "N": 2})

        # The residues are fine
        self.assertEqual(res1.atoms(), set(self.atoms[:2]))
        self.assertEqual(res5.atoms(), set(self.atoms[8:10]))
        self.assertIs(self.atoms[10].residue, res6)

        # The ligands are fine
        self.assertEqual(ligand1.atoms(), set(self.atoms[-9:-6]))
        self.assertEqual(ligand2.atoms(), set(self.atoms[-6:-3]))
        self.assertEqual(ligand3.atoms(), set(self.atoms[-3:]))
        self.assertIs(self.atoms[-9].ligand, ligand1)
        self.assertIs(self.atoms[-5].ligand, ligand2)
        self.assertIs(self.atoms[-2].ligand, ligand3)
        self.assertIs(self.atoms[0].ligand, None)

        # The properties can be messed with
        res9.name = "HIS"
        self.assertEqual(model.residues(name="HIS"), {res4, res5, res9})
        self.assertEqual(chainb.residues(name="HIS"), (res4, res5))
        self.assertEqual(chaina.residues(name="HIS"), ())

        # Membership is fine
        self.assertIn(chaina, model)
        self.assertIn(ligand2, chainb)
        self.assertIn(res1, model)
        self.assertNotIn(res1, chainb)
        self.assertNotIn(res9, chaina)
        self.assertNotIn(res9, chainb)

        # Stuff can be added and removed
        chaina.remove(res2)
        self.assertIsNone(res2.chain)
        self.assertEqual(chaina.residues(), (res1, res3))
        chainb.add(ligand3)
        self.assertEqual(chainb.ligands(), {ligand3, ligand2})
        self.assertIs(ligand3.chain, chainb)

        # Specific properties - residues
        self.assertIs(res1.next, res2)
        self.assertIs(res2.next, res3)
        self.assertIsNone(res3.next)
        self.assertIs(res3.previous, res2)
        self.assertIs(res2.previous, res1)
        self.assertIsNone(res1.previous)
        self.assertEqual(res1.code, "M")
        self.assertEqual(res1.full_name, "methionine")
        res1.name = "SPLO"
        self.assertEqual(res1.code, "X")
        self.assertEqual(res1.full_name, "SPLO")

        # Specific properties - chains
        self.assertEqual(len(chainc), 3)
        self.assertEqual(chainc.length, 3)
        self.assertEqual(chainc[1], res8)
        self.assertEqual(list(chainc), [res7, res8, res9])
        for res in chainc:
            pass
        self.assertEqual(chainc.sequence, "LIH")
        self.assertEqual(chainc.rep_sequence, "AALIH")

        # Atom Nearby residues
        self.assertEqual(self.atoms[0].nearby_residues(1), {res1, res2, res5})
        self.assertEqual(self.atoms[0].nearby_residues(2),
                         {res1, res2, res3, res4, res5, res6, res7})
        self.assertEqual(self.atoms[0].nearby_residues(2, ligands=True),
                         {res1, res2, res3, res4, res5, res6, res7, ligand1})

        # Structure Nearby residues
        self.assertEqual(res1.nearby_residues(1), {res2, res3, res5, res6})
        self.assertEqual(res1.nearby_residues(1, element="C"),
                         {res2, res3, res6})

        # Model grid
        self.assertEqual(list(model.grid()), [(x, y, z) for x in range(3)
                                              for y in range(3)
                                              for z in range(3)])
        model.translate(-0.1, -0.1, -0.1)
        self.assertEqual(list(model.grid()), [(x, y, z) for x in range(-1, 3)
                                              for y in range(-1, 3)
                                              for z in range(-1, 3)])
        model.translate(-0.8, -0.8, -0.8)
        self.assertEqual(list(model.grid()), [(x, y, z) for x in range(-1, 3)
                                              for y in range(-1, 3)
                                              for z in range(-1, 3)])
        model.translate(1, 1, 1)
        self.assertEqual(list(model.grid()), [(x, y, z) for x in range(4)
                                              for y in range(4)
                                              for z in range(4)])
        model.translate(-0.1, -0.1, -0.1)
        self.assertEqual(list(model.grid(size=0.5)),
                         [(x, y, z) for x in [0, 0.5, 1, 1.5, 2]
                          for y in [0, 0.5, 1, 1.5, 2]
                          for z in [0, 0.5, 1, 1.5, 2]])
        self.assertEqual(
            list(model.grid(size=0.5, margin=1.5)),
            [(x, y, z)
             for x in [-1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5]
             for y in [-1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5]
             for z in [-1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5]])
        self.atoms[0].translate(-0.1, -0.5, -0.6)
        self.atoms[-1].translate(0.1, 0.5, 0.6)
        self.assertEqual(
            list(model.grid(size=0.25)),
            [(x, y, z)
             for x in [-0.25, 0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25]
             for y in [
                 -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25,
                 2.5
             ] for z in [
                 -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2,
                 2.25, 2.5, 2.75
             ]])
        self.atoms[0].translate(0.1, 0.5, 0.6)
        self.atoms[-1].translate(-0.1, -0.5, -0.6)
        self.assertEqual(list(model.grid(size=5)), [(x, y, z) for x in [0, 5]
                                                    for y in [0, 5]
                                                    for z in [0, 5]])

        # Molecule interactions
        self.assertEqual(
            ligand1.pairing_with(ligand2), {
                self.atoms[19]: self.atoms[22],
                self.atoms[20]: self.atoms[21],
                self.atoms[18]: self.atoms[23],
            })
        self.atoms[18].element, self.atoms[21].element = "NN"
        self.atoms[19].element, self.atoms[22].element = "OO"
        self.atoms[20].element, self.atoms[23].element = "PP"
        self.assertEqual(
            ligand1.pairing_with(ligand2), {
                self.atoms[18]: self.atoms[21],
                self.atoms[19]: self.atoms[22],
                self.atoms[20]: self.atoms[23],
            })
        ligand1.superimpose_onto(ligand2)
        self.assertEqual(self.atoms[18].location, self.atoms[21].location)
        self.assertEqual(self.atoms[19].location, self.atoms[22].location)
        self.assertEqual(self.atoms[20].location, self.atoms[23].location)
        self.assertEqual(ligand1.rmsd_with(ligand2), 0)
        self.atoms[18].move_to(0, 0, 0)
        self.atoms[19].move_to(0, 0, 1)
        self.atoms[20].move_to(0, 0, 2)
        self.assertAlmostEqual(ligand1.rmsd_with(ligand2), 2.08, delta=0.05)
        self.assertEqual(ligand1.rmsd_with(ligand2, superimpose=True), 0)
        self.assertEqual(self.atoms[18].location, (0, 0, 0))
        self.assertEqual(self.atoms[19].location, (0, 0, 1))
        self.assertEqual(self.atoms[20].location, (0, 0, 2))
        self.assertEqual(self.atoms[26].location, (2, 2, 2))
        self.assertEqual(self.atoms[17].location, (2, 2, 1))
        self.assertEqual(self.atoms[8].location, (2, 2, 0))
    def test_structure_processing(self):
        # Create five atoms of a residue
        atom1 = atomium.Atom("N", 0, 0, 0, 1, "N", 0.5, 0.5, [0] * 6)
        atom2 = atomium.Atom("C", 1.5, 0, 0, 2, "CA", 0, 0.4, [0] * 6)
        atom3 = atomium.Atom("C", 1.5, 1.5, 0, 3, "CB", 0, 0.3, [1] * 6)
        atom4 = atomium.Atom("C", 3, 0, 0, 4, "C", 0, 0.2, [0] * 6)
        atom5 = atomium.Atom("O", 3, -1.5, 0, 5, "O", 0, 0.1, [0] * 6)

        # Check basic atom properties
        self.assertEqual(atom2.element, "C")
        self.assertEqual(atom1.location, (0, 0, 0))
        self.assertEqual(tuple(atom1), (0, 0, 0))
        self.assertEqual(atom2.name, "CA")
        self.assertEqual(atom1.charge, 0.5)
        self.assertEqual(atom2.charge, 0)
        self.assertEqual(atom2.bvalue, 0.4)
        self.assertEqual(atom3.anisotropy, [1] * 6)

        # Check can update some properties
        atom1.name = "HG"
        atom1.charge = 200
        atom1.bvalue = 20
        self.assertEqual(atom1.name, "HG")
        self.assertEqual(atom1.charge, 200)
        self.assertEqual(atom1.bvalue, 20)
        atom1.name = "N"
        atom1.charge = 0.5
        atom1.bvalue = 0.5

        # Check atoms are not part of any higher structures
        for atom in (atom1, atom2, atom3, atom4, atom5):
            self.assertIsNone(atom.het)
            self.assertIsNone(atom.chain)
            self.assertIsNone(atom.model)
            self.assertEqual(atom.bonded_atoms, set())

        # Check atoms' calculated properties
        self.assertAlmostEqual(atom5.mass, 16, delta=0.05)
        self.assertEqual(atom1.covalent_radius, 0.71)
        for atom in (atom1, atom2, atom3, atom4, atom5):
            self.assertFalse(atom.is_metal)
            self.assertFalse(atom.is_backbone) # Not yet
            self.assertFalse(atom.is_side_chain) # Not yet

        # Check atom magic methods
        self.assertEqual(list(atom5), [3, -1.5, 0])
        for a1 in (atom1, atom2, atom3, atom4, atom5):
            for a2 in (atom1, atom2, atom3, atom4, atom5):
                if a1 is a2:
                    self.assertEqual(a1, a2)
                else:
                    self.assertNotEqual(a1, a2)

        # Check atom safe methods
        self.assertEqual(atom1.distance_to(atom2), 1.5)
        self.assertEqual(atom1.distance_to(atom3), 4.5 ** 0.5)
        self.assertEqual(atom2.angle(atom3, atom4), math.pi / 2)
        for atom in (atom1, atom2, atom3, atom4, atom5):
            self.assertEqual(atom.nearby_atoms(5), set()) # Not without model
            self.assertEqual(atom.nearby_hets(5), set()) # Ditto

        # Check atom side effect methods
        atom2.translate(0, 0, 1)
        self.assertEqual(atom2.location, (1.5, 0, 1))
        atom2.translate(0, 0, -1)
        self.assertEqual(atom2.location, (1.5, 0, 0))
        atom2.transform([[-1, 0, 0], [0, 1, 0], [0, 0, -1]])
        self.assertEqual(atom2.location, (-1.5, 0, 0))
        atom2.transform([[-1, 0, 0], [0, 1, 0], [0, 0, -1]])
        self.assertEqual(atom2.location, (1.5, 0, 0))
        atom2.rotate(math.pi / 2, "y")
        self.assertEqual(atom2.location, (0, 0, -1.5))
        atom2.rotate(math.pi * 1.5, "y")
        self.assertEqual(atom2.location, (1.5, 0, 0))
        atom2.move_to(10, 10, 10)
        self.assertEqual(atom2.location, (10, 10, 10))
        atom2.move_to(1.5, 0, 0)

        # Bond atoms
        atom1.bond(atom2)
        atom2.bond(atom3)
        atom2.bond(atom4)
        atom4.bond(atom5)
        self.assertEqual(atom1.bonded_atoms, {atom2})
        self.assertEqual(atom2.bonded_atoms, {atom1, atom3, atom4})
        self.assertEqual(atom3.bonded_atoms, {atom2})
        self.assertEqual(atom4.bonded_atoms, {atom2, atom5})
        self.assertEqual(atom5.bonded_atoms, {atom4})

        # Check can copy atom
        copy = atom2.copy()
        self.assertEqual(copy.element, "C")
        self.assertEqual(copy.location, (1.5, 0, 0))
        self.assertEqual(copy.name, "CA")
        self.assertEqual(copy.id, 2)
        self.assertEqual(copy.charge, 0)
        self.assertEqual(copy.charge, 0)
        self.assertEqual(copy.bvalue, 0.4)
        self.assertEqual(copy.anisotropy, [0] * 6)
        self.assertIsNone(copy.het)
        self.assertIsNone(copy.chain)
        self.assertIsNone(copy.model)
        self.assertEqual(copy.bonded_atoms, set())
        self.assertEqual(atom2, copy)

        # Can copy atom with new ID
        copy = atom2.copy(id=10000)
        self.assertEqual(copy.id, 10000)

        # Create residue
        res1 = atomium.Residue(
         atom1, atom2, atom3, atom4, atom5, id="A5", name="AL"
        )

        # Check basic residue properties
        self.assertEqual(res1.id, "A5")
        self.assertEqual(res1.name, "AL")
        self.assertEqual(res1.code, "X")
        res1.name = "ALA"
        self.assertEqual(res1.name, "ALA")
        self.assertEqual(res1.code, "A")
        self.assertIsNone(res1.next)
        self.assertIsNone(res1.previous)
        self.assertIsNone(res1.chain)

        # Check residue and atoms
        for atom in (atom1, atom2, atom3, atom4, atom5):
            self.assertIs(atom.het, res1)
        self.assertTrue(atom1.is_backbone)
        self.assertTrue(atom3.is_side_chain)
        self.assertEqual(res1.atoms(), {atom1, atom2, atom3, atom4, atom5})
        self.assertEqual(res1.atoms(element="C"), {atom2, atom3, atom4})
        self.assertEqual(res1.atoms(name="O"), {atom5})
        self.assertEqual(res1.atoms(is_backbone=True), {atom1, atom2, atom4, atom5})
        self.assertEqual(res1.atoms(mass__gt=13), {atom1, atom5})
        self.assertEqual(res1.atoms(name__regex="N|O"), {atom1, atom5})

        # Check residue is container
        self.assertIn(atom1, res1)
        self.assertIn(copy, res1)

        # Check residue calculated properties
        self.assertEqual(res1.code, "A")
        self.assertEqual(res1.full_name, "alanine")
        self.assertIsNone(res1.model)
        self.assertAlmostEqual(res1.mass, 66, delta=0.05)
        self.assertEqual(res1.charge, 0.5)
        self.assertEqual(res1.formula, {"C": 3, "O": 1, "N": 1})
        self.assertAlmostEqual(res1.center_of_mass[0], 1.818, delta=0.001)
        self.assertAlmostEqual(res1.center_of_mass[1], -0.091, delta=0.001)
        self.assertEqual(res1.center_of_mass[2], 0)
        self.assertAlmostEqual(res1.radius_of_gyration, 1.473, delta=0.001)

        # Check residue safe methods
        self.assertEqual(len(tuple(res1.pairwise_atoms())), 10)
        self.assertEqual(res1.nearby_hets(10), set())
        self.assertEqual(res1.nearby_atoms(10), set())
        self.assertEqual(res1.nearby_chains(10), set())
        self.assertEqual(
         tuple(res1.create_grid(size=3)),
         ((0, -3, 0), (0, 0, 0), (0, 3, 0), (3, -3, 0), (3, 0, 0), (3, 3, 0))
        )
        self.assertEqual(res1.atoms_in_sphere((1.5, 0, 0), 1.5), {atom2, atom1, atom3, atom4})
        self.assertEqual(res1.atoms_in_sphere((1.5, 0, 0), 1.5, element="C"), {atom2, atom3, atom4})
        res1.check_ids()
        self.assertFalse(res1.helix)
        self.assertFalse(res1.strand)

        # Check residue side effect methods
        res1.translate(0, 0, 1)
        self.assertEqual(atom2.location, (1.5, 0, 1))
        res1.translate(0, 0, -1)
        self.assertEqual(atom2.location, (1.5, 0, 0))
        res1.transform([[-1, 0, 0], [0, 1, 0], [0, 0, -1]])
        self.assertEqual(atom2.location, (-1.5, 0, 0))
        res1.transform([[-1, 0, 0], [0, 1, 0], [0, 0, -1]])
        self.assertEqual(atom2.location, (1.5, 0, 0))
        res1.rotate(math.pi / 2, "y")
        self.assertEqual(atom2.location, (0, 0, -1.5))
        res1.rotate(math.pi * 1.5, "y")
        self.assertEqual(atom2.location, (1.5, 0, 0))

        # Can make copy of residue
        res_copy = res1.copy()
        self.assertEqual(res1, res_copy)
        self.assertEqual(res_copy.id, "A5")
        self.assertEqual(res_copy.name, "ALA")
        self.assertEqual(res1.pairing_with(res_copy), {
         atom1: res_copy.atom(1), atom2: res_copy.atom(2),
         atom3: res_copy.atom(3), atom4: res_copy.atom(4),
         atom5: res_copy.atom(5)
        })
        self.assertEqual(len(res1.atoms() | res_copy.atoms()), 10)
        self.assertEqual(res1.rmsd_with(res_copy), 0)
        res_copy.atom(1).translate(1)
        self.assertAlmostEqual(res1.rmsd_with(res_copy), 0.4, delta=0.001)

        # Can make copy of residue with new IDs
        res_copy = res1.copy(id="C5", atom_ids=lambda i: i * 100)
        self.assertEqual(res_copy.id, "C5")
        self.assertEqual({a.id for a in res_copy.atoms()}, {100, 200, 300, 400, 500})

        # Make more residues
        atom6 = atomium.Atom("N", 4.5, 0, 0, 6, "N", 0, 0.5, [0] * 6)
        atom7 = atomium.Atom("C", 6, 0, 0, 7, "CA", 0, 0.5, [0] * 6)
        atom8 = atomium.Atom("C", 6, -1.5, 0, 8, "CB", 0, 0.5, [0] * 6)
        atom9 = atomium.Atom("S", 6, -3, 0, 9, "S", 0, 0.5, [0] * 6)
        atom10 = atomium.Atom("C", 7.5, 0, 0, 10, "C", 0, 0.5, [0] * 6)
        atom11 = atomium.Atom("O", 7.5, 1.5, 0, 11, "O", 0, 0.5, [0] * 6)
        atom12 = atomium.Atom("N", 9, 0, 0, 12, "CA", 0, 0.5, [0] * 6)
        atom13 = atomium.Atom("C", 10.5, 0, 0, 13, "CB", 0, 0.5, [0] * 6)
        atom14 = atomium.Atom("C", 10.5, 1.5, 0, 14, "OG", 0, 0.5, [0] * 6)
        atom15 = atomium.Atom("O", 10.5, 3, 0, 15, "C", 0, 0.5, [0] * 6)
        atom16 = atomium.Atom("C", 12, 0, 0, 16, "C", 0, 0.5, [0] * 6)
        atom17 = atomium.Atom("O", 13.5, 1.25, 0, 17, "OX1", 0, 0.5, [0] * 6)
        atom18 = atomium.Atom("O", 13.5, -1.25, 0, 18, "OX2", 0, 0.5, [0] * 6)
        atom6.bond(atom7)
        atom6.bond(atom4)
        atom7.bond(atom8)
        atom7.bond(atom10)
        atom8.bond(atom9)
        atom10.bond(atom11)
        atom10.bond(atom12)
        atom12.bond(atom13)
        atom13.bond(atom14)
        atom13.bond(atom16)
        atom14.bond(atom15)
        atom16.bond(atom17)
        atom16.bond(atom18)
        res2 = atomium.Residue(
         atom6, atom7, atom8, atom9, atom10, atom11, id="A5A", name="CYS"
        )
        res3 = atomium.Residue(
         atom12, atom13, atom14, atom15, atom16, atom17, atom18, id="A6", name="SER"
        )

        # Connect residues
        res1.next = res2
        res3.previous = res2
        self.assertIs(res1.next, res2)
        self.assertIs(res2.next, res3)
        self.assertIs(res3.previous, res2)
        self.assertIs(res2.previous, res1)

        # Create chain
        chain1 = atomium.Chain(
         res1, res2, res3, id="A", sequence="MACSD", helices=((res1, res2),), strands=((res3,),)
        )
        self.assertEqual(chain1.id, "A")
        self.assertEqual(chain1.internal_id, "A")
        self.assertIsNone(chain1.name)
        self.assertIsNone(chain1.model)

        # Chain properties
        self.assertEqual(chain1.sequence, "MACSD")
        chain1.sequence = "MACSDA"
        self.assertEqual(chain1.sequence, "MACSDA")
        self.assertEqual(chain1.present_sequence, "ACS")
        self.assertEqual(chain1.helices[0], (res1, res2))
        self.assertEqual(chain1.strands[0], (res3,))

        # Check chain residues and atoms
        self.assertEqual(chain1.residues(), (res1, res2, res3))
        self.assertEqual(chain1.residues(mass__gt=80), (res2, res3))
        self.assertEqual(chain1.residues(mass__lt=98.1), (res1, res3))
        self.assertEqual(chain1.atoms(), {
         atom1, atom2, atom3, atom4, atom5, atom6, atom7, atom8, atom9, atom10,
         atom11, atom12, atom13, atom14, atom15, atom16, atom17, atom18
        })
        self.assertEqual(chain1.atoms(het__name="ALA"), {
         atom1, atom2, atom3, atom4, atom5
        })
        self.assertEqual(chain1.atoms(het__name="CYS"), {
         atom6, atom7, atom8, atom9, atom10, atom11
        })
        self.assertEqual(chain1.atoms(het__name__regex="CYS|ALA"), {
         atom1, atom2, atom3, atom4, atom5, atom6, atom7, atom8, atom9, atom10, atom11
        })
        self.assertTrue(res1.helix)
        self.assertFalse(res1.strand)
        self.assertTrue(res2.helix)
        self.assertFalse(res2.strand)
        self.assertFalse(res3.helix)
        self.assertTrue(res3.strand)

        # Check chain magic methods
        self.assertEqual(chain1.length, 3)
        self.assertIs(chain1[0], res1)
        for res in chain1: self.assertIn(res, (res1, res2, res3))
        self.assertIn(res1, chain1)
        self.assertIn(atom10, chain1)

        # Check chain ligands and atoms
        self.assertEqual(chain1.ligands(), set())
        self.assertEqual(
         chain1.atoms(element__regex="O|S"),
         {atom5, atom9, atom11, atom15, atom17, atom18}
        )
        self.assertEqual(
         chain1.atoms(het__name="CYS"),
         {atom6, atom7, atom8, atom9, atom10, atom11}
        )

        # Make copy of chain
        chain2 = chain1.copy(
         id="B",
         residue_ids=lambda i: i.replace("A", "B"),
         atom_ids=lambda i: i * 100
        )
        self.assertEqual(chain1, chain2)
        self.assertEqual(chain2.id, "B")
        self.assertEqual(chain2.internal_id, "B")
        self.assertEqual([res.id for res in chain2], ["B5", "B5B", "B6"])
        self.assertEqual({a.id for a in chain2.atoms()}, set([x * 100 for x in range(1, 19)]))
        self.assertIs(chain2[0].next, chain2[1])
        self.assertIs(chain2[2].previous, chain2[1])
        self.assertEqual(chain2.helices, ((chain2[0], chain2[1]),))
        self.assertEqual(chain2.strands, ((chain2[2],),))

        # Move chain into place
        chain2.rotate(math.pi, "x")
        chain2.rotate(math.pi, "y")
        chain2.translate(12, -10.5)
        self.assertEqual(chain1.rmsd_with(chain2), 0)

        # Make ligand
        copper_atom = atomium.Atom("Cu", 6, -5.25, 2, 100, "Cu", 2, 0, [0] * 6)
        copper = atomium.Ligand(
         copper_atom, id="A100", internal_id="M", name="CU", chain=chain1, full_name="copper"
        )

        # Check ligand properties
        self.assertEqual(copper.id, "A100")
        self.assertEqual(copper.name, "CU")
        self.assertEqual(copper.full_name, "copper")
        copper.full_name = None
        self.assertEqual(copper.full_name, "CU")
        self.assertEqual(copper.internal_id, "M")
        self.assertIs(copper.chain, chain1)
        self.assertIsNone(copper.model)
        self.assertEqual(copper.atom(), copper_atom)
        self.assertIn(copper_atom, copper)
        self.assertFalse(copper.is_water)

        # Can make copy of ligand
        cu_copy = copper.copy()
        self.assertEqual(copper, cu_copy)
        self.assertEqual(cu_copy.id, "A100")
        self.assertEqual(cu_copy.name, "CU")
        self.assertEqual(len(cu_copy.atoms() | cu_copy.atoms()), 1)
        self.assertFalse(cu_copy.is_water)

        # Can make copy of ligand with new IDs
        cu_copy = copper.copy(id="C100", atom_ids=lambda i: i * 100)
        self.assertEqual(cu_copy.id, "C100")
        self.assertEqual(cu_copy.atom().id, 10000)

        # Create waters
        hoh1 = atomium.Ligand(
         atomium.Atom("O", 3, -3, 3, 500, "O", 0, 0, [0] * 6),
         id="A1000", name="HOH", water=True
        )
        self.assertTrue(hoh1.is_water)
        hoh2 = atomium.Ligand(
         atomium.Atom("O", 3, -9, -3, 500, "O", 0, 0, [0] * 6),
         id="B1000", name="HOH", water=True
        )
        self.assertTrue(hoh2.is_water)

        # Create model
        model = atomium.Model(chain1, chain2, copper, hoh1, hoh2)
        
        # Model properties
        self.assertIsNone(model.file)
        self.assertEqual(model.chains(), {chain1, chain2})
        self.assertEqual(model.ligands(), {copper})
        self.assertEqual(chain1.ligands(), {copper})
        self.assertEqual(model.waters(), {hoh1, hoh2})
        self.assertEqual(model.molecules(), {chain1, chain2, copper, hoh1, hoh2})
        self.assertEqual(model.residues(), set(chain1.residues() + chain2.residues()))
        self.assertEqual(model.residues(name="ALA"), {chain1[0], chain2[0]})
        self.assertEqual(model.ligand(), copper)
        self.assertEqual(model.atom(1), atom1)
        self.assertEqual(model.atom(name="N", het__name="ALA", chain__id="A"), atom1)

        # Everything points upwards correctly
        self.assertIs(atom1.model, model)
        self.assertIs(res1.model, model)
        self.assertIs(chain1.model, model)
        self.assertIs(copper.model, model)

        # Now that atoms are in a model, find nearby things
        self.assertEqual(atom2.nearby_atoms(1.5), {atom1, atom3, atom4})
        self.assertEqual(atom4.nearby_atoms(1.5), {atom2, atom5, atom6})
        self.assertEqual(atom4.nearby_atoms(1.5, het__name="CYS"), {atom6})
        self.assertEqual(atom4.nearby_hets(1.5), {res2})
        self.assertEqual(atom4.nearby_hets(9), {res2, res3, chain2[1], copper, hoh1})
        self.assertEqual(atom4.nearby_hets(9, ligands=False), {res2, res3, chain2[1]})
        self.assertEqual(atom4.nearby_hets(9, residues=False), {copper, hoh1})
        self.assertEqual(atom4.nearby_hets(9, residues=False, het__is_water=False), {copper})
        self.assertEqual(atom4.nearby_chains(9), {chain2})
        self.assertEqual(atom4.nearby_chains(9, chain__id="A"), set())
        self.assertEqual(res2.nearby_hets(3), {res1, res3})
        self.assertEqual(res2.nearby_hets(6), {res1, res3, hoh1, copper, chain2[1]})
        self.assertEqual(res2.nearby_hets(6, ligands=False), {res1, res3, chain2[1]})
        self.assertEqual(copper.nearby_chains(5), {chain2})
        self.assertEqual(chain2.nearby_chains(5), {chain1})

        # Dehydrate model
        model.dehydrate()
        self.assertEqual(model.waters(), set())
        self.assertEqual(model.ligands(), {copper})
        self.assertEqual(model.chains(), {chain1, chain2})