Example #1
0
class cif(DictMixin):
    def __init__(self, blocks=None):
        if blocks is not None:
            self.blocks = OrderedDict(blocks)
        else:
            self.blocks = OrderedDict()
        self.keys_lower = dict([(key.lower(), key)
                                for key in self.blocks.keys()])

    def __setitem__(self, key, value):
        assert isinstance(value, block)
        if not re.match(tag_re, '_' + key):
            raise Sorry("%s is not a valid data block name" % key)
        self.blocks[key] = value
        self.keys_lower[key.lower()] = key

    def get(self, key, default=None):
        key_lower = self.keys_lower.get(key.lower())
        if (key_lower is None):
            return default
        return self.blocks.get(key_lower, default)

    def __getitem__(self, key):
        result = self.get(key)
        if (result is None):
            raise KeyError('Unknown CIF data block name: "%s"' % key)
        return result

    def __delitem__(self, key):
        del self.blocks[self.keys_lower[key.lower()]]
        del self.keys_lower[key.lower()]

    def keys(self):
        return self.blocks.keys()

    def __repr__(self):
        return repr(OrderedDict(self.iteritems()))

    def __copy__(self):
        return cif(self.blocks.copy())

    copy = __copy__

    def __deepcopy__(self, memo):
        return cif(copy.deepcopy(self.blocks, memo))

    def deepcopy(self):
        return copy.deepcopy(self)

    def show(self,
             out=None,
             indent="  ",
             indent_row=None,
             data_name_field_width=34,
             loop_format_strings=None,
             align_columns=True):
        if out is None:
            out = sys.stdout
        for name, block in self.items():
            print >> out, "data_%s" % name
            block.show(out=out,
                       indent=indent,
                       indent_row=indent_row,
                       data_name_field_width=data_name_field_width,
                       loop_format_strings=loop_format_strings,
                       align_columns=align_columns)

    def __str__(self):
        s = StringIO()
        self.show(out=s)
        return s.getvalue()

    def validate(self,
                 dictionary,
                 show_warnings=True,
                 error_handler=None,
                 out=None):
        if out is None: out = sys.stdout
        from iotbx.cif import validation
        errors = {}
        if error_handler is None:
            error_handler = validation.ErrorHandler()
        for key, block in self.blocks.iteritems():
            error_handler = error_handler.__class__()
            dictionary.set_error_handler(error_handler)
            block.validate(dictionary)
            errors.setdefault(key, error_handler)
            if error_handler.error_count or error_handler.warning_count:
                error_handler.show(show_warnings=show_warnings, out=out)
        return error_handler

    def sort(self, recursive=False, key=None, reverse=False):
        self.blocks = OrderedDict(
            sorted(self.blocks.items(), key=key, reverse=reverse))
        if recursive:
            for b in self.blocks.values():
                b.sort(recursive=recursive, reverse=reverse)
Example #2
0
class cif(DictMixin):
  def __init__(self, blocks=None):
    if blocks is not None:
      self.blocks = OrderedDict(blocks)
    else:
      self.blocks = OrderedDict()
    self.keys_lower = dict([(key.lower(), key) for key in self.blocks.keys()])

  def __setitem__(self, key, value):
    assert isinstance(value, block)
    if not re.match(tag_re, '_'+key):
      raise Sorry("%s is not a valid data block name" %key)
    self.blocks[key] = value
    self.keys_lower[key.lower()] = key

  def get(self, key, default=None):
    key_lower = self.keys_lower.get(key.lower())
    if (key_lower is None):
      return default
    return self.blocks.get(key_lower, default)

  def __getitem__(self, key):
    result = self.get(key)
    if (result is None):
      raise KeyError('Unknown CIF data block name: "%s"' % key)
    return result

  def __delitem__(self, key):
    del self.blocks[self.keys_lower[key.lower()]]
    del self.keys_lower[key.lower()]

  def keys(self):
    return self.blocks.keys()

  def __repr__(self):
    return repr(OrderedDict(self.iteritems()))

  def __copy__(self):
    return cif(self.blocks.copy())

  copy = __copy__

  def __deepcopy__(self, memo):
    return cif(copy.deepcopy(self.blocks, memo))

  def deepcopy(self):
    return copy.deepcopy(self)

  def show(self, out=None, indent="  ", indent_row=None,
           data_name_field_width=34,
           loop_format_strings=None):
    if out is None:
      out = sys.stdout
    for name, block in self.items():
      print >> out, "data_%s" %name
      block.show(
        out=out, indent=indent, indent_row=indent_row,
        data_name_field_width=data_name_field_width,
        loop_format_strings=loop_format_strings)

  def __str__(self):
    s = StringIO()
    self.show(out=s)
    return s.getvalue()

  def validate(self, dictionary, show_warnings=True, error_handler=None, out=None):
    if out is None: out = sys.stdout
    from iotbx.cif import validation
    errors = {}
    if error_handler is None:
      error_handler = validation.ErrorHandler()
    for key, block in self.blocks.iteritems():
      error_handler = error_handler.__class__()
      dictionary.set_error_handler(error_handler)
      block.validate(dictionary)
      errors.setdefault(key, error_handler)
      if error_handler.error_count or error_handler.warning_count:
        error_handler.show(show_warnings=show_warnings, out=out)
    return error_handler

  def sort(self, recursive=False, key=None, reverse=False):
    self.blocks = OrderedDict(sorted(self.blocks.items(), key=key, reverse=reverse))
    if recursive:
      for b in self.blocks.values():
        b.sort(recursive=recursive, reverse=reverse)
Example #3
0
class align_crystal(object):

  vector_names = {
    a.elems: 'a',
    b.elems: 'b',
    c.elems: 'c',
  }

  def __init__(self, experiment, vectors, frame='reciprocal', mode='main'):
    from libtbx.utils import Sorry
    self.experiment = experiment
    self.vectors = vectors
    self.frame = frame
    self.mode = mode

    gonio = experiment.goniometer
    scan = experiment.scan

    self.s0 = matrix.col(self.experiment.beam.get_s0())
    self.rotation_axis = matrix.col(gonio.get_rotation_axis())

    from dxtbx.model import MultiAxisGoniometer
    if not isinstance(gonio, MultiAxisGoniometer):
      raise Sorry('Only MultiAxisGoniometer models supported')
    axes = gonio.get_axes()
    if len(axes) != 3:
      raise Sorry('Only 3-axis goniometers supported')
    e1, e2, e3 = (matrix.col(e) for e in reversed(axes))

    fixed_rotation = matrix.sqr(gonio.get_fixed_rotation())
    setting_rotation = matrix.sqr(gonio.get_setting_rotation())
    rotation_axis = matrix.col(gonio.get_rotation_axis_datum())
    rotation_matrix = rotation_axis.axis_and_angle_as_r3_rotation_matrix(
      experiment.scan.get_oscillation()[0], deg=True)

    from dials.algorithms.refinement import rotation_decomposition

    results = OrderedDict()

    # from https://github.com/legrandp/xdsme/blob/master/XOalign/XOalign.py#L427
    #  referential_permutations sign permutations for four permutations of
    #        parallel/antiparallel (rotation axis & beam)
    #    y1 // e1, y2 // beamVector;  y1 anti// e1, y2 // beamVector
    #    y1 // e1, y2 anti// beamVector;  y1 anti// e1, y2 anti// beamVector

    ex = matrix.col((1, 0, 0))
    ey = matrix.col((0, 1, 0))
    ez = matrix.col((0, 0, 1))

    referential_permutations = ([ ex,  ey,  ez],
                                [-ex, -ey,  ez],
                                [ ex, -ey, -ez],
                                [-ex,  ey, -ez])

    for (v1_, v2_) in self.vectors:
      results[(v1_, v2_)] = OrderedDict()
      space_group = self.experiment.crystal.get_space_group()
      for smx in list(space_group.smx())[:]:
        results[(v1_, v2_)][smx] = []
        crystal = copy.deepcopy(self.experiment.crystal)
        cb_op = sgtbx.change_of_basis_op(smx)
        crystal = crystal.change_basis(cb_op)

        # Goniometer datum setting [D] at which the orientation was determined
        D = (setting_rotation * rotation_matrix * fixed_rotation).inverse()

        # The setting matrix [U] will vary with the datum setting according to
        # [U] = [D] [U0]
        U = matrix.sqr(crystal.get_U())

        # XXX In DIALS recorded U is equivalent to U0 - D is applied to U inside
        # prediction
        U0 = U

        B = matrix.sqr(crystal.get_B())

        if self.frame == 'direct':
          B = B.inverse().transpose()

        v1_0 = U0 * B * v1_
        v2_0 = U0 * B * v2_

        #c  (b) The laboratory frame vectors l1 & l2 are normally specified with the
        #c MODE command: MODE MAIN (the default) sets l1 (along which v1 will be
        #c placed) along the principle goniostat axis e1 (Omega), and l2 along
        #c the beam s0. This allows rotation for instance around a principle axis.
        #c The other mode is MODE CUSP, which puts l1 (v1) perpendicular to the
        #c beam (s0) and the e1 (Omega) axis, and l2 (v2) in the plane containing
        #c l1 & e1 (ie l1 = e1 x s0, l2 = e1).

        if self.mode == 'cusp':
          l1 = self.rotation_axis.cross(self.s0)
          l2 = self.rotation_axis
        else:
          l1 = self.rotation_axis.normalize()
          l3 = l1.cross(self.s0).normalize()
          l2 = l1.cross(l3)

        for perm in referential_permutations:
          S = matrix.sqr(perm[0].elems + perm[1].elems + perm[2].elems)
          from rstbx.cftbx.coordinate_frame_helpers import align_reference_frame
          R = align_reference_frame(v1_0, S * l1, v2_0, S * l2)

          solutions = rotation_decomposition.solve_r3_rotation_for_angles_given_axes(
            R, e1, e2, e3, return_both_solutions=True, deg=True)

          if solutions is None:
            continue

          results[(v1_, v2_)][smx].extend(solutions)

    self.all_solutions = results

    self.unique_solutions = OrderedDict()
    for (v1, v2), result in results.iteritems():
      for solutions in result.itervalues():
        for solution in solutions:
          k = tuple(round(a, 3) for a in solution[1:])
          self.unique_solutions.setdefault(k, OrderedSet())
          self.unique_solutions[k].add((v1, v2))

  def _vector_as_str(self, v):
    v = v.elems
    if v in self.vector_names:
      vstr = self.vector_names[v]
      if self.frame == 'reciprocal':
        vstr += '*'
    else:
      vstr = str(v)
    return vstr

  def show(self):
    from libtbx import table_utils
    self.info()

    rows = []
    names = self.experiment.goniometer.get_names()

    space_group = self.experiment.crystal.get_space_group()
    reciprocal = self.frame == 'reciprocal'
    for angles, vector_pairs in self.unique_solutions.iteritems():
      v1, v2 = list(vector_pairs)[0]
      rows.append((
        describe(v1, space_group, reciprocal=reciprocal),
        describe(v2, space_group, reciprocal=reciprocal),
        '% 7.3f' %angles[0], '% 7.3f' %angles[1],
      ))
    rows = [('Primary axis', 'Secondary axis', names[1], names[0])] + \
           sorted(rows)
    print 'Independent solutions:'
    print table_utils.format(rows=rows, has_header=True)

  def as_json(self, filename=None):
    names = self.experiment.goniometer.get_names()
    solutions = []
    space_group = self.experiment.crystal.get_space_group()
    reciprocal = self.frame == 'reciprocal'
    for angles, solns in self.unique_solutions.iteritems():
      solutions.append({
        'primary_axis': [self._vector_as_str(v1) for v1, v2 in solns],
        'secondary_axis': [self._vector_as_str(v2) for v1, v2 in solns],
        'primary_axis_type': [axis_type(v1, space_group) for v1, v2 in solns],
        'secondary_axis_type': [axis_type(v2, space_group) for v1, v2 in solns],
        names[1]: angles[0],
        names[0]: angles[1]
      })
    d = {'solutions': solutions,
         'goniometer': self.experiment.goniometer.to_dict()}
    import json
    if filename is not None:
      return json.dump(d, open(filename, 'wb'), indent=2)
    else:
      return json.dumps(d, indent=2)

  def info(self):
    from libtbx import table_utils

    U = matrix.sqr(self.experiment.crystal.get_U())
    B = matrix.sqr(self.experiment.crystal.get_B())

    a_star_ = U * B * a_star
    b_star_ = U * B * b_star
    c_star_ = U * B * c_star

    Binvt = B.inverse().transpose()

    a_ = U * Binvt * a
    b_ = U * Binvt * b
    c_ = U * Binvt * c

    names = self.experiment.goniometer.get_names()
    axes = self.experiment.goniometer.get_axes()
    rows = [['Experimental axis', 'a*', 'b*', 'c*']]
    rows.append([names[0]] + [
      '%.3f' %smallest_angle(axis.angle(matrix.col(axes[0]), deg=True))
      for axis in (a_star_, b_star_, c_star_)])
    rows.append(['Beam'] + [
      '%.3f' %smallest_angle(axis.angle(self.s0, deg=True))
      for axis in (a_star_, b_star_, c_star_)])
    rows.append([names[2]] + [
      '%.3f' %smallest_angle(axis.angle(matrix.col(axes[2]), deg=True))
      for axis in (a_star_, b_star_, c_star_)])
    print 'Angles between reciprocal cell axes and principal experimental axes:'
    print table_utils.format(rows=rows, has_header=True)
    print

    rows = [['Experimental axis', 'a', 'b', 'c']]
    rows.append([names[0]] + [
      '%.3f' %smallest_angle(axis.angle(matrix.col(axes[0]), deg=True))
      for axis in (a_, b_, c_)])
    rows.append(['Beam'] + [
      '%.3f' %smallest_angle(axis.angle(self.s0, deg=True))
      for axis in (a_, b_, c_)])
    rows.append([names[2]] + [
      '%.3f' %smallest_angle(axis.angle(matrix.col(axes[2]), deg=True))
      for axis in (a_, b_, c_)])
    print 'Angles between unit cell axes and principal experimental axes:'
    print table_utils.format(rows=rows, has_header=True)
    print
Example #4
0
  def __init__(self, experiment, vectors, frame='reciprocal', mode='main'):
    from libtbx.utils import Sorry
    self.experiment = experiment
    self.vectors = vectors
    self.frame = frame
    self.mode = mode

    gonio = experiment.goniometer
    scan = experiment.scan

    self.s0 = matrix.col(self.experiment.beam.get_s0())
    self.rotation_axis = matrix.col(gonio.get_rotation_axis())

    from dxtbx.model import MultiAxisGoniometer
    if not isinstance(gonio, MultiAxisGoniometer):
      raise Sorry('Only MultiAxisGoniometer models supported')
    axes = gonio.get_axes()
    if len(axes) != 3:
      raise Sorry('Only 3-axis goniometers supported')
    e1, e2, e3 = (matrix.col(e) for e in reversed(axes))

    fixed_rotation = matrix.sqr(gonio.get_fixed_rotation())
    setting_rotation = matrix.sqr(gonio.get_setting_rotation())
    rotation_axis = matrix.col(gonio.get_rotation_axis_datum())
    rotation_matrix = rotation_axis.axis_and_angle_as_r3_rotation_matrix(
      experiment.scan.get_oscillation()[0], deg=True)

    from dials.algorithms.refinement import rotation_decomposition

    results = OrderedDict()

    # from https://github.com/legrandp/xdsme/blob/master/XOalign/XOalign.py#L427
    #  referential_permutations sign permutations for four permutations of
    #        parallel/antiparallel (rotation axis & beam)
    #    y1 // e1, y2 // beamVector;  y1 anti// e1, y2 // beamVector
    #    y1 // e1, y2 anti// beamVector;  y1 anti// e1, y2 anti// beamVector

    ex = matrix.col((1, 0, 0))
    ey = matrix.col((0, 1, 0))
    ez = matrix.col((0, 0, 1))

    referential_permutations = ([ ex,  ey,  ez],
                                [-ex, -ey,  ez],
                                [ ex, -ey, -ez],
                                [-ex,  ey, -ez])

    for (v1_, v2_) in self.vectors:
      results[(v1_, v2_)] = OrderedDict()
      space_group = self.experiment.crystal.get_space_group()
      for smx in list(space_group.smx())[:]:
        results[(v1_, v2_)][smx] = []
        crystal = copy.deepcopy(self.experiment.crystal)
        cb_op = sgtbx.change_of_basis_op(smx)
        crystal = crystal.change_basis(cb_op)

        # Goniometer datum setting [D] at which the orientation was determined
        D = (setting_rotation * rotation_matrix * fixed_rotation).inverse()

        # The setting matrix [U] will vary with the datum setting according to
        # [U] = [D] [U0]
        U = matrix.sqr(crystal.get_U())

        # XXX In DIALS recorded U is equivalent to U0 - D is applied to U inside
        # prediction
        U0 = U

        B = matrix.sqr(crystal.get_B())

        if self.frame == 'direct':
          B = B.inverse().transpose()

        v1_0 = U0 * B * v1_
        v2_0 = U0 * B * v2_

        #c  (b) The laboratory frame vectors l1 & l2 are normally specified with the
        #c MODE command: MODE MAIN (the default) sets l1 (along which v1 will be
        #c placed) along the principle goniostat axis e1 (Omega), and l2 along
        #c the beam s0. This allows rotation for instance around a principle axis.
        #c The other mode is MODE CUSP, which puts l1 (v1) perpendicular to the
        #c beam (s0) and the e1 (Omega) axis, and l2 (v2) in the plane containing
        #c l1 & e1 (ie l1 = e1 x s0, l2 = e1).

        if self.mode == 'cusp':
          l1 = self.rotation_axis.cross(self.s0)
          l2 = self.rotation_axis
        else:
          l1 = self.rotation_axis.normalize()
          l3 = l1.cross(self.s0).normalize()
          l2 = l1.cross(l3)

        for perm in referential_permutations:
          S = matrix.sqr(perm[0].elems + perm[1].elems + perm[2].elems)
          from rstbx.cftbx.coordinate_frame_helpers import align_reference_frame
          R = align_reference_frame(v1_0, S * l1, v2_0, S * l2)

          solutions = rotation_decomposition.solve_r3_rotation_for_angles_given_axes(
            R, e1, e2, e3, return_both_solutions=True, deg=True)

          if solutions is None:
            continue

          results[(v1_, v2_)][smx].extend(solutions)

    self.all_solutions = results

    self.unique_solutions = OrderedDict()
    for (v1, v2), result in results.iteritems():
      for solutions in result.itervalues():
        for solution in solutions:
          k = tuple(round(a, 3) for a in solution[1:])
          self.unique_solutions.setdefault(k, OrderedSet())
          self.unique_solutions[k].add((v1, v2))
Example #5
0
  def __init__(self, pdb_hierarchy,
               sequences,
               alignment_params=None,
               crystal_symmetry=None,
               coordinate_precision=5,
               occupancy_precision=3,
               b_iso_precision=5,
               u_aniso_precision=5):

    pdb_hierarchy_as_cif_block.__init__(
      self, pdb_hierarchy, crystal_symmetry=crystal_symmetry,
    coordinate_precision=coordinate_precision,
    occupancy_precision=occupancy_precision,
    b_iso_precision=b_iso_precision,
    u_aniso_precision=u_aniso_precision)

    import mmtbx.validation.sequence
    validation = mmtbx.validation.sequence.validation(
      pdb_hierarchy=pdb_hierarchy,
      sequences=sequences,
      params=alignment_params,
      extract_residue_groups=True,
      log=null_out(), # silence output
    )

    entity_loop = iotbx.cif.model.loop(header=(
      '_entity.id',
      '_entity.type',
      #'_entity.src_method',
      #'_entity.pdbx_description',
      '_entity.formula_weight',
      '_entity.pdbx_number_of_molecules',
      #'_entity.details',
      #'_entity.pdbx_mutation',
      #'_entity.pdbx_fragment',
      #'_entity.pdbx_ec'
    ))

    entity_poly_loop = iotbx.cif.model.loop(header=(
      '_entity_poly.entity_id',
      '_entity_poly.type',
      '_entity_poly.nstd_chirality',
      '_entity_poly.nstd_linkage',
      '_entity_poly.nstd_monomer',
      '_entity_poly.pdbx_seq_one_letter_code',
      '_entity_poly.pdbx_seq_one_letter_code_can',
      '_entity_poly.pdbx_strand_id',
      '_entity_poly.type_details'
    ))

    entity_poly_seq_loop = iotbx.cif.model.loop(header=(
      '_entity_poly_seq.entity_id',
      '_entity_poly_seq.num',
      '_entity_poly_seq.mon_id',
      '_entity_poly_seq.hetero',
    ))

    sequence_counts = OrderedDict()
    sequence_to_chain_ids = {}
    entity_id = 0
    sequence_to_entity_id = {}
    chain_id_to_entity_id = {}
    sequence_to_chains = {}
    residue_group_to_seq_num_mapping = {}
    aligned_pdb_chains = OrderedSet()
    non_polymer_counts = dict_with_default_0()
    non_polymer_resname_to_entity_id = OrderedDict()

    for chain in validation.chains:
      sequence = chain.alignment.b
      if sequence not in sequence_to_entity_id:
        entity_id += 1
        sequence_to_entity_id[sequence] = entity_id
      sequence_counts.setdefault(sequence, 0)
      sequence_counts[sequence] += 1
      sequence_to_chain_ids.setdefault(sequence, [])
      sequence_to_chain_ids[sequence].append(chain.chain_id)
      sequence_to_chains.setdefault(sequence, [])
      sequence_to_chains[sequence].append(chain)
      chain_id_to_entity_id[chain.chain_id] = sequence_to_entity_id[sequence]
      aligned_pdb_chains.add(chain.residue_groups[0].parent())
      unaligned_pdb_chains = OrderedSet(pdb_hierarchy.chains()) - aligned_pdb_chains

      assert len(chain.residue_groups) + chain.n_missing_start + chain.n_missing_end == len(sequence)
      residue_groups = [None] * chain.n_missing_start + chain.residue_groups + [None] * chain.n_missing_end
      i = chain.n_missing_start
      seq_num = 0
      for i, residue_group in enumerate(residue_groups):
        if residue_group is None and chain.alignment.b[i] == '-':
          # a deletion
          continue
        seq_num += 1
        if residue_group is not None:
          residue_group_to_seq_num_mapping[
            residue_group] = seq_num

    for pdb_chain in unaligned_pdb_chains:
      for residue_group in pdb_chain.residue_groups():
        for resname in residue_group.unique_resnames():
          if resname not in non_polymer_resname_to_entity_id:
            entity_id += 1
            non_polymer_resname_to_entity_id[resname] = entity_id
          non_polymer_counts[resname] += 1

    for sequence, count in sequence_counts.iteritems():
      entity_poly_seq_num = 0
      entity_id = sequence_to_entity_id[sequence]

      entity_loop.add_row((
        entity_id,
        'polymer', #polymer/non-polymer/macrolide/water
        #'?', #src_method
        #'?', # pdbx_description
        '?', # formula_weight
        len(sequence_to_chains[sequence]), # pdbx_number_of_molecules
        #'?', # details
        #'?', # pdbx_mutation
        #'?', # pdbx_fragment
        #'?' # pdbx_ec
      ))

      # The definition of the cif item _entity_poly.pdbx_seq_one_letter_code
      # says that modifications and non-standard amino acids should be encoded
      # as 'X', however in practice the PDB seem to encode them as the three-letter
      # code in parentheses.
      pdbx_seq_one_letter_code = []
      pdbx_seq_one_letter_code_can = []

      chains = sequence_to_chains[sequence]

      from iotbx.pdb import amino_acid_codes

      chain = chains[0]
      matches = chain.alignment.matches()

      for i, one_letter_code in enumerate(sequence):

        #Data items in the ENTITY_POLY_SEQ category specify the sequence
        #of monomers in a polymer. Allowance is made for the possibility
        #of microheterogeneity in a sample by allowing a given sequence
        #number to be correlated with more than one monomer ID. The
        #corresponding ATOM_SITE entries should reflect this
        #heterogeneity.

        monomer_id = None
        if i >= chain.n_missing_start and i < (len(sequence) - chain.n_missing_end):
          monomer_id = chain.resnames[i-chain.n_missing_start]

        if monomer_id is None and one_letter_code == '-': continue

        pdbx_seq_one_letter_code_can.append(one_letter_code)

        if monomer_id is None:
          if sequence_to_chains[sequence][0].chain_type == mmtbx.validation.sequence.PROTEIN:
            monomer_id = amino_acid_codes.three_letter_given_one_letter.get(
              one_letter_code, "UNK") # XXX
          else:
            monomer_id = one_letter_code
        else:
          if sequence_to_chains[sequence][0].chain_type == mmtbx.validation.sequence.PROTEIN:
            one_letter_code = amino_acid_codes.one_letter_given_three_letter.get(
              monomer_id, "(%s)" %monomer_id)

        pdbx_seq_one_letter_code.append(one_letter_code)

        entity_poly_seq_num += 1

        entity_poly_seq_loop.add_row((
          entity_id,
          entity_poly_seq_num,
          monomer_id,
          'no', #XXX
        ))

      entity_poly_type = '?'
      entity_nstd_chirality = 'n'
      # we should probably determine the chirality more correctly by examining
      # the chirality of the backbone chain rather than relying on the residue
      # names to be correct
      if chain.chain_type == mmtbx.validation.sequence.PROTEIN:
        n_d_peptides = 0
        n_l_peptides = 0
        n_achiral_peptides = 0
        n_unknown = 0
        for resname in chain.resnames:
          if resname == "GLY":
            n_achiral_peptides += 1
          elif resname in iotbx.pdb.common_residue_names_amino_acid:
            n_l_peptides += 1
          elif resname in amino_acid_codes.three_letter_l_given_three_letter_d:
            n_d_peptides += 1
          else:
            n_unknown += 1
        n_total = sum([n_d_peptides, n_l_peptides, n_achiral_peptides, n_unknown])
        if (n_l_peptides + n_achiral_peptides)/n_total > 0.5:
          entity_poly_type = 'polypeptide(L)'
          if n_d_peptides > 0:
            entity_nstd_chirality = 'y'
        elif (n_d_peptides + n_achiral_peptides)/n_total > 0.5:
          entity_poly_type = 'polypeptide(D)'
          if n_l_peptides > 0:
            entity_nstd_chirality = 'y'
      elif chain.chain_type == mmtbx.validation.sequence.NUCLEIC_ACID:
        n_dna = 0
        n_rna = 0
        n_unknown = 0
        for resname in chain.resnames:
          if resname is not None and resname.strip().upper() in (
            'AD', 'CD', 'GD', 'TD', 'DA', 'DC', 'DG', 'DT'):
            n_dna += 1
          elif resname is not None and resname.strip().upper() in (
            'A', 'C', 'G', 'T', '+A', '+C', '+G', '+T'):
            n_rna += 1
          else:
            n_unknown += 1
        n_total = sum([n_dna + n_rna + n_unknown])
        if n_dna/n_total > 0.5 and n_rna == 0:
          entity_poly_type = 'polydeoxyribonucleotide'
        elif n_rna/n_total > 0.5 and n_dna == 0:
          entity_poly_type = 'polyribonucleotide'
        elif (n_rna + n_dna)/n_total > 0.5:
          entity_poly_type = 'polydeoxyribonucleotide/polyribonucleotide hybrid'

      entity_poly_loop.add_row((
        entity_id,
        entity_poly_type,
        entity_nstd_chirality,
        'no',
        'no',
        wrap_always("".join(pdbx_seq_one_letter_code), width=80).strip(),
        wrap_always("".join(pdbx_seq_one_letter_code_can), width=80).strip(),
        ','.join(sequence_to_chain_ids[sequence]),
        '?'
      ))

    for resname, entity_id in non_polymer_resname_to_entity_id.iteritems():
      entity_type = "non-polymer"
      if resname == "HOH":
        entity_type = "water" # XXX
      entity_loop.add_row((
        entity_id,
        entity_type, #polymer/non-polymer/macrolide/water
        #'?', #src_method
        #'?', # pdbx_description
        '?', # formula_weight
        non_polymer_counts[resname], # pdbx_number_of_molecules
        #'?', # details
        #'?', # pdbx_mutation
        #'?', # pdbx_fragment
        #'?' # pdbx_ec
      ))

    self.cif_block.add_loop(entity_loop)
    self.cif_block.add_loop(entity_poly_loop)
    self.cif_block.add_loop(entity_poly_seq_loop)
    self.cif_block.update(pdb_hierarchy.as_cif_block())

    label_entity_id = self.cif_block['_atom_site.label_entity_id']
    auth_seq_id = self.cif_block['_atom_site.auth_seq_id']
    ins_code = self.cif_block['_atom_site.pdbx_PDB_ins_code']
    auth_asym_id = self.cif_block['_atom_site.auth_asym_id']
    label_seq_id = flex.std_string(auth_seq_id.size(), '.')
    ins_code = ins_code.deep_copy()
    ins_code.set_selected(ins_code == '?', '')
    for residue_group, seq_num in residue_group_to_seq_num_mapping.iteritems():
      sel = ((auth_asym_id == residue_group.parent().id) &
             (ins_code == residue_group.icode.strip()) &
             (auth_seq_id == residue_group.resseq.strip()))
      label_seq_id.set_selected(sel, str(seq_num))
      label_entity_id.set_selected(
        sel, str(chain_id_to_entity_id[residue_group.parent().id]))

    for pdb_chain in unaligned_pdb_chains:
      for residue_group in pdb_chain.residue_groups():
        sel = ((auth_asym_id == residue_group.parent().id) &
               (ins_code == residue_group.icode.strip()) &
               (auth_seq_id == residue_group.resseq.strip()))
        label_entity_id.set_selected(
          sel, str(non_polymer_resname_to_entity_id[residue_group.unique_resnames()[0]]))

    self.cif_block['_atom_site.label_seq_id'] = label_seq_id

    # reorder the loops
    atom_site_loop = self.cif_block['_atom_site']
    atom_site_aniso_loop = self.cif_block.get('_atom_site_anisotrop')
    del self.cif_block['_atom_site']
    self.cif_block.add_loop(atom_site_loop)
    if atom_site_aniso_loop is not None:
      del self.cif_block['_atom_site_anisotrop']
      self.cif_block.add_loop(atom_site_aniso_loop)