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())}." )
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
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
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
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)