diff --git a/docs/api/controllers.rst b/docs/api/controllers.rst index 9af1001..4679486 100644 --- a/docs/api/controllers.rst +++ b/docs/api/controllers.rst @@ -1,7 +1,7 @@ .. _api-controllers: controllers ------------ +=========== .. automodule:: mloop.controllers :members: diff --git a/docs/api/index.rst b/docs/api/index.rst index 3d2ff16..b8d6915 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,5 +1,6 @@ .. _sec-api: +========== M-LOOP API ========== diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 80eb1e9..9d443c8 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -1,5 +1,5 @@ interfaces ----------- +========== .. automodule:: mloop.interfaces :members: diff --git a/docs/api/launchers.rst b/docs/api/launchers.rst index 7d3c105..3e9454c 100644 --- a/docs/api/launchers.rst +++ b/docs/api/launchers.rst @@ -1,5 +1,5 @@ launchers ---------- +========= .. automodule:: mloop.launchers :members: diff --git a/docs/api/learners.rst b/docs/api/learners.rst index 642105a..7385be9 100644 --- a/docs/api/learners.rst +++ b/docs/api/learners.rst @@ -1,7 +1,7 @@ .. _api-learners: learners ---------- +======== .. automodule:: mloop.learners :members: diff --git a/docs/api/mloop.rst b/docs/api/mloop.rst index a0127dd..affcb8f 100644 --- a/docs/api/mloop.rst +++ b/docs/api/mloop.rst @@ -1,4 +1,4 @@ mloop ------ +===== .. automodule:: mloop diff --git a/docs/api/t_esting.rst b/docs/api/t_esting.rst index 9bb25ae..1209b5a 100644 --- a/docs/api/t_esting.rst +++ b/docs/api/t_esting.rst @@ -1,5 +1,5 @@ testing -------- +======= .. automodule:: mloop.testing :members: diff --git a/docs/api/utilities.rst b/docs/api/utilities.rst index 1f22fb5..8e63990 100644 --- a/docs/api/utilities.rst +++ b/docs/api/utilities.rst @@ -1,5 +1,5 @@ utilities ---------- +========= .. automodule:: mloop.utilities :members: diff --git a/docs/api/visualizations.rst b/docs/api/visualizations.rst index f602372..91d7209 100644 --- a/docs/api/visualizations.rst +++ b/docs/api/visualizations.rst @@ -1,5 +1,5 @@ visualizations --------------- +============== .. automodule:: mloop.visualizations :members: diff --git a/docs/interfaces.rst b/docs/interfaces.rst index 497348e..926fb9d 100644 --- a/docs/interfaces.rst +++ b/docs/interfaces.rst @@ -54,7 +54,7 @@ Shell interface The shell interface is used when experiments can be run from a command in a shell. M-LOOP will still need to be configured and executed in the same manner described for a file interface as describe in :ref:`tutorial `. The only difference is how M-LOOP starts the experiment and reads data. To use this interface you must include the following options:: - interface='shell' + interface_type='shell' command='./run_exp' params_args_type='direct' diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 9c113f3..a7d0dd9 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -11,7 +11,7 @@ There are two different approaches to using M-LOOP: 1. You can execute M-LOOP from a command line (or shell) and configure it using a text file. 2. You can use M-LOOP as a :ref:`python API `. -If you have a standard experiment, that is operated by LabVIEW, Simulink or some other method, then your should use option 1 and follow the :ref:` first tutorial `. If your experiment is operated using python, you should consider using option 2 as it will give you more flexibility and control, in which case, look at the :ref:`second tutorial `. +If you have a standard experiment, that is operated by LabVIEW, Simulink or some other method, then you should use option 1 and follow the :ref:`first tutorial `. If your experiment is operated using python, you should consider using option 2 as it will give you more flexibility and control, in which case, look at the :ref:`second tutorial `. .. _sec-standard-experiment: @@ -31,7 +31,7 @@ There are three stages: M-LOOP - M-LOOP first looks for the configuration file *exp_input.txt*, which contains options like the number of parameters and their limits, in the folder it is executed, then starts the optimization process. + M-LOOP first looks for the configuration file *exp_config.txt*, which contains options like the number of parameters and their limits, in the folder it is executed, then starts the optimization process. 2. M-LOOP controls and optimizes the experiment by exchanging files written to disk. M-LOOP produces a file called *exp_input.txt* which contains a variable params with the next parameters to be run by the experiment. The experiment is expected to run an experiment with these parameters and measure the resultant cost. The experiment should then write the file *exp_output.txt* which contains at least the variable cost which quantifies the performance of that experimental run, and optionally, the variables uncer (for uncertainty) and bad (if the run failed). This process is repeated many times until the halting condition is met. @@ -68,15 +68,19 @@ You can add comments to your file using #, everything past # will be ignored. Ex num_params = 2 #number of parameters min_boundary = [-1,-1] #minimum boundary max_boundary = [1,1] #maximum boundary + first_params = [0.5,0.5] #first parameters to try + trust_region = 0.4 #maximum % move distance from best params #Halting conditions max_num_runs = 1000 #maximum number of runs max_num_runs_without_better_params = 50 #maximum number of runs without finding better parameters target_cost = 0.01 #optimization halts when a cost below this target is found + + #Learner options + cost_has_noise = True #whether the cost are corrupted by noise or not - #Learner specific options - first_params = [0.5,0.5] #first parameters to try - trust_region = 0.4 #maximum % move distance from best params + #Timing options + no_delay = True #wait for learner to make generate new parameters or use training algorithms #File format options interface_file_type = 'txt' #file types of *exp_input.mat* and *exp_output.mat* @@ -86,7 +90,7 @@ You can add comments to your file using #, everything past # will be ignored. Ex #Visualizations visualizations = True -We will now explain the options in each of their groups. In almost all cases you will only need to the parameters settings and halting conditions, but we have also describe a few of the most commonly used extra options. +We will now explain the options in each of their groups. In almost all cases you will only need to the parameters settings and halting conditions, but we have also described a few of the most commonly used extra options. Parameter settings ~~~~~~~~~~~~~~~~~~ @@ -99,6 +103,10 @@ The number of parameters and their limits is defined with three keywords:: num_params defines the number of parameters, min_boundary defines the minimum value each of the parameters can take and max_boundary defines the maximum value each parameter can take. Here there are two value which each must be between -1 and 1. +first_parameters defines the first parameters the learner will try. You only need to set this if you have a safe set of parameters you want the experiment to start with. Just delete this keyword if any set of parameters in the boundaries will work. + +trust_region defines the maximum change allowed in the parameters from the best parameters found so far. In the current example the region size is 2 by 2, with a trust region of 40% thus the maximum allowed change for the second run will be [0 +/- 0.8, 0 +/- 0.8]. This is only needed if your experiment produces bad results when the parameters are changes significantly between runs. Simply delete this keyword if your experiment works with any set of parameters within the boundaries. + Halting conditions ~~~~~~~~~~~~~~~~~~ @@ -107,6 +115,8 @@ The halting conditions define when the simulation will stop. We present three op max_num_runs = 100 max_num_runs_without_better_params = 10 target_cost = 0.1 + first_params = [0.5,0.5] + trust_region = 0.4 max_num_runs is the maximum number of runs that the optimization algorithm is allowed to run. max_num_runs_without_better_params is the maximum number of runs allowed before a lower cost and better parameters is found. Finally, when target_cost is set, if a run produces a cost that is less than this value the optimization process will stop. @@ -119,19 +129,23 @@ If you do not want one of the halting conditions, simply delete it from your fil max_num_runs_without_better_params = 10 -Learner specific options -~~~~~~~~~~~~~~~~~~~~~~~~ +Learner Options +~~~~~~~~~~~~~~~ -There are many learner specific options (and different learner algorithms) described in :ref:`sec-examples`. Here we consider just a couple of the most commonly used ones. M-LOOP has been designed to find an optimum quickly with no custom configuration as long as the experiment is able to provide a cost for every parameter it provides. +There are many learner specific options (and different learner algorithms) described in :ref:`sec-examples`. Here we just present a common one:: -However if your experiment will fail to work if there are sudden and significant changes to your parameters you may need to set the following options:: + cost_has_noise = True + +If the cost you provide has noise in it, meaning your the cost you calculate would fluctuate if you did multiple experiments with the same parameters, then set this flag to True. If the costs your provide have no noise then set this flag to False. M-LOOP will automatically determine if the costs have noise in them or not, so if you are unsure, just delete this keyword and it will use the default value of True. - first_parameters = [0.5,0.5] - trust_region = 0.4 +Timing options +~~~~~~~~~~~~~~ -first_parameters defines the first parameters the learner will try. trust_region defines the maximum change allowed in the parameters from the best parameters found so far. In the current example the region size is 2 by 2, with a trust region of 40% thus the maximum allowed change for the second run will be [0 +/- 0.8, 0 +/- 0.8]. +M-LOOP learns how the experiment works by fitting the parameters and costs using a gaussian process. This learning process can take some time. If M-LOOP is asked for new parameters before it has time to generate a new prediction, it will use the training algorithm to provide a new set of parameters to test. This allows for an experiment to be run while the learner is still thinking. The training algorithm by default is differential evolution, this algorithm is also used to do the first initial set of experiments which are then used to train M-LOOP. If you would prefer M-LOOP waits for the learner to come up with its best prediction before running another experiment you can change this behavior with the option:: -If you experiment reliably produces costs for any parameter set you will not need these settings and you can just delete them. + no_delay = True + +Set no_delay to true to ensure there is no pauses between experiments and set it to false if you to give M-LOOP to have the time to come up with its most informed choice. Sometimes doing fewer more intelligent experiments will lead to an optimal quicker than many quick unintelligent experiments. You can delete the keyword if you are unsure and it will default to True. File format options ~~~~~~~~~~~~~~~~~~~ @@ -178,7 +192,7 @@ When writing the file *exp_output.txt* there are three keywords and values you c cost refers to the cost calculated from the experimental data. uncer, is optional, and refers to the uncertainty in the cost measurement made. Note, M-LOOP by default assumes there is some noise corrupting costs, which is fitted and compensated for. Hence, if there is some noise in your costs which you are unable to predict from a single measurement, do not worry, you do not have to estimate uncer, you can just leave it out. Lastly bad can be used to indicate an experiment failed and was not able to produce a cost. If the experiment worked set bad = false and if it failed set bad = true. -Note you do not have to include all of the keywords, you must provide at least a cost or the bad keyword set to false. For example a successful run can simply be:: +Note you do not have to include all of the keywords, you must provide at least a cost or the bad keyword set to true. For example a successful run can simply be:: cost = 0.3 @@ -219,7 +233,7 @@ M-LOOP, by default, will produce a set of visualizations. These plots show the o Python controlled experiment ============================ -If you have an experiment that is already under python control you can use M-LOOP as an API. Below we go over the example python script *python_controlled_experiment.py* you should also read over the :ref:` first tutorial ` to get a general idea of how M-LOOP works. +If you have an experiment that is already under python control you can use M-LOOP as an API. Below we go over the example python script *python_controlled_experiment.py* you should also read over the :ref:`first tutorial ` to get a general idea of how M-LOOP works. When integrating M-LOOP into your laboratory remember that it will be controlling you experiment, not vice versa. Hence, at the top level of your python script you will execute M-LOOP which will then call on your experiment when needed. Your experiment will not be making calls of M-LOOP. diff --git a/examples/shell_interface_config.txt b/examples/shell_interface_config.txt index e988077..7fa786e 100644 --- a/examples/shell_interface_config.txt +++ b/examples/shell_interface_config.txt @@ -3,4 +3,4 @@ interface_type = 'shell' #The type of interface command = 'python shell_script.py' #The command for the command line to run the experiment to get a cost from the parameters -params_args_type = 'direct' #The format of the parameters when providing them on the command line. 'direct' simply appends them, e.g. python CLIscript.py 7 2 1, 'named' names each parameter, e.g. python CLIscript.py --param1 7 --param2 2 --param3 1 \ No newline at end of file +params_args_type = 'direct' #The format of the parameters when providing them on the command line. 'direct' simply appends them, e.g. python shell_script.py 7 2 1, 'named' names each parameter, e.g. python shell_script.py --param1 7 --param2 2 --param3 1 \ No newline at end of file diff --git a/examples/tutorial_config.txt b/examples/tutorial_config.txt index cc8216a..cd07d29 100644 --- a/examples/tutorial_config.txt +++ b/examples/tutorial_config.txt @@ -8,15 +8,19 @@ interface_type = 'file' num_params = 2 #number of parameters min_boundary = [-1,-1] #minimum boundary max_boundary = [1,1] #maximum boundary +first_params = [0.5,0.5] #first parameters to try +trust_region = 0.4 #maximum % move distance from best params #Halting conditions max_num_runs = 1000 #maximum number of runs max_num_runs_without_better_params = 50 #maximum number of runs without finding better parameters target_cost = 0.01 #optimization halts when a cost below this target is found -#Learner specific options -first_params = [0.5,0.5] #first parameters to try -trust_region = 0.4 #maximum % move distance from best params +#Learner options +cost_has_noise = True #whether the cost are corrupted by noise or not + +#Timing options +no_delay = True #wait for learner to make generate new parameters or use training algorithms #File format options interface_file_type = 'txt' #file types of *exp_input.mat* and *exp_output.mat* diff --git a/mloop/__init__.py b/mloop/__init__.py index 06df418..9e53155 100644 --- a/mloop/__init__.py +++ b/mloop/__init__.py @@ -12,5 +12,5 @@ import os -__version__= "2.1.0" +__version__= "2.1.1" __all__ = ['controllers','interfaces','launchers','learners','testing','utilities','visualizations','cmd'] \ No newline at end of file diff --git a/mloop/controllers.py b/mloop/controllers.py index e4b6964..d018367 100644 --- a/mloop/controllers.py +++ b/mloop/controllers.py @@ -282,7 +282,7 @@ def _get_cost_and_in_dict(self): except ValueError: self.log.error('One of the values you provided in the cost dict could not be converted into the right type.') raise - if self.curr_bad and 'cost' in dict: + if self.curr_bad and ('cost' in in_dict): self.log.warning('The cost provided with the bad run will be saved, but not used by the learners.') self.in_costs.append(self.curr_cost) @@ -334,6 +334,8 @@ def optimize(self): self._start_up() self._optimization_routine() log.info('Controller finished. Closing down M-LOOP. Please wait a moment...') + except ControllerInterrupt: + self.log.warning('Controller ended by interruption.') except (KeyboardInterrupt,SystemExit): log.warning('!!! Do not give the interrupt signal again !!! \n M-LOOP stopped with keyboard interupt or system exit. Please wait at least 1 minute for the threads to safely shut down. \n ') log.warning('Closing down controller.') @@ -392,22 +394,19 @@ def _optimization_routine(self): Runs controller main loop. Gives parameters to experiment and saves costs returned. ''' self.log.debug('Start controller loop.') - try: + self.log.info('Run:' + str(self.num_in_costs +1)) + next_params = self._first_params() + self._put_params_and_out_dict(next_params) + self.save_archive() + self._get_cost_and_in_dict() + while self.check_end_conditions(): self.log.info('Run:' + str(self.num_in_costs +1)) - next_params = self._first_params() + next_params = self._next_params() self._put_params_and_out_dict(next_params) self.save_archive() self._get_cost_and_in_dict() - while self.check_end_conditions(): - self.log.info('Run:' + str(self.num_in_costs +1)) - next_params = self._next_params() - self._put_params_and_out_dict(next_params) - self.save_archive() - self._get_cost_and_in_dict() - self.log.debug('End controller loop.') - except ControllerInterrupt: - self.log.warning('Controller ended by interruption.') - + self.log.debug('End controller loop.') + def _first_params(self): ''' Checks queue to get first parameters. @@ -619,7 +618,7 @@ def __init__(self, interface, self.new_params_event = self.gp_learner.new_params_event self.remaining_kwargs = self.gp_learner.remaining_kwargs self.generation_num = self.gp_learner.generation_num - + def _put_params_and_out_dict(self, params): ''' Override _put_params_and_out_dict function, used when the training learner creates parameters. Makes the defualt param_type the training type and sets last_training_run_flag. @@ -676,27 +675,35 @@ def _optimization_routine(self): ''' Overrides _optimization_routine. Uses the parent routine for the training runs. Implements a customized _optimization_rountine when running the Gaussian Process learner. ''' - #Run the training runs using the standard optimization routine. Adjust the number of max_runs - save_max_num_runs = self.max_num_runs - self.max_num_runs = self.num_training_runs - 1 + #Run the training runs using the standard optimization routine. self.log.debug('Starting training optimization.') - super(GaussianProcessController,self)._optimization_routine() - - #Start last training run self.log.info('Run:' + str(self.num_in_costs +1)) - next_params = self._next_params() + next_params = self._first_params() self._put_params_and_out_dict(next_params) - - #Begin GP optimization routine - self.max_num_runs = save_max_num_runs - - self.log.debug('Starting GP optimization.') - self.new_params_event.set() self.save_archive() self._get_cost_and_in_dict() + while (self.num_in_costs < self.num_training_runs) and self.check_end_conditions(): + self.log.info('Run:' + str(self.num_in_costs +1)) + next_params = self._next_params() + self._put_params_and_out_dict(next_params) + self.save_archive() + self._get_cost_and_in_dict() + + if self.check_end_conditions(): + #Start last training run + self.log.info('Run:' + str(self.num_in_costs +1)) + next_params = self._next_params() + self._put_params_and_out_dict(next_params) + + self.log.debug('Starting GP optimization.') + self.new_params_event.set() + self.save_archive() + self._get_cost_and_in_dict() + self.log.debug('End training runs.') + + gp_consec = 0 + gp_count = 0 - gp_consec = 0 - gp_count = 0 while self.check_end_conditions(): self.log.info('Run:' + str(self.num_in_costs +1)) if gp_consec==self.generation_num or (self.no_delay and self.gp_learner_params_queue.empty()): @@ -723,12 +730,7 @@ def _shut_down(self): self.log.debug('GP learner end set.') self.end_gp_learner.set() self.gp_learner.join() - #self.gp_learner.join(self.gp_learner.learner_wait*3) - ''' - if self.gp_learner.is_alive(): - self.log.warning('GP Learner did not join in time had to terminate.') - self.gp_learner.terminate() - ''' + self.log.debug('GP learner joined') last_dict = None while not self.gp_learner_params_queue.empty(): @@ -750,7 +752,7 @@ def _shut_down(self): self.archive_dict.update(last_dict) else: if self.gp_learner.predict_global_minima_at_end or self.gp_learner.predict_local_minima_at_end: - self.log.warning('GP Learner may not have closed properly unable to get best and/or all minima.') + self.log.info('GP Learner did not provide best and/or all minima.') super(GaussianProcessController,self)._shut_down() def print_results(self): diff --git a/mloop/launchers.py b/mloop/launchers.py index 5cafb09..a41e378 100644 --- a/mloop/launchers.py +++ b/mloop/launchers.py @@ -27,7 +27,6 @@ def launch_from_file(config_filename, except (IOError, OSError): print('Unable to open M-LOOP configuration file:' + repr(config_filename)) raise - file_kwargs.update(kwargs) #Main run sequence #Create interface and extract unused keywords diff --git a/mloop/learners.py b/mloop/learners.py index 14556f2..b4e8b76 100644 --- a/mloop/learners.py +++ b/mloop/learners.py @@ -919,18 +919,15 @@ def __init__(self, #Basic optimization settings num_params = int(self.training_dict['num_params']) - min_boundary = np.squeeze(np.array(self.training_dict['min_boundary'], dtype=float)) - max_boundary = np.squeeze(np.array(self.training_dict['max_boundary'], dtype=float)) + min_boundary = mlu.safe_cast_to_array(self.training_dict['min_boundary']) + max_boundary = mlu.safe_cast_to_array(self.training_dict['max_boundary']) #Configuration of the learner self.cost_has_noise = bool(self.training_dict['cost_has_noise']) - self.length_scale = np.squeeze(np.array(self.training_dict['length_scale'])) + self.length_scale = mlu.safe_cast_to_array(self.training_dict['length_scale']) self.length_scale_history = list(self.training_dict['length_scale_history']) self.noise_level = float(self.training_dict['noise_level']) - if isinstance(self.training_dict['noise_level_history'], np.ndarray): - self.noise_level_history = list(np.squeeze(self.training_dict['noise_level_history'])) - else: - self.noise_level_history = list( self.training_dict['noise_level_history']) + self.noise_level_history = mlu.safe_cast_to_list(self.training_dict['noise_level_history']) #Counters self.costs_count = int(self.training_dict['costs_count']) @@ -938,48 +935,39 @@ def __init__(self, self.params_count = int(self.training_dict['params_count']) #Data from previous experiment - self.all_params = np.array(self.training_dict['all_params'], dtype=float) - self.all_costs = np.squeeze(np.array(self.training_dict['all_costs'], dtype=float)) - self.all_uncers = np.squeeze(np.array(self.training_dict['all_uncers'], dtype=float)) - - if isinstance(self.training_dict['bad_run_indexs'], np.ndarray): - self.bad_run_indexs = list(np.squeeze(self.training_dict['bad_run_indexs'])) - else: - self.bad_run_indexs = list(self.training_dict['bad_run_indexs']) + self.all_params = np.array(self.training_dict['all_params']) + self.all_costs = mlu.safe_cast_to_array(self.training_dict['all_costs']) + self.all_uncers = mlu.safe_cast_to_array(self.training_dict['all_uncers']) + self.bad_run_indexs = mlu.safe_cast_to_list(self.training_dict['bad_run_indexs']) #Derived properties self.best_cost = float(self.training_dict['best_cost']) - self.best_params = np.squeeze(np.array(self.training_dict['best_params'], dtype=float)) + self.best_params = mlu.safe_cast_to_array(self.training_dict['best_params']) self.best_index = int(self.training_dict['best_index']) self.worst_cost = float(self.training_dict['worst_cost']) self.worst_index = int(self.training_dict['worst_index']) self.cost_range = float(self.training_dict['cost_range']) try: - self.predicted_best_parameters = np.squeeze(np.array(self.training_dict['predicted_best_parameters'])) + self.predicted_best_parameters = mlu.safe_cast_to_array(self.training_dict['predicted_best_parameters']) self.predicted_best_cost = float(self.training_dict['predicted_best_cost']) self.predicted_best_uncertainty = float(self.training_dict['predicted_best_uncertainty']) self.has_global_minima = True except KeyError: self.has_global_minima = False try: - self.local_minima_parameters = list(self.training_dict['local_minima_parameters']) - - if isinstance(self.training_dict['local_minima_costs'], np.ndarray): - self.local_minima_costs = list(np.squeeze(self.training_dict['local_minima_costs'])) - else: - self.local_minima_costs = list(self.training_dict['local_minima_costs']) - if isinstance(self.training_dict['local_minima_uncers'], np.ndarray): - self.local_minima_uncers = list(np.squeeze(self.training_dict['local_minima_uncers'])) - else: - self.local_minima_uncers = list(self.training_dict['local_minima_uncers']) + self.local_minima_parameters = mlu.safe_cast_to_list(self.training_dict['local_minima_parameters']) + self.local_minima_costs = mlu.safe_cast_to_list(self.training_dict['local_minima_costs']) + self.local_minima_uncers = mlu.safe_cast_to_list(self.training_dict['local_minima_uncers']) self.has_local_minima = True except KeyError: self.has_local_minima = False - - super(GaussianProcessLearner,self).__init__(num_params=num_params, + if 'num_params' in kwargs: + super(GaussianProcessLearner,self).__init__(**kwargs) + else: + super(GaussianProcessLearner,self).__init__(num_params=num_params, min_boundary=min_boundary, max_boundary=max_boundary, **kwargs) @@ -1073,9 +1061,9 @@ def __init__(self, if self.default_bad_uncertainty < 0: self.log.error('Default bad uncertainty must be positive.') raise ValueError - if (self.default_bad_cost is None) and (self.default_bad_cost is None): + if (self.default_bad_cost is None) and (self.default_bad_uncertainty is None): self.bad_defaults_set = False - elif (self.default_bad_cost is not None) and (self.default_bad_cost is not None): + elif (self.default_bad_cost is not None) and (self.default_bad_uncertainty is not None): self.bad_defaults_set = True else: self.log.error('Both the default cost and uncertainty must be set for a bad run or they must both be set to None.') @@ -1156,13 +1144,14 @@ def get_params_and_costs(self): new_costs = [] new_uncers = [] new_bads = [] - new_costs_count = 0 update_bads_flag = False while not self.costs_in_queue.empty(): (param, cost, uncer, bad) = self.costs_in_queue.get_nowait() + self.costs_count +=1 + if bad: - new_bads.append(self.data_count) + new_bads.append(self.costs_count-1) if self.bad_defaults_set: cost = self.default_bad_cost uncer = self.default_bad_uncertainty @@ -1181,18 +1170,15 @@ def get_params_and_costs(self): self.log.error('Provided uncertainty must be larger or equal to zero:' + repr(uncer)) uncer = max(float(uncer), self.minimum_uncertainty) - new_costs_count += 1 - self.costs_count +=1 - cost_change_flag = False if cost > self.worst_cost: self.worst_cost = cost - self.worst_index = self.costs_count + self.worst_index = self.costs_count-1 cost_change_flag = True if cost < self.best_cost: self.best_cost = cost self.best_params = param - self.best_index = self.costs_count + self.best_index = self.costs_count-1 cost_change_flag = True if cost_change_flag: self.cost_range = self.worst_cost - self.best_cost @@ -1202,7 +1188,8 @@ def get_params_and_costs(self): new_params.append(param) new_costs.append(cost) new_uncers.append(uncer) - + + if self.all_params.size==0: self.all_params = np.array(new_params, dtype=float) self.all_costs = np.array(new_costs, dtype=float) @@ -1212,13 +1199,15 @@ def get_params_and_costs(self): self.all_costs = np.concatenate((self.all_costs, np.array(new_costs, dtype=float))) self.all_uncers = np.concatenate((self.all_uncers, np.array(new_uncers, dtype=float))) + self.bad_run_indexs.append(new_bads) + if self.all_params.shape != (self.costs_count,self.num_params): self.log('Saved GP params are the wrong size. THIS SHOULD NOT HAPPEN:' + repr(self.all_params)) if self.all_costs.shape != (self.costs_count,): self.log('Saved GP costs are the wrong size. THIS SHOULD NOT HAPPEN:' + repr(self.all_costs)) if self.all_uncers.shape != (self.costs_count,): self.log('Saved GP uncertainties are the wrong size. THIS SHOULD NOT HAPPEN:' + repr(self.all_uncers)) - + if update_bads_flag: self.update_bads() @@ -1366,6 +1355,9 @@ def run(self): raise LearnerInterrupt() except LearnerInterrupt: pass + if self.predict_global_minima_at_end or self.predict_local_minima_at_end: + self.get_params_and_costs() + self.fit_gaussian_process() end_dict = {} if self.predict_global_minima_at_end: self.find_global_minima() diff --git a/mloop/utilities.py b/mloop/utilities.py index 87c90e3..2ec4b26 100644 --- a/mloop/utilities.py +++ b/mloop/utilities.py @@ -57,6 +57,8 @@ def _config_logger(log_filename = default_log_filename, file_log_level (Optional[int]) : Level of log output for file, default is logging.DEBUG = 10 console_log_level (Optional[int]) :Level of log output for console, defalut is logging.INFO = 20 + Returns: + dictionary: Dict with extra keywords not used by the logging configuration. ''' if not os.path.exists(log_foldername): os.makedirs(log_foldername) @@ -173,6 +175,49 @@ def check_file_type_supported(file_type): ''' return file_type == 'mat' or 'txt' or 'pkl' +def safe_cast_to_array(in_array): + ''' + Attempts to safely cast the input to an array. Takes care of border cases + + Args: + in_array (array or equivalent): The array (or otherwise) to be converted to a list. + + Returns: + array : array that has been squeezed and 0-D cases change to 1-D cases + + ''' + + out_array = np.squeeze(np.array(in_array)) + + if out_array.shape == (): + out_array = np.array([out_array[()]]) + + return out_array + +def safe_cast_to_list(in_array): + ''' + Attempts to safely cast a numpy array to a list, if not a numpy array just casts to list on the object. + + Args: + in_array (array or equivalent): The array (or otherwise) to be converted to a list. + + Returns: + list : List of elements from in_array + + ''' + + if isinstance(in_array, np.ndarray): + t_array = np.squeeze(in_array) + if t_array.shape == (): + out_list = [t_array[()]] + else: + out_list = list(t_array) + else: + out_list = list(in_array) + + return out_list + + class NullQueueListener(): ''' Shell class with start and stop functions that do nothing. Queue listener is not implemented in python 2. Current fix is to simply use the multiprocessing class to pipe straight to the cmd line if running on python 2. This is class is just a placeholder. diff --git a/mloop/visualizations.py b/mloop/visualizations.py index 9f47743..763b649 100644 --- a/mloop/visualizations.py +++ b/mloop/visualizations.py @@ -340,7 +340,7 @@ def plot_params_vs_generations(self): def create_gaussian_process_learner_visualizations(filename, file_type='pkl', plot_cross_sections=True, - plot_all_minima_vs_cost=True, + plot_all_minima_vs_cost=False, plot_hyperparameters_vs_run=True): ''' Runs the plots from a gaussian process learner file. @@ -351,7 +351,7 @@ def create_gaussian_process_learner_visualizations(filename, Keyword Args: file_type (Optional [string]): File type 'pkl' pickle, 'mat' matlab or 'txt' text. plot_cross_sections (Optional [bool]): If True plot predict landscape cross sections, else do not. Default True. - plot_all_minima_vs_cost (Optional [bool]): If True plot all minima parameters versus cost number, False does not. If None it will only make the plots if all minima were previously calculated. Default None. + plot_all_minima_vs_cost (Optional [bool]): If True plot all minima parameters versus cost number, False does not. If None it will only make the plots if all minima were previously calculated. Default False. ''' visualization = GaussianProcessVisualizer(filename, file_type=file_type) if plot_cross_sections: @@ -486,8 +486,7 @@ def plot_all_minima_vs_cost(self): ''' Produce figure of the all the local minima versus cost. ''' - if not self.has_all_minima: - self.find_all_minima() + self.find_all_minima() global figure_counter, legend_loc figure_counter += 1 plt.figure(figure_counter) diff --git a/setup.py b/setup.py index ef248c5..c6b6017 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ def main(): }, setup_requires=['pytest-runner'], - install_requires = ['pip>=7.0' + install_requires = ['pip>=7.0', 'docutils>=0.3', 'numpy>=1.11', 'scipy>=0.17', @@ -39,7 +39,7 @@ def main(): license = 'MIT', keywords = 'automated machine learning optimization optimisation science experiment quantum', url = 'https://github.com/michaelhush/M-LOOP/', - download_url = 'https://github.com/michaelhush/M-LOOP/tarball/v2.1.0', + download_url = 'https://github.com/michaelhush/M-LOOP/tarball/v2.1.1', classifiers = ['Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Science/Research', diff --git a/tests/test_units.py b/tests/test_units.py new file mode 100644 index 0000000..905143a --- /dev/null +++ b/tests/test_units.py @@ -0,0 +1,80 @@ +''' +Unit test for all of the example scripts provided in the examples folder. +''' +from __future__ import absolute_import, division, print_function + +import os +import unittest +import math +import mloop.interfaces as mli +import mloop.controllers as mlc +import numpy as np +import multiprocessing as mp + +class CostListInterface(mli.Interface): + def __init__(self, cost_list): + super(CostListInterface,self).__init__() + self.call_count = 0 + self.cost_list = cost_list + def get_next_cost_dict(self,params_dict): + if np.isfinite(self.cost_list[self.call_count]): + cost_dict = {'cost': self.cost_list[self.call_count]} + else: + cost_dict = {'bad': True} + self.call_count += 1 + return cost_dict + +class TestUnits(unittest.TestCase): + + def test_max_num_runs(self): + cost_list = [5.,4.,3.,2.,1.] + interface = CostListInterface(cost_list) + controller = mlc.create_controller(interface, + max_num_runs = 5, + target_cost = -1, + max_num_runs_without_better_params = 10) + controller.optimize() + self.assertTrue(controller.best_cost == 1.) + self.assertTrue(np.array_equiv(np.array(controller.in_costs), + np.array(cost_list))) + + + def test_max_num_runs_without_better_params(self): + cost_list = [1.,2.,3.,4.,5.] + interface = CostListInterface(cost_list) + controller = mlc.create_controller(interface, + max_num_runs = 10, + target_cost = -1, + max_num_runs_without_better_params = 4) + controller.optimize() + self.assertTrue(controller.best_cost == 1.) + self.assertTrue(np.array_equiv(np.array(controller.in_costs), + np.array(cost_list))) + + def test_target_cost(self): + cost_list = [1.,2.,-1.] + interface = CostListInterface(cost_list) + controller = mlc.create_controller(interface, + max_num_runs = 10, + target_cost = -1, + max_num_runs_without_better_params = 4) + controller.optimize() + self.assertTrue(controller.best_cost == -1.) + self.assertTrue(np.array_equiv(np.array(controller.in_costs), + np.array(cost_list))) + + def test_bad(self): + cost_list = [1., float('nan'),2.,float('nan'),-1.] + interface = CostListInterface(cost_list) + controller = mlc.create_controller(interface, + max_num_runs = 10, + target_cost = -1, + max_num_runs_without_better_params = 4) + controller.optimize() + self.assertTrue(controller.best_cost == -1.) + for x,y in zip(controller.in_costs,cost_list): + self.assertTrue(x==y or (math.isnan(x) and math.isnan(y))) + +if __name__ == "__main__": + mp.freeze_support() + unittest.main() \ No newline at end of file