Esempio n. 1
0
    def __init__(self, spec, validate: bool = True, **config_kwargs):
        """
        Workspaces hold the model, data and measurements.

        Args:
            spec (:obj:`jsonable`): The HistFactory JSON specification
            validate (:obj:`bool`): Whether to validate against a JSON schema
            config_kwargs: Possible keyword arguments for the workspace configuration

        Returns:
            model (:class:`~pyhf.workspace.Workspace`): The Workspace instance

        """
        spec = copy.deepcopy(spec)
        super().__init__(spec, channels=spec['channels'])
        self.schema = config_kwargs.pop('schema', 'workspace.json')
        self.version = config_kwargs.pop('version', spec.get('version', None))

        # run jsonschema validation of input specification against the (provided) schema
        if validate:
            log.info(f"Validating spec against schema: {self.schema}")
            utils.validate(self, self.schema, version=self.version)

        self.measurement_names = []
        for measurement in self.get('measurements', []):
            self.measurement_names.append(measurement['name'])

        self.observations = {}
        for obs in self['observations']:
            self.observations[obs['name']] = obs['data']

        if config_kwargs:
            raise exceptions.Unsupported(
                f"Unsupported options were passed in: {list(config_kwargs.keys())}."
            )
Esempio n. 2
0
    def get_measurement(self, measurement_name=None, measurement_index=None):
        """
        Get a measurement object.

        The following logic is used:

          1. if the measurement name is given, find the measurement for the given name
          2. if the measurement index is given, return the measurement at that index
          3. if there are measurements but none of the above have been specified, return the 0th measurement

        Raises:
          ~pyhf.exceptions.InvalidMeasurement: If the measurement was not found

        Args:
            measurement_name (:obj:`str`): The name of the measurement to use
            measurement_index (:obj:`int`): The index of the measurement to use

        Returns:
            :obj:`dict`: A measurement object adhering to the schema defs.json#/definitions/measurement

        """
        measurement = None
        if self.measurement_names:
            if measurement_name is not None:
                if measurement_name not in self.measurement_names:
                    log.debug(
                        f"measurements defined: {self.measurement_names}")
                    raise exceptions.InvalidMeasurement(
                        f'no measurement by name \'{measurement_name:s}\' was found in the workspace, pick from one of the valid ones above'
                    )
                measurement = self['measurements'][
                    self.measurement_names.index(measurement_name)]
            else:
                if measurement_index is None and len(
                        self.measurement_names) > 1:
                    log.warning(
                        'multiple measurements defined. Taking the first measurement.'
                    )

                measurement_index = (measurement_index
                                     if measurement_index is not None else 0)
                try:
                    measurement = self['measurements'][measurement_index]
                except IndexError:
                    raise exceptions.InvalidMeasurement(
                        f"The measurement index {measurement_index} is out of bounds as only {len(self.measurement_names)} measurement(s) have been defined."
                    )
        else:
            raise exceptions.InvalidMeasurement(
                "No measurements have been defined.")

        utils.validate(measurement, 'measurement.json', self.version)
        return measurement
Esempio n. 3
0
    def __init__(self, spec, **config_kwargs):
        """
        Construct a PatchSet.

        Args:
            spec (:obj:`jsonable`): The patchset JSON specification
            config_kwargs: Possible keyword arguments for the patchset validation

        Returns:
            patchset (:class:`~pyhf.patchset.PatchSet`): The PatchSet instance.

        """
        self.schema = config_kwargs.pop('schema', 'patchset.json')
        self._version = config_kwargs.pop('version', spec.get('version', None))

        # run jsonschema validation of input specification against the (provided) schema
        log.info(f"Validating spec against schema: {self.schema}")
        utils.validate(spec, self.schema, version=self._version)

        # set properties based on metadata
        self._metadata = spec['metadata']

        # list of all patch objects
        self._patches = []
        # look-up table for retrieving patch by name or values
        self._patches_by_key = {'name': {}, 'values': {}}

        # inflate all patches
        for patchspec in spec['patches']:
            patch = Patch(patchspec)

            if patch.name in self._patches_by_key:
                raise exceptions.InvalidPatchSet(
                    f'Multiple patches were defined by name for {patch}.')

            if patch.values in self._patches_by_key:
                raise exceptions.InvalidPatchSet(
                    f'Multiple patches were defined by values for {patch}.')

            if len(patch.values) != len(self.labels):
                raise exceptions.InvalidPatchSet(
                    f'Incompatible number of values ({len(patch.values)} for {patch} in patchset. Expected {len(self.labels)}.'
                )

            # all good, register patch
            self._patches.append(patch)
            # register lookup keys for the patch
            self._patches_by_key[patch.name] = patch
            self._patches_by_key[patch.values] = patch
Esempio n. 4
0
def parse(configfile, rootdir, track_progress=False):
    toplvl = ET.parse(configfile)
    inputs = tqdm.tqdm(
        [x.text for x in toplvl.findall('Input')],
        unit='channel',
        disable=not (track_progress),
    )

    channels = {}
    parameter_configs = []
    for inp in inputs:
        inputs.set_description(f'Processing {inp}')
        channel, data, samples, channel_parameter_configs = process_channel(
            ET.parse(Path(rootdir).joinpath(inp)), rootdir, track_progress)
        channels[channel] = {'data': data, 'samples': samples}
        parameter_configs.extend(channel_parameter_configs)

    parameter_configs = dedupe_parameters(parameter_configs)
    result = {
        'measurements':
        process_measurements(toplvl,
                             other_parameter_configs=parameter_configs),
        'channels': [{
            'name': channel_name,
            'samples': channel_spec['samples']
        } for channel_name, channel_spec in channels.items()],
        'observations': [{
            'name': channel_name,
            'data': channel_spec['data']
        } for channel_name, channel_spec in channels.items()],
        'version':
        utils.SCHEMA_VERSION,
    }
    utils.validate(result, 'workspace.json')

    return result
Esempio n. 5
0
    def __init__(
        self,
        spec,
        modifier_set=None,
        batch_size=None,
        validate: bool = True,
        **config_kwargs,
    ):
        """
        Construct a HistFactory Model.

        Args:
            spec (:obj:`jsonable`): The HistFactory JSON specification
            batch_size (:obj:`None` or :obj:`int`): Number of simultaneous (batched)
             Models to compute.
            validate (:obj:`bool`): Whether to validate against a JSON schema
            config_kwargs: Possible keyword arguments for the model configuration

        Returns:
            model (:class:`~pyhf.pdf.Model`): The Model instance.

        """
        modifier_set = modifier_set or histfactory_set

        self.batch_size = batch_size
        # deep-copy "spec" as it may be modified by config
        self.spec = copy.deepcopy(spec)
        self.schema = config_kwargs.pop('schema', 'model.json')
        self.version = config_kwargs.pop('version', None)
        # run jsonschema validation of input specification against the (provided) schema
        if validate:
            log.info(f"Validating spec against schema: {self.schema:s}")
            utils.validate(self.spec, self.schema, version=self.version)
        # build up our representation of the specification
        poi_name = config_kwargs.pop('poi_name', 'mu')
        self.config = _ModelConfig(self.spec, **config_kwargs)

        modifiers, _nominal_rates = _nominal_and_modifiers_from_spec(
            modifier_set, self.config, self.spec, self.batch_size)

        poi_name = None if poi_name == "" else poi_name
        if poi_name is not None:
            self.config.set_poi(poi_name)

        self.main_model = _MainModel(
            self.config,
            modifiers=modifiers,
            nominal_rates=_nominal_rates,
            batch_size=self.batch_size,
        )

        # the below call needs auxdata order for example
        self.constraint_model = _ConstraintModel(config=self.config,
                                                 batch_size=self.batch_size)

        sizes = []
        if self.main_model.has_pdf():
            sizes.append(self.config.nmaindata)
        if self.constraint_model.has_pdf():
            sizes.append(self.config.nauxdata)
        self.fullpdf_tv = _tensorviewer_from_sizes(sizes, ['main', 'aux'],
                                                   self.batch_size)