예제 #1
0
    def _get_scaler(self):
        if self._scaler is None:

            # in here check if
            #
            # (1) self._scaled_merged_reflections is set and
            # (2) there is no sweep information
            #
            # if both of these are true then produce a null scaler
            # which will wrap this information

            from libtbx import Auto

            scale_dir = PhilIndex.params.xia2.settings.scale.directory
            if scale_dir is Auto:
                scale_dir = "scale"
            working_directory = Environment.generate_directory(
                [self._name, scale_dir])

            self._scaler = Scaler()

            # put an inverse link in place... to support RD analysis
            # involved change to Scaler interface definition

            self._scaler.set_scaler_xcrystal(self)

            if self._anomalous:
                self._scaler.set_scaler_anomalous(True)

            # set up a sensible working directory
            self._scaler.set_working_directory(working_directory)

            # set the reference reflection file, if we have one...
            if self._reference_reflection_file:
                self._scaler.set_scaler_reference_reflection_file(
                    self._reference_reflection_file)

            # and FreeR file
            if self._freer_file:
                self._scaler.set_scaler_freer_file(self._freer_file)

            # and spacegroup information
            if self._user_spacegroup:
                # compute the lattice and pointgroup from this...

                pointgroup = Syminfo.get_pointgroup(self._user_spacegroup)

                self._scaler.set_scaler_input_spacegroup(self._user_spacegroup)
                self._scaler.set_scaler_input_pointgroup(pointgroup)

            integraters = self._get_integraters()

            # then feed them to the scaler

            for i in integraters:
                self._scaler.add_scaler_integrater(i)

        return self._scaler
예제 #2
0
파일: XCrystal.py 프로젝트: xia2/xia2
  def _get_scaler(self):
    if self._scaler is None:

      # in here check if
      #
      # (1) self._scaled_merged_reflections is set and
      # (2) there is no sweep information
      #
      # if both of these are true then produce a null scaler
      # which will wrap this information

      from libtbx import Auto
      scale_dir = PhilIndex.params.xia2.settings.scale.directory
      if scale_dir is Auto:
        scale_dir = 'scale'
      working_directory = Environment.generate_directory([self._name, scale_dir])

      self._scaler = Scaler()

      # put an inverse link in place... to support RD analysis
      # involved change to Scaler interface definition

      self._scaler.set_scaler_xcrystal(self)

      if self._anomalous:
        self._scaler.set_scaler_anomalous(True)

      # set up a sensible working directory
      self._scaler.set_working_directory(working_directory)

      # set the reference reflection file, if we have one...
      if self._reference_reflection_file:
        self._scaler.set_scaler_reference_reflection_file(
            self._reference_reflection_file)

      # and FreeR file
      if self._freer_file:
        self._scaler.set_scaler_freer_file(self._freer_file)

      # and spacegroup information
      if self._user_spacegroup:
        # compute the lattice and pointgroup from this...

        pointgroup = Syminfo.get_pointgroup(self._user_spacegroup)

        self._scaler.set_scaler_input_spacegroup(
            self._user_spacegroup)
        self._scaler.set_scaler_input_pointgroup(pointgroup)

      integraters = self._get_integraters()

      # then feed them to the scaler

      for i in integraters:
        self._scaler.add_scaler_integrater(i)

    return self._scaler
예제 #3
0
class XCrystal(object):
    """An object to maintain all of the information about a crystal. This
    will contain the experimental information in XWavelength objects,
    and also amino acid sequence, heavy atom information."""
    def __init__(self, name, project):

        self._name = name

        # separate out the anomalous pairs or merge them together?
        self._anomalous = False

        # FIXME check that project is an XProject
        self._project = project

        # these should be populated with the objects defined above
        self._aa_sequence = None

        # note that I am making allowances for > 1 heavy atom class...
        # FIXME 18/SEP/06 these should be in a dictionary which is keyed
        # by the element name...
        self._ha_info = {}
        self._wavelengths = {}
        self._samples = {}
        self._lattice_manager = None

        # hooks to dangle command interfaces from

        self._scaler = None

        self._refiner = None

        # things to store input reflections which are used to define
        # the setting... this will be passed into the Scaler if
        # defined... likewise the FreeR column file
        self._reference_reflection_file = None
        self._freer_file = None
        self._user_spacegroup = None

        # things to help the great passing on of information
        self._scaled_merged_reflections = None

        # derived information
        self._nmol = 1

    # serialization functions

    def to_dict(self):
        obj = {}
        obj["__id__"] = "XCrystal"
        import inspect

        attributes = inspect.getmembers(self,
                                        lambda m: not (inspect.isroutine(m)))
        for a in attributes:
            if a[0] == "_scaler" and a[1] is not None:
                obj[a[0]] = a[1].to_dict()
            elif a[0] == "_wavelengths":
                wavs = {}
                for wname, wav in a[1].iteritems():
                    wavs[wname] = wav.to_dict()
                obj[a[0]] = wavs
            elif a[0] == "_samples":
                samples = {}
                for sname, sample in a[1].iteritems():
                    samples[sname] = sample.to_dict()
                obj[a[0]] = samples
            elif a[0] == "_project":
                # don't serialize this since the parent xproject *should* contain
                # the pointer to the child xcrystal
                continue
            elif a[0] == "_aa_sequence" and a[1] is not None:
                obj[a[0]] = a[1].to_dict()
            elif a[0] == "_ha_info" and a[1] is not None:
                d = {}
                for k, v in a[1].iteritems():
                    d[k] = v.to_dict()
                obj[a[0]] = d
            elif a[0].startswith("__"):
                continue
            else:
                obj[a[0]] = a[1]
        return obj

    @classmethod
    def from_dict(cls, obj):
        from xia2.Schema.XWavelength import XWavelength
        from xia2.Schema.XSample import XSample

        assert obj["__id__"] == "XCrystal"
        return_obj = cls(name=None, project=None)
        for k, v in obj.iteritems():
            if k == "_scaler" and v is not None:
                from libtbx.utils import import_python_object

                cls = import_python_object(
                    import_path=".".join((v["__module__"], v["__name__"])),
                    error_prefix="",
                    target_must_be="",
                    where_str="",
                ).object
                v = cls.from_dict(v)
                v._scalr_xcrystal = return_obj
            elif k == "_wavelengths":
                v_ = {}
                for wname, wdict in v.iteritems():
                    wav = XWavelength.from_dict(wdict)
                    wav._crystal = return_obj
                    v_[wname] = wav
                v = v_
            elif k == "_samples":
                v_ = {}
                for sname, sdict in v.iteritems():
                    sample = XSample.from_dict(sdict)
                    sample._crystal = return_obj
                    v_[sname] = sample
                v = v_
            elif k == "_aa_sequence" and v is not None:
                v = _aa_sequence.from_dict(v)
            elif k == "_ha_info" and v is not None:
                for k_, v_ in v.iteritems():
                    v[k_] = _ha_info.from_dict(v_)
            setattr(return_obj, k, v)
        sweep_dict = {}
        for sample in return_obj._samples.values():
            for i, sname in enumerate(sample._sweeps):
                found_sweep = False
                for wav in return_obj._wavelengths.values():
                    if found_sweep:
                        break
                    for sweep in wav._sweeps:
                        if sweep.get_name() == sname:
                            sample._sweeps[i] = sweep
                            sweep._sample = sample
                            found_sweep = True
                            break
            for s in sample._sweeps:
                assert not isinstance(s, basestring)
        if return_obj._scaler is not None:
            for intgr in return_obj._get_integraters():
                return_obj._scaler._scalr_integraters[
                    intgr.get_integrater_epoch()] = intgr
                if (hasattr(return_obj._scaler, "_sweep_handler")
                        and return_obj._scaler._sweep_handler is not None):
                    if (intgr.get_integrater_epoch() in return_obj._scaler.
                            _sweep_handler._sweep_information):
                        return_obj._scaler._sweep_handler._sweep_information[
                            intgr.get_integrater_epoch()]._integrater = intgr
        return return_obj

    def get_output(self):

        result = "Crystal: %s\n" % self._name

        if self._aa_sequence:
            result += "Sequence: %s\n" % self._aa_sequence.get_sequence()
        for wavelength in self._wavelengths.keys():
            result += self._wavelengths[wavelength].get_output()

        scaler = self._get_scaler()
        if scaler.get_scaler_finish_done():
            for wname, xwav in self._wavelengths.iteritems():
                for xsweep in xwav.get_sweeps():
                    idxr = xsweep._get_indexer()
                    if PhilIndex.params.xia2.settings.show_template:
                        result += "%s\n" % banner(
                            "Autoindexing %s (%s)" %
                            (idxr.get_indexer_sweep_name(),
                             idxr.get_template()))
                    else:
                        result += "%s\n" % banner(
                            "Autoindexing %s" % idxr.get_indexer_sweep_name())
                    result += "%s\n" % idxr.show_indexer_solutions()

                    intgr = xsweep._get_integrater()
                    if PhilIndex.params.xia2.settings.show_template:
                        result += "%s\n" % banner(
                            "Integrating %s (%s)" %
                            (intgr.get_integrater_sweep_name(),
                             intgr.get_template()))
                    else:
                        result += "%s\n" % banner(
                            "Integrating %s" %
                            intgr.get_integrater_sweep_name())
                    result += "%s\n" % intgr.show_per_image_statistics()

            result += "%s\n" % banner("Scaling %s" % self.get_name())

            for (
                (dname, sname),
                (limit, suggested),
            ) in scaler.get_scaler_resolution_limits().iteritems():
                if suggested is None or limit == suggested:
                    result += "Resolution limit for %s/%s: %5.2f\n" % (
                        dname,
                        sname,
                        limit,
                    )
                else:
                    result += (
                        "Resolution limit for %s/%s: %5.2f (%5.2f suggested)\n"
                        % (dname, sname, limit, suggested))

        # this is now deprecated - be explicit in what you are
        # asking for...
        reflections_all = self.get_scaled_merged_reflections()
        statistics_all = self._get_scaler().get_scaler_statistics()

        # print some of these statistics, perhaps?

        for key in statistics_all.keys():
            result += format_statistics(statistics_all[key],
                                        caption="For %s/%s/%s" % key)

        # then print out some "derived" information based on the
        # scaling - this is presented through the Scaler interface
        # explicitly...

        cell = self._get_scaler().get_scaler_cell()
        cell_esd = self._get_scaler().get_scaler_cell_esd()
        spacegroups = self._get_scaler().get_scaler_likely_spacegroups()

        spacegroup = spacegroups[0]
        resolution = self._get_scaler().get_scaler_highest_resolution()

        from cctbx import sgtbx

        sg = sgtbx.space_group_type(str(spacegroup))
        spacegroup = sg.lookup_symbol()
        CIF.set_spacegroup(sg)
        mmCIF.set_spacegroup(sg)

        if len(self._wavelengths) == 1:
            CIF.set_wavelengths(
                [w.get_wavelength() for w in self._wavelengths.itervalues()])
            mmCIF.set_wavelengths(
                [w.get_wavelength() for w in self._wavelengths.itervalues()])
        else:
            for wavelength in self._wavelengths.keys():
                full_wave_name = "%s_%s_%s" % (
                    self._project._name,
                    self._name,
                    wavelength,
                )
                CIF.get_block(full_wave_name)[
                    "_diffrn_radiation_wavelength"] = self._wavelengths[
                        wavelength].get_wavelength()
                mmCIF.get_block(full_wave_name)[
                    "_diffrn_radiation_wavelength"] = self._wavelengths[
                        wavelength].get_wavelength()
            CIF.set_wavelengths({
                name: wave.get_wavelength()
                for name, wave in self._wavelengths.iteritems()
            })
            mmCIF.set_wavelengths({
                name: wave.get_wavelength()
                for name, wave in self._wavelengths.iteritems()
            })

        result += "Assuming spacegroup: %s\n" % spacegroup
        if len(spacegroups) > 1:
            result += "Other likely alternatives are:\n"
            for sg in spacegroups[1:]:
                result += "%s\n" % sg

        if cell_esd:
            from libtbx.utils import format_float_with_standard_uncertainty

            def match_formatting(dimA, dimB):
                def conditional_split(s):
                    return ((s[:s.index(".")],
                             s[s.index("."):]) if "." in s else (s, ""))

                A, B = conditional_split(dimA), conditional_split(dimB)
                maxlen = (max(len(A[0]), len(B[0])), max(len(A[1]), len(B[1])))
                return (
                    A[0].rjust(maxlen[0]) + A[1].ljust(maxlen[1]),
                    B[0].rjust(maxlen[0]) + B[1].ljust(maxlen[1]),
                )

            formatted_cell_esds = tuple(
                format_float_with_standard_uncertainty(v, sd)
                for v, sd in zip(cell, cell_esd))
            formatted_rows = (formatted_cell_esds[0:3],
                              formatted_cell_esds[3:6])
            formatted_rows = zip(*(match_formatting(l, a)
                                   for l, a in zip(*formatted_rows)))
            result += "Unit cell (with estimated std devs):\n"
            result += "%s %s %s\n%s %s %s\n" % (formatted_rows[0] +
                                                formatted_rows[1])
        else:
            result += "Unit cell:\n"
            result += "%7.3f %7.3f %7.3f\n%7.3f %7.3f %7.3f\n" % tuple(cell)

        # now, use this information and the sequence (if provided)
        # and also matthews_coef (should I be using this directly, here?)
        # to compute a likely number of molecules in the ASU and also
        # the solvent content...

        if self._aa_sequence:
            residues = self._aa_sequence.get_sequence()
            if residues:
                nres = len(residues)

                # first compute the number of molecules using the K&R
                # method

                nmol = compute_nmol(
                    cell[0],
                    cell[1],
                    cell[2],
                    cell[3],
                    cell[4],
                    cell[5],
                    spacegroup,
                    resolution,
                    nres,
                )

                # then compute the solvent fraction

                solvent = compute_solvent(
                    cell[0],
                    cell[1],
                    cell[2],
                    cell[3],
                    cell[4],
                    cell[5],
                    spacegroup,
                    nmol,
                    nres,
                )

                result += "Likely number of molecules in ASU: %d\n" % nmol
                result += "Giving solvent fraction:        %4.2f\n" % solvent

                self._nmol = nmol

        if isinstance(reflections_all, type({})):
            for format in reflections_all.keys():
                result += "%s format:\n" % format
                reflections = reflections_all[format]

                if isinstance(reflections, type({})):
                    for wavelength in reflections.keys():
                        target = FileHandler.get_data_file(
                            reflections[wavelength])
                        result += "Scaled reflections (%s): %s\n" % (
                            wavelength, target)

                else:
                    target = FileHandler.get_data_file(reflections)
                    result += "Scaled reflections: %s\n" % target

        CIF.write_cif()
        mmCIF.write_cif()

        return result

    def summarise(self):
        """Produce a short summary of this crystal."""

        summary = ["Crystal: %s" % self._name]

        if self._aa_sequence:
            summary.append("Sequence length: %d" %
                           len(self._aa_sequence.get_sequence()))

        for wavelength in self._wavelengths.keys():
            for record in self._wavelengths[wavelength].summarise():
                summary.append(record)

        statistics_all = self._get_scaler().get_scaler_statistics()

        for key in statistics_all:
            pname, xname, dname = key

            summary.append("For %s/%s/%s:" % key)
            available = statistics_all[key].keys()

            stats = []
            keys = [
                "High resolution limit",
                "Low resolution limit",
                "Completeness",
                "Multiplicity",
                "I/sigma",
                "Rmerge(I+/-)",
                "CC half",
                "Anomalous completeness",
                "Anomalous multiplicity",
            ]

            for k in keys:
                if k in available:
                    stats.append(k)

            for s in stats:
                format_str = formats[s]
                if isinstance(statistics_all[key][s], float):
                    expanded_format_str = " ".join(
                        [format_str] + [format_str.strip()] *
                        (len(statistics_all[key][s]) - 1))
                    summary.append("%s: " % (s.ljust(40)) +
                                   expanded_format_str %
                                   (statistics_all[key][s]))
                elif isinstance(statistics_all[key][s], basestring):
                    summary.append("%s: %s" %
                                   (s.ljust(40), statistics_all[key][s]))
                else:
                    expanded_format_str = " ".join(
                        [format_str] + [format_str.strip()] *
                        (len(statistics_all[key][s]) - 1))
                    summary.append("%s " % s.ljust(43) + expanded_format_str %
                                   tuple(statistics_all[key][s]))

        cell = self._get_scaler().get_scaler_cell()
        spacegroup = self._get_scaler().get_scaler_likely_spacegroups()[0]

        summary.append("Cell: %7.3f %7.3f %7.3f %7.3f %7.3f %7.3f" %
                       tuple(cell))
        summary.append("Spacegroup: %s" % spacegroup)

        return summary

    def set_reference_reflection_file(self, reference_reflection_file):
        """Set a reference reflection file to use to standardise the
        setting, FreeR etc."""

        # check here it is an MTZ file

        self._reference_reflection_file = reference_reflection_file

    def set_freer_file(self, freer_file):
        """Set a FreeR column file to use to standardise the FreeR column."""
        self._freer_file = freer_file

    def set_user_spacegroup(self, user_spacegroup):
        """Set a user assigned spacegroup - which needs to be propogated."""
        self._user_spacegroup = user_spacegroup

    def get_user_spacegroup(self):
        return self._user_spacegroup

    def get_reference_reflection_file(self):
        return self._reference_reflection_file

    def get_freer_file(self):
        return self._freer_file

    def set_scaled_merged_reflections(self, scaled_merged_reflections):
        self._scaled_merged_reflections = scaled_merged_reflections

    def get_project(self):
        return self._project

    def get_name(self):
        return self._name

    def get_aa_sequence(self):
        return self._aa_sequence

    def set_aa_sequence(self, aa_sequence):
        if not self._aa_sequence:
            self._aa_sequence = _aa_sequence(aa_sequence)
        else:
            self._aa_sequence.set_sequence(aa_sequence)

    def get_ha_info(self):
        return self._ha_info

    def set_ha_info(self, ha_info_dict):
        self._anomalous = True

        atom = ha_info_dict["atom"]

        if atom in self._ha_info:
            # update this description
            if "number_per_monomer" in ha_info_dict:
                self._ha_info[atom].set_number_per_monomer(
                    ha_info_dict["number_per_monomer"])
            if "number_total" in ha_info_dict:
                self._ha_info[atom].set_number_total(
                    ha_info_dict["number_total"])

        else:
            # implant a new atom
            self._ha_info[atom] = _ha_info(atom)
            if "number_per_monomer" in ha_info_dict:
                self._ha_info[atom].set_number_per_monomer(
                    ha_info_dict["number_per_monomer"])
            if "number_total" in ha_info_dict:
                self._ha_info[atom].set_number_total(
                    ha_info_dict["number_total"])

    def get_wavelength_names(self):
        """Get a list of wavelengths belonging to this crystal."""
        return sorted(self._wavelengths)

    def get_xwavelength(self, wavelength_name):
        """Get a named xwavelength object back."""
        return self._wavelengths[wavelength_name]

    def add_wavelength(self, xwavelength):
        if xwavelength.__class__.__name__ != "XWavelength":
            raise RuntimeError("input should be an XWavelength object")

        if xwavelength.get_name() in self._wavelengths.keys():
            raise RuntimeError("XWavelength with name %s already exists" %
                               xwavelength.get_name())

        self._wavelengths[xwavelength.get_name()] = xwavelength

        # bug # 2326 - need to decide when we're anomalous
        if len(self._wavelengths.keys()) > 1:
            self._anomalous = True

        if xwavelength.get_f_pr() != 0.0 or xwavelength.get_f_prpr() != 0.0:
            self._anomalous = True

    def get_xsample(self, sample_name):
        """Get a named xsample object back."""
        return self._samples[sample_name]

    def add_sample(self, xsample):

        if xsample.__class__.__name__ != "XSample":
            raise RuntimeError("input should be an XSample object")

        if xsample.get_name() in self._samples.keys():
            raise RuntimeError("XSample with name %s already exists" %
                               xsample.get_name())

        self._samples[xsample.get_name()] = xsample

    def remove_sweep(self, s):
        """Find and remove the sweep s from this crystal."""

        for wave in self._wavelengths.keys():
            self._wavelengths[wave].remove_sweep(s)

    def _get_integraters(self):
        integraters = []

        for wave in self._wavelengths.keys():
            for i in self._wavelengths[wave]._get_integraters():
                integraters.append(i)

        return integraters

    def _get_indexers(self):
        indexers = []

        for wave in self._wavelengths.keys():
            for i in self._wavelengths[wave]._get_indexers():
                indexers.append(i)

        return indexers

    def get_all_image_names(self):
        """Get a full list of all images from this crystal..."""

        # for RD analysis ...

        result = []
        for wavelength in self._wavelengths.keys():
            result.extend(self._wavelengths[wavelength].get_all_image_names())
        return result

    def set_lattice(self, lattice, cell):
        """Configure the cell - if it is already set, then manage this
        carefully..."""

        # FIXME this should also verify that the cell for the provided
        # lattice exactly matches the limitations provided in IUCR
        # tables A.

        if self._lattice_manager:
            self._update_lattice(lattice, cell)
        else:
            self._lattice_manager = _lattice_manager(lattice, cell)

    def _update_lattice(self, lattice, cell):
        """Inspect the available lattices and see if this matches
        one of them..."""

        # FIXME need to think in here in terms of the lattice
        # being higher than the current one...
        # though that shouldn't happen, because if this is the
        # next processing, this should have taken the top
        # lattice supplied earler as input...

        while lattice != self._lattice_manager.get_lattice()["lattice"]:
            self._lattice_manager.kill_lattice()

        # this should now point to the correct lattice class...
        # check that the unit cell matches reasonably well...

        cell_orig = self._lattice_manager.get_lattice()["cell"]

        dist = 0.0

        for j in range(6):
            dist += math.fabs(cell_orig[j] - cell[j])

        # allow average of 1 degree, 1 angstrom
        if dist > 6.0:
            raise RuntimeError("new lattice incompatible: %s vs. %s" % (
                "[%6.2f, %6.2f, %6.2f, %6.2f, %6.2f, %6.2f]" % tuple(cell),
                "[%6.2f, %6.2f, %6.2f, %6.2f, %6.2f, %6.2f]" %
                tuple(cell_orig),
            ))

        # if we reach here we're satisfied that the new lattice matches...
        # FIXME write out some messages here to Chatter.

    def get_lattice(self):
        if self._lattice_manager:
            return self._lattice_manager.get_lattice()

        return None

    def set_anomalous(self, anomalous=True):
        self._anomalous = anomalous

    def get_anomalous(self):
        return self._anomalous

    # "power" methods - now where these actually perform some real calculations
    # to get some real information out - beware, this will actually run
    # programs...

    def get_scaled_merged_reflections(self):
        """Return a reflection file (or files) containing all of the
        merged reflections for this XCrystal."""

        return self._get_scaler().get_scaled_merged_reflections()

    def get_scaled_reflections(self, format):
        """Get specific reflection files."""

        return self._get_scaler().get_scaled_reflections(format)

    def get_cell(self):
        """Get the final unit cell from scaling."""
        return self._get_scaler().get_scaler_cell()

    def get_likely_spacegroups(self):
        """Get the list if likely spacegroups from the scaling."""
        return self._get_scaler().get_scaler_likely_spacegroups()

    def get_statistics(self):
        """Get the scaling statistics for this sample."""
        return self._get_scaler().get_scaler_statistics()

    def _get_scaler(self):
        if self._scaler is None:

            # in here check if
            #
            # (1) self._scaled_merged_reflections is set and
            # (2) there is no sweep information
            #
            # if both of these are true then produce a null scaler
            # which will wrap this information

            from libtbx import Auto

            scale_dir = PhilIndex.params.xia2.settings.scale.directory
            if scale_dir is Auto:
                scale_dir = "scale"
            working_directory = Environment.generate_directory(
                [self._name, scale_dir])

            self._scaler = Scaler()

            # put an inverse link in place... to support RD analysis
            # involved change to Scaler interface definition

            self._scaler.set_scaler_xcrystal(self)

            if self._anomalous:
                self._scaler.set_scaler_anomalous(True)

            # set up a sensible working directory
            self._scaler.set_working_directory(working_directory)

            # set the reference reflection file, if we have one...
            if self._reference_reflection_file:
                self._scaler.set_scaler_reference_reflection_file(
                    self._reference_reflection_file)

            # and FreeR file
            if self._freer_file:
                self._scaler.set_scaler_freer_file(self._freer_file)

            # and spacegroup information
            if self._user_spacegroup:
                # compute the lattice and pointgroup from this...

                pointgroup = Syminfo.get_pointgroup(self._user_spacegroup)

                self._scaler.set_scaler_input_spacegroup(self._user_spacegroup)
                self._scaler.set_scaler_input_pointgroup(pointgroup)

            integraters = self._get_integraters()

            # then feed them to the scaler

            for i in integraters:
                self._scaler.add_scaler_integrater(i)

        return self._scaler

    def serialize(self):
        scaler = self._get_scaler()
        if scaler.get_scaler_finish_done():
            scaler.as_json(filename=os.path.join(
                scaler.get_working_directory(), "xia2.json"))
예제 #4
0
파일: XCrystal.py 프로젝트: xia2/xia2
class XCrystal(object):
  '''An object to maintain all of the information about a crystal. This
  will contain the experimental information in XWavelength objects,
  and also amino acid sequence, heavy atom information.'''

  def __init__(self, name, project):

    self._name = name

    # separate out the anomalous pairs or merge them together?
    self._anomalous = False

    # FIXME check that project is an XProject
    self._project = project

    # these should be populated with the objects defined above
    self._aa_sequence = None

    # note that I am making allowances for > 1 heavy atom class...
    # FIXME 18/SEP/06 these should be in a dictionary which is keyed
    # by the element name...
    self._ha_info = {}
    self._wavelengths = {}
    self._samples = {}
    self._lattice_manager = None

    # hooks to dangle command interfaces from

    self._scaler = None

    self._refiner = None

    # things to store input reflections which are used to define
    # the setting... this will be passed into the Scaler if
    # defined... likewise the FreeR column file
    self._reference_reflection_file = None
    self._freer_file = None
    self._user_spacegroup = None

    # things to help the great passing on of information
    self._scaled_merged_reflections = None

    # derived information
    self._nmol = 1

  # serialization functions

  def to_dict(self):
    obj = {}
    obj['__id__'] = 'XCrystal'
    import inspect
    attributes = inspect.getmembers(self, lambda m:not(inspect.isroutine(m)))
    for a in attributes:
      if a[0] == '_scaler' and a[1] is not None:
        obj[a[0]] = a[1].to_dict()
      elif a[0] == '_wavelengths':
        wavs = {}
        for wname, wav in a[1].iteritems():
          wavs[wname] = wav.to_dict()
        obj[a[0]] = wavs
      elif a[0] == '_samples':
        samples = {}
        for sname, sample in a[1].iteritems():
          samples[sname] = sample.to_dict()
        obj[a[0]] = samples
      elif a[0] == '_project':
        # don't serialize this since the parent xproject *should* contain
        # the pointer to the child xcrystal
        continue
      elif a[0] == '_aa_sequence' and a[1] is not None:
        obj[a[0]] = a[1].to_dict()
      elif a[0] == '_ha_info' and a[1] is not None:
        d = {}
        for k, v in a[1].iteritems():
          d[k] = v.to_dict()
        obj[a[0]] = d
      elif a[0].startswith('__'):
        continue
      else:
        obj[a[0]] = a[1]
    return obj

  @classmethod
  def from_dict(cls, obj):
    from xia2.Schema.XWavelength import XWavelength
    from xia2.Schema.XSample import XSample
    assert obj['__id__'] == 'XCrystal'
    return_obj = cls(name=None, project=None)
    for k, v in obj.iteritems():
      if k == '_scaler' and v is not None:
        from libtbx.utils import import_python_object
        cls = import_python_object(
          import_path=".".join((v['__module__'], v['__name__'])),
          error_prefix='', target_must_be='', where_str='').object
        v = cls.from_dict(v)
        v._scalr_xcrystal = return_obj
      elif k == '_wavelengths':
        v_ = {}
        for wname, wdict in v.iteritems():
          wav = XWavelength.from_dict(wdict)
          wav._crystal = return_obj
          v_[wname] = wav
        v = v_
      elif k == '_samples':
        v_ = {}
        for sname, sdict in v.iteritems():
          sample = XSample.from_dict(sdict)
          sample._crystal = return_obj
          v_[sname] = sample
        v = v_
      elif k == '_aa_sequence' and v is not None:
        v = _aa_sequence.from_dict(v)
      elif k == '_ha_info' and v is not None:
        for k_, v_ in v.iteritems():
          v[k_] = _ha_info.from_dict(v_)
      setattr(return_obj, k, v)
    sweep_dict = {}
    for sample in return_obj._samples.values():
      for i, sname in enumerate(sample._sweeps):
        found_sweep = False
        for wav in return_obj._wavelengths.values():
          if found_sweep: break
          for sweep in wav._sweeps:
            if sweep.get_name() == sname:
              sample._sweeps[i] = sweep
              sweep._sample = sample
              found_sweep = True
              break
      for s in sample._sweeps: assert not isinstance(s, basestring)
    if return_obj._scaler is not None:
      for intgr in return_obj._get_integraters():
        return_obj._scaler._scalr_integraters[intgr.get_integrater_epoch()] \
          = intgr
        if (hasattr(return_obj._scaler, '_sweep_handler') and
            return_obj._scaler._sweep_handler is not None):
          if intgr.get_integrater_epoch() in \
             return_obj._scaler._sweep_handler._sweep_information:
            return_obj._scaler._sweep_handler._sweep_information[
              intgr.get_integrater_epoch()]._integrater = intgr
    return return_obj

  def get_output(self):

    result = 'Crystal: %s\n' % self._name

    if self._aa_sequence:
      result += 'Sequence: %s\n' % self._aa_sequence.get_sequence()
    for wavelength in self._wavelengths.keys():
      result += self._wavelengths[wavelength].get_output()

    scaler = self._get_scaler()
    if scaler.get_scaler_finish_done():
      for wname, xwav in self._wavelengths.iteritems():
        for xsweep in xwav.get_sweeps():
          idxr = xsweep._get_indexer()
          if PhilIndex.params.xia2.settings.show_template:
            result += '%s\n' %banner('Autoindexing %s (%s)' %(
              idxr.get_indexer_sweep_name(), idxr.get_template()))
          else:
            result += '%s\n' %banner(
              'Autoindexing %s' %idxr.get_indexer_sweep_name())
          result += '%s\n' %idxr.show_indexer_solutions()

          intgr = xsweep._get_integrater()
          if PhilIndex.params.xia2.settings.show_template:
            result += '%s\n' %banner('Integrating %s (%s)' %(
              intgr.get_integrater_sweep_name(), intgr.get_template()))
          else:
            result += '%s\n' %banner(
              'Integrating %s' %intgr.get_integrater_sweep_name())
          result += '%s\n' % intgr.show_per_image_statistics()

      result += '%s\n' %banner('Scaling %s' %self.get_name())

      for (dname, sname), (limit, suggested) in scaler.get_scaler_resolution_limits().iteritems():
        if suggested is None or limit == suggested:
          result += 'Resolution limit for %s/%s: %5.2f\n' %(dname, sname, limit)
        else:
          result += 'Resolution limit for %s/%s: %5.2f (%5.2f suggested)\n' %(dname, sname, limit, suggested)

    # this is now deprecated - be explicit in what you are
    # asking for...
    reflections_all = self.get_scaled_merged_reflections()
    statistics_all = self._get_scaler().get_scaler_statistics()

    # print some of these statistics, perhaps?

    for key in statistics_all.keys():
      result += format_statistics(statistics_all[key], caption='For %s/%s/%s' % key)

    # then print out some "derived" information based on the
    # scaling - this is presented through the Scaler interface
    # explicitly...

    cell = self._get_scaler().get_scaler_cell()
    cell_esd = self._get_scaler().get_scaler_cell_esd()
    spacegroups = self._get_scaler().get_scaler_likely_spacegroups()

    spacegroup = spacegroups[0]
    resolution = self._get_scaler().get_scaler_highest_resolution()

    from cctbx import sgtbx
    sg = sgtbx.space_group_type(str(spacegroup))
    spacegroup = sg.lookup_symbol()
    CIF.set_spacegroup(sg)
    mmCIF.set_spacegroup(sg)

    if len(self._wavelengths) == 1:
      CIF.set_wavelengths([w.get_wavelength() for w in self._wavelengths.itervalues()])
      mmCIF.set_wavelengths([w.get_wavelength() for w in self._wavelengths.itervalues()])
    else:
      for wavelength in self._wavelengths.keys():
        full_wave_name = '%s_%s_%s' % (self._project._name, self._name, wavelength)
        CIF.get_block(full_wave_name)['_diffrn_radiation_wavelength'] = \
          self._wavelengths[wavelength].get_wavelength()
        mmCIF.get_block(full_wave_name)['_diffrn_radiation_wavelength'] = \
          self._wavelengths[wavelength].get_wavelength()
      CIF.set_wavelengths({name: wave.get_wavelength() for name, wave in self._wavelengths.iteritems()})
      mmCIF.set_wavelengths({name: wave.get_wavelength() for name, wave in self._wavelengths.iteritems()})

    result += 'Assuming spacegroup: %s\n' % spacegroup
    if len(spacegroups) > 1:
      result += 'Other likely alternatives are:\n'
      for sg in spacegroups[1:]:
        result += '%s\n' % sg

    if cell_esd:
      from libtbx.utils import format_float_with_standard_uncertainty
      def match_formatting(dimA, dimB):
        def conditional_split(s):
          return (s[:s.index('.')],s[s.index('.'):]) if '.' in s else (s, '')
        A, B = conditional_split(dimA), conditional_split(dimB)
        maxlen = (max(len(A[0]), len(B[0])), max(len(A[1]), len(B[1])))
        return (
          A[0].rjust(maxlen[0])+A[1].ljust(maxlen[1]),
          B[0].rjust(maxlen[0])+B[1].ljust(maxlen[1])
        )
      formatted_cell_esds = tuple(format_float_with_standard_uncertainty(v, sd) for v, sd in zip(cell, cell_esd))
      formatted_rows = (formatted_cell_esds[0:3], formatted_cell_esds[3:6])
      formatted_rows = zip(*(match_formatting(l, a) for l, a in zip(*formatted_rows)))
      result += 'Unit cell (with estimated std devs):\n'
      result += '%s %s %s\n%s %s %s\n' % (formatted_rows[0] + formatted_rows[1])
    else:
      result += 'Unit cell:\n'
      result += '%7.3f %7.3f %7.3f\n%7.3f %7.3f %7.3f\n' % tuple(cell)

    # now, use this information and the sequence (if provided)
    # and also matthews_coef (should I be using this directly, here?)
    # to compute a likely number of molecules in the ASU and also
    # the solvent content...

    if self._aa_sequence:
      residues = self._aa_sequence.get_sequence()
      if residues:
        nres = len(residues)

        # first compute the number of molecules using the K&R
        # method

        nmol = compute_nmol(cell[0], cell[1], cell[2],
                            cell[3], cell[4], cell[5],
                            spacegroup, resolution, nres)

        # then compute the solvent fraction

        solvent = compute_solvent(cell[0], cell[1], cell[2],
                                  cell[3], cell[4], cell[5],
                                  spacegroup, nmol, nres)

        result += 'Likely number of molecules in ASU: %d\n' % nmol
        result += 'Giving solvent fraction:        %4.2f\n' % solvent

        self._nmol = nmol

    if type(reflections_all) == type({}):
      for format in reflections_all.keys():
        result += '%s format:\n' % format
        reflections = reflections_all[format]

        if type(reflections) == type({}):
          for wavelength in reflections.keys():
            target = FileHandler.get_data_file(
                reflections[wavelength])
            result += 'Scaled reflections (%s): %s\n' % \
                      (wavelength, target)

        else:
          target = FileHandler.get_data_file(
              reflections)
          result += 'Scaled reflections: %s\n' % target

    CIF.write_cif()
    mmCIF.write_cif()

    return result

  def summarise(self):
    '''Produce a short summary of this crystal.'''

    summary = ['Crystal: %s' % self._name]

    if self._aa_sequence:
      summary.append('Sequence length: %d' % \
                     len(self._aa_sequence.get_sequence()))

    for wavelength in self._wavelengths.keys():
      for record in self._wavelengths[wavelength].summarise():
        summary.append(record)

    statistics_all = self._get_scaler().get_scaler_statistics()

    for key in statistics_all:
      pname, xname, dname = key

      summary.append('For %s/%s/%s:' % key)
      available = statistics_all[key].keys()

      stats = []
      keys = [
        'High resolution limit',
        'Low resolution limit',
        'Completeness',
        'Multiplicity',
        'I/sigma',
        'Rmerge(I+/-)',
        'CC half',
        'Anomalous completeness',
        'Anomalous multiplicity']

      for k in keys:
        if k in available:
          stats.append(k)

      for s in stats:
        format_str = formats[s]
        if isinstance(statistics_all[key][s], float):
          expanded_format_str = " ".join([format_str] + [format_str.strip()] * (len(statistics_all[key][s])-1))
          summary.append(
            '%s: ' %(s.ljust(40)) + expanded_format_str % (statistics_all[key][s]))
        elif isinstance(statistics_all[key][s], basestring):
          summary.append(
            '%s: %s' % (s.ljust(40), statistics_all[key][s]))
        else:
          expanded_format_str = " ".join([format_str] + [format_str.strip()] * (len(statistics_all[key][s])-1))
          summary.append(
            '%s ' % s.ljust(43) + expanded_format_str % tuple(statistics_all[key][s]))

    cell = self._get_scaler().get_scaler_cell()
    spacegroup = self._get_scaler().get_scaler_likely_spacegroups()[0]

    summary.append('Cell: %7.3f %7.3f %7.3f %7.3f %7.3f %7.3f' % \
                   tuple(cell))
    summary.append('Spacegroup: %s' % spacegroup)

    return summary

  def set_reference_reflection_file(self, reference_reflection_file):
    '''Set a reference reflection file to use to standardise the
    setting, FreeR etc.'''

    # check here it is an MTZ file

    self._reference_reflection_file = reference_reflection_file

  def set_freer_file(self, freer_file):
    '''Set a FreeR column file to use to standardise the FreeR column.'''
    self._freer_file = freer_file

  def set_user_spacegroup(self, user_spacegroup):
    '''Set a user assigned spacegroup - which needs to be propogated.'''
    self._user_spacegroup = user_spacegroup

  def get_user_spacegroup(self):
    return self._user_spacegroup

  def get_reference_reflection_file(self):
    return self._reference_reflection_file

  def get_freer_file(self):
    return self._freer_file

  def set_scaled_merged_reflections(self, scaled_merged_reflections):
    self._scaled_merged_reflections = scaled_merged_reflections

  def get_project(self):
    return self._project

  def get_name(self):
    return self._name

  def get_aa_sequence(self):
    return self._aa_sequence

  def set_aa_sequence(self, aa_sequence):
    if not self._aa_sequence:
      self._aa_sequence = _aa_sequence(aa_sequence)
    else:
      self._aa_sequence.set_sequence(aa_sequence)

  def get_ha_info(self):
    return self._ha_info

  def set_ha_info(self, ha_info_dict):
    # FIXED I need to decide how to implement this...
    # do so from the dictionary...

    # bug # 2326 - need to decide when we're anomalous
    self._anomalous = True

    atom = ha_info_dict['atom']

    if atom in self._ha_info:
      # update this description
      if 'number_per_monomer' in ha_info_dict:
        self._ha_info[atom].set_number_per_monomer(
            ha_info_dict['number_per_monomer'])
      if 'number_total' in ha_info_dict:
        self._ha_info[atom].set_number_total(
            ha_info_dict['number_total'])

    else:
      # implant a new atom
      self._ha_info[atom] = _ha_info(atom)
      if 'number_per_monomer' in ha_info_dict:
        self._ha_info[atom].set_number_per_monomer(
            ha_info_dict['number_per_monomer'])
      if 'number_total' in ha_info_dict:
        self._ha_info[atom].set_number_total(
            ha_info_dict['number_total'])


  def get_wavelength_names(self):
    '''Get a list of wavelengths belonging to this crystal.'''
    return sorted(self._wavelengths)

  def get_xwavelength(self, wavelength_name):
    '''Get a named xwavelength object back.'''
    return self._wavelengths[wavelength_name]

  def add_wavelength(self, xwavelength):
    if xwavelength.__class__.__name__ != 'XWavelength':
      raise RuntimeError, 'input should be an XWavelength object'

    if xwavelength.get_name() in self._wavelengths.keys():
      raise RuntimeError, 'XWavelength with name %s already exists' % \
            xwavelength.get_name()

    self._wavelengths[xwavelength.get_name()] = xwavelength

    # bug # 2326 - need to decide when we're anomalous
    if len(self._wavelengths.keys()) > 1:
      self._anomalous = True

    if xwavelength.get_f_pr() != 0.0 or xwavelength.get_f_prpr() != 0.0:
      self._anomalous = True


  def get_xsample(self, sample_name):
    '''Get a named xsample object back.'''
    return self._samples[sample_name]

  def add_sample(self, xsample):

    if xsample.__class__.__name__ != 'XSample':
      raise RuntimeError, 'input should be an XSample object'

    if xsample.get_name() in self._samples.keys():
      raise RuntimeError, 'XSample with name %s already exists' % \
            xsample.get_name()

    self._samples[xsample.get_name()] = xsample


  def remove_sweep(self, s):
    '''Find and remove the sweep s from this crystal.'''

    for wave in self._wavelengths.keys():
      self._wavelengths[wave].remove_sweep(s)


  def _get_integraters(self):
    integraters = []

    for wave in self._wavelengths.keys():
      for i in self._wavelengths[wave]._get_integraters():
        integraters.append(i)

    return integraters

  def _get_indexers(self):
    indexers = []

    for wave in self._wavelengths.keys():
      for i in self._wavelengths[wave]._get_indexers():
        indexers.append(i)

    return indexers

  def get_all_image_names(self):
    '''Get a full list of all images from this crystal...'''

    # for RD analysis ...

    result = []
    for wavelength in self._wavelengths.keys():
      result.extend(self._wavelengths[wavelength].get_all_image_names())
    return result

  def set_lattice(self, lattice, cell):
    '''Configure the cell - if it is already set, then manage this
    carefully...'''

    # FIXME this should also verify that the cell for the provided
    # lattice exactly matches the limitations provided in IUCR
    # tables A.

    if self._lattice_manager:
      self._update_lattice(lattice, cell)
    else:
      self._lattice_manager = _lattice_manager(lattice, cell)

  def _update_lattice(self, lattice, cell):
    '''Inspect the available lattices and see if this matches
    one of them...'''

    # FIXME need to think in here in terms of the lattice
    # being higher than the current one...
    # though that shouldn't happen, because if this is the
    # next processing, this should have taken the top
    # lattice supplied earler as input...

    while lattice != self._lattice_manager.get_lattice()['lattice']:
      self._lattice_manager.kill_lattice()

    # this should now point to the correct lattice class...
    # check that the unit cell matches reasonably well...

    cell_orig = self._lattice_manager.get_lattice()['cell']

    dist = 0.0

    for j in range(6):
      dist += math.fabs(cell_orig[j] - cell[j])

    # allow average of 1 degree, 1 angstrom
    if dist > 6.0:
      raise RuntimeError, 'new lattice incompatible: %s vs. %s' % \
            ('[%6.2f, %6.2f, %6.2f, %6.2f, %6.2f, %6.2f]' % \
             tuple(cell),
             '[%6.2f, %6.2f, %6.2f, %6.2f, %6.2f, %6.2f]' % \
             tuple(cell_orig))

    # if we reach here we're satisfied that the new lattice matches...
    # FIXME write out some messages here to Chatter.

  def get_lattice(self):
    if self._lattice_manager:
      return self._lattice_manager.get_lattice()

    return None

  def set_anomalous(self, anomalous=True):
    self._anomalous = anomalous

  def get_anomalous(self):
    return self._anomalous

  # "power" methods - now where these actually perform some real calculations
  # to get some real information out - beware, this will actually run
  # programs...

  def get_scaled_merged_reflections(self):
    '''Return a reflection file (or files) containing all of the
    merged reflections for this XCrystal.'''

    return self._get_scaler().get_scaled_merged_reflections()

  def get_scaled_reflections(self, format):
    '''Get specific reflection files.'''

    return self._get_scaler().get_scaled_reflections(format)

  def get_cell(self):
    '''Get the final unit cell from scaling.'''
    return self._get_scaler().get_scaler_cell()

  def get_likely_spacegroups(self):
    '''Get the list if likely spacegroups from the scaling.'''
    return self._get_scaler().get_scaler_likely_spacegroups()

  def get_statistics(self):
    '''Get the scaling statistics for this sample.'''
    return self._get_scaler().get_scaler_statistics()

  def _get_scaler(self):
    if self._scaler is None:

      # in here check if
      #
      # (1) self._scaled_merged_reflections is set and
      # (2) there is no sweep information
      #
      # if both of these are true then produce a null scaler
      # which will wrap this information

      from libtbx import Auto
      scale_dir = PhilIndex.params.xia2.settings.scale.directory
      if scale_dir is Auto:
        scale_dir = 'scale'
      working_directory = Environment.generate_directory([self._name, scale_dir])

      self._scaler = Scaler()

      # put an inverse link in place... to support RD analysis
      # involved change to Scaler interface definition

      self._scaler.set_scaler_xcrystal(self)

      if self._anomalous:
        self._scaler.set_scaler_anomalous(True)

      # set up a sensible working directory
      self._scaler.set_working_directory(working_directory)

      # set the reference reflection file, if we have one...
      if self._reference_reflection_file:
        self._scaler.set_scaler_reference_reflection_file(
            self._reference_reflection_file)

      # and FreeR file
      if self._freer_file:
        self._scaler.set_scaler_freer_file(self._freer_file)

      # and spacegroup information
      if self._user_spacegroup:
        # compute the lattice and pointgroup from this...

        pointgroup = Syminfo.get_pointgroup(self._user_spacegroup)

        self._scaler.set_scaler_input_spacegroup(
            self._user_spacegroup)
        self._scaler.set_scaler_input_pointgroup(pointgroup)

      integraters = self._get_integraters()

      # then feed them to the scaler

      for i in integraters:
        self._scaler.add_scaler_integrater(i)

    return self._scaler

  def serialize(self):
    scaler = self._get_scaler()
    if scaler.get_scaler_finish_done():
      scaler.as_json(
        filename=os.path.join(scaler.get_working_directory(), "xia2.json"))