#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) IBM Corporation 2018
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__author__ = "Alexis Asseman, Tomasz Kornuta"
import copy
from abc import ABCMeta
from collections import Mapping
from miprometheus.utils.singleton import SingletonMetaClass
[docs]class ParamRegistry(Mapping, metaclass=MetaSingletonABC):
"""
Registry singleton for the parameters.
Registers `default` values (coming from workers, models, problems, etc) as well as \
`config` values loaded by the user for a particular experiment.
Parameters can be read from the registry by indexing.
The returned parameters are the `default` ones superseded by all the `config` ones.
The merging of `default` and `config` parameters is computed every time the registry is changed.
Can contain nested parameters sections (acts as a dict).
.. warning::
This class should not be used except through :py:class:`ParamInterface`.
"""
[docs] def __init__(self):
"""
Constructor:
- Call base constructor (:py:class:`Mapping`),
- Initializes empty parameters dicts for:
- `Default` parameters,
- `Config` parameters,
- Resulting tree.
"""
super(ParamRegistry, self).__init__()
# Default parameters set in the code.
self._default_params = {}
# Parameters read from configuration files.
self._superseding_config_params = {}
# Resulting parameters.
self._params = dict()
def _update_params(self):
"""
Update the resulting parameters dict from the `default` parameters dict superseded by the \
`config` params registry.
"""
# deep copy to avoid the config params leaking to `self._default_params`
self._params = copy.deepcopy(self._default_params)
self.update_dict_recursively(self._params, self._superseding_config_params)
[docs] def add_default_params(self, default_params: dict):
"""
Appends ``default_params`` to the `default` parameter dict of the current :py:class:`ParamRegistry`, \
and update the resulting parameters dict.
.. note::
This method should be used by the objects necessitating default values \
(problems, models, workers etc.).
:param default_params: Dictionary containing default values.
:type default_params: dict
"""
# Update default params list.
self.update_dict_recursively(self._default_params, default_params)
# Merge default with config list.
self._update_params()
[docs] def add_config_params(self, config_params: dict):
"""
Appends ``config_params`` to the `config` parameter dict of the current :py:class:`ParamRegistry`, \
and update the resulting parameters dict.
.. note::
This is intended for the user to dynamically (re)configure his experiments.
:param config_params: Dictionary containing config values.
:type config_params: dict
"""
# Update config params list.
self.update_dict_recursively(self._superseding_config_params, config_params)
# Merge default with config list.
self._update_params()
[docs] def del_default_params(self, keypath: list):
"""
Removes an entry from the `default` parameter dict of the current :py:class:`ParamRegistry`, \
and update the resulting parameters dict.
The entry can either be a subtree or a leaf of the `default` parameter dict.
:param keypath: list of keys to subtree / leaf in the `default` parameter dict.
:type keypath: list
"""
self.delete_subtree(self._default_params, keypath)
self._update_params()
[docs] def del_config_params(self, keypath: list):
"""
Removes an entry from the `config` parameter dict of the current :py:class:`ParamRegistry`, \
and update the resulting parameters dict.
The entry can either be a subtree or a leaf of the `config` parameter dict.
:param keypath: list of keys to subtree / leaf in the `config` parameter dict.
:type keypath: list
"""
self.delete_subtree(self._superseding_config_params, keypath)
self._update_params()
[docs] def __getitem__(self, key):
"""
Get parameter value under ``key``.
The parameter dict is derived from the default parameters updated with the config parameters.
:param key: key to value in the :py:class:`ParamRegistry`.
:type key: str
:return: Parameter value
"""
return self._params[key]
[docs] def __iter__(self):
"""
:return: Iterator over the :py:class:`ParamRegistry`.
"""
return iter(self._params)
[docs] def __len__(self):
"""
:return: Length of the :py:class:`ParamRegistry`.
"""
return len(self._params)
[docs] def update_dict_recursively(self, current_node, update_node):
"""
Recursively update the ``current_node`` of the :py:class:`ParamRegistry` with the values of \
the ``update_node``.
Starts from the root of the ``current_node``.
:param current_node: Current (default or config) node.
:type current_node: :py:class:`ParamRegistry` (inheriting from :py:class:`Mapping`)
:param update_node: Values to be added/updated to the ``current_node``.
:type update_node: :py:class:`ParamRegistry` (inheriting from :py:class:`Mapping`)
:return: Updated current node.
"""
for k, v in update_node.items():
if isinstance(v, Mapping):
current_node[k] = self.update_dict_recursively(current_node.get(k, {}), v)
else:
current_node[k] = v
return current_node
[docs] @staticmethod
def delete_subtree(current_dict, keypath: list):
"""
Delete the subtree indexed by the ``keypath`` from the ``current_dict``.
:param current_dict: dictionary to act on.
:type current_dict: dict
:param keypath: list of keys to subtree in ``current_dict`` to delete
:type keypath: list
"""
if len(keypath) < 1:
raise KeyError
def lookup_recursion(dic, key, *keys):
if keys:
return lookup_recursion(dic[key], *keys)
return dic[key]
lookup_keys = keypath[:-1] # We keep the last key for use with `del`
if len(keypath) > 0:
r = lookup_recursion(current_dict, *lookup_keys)
del r[keypath[-1]]
else:
del current_dict[keypath[-1]]
if __name__ == '__main__':
params = ParamRegistry()
params.add_default_params({'default_0': {'default_1': 'str'}})
params.add_config_params({'config_0': {'config_1': 'int'}})
print(dict(params))
params.del_config_params(['config_0', 'config_1'])
print(dict(params))
params.del_default_params(['default_0', 'default_1'])
print(dict(params))