• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LSDOlab / modopt / 24740018752

21 Apr 2026 06:40PM UTC coverage: 81.211% (+0.02%) from 81.196%
24740018752

push

github

anugrahjo
Fix a typo in pyproject

6155 of 7579 relevant lines covered (81.21%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.16
/modopt/core/postprocessing.py
1
import warnings
1✔
2
import numpy as np
1✔
3
try:
1✔
4
    import h5py
1✔
5
except ImportError:
×
6
    warnings.warn("h5py not found, saving and loading data disabled")
×
7
    h5py = None
×
8

9
def import_h5py_file(filepath):
1✔
10
    if h5py is None:
1✔
11
        raise ImportError("h5py not found, saving and loading data disabled")
×
12
    
13
    return h5py.File(filepath, 'r')
1✔
14
    
15
def print_record_contents(filepath, suppress_print=False):
1✔
16
    '''
17
    Print and return the contents of the record file.
18
    
19
    Parameters
20
    ----------
21
    filepath : str
22
        Path to the record file.
23
    suppress_print : bool, default=False
24
        If False, print the contents of the record file.
25
        Otherwise, return the contents as a tuple.
26

27
    Returns
28
    -------
29
    attributes : list
30
        List of attributes of optimization.
31
    opt_vars : list
32
        List of recorded optimizer variables.
33
    callback_vars : list
34
        List of recorded callback variables.
35
    results : list
36
        List of results of optimization.
37

38
    Examples
39
    --------
40
    >>> import numpy as np
41
    >>> import modopt as mo
42
    >>> obj = lambda x: np.sum(x**2)
43
    >>> grad = lambda x: 2*x
44
    >>> con = lambda x: np.array([x[0] + x[1], x[0] - x[1]])
45
    >>> jac = lambda x: np.array([[1, 1], [1, -1]])
46
    >>> xl = np.array([1.0, -np.inf])
47
    >>> x0 = np.array([500., 50.])
48
    >>> cl = 1.0
49
    >>> cu = np.array([1., np.inf])
50
    >>> problem = mo.ProblemLite(x0, obj=obj, grad=grad, con=con, jac=jac, xl=xl, cl=cl, cu=cu)
51
    >>> optimizer = mo.SLSQP(problem, recording=True)
52
    >>> results   = optimizer.solve()
53
    >>> from modopt.postprocessing import print_record_contents
54
    >>> print_record_contents(results['out_dir']+'/record.hdf5')  # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
55
    Available data in the record:
56
    -----------------------------
57
     - Attributes of optimization    : ['c_lower', 'c_scaler', 'c_upper', 'constrained', 'hot_start_from', 'modopt_output_files', 'nc', 
58
       'nx', 'o_scaler', 'problem_name', 'readable_outputs', 'recording', 'solver_name', 'solver_options-callback', 'solver_options-disp', 
59
       'solver_options-ftol', 'solver_options-maxiter', 'timestamp', 'visualize', 'x0', 'x_lower', 'x_scaler', 'x_upper']
60
     - Recorded optimizer variables  : ['x']
61
     - Recorded callback variables   : ['con', 'grad', 'jac', 'obj', 'x']
62
     - Results of optimization       : ['con_evals', 'fun', 'grad_evals', 'hess_evals', 'jac', 'jac_evals', 'message', 'nfev', 'nit', 
63
       'njev', 'obj_evals', 'out_dir', 'reused_callbacks', 'status', 'success', 'total_callbacks', 'x']
64
    ([...], [...], [...])
65
    '''
66
    file = import_h5py_file(filepath)
1✔
67

68
    attributes = list(file.attrs.keys())
1✔
69

70
    if 'iteration_0' in file.keys():
1✔
71
        opt_vars = list(file['iteration_0'].keys())
1✔
72
    else:
73
        opt_vars = []
×
74

75
    callback_vars = set()
1✔
76
    for key in file.keys():
1✔
77
        if key.startswith('callback_'):
1✔
78
            in_vars  = set(file[key]['inputs'].keys())
1✔
79
            out_vars = set(file[key]['outputs'].keys())
1✔
80
            callback_vars = callback_vars | in_vars | out_vars
1✔
81
    callback_vars  = sorted(list(callback_vars))
1✔
82

83
    try:
1✔
84
        results = list(file['results'].keys())
1✔
85
    except:
×
86
        results = []
×
87
        warnings.warn("No results found in the record file.")
×
88

89
    file.close()
1✔
90

91
    if not suppress_print:
1✔
92
        print("Available data in the record:")
1✔
93
        print("-----------------------------")
1✔
94
        print(f" - {'Attributes of optimization':30}:", attributes)
1✔
95
        print(f" - {'Recorded optimizer variables':30}:", opt_vars)
1✔
96
        print(f" - {'Recorded callback variables':30}:", callback_vars)
1✔
97
        print(f" - {'Results of optimization':30}:", results)
1✔
98

99
    return attributes, opt_vars, callback_vars, results
1✔
100

101
def load_results(filepath):
1✔
102
    '''
103
    Load the results of optimization from the record as a dictionary.
104

105
    Parameters
106
    ----------
107
    filepath : str
108
        Path to the record file.
109

110
    Returns
111
    -------
112
    out_data : dict
113
        Dictionary with optimization results.
114

115
    Examples
116
    --------
117
    >>> import numpy as np
118
    >>> import modopt as mo
119
    >>> obj = lambda x: np.sum(x**2)
120
    >>> grad = lambda x: 2*x
121
    >>> con = lambda x: np.array([x[0] + x[1], x[0] - x[1]])
122
    >>> jac = lambda x: np.array([[1, 1], [1, -1]])
123
    >>> xl = np.array([1.0, -np.inf])
124
    >>> x0 = np.array([500., 50.])
125
    >>> cl = 1.0
126
    >>> cu = np.array([1., np.inf])
127
    >>> problem = mo.ProblemLite(x0, obj=obj, grad=grad, con=con, jac=jac, xl=xl, cl=cl, cu=cu)
128
    >>> optimizer = mo.SLSQP(problem, recording=True)
129
    >>> results   = optimizer.solve()
130
    >>> from modopt.postprocessing import load_results
131
    >>> load_results(results['out_dir']+'/record.hdf5')  # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
132
    {'con_evals': 3, 'fun': 1.0000000209233804, 'grad_evals': 2, 'hess_evals': 0, 'jac': array([ 2.0...e+00, -2.0...e-08]), 
133
    'jac_evals': 2, 'message': 'Optimization terminated successfully', 'nfev': 2, 'nit': 2, 'njev': 2, 'obj_evals': 2, 
134
    'out_dir': '...', 'reused_callbacks': 0, 'status': 0, 'success': True, 'total_callbacks': 9, 'x': array([ 1.0..., -1.0...e-08])}
135

136
    '''
137
    result_dict = {}
1✔
138
    with import_h5py_file(filepath) as file:
1✔
139
        for key, dset in file["results"].items():
1✔
140
            if h5py.check_string_dtype(dset.dtype) is not None: # Check if the dataset is a string type
1✔
141
                result_dict[key] = dset.asstr()[()]
1✔
142
            else:
143
                val = dset[()]
1✔
144
                if isinstance(val, np.generic): # Base class for all NumPy scalar types.
1✔
145
                    result_dict[key] = val.item()
1✔
146
                else:
147
                    result_dict[key] = val
1✔
148
    return result_dict
1✔
149

150
def load_attributes(filepath):
1✔
151
    '''
152
    Load the attributes of optimization from the record as a dictionary.
153

154
    Parameters
155
    ----------
156
    filepath : str
157
        Path to the record file.
158

159
    Returns
160
    -------
161
    out_data : dict
162
        Dictionary with optimization attributes.
163

164
    Examples
165
    --------
166
    >>> import numpy as np
167
    >>> import modopt as mo
168
    >>> obj = lambda x: np.sum(x**2)
169
    >>> grad = lambda x: 2*x
170
    >>> con = lambda x: np.array([x[0] + x[1], x[0] - x[1]])
171
    >>> jac = lambda x: np.array([[1, 1], [1, -1]])
172
    >>> xl = np.array([1.0, -np.inf])
173
    >>> x0 = np.array([500., 50.])
174
    >>> cl = 1.0
175
    >>> cu = np.array([1., np.inf])
176
    >>> problem = mo.ProblemLite(x0, obj=obj, grad=grad, con=con, jac=jac, xl=xl, cl=cl, cu=cu)
177
    >>> optimizer = mo.SLSQP(problem, recording=True)
178
    >>> results   = optimizer.solve()
179
    >>> from modopt.postprocessing import load_attributes
180
    >>> load_attributes(results['out_dir']+'/record.hdf5')  # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
181
    {'c_lower': array([1., 1.]), 'c_scaler': array([1., 1.]), 'c_upper': array([ 1., inf]), 'constrained': True, 
182
    'hot_start_from': 'None', 'modopt_output_files': ['directory: ...', 'modopt_results.out', 'record.hdf5'], 
183
    'nc': 2, 'nx': 2, 'o_scaler': array([1.]), 'problem_name': 'unnamed_problem', 'readable_outputs': [], 
184
    'recording': 'True', 'solver_name': 'scipy-slsqp', 'solver_options-callback': 'None', 'solver_options-disp': False, 
185
    'solver_options-ftol': 1e-06, 'solver_options-maxiter': 100, 'timestamp': '...', 'visualize': [], 
186
    'x0': array([500.,  50.]), 'x_lower': array([  1., -inf]), 'x_scaler': array([1., 1.]), 'x_upper': array([inf, inf])}
187
    '''
188
    LIST_ATTRS = {"visualize", "modopt_output_files", "readable_outputs"}
1✔
189
    attr_dict = {}
1✔
190
    with import_h5py_file(filepath) as file: # This closes the file automatically
1✔
191
        for key, val in file.attrs.items():
1✔
192
            if key in LIST_ATTRS:
1✔
193
                attr_dict[key] = list(val)
1✔
194
            elif isinstance(val, np.generic): # Base class for all NumPy scalar types. 
1✔
195
                attr_dict[key] = val.item()
1✔
196
            else:
197
                attr_dict[key] = val
1✔
198
    return attr_dict
1✔
199

200

201
def load_variables(filepath, vars, callback_context=False):
1✔
202
    '''
203
    Load specified variable iterates from the record file.
204
    Returns a dictionary with the variable names as keys and lists of variable iterates as values.
205
    Note that the keys for callback variables will be prefixed with 'callback\_'
206
    as opposed to optimizer variables that will have its key as the specified variable name.
207

208
    Parameters
209
    ----------
210
    filepath : str
211
        Path to the record file.
212
    vars : str or list
213
        Variable names to load from the record file.
214
        If only specific scalar variables are needed from an array, use the format 'var_name[idx]'.
215
        For example, 'x[0]' will load the iterates for the first element of the array 'x', and
216
        'jac[i,j]' will load the iterates for the (i,j)-th element of the array 'jac'.
217
    callback_context : bool, default=False
218
        If True, load the callback index and inputs for each callback variable in ``vars``,
219
        in addition to the callback variable iterates.
220
        The context is stored as a separate list in the output dictionary with its key
221
        formatted as the variable name prefixed by 'callback\_context\_'.
222
        Each context in the list is a dictionary with the keys 'callback\_index', and
223
        the name of the input variable(s) used in the callback (e.g. 'x', 'lag\_mult', etc.).
224

225
    Returns
226
    -------
227
    out_data : dict
228
        Dictionary with variable names as keys and lists of variable iterates as values.
229
        Keys for callback variables is prefixed with 'callback\_'
230
        and keys for callback variable context is prefixed with 'callback\_context\_'.
231

232
    Examples
233
    --------
234
    >>> import numpy as np
235
    >>> import modopt as mo
236
    >>> obj = lambda x: np.sum(x**2)
237
    >>> grad = lambda x: 2*x
238
    >>> con = lambda x: np.array([x[0] + x[1], x[0] - x[1]])
239
    >>> jac = lambda x: np.array([[1, 1], [1, -1]])
240
    >>> xl = np.array([1.0, -np.inf])
241
    >>> x0 = np.array([500., 50.])
242
    >>> cl = 1.0
243
    >>> cu = np.array([1., np.inf])
244
    >>> problem = mo.ProblemLite(x0, obj=obj, grad=grad, con=con, jac=jac, xl=xl, cl=cl, cu=cu)
245
    >>> optimizer = mo.SLSQP(problem, recording=True)
246
    >>> results   = optimizer.solve()
247
    >>> from modopt.postprocessing import load_variables
248
    >>> load_variables(results['out_dir']+'/record.hdf5', ['x[0]', 'obj', 'con[1]', 'grad', 'jac[0,1]']) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
249
    {'x[0]': [500.0, 1.0000000..., 1.00000...], 
250
    'callback_x[0]': [500.0, 500.0, 500.0, 500.0, 500.0, 1.00000..., 1.000000..., 1.00000..., 1.000000...], 
251
    'callback_obj': [252500.0, 1.000000...], 'callback_con[1]': [450.0, 450.0, 1.000000...], 
252
    'callback_grad': [array([1000.,  100.]), array([ 2.00000...e+00, -2.0...e-08])], 'callback_jac[0,1]': [1.0, 1.0]}
253
    >>> load_variables(results['out_dir']+'/record.hdf5', ['x[0]', 'obj', 'con[1]', 'grad', 'jac[0,1]'], callback_context=True) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
254
    {'x[0]': [500.0, 1.0000000..., 1.00000...],
255
    'callback_x[0]': [500.0, 500.0, 500.0, 500.0, 500.0, 1.00000..., 1.000000..., 1.00000..., 1.000000...],
256
    'callback_context_x[0]': [{'callback_index': 0, 'x': array([500.,  50.])}, {'callback_index': 1, 'x': array([500.,  50.])}, ..., {'callback_index': 8, 'x': array([ 1.000...e+00, -1.0...e-08])}],
257
    'callback_obj': [252500.0, 1.000000...],
258
    'callback_context_obj': [{'callback_index': 1, 'x': array([500.,  50.])}, {'callback_index': 5, 'x': array([ 1.000...e+00, -1.0...e-08])}],
259
    'callback_con[1]': [450.0, 450.0, 1.000000...],
260
    'callback_context_con[1]': [{'callback_index': 0, 'x': array([500.,  50.])}, {'callback_index': 3, 'x': array([500.,  50.])}, {'callback_index': 6, 'x': array([ 1.000...e+00, -1.0...e-08])}],
261
    'callback_grad': [array([1000.,  100.]), array([ 2.00000...e+00, -2.0...e-08])],
262
    'callback_context_grad': [{'callback_index': 2, 'x': array([500.,  50.])}, {'callback_index': 7, 'x': array([ 1.000...e+00, -1.0...e-08])}],
263
    'callback_jac[0,1]': [1.0, 1.0],
264
    'callback_context_jac[0,1]': [{'callback_index': 4, 'x': array([500.,  50.])}, {'callback_index': 8, 'x': array([ 1.000...e+00, -1.0...e-08])}]}
265

266
    '''
267
    if not isinstance(filepath, str):
1✔
268
        raise ValueError("'filepath' must be a string.")
×
269
    if not isinstance(vars, (str, list)):
1✔
270
        raise ValueError("'vars' must be a string or a list of strings")
×
271
    if isinstance(vars, str):
1✔
272
        vars = [vars]
×
273
    if not all(isinstance(var, str) for var in vars):
1✔
274
        raise ValueError("'vars' must be a string or a list of strings")
×
275
    
276
    attrs, opt_vars, callback_vars, results = print_record_contents(filepath, suppress_print=True)
1✔
277
    
278
    file  = import_h5py_file(filepath)
1✔
279
    n_iter = len([key for key in file.keys() if key.startswith('iteration_')])
1✔
280
    n_cb   = len([key for key in file.keys() if key.startswith('callback_')])
1✔
281

282
    out_data = {}
1✔
283
    for in_var in vars:
1✔
284
        var = in_var.split('[')[0]
1✔
285
        if var not in opt_vars+callback_vars:
1✔
286
            raise ValueError(f"Variable {var} not found in any of the callbacks or optimizer output data in the record.")
×
287
        if var in opt_vars:
1✔
288
            out_data[in_var] = []
1✔
289
        if var in callback_vars:
1✔
290
            out_data[f'callback_{in_var}'] = []
1✔
291
            # out_data[f'callback_indices_{in_var}'] = []
292
            if callback_context:
1✔
293
                out_data[f'callback_context_{in_var}'] = []
1✔
294

295
    for i in range(n_iter):
1✔
296
        for in_var in vars:
1✔
297
            var = in_var.split('[')[0]
1✔
298
            if var not in opt_vars:
1✔
299
                continue
1✔
300
            if '[' not in in_var:
1✔
301
                out_data[in_var].append(file[f'iteration_{i}'][var][()])
×
302
            elif ',' not in in_var:
1✔
303
                idx = int(in_var.split('[')[1].split(']')[0])
1✔
304
                out_data[in_var].append(file[f'iteration_{i}'][var][idx].item())
1✔
305
            else:
306
                idx1, idx2 = map(int, in_var.split('[')[1].split(']')[0].split(','))
×
307
                out_data[in_var].append(file[f'iteration_{i}'][var][idx1, idx2].item())
×
308

309
    for i in range(n_cb):
1✔
310
        for in_var in vars:
1✔
311
            var = in_var.split('[')[0]
1✔
312

313
            if var not in callback_vars:
1✔
314
                continue
×
315

316
            current_cb_vars = list(file[f'callback_{i}']['outputs'].keys()) + list(file[f'callback_{i}']['inputs'].keys())
1✔
317
            if var not in current_cb_vars:
1✔
318
                continue
1✔
319
            
320
            group_key = 'outputs' if var in list(file[f'callback_{i}']['outputs'].keys()) else 'inputs'
1✔
321
            if '[' not in in_var:
1✔
322
                value = file[f'callback_{i}'][group_key][var][()]
1✔
323
                value = value.item() if isinstance(value, np.generic) else value
1✔
324
                out_data[f'callback_{in_var}'].append(value)
1✔
325
            elif ',' not in in_var:
1✔
326
                idx = int(in_var.split('[')[1].split(']')[0])
1✔
327
                out_data[f'callback_{in_var}'].append(file[f'callback_{i}'][group_key][var][idx].item())
1✔
328
            else:
329
                idx1, idx2 = map(int, in_var.split('[')[1].split(']')[0].split(','))
1✔
330
                out_data[f'callback_{in_var}'].append(file[f'callback_{i}'][group_key][var][idx1, idx2].item())
1✔
331

332
            # if group_key == 'outputs':
333
            #     out_data[f'callback_indices_{in_var}'].append(i)
334
            if callback_context:
1✔
335
                inputs = list(file[f'callback_{i}']['inputs'].keys())
1✔
336
                group  = file[f'callback_{i}']['inputs']
1✔
337
                out_data[f'callback_context_{in_var}'].append(
1✔
338
                    {'callback_index': i} | {key: group[key][()] for key in inputs}
339
                )
340
    
341
    file.close()
1✔
342

343
    return out_data
1✔
344

345
def print_dict_as_table(data):
1✔
346
    """
347
    Print any input dictionary as a table.
348

349
    Parameters
350
    ----------
351
    data : dict
352
        Dictionary to print as a table.
353

354
    Examples
355
    --------
356
    >>> data = {'a': 0, 'b': "string", 'c': ['a', 'b', 'c']}
357
    >>> print_dict_as_table(data)
358
    --------------------------------------------------
359
            a                        : 0
360
            b                        : string
361
            c                        : ['a', 'b', 'c']
362
    --------------------------------------------------
363
    """
364
    print("--------------------------------------------------")
1✔
365
    for key, value in data.items():
1✔
366
        print(f"        {key:24} : {value}")
1✔
367
    print("--------------------------------------------------")
1✔
368

369
if __name__ == "__main__":
1✔
370
    import doctest
×
371
    doctest.testmod()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc