popt.misc_tools.optim_tools

Collection of tools that can be used in optimization schemes. Only put tools here that are so general that they can be used by several optimization schemes. If some method is only applicable to the update scheme you are implementing, leave it in that class.

  1"""
  2Collection of tools that can be used in optimization schemes. Only put tools here that are so general that they can
  3be used by several optimization schemes. If some method is only applicable to the update scheme you are
  4implementing, leave it in that class.
  5"""
  6import numpy as np
  7from scipy.linalg import block_diag
  8import os
  9from datetime import datetime
 10from scipy.optimize import OptimizeResult
 11
 12
 13def aug_optim_state(state, list_state):
 14    """
 15    Augment the state variables to get one augmented array.
 16
 17    Input:
 18            - state:                Dictionary of state variable for optimization. OBS: 1D arrays!
 19            - list_state:           Fixed list of keys in state dict.
 20
 21    Output:
 22            - aug_state:            Augmented 1D array of state variables.
 23
 24    """
 25    # Start with ensemble of first state variable
 26    aug = state[list_state[0]]
 27
 28    # Loop over the next states (if exists)
 29    for i in range(1, len(list_state)):
 30        aug = np.hstack((aug, state[list_state[i]]))
 31
 32    # Return the augmented array
 33    return aug
 34
 35
 36def update_optim_state(aug_state, state, list_state):
 37    """
 38    Extract the separate state variables from an augmented state array. It is assumed that the augmented state
 39    array is made in aug_optim_state method, hence this is the reverse method.
 40
 41    Input:
 42            - aug_state:                Augmented state array. OBS: 1D array.
 43            - state:                    Dictionary of state variable for optimization.
 44            - list_state:               Fixed list of keys in state dict.
 45
 46    Output:
 47            - state:                    State dictionary updated with aug_state.
 48
 49    """
 50
 51    # Loop over all entries in list_state and extract an array with same number of rows as the key in state
 52    # determines from aug and replace the values in state[key].
 53    # Init. a variable to keep track of which row in 'aug' we start from in each loop
 54    aug_row = 0
 55    for _, key in enumerate(list_state):
 56        # Find no. rows in state[key] to determine how many rows from aug to extract
 57        no_rows = state[key].shape[0]
 58
 59        # Extract the rows from aug and update 'state[key]'
 60        state[key] = aug_state[aug_row:aug_row + no_rows]
 61
 62        # Update tracking variable for row in 'aug'
 63        aug_row += no_rows
 64
 65    # Return
 66    return state
 67
 68
 69def corr2BlockDiagonal(state, corr):
 70    """
 71    Makes the correlation matrix block diagonal. The blocks are the state varible types.
 72
 73    Parameters
 74    ----------
 75    state: dict
 76        Current control state, including state names
 77
 78    corr : array_like
 79        Correlation matrix, of shape (d, d)
 80
 81    Returns
 82    -------
 83    corr_blocks : list
 84        block matrices, one for each variable type
 85
 86    """
 87
 88    statenames = list(state.keys())
 89    corr_blocks = []
 90    for name in statenames:
 91        dim = state[name].size
 92        corr_blocks.append(corr[:dim, :dim])
 93        corr = corr[dim:, dim:]
 94    return corr_blocks
 95
 96
 97def time_correlation(a, state, n_timesteps, dt=1.0):
 98    """
 99    Constructs correlation matrix with time correlation
100    using an autoregressive model.
101
102    .. math::
103        Corr(t_1, t_2) = a^{|t_1 - t_2|}
104    
105    Assumes that each varaible in state is time-order such that
106    `x = [x1, x2,..., xi,..., xn]`, where `i` is the time index, 
107    and `xi` is d-dimensional.
108
109    Parameters
110    -------------------------------------------------------------
111    a : float
112        Correlation coef, in range (0, 1).
113
114    state : dict
115        Control state (represented in a dict).
116    
117    n_timesteps : int
118        Number of time-steps to correlate for each component.
119    
120    dt : float or int
121        Duration between each time-step. Default is 1.
122
123    Returns
124    -------------------------------------------------------------
125    out : numpy.ndarray
126        Correlation matrix with time correlation    
127    """
128    dim_states = [int(state[name].size/n_timesteps) for name in list(state.keys())]
129    blocks     = []
130
131    # Construct correlation matrix
132    # m: variable type index  
133    # i: first time index
134    # j: second time index
135    # k: first dim index
136    # l: second dim index
137    for m in dim_states:
138        corr_single_block = np.zeros((m*n_timesteps, m*n_timesteps))
139        for i in range(n_timesteps):
140            for j in range(n_timesteps):
141                for k in range(m):
142                    for l in range(m):
143                       corr_single_block[i*m + k, j*m + l] = (k==l)*a**abs(dt*(i-j))
144        blocks.append(corr_single_block)
145
146    return block_diag(*blocks)
147
148
149def cov2corr(cov):
150    """
151    Transfroms a covaraince matrix to a correlation matrix
152
153    Parameters
154    -------------
155    cov : array_like
156        The covaraince matrix, of shape (d,d).
157
158    Returns
159    -------------
160    out : numpy.ndarray
161        The correlation matrix, of shape (d,d)
162    """
163    std  = np.sqrt(np.diag(cov))
164    corr = np.divide(cov, np.outer(std, std))
165    return corr
166
167
168def corr2cov(corr, std):
169    """
170    Transfroms a correlation matrix to a covaraince matrix
171
172    Parameters
173    ----------
174    corr : array_like
175        The correlation matrix, of shape (d,d).
176
177    std : array_like
178        Array of the standard deviations, of shape (d, ).
179
180    Returns
181    -------
182    out : numpy.ndarray
183        The covaraince matrix, of shape (d,d)
184    """
185    cov = np.multiply(corr, np.outer(std, std))
186    return cov
187
188
189def get_sym_pos_semidef(a):
190    """
191    Force matrix to positive semidefinite
192
193    Parameters
194    ----------
195    a : array_like
196        The input matrix, of shape (d,d)
197
198    Returns
199    -------
200    a : numpy.ndarray
201        The positive semidefinite matrix, of shape (d,d)
202    """
203
204    rtol = 1e-05
205    if not isinstance(a, int):
206        S, U = np.linalg.eigh(a)
207        if not np.all(S > 0):
208            S = np.clip(S, rtol, None)
209            a = (U * S) @ U.T
210    else:
211        a = np.maximum(a, rtol)
212    return a
213
214
215def clip_state(x, bounds):
216    """
217    Clip a state vector according to the bounds
218
219    Parameters
220    ----------
221    x : array_like
222        The input state
223
224    bounds : array_like
225        (min, max) pairs for each element in x. None is used to specify no bound.
226
227    Returns
228    -------
229    x : numpy.ndarray
230        The state after truncation
231    """
232
233    any_not_none = any(any(item) for item in bounds)
234    if any_not_none:
235        lb = np.array(bounds)[:, 0]
236        lb = np.where(lb is None, -np.inf, lb)
237        ub = np.array(bounds)[:, 1]
238        ub = np.where(ub is None, -np.inf, ub)
239        x = np.clip(x, lb, ub)
240    return x
241
242
243def get_optimize_result(obj):
244    """
245    Collect optimize results based on requested
246
247    Parameters
248    ----------
249    obj : popt.loop.optimize.Optimize
250        An instance of an optimization class
251
252    Returns
253    -------
254    save_dict : scipy.optimize.OptimizeResult
255        The requested optimization results
256    """
257
258    # Initialize dictionary of variables to save
259    save_dict = OptimizeResult({'success': True, 'x': obj.mean_state, 'fun': np.mean(obj.obj_func_values),
260                                'nit':  obj.iteration, 'nfev': obj.nfev, 'njev': obj.njev})
261    if hasattr(obj, 'epf') and obj.epf:
262        save_dict['epf_iteration'] = obj.epf_iteration
263
264    if 'savedata' in obj.options:
265
266        # Make sure "SAVEDATA" gives a list
267        if isinstance( obj.options['savedata'], list):
268            savedata = obj.options['savedata']
269        else:
270            savedata = [ obj.options['savedata']]
271      
272        # Loop over variables to store in save list
273        for save_typ in savedata:
274            if 'mean_state' in save_typ:
275                continue  # mean_state is alwaysed saved as 'x'
276            if save_typ in locals():
277                save_dict[save_typ] = eval('{}'.format(save_typ))
278            elif hasattr( obj, save_typ):
279                save_dict[save_typ] = eval(' obj.{}'.format(save_typ))
280            else:
281                print(f'Cannot save {save_typ}!\n\n')
282
283    if 'save_folder' in obj.options:
284        save_dict['save_folder'] = obj.options['save_folder']
285
286    return save_dict
287
288
289def save_optimize_results(intermediate_result):
290    """
291    Save optimize results
292
293    Parameters
294    ----------
295    intermediate_result : scipy.optimize.OptimizeResult
296        An instance of an OptimizeResult class
297    """
298    # Cast to OptimizeResult if a ndarray is passed as argument
299    if type(intermediate_result) is np.ndarray:
300        intermediate_result = OptimizeResult({'x': intermediate_result})
301
302    # Make folder (if it does not exist)
303    if 'save_folder' in intermediate_result:
304        save_folder = intermediate_result['save_folder']
305        if not os.path.exists(save_folder):
306            os.makedirs(save_folder)
307    else:
308        save_folder = './'
309
310    if 'nit' in intermediate_result:
311        suffix = str(intermediate_result['nit'])
312    else:
313        now = datetime.now()  # current date and time
314        suffix = now.strftime("%m_%d_%Y_%H_%M_%S")
315
316    # Save the variables
317    if 'epf_iteration' in intermediate_result:
318        np.savez(save_folder + '/optimize_result_{0}_{1}'.format(suffix, str(intermediate_result['epf_iteration'])), **intermediate_result)
319    else:
320        np.savez(save_folder + '/optimize_result_{0}'.format(suffix), **intermediate_result)
def aug_optim_state(state, list_state):
14def aug_optim_state(state, list_state):
15    """
16    Augment the state variables to get one augmented array.
17
18    Input:
19            - state:                Dictionary of state variable for optimization. OBS: 1D arrays!
20            - list_state:           Fixed list of keys in state dict.
21
22    Output:
23            - aug_state:            Augmented 1D array of state variables.
24
25    """
26    # Start with ensemble of first state variable
27    aug = state[list_state[0]]
28
29    # Loop over the next states (if exists)
30    for i in range(1, len(list_state)):
31        aug = np.hstack((aug, state[list_state[i]]))
32
33    # Return the augmented array
34    return aug

Augment the state variables to get one augmented array.

Input: - state: Dictionary of state variable for optimization. OBS: 1D arrays! - list_state: Fixed list of keys in state dict.

Output: - aug_state: Augmented 1D array of state variables.

def update_optim_state(aug_state, state, list_state):
37def update_optim_state(aug_state, state, list_state):
38    """
39    Extract the separate state variables from an augmented state array. It is assumed that the augmented state
40    array is made in aug_optim_state method, hence this is the reverse method.
41
42    Input:
43            - aug_state:                Augmented state array. OBS: 1D array.
44            - state:                    Dictionary of state variable for optimization.
45            - list_state:               Fixed list of keys in state dict.
46
47    Output:
48            - state:                    State dictionary updated with aug_state.
49
50    """
51
52    # Loop over all entries in list_state and extract an array with same number of rows as the key in state
53    # determines from aug and replace the values in state[key].
54    # Init. a variable to keep track of which row in 'aug' we start from in each loop
55    aug_row = 0
56    for _, key in enumerate(list_state):
57        # Find no. rows in state[key] to determine how many rows from aug to extract
58        no_rows = state[key].shape[0]
59
60        # Extract the rows from aug and update 'state[key]'
61        state[key] = aug_state[aug_row:aug_row + no_rows]
62
63        # Update tracking variable for row in 'aug'
64        aug_row += no_rows
65
66    # Return
67    return state

Extract the separate state variables from an augmented state array. It is assumed that the augmented state array is made in aug_optim_state method, hence this is the reverse method.

Input: - aug_state: Augmented state array. OBS: 1D array. - state: Dictionary of state variable for optimization. - list_state: Fixed list of keys in state dict.

Output: - state: State dictionary updated with aug_state.

def corr2BlockDiagonal(state, corr):
70def corr2BlockDiagonal(state, corr):
71    """
72    Makes the correlation matrix block diagonal. The blocks are the state varible types.
73
74    Parameters
75    ----------
76    state: dict
77        Current control state, including state names
78
79    corr : array_like
80        Correlation matrix, of shape (d, d)
81
82    Returns
83    -------
84    corr_blocks : list
85        block matrices, one for each variable type
86
87    """
88
89    statenames = list(state.keys())
90    corr_blocks = []
91    for name in statenames:
92        dim = state[name].size
93        corr_blocks.append(corr[:dim, :dim])
94        corr = corr[dim:, dim:]
95    return corr_blocks

Makes the correlation matrix block diagonal. The blocks are the state varible types.

Parameters
  • state (dict): Current control state, including state names
  • corr (array_like): Correlation matrix, of shape (d, d)
Returns
  • corr_blocks (list): block matrices, one for each variable type
def time_correlation(a, state, n_timesteps, dt=1.0):
 98def time_correlation(a, state, n_timesteps, dt=1.0):
 99    """
100    Constructs correlation matrix with time correlation
101    using an autoregressive model.
102
103    .. math::
104        Corr(t_1, t_2) = a^{|t_1 - t_2|}
105    
106    Assumes that each varaible in state is time-order such that
107    `x = [x1, x2,..., xi,..., xn]`, where `i` is the time index, 
108    and `xi` is d-dimensional.
109
110    Parameters
111    -------------------------------------------------------------
112    a : float
113        Correlation coef, in range (0, 1).
114
115    state : dict
116        Control state (represented in a dict).
117    
118    n_timesteps : int
119        Number of time-steps to correlate for each component.
120    
121    dt : float or int
122        Duration between each time-step. Default is 1.
123
124    Returns
125    -------------------------------------------------------------
126    out : numpy.ndarray
127        Correlation matrix with time correlation    
128    """
129    dim_states = [int(state[name].size/n_timesteps) for name in list(state.keys())]
130    blocks     = []
131
132    # Construct correlation matrix
133    # m: variable type index  
134    # i: first time index
135    # j: second time index
136    # k: first dim index
137    # l: second dim index
138    for m in dim_states:
139        corr_single_block = np.zeros((m*n_timesteps, m*n_timesteps))
140        for i in range(n_timesteps):
141            for j in range(n_timesteps):
142                for k in range(m):
143                    for l in range(m):
144                       corr_single_block[i*m + k, j*m + l] = (k==l)*a**abs(dt*(i-j))
145        blocks.append(corr_single_block)
146
147    return block_diag(*blocks)

Constructs correlation matrix with time correlation using an autoregressive model.

$$Corr(t_1, t_2) = a^{|t_1 - t_2|}$$

Assumes that each varaible in state is time-order such that x = [x1, x2,..., xi,..., xn], where i is the time index, and xi is d-dimensional.

Parameters
  • a (float): Correlation coef, in range (0, 1).
  • state (dict): Control state (represented in a dict).
  • n_timesteps (int): Number of time-steps to correlate for each component.
  • dt (float or int): Duration between each time-step. Default is 1.
Returns
  • out (numpy.ndarray): Correlation matrix with time correlation
def cov2corr(cov):
150def cov2corr(cov):
151    """
152    Transfroms a covaraince matrix to a correlation matrix
153
154    Parameters
155    -------------
156    cov : array_like
157        The covaraince matrix, of shape (d,d).
158
159    Returns
160    -------------
161    out : numpy.ndarray
162        The correlation matrix, of shape (d,d)
163    """
164    std  = np.sqrt(np.diag(cov))
165    corr = np.divide(cov, np.outer(std, std))
166    return corr

Transfroms a covaraince matrix to a correlation matrix

Parameters
  • cov (array_like): The covaraince matrix, of shape (d,d).
Returns
  • out (numpy.ndarray): The correlation matrix, of shape (d,d)
def corr2cov(corr, std):
169def corr2cov(corr, std):
170    """
171    Transfroms a correlation matrix to a covaraince matrix
172
173    Parameters
174    ----------
175    corr : array_like
176        The correlation matrix, of shape (d,d).
177
178    std : array_like
179        Array of the standard deviations, of shape (d, ).
180
181    Returns
182    -------
183    out : numpy.ndarray
184        The covaraince matrix, of shape (d,d)
185    """
186    cov = np.multiply(corr, np.outer(std, std))
187    return cov

Transfroms a correlation matrix to a covaraince matrix

Parameters
  • corr (array_like): The correlation matrix, of shape (d,d).
  • std (array_like): Array of the standard deviations, of shape (d, ).
Returns
  • out (numpy.ndarray): The covaraince matrix, of shape (d,d)
def get_sym_pos_semidef(a):
190def get_sym_pos_semidef(a):
191    """
192    Force matrix to positive semidefinite
193
194    Parameters
195    ----------
196    a : array_like
197        The input matrix, of shape (d,d)
198
199    Returns
200    -------
201    a : numpy.ndarray
202        The positive semidefinite matrix, of shape (d,d)
203    """
204
205    rtol = 1e-05
206    if not isinstance(a, int):
207        S, U = np.linalg.eigh(a)
208        if not np.all(S > 0):
209            S = np.clip(S, rtol, None)
210            a = (U * S) @ U.T
211    else:
212        a = np.maximum(a, rtol)
213    return a

Force matrix to positive semidefinite

Parameters
  • a (array_like): The input matrix, of shape (d,d)
Returns
  • a (numpy.ndarray): The positive semidefinite matrix, of shape (d,d)
def clip_state(x, bounds):
216def clip_state(x, bounds):
217    """
218    Clip a state vector according to the bounds
219
220    Parameters
221    ----------
222    x : array_like
223        The input state
224
225    bounds : array_like
226        (min, max) pairs for each element in x. None is used to specify no bound.
227
228    Returns
229    -------
230    x : numpy.ndarray
231        The state after truncation
232    """
233
234    any_not_none = any(any(item) for item in bounds)
235    if any_not_none:
236        lb = np.array(bounds)[:, 0]
237        lb = np.where(lb is None, -np.inf, lb)
238        ub = np.array(bounds)[:, 1]
239        ub = np.where(ub is None, -np.inf, ub)
240        x = np.clip(x, lb, ub)
241    return x

Clip a state vector according to the bounds

Parameters
  • x (array_like): The input state
  • bounds (array_like): (min, max) pairs for each element in x. None is used to specify no bound.
Returns
  • x (numpy.ndarray): The state after truncation
def get_optimize_result(obj):
244def get_optimize_result(obj):
245    """
246    Collect optimize results based on requested
247
248    Parameters
249    ----------
250    obj : popt.loop.optimize.Optimize
251        An instance of an optimization class
252
253    Returns
254    -------
255    save_dict : scipy.optimize.OptimizeResult
256        The requested optimization results
257    """
258
259    # Initialize dictionary of variables to save
260    save_dict = OptimizeResult({'success': True, 'x': obj.mean_state, 'fun': np.mean(obj.obj_func_values),
261                                'nit':  obj.iteration, 'nfev': obj.nfev, 'njev': obj.njev})
262    if hasattr(obj, 'epf') and obj.epf:
263        save_dict['epf_iteration'] = obj.epf_iteration
264
265    if 'savedata' in obj.options:
266
267        # Make sure "SAVEDATA" gives a list
268        if isinstance( obj.options['savedata'], list):
269            savedata = obj.options['savedata']
270        else:
271            savedata = [ obj.options['savedata']]
272      
273        # Loop over variables to store in save list
274        for save_typ in savedata:
275            if 'mean_state' in save_typ:
276                continue  # mean_state is alwaysed saved as 'x'
277            if save_typ in locals():
278                save_dict[save_typ] = eval('{}'.format(save_typ))
279            elif hasattr( obj, save_typ):
280                save_dict[save_typ] = eval(' obj.{}'.format(save_typ))
281            else:
282                print(f'Cannot save {save_typ}!\n\n')
283
284    if 'save_folder' in obj.options:
285        save_dict['save_folder'] = obj.options['save_folder']
286
287    return save_dict

Collect optimize results based on requested

Parameters
Returns
  • save_dict (scipy.optimize.OptimizeResult): The requested optimization results
def save_optimize_results(intermediate_result):
290def save_optimize_results(intermediate_result):
291    """
292    Save optimize results
293
294    Parameters
295    ----------
296    intermediate_result : scipy.optimize.OptimizeResult
297        An instance of an OptimizeResult class
298    """
299    # Cast to OptimizeResult if a ndarray is passed as argument
300    if type(intermediate_result) is np.ndarray:
301        intermediate_result = OptimizeResult({'x': intermediate_result})
302
303    # Make folder (if it does not exist)
304    if 'save_folder' in intermediate_result:
305        save_folder = intermediate_result['save_folder']
306        if not os.path.exists(save_folder):
307            os.makedirs(save_folder)
308    else:
309        save_folder = './'
310
311    if 'nit' in intermediate_result:
312        suffix = str(intermediate_result['nit'])
313    else:
314        now = datetime.now()  # current date and time
315        suffix = now.strftime("%m_%d_%Y_%H_%M_%S")
316
317    # Save the variables
318    if 'epf_iteration' in intermediate_result:
319        np.savez(save_folder + '/optimize_result_{0}_{1}'.format(suffix, str(intermediate_result['epf_iteration'])), **intermediate_result)
320    else:
321        np.savez(save_folder + '/optimize_result_{0}'.format(suffix), **intermediate_result)

Save optimize results

Parameters
  • intermediate_result (scipy.optimize.OptimizeResult): An instance of an OptimizeResult class