def PyExec(self):
        """ Alg execution. """
        instrument = self.getProperty(INSTRUMENT_PROP).value
        run_number = self.getProperty(RUN_NUM_PROP).value
        fit_deadtime = self.getProperty(FIT_DEADTIME_PROP).value
        fix_phases = self.getProperty(FIX_PHASES_PROP).value
        default_level = self.getProperty(DEFAULT_LEVEL).value
        sigma_looseness = self.getProperty(SIGMA_LOOSENESS_PROP).value
        groupings_file = self.getProperty(GROUPINGS_PROP).value
        in_phases_file = self.getProperty(PHASES_PROP).value
        in_deadtimes_file = self.getProperty(DEADTIMES_PROP).value
        out_phases_file = self.getProperty(PHASES_RESULT_PROP).value
        out_deadtimes_file = self.getProperty(DEADTIMES_RESULT_PROP).value

        isis = config.getFacility('ISIS')
        padding = isis.instrument(instrument).zeroPadding(0)
        run_name = instrument + str(run_number).zfill(padding)

        try:
            run_number = int(run_number)
        except:
            raise RuntimeError("'%s' is not an integer run number." %
                               run_number)
        try:
            run_file_path = FileFinder.findRuns(run_name)[0]
        except:
            raise RuntimeError("Unable to find file for run %i" % run_number)

        if groupings_file == "":
            groupings_file = DEFAULT_GROUPINGS_FILENAME % instrument

        # Load data and other info from input files.

        def temp_hidden_ws_name():
            """Generate a unique name for a temporary, hidden workspace."""
            selection = string.ascii_lowercase + string.ascii_uppercase + string.digits
            return '__temp_MaxEnt_' + ''.join(
                random.choice(selection) for _ in range(20))

        input_data_ws_name = temp_hidden_ws_name()
        LoadMuonNexus(Filename=run_file_path,
                      OutputWorkspace=input_data_ws_name)
        input_data_ws = mtd[input_data_ws_name]

        if isinstance(input_data_ws, WorkspaceGroup):
            Logger.get("MaxEnt").warning(
                "Multi-period data is not currently supported.  Just using first period."
            )
            input_data_ws = input_data_ws[0]

        groupings_ws_name = temp_hidden_ws_name()
        LoadDetectorsGroupingFile(InputFile=groupings_file,
                                  OutputWorkspace=groupings_ws_name)
        groupings_ws = mtd[groupings_ws_name]

        def yield_floats_from_file(path):
            """Given a path to a file with a float on each line, will return
            the floats one at a time.  Throws otherwise.  Strips whitespace
            and ignores empty lines."""
            with open(path, 'r') as f:
                for i, line in enumerate(line.strip() for line in f):
                    if line == "":
                        continue
                    try:
                        yield float(line)
                    except:
                        raise RuntimeError(
                            "Parsing error in '%s': Line %d: '%s'." %
                            (path, i, line))

        input_phases = np.array(list(yield_floats_from_file(in_phases_file)))
        input_phases_size = len(input_phases)
        input_deadtimes = np.array(
            list(yield_floats_from_file(in_deadtimes_file)))
        input_deadtimes_size = len(input_deadtimes)

        n_bins = input_data_ws.blocksize()
        n_detectors = input_data_ws.getNumberHistograms()

        def time_value_to_time_channel_index(value):
            """Given a time value, will return the index of the time channel in
            which the value falls."""
            bin_width = input_data_ws.readX(0)[1] - input_data_ws.readX(0)[0]
            diff = value - input_data_ws.readX(0)[0]
            return int(diff / bin_width)

        # Mantid corrects for time zero on loading, so we want to find the actual channels
        # where 0.0 occurs, and where we have values of 0.1 onwards.
        time_zero_channel = time_value_to_time_channel_index(0.0)
        first_good_channel = time_value_to_time_channel_index(0.1)

        input_data = np.concatenate(
            [input_data_ws.readY(i) for i in range(n_detectors)])

        groupings = [
            groupings_ws.readY(row)[0]
            for row in range(groupings_ws.getNumberHistograms())
        ]
        groupings = map(int, groupings)
        n_groups = len(set(groupings))

        # Cleanup.

        input_data_ws.delete()
        groupings_ws.delete()

        # We're faced with the problem of providing more than a dozen parameters to
        # the Fortran, which can be a bit messy (especially on the Fortran side of
        # things where we need to make "Cf2py" declarations).  A cleaner way of
        # doing this is to simply pass in a few callbacks -- one for each input
        # type -- and have the Fortran provide the name of the variable it wants
        # to the callback.  The callback will then look up the corresponding value
        # and feed it back to the Fortran.
        #
        # We also have a callback for printing to the results log.

        self.int_vars = {
            "RunNo": run_number,
            "frames": FRAMES,
            "res": RES,
            "Tzeroch": time_zero_channel,
            "firstgoodch": first_good_channel,
            "ptstofit": POINTS_TO_FIT,
            "histolen": n_bins,
            "nhisto": n_detectors,
            "n_groups": n_groups,
        }

        self.float_vars = {
            "deflevel": default_level,
            "sigloose": sigma_looseness,
        }

        self.bool_vars = {
            "fixphase": fix_phases,
            "fitdt": fit_deadtime,
        }

        self._assert_map_values_are_of_expected_type()

        def lookup(par_name, par_map, default):
            """The basis of the callbacks passed to the Fortran.  Given a parameter
            name it will consult the appropriate variable map, and return the
            corresponding value of the parameter.  Else return a default and log a
            warning if a parameter with the name does not exist."""
            par_name = par_name.strip()
            if par_name in par_map:
                return par_map[par_name]
            msg = """WARNING: tried to find a value for parameter with name %s but
            could not find one.  Default of \"%s\" provided.""" % (par_name,
                                                                   default)
            Logger.get("MaxEnt").warning(msg)
            return default

        def log(priority, message):
            """Log the given message with given priority."""
            try:
                logger = getattr(Logger.get("MaxEnt"), priority.lower())
            except AttributeError:
                # If we don't recognise the priority, use warning() as a default.
                logger = getattr(Logger.get("MaxEnt"), "warning")
            logger(message)
            return True

        # The Fortran expects arrays to be of a certain size, so any arrays that
        # aren't big enough need to be padded.
        input_phases = self._pad_to_length_with_zeros(input_phases, MAX_HISTOS)
        input_deadtimes = self._pad_to_length_with_zeros(
            input_deadtimes, MAX_HISTOS)
        input_data = self._pad_to_length_with_zeros(input_data,
                                                    MAX_INPUT_DATA_SIZE)
        groupings = self._pad_to_length_with_zeros(groupings, MAX_HISTOS)

        # TODO: Return the contents of "NNNNN.max", instead of writing to file.
        f_out, fchan_out, output_deadtimes, output_phases, chi_sq = maxent.mantid_maxent(
            # Input data and other info:
            input_data,
            groupings,
            input_deadtimes,
            input_phases,
            # Variable-lookup callbacks:
            lambda par_name: lookup(par_name, self.int_vars, 0),
            lambda par_name: lookup(par_name, self.float_vars, 0.0),
            lambda par_name: lookup(par_name, self.bool_vars, False),
            # Callback for logging:
            log)

        def write_items_to_file(path, items):
            """Given a path to a file and a list of items, will write the items
            to the file, one on each line."""
            with open(path, 'w') as f:
                for item in items:
                    f.write(str(item) + "\n")

        # Chop the padded outputs back down to the correct size.
        output_phases = output_phases[:input_phases_size]
        output_deadtimes = output_deadtimes[:input_deadtimes_size]
        input_phases = input_phases[:input_phases_size]
        input_deadtimes = input_deadtimes[:input_deadtimes_size]
        fchan_out = fchan_out[:n_bins]
        f_out = f_out[:n_bins]

        write_items_to_file(out_phases_file, output_phases)
        write_items_to_file(out_deadtimes_file, output_deadtimes)

        log_output = "\nDead times in:\n" +  str(input_deadtimes) + "\n" +\
                     "\nDead times out:\n" + str(output_deadtimes) + "\n" +\
                     "\nPhases in:\n" +      str(input_phases) + "\n" +\
                     "\nPhases out:\n" +     str(output_phases) + "\n" + \
                     "\nGroupings:\n" +      str(groupings) + "\n" +\
                     "\nChi Squared:\n" +    str(chi_sq) + "\n" +\
                     "\nInput variables:\n"

        for type_map in self.int_vars, self.float_vars, self.bool_vars:
            for name, value in type_map.items():
                log_output += str(name) + " = " + str(value) + "\n"

        Logger.get("MaxEnt").notice(log_output)

        # Generate our own output ws name if the user has not provided one.
        out_ws_name = self.getPropertyValue(OUT_WS_PROP)
        if out_ws_name == "":
            out_ws_name = run_name + "; MaxEnt"
            self.setPropertyValue(OUT_WS_PROP, out_ws_name)

        out_ws = CreateWorkspace(OutputWorkspace=out_ws_name,
                                 DataX=fchan_out[:n_bins],
                                 DataY=f_out[:n_bins])
        self.setProperty(OUT_WS_PROP, out_ws)

        # MaxEnt inputs table.
        input_table_name = run_name + "; MaxEnt Input"
        input_table = CreateEmptyTableWorkspace(
            OutputWorkspace=input_table_name)
        input_table.addColumn("str", "Name")
        input_table.addColumn("str", "Value")
        inputs = itertools.chain(self.int_vars.items(),
                                 self.float_vars.items(),
                                 self.bool_vars.items())
        for name, value in inputs:
            input_table.addRow([str(name), str(value)])

        # Deadtimes and phases input/output table.
        dead_phases_table_name = run_name + "; MaxEnt Deadtimes & Phases"
        dead_phases_table = CreateEmptyTableWorkspace(
            OutputWorkspace=dead_phases_table_name)
        for column_name in "Deadtimes In", "Deadtimes Out", "Phases In", "Phases Out":
            dead_phases_table.addColumn("double", column_name)
        for row in zip(input_deadtimes, output_deadtimes, input_phases,
                       output_phases):
            dead_phases_table.addRow(list(map(float, row)))

        # Chi-squared output table.
        chisq_table_name = run_name + "; MaxEnt Chi^2"
        chisq_table = CreateEmptyTableWorkspace(
            OutputWorkspace=chisq_table_name)
        chisq_table.addColumn("int", "Cycle")
        for iteration in range(10):
            chisq_table.addColumn("double", "Iter " + str(iteration + 1))
        for cycle, data in enumerate(chi_sq):
            chisq_table.addRow([cycle + 1] + list(map(float, data)))

        all_output_ws = [
            input_table_name, dead_phases_table_name, chisq_table_name,
            out_ws_name
        ]

        # The output workspaces of this algorithm belong in the same groups
        # that are created by the muon interface.  If the appropriate group
        # doesn't exist already then it needs to be created.
        if not run_name in mtd:
            GroupWorkspaces(InputWorkspaces=all_output_ws,
                            OutputWorkspace=run_name)
        else:
            group = mtd[run_name]
            for output_ws in all_output_ws:
                if not group.contains(output_ws):
                    group.add(output_ws)

        out_ws.getAxis(0).getUnit().setLabel("Field", "G")
        out_ws.setYUnitLabel("P(B)")

        if INSIDE_MANTIDPLOT:
            mantidplot.plotSpectrum(out_ws, 0)
Beispiel #2
0
    def PyExec(self):
        """ Alg execution. """
        instrument         = self.getProperty(INSTRUMENT_PROP).value
        run_number         = self.getProperty(RUN_NUM_PROP).value
        fit_deadtime       = self.getProperty(FIT_DEADTIME_PROP).value
        fix_phases         = self.getProperty(FIX_PHASES_PROP).value
        default_level      = self.getProperty(DEFAULT_LEVEL).value
        sigma_looseness    = self.getProperty(SIGMA_LOOSENESS_PROP).value
        groupings_file     = self.getProperty(GROUPINGS_PROP).value
        in_phases_file     = self.getProperty(PHASES_PROP).value
        in_deadtimes_file  = self.getProperty(DEADTIMES_PROP).value
        out_phases_file    = self.getProperty(PHASES_RESULT_PROP).value
        out_deadtimes_file = self.getProperty(DEADTIMES_RESULT_PROP).value

        isis = config.getFacility('ISIS')
        padding = isis.instrument(instrument).zeroPadding(0)
        run_name = instrument + str(run_number).zfill(padding)

        try:
            run_number = int(run_number)
        except:
            raise RuntimeError("'%s' is not an integer run number." % run_number)
        try:
            run_file_path = FileFinder.findRuns(run_name)[0]
        except:
            raise RuntimeError("Unable to find file for run %i" % run_number)

        if groupings_file == "":
            groupings_file = DEFAULT_GROUPINGS_FILENAME % instrument

        # Load data and other info from input files.

        def temp_hidden_ws_name():
            """Generate a unique name for a temporary, hidden workspace."""
            selection = string.ascii_lowercase + string.ascii_uppercase + string.digits
            return '__temp_MaxEnt_' + ''.join(random.choice(selection) for _ in range(20))

        input_data_ws_name = temp_hidden_ws_name()
        LoadMuonNexus(Filename=run_file_path, OutputWorkspace=input_data_ws_name)
        input_data_ws = mtd[input_data_ws_name]
        
        if isinstance(input_data_ws, WorkspaceGroup):
            Logger.get("MaxEnt").warning("Multi-period data is not currently supported.  Just using first period.")
            input_data_ws = input_data_ws[0]

        groupings_ws_name = temp_hidden_ws_name()
        LoadDetectorsGroupingFile(InputFile=groupings_file, OutputWorkspace=groupings_ws_name)
        groupings_ws = mtd[groupings_ws_name]

        def yield_floats_from_file(path):
            """Given a path to a file with a float on each line, will return
            the floats one at a time.  Throws otherwise.  Strips whitespace
            and ignores empty lines."""
            with open(path, 'r') as f:
                for i, line in enumerate(line.strip() for line in f):
                    if line == "":
                        continue
                    try:
                        yield float(line)
                    except:
                        raise RuntimeError("Parsing error in '%s': Line %d: '%s'." % 
                                           (path, i, line))

        input_phases         = np.array(list(yield_floats_from_file(in_phases_file)))
        input_phases_size    = len(input_phases)
        input_deadtimes      = np.array(list(yield_floats_from_file(in_deadtimes_file)))
        input_deadtimes_size = len(input_deadtimes)

        n_bins      = input_data_ws.blocksize()
        n_detectors = input_data_ws.getNumberHistograms()

        def time_value_to_time_channel_index(value):
            """Given a time value, will return the index of the time channel in
            which the value falls."""
            bin_width = input_data_ws.readX(0)[1] - input_data_ws.readX(0)[0]
            diff = value - input_data_ws.readX(0)[0]
            return int(diff / bin_width)

        # Mantid corrects for time zero on loading, so we want to find the actual channels
        # where 0.0 occurs, and where we have values of 0.1 onwards.
        time_zero_channel  = time_value_to_time_channel_index(0.0)
        first_good_channel = time_value_to_time_channel_index(0.1)

        input_data = np.concatenate([input_data_ws.readY(i) for i in range(n_detectors)])

        groupings = [groupings_ws.readY(row)[0] for row in range(groupings_ws.getNumberHistograms())]
        groupings = map(int, groupings)
        n_groups = len(set(groupings))

        # Cleanup.

        input_data_ws.delete()
        groupings_ws.delete()

        # We're faced with the problem of providing more than a dozen parameters to
        # the Fortran, which can be a bit messy (especially on the Fortran side of
        # things where we need to make "Cf2py" declarations).  A cleaner way of
        # doing this is to simply pass in a few callbacks -- one for each input
        # type -- and have the Fortran provide the name of the variable it wants
        # to the callback.  The callback will then look up the corresponding value
        # and feed it back to the Fortran.
        #
        # We also have a callback for printing to the results log.

        self.int_vars = {
            "RunNo"       : run_number,
            "frames"      : FRAMES,
            "res"         : RES,
            "Tzeroch"     : time_zero_channel,
            "firstgoodch" : first_good_channel,
            "ptstofit"    : POINTS_TO_FIT,
            "histolen"    : n_bins,
            "nhisto"      : n_detectors,
            "n_groups"    : n_groups,
        }

        self.float_vars = {
            "deflevel" : default_level,
            "sigloose" : sigma_looseness,
        }

        self.bool_vars = {
            "fixphase" : fix_phases,
            "fitdt"    : fit_deadtime,
        }

        self._assert_map_values_are_of_expected_type()

        def lookup(par_name, par_map, default):
            """The basis of the callbacks passed to the Fortran.  Given a parameter
            name it will consult the appropriate variable map, and return the
            corresponding value of the parameter.  Else return a default and log a
            warning if a parameter with the name does not exist."""
            par_name = par_name.strip()
            if par_name in par_map:
                return par_map[par_name]
            msg = """WARNING: tried to find a value for parameter with name %s but
            could not find one.  Default of \"%s\" provided.""" % (par_name, default)
            Logger.get("MaxEnt").warning(msg)
            return default

        def log(priority, message):
            """Log the given message with given priority."""
            try:
                logger = getattr(Logger.get("MaxEnt"), priority.lower())
            except AttributeError:
                # If we don't recognise the priority, use warning() as a default.
                logger = getattr(Logger.get("MaxEnt"), "warning")
            logger(message)
            return True

        # The Fortran expects arrays to be of a certain size, so any arrays that
        # aren't big enough need to be padded.
        input_phases    = self._pad_to_length_with_zeros(input_phases, MAX_HISTOS)
        input_deadtimes = self._pad_to_length_with_zeros(input_deadtimes, MAX_HISTOS)
        input_data      = self._pad_to_length_with_zeros(input_data, MAX_INPUT_DATA_SIZE)
        groupings       = self._pad_to_length_with_zeros(groupings, MAX_HISTOS)

        # TODO: Return the contents of "NNNNN.max", instead of writing to file.
        f_out, fchan_out, output_deadtimes, output_phases, chi_sq = maxent.mantid_maxent(
            # Input data and other info:
            input_data,
            groupings,
            input_deadtimes,
            input_phases,
            # Variable-lookup callbacks:
            lambda par_name: lookup(par_name, self.int_vars,   0),
            lambda par_name: lookup(par_name, self.float_vars, 0.0),
            lambda par_name: lookup(par_name, self.bool_vars,  False),
            # Callback for logging:
            log
        )

        def write_items_to_file(path, items):
            """Given a path to a file and a list of items, will write the items
            to the file, one on each line."""
            with open(path, 'w') as f:
                for item in items:
                    f.write(str(item) + "\n")

        # Chop the padded outputs back down to the correct size.
        output_phases    = output_phases[:input_phases_size]
        output_deadtimes = output_deadtimes[:input_deadtimes_size]
        input_phases     = input_phases[:input_phases_size]
        input_deadtimes  = input_deadtimes[:input_deadtimes_size]
        fchan_out        = fchan_out[:n_bins]
        f_out            = f_out[:n_bins]

        write_items_to_file(out_phases_file,    output_phases)
        write_items_to_file(out_deadtimes_file, output_deadtimes)
                 
        log_output = "\nDead times in:\n" +  str(input_deadtimes) + "\n" +\
                     "\nDead times out:\n" + str(output_deadtimes) + "\n" +\
                     "\nPhases in:\n" +      str(input_phases) + "\n" +\
                     "\nPhases out:\n" +     str(output_phases) + "\n" + \
                     "\nGroupings:\n" +      str(groupings) + "\n" +\
                     "\nChi Squared:\n" +    str(chi_sq) + "\n" +\
                     "\nInput variables:\n"

        for type_map in self.int_vars, self.float_vars, self.bool_vars:
            for name, value in type_map.items():
                log_output += str(name) + " = " + str(value) + "\n"

        Logger.get("MaxEnt").notice(log_output)

        # Generate our own output ws name if the user has not provided one.
        out_ws_name = self.getPropertyValue(OUT_WS_PROP)
        if out_ws_name == "":
            out_ws_name = run_name + "; MaxEnt"
            self.setPropertyValue(OUT_WS_PROP, out_ws_name)

        out_ws = CreateWorkspace(OutputWorkspace=out_ws_name,
                                 DataX=fchan_out[:n_bins],
                                 DataY=f_out[:n_bins])
        self.setProperty(OUT_WS_PROP, out_ws)

        # MaxEnt inputs table.
        input_table_name = run_name + "; MaxEnt Input"
        input_table = CreateEmptyTableWorkspace(OutputWorkspace = input_table_name)
        input_table.addColumn("str", "Name")
        input_table.addColumn("str", "Value")
        inputs = itertools.chain(self.int_vars.items(), 
                                 self.float_vars.items(),
                                 self.bool_vars.items())
        for name, value in inputs:
            input_table.addRow([str(name), str(value)])

        # Deadtimes and phases input/output table.
        dead_phases_table_name = run_name + "; MaxEnt Deadtimes & Phases"
        dead_phases_table = CreateEmptyTableWorkspace(OutputWorkspace = dead_phases_table_name)
        for column_name in "Deadtimes In", "Deadtimes Out", "Phases In", "Phases Out":
          dead_phases_table.addColumn("double", column_name)
        for row in zip(input_deadtimes, output_deadtimes, input_phases, output_phases):
            dead_phases_table.addRow(list(map(float, row)))

        # Chi-squared output table.
        chisq_table_name = run_name + "; MaxEnt Chi^2"
        chisq_table = CreateEmptyTableWorkspace(OutputWorkspace = chisq_table_name)
        chisq_table.addColumn("int", "Cycle")
        for iteration in range(10):
          chisq_table.addColumn("double", "Iter " + str(iteration + 1))
        for cycle, data in enumerate(chi_sq):
            chisq_table.addRow([cycle + 1] + list(map(float,data)))

        all_output_ws = [input_table_name,
                         dead_phases_table_name,
                         chisq_table_name,
                         out_ws_name]

        # The output workspaces of this algorithm belong in the same groups
        # that are created by the muon interface.  If the appropriate group
        # doesn't exist already then it needs to be created.
        if not run_name in mtd:
            GroupWorkspaces(InputWorkspaces = all_output_ws,
                            OutputWorkspace = run_name)
        else:
            group = mtd[run_name]
            for output_ws in all_output_ws:
              if not group.contains(output_ws):
                group.add(output_ws)

        out_ws.getAxis(0).getUnit().setLabel("Field", "G")
        out_ws.setYUnitLabel("P(B)")

        if INSIDE_MANTIDPLOT:
            mantidplot.plotSpectrum(out_ws, 0)