diff --git a/bin/hyppopy_exe.py b/bin/hyppopy_exe.py index 2296c38..10f41e7 100644 --- a/bin/hyppopy_exe.py +++ b/bin/hyppopy_exe.py @@ -1,85 +1,86 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # # DKFZ # # # Copyright (c) German Cancer Research Center, # Division of Medical and Biological Informatics. # All rights reserved. # # This software is distributed WITHOUT ANY WARRANTY; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. # # See LICENSE.txt or http://www.mitk.org for details. # # Author: Sven Wanner (s.wanner@dkfz.de) -from hyppopy.workflows.unet_usecase import unet_usecase -from hyppopy.workflows.svc_usecase import svc_usecase -from hyppopy.workflows.randomforest_usecase import randomforest_usecase +from hyppopy.workflows.unet_usecase.unet_usecase import unet_usecase +from hyppopy.workflows.svc_usecase.svc_usecase import svc_usecase +from hyppopy.workflows.randomforest_usecase.randomforest_usecase import randomforest_usecase import os import sys import argparse import hyppopy.solverfactory as sfac solver_factory = sfac.SolverFactory.instance() def print_warning(msg): print("\n!!!!! WARNING !!!!!") print(msg) sys.exit() def args_check(args): if not args.workflow: print_warning("No workflow specified, check --help") if not args.config: print_warning("Missing config parameter, check --help") if not args.data: print_warning("Missing data parameter, check --help") if not os.path.isdir(args.data): print_warning("Couldn't find data path, please check your input --data") if not os.path.isfile(args.config): tmp = os.path.join(args.data, args.config) if not os.path.isfile(tmp): print_warning("Couldn't find the config file, please check your input --config") args.config = tmp - if args.plugin not in solver_factory.list_solver(): - print_warning(f"The requested plugin {args.plugin} is not available, please check for typos. Plugin options :" - f"{', '.join(solver_factory.list_solver())}") if __name__ == "__main__": parser = argparse.ArgumentParser(description='UNet Hyppopy UseCase Example Optimization.') parser.add_argument('-w', '--workflow', type=str, help='workflow to be executed') - parser.add_argument('-p', '--plugin', type=str, default='hyperopt', + parser.add_argument('-p', '--plugin', type=str, default='', help='plugin to be used default=[hyperopt], optunity') parser.add_argument('-d', '--data', type=str, help='training data path') parser.add_argument('-c', '--config', type=str, help='config filename, .xml or .json formats are supported.' 'pass a full path filename or the filename only if the' 'configfile is in the data folder') parser.add_argument('-i', '--iterations', type=int, default=0, help='number of iterations, default=[0] if set to 0 the value set via configfile is used, ' 'otherwise the configfile value will be overwritten') args = parser.parse_args() args_check(args) if args.workflow == "svc_usecase": - svc_usecase.svc_usecase(args) + uc = svc_usecase(args) elif args.workflow == "randomforest_usecase": - randomforest_usecase.randomforest_usecase(args) + uc = randomforest_usecase(args) elif args.workflow == "unet_usecase": - unet_usecase.unet_usecase(args) + uc = unet_usecase(args) else: print(f"No workflow called {args.workflow} found!") + sys.exit() + + uc.run() + print(uc.get_results()) diff --git a/hyppopy/plugins/hyperopt_solver_plugin.py b/hyppopy/plugins/hyperopt_solver_plugin.py index b21bc2e..c94ae6a 100644 --- a/hyppopy/plugins/hyperopt_solver_plugin.py +++ b/hyppopy/plugins/hyperopt_solver_plugin.py @@ -1,67 +1,70 @@ # -*- coding: utf-8 -*- # # DKFZ # # # Copyright (c) German Cancer Research Center, # Division of Medical and Biological Informatics. # All rights reserved. # # This software is distributed WITHOUT ANY WARRANTY; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. # # See LICENSE.txt or http://www.mitk.org for details. # # Author: Sven Wanner (s.wanner@dkfz.de) import os import logging from hyppopy.globals import DEBUGLEVEL LOG = logging.getLogger(os.path.basename(__file__)) LOG.setLevel(DEBUGLEVEL) from pprint import pformat from hyperopt import fmin, tpe, hp, STATUS_OK, STATUS_FAIL, Trials from yapsy.IPlugin import IPlugin from hyppopy.solverpluginbase import SolverPluginBase class hyperopt_Solver(SolverPluginBase, IPlugin): trials = None best = None def __init__(self): SolverPluginBase.__init__(self) LOG.debug("initialized") def loss_function(self, params): try: loss = self.loss(self.data, params) status = STATUS_OK except Exception as e: LOG.error(f"execution of self.loss(self.data, params) failed due to:\n {e}") status = STATUS_FAIL return {'loss': loss, 'status': status} def execute_solver(self, parameter): LOG.debug(f"execute_solver using solution space:\n\n\t{pformat(parameter)}\n") self.trials = Trials() try: self.best = fmin(fn=self.loss_function, space=parameter, algo=tpe.suggest, max_evals=self.settings.max_iterations, trials=self.trials) except Exception as e: msg = f"internal error in hyperopt.fmin occured. {e}" LOG.error(msg) raise BrokenPipeError(msg) def convert_results(self): + txt = "" solution = dict([(k, v) for k, v in self.best.items() if v is not None]) - print('Solution\n========') - print("\n".join(map(lambda x: "%s \t %s" % (x[0], str(x[1])), solution.items()))) + txt += 'Solution Hyperopt Plugin\n========\n' + txt += "\n".join(map(lambda x: "%s \t %s" % (x[0], str(x[1])), solution.items())) + txt += "\n" + return txt diff --git a/hyppopy/plugins/optunity_solver_plugin.py b/hyppopy/plugins/optunity_solver_plugin.py index 9b2779c..c92ab52 100644 --- a/hyppopy/plugins/optunity_solver_plugin.py +++ b/hyppopy/plugins/optunity_solver_plugin.py @@ -1,69 +1,72 @@ # -*- coding: utf-8 -*- # # DKFZ # # # Copyright (c) German Cancer Research Center, # Division of Medical and Biological Informatics. # All rights reserved. # # This software is distributed WITHOUT ANY WARRANTY; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. # # See LICENSE.txt or http://www.mitk.org for details. # # Author: Sven Wanner (s.wanner@dkfz.de) import os import logging from hyppopy.globals import DEBUGLEVEL LOG = logging.getLogger(os.path.basename(__file__)) LOG.setLevel(DEBUGLEVEL) from pprint import pformat import optunity from yapsy.IPlugin import IPlugin from hyppopy.solverpluginbase import SolverPluginBase class optunity_Solver(SolverPluginBase, IPlugin): solver_info = None trials = None best = None status = None def __init__(self): SolverPluginBase.__init__(self) LOG.debug("initialized") def loss_function(self, **params): try: loss = self.loss(self.data, params) self.status.append('ok') return loss except Exception as e: LOG.error(f"computing loss failed due to:\n {e}") self.status.append('fail') return 1e9 def execute_solver(self, parameter): LOG.debug(f"execute_solver using solution space:\n\n\t{pformat(parameter)}\n") self.status = [] try: self.best, self.trials, self.solver_info = optunity.minimize_structured(f=self.loss_function, num_evals=self.settings.max_iterations, search_space=parameter) except Exception as e: LOG.error(f"internal error in optunity.minimize_structured occured. {e}") raise BrokenPipeError(f"internal error in optunity.minimize_structured occured. {e}") def convert_results(self): solution = dict([(k, v) for k, v in self.best.items() if v is not None]) - print('Solution\n========') - print("\n".join(map(lambda x: "%s \t %s" % (x[0], str(x[1])), solution.items()))) - print(f"Solver used: {self.solver_info['solver_name']}") - print(f"Optimum: {self.trials.optimum}") - print(f"Iterations used: {self.trials.stats['num_evals']}") - print(f"Duration: {self.trials.stats['time']} s") + + txt = "" + txt += 'Solution Optunity Plugin\n========\n' + txt += "\n".join(map(lambda x: "%s \t %s" % (x[0], str(x[1])), solution.items())) + txt += f"\nSolver used: {self.solver_info['solver_name']}" + txt += f"\nOptimum: {self.trials.optimum}" + txt += f"\nIterations used: {self.trials.stats['num_evals']}" + txt += f"\nDuration: {self.trials.stats['time']} s\n" + return txt diff --git a/hyppopy/solver.py b/hyppopy/solver.py index e2a9a5e..14262fd 100644 --- a/hyppopy/solver.py +++ b/hyppopy/solver.py @@ -1,84 +1,84 @@ # -*- coding: utf-8 -*- # # DKFZ # # # Copyright (c) German Cancer Research Center, # Division of Medical and Biological Informatics. # All rights reserved. # # This software is distributed WITHOUT ANY WARRANTY; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. # # See LICENSE.txt or http://www.mitk.org for details. # # Author: Sven Wanner (s.wanner@dkfz.de) import os import logging from hyppopy.globals import DEBUGLEVEL LOG = logging.getLogger(os.path.basename(__file__)) LOG.setLevel(DEBUGLEVEL) class Solver(object): _name = None _solver_plugin = None _settings_plugin = None def __init__(self): pass def set_data(self, data): self.solver.set_data(data) def set_parameters(self, params): self.settings.set(params) self.settings.set_attributes(self.solver) def read_parameter(self, fname): self.settings.read(fname) self.settings.set_attributes(self.settings) def set_loss_function(self, loss_func): self.solver.set_loss_function(loss_func) def run(self): self.solver.settings = self.settings self.solver.run() def get_results(self): - self.solver.get_results() + return self.solver.get_results() @property def is_ready(self): return self.solver is not None and self.settings is not None @property def solver(self): return self._solver_plugin @solver.setter def solver(self, value): self._solver_plugin = value @property def settings(self): return self._settings_plugin @settings.setter def settings(self, value): self._settings_plugin = value @property def name(self): return self._name @name.setter def name(self, value): if not isinstance(value, str): LOG.error(f"Invalid input, str type expected for value, got {type(value)} instead") raise IOError(f"Invalid input, str type expected for value, got {type(value)} instead") self._name = value diff --git a/hyppopy/solverpluginbase.py b/hyppopy/solverpluginbase.py index 91ce6ba..63a16d7 100644 --- a/hyppopy/solverpluginbase.py +++ b/hyppopy/solverpluginbase.py @@ -1,86 +1,86 @@ # -*- coding: utf-8 -*- # # DKFZ # # # Copyright (c) German Cancer Research Center, # Division of Medical and Biological Informatics. # All rights reserved. # # This software is distributed WITHOUT ANY WARRANTY; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. # # See LICENSE.txt or http://www.mitk.org for details. # # Author: Sven Wanner (s.wanner@dkfz.de) import abc import os import logging from hyppopy.globals import DEBUGLEVEL from hyppopy.settingspluginbase import SettingsPluginBase LOG = logging.getLogger(os.path.basename(__file__)) LOG.setLevel(DEBUGLEVEL) class SolverPluginBase(object): data = None loss = None _settings = None _name = None def __init__(self): pass @abc.abstractmethod def loss_function(self, params): raise NotImplementedError('users must define loss_func to use this base class') @abc.abstractmethod def execute_solver(self): raise NotImplementedError('users must define execute_solver to use this base class') @abc.abstractmethod def convert_results(self): raise NotImplementedError('users must define convert_results to use this base class') def set_data(self, data): self.data = data def set_loss_function(self, func): self.loss = func def get_results(self): - self.convert_results() + return self.convert_results() def run(self): self.execute_solver(self.settings.get_hyperparameter()) @property def name(self): return self._name @name.setter def name(self, value): if not isinstance(value, str): msg = f"Invalid input, str type expected for value, got {type(value)} instead" LOG.error(msg) raise IOError(msg) self._name = value @property def settings(self): return self._settings @settings.setter def settings(self, value): if not isinstance(value, SettingsPluginBase): msg = f"Invalid input, SettingsPluginBase type expected for value, got {type(value)} instead" LOG.error(msg) raise IOError(msg) self._settings = value diff --git a/hyppopy/tests/data/Iris/rf_config.json b/hyppopy/tests/data/Iris/rf_config.json new file mode 100644 index 0000000..869c319 --- /dev/null +++ b/hyppopy/tests/data/Iris/rf_config.json @@ -0,0 +1,42 @@ +{"hyperparameter": { + "n_estimators": { + "domain": "uniform", + "data": "[3,500]", + "type": "int" + }, + "criterion": { + "domain": "categorical", + "data": "[gini,entropy]", + "type": "str" + }, + "max_depth": { + "domain": "uniform", + "data": "[3, 50]", + "type": "int" + }, + "min_samples_split": { + "domain": "uniform", + "data": "[0.0001,1]", + "type": "float" + }, + "min_samples_leaf": { + "domain": "uniform", + "data": "[0.0001,0.5]", + "type": "float" + }, + "max_features": { + "domain": "categorical", + "data": "[auto,sqrt,log2]", + "type": "str" + } +}, +"settings": { + "solver": { + "max_iterations": "3", + "use_plugin" : "optunity" + }, + "custom": { + "data_name": "train_data.npy", + "labels_name": "train_labels.npy" + } +}} \ No newline at end of file diff --git a/hyppopy/tests/data/Iris/rf_config.xml b/hyppopy/tests/data/Iris/rf_config.xml new file mode 100644 index 0000000..925d164 --- /dev/null +++ b/hyppopy/tests/data/Iris/rf_config.xml @@ -0,0 +1,44 @@ + + + + uniform + [3,200] + int + + + categorical + [gini,entropy] + str + + + uniform + [3, 50] + int + + + uniform + [0.0001,1] + float + + + uniform + [0.0001,0.5] + float + + + categorical + [auto,sqrt,log2] + str + + + + + 3 + optunity + + + train_data.npy + train_labels.npy + + + \ No newline at end of file diff --git a/hyppopy/tests/data/Iris/svc_config.json b/hyppopy/tests/data/Iris/svc_config.json new file mode 100644 index 0000000..0628f97 --- /dev/null +++ b/hyppopy/tests/data/Iris/svc_config.json @@ -0,0 +1,32 @@ +{"hyperparameter": { + "C": { + "domain": "uniform", + "data": "[0,20]", + "type": "float" + }, + "gamma": { + "domain": "uniform", + "data": "[0.0001,20.0]", + "type": "float" + }, + "kernel": { + "domain": "categorical", + "data": "[linear, sigmoid, poly, rbf]", + "type": "str" + }, + "decision_function_shape": { + "domain": "categorical", + "data": "[ovo,ovr]", + "type": "str" + } +}, +"settings": { + "solver": { + "max_iterations": "3", + "use_plugin" : "optunity" + }, + "custom": { + "data_name": "train_data.npy", + "labels_name": "train_labels.npy" + } +}} \ No newline at end of file diff --git a/hyppopy/tests/data/Iris/svc_config.xml b/hyppopy/tests/data/Iris/svc_config.xml new file mode 100644 index 0000000..fb4f50b --- /dev/null +++ b/hyppopy/tests/data/Iris/svc_config.xml @@ -0,0 +1,34 @@ + + + + uniform + [0,20] + float + + + uniform + [0.0001,20.0] + float + + + categorical + [linear,sigmoid,poly,rbf] + str + + + categorical + [ovo,ovr] + str + + + + + 3 + hyperopt + + + train_data.npy + train_labels.npy + + + \ No newline at end of file diff --git a/hyppopy/tests/data/Iris/train_data.npy b/hyppopy/tests/data/Iris/train_data.npy new file mode 100644 index 0000000..b4cdefd Binary files /dev/null and b/hyppopy/tests/data/Iris/train_data.npy differ diff --git a/hyppopy/tests/data/Iris/train_labels.npy b/hyppopy/tests/data/Iris/train_labels.npy new file mode 100644 index 0000000..797abe7 Binary files /dev/null and b/hyppopy/tests/data/Iris/train_labels.npy differ diff --git a/hyppopy/tests/data/Titanic/rf_config.json b/hyppopy/tests/data/Titanic/rf_config.json new file mode 100644 index 0000000..7993c78 --- /dev/null +++ b/hyppopy/tests/data/Titanic/rf_config.json @@ -0,0 +1,27 @@ +{"hyperparameter": { + "n_estimators": { + "domain": "uniform", + "data": "[3,500]", + "type": "int" + }, + "criterion": { + "domain": "categorical", + "data": "[gini,entropy]", + "type": "str" + }, + "max_depth": { + "domain": "uniform", + "data": "[3, 50]", + "type": "int" + } +}, +"settings": { + "solver": { + "max_iterations": "3", + "use_plugin" : "optunity" + }, + "custom": { + "data_name": "train_cleaned.csv", + "labels_name": "Survived" + } +}} \ No newline at end of file diff --git a/hyppopy/tests/data/Titanic/rf_config.xml b/hyppopy/tests/data/Titanic/rf_config.xml new file mode 100644 index 0000000..5dd0797 --- /dev/null +++ b/hyppopy/tests/data/Titanic/rf_config.xml @@ -0,0 +1,29 @@ + + + + uniform + [3,200] + int + + + categorical + [gini,entropy] + str + + + uniform + [3, 50] + int + + + + + 3 + optunity + + + train_cleaned.csv + Survived + + + \ No newline at end of file diff --git a/hyppopy/tests/data/Titanic/svc_config.json b/hyppopy/tests/data/Titanic/svc_config.json new file mode 100644 index 0000000..3291024 --- /dev/null +++ b/hyppopy/tests/data/Titanic/svc_config.json @@ -0,0 +1,32 @@ +{"hyperparameter": { + "C": { + "domain": "uniform", + "data": "[0,20]", + "type": "float" + }, + "gamma": { + "domain": "uniform", + "data": "[0.0001,20.0]", + "type": "float" + }, + "kernel": { + "domain": "categorical", + "data": "[linear, sigmoid, poly, rbf]", + "type": "str" + }, + "decision_function_shape": { + "domain": "categorical", + "data": "[ovo,ovr]", + "type": "str" + } +}, +"settings": { + "solver": { + "max_iterations": "3", + "use_plugin" : "hyperopt" + }, + "custom": { + "data_name": "train_cleaned.csv", + "labels_name": "Survived" + } +}} \ No newline at end of file diff --git a/hyppopy/tests/data/Titanic/svc_config.xml b/hyppopy/tests/data/Titanic/svc_config.xml new file mode 100644 index 0000000..5c2b275 --- /dev/null +++ b/hyppopy/tests/data/Titanic/svc_config.xml @@ -0,0 +1,34 @@ + + + + uniform + [0,20] + float + + + uniform + [0.0001,20.0] + float + + + categorical + [linear,sigmoid,poly,rbf] + str + + + categorical + [ovo,ovr] + str + + + + + 3 + hyperopt + + + train_cleaned.csv + Survived + + + \ No newline at end of file diff --git a/hyppopy/tests/data/Titanic/train_cleaned.csv b/hyppopy/tests/data/Titanic/train_cleaned.csv new file mode 100644 index 0000000..c587d3b --- /dev/null +++ b/hyppopy/tests/data/Titanic/train_cleaned.csv @@ -0,0 +1,892 @@ +Pclass,Sex,Age,Embarked_C,Embarked_Q,Survived +3,1,22.0,0,0,0 +1,0,38.0,1,0,1 +3,0,26.0,0,0,1 +1,0,35.0,0,0,1 +3,1,35.0,0,0,0 +3,1,42.0,0,1,0 +1,1,54.0,0,0,0 +3,1,2.0,0,0,0 +3,0,27.0,0,0,1 +2,0,14.0,1,0,1 +3,0,4.0,0,0,1 +1,0,58.0,0,0,1 +3,1,20.0,0,0,0 +3,1,39.0,0,0,0 +3,0,14.0,0,0,0 +2,0,55.0,0,0,1 +3,1,2.0,0,1,0 +2,1,42.0,0,0,1 +3,0,31.0,0,0,0 +3,0,4.574166666666667,1,0,1 +2,1,35.0,0,0,0 +2,1,34.0,0,0,1 +3,0,15.0,0,1,1 +1,1,28.0,0,0,1 +3,0,8.0,0,0,0 +3,0,38.0,0,0,1 +3,1,42.0,1,0,0 +1,1,19.0,0,0,0 +3,0,70.0,0,1,1 +3,1,42.0,0,0,0 +1,1,40.0,1,0,0 +1,0,4.574166666666667,1,0,1 +3,0,70.0,0,1,1 +2,1,66.0,0,0,0 +1,1,28.0,1,0,0 +1,1,42.0,0,0,0 +3,1,42.0,1,0,1 +3,1,21.0,0,0,0 +3,0,18.0,0,0,0 +3,0,14.0,1,0,1 +3,0,40.0,0,0,0 +2,0,27.0,0,0,0 +3,1,42.0,1,0,0 +2,0,3.0,1,0,1 +3,0,19.0,0,1,1 +3,1,42.0,0,0,0 +3,1,42.0,0,1,0 +3,0,70.0,0,1,1 +3,1,42.0,1,0,0 +3,0,18.0,0,0,0 +3,1,7.0,0,0,0 +3,1,21.0,0,0,0 +1,0,49.0,1,0,1 +2,0,29.0,0,0,1 +1,1,65.0,1,0,0 +1,1,42.0,0,0,1 +2,0,21.0,0,0,1 +3,1,28.5,1,0,0 +2,0,5.0,0,0,1 +3,1,11.0,0,0,0 +3,1,22.0,1,0,0 +1,0,38.0,0,0,1 +1,1,45.0,0,0,0 +3,1,4.0,0,0,0 +1,1,42.0,1,0,0 +3,1,40.0,1,0,1 +2,0,29.0,0,0,1 +3,1,19.0,0,0,0 +3,0,17.0,0,0,1 +3,1,26.0,0,0,0 +2,1,32.0,0,0,0 +3,0,16.0,0,0,0 +2,1,21.0,0,0,0 +3,1,26.0,1,0,0 +3,1,32.0,0,0,1 +3,1,25.0,0,0,0 +3,1,42.0,0,0,0 +3,1,42.0,0,0,0 +2,1,0.83,0,0,1 +3,0,30.0,0,0,1 +3,1,22.0,0,0,0 +3,1,29.0,0,0,1 +3,0,70.0,0,1,1 +1,1,28.0,0,0,0 +2,0,17.0,0,0,1 +3,0,33.0,0,0,1 +3,1,16.0,0,0,0 +3,1,42.0,0,0,0 +1,0,23.0,0,0,1 +3,1,24.0,0,0,0 +3,1,29.0,0,0,0 +3,1,20.0,0,0,0 +1,1,46.0,0,0,0 +3,1,26.0,0,0,0 +3,1,59.0,0,0,0 +3,1,42.0,0,0,0 +1,1,71.0,1,0,0 +1,1,23.0,1,0,1 +2,0,34.0,0,0,1 +2,1,34.0,0,0,0 +3,0,28.0,0,0,0 +3,1,42.0,0,0,0 +1,1,21.0,0,0,0 +3,1,33.0,0,0,0 +3,1,37.0,0,0,0 +3,1,28.0,0,0,0 +3,0,21.0,0,0,1 +3,1,42.0,0,0,1 +3,1,38.0,0,0,0 +3,0,70.0,0,1,1 +1,1,47.0,0,0,0 +3,0,14.5,1,0,0 +3,1,22.0,0,0,0 +3,0,20.0,0,0,0 +3,0,17.0,1,0,0 +3,1,21.0,0,0,0 +3,1,70.5,0,1,0 +2,1,29.0,0,0,0 +1,1,24.0,1,0,0 +3,0,2.0,0,0,0 +2,1,21.0,0,0,0 +3,1,42.0,0,0,0 +2,1,32.5,1,0,0 +2,0,32.5,0,0,1 +1,1,54.0,0,0,0 +3,1,12.0,1,0,1 +3,1,42.0,0,1,0 +3,1,24.0,0,0,1 +3,0,70.0,1,0,1 +3,1,45.0,0,0,0 +3,1,33.0,1,0,0 +3,1,20.0,0,0,0 +3,0,47.0,0,0,0 +2,0,29.0,0,0,1 +2,1,25.0,0,0,0 +2,1,23.0,1,0,0 +1,0,19.0,0,0,1 +1,1,37.0,0,0,0 +3,1,16.0,0,0,0 +1,1,24.0,1,0,0 +3,0,4.574166666666667,1,0,0 +3,0,22.0,0,0,1 +3,0,24.0,0,0,1 +3,1,19.0,0,1,0 +2,1,18.0,0,0,0 +2,1,19.0,0,0,0 +3,1,27.0,0,0,1 +3,0,9.0,0,0,0 +2,1,36.5,0,0,0 +2,1,42.0,0,0,0 +2,1,51.0,0,0,0 +1,0,22.0,0,0,1 +3,1,55.5,0,0,0 +3,1,40.5,0,0,0 +3,1,42.0,0,0,0 +1,1,51.0,1,0,0 +3,0,16.0,0,1,1 +3,1,30.0,0,0,0 +3,1,42.0,0,0,0 +3,1,40.0,0,0,0 +3,1,44.0,0,0,0 +2,0,40.0,0,0,1 +3,1,26.0,0,0,0 +3,1,17.0,0,0,0 +3,1,1.0,0,0,0 +3,1,9.0,0,0,1 +1,0,4.574166666666667,0,0,1 +3,0,45.0,0,0,0 +1,1,42.0,0,0,0 +3,1,28.0,0,0,0 +1,1,61.0,0,0,0 +3,1,4.0,0,1,0 +3,0,1.0,0,0,1 +3,1,21.0,0,0,0 +1,1,56.0,1,0,0 +3,1,18.0,0,0,0 +3,1,40.0,0,0,0 +1,0,50.0,1,0,0 +2,1,30.0,0,0,0 +3,1,36.0,0,0,0 +3,0,70.0,0,0,0 +2,1,42.0,1,0,0 +3,1,9.0,0,0,0 +2,1,1.0,0,0,1 +3,0,4.0,0,0,1 +1,1,42.0,0,0,0 +3,0,4.574166666666667,0,1,1 +1,1,45.0,0,0,1 +3,1,40.0,0,1,0 +3,1,36.0,0,0,0 +2,0,32.0,0,0,1 +2,1,19.0,0,0,0 +3,0,19.0,0,0,1 +2,1,3.0,0,0,1 +1,0,44.0,1,0,1 +1,0,58.0,1,0,1 +3,1,42.0,0,1,0 +3,1,42.0,0,0,0 +3,0,70.0,0,1,1 +2,0,24.0,0,0,0 +3,1,28.0,0,0,0 +3,1,42.0,0,0,0 +3,1,34.0,0,0,0 +3,1,45.5,1,0,0 +3,1,18.0,0,0,1 +3,0,2.0,0,0,0 +3,1,32.0,0,0,0 +3,1,26.0,1,0,1 +3,0,16.0,0,1,1 +1,1,40.0,1,0,1 +3,1,24.0,0,0,0 +2,0,35.0,0,0,1 +3,1,22.0,0,0,0 +2,1,30.0,0,0,0 +3,1,42.0,0,1,0 +1,0,31.0,1,0,1 +3,0,27.0,0,0,1 +2,1,42.0,0,0,0 +1,0,32.0,1,0,1 +2,1,30.0,0,0,0 +3,1,16.0,0,0,1 +2,1,27.0,0,0,0 +3,1,51.0,0,0,0 +3,1,42.0,0,0,0 +1,1,38.0,0,0,1 +3,1,22.0,0,0,0 +2,1,19.0,0,0,1 +3,1,20.5,0,0,0 +2,1,18.0,0,0,0 +3,0,70.0,0,0,0 +1,0,35.0,0,0,1 +3,1,29.0,0,0,0 +2,1,59.0,0,0,0 +3,0,5.0,0,0,1 +2,1,24.0,0,0,0 +3,0,70.0,0,0,0 +2,1,44.0,0,0,0 +2,0,8.0,0,0,1 +2,1,19.0,0,0,0 +2,1,33.0,0,0,0 +3,0,70.0,1,0,0 +3,0,70.0,0,1,1 +2,1,29.0,0,0,0 +3,1,22.0,0,0,0 +3,1,30.0,1,0,0 +1,1,44.0,0,1,0 +3,0,25.0,0,0,0 +2,0,24.0,0,0,1 +1,1,37.0,0,0,1 +2,1,54.0,0,0,0 +3,1,42.0,0,0,0 +3,0,29.0,0,0,0 +1,1,62.0,0,0,0 +3,1,30.0,0,0,0 +3,0,41.0,0,0,0 +3,0,29.0,1,0,1 +1,0,4.574166666666667,1,0,1 +1,0,30.0,0,0,1 +1,0,35.0,1,0,1 +2,0,50.0,0,0,1 +3,1,42.0,0,1,0 +3,1,3.0,0,0,1 +1,1,52.0,0,0,0 +1,1,40.0,0,0,0 +3,0,70.0,0,1,0 +2,1,36.0,0,0,0 +3,1,16.0,0,0,0 +3,1,25.0,0,0,1 +1,0,58.0,0,0,1 +1,0,35.0,0,0,1 +1,1,42.0,0,0,0 +3,1,25.0,0,0,1 +2,0,41.0,0,0,1 +1,1,37.0,1,0,0 +3,0,70.0,0,1,1 +1,0,63.0,0,0,1 +3,0,45.0,0,0,0 +2,1,42.0,0,0,0 +3,1,7.0,0,1,0 +3,0,35.0,0,0,1 +3,1,65.0,0,1,0 +3,1,28.0,0,0,0 +3,1,16.0,0,0,0 +3,1,19.0,0,0,1 +1,1,42.0,0,0,0 +3,1,33.0,1,0,0 +3,1,30.0,0,0,1 +3,1,22.0,0,0,0 +2,1,42.0,0,0,1 +3,0,22.0,0,1,1 +1,0,26.0,0,0,1 +1,0,19.0,1,0,1 +2,1,36.0,1,0,0 +3,0,24.0,0,0,0 +3,1,24.0,0,0,0 +1,1,42.0,1,0,0 +3,1,23.5,1,0,0 +1,0,2.0,0,0,0 +1,1,42.0,0,0,1 +1,0,50.0,1,0,1 +3,0,70.0,0,1,1 +3,1,42.0,0,1,1 +3,1,19.0,0,0,0 +2,0,70.0,0,1,1 +3,1,42.0,0,0,0 +1,1,0.92,0,0,1 +1,0,70.0,1,0,1 +1,0,17.0,1,0,1 +2,1,30.0,1,0,0 +1,0,30.0,1,0,1 +1,0,24.0,1,0,1 +1,0,18.0,1,0,1 +2,0,26.0,0,0,0 +3,1,28.0,0,0,0 +2,1,43.0,0,0,0 +3,0,26.0,0,0,1 +2,0,24.0,0,0,1 +2,1,54.0,0,0,0 +1,0,31.0,0,0,1 +1,0,40.0,1,0,1 +3,1,22.0,0,0,0 +3,1,27.0,0,0,0 +2,0,30.0,0,1,1 +2,0,22.0,0,0,1 +3,1,42.0,0,0,0 +1,0,36.0,1,0,1 +3,1,61.0,0,0,0 +2,0,36.0,0,0,1 +3,0,31.0,0,0,1 +1,0,16.0,1,0,1 +3,0,70.0,0,1,1 +1,1,45.5,0,0,0 +1,1,38.0,0,0,0 +3,1,16.0,0,0,0 +1,0,4.574166666666667,0,0,1 +3,1,42.0,0,0,0 +1,1,29.0,0,0,0 +1,0,41.0,1,0,1 +3,1,45.0,0,0,1 +1,1,45.0,0,0,0 +2,1,2.0,0,0,1 +1,0,24.0,0,0,1 +2,1,28.0,0,0,0 +2,1,25.0,0,0,0 +2,1,36.0,0,0,0 +2,0,24.0,0,0,1 +2,0,40.0,0,0,1 +3,0,4.574166666666667,0,0,1 +3,1,3.0,0,0,1 +3,1,42.0,0,0,0 +3,1,23.0,0,0,0 +1,1,42.0,0,0,0 +3,1,15.0,1,0,0 +3,1,25.0,0,0,0 +3,1,42.0,1,0,0 +3,1,28.0,0,0,0 +1,0,22.0,0,0,1 +2,0,38.0,0,0,0 +3,0,70.0,0,1,1 +3,0,70.0,0,1,1 +3,1,40.0,0,0,0 +2,1,29.0,1,0,0 +3,0,45.0,1,0,0 +3,1,35.0,0,0,0 +3,1,42.0,0,1,0 +3,1,30.0,0,0,0 +1,0,60.0,1,0,1 +3,0,4.574166666666667,1,0,1 +3,0,70.0,0,1,1 +1,0,24.0,1,0,1 +1,1,25.0,1,0,1 +3,1,18.0,0,0,0 +3,1,19.0,0,0,0 +1,1,22.0,1,0,0 +3,0,3.0,0,0,0 +1,0,4.574166666666667,1,0,1 +3,0,22.0,0,0,1 +1,1,27.0,1,0,0 +3,1,20.0,1,0,0 +3,1,19.0,0,0,0 +1,0,42.0,1,0,1 +3,0,1.0,1,0,1 +3,1,32.0,0,0,0 +1,0,35.0,0,0,1 +3,1,42.0,0,0,0 +2,1,18.0,0,0,0 +3,1,1.0,0,0,0 +2,0,36.0,0,0,1 +3,1,42.0,0,1,0 +2,0,17.0,1,0,1 +1,1,36.0,0,0,1 +3,1,21.0,0,0,1 +3,1,28.0,0,0,0 +1,0,23.0,1,0,1 +3,0,24.0,0,0,1 +3,1,22.0,0,0,0 +3,0,31.0,0,0,0 +2,1,46.0,0,0,0 +2,1,23.0,0,0,0 +2,0,28.0,0,0,1 +3,1,39.0,0,0,1 +3,1,26.0,0,0,0 +3,0,21.0,0,0,0 +3,1,28.0,0,0,0 +3,0,20.0,0,0,0 +2,1,34.0,0,0,0 +3,1,51.0,0,0,0 +2,1,3.0,0,0,1 +3,1,21.0,0,0,0 +3,0,70.0,0,0,0 +3,1,42.0,0,0,0 +3,1,42.0,0,1,0 +1,0,33.0,0,1,1 +2,1,42.0,0,0,0 +3,1,44.0,0,0,1 +3,0,4.574166666666667,0,0,0 +2,0,34.0,0,0,1 +2,0,18.0,0,0,1 +2,1,30.0,0,0,0 +3,0,10.0,0,0,0 +3,1,42.0,1,0,0 +3,1,21.0,0,1,0 +3,1,29.0,0,0,0 +3,0,28.0,0,0,0 +3,1,18.0,0,0,0 +3,1,42.0,0,0,0 +2,0,28.0,0,0,1 +2,0,19.0,0,0,1 +3,1,42.0,0,1,0 +3,1,32.0,0,0,1 +1,1,28.0,0,0,1 +3,0,4.574166666666667,0,0,1 +2,0,42.0,0,0,1 +3,1,17.0,0,0,0 +1,1,50.0,0,0,0 +1,0,14.0,0,0,1 +3,0,21.0,0,0,0 +2,0,24.0,0,0,1 +1,1,64.0,0,0,0 +2,1,31.0,0,0,0 +2,0,45.0,0,0,1 +3,1,20.0,0,0,0 +3,1,25.0,0,0,0 +2,0,28.0,0,0,1 +3,1,42.0,0,0,1 +1,1,4.0,0,0,1 +2,0,13.0,0,0,1 +1,1,34.0,0,0,1 +3,0,5.0,1,0,1 +1,1,52.0,0,0,1 +2,1,36.0,0,0,0 +3,1,42.0,0,0,0 +1,1,30.0,1,0,0 +1,1,49.0,1,0,1 +3,1,42.0,0,0,0 +3,1,29.0,1,0,1 +1,1,65.0,0,0,0 +1,0,4.574166666666667,0,0,1 +2,0,50.0,0,0,1 +3,1,42.0,0,1,0 +1,1,48.0,0,0,1 +3,1,34.0,0,0,0 +1,1,47.0,0,0,0 +2,1,48.0,0,0,0 +3,1,42.0,0,0,0 +3,1,38.0,0,0,0 +2,1,42.0,0,0,0 +1,1,56.0,0,0,0 +3,1,42.0,0,1,0 +3,0,0.75,1,0,1 +3,1,42.0,0,0,0 +3,1,38.0,0,0,0 +2,0,33.0,0,0,1 +2,0,23.0,1,0,1 +3,0,22.0,0,0,0 +1,1,42.0,0,0,0 +2,1,34.0,0,0,0 +3,1,29.0,0,0,0 +3,1,22.0,0,0,0 +3,0,2.0,0,0,1 +3,1,9.0,0,0,0 +2,1,42.0,0,0,0 +3,1,50.0,0,0,0 +3,0,63.0,0,0,1 +1,1,25.0,1,0,1 +3,0,70.0,0,0,0 +1,0,35.0,0,0,1 +1,1,58.0,1,0,0 +3,1,30.0,0,0,0 +3,1,9.0,0,0,1 +3,1,42.0,0,0,0 +3,1,21.0,0,0,0 +1,1,55.0,0,0,0 +1,1,71.0,1,0,0 +3,1,21.0,0,0,0 +3,1,42.0,1,0,0 +1,0,54.0,1,0,1 +3,1,42.0,0,0,0 +1,0,25.0,0,0,0 +3,1,24.0,0,0,0 +3,1,17.0,0,0,0 +3,0,21.0,0,1,0 +3,0,70.0,0,1,0 +3,0,37.0,0,0,0 +1,0,16.0,0,0,1 +1,1,18.0,1,0,0 +2,0,33.0,0,0,1 +1,1,42.0,0,0,1 +3,1,28.0,0,0,0 +3,1,26.0,0,0,1 +3,1,29.0,0,1,1 +3,1,42.0,0,0,0 +1,1,36.0,0,0,1 +1,0,54.0,1,0,1 +3,1,24.0,0,0,0 +1,1,47.0,0,0,0 +2,0,34.0,0,0,1 +3,1,42.0,0,1,0 +2,0,36.0,0,0,1 +3,1,32.0,0,0,0 +1,0,30.0,0,0,1 +3,1,22.0,0,0,0 +3,1,42.0,1,0,0 +1,0,44.0,1,0,1 +3,1,42.0,1,0,0 +3,1,40.5,0,1,0 +2,0,50.0,0,0,1 +1,1,42.0,0,0,0 +3,1,39.0,0,0,0 +2,1,23.0,0,0,0 +2,0,2.0,0,0,1 +3,1,42.0,1,0,0 +3,1,17.0,1,0,0 +3,0,4.574166666666667,1,0,1 +3,0,30.0,0,0,0 +2,0,7.0,0,0,1 +1,1,45.0,0,0,0 +1,0,30.0,1,0,1 +3,1,42.0,0,0,0 +1,0,22.0,1,0,1 +1,0,36.0,0,0,1 +3,0,9.0,0,0,0 +3,0,11.0,0,0,0 +2,1,32.0,0,0,1 +1,1,50.0,1,0,0 +1,1,64.0,0,0,0 +2,0,19.0,0,0,1 +2,1,42.0,1,0,1 +3,1,33.0,0,0,0 +2,1,8.0,0,0,1 +1,1,17.0,1,0,1 +2,1,27.0,0,0,0 +3,1,42.0,0,1,0 +3,1,22.0,1,0,1 +3,0,22.0,0,0,1 +1,1,62.0,0,0,0 +1,0,48.0,1,0,1 +1,1,42.0,1,0,0 +1,0,39.0,0,0,1 +3,0,36.0,0,0,1 +3,1,42.0,0,1,0 +3,1,40.0,0,0,0 +2,1,28.0,0,0,0 +3,1,42.0,0,0,0 +3,0,70.0,0,0,0 +3,1,24.0,0,0,0 +3,1,19.0,0,0,0 +3,0,29.0,0,0,0 +3,1,42.0,1,0,0 +3,1,32.0,0,0,1 +2,1,62.0,0,0,1 +1,0,53.0,0,0,1 +1,1,36.0,0,0,1 +3,0,70.0,0,1,1 +3,1,16.0,0,0,0 +3,1,19.0,0,0,0 +2,0,34.0,0,0,1 +1,0,39.0,0,0,1 +3,0,4.574166666666667,1,0,0 +3,1,32.0,0,0,1 +2,0,25.0,0,0,1 +1,0,39.0,1,0,1 +2,1,54.0,0,0,0 +1,1,36.0,1,0,0 +3,1,42.0,1,0,0 +1,0,18.0,0,0,1 +2,1,47.0,0,0,0 +1,1,60.0,1,0,1 +3,1,22.0,0,0,0 +3,1,42.0,0,0,0 +3,1,35.0,0,0,0 +1,0,52.0,1,0,1 +3,1,47.0,0,0,0 +3,0,70.0,0,1,0 +2,1,37.0,0,0,0 +3,1,36.0,0,0,0 +2,0,70.0,0,0,1 +3,1,49.0,0,0,0 +3,1,42.0,1,0,0 +1,1,49.0,1,0,1 +2,0,24.0,0,0,1 +3,1,42.0,0,0,0 +1,1,42.0,0,0,0 +3,1,44.0,0,0,0 +1,1,35.0,1,0,1 +3,1,36.0,0,0,0 +3,1,30.0,0,0,0 +1,1,27.0,0,0,1 +2,0,22.0,1,0,1 +1,0,40.0,0,0,1 +3,0,39.0,0,0,0 +3,1,42.0,0,0,0 +3,0,70.0,0,1,1 +3,1,42.0,0,1,0 +3,1,35.0,0,0,0 +2,0,24.0,0,0,1 +3,1,34.0,0,0,0 +3,0,26.0,0,0,0 +2,0,4.0,0,0,1 +2,1,26.0,0,0,0 +3,1,27.0,1,0,0 +1,1,42.0,0,0,1 +3,1,20.0,1,0,1 +3,1,21.0,0,0,0 +3,1,21.0,0,0,0 +1,1,61.0,0,0,0 +2,1,57.0,0,1,0 +1,0,21.0,0,0,1 +3,1,26.0,0,0,0 +3,1,42.0,0,1,0 +1,1,80.0,0,0,1 +3,1,51.0,0,0,0 +1,1,32.0,1,0,1 +1,1,42.0,0,0,0 +3,0,9.0,0,0,0 +2,0,28.0,0,0,1 +3,1,32.0,0,0,0 +2,1,31.0,0,0,0 +3,0,41.0,0,0,0 +3,1,42.0,0,0,0 +3,1,20.0,0,0,0 +1,0,24.0,1,0,1 +3,0,2.0,0,0,0 +3,1,42.0,0,0,1 +3,0,0.75,1,0,1 +1,1,48.0,1,0,1 +3,1,19.0,0,0,0 +1,1,56.0,1,0,1 +3,1,42.0,0,0,0 +3,0,23.0,0,0,1 +3,1,42.0,0,0,0 +2,0,18.0,0,0,1 +3,1,21.0,0,0,0 +3,0,70.0,0,1,1 +3,0,18.0,0,1,0 +2,1,24.0,0,0,0 +3,1,42.0,0,0,0 +3,0,32.0,0,1,0 +2,1,23.0,0,0,0 +1,1,58.0,1,0,0 +1,1,50.0,0,0,1 +3,1,40.0,1,0,0 +1,1,47.0,0,0,0 +3,1,36.0,0,0,0 +3,1,20.0,0,0,1 +2,1,32.0,0,0,0 +2,1,25.0,0,0,0 +3,1,42.0,0,0,0 +3,1,43.0,0,0,0 +1,0,4.574166666666667,0,0,1 +2,0,40.0,0,0,1 +1,1,31.0,0,0,0 +2,1,70.0,0,0,0 +2,1,31.0,0,0,1 +2,1,42.0,0,0,0 +3,1,18.0,0,0,0 +3,1,24.5,0,0,0 +3,0,18.0,0,0,1 +3,0,43.0,0,0,0 +1,1,36.0,1,0,1 +3,0,70.0,0,1,0 +1,1,27.0,1,0,1 +3,1,20.0,0,0,0 +3,1,14.0,0,0,0 +2,1,60.0,0,0,0 +2,1,25.0,1,0,0 +3,1,14.0,0,0,0 +3,1,19.0,0,0,0 +3,1,18.0,0,0,0 +1,0,15.0,0,0,1 +1,1,31.0,0,0,1 +3,0,4.0,1,0,1 +3,1,42.0,0,0,1 +3,1,25.0,1,0,0 +1,1,60.0,0,0,0 +2,1,52.0,0,0,0 +3,1,44.0,0,0,0 +3,0,70.0,0,1,1 +1,1,49.0,1,0,0 +3,1,42.0,0,0,0 +1,0,18.0,1,0,1 +1,1,35.0,0,0,1 +3,0,18.0,1,0,0 +3,1,25.0,0,1,0 +3,1,26.0,0,0,0 +2,1,39.0,0,0,0 +2,0,45.0,0,0,1 +1,1,42.0,0,0,1 +1,0,22.0,0,0,1 +3,1,40.0,1,0,1 +1,0,24.0,1,0,1 +1,1,42.0,0,0,0 +1,1,48.0,0,0,1 +3,1,29.0,0,0,0 +2,1,52.0,0,0,0 +3,1,19.0,0,0,0 +1,0,38.0,1,0,1 +2,0,27.0,0,0,1 +3,1,42.0,0,1,0 +3,1,33.0,0,0,0 +2,0,6.0,0,0,1 +3,1,17.0,0,0,0 +2,1,34.0,0,0,0 +2,1,50.0,0,0,0 +1,1,27.0,0,0,1 +3,1,20.0,0,0,0 +2,0,30.0,0,0,1 +3,0,70.0,0,1,1 +2,1,25.0,0,0,0 +3,0,25.0,0,0,0 +1,0,29.0,0,0,1 +3,1,11.0,1,0,0 +2,1,42.0,0,0,0 +2,1,23.0,0,0,0 +2,1,23.0,0,0,0 +3,1,28.5,0,0,0 +3,0,48.0,0,0,0 +1,1,35.0,1,0,1 +3,1,42.0,0,0,0 +3,1,42.0,0,0,0 +1,1,42.0,0,0,1 +1,1,36.0,0,0,0 +1,0,21.0,1,0,1 +3,1,24.0,0,0,0 +3,1,31.0,0,0,1 +1,1,70.0,0,0,0 +3,1,16.0,0,0,0 +2,0,30.0,0,0,1 +1,1,19.0,0,0,0 +3,1,31.0,0,1,0 +2,0,4.0,0,0,1 +3,1,6.0,0,0,1 +3,1,33.0,0,0,0 +3,1,23.0,0,0,0 +2,0,48.0,0,0,1 +2,1,0.67,0,0,1 +3,1,28.0,0,0,0 +2,1,18.0,0,0,0 +3,1,34.0,0,0,0 +1,0,33.0,0,0,1 +3,1,42.0,0,0,0 +3,1,41.0,0,0,0 +3,1,20.0,1,0,1 +1,0,36.0,0,0,1 +3,1,16.0,0,0,0 +1,0,51.0,0,0,1 +1,1,35.898148148148145,1,0,0 +3,0,30.5,0,1,0 +3,1,42.0,0,1,0 +3,1,32.0,0,0,0 +3,1,24.0,0,0,0 +3,1,48.0,0,0,0 +2,0,57.0,0,0,0 +3,1,42.0,1,0,0 +2,0,54.0,0,0,1 +3,1,18.0,0,0,0 +3,1,42.0,0,1,0 +3,0,5.0,0,0,1 +3,1,42.0,0,1,0 +1,0,43.0,0,0,1 +3,0,13.0,1,0,1 +1,0,17.0,0,0,1 +1,1,29.0,0,0,0 +3,1,42.0,0,0,0 +3,1,25.0,0,0,0 +3,1,25.0,0,0,0 +3,0,18.0,0,0,1 +3,1,8.0,0,1,0 +3,1,1.0,0,0,1 +1,1,46.0,1,0,0 +3,1,42.0,0,1,0 +2,1,16.0,0,0,0 +3,0,70.0,0,0,0 +1,1,42.0,1,0,0 +3,1,25.0,0,0,0 +2,1,39.0,0,0,0 +1,0,49.0,0,0,1 +3,0,31.0,0,0,1 +3,1,30.0,1,0,0 +3,0,30.0,0,0,0 +2,1,34.0,0,0,0 +2,0,31.0,0,0,1 +1,1,11.0,0,0,1 +3,1,0.42,1,0,1 +3,1,27.0,0,0,1 +3,1,31.0,0,0,0 +1,1,39.0,0,0,0 +3,0,18.0,0,0,0 +2,1,39.0,0,0,0 +1,0,33.0,0,0,1 +3,1,26.0,0,0,0 +3,1,39.0,0,0,0 +2,1,35.0,0,0,0 +3,0,6.0,0,0,0 +3,1,30.5,0,0,0 +1,1,42.0,0,0,0 +3,0,23.0,0,0,0 +2,1,31.0,1,0,0 +3,1,43.0,0,0,0 +3,1,10.0,0,0,0 +1,0,52.0,0,0,1 +3,1,27.0,0,0,1 +1,1,38.0,0,0,0 +3,0,27.0,0,0,1 +3,1,2.0,0,0,0 +3,1,42.0,0,1,0 +3,1,42.0,0,0,0 +2,1,1.0,1,0,1 +3,1,42.0,0,1,1 +1,0,62.0,0,0,1 +3,0,15.0,1,0,1 +2,1,0.83,0,0,1 +3,1,42.0,1,0,0 +3,1,23.0,0,0,0 +3,1,18.0,0,0,0 +1,0,39.0,1,0,1 +3,1,21.0,0,0,0 +3,1,42.0,0,0,0 +3,1,32.0,0,0,1 +1,1,42.0,1,0,1 +3,1,20.0,0,0,0 +2,1,16.0,0,0,0 +1,0,30.0,1,0,1 +3,1,34.5,1,0,0 +3,1,17.0,0,0,0 +3,1,42.0,0,0,0 +3,1,42.0,0,0,0 +3,1,35.0,1,0,0 +2,1,28.0,0,0,0 +1,0,4.574166666666667,1,0,1 +3,1,4.0,0,0,0 +3,1,74.0,0,0,0 +3,0,9.0,1,0,0 +1,0,16.0,0,0,1 +2,0,44.0,0,0,0 +3,0,18.0,0,0,1 +1,0,45.0,0,0,1 +1,1,51.0,0,0,1 +3,0,24.0,1,0,1 +3,1,42.0,1,0,0 +3,1,41.0,0,0,0 +2,1,21.0,0,0,0 +1,0,48.0,0,0,1 +3,0,70.0,0,0,0 +2,1,24.0,0,0,0 +2,0,42.0,0,0,1 +2,0,27.0,1,0,1 +1,1,31.0,0,0,0 +3,1,42.0,0,0,0 +3,1,4.0,0,0,1 +3,1,26.0,0,0,0 +1,0,47.0,0,0,1 +1,1,33.0,0,0,0 +3,1,47.0,0,0,0 +2,0,28.0,1,0,1 +3,0,15.0,1,0,1 +3,1,20.0,0,0,0 +3,1,19.0,0,0,0 +3,1,42.0,0,0,0 +1,0,56.0,1,0,1 +2,0,25.0,0,0,1 +3,1,33.0,0,0,0 +3,0,22.0,0,0,0 +2,1,28.0,0,0,0 +3,1,25.0,0,0,0 +3,0,39.0,0,1,0 +2,1,27.0,0,0,0 +1,0,19.0,0,0,1 +3,0,70.0,0,0,0 +1,1,26.0,1,0,1 +3,1,32.0,0,1,0 diff --git a/hyppopy/tests/test_workflows.py b/hyppopy/tests/test_workflows.py new file mode 100644 index 0000000..ac64601 --- /dev/null +++ b/hyppopy/tests/test_workflows.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +# +# DKFZ +# +# +# Copyright (c) German Cancer Research Center, +# Division of Medical and Biological Informatics. +# All rights reserved. +# +# This software is distributed WITHOUT ANY WARRANTY; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. +# +# See LICENSE.txt or http://www.mitk.org for details. +# +# Author: Sven Wanner (s.wanner@dkfz.de) + +import os +import unittest +from hyppopy.globals import TESTDATA_DIR +IRIS_DATA = os.path.join(TESTDATA_DIR, 'Iris') +TITANIC_DATA = os.path.join(TESTDATA_DIR, 'Titanic') + +from hyppopy.workflows.svc_usecase.svc_usecase import svc_usecase +from hyppopy.workflows.randomforest_usecase.randomforest_usecase import randomforest_usecase + + +class Args(object): + + def __init__(self): + pass + + def set_arg(self, name, value): + setattr(self, name, value) + + +class WorkflowTestSuite(unittest.TestCase): + + def setUp(self): + self.results = [] + + def test_workflow_svc_on_iris_from_xml(self): + svc_args_xml = Args() + svc_args_xml.set_arg('plugin', '') + svc_args_xml.set_arg('data', IRIS_DATA) + svc_args_xml.set_arg('config', os.path.join(IRIS_DATA, 'svc_config.xml')) + uc = svc_usecase(svc_args_xml) + uc.run() + self.results.append(uc.get_results()) + self.assertTrue(uc.get_results().find("Solution") != -1) + + def test_workflow_rf_on_iris_from_xml(self): + rf_args_xml = Args() + rf_args_xml.set_arg('plugin', '') + rf_args_xml.set_arg('data', IRIS_DATA) + rf_args_xml.set_arg('config', os.path.join(IRIS_DATA, 'rf_config.xml')) + uc = svc_usecase(rf_args_xml) + uc.run() + self.results.append(uc.get_results()) + self.assertTrue(uc.get_results().find("Solution") != -1) + + def test_workflow_svc_on_iris_from_json(self): + svc_args_json = Args() + svc_args_json.set_arg('plugin', '') + svc_args_json.set_arg('data', IRIS_DATA) + svc_args_json.set_arg('config', os.path.join(IRIS_DATA, 'svc_config.json')) + uc = svc_usecase(svc_args_json) + uc.run() + self.results.append(uc.get_results()) + self.assertTrue(uc.get_results().find("Solution") != -1) + + def test_workflow_rf_on_iris_from_json(self): + rf_args_json = Args() + rf_args_json.set_arg('plugin', '') + rf_args_json.set_arg('data', IRIS_DATA) + rf_args_json.set_arg('config', os.path.join(IRIS_DATA, 'rf_config.json')) + uc = svc_usecase(rf_args_json) + uc.run() + self.results.append(uc.get_results()) + self.assertTrue(uc.get_results().find("Solution") != -1) + + # def test_workflow_svc_on_titanic_from_xml(self): + # svc_args_xml = Args() + # svc_args_xml.set_arg('plugin', '') + # svc_args_xml.set_arg('data', TITANIC_DATA) + # svc_args_xml.set_arg('config', os.path.join(TITANIC_DATA, 'svc_config.xml')) + # uc = svc_usecase(svc_args_xml) + # uc.run() + # self.results.append(uc.get_results()) + # self.assertTrue(uc.get_results().find("Solution") != -1) + + def test_workflow_rf_on_titanic_from_xml(self): + rf_args_xml = Args() + rf_args_xml.set_arg('plugin', '') + rf_args_xml.set_arg('data', TITANIC_DATA) + rf_args_xml.set_arg('config', os.path.join(TITANIC_DATA, 'rf_config.xml')) + uc = svc_usecase(rf_args_xml) + uc.run() + self.results.append(uc.get_results()) + self.assertTrue(uc.get_results().find("Solution") != -1) + + # def test_workflow_svc_on_titanic_from_json(self): + # svc_args_json = Args() + # svc_args_json.set_arg('plugin', '') + # svc_args_json.set_arg('data', TITANIC_DATA) + # svc_args_json.set_arg('config', os.path.join(TITANIC_DATA, 'svc_config.json')) + # uc = svc_usecase(svc_args_json) + # uc.run() + # self.results.append(uc.get_results()) + # self.assertTrue(uc.get_results().find("Solution") != -1) + + def test_workflow_rf_on_titanic_from_json(self): + rf_args_json = Args() + rf_args_json.set_arg('plugin', '') + rf_args_json.set_arg('data', TITANIC_DATA) + rf_args_json.set_arg('config', os.path.join(TITANIC_DATA, 'rf_config.json')) + uc = svc_usecase(rf_args_json) + uc.run() + self.results.append(uc.get_results()) + self.assertTrue(uc.get_results().find("Solution") != -1) + + def tearDown(self): + print("") + for r in self.results: + print(r) + + +if __name__ == '__main__': + unittest.main() + diff --git a/hyppopy/workflowbase.py b/hyppopy/workflowbase.py new file mode 100644 index 0000000..548c0e9 --- /dev/null +++ b/hyppopy/workflowbase.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# +# DKFZ +# +# +# Copyright (c) German Cancer Research Center, +# Division of Medical and Biological Informatics. +# All rights reserved. +# +# This software is distributed WITHOUT ANY WARRANTY; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. +# +# See LICENSE.txt or http://www.mitk.org for details. +# +# Author: Sven Wanner (s.wanner@dkfz.de) + +import hyppopy.solverfactory as sfac +from hyppopy.deepdict import DeepDict +from hyppopy.globals import SETTINGSPATH + +import os +import abc +import logging +from hyppopy.globals import DEBUGLEVEL +LOG = logging.getLogger(os.path.basename(__file__)) +LOG.setLevel(DEBUGLEVEL) + + +class Workflow(object): + _solver = None + _args = None + + def __init__(self, args): + self._args = args + factory = sfac.SolverFactory.instance() + if args.plugin is None or args.plugin == '': + dd = DeepDict(args.config) + ppath = "use_plugin" + if not dd.has_section(ppath): + msg = f"invalid config file, missing section {ppath}" + LOG.error(msg) + raise LookupError(msg) + plugin = dd[SETTINGSPATH+'/'+ppath] + else: + plugin = args.plugin + self._solver = factory.get_solver(plugin) + self.solver.read_parameter(args.config) + + def run(self): + self.setup() + self.solver.set_loss_function(self.blackbox_function) + self.solver.run() + self.test() + + def get_results(self): + return self.solver.get_results() + + @abc.abstractmethod + def setup(self): + raise NotImplementedError('the user has to implement this function') + + @abc.abstractmethod + def blackbox_function(self): + raise NotImplementedError('the user has to implement this function') + + @abc.abstractmethod + def test(self): + pass + + @property + def solver(self): + return self._solver + + @property + def args(self): + return self._args diff --git a/hyppopy/workflows/randomforest_usecase/randomforest_usecase.py b/hyppopy/workflows/randomforest_usecase/randomforest_usecase.py index 683a2af..18137ca 100644 --- a/hyppopy/workflows/randomforest_usecase/randomforest_usecase.py +++ b/hyppopy/workflows/randomforest_usecase/randomforest_usecase.py @@ -1,65 +1,60 @@ # -*- coding: utf-8 -*- # # DKFZ # # # Copyright (c) German Cancer Research Center, # Division of Medical and Biological Informatics. # All rights reserved. # # This software is distributed WITHOUT ANY WARRANTY; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. # # See LICENSE.txt or http://www.mitk.org for details. # # Author: Sven Wanner (s.wanner@dkfz.de) import os import numpy as np import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score -import hyppopy.solverfactory as sfac +from hyppopy.workflowbase import Workflow def data_loader(path, data_name, labels_name): if data_name.endswith(".npy"): if not labels_name.endswith(".npy"): raise IOError("Expect both data_name and labels_name being of type .npy!") data = [np.load(os.path.join(path, data_name)), np.load(os.path.join(path, labels_name))] elif data_name.endswith(".csv"): try: dataset = pd.read_csv(os.path.join(path, data_name)) y = dataset[labels_name].values X = dataset.drop([labels_name], axis=1).values data = [X, y] except Exception as e: print("Precondition violation, this usage case expects as data_name a " "csv file and as label_name a name of a column in this csv table!") else: raise NotImplementedError("This combination of data_name and labels_name " "does not yet exist, feel free to add it") return data -def randomforest_usecase(args): - print("Execute Random Forest UseCase...") +class randomforest_usecase(Workflow): - factory = sfac.SolverFactory.instance() - solver = factory.get_solver(args.plugin) - solver.read_parameter(args.config) + def __init__(self, args): + Workflow.__init__(self, args) - data = data_loader(args.data, solver.settings.data_name, solver.settings.labels_name) - solver.set_data(data) + def setup(self): + data = data_loader(self.args.data, self.solver.settings.data_name, self.solver.settings.labels_name) + self.solver.set_data(data) - def rf_loss(data, params): + def blackbox_function(self, data, params): if "n_estimators" in params.keys(): params["n_estimators"] = int(round(params["n_estimators"])) clf = RandomForestClassifier(**params) return -cross_val_score(estimator=clf, X=data[0], y=data[1], cv=3).mean() - - solver.set_loss_function(rf_loss) - solver.run() - solver.get_results() diff --git a/hyppopy/workflows/svc_usecase/svc_usecase.py b/hyppopy/workflows/svc_usecase/svc_usecase.py index 45ab10c..60ae83d 100644 --- a/hyppopy/workflows/svc_usecase/svc_usecase.py +++ b/hyppopy/workflows/svc_usecase/svc_usecase.py @@ -1,63 +1,58 @@ # -*- coding: utf-8 -*- # # DKFZ # # # Copyright (c) German Cancer Research Center, # Division of Medical and Biological Informatics. # All rights reserved. # # This software is distributed WITHOUT ANY WARRANTY; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. # # See LICENSE.txt or http://www.mitk.org for details. # # Author: Sven Wanner (s.wanner@dkfz.de) import os import numpy as np import pandas as pd from sklearn.svm import SVC from sklearn.model_selection import cross_val_score -import hyppopy.solverfactory as sfac +from hyppopy.workflowbase import Workflow def data_loader(path, data_name, labels_name): if data_name.endswith(".npy"): if not labels_name.endswith(".npy"): raise IOError("Expect both data_name and labels_name being of type .npy!") data = [np.load(os.path.join(path, data_name)), np.load(os.path.join(path, labels_name))] elif data_name.endswith(".csv"): try: dataset = pd.read_csv(os.path.join(path, data_name)) y = dataset[labels_name].values X = dataset.drop([labels_name], axis=1).values data = [X, y] except Exception as e: print("Precondition violation, this usage case expects as data_name a " "csv file and as label_name a name of a column in this csv table!") else: raise NotImplementedError("This combination of data_name and labels_name " "does not yet exist, feel free to add it") return data -def svc_usecase(args): - print("Execute SVC UseCase...") +class svc_usecase(Workflow): - factory = sfac.SolverFactory.instance() - solver = factory.get_solver(args.plugin) - solver.read_parameter(args.config) + def __init__(self, args): + Workflow.__init__(self, args) - data = data_loader(args.data, solver.settings.data_name, solver.settings.labels_name) - solver.set_data(data) + def setup(self): + data = data_loader(self.args.data, self.solver.settings.data_name, self.solver.settings.labels_name) + self.solver.set_data(data) - def svc_loss(data, params): + def blackbox_function(self, data, params): clf = SVC(**params) return -cross_val_score(estimator=clf, X=data[0], y=data[1], cv=3).mean() - - solver.set_loss_function(svc_loss) - solver.run() - solver.get_results()