From e4f3bc46476cd7c3987632cde41f4b3ba7f79b11 Mon Sep 17 00:00:00 2001 From: nikohansen Date: Wed, 11 Dec 2024 13:38:56 +0100 Subject: [PATCH] [parameter-sweep] implement using color maps for parameter sweeps --- src/cocopp/compall/pprldmany.py | 6 +-- src/cocopp/config.py | 78 +++++++++++++++++++++++++++++++++ src/cocopp/genericsettings.py | 9 ++++ src/cocopp/rungeneric.py | 15 ++++++- 4 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/cocopp/compall/pprldmany.py b/src/cocopp/compall/pprldmany.py index 67434c8..3e7e4d6 100644 --- a/src/cocopp/compall/pprldmany.py +++ b/src/cocopp/compall/pprldmany.py @@ -830,11 +830,11 @@ def algname_to_label(algname, dirname=None): data = dictData[alg] except KeyError: continue - - args = dict(styles[i % len(styles)]) # kw-args passed to plot + style = styles[i % len(styles)] + args = dict(style) # kw-args passed to plot args.setdefault('markeredgewidth', 1.0) # was: 1.5 args.setdefault('markerfacecolor', 'None') # transparent - args.setdefault('markeredgecolor', styles[i % len(styles)]['color']) + args.setdefault('markeredgecolor', style['color']) args.setdefault('markersize', 9) # was: 12 args.setdefault('linewidth', 1) args['markersize'] *= genericsettings.marker_size_multiplier diff --git a/src/cocopp/config.py b/src/cocopp/config.py index 3792e99..7b3b2a1 100644 --- a/src/cocopp/config.py +++ b/src/cocopp/config.py @@ -17,8 +17,11 @@ """ import importlib +import collections import warnings import numpy as np +import matplotlib.pyplot as plt +from matplotlib import colors as _colors from . import ppfigdim from . import genericsettings as settings, pproc, pprldistr from . import testbedsettings as tbs @@ -47,11 +50,86 @@ def config_target_values_setting(is_expensive, is_runlength_based): settings.maxevals_fix_display = settings.xlimit_expensive settings.runlength_based_targets = is_runlength_based or is_expensive +def map_indices_to_line_styles(names): + """helper function for `config_line_styles`. + + Check for equal names after a float number (which represents a + parameter value) and map the respective index in names to the index of + first appearence. This is not used for determining the color. + """ + nn = [] + for n in names: + found = False + for i in range(len(n)): + if '0' <= n[i] <= '9' or n[i] == '.': + found = True + continue + elif found: + break + nn.append(n[i:]) + res = {k: nn.index(v) for k, v in enumerate(nn)} + return res + +def config_line_styles(): + '''configure `genericsettings.line_styles` for a parameter sweet. + + The colormap and range can be changed via the ``--parameter_sweep=`` + value, the default value is ``plasma.0.9``, ``viridis`` and + ``gnuplot2.0.85`` are viable alternatives. + + The order of the input arguments determines the positioning in the + color map. The ``line_style_mapping`` attribute of `genericsettings` + can be used to chose the marker and line style like ``{input_position: + position_in_original_line_styles}``. When ``input_position`` is not + present, the usual marker line style combination is used matching + ``input_position``. + + TODO: we may want to have different color maps for different algorithms? + TODO: or we may want to keep the original symbol colors? + ''' + s = settings.parameter_sweep + if not s or s in (0, '0', None, 'None', False, 'False', 'false', 'off', 'Off', 'OFF'): + return + if s in (1, '1', True, 'True', 'true', 't', 'on', 'On', 'ON'): + cvals = ['plasma..9', 'Greens_r..7', 'Greys_r..7', 'Reds_r..7'] # TODO: move to genericsettings + else: + try: + cvals = s.split(',') + except AttributeError: + warnings.warn("--parameter_sweep={0} value not recognized, you may" + "use 'on' or 'off' or a \nmatplotlib color map name, see also " + "``help(cocopp.config.config_line_styles)``.".format(s)) + return + # map algorithm argument index to "true" algorithm index + mapping = settings.line_style_mapping or map_indices_to_line_styles( + settings._current_args) + def str_to_colormap(s, len_): + """return a color iterator""" + cvals = s.split('.') + cmap = cvals[0] + c0 = float('.' + cvals[1]) if len(cvals) > 1 and len(cvals[1]) > 1 else 0 + c1 = float('.' + cvals[2]) if len(cvals) > 2 else 1 + return iter(_colors.to_hex(c) for c in plt.get_cmap(cmap)( + np.linspace(c0, c1, len_))) + counts = collections.Counter(mapping.values()) + color_maps = [str_to_colormap(cvals[i % len(cvals)], counts[mapping[i]]) + for i in mapping] + + # modify color in settings.line_styles + settings._default_line_styles = [d.copy() for d in settings.line_styles] # a backup + for i, j in mapping.items(): + s = settings.line_styles[i] + # s['markeredgecolor'] = s['color'] # doesn't work + s['color'] = next(color_maps[j]) + for key in s.keys(): + if key != 'color': + s[key] = settings.line_styles[j][key] def config(suite_name=None): """called from a high level, e.g. rungeneric, to configure the lower level modules via modifying parameter settings. """ + config_line_styles() config_target_values_setting(settings.isExpensive, settings.runlength_based_targets) if suite_name: tbs.load_current_testbed(suite_name, pproc.TargetValues) diff --git a/src/cocopp/genericsettings.py b/src/cocopp/genericsettings.py index 8df288c..8755de5 100644 --- a/src/cocopp/genericsettings.py +++ b/src/cocopp/genericsettings.py @@ -18,6 +18,12 @@ use_recommendations = [False] '''use only recommendations data (.mdat files) for the respective algorithm where the last element is recycled for the remaining algorithms''' +parameter_sweep = False +'''may be `True` or `'on'` or color map names to sweep through (versatile interface). + See also `cocopp.config.config_line_styles`.''' +line_style_mapping = {} +'''map the input argument position to a line style position, by default the identity. + This could useful to get the same style for several algorithm variants.''' force_assertions = False # another debug flag for time-consuming assertions in_a_hurry = 1000 # [0, 1000] lower resolution, no eps, saves 30% time @@ -86,6 +92,9 @@ for a final camera-ready paper version. """ +_current_args = None +'''arguments found in rungeneric.main, namely a list of folders + returned by `COCODataArchive.get_extended` of ``cocopp.archives.all``.''' # single_target_pprldistr_values = (10., 1e-1, 1e-4, 1e-8) # used as default in pprldistr.plot method, on graph for each # single_target_function_values = (1e1, 1e0, 1e-1, 1e-2, 1e-4, 1e-6, 1e-8) # one figure for each, seems not in use diff --git a/src/cocopp/rungeneric.py b/src/cocopp/rungeneric.py index f6dae7c..e1212ca 100644 --- a/src/cocopp/rungeneric.py +++ b/src/cocopp/rungeneric.py @@ -37,7 +37,9 @@ # Used by getopt: short_options = "hvo:" long_options = ["help", "output-dir=", "noisy", "noise-free", - "tab-only", "fig-only", "rld-only", "no-rld-single-fcts", + "tab-only", "fig-only", + "parameter-sweep=", + "rld-only", "no-rld-single-fcts", "verbose", "settings=", "conv", "expensive", "runlength-based", "los-only", "crafting-effort=", "pickle", @@ -185,6 +187,13 @@ def main(argv=None): all folder/file arguments are prepended with the given value which must be a valid path. + --parameter-sweep=colormaps + + 'on' is a valid value too. Parse the algorithm name for a float + first and use the value to determine the color from a colormap. + `colormaps` can be 'plasma.0.9,viridis' indicating the map names + separated by commata and optionally the range for each map. + --in-a-hurry takes values between 0 (default) and 1000, fast processing that @@ -337,6 +346,8 @@ def main(argv=None): genericsettings.isLogLoss = False elif o == "--no-interactive": genericsettings.interactive_mode = False + elif o == "--parameter-sweep": + genericsettings.parameter_sweep = a if a != '' else True else: is_assigned = False if o in longoptlist or o in shortoptlist: @@ -386,6 +397,8 @@ def main(argv=None): # TODO: we would like the users input with timeout to confirm # and otherwise raise a ValueError + genericsettings._current_args = args + update_background_algorithms(inputdir) print(' Using %d data set%s:' % (len(args), 's' if len(args) > 1 else ''))