コード例 #1
0
ファイル: test_sym.py プロジェクト: dimar/gemmi
 def test_invert(self):
     for xyz in ['-y,-x,-z+1/4', 'y,-x,z+3/4', 'y,x,-z', 'y+1/2,x,-z+1/3']:
         op = gemmi.Op(xyz)
         self.assertEqual(op * op.inverse(), 'x,y,z')
         self.assertEqual(op.inverse().inverse(), op)
     op = gemmi.Op("-y+z,x+z,-x+y+z")  # det=3
     self.assertRaises(RuntimeError, op.inverse)
コード例 #2
0
 def test_combine(self):
     a = gemmi.Op('x+1/3,z,-y')
     self.assertEqual(a.combine(a).triplet(), 'x+2/3,-y,-z')
     self.assertEqual('x,-y,z' * gemmi.Op('-x,-y,z'), '-x,y,z')
     a = gemmi.Op('-y+1/4,x+3/4,z+1/4')
     b = gemmi.Op('-x+1/2,y,-z')
     self.assertEqual((a * b).triplet(), '-y+1/4,-x+1/4,-z+1/4')
     c = '-y,-z,-x'
     self.assertNotEqual(b * c, c * b)
     self.assertEqual((a * c).triplet(), 'z+1/4,-y+3/4,-x+1/4')
     self.assertEqual(b * c, gemmi.Op('y+1/2,-z,x'))
     self.assertEqual(c * b, '-y,z,x+1/2')
コード例 #3
0
def verify_hall_symbol(entry):
    if not gemmi:
        return
    hall_ops = gemmi.symops_from_hall(entry['hall'])
    assert len(hall_ops.sym_ops) == len(entry['symops'])
    assert len(hall_ops.cen_ops) == len(entry['cenops'])
    # centering vectors are exactly the same
    assert (set(gemmi.Op().translated(tr) for tr in hall_ops.cen_ops) ==
            set(gemmi.Op(e) for e in entry['cenops'])), entry
    # symops differ in some cases but are the same modulo centering vectors
    given = set(gemmi.Op(s) * c for s in entry['symops']
                for c in entry['cenops'])
    assert given == set(hall_ops), entry
コード例 #4
0
 def test_table(self):
     for sg in gemmi.spacegroup_table():
         if sg.ccp4 != 0:
             self.assertEqual(sg.ccp4 % 1000, sg.number)
         if sg.operations().is_centric():
             self.assertEqual(sg.laue_str(), sg.point_group_hm())
         else:
             self.assertNotEqual(sg.laue_str(), sg.point_group_hm())
         if sgtbx:
             hall = sg.hall.encode()
             cctbx_sg = sgtbx.space_group(hall)
             cctbx_info = sgtbx.space_group_info(group=cctbx_sg)
             self.assertEqual(sg.is_reference_setting(),
                              cctbx_info.is_reference_setting())
             #to_ref = cctbx_info.change_of_basis_op_to_reference_setting()
             #from_ref = '%s' % cob_to_ref.inverse().c()
             c2p_sg = gemmi.Op(cctbx_sg.z2p_op().c().inverse().as_xyz())
             self.assertEqual(sg.centred_to_primitive(), c2p_sg)
         ops = gemmi.get_spacegroup_reference_setting(sg.number).operations()
         ops.change_basis_forward(sg.basisop)
         self.assertEqual(ops, sg.operations())
     itb = gemmi.spacegroup_table_itb()
     if sgtbx:
         for s in sgtbx.space_group_symbol_iterator():
             self.assertEqual(s.hall().strip(), next(itb).hall)
         with self.assertRaises(StopIteration):
             next(itb)
コード例 #5
0
ファイル: ob_spacegroups.py プロジェクト: sizmailov/gemmi
def parse_chunk(lines):
    return {
        'number': int(lines[0]),
        'hall': lines[1].strip(),
        'xhm': lines[2].strip(),
        'symops': [gemmi.Op(line) for line in lines[3:]]
    }
コード例 #6
0
 def test_invert(self):
     for xyz in ['-y,-x,-z+1/4', 'y,-x,z+3/4', 'y,x,-z', 'y+1/2,x,-z+1/3']:
         op = gemmi.Op(xyz)
         self.assertEqual(op * op.inverse(), 'x,y,z')
         self.assertEqual(op.inverse().inverse(), op)
     # test change-of-basis op between hexagonal and trigonal settings
     op = gemmi.Op("-y+z,x+z,-x+y+z")  # det=3
     self.assertEqual(op.det_rot(), 3 * gemmi.Op.DEN**3)
     inv = op.inverse()
     self.assertEqual(inv * op, 'x,y,z')
     self.assertEqual(op * inv, 'x,y,z')
     expected_inv = '-1/3*x+2/3*y-1/3*z,-2/3*x+1/3*y+1/3*z,1/3*x+1/3*y+1/3*z'
     self.assertEqual(inv.triplet(), expected_inv)
     self.assertEqual(gemmi.Op(expected_inv), inv)
     op = gemmi.Op('1/2*x+1/2*y,-1/2*x+1/2*y,z')
     self.assertEqual(op.inverse().triplet(), 'x-y,x+y,z')
コード例 #7
0
 def _spgr(self) -> gemmi.SpaceGroup:
     if self.symmops:
         symm_ops = self.symmops
     else:
         symm_ops = self.symmops_from_spgr
     return gemmi.find_spacegroup_by_ops(
         gemmi.GroupOps([gemmi.Op(o) for o in symm_ops]))
コード例 #8
0
ファイル: test_sym.py プロジェクト: ConorFWild/gemmi
 def test_groupops(self):
     gops = gemmi.GroupOps([gemmi.Op(t) for t in ['x, y, z',
                                                  'x, -y, z+1/2',
                                                  'x+1/2, y+1/2, z',
                                                  'x+1/2, -y+1/2, z+1/2']])
     self.assertEqual(gops.find_centering(), 'C')
     self.assertEqual(len(gops), 4)
     self.assertEqual(gemmi.find_spacegroup_by_ops(gops).hm, 'C 1 c 1')
コード例 #9
0
 def test_triplet_roundtrip(self):
     singles = list(CANONICAL_SINGLES.keys())
     for i in range(4):
         items = [random.choice(singles) for j in range(3)]
         triplet = ','.join(items)
         op = gemmi.parse_triplet(triplet)
         self.assertEqual(op.triplet(), triplet)
     self.assertEqual(gemmi.Op(' x , - y, + z ').triplet(), 'x,-y,z')
コード例 #10
0
 def change_basis(self, name_a, name_b, basisop_triplet):
     basisop = gemmi.Op(basisop_triplet)
     a = gemmi.find_spacegroup_by_name(name_a)
     b = gemmi.find_spacegroup_by_name(name_b)
     ops = a.operations()
     ops.change_basis(basisop)
     self.assertEqual(ops, b.operations())
     ops.change_basis(basisop.inverse())
     self.assertEqual(ops, a.operations())
コード例 #11
0
 def test_invert(self):
     for xyz in ['-y,-x,-z+1/4', 'y,-x,z+3/4', 'y,x,-z', 'y+1/2,x,-z+1/3']:
         op = gemmi.Op(xyz)
         self.assertEqual(op * op.inverse(), 'x,y,z')
         self.assertEqual(op.inverse().inverse(), op)
     # test change-of-basis op between hexagonal and trigonal settings
     op = gemmi.Op("-y+z,x+z,-x+y+z")  # det=3
     self.assertEqual(op.det_rot(), 3 * gemmi.Op.DEN**3)
     inv = op.inverse()
     self.assertEqual(inv * op, 'x,y,z')
     self.assertEqual(op * inv, 'x,y,z')
     expected_inv = '-x/3+2/3*y-z/3,-2/3*x+y/3+z/3,x/3+y/3+z/3'
     self.assertEqual(inv.triplet(), expected_inv)
     self.assertEqual(gemmi.Op(expected_inv), inv)
     op = gemmi.Op('1/2*x+1/2*y,-1/2*x+1/2*y,z')
     self.assertEqual(op.inverse().triplet(), 'x-y,x+y,z')
     # check also alternative writing
     op2 = gemmi.Op('x/2+y/2,-a/2+k/2,z')
     self.assertEqual(op, op2)
コード例 #12
0
def main():
    syminfo = parse_syminfo(sys.argv[1])
    seen_nums = set()
    for entry in syminfo:
        ccp4 = entry['ccp4']
        hall = entry['hall']
        basisop = entry['basisop']
        num = entry['number']
        xhm = entry['xhm']
        if num not in seen_nums:
            seen_nums.add(num)
            assert ccp4 == num
            if basisop != 'x,y,z':
                print(num, xhm, basisop)
        assert ccp4 == 0 or ccp4 % 1000 == num
        if ccp4 == num:
            if basisop != 'x,y,z':
                pass  # print(ccp4, basisop)
        if basisop != 'x,y,z':
            if '(%s)' % basisop not in hall:
                print('Hall symbol "%s" w/o basisop: %s' % (hall, basisop))
        hall_ops = gemmi.symops_from_hall(hall)
        assert len(hall_ops.cen_ops) == len(entry['cenops'])
        assert (set(gemmi.Op().translated(tr)
                    for tr in hall_ops.cen_ops) == set(entry['cenops']))
        assert len(hall_ops.sym_ops) == len(entry['symops'])
        # symops differ in about dozen cases but are the same modulo
        # centering vectors
        generated = set(hall_ops)
        given = set(s * c for s in entry['symops'] for c in entry['cenops'])
        assert generated == given, entry
    print('OK. %d entries.' % len(syminfo))

    hall_ref = read_ref()
    xhm_set = set()
    for d in syminfo:
        xhm = d['xhm']
        if xhm and xhm in xhm_set:
            print('dup xHM:', xhm)
        xhm_set.add(xhm)
        ref = hall_ref.get(xhm)
        if ref is not None:
            assert d['number'] == ref[0]
            hall1 = d['hall']
            hall2 = ref[1]
            sym1 = gemmi.symops_from_hall(hall1)
            sym2 = gemmi.symops_from_hall(hall2)
            assert set(sym1) == set(sym2), (hall1, hall2)
        else:
            print('extra:', xhm, '  (%d)' % d['number'], d['hall'])
    missing = set(hall_ref.keys()) - xhm_set
    for d in sorted(missing, key=lambda m: hall_ref[m][0]):
        print('missing:', d, '  (%d)' % hall_ref[d][0])
コード例 #13
0
ファイル: test_unitcell.py プロジェクト: project-gemmi/gemmi
 def test_change_of_basis(self):
     uc = gemmi.UnitCell(20, 30, 39, 73, 93, 99)
     op = gemmi.Op('y-x/2,-2/3*z+2/3*y,3*x')
     uc2 = uc.changed_basis_backward(op, set_images=False)
     # compare with result from cctbx:
     #  from cctbx import sgtbx, uctbx
     #  u = uctbx.unit_cell((20,30,39, 73,93,99))
     #  op = sgtbx.change_of_basis_op('y-x/2,-2/3*z+2/3*y,3*x').inverse()
     #  print(u.change_basis(cb_op=op).parameters())
     expected = (117.9468784563987, 25.977921933207348, 20.0, 130.5,
                 107.65517573180257, 82.63132106791868)
     assert_almost_equal_seq(self, uc2.parameters, expected)
     uc3 = uc2.changed_basis_forward(op, set_images=True)
     assert_almost_equal_seq(self, uc3.parameters, uc.parameters)
コード例 #14
0
ファイル: phases.py プロジェクト: ianhi/reciprocalspaceship
def get_phase_restrictions(H, spacegroup):
    """
    Return phase restrictions for Miller indices in a given space group.

    If there are no phase restrictions, an empty list is returned for that
    Miller index. If a given Miller index is systematically absent an
    empty list is also returned. 

    Parameters
    ----------
    H : array
        n x 3 array of Miller indices
    spacegroup : gemmi.SpaceGroup
        Space group for determining phase restrictions

    Returns
    -------
    restrictions : list of lists
         List of lists of phase restrictions for each Miller index. An empty
         list is returned for Miller indices without phase restrictions
    """
    from reciprocalspaceship.utils.asu import is_absent, is_centric
    from reciprocalspaceship.utils.symop import apply_to_hkl, phase_shift
    friedel_op = gemmi.Op("-x,-y,-z")
    #Grabs all the non-identity symops
    ops = spacegroup.operations().sym_ops[1:]
    restrictions = [[]] * len(H)
    #This is the case for P1
    if len(ops) == 0:
        return restrictions

    #Phase restrictions only apply to centrics. We'll also ignore any absent refls
    mask = (is_centric(H, spacegroup)) & (~is_absent(H, spacegroup))
    idx = np.where(mask)[0]
    h = H[mask, :]
    hits = np.column_stack([
        np.all(apply_to_hkl(h, op) == apply_to_hkl(h, friedel_op), axis=1)
        for op in ops
    ])

    hits[np.cumsum(hits, axis=-1) > 1] = False  #Remove duplicate hits
    shifts = np.column_stack([np.rad2deg(phase_shift(h, op)) for op in ops])
    shifts = shifts[np.arange(len(hits)), hits.argmax(-1)]
    restriction = np.column_stack((shifts / 2., 180. + shifts / 2.))
    restriction = canonicalize_phases(restriction)
    restriction.sort(-1)
    for i, _ in np.argwhere(hits):
        restrictions[idx[i]] = restriction[i].tolist()

    return restrictions
コード例 #15
0
def parse_syminfo(path):
    data = []
    cur = None
    with open(path) as f:
        for line in f:
            line = line.strip()
            if not line or line[0] == '#':
                continue
            #print('"%s"' % line)
            if line == 'begin_spacegroup':
                assert cur is None, line
                cur = {'symops': [], 'cenops': []}
                continue
            assert cur is not None, line
            if line == 'end_spacegroup':
                for must_have in ['basisop', 'ccp4', 'number', 'hall', 'xhm']:
                    assert must_have in cur, must_have
                for must_have_list in ['symops', 'cenops']:
                    assert len(cur[must_have_list]) > 0
                data.append(cur)
                cur = None
            elif line.startswith('number '):
                cur['number'] = int(line[6:])
            elif line.startswith('basisop '):
                cur['basisop'] = line[8:]
            elif line.startswith('symbol ccp4 '):
                cur['ccp4'] = int(line[12:])
            elif line.startswith('symbol xHM '):
                cur['xhm'] = line[11:].strip(" '").replace(' :', ':')
            elif line.startswith('symbol Hall '):
                cur['hall'] = line[12:].strip(" '")
            elif line.startswith('symop '):
                cur['symops'].append(gemmi.Op(line[6:]))
            elif line.startswith('cenop '):
                cur['cenops'].append(gemmi.Op(line[6:]))
    return data
コード例 #16
0
    def apply_symop(self, symop, inplace=False):
        """
        Apply symmetry operation to all reflections in DataSet object. 

        Parameters
        ----------
        symop : str, gemmi.Op
            Gemmi symmetry operation or string representing symmetry op
        inplace : bool
            Whether to return a new DataFrame or make the change in place
        """
        if isinstance(symop, str):
            symop = gemmi.Op(symop)
        elif not isinstance(symop, gemmi.Op):
            raise ValueError(f"Provided symop is not of type gemmi.Op")

        dataset = self

        # Handle phase flips associated with Friedel operator
        if symop.det_rot() < 0:
            phic = -1
        else:
            phic = 1

        # Apply symop to generate new HKL indices and phase shifts
        H = dataset.get_hkls()
        hkl = apply_to_hkl(H, symop)
        phase_shifts = phase_shift(H, symop)

        dataset[['H', 'K', 'L']] = hkl
        dataset[['H', 'K', 'L']] = dataset[['H', 'K',
                                            'L']].astype(rs.HKLIndexDtype())

        # Shift phases according to symop
        for key in dataset.get_phase_keys():
            dataset[key] += np.rad2deg(phase_shifts)
            dataset[key] *= phic
            dataset[key] = utils.canonicalize_phases(dataset[key], deg=True)
        for key in dataset.get_complex_keys():
            dataset[key] *= np.exp(1j * phase_shifts)
            if symop.det_rot() < 0:
                dataset[key] = np.conjugate(dataset[key])

        return dataset
コード例 #17
0
def get_phase_restrictions(H, spacegroup):
    """
    Return phase restrictions for Miller indices in a given space group.

    If there are no phase restrictions, an empty list is returned for that
    Miller index. If a given Miller index is systematically absent an
    empty list is also returned. 

    Parameters
    ----------
    H : array
        n x 3 array of Miller indices
    spacegroup : gemmi.SpaceGroup
        Space group for determining phase restrictions

    Returns
    -------
    restrictions : list of lists
         List of lists of phase restrictions for each Miller index. An empty
         list is returned for Miller indices without phase restrictions
    """
    from reciprocalspaceship.utils.structurefactors import is_centric, is_absent

    restrictions = []
    for h in H:
        if not is_centric([h], spacegroup)[0] or is_absent([h], spacegroup)[0]:
            restrictions.append([])
        else:
            friedelop = gemmi.Op("-x,-y,-z")
            hit = False
            for op in spacegroup.operations().sym_ops[1:]:
                if op.apply_to_hkl(h) == friedelop.apply_to_hkl(h):
                    shift = np.rad2deg(op.phase_shift(h))
                    restriction = np.array([shift / 2, 180 + (shift / 2)])
                    restriction = canonicalize_phases(restriction)
                    restriction.sort()
                    restrictions.append(restriction.tolist())
                    hit = True
                    break

            # Handle [0, 0, 0] in P1
            if not hit:
                restrictions.append([])
    return restrictions
コード例 #18
0
def test_apply_symop_hkl(data_fmodel, inplace, op):
    """
    Test DataSet.apply_symop() using fmodel dataset. This test is purely
    for the HKL indices, but will not explicitly test phase shift for cases
    other than pure rotations.
    """

    copy = data_fmodel.copy()

    if isinstance(op, (gemmi.Op, str)):
        if isinstance(op, str):
            op = gemmi.Op(op)

        result = data_fmodel.apply_symop(op, inplace=inplace)
        expectedH = [op.apply_to_hkl(h) for h in copy.get_hkls()]
        expectedH = np.array(expectedH)

        assert np.array_equal(result["FMODEL"].to_numpy(),
                              copy["FMODEL"].to_numpy())
        assert np.array_equal(result.get_hkls(), expectedH)

        # Confirm Miller indices are still correct dtype
        temp = result.reset_index()
        assert isinstance(temp.H.dtype, rs.HKLIndexDtype)
        assert isinstance(temp.K.dtype, rs.HKLIndexDtype)
        assert isinstance(temp.L.dtype, rs.HKLIndexDtype)

        # Confirm copy when desired
        if inplace:
            assert id(result) == id(data_fmodel)
        else:
            assert id(result) != id(data_fmodel)

    else:
        with pytest.raises(ValueError):
            result = data_fmodel.apply_symop(op, inplace=inplace)
コード例 #19
0
 def is_centrosymm(self) -> bool:
     """
     Whether a structuere is centro symmetric or not.
     """
     ops = gemmi.GroupOps([gemmi.Op(o) for o in self.symmops])
     return ops.is_centric()
コード例 #20
0
    def stack_anomalous(self, plus_labels=None, minus_labels=None):
        """
        Convert data from two-column anomalous format to one-column
        format. Intensities, structure factor amplitudes, or other data 
        are converted from separate columns corresponding to a single 
        Miller index to the same data column at different rows indexed 
        by the Friedel-plus or Friedel-minus Miller index. 

        This method will return a DataSet with, at most, twice as many rows as the 
        original --  one row for each Friedel pair. In most cases, the resulting 
        DataSet will be smaller, because centric reflections will not be stacked. 
        For a merged DataSet, this has the effect of mapping reflections 
        from the positive reciprocal space ASU to the positive and negative 
        reciprocal space ASU, for Friedel-plus and Friedel-minus reflections, 
        respectively. 

        Notes
        -----
        - A ValueError is raised if invoked with an unmerged DataSet
        - It is assumed that Friedel-plus column labels are suffixed with (+),
          and that Friedel-minus column labels are suffixed with (-)
        - Corresponding column labels are expected to be given in the same order

        Parameters
        ----------
        plus_labels: str or list-like
            Column label or list of column labels of data associated with
            Friedel-plus reflection (Defaults to columns suffixed with "(+)")
        minus_labels: str or list-like
            Column label or list of column labels of data associated with
            Friedel-minus reflection (Defaults to columns suffixed with "(-)")

        Returns
        -------
        DataSet

        See Also
        --------
        DataSet.unstack_anomalous : Opposite of stack_anomalous
        """
        if not self.merged:
            raise ValueError(
                "DataSet.stack_anomalous() cannot be called with unmerged data"
            )

        # Default behavior: Use labels suffixed with "(+)" or "(-)"
        if (plus_labels is None and minus_labels is None):
            plus_labels = [l for l in self.columns if "(+)" in l]
            minus_labels = [l for l in self.columns if "(-)" in l]

        # Validate column labels
        if isinstance(plus_labels, str) and isinstance(minus_labels, str):
            plus_labels = [plus_labels]
            minus_labels = [minus_labels]
        elif (isinstance(plus_labels, list)
              and isinstance(minus_labels, list)):
            if len(plus_labels) != len(minus_labels):
                raise ValueError(
                    f"plus_labels: {plus_labels} and minus_labels: "
                    f"{minus_labels} do not have same length.")
        else:
            raise ValueError(
                f"plus_labels and minus_labels must have same type "
                f"and be str or list: plus_labels is type "
                f"{type(plus_labels)} and minus_labe is type "
                f"{type(minus_labels)}.")

        for plus, minus in zip(plus_labels, minus_labels):
            if self[plus].dtype != self[minus].dtype:
                raise ValueError(f"Corresponding labels in {plus_labels} and "
                                 f"{minus_labels} are not the same dtype: "
                                 f"{self[plus].dtype} and {self[minus].dtype}")

        # Map Friedel reflections to +/- ASU
        centrics = self.label_centrics()["CENTRIC"]
        dataset_plus = self.drop(columns=minus_labels)
        dataset_minus = self.loc[~centrics].drop(columns=plus_labels)
        dataset_minus.apply_symop(gemmi.Op("-x,-y,-z"), inplace=True)

        # Rename columns and update dtypes
        new_labels = [l.rstrip("(+)") for l in plus_labels]
        column_mapping_plus = dict(zip(plus_labels, new_labels))
        column_mapping_minus = dict(zip(minus_labels, new_labels))
        dataset_plus.rename(columns=column_mapping_plus, inplace=True)
        dataset_minus.rename(columns=column_mapping_minus, inplace=True)
        F = dataset_plus.append(dataset_minus)
        for label in new_labels:
            F[label] = F[label].from_friedel_dtype()

        return F
コード例 #21
0
    # Test inplace
    if inplace:
        assert id(result) == id(data_fmodel)
    else:
        assert id(result) != id(data_fmodel)

    # Test labels
    if return_labels:
        assert len(labels) == bins


@pytest.mark.parametrize("inplace", [True, False])
@pytest.mark.parametrize("op", [
    "-x,-y,-z",
    gemmi.Op("x,y,z"),
    gemmi.Op("-x,-y,-z"),
    gemmi.Op("x,y,-z"),
    gemmi.Op("x,-y,-z"),
    gemmi.Op("-x,y,-z"),
    5,
])
def test_apply_symop_hkl(data_fmodel, inplace, op):
    """
    Test DataSet.apply_symop() using fmodel dataset. This test is purely
    for the HKL indices, but will not explicitly test phase shift for cases
    other than pure rotations.
    """

    copy = data_fmodel.copy()
コード例 #22
0
        np.cos(np.deg2rad(friedel.loc[H.tolist(), "PHIFMODEL"].to_numpy())),
        np.cos(
            np.deg2rad(
                -1 * friedel.loc[(-1 * H).tolist(), "PHIFMODEL"].to_numpy())),
        atol=1e-5,
    )
    assert np.allclose(
        friedel.loc[H.tolist(), "sf"].to_numpy(),
        np.conjugate(friedel.loc[(-1 * H).tolist(), "sf"].to_numpy()),
    )


@pytest.mark.parametrize(
    "op",
    [
        gemmi.Op("x,y,z+1/4"),
        gemmi.Op("x,y+1/4,z"),
        gemmi.Op("x+1/4,y,z"),
        gemmi.Op("x+1/4,y+1/4,z"),
        gemmi.Op("x+1/4,y,z+1/4"),
        gemmi.Op("x,y+1/4,z+1/4"),
        gemmi.Op("x+1/4,y+1/4,z+1/4"),
        gemmi.Op("x,y+1/4,z-1/4"),
        gemmi.Op("x-3/4,y+1/4,z-1/4"),
        gemmi.Op("x-3/4,y+1/4,z-3/4"),
        gemmi.Op("x-3/4,y+3/4,z-3/4"),
        gemmi.Op("x,y,z+1/3"),
        gemmi.Op("x,y+1/3,z"),
        gemmi.Op("x+1/3,y,z"),
        gemmi.Op("x+1/3,y+1/3,z"),
        gemmi.Op("x,y+1/3,z+1/3"),