From f231fc3ead5ad233ca9968e56df252aa307addf2 Mon Sep 17 00:00:00 2001 From: Volker Austrup Date: Thu, 7 Dec 2023 21:53:01 +0000 Subject: [PATCH 1/3] add mode logical_and to pruning --- src/pyhf/workspace.py | 83 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/src/pyhf/workspace.py b/src/pyhf/workspace.py index 7ce0bc486d..72f0cf21e5 100644 --- a/src/pyhf/workspace.py +++ b/src/pyhf/workspace.py @@ -583,16 +583,40 @@ def _prune_and_rename( ), ) for modifier in sample['modifiers'] - if modifier['name'] not in prune_modifiers - and modifier['type'] not in prune_modifier_types + if ( + channel['name'] not in prune_channels + and prune_channels != [] + ) # want to remove only if channel is in prune_channels or if prune_channels is empty, i.e. we want to prune this modifier for every channel + or ( + sample['name'] not in prune_samples + and prune_samples != [] + ) # want to remove only if sample is in prune_samples or if prune_samples is empty, i.e. we want to prune this modifier for every sample + or ( + modifier['name'] not in prune_modifiers + and modifier['type'] not in prune_modifier_types + ) + or prune_measurements + != [] # need to keep the modifier in case it is used in another measurement ], } for sample in channel['samples'] - if sample['name'] not in prune_samples + if ( + channel['name'] not in prune_channels + and prune_channels != [] + ) # want to remove only if channel is in prune_channels or if prune_channels is empty, i.e. we want to prune this sample for every channel + or sample['name'] not in prune_samples + or prune_modifiers + != [] # we only want to remove this sample if we did not specify modifiers to prune + or prune_modifier_types != [] ], } for channel in self['channels'] if channel['name'] not in prune_channels + or ( # we only want to remove this channel if we did not specify any samples or modifiers to prune + prune_samples != [] + or prune_modifiers != [] + or prune_modifier_types != [] + ) ], 'measurements': [ { @@ -607,8 +631,14 @@ def _prune_and_rename( parameter['name'], parameter['name'] ), ) - for parameter in measurement['config']['parameters'] - if parameter['name'] not in prune_modifiers + for parameter in measurement['config'][ + 'parameters' + ] # we only want to remove this parameter if measurement is in prune_measurements or if prune_measurements is empty + if ( + measurement['name'] not in prune_measurements + and prune_measurements != [] + ) + or parameter['name'] not in prune_modifiers ], 'poi': rename_modifiers.get( measurement['config']['poi'], measurement['config']['poi'] @@ -617,6 +647,8 @@ def _prune_and_rename( } for measurement in self['measurements'] if measurement['name'] not in prune_measurements + or prune_modifiers + != [] # we only want to remove this measurement if we did not specify parameters to remove ], 'observations': [ dict( @@ -625,6 +657,11 @@ def _prune_and_rename( ) for observation in self['observations'] if observation['name'] not in prune_channels + or ( # we only want to remove this channel if we did not specify any samples or modifiers to prune + prune_samples != [] + or prune_modifiers != [] + or prune_modifier_types != [] + ) ], 'version': self['version'], } @@ -637,6 +674,7 @@ def prune( samples=None, channels=None, measurements=None, + mode="logical_or", ): """ Return a new, pruned workspace specification. This will not modify the original workspace. @@ -649,6 +687,7 @@ def prune( samples: A :obj:`list` of samples to prune. channels: A :obj:`list` of channels to prune. measurements: A :obj:`list` of measurements to prune. + mode (:obj: string): `logical_or` or `logical_and` to chain pruning with a logical OR or a logical AND, respectively. Default: `logical_or`. Returns: ~pyhf.workspace.Workspace: A new workspace object with the specified components removed @@ -657,6 +696,12 @@ def prune( ~pyhf.exceptions.InvalidWorkspaceOperation: An item name to prune does not exist in the workspace. """ + + if mode not in ["logical_and", "logical_or"]: + raise ValueError( + "Pruning mode must be either `logical_and` or `logical_or`." + ) + # avoid mutable defaults modifiers = [] if modifiers is None else modifiers modifier_types = [] if modifier_types is None else modifier_types @@ -664,12 +709,28 @@ def prune( channels = [] if channels is None else channels measurements = [] if measurements is None else measurements - return self._prune_and_rename( - prune_modifiers=modifiers, - prune_modifier_types=modifier_types, - prune_samples=samples, - prune_channels=channels, - prune_measurements=measurements, + if mode == "logical_and": + if samples != [] and measurements != []: + raise ValueError( + "Pruning of measurements and samples cannot be run with mode `logical_and`." + ) + if modifier_types != [] and measurements != []: + raise ValueError( + "Pruning of measurements and modifier_types cannot be run with mode `logical_and`." + ) + return self._prune_and_rename( + prune_modifiers=modifiers, + prune_modifier_types=modifier_types, + prune_samples=samples, + prune_channels=channels, + prune_measurements=measurements, + ) + return ( + self._prune_and_rename(prune_modifiers=modifiers) + ._prune_and_rename(prune_modifier_types=modifier_types) + ._prune_and_rename(prune_samples=samples) + ._prune_and_rename(prune_channels=channels) + ._prune_and_rename(prune_measurements=measurements) ) def rename(self, modifiers=None, samples=None, channels=None, measurements=None): From 0360984e2e181b5e04ead0702a785fe59d2f398d Mon Sep 17 00:00:00 2001 From: Volker Austrup Date: Fri, 8 Dec 2023 16:18:18 +0000 Subject: [PATCH 2/3] clean up _prune_and_rename --- src/pyhf/workspace.py | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/pyhf/workspace.py b/src/pyhf/workspace.py index 72f0cf21e5..2c73e487c1 100644 --- a/src/pyhf/workspace.py +++ b/src/pyhf/workspace.py @@ -583,40 +583,39 @@ def _prune_and_rename( ), ) for modifier in sample['modifiers'] + # we want to remove modifiers only if channel is not in list of channels to keep, + # we want to remove modifiers only if sample is not in list of samples to keep if ( - channel['name'] not in prune_channels - and prune_channels != [] - ) # want to remove only if channel is in prune_channels or if prune_channels is empty, i.e. we want to prune this modifier for every channel + prune_channels + and channel['name'] not in prune_channels + ) or ( - sample['name'] not in prune_samples - and prune_samples != [] - ) # want to remove only if sample is in prune_samples or if prune_samples is empty, i.e. we want to prune this modifier for every sample + prune_samples + and sample['name'] not in prune_samples + ) or ( modifier['name'] not in prune_modifiers and modifier['type'] not in prune_modifier_types ) + # need to keep the modifier in case it is used in another measurement or prune_measurements - != [] # need to keep the modifier in case it is used in another measurement ], } for sample in channel['samples'] - if ( - channel['name'] not in prune_channels - and prune_channels != [] - ) # want to remove only if channel is in prune_channels or if prune_channels is empty, i.e. we want to prune this sample for every channel + # we want to remove samples only if channel is not in list of channels to keep, + # we want to remove samples only if no modifiers are to be pruned + if (prune_channels and channel['name'] not in prune_channels) or sample['name'] not in prune_samples or prune_modifiers - != [] # we only want to remove this sample if we did not specify modifiers to prune - or prune_modifier_types != [] + or prune_modifier_types ], } for channel in self['channels'] + # we want to remove channels only if no samples or modifiers are to be pruned if channel['name'] not in prune_channels - or ( # we only want to remove this channel if we did not specify any samples or modifiers to prune - prune_samples != [] - or prune_modifiers != [] - or prune_modifier_types != [] - ) + or prune_samples + or prune_modifiers + or prune_modifier_types ], 'measurements': [ { @@ -634,9 +633,11 @@ def _prune_and_rename( for parameter in measurement['config'][ 'parameters' ] # we only want to remove this parameter if measurement is in prune_measurements or if prune_measurements is empty + # we want to remove parameters from a measurement only + # if measurement is not in keep_measurements if ( - measurement['name'] not in prune_measurements - and prune_measurements != [] + prune_measurements + and measurement['name'] not in prune_measurements ) or parameter['name'] not in prune_modifiers ], @@ -646,9 +647,8 @@ def _prune_and_rename( }, } for measurement in self['measurements'] - if measurement['name'] not in prune_measurements - or prune_modifiers - != [] # we only want to remove this measurement if we did not specify parameters to remove + # we want to remove measurements only if no parameters are to be pruned + if measurement['name'] not in prune_measurements or prune_modifiers ], 'observations': [ dict( @@ -656,12 +656,12 @@ def _prune_and_rename( name=rename_channels.get(observation['name'], observation['name']), ) for observation in self['observations'] + # we want to remove this channels only + # if no samples or modifiers are to be pruned if observation['name'] not in prune_channels - or ( # we only want to remove this channel if we did not specify any samples or modifiers to prune - prune_samples != [] - or prune_modifiers != [] - or prune_modifier_types != [] - ) + or prune_samples + or prune_modifiers + or prune_modifier_types ], 'version': self['version'], } From c12163ce4d0bed6e028d3bf274705933607ecd07 Mon Sep 17 00:00:00 2001 From: Volker Austrup Date: Fri, 8 Dec 2023 16:26:19 +0000 Subject: [PATCH 3/3] cannot prune channels per measurement --- src/pyhf/workspace.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pyhf/workspace.py b/src/pyhf/workspace.py index 2c73e487c1..b63841c02c 100644 --- a/src/pyhf/workspace.py +++ b/src/pyhf/workspace.py @@ -714,6 +714,10 @@ def prune( raise ValueError( "Pruning of measurements and samples cannot be run with mode `logical_and`." ) + if channels != [] and measurements != []: + raise ValueError( + "Pruning of measurements and channels cannot be run with mode `logical_and`." + ) if modifier_types != [] and measurements != []: raise ValueError( "Pruning of measurements and modifier_types cannot be run with mode `logical_and`."