From 3a0b3c05238acc085ceca00b6dcc3ca7b568f342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20L=C3=BCghausen?= Date: Mon, 24 Jan 2022 17:39:58 +0100 Subject: [PATCH] [python] Add `verbose` deprecation notice and `util.py` - Add a deprecation notice for the optional `verbose` argument - Create `tools/util.py` to contain technical helpers such as the deprecation warning used in various submodules. --- pypmc/__init__.py | 32 +++-------------------- pypmc/mix_adapt/hierarchical.py | 14 +++++----- pypmc/mix_adapt/hierarchical_test.py | 4 +-- pypmc/mix_adapt/pmc.pyx | 8 +++--- pypmc/mix_adapt/pmc_test.py | 8 +++--- pypmc/mix_adapt/variational.pyx | 7 ++--- pypmc/mix_adapt/variational_test.py | 10 ++++---- pypmc/tools/__init__.py | 2 +- pypmc/tools/_partition.py | 8 +++--- pypmc/tools/util.py | 38 ++++++++++++++++++++++++++++ 10 files changed, 72 insertions(+), 59 deletions(-) create mode 100644 pypmc/tools/util.py diff --git a/pypmc/__init__.py b/pypmc/__init__.py index fcd022e..9adafd7 100644 --- a/pypmc/__init__.py +++ b/pypmc/__init__.py @@ -8,32 +8,6 @@ # import these submodules by default from . import mix_adapt, density, sampler, tools -_log_to_stdout = False -def log_to_stdout(verbose=False): - ''' - Turn on logging and add a handler which prints to stderr, if not active - yet. - - :param verbose: - - Bool; if ``True``, output non-critical status information - - ''' - global _log_to_stdout - import logging - import sys - logger = logging.getLogger(__name__) - - if verbose: - logger.setLevel(logging.INFO) - else: - logger.setLevel(logging.WARNING) - - if not _log_to_stdout: - formatter = logging.Formatter('[%(levelname)s] %(message)s') - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(formatter) - logger.addHandler(handler) - _log_to_stdout = True - -log_to_stdout() +# Log to stdout per default. The log handler can be removed to use pypmc +# as a library. +tools.util.log_to_stdout() diff --git a/pypmc/mix_adapt/hierarchical.py b/pypmc/mix_adapt/hierarchical.py index 9536fb2..f37efb6 100644 --- a/pypmc/mix_adapt/hierarchical.py +++ b/pypmc/mix_adapt/hierarchical.py @@ -56,7 +56,7 @@ def __init__(self, input_components, initial_guess): # the i-th element is :math:`min_j KL(f_i || g_j)` self.min_kl = np.zeros(self.nin) + np.inf - def _cleanup(self, kill, verbose): + def _cleanup(self, kill): """Look for dead components (weight=0) and remove them if enabled by ``kill``. Resize storage. Recompute determinant and covariance. @@ -175,18 +175,18 @@ def run(self, eps=1e-4, kill=True, max_steps=50, verbose=False): Perform a maximum number of update steps. - :param verbose: - - Output information on progress of algorithm. - """ + if verbose: + from pypmc.tools.util import depr_warn_verbose + depr_warn_verbose(__name__) + old_distance = np.finfo(np.float64).max new_distance = np.finfo(np.float64).max logger.info('Starting hierarchical clustering with %d components.' % len(self.g.components)) converged = False for step in range(1, max_steps + 1): - self._cleanup(kill, verbose) + self._cleanup(kill) self._regroup() self._refit() @@ -211,7 +211,7 @@ def run(self, eps=1e-4, kill=True, max_steps=50, verbose=False): # save distance for comparison in next step old_distance = new_distance - self._cleanup(kill, verbose) + self._cleanup(kill) logger.info('%d components remain.' % len(self.g.components)) if converged: diff --git a/pypmc/mix_adapt/hierarchical_test.py b/pypmc/mix_adapt/hierarchical_test.py index 8eff038..a402694 100644 --- a/pypmc/mix_adapt/hierarchical_test.py +++ b/pypmc/mix_adapt/hierarchical_test.py @@ -29,7 +29,7 @@ class TestHierarchical(unittest.TestCase): def test_prune(self): h = Hierarchical(self.input_components1, self.initial_guess1) - h.run(verbose=True) + h.run() sol = h.g # only one component should survive and have weight 1.0 @@ -46,7 +46,7 @@ def test_prune(self): def test_cluster(self): h = Hierarchical(self.input_components2, self.initial_guess2) - h.run(verbose=True) + h.run() sol = h.g # both components should survive and have equal weight diff --git a/pypmc/mix_adapt/pmc.pyx b/pypmc/mix_adapt/pmc.pyx index b6b542a..a9c47b6 100644 --- a/pypmc/mix_adapt/pmc.pyx +++ b/pypmc/mix_adapt/pmc.pyx @@ -429,11 +429,11 @@ class PMC(object): .. math:: \| L_t - L_{t-1} \| < \epsilon_a . - :param verbose: - - Output status information after each update. - ''' + if verbose: + from pypmc.tools.util import depr_warn_verbose + depr_warn_verbose( __name__) + old_K = None for i in range(1, iterations + 1): diff --git a/pypmc/mix_adapt/pmc_test.py b/pypmc/mix_adapt/pmc_test.py index 95016e2..38eccaa 100644 --- a/pypmc/mix_adapt/pmc_test.py +++ b/pypmc/mix_adapt/pmc_test.py @@ -390,7 +390,7 @@ def test_invalid_usage(self): def test_adaptation(self): pmc = PMC(self.samples, self.prop, self.weights, latent=self.latent, rb=False) - converged = pmc.run(verbose=True) + converged = pmc.run() outdensity = pmc.density self.assertEqual(converged, 2) @@ -422,7 +422,7 @@ def test_with_overlap(self): samples, latent = target.propose(10**4, trace=True, shuffle=False) pmc = PMC(samples, prop, latent=latent, rb=True) - pmc.run(verbose=True) + pmc.run() adapted_prop = pmc.density adapted_comp_weights = adapted_prop.weights @@ -467,7 +467,7 @@ def test_prune(self): pmc = PMC(samples, prop, rb=True) pmc_prune = 0.5 / len(prop) - converge_step = pmc.run(30, verbose=True, prune=pmc_prune) + converge_step = pmc.run(30, prune=pmc_prune) adapted_prop = pmc.density adapted_comp_weights = adapted_prop.weights @@ -520,7 +520,7 @@ def test_prune(self): pmc = PMC(samples, prop, rb=True, mindof=mindof, maxdof=maxdof) pmc_prune = 0.5 / len(prop) - converge_step = pmc.run(30, verbose=True, prune=pmc_prune) + converge_step = pmc.run(30, prune=pmc_prune) adapted_prop = pmc.density adapted_comp_weights = adapted_prop.weights diff --git a/pypmc/mix_adapt/variational.pyx b/pypmc/mix_adapt/variational.pyx index 4824770..fc55ba6 100644 --- a/pypmc/mix_adapt/variational.pyx +++ b/pypmc/mix_adapt/variational.pyx @@ -315,10 +315,11 @@ class GaussianInference(object): .. math:: \| L_t - L_{t-1} \| < \epsilon_a . - :param verbose: - Output status information after each update. - ''' + if verbose: + from pypmc.tools.util import depr_warn_verbose + depr_warn_verbose( __name__) + old_K = None for i in range(1, iterations + 1): # recompute bound in 1st step or if components were removed diff --git a/pypmc/mix_adapt/variational_test.py b/pypmc/mix_adapt/variational_test.py index 45e2f14..ba631d3 100644 --- a/pypmc/mix_adapt/variational_test.py +++ b/pypmc/mix_adapt/variational_test.py @@ -344,7 +344,7 @@ def test_weighted(self): samples = sam.samples[:] clust = GaussianInference(samples, 2, weights=weights, m=np.vstack((prop_mean1,prop_mean2))) - converged = clust.run(verbose=True) + converged = clust.run() self.assertTrue(converged) resulting_mixture = clust.make_mixture() @@ -418,7 +418,7 @@ def test_prune(self): # to result in same numbers, except it works also when # components were reduced => nsteps2 + 1 eps = 1e-15 - nsteps2 = infer2.run(20, verbose=True) + nsteps2 = infer2.run(20) self.assertEqual(nsteps2 + 1, nsteps) result2 = infer2.make_mixture() @@ -493,7 +493,7 @@ def test_initial_guess(self): def test_1d(self): vb = VBMerge(self.input_mix, N=self.N, initial_guess=self.initial_guess) - vb.run(verbose=True) + vb.run() mix = vb.make_mixture() self.assertEqual(len(mix), 1) # means agree as m0 = 0 but correction from beta0 @@ -609,7 +609,7 @@ def test_bimodal(self): # restart, should converge immediately pripos = vb.prior_posterior() vb2 = VBMerge(input_mix, vb.N, **pripos) - nsteps = vb2.run(verbose=True) + nsteps = vb2.run() self.assertEqual(nsteps, 1) self.assertEqual(vb2.likelihood_bound(), vb.likelihood_bound()) @@ -699,7 +699,7 @@ def test_bound(self): # converge exactly in two steps # prune out one component after first update # then get same bound twice - self.assertEqual(vb.run(verbose=True), 2) + self.assertEqual(vb.run(), 2) self.assertEqual(vb.K, 1) res = vb.make_mixture() np.testing.assert_allclose(res.components[0].mu , target_mean, rtol=1e-3) diff --git a/pypmc/tools/__init__.py b/pypmc/tools/__init__.py index de10441..59d3925 100644 --- a/pypmc/tools/__init__.py +++ b/pypmc/tools/__init__.py @@ -5,4 +5,4 @@ from ._history import History from ._partition import partition, patch_data from ._plot import plot_mixture, plot_responsibility -from . import indicator, convergence +from . import indicator, convergence, util diff --git a/pypmc/tools/_partition.py b/pypmc/tools/_partition.py index a70e22a..3bf010d 100644 --- a/pypmc/tools/_partition.py +++ b/pypmc/tools/_partition.py @@ -48,11 +48,11 @@ def patch_data(data, L=100, try_diag=True, verbose=False): that fails as well, the patch is skipped. If ``False`` the patch is skipped directly. - :param verbose: - - Bool; If ``True`` print all status information. - ''' + if verbose: + from pypmc.tools.util import depr_warn_verbose + depr_warn_verbose(__name__) + # patch data into length L patches patches = _np.array([data[patch_start:patch_start + L] for patch_start in range(0, len(data), L)]) diff --git a/pypmc/tools/util.py b/pypmc/tools/util.py new file mode 100644 index 0000000..8966d97 --- /dev/null +++ b/pypmc/tools/util.py @@ -0,0 +1,38 @@ +import logging + +_log_to_stdout = False +def log_to_stdout(verbose=False): + ''' + Turn on logging and add a handler which prints to stderr, if not active + yet. + + :param verbose: + + Bool; if ``True``, output non-critical status information + + ''' + global _log_to_stdout + import logging + import sys + logger = logging.getLogger(__name__) + + if verbose: + logger.setLevel(logging.INFO) + else: + logger.setLevel(logging.WARNING) + + if not _log_to_stdout: + formatter = logging.Formatter('[%(levelname)s] %(message)s') + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(formatter) + logger.addHandler(handler) + _log_to_stdout = True + +def depr_warn_verbose(logger_name): + logger = logging.getLogger(logger_name) + logger.warn( + f"The optional argument 'verbose' is deprecated and " + f"will be removed in the future. Instead, use the log " + f"level of logging.getLogger({logger_name}) or a parent." + ) +